Skip to content

Commit a33ce15

Browse files
committed
FIxed #4297
1 parent 18cdd82 commit a33ce15

5 files changed

Lines changed: 206 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Release Notes for Craft Commerce
22

3+
## Unreleased
4+
5+
- Fixed a bug where GraphQL queries using `relatedTo*` arguments within `hasProduct` or `hasVariant` inputs caused a server error. ([#4297](https://github.com/craftcms/commerce/issues/4297))
6+
37
## 5.6.4 - 2026-05-06
48

59
- Fixed a SQL error that occurred when processing a fulfillment on PostgreSQL. ([#4291](https://github.com/craftcms/commerce/issues/4291))

src/Plugin.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
use craft\commerce\fieldlayoutelements\VariantTitleField;
4545
use craft\commerce\fields\Products as ProductsField;
4646
use craft\commerce\fields\Variants as VariantsField;
47+
use craft\commerce\gql\handlers\HasProduct;
48+
use craft\commerce\gql\handlers\HasVariant;
4749
use craft\commerce\gql\handlers\RelatedProducts;
4850
use craft\commerce\gql\handlers\RelatedVariants;
4951
use craft\commerce\gql\interfaces\elements\Product as GqlProductInterface;
@@ -1044,6 +1046,8 @@ private function _registerGqlEagerLoadableFields(): void
10441046
private function _registerGqlArgumentHandlers(): void
10451047
{
10461048
Event::on(ArgumentManager::class, ArgumentManager::EVENT_DEFINE_GQL_ARGUMENT_HANDLERS, static function(RegisterGqlArgumentHandlersEvent $event) {
1049+
$event->handlers['hasProduct'] = HasProduct::class;
1050+
$event->handlers['hasVariant'] = HasVariant::class;
10471051
$event->handlers['relatedToProducts'] = RelatedProducts::class;
10481052
$event->handlers['relatedToVariants'] = RelatedVariants::class;
10491053
});

src/gql/handlers/HasProduct.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
/**
3+
* @link https://craftcms.com/
4+
* @copyright Copyright (c) Pixel & Tonic, Inc.
5+
* @license https://craftcms.github.io/license/
6+
*/
7+
8+
namespace craft\commerce\gql\handlers;
9+
10+
use craft\gql\base\ArgumentHandler;
11+
12+
/**
13+
* Class HasProduct
14+
*
15+
* @author Pixel & Tonic, Inc. <support@pixelandtonic.com>
16+
* @since 5.6.5
17+
*/
18+
class HasProduct extends ArgumentHandler
19+
{
20+
protected string $argumentName = 'hasProduct';
21+
22+
/**
23+
* @inheritdoc
24+
*/
25+
protected function handleArgument(mixed $argumentValue): mixed
26+
{
27+
if (is_array($argumentValue)) {
28+
return $this->argumentManager->prepareArguments($argumentValue);
29+
}
30+
31+
return $argumentValue;
32+
}
33+
}

src/gql/handlers/HasVariant.php

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
/**
3+
* @link https://craftcms.com/
4+
* @copyright Copyright (c) Pixel & Tonic, Inc.
5+
* @license https://craftcms.github.io/license/
6+
*/
7+
8+
namespace craft\commerce\gql\handlers;
9+
10+
use craft\gql\base\ArgumentHandler;
11+
12+
/**
13+
* Class HasVariant
14+
*
15+
* @author Pixel & Tonic, Inc. <support@pixelandtonic.com>
16+
* @since 5.6.5
17+
*/
18+
class HasVariant extends ArgumentHandler
19+
{
20+
protected string $argumentName = 'hasVariant';
21+
22+
/**
23+
* @inheritdoc
24+
*/
25+
protected function handleArgument(mixed $argumentValue): mixed
26+
{
27+
if (is_array($argumentValue)) {
28+
return $this->argumentManager->prepareArguments($argumentValue);
29+
}
30+
31+
return $argumentValue;
32+
}
33+
}
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<?php
2+
/**
3+
* @link https://craftcms.com/
4+
* @copyright Copyright (c) Pixel & Tonic, Inc.
5+
* @license https://craftcms.github.io/license/
6+
*/
7+
8+
namespace craftcommercetests\unit\gql;
9+
10+
use Codeception\Test\Unit;
11+
use craft\commerce\gql\handlers\HasProduct;
12+
use craft\commerce\gql\handlers\HasVariant;
13+
use craft\gql\ArgumentManager;
14+
use craft\gql\handlers\RelatedEntries;
15+
use UnitTester;
16+
17+
/**
18+
* ArgumentHandlerTest
19+
*
20+
* @author Pixel & Tonic, Inc. <support@pixelandtonic.com>
21+
* @since 5.6.5
22+
*/
23+
class ArgumentHandlerTest extends Unit
24+
{
25+
/**
26+
* @var UnitTester
27+
*/
28+
protected UnitTester $tester;
29+
30+
/**
31+
* Tests that HasProduct processes nested GQL relation arguments (e.g. relatedToEntries)
32+
* by delegating to ArgumentManager, converting them to proper relatedTo criteria.
33+
*/
34+
public function testHasProductProcessesNestedRelationArgs(): void
35+
{
36+
$relatedEntryIds = [42, 99];
37+
38+
$relatedEntriesHandler = $this->make(RelatedEntries::class, [
39+
'getIds' => fn() => [$relatedEntryIds],
40+
]);
41+
42+
$argumentManager = new ArgumentManager();
43+
$relatedEntriesHandler->setArgumentManager($argumentManager);
44+
$argumentManager->setHandler('relatedToEntries', $relatedEntriesHandler);
45+
46+
$hasProductHandler = new HasProduct();
47+
$hasProductHandler->setArgumentManager($argumentManager);
48+
$argumentManager->setHandler('hasProduct', $hasProductHandler);
49+
50+
$result = $argumentManager->prepareArguments([
51+
'hasProduct' => [
52+
'relatedToEntries' => [['section' => 'news']],
53+
],
54+
]);
55+
56+
self::assertIsArray($result['hasProduct']);
57+
self::assertArrayNotHasKey('relatedToEntries', $result['hasProduct']);
58+
self::assertArrayHasKey('relatedTo', $result['hasProduct']);
59+
self::assertSame(['and', ['element' => $relatedEntryIds]], $result['hasProduct']['relatedTo']);
60+
}
61+
62+
/**
63+
* Tests that HasProduct passes standard (non-GQL) criteria through unchanged.
64+
*/
65+
public function testHasProductPassesThroughStandardArgs(): void
66+
{
67+
$argumentManager = new ArgumentManager();
68+
$hasProductHandler = new HasProduct();
69+
$hasProductHandler->setArgumentManager($argumentManager);
70+
$argumentManager->setHandler('hasProduct', $hasProductHandler);
71+
72+
$result = $argumentManager->prepareArguments([
73+
'hasProduct' => [
74+
'slug' => 'rad-hoodie',
75+
'type' => 'hoodies',
76+
],
77+
]);
78+
79+
self::assertSame(['slug' => 'rad-hoodie', 'type' => 'hoodies'], $result['hasProduct']);
80+
}
81+
82+
/**
83+
* Tests that HasVariant processes nested GQL relation arguments (e.g. relatedToEntries)
84+
* by delegating to ArgumentManager, converting them to proper relatedTo criteria.
85+
*/
86+
public function testHasVariantProcessesNestedRelationArgs(): void
87+
{
88+
$relatedEntryIds = [7, 13];
89+
90+
$relatedEntriesHandler = $this->make(RelatedEntries::class, [
91+
'getIds' => fn() => [$relatedEntryIds],
92+
]);
93+
94+
$argumentManager = new ArgumentManager();
95+
$relatedEntriesHandler->setArgumentManager($argumentManager);
96+
$argumentManager->setHandler('relatedToEntries', $relatedEntriesHandler);
97+
98+
$hasVariantHandler = new HasVariant();
99+
$hasVariantHandler->setArgumentManager($argumentManager);
100+
$argumentManager->setHandler('hasVariant', $hasVariantHandler);
101+
102+
$result = $argumentManager->prepareArguments([
103+
'hasVariant' => [
104+
'relatedToEntries' => [['section' => 'news']],
105+
],
106+
]);
107+
108+
self::assertIsArray($result['hasVariant']);
109+
self::assertArrayNotHasKey('relatedToEntries', $result['hasVariant']);
110+
self::assertArrayHasKey('relatedTo', $result['hasVariant']);
111+
self::assertSame(['and', ['element' => $relatedEntryIds]], $result['hasVariant']['relatedTo']);
112+
}
113+
114+
/**
115+
* Tests that HasVariant passes standard (non-GQL) criteria through unchanged.
116+
*/
117+
public function testHasVariantPassesThroughStandardArgs(): void
118+
{
119+
$argumentManager = new ArgumentManager();
120+
$hasVariantHandler = new HasVariant();
121+
$hasVariantHandler->setArgumentManager($argumentManager);
122+
$argumentManager->setHandler('hasVariant', $hasVariantHandler);
123+
124+
$result = $argumentManager->prepareArguments([
125+
'hasVariant' => [
126+
'sku' => 'hct-blue',
127+
],
128+
]);
129+
130+
self::assertSame(['sku' => 'hct-blue'], $result['hasVariant']);
131+
}
132+
}

0 commit comments

Comments
 (0)