Passed
Push — trunk ( ff423b...970276 )
by Christian
13:19 queued 19s
created

ElasticsearchFieldMapper::translated()   B

Complexity

Conditions 7
Paths 9

Size

Total Lines 30
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 14
c 1
b 0
f 0
nc 9
nop 4
dl 0
loc 30
rs 8.8333
1
<?php declare(strict_types=1);
2
3
namespace Shopware\Elasticsearch\Framework;
4
5
use Doctrine\DBAL\Exception;
6
use Shopware\Core\Defaults;
7
use Shopware\Core\Framework\Context;
8
use Shopware\Core\Framework\Log\Package;
9
use Shopware\Core\System\CustomField\CustomFieldTypes;
10
11
/**
12
 * @final
13
 */
14
#[Package('buyers-experience')]
15
class ElasticsearchFieldMapper
16
{
17
    /**
18
     * @internal
19
     */
20
    public function __construct(
21
        private readonly ElasticsearchIndexingUtils $indexingUtils
22
    ) {
23
    }
24
25
    /**
26
     * Maps an array of items to a language-specific array of translated values for a given field.
27
     *
28
     * This method is typically used in the context of product definitions, where each item represents a database record to be indexed in Elasticsearch.
29
     *
30
     * @param string $field                                       The field to be translated.
31
     * @param array<int|string, array<string, string|null>> $items            An array of items with language information.
32
     * @param array<int|string, array<string, string|null>> $fallbackItems    An array of fallback items to inherit values from if the child item has null values.
33
     * @param bool $stripText                                     (Optional) Indicates whether to strip text values.
34
     *
35
     * @return array<string, string|null>                         An array where language IDs are keys and translated values are values.
36
     *
37
     * @example
38
     *
39
     *  ```php
40
     *  $items = [['name' => 'foo', 'languageId' => 'de-DE'], ['name' => null, 'languageId' => 'en-GB']];
41
     *  // $fallbackItems is used for inheritance of values from parent to child if child value is null
42
     *  $fallbackItems = [['name' => 'foo-baz', 'languageId' => 'de-DE'], ['name' => 'bar', 'languageId' => 'en-GB'], ['name' => 'baz', 'languageId' => 'vi-VN'];
43
     *
44
     *  $esValue = ElasticsearchIndexingHelper::mapTranslatedField('name', $items, $fallbackItems);
45
     *  // ['de-DE' => 'foo', 'en-GB' => 'bar', 'vi-VN' => 'baz']
46
     *  ```
47
     */
48
    public static function translated(string $field, array $items, array $fallbackItems = [], bool $stripText = true): array
49
    {
50
        $value = [];
51
        $fallbackValue = [];
52
53
        // make sure every languages from parent also indexed
54
        $items = array_merge($fallbackItems, $items);
55
56
        foreach ($fallbackItems as $item) {
57
            if (empty($item['languageId'])) {
58
                continue;
59
            }
60
61
            $fallbackValue[$item['languageId']] = $item[$field] ?? null;
62
        }
63
64
        foreach ($items as $item) {
65
            $languageId = $item['languageId'] ?? Defaults::LANGUAGE_SYSTEM;
66
67
            // if child value is null, it should be inherited from parent
68
            $newValue = $item[$field] ?? ($fallbackValue[$languageId] ?? null);
69
70
            if ($stripText && \is_string($newValue) && $newValue !== '') {
71
                $newValue = ElasticsearchIndexingUtils::stripText($newValue);
72
            }
73
74
            $value[$languageId] = $newValue;
75
        }
76
77
        return $value;
78
    }
79
80
    /**
81
     * Maps an array of items to nested arrays of multilingual fields, each field is an array of multilingual values keyed by language ID.
82
     *
83
     * This method is commonly used to handle associations, such as product names and descriptions in different languages.
84
     *
85
     * @param array<int, array{id: string, languageId?: string}> $items     An array of items with language information.
86
     * @param string[] $translatedFields                                    An array of fields to be translated.
87
     *
88
     * @return array<int, array<string, array<string, string>>>             An array of items with nested arrays containing translated values.
89
     *
90
     * @example
91
     *
92
     * ```php
93
     * $items = [['id' => 'fooId', 'name' => 'foo in EN', 'languageId' => 'en-GB'], ['id' => 'fooId', 'name' => 'foo in De', 'languageId' => 'de-DE'], ['id' => 'barId', 'name' => 'bar', 'description' => 'bar description', 'languageId' => 'en-GB']];
94
     * $esValue = ElasticsearchIndexingHelper::mapToManyAssociations($items, ['name', 'description']);
95
     * // [
96
     *      [
97
     *          'id' => 'fooId',
98
     *          'name' => [
99
     *              'en-GB' => 'foo in EN',
100
     *              'de-DE' => 'foo in De',
101
     *          ],
102
     *          'description' => [
103
     *              'en-GB' => null,
104
     *          ]
105
     *      ],
106
     *      [
107
     *          'id' => 'barId',
108
     *          'name' => ['en-GB' => 'bar'],
109
     *          'description' => [
110
     *              'en-GB' => 'bar description',
111
     *          ],
112
     *      ],
113
     * ]
114
     * ```
115
     */
116
    public static function toManyAssociations(array $items, array $translatedFields): array
117
    {
118
        // Group items by 'id'
119
        $groupedItems = [];
120
121
        foreach ($items as $item) {
122
            if (empty($item['languageId'])) {
123
                continue;
124
            }
125
126
            $itemId = $item['id'];
127
            if (!isset($groupedItems[$itemId])) {
128
                $groupedItems[$itemId] = [
129
                    'id' => $itemId,
130
                    '_count' => 1,
131
                ];
132
            }
133
134
            foreach ($translatedFields as $field) {
135
                if (!empty($item[$field])) {
136
                    $groupedItems[$itemId][$field][$item['languageId']] = $item[$field];
137
                } elseif (!isset($groupedItems[$itemId][$field])) {
138
                    $groupedItems[$itemId][$field][$item['languageId']] = null;
139
                }
140
            }
141
        }
142
143
        // Convert grouped items into a numerically indexed array
144
        return array_values($groupedItems);
145
    }
146
147
    /**
148
     * @description This method is used to format custom fields to the correct format
149
     *
150
     * @param array<string, mixed> $customFields array of raw custom fields from database keyed by language ID
151
     *
152
     * @throws \JsonException
153
     *
154
     * @return array<string, array<string, mixed>> array of formatted custom fields keyed by language ID
155
     */
156
    public function customFields(string $entity, array $customFields, Context $context): array
157
    {
158
        $mapped = [];
159
160
        foreach ($customFields as $languageId => $customField) {
161
            if (empty($customField)) {
162
                continue;
163
            }
164
165
            // MariaDB servers gives the result as string and not directly decoded
166
            if (\is_string($customField)) {
167
                $customField = json_decode($customField, true, 512, \JSON_THROW_ON_ERROR);
168
            }
169
170
            $mapped[$languageId] = $this->formatCustomField($entity, $customField ?: [], $context);
171
        }
172
173
        return $mapped;
174
    }
175
176
    /**
177
     * @param array<string, mixed> $customFields
178
     *
179
     * @throws Exception
180
     *
181
     * @return array<string, mixed>
182
     */
183
    private function formatCustomField(string $entity, array $customFields, Context $context): array
184
    {
185
        $types = $this->indexingUtils->getCustomFieldTypes($entity, $context);
186
187
        foreach ($customFields as $name => $customField) {
188
            $type = $types[$name] ?? null;
189
190
            if ($type === null) {
191
                unset($customFields[$name]);
192
193
                continue;
194
            }
195
196
            if ($type === CustomFieldTypes::BOOL) {
197
                $customFields[$name] = (bool) $customField;
198
            } elseif (\is_numeric($customField)) {
199
                $customFields[$name] = (float) $customField;
200
            }
201
        }
202
203
        return $customFields;
204
    }
205
}
206