Passed
Push — trunk ( 325349...f71779 )
by Christian
12:37 queued 16s
created

mapToManyAssociations()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 28
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 15
nc 6
nop 2
dl 0
loc 28
rs 9.2222
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Elasticsearch\Framework;
4
5
use OpenSearchDSL\Query\Compound\BoolQuery;
6
use OpenSearchDSL\Query\FullText\MatchPhrasePrefixQuery;
7
use OpenSearchDSL\Query\FullText\MatchQuery;
8
use OpenSearchDSL\Query\TermLevel\WildcardQuery;
9
use Shopware\Core\Framework\Context;
10
use Shopware\Core\Framework\DataAbstractionLayer\EntityDefinition;
11
use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
12
use Shopware\Core\Framework\Feature;
13
use Shopware\Core\Framework\Log\Package;
14
15
#[Package('core')]
16
abstract class AbstractElasticsearchDefinition
17
{
18
    final public const KEYWORD_FIELD = [
19
        'type' => 'keyword',
20
        'normalizer' => 'sw_lowercase_normalizer',
21
    ];
22
23
    final public const BOOLEAN_FIELD = ['type' => 'boolean'];
24
25
    final public const FLOAT_FIELD = ['type' => 'double'];
26
27
    final public const INT_FIELD = ['type' => 'long'];
28
29
    final public const SEARCH_FIELD = [
30
        'fields' => [
31
            'search' => ['type' => 'text'],
32
            'ngram' => ['type' => 'text', 'analyzer' => 'sw_ngram_analyzer'],
33
        ],
34
    ];
35
36
    abstract public function getEntityDefinition(): EntityDefinition;
37
38
    /**
39
     * @return array{_source: array{includes: string[]}, properties: array<mixed>}
40
     */
41
    abstract public function getMapping(Context $context): array;
42
43
    /**
44
     * @param array<string> $ids
45
     *
46
     * @return array<string, array<string, mixed>>
47
     */
48
    public function fetch(array $ids, Context $context): array
49
    {
50
        return [];
51
    }
52
53
    /**
54
     * @deprecated tag:v6.6.0 - Will become abstract, implementation should implement their own `buildTermQuery`
55
     */
56
    public function buildTermQuery(Context $context, Criteria $criteria): BoolQuery
57
    {
58
        Feature::triggerDeprecationOrThrow(
59
            'ES_MULTILINGUAL_INDEX',
60
            'Will become abstract, implementation should implement their own `buildTermQuery`'
61
        );
62
63
        $bool = new BoolQuery();
64
65
        $term = (string) $criteria->getTerm();
66
67
        $queries = [
68
            new MatchQuery('fullTextBoosted', $term, ['boost' => 10]), // boosted word matches
69
            new MatchQuery('fullText', $term, ['boost' => 5]), // whole word matches
70
            new MatchQuery('fullText', $term, ['fuzziness' => 'auto', 'boost' => 3]), // word matches not exactly =>
71
            new MatchPhrasePrefixQuery('fullText', $term, ['boost' => 1, 'slop' => 5]), // one of the words begins with: "Spachtel" => "Spachtelmasse"
72
            new WildcardQuery('fullText', '*' . mb_strtolower($term) . '*'), // part of a word matches: "masse" => "Spachtelmasse"
73
            new MatchQuery('fullText.ngram', $term),
74
        ];
75
76
        foreach ($queries as $query) {
77
            $bool->add($query, BoolQuery::SHOULD);
78
        }
79
80
        $bool->addParameter('minimum_should_match', 1);
81
82
        return $bool;
83
    }
84
85
    protected function stripText(string $text): string
86
    {
87
        // Remove all html elements to save up space
88
        $text = strip_tags($text);
89
90
        if (mb_strlen($text) >= 32766) {
91
            return mb_substr($text, 0, 32766);
92
        }
93
94
        return $text;
95
    }
96
97
    /**
98
     * @param array<int, array<string, string>> $items
99
     *
100
     * @return array<int|string, mixed>
101
     */
102
    protected function mapTranslatedField(string $field, bool $stripText = true, ...$items): array
103
    {
104
        $value = [];
105
106
        foreach ($items as $item) {
107
            if (empty($item['languageId'])) {
108
                continue;
109
            }
110
            $languageId = $item['languageId'];
111
            $newValue = $item[$field] ?? null;
112
113
            if ($stripText && \is_string($newValue)) {
114
                $newValue = $this->stripText($newValue);
115
            }
116
117
            // if child value is null, it should be inherited from parent
118
            $value[$languageId] = $newValue === null ? ($value[$languageId] ?? '') : $newValue;
119
        }
120
121
        return $value;
122
    }
123
124
    /**
125
     * @param array<int, array{id: string, languageId?: string}> $items
126
     * @param string[] $translatedFields
127
     *
128
     * @return array<int, array<string, array<string, string>>>
129
     */
130
    protected function mapToManyAssociations(array $items, array $translatedFields): array
131
    {
132
        $result = [];
133
134
        foreach ($items as $item) {
135
            if (empty($item['languageId'])) {
136
                continue;
137
            }
138
139
            $result[$item['id']] = $result[$item['id']] ?? array_merge([
140
                'id' => $item['id'],
141
                '_count' => 1,
142
            ], $item);
143
144
            foreach ($translatedFields as $field) {
145
                if (empty($item[$field])) {
146
                    continue;
147
                }
148
149
                if (!\is_array($result[$item['id']][$field])) {
150
                    unset($result[$item['id']][$field]);
151
                }
152
153
                $result[$item['id']][$field][$item['languageId']] = $this->stripText($item[$field]);
154
            }
155
        }
156
157
        return array_values($result);
158
    }
159
160
    /**
161
     * @param array<int, array{id: string, languageId?: string}> $items
162
     * @param string[] $translatedFields
163
     *
164
     * @return array<string, array<string, string>>
165
     */
166
    protected function mapToOneAssociations(array $items, array $translatedFields): array
167
    {
168
        $result = [];
169
170
        foreach ($items as $item) {
171
            if (empty($item['languageId'])) {
172
                continue;
173
            }
174
175
            foreach ($translatedFields as $field) {
176
                if (empty($item[$field])) {
177
                    continue;
178
                }
179
180
                $result[$field][$item['languageId']] = $this->stripText($item[$field]);
181
            }
182
        }
183
184
        return $result;
185
    }
186
187
    /**
188
     * @return array<string, mixed>
189
     */
190
    protected static function getTextFieldConfig(): array
191
    {
192
        return self::KEYWORD_FIELD + self::SEARCH_FIELD;
193
    }
194
}
195