diff --git a/composer.json b/composer.json index d1976513..6546c92a 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "magento/quality-patches", "description": "Provides quality patches for Magento 2", "type": "magento2-component", - "version": "1.0.25", + "version": "1.0.26", "license": "proprietary", "repositories": { "repo": { diff --git a/patches.json b/patches.json index 31335f3f..3fe9379c 100644 --- a/patches.json +++ b/patches.json @@ -441,6 +441,9 @@ ">=2.3.2 <=2.3.3-p1": { "file": "os/MDVA-30565_2.3.3-p1.patch" }, + ">=2.3.4 <=2.3.4-p2": { + "file": "os/MDVA-38531_2.3.4-p2.patch" + }, ">=2.3.6 <2.4.0 || >=2.4.1 <2.4.2": { "file": "os/MDVA-35423_2.4.1.patch" } @@ -1995,6 +1998,9 @@ ">=2.3.4 <=2.3.4-p2": { "file": "os/MDVA-34665_2.3.4-p2_v2.patch" }, + ">=2.3.5 <=2.3.5-p2": { + "file": "os/MDVA-38535_2.3.5-p1.patch" + }, ">=2.4.0 <2.4.3": { "file": "os/MDVA-37350_2.4.1.patch" } @@ -2011,6 +2017,9 @@ "Fixes the issue with missing bundled products on category pages.": { "1.1.4": { "file": "commerce/MDVA-34665_1.1.4_v2.patch" + }, + ">=1.1.5 <=1.1.5-p1": { + "file": "commerce/MDVA-38535_1.1.5.patch" } } }, @@ -2370,5 +2379,61 @@ } } } + }, + "MDVA-38468": { + "magento/magento2-base": { + "Fixes the error when saving CMS pages - Item with the same ID 'PAGE_ID' already exists.": { + ">=2.3.2 <=2.3.5-p2": { + "file": "os/MDVA-38468_2.3.2-p2.patch" + } + } + }, + "magento/magento2-ee-base": { + "Fixes the error when saving CMS pages - Item with the same ID 'PAGE_ID' already exists.": { + ">=2.3.2 <=2.3.5-p2": { + "file": "commerce/MDVA-38468_2.3.2-p2.patch" + } + } + } + }, + "MDVA-34680": { + "magento/magento2-base": { + "Fixes the issue where Customer Account created time is not filtered correctly in customers grid.": { + ">=2.3.6 <=2.3.7 || >=2.4.1 <2.4.3": { + "file": "os/MDVA-34680_2.4.1.patch" + } + } + } + }, + "MDVA-37068": { + "magento/magento2-base": { + "Fixes the issue where the incorrect tax rate displays when the shopping cart has only virtual products.": { + ">=2.3.1 <2.4.4": { + "file": "os/MDVA-37068_2.3.5-p2.patch" + } + } + } + }, + "MDVA-38608": { + "magento/magento2-base": { + "Fixes the issue where temporary tables are not deleted when the reindex is not finished successfully.": { + ">=2.3.0 <2.4.3": { + "file": "os/MDVA-38608_2.3.2-p2.patch" + } + } + } + }, + "MDVA-38308": { + "magento/magento2-base": { + "Fixes the issues related to adding Vimeo videos to products.": { + ">=2.3.5 <=2.3.6-p1 || >=2.4.0 <=2.4.1-p1": { + "file": "os/MDVA-38308_2.4.1-p1.patch", + "require": ["MDVA-35092"] + }, + ">=2.4.2 <2.4.4": { + "file": "os/MDVA-38308_2.4.2-p1.patch" + } + } + } } } diff --git a/patches/commerce/MDVA-38468_2.3.2-p2.patch b/patches/commerce/MDVA-38468_2.3.2-p2.patch new file mode 100644 index 00000000..124b3585 --- /dev/null +++ b/patches/commerce/MDVA-38468_2.3.2-p2.patch @@ -0,0 +1,457 @@ +diff --git a/vendor/magento/module-versions-cms/Block/Adminhtml/Cms/Hierarchy/Edit/Form.php b/vendor/magento/module-versions-cms/Block/Adminhtml/Cms/Hierarchy/Edit/Form.php +index ec424200e96..578d660a6f2 100644 +--- a/vendor/magento/module-versions-cms/Block/Adminhtml/Cms/Hierarchy/Edit/Form.php ++++ b/vendor/magento/module-versions-cms/Block/Adminhtml/Cms/Hierarchy/Edit/Form.php +@@ -527,10 +527,8 @@ class Form extends \Magento\Backend\Block\Widget\Form\Generic + $this->setData('current_scope_id', $nodeModel->getScopeId()); + + $this->setData('use_default_scope', $nodeModel->getIsInherited()); +- $nodeHeritageModel = $nodeModel->getHeritage(); +- $nodes = $nodeHeritageModel->getNodesData(); ++ $nodes = $nodeModel->getNodesData(); + unset($nodeModel); +- unset($nodeHeritageModel); + + foreach ($nodes as &$node) { + $node['assigned_to_store'] = !$this->getData('use_default_scope'); +diff --git a/vendor/magento/module-versions-cms/Block/Adminhtml/Cms/Page/Edit/Tab/Hierarchy.php b/vendor/magento/module-versions-cms/Block/Adminhtml/Cms/Page/Edit/Tab/Hierarchy.php +index d70a5f5b70b..eb5343be0af 100644 +--- a/vendor/magento/module-versions-cms/Block/Adminhtml/Cms/Page/Edit/Tab/Hierarchy.php ++++ b/vendor/magento/module-versions-cms/Block/Adminhtml/Cms/Page/Edit/Tab/Hierarchy.php +@@ -5,6 +5,8 @@ + */ + namespace Magento\VersionsCms\Block\Adminhtml\Cms\Page\Edit\Tab; + ++use Magento\Store\Model\StoreManagerInterface; ++ + /** + * Cms Page Edit Hierarchy Tab Block + */ +@@ -52,12 +54,18 @@ class Hierarchy extends \Magento\Backend\Block\Template implements \Magento\Back + protected $_template = 'page/tab/hierarchy.phtml'; + + /** ++ * @var StoreManagerInterface $storeManager ++ */ ++ private $storeManager; ++ ++ /** + * @param \Magento\Backend\Block\Template\Context $context + * @param \Magento\Framework\Json\EncoderInterface $jsonEncoder + * @param \Magento\Framework\Json\DecoderInterface $jsonDecoder + * @param \Magento\VersionsCms\Helper\Hierarchy $cmsHierarchy + * @param \Magento\Framework\Registry $registry + * @param \Magento\VersionsCms\Model\ResourceModel\Hierarchy\Node\CollectionFactory $nodeCollectionFactory ++ * @param StoreManagerInterface $storeManager + * @param array $data + */ + public function __construct( +@@ -67,6 +75,7 @@ class Hierarchy extends \Magento\Backend\Block\Template implements \Magento\Back + \Magento\VersionsCms\Helper\Hierarchy $cmsHierarchy, + \Magento\Framework\Registry $registry, + \Magento\VersionsCms\Model\ResourceModel\Hierarchy\Node\CollectionFactory $nodeCollectionFactory, ++ StoreManagerInterface $storeManager, + array $data = [] + ) { + $this->_jsonDecoder = $jsonDecoder; +@@ -74,6 +83,7 @@ class Hierarchy extends \Magento\Backend\Block\Template implements \Magento\Back + $this->_coreRegistry = $registry; + $this->_cmsHierarchy = $cmsHierarchy; + $this->_nodeCollectionFactory = $nodeCollectionFactory; ++ $this->storeManager = $storeManager; + parent::__construct($context, $data); + } + +@@ -159,6 +169,7 @@ class Hierarchy extends \Magento\Backend\Block\Template implements \Magento\Back + 'node_id' => $item->getId(), + 'parent_node_id' => $item->getParentNodeId(), + 'label' => $item->getLabel(), ++ 'store_label' => $this->getNodeStoreName((int)$item->getScopeId()), + 'page_exists' => (bool)$item->getPageExists(), + 'page_id' => $item->getPageId(), + 'current_page' => (bool)$item->getCurrentPage(), +@@ -172,6 +183,22 @@ class Hierarchy extends \Magento\Backend\Block\Template implements \Magento\Back + } + + /** ++ * Return store name for node by scope_id ++ * ++ * @param int $scopeId ++ * @return string ++ * @throws \Magento\Framework\Exception\NoSuchEntityException ++ */ ++ private function getNodeStoreName(int $scopeId) ++ { ++ $scope = $this->storeManager->getStore($scopeId); ++ if ($scope->getId() === '0') { ++ return 'All Store Views'; ++ } ++ return $scope->getName(); ++ } ++ ++ /** + * Return page store ids. + * + * @param object $node +diff --git a/vendor/magento/module-versions-cms/Model/ResourceModel/Hierarchy/Node.php b/vendor/magento/module-versions-cms/Model/ResourceModel/Hierarchy/Node.php +index 5c366985940..22d59d7953a 100644 +--- a/vendor/magento/module-versions-cms/Model/ResourceModel/Hierarchy/Node.php ++++ b/vendor/magento/module-versions-cms/Model/ResourceModel/Hierarchy/Node.php +@@ -12,6 +12,7 @@ use Magento\VersionsCms\Model\Hierarchy\NodeFactory; + + /** + * Cms Hierarchy Pages Node Resource Model ++ * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) + */ + class Node extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb + { +@@ -309,6 +310,7 @@ class Node extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb + $nodes = []; + $rowSet = $this->getConnection()->fetchAll($select); + foreach ($rowSet as $row) { ++ // phpcs:ignore Magento2.Functions.DiscouragedFunction + $nodes[intval($row['parent_node_id'])][$row[$this->getIdFieldName()]] = $row; + } + +@@ -779,23 +781,25 @@ class Node extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb + + /** + * Remove node which are representing specified page from defined nodes. ++ * + * Which will also remove child nodes by foreign key. + * + * @param int $pageId + * @param int|array $nodes ++ * @param array $scopeIds + * @return $this + */ +- public function removePageFromNodes($pageId, $nodes) ++ public function removePageFromNodes($pageId, $nodes, $scopeIds = []) + { +- $whereClause = ['page_id = ?' => $pageId, 'parent_node_id IN (?)' => $nodes]; ++ $whereClause = empty($scopeIds) ? ['page_id = ?' => $pageId, 'parent_node_id IN (?)' => $nodes] ++ : ['page_id = ?' => $pageId, 'parent_node_id IN (?)' => $nodes, 'scope_id IN (?)' => $scopeIds]; + $this->getConnection()->delete($this->getMainTable(), $whereClause); + + return $this; + } + + /** +- * Remove nodes defined by id. +- * Which will also remove their child nodes by foreign key. ++ * Remove nodes defined by id. Which will also remove their child nodes by foreign key. + * + * @param int|int[] $nodeIds + * @return $this +@@ -807,8 +811,7 @@ class Node extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb + } + + /** +- * Retrieve tree meta data flags from secondary table. +- * Filtering by root node of passed node. ++ * Retrieve tree meta data flags from secondary table. Filtering by root node of passed node. + * + * @param \Magento\VersionsCms\Model\Hierarchy\Node $object + * @return array +@@ -824,8 +827,7 @@ class Node extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb + } + + /** +- * Prepare load select but without where part. +- * So all extra joins to secondary tables will be present. ++ * Prepare load select but without where part. So all extra joins to secondary tables will be present. + * + * @return \Magento\Framework\DB\Select + */ +diff --git a/vendor/magento/module-versions-cms/Model/ResourceModel/Hierarchy/Node/Collection.php b/vendor/magento/module-versions-cms/Model/ResourceModel/Hierarchy/Node/Collection.php +index 2de683d3cd9..3b3be656906 100644 +--- a/vendor/magento/module-versions-cms/Model/ResourceModel/Hierarchy/Node/Collection.php ++++ b/vendor/magento/module-versions-cms/Model/ResourceModel/Hierarchy/Node/Collection.php +@@ -245,6 +245,7 @@ class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\Ab + $connection->quoteInto($onClause, $page), + ['page_exists' => $ifPageExistExpr, 'current_page' => $ifCurrentPageExpr] + ); ++ $this->getSelect()->group('main_table.node_id'); + + $this->setFlag('page_exists_joined', true); + } +diff --git a/vendor/magento/module-versions-cms/Observer/Backend/CmsPageSaveAfterObserver.php b/vendor/magento/module-versions-cms/Observer/Backend/CmsPageSaveAfterObserver.php +index 6029e1dffe2..e83e6fe6b1f 100644 +--- a/vendor/magento/module-versions-cms/Observer/Backend/CmsPageSaveAfterObserver.php ++++ b/vendor/magento/module-versions-cms/Observer/Backend/CmsPageSaveAfterObserver.php +@@ -8,10 +8,18 @@ namespace Magento\VersionsCms\Observer\Backend; + use Magento\Cms\Model\Page; + use Magento\Framework\Event\Observer as EventObserver; + use Magento\Framework\Event\ObserverInterface; ++use Magento\Framework\Exception\LocalizedException; ++use Magento\Store\Model\ScopeInterface; ++use Magento\Store\Model\ScopeResolver; + use Magento\VersionsCms\Helper\Hierarchy; + use Magento\VersionsCms\Model\Hierarchy\Node as HierarchyNode; + use Magento\VersionsCms\Model\ResourceModel\Hierarchy\Node; ++use Magento\VersionsCms\Model\ResourceModel\Hierarchy\Node\Collection; ++use Magento\VersionsCms\Model\ResourceModel\Hierarchy\Node\CollectionFactory; + ++/** ++ * Create and delete nodes after cms page save ++ */ + class CmsPageSaveAfterObserver implements ObserverInterface + { + /** +@@ -30,18 +38,34 @@ class CmsPageSaveAfterObserver implements ObserverInterface + protected $hierarchyNodeResource; + + /** ++ * @var CollectionFactory ++ */ ++ private $nodeCollectionFactory; ++ ++ /** ++ * @var ScopeResolver ++ */ ++ private $scopeResolver; ++ ++ /** + * @param Hierarchy $cmsHierarchy + * @param HierarchyNode $hierarchyNode + * @param Node $hierarchyNodeResource ++ * @param CollectionFactory $nodeCollectionFactory ++ * @param ScopeResolver $scopeResolver + */ + public function __construct( + Hierarchy $cmsHierarchy, + HierarchyNode $hierarchyNode, +- Node $hierarchyNodeResource ++ Node $hierarchyNodeResource, ++ CollectionFactory $nodeCollectionFactory, ++ ScopeResolver $scopeResolver + ) { + $this->cmsHierarchy = $cmsHierarchy; + $this->hierarchyNode = $hierarchyNode; + $this->hierarchyNodeResource = $hierarchyNodeResource; ++ $this->nodeCollectionFactory = $nodeCollectionFactory; ++ $this->scopeResolver = $scopeResolver; + } + + /** +@@ -64,12 +88,7 @@ class CmsPageSaveAfterObserver implements ObserverInterface + $this->hierarchyNode->updateRewriteUrls($page); + } + +- /** +- * Append page to selected nodes it will remove pages from other nodes +- * which are not specified in array. So should be called even array is empty! +- * Returns array of new ids for page nodes array( oldId => newId ). +- */ +- $this->hierarchyNode->appendPageToNodes($page, $page->getAppendToNodes()); ++ $this->appendPageToNodes($page); + + /** + * Update sort order for nodes in parent nodes which have current page as child +@@ -80,4 +99,165 @@ class CmsPageSaveAfterObserver implements ObserverInterface + + return $this; + } ++ ++ /** ++ * Append page to selected nodes. Removing page nodes with wrong scope after changing store in "Page in Websites" ++ * ++ * @param Page $page ++ * @return $this ++ * @throws LocalizedException ++ */ ++ private function appendPageToNodes(Page $page) ++ { ++ $nodes = $page->getAppendToNodes(); ++ $parentNodes = $this->getParentNodes($nodes, $page); ++ $pageData = ['page_id' => $page->getId(), 'identifier' => null, 'label' => null]; ++ $removeFromNodes = []; ++ $scopeIds = []; ++ foreach ($parentNodes as $parentNode) { ++ /* @var $parentNode HierarchyNode */ ++ if (!isset($nodes[$parentNode->getId()])) { ++ //Delete node after uncheck checkbox ++ $removeFromNodes[] = $parentNode->getId(); ++ $scopeIds[] = $parentNode->getScopeId(); ++ continue; ++ } ++ $nodeScopeId = (int)$parentNode->getScopeId(); ++ ++ if (!$this->isBelongsToNodeScope($parentNode->getScope(), $nodeScopeId, (array)$page->getStoreId())) { ++ //If parent node scope_id assigned to store which not in "Page In Websites" - delete node ++ $scopeIds[] = $nodeScopeId; ++ $removeFromNodes[] = $parentNode->getId(); ++ continue; ++ } ++ ++ $requestUrl = $parentNode->getIdentifier() . '/' . $page->getIdentifier(); ++ if ($this->isNodeExist($requestUrl, $nodeScopeId, (int)$parentNode->getId(), (int)$page->getId())) { ++ throw new LocalizedException( ++ __( ++ 'This page cannot be assigned to node, because a node or page with' ++ . ' the same URL Key already exists in this tree part.' ++ ) ++ ); ++ } ++ if (!$this->isNodeExist($requestUrl, $nodeScopeId, (int)$parentNode->getId())) { ++ $sortOrder = $nodes[$parentNode->getId()]; ++ $this->createNewNode($parentNode, $pageData, $sortOrder, $page->getIdentifier()); ++ } ++ } ++ if (!empty($removeFromNodes) && $nodes !== null && !empty($scopeIds)) { ++ $this->hierarchyNodeResource->removePageFromNodes($page->getId(), $removeFromNodes, $scopeIds); ++ } ++ ++ return $this; ++ } ++ ++ /** ++ * Check if node scope is "All store view" or it is same as page scope ++ * ++ * @param string $nodeScope ++ * @param int $nodeScopeId ++ * @param array $pageStoreIds ++ * @return bool ++ */ ++ private function isBelongsToNodeScope(string $nodeScope, int $nodeScopeId, array $pageStoreIds): bool ++ { ++ if (empty($pageStoreIds)) { ++ return false; ++ } ++ ++ foreach ($pageStoreIds as $storeId) { ++ $isScopeValid = $this->scopeResolver->isBelongsToScope( ++ $nodeScope, ++ $nodeScopeId, ++ ScopeInterface::SCOPE_STORE, ++ $storeId ++ ); ++ if ($isScopeValid) { ++ return true; ++ } ++ } ++ ++ return false; ++ } ++ ++ /** ++ * Create new page node ++ * ++ * @param HierarchyNode $parentNode ++ * @param array $pageData ++ * @param int $sortOrder ++ * @param string $pageIdentifier ++ * @return mixed ++ */ ++ private function createNewNode(HierarchyNode $parentNode, array $pageData, int $sortOrder, string $pageIdentifier) ++ { ++ $newNode = clone $parentNode; ++ if ($parentNode->getScopeId() !== HierarchyNode::NODE_SCOPE_DEFAULT_ID) { ++ $newNode->setScope(HierarchyNode::NODE_SCOPE_STORE); ++ } ++ $newNode->setScopeId($parentNode->getScopeId()); ++ ++ $newNode->addData( ++ $pageData ++ )->setParentNodeId( ++ $newNode->getId() ++ )->unsetData( ++ $this->hierarchyNode->getIdFieldName() ++ )->setLevel( ++ $newNode->getLevel() + 1 ++ )->setSortOrder( ++ $sortOrder ++ )->setRequestUrl( ++ $newNode->getRequestUrl() . '/' . $pageIdentifier ++ )->setXpath( ++ $newNode->getXpath() . '/' ++ ); ++ $newNode->save(); ++ ++ return $newNode; ++ } ++ ++ /** ++ * Return parent nodes collection ++ * ++ * @param array $nodes ++ * @param Page $page ++ * @return Collection ++ */ ++ private function getParentNodes(array $nodes, Page $page) ++ { ++ $nodesToFilter = ($nodes === null) ? [] : array_keys($nodes); ++ $nodeCollection = $this->nodeCollectionFactory->create(); ++ $parentNodes = $nodeCollection->joinPageExistsNodeInfo( ++ $page ++ )->applyPageExistsOrNodeIdFilter( ++ $nodesToFilter, ++ $page ++ ); ++ ++ return $parentNodes; ++ } ++ ++ /** ++ * Check if current page node is exist ++ * ++ * @param string $requestUrl ++ * @param int $scopeId ++ * @param int $parentNodeId ++ * @param int|null $currentPageId ++ * @return bool ++ */ ++ private function isNodeExist(string $requestUrl, int $scopeId, int $parentNodeId, ?int $currentPageId = null): bool ++ { ++ $nodeCollection = $this->nodeCollectionFactory->create(); ++ $nodeCollection->addFieldToFilter('request_url', $requestUrl) ++ ->addFieldToFilter('scope_id', $scopeId) ++ ->addFieldToFilter('parent_node_id', $parentNodeId); ++ ++ if ($currentPageId !== null) { ++ $nodeCollection->addFieldToFilter('page_id', ['neq' => $currentPageId]); ++ } ++ return $nodeCollection->getSize() ? true : false; ++ } + } +diff --git a/vendor/magento/module-versions-cms/Observer/Backend/CmsPageSaveBeforeObserver.php b/vendor/magento/module-versions-cms/Observer/Backend/CmsPageSaveBeforeObserver.php +index 6311cb26db7..e72a43d291c 100644 +--- a/vendor/magento/module-versions-cms/Observer/Backend/CmsPageSaveBeforeObserver.php ++++ b/vendor/magento/module-versions-cms/Observer/Backend/CmsPageSaveBeforeObserver.php +@@ -36,14 +36,11 @@ class CmsPageSaveBeforeObserver implements ObserverInterface + { + /** @var Page $page */ + $page = $observer->getEvent()->getObject(); +- +- if (!$page->getId()) { ++ $nodesData = $this->getNodesOrder($page->getNodesData()); ++ if (!$page->getId() && empty($nodesData['appendToNodes'])) { + // Newly created page should be auto assigned to website root + $page->setWebsiteRoot(true); + } +- +- $nodesData = $this->getNodesOrder($page->getNodesData()); +- + $page->setNodesSortOrder($nodesData['sortOrder']); + $page->setAppendToNodes($nodesData['appendToNodes']); + return $this; +diff --git a/vendor/magento/module-versions-cms/view/adminhtml/templates/page/tab/hierarchy.phtml b/vendor/magento/module-versions-cms/view/adminhtml/templates/page/tab/hierarchy.phtml +index 8f2f0bcaac6..27710ef9b68 100644 +--- a/vendor/magento/module-versions-cms/view/adminhtml/templates/page/tab/hierarchy.phtml ++++ b/vendor/magento/module-versions-cms/view/adminhtml/templates/page/tab/hierarchy.phtml +@@ -75,10 +75,14 @@ + for (var i = 0, l = this.nodes.length; i < l; i++) { + var dd = (this.nodes[i].parent_node_id && this.nodes[i].current_page) ? true : false; + var cls = this.nodes[i].current_page ? 'cms-current' : ''; ++ var label = this.nodes[i].label.escapeHTML().replace('\'', ''').replace('"', '"') ++ + " (" ++ + this.nodes[i].store_label.escapeHTML().replace('\'', ''').replace('"', '"') ++ + ")"; + cls += this.nodes[i].page_id ? ' cms_page' : ' cms_node'; + var node = new Ext.tree.TreeNode({ + id: this.nodes[i].node_id, +- text: this.nodes[i].label.escapeHTML().replace('\'', ''').replace('"', '"'), ++ text: label, + cls: cls, + expanded: this.nodes[i].page_exists, + allowDrop: true, diff --git a/patches/commerce/MDVA-38535_1.1.5.patch b/patches/commerce/MDVA-38535_1.1.5.patch new file mode 100644 index 00000000..2495695c --- /dev/null +++ b/patches/commerce/MDVA-38535_1.1.5.patch @@ -0,0 +1,855 @@ +diff --git a/vendor/magento/module-inventory-cache/Model/FlushCacheByCategoryIds.php b/vendor/magento/module-inventory-cache/Model/FlushCacheByCategoryIds.php +new file mode 100644 +index 0000000..8127b9b +--- /dev/null ++++ b/vendor/magento/module-inventory-cache/Model/FlushCacheByCategoryIds.php +@@ -0,0 +1,72 @@ ++cacheContextFactory = $cacheContextFactory; ++ $this->eventManager = $eventManager; ++ $this->categoryCacheTag = $categoryCacheTag; ++ $this->appCache = $appCache; ++ } ++ ++ /** ++ * Clean cache for given category ids. ++ * ++ * @param array $categoryIds ++ * @return void ++ */ ++ public function execute(array $categoryIds): void ++ { ++ if ($categoryIds) { ++ $cacheContext = $this->cacheContextFactory->create(); ++ $cacheContext->registerEntities($this->categoryCacheTag, $categoryIds); ++ $this->eventManager->dispatch('clean_cache_by_tags', ['object' => $cacheContext]); ++ $this->appCache->clean($cacheContext->getIdentities()); ++ } ++ } ++} +\ No newline at end of file +diff --git a/vendor/magento/module-inventory-cache/Model/ResourceModel/GetProductIdsByStockIds.php b/vendor/magento/module-inventory-cache/Model/ResourceModel/GetProductIdsByStockIds.php +deleted file mode 100644 +index ec148ea..0000000 +--- a/vendor/magento/module-inventory-cache/Model/ResourceModel/GetProductIdsByStockIds.php ++++ /dev/null +@@ -1,87 +0,0 @@ +-resource = $resource; +- $this->defaultStockProvider = $defaultStockProvider; +- $this->stockIndexTableNameResolver = $stockIndexTableNameResolver; +- $this->productTableName = $productTableName; +- } +- +- /** +- * Get product ids for given stock form index table. +- * +- * @param array $stockIds +- * @return array +- */ +- public function execute(array $stockIds): array +- { +- $productIds = [[]]; +- foreach ($stockIds as $stockId) { +- if ($this->defaultStockProvider->getId() === (int)$stockId) { +- continue; +- } +- $stockIndexTableName = $this->stockIndexTableNameResolver->execute($stockId); +- $connection = $this->resource->getConnection(); +- +- $sql = $connection->select() +- ->from(['stock_index' => $stockIndexTableName], []) +- ->join( +- ['product' => $this->resource->getTableName($this->productTableName)], +- 'product.sku = stock_index.' . IndexStructure::SKU, +- ['product.entity_id'] +- ); +- $productIds[] = $connection->fetchCol($sql); +- } +- $productIds = array_merge(...$productIds); +- +- return array_unique($productIds); +- } +-} +diff --git a/vendor/magento/module-inventory-cache/Plugin/InventoryIndexer/Indexer/Source/SourceItemIndexer/CacheFlush.php b/vendor/magento/module-inventory-cache/Plugin/InventoryIndexer/Indexer/Source/SourceItemIndexer/CacheFlush.php +deleted file mode 100644 +index 5da92eb..0000000 +--- a/vendor/magento/module-inventory-cache/Plugin/InventoryIndexer/Indexer/Source/SourceItemIndexer/CacheFlush.php ++++ /dev/null +@@ -1,55 +0,0 @@ +-flushCacheByIds = $flushCacheByIds; +- $this->getProductIdsBySourceItemIds = $getProductIdsBySourceItemIds; +- } +- +- /** +- * Clean cache for specific products after source items reindex. +- * +- * @param SourceItemIndexer $subject +- * @param array $sourceItemIds +- * @param null $result +- * @throws \Exception in case catalog product entity type hasn't been initialize. +- * @SuppressWarnings(PHPMD.UnusedFormalParameter) +- */ +- public function afterExecuteList(SourceItemIndexer $subject, $result, array $sourceItemIds) +- { +- $productIds = $this->getProductIdsBySourceItemIds->execute($sourceItemIds); +- $this->flushCacheByIds->execute($productIds); +- } +-} +diff --git a/vendor/magento/module-inventory-cache/Plugin/InventoryIndexer/Indexer/SourceItem/Strategy/Sync/CacheFlush.php b/vendor/magento/module-inventory-cache/Plugin/InventoryIndexer/Indexer/SourceItem/Strategy/Sync/CacheFlush.php +new file mode 100644 +index 0000000..4a45d9e +--- /dev/null ++++ b/vendor/magento/module-inventory-cache/Plugin/InventoryIndexer/Indexer/SourceItem/Strategy/Sync/CacheFlush.php +@@ -0,0 +1,75 @@ ++flushCacheByIds = $flushCacheByIds; ++ $this->getProductIdsBySourceItemIds = $getProductIdsBySourceItemIds; ++ $this->getCategoryIdsByProductIds = $getCategoryIdsByProductIds; ++ $this->flushCategoryByCategoryIds = $flushCategoryByCategoryIds; ++ } ++ ++ /** ++ * Clean cache for specific products after source items reindex. ++ * ++ * @param Sync $subject ++ * @param void $result ++ * @param array $sourceItemIds ++ * @throws \Exception in case catalog product entity type hasn't been initialize. ++ * @SuppressWarnings(PHPMD.UnusedFormalParameter) ++ */ ++ public function afterExecuteList(Sync $subject, $result, array $sourceItemIds) ++ { ++ $productIds = $this->getProductIdsBySourceItemIds->execute($sourceItemIds); ++ $categoryIds = $this->getCategoryIdsByProductIds->execute($productIds); ++ $this->flushCategoryByCategoryIds->execute($categoryIds); ++ $this->flushCacheByIds->execute($productIds); ++ } ++} +\ No newline at end of file +diff --git a/vendor/magento/module-inventory-cache/Plugin/InventoryIndexer/Indexer/Stock/StockIndexer/CacheFlush.php b/vendor/magento/module-inventory-cache/Plugin/InventoryIndexer/Indexer/Stock/StockIndexer/CacheFlush.php +deleted file mode 100644 +index 8f050f1..0000000 +--- a/vendor/magento/module-inventory-cache/Plugin/InventoryIndexer/Indexer/Stock/StockIndexer/CacheFlush.php ++++ /dev/null +@@ -1,61 +0,0 @@ +-flushCacheByProductIds = $flushCacheByProductIds; +- $this->getProductIdsByStockIds = $getProductIdsForCacheFlush; +- } +- +- /** +- * Clean cache after non default stock reindex. +- * +- * @param StockIndexer $subject +- * @param callable $proceed +- * @param array $stockIds +- * @return void +- * @throws \Exception in case product entity type hasn't been initialize. +- * @SuppressWarnings(PHPMD.UnusedFormalParameter) +- */ +- public function aroundExecuteList(StockIndexer $subject, callable $proceed, array $stockIds) +- { +- $beforeReindexProductIds = $this->getProductIdsByStockIds->execute($stockIds); +- $proceed($stockIds); +- $afterReindexProductIds = $this->getProductIdsByStockIds->execute($stockIds); +- $productIdsForCacheClean = array_diff($beforeReindexProductIds, $afterReindexProductIds); +- if ($productIdsForCacheClean) { +- $this->flushCacheByProductIds->execute($productIdsForCacheClean); +- } +- } +-} +diff --git a/vendor/magento/module-inventory-cache/Plugin/InventoryIndexer/Indexer/Stock/Strategy/Sync/CacheFlush.php b/vendor/magento/module-inventory-cache/Plugin/InventoryIndexer/Indexer/Stock/Strategy/Sync/CacheFlush.php +new file mode 100644 +index 0000000..e3ae2c8 +--- /dev/null ++++ b/vendor/magento/module-inventory-cache/Plugin/InventoryIndexer/Indexer/Stock/Strategy/Sync/CacheFlush.php +@@ -0,0 +1,61 @@ ++flushCacheByProductIds = $flushCacheByProductIds; ++ $this->getProductIdsByStockIds = $getProductIdsForCacheFlush; ++ } ++ ++ /** ++ * Clean cache after non default stock reindex. ++ * ++ * @param Sync $subject ++ * @param callable $proceed ++ * @param array $stockIds ++ * @return void ++ * @throws \Exception in case product entity type hasn't been initialize. ++ * @SuppressWarnings(PHPMD.UnusedFormalParameter) ++ */ ++ public function aroundExecuteList(Sync $subject, callable $proceed, array $stockIds) ++ { ++ $beforeReindexProductIds = $this->getProductIdsByStockIds->execute($stockIds); ++ $proceed($stockIds); ++ $afterReindexProductIds = $this->getProductIdsByStockIds->execute($stockIds); ++ $productIdsForCacheClean = array_diff($beforeReindexProductIds, $afterReindexProductIds); ++ if ($productIdsForCacheClean) { ++ $this->flushCacheByProductIds->execute($productIdsForCacheClean); ++ } ++ } ++} +\ No newline at end of file +diff --git a/vendor/magento/module-inventory-cache/etc/di.xml b/vendor/magento/module-inventory-cache/etc/di.xml +index 0b6c102..ee6ed20 100644 +--- a/vendor/magento/module-inventory-cache/etc/di.xml ++++ b/vendor/magento/module-inventory-cache/etc/di.xml +@@ -6,20 +6,20 @@ + */ + --> + +- +- ++ ++ + +- +- ++ ++ + +- ++ + +- catalog_product_entity ++ Magento\Catalog\Model\Product::CACHE_TAG + + +- ++ + +- Magento\Catalog\Model\Product::CACHE_TAG ++ Magento\Catalog\Model\Product::CACHE_PRODUCT_CATEGORY_TAG + + +- ++ +\ No newline at end of file +diff --git a/vendor/magento/module-inventory-catalog/Plugin/InventoryIndexer/Indexer/SourceItem/PriceIndexUpdater.php b/vendor/magento/module-inventory-catalog/Plugin/InventoryIndexer/Indexer/SourceItem/PriceIndexUpdater.php +deleted file mode 100644 +index e5c2988..0000000 +--- a/vendor/magento/module-inventory-catalog/Plugin/InventoryIndexer/Indexer/SourceItem/PriceIndexUpdater.php ++++ /dev/null +@@ -1,57 +0,0 @@ +-priceIndexProcessor = $priceIndexProcessor; +- $this->productIdsBySourceItemIds = $productIdsBySourceItemIds; +- } +- +- /** +- * @param SourceItemIndexer $subject +- * @param $result +- * @param array $sourceItemIds +- * @SuppressWarnings(PHPMD.UnusedFormalParameter) +- */ +- public function afterExecuteList( +- SourceItemIndexer $subject, +- $result, +- array $sourceItemIds +- ): void { +- $productIds = $this->productIdsBySourceItemIds->execute($sourceItemIds); +- if (!empty($productIds)) { +- $this->priceIndexProcessor->reindexList($productIds); +- } +- } +-} +diff --git a/vendor/magento/module-inventory-catalog/Plugin/InventoryIndexer/Indexer/SourceItem/Strategy/Sync/PriceIndexUpdater.php b/vendor/magento/module-inventory-catalog/Plugin/InventoryIndexer/Indexer/SourceItem/Strategy/Sync/PriceIndexUpdater.php +new file mode 100644 +index 0000000..045b90a +--- /dev/null ++++ b/vendor/magento/module-inventory-catalog/Plugin/InventoryIndexer/Indexer/SourceItem/Strategy/Sync/PriceIndexUpdater.php +@@ -0,0 +1,59 @@ ++priceIndexProcessor = $priceIndexProcessor; ++ $this->productIdsBySourceItemIds = $productIdsBySourceItemIds; ++ } ++ ++ /** ++ * Reindex product prices. ++ * ++ * @param Sync $subject ++ * @param void $result ++ * @param array $sourceItemIds ++ * @SuppressWarnings(PHPMD.UnusedFormalParameter) ++ */ ++ public function afterExecuteList( ++ Sync $subject, ++ $result, ++ array $sourceItemIds ++ ): void { ++ $productIds = $this->productIdsBySourceItemIds->execute($sourceItemIds); ++ if (!empty($productIds)) { ++ $this->priceIndexProcessor->reindexList($productIds); ++ } ++ } ++} +diff --git a/vendor/magento/module-inventory-catalog/Plugin/InventoryIndexer/Indexer/Stock/Strategy/Sync/PriceIndexUpdatePlugin.php b/vendor/magento/module-inventory-catalog/Plugin/InventoryIndexer/Indexer/Stock/Strategy/Sync/PriceIndexUpdatePlugin.php +new file mode 100644 +index 0000000..4d94111 +--- /dev/null ++++ b/vendor/magento/module-inventory-catalog/Plugin/InventoryIndexer/Indexer/Stock/Strategy/Sync/PriceIndexUpdatePlugin.php +@@ -0,0 +1,57 @@ ++getProductIdsByStockIds = $getProductIdsForCacheFlush; ++ $this->priceIndexProcessor = $priceIndexProcessor; ++ } ++ ++ /** ++ * Update prices after non default stock reindex. ++ * ++ * @param Sync $subject ++ * @param void $result ++ * @param array $stockIds ++ * @return void ++ * @SuppressWarnings(PHPMD.UnusedFormalParameter) ++ */ ++ public function afterExecuteList(Sync $subject, $result, array $stockIds) ++ { ++ $productIds = $this->getProductIdsByStockIds->execute($stockIds); ++ if (!empty($productIds)) { ++ $this->priceIndexProcessor->reindexList($productIds); ++ } ++ } ++} +\ No newline at end of file +diff --git a/vendor/magento/module-inventory-catalog/etc/di.xml b/vendor/magento/module-inventory-catalog/etc/di.xml +index 3859f8f..ec83745 100644 +--- a/vendor/magento/module-inventory-catalog/etc/di.xml ++++ b/vendor/magento/module-inventory-catalog/etc/di.xml +@@ -20,8 +20,11 @@ + + +- +- ++ ++ ++ ++ ++ + + + +diff --git a/vendor/magento/module-inventory-indexer/Model/ResourceModel/GetCategoryIdsByProductIds.php b/vendor/magento/module-inventory-indexer/Model/ResourceModel/GetCategoryIdsByProductIds.php +new file mode 100644 +index 0000000..1ea5bf6 +--- /dev/null ++++ b/vendor/magento/module-inventory-indexer/Model/ResourceModel/GetCategoryIdsByProductIds.php +@@ -0,0 +1,47 @@ ++resourceConnection = $resourceConnection; ++ } ++ ++ /** ++ * Get category ids for products ++ * ++ * @param array $productIds ++ * @return array ++ */ ++ public function execute(array $productIds): array ++ { ++ $connection = $this->resourceConnection->getConnection(); ++ $categoryProductTable = $this->resourceConnection->getTableName('catalog_category_product'); ++ $select = $connection->select() ++ ->from(['catalog_category_product' => $categoryProductTable], ['category_id']) ++ ->where('product_id IN (?)', $productIds); ++ ++ return $connection->fetchCol($select); ++ } ++} +\ No newline at end of file +diff --git a/vendor/magento/module-inventory-indexer/Model/ResourceModel/GetProductIdsByStockIds.php b/vendor/magento/module-inventory-indexer/Model/ResourceModel/GetProductIdsByStockIds.php +new file mode 100644 +index 0000000..e8e3a32 +--- /dev/null ++++ b/vendor/magento/module-inventory-indexer/Model/ResourceModel/GetProductIdsByStockIds.php +@@ -0,0 +1,86 @@ ++resource = $resource; ++ $this->defaultStockProvider = $defaultStockProvider; ++ $this->stockIndexTableNameResolver = $stockIndexTableNameResolver; ++ $this->productTableName = $productTableName; ++ } ++ ++ /** ++ * Get product ids for given stock form index table. ++ * ++ * @param array $stockIds ++ * @return array ++ */ ++ public function execute(array $stockIds): array ++ { ++ $productIds = [[]]; ++ foreach ($stockIds as $stockId) { ++ if ($this->defaultStockProvider->getId() === (int)$stockId) { ++ continue; ++ } ++ $stockIndexTableName = $this->stockIndexTableNameResolver->execute($stockId); ++ $connection = $this->resource->getConnection(); ++ $sql = $connection->select() ++ ->from(['stock_index' => $stockIndexTableName], []) ++ ->join( ++ ['product' => $this->resource->getTableName($this->productTableName)], ++ 'product.sku = stock_index.' . IndexStructure::SKU, ++ ['product.entity_id'] ++ ); ++ $productIds[] = $connection->fetchCol($sql); ++ } ++ $productIds = array_merge(...$productIds); ++ ++ return array_unique($productIds); ++ } ++} +diff --git a/vendor/magento/module-inventory-indexer/etc/di.xml b/vendor/magento/module-inventory-indexer/etc/di.xml +index dd79461..e943b67 100644 +--- a/vendor/magento/module-inventory-indexer/etc/di.xml ++++ b/vendor/magento/module-inventory-indexer/etc/di.xml +@@ -61,4 +61,9 @@ + stock_ + + ++ ++ ++ catalog_product_entity ++ ++ + diff --git a/patches/os/MDVA-34680_2.4.1.patch b/patches/os/MDVA-34680_2.4.1.patch new file mode 100644 index 00000000..682cf032 --- /dev/null +++ b/patches/os/MDVA-34680_2.4.1.patch @@ -0,0 +1,67 @@ +diff --git a/vendor/magento/module-customer/Model/ResourceModel/Grid/Collection.php b/vendor/magento/module-customer/Model/ResourceModel/Grid/Collection.php +index 0fab27161ce..e14594daf80 100644 +--- a/vendor/magento/module-customer/Model/ResourceModel/Grid/Collection.php ++++ b/vendor/magento/module-customer/Model/ResourceModel/Grid/Collection.php +@@ -7,10 +7,12 @@ namespace Magento\Customer\Model\ResourceModel\Grid; + + use Magento\Customer\Model\ResourceModel\Customer; + use Magento\Customer\Ui\Component\DataProvider\Document; ++use Magento\Framework\App\ObjectManager; + use Magento\Framework\Data\Collection\Db\FetchStrategyInterface as FetchStrategy; + use Magento\Framework\Data\Collection\EntityFactoryInterface as EntityFactory; + use Magento\Framework\Event\ManagerInterface as EventManager; + use Magento\Framework\Locale\ResolverInterface; ++use Magento\Framework\Stdlib\DateTime\TimezoneInterface; + use Magento\Framework\View\Element\UiComponent\DataProvider\SearchResult; + use Psr\Log\LoggerInterface as Logger; + +@@ -25,6 +27,11 @@ class Collection extends SearchResult + private $localeResolver; + + /** ++ * @var TimezoneInterface ++ */ ++ private $timeZone; ++ ++ /** + * @inheritdoc + */ + protected $document = Document::class; +@@ -42,6 +49,7 @@ class Collection extends SearchResult + * @param ResolverInterface $localeResolver + * @param string $mainTable + * @param string $resourceModel ++ * @param TimezoneInterface|null $timeZone + */ + public function __construct( + EntityFactory $entityFactory, +@@ -50,10 +58,13 @@ class Collection extends SearchResult + EventManager $eventManager, + ResolverInterface $localeResolver, + $mainTable = 'customer_grid_flat', +- $resourceModel = Customer::class ++ $resourceModel = Customer::class, ++ TimezoneInterface $timeZone = null + ) { + $this->localeResolver = $localeResolver; + parent::__construct($entityFactory, $logger, $fetchStrategy, $eventManager, $mainTable, $resourceModel); ++ $this->timeZone = $timeZone ?: ObjectManager::getInstance() ++ ->get(TimezoneInterface::class); + } + + /** +@@ -81,6 +92,14 @@ class Collection extends SearchResult + return $this; + } + ++ if ($field === 'created_at') { ++ if (is_array($condition)) { ++ foreach ($condition as $key => $value) { ++ $condition[$key] = $this->timeZone->convertConfigTimeToUtc($value); ++ } ++ } ++ } ++ + if (is_string($field) && count(explode('.', $field)) === 1) { + $field = 'main_table.' . $field; + } diff --git a/patches/os/MDVA-37068_2.3.5-p2.patch b/patches/os/MDVA-37068_2.3.5-p2.patch new file mode 100644 index 00000000..1fe7bf8b --- /dev/null +++ b/patches/os/MDVA-37068_2.3.5-p2.patch @@ -0,0 +1,108 @@ +diff --git a/vendor/magento/module-checkout/view/frontend/web/js/model/checkout-data-resolver.js b/vendor/magento/module-checkout/view/frontend/web/js/model/checkout-data-resolver.js +index 66539ad..b96a01c 100644 +--- a/vendor/magento/module-checkout/view/frontend/web/js/model/checkout-data-resolver.js ++++ b/vendor/magento/module-checkout/view/frontend/web/js/model/checkout-data-resolver.js +@@ -43,13 +43,6 @@ define([ + resolveEstimationAddress: function () { + var address; + +- if (checkoutData.getShippingAddressFromData()) { +- address = addressConverter.formAddressDataToQuoteAddress(checkoutData.getShippingAddressFromData()); +- selectShippingAddress(address); +- } else { +- this.resolveShippingAddress(); +- } +- + if (quote.isVirtual()) { + if (checkoutData.getBillingAddressFromData()) { + address = addressConverter.formAddressDataToQuoteAddress( +@@ -59,6 +52,11 @@ define([ + } else { + this.resolveBillingAddress(); + } ++ } else if (checkoutData.getShippingAddressFromData()) { ++ address = addressConverter.formAddressDataToQuoteAddress(checkoutData.getShippingAddressFromData()); ++ selectShippingAddress(address); ++ } else { ++ this.resolveShippingAddress(); + } + }, + +diff --git a/vendor/magento/module-tax/Model/Sales/Total/Quote/CommonTaxCollector.php b/vendor/magento/module-tax/Model/Sales/Total/Quote/CommonTaxCollector.php +index 877aec3..7a0d10a 100644 +--- a/vendor/magento/module-tax/Model/Sales/Total/Quote/CommonTaxCollector.php ++++ b/vendor/magento/module-tax/Model/Sales/Total/Quote/CommonTaxCollector.php +@@ -6,6 +6,7 @@ + + namespace Magento\Tax\Model\Sales\Total\Quote; + ++use Magento\Customer\Api\AccountManagementInterface as CustomerAccountManagement; + use Magento\Customer\Api\Data\AddressInterfaceFactory as CustomerAddressFactory; + use Magento\Customer\Api\Data\AddressInterface as CustomerAddress; + use Magento\Customer\Api\Data\RegionInterfaceFactory as CustomerAddressRegionFactory; +@@ -145,6 +146,11 @@ class CommonTaxCollector extends AbstractTotal + private $quoteDetailsItemExtensionFactory; + + /** ++ * @var CustomerAccountManagement ++ */ ++ private $customerAccountManagement; ++ ++ /** + * Class constructor + * + * @param \Magento\Tax\Model\Config $taxConfig +@@ -156,6 +162,8 @@ class CommonTaxCollector extends AbstractTotal + * @param CustomerAddressRegionFactory $customerAddressRegionFactory + * @param TaxHelper|null $taxHelper + * @param QuoteDetailsItemExtensionInterfaceFactory|null $quoteDetailsItemExtensionInterfaceFactory ++ * @param CustomerAccountManagement|null $customerAccountManagement ++ * @SuppressWarnings(PHPMD.ExcessiveParameterList) + */ + public function __construct( + \Magento\Tax\Model\Config $taxConfig, +@@ -166,7 +174,8 @@ class CommonTaxCollector extends AbstractTotal + CustomerAddressFactory $customerAddressFactory, + CustomerAddressRegionFactory $customerAddressRegionFactory, + TaxHelper $taxHelper = null, +- QuoteDetailsItemExtensionInterfaceFactory $quoteDetailsItemExtensionInterfaceFactory = null ++ QuoteDetailsItemExtensionInterfaceFactory $quoteDetailsItemExtensionInterfaceFactory = null, ++ ?CustomerAccountManagement $customerAccountManagement = null + ) { + $this->taxCalculationService = $taxCalculationService; + $this->quoteDetailsDataObjectFactory = $quoteDetailsDataObjectFactory; +@@ -178,6 +187,8 @@ class CommonTaxCollector extends AbstractTotal + $this->taxHelper = $taxHelper ?: ObjectManager::getInstance()->get(TaxHelper::class); + $this->quoteDetailsItemExtensionFactory = $quoteDetailsItemExtensionInterfaceFactory ?: + ObjectManager::getInstance()->get(QuoteDetailsItemExtensionInterfaceFactory::class); ++ $this->customerAccountManagement = $customerAccountManagement ?? ++ ObjectManager::getInstance()->get(CustomerAccountManagement::class); + } + + /** +@@ -413,7 +424,24 @@ class CommonTaxCollector extends AbstractTotal + public function populateAddressData(QuoteDetailsInterface $quoteDetails, QuoteAddress $address) + { + $quoteDetails->setBillingAddress($this->mapAddress($address->getQuote()->getBillingAddress())); +- $quoteDetails->setShippingAddress($this->mapAddress($address)); ++ if ($address->getAddressType() === QuoteAddress::ADDRESS_TYPE_BILLING ++ && !$address->getCountryId() ++ && $address->getQuote()->isVirtual() ++ && $address->getQuote()->getCustomerId() ++ ) { ++ $defaultBillingAddress = $this->customerAccountManagement->getDefaultBillingAddress( ++ $address->getQuote()->getCustomerId() ++ ); ++ $addressCopy = $address; ++ if ($defaultBillingAddress) { ++ $addressCopy = clone $address; ++ $addressCopy->importCustomerAddressData($defaultBillingAddress); ++ } ++ ++ $quoteDetails->setShippingAddress($this->mapAddress($addressCopy)); ++ } else { ++ $quoteDetails->setShippingAddress($this->mapAddress($address)); ++ } + return $quoteDetails; + } + diff --git a/patches/os/MDVA-38308_2.4.1-p1.patch b/patches/os/MDVA-38308_2.4.1-p1.patch new file mode 100644 index 00000000..4cd1ed07 --- /dev/null +++ b/patches/os/MDVA-38308_2.4.1-p1.patch @@ -0,0 +1,38 @@ +diff --git a/vendor/magento/framework/File/Uploader.php b/vendor/magento/framework/File/Uploader.php +index 0913ed01571..971161043fb 100644 +--- a/vendor/magento/framework/File/Uploader.php ++++ b/vendor/magento/framework/File/Uploader.php +@@ -710,20 +710,22 @@ class Uploader + */ + public static function getNewFileName($destinationFile) + { ++ /** @var Filesystem $fileSystem */ ++ $fileSystem = ObjectManager::getInstance()->get(Filesystem::class); ++ $local = $fileSystem->getDirectoryRead(DirectoryList::ROOT); ++ ++ $fileExists = function ($path) use ($local) { ++ return $local->isExist($path); ++ }; ++ + $fileInfo = pathinfo($destinationFile); +- if (file_exists($destinationFile)) { +- $index = 1; +- $baseName = $fileInfo['filename'] . '.' . $fileInfo['extension']; +- while (file_exists($fileInfo['dirname'] . '/' . $baseName)) { +- $baseName = $fileInfo['filename'] . '_' . $index . '.' . $fileInfo['extension']; +- $index++; +- } +- $destFileName = $baseName; +- } else { +- return $fileInfo['basename']; ++ $index = 1; ++ while ($fileExists($fileInfo['dirname'] . '/' . $fileInfo['basename'])) { ++ $fileInfo['basename'] = $fileInfo['filename'] . '_' . ($index++); ++ $fileInfo['basename'] .= isset($fileInfo['extension']) ? '.' . $fileInfo['extension'] : ''; + } + +- return $destFileName; ++ return $fileInfo['basename']; + } + + /** diff --git a/patches/os/MDVA-38308_2.4.2-p1.patch b/patches/os/MDVA-38308_2.4.2-p1.patch new file mode 100644 index 00000000..7fb31285 --- /dev/null +++ b/patches/os/MDVA-38308_2.4.2-p1.patch @@ -0,0 +1,14 @@ +diff --git a/vendor/magento/framework/File/Uploader.php b/vendor/magento/framework/File/Uploader.php +index 5e0bf593fef..067e2611b40 100644 +--- a/vendor/magento/framework/File/Uploader.php ++++ b/vendor/magento/framework/File/Uploader.php +@@ -803,7 +803,8 @@ class Uploader + $fileInfo = pathinfo($destinationFile); + $index = 1; + while ($fileExists($fileInfo['dirname'] . '/' . $fileInfo['basename'])) { +- $fileInfo['basename'] = $fileInfo['filename'] . '_' . $index++ . '.' . $fileInfo['extension']; ++ $fileInfo['basename'] = $fileInfo['filename'] . '_' . ($index++); ++ $fileInfo['basename'] .= isset($fileInfo['extension']) ? '.' . $fileInfo['extension'] : ''; + } + + return $fileInfo['basename']; diff --git a/patches/os/MDVA-38468_2.3.2-p2.patch b/patches/os/MDVA-38468_2.3.2-p2.patch new file mode 100644 index 00000000..b1f040bb --- /dev/null +++ b/patches/os/MDVA-38468_2.3.2-p2.patch @@ -0,0 +1,152 @@ +diff --git a/vendor/magento/module-store/Model/ScopeResolver.php b/vendor/magento/module-store/Model/ScopeResolver.php +new file mode 100644 +index 00000000000..1b505ffd28f +--- /dev/null ++++ b/vendor/magento/module-store/Model/ScopeResolver.php +@@ -0,0 +1,146 @@ ++scopeTree = $scopeTree; ++ } ++ ++ /** ++ * Check is some scope belongs to other scope ++ * ++ * @param string $baseScope ++ * @param int $baseScopeId ++ * @param string $requestedScope ++ * @param int $requestedScopeId ++ * @return bool ++ */ ++ public function isBelongsToScope( ++ string $baseScope, ++ int $baseScopeId, ++ string $requestedScope, ++ int $requestedScopeId ++ ) : bool { ++ /* All scopes belongs to All Store Views */ ++ if ($baseScope === ScopeConfigInterface::SCOPE_TYPE_DEFAULT) { ++ return true; ++ } ++ ++ $scopeNode = $this->getScopeNode($baseScope, $baseScopeId, [$this->scopeTree->get()]); ++ if (empty($scopeNode)) { ++ return false; ++ } ++ ++ return $this->isBelongsToScopeRecurse($requestedScope, $requestedScopeId, [$scopeNode]); ++ } ++ ++ /** ++ * Check is Belongs some scope to other scope (internal recurse) ++ * ++ * @param string $requestedScope ++ * @param int $requestedScopeId ++ * @param array $tree ++ * @return bool ++ */ ++ private function isBelongsToScopeRecurse( ++ string $requestedScope, ++ int $requestedScopeId, ++ array $tree ++ ) : bool { ++ foreach ($tree as $node) { ++ if ($this->isScopeEquals($node['scope'], $requestedScope) && (int)$node['scope_id'] === $requestedScopeId) { ++ return true; ++ } ++ if (!empty($node['scopes'])) { ++ $isBelongsToChild = $this->isBelongsToScopeRecurse( ++ $requestedScope, ++ $requestedScopeId, ++ $node['scopes'] ++ ); ++ if ($isBelongsToChild) { ++ return $isBelongsToChild; ++ } ++ } ++ } ++ ++ return false; ++ } ++ ++ /** ++ * Get tree by scope ++ * ++ * @param string $scope ++ * @param int $scopeId ++ * @param array $tree ++ * @return array ++ */ ++ private function getScopeNode(string $scope, int $scopeId, array $tree): array ++ { ++ foreach ($tree as $node) { ++ if ($this->isScopeEquals($node['scope'], $scope) && (int)$node['scope_id'] === $scopeId) { ++ return $node; ++ } ++ if (!empty($node['scopes'])) { ++ $found = $this->getScopeNode($scope, $scopeId, $node['scopes']); ++ if (!empty($found)) { ++ return $found; ++ } ++ } ++ } ++ ++ return []; ++ } ++ ++ /** ++ * Is scope equals with normalize names ++ * ++ * @param string $firstScope ++ * @param string $secondScope ++ * @return bool ++ */ ++ private function isScopeEquals(string $firstScope, string $secondScope): bool ++ { ++ return $this->normalizeScopeName($firstScope) === $this->normalizeScopeName($secondScope); ++ } ++ ++ /** ++ * Normalize scope name ++ * ++ * @param string $scope ++ * @return string ++ */ ++ private function normalizeScopeName(string $scope): string ++ { ++ switch ($scope) { ++ case ScopeInterface::SCOPE_STORES: ++ return ScopeInterface::SCOPE_STORE; ++ case ScopeInterface::SCOPE_WEBSITES: ++ return ScopeInterface::SCOPE_WEBSITE; ++ case ScopeInterface::SCOPE_GROUPS: ++ return ScopeInterface::SCOPE_GROUP; ++ default: ++ return $scope; ++ } ++ } ++} diff --git a/patches/os/MDVA-38531_2.3.4-p2.patch b/patches/os/MDVA-38531_2.3.4-p2.patch new file mode 100644 index 00000000..e49c6ec1 --- /dev/null +++ b/patches/os/MDVA-38531_2.3.4-p2.patch @@ -0,0 +1,416 @@ +diff --git a/vendor/magento/module-persistent/Model/Checkout/GuestShippingInformationManagementPlugin.php b/vendor/magento/module-persistent/Model/Checkout/GuestShippingInformationManagementPlugin.php +new file mode 100644 +index 00000000000..e78cae054e6 +--- /dev/null ++++ b/vendor/magento/module-persistent/Model/Checkout/GuestShippingInformationManagementPlugin.php +@@ -0,0 +1,99 @@ ++persistenceDataHelper = $persistenceDataHelper; ++ $this->persistenceSessionHelper = $persistenceSessionHelper; ++ $this->customerSession = $customerSession; ++ $this->quoteManager = $quoteManager; ++ } ++ ++ /** ++ * Convert shopping cart from persistent cart to guest cart after shipping information saved ++ * ++ * Check if shopping cart is persistent and customer is not logged in, and only one payment method is available, ++ * then converts the shopping cart guest cart. ++ * ++ * @param GuestShippingInformationManagement $subject ++ * @param PaymentDetailsInterface $result ++ * @return PaymentDetailsInterface ++ * @SuppressWarnings(PHPMD.UnusedFormalParameter) ++ */ ++ public function afterSaveAddressInformation( ++ GuestShippingInformationManagement $subject, ++ PaymentDetailsInterface $result ++ ): PaymentDetailsInterface { ++ if ($this->persistenceSessionHelper->isPersistent() ++ && !$this->customerSession->isLoggedIn() ++ && $this->persistenceDataHelper->isShoppingCartPersist() ++ && $this->quoteManager->isPersistent() ++ && count($result->getPaymentMethods()) === 1 ++ ) { ++ $this->customerSession->setCustomerId(null); ++ $this->customerSession->setCustomerGroupId(null); ++ $this->quoteManager->convertCustomerCartToGuest(); ++ } ++ return $result; ++ } ++} +diff --git a/vendor/magento/module-persistent/Model/QuoteManager.php b/vendor/magento/module-persistent/Model/QuoteManager.php +index ebddfe5a436..07ce85a5097 100644 +--- a/vendor/magento/module-persistent/Model/QuoteManager.php ++++ b/vendor/magento/module-persistent/Model/QuoteManager.php +@@ -5,8 +5,18 @@ + */ + namespace Magento\Persistent\Model; + ++use Magento\Customer\Api\Data\CustomerInterfaceFactory; ++use Magento\Customer\Api\Data\GroupInterface; ++use Magento\Framework\App\ObjectManager; ++use Magento\Persistent\Helper\Data; ++use Magento\Quote\Api\CartRepositoryInterface; ++use Magento\Quote\Api\Data\CartExtensionFactory; ++use Magento\Quote\Api\Data\CartInterface; ++use Magento\Quote\Model\Quote; ++use Magento\Quote\Model\Quote\ShippingAssignment\ShippingAssignmentProcessor; ++ + /** +- * Class QuoteManager ++ * Quote manager model + * + * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ +@@ -29,7 +39,7 @@ class QuoteManager + /** + * Persistent data + * +- * @var \Magento\Persistent\Helper\Data ++ * @var Data + */ + protected $persistentData; + +@@ -41,26 +51,52 @@ class QuoteManager + protected $_setQuotePersistent = true; + + /** +- * @var \Magento\Quote\Api\CartRepositoryInterface ++ * @var CartRepositoryInterface + */ + protected $quoteRepository; + + /** ++ * @var ShippingAssignmentProcessor ++ */ ++ private $shippingAssignmentProcessor; ++ /** ++ * @var CartExtensionFactory ++ */ ++ private $cartExtensionFactory; ++ ++ /** ++ * @var CustomerInterfaceFactory ++ */ ++ private $customerDataFactory; ++ ++ /** + * @param \Magento\Persistent\Helper\Session $persistentSession +- * @param \Magento\Persistent\Helper\Data $persistentData ++ * @param Data $persistentData + * @param \Magento\Checkout\Model\Session $checkoutSession +- * @param \Magento\Quote\Api\CartRepositoryInterface $quoteRepository ++ * @param CartRepositoryInterface $quoteRepository ++ * @param CartExtensionFactory|null $cartExtensionFactory ++ * @param ShippingAssignmentProcessor|null $shippingAssignmentProcessor ++ * @param CustomerInterfaceFactory|null $customerDataFactory + */ + public function __construct( + \Magento\Persistent\Helper\Session $persistentSession, +- \Magento\Persistent\Helper\Data $persistentData, ++ Data $persistentData, + \Magento\Checkout\Model\Session $checkoutSession, +- \Magento\Quote\Api\CartRepositoryInterface $quoteRepository ++ CartRepositoryInterface $quoteRepository, ++ ?CartExtensionFactory $cartExtensionFactory = null, ++ ?ShippingAssignmentProcessor $shippingAssignmentProcessor = null, ++ ?CustomerInterfaceFactory $customerDataFactory = null + ) { + $this->persistentSession = $persistentSession; + $this->persistentData = $persistentData; + $this->checkoutSession = $checkoutSession; + $this->quoteRepository = $quoteRepository; ++ $this->cartExtensionFactory = $cartExtensionFactory ++ ?? ObjectManager::getInstance()->get(CartExtensionFactory::class); ++ $this->shippingAssignmentProcessor = $shippingAssignmentProcessor ++ ?? ObjectManager::getInstance()->get(ShippingAssignmentProcessor::class); ++ $this->customerDataFactory = $customerDataFactory ++ ?? ObjectManager::getInstance()->get(CustomerInterfaceFactory::class); + } + + /** +@@ -71,7 +107,7 @@ class QuoteManager + */ + public function setGuest($checkQuote = false) + { +- /** @var $quote \Magento\Quote\Model\Quote */ ++ /** @var $quote Quote */ + $quote = $this->checkoutSession->getQuote(); + if ($quote && $quote->getId()) { + if ($checkQuote && !$this->persistentData->isShoppingCartPersist() && !$quote->getIsPersistent()) { +@@ -82,22 +118,41 @@ class QuoteManager + $quote->getPaymentsCollection()->walk('delete'); + $quote->getAddressesCollection()->walk('delete'); + $this->_setQuotePersistent = false; ++ $this->cleanCustomerData($quote); + $quote->setIsActive(true) +- ->setCustomerId(null) +- ->setCustomerEmail(null) +- ->setCustomerFirstname(null) +- ->setCustomerLastname(null) +- ->setCustomerGroupId(\Magento\Customer\Api\Data\GroupInterface::NOT_LOGGED_IN_ID) + ->setIsPersistent(false) + ->removeAllAddresses(); + //Create guest addresses + $quote->getShippingAddress(); + $quote->getBillingAddress(); ++ $this->setShippingAssignments($quote); + $quote->collectTotals(); + $this->quoteRepository->save($quote); + } + + $this->persistentSession->getSession()->removePersistentCookie(); ++ $this->persistentSession->setSession(null); ++ } ++ ++ /** ++ * Clear customer data in quote ++ * ++ * @param Quote $quote ++ */ ++ private function cleanCustomerData($quote) ++ { ++ /** ++ * Set empty customer object in quote to avoid restore customer id ++ * @see Quote::beforeSave() ++ */ ++ if ($quote->getCustomerId()) { ++ $quote->setCustomer($this->customerDataFactory->create()); ++ } ++ $quote->setCustomerId(null) ++ ->setCustomerEmail(null) ++ ->setCustomerFirstname(null) ++ ->setCustomerLastname(null) ++ ->setCustomerGroupId(GroupInterface::NOT_LOGGED_IN_ID); + } + + /** +@@ -111,7 +166,7 @@ class QuoteManager + public function convertCustomerCartToGuest() + { + $quoteId = $this->checkoutSession->getQuoteId(); +- /** @var $quote \Magento\Quote\Model\Quote */ ++ /** @var $quote Quote */ + $quote = $this->quoteRepository->get($quoteId); + if ($quote && $quote->getId()) { + $this->_setQuotePersistent = false; +@@ -126,6 +181,7 @@ class QuoteManager + $quote->getAddressesCollection()->walk('setEmail', ['email' => null]); + $quote->collectTotals(); + $this->persistentSession->getSession()->removePersistentCookie(); ++ $this->persistentSession->setSession(null); + $this->quoteRepository->save($quote); + } + } +@@ -144,7 +200,7 @@ class QuoteManager + $quote->setIsActive(true) + ->setIsPersistent(false) + ->setCustomerId(null) +- ->setCustomerGroupId(\Magento\Customer\Api\Data\GroupInterface::NOT_LOGGED_IN_ID); ++ ->setCustomerGroupId(GroupInterface::NOT_LOGGED_IN_ID); + } + } + +@@ -157,4 +213,23 @@ class QuoteManager + { + return $this->_setQuotePersistent; + } ++ ++ /** ++ * Create shipping assignment for shopping cart ++ * ++ * @param CartInterface $quote ++ */ ++ private function setShippingAssignments(CartInterface $quote): void ++ { ++ $shippingAssignments = []; ++ if (!$quote->isVirtual() && $quote->getItemsQty() > 0) { ++ $shippingAssignments[] = $this->shippingAssignmentProcessor->create($quote); ++ } ++ $cartExtension = $quote->getExtensionAttributes(); ++ if ($cartExtension === null) { ++ $cartExtension = $this->cartExtensionFactory->create(); ++ } ++ $cartExtension->setShippingAssignments($shippingAssignments); ++ $quote->setExtensionAttributes($cartExtension); ++ } + } +diff --git a/vendor/magento/module-persistent/Observer/MakePersistentQuoteGuestObserver.php b/vendor/magento/module-persistent/Observer/MakePersistentQuoteGuestObserver.php +index f2f9b96fa82..09b1147e27c 100644 +--- a/vendor/magento/module-persistent/Observer/MakePersistentQuoteGuestObserver.php ++++ b/vendor/magento/module-persistent/Observer/MakePersistentQuoteGuestObserver.php +@@ -1,6 +1,5 @@ + _persistentSession = $persistentSession; + $this->_persistentData = $persistentData; + $this->_customerSession = $customerSession; +- $this->quoteManager = $quoteManager; ++ $this->checkoutSession = $checkoutSession; + } + + /** +@@ -74,7 +73,7 @@ class MakePersistentQuoteGuestObserver implements ObserverInterface + if (($this->_persistentSession->isPersistent() && !$this->_customerSession->isLoggedIn()) + || $this->_persistentData->isShoppingCartPersist() + ) { +- $this->quoteManager->setGuest(true); ++ $this->checkoutSession->clearQuote()->clearStorage(); + } + } + } +diff --git a/vendor/magento/module-persistent/Observer/RemoveGuestPersistenceOnEmptyCartObserver.php b/vendor/magento/module-persistent/Observer/RemoveGuestPersistenceOnEmptyCartObserver.php +index fe754711c91..efc9ecd4c1a 100644 +--- a/vendor/magento/module-persistent/Observer/RemoveGuestPersistenceOnEmptyCartObserver.php ++++ b/vendor/magento/module-persistent/Observer/RemoveGuestPersistenceOnEmptyCartObserver.php +@@ -10,6 +10,8 @@ use Magento\Framework\Exception\NoSuchEntityException; + + /** + * Observer to remove persistent session if guest empties persistent cart previously created and added to by customer. ++ * ++ * @SuppressWarnings(PHPMD.CookieAndSessionMisuse) + */ + class RemoveGuestPersistenceOnEmptyCartObserver implements ObserverInterface + { +@@ -96,6 +98,8 @@ class RemoveGuestPersistenceOnEmptyCartObserver implements ObserverInterface + } + + if (!$cart || $cart->getItemsCount() == 0) { ++ $this->customerSession->setCustomerId(null) ++ ->setCustomerGroupId(null); + $this->quoteManager->setGuest(); + } + } +diff --git a/vendor/magento/module-persistent/etc/frontend/di.xml b/vendor/magento/module-persistent/etc/frontend/di.xml +index 3c33f8a51c4..24663f5ab4f 100644 +--- a/vendor/magento/module-persistent/etc/frontend/di.xml ++++ b/vendor/magento/module-persistent/etc/frontend/di.xml +@@ -49,4 +49,9 @@ + + + ++ ++ ++ Magento\Quote\Model\Quote\ShippingAssignment\ShippingAssignmentProcessor\Proxy ++ ++ + +diff --git a/vendor/magento/module-persistent/etc/webapi_rest/di.xml b/vendor/magento/module-persistent/etc/webapi_rest/di.xml +index e955dd81b19..cb0aec6b460 100644 +--- a/vendor/magento/module-persistent/etc/webapi_rest/di.xml ++++ b/vendor/magento/module-persistent/etc/webapi_rest/di.xml +@@ -9,4 +9,8 @@ + + + ++ ++ ++ + +diff --git a/vendor/magento/module-persistent/etc/webapi_soap/di.xml b/vendor/magento/module-persistent/etc/webapi_soap/di.xml +index e955dd81b19..cb0aec6b460 100644 +--- a/vendor/magento/module-persistent/etc/webapi_soap/di.xml ++++ b/vendor/magento/module-persistent/etc/webapi_soap/di.xml +@@ -9,4 +9,8 @@ + + + ++ ++ ++ + diff --git a/patches/os/MDVA-38535_2.3.5-p1.patch b/patches/os/MDVA-38535_2.3.5-p1.patch new file mode 100644 index 00000000..31dbb445 --- /dev/null +++ b/patches/os/MDVA-38535_2.3.5-p1.patch @@ -0,0 +1,47 @@ +diff --git a/vendor/magento/module-catalog-inventory/Model/Indexer/Stock/CacheCleaner.php b/vendor/magento/module-catalog-inventory/Model/Indexer/Stock/CacheCleaner.php +index b3fa074..94b8dd7 100644 +--- a/vendor/magento/module-catalog-inventory/Model/Indexer/Stock/CacheCleaner.php ++++ b/vendor/magento/module-catalog-inventory/Model/Indexer/Stock/CacheCleaner.php +@@ -8,6 +8,7 @@ + + namespace Magento\CatalogInventory\Model\Indexer\Stock; + ++use Magento\Catalog\Model\Category; + use Magento\CatalogInventory\Api\StockConfigurationInterface; + use Magento\Framework\App\ResourceConnection; + use Magento\Framework\App\ObjectManager; +@@ -90,6 +91,11 @@ class CacheCleaner + if ($productIds) { + $this->cacheContext->registerEntities(Product::CACHE_TAG, array_unique($productIds)); + $this->eventManager->dispatch('clean_cache_by_tags', ['object' => $this->cacheContext]); ++ $categoryIds = $this->getCategoryIdsByProductIds($productIds); ++ if ($categoryIds){ ++ $this->cacheContext->registerEntities(Category::CACHE_TAG, array_unique($categoryIds)); ++ $this->eventManager->dispatch('clean_cache_by_tags', ['object' => $this->cacheContext]); ++ } + } + } + +@@ -162,6 +168,22 @@ class CacheCleaner + } + + /** ++ * Get category ids for products ++ * ++ * @param array $productIds ++ * @return array ++ */ ++ private function getCategoryIdsByProductIds(array $productIds): array ++ { ++ $categoryProductTable = $this->getConnection()->getTableName('catalog_category_product'); ++ $select = $this->getConnection()->select() ++ ->from(['catalog_category_product' => $categoryProductTable], ['category_id']) ++ ->where('product_id IN (?)', $productIds); ++ ++ return $this->getConnection()->fetchCol($select); ++ } ++ ++ /** + * Get database connection. + * + * @return AdapterInterface diff --git a/patches/os/MDVA-38608_2.3.2-p2.patch b/patches/os/MDVA-38608_2.3.2-p2.patch new file mode 100644 index 00000000..c33ba8ff --- /dev/null +++ b/patches/os/MDVA-38608_2.3.2-p2.patch @@ -0,0 +1,19 @@ +diff --git a/vendor/magento/module-catalog-rule/Model/Indexer/IndexerTableSwapper.php b/vendor/magento/module-catalog-rule/Model/Indexer/IndexerTableSwapper.php +index f99f8c50a7f9a..0ddae74ff0a55 100644 +--- a/vendor/magento/module-catalog-rule/Model/Indexer/IndexerTableSwapper.php ++++ b/vendor/magento/module-catalog-rule/Model/Indexer/IndexerTableSwapper.php +@@ -122,4 +122,14 @@ public function swapIndexTables(array $originalTablesNames) + $this->resourceConnection->getConnection()->dropTable($tableName); + } + } ++ ++ /** ++ * Cleanup leftover temporary tables ++ */ ++ public function __destruct() ++ { ++ foreach ($this->temporaryTables as $tableName) { ++ $this->resourceConnection->getConnection()->dropTable($tableName); ++ } ++ } + }