gettext_reader::read()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
c 0
b 0
f 0
nc 1
nop 1
dl 0
loc 2
rs 10
1
<?php
2
/*
3
   Copyright (c) 2003, 2009 Danilo Segan <[email protected]>.
4
   Copyright (c) 2005 Nico Kaiser <[email protected]>
5
6
   This file is part of PHP-gettext.
7
8
   PHP-gettext is free software; you can redistribute it and/or modify
9
   it under the terms of the GNU General Public License as published by
10
   the Free Software Foundation; either version 2 of the License, or
11
   (at your option) any later version.
12
13
   PHP-gettext is distributed in the hope that it will be useful,
14
   but WITHOUT ANY WARRANTY; without even the implied warranty of
15
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16
   GNU General Public License for more details.
17
18
   You should have received a copy of the GNU General Public License
19
   along with PHP-gettext; if not, write to the Free Software
20
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21
22
*/
23
24
/**
25
 * Provides a simple gettext replacement that works independently from
26
 * the system's gettext abilities.
27
 * It can read MO files and use them for translating strings.
28
 * The files are passed to gettext_reader as a Stream (see streams.php)
29
 *
30
 * This version has the ability to cache all strings and translations to
31
 * speed up the string lookup.
32
 * While the cache is enabled by default, it can be switched off with the
33
 * second parameter in the constructor (e.g. whenusing very large MO files
34
 * that you don't want to keep in memory)
35
 */
36
class gettext_reader {
37
    //public:
38
    var $error = 0; // public variable that holds error code (0 if no error)
39
40
    //private:
41
    var $BYTEORDER = 0; // 0: low endian, 1: big endian
42
    var $STREAM = null;
43
    var $short_circuit = false;
44
    var $enable_cache = false;
45
    var $originals = null; // offset of original table
46
    var $translations = null; // offset of translation table
47
    var $pluralheader = null; // cache header field for plural forms
48
    var $total = 0; // total string count
49
    var $table_originals = null; // table for original strings (offsets)
50
    var $table_translations = null; // table for translated strings (offsets)
51
    var $cache_translations = null; // original -> translation mapping
52
53
54
    /* Methods */
55
56
57
    /**
58
     * Reads a 32bit Integer from the Stream
59
     *
60
     * @access private
61
     * @return Integer from the Stream
62
     */
63
    function readint() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
64
        if ($this->BYTEORDER == 0) {
65
        // low endian
66
        $input = unpack('V', $this->STREAM->read(4));
67
        return array_shift($input);
0 ignored issues
show
Bug introduced by
It seems like $input can also be of type false; however, parameter $array of array_shift() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

67
        return array_shift(/** @scrutinizer ignore-type */ $input);
Loading history...
68
        } else {
69
        // big endian
70
        $input = unpack('N', $this->STREAM->read(4));
71
        return array_shift($input);
72
        }
73
    }
74
75
    function read($bytes) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
76
    return $this->STREAM->read($bytes);
77
    }
78
79
    /**
80
     * Reads an array of Integers from the Stream
81
     *
82
     * @param int count How many elements should be read
83
     * @return Array of Integers
84
     */
85
    function readintarray($count) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
86
    if ($this->BYTEORDER == 0) {
87
        // low endian
88
        return unpack('V'.$count, $this->STREAM->read(4 * $count));
89
        } else {
90
        // big endian
91
        return unpack('N'.$count, $this->STREAM->read(4 * $count));
92
        }
93
    }
94
95
    /**
96
     * Constructor
97
     *
98
     * @param object Reader the StreamReader object
0 ignored issues
show
Bug introduced by
The type Reader was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
99
     * @param boolean enable_cache Enable or disable caching of strings (default on)
0 ignored issues
show
Bug introduced by
The type enable_cache was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
100
     */
101
    function __construct($Reader, $enable_cache = true) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
102
    // If there isn't a StreamReader, turn on short circuit mode.
103
    if (!$Reader || isset($Reader->error)) {
104
        $this->short_circuit = true;
105
        return;
106
    }
107
108
    // Caching can be turned off
109
    $this->enable_cache = $enable_cache;
110
111
    $MAGIC1 = "\x95\x04\x12\xde";
112
    $MAGIC2 = "\xde\x12\x04\x95";
113
114
    $this->STREAM = $Reader;
115
    $magic = $this->read(4);
116
    if ($magic == $MAGIC1) {
117
        $this->BYTEORDER = 1;
118
    } elseif ($magic == $MAGIC2) {
119
        $this->BYTEORDER = 0;
120
    } else {
121
        $this->error = 1; // not MO file
122
        return false;
123
    }
124
125
    // FIXME: Do we care about revision? We should.
126
    $revision = $this->readint();
0 ignored issues
show
Unused Code introduced by
The assignment to $revision is dead and can be removed.
Loading history...
127
128
    $this->total = $this->readint();
129
    $this->originals = $this->readint();
130
    $this->translations = $this->readint();
131
    }
132
133
    /**
134
     * Loads the translation tables from the MO file into the cache
135
     * If caching is enabled, also loads all strings into a cache
136
     * to speed up translation lookups
137
     *
138
     * @access private
139
     */
140
    function load_tables() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
141
    if (is_array($this->cache_translations) &&
142
      is_array($this->table_originals) &&
143
      is_array($this->table_translations)) {
144
            return;
145
    }
146
147
    /* get original and translations tables */
148
    if (!is_array($this->table_originals)) {
149
        $this->STREAM->seekto($this->originals);
150
        $this->table_originals = $this->readintarray($this->total * 2);
151
    }
152
    if (!is_array($this->table_translations)) {
153
        $this->STREAM->seekto($this->translations);
154
        $this->table_translations = $this->readintarray($this->total * 2);
155
    }
156
157
    if ($this->enable_cache) {
158
        $this->cache_translations = array();
159
        /* read all strings in the cache */
160
        for ($i = 0; $i < $this->total; $i++) {
161
        $this->STREAM->seekto($this->table_originals[$i * 2 + 2]);
162
        $original = $this->STREAM->read($this->table_originals[$i * 2 + 1]);
163
        $this->STREAM->seekto($this->table_translations[$i * 2 + 2]);
164
        $translation = $this->STREAM->read($this->table_translations[$i * 2 + 1]);
165
        $this->cache_translations[$original] = $translation;
166
        }
167
    }
168
    }
169
170
    /**
171
     * Returns a string from the "originals" table
172
     *
173
     * @access private
174
     * @param int num Offset number of original string
175
     * @return string Requested string if found, otherwise ''
176
     */
177
    function get_original_string($num) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
178
    $length = $this->table_originals[$num * 2 + 1];
179
    $offset = $this->table_originals[$num * 2 + 2];
180
    if (!$length) {
181
            return '';
182
    }
183
    $this->STREAM->seekto($offset);
184
    $data = $this->STREAM->read($length);
185
    return (string) $data;
186
    }
187
188
    /**
189
     * Returns a string from the "translations" table
190
     *
191
     * @access private
192
     * @param int num Offset number of original string
193
     * @return string Requested string if found, otherwise ''
194
     */
195
    function get_translation_string($num) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
196
    $length = $this->table_translations[$num * 2 + 1];
197
    $offset = $this->table_translations[$num * 2 + 2];
198
    if (!$length) {
199
            return '';
200
    }
201
    $this->STREAM->seekto($offset);
202
    $data = $this->STREAM->read($length);
203
    return (string) $data;
204
    }
205
206
    /**
207
     * Binary search for string
208
     *
209
     * @access private
210
     * @param string string
211
     * @param int start (internally used in recursive function)
212
     * @param int end (internally used in recursive function)
213
     * @return int string number (offset in originals table)
214
     */
215
    function find_string($string, $start = -1, $end = -1) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
216
    if (($start == -1) or ($end == -1)) {
217
        // find_string is called with only one parameter, set start end end
218
        $start = 0;
219
        $end = $this->total;
220
    }
221
    if (abs($start - $end) <= 1) {
222
        // We're done, now we either found the string, or it doesn't exist
223
        $txt = $this->get_original_string($start);
224
        if ($string == $txt) {
225
                return $start;
226
        } else {
227
                return -1;
228
        }
229
    } else if ($start > $end) {
230
        // start > end -> turn around and start over
231
        return $this->find_string($string, $end, $start);
232
    } else {
233
        // Divide table in two parts
234
        $half = (int) (($start + $end) / 2);
235
        $cmp = strcmp($string, $this->get_original_string($half));
236
        if ($cmp == 0) {
237
                // string is exactly in the middle => return it
238
        return $half;
239
        } else if ($cmp < 0) {
240
                // The string is in the upper half
241
        return $this->find_string($string, $start, $half);
242
        } else {
243
                // The string is in the lower half
244
        return $this->find_string($string, $half, $end);
245
        }
246
    }
247
    }
248
249
    /**
250
     * Translates a string
251
     *
252
     * @access public
253
     * @param string string to be translated
254
     * @return string translated string (or original, if not found)
255
     */
256
    function translate($string) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
257
    if ($this->short_circuit) {
258
            return $string;
259
    }
260
    $this->load_tables();
261
262
    if ($this->enable_cache) {
263
        // Caching enabled, get translated string from cache
264
        if (array_key_exists($string, $this->cache_translations)) {
265
                return $this->cache_translations[$string];
266
        } else {
267
                return $string;
268
        }
269
    } else {
270
        // Caching not enabled, try to find string
271
        $num = $this->find_string($string);
272
        if ($num == -1) {
273
                return $string;
274
        } else {
275
                return $this->get_translation_string($num);
276
        }
277
    }
278
    }
279
280
    /**
281
     * Sanitize plural form expression for use in PHP eval call.
282
     *
283
     * @access private
284
     * @return string sanitized plural form expression
285
     */
286
    function sanitize_plural_expression($expr) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
287
    // Get rid of disallowed characters.
288
    $expr = preg_replace('@[^a-zA-Z0-9_:;\(\)\?\|\&=!<>+*/\%-]@', '', $expr);
289
290
    // Add parenthesis for tertiary '?' operator.
291
    $expr .= ';';
292
    $res = '';
293
    $p = 0;
294
    for ($i = 0; $i < strlen($expr); $i++) {
295
        $ch = $expr[$i];
296
        switch ($ch) {
297
        case '?':
298
        $res .= ' ? (';
299
        $p++;
300
        break;
301
        case ':':
302
        $res .= ') : (';
303
        break;
304
        case ';':
305
        $res .= str_repeat(')', $p).';';
306
        $p = 0;
307
        break;
308
        default:
309
        $res .= $ch;
310
        }
311
    }
312
    return $res;
313
    }
314
315
    /**
316
     * Parse full PO header and extract only plural forms line.
317
     *
318
     * @access private
319
     * @return string verbatim plural form header field
320
     */
321
    function extract_plural_forms_header_from_po_header($header) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
322
    if (preg_match("/(^|\n)plural-forms: ([^\n]*)\n/i", $header, $regs)) {
323
            $expr = $regs[2];
324
    } else {
325
            $expr = "nplurals=2; plural=n == 1 ? 0 : 1;";
326
    }
327
    return $expr;
328
    }
329
330
    /**
331
     * Get possible plural forms from MO header
332
     *
333
     * @access private
334
     * @return string plural form header
335
     */
336
    function get_plural_forms() {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
337
    // lets assume message number 0 is header
338
    // this is true, right?
339
    $this->load_tables();
340
341
    // cache header field for plural forms
342
    if (!is_string($this->pluralheader)) {
343
        if ($this->enable_cache) {
344
        $header = $this->cache_translations[""];
345
        } else {
346
        $header = $this->get_translation_string(0);
347
        }
348
        $expr = $this->extract_plural_forms_header_from_po_header($header);
349
        $this->pluralheader = $this->sanitize_plural_expression($expr);
350
    }
351
    return $this->pluralheader;
352
    }
353
354
    /**
355
     * Detects which plural form to take
356
     *
357
     * @access private
358
     * @param n count
359
     * @return int array index of the right plural form
360
     */
361
    function select_string($n) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
362
    if (!is_int($n)) {
0 ignored issues
show
introduced by
The condition is_int($n) is always false.
Loading history...
363
        throw new InvalidArgumentException(
364
        "Select_string only accepts integers: ".$n);
365
    }
366
    $string = $this->get_plural_forms();
367
    $string = str_replace('nplurals', "\$total", $string);
368
    $string = str_replace("n", $n, $string);
369
    $string = str_replace('plural', "\$plural", $string);
370
371
    $total = 0;
372
    $plural = 0;
373
374
    eval("$string");
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
375
    if ($plural >= $total) {
376
        $plural = $total - 1;
377
    }
378
    return $plural;
379
    }
380
381
    /**
382
     * Plural version of gettext
383
     *
384
     * @access public
385
     * @param string single
386
     * @param string plural
387
     * @param string number
388
     * @return translated plural form
0 ignored issues
show
Bug introduced by
The type translated was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
389
     */
390
    function ngettext($single, $plural, $number) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
391
    if ($this->short_circuit) {
392
        if ($number != 1) {
393
                return $plural;
394
        } else {
395
                return $single;
396
        }
397
    }
398
399
    // find out the appropriate form
400
    $select = $this->select_string($number);
401
402
    // this should contains all strings separated by NULLs
403
    $key = $single.chr(0).$plural;
404
405
406
    if ($this->enable_cache) {
407
        if (!array_key_exists($key, $this->cache_translations)) {
408
        return ($number != 1) ? $plural : $single;
409
        } else {
410
        $result = $this->cache_translations[$key];
411
        $list = explode(chr(0), $result);
412
        return $list[$select];
413
        }
414
    } else {
415
        $num = $this->find_string($key);
416
        if ($num == -1) {
417
        return ($number != 1) ? $plural : $single;
418
        } else {
419
        $result = $this->get_translation_string($num);
420
        $list = explode(chr(0), $result);
421
        return $list[$select];
422
        }
423
    }
424
    }
425
426
    function pgettext($context, $msgid) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
427
    $key = $context.chr(4).$msgid;
428
    $ret = $this->translate($key);
429
    if (strpos($ret, "\004") !== false) {
430
        return $msgid;
431
    } else {
432
        return $ret;
433
    }
434
    }
435
436
    function npgettext($context, $singular, $plural, $number) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
437
    $key = $context.chr(4).$singular;
438
    $ret = $this->ngettext($key, $plural, $number);
439
    if (strpos($ret, "\004") !== false) {
440
        return $singular;
441
    } else {
442
        return $ret;
443
    }
444
445
    }
446
}
447
448
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...
449