Completed
Push — master ( 57d49a...035e38 )
by Oscar
02:09
created

Translations::__clone()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 5
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 10
rs 9.4285
1
<?php
2
3
namespace Gettext;
4
5
use Gettext\Languages\Language;
6
use BadMethodCallException;
7
use InvalidArgumentException;
8
9
/**
10
 * Class to manage a collection of translations.
11
 * 
12
 * @method addFromCsvFile(string $filename, array $options = [])
13
 * @method addFromCsvString(array $options = [])
14
 * @method toCsvFile(string $filename, array $options = [])
15
 * @method toCsvString(array $options = [])
16
 * @method addFromCsvDictionaryFile(string $filename, array $options = [])
17
 * @method addFromCsvDictionaryString(array $options = [])
18
 * @method toCsvDictionaryFile(string $filename, array $options = [])
19
 * @method toCsvDictionaryString(array $options = [])
20
 * @method addFromJedFile(string $filename, array $options = [])
21
 * @method addFromJedString(array $options = [])
22
 * @method toJedFile(string $filename, array $options = [])
23
 * @method toJedString(array $options = [])
24
 * @method addFromJsonFile(string $filename, array $options = [])
25
 * @method addFromJsonString(array $options = [])
26
 * @method toJsonFile(string $filename, array $options = [])
27
 * @method toJsonString(array $options = [])
28
 * @method addFromJsonDictionaryFile(string $filename, array $options = [])
29
 * @method addFromJsonDictionaryString(array $options = [])
30
 * @method toJsonDictionaryFile(string $filename, array $options = [])
31
 * @method toJsonDictionaryString(array $options = [])
32
 * @method addFromMoFile(string $filename, array $options = [])
33
 * @method addFromMoString(array $options = [])
34
 * @method toMoFile(string $filename, array $options = [])
35
 * @method toMoString(array $options = [])
36
 * @method addFromPhpArrayFile(string $filename, array $options = [])
37
 * @method addFromPhpArrayString(array $options = [])
38
 * @method toPhpArrayFile(string $filename, array $options = [])
39
 * @method toPhpArrayString(array $options = [])
40
 * @method addFromPoFile(string $filename, array $options = [])
41
 * @method addFromPoString(array $options = [])
42
 * @method toPoFile(string $filename, array $options = [])
43
 * @method toPoString(array $options = [])
44
 * @method addFromXliffFile(string $filename, array $options = [])
45
 * @method addFromXliffString(array $options = [])
46
 * @method toXliffFile(string $filename, array $options = [])
47
 * @method toXliffString(array $options = [])
48
 * @method addFromYamlFile(string $filename, array $options = [])
49
 * @method addFromYamlString(array $options = [])
50
 * @method toYamlFile(string $filename, array $options = [])
51
 * @method toYamlString(array $options = [])
52
 * @method addFromYamlDictionaryFile(string $filename, array $options = [])
53
 * @method addFromYamlDictionaryString(array $options = [])
54
 * @method toYamlDictionaryFile(string $filename, array $options = [])
55
 * @method toYamlDictionaryString(array $options = [])
56
 * 
57
 */
58
class Translations extends \ArrayObject
59
{
60
    const HEADER_LANGUAGE = 'Language';
61
    const HEADER_PLURAL = 'Plural-Forms';
62
    const HEADER_DOMAIN = 'X-Domain';
63
64
    public static $insertDate = true;
65
66
    private $headers;
67
68
    /**
69
     * @see \ArrayObject::__construct()
70
     */
71
    public function __construct($input = [], $flags = 0, $iterator_class = 'ArrayIterator')
72
    {
73
        $this->headers = [
74
            'Project-Id-Version' => '',
75
            'Report-Msgid-Bugs-To' => '',
76
            'Last-Translator' => '',
77
            'Language-Team' => '',
78
            'MIME-Version' => '1.0',
79
            'Content-Type' => 'text/plain; charset=UTF-8',
80
            'Content-Transfer-Encoding' => '8bit',
81
        ];
82
83
        if (static::$insertDate) {
84
            $this->headers['POT-Creation-Date'] = $this->headers['PO-Revision-Date'] = date('c');
85
        }
86
87
        $this->headers[self::HEADER_LANGUAGE] = '';
88
        parent::__construct($input, $flags, $iterator_class);
89
    }
90
91
    /**
92
     * Magic method to create new instances using extractors
93
     * For example: Translations::fromMoFile($filename, $options);.
94
     *
95
     * @return Translations
96
     */
97
    public static function __callStatic($name, $arguments)
98
    {
99
        if (!preg_match('/^from(\w+)(File|String)$/i', $name, $matches)) {
100
            throw new BadMethodCallException("The method $name does not exists");
101
        }
102
103
        return call_user_func_array([new static(), 'add'.ucfirst($name)], $arguments);
104
    }
105
106
    /**
107
     * Magic method to import/export the translations to a specific format
108
     * For example: $translations->toMoFile($filename, $options);
109
     * For example: $translations->addFromMoFile($filename, $options);.
110
     *
111
     * @return self|bool
112
     */
113
    public function __call($name, $arguments)
114
    {
115
        if (!preg_match('/^(addFrom|to)(\w+)(File|String)$/i', $name, $matches)) {
116
            throw new BadMethodCallException("The method $name does not exists");
117
        }
118
119
        if ($matches[1] === 'addFrom') {
120
            $extractor = 'Gettext\\Extractors\\'.$matches[2].'::from'.$matches[3];
121
            $source = array_shift($arguments);
122
            $options = array_shift($arguments) ?: [];
123
124
            call_user_func($extractor, $source, $this, $options);
125
126
            return $this;
127
        }
128
129
        $generator = 'Gettext\\Generators\\'.$matches[2].'::to'.$matches[3];
130
131
        array_unshift($arguments, $this);
132
133
        return call_user_func_array($generator, $arguments);
134
    }
135
136
    /**
137
     * Magic method to clone each translation on clone the translations object.
138
     */
139
    public function __clone()
140
    {
141
        $array = [];
142
143
        foreach ($this as $key => $translation) {
144
            $array[$key] = clone $translation;
145
        }
146
147
        $this->exchangeArray($array);
148
    }
149
150
    /**
151
     * Control the new translations added.
152
     *
153
     * @param mixed       $index
154
     * @param Translation $value
155
     *
156
     * @throws InvalidArgumentException If the value is not an instance of Gettext\Translation
157
     *
158
     * @return Translation
159
     */
160
    public function offsetSet($index, $value)
161
    {
162
        if (!($value instanceof Translation)) {
163
            throw new InvalidArgumentException('Only instances of Gettext\\Translation must be added to a Gettext\\Translations');
164
        }
165
166
        $id = $value->getId();
167
168
        if ($this->offsetExists($id)) {
169
            $this[$id]->mergeWith($value);
170
171
            return $this[$id];
172
        }
173
174
        parent::offsetSet($id, $value);
175
176
        return $value;
177
    }
178
179
    /**
180
     * Set the plural definition.
181
     *
182
     * @param int    $count
183
     * @param string $rule
184
     * 
185
     * @return self
186
     */
187
    public function setPluralForms($count, $rule)
188
    {
189
        $this->setHeader(self::HEADER_PLURAL, "nplurals={$count}; plural={$rule};");
190
191
        return $this;
192
    }
193
194
    /**
195
     * Returns the parsed plural definition.
196
     *
197
     * @param null|array [count, rule]
198
     */
199
    public function getPluralForms()
200
    {
201
        $header = $this->getHeader(self::HEADER_PLURAL);
202
203
        if (!empty($header) && preg_match('/^nplurals\s*=\s*(\d+)\s*;\s*plural\s*=\s*([^;]+)\s*;$/', $header, $matches)) {
204
            return [intval($matches[1]), $matches[2]];
205
        }
206
    }
207
208
    /**
209
     * Set a new header.
210
     *
211
     * @param string $name
212
     * @param string $value
213
     * 
214
     * @return self
215
     */
216
    public function setHeader($name, $value)
217
    {
218
        $name = trim($name);
219
        $this->headers[$name] = trim($value);
220
221
        return $this;
222
    }
223
224
    /**
225
     * Returns a header value.
226
     *
227
     * @param string $name
228
     *
229
     * @return null|string
230
     */
231
    public function getHeader($name)
232
    {
233
        return isset($this->headers[$name]) ? $this->headers[$name] : null;
234
    }
235
236
    /**
237
     * Returns all header for this translations (in alphabetic order).
238
     *
239
     * @return array
240
     */
241
    public function getHeaders()
242
    {
243
        ksort($this->headers);
244
245
        return $this->headers;
246
    }
247
248
    /**
249
     * Removes all headers.
250
     * 
251
     * @return self
252
     */
253
    public function deleteHeaders()
254
    {
255
        $this->headers = [];
256
257
        return $this;
258
    }
259
260
    /**
261
     * Removes one header.
262
     *
263
     * @param string $name
264
     * 
265
     * @return self
266
     */
267
    public function deleteHeader($name)
268
    {
269
        unset($this->headers[$name]);
270
271
        return $this;
272
    }
273
274
    /**
275
     * Returns the language value.
276
     *
277
     * @return string $language
278
     */
279
    public function getLanguage()
280
    {
281
        return $this->getHeader(self::HEADER_LANGUAGE);
282
    }
283
284
    /**
285
     * Sets the language and the plural forms.
286
     *
287
     * @param string $language
288
     * 
289
     * @throws InvalidArgumentException if the language hasn't been recognized
290
     *
291
     * @return self
292
     */
293
    public function setLanguage($language)
294
    {
295
        $this->setHeader(self::HEADER_LANGUAGE, trim($language));
296
297
        if (($info = Language::getById($language))) {
298
            return $this->setPluralForms(count($info->categories), $info->formula);
299
        }
300
301
        throw new InvalidArgumentException(sprintf('The language "%s" is not valid', $language));
302
    }
303
304
    /**
305
     * Checks whether the language is empty or not.
306
     *
307
     * @return bool
308
     */
309
    public function hasLanguage()
310
    {
311
        $language = $this->getLanguage();
312
313
        return (is_string($language) && ($language !== '')) ? true : false;
314
    }
315
316
    /**
317
     * Set a new domain for this translations.
318
     *
319
     * @param string $domain
320
     * 
321
     * @return self
322
     */
323
    public function setDomain($domain)
324
    {
325
        $this->setHeader(self::HEADER_DOMAIN, trim($domain));
326
327
        return $this;
328
    }
329
330
    /**
331
     * Returns the domain.
332
     *
333
     * @return string
334
     */
335
    public function getDomain()
336
    {
337
        return $this->getHeader(self::HEADER_DOMAIN);
338
    }
339
340
    /**
341
     * Checks whether the domain is empty or not.
342
     *
343
     * @return bool
344
     */
345
    public function hasDomain()
346
    {
347
        $domain = $this->getDomain();
348
349
        return (is_string($domain) && ($domain !== '')) ? true : false;
350
    }
351
352
    /**
353
     * Search for a specific translation.
354
     *
355
     * @param string|Translation $context  The context of the translation or a translation instance
356
     * @param string             $original The original string
357
     *
358
     * @return Translation|false
359
     */
360
    public function find($context, $original = '')
361
    {
362
        if ($context instanceof Translation) {
363
            $id = $context->getId();
364
        } else {
365
            $id = Translation::generateId($context, $original);
366
        }
367
368
        return $this->offsetExists($id) ? $this[$id] : false;
369
    }
370
371
    /**
372
     * Creates and insert/merges a new translation.
373
     *
374
     * @param string $context  The translation context
375
     * @param string $original The translation original string
376
     * @param string $plural   The translation original plural string
377
     *
378
     * @return Translation The translation created
379
     */
380
    public function insert($context, $original, $plural = '')
381
    {
382
        return $this->offsetSet(null, new Translation($context, $original, $plural));
383
    }
384
385
    /**
386
     * Merges this translations with other translations.
387
     *
388
     * @param Translations $translations        The translations instance to merge with
389
     * @param int          $options
390
     * 
391
     * @return self
392
     */
393
    public function mergeWith(Translations $translations, $options = Merge::DEFAULT)
394
    {
395
        return $this
396
            ->mergeHeadersWith($translations, $options)
397
            ->mergeTranslationsWith($translations, $options);
398
    }
399
400
    /**
401
     * Merge the headers of two translations
402
     * 
403
     * @param Translations $translations
404
     * @param int          $options
405
     * 
406
     * @return self
407
     */
408
    public function mergeHeadersWith(Translations $translations, $options = Merge::DEFAULT)
409
    {
410
        if ($options & Merge::HEADERS_REMOVE) {
411
            foreach (array_keys($this->getHeaders()) as $name) {
412
                if ($translations->getHeader($name) === null) {
413
                    $this->deleteHeader($name);
414
                }
415
            }
416
        }
417
418
        foreach ($translations->getHeaders() as $name => $value) {
419
            $current = $this->getHeader($name);
420
421
            if (empty($current)) {
422
                if ($options & Merge::HEADERS_ADD) {
423
                    $this->setHeader($name, $value);
424
                }
425
                continue;
426
            }
427
428
            if (empty($value)) {
429
                continue;
430
            }
431
432
            switch ($name) {
433
                case self::HEADER_LANGUAGE:
434
                case self::HEADER_PLURAL:
435
                    if ($options & Merge::LANGUAGE_OVERRIDE) {
436
                        $this->setHeader($name, $value);
437
                    }
438
                    break;
439
440
                case self::HEADER_DOMAIN:
441
                    if ($options & Merge::DOMAIN_OVERRIDE) {
442
                        $this->setHeader($name, $value);
443
                    }
444
                    break;
445
446
                default:
447
                    if ($options & Merge::HEADERS_OVERRIDE) {
448
                        $this->setHeader($name, $value);
449
                    }
450
            }
451
        }
452
453
        return $this;
454
    }
455
456
    /**
457
     * Merge the translations of two translations
458
     * 
459
     * @param Translations $translations
460
     * @param int          $options
461
     * 
462
     * @return self
463
     */
464
    public function mergeTranslationsWith(Translations $translations, $options = Merge::DEFAULT)
465
    {
466
        if ($options & Merge::REMOVE) {
467
            $filtered = [];
468
469
            foreach ($this as $entry) {
470
                if ($translations->find($entry)) {
471
                    $filtered[$entry->getId()] = $entry;
472
                }
473
            }
474
475
            $this->exchangeArray($filtered);
476
        }
477
478
        foreach ($translations as $entry) {
479
            if (($existing = $this->find($entry))) {
480
                $existing->mergeWith($entry, $options);
481
            } elseif ($options & Merge::ADD) {
482
                $this[] = $entry;
483
            }
484
        }
485
486
        return $this;
487
    }
488
}
489