CakePHP
  • Documentation
    • Book
    • API
    • Videos
    • Logos & Trademarks
  • Business Solutions
  • Swag
  • Road Trip
  • Team
  • Community
    • Community
    • Team
    • Issues (Github)
    • YouTube Channel
    • Get Involved
    • Bakery
    • Featured Resources
    • Newsletter
    • Certification
    • My CakePHP
    • CakeFest
    • Facebook
    • Twitter
    • Help & Support
    • Forum
    • Stack Overflow
    • IRC
    • Slack
    • Paid Support
CakePHP

C CakePHP 3.7 Red Velvet API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 3.7
      • 3.7
      • 3.6
      • 3.5
      • 3.4
      • 3.3
      • 3.2
      • 3.1
      • 3.0
      • 2.10
      • 2.9
      • 2.8
      • 2.7
      • 2.6
      • 2.5
      • 2.4
      • 2.3
      • 2.2
      • 2.1
      • 2.0
      • 1.3
      • 1.2

Namespaces

  • Cake
    • Auth
      • Storage
    • Cache
      • Engine
    • Collection
      • Iterator
    • Command
    • Console
      • Exception
    • Controller
      • Component
      • Exception
    • Core
      • Configure
        • Engine
      • Exception
      • Retry
    • Database
      • Driver
      • Exception
      • Expression
      • Schema
      • Statement
      • Type
    • Datasource
      • Exception
    • Error
      • Middleware
    • Event
      • Decorator
    • Filesystem
    • Form
    • Http
      • Client
        • Adapter
        • Auth
      • Cookie
      • Exception
      • Middleware
      • Session
    • I18n
      • Formatter
      • Middleware
      • Parser
    • Log
      • Engine
    • Mailer
      • Exception
      • Transport
    • Network
      • Exception
    • ORM
      • Association
      • Behavior
        • Translate
      • Exception
      • Locator
      • Rule
    • Routing
      • Exception
      • Filter
      • Middleware
      • Route
    • Shell
      • Helper
      • Task
    • TestSuite
      • Fixture
      • Stub
    • Utility
      • Exception
    • Validation
    • View
      • Exception
      • Form
      • Helper
      • Widget
  • None

Classes

  • AssetsTask
  • CommandTask
  • ExtractTask
  • LoadTask
  • UnloadTask
  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:         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:         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));
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:                     $occurrences = [];
539:                     foreach ($files as $file => $lines) {
540:                         $lines = array_unique($lines);
541:                         $occurrences[] = $file . ':' . implode(';', $lines);
542:                     }
543:                     $occurrences = implode("\n#: ", $occurrences);
544:                     $header = '';
545:                     if (!$this->param('no-location')) {
546:                         $header = '#: ' . str_replace(DIRECTORY_SEPARATOR, '/', str_replace($paths, '', $occurrences)) . "\n";
547:                     }
548: 
549:                     $sentence = '';
550:                     if ($context !== '') {
551:                         $sentence .= "msgctxt \"{$context}\"\n";
552:                     }
553:                     if ($plural === false) {
554:                         $sentence .= "msgid \"{$msgid}\"\n";
555:                         $sentence .= "msgstr \"\"\n\n";
556:                     } else {
557:                         $sentence .= "msgid \"{$msgid}\"\n";
558:                         $sentence .= "msgid_plural \"{$plural}\"\n";
559:                         $sentence .= "msgstr[0] \"\"\n";
560:                         $sentence .= "msgstr[1] \"\"\n\n";
561:                     }
562: 
563:                     if ($domain !== 'default' && $this->_merge) {
564:                         $this->_store('default', $header, $sentence);
565:                     } else {
566:                         $this->_store($domain, $header, $sentence);
567:                     }
568:                 }
569:             }
570:         }
571:     }
572: 
573:     /**
574:      * Prepare a file to be stored
575:      *
576:      * @param string $domain The domain
577:      * @param string $header The header content.
578:      * @param string $sentence The sentence to store.
579:      * @return void
580:      */
581:     protected function _store($domain, $header, $sentence)
582:     {
583:         if (!isset($this->_storage[$domain])) {
584:             $this->_storage[$domain] = [];
585:         }
586:         if (!isset($this->_storage[$domain][$sentence])) {
587:             $this->_storage[$domain][$sentence] = $header;
588:         } else {
589:             $this->_storage[$domain][$sentence] .= $header;
590:         }
591:     }
592: 
593:     /**
594:      * Write the files that need to be stored
595:      *
596:      * @return void
597:      */
598:     protected function _writeFiles()
599:     {
600:         $overwriteAll = false;
601:         if (!empty($this->params['overwrite'])) {
602:             $overwriteAll = true;
603:         }
604:         foreach ($this->_storage as $domain => $sentences) {
605:             $output = $this->_writeHeader();
606:             foreach ($sentences as $sentence => $header) {
607:                 $output .= $header . $sentence;
608:             }
609: 
610:             // Remove vendor prefix if present.
611:             $slashPosition = strpos($domain, '/');
612:             if ($slashPosition !== false) {
613:                 $domain = substr($domain, $slashPosition + 1);
614:             }
615: 
616:             $filename = str_replace('/', '_', $domain) . '.pot';
617:             $File = new File($this->_output . $filename);
618:             $response = '';
619:             while ($overwriteAll === false && $File->exists() && strtoupper($response) !== 'Y') {
620:                 $this->out();
621:                 $response = $this->in(
622:                     sprintf('Error: %s already exists in this location. Overwrite? [Y]es, [N]o, [A]ll', $filename),
623:                     ['y', 'n', 'a'],
624:                     'y'
625:                 );
626:                 if (strtoupper($response) === 'N') {
627:                     $response = '';
628:                     while (!$response) {
629:                         $response = $this->in('What would you like to name this file?', null, 'new_' . $filename);
630:                         $File = new File($this->_output . $response);
631:                         $filename = $response;
632:                     }
633:                 } elseif (strtoupper($response) === 'A') {
634:                     $overwriteAll = true;
635:                 }
636:             }
637:             $File->write($output);
638:             $File->close();
639:         }
640:     }
641: 
642:     /**
643:      * Build the translation template header
644:      *
645:      * @return string Translation template header
646:      */
647:     protected function _writeHeader()
648:     {
649:         $output = "# LANGUAGE translation of CakePHP Application\n";
650:         $output .= "# Copyright YEAR NAME <EMAIL@ADDRESS>\n";
651:         $output .= "#\n";
652:         $output .= "#, fuzzy\n";
653:         $output .= "msgid \"\"\n";
654:         $output .= "msgstr \"\"\n";
655:         $output .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
656:         $output .= '"POT-Creation-Date: ' . date('Y-m-d H:iO') . "\\n\"\n";
657:         $output .= "\"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\\n\"\n";
658:         $output .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
659:         $output .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n";
660:         $output .= "\"MIME-Version: 1.0\\n\"\n";
661:         $output .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
662:         $output .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
663:         $output .= "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n\n";
664: 
665:         return $output;
666:     }
667: 
668:     /**
669:      * Get the strings from the position forward
670:      *
671:      * @param int $position Actual position on tokens array
672:      * @param int $target Number of strings to extract
673:      * @return array Strings extracted
674:      */
675:     protected function _getStrings(&$position, $target)
676:     {
677:         $strings = [];
678:         $count = count($strings);
679:         while ($count < $target && ($this->_tokens[$position] === ',' || $this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING || $this->_tokens[$position][0] == T_LNUMBER)) {
680:             $count = count($strings);
681:             if ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING && $this->_tokens[$position + 1] === '.') {
682:                 $string = '';
683:                 while ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING || $this->_tokens[$position] === '.') {
684:                     if ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING) {
685:                         $string .= $this->_formatString($this->_tokens[$position][1]);
686:                     }
687:                     $position++;
688:                 }
689:                 $strings[] = $string;
690:             } elseif ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING) {
691:                 $strings[] = $this->_formatString($this->_tokens[$position][1]);
692:             } elseif ($this->_tokens[$position][0] == T_LNUMBER) {
693:                 $strings[] = $this->_tokens[$position][1];
694:             }
695:             $position++;
696:         }
697: 
698:         return $strings;
699:     }
700: 
701:     /**
702:      * Format a string to be added as a translatable string
703:      *
704:      * @param string $string String to format
705:      * @return string Formatted string
706:      */
707:     protected function _formatString($string)
708:     {
709:         $quote = substr($string, 0, 1);
710:         $string = substr($string, 1, -1);
711:         if ($quote === '"') {
712:             $string = stripcslashes($string);
713:         } else {
714:             $string = strtr($string, ["\\'" => "'", '\\\\' => '\\']);
715:         }
716:         $string = str_replace("\r\n", "\n", $string);
717: 
718:         return addcslashes($string, "\0..\37\\\"");
719:     }
720: 
721:     /**
722:      * Indicate an invalid marker on a processed file
723:      *
724:      * @param string $file File where invalid marker resides
725:      * @param int $line Line number
726:      * @param string $marker Marker found
727:      * @param int $count Count
728:      * @return void
729:      */
730:     protected function _markerError($file, $line, $marker, $count)
731:     {
732:         if (strpos($this->_file, CAKE_CORE_INCLUDE_PATH) === false) {
733:             $this->_countMarkerError++;
734:         }
735: 
736:         if (!$this->_markerError) {
737:             return;
738:         }
739: 
740:         $this->err(sprintf("Invalid marker content in %s:%s\n* %s(", $file, $line, $marker));
741:         $count += 2;
742:         $tokenCount = count($this->_tokens);
743:         $parenthesis = 1;
744: 
745:         while ((($tokenCount - $count) > 0) && $parenthesis) {
746:             if (is_array($this->_tokens[$count])) {
747:                 $this->err($this->_tokens[$count][1], false);
748:             } else {
749:                 $this->err($this->_tokens[$count], false);
750:                 if ($this->_tokens[$count] === '(') {
751:                     $parenthesis++;
752:                 }
753: 
754:                 if ($this->_tokens[$count] === ')') {
755:                     $parenthesis--;
756:                 }
757:             }
758:             $count++;
759:         }
760:         $this->err("\n", true);
761:     }
762: 
763:     /**
764:      * Search files that may contain translatable strings
765:      *
766:      * @return void
767:      */
768:     protected function _searchFiles()
769:     {
770:         $pattern = false;
771:         if (!empty($this->_exclude)) {
772:             $exclude = [];
773:             foreach ($this->_exclude as $e) {
774:                 if (DIRECTORY_SEPARATOR !== '\\' && $e[0] !== DIRECTORY_SEPARATOR) {
775:                     $e = DIRECTORY_SEPARATOR . $e;
776:                 }
777:                 $exclude[] = preg_quote($e, '/');
778:             }
779:             $pattern = '/' . implode('|', $exclude) . '/';
780:         }
781:         foreach ($this->_paths as $path) {
782:             $path = realpath($path) . DIRECTORY_SEPARATOR;
783:             $Folder = new Folder($path);
784:             $files = $Folder->findRecursive('.*\.(php|ctp|thtml|inc|tpl)', true);
785:             if (!empty($pattern)) {
786:                 $files = preg_grep($pattern, $files, PREG_GREP_INVERT);
787:                 $files = array_values($files);
788:             }
789:             $this->_files = array_merge($this->_files, $files);
790:         }
791:         $this->_files = array_unique($this->_files);
792:     }
793: 
794:     /**
795:      * Returns whether this execution is meant to extract string only from directories in folder represented by the
796:      * APP constant, i.e. this task is extracting strings from same application.
797:      *
798:      * @return bool
799:      */
800:     protected function _isExtractingApp()
801:     {
802:         return $this->_paths === [APP];
803:     }
804: 
805:     /**
806:      * Checks whether or not a given path is usable for writing.
807:      *
808:      * @param string $path Path to folder
809:      * @return bool true if it exists and is writable, false otherwise
810:      */
811:     protected function _isPathUsable($path)
812:     {
813:         if (!is_dir($path)) {
814:             mkdir($path, 0770, true);
815:         }
816: 
817:         return is_dir($path) && is_writable($path);
818:     }
819: }
820: 
Follow @CakePHP
#IRC
OpenHub
Rackspace
  • Business Solutions
  • Showcase
  • Documentation
  • Book
  • API
  • Videos
  • Logos & Trademarks
  • Community
  • Team
  • Issues (Github)
  • YouTube Channel
  • Get Involved
  • Bakery
  • Featured Resources
  • Newsletter
  • Certification
  • My CakePHP
  • CakeFest
  • Facebook
  • Twitter
  • Help & Support
  • Forum
  • Stack Overflow
  • IRC
  • Slack
  • Paid Support

Generated using CakePHP API Docs