Search

Mar 28, 2023

Magento2: Page Cache is not cleared for the parent product on the child product save

We changed price, color on some product, but sometimes it's not updated on the front end, after we cleared cache, it's updated. 

The issue is on Magento 2.4.4-p1 


SOLUTION:

Please check the below patch to fix

diff --git a/vendor/magento/module-configurable-product/Plugin/Model/ResourceModel/Product.php b/vendor/magento/module-configurable-product/Plugin/Model/ResourceModel/Product.php
index 2f333e7ca6f6..9c226641b6e3 100644
--- a/vendor/magento/module-configurable-product/Plugin/Model/ResourceModel/Product.php
+++ b/vendor/magento/module-configurable-product/Plugin/Model/ResourceModel/Product.php
@@ -4,20 +4,29 @@
  * Copyright © Magento, Inc. All rights reserved.
  * See COPYING.txt for license details.
  */
+declare(strict_types=1);
 
 namespace Magento\ConfigurableProduct\Plugin\Model\ResourceModel;
 
 use Magento\Catalog\Api\Data\ProductAttributeInterface;
 use Magento\Catalog\Api\ProductAttributeRepositoryInterface;
+use Magento\Catalog\Model\Indexer\Product\Category\Action\Rows;
+use Magento\Catalog\Model\Indexer\Product\Price\Processor;
+use Magento\Catalog\Model\Product as ProductModel;
+use Magento\Catalog\Model\ResourceModel\Product as ProductResource;
 use Magento\ConfigurableProduct\Api\Data\OptionInterface;
 use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
 use Magento\Framework\Api\FilterBuilder;
 use Magento\Framework\Api\SearchCriteriaBuilder;
 use Magento\Framework\App\ObjectManager;
+use Magento\Framework\DataObject;
 use Magento\Framework\Indexer\ActionInterface;
+use Magento\Framework\Indexer\IndexerRegistry;
 
 /**
  * Plugin product resource model
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  */
 class Product
 {
@@ -46,21 +55,35 @@ class Product
      */
     private $filterBuilder;
 
+    /**
+     * @var IndexerRegistry
+     */
+    private $indexerRegistry;
+
+    /**
+     * @var Rows
+     */
+    private $rowsAction;
+
     /**
      * Initialize Product dependencies.
      *
      * @param Configurable $configurable
      * @param ActionInterface $productIndexer
-     * @param ProductAttributeRepositoryInterface $productAttributeRepository
-     * @param SearchCriteriaBuilder $searchCriteriaBuilder
-     * @param FilterBuilder $filterBuilder
+     * @param ProductAttributeRepositoryInterface|null $productAttributeRepository
+     * @param SearchCriteriaBuilder|null $searchCriteriaBuilder
+     * @param FilterBuilder|null $filterBuilder
+     * @param IndexerRegistry|null $indexerRegistry
+     * @param Rows|null $rowsAction
      */
     public function __construct(
         Configurable $configurable,
         ActionInterface $productIndexer,
         ProductAttributeRepositoryInterface $productAttributeRepository = null,
-        SearchCriteriaBuilder $searchCriteriaBuilder = null,
-        FilterBuilder $filterBuilder = null
+        ?SearchCriteriaBuilder $searchCriteriaBuilder = null,
+        ?FilterBuilder $filterBuilder = null,
+        ?IndexerRegistry $indexerRegistry = null,
+        ?Rows $rowsAction = null
     ) {
         $this->configurable = $configurable;
         $this->productIndexer = $productIndexer;
@@ -70,35 +93,62 @@ public function __construct(
             ->get(SearchCriteriaBuilder::class);
         $this->filterBuilder = $filterBuilder ?: ObjectManager::getInstance()
             ->get(FilterBuilder::class);
+        $this->indexerRegistry = $indexerRegistry ?: ObjectManager::getInstance()
+            ->get(IndexerRegistry::class);
+        $this->rowsAction = $rowsAction ?: ObjectManager::getInstance()
+            ->get(Rows::class);
     }
 
     /**
      * We need reset attribute set id to attribute after related simple product was saved
      *
-     * @param \Magento\Catalog\Model\ResourceModel\Product $subject
-     * @param \Magento\Framework\DataObject $object
+     * @param ProductResource $subject
+     * @param DataObject $object
      * @return void
-     * @throws \Magento\Framework\Exception\NoSuchEntityException
      *
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
     public function beforeSave(
-        \Magento\Catalog\Model\ResourceModel\Product $subject,
-        \Magento\Framework\DataObject $object
+        ProductResource $subject,
+        DataObject $object
     ) {
-        /** @var \Magento\Catalog\Model\Product $object */
+        /** @var ProductModel $object */
         if ($object->getTypeId() == Configurable::TYPE_CODE) {
             $object->getTypeInstance()->getSetAttributes($object);
             $this->resetConfigurableOptionsData($object);
         }
     }
 
+    /**
+     * Invalidate cache and per<!-- HTML generated using hilite.me --><div style="background: #ffffff; overflow:auto;width:auto;border:solid gray;border-width:.1em .1em .1em .8em;padding:.2em .6em;"><pre style="margin: 0; line-height: 125%">diff --git a/vendor/magento/module-configurable-product/Plugin/Model/ResourceModel/Product.php b/vendor/magento/module-configurable-product/Plugin/Model/ResourceModel/Product.php
index 2f333e7ca6f6..9c226641b6e3 100644
--- a/vendor/magento/module-configurable-product/Plugin/Model/ResourceModel/Product.php
+++ b/vendor/magento/module-configurable-product/Plugin/Model/ResourceModel/Product.php
@@ -4,20 +4,29 @@
  * Copyright © Magento, Inc. All rights reserved.
  * See COPYING.txt for license details.
  */
+declare(strict_types=1);
 
 namespace Magento\ConfigurableProduct\Plugin\Model\ResourceModel;
 
 use Magento\Catalog\Api\Data\ProductAttributeInterface;
 use Magento\Catalog\Api\ProductAttributeRepositoryInterface;
+use Magento\Catalog\Model\Indexer\Product\Category\Action\Rows;
+use Magento\Catalog\Model\Indexer\Product\Price\Processor;
+use Magento\Catalog\Model\Product as ProductModel;
+use Magento\Catalog\Model\ResourceModel\Product as ProductResource;
 use Magento\ConfigurableProduct\Api\Data\OptionInterface;
 use Magento\ConfigurableProduct\Model\Product\Type\Configurable;
 use Magento\Framework\Api\FilterBuilder;
 use Magento\Framework\Api\SearchCriteriaBuilder;
 use Magento\Framework\App\ObjectManager;
+use Magento\Framework\DataObject;
 use Magento\Framework\Indexer\ActionInterface;
+use Magento\Framework\Indexer\IndexerRegistry;
 
 /**
  * Plugin product resource model
+ *
+ * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
  */
 class Product
 {
@@ -46,21 +55,35 @@ class Product
      */
     private $filterBuilder;
 
+    /**
+     * @var IndexerRegistry
+     */
+    private $indexerRegistry;
+
+    /**
+     * @var Rows
+     */
+    private $rowsAction;
+
     /**
      * Initialize Product dependencies.
      *
      * @param Configurable $configurable
      * @param ActionInterface $productIndexer
-     * @param ProductAttributeRepositoryInterface $productAttributeRepository
-     * @param SearchCriteriaBuilder $searchCriteriaBuilder
-     * @param FilterBuilder $filterBuilder
+     * @param ProductAttributeRepositoryInterface|null $productAttributeRepository
+     * @param SearchCriteriaBuilder|null $searchCriteriaBuilder
+     * @param FilterBuilder|null $filterBuilder
+     * @param IndexerRegistry|null $indexerRegistry
+     * @param Rows|null $rowsAction
      */
     public function __construct(
         Configurable $configurable,
         ActionInterface $productIndexer,
         ProductAttributeRepositoryInterface $productAttributeRepository = null,
-        SearchCriteriaBuilder $searchCriteriaBuilder = null,
-        FilterBuilder $filterBuilder = null
+        ?SearchCriteriaBuilder $searchCriteriaBuilder = null,
+        ?FilterBuilder $filterBuilder = null,
+        ?IndexerRegistry $indexerRegistry = null,
+        ?Rows $rowsAction = null
     ) {
         $this-&gt;configurable = $configurable;
         $this-&gt;productIndexer = $productIndexer;
@@ -70,35 +93,62 @@ public function __construct(
             -&gt;get(SearchCriteriaBuilder::class);
         $this-&gt;filterBuilder = $filterBuilder ?: ObjectManager::getInstance()
             -&gt;get(FilterBuilder::class);
+        $this-&gt;indexerRegistry = $indexerRegistry ?: ObjectManager::getInstance()
+            -&gt;get(IndexerRegistry::class);
+        $this-&gt;rowsAction = $rowsAction ?: ObjectManager::getInstance()
+            -&gt;get(Rows::class);
     }
 
     /**
      * We need reset attribute set id to attribute after related simple product was saved
      *
-     * @param \Magento\Catalog\Model\ResourceModel\Product $subject
-     * @param \Magento\Framework\DataObject $object
+     * @param ProductResource $subject
+     * @param DataObject $object
      * @return void
-     * @throws \Magento\Framework\Exception\NoSuchEntityException
      *
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
     public function beforeSave(
-        \Magento\Catalog\Model\ResourceModel\Product $subject,
-        \Magento\Framework\DataObject $object
+        ProductResource $subject,
+        DataObject $object
     ) {
-        /** @var \Magento\Catalog\Model\Product $object */
+        /** @var ProductModel $object */
         if ($object-&gt;getTypeId() == Configurable::TYPE_CODE) {
             $object-&gt;getTypeInstance()-&gt;getSetAttributes($object);
             $this-&gt;resetConfigurableOptionsData($object);
         }
     }
 
+    /**
+     * Invalidate cache and perform reindexing for configurable associated product
+     *
+     * @param ProductResource $subject
+     * @param ProductResource $result
+     * @param DataObject $object
+     * @return ProductResource
+     *
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function afterSave(
+        ProductResource $subject,
+        ProductResource $result,
+        DataObject $object
+    ): ProductResource {
+        $productId = $object-&gt;getId();
+        $priceIndexer = $this-&gt;indexerRegistry-&gt;get(Processor::INDEXER_ID);
+        if ($priceIndexer-&gt;isScheduled()
+            &amp;&amp; count($this-&gt;configurable-&gt;getParentIdsByChild($productId)) &gt; 0) {
+            $this-&gt;rowsAction-&gt;execute([$productId]);
+        }
+
+        return $result;
+    }
+
     /**
      * Set null for configurable options attribute of configurable product
      *
-     * @param \Magento\Catalog\Model\Product $object
+     * @param ProductModel $object
      * @return void
-     * @throws \Magento\Framework\Exception\NoSuchEntityException
      */
     private function resetConfigurableOptionsData($object)
     {
@@ -128,16 +178,16 @@ private function resetConfigurableOptionsData($object)
     /**
      * Gather configurable parent ids of product being deleted and reindex after delete is complete.
      *
-     * @param \Magento\Catalog\Model\ResourceModel\Product $subject
+     * @param ProductResource $subject
      * @param \Closure $proceed
-     * @param \Magento\Catalog\Model\Product $product
-     * @return \Magento\Catalog\Model\ResourceModel\Product
+     * @param ProductModel $product
+     * @return ProductResource
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
     public function aroundDelete(
-        \Magento\Catalog\Model\ResourceModel\Product $subject,
+        ProductResource $subject,
         \Closure $proceed,
-        \Magento\Catalog\Model\Product $product
+        ProductModel $product
     ) {
         $configurableProductIds = $this-&gt;configurable-&gt;getParentIdsByChild($product-&gt;getId());
         $result = $proceed($product);
</pre></div>
form reindexing for configurable associated product
+     *
+     * @param ProductResource $subject
+     * @param ProductResource $result
+     * @param DataObject $object
+     * @return ProductResource
+     *
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function afterSave(
+        ProductResource $subject,
+        ProductResource $result,
+        DataObject $object
+    ): ProductResource {
+        $productId = $object->getId();
+        $priceIndexer = $this->indexerRegistry->get(Processor::INDEXER_ID);
+        if ($priceIndexer->isScheduled()
+            && count($this->configurable->getParentIdsByChild($productId)) > 0) {
+            $this->rowsAction->execute([$productId]);
+        }
+
+        return $result;
+    }
+
     /**
      * Set null for configurable options attribute of configurable product
      *
-     * @param \Magento\Catalog\Model\Product $object
+     * @param ProductModel $object
      * @return void
-     * @throws \Magento\Framework\Exception\NoSuchEntityException
      */
     private function resetConfigurableOptionsData($object)
     {
@@ -128,16 +178,16 @@ private function resetConfigurableOptionsData($object)
     /**
      * Gather configurable parent ids of product being deleted and reindex after delete is complete.
      *
-     * @param \Magento\Catalog\Model\ResourceModel\Product $subject
+     * @param ProductResource $subject
      * @param \Closure $proceed
-     * @param \Magento\Catalog\Model\Product $product
-     * @return \Magento\Catalog\Model\ResourceModel\Product
+     * @param ProductModel $product
+     * @return ProductResource
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
     public function aroundDelete(
-        \Magento\Catalog\Model\ResourceModel\Product $subject,
+        ProductResource $subject,
         \Closure $proceed,
-        \Magento\Catalog\Model\Product $product
+        ProductModel $product
     ) {
         $configurableProductIds = $this->configurable->getParentIdsByChild($product->getId());
         $result = $proceed($product);

Magento2: Uncaught SyntaxError: Unexpected token const error in Admin panel

 We are using Magento 2.4.4-p2 on STAGING site. We got the JS error on admin page:
+ Uncaught SyntaxError: Unexpected token 'const' at tab.min.js and collapse.min.js
+ Uncaught TypeError: $(...).filter(...).collapse is not a function.

Reference: 


SOLUTION FOR MAGENTO CLOUD (NOT WORK):

1. Update ece-tools
composer update magento/ece-tools --with-dependencies

2. Add the patch on magento.env.yaml and pushed to cloud site

stage: 
  build:
    QUALITY_PATCHES:
      - MDVA-44887



SOLUTION 2:

Follow the below patch

diff --git a/lib/web/jquery/bootstrap/collapse.js b/lib/web/jquery/bootstrap/collapse.js
index 95e28cec248..5a978bcfafb 100644
--- a/lib/web/jquery/bootstrap/collapse.js
+++ b/lib/web/jquery/bootstrap/collapse.js
@@ -32,7 +32,7 @@ define([
     const VERSION = '5.1.3'
     const NAME = 'collapse'
     const DATA_KEY = 'bs.collapse'
-    const EVENT_KEY = `.${DATA_KEY}`
+    const EVENT_KEY = `.${DATA_KEY}`;
     const DATA_API_KEY = '.data-api'
 
     const Default = {
@@ -45,17 +45,17 @@ define([
         parent: '(null|element)'
     }
 
-    const EVENT_SHOW = `show${EVENT_KEY}`
-    const EVENT_SHOWN = `shown${EVENT_KEY}`
-    const EVENT_HIDE = `hide${EVENT_KEY}`
-    const EVENT_HIDDEN = `hidden${EVENT_KEY}`
-    const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
+    const EVENT_SHOW = `show${EVENT_KEY}`;
+    const EVENT_SHOWN = `shown${EVENT_KEY}`;
+    const EVENT_HIDE = `hide${EVENT_KEY}`;
+    const EVENT_HIDDEN = `hidden${EVENT_KEY}`;
+    const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;
 
     const CLASS_NAME_SHOW = 'show'
     const CLASS_NAME_COLLAPSE = 'collapse'
     const CLASS_NAME_COLLAPSING = 'collapsing'
     const CLASS_NAME_COLLAPSED = 'collapsed'
-    const CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`
+    const CLASS_NAME_DEEPER_CHILDREN = `:scope .${CLASS_NAME_COLLAPSE} .${CLASS_NAME_COLLAPSE}`;
     const CLASS_NAME_HORIZONTAL = 'collapse-horizontal'
 
     const WIDTH = 'width'
@@ -204,10 +204,10 @@ define([
         }
 
         const capitalizedDimension = dimension[0].toUpperCase() + dimension.slice(1)
-        const scrollSize = `scroll${capitalizedDimension}`
+        const scrollSize = `scroll${capitalizedDimension}`;
 
         this._queueCallback(complete, this._element, true)
-        this._element.style[dimension] = `${this._element[scrollSize]}px`
+        this._element.style[dimension] = `${this._element[scrollSize]}px`;
     }
 
     Collapse.prototype.hide = function() {
@@ -222,7 +222,7 @@ define([
 
         const dimension = this._getDimension()
 
-        this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`
+        this._element.style[dimension] = `${this._element.getBoundingClientRect()[dimension]}px`;
 
         reflow(this._element)
 
diff --git a/lib/web/jquery/bootstrap/tab.js b/lib/web/jquery/bootstrap/tab.js
index 928f8de3f37..9805d0b0123 100644
--- a/lib/web/jquery/bootstrap/tab.js
+++ b/lib/web/jquery/bootstrap/tab.js
@@ -28,14 +28,14 @@ define([
     const VERSION = '5.1.3'
     const NAME = 'tab'
     const DATA_KEY = 'bs.tab'
-    const EVENT_KEY = `.${DATA_KEY}`
+    const EVENT_KEY = `.${DATA_KEY}`;
     const DATA_API_KEY = '.data-api'
 
-    const EVENT_HIDE = `hide${EVENT_KEY}`
-    const EVENT_HIDDEN = `hidden${EVENT_KEY}`
-    const EVENT_SHOW = `show${EVENT_KEY}`
-    const EVENT_SHOWN = `shown${EVENT_KEY}`
-    const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`
+    const EVENT_HIDE = `hide${EVENT_KEY}`;
+    const EVENT_HIDDEN = `hidden${EVENT_KEY}`;
+    const EVENT_SHOW = `show${EVENT_KEY}`;
+    const EVENT_SHOWN = `shown${EVENT_KEY}`;
+    const EVENT_CLICK_DATA_API = `click${EVENT_KEY}${DATA_API_KEY}`;
 
     const CLASS_NAME_DROPDOWN_MENU = 'dropdown-menu'
     const CLASS_NAME_ACTIVE = 'active'