Completed
Push — master ( f201ec...5576ee )
by Mārtiņš
01:40
created

MessageStorage::saveTranslatedValue()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 29
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 29
ccs 15
cts 15
cp 1
rs 8.5806
c 0
b 0
f 0
cc 4
eloc 14
nc 4
nop 3
crap 4
1
<?php
2
3
namespace Printful\GettextCms;
4
5
use Gettext\Languages\Language;
6
use Gettext\Translation;
7
use Gettext\Translations;
8
use Printful\GettextCms\Exceptions\InvalidTranslationException;
9
use Printful\GettextCms\Interfaces\MessageRepositoryInterface;
10
use Printful\GettextCms\Structures\MessageItem;
11
12
/**
13
 * Class handles all saving and retrieving logic of translations using the given repository
14
 */
15
class MessageStorage
16
{
17
    /** @var MessageRepositoryInterface */
18
    private $repository;
19
20
    /**
21
     * Cache for plural form counts
22
     * @var array [locale => plural count, ..]
23
     */
24
    private $pluralFormCache = [];
25
26 30
    public function __construct(MessageRepositoryInterface $repository)
27
    {
28 30
        $this->repository = $repository;
29 30
    }
30
31
    /**
32
     * Save translation object for a single domain and locale
33
     *
34
     * @param Translations $translations
35
     * @throws InvalidTranslationException
36
     */
37 7
    public function saveTranslations(Translations $translations)
38
    {
39 7
        $locale = $translations->getLanguage();
40 7
        $domain = (string)$translations->getDomain();
41
42 7
        if (!$locale) {
43 1
            throw new InvalidTranslationException('Locale is missing');
44
        }
45
46 6
        if (!$domain) {
47 1
            throw new InvalidTranslationException('Domain is missing');
48
        }
49
50 5
        foreach ($translations as $v) {
51 5
            $this->saveSingleTranslation($locale, $domain, $v);
52
        }
53 5
    }
54
55
    /**
56
     * Save a translation to repository by merging it to a previously saved version.
57
     *
58
     * @param string $locale
59
     * @param string $domain
60
     * @param Translation $translation
61
     * @return bool
62
     */
63 23
    public function saveSingleTranslation(string $locale, string $domain, Translation $translation): bool
64
    {
65
        // Make a clone so we don't modify the passed instance
66 23
        $translation = $translation->getClone();
67
68 23
        $key = $this->getKey($locale, $domain, $translation);
69
70 23
        $existingItem = $this->repository->getSingle($key);
71
72 23
        if ($existingItem->exists()) {
73 5
            $existingTranslation = $this->itemToTranslation($existingItem);
74 5
            $translation->mergeWith($existingTranslation);
75 5
            $item = $this->translationToItem($locale, $domain, $translation);
76
77
            // Override the disabled state of the existing translation
78 5
            $item->isDisabled = $translation->isDisabled();
79
        } else {
80 23
            $item = $this->translationToItem($locale, $domain, $translation);
81
        }
82
83 23
        $item->hasOriginalTranslation = $translation->hasTranslation();
84 23
        $item->requiresTranslating = $this->requiresTranslating($locale, $translation);
85
86 23
        return $this->repository->save($item);
87
    }
88
89
    /**
90
     * Function for saving only translated values.
91
     * This will not modify disabled state and will not create new entries in repository, only modifies existing
92
     *
93
     * @param string $locale
94
     * @param string $domain
95
     * @param Translation $translation
96
     * @return bool
97
     */
98 3
    public function saveTranslatedValue(string $locale, string $domain, Translation $translation): bool
99
    {
100 3
        if (!$translation->hasTranslation()) {
101 1
            return false;
102
        }
103
104 2
        $existingItem = $this->repository->getSingle($this->getKey($locale, $domain, $translation));
105
106 2
        if (!$existingItem->exists()) {
107 1
            return false;
108
        }
109
110 1
        $existingTranslation = $this->itemToTranslation($existingItem);
111
112 1
        $existingTranslation->setTranslation($translation->getTranslation());
113
114
        // Make sure we do not drop previous plural translations if current one does not contain one
115 1
        $pluralTranslations = $translation->getPluralTranslations();
116 1
        if ($pluralTranslations) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $pluralTranslations of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
117 1
            $existingTranslation->setPluralTranslations($pluralTranslations);
118
        }
119
120 1
        $item = $this->translationToItem($locale, $domain, $existingTranslation);
121
122 1
        $item->hasOriginalTranslation = true;
123
        // It's still possible that plurals are missing and this translation still needs work
124 1
        $item->requiresTranslating = $this->requiresTranslating($locale, $translation);
125
126 1
        return $this->repository->save($item);
127
    }
128
129
    /**
130
     * Check if some translations are missing (original or missing plural forms)
131
     *
132
     * @param string $locale
133
     * @param Translation $translation
134
     * @return bool
135
     */
136 23
    private function requiresTranslating(string $locale, Translation $translation): bool
137
    {
138 23
        if (!$translation->hasTranslation()) {
139 6
            return true;
140
        }
141
142 20
        if ($translation->hasPlural()) {
143 6
            $translatedPluralCount = count(array_filter($translation->getPluralTranslations()));
144
            // If there are less plural translations than language requires, this needs translating
145 6
            return $this->getPluralCount($locale) !== $translatedPluralCount;
146
        }
147
148 15
        return false;
149
    }
150
151
    /**
152
     * Get number of plural forms for this locale
153
     *
154
     * @param string $locale
155
     * @return int
156
     * @throws \InvalidArgumentException Thrown in the locale is not correct
157
     */
158 6
    private function getPluralCount(string $locale): int
159
    {
160 6
        if (!array_key_exists($locale, $this->pluralFormCache)) {
161 6
            $info = Language::getById($locale);
162 6
            $this->pluralFormCache[$locale] = count($info->categories);
163
        }
164
165 6
        return $this->pluralFormCache[$locale];
166
    }
167
168
    /**
169
     * Generate a unique key for storage (basically a primary key)
170
     *
171
     * @param string $locale
172
     * @param string $domain
173
     * @param Translation $translation
174
     * @return string
175
     */
176 24
    private function getKey(string $locale, string $domain, Translation $translation): string
177
    {
178 24
        return md5($locale . '|' . $domain . '|' . $translation->getContext() . '|' . $translation->getOriginal());
179
    }
180
181
    /**
182
     * Convert a message item to a translation item
183
     *
184
     * @param MessageItem $item
185
     * @return Translation
186
     */
187 19
    private function itemToTranslation(MessageItem $item): Translation
188
    {
189 19
        $translation = new Translation($item->context, $item->original, $item->originalPlural);
190
191 19
        $translation->setTranslation($item->translation);
192 19
        $translation->setPluralTranslations($item->pluralTranslations);
193 19
        $translation->setDisabled($item->isDisabled);
194
195 19
        foreach ($item->references as $v) {
196 3
            $translation->addReference($v[0], $v[1]);
197
        }
198
199 19
        foreach ($item->comments as $v) {
200 1
            $translation->addComment($v);
201
        }
202
203 19
        foreach ($item->extractedComments as $v) {
204 1
            $translation->addExtractedComment($v);
205
        }
206
207 19
        return $translation;
208
    }
209
210
    /**
211
     * Convert a translation item to a message item
212
     *
213
     * @param string $locale
214
     * @param string $domain
215
     * @param Translation $translation
216
     * @return MessageItem
217
     */
218 23
    private function translationToItem(string $locale, string $domain, Translation $translation): MessageItem
219
    {
220 23
        $item = new MessageItem;
221
222 23
        $item->key = $this->getKey($locale, $domain, $translation);
223 23
        $item->domain = $domain;
224 23
        $item->locale = $locale;
225 23
        $item->context = (string)$translation->getContext();
226 23
        $item->original = $translation->getOriginal();
227 23
        $item->translation = $translation->getTranslation();
228 23
        $item->originalPlural = $translation->getPlural();
229 23
        $item->pluralTranslations = $translation->getPluralTranslations();
230 23
        $item->references = $translation->getReferences();
231 23
        $item->comments = $translation->getComments();
232 23
        $item->extractedComments = $translation->getExtractedComments();
233 23
        $item->isDisabled = $translation->isDisabled();
234
235 23
        foreach ($item->references as $reference) {
236 3
            if ($this->isJsFile($reference[0] ?? '')) {
237 2
                $item->isJs = true;
238 3
                break;
239
            }
240
        }
241
242 23
        return $item;
243
    }
244
245
    /**
246
     * Check if this is considered a JS file.
247
     *
248
     * @param string $filename
249
     * @return bool
250
     */
251 3
    private function isJsFile(string $filename): bool
252
    {
253 3
        $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
254
255 3
        return in_array($extension, ['vue', 'js'], true);
256
    }
257
258
    /**
259
     * All translations, including disabled, enabled and untranslated
260
     *
261
     * @param string $locale
262
     * @param $domain
263
     * @return Translations
264
     */
265 5
    public function getAll(string $locale, $domain): Translations
266
    {
267 5
        return $this->convertItems(
268 5
            $locale,
269 5
            (string)$domain,
270 5
            $this->repository->getAll($locale, (string)$domain)
271
        );
272
    }
273
274
    /**
275
     * All enabled translations including untranslated
276
     *
277
     * @param string $locale
278
     * @param $domain
279
     * @return Translations
280
     */
281 3
    public function getAllEnabled(string $locale, $domain): Translations
282
    {
283 3
        return $this->convertItems(
284 3
            $locale,
285 3
            (string)$domain,
286 3
            $this->repository->getEnabled($locale, (string)$domain)
287
        );
288
    }
289
290
    /**
291
     * Enabled and translated translations only
292
     *
293
     * @param string $locale
294
     * @param $domain
295
     * @return Translations
296
     */
297 10
    public function getEnabledTranslated(string $locale, $domain): Translations
298
    {
299 10
        $items = $this->repository->getEnabledTranslated($locale, (string)$domain);
300
301 10
        return $this->convertItems($locale, (string)$domain, $items);
302
    }
303
304
    /**
305
     * Enabled and translated JS translations
306
     *
307
     * @param string $locale
308
     * @param $domain
309
     * @return Translations
310
     */
311 2
    public function getEnabledTranslatedJs(string $locale, $domain): Translations
312
    {
313 2
        $items = $this->repository->getEnabledTranslatedJs($locale, (string)$domain);
314
315 2
        return $this->convertItems($locale, (string)$domain, $items);
316
    }
317
318
    /**
319
     * Enabled and translated translations only
320
     *
321
     * @param string $locale
322
     * @param $domain
323
     * @return Translations
324
     */
325 6
    public function getRequiresTranslating(string $locale, $domain): Translations
326
    {
327 6
        return $this->convertItems(
328 6
            $locale,
329 6
            (string)$domain,
330 6
            $this->repository->getRequiresTranslating($locale, (string)$domain)
331
        );
332
    }
333
334
    /**
335
     * Converts message items to a translation object
336
     *
337
     * @param string $locale
338
     * @param string|null $domain
339
     * @param MessageItem[] $items
340
     * @return Translations
341
     */
342 23
    private function convertItems(string $locale, $domain, array $items): Translations
343
    {
344 23
        $domain = (string)$domain;
345
346 23
        $translations = new Translations;
347 23
        $translations->setDomain($domain);
348 23
        $translations->setLanguage($locale);
349
350 23
        foreach ($items as $v) {
351 19
            $translation = $this->itemToTranslation($v);
352 19
            $translations[] = $translation;
353
        }
354
355 23
        return $translations;
356
    }
357
358
    /**
359
     * Mark all messages in the given domain and locale as disabled
360
     *
361
     * @param string $locale
362
     * @param string $domain
363
     */
364 2
    public function disableAll(string $locale, string $domain)
365
    {
366 2
        $this->repository->disableAll($locale, $domain);
367
    }
368
}