PoStreamReader::readHeader()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 7
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 10
rs 9.4285
1
<?php
2
3
/**
4
 * @file
5
 * Contains \Drupal\Component\Gettext\PoStreamReader.
6
 */
7
8
namespace Drupal\Component\Gettext;
9
10
use Drupal\Component\Utility\SafeMarkup;
11
12
/**
13
 * Implements Gettext PO stream reader.
14
 *
15
 * The PO file format parsing is implemented according to the documentation at
16
 * http://www.gnu.org/software/gettext/manual/gettext.html#PO-Files
17
 */
18
class PoStreamReader implements PoStreamInterface, PoReaderInterface {
19
20
  /**
21
   * Source line number of the stream being parsed.
22
   *
23
   * @var int
24
   */
25
  private $_line_number = 0;
26
27
  /**
28
   * Parser context for the stream reader state machine.
29
   *
30
   * Possible contexts are:
31
   *  - 'COMMENT' (#)
32
   *  - 'MSGID' (msgid)
33
   *  - 'MSGID_PLURAL' (msgid_plural)
34
   *  - 'MSGCTXT' (msgctxt)
35
   *  - 'MSGSTR' (msgstr or msgstr[])
36
   *  - 'MSGSTR_ARR' (msgstr_arg)
37
   *
38
   * @var string
39
   */
40
  private $_context = 'COMMENT';
41
42
  /**
43
   * Current entry being read. Incomplete.
44
   *
45
   * @var array
46
   */
47
  private $_current_item = array();
48
49
  /**
50
   * Current plural index for plural translations.
51
   *
52
   * @var int
53
   */
54
  private $_current_plural_index = 0;
55
56
  /**
57
   * URI of the PO stream that is being read.
58
   *
59
   * @var string
60
   */
61
  private $_uri = '';
62
63
  /**
64
   * Language code for the PO stream being read.
65
   *
66
   * @var string
67
   */
68
  private $_langcode = NULL;
69
70
  /**
71
   * File handle of the current PO stream.
72
   *
73
   * @var resource
74
   */
75
  private $_fd;
76
77
  /**
78
   * The PO stream header.
79
   *
80
   * @var \Drupal\Component\Gettext\PoHeader
81
   */
82
  private $_header;
83
84
  /**
85
   * Object wrapper for the last read source/translation pair.
86
   *
87
   * @var \Drupal\Component\Gettext\PoItem
88
   */
89
  private $_last_item;
90
91
  /**
92
   * Indicator of whether the stream reading is finished.
93
   *
94
   * @var bool
95
   */
96
  private $_finished;
97
98
  /**
99
   * Array of translated error strings recorded on reading this stream so far.
100
   *
101
   * @var array
102
   */
103
  private $_errors;
104
105
  /**
106
   * {@inheritdoc}
107
   */
108
  public function getLangcode() {
109
    return $this->_langcode;
110
  }
111
112
  /**
113
   * {@inheritdoc}
114
   */
115
  public function setLangcode($langcode) {
116
    $this->_langcode = $langcode;
117
  }
118
119
  /**
120
   * {@inheritdoc}
121
   */
122
  public function getHeader() {
123
    return $this->_header;
124
  }
125
126
  /**
127
   * Implements Drupal\Component\Gettext\PoMetadataInterface::setHeader().
128
   *
129
   * Not applicable to stream reading and therefore not implemented.
130
   */
131
  public function setHeader(PoHeader $header) {
132
  }
133
134
  /**
135
   * {@inheritdoc}
136
   */
137
  public function getURI() {
138
    return $this->_uri;
139
  }
140
141
  /**
142
   * {@inheritdoc}
143
   */
144
  public function setURI($uri) {
145
    $this->_uri = $uri;
146
  }
147
148
  /**
149
   * Implements Drupal\Component\Gettext\PoStreamInterface::open().
150
   *
151
   * Opens the stream and reads the header. The stream is ready for reading
152
   * items after.
153
   *
154
   * @throws Exception
155
   *   If the URI is not yet set.
156
   */
157
  public function open() {
158
    if (!empty($this->_uri)) {
159
      $this->_fd = fopen($this->_uri, 'rb');
160
      $this->readHeader();
161
    }
162
    else {
163
      throw new \Exception('Cannot open stream without URI set.');
164
    }
165
  }
166
167
  /**
168
   * Implements Drupal\Component\Gettext\PoStreamInterface::close().
169
   *
170
   * @throws Exception
171
   *   If the stream is not open.
172
   */
173
  public function close() {
174
    if ($this->_fd) {
175
      fclose($this->_fd);
176
    }
177
    else {
178
      throw new \Exception('Cannot close stream that is not open.');
179
    }
180
  }
181
182
  /**
183
   * {@inheritdoc}
184
   */
185
  public function readItem() {
186
    // Clear out the last item.
187
    $this->_last_item = NULL;
188
189
    // Read until finished with the stream or a complete item was identified.
190
    while (!$this->_finished && is_null($this->_last_item)) {
191
      $this->readLine();
192
    }
193
194
    return $this->_last_item;
195
  }
196
197
  /**
198
   * Sets the seek position for the current PO stream.
199
   *
200
   * @param int $seek
201
   *   The new seek position to set.
202
   */
203
  public function setSeek($seek) {
204
    fseek($this->_fd, $seek);
205
  }
206
207
  /**
208
   * Gets the pointer position of the current PO stream.
209
   */
210
  public function getSeek() {
211
    return ftell($this->_fd);
212
  }
213
214
  /**
215
   * Read the header from the PO stream.
216
   *
217
   * The header is a special case PoItem, using the empty string as source and
218
   * key-value pairs as translation. We just reuse the item reader logic to
219
   * read the header.
220
   */
221
  private function readHeader() {
222
    $item = $this->readItem();
223
    // Handle the case properly when the .po file is empty (0 bytes).
224
    if (!$item) {
225
      return;
226
    }
227
    $header = new PoHeader;
228
    $header->setFromString(trim($item->getTranslation()));
229
    $this->_header = $header;
230
  }
231
232
  /**
233
   * Reads a line from the PO stream and stores data internally.
234
   *
235
   * Expands $this->_current_item based on new data for the current item. If
236
   * this line ends the current item, it is saved with setItemFromArray() with
237
   * data from $this->_current_item.
238
   *
239
   * An internal state machine is maintained in this reader using
240
   * $this->_context as the reading state. PO items are in between COMMENT
241
   * states (when items have at least one line or comment in between them) or
242
   * indicated by MSGSTR or MSGSTR_ARR followed immediately by an MSGID or
243
   * MSGCTXT (when items closely follow each other).
244
   *
245
   * @return
246
   *   FALSE if an error was logged, NULL otherwise. The errors are considered
247
   *   non-blocking, so reading can continue, while the errors are collected
248
   *   for later presentation.
249
   */
250
  private function readLine() {
251
    // Read a line and set the stream finished indicator if it was not
252
    // possible anymore.
253
    $line = fgets($this->_fd);
254
    $this->_finished = ($line === FALSE);
255
256
    if (!$this->_finished) {
257
258
      if ($this->_line_number == 0) {
259
        // The first line might come with a UTF-8 BOM, which should be removed.
260
        $line = str_replace("\xEF\xBB\xBF", '', $line);
261
        // Current plurality for 'msgstr[]'.
262
        $this->_current_plural_index = 0;
263
      }
264
265
      // Track the line number for error reporting.
266
      $this->_line_number++;
267
268
      // Initialize common values for error logging.
269
      $log_vars = array(
270
        '%uri' => $this->getURI(),
271
        '%line' => $this->_line_number,
272
      );
273
274
      // Trim away the linefeed. \\n might appear at the end of the string if
275
      // another line continuing the same string follows. We can remove that.
276
      $line = trim(strtr($line, array("\\\n" => "")));
277
278
      if (!strncmp('#', $line, 1)) {
279
        // Lines starting with '#' are comments.
280
281
        if ($this->_context == 'COMMENT') {
282
          // Already in comment context, add to current comment.
283
          $this->_current_item['#'][] = substr($line, 1);
284
        }
285
        elseif (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
286
          // We are currently in string context, save current item.
287
          $this->setItemFromArray($this->_current_item);
288
289
          // Start a new entry for the comment.
290
          $this->_current_item = array();
291
          $this->_current_item['#'][] = substr($line, 1);
292
293
          $this->_context = 'COMMENT';
294
          return;
295
        }
296
        else {
297
          // A comment following any other context is a syntax error.
298
          $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgstr" was expected but not found on line %line.', $log_vars);
0 ignored issues
show
Deprecated Code introduced by
The method Drupal\Component\Utility\SafeMarkup::format() has been deprecated with message: in Drupal 8.0.0, will be removed before Drupal 9.0.0. Use \Drupal\Component\Render\FormattableMarkup.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
299
          return FALSE;
300
        }
301
        return;
302
      }
303
      elseif (!strncmp('msgid_plural', $line, 12)) {
304
        // A plural form for the current source string.
305
306
        if ($this->_context != 'MSGID') {
307
          // A plural form can only be added to an msgid directly.
308
          $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgid_plural" was expected but not found on line %line.', $log_vars);
0 ignored issues
show
Deprecated Code introduced by
The method Drupal\Component\Utility\SafeMarkup::format() has been deprecated with message: in Drupal 8.0.0, will be removed before Drupal 9.0.0. Use \Drupal\Component\Render\FormattableMarkup.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
309
          return FALSE;
310
        }
311
312
        // Remove 'msgid_plural' and trim away whitespace.
313
        $line = trim(substr($line, 12));
314
315
        // Only the plural source string is left, parse it.
316
        $quoted = $this->parseQuoted($line);
317
        if ($quoted === FALSE) {
318
          // The plural form must be wrapped in quotes.
319
          $this->_errors[] = SafeMarkup::format('The translation stream %uri contains a syntax error on line %line.', $log_vars);
0 ignored issues
show
Deprecated Code introduced by
The method Drupal\Component\Utility\SafeMarkup::format() has been deprecated with message: in Drupal 8.0.0, will be removed before Drupal 9.0.0. Use \Drupal\Component\Render\FormattableMarkup.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
320
          return FALSE;
321
        }
322
323
        // Append the plural source to the current entry.
324
        if (is_string($this->_current_item['msgid'])) {
325
          // The first value was stored as string. Now we know the context is
326
          // plural, it is converted to array.
327
          $this->_current_item['msgid'] = array($this->_current_item['msgid']);
328
        }
329
        $this->_current_item['msgid'][] = $quoted;
330
331
        $this->_context = 'MSGID_PLURAL';
332
        return;
333
      }
334 View Code Duplication
      elseif (!strncmp('msgid', $line, 5)) {
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...
335
        // Starting a new message.
336
337
        if (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
338
          // We are currently in string context, save current item.
339
          $this->setItemFromArray($this->_current_item);
340
341
          // Start a new context for the msgid.
342
          $this->_current_item = array();
343
        }
344
        elseif ($this->_context == 'MSGID') {
345
          // We are currently already in the context, meaning we passed an id with no data.
346
          $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgid" is unexpected on line %line.', $log_vars);
0 ignored issues
show
Deprecated Code introduced by
The method Drupal\Component\Utility\SafeMarkup::format() has been deprecated with message: in Drupal 8.0.0, will be removed before Drupal 9.0.0. Use \Drupal\Component\Render\FormattableMarkup.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
347
          return FALSE;
348
        }
349
350
        // Remove 'msgid' and trim away whitespace.
351
        $line = trim(substr($line, 5));
352
353
        // Only the message id string is left, parse it.
354
        $quoted = $this->parseQuoted($line);
355
        if ($quoted === FALSE) {
356
          // The message id must be wrapped in quotes.
357
          $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgid" on line %line.', $log_vars, $log_vars);
0 ignored issues
show
Unused Code introduced by
The call to SafeMarkup::format() has too many arguments starting with $log_vars.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
Deprecated Code introduced by
The method Drupal\Component\Utility\SafeMarkup::format() has been deprecated with message: in Drupal 8.0.0, will be removed before Drupal 9.0.0. Use \Drupal\Component\Render\FormattableMarkup.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
358
          return FALSE;
359
        }
360
361
        $this->_current_item['msgid'] = $quoted;
362
        $this->_context = 'MSGID';
363
        return;
364
      }
365 View Code Duplication
      elseif (!strncmp('msgctxt', $line, 7)) {
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...
366
        // Starting a new context.
367
368
        if (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
369
          // We are currently in string context, save current item.
370
          $this->setItemFromArray($this->_current_item);
371
          $this->_current_item = array();
372
        }
373
        elseif (!empty($this->_current_item['msgctxt'])) {
374
          // A context cannot apply to another context.
375
          $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgctxt" is unexpected on line %line.', $log_vars);
0 ignored issues
show
Deprecated Code introduced by
The method Drupal\Component\Utility\SafeMarkup::format() has been deprecated with message: in Drupal 8.0.0, will be removed before Drupal 9.0.0. Use \Drupal\Component\Render\FormattableMarkup.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
376
          return FALSE;
377
        }
378
379
        // Remove 'msgctxt' and trim away whitespaces.
380
        $line = trim(substr($line, 7));
381
382
        // Only the msgctxt string is left, parse it.
383
        $quoted = $this->parseQuoted($line);
384
        if ($quoted === FALSE) {
385
          // The context string must be quoted.
386
          $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgctxt" on line %line.', $log_vars);
0 ignored issues
show
Deprecated Code introduced by
The method Drupal\Component\Utility\SafeMarkup::format() has been deprecated with message: in Drupal 8.0.0, will be removed before Drupal 9.0.0. Use \Drupal\Component\Render\FormattableMarkup.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
387
          return FALSE;
388
        }
389
390
        $this->_current_item['msgctxt'] = $quoted;
391
392
        $this->_context = 'MSGCTXT';
393
        return;
394
      }
395
      elseif (!strncmp('msgstr[', $line, 7)) {
396
        // A message string for a specific plurality.
397
398
        if (($this->_context != 'MSGID') &&
399
            ($this->_context != 'MSGCTXT') &&
400
            ($this->_context != 'MSGID_PLURAL') &&
401
            ($this->_context != 'MSGSTR_ARR')) {
402
          // Plural message strings must come after msgid, msgxtxt,
403
          // msgid_plural, or other msgstr[] entries.
404
          $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgstr[]" is unexpected on line %line.', $log_vars);
0 ignored issues
show
Deprecated Code introduced by
The method Drupal\Component\Utility\SafeMarkup::format() has been deprecated with message: in Drupal 8.0.0, will be removed before Drupal 9.0.0. Use \Drupal\Component\Render\FormattableMarkup.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
405
          return FALSE;
406
        }
407
408
        // Ensure the plurality is terminated.
409
        if (strpos($line, ']') === FALSE) {
410
          $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgstr[]" on line %line.', $log_vars);
0 ignored issues
show
Deprecated Code introduced by
The method Drupal\Component\Utility\SafeMarkup::format() has been deprecated with message: in Drupal 8.0.0, will be removed before Drupal 9.0.0. Use \Drupal\Component\Render\FormattableMarkup.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
411
          return FALSE;
412
        }
413
414
        // Extract the plurality.
415
        $frombracket = strstr($line, '[');
416
        $this->_current_plural_index = substr($frombracket, 1, strpos($frombracket, ']') - 1);
0 ignored issues
show
Documentation Bug introduced by
The property $_current_plural_index was declared of type integer, but substr($frombracket, 1, ...$frombracket, ']') - 1) is of type string. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
417
418
        // Skip to the next whitespace and trim away any further whitespace,
419
        // bringing $line to the message text only.
420
        $line = trim(strstr($line, " "));
421
422
        $quoted = $this->parseQuoted($line);
423
        if ($quoted === FALSE) {
424
          // The string must be quoted.
425
          $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgstr[]" on line %line.', $log_vars);
0 ignored issues
show
Deprecated Code introduced by
The method Drupal\Component\Utility\SafeMarkup::format() has been deprecated with message: in Drupal 8.0.0, will be removed before Drupal 9.0.0. Use \Drupal\Component\Render\FormattableMarkup.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
426
          return FALSE;
427
        }
428
        if (!isset($this->_current_item['msgstr']) || !is_array($this->_current_item['msgstr'])) {
429
          $this->_current_item['msgstr'] = array();
430
        }
431
432
        $this->_current_item['msgstr'][$this->_current_plural_index] = $quoted;
433
434
        $this->_context = 'MSGSTR_ARR';
435
        return;
436
      }
437
      elseif (!strncmp("msgstr", $line, 6)) {
438
        // A string pair for an msgid (with optional context).
439
440
        if (($this->_context != 'MSGID') && ($this->_context != 'MSGCTXT')) {
441
          // Strings are only valid within an id or context scope.
442
          $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: "msgstr" is unexpected on line %line.', $log_vars);
0 ignored issues
show
Deprecated Code introduced by
The method Drupal\Component\Utility\SafeMarkup::format() has been deprecated with message: in Drupal 8.0.0, will be removed before Drupal 9.0.0. Use \Drupal\Component\Render\FormattableMarkup.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
443
          return FALSE;
444
        }
445
446
        // Remove 'msgstr' and trim away away whitespaces.
447
        $line = trim(substr($line, 6));
448
449
        // Only the msgstr string is left, parse it.
450
        $quoted = $this->parseQuoted($line);
451
        if ($quoted === FALSE) {
452
          // The string must be quoted.
453
          $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: invalid format for "msgstr" on line %line.', $log_vars);
0 ignored issues
show
Deprecated Code introduced by
The method Drupal\Component\Utility\SafeMarkup::format() has been deprecated with message: in Drupal 8.0.0, will be removed before Drupal 9.0.0. Use \Drupal\Component\Render\FormattableMarkup.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
454
          return FALSE;
455
        }
456
457
        $this->_current_item['msgstr'] = $quoted;
458
459
        $this->_context = 'MSGSTR';
460
        return;
461
      }
462
      elseif ($line != '') {
463
        // Anything that is not a token may be a continuation of a previous token.
464
465
        $quoted = $this->parseQuoted($line);
466
        if ($quoted === FALSE) {
467
          // This string must be quoted.
468
          $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: string continuation expected on line %line.', $log_vars);
0 ignored issues
show
Deprecated Code introduced by
The method Drupal\Component\Utility\SafeMarkup::format() has been deprecated with message: in Drupal 8.0.0, will be removed before Drupal 9.0.0. Use \Drupal\Component\Render\FormattableMarkup.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
469
          return FALSE;
470
        }
471
472
        // Append the string to the current item.
473
        if (($this->_context == 'MSGID') || ($this->_context == 'MSGID_PLURAL')) {
474
          if (is_array($this->_current_item['msgid'])) {
475
            // Add string to last array element for plural sources.
476
            $last_index = count($this->_current_item['msgid']) - 1;
477
            $this->_current_item['msgid'][$last_index] .= $quoted;
478
          }
479
          else {
480
            // Singular source, just append the string.
481
            $this->_current_item['msgid'] .= $quoted;
482
          }
483
        }
484
        elseif ($this->_context == 'MSGCTXT') {
485
          // Multiline context name.
486
          $this->_current_item['msgctxt'] .= $quoted;
487
        }
488
        elseif ($this->_context == 'MSGSTR') {
489
          // Multiline translation string.
490
          $this->_current_item['msgstr'] .= $quoted;
491
        }
492
        elseif ($this->_context == 'MSGSTR_ARR') {
493
          // Multiline plural translation string.
494
          $this->_current_item['msgstr'][$this->_current_plural_index] .= $quoted;
495
        }
496
        else {
497
          // No valid context to append to.
498
          $this->_errors[] = SafeMarkup::format('The translation stream %uri contains an error: unexpected string on line %line.', $log_vars);
0 ignored issues
show
Deprecated Code introduced by
The method Drupal\Component\Utility\SafeMarkup::format() has been deprecated with message: in Drupal 8.0.0, will be removed before Drupal 9.0.0. Use \Drupal\Component\Render\FormattableMarkup.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
499
          return FALSE;
500
        }
501
        return;
502
      }
503
    }
504
505
    // Empty line read or EOF of PO stream, close out the last entry.
506
    if (($this->_context == 'MSGSTR') || ($this->_context == 'MSGSTR_ARR')) {
507
      $this->setItemFromArray($this->_current_item);
508
      $this->_current_item = array();
509
    }
510
    elseif ($this->_context != 'COMMENT') {
511
      $this->_errors[] = SafeMarkup::format('The translation stream %uri ended unexpectedly at line %line.', $log_vars);
0 ignored issues
show
Bug introduced by
The variable $log_vars does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Deprecated Code introduced by
The method Drupal\Component\Utility\SafeMarkup::format() has been deprecated with message: in Drupal 8.0.0, will be removed before Drupal 9.0.0. Use \Drupal\Component\Render\FormattableMarkup.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
512
      return FALSE;
513
    }
514
  }
515
516
  /**
517
   * Store the parsed values as a PoItem object.
518
   */
519
  public function setItemFromArray($value) {
520
    $plural = FALSE;
521
522
    $comments = '';
523
    if (isset($value['#'])) {
524
      $comments = $this->shortenComments($value['#']);
525
    }
526
527
    if (is_array($value['msgstr'])) {
528
      // Sort plural variants by their form index.
529
      ksort($value['msgstr']);
530
      $plural = TRUE;
531
    }
532
533
    $item = new PoItem();
534
    $item->setContext(isset($value['msgctxt']) ? $value['msgctxt'] : '');
535
    $item->setSource($value['msgid']);
536
    $item->setTranslation($value['msgstr']);
537
    $item->setPlural($plural);
538
    $item->setComment($comments);
539
    $item->setLangcode($this->_langcode);
540
541
    $this->_last_item = $item;
542
543
    $this->_context = 'COMMENT';
544
  }
545
546
  /**
547
   * Parses a string in quotes.
548
   *
549
   * @param $string
550
   *   A string specified with enclosing quotes.
551
   *
552
   * @return
553
   *   The string parsed from inside the quotes.
554
   */
555
  function parseQuoted($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...
556
    if (substr($string, 0, 1) != substr($string, -1, 1)) {
557
      // Start and end quotes must be the same.
558
      return FALSE;
559
    }
560
    $quote = substr($string, 0, 1);
561
    $string = substr($string, 1, -1);
562
    if ($quote == '"') {
563
      // Double quotes: strip slashes.
564
      return stripcslashes($string);
565
    }
566
    elseif ($quote == "'") {
567
      // Simple quote: return as-is.
568
      return $string;
569
    }
570
    else {
571
      // Unrecognized quote.
572
      return FALSE;
573
    }
574
  }
575
576
  /**
577
   * Generates a short, one-string version of the passed comment array.
578
   *
579
   * @param $comment
580
   *   An array of strings containing a comment.
581
   *
582
   * @return
583
   *   Short one-string version of the comment.
584
   */
585
  private function shortenComments($comment) {
586
    $comm = '';
587
    while (count($comment)) {
588
      $test = $comm . substr(array_shift($comment), 1) . ', ';
589
      if (strlen($comm) < 130) {
590
        $comm = $test;
591
      }
592
      else {
593
        break;
594
      }
595
    }
596
    return trim(substr($comm, 0, -2));
597
  }
598
599
}
600