Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
e83b225
#40226 - Conditionally add salesrule attributes to eav query for FE/R…
Oct 28, 2025
781845d
#40226 - Adding copyright for Static Tests
Oct 28, 2025
a6e85bb
Merge branch '2.4-develop' into Fix-for-40226
senthilengg Oct 28, 2025
e3040a0
#40226 - Made Before Dispatch to return void
Oct 28, 2025
332adf6
#40226 - Skip optimisation if trigger_recollect is set to 1 in DB to …
Oct 28, 2025
370a034
Merge branch '2.4-develop' into Fix-for-40226
senthilengg Oct 28, 2025
d045762
#40226 - Static Test Fixes & Removed confusing comments
Oct 28, 2025
f00101c
#40226 - Introduced separate file for persisting trigger recollect & …
Oct 29, 2025
9874caf
#40226 - Unit Test Fixes and Suppress warning on Query Parser
Oct 29, 2025
3d0d5e8
#40226 - Added missed property
Oct 29, 2025
659330f
Merge branch '2.4-develop' into Fix-for-40226
senthilengg Oct 29, 2025
e08b0c6
#40226 - Reset State Post GraphQl Dispatch and Remove additional Stat…
Oct 29, 2025
dfd5889
#40226 - Correcting typos in di
Oct 29, 2025
776bbf0
#40226-Removing Unwanted files and added test cases for all new plugins
Oct 30, 2025
e079e87
#40226 - Final Unit Test updates and Static test fixes
Oct 30, 2025
073a384
Merge branch '2.4-develop' into Fix-for-40226
senthilengg Nov 2, 2025
ddf43e0
Merge branch '2.4-develop' into Fix-for-40226
senthilengg Nov 5, 2025
2750b4d
Merge branch '2.4-develop' into Fix-for-40226
senthilengg Nov 6, 2025
08d67f5
Merge branch '2.4-develop' into Fix-for-40226
senthilengg Nov 7, 2025
c0077b0
Merge branch '2.4-develop' into Fix-for-40226
senthilengg Nov 8, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions app/code/Magento/SalesRule/Model/Plugin/FrontController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php
/**
* Copyright 2025 Adobe
* All Rights Reserved.
*/
declare(strict_types=1);

namespace Magento\SalesRule\Model\Plugin;

class FrontController
{
/**
* @param RequestTypeRegistry $requestTypeRegistry
*/
public function __construct(
private RequestTypeRegistry $requestTypeRegistry
) {
}

/**
* Identify the Request Type and set true if it is a GET request
*
* @param \Magento\Framework\App\FrontControllerInterface $subject
* @param \Magento\Framework\App\RequestInterface $request
* @return void
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function beforeDispatch(
\Magento\Framework\App\FrontControllerInterface $subject,
\Magento\Framework\App\RequestInterface $request
) {
$method = strtoupper($request->getMethod());
$isReadOnly = ($method === 'GET');

$this->requestTypeRegistry->setIsGetRequestOrQuery($isReadOnly);
}
}
38 changes: 38 additions & 0 deletions app/code/Magento/SalesRule/Model/Plugin/GraphQlController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php
/**
* Copyright 2025 Adobe
* All Rights Reserved.
*/
declare(strict_types=1);

namespace Magento\SalesRule\Model\Plugin;

use Magento\Framework\App\ResponseInterface;
use Magento\Framework\App\FrontControllerInterface;

class GraphQlController
{
/**
* @param RequestTypeRegistry $requestTypeRegistry
*/
public function __construct(
private RequestTypeRegistry $requestTypeRegistry
) {
}

/**
* Reset request type registry after dispatching GraphQL controller
*
* @param FrontControllerInterface $subject
* @param ResponseInterface $result
* @return ResponseInterface $result
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function afterDispatch(
FrontControllerInterface $subject,
ResponseInterface $result
) : ResponseInterface {
$this->requestTypeRegistry->reset();
return $result;
}
}
47 changes: 47 additions & 0 deletions app/code/Magento/SalesRule/Model/Plugin/QueryParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php
/**
* Copyright 2025 Adobe
* All Rights Reserved.
*/
declare(strict_types=1);

namespace Magento\SalesRule\Model\Plugin;

class QueryParser
{

/**
* @param RequestTypeRegistry $requestTypeRegistry
*/
public function __construct(
private RequestTypeRegistry $requestTypeRegistry
) {
}

/**
* Set QueryType to RequestTypeRegistry
*
* @param \Magento\Framework\GraphQl\Query\QueryParser $subject
* @param \GraphQL\Language\AST\DocumentNode $documentNode
* @return \GraphQL\Language\AST\DocumentNode $documentNode
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function afterParse(\Magento\Framework\GraphQl\Query\QueryParser $subject, $documentNode)
{
// Get the first operation definition
$operation = null;
foreach ($documentNode->definitions as $definition) {
if ($definition instanceof \GraphQL\Language\AST\OperationDefinitionNode) {
$operation = $definition;
break;
}
}

if ($operation) {
$isOperationTypeQuery = ($operation->operation === 'query');
$this->requestTypeRegistry->setIsGetRequestOrQuery($isOperationTypeQuery);
}

return $documentNode;
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
<?php
/**
* Copyright 2013 Adobe
* Copyright 2025 Adobe
* All Rights Reserved.
*/
declare(strict_types=1);

namespace Magento\SalesRule\Model\Plugin;

use Magento\Framework\App\CacheInterface;
use Magento\Framework\Serialize\SerializerInterface;
use Magento\Quote\Model\Quote\Config;
use Magento\Quote\Model\Quote;
use Magento\SalesRule\Model\ResourceModel\Rule as RuleResource;

class QuoteConfigProductAttributes
Expand All @@ -21,11 +25,28 @@ class QuoteConfigProductAttributes
*/
private $activeAttributeCodes;

/**
* Cache key for active salesrule attributes
*/
private const CACHE_KEY = 'salesrule_active_product_attributes';

/**
* Cache tag for salesrule attributes
*/
private const CACHE_TAG = 'salesrule';

/**
* @param RuleResource $ruleResource
* @param RequestTypeRegistry $requestTypeRegistry
* @param CacheInterface $cache
* @param SerializerInterface $serializer
*/
public function __construct(RuleResource $ruleResource)
{
public function __construct(
RuleResource $ruleResource,
private RequestTypeRegistry $requestTypeRegistry,
private CacheInterface $cache,
private SerializerInterface $serializer
) {
$this->ruleResource = $ruleResource;
}

Expand All @@ -40,8 +61,24 @@ public function __construct(RuleResource $ruleResource)
*/
public function afterGetProductAttributes(Config $subject, array $attributeKeys): array
{
if ($this->activeAttributeCodes === null) {
$this->activeAttributeCodes = array_column($this->ruleResource->getActiveAttributes(), 'attribute_code');
if ($this->requestTypeRegistry->isGetRequestOrQuery()) {
return $attributeKeys;
}

$cachedData = $this->cache->load(self::CACHE_KEY);

if ($cachedData !== false) {
$this->activeAttributeCodes = $this->serializer->unserialize($cachedData);
} else {
$this->activeAttributeCodes = array_column(
$this->ruleResource->getActiveAttributes(),
'attribute_code'
);
$this->cache->save(
$this->serializer->serialize($this->activeAttributeCodes),
self::CACHE_KEY,
[self::CACHE_TAG]
);
}

return array_merge($attributeKeys, $this->activeAttributeCodes);
Expand Down
36 changes: 36 additions & 0 deletions app/code/Magento/SalesRule/Model/Plugin/QuoteItemCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php
/**
* Copyright 2025 Adobe
* All Rights Reserved.
*/
declare(strict_types=1);

namespace Magento\SalesRule\Model\Plugin;

class QuoteItemCollection
{
/**
* @param RequestTypeRegistry $requestTypeRegistry
*/
public function __construct(
private RequestTypeRegistry $requestTypeRegistry
) {
}

/**
* Set the Request Type before setting the quote in the Quote Item Collection.
*
* @param \Magento\Quote\Model\ResourceModel\Quote\Item\Collection $subject The collection instance.
* @param \Magento\Quote\Model\Quote $quote The quote to be processed.
* @return void
* @SuppressWarnings(PHPMD.UnusedFormalParameter)
*/
public function beforeSetQuote(
\Magento\Quote\Model\ResourceModel\Quote\Item\Collection $subject,
\Magento\Quote\Model\Quote $quote
) {
if ($quote->getTriggerRecollect() == 1 && $this->requestTypeRegistry->isGetRequestOrQuery()) {
$this->requestTypeRegistry->setIsGetRequestOrQuery(false);
}
}
}
53 changes: 53 additions & 0 deletions app/code/Magento/SalesRule/Model/Plugin/RequestTypeRegistry.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php
/**
* Copyright 2025 Adobe
* All Rights Reserved.
*/
declare(strict_types=1);

namespace Magento\SalesRule\Model\Plugin;

/**
* Tracks whether the request is http post or mutation
*
* This class is used to optimize performance by only loading salesrule product attributes
* when they're actually needed during totals collection, rather than on every quote load.
*/
class RequestTypeRegistry
{
/**
* @var bool
*/
private $isGetRequestOrQuery= false;

/**
* Set Request state
*
* @param bool $state
* @return void
*/
public function setIsGetRequestOrQuery(bool $state): void
{
$this->isGetRequestOrQuery = $state;
}

/**
* Check if request is a get or query
*
* @return bool
*/
public function isGetRequestOrQuery(): bool
{
return $this->isGetRequestOrQuery;
}

/**
* Resets the state of the object by setting the isGetRequestOrQuery property to false.
*
* @return void
*/
public function reset(): void
{
$this->isGetRequestOrQuery = false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
<?php
/**
* Copyright 2025 Adobe
* All Rights Reserved.
*/
declare(strict_types=1);

namespace Magento\SalesRule\Test\Unit\Model\Plugin;

use Magento\Framework\App\FrontControllerInterface;
use Magento\Framework\App\RequestInterface;
use Magento\SalesRule\Model\Plugin\FrontController;
use Magento\SalesRule\Model\Plugin\RequestTypeRegistry;
use PHPUnit\Framework\MockObject\MockObject;
use PHPUnit\Framework\TestCase;

class FrontControllerTest extends TestCase
{
/** @var RequestTypeRegistry|MockObject */
private $requestTypeRegistry;

/** @var FrontController */
private $plugin;

/**
* @var RequestInterface|MockObject
*/
private RequestInterface $request;

/**
* @var FrontControllerInterface|MockObject
*/
private FrontControllerInterface $subject;

protected function setUp(): void
{
$this->requestTypeRegistry = $this->createMock(RequestTypeRegistry::class);
$this->plugin = new FrontController($this->requestTypeRegistry);
$this->request = $this->getMockBuilder(RequestInterface::class)
->addMethods(['getMethod'])
->getMockForAbstractClass();
$this->subject = $this->createMock(FrontControllerInterface::class);
}

public function testBeforeDispatchSetsTrueForGetRequests(): void
{
$this->request->method('getMethod')->willReturn('GET');

$this->requestTypeRegistry
->expects($this->once())
->method('setIsGetRequestOrQuery')
->with(true);

$this->plugin->beforeDispatch($this->subject, $this->request);
}

public function testBeforeDispatchSetsFalseForPostRequests(): void
{
$this->request->method('getMethod')->willReturn('POST');

$this->requestTypeRegistry
->expects($this->once())
->method('setIsGetRequestOrQuery')
->with(false);

$this->plugin->beforeDispatch($this->subject, $this->request);
}

public function testBeforeDispatchIsCaseInsensitive(): void
{

$this->request->method('getMethod')->willReturn('get'); // lowercase

$this->requestTypeRegistry
->expects($this->once())
->method('setIsGetRequestOrQuery')
->with(true);

$this->plugin->beforeDispatch($this->subject, $this->request);
}
}
Loading