Completed
Push — master ( 5576ee...a16f3a )
by Mārtiņš
02:02
created

MessageStorage::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
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 33
    public function __construct(MessageRepositoryInterface $repository)
27
    {
28 33
        $this->repository = $repository;
29 33
    }
30
31
    /**
32
     * Save translation object for a single domain and locale
33
     *
34
     * @param Translations $translations
35
     * @throws InvalidTranslationException
36
     */
37 8
    public function createOrUpdate(Translations $translations)
38
    {
39 8
        $this->validateHeaders($translations);
40
41 6
        $locale = $translations->getLanguage();
42 6
        $domain = $translations->getDomain();
43
44 6
        foreach ($translations as $translation) {
45 6
            $this->createOrUpdateSingle($locale, $domain, $translation);
46
        }
47 6
    }
48
49
    /**
50
     * @param Translations $translations
51
     * @throws InvalidTranslationException
52
     */
53 8
    private function validateHeaders(Translations $translations)
54
    {
55 8
        if (!$translations->getLanguage()) {
56 1
            throw new InvalidTranslationException('Locale is missing');
57
        }
58
59 7
        if (!$translations->getDomain()) {
60 1
            throw new InvalidTranslationException('Domain is missing');
61
        }
62 6
    }
63
64
    /**
65
     * Save a translation to repository by merging it to a previously saved version.
66
     *
67
     * @param string $locale
68
     * @param string $domain
69
     * @param Translation $translation
70
     * @return bool
71
     */
72 24
    public function createOrUpdateSingle(string $locale, string $domain, Translation $translation): bool
73
    {
74
        // Make a clone so we don't modify the passed instance
75 24
        $translation = $translation->getClone();
76
77 24
        $key = $this->getKey($locale, $domain, $translation);
78
79 24
        $existingItem = $this->repository->getSingle($key);
80
81 24
        if ($existingItem->exists()) {
82 5
            $existingTranslation = $this->itemToTranslation($existingItem);
83 5
            $translation->mergeWith($existingTranslation);
84 5
            $item = $this->translationToItem($locale, $domain, $translation);
85
86
            // Override the disabled state of the existing translation
87 5
            $item->isDisabled = $translation->isDisabled();
88
        } else {
89 24
            $item = $this->translationToItem($locale, $domain, $translation);
90
        }
91
92 24
        $item->hasOriginalTranslation = $translation->hasTranslation();
93 24
        $item->requiresTranslating = $this->requiresTranslating($locale, $translation);
94
95 24
        return $this->repository->save($item);
96
    }
97
98
    /**
99
     * Save translation object for a single domain and locale
100
     *
101
     * @param Translations $translations
102
     * @throws InvalidTranslationException
103
     */
104 1
    public function saveTranslated(Translations $translations)
105
    {
106 1
        $this->validateHeaders($translations);
107
108 1
        $locale = $translations->getLanguage();
109 1
        $domain = $translations->getDomain();
110
111 1
        foreach ($translations as $translation) {
112 1
            $this->saveTranslatedSingle($locale, $domain, $translation);
113
        }
114 1
    }
115
116
    /**
117
     * Function for saving only translated values.
118
     * This will not modify disabled state and will not create new entries in repository, only modifies existing
119
     *
120
     * @param string $locale
121
     * @param string $domain
122
     * @param Translation $translation
123
     * @return bool
124
     */
125 4
    public function saveTranslatedSingle(string $locale, string $domain, Translation $translation): bool
126
    {
127 4
        if (!$translation->hasTranslation()) {
128 1
            return false;
129
        }
130
131 3
        $existingItem = $this->repository->getSingle($this->getKey($locale, $domain, $translation));
132
133 3
        if (!$existingItem->exists()) {
134 1
            return false;
135
        }
136
137 2
        $existingTranslation = $this->itemToTranslation($existingItem);
138
139 2
        $existingTranslation->setTranslation($translation->getTranslation());
140
141
        // Make sure we do not drop previous plural translations if current one does not contain one
142 2
        $pluralTranslations = $translation->getPluralTranslations();
143 2
        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...
144 1
            $existingTranslation->setPluralTranslations($pluralTranslations);
145
        }
146
147 2
        $item = $this->translationToItem($locale, $domain, $existingTranslation);
148
149 2
        $item->hasOriginalTranslation = true;
150
        // It's still possible that plurals are missing and this translation still needs work
151 2
        $item->requiresTranslating = $this->requiresTranslating($locale, $translation);
152
153 2
        return $this->repository->save($item);
154
    }
155
156
    /**
157
     * Check if some translations are missing (original or missing plural forms)
158
     *
159
     * @param string $locale
160
     * @param Translation $translation
161
     * @return bool
162
     */
163 24
    private function requiresTranslating(string $locale, Translation $translation): bool
164
    {
165 24
        if (!$translation->hasTranslation()) {
166 7
            return true;
167
        }
168
169 21
        if ($translation->hasPlural()) {
170 6
            $translatedPluralCount = count(array_filter($translation->getPluralTranslations()));
171
            // If there are less plural translations than language requires, this needs translating
172 6
            return $this->getPluralCount($locale) !== $translatedPluralCount;
173
        }
174
175 16
        return false;
176
    }
177
178
    /**
179
     * Get number of plural forms for this locale
180
     *
181
     * @param string $locale
182
     * @return int
183
     * @throws \InvalidArgumentException Thrown in the locale is not correct
184
     */
185 6
    private function getPluralCount(string $locale): int
186
    {
187 6
        if (!array_key_exists($locale, $this->pluralFormCache)) {
188 6
            $info = Language::getById($locale);
189 6
            $this->pluralFormCache[$locale] = count($info->categories);
190
        }
191
192 6
        return $this->pluralFormCache[$locale];
193
    }
194
195
    /**
196
     * Generate a unique key for storage (basically a primary key)
197
     *
198
     * @param string $locale
199
     * @param string $domain
200
     * @param Translation $translation
201
     * @return string
202
     */
203 25
    private function getKey(string $locale, string $domain, Translation $translation): string
204
    {
205 25
        return md5($locale . '|' . $domain . '|' . $translation->getContext() . '|' . $translation->getOriginal());
206
    }
207
208
    /**
209
     * Convert a message item to a translation item
210
     *
211
     * @param MessageItem $item
212
     * @return Translation
213
     */
214 20
    private function itemToTranslation(MessageItem $item): Translation
215
    {
216 20
        $translation = new Translation($item->context, $item->original, $item->originalPlural);
217
218 20
        $translation->setTranslation($item->translation);
219 20
        $translation->setPluralTranslations($item->pluralTranslations);
220 20
        $translation->setDisabled($item->isDisabled);
221
222 20
        foreach ($item->references as $v) {
223 3
            $translation->addReference($v[0], $v[1]);
224
        }
225
226 20
        foreach ($item->comments as $v) {
227 1
            $translation->addComment($v);
228
        }
229
230 20
        foreach ($item->extractedComments as $v) {
231 1
            $translation->addExtractedComment($v);
232
        }
233
234 20
        return $translation;
235
    }
236
237
    /**
238
     * Convert a translation item to a message item
239
     *
240
     * @param string $locale
241
     * @param string $domain
242
     * @param Translation $translation
243
     * @return MessageItem
244
     */
245 24
    private function translationToItem(string $locale, string $domain, Translation $translation): MessageItem
246
    {
247 24
        $item = new MessageItem;
248
249 24
        $item->key = $this->getKey($locale, $domain, $translation);
250 24
        $item->domain = $domain;
251 24
        $item->locale = $locale;
252 24
        $item->context = (string)$translation->getContext();
253 24
        $item->original = $translation->getOriginal();
254 24
        $item->translation = $translation->getTranslation();
255 24
        $item->originalPlural = $translation->getPlural();
256 24
        $item->pluralTranslations = $translation->getPluralTranslations();
257 24
        $item->references = $translation->getReferences();
258 24
        $item->comments = $translation->getComments();
259 24
        $item->extractedComments = $translation->getExtractedComments();
260 24
        $item->isDisabled = $translation->isDisabled();
261
262 24
        foreach ($item->references as $reference) {
263 3
            if ($this->isJsFile($reference[0] ?? '')) {
264 2
                $item->isJs = true;
265 3
                break;
266
            }
267
        }
268
269 24
        return $item;
270
    }
271
272
    /**
273
     * Check if this is considered a JS file.
274
     *
275
     * @param string $filename
276
     * @return bool
277
     */
278 3
    private function isJsFile(string $filename): bool
279
    {
280 3
        $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
281
282 3
        return in_array($extension, ['vue', 'js'], true);
283
    }
284
285
    /**
286
     * All translations, including disabled, enabled and untranslated
287
     *
288
     * @param string $locale
289
     * @param $domain
290
     * @return Translations
291
     */
292 5
    public function getAll(string $locale, $domain): Translations
293
    {
294 5
        return $this->convertItems(
295 5
            $locale,
296 5
            (string)$domain,
297 5
            $this->repository->getAll($locale, (string)$domain)
298
        );
299
    }
300
301
    /**
302
     * All enabled translations including untranslated
303
     *
304
     * @param string $locale
305
     * @param $domain
306
     * @return Translations
307
     */
308 3
    public function getAllEnabled(string $locale, $domain): Translations
309
    {
310 3
        return $this->convertItems(
311 3
            $locale,
312 3
            (string)$domain,
313 3
            $this->repository->getEnabled($locale, (string)$domain)
314
        );
315
    }
316
317
    /**
318
     * Enabled and translated translations only
319
     *
320
     * @param string $locale
321
     * @param $domain
322
     * @return Translations
323
     */
324 11
    public function getEnabledTranslated(string $locale, $domain): Translations
325
    {
326 11
        $items = $this->repository->getEnabledTranslated($locale, (string)$domain);
327
328 11
        return $this->convertItems($locale, (string)$domain, $items);
329
    }
330
331
    /**
332
     * Enabled and translated JS translations
333
     *
334
     * @param string $locale
335
     * @param $domain
336
     * @return Translations
337
     */
338 2
    public function getEnabledTranslatedJs(string $locale, $domain): Translations
339
    {
340 2
        $items = $this->repository->getEnabledTranslatedJs($locale, (string)$domain);
341
342 2
        return $this->convertItems($locale, (string)$domain, $items);
343
    }
344
345
    /**
346
     * Enabled and translated translations only
347
     *
348
     * @param string $locale
349
     * @param $domain
350
     * @return Translations
351
     */
352 6
    public function getRequiresTranslating(string $locale, $domain): Translations
353
    {
354 6
        return $this->convertItems(
355 6
            $locale,
356 6
            (string)$domain,
357 6
            $this->repository->getRequiresTranslating($locale, (string)$domain)
358
        );
359
    }
360
361
    /**
362
     * Converts message items to a translation object
363
     *
364
     * @param string $locale
365
     * @param string|null $domain
366
     * @param MessageItem[] $items
367
     * @return Translations
368
     */
369 24
    private function convertItems(string $locale, $domain, array $items): Translations
370
    {
371 24
        $domain = (string)$domain;
372
373 24
        $translations = new Translations;
374 24
        $translations->setDomain($domain);
375 24
        $translations->setLanguage($locale);
376
377 24
        foreach ($items as $v) {
378 20
            $translation = $this->itemToTranslation($v);
379 20
            $translations[] = $translation;
380
        }
381
382 24
        return $translations;
383
    }
384
385
    /**
386
     * Mark all messages in the given domain and locale as disabled
387
     *
388
     * @param string $locale
389
     * @param string $domain
390
     */
391 2
    public function disableAll(string $locale, string $domain)
392
    {
393 2
        $this->repository->disableAll($locale, $domain);
394
    }
395
}