CriteriaParser::getNestedPath()   A
last analyzed

Complexity

Conditions 5
Paths 12

Size

Total Lines 22
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 11
nc 12
nop 2
dl 0
loc 22
rs 9.6111
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Elasticsearch\Framework\DataAbstractionLayer;
4
5
use OpenSearchDSL\Aggregation\AbstractAggregation;
6
use OpenSearchDSL\Aggregation\Bucketing;
7
use OpenSearchDSL\Aggregation\Bucketing\CompositeAggregation;
8
use OpenSearchDSL\Aggregation\Bucketing\NestedAggregation;
9
use OpenSearchDSL\Aggregation\Bucketing\ReverseNestedAggregation;
10
use OpenSearchDSL\Aggregation\Metric;
11
use OpenSearchDSL\Aggregation\Metric\ValueCountAggregation;
12
use OpenSearchDSL\BuilderInterface;
13
use OpenSearchDSL\Query\Compound\BoolQuery;
14
use OpenSearchDSL\Query\Compound\DisMaxQuery;
15
use OpenSearchDSL\Query\FullText\MultiMatchQuery;
16
use OpenSearchDSL\Query\Joining\NestedQuery;
17
use OpenSearchDSL\Query\TermLevel\ExistsQuery;
18
use OpenSearchDSL\Query\TermLevel\PrefixQuery;
19
use OpenSearchDSL\Query\TermLevel\RangeQuery;
20
use OpenSearchDSL\Query\TermLevel\TermQuery;
21
use OpenSearchDSL\Query\TermLevel\TermsQuery;
22
use OpenSearchDSL\Query\TermLevel\WildcardQuery;
23
use OpenSearchDSL\Sort\FieldSort;
24
use Shopware\Core\Checkout\Cart\Price\Struct\CartPrice;
25
use Shopware\Core\Defaults;
26
use Shopware\Core\Framework\Adapter\Storage\AbstractKeyValueStorage;
27
use Shopware\Core\Framework\Context;
28
use Shopware\Core\Framework\DataAbstractionLayer\Dbal\EntityDefinitionQueryHelper;
29
use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
30
use Shopware\Core\Framework\DataAbstractionLayer\Field\AssociationField;
31
use Shopware\Core\Framework\DataAbstractionLayer\Field\BoolField;
32
use Shopware\Core\Framework\DataAbstractionLayer\Field\CustomFields;
33
use Shopware\Core\Framework\DataAbstractionLayer\Field\DateTimeField;
34
use Shopware\Core\Framework\DataAbstractionLayer\Field\Field;
35
use Shopware\Core\Framework\DataAbstractionLayer\Field\FloatField;
36
use Shopware\Core\Framework\DataAbstractionLayer\Field\IntField;
37
use Shopware\Core\Framework\DataAbstractionLayer\Field\PriceField;
38
use Shopware\Core\Framework\DataAbstractionLayer\Field\TranslatedField;
39
use Shopware\Core\Framework\DataAbstractionLayer\Search\Aggregation\Aggregation;
40
use Shopware\Core\Framework\DataAbstractionLayer\Search\Aggregation\Bucket\DateHistogramAggregation;
41
use Shopware\Core\Framework\DataAbstractionLayer\Search\Aggregation\Bucket\FilterAggregation;
42
use Shopware\Core\Framework\DataAbstractionLayer\Search\Aggregation\Bucket\TermsAggregation;
43
use Shopware\Core\Framework\DataAbstractionLayer\Search\Aggregation\Metric\AvgAggregation;
44
use Shopware\Core\Framework\DataAbstractionLayer\Search\Aggregation\Metric\CountAggregation;
45
use Shopware\Core\Framework\DataAbstractionLayer\Search\Aggregation\Metric\EntityAggregation;
46
use Shopware\Core\Framework\DataAbstractionLayer\Search\Aggregation\Metric\MaxAggregation;
47
use Shopware\Core\Framework\DataAbstractionLayer\Search\Aggregation\Metric\MinAggregation;
48
use Shopware\Core\Framework\DataAbstractionLayer\Search\Aggregation\Metric\RangeAggregation;
49
use Shopware\Core\Framework\DataAbstractionLayer\Search\Aggregation\Metric\StatsAggregation;
50
use Shopware\Core\Framework\DataAbstractionLayer\Search\Aggregation\Metric\SumAggregation;
51
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\AndFilter;
52
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\ContainsFilter;
53
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsAnyFilter;
54
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
55
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\Filter;
56
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\MultiFilter;
57
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\NotFilter;
58
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\OrFilter;
59
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\PrefixFilter;
60
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\RangeFilter;
61
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\SingleFieldFilter;
62
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\SuffixFilter;
63
use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\XOrFilter;
64
use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\CountSorting;
65
use Shopware\Core\Framework\DataAbstractionLayer\Search\Sorting\FieldSorting;
66
use Shopware\Core\Framework\Log\Package;
67
use Shopware\Core\Framework\Uuid\Uuid;
68
use Shopware\Core\System\CustomField\CustomFieldService;
69
use Shopware\Elasticsearch\Framework\ElasticsearchDateHistogramAggregation;
70
use Shopware\Elasticsearch\Framework\ElasticsearchHelper;
71
use Shopware\Elasticsearch\Framework\Indexing\ElasticsearchIndexer;
72
use Shopware\Elasticsearch\Sort\CountSort;
73
74
#[Package('core')]
75
class CriteriaParser
76
{
77
    /**
78
     * @internal
79
     */
80
    public function __construct(
81
        private readonly EntityDefinitionQueryHelper $helper,
82
        private readonly CustomFieldService $customFieldService,
83
        private readonly AbstractKeyValueStorage $keyValueStorage
84
    ) {
85
    }
86
87
    public function buildAccessor(EntityDefinition $definition, string $fieldName, Context $context): string
88
    {
89
        $root = $definition->getEntityName();
90
91
        $parts = explode('.', $fieldName);
92
        if ($root === $parts[0]) {
93
            array_shift($parts);
94
        }
95
96
        $field = $this->helper->getField($fieldName, $definition, $root, false);
97
        if ($field instanceof TranslatedField) {
98
            $ordered = [];
99
            foreach ($parts as $part) {
100
                $ordered[] = $part;
101
            }
102
            $parts = $ordered;
103
        }
104
105
        if (!$field instanceof PriceField) {
106
            return implode('.', $parts);
107
        }
108
109
        if (\in_array(end($parts), ['net', 'gross'], true)) {
110
            $taxState = end($parts);
111
            array_pop($parts);
112
        } elseif ($context->getTaxState() === CartPrice::TAX_STATE_GROSS) {
113
            $taxState = 'gross';
114
        } else {
115
            $taxState = 'net';
116
        }
117
118
        $currencyId = $context->getCurrencyId();
119
        if (Uuid::isValid((string) end($parts))) {
120
            $currencyId = end($parts);
121
            array_pop($parts);
122
        }
123
124
        $parts[] = 'c_' . $currencyId;
125
        $parts[] = $taxState;
126
127
        return implode('.', $parts);
128
    }
129
130
    public function parseSorting(FieldSorting $sorting, EntityDefinition $definition, Context $context): FieldSort
131
    {
132
        if ($this->isCheapestPriceField($sorting->getField())) {
133
            return new FieldSort('_script', $sorting->getDirection(), null, [
134
                'type' => 'number',
135
                'script' => [
136
                    'id' => 'cheapest_price',
137
                    'params' => $this->getCheapestPriceParameters($context),
138
                ],
139
            ]);
140
        }
141
142
        if ($this->isCheapestPriceField($sorting->getField(), true)) {
143
            return new FieldSort('_script', $sorting->getDirection(), null, [
144
                'type' => 'number',
145
                'script' => [
146
                    'id' => 'cheapest_price_percentage',
147
                    'params' => ['accessors' => $this->getCheapestPriceAccessors($context, true)],
148
                ],
149
            ]);
150
        }
151
152
        if ($this->keyValueStorage->get(ElasticsearchIndexer::ENABLE_MULTILINGUAL_INDEX_KEY, false)) {
0 ignored issues
show
Deprecated Code introduced by
The constant Shopware\Elasticsearch\F..._MULTILINGUAL_INDEX_KEY has been deprecated: tag:v6.6.0 - reason:blue-green-deployment - will be removed ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

152
        if ($this->keyValueStorage->get(/** @scrutinizer ignore-deprecated */ ElasticsearchIndexer::ENABLE_MULTILINGUAL_INDEX_KEY, false)) {

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
153
            $field = $this->helper->getField($sorting->getField(), $definition, $definition->getEntityName(), false);
154
155
            if ($field instanceof TranslatedField) {
156
                return $this->createTranslatedSorting($definition->getEntityName(), $sorting, $context);
157
            }
158
        }
159
160
        $accessor = $this->buildAccessor($definition, $sorting->getField(), $context);
161
162
        if ($sorting instanceof CountSorting) {
163
            return new CountSort($accessor, $sorting->getDirection());
164
        }
165
166
        return new FieldSort($accessor, $sorting->getDirection());
167
    }
168
169
    public function parseAggregation(Aggregation $aggregation, EntityDefinition $definition, Context $context): ?AbstractAggregation
170
    {
171
        $fieldName = $this->buildAccessor($definition, $aggregation->getField(), $context);
172
173
        $fields = $aggregation->getFields();
174
175
        $path = null;
176
        if (\count($fields) > 0) {
177
            $path = $this->getNestedPath($definition, $fields[0]);
178
        }
179
180
        $esAggregation = $this->createAggregation($aggregation, $fieldName, $definition, $context);
181
182
        if (!$path || $aggregation instanceof FilterAggregation) {
183
            return $esAggregation;
184
        }
185
186
        $nested = new NestedAggregation($aggregation->getName(), $path);
187
        $nested->addAggregation($esAggregation);
188
189
        return $nested;
190
    }
191
192
    public function parseFilter(Filter $filter, EntityDefinition $definition, string $root, Context $context): BuilderInterface
193
    {
194
        return match (true) {
195
            $filter instanceof NotFilter => $this->parseNotFilter($filter, $definition, $root, $context),
196
            $filter instanceof MultiFilter => $this->parseMultiFilter($filter, $definition, $root, $context),
197
            $filter instanceof EqualsFilter => $this->parseEqualsFilter($filter, $definition, $context),
198
            $filter instanceof EqualsAnyFilter => $this->parseEqualsAnyFilter($filter, $definition, $context),
199
            $filter instanceof ContainsFilter => $this->parseContainsFilter($filter, $definition, $context),
200
            $filter instanceof PrefixFilter => $this->parsePrefixFilter($filter, $definition, $context),
201
            $filter instanceof SuffixFilter => $this->parseSuffixFilter($filter, $definition, $context),
202
            $filter instanceof RangeFilter => $this->parseRangeFilter($filter, $definition, $context),
203
            default => throw new \RuntimeException(sprintf('Unsupported filter %s', $filter::class)),
204
        };
205
    }
206
207
    protected function parseFilterAggregation(FilterAggregation $aggregation, EntityDefinition $definition, Context $context): AbstractAggregation
208
    {
209
        if ($aggregation->getAggregation() === null) {
210
            throw new \RuntimeException(sprintf('Filter aggregation %s contains no nested aggregation.', $aggregation->getName()));
211
        }
212
213
        $nested = $this->parseAggregation($aggregation->getAggregation(), $definition, $context);
214
        if ($nested === null) {
215
            throw new \RuntimeException(sprintf('Nested filter aggregation %s can not be parsed.', $aggregation->getName()));
216
        }
217
218
        // when aggregation inside the filter aggregation points to a nested object (e.g. product.properties.id) we have to add all filters
219
        // which points to the same association to the same "nesting" level like the nested aggregation for this association
220
        $path = $nested instanceof NestedAggregation ? $nested->getPath() : null;
0 ignored issues
show
introduced by
$nested is always a sub-type of OpenSearchDSL\Aggregatio...eting\NestedAggregation.
Loading history...
221
        $bool = new BoolQuery();
222
223
        $filters = [];
224
        foreach ($aggregation->getFilter() as $filter) {
225
            $query = $this->parseFilter($filter, $definition, $definition->getEntityName(), $context);
226
227
            if (!$query instanceof NestedQuery) {
228
                $filters[] = new Bucketing\FilterAggregation($aggregation->getName(), $query);
229
230
                continue;
231
            }
232
233
            // same property path as the "real" aggregation
234
            if ($query->getPath() === $path) {
235
                $bool->add($query->getQuery());
236
237
                continue;
238
            }
239
240
            // query points to a nested document property - we have to define that the filter points to this field
241
            $parsed = new NestedAggregation($aggregation->getName(), $query->getPath());
242
243
            // now we can defined a filter which points to the nested field (remove NestedQuery layer)
244
            $filter = new Bucketing\FilterAggregation($aggregation->getName(), $query->getQuery());
245
246
            // afterwards we reset the nesting to allow following filters to point to another nested property
247
            $reverse = new ReverseNestedAggregation($aggregation->getName());
248
249
            $filter->addAggregation($reverse);
250
251
            $parsed->addAggregation($filter);
252
253
            $filters[] = $parsed;
254
        }
255
256
        // nested aggregation should have filters - we have to remap the nesting
257
        $mapped = $nested;
258
        if (\count($bool->getQueries()) > 0 && $nested instanceof NestedAggregation) {
259
            $real = $nested->getAggregation($nested->getName());
260
            if (!$real instanceof AbstractAggregation) {
261
                throw new \RuntimeException(sprintf('Nested filter aggregation %s can not be parsed.', $aggregation->getName()));
262
            }
263
264
            $filter = new Bucketing\FilterAggregation($aggregation->getName(), $bool);
265
            $filter->addAggregation($real);
266
267
            $mapped = new NestedAggregation($aggregation->getName(), $nested->getPath());
268
            $mapped->addAggregation($filter);
269
        }
270
271
        // at this point we have to walk over all filters and create one nested filter for it
272
        $parent = null;
273
        $root = $mapped;
274
        foreach ($filters as $filter) {
275
            if ($parent === null) {
276
                $parent = $filter;
277
                $root = $filter;
278
279
                continue;
280
            }
281
282
            $parent->addAggregation($filter);
283
284
            if (!$filter instanceof NestedAggregation) {
285
                $parent = $filter;
286
287
                continue;
288
            }
289
290
            $filter = $filter->getAggregation($filter->getName());
291
            if (!$filter instanceof AbstractAggregation) {
292
                throw new \RuntimeException('Expected nested+filter+reverse pattern for parsed filters to set next parent correctly');
293
            }
294
295
            $parent = $filter->getAggregation($filter->getName());
296
            if (!$parent instanceof AbstractAggregation) {
297
                throw new \RuntimeException('Expected nested+filter+reverse pattern for parsed filters to set next parent correctly');
298
            }
299
        }
300
301
        // it can happen, that $parent is not defined if the "real" aggregation is a nested and all filters points to the same property
302
        // than we return the following structure:  [nested-agg] + filter-agg + real-agg    ( [] = optional )
303
        if ($parent === null) {
304
            return $root;
305
        }
306
307
        // at this point we have some other filters which point to another nested object as the "real" aggregation
308
        // than we return the following structure:  [nested-agg] + filter-agg + [reverse-nested-agg] + [nested-agg] + real-agg   ( [] = optional )
309
        $parent->addAggregation($mapped);
310
311
        return $root;
312
    }
313
314
    protected function parseTermsAggregation(TermsAggregation $aggregation, string $fieldName, EntityDefinition $definition, Context $context): AbstractAggregation
315
    {
316
        if ($aggregation->getSorting() === null) {
317
            $terms = new Bucketing\TermsAggregation($aggregation->getName(), $fieldName);
318
319
            if ($nested = $aggregation->getAggregation()) {
320
                $terms->addAggregation(
321
                    $this->parseNestedAggregation($nested, $definition, $context)
322
                );
323
            }
324
325
            // set default size to 10.000 => max for default configuration
326
            $terms->addParameter('size', ElasticsearchHelper::MAX_SIZE_VALUE);
327
328
            if ($aggregation->getLimit()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $aggregation->getLimit() of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
329
                $terms->addParameter('size', (string) $aggregation->getLimit());
330
            }
331
332
            return $terms;
333
        }
334
335
        $composite = new CompositeAggregation($aggregation->getName());
336
337
        $accessor = $this->buildAccessor($definition, $aggregation->getSorting()->getField(), $context);
338
339
        $sorting = new Bucketing\TermsAggregation($aggregation->getName() . '.sorting', $accessor);
340
        $sorting->addParameter('order', $aggregation->getSorting()->getDirection());
341
        $composite->addSource($sorting);
342
343
        $terms = new Bucketing\TermsAggregation($aggregation->getName() . '.key', $fieldName);
344
        $composite->addSource($terms);
345
346
        if ($nested = $aggregation->getAggregation()) {
347
            $composite->addAggregation(
348
                $this->parseNestedAggregation($nested, $definition, $context)
349
            );
350
        }
351
352
        // set default size to 10.000 => max for default configuration
353
        $composite->addParameter('size', ElasticsearchHelper::MAX_SIZE_VALUE);
354
355
        if ($aggregation->getLimit()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $aggregation->getLimit() of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
356
            $composite->addParameter('size', (string) $aggregation->getLimit());
357
        }
358
359
        return $composite;
360
    }
361
362
    protected function parseStatsAggregation(StatsAggregation $aggregation, string $fieldName, Context $context): Metric\StatsAggregation
363
    {
364
        if ($this->isCheapestPriceField($aggregation->getField())) {
365
            return new Metric\StatsAggregation($aggregation->getName(), null, [
366
                'id' => 'cheapest_price',
367
                'params' => $this->getCheapestPriceParameters($context),
368
            ]);
369
        }
370
371
        if ($this->isCheapestPriceField($aggregation->getField(), true)) {
372
            return new Metric\StatsAggregation($aggregation->getName(), null, [
373
                'id' => 'cheapest_price_percentage',
374
                'params' => ['accessors' => $this->getCheapestPriceAccessors($context, true)],
375
            ]);
376
        }
377
378
        return new Metric\StatsAggregation($aggregation->getName(), $fieldName);
379
    }
380
381
    protected function parseEntityAggregation(EntityAggregation $aggregation, string $fieldName): Bucketing\TermsAggregation
382
    {
383
        $bucketingAggregation = new Bucketing\TermsAggregation($aggregation->getName(), $fieldName);
384
385
        $bucketingAggregation->addParameter('size', ElasticsearchHelper::MAX_SIZE_VALUE);
386
387
        return $bucketingAggregation;
388
    }
389
390
    protected function parseDateHistogramAggregation(DateHistogramAggregation $aggregation, string $fieldName, EntityDefinition $definition, Context $context): CompositeAggregation
391
    {
392
        $composite = new CompositeAggregation($aggregation->getName());
393
394
        if ($fieldSorting = $aggregation->getSorting()) {
395
            $accessor = $this->buildAccessor($definition, $fieldSorting->getField(), $context);
396
397
            $sorting = new Bucketing\TermsAggregation($aggregation->getName() . '.sorting', $accessor);
398
            $sorting->addParameter('order', $fieldSorting->getDirection());
399
400
            $composite->addSource($sorting);
401
        }
402
403
        $histogram = new ElasticsearchDateHistogramAggregation(
404
            $aggregation->getName() . '.key',
405
            $fieldName,
406
            $aggregation->getInterval(),
407
            'yyyy-MM-dd HH:mm:ss'
408
        );
409
410
        if ($aggregation->getTimeZone()) {
411
            $histogram->addParameter('time_zone', $aggregation->getTimeZone());
412
        }
413
414
        $composite->addSource($histogram);
415
416
        if ($nested = $aggregation->getAggregation()) {
417
            $composite->addAggregation(
418
                $this->parseNestedAggregation($nested, $definition, $context)
419
            );
420
        }
421
422
        return $composite;
423
    }
424
425
    protected function parseRangeAggregation(RangeAggregation $aggregation, string $fieldName): Bucketing\RangeAggregation
426
    {
427
        return new Bucketing\RangeAggregation(
428
            $aggregation->getName(),
429
            $fieldName,
430
            $aggregation->getRanges()
431
        );
432
    }
433
434
    /**
435
     * @return array<string, mixed>
436
     */
437
    private function getCheapestPriceParameters(Context $context): array
438
    {
439
        return [
440
            'accessors' => $this->getCheapestPriceAccessors($context),
441
            'decimals' => 10 ** $context->getRounding()->getDecimals(),
442
            'round' => $this->useCashRounding($context),
443
            'multiplier' => 100 / ($context->getRounding()->getInterval() * 100),
444
        ];
445
    }
446
447
    private function useCashRounding(Context $context): bool
448
    {
449
        if ($context->getRounding()->getDecimals() !== 2) {
450
            return false;
451
        }
452
453
        if ($context->getTaxState() === CartPrice::TAX_STATE_GROSS) {
454
            return true;
455
        }
456
457
        return $context->getRounding()->roundForNet();
458
    }
459
460
    /**
461
     * @return array<int, array<string, string|float>>
462
     */
463
    private function getCheapestPriceAccessors(Context $context, bool $percentage = false): array
464
    {
465
        $accessors = [];
466
467
        $tax = $context->getTaxState() === CartPrice::TAX_STATE_GROSS ? 'gross' : 'net';
468
469
        $ruleIds = array_merge($context->getRuleIds(), ['default']);
470
471
        foreach ($ruleIds as $ruleId) {
472
            $key = implode('_', [
473
                'cheapest_price',
474
                'rule' . $ruleId,
475
                'currency' . $context->getCurrencyId(),
476
                $tax,
477
            ]);
478
479
            if ($percentage) {
480
                $key .= '_percentage';
481
            }
482
483
            $accessors[] = ['key' => $key, 'factor' => 1];
484
485
            if ($context->getCurrencyId() === Defaults::CURRENCY) {
486
                continue;
487
            }
488
489
            $key = implode('_', [
490
                'cheapest_price',
491
                'rule' . $ruleId,
492
                'currency' . Defaults::CURRENCY,
493
                $tax,
494
            ]);
495
496
            if ($percentage) {
497
                $key .= '_percentage';
498
            }
499
500
            $accessors[] = ['key' => $key, 'factor' => $context->getCurrencyFactor()];
501
        }
502
503
        return $accessors;
504
    }
505
506
    private function parseNestedAggregation(Aggregation $aggregation, EntityDefinition $definition, Context $context): AbstractAggregation
507
    {
508
        $fieldName = $this->buildAccessor($definition, $aggregation->getField(), $context);
509
510
        return $this->createAggregation($aggregation, $fieldName, $definition, $context);
511
    }
512
513
    private function createAggregation(Aggregation $aggregation, string $fieldName, EntityDefinition $definition, Context $context): AbstractAggregation
514
    {
515
        if ($this->keyValueStorage->get(ElasticsearchIndexer::ENABLE_MULTILINGUAL_INDEX_KEY, false)) {
0 ignored issues
show
Deprecated Code introduced by
The constant Shopware\Elasticsearch\F..._MULTILINGUAL_INDEX_KEY has been deprecated: tag:v6.6.0 - reason:blue-green-deployment - will be removed ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

515
        if ($this->keyValueStorage->get(/** @scrutinizer ignore-deprecated */ ElasticsearchIndexer::ENABLE_MULTILINGUAL_INDEX_KEY, false)) {

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
516
            $field = $this->getField($definition, $fieldName);
517
518
            if ($field instanceof TranslatedField) {
519
                $fieldName = $this->getTranslatedFieldName($fieldName, $context->getLanguageId());
520
            }
521
        }
522
523
        return match (true) {
524
            $aggregation instanceof StatsAggregation => $this->parseStatsAggregation($aggregation, $fieldName, $context),
525
            $aggregation instanceof AvgAggregation => new Metric\AvgAggregation($aggregation->getName(), $fieldName),
526
            $aggregation instanceof EntityAggregation => $this->parseEntityAggregation($aggregation, $fieldName),
527
            $aggregation instanceof MaxAggregation => new Metric\MaxAggregation($aggregation->getName(), $fieldName),
528
            $aggregation instanceof MinAggregation => new Metric\MinAggregation($aggregation->getName(), $fieldName),
529
            $aggregation instanceof SumAggregation => new Metric\SumAggregation($aggregation->getName(), $fieldName),
530
            $aggregation instanceof CountAggregation => new ValueCountAggregation($aggregation->getName(), $fieldName),
531
            $aggregation instanceof FilterAggregation => $this->parseFilterAggregation($aggregation, $definition, $context),
532
            $aggregation instanceof TermsAggregation => $this->parseTermsAggregation($aggregation, $fieldName, $definition, $context),
0 ignored issues
show
Bug introduced by
$aggregation of type Shopware\Core\Framework\...ucket\FilterAggregation is incompatible with the type Shopware\Core\Framework\...Bucket\TermsAggregation expected by parameter $aggregation of Shopware\Elasticsearch\F...parseTermsAggregation(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

532
            $aggregation instanceof TermsAggregation => $this->parseTermsAggregation(/** @scrutinizer ignore-type */ $aggregation, $fieldName, $definition, $context),
Loading history...
533
            $aggregation instanceof DateHistogramAggregation => $this->parseDateHistogramAggregation($aggregation, $fieldName, $definition, $context),
0 ignored issues
show
Bug introduced by
$aggregation of type Shopware\Core\Framework\...ucket\FilterAggregation is incompatible with the type Shopware\Core\Framework\...ateHistogramAggregation expected by parameter $aggregation of Shopware\Elasticsearch\F...eHistogramAggregation(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

533
            $aggregation instanceof DateHistogramAggregation => $this->parseDateHistogramAggregation(/** @scrutinizer ignore-type */ $aggregation, $fieldName, $definition, $context),
Loading history...
534
            $aggregation instanceof RangeAggregation => $this->parseRangeAggregation($aggregation, $fieldName),
0 ignored issues
show
Bug introduced by
$aggregation of type Shopware\Core\Framework\...ucket\FilterAggregation is incompatible with the type Shopware\Core\Framework\...Metric\RangeAggregation expected by parameter $aggregation of Shopware\Elasticsearch\F...parseRangeAggregation(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

534
            $aggregation instanceof RangeAggregation => $this->parseRangeAggregation(/** @scrutinizer ignore-type */ $aggregation, $fieldName),
Loading history...
535
            default => throw new \RuntimeException(sprintf('Provided aggregation of class %s not supported', $aggregation::class)),
536
        };
537
    }
538
539
    private function parseEqualsFilter(EqualsFilter $filter, EntityDefinition $definition, Context $context): BuilderInterface
540
    {
541
        if ($this->keyValueStorage->get(ElasticsearchIndexer::ENABLE_MULTILINGUAL_INDEX_KEY, false)) {
0 ignored issues
show
Deprecated Code introduced by
The constant Shopware\Elasticsearch\F..._MULTILINGUAL_INDEX_KEY has been deprecated: tag:v6.6.0 - reason:blue-green-deployment - will be removed ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

541
        if ($this->keyValueStorage->get(/** @scrutinizer ignore-deprecated */ ElasticsearchIndexer::ENABLE_MULTILINGUAL_INDEX_KEY, false)) {

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
542
            $fieldName = $this->buildAccessor($definition, $filter->getField(), $context);
543
544
            $field = $this->getField($definition, $fieldName);
545
546
            if ($filter->getValue() === null) {
547
                $query = new BoolQuery();
548
549
                if ($field instanceof TranslatedField) {
550
                    foreach ($context->getLanguageIdChain() as $languageId) {
551
                        $query->add(new ExistsQuery(sprintf('%s.%s', $fieldName, $languageId)), BoolQuery::MUST_NOT);
552
                    }
553
                } else {
554
                    $query->add(new ExistsQuery($fieldName), BoolQuery::MUST_NOT);
555
                }
556
557
                return $this->createNestedQuery($query, $definition, $filter->getField());
558
            }
559
560
            $value = $this->parseValue($definition, $filter, $filter->getValue());
561
            $query = new TermQuery($fieldName, $value);
562
563
            if ($field instanceof TranslatedField) {
564
                $multiMatchFields = [];
565
566
                foreach ($context->getLanguageIdChain() as $languageId) {
567
                    $multiMatchFields[] = $this->getTranslatedFieldName($fieldName, $languageId);
568
                }
569
570
                $query = new MultiMatchQuery($multiMatchFields, $value, [
571
                    'type' => 'best_fields',
572
                ]);
573
            }
574
575
            return $this->createNestedQuery($query, $definition, $filter->getField());
576
        }
577
578
        $fieldName = $this->buildAccessor($definition, $filter->getField(), $context);
579
580
        if ($filter->getValue() === null) {
581
            $query = new BoolQuery();
582
            $query->add(new ExistsQuery($fieldName), BoolQuery::MUST_NOT);
583
584
            return $this->createNestedQuery($query, $definition, $filter->getField());
585
        }
586
587
        $value = $this->parseValue($definition, $filter, $filter->getValue());
588
589
        $query = new TermQuery($fieldName, $value);
590
591
        return $this->createNestedQuery($query, $definition, $filter->getField());
592
    }
593
594
    private function parseEqualsAnyFilter(EqualsAnyFilter $filter, EntityDefinition $definition, Context $context): BuilderInterface
595
    {
596
        $fieldName = $this->buildAccessor($definition, $filter->getField(), $context);
597
598
        if ($this->keyValueStorage->get(ElasticsearchIndexer::ENABLE_MULTILINGUAL_INDEX_KEY, false)) {
0 ignored issues
show
Deprecated Code introduced by
The constant Shopware\Elasticsearch\F..._MULTILINGUAL_INDEX_KEY has been deprecated: tag:v6.6.0 - reason:blue-green-deployment - will be removed ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

598
        if ($this->keyValueStorage->get(/** @scrutinizer ignore-deprecated */ ElasticsearchIndexer::ENABLE_MULTILINGUAL_INDEX_KEY, false)) {

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
599
            $field = $this->getField($definition, $fieldName);
600
601
            $value = $this->parseValue($definition, $filter, \array_values($filter->getValue()));
602
603
            $query = new TermsQuery($fieldName, $value);
604
605
            if ($field instanceof TranslatedField) {
606
                $query = new DisMaxQuery();
607
                foreach ($context->getLanguageIdChain() as $languageId) {
608
                    $accessor = $this->getTranslatedFieldName($fieldName, $languageId);
609
                    $query->addQuery(new TermsQuery($accessor, $value));
610
                }
611
            }
612
613
            return $this->createNestedQuery(
614
                $query,
615
                $definition,
616
                $filter->getField()
617
            );
618
        }
619
620
        $value = $this->parseValue($definition, $filter, \array_values($filter->getValue()));
621
622
        return $this->createNestedQuery(
623
            new TermsQuery($fieldName, $value),
624
            $definition,
625
            $filter->getField()
626
        );
627
    }
628
629
    private function parseContainsFilter(ContainsFilter $filter, EntityDefinition $definition, Context $context): BuilderInterface
630
    {
631
        $accessor = $this->buildAccessor($definition, $filter->getField(), $context);
632
633
        /** @var string $value */
634
        $value = $filter->getValue();
635
636
        if ($this->keyValueStorage->get(ElasticsearchIndexer::ENABLE_MULTILINGUAL_INDEX_KEY, false)) {
0 ignored issues
show
Deprecated Code introduced by
The constant Shopware\Elasticsearch\F..._MULTILINGUAL_INDEX_KEY has been deprecated: tag:v6.6.0 - reason:blue-green-deployment - will be removed ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

636
        if ($this->keyValueStorage->get(/** @scrutinizer ignore-deprecated */ ElasticsearchIndexer::ENABLE_MULTILINGUAL_INDEX_KEY, false)) {

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
637
            $field = $this->getField($definition, $filter->getField());
638
639
            $query = new WildcardQuery($accessor, '*' . $value . '*');
640
641
            if ($field instanceof TranslatedField) {
642
                $query = new DisMaxQuery();
643
                foreach ($context->getLanguageIdChain() as $languageId) {
644
                    $fieldName = $this->getTranslatedFieldName($accessor, $languageId);
645
                    $query->addQuery(new WildcardQuery($fieldName, '*' . $value . '*'));
646
                }
647
            }
648
649
            return $this->createNestedQuery(
650
                $query,
651
                $definition,
652
                $filter->getField()
653
            );
654
        }
655
656
        return $this->createNestedQuery(
657
            new WildcardQuery($accessor, '*' . $value . '*'),
658
            $definition,
659
            $filter->getField()
660
        );
661
    }
662
663
    private function parsePrefixFilter(PrefixFilter $filter, EntityDefinition $definition, Context $context): BuilderInterface
664
    {
665
        $accessor = $this->buildAccessor($definition, $filter->getField(), $context);
666
667
        $value = $filter->getValue();
668
669
        if ($this->keyValueStorage->get(ElasticsearchIndexer::ENABLE_MULTILINGUAL_INDEX_KEY, false)) {
0 ignored issues
show
Deprecated Code introduced by
The constant Shopware\Elasticsearch\F..._MULTILINGUAL_INDEX_KEY has been deprecated: tag:v6.6.0 - reason:blue-green-deployment - will be removed ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

669
        if ($this->keyValueStorage->get(/** @scrutinizer ignore-deprecated */ ElasticsearchIndexer::ENABLE_MULTILINGUAL_INDEX_KEY, false)) {

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
670
            $field = $this->getField($definition, $filter->getField());
671
672
            $query = new PrefixQuery($accessor, $value);
673
674
            if ($field instanceof TranslatedField) {
675
                $multiMatchFields = [];
676
677
                foreach ($context->getLanguageIdChain() as $languageId) {
678
                    $multiMatchFields[] = $this->getTranslatedFieldName($accessor, $languageId) . '.search';
679
                }
680
681
                $query = new MultiMatchQuery($multiMatchFields, $value, [
682
                    'type' => 'phrase_prefix',
683
                    'slop' => 5,
684
                ]);
685
            }
686
687
            return $this->createNestedQuery(
688
                $query,
689
                $definition,
690
                $filter->getField()
691
            );
692
        }
693
694
        return $this->createNestedQuery(
695
            new PrefixQuery($accessor, $value),
696
            $definition,
697
            $filter->getField()
698
        );
699
    }
700
701
    private function parseSuffixFilter(SuffixFilter $filter, EntityDefinition $definition, Context $context): BuilderInterface
702
    {
703
        $accessor = $this->buildAccessor($definition, $filter->getField(), $context);
704
705
        $value = $filter->getValue();
706
707
        if ($this->keyValueStorage->get(ElasticsearchIndexer::ENABLE_MULTILINGUAL_INDEX_KEY, false)) {
0 ignored issues
show
Deprecated Code introduced by
The constant Shopware\Elasticsearch\F..._MULTILINGUAL_INDEX_KEY has been deprecated: tag:v6.6.0 - reason:blue-green-deployment - will be removed ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

707
        if ($this->keyValueStorage->get(/** @scrutinizer ignore-deprecated */ ElasticsearchIndexer::ENABLE_MULTILINGUAL_INDEX_KEY, false)) {

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
708
            $field = $this->getField($definition, $filter->getField());
709
710
            $query = new WildcardQuery($accessor, '*' . $value);
711
712
            if ($field instanceof TranslatedField) {
713
                $query = new DisMaxQuery();
714
                foreach ($context->getLanguageIdChain() as $languageId) {
715
                    $fieldName = $this->getTranslatedFieldName($accessor, $languageId);
716
                    $query->addQuery(new WildcardQuery($fieldName, '*' . $value));
717
                }
718
            }
719
720
            return $this->createNestedQuery(
721
                $query,
722
                $definition,
723
                $filter->getField()
724
            );
725
        }
726
727
        return $this->createNestedQuery(
728
            new WildcardQuery($accessor, '*' . $value),
729
            $definition,
730
            $filter->getField()
731
        );
732
    }
733
734
    private function parseRangeFilter(RangeFilter $filter, EntityDefinition $definition, Context $context): BuilderInterface
735
    {
736
        if ($this->isCheapestPriceField($filter->getField())) {
737
            return new ScriptIdQuery('cheapest_price_filter', [
738
                'params' => array_merge(
739
                    $this->getRangeParameters($filter),
740
                    $this->getCheapestPriceParameters($context)
741
                ),
742
            ]);
743
        }
744
745
        if ($this->isCheapestPriceField($filter->getField(), true)) {
746
            return new ScriptIdQuery('cheapest_price_percentage_filter', [
747
                'params' => array_merge(
748
                    $this->getRangeParameters($filter),
749
                    ['accessors' => $this->getCheapestPriceAccessors($context, true)]
750
                ),
751
            ]);
752
        }
753
754
        $accessor = $this->buildAccessor($definition, $filter->getField(), $context);
755
756
        if ($this->keyValueStorage->get(ElasticsearchIndexer::ENABLE_MULTILINGUAL_INDEX_KEY, false)) {
0 ignored issues
show
Deprecated Code introduced by
The constant Shopware\Elasticsearch\F..._MULTILINGUAL_INDEX_KEY has been deprecated: tag:v6.6.0 - reason:blue-green-deployment - will be removed ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

756
        if ($this->keyValueStorage->get(/** @scrutinizer ignore-deprecated */ ElasticsearchIndexer::ENABLE_MULTILINGUAL_INDEX_KEY, false)) {

This class constant has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the constant will be removed from the class and what other constant to use instead.

Loading history...
757
            $field = $this->getField($definition, $filter->getField());
758
759
            $value = $this->parseValue($definition, $filter, $filter->getParameters());
760
            $query = new RangeQuery($accessor, $value);
761
762
            if ($field instanceof TranslatedField) {
763
                $query = new DisMaxQuery();
764
                foreach ($context->getLanguageIdChain() as $languageId) {
765
                    $fieldName = $this->getTranslatedFieldName($accessor, $languageId);
766
                    $query->addQuery(new RangeQuery($fieldName, $value));
767
                }
768
            }
769
770
            return $this->createNestedQuery(
771
                $query,
772
                $definition,
773
                $filter->getField()
774
            );
775
        }
776
777
        return $this->createNestedQuery(
778
            new RangeQuery($accessor, $this->parseValue($definition, $filter, $filter->getParameters())),
779
            $definition,
780
            $filter->getField()
781
        );
782
    }
783
784
    private function isCheapestPriceField(string $field, bool $percentage = false): bool
785
    {
786
        if ($percentage) {
787
            $haystack = ['product.cheapestPrice.percentage', 'cheapestPrice.percentage'];
788
        } else {
789
            $haystack = ['product.cheapestPrice', 'cheapestPrice'];
790
        }
791
792
        return \in_array($field, $haystack, true);
793
    }
794
795
    /**
796
     * @return array<string, float>
797
     */
798
    private function getRangeParameters(RangeFilter $filter): array
799
    {
800
        $params = [];
801
        foreach ($filter->getParameters() as $key => $value) {
802
            $params[$key] = (float) $value;
803
        }
804
805
        return $params;
806
    }
807
808
    private function parseNotFilter(NotFilter $filter, EntityDefinition $definition, string $root, Context $context): BuilderInterface
809
    {
810
        $bool = new BoolQuery();
811
        if (\count($filter->getQueries()) === 0) {
812
            return $bool;
813
        }
814
815
        if (\count($filter->getQueries()) === 1) {
816
            $bool->add(
817
                $this->parseFilter($filter->getQueries()[0], $definition, $root, $context),
818
                BoolQuery::MUST_NOT
819
            );
820
821
            return $bool;
822
        }
823
824
        $multiFilter = match ($filter->getOperator()) {
825
            MultiFilter::CONNECTION_OR => new OrFilter(),
826
            MultiFilter::CONNECTION_XOR => new XOrFilter(),
827
            default => new AndFilter(),
828
        };
829
830
        foreach ($filter->getQueries() as $query) {
831
            $multiFilter->addQuery($query);
832
        }
833
834
        $bool->add(
835
            $this->parseFilter($multiFilter, $definition, $root, $context),
836
            BoolQuery::MUST_NOT
837
        );
838
839
        return $bool;
840
    }
841
842
    private function parseMultiFilter(MultiFilter $filter, EntityDefinition $definition, string $root, Context $context): BuilderInterface
843
    {
844
        return match ($filter->getOperator()) {
845
            MultiFilter::CONNECTION_OR => $this->parseOrMultiFilter($filter, $definition, $root, $context),
846
            MultiFilter::CONNECTION_AND => $this->parseAndMultiFilter($filter, $definition, $root, $context),
847
            MultiFilter::CONNECTION_XOR => $this->parseXorMultiFilter($filter, $definition, $root, $context),
848
            default => throw new \InvalidArgumentException('Operator ' . $filter->getOperator() . ' not allowed'),
849
        };
850
    }
851
852
    private function parseAndMultiFilter(MultiFilter $filter, EntityDefinition $definition, string $root, Context $context): BuilderInterface
853
    {
854
        $grouped = [];
855
        $bool = new BoolQuery();
856
857
        foreach ($filter->getQueries() as $nested) {
858
            $query = $this->parseFilter($nested, $definition, $root, $context);
859
860
            if (!$query instanceof NestedQuery) {
861
                $bool->add($query, BoolQuery::MUST);
862
863
                continue;
864
            }
865
866
            if (!\array_key_exists($query->getPath(), $grouped)) {
867
                $grouped[$query->getPath()] = new BoolQuery();
868
                $bool->add(new NestedQuery($query->getPath(), $grouped[$query->getPath()]));
869
            }
870
871
            $grouped[$query->getPath()]->add($query->getQuery());
872
        }
873
874
        return $bool;
875
    }
876
877
    private function parseOrMultiFilter(MultiFilter $filter, EntityDefinition $definition, string $root, Context $context): BuilderInterface
878
    {
879
        $bool = new BoolQuery();
880
881
        foreach ($filter->getQueries() as $nested) {
882
            $bool->add(
883
                $this->parseFilter($nested, $definition, $root, $context),
884
                BoolQuery::SHOULD
885
            );
886
        }
887
888
        return $bool;
889
    }
890
891
    private function parseXorMultiFilter(MultiFilter $filter, EntityDefinition $definition, string $root, Context $context): BuilderInterface
892
    {
893
        $bool = new BoolQuery();
894
895
        foreach ($filter->getQueries() as $nested) {
896
            $xorQuery = new BoolQuery();
897
            foreach ($filter->getQueries() as $mustNot) {
898
                if ($nested === $mustNot) {
899
                    $xorQuery->add($this->parseFilter($nested, $definition, $root, $context), BoolQuery::MUST);
900
901
                    continue;
902
                }
903
904
                $xorQuery->add($this->parseFilter($mustNot, $definition, $root, $context), BoolQuery::MUST_NOT);
905
            }
906
907
            $bool->add(
908
                $xorQuery,
909
                BoolQuery::SHOULD
910
            );
911
        }
912
913
        return $bool;
914
    }
915
916
    private function createNestedQuery(BuilderInterface $query, EntityDefinition $definition, string $field): BuilderInterface
917
    {
918
        $path = $this->getNestedPath($definition, $field);
919
920
        if ($path) {
921
            return new NestedQuery($path, $query);
922
        }
923
924
        return $query;
925
    }
926
927
    private function getField(EntityDefinition $definition, string $fieldName): ?Field
928
    {
929
        $root = $definition->getEntityName();
930
931
        $parts = explode('.', $fieldName);
932
        if ($root === $parts[0]) {
933
            array_shift($parts);
934
        }
935
936
        return $this->helper->getField($fieldName, $definition, $root, false);
937
    }
938
939
    private function getNestedPath(EntityDefinition $definition, string $accessor): ?string
940
    {
941
        if (mb_strpos($accessor, $definition->getEntityName() . '.') === false) {
942
            $accessor = $definition->getEntityName() . '.' . $accessor;
943
        }
944
945
        $fields = EntityDefinitionQueryHelper::getFieldsOfAccessor($definition, $accessor);
946
947
        $path = [];
948
        foreach ($fields as $field) {
949
            if (!$field instanceof AssociationField) {
950
                break;
951
            }
952
953
            $path[] = $field->getPropertyName();
954
        }
955
956
        if (empty($path)) {
957
            return null;
958
        }
959
960
        return implode('.', $path);
961
    }
962
963
    private function parseValue(EntityDefinition $definition, SingleFieldFilter $filter, mixed $value): mixed
964
    {
965
        $field = $this->getField($definition, $filter->getField());
966
967
        if ($field instanceof TranslatedField) {
968
            $field = EntityDefinitionQueryHelper::getTranslatedField($definition, $field);
969
        }
970
971
        if ($field instanceof CustomFields) {
972
            $accessor = \explode('.', $filter->getField());
973
            $last = \array_pop($accessor);
974
975
            $temp = $this->customFieldService->getCustomField($last);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $temp is correct as $this->customFieldService->getCustomField($last) targeting Shopware\Core\System\Cus...rvice::getCustomField() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
976
977
            $field = $temp ?? $field;
978
        }
979
980
        if ($field instanceof BoolField) {
981
            return match (true) {
982
                $value === null => null,
983
                \is_array($value) => \array_map(fn ($value) => (bool) $value, $value),
984
                default => (bool) $value,
985
            };
986
        }
987
988
        if ($field instanceof DateTimeField) {
989
            return match (true) {
990
                $value === null => null,
991
                \is_array($value) => \array_map(fn ($value) => (new \DateTime($value))->format('Y-m-d H:i:s.000'), $value),
992
                default => (new \DateTime($value))->format('Y-m-d H:i:s.000'),
993
            };
994
        }
995
996
        if ($field instanceof FloatField) {
997
            return match (true) {
998
                $value === null => null,
999
                \is_array($value) => \array_map(fn ($value) => (float) $value, $value),
1000
                default => (float) $value,
1001
            };
1002
        }
1003
1004
        if ($field instanceof IntField) {
1005
            return match (true) {
1006
                $value === null => null,
1007
                \is_array($value) => \array_map(fn ($value) => (int) $value, $value),
1008
                default => (int) $value,
1009
            };
1010
        }
1011
1012
        return $value;
1013
    }
1014
1015
    private function createTranslatedSorting(string $root, FieldSorting $sorting, Context $context): FieldSort
1016
    {
1017
        $parts = explode('.', $sorting->getField());
1018
        if ($root === $parts[0]) {
1019
            array_shift($parts);
1020
        }
1021
1022
        if ($parts[0] === 'customFields') {
1023
            $customField = $this->customFieldService->getCustomField($parts[1]);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $customField is correct as $this->customFieldServic...tCustomField($parts[1]) targeting Shopware\Core\System\Cus...rvice::getCustomField() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
1024
1025
            if ($customField instanceof IntField || $customField instanceof FloatField) {
1026
                return new FieldSort('_script', $sorting->getDirection(), null, [
1027
                    'type' => 'number',
1028
                    'script' => [
1029
                        'id' => 'numeric_translated_field_sorting',
1030
                        'params' => [
1031
                            'field' => 'customFields',
1032
                            'languages' => $context->getLanguageIdChain(),
1033
                            'suffix' => $parts[1] ?? '',
1034
                            'order' => strtolower($sorting->getDirection()),
1035
                        ],
1036
                    ],
1037
                ]);
1038
            }
1039
1040
            return new FieldSort('_script', $sorting->getDirection(), null, [
1041
                'type' => 'string',
1042
                'script' => [
1043
                    'id' => 'translated_field_sorting',
1044
                    'params' => [
1045
                        'field' => 'customFields',
1046
                        'languages' => $context->getLanguageIdChain(),
1047
                        'suffix' => $parts[1] ?? '',
1048
                    ],
1049
                ],
1050
            ]);
1051
        }
1052
1053
        return new FieldSort('_script', $sorting->getDirection(), null, [
1054
            'type' => 'string',
1055
            'script' => [
1056
                'id' => 'translated_field_sorting',
1057
                'params' => [
1058
                    'field' => implode('.', $parts),
1059
                    'languages' => $context->getLanguageIdChain(),
1060
                ],
1061
            ],
1062
        ]);
1063
    }
1064
1065
    private function getTranslatedFieldName(string $accessor, string $languageId): string
1066
    {
1067
        $parts = explode('.', $accessor);
1068
1069
        if ($parts[0] !== 'customFields') {
1070
            return sprintf('%s.%s', $accessor, $languageId);
1071
        }
1072
1073
        return sprintf('%s.%s.%s', $parts[0], $languageId, $parts[1]);
1074
    }
1075
}
1076