Completed
Push — master ( 14b1fe...69643f )
by Mark
04:01
created

ExtractTask::_buildFiles()   C

Complexity

Conditions 11
Paths 19

Size

Total Lines 52

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 11
nc 19
nop 0
dl 0
loc 52
rs 6.9006
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
4
 * Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
5
 *
6
 * Licensed under The MIT License
7
 * For full copyright and license information, please see the LICENSE.txt
8
 * Redistributions of files must retain the above copyright notice.
9
 *
10
 * @copyright     Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
11
 * @link          https://cakephp.org CakePHP(tm) Project
12
 * @since         1.2.0
13
 * @license       https://opensource.org/licenses/mit-license.php MIT License
14
 */
15
namespace Cake\Shell\Task;
16
17
use Cake\Console\Shell;
18
use Cake\Core\App;
19
use Cake\Core\Exception\MissingPluginException;
20
use Cake\Core\Plugin;
21
use Cake\Filesystem\File;
22
use Cake\Filesystem\Folder;
23
use Cake\Utility\Inflector;
24
25
/**
26
 * Language string extractor
27
 */
28
class ExtractTask extends Shell
29
{
30
31
    /**
32
     * Paths to use when looking for strings
33
     *
34
     * @var array
35
     */
36
    protected $_paths = [];
37
38
    /**
39
     * Files from where to extract
40
     *
41
     * @var array
42
     */
43
    protected $_files = [];
44
45
    /**
46
     * Merge all domain strings into the default.pot file
47
     *
48
     * @var bool
49
     */
50
    protected $_merge = false;
51
52
    /**
53
     * Use relative paths in the pot files rather than full path
54
     *
55
     * @var bool
56
     */
57
    protected $_relativePaths = false;
58
59
    /**
60
     * Current file being processed
61
     *
62
     * @var string|null
63
     */
64
    protected $_file;
65
66
    /**
67
     * Contains all content waiting to be write
68
     *
69
     * @var array
70
     */
71
    protected $_storage = [];
72
73
    /**
74
     * Extracted tokens
75
     *
76
     * @var array
77
     */
78
    protected $_tokens = [];
79
80
    /**
81
     * Extracted strings indexed by domain.
82
     *
83
     * @var array
84
     */
85
    protected $_translations = [];
86
87
    /**
88
     * Destination path
89
     *
90
     * @var string|null
91
     */
92
    protected $_output;
93
94
    /**
95
     * An array of directories to exclude.
96
     *
97
     * @var array
98
     */
99
    protected $_exclude = [];
100
101
    /**
102
     * Holds the validation string domain to use for validation messages when extracting
103
     *
104
     * @var string
105
     */
106
    protected $_validationDomain = 'default';
107
108
    /**
109
     * Holds whether this call should extract the CakePHP Lib messages
110
     *
111
     * @var bool
112
     */
113
    protected $_extractCore = false;
114
115
    /**
116
     * Displays marker error(s) if true
117
     * @var bool
118
     */
119
    protected $_markerError;
120
121
    /**
122
     * Count number of marker errors found
123
     * @var bool
124
     */
125
    protected $_countMarkerError = 0;
126
127
    /**
128
     * No welcome message.
129
     *
130
     * @return void
131
     */
132
    protected function _welcome()
133
    {
134
    }
135
136
    /**
137
     * Method to interact with the User and get path selections.
138
     *
139
     * @return void
140
     */
141
    protected function _getPaths()
142
    {
143
        $defaultPath = APP;
144
        while (true) {
145
            $currentPaths = count($this->_paths) > 0 ? $this->_paths : ['None'];
146
            $message = sprintf(
147
                "Current paths: %s\nWhat is the path you would like to extract?\n[Q]uit [D]one",
148
                implode(', ', $currentPaths)
149
            );
150
            $response = $this->in($message, null, $defaultPath);
151
            if (strtoupper($response) === 'Q') {
152
                $this->err('Extract Aborted');
153
                $this->_stop();
154
155
                return;
156
            }
157
            if (strtoupper($response) === 'D' && count($this->_paths)) {
158
                $this->out();
159
160
                return;
161
            }
162
            if (strtoupper($response) === 'D') {
163
                $this->warn('No directories selected. Please choose a directory.');
164
            } elseif (is_dir($response)) {
165
                $this->_paths[] = $response;
166
                $defaultPath = 'D';
167
            } else {
168
                $this->err('The directory path you supplied was not found. Please try again.');
169
            }
170
            $this->out();
171
        }
172
    }
173
174
    /**
175
     * Execution method always used for tasks
176
     *
177
     * @return void
178
     */
179
    public function main()
180
    {
181
        if (!empty($this->params['exclude'])) {
182
            $this->_exclude = explode(',', $this->params['exclude']);
183
        }
184
        if (isset($this->params['files']) && !is_array($this->params['files'])) {
185
            $this->_files = explode(',', $this->params['files']);
186
        }
187
        if (isset($this->params['paths'])) {
188
            $this->_paths = explode(',', $this->params['paths']);
189
        } elseif (isset($this->params['plugin'])) {
190
            $plugin = Inflector::camelize($this->params['plugin']);
191
            if (!Plugin::isLoaded($plugin)) {
192
                throw new MissingPluginException(['plugin' => $plugin]);
193
            }
194
            $this->_paths = [Plugin::classPath($plugin)];
195
            $this->params['plugin'] = $plugin;
196
        } else {
197
            $this->_getPaths();
198
        }
199
200 View Code Duplication
        if (isset($this->params['extract-core'])) {
201
            $this->_extractCore = !(strtolower($this->params['extract-core']) === 'no');
202
        } else {
203
            $response = $this->in('Would you like to extract the messages from the CakePHP core?', ['y', 'n'], 'n');
204
            $this->_extractCore = strtolower($response) === 'y';
205
        }
206
207
        if (!empty($this->params['exclude-plugins']) && $this->_isExtractingApp()) {
208
            $this->_exclude = array_merge($this->_exclude, App::path('Plugin'));
209
        }
210
211
        if (!empty($this->params['validation-domain'])) {
212
            $this->_validationDomain = $this->params['validation-domain'];
213
        }
214
215
        if ($this->_extractCore) {
216
            $this->_paths[] = CAKE;
217
        }
218
219
        if (isset($this->params['output'])) {
220
            $this->_output = $this->params['output'];
221
        } elseif (isset($this->params['plugin'])) {
222
            $this->_output = $this->_paths[0] . 'Locale';
223
        } else {
224
            $message = "What is the path you would like to output?\n[Q]uit";
225
            while (true) {
226
                $response = $this->in($message, null, rtrim($this->_paths[0], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'Locale');
227
                if (strtoupper($response) === 'Q') {
228
                    $this->err('Extract Aborted');
229
                    $this->_stop();
230
231
                    return;
232
                }
233
                if ($this->_isPathUsable($response)) {
234
                    $this->_output = $response . DIRECTORY_SEPARATOR;
235
                    break;
236
                }
237
238
                $this->err('');
239
                $this->err(
240
                    '<error>The directory path you supplied was ' .
241
                    'not found. Please try again.</error>'
242
                );
243
                $this->out();
244
            }
245
        }
246
247 View Code Duplication
        if (isset($this->params['merge'])) {
248
            $this->_merge = !(strtolower($this->params['merge']) === 'no');
249
        } else {
250
            $this->out();
251
            $response = $this->in('Would you like to merge all domain strings into the default.pot file?', ['y', 'n'], 'n');
252
            $this->_merge = strtolower($response) === 'y';
253
        }
254
255
        $this->_markerError = $this->param('marker-error');
256
        $this->_relativePaths = $this->param('relative-paths');
257
258
        if (empty($this->_files)) {
259
            $this->_searchFiles();
260
        }
261
262
        $this->_output = rtrim($this->_output, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
263
        if (!$this->_isPathUsable($this->_output)) {
264
            $this->err(sprintf('The output directory %s was not found or writable.', $this->_output));
265
            $this->_stop();
266
267
            return;
268
        }
269
270
        $this->_extract();
271
    }
272
273
    /**
274
     * Add a translation to the internal translations property
275
     *
276
     * Takes care of duplicate translations
277
     *
278
     * @param string $domain The domain
279
     * @param string $msgid The message string
280
     * @param array $details Context and plural form if any, file and line references
281
     * @return void
282
     */
283
    protected function _addTranslation($domain, $msgid, $details = [])
284
    {
285
        $context = isset($details['msgctxt']) ? $details['msgctxt'] : '';
286
287
        if (empty($this->_translations[$domain][$msgid][$context])) {
288
            $this->_translations[$domain][$msgid][$context] = [
289
                'msgid_plural' => false
290
            ];
291
        }
292
293
        if (isset($details['msgid_plural'])) {
294
            $this->_translations[$domain][$msgid][$context]['msgid_plural'] = $details['msgid_plural'];
295
        }
296
297
        if (isset($details['file'])) {
298
            $line = isset($details['line']) ? $details['line'] : 0;
299
            $this->_translations[$domain][$msgid][$context]['references'][$details['file']][] = $line;
300
        }
301
    }
302
303
    /**
304
     * Extract text
305
     *
306
     * @return void
307
     */
308
    protected function _extract()
309
    {
310
        $this->out();
311
        $this->out();
312
        $this->out('Extracting...');
313
        $this->hr();
314
        $this->out('Paths:');
315
        foreach ($this->_paths as $path) {
316
            $this->out('   ' . $path);
317
        }
318
        $this->out('Output Directory: ' . $this->_output);
319
        $this->hr();
320
        $this->_extractTokens();
321
        $this->_buildFiles();
322
        $this->_writeFiles();
323
        $this->_paths = $this->_files = $this->_storage = [];
324
        $this->_translations = $this->_tokens = [];
325
        $this->out();
326
        if ($this->_countMarkerError) {
327
            $this->err("{$this->_countMarkerError} marker error(s) detected.");
328
            $this->err(" => Use the --marker-error option to display errors.");
329
        }
330
331
        $this->out('Done.');
332
    }
333
334
    /**
335
     * Gets the option parser instance and configures it.
336
     *
337
     * @return \Cake\Console\ConsoleOptionParser
338
     */
339
    public function getOptionParser()
340
    {
341
        $parser = parent::getOptionParser();
342
        $parser->setDescription(
343
            'CakePHP Language String Extraction:'
344
        )->addOption('app', [
345
            'help' => 'Directory where your application is located.'
346
        ])->addOption('paths', [
347
            'help' => 'Comma separated list of paths.'
348
        ])->addOption('merge', [
349
            'help' => 'Merge all domain strings into the default.po file.',
350
            'choices' => ['yes', 'no']
351
        ])->addOption('relative-paths', [
352
            'help' => 'Use relative paths in the .pot file',
353
            'boolean' => true,
354
            'default' => false,
355
        ])->addOption('output', [
356
            'help' => 'Full path to output directory.'
357
        ])->addOption('files', [
358
            'help' => 'Comma separated list of files.'
359
        ])->addOption('exclude-plugins', [
360
            'boolean' => true,
361
            'default' => true,
362
            'help' => 'Ignores all files in plugins if this command is run inside from the same app directory.'
363
        ])->addOption('plugin', [
364
            'help' => 'Extracts tokens only from the plugin specified and puts the result in the plugin\'s Locale directory.'
365
        ])->addOption('ignore-model-validation', [
366
            'boolean' => true,
367
            'default' => false,
368
            'help' => 'Ignores validation messages in the $validate property.' .
369
                ' If this flag is not set and the command is run from the same app directory,' .
370
                ' all messages in model validation rules will be extracted as tokens.'
371
        ])->addOption('validation-domain', [
372
            'help' => 'If set to a value, the localization domain to be used for model validation messages.'
373
        ])->addOption('exclude', [
374
            'help' => 'Comma separated list of directories to exclude.' .
375
                ' Any path containing a path segment with the provided values will be skipped. E.g. test,vendors'
376
        ])->addOption('overwrite', [
377
            'boolean' => true,
378
            'default' => false,
379
            'help' => 'Always overwrite existing .pot files.'
380
        ])->addOption('extract-core', [
381
            'help' => 'Extract messages from the CakePHP core libs.',
382
            'choices' => ['yes', 'no']
383
        ])->addOption('no-location', [
384
            'boolean' => true,
385
            'default' => false,
386
            'help' => 'Do not write file locations for each extracted message.',
387
        ])->addOption('marker-error', [
388
            'boolean' => true,
389
            'default' => false,
390
            'help' => 'Do not display marker error.',
391
        ]);
392
393
        return $parser;
394
    }
395
396
    /**
397
     * Extract tokens out of all files to be processed
398
     *
399
     * @return void
400
     */
401
    protected function _extractTokens()
402
    {
403
        /** @var \Cake\Shell\Helper\ProgressHelper $progress */
404
        $progress = $this->helper('progress');
405
        $progress->init(['total' => count($this->_files)]);
406
        $isVerbose = $this->param('verbose');
407
408
        $functions = [
409
            '__' => ['singular'],
410
            '__n' => ['singular', 'plural'],
411
            '__d' => ['domain', 'singular'],
412
            '__dn' => ['domain', 'singular', 'plural'],
413
            '__x' => ['context', 'singular'],
414
            '__xn' => ['context', 'singular', 'plural'],
415
            '__dx' => ['domain', 'context', 'singular'],
416
            '__dxn' => ['domain', 'context', 'singular', 'plural'],
417
        ];
418
        $pattern = '/(' . implode('|', array_keys($functions)) . ')\s*\(/';
419
420
        foreach ($this->_files as $file) {
421
            $this->_file = $file;
422
            if ($isVerbose) {
423
                $this->out(sprintf('Processing %s...', $file), 1, Shell::VERBOSE);
424
            }
425
426
            $code = file_get_contents($file);
427
428
            if (preg_match($pattern, $code) === 1) {
429
                $allTokens = token_get_all($code);
430
431
                $this->_tokens = [];
432
                foreach ($allTokens as $token) {
433
                    if (!is_array($token) || ($token[0] !== T_WHITESPACE && $token[0] !== T_INLINE_HTML)) {
434
                        $this->_tokens[] = $token;
435
                    }
436
                }
437
                unset($allTokens);
438
439
                foreach ($functions as $functionName => $map) {
440
                    $this->_parse($functionName, $map);
441
                }
442
            }
443
444
            if (!$isVerbose) {
445
                $progress->increment(1);
446
                $progress->draw();
447
            }
448
        }
449
    }
450
451
    /**
452
     * Parse tokens
453
     *
454
     * @param string $functionName Function name that indicates translatable string (e.g: '__')
455
     * @param array $map Array containing what variables it will find (e.g: domain, singular, plural)
456
     * @return void
457
     */
458
    protected function _parse($functionName, $map)
459
    {
460
        $count = 0;
461
        $tokenCount = count($this->_tokens);
462
463
        while (($tokenCount - $count) > 1) {
464
            $countToken = $this->_tokens[$count];
465
            $firstParenthesis = $this->_tokens[$count + 1];
466
            if (!is_array($countToken)) {
467
                $count++;
468
                continue;
469
            }
470
471
            list($type, $string, $line) = $countToken;
472
            if (($type == T_STRING) && ($string === $functionName) && ($firstParenthesis === '(')) {
473
                $position = $count;
474
                $depth = 0;
475
476
                while (!$depth) {
477
                    if ($this->_tokens[$position] === '(') {
478
                        $depth++;
479
                    } elseif ($this->_tokens[$position] === ')') {
480
                        $depth--;
481
                    }
482
                    $position++;
483
                }
484
485
                $mapCount = count($map);
486
                $strings = $this->_getStrings($position, $mapCount);
487
488
                if ($mapCount === count($strings)) {
489
                    $singular = $plural = $context = null;
490
                    /**
491
                     * @var string $singular
492
                     * @var string|null $plural
493
                     * @var string|null $context
494
                     */
495
                    extract(array_combine($map, $strings));
0 ignored issues
show
Bug introduced by
array_combine($map, $strings) cannot be passed to extract() as the parameter $var_array expects a reference.
Loading history...
496
                    $domain = isset($domain) ? $domain : 'default';
497
                    $details = [
498
                        'file' => $this->_file,
499
                        'line' => $line,
500
                    ];
501
                    if ($this->_relativePaths) {
502
                        $details['file'] = '.' . str_replace(ROOT, '', $details['file']);
503
                    }
504
                    if ($plural !== null) {
505
                        $details['msgid_plural'] = $plural;
506
                    }
507
                    if ($context !== null) {
508
                        $details['msgctxt'] = $context;
509
                    }
510
                    $this->_addTranslation($domain, $singular, $details);
511
                } else {
512
                    $this->_markerError($this->_file, $line, $functionName, $count);
513
                }
514
            }
515
            $count++;
516
        }
517
    }
518
519
    /**
520
     * Build the translate template file contents out of obtained strings
521
     *
522
     * @return void
523
     */
524
    protected function _buildFiles()
525
    {
526
        $paths = $this->_paths;
527
        $paths[] = realpath(APP) . DIRECTORY_SEPARATOR;
528
529
        usort($paths, function ($a, $b) {
530
            return strlen($a) - strlen($b);
531
        });
532
533
        foreach ($this->_translations as $domain => $translations) {
534
            foreach ($translations as $msgid => $contexts) {
535
                foreach ($contexts as $context => $details) {
536
                    $plural = $details['msgid_plural'];
537
                    $files = $details['references'];
538
                    $header = '';
539
540
                    if (!$this->param('no-location')) {
541
                        $occurrences = [];
542
                        foreach ($files as $file => $lines) {
543
                            $lines = array_unique($lines);
544
                            foreach ($lines as $line) {
545
                                $occurrences[] = $file . ':' . $line;
546
                            }
547
                        }
548
                        $occurrences = implode("\n#: ", $occurrences);
549
550
                        $header = '#: ' . str_replace(DIRECTORY_SEPARATOR, '/', str_replace($paths, '', $occurrences)) . "\n";
551
                    }
552
553
                    $sentence = '';
554
                    if ($context !== '') {
555
                        $sentence .= "msgctxt \"{$context}\"\n";
556
                    }
557
                    if ($plural === false) {
558
                        $sentence .= "msgid \"{$msgid}\"\n";
559
                        $sentence .= "msgstr \"\"\n\n";
560
                    } else {
561
                        $sentence .= "msgid \"{$msgid}\"\n";
562
                        $sentence .= "msgid_plural \"{$plural}\"\n";
563
                        $sentence .= "msgstr[0] \"\"\n";
564
                        $sentence .= "msgstr[1] \"\"\n\n";
565
                    }
566
567
                    if ($domain !== 'default' && $this->_merge) {
568
                        $this->_store('default', $header, $sentence);
569
                    } else {
570
                        $this->_store($domain, $header, $sentence);
571
                    }
572
                }
573
            }
574
        }
575
    }
576
577
    /**
578
     * Prepare a file to be stored
579
     *
580
     * @param string $domain The domain
581
     * @param string $header The header content.
582
     * @param string $sentence The sentence to store.
583
     * @return void
584
     */
585
    protected function _store($domain, $header, $sentence)
586
    {
587
        if (!isset($this->_storage[$domain])) {
588
            $this->_storage[$domain] = [];
589
        }
590
        if (!isset($this->_storage[$domain][$sentence])) {
591
            $this->_storage[$domain][$sentence] = $header;
592
        } else {
593
            $this->_storage[$domain][$sentence] .= $header;
594
        }
595
    }
596
597
    /**
598
     * Write the files that need to be stored
599
     *
600
     * @return void
601
     */
602
    protected function _writeFiles()
603
    {
604
        $overwriteAll = false;
605
        if (!empty($this->params['overwrite'])) {
606
            $overwriteAll = true;
607
        }
608
        foreach ($this->_storage as $domain => $sentences) {
609
            $output = $this->_writeHeader();
610
            foreach ($sentences as $sentence => $header) {
611
                $output .= $header . $sentence;
612
            }
613
614
            // Remove vendor prefix if present.
615
            $slashPosition = strpos($domain, '/');
616
            if ($slashPosition !== false) {
617
                $domain = substr($domain, $slashPosition + 1);
618
            }
619
620
            $filename = str_replace('/', '_', $domain) . '.pot';
621
            $File = new File($this->_output . $filename);
622
            $response = '';
623
            while ($overwriteAll === false && $File->exists() && strtoupper($response) !== 'Y') {
624
                $this->out();
625
                $response = $this->in(
626
                    sprintf('Error: %s already exists in this location. Overwrite? [Y]es, [N]o, [A]ll', $filename),
627
                    ['y', 'n', 'a'],
628
                    'y'
629
                );
630
                if (strtoupper($response) === 'N') {
631
                    $response = '';
632
                    while (!$response) {
633
                        $response = $this->in('What would you like to name this file?', null, 'new_' . $filename);
634
                        $File = new File($this->_output . $response);
635
                        $filename = $response;
636
                    }
637
                } elseif (strtoupper($response) === 'A') {
638
                    $overwriteAll = true;
639
                }
640
            }
641
            $File->write($output);
642
            $File->close();
643
        }
644
    }
645
646
    /**
647
     * Build the translation template header
648
     *
649
     * @return string Translation template header
650
     */
651
    protected function _writeHeader()
652
    {
653
        $output = "# LANGUAGE translation of CakePHP Application\n";
654
        $output .= "# Copyright YEAR NAME <EMAIL@ADDRESS>\n";
655
        $output .= "#\n";
656
        $output .= "#, fuzzy\n";
657
        $output .= "msgid \"\"\n";
658
        $output .= "msgstr \"\"\n";
659
        $output .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
660
        $output .= '"POT-Creation-Date: ' . date('Y-m-d H:iO') . "\\n\"\n";
661
        $output .= "\"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\\n\"\n";
662
        $output .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
663
        $output .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n";
664
        $output .= "\"MIME-Version: 1.0\\n\"\n";
665
        $output .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
666
        $output .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
667
        $output .= "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n\n";
668
669
        return $output;
670
    }
671
672
    /**
673
     * Get the strings from the position forward
674
     *
675
     * @param int $position Actual position on tokens array
676
     * @param int $target Number of strings to extract
677
     * @return array Strings extracted
678
     */
679
    protected function _getStrings(&$position, $target)
680
    {
681
        $strings = [];
682
        $count = count($strings);
683
        while ($count < $target && ($this->_tokens[$position] === ',' || $this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING || $this->_tokens[$position][0] == T_LNUMBER)) {
684
            $count = count($strings);
685
            if ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING && $this->_tokens[$position + 1] === '.') {
686
                $string = '';
687
                while ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING || $this->_tokens[$position] === '.') {
688 View Code Duplication
                    if ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING) {
689
                        $string .= $this->_formatString($this->_tokens[$position][1]);
690
                    }
691
                    $position++;
692
                }
693
                $strings[] = $string;
694 View Code Duplication
            } elseif ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING) {
695
                $strings[] = $this->_formatString($this->_tokens[$position][1]);
696
            } elseif ($this->_tokens[$position][0] == T_LNUMBER) {
697
                $strings[] = $this->_tokens[$position][1];
698
            }
699
            $position++;
700
        }
701
702
        return $strings;
703
    }
704
705
    /**
706
     * Format a string to be added as a translatable string
707
     *
708
     * @param string $string String to format
709
     * @return string Formatted string
710
     */
711
    protected function _formatString($string)
712
    {
713
        $quote = substr($string, 0, 1);
714
        $string = substr($string, 1, -1);
715
        if ($quote === '"') {
716
            $string = stripcslashes($string);
717
        } else {
718
            $string = strtr($string, ["\\'" => "'", '\\\\' => '\\']);
719
        }
720
        $string = str_replace("\r\n", "\n", $string);
721
722
        return addcslashes($string, "\0..\37\\\"");
723
    }
724
725
    /**
726
     * Indicate an invalid marker on a processed file
727
     *
728
     * @param string $file File where invalid marker resides
729
     * @param int $line Line number
730
     * @param string $marker Marker found
731
     * @param int $count Count
732
     * @return void
733
     */
734
    protected function _markerError($file, $line, $marker, $count)
735
    {
736
        if (strpos($this->_file, CAKE_CORE_INCLUDE_PATH) === false) {
737
            $this->_countMarkerError++;
738
        }
739
740
        if (!$this->_markerError) {
741
            return;
742
        }
743
744
        $this->err(sprintf("Invalid marker content in %s:%s\n* %s(", $file, $line, $marker));
745
        $count += 2;
746
        $tokenCount = count($this->_tokens);
747
        $parenthesis = 1;
748
749
        while ((($tokenCount - $count) > 0) && $parenthesis) {
750
            if (is_array($this->_tokens[$count])) {
751
                $this->err($this->_tokens[$count][1], false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
752
            } else {
753
                $this->err($this->_tokens[$count], false);
0 ignored issues
show
Documentation introduced by
false is of type boolean, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
754
                if ($this->_tokens[$count] === '(') {
755
                    $parenthesis++;
756
                }
757
758
                if ($this->_tokens[$count] === ')') {
759
                    $parenthesis--;
760
                }
761
            }
762
            $count++;
763
        }
764
        $this->err("\n", true);
0 ignored issues
show
Documentation introduced by
true is of type boolean, but the function expects a integer.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
765
    }
766
767
    /**
768
     * Search files that may contain translatable strings
769
     *
770
     * @return void
771
     */
772
    protected function _searchFiles()
773
    {
774
        $pattern = false;
775
        if (!empty($this->_exclude)) {
776
            $exclude = [];
777
            foreach ($this->_exclude as $e) {
778
                if (DIRECTORY_SEPARATOR !== '\\' && $e[0] !== DIRECTORY_SEPARATOR) {
779
                    $e = DIRECTORY_SEPARATOR . $e;
780
                }
781
                $exclude[] = preg_quote($e, '/');
782
            }
783
            $pattern = '/' . implode('|', $exclude) . '/';
784
        }
785
        foreach ($this->_paths as $path) {
786
            $path = realpath($path) . DIRECTORY_SEPARATOR;
787
            $Folder = new Folder($path);
788
            $files = $Folder->findRecursive('.*\.(php|ctp|thtml|inc|tpl)', true);
789
            if (!empty($pattern)) {
790
                $files = preg_grep($pattern, $files, PREG_GREP_INVERT);
791
                $files = array_values($files);
792
            }
793
            $this->_files = array_merge($this->_files, $files);
794
        }
795
        $this->_files = array_unique($this->_files);
796
    }
797
798
    /**
799
     * Returns whether this execution is meant to extract string only from directories in folder represented by the
800
     * APP constant, i.e. this task is extracting strings from same application.
801
     *
802
     * @return bool
803
     */
804
    protected function _isExtractingApp()
805
    {
806
        return $this->_paths === [APP];
807
    }
808
809
    /**
810
     * Checks whether or not a given path is usable for writing.
811
     *
812
     * @param string $path Path to folder
813
     * @return bool true if it exists and is writable, false otherwise
814
     */
815
    protected function _isPathUsable($path)
816
    {
817
        if (!is_dir($path)) {
818
            mkdir($path, 0770, true);
819
        }
820
821
        return is_dir($path) && is_writable($path);
822
    }
823
}
824