Completed
Push — master ( a4c536...c246ca )
by Oscar
02:29
created

Translations::getHeaders()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 3
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 6
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
class Translations extends \ArrayObject
13
{
14
    const MERGE_ADD = 1;
15
    const MERGE_REMOVE = 2;
16
    const MERGE_HEADERS_MINES = 8;
17
    const MERGE_HEADERS_THEIRS = 16;
18
    const MERGE_LANGUAGE_OVERRIDE = 32;
19
    const MERGE_DOMAIN_OVERRIDE = 64;
20
21
    const HEADER_LANGUAGE = 'Language';
22
    const HEADER_PLURAL = 'Plural-Forms';
23
    const HEADER_DOMAIN = 'X-Domain';
24
25
    public static $insertDate = true;
26
27
    private $headers;
28
29
    /**
30
     * @see \ArrayObject::__construct()
31
     */
32
    public function __construct($input = [], $flags = 0, $iterator_class = 'ArrayIterator')
33
    {
34
        $this->headers = [
35
            'Project-Id-Version' => '',
36
            'Report-Msgid-Bugs-To' => '',
37
            'Last-Translator' => '',
38
            'Language-Team' => '',
39
            'MIME-Version' => '1.0',
40
            'Content-Type' => 'text/plain; charset=UTF-8',
41
            'Content-Transfer-Encoding' => '8bit',
42
        ];
43
44
        if (static::$insertDate) {
45
            $this->headers['POT-Creation-Date'] = $this->headers['PO-Revision-Date'] = date('c');
46
        }
47
48
        $this->headers[self::HEADER_LANGUAGE] = '';
49
        parent::__construct($input, $flags, $iterator_class);
50
    }
51
52
    /**
53
     * Magic method to create new instances using extractors
54
     * For example: Translations::fromMoFile($filename, $options);.
55
     *
56
     * @return Translations
57
     */
58
    public static function __callStatic($name, $arguments)
59
    {
60
        if (!preg_match('/^from(\w+)(File|String)$/i', $name, $matches)) {
61
            throw new BadMethodCallException("The method $name does not exists");
62
        }
63
64
        return call_user_func_array([new static(), 'add'.ucfirst($name)], $arguments);
65
    }
66
67
    /**
68
     * Magic method to import/export the translations to a specific format
69
     * For example: $translations->toMoFile($filename, $options);
70
     * For example: $translations->addFromMoFile($filename, $options);.
71
     *
72
     * @return self|bool
73
     */
74
    public function __call($name, $arguments)
75
    {
76
        if (!preg_match('/^(addFrom|to)(\w+)(File|String)$/i', $name, $matches)) {
77
            throw new BadMethodCallException("The method $name does not exists");
78
        }
79
80
        if ($matches[1] === 'addFrom') {
81
            $extractor = 'Gettext\\Extractors\\'.$matches[2].'::from'.$matches[3];
82
            $source = array_shift($arguments);
83
            $options = array_shift($arguments) ?: [];
84
85
            call_user_func($extractor, $source, $this, $options);
86
87
            return $this;
88
        }
89
90
        $generator = 'Gettext\\Generators\\'.$matches[2].'::to'.$matches[3];
91
92
        array_unshift($arguments, $this);
93
94
        return call_user_func_array($generator, $arguments);
95
    }
96
97
    /**
98
     * Magic method to clone each translation on clone the translations object.
99
     */
100
    public function __clone()
101
    {
102
        $array = [];
103
104
        foreach ($this as $key => $translation) {
105
            $array[$key] = clone $translation;
106
        }
107
108
        $this->exchangeArray($array);
109
    }
110
111
    /**
112
     * Control the new translations added.
113
     *
114
     * @param mixed       $index
115
     * @param Translation $value
116
     *
117
     * @throws InvalidArgumentException If the value is not an instance of Gettext\Translation
118
     *
119
     * @return Translation
120
     */
121
    public function offsetSet($index, $value)
122
    {
123
        if (!($value instanceof Translation)) {
124
            throw new InvalidArgumentException('Only instances of Gettext\\Translation must be added to a Gettext\\Translations');
125
        }
126
127
        $id = $value->getId();
128
129
        if ($this->offsetExists($id)) {
130
            $this[$id]->mergeWith($value);
131
132
            return $this[$id];
133
        }
134
135
        parent::offsetSet($id, $value);
136
137
        return $value;
138
    }
139
140
    /**
141
     * Set the plural definition.
142
     *
143
     * @param int    $count
144
     * @param string $rule
145
     * 
146
     * @return self
147
     */
148
    public function setPluralForms($count, $rule)
149
    {
150
        $this->setHeader(self::HEADER_PLURAL, "nplurals={$count}; plural={$rule};");
151
152
        return $this;
153
    }
154
155
    /**
156
     * Returns the parsed plural definition.
157
     *
158
     * @param null|array [count, rule]
159
     */
160
    public function getPluralForms()
161
    {
162
        $header = $this->getHeader(self::HEADER_PLURAL);
163
164
        if (!empty($header) && preg_match('/^nplurals\s*=\s*(\d+)\s*;\s*plural\s*=\s*([^;]+)\s*;$/', $header, $matches)) {
165
            return [intval($matches[1]), $matches[2]];
166
        }
167
    }
168
169
    /**
170
     * Set a new header.
171
     *
172
     * @param string $name
173
     * @param string $value
174
     * 
175
     * @return self
176
     */
177
    public function setHeader($name, $value)
178
    {
179
        $name = trim($name);
180
        $this->headers[$name] = trim($value);
181
182
        return $this;
183
    }
184
185
    /**
186
     * Returns a header value.
187
     *
188
     * @param string $name
189
     *
190
     * @return null|string
191
     */
192
    public function getHeader($name)
193
    {
194
        return isset($this->headers[$name]) ? $this->headers[$name] : null;
195
    }
196
197
    /**
198
     * Returns all header for this translations (in alphabetic order).
199
     *
200
     * @return array
201
     */
202
    public function getHeaders()
203
    {
204
        ksort($this->headers);
205
206
        return $this->headers;
207
    }
208
209
    /**
210
     * Removes all headers.
211
     * 
212
     * @return self
213
     */
214
    public function deleteHeaders()
215
    {
216
        $this->headers = [];
217
218
        return $this;
219
    }
220
221
    /**
222
     * Removes one header.
223
     *
224
     * @param string $name
225
     * 
226
     * @return self
227
     */
228
    public function deleteHeader($name)
229
    {
230
        unset($this->headers[$name]);
231
232
        return $this;
233
    }
234
235
    /**
236
     * Returns the language value.
237
     *
238
     * @return string $language
239
     */
240
    public function getLanguage()
241
    {
242
        return $this->getHeader(self::HEADER_LANGUAGE);
243
    }
244
245
    /**
246
     * Sets the language and the plural forms.
247
     *
248
     * @param string $language
249
     * 
250
     * @throws InvalidArgumentException if the language hasn't been recognized
251
     *
252
     * @return self
253
     */
254
    public function setLanguage($language)
255
    {
256
        $this->setHeader(self::HEADER_LANGUAGE, trim($language));
257
258
        if (($info = Language::getById($language))) {
259
            return $this->setPluralForms(count($info->categories), $info->formula);
260
        }
261
262
        throw new InvalidArgumentException(sprintf('The language "%s" is not valid', $language));
263
    }
264
265
    /**
266
     * Checks whether the language is empty or not.
267
     *
268
     * @return bool
269
     */
270
    public function hasLanguage()
271
    {
272
        $language = $this->getLanguage();
273
274
        return (is_string($language) && ($language !== '')) ? true : false;
275
    }
276
277
    /**
278
     * Set a new domain for this translations.
279
     *
280
     * @param string $domain
281
     * 
282
     * @return self
283
     */
284
    public function setDomain($domain)
285
    {
286
        $this->setHeader(self::HEADER_DOMAIN, trim($domain));
287
288
        return $this;
289
    }
290
291
    /**
292
     * Returns the domain.
293
     *
294
     * @return string
295
     */
296
    public function getDomain()
297
    {
298
        return $this->getHeader(self::HEADER_DOMAIN);
299
    }
300
301
    /**
302
     * Checks whether the domain is empty or not.
303
     *
304
     * @return bool
305
     */
306
    public function hasDomain()
307
    {
308
        $domain = $this->getDomain();
309
310
        return (is_string($domain) && ($domain !== '')) ? true : false;
311
    }
312
313
    /**
314
     * Search for a specific translation.
315
     *
316
     * @param string|Translation $context  The context of the translation or a translation instance
317
     * @param string             $original The original string
318
     *
319
     * @return Translation|false
320
     */
321
    public function find($context, $original = '')
322
    {
323
        if ($context instanceof Translation) {
324
            $id = $context->getId();
325
        } else {
326
            $id = Translation::generateId($context, $original);
327
        }
328
329
        return $this->offsetExists($id) ? $this[$id] : false;
330
    }
331
332
    /**
333
     * Creates and insert/merges a new translation.
334
     *
335
     * @param string $context  The translation context
336
     * @param string $original The translation original string
337
     * @param string $plural   The translation original plural string
338
     *
339
     * @return Translation The translation created
340
     */
341
    public function insert($context, $original, $plural = '')
342
    {
343
        return $this->offsetSet(null, new Translation($context, $original, $plural));
344
    }
345
346
    /**
347
     * Merges this translations with other translations.
348
     *
349
     * @param Translations $translations        The translations instance to merge with
350
     * @param int          $options             One or various Translations::MERGE_* constants to define how to merge the translations
351
     * @param int          $translationOptions  One or various Translation::MERGE_* constants to define how to merge each translation
352
     * 
353
     * @return self
354
     */
355
    public function mergeWith(Translations $translations, $options = self::MERGE_ADD, $translationOptions = Translation::MERGE_TRANSLATION_OVERRIDE)
356
    {
357
        if ($options === null) {
358
            $options = self::$mergeDefault;
359
        }
360
361
        if ($options & self::MERGE_HEADERS_THEIRS) {
362
            $this->deleteHeader();
0 ignored issues
show
Bug introduced by
The call to deleteHeader() misses a required argument $name.

This check looks for function calls that miss required arguments.

Loading history...
363
        }
364
365
        if (!($options & self::MERGE_HEADERS_MINES)) {
366
            foreach ($translations->getHeaders() as $name => $value) {
367
                $current = $this->getHeader($name);
368
369
                if ($current === null) {
370
                    $this->setHeader($name, $value);
371
                    continue;
372
                }
373
374
                switch ($name) {
375
                    case self::HEADER_LANGUAGE:
376 View Code Duplication
                    case self::HEADER_PLURAL:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
377
                        if (!$current || ($value && ($options & self::MERGE_LANGUAGE_OVERRIDE))) {
378
                            $this->setHeader($name, $value);
379
                        }
380
                        continue 2;
381
382 View Code Duplication
                    case self::HEADER_DOMAIN:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
383
                        if (!$current || ($value && ($options & self::MERGE_DOMAIN_OVERRIDE))) {
384
                            $this->setHeader($name, $value);
385
                        }
386
                        continue 2;
387
388
                    default:
389
                        if (!$current) {
390
                            $this->setHeader($name, $value);
391
                        }
392
                }
393
            }
394
        }
395
396
        $add = (boolean) ($options & self::MERGE_ADD);
397
398
        foreach ($translations as $entry) {
399
            if (($existing = $this->find($entry))) {
400
                $existing->mergeWith($entry, $translationOptions);
401
            } elseif ($add) {
402
                $this[] = clone $entry;
403
            }
404
        }
405
406
        if ($options & self::MERGE_REMOVE) {
407
            $filtered = [];
408
409
            foreach ($this as $entry) {
410
                if ($translations->find($entry)) {
411
                    $filtered[] = $entry;
412
                }
413
            }
414
415
            $this->exchangeArray($filtered);
416
        }
417
418
        return $this;
419
    }
420
}
421