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.8 Red Velvet API

  • Overview
  • Tree
  • Deprecated
  • Version:
    • 3.8
      • 3.8
      • 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:      * Paths to use when looking for strings
 32:      *
 33:      * @var array
 34:      */
 35:     protected $_paths = [];
 36: 
 37:     /**
 38:      * Files from where to extract
 39:      *
 40:      * @var array
 41:      */
 42:     protected $_files = [];
 43: 
 44:     /**
 45:      * Merge all domain strings into the default.pot file
 46:      *
 47:      * @var bool
 48:      */
 49:     protected $_merge = false;
 50: 
 51:     /**
 52:      * Use relative paths in the pot files rather than full path
 53:      *
 54:      * @var bool
 55:      */
 56:     protected $_relativePaths = false;
 57: 
 58:     /**
 59:      * Current file being processed
 60:      *
 61:      * @var string|null
 62:      */
 63:     protected $_file;
 64: 
 65:     /**
 66:      * Contains all content waiting to be write
 67:      *
 68:      * @var array
 69:      */
 70:     protected $_storage = [];
 71: 
 72:     /**
 73:      * Extracted tokens
 74:      *
 75:      * @var array
 76:      */
 77:     protected $_tokens = [];
 78: 
 79:     /**
 80:      * Extracted strings indexed by domain.
 81:      *
 82:      * @var array
 83:      */
 84:     protected $_translations = [];
 85: 
 86:     /**
 87:      * Destination path
 88:      *
 89:      * @var string|null
 90:      */
 91:     protected $_output;
 92: 
 93:     /**
 94:      * An array of directories to exclude.
 95:      *
 96:      * @var array
 97:      */
 98:     protected $_exclude = [];
 99: 
100:     /**
101:      * Holds the validation string domain to use for validation messages when extracting
102:      *
103:      * @var string
104:      */
105:     protected $_validationDomain = 'default';
106: 
107:     /**
108:      * Holds whether this call should extract the CakePHP Lib messages
109:      *
110:      * @var bool
111:      */
112:     protected $_extractCore = false;
113: 
114:     /**
115:      * Displays marker error(s) if true
116:      * @var bool
117:      */
118:     protected $_markerError;
119: 
120:     /**
121:      * Count number of marker errors found
122:      * @var bool
123:      */
124:     protected $_countMarkerError = 0;
125: 
126:     /**
127:      * No welcome message.
128:      *
129:      * @return void
130:      */
131:     protected function _welcome()
132:     {
133:     }
134: 
135:     /**
136:      * Method to interact with the User and get path selections.
137:      *
138:      * @return void
139:      */
140:     protected function _getPaths()
141:     {
142:         $defaultPath = APP;
143:         while (true) {
144:             $currentPaths = count($this->_paths) > 0 ? $this->_paths : ['None'];
145:             $message = sprintf(
146:                 "Current paths: %s\nWhat is the path you would like to extract?\n[Q]uit [D]one",
147:                 implode(', ', $currentPaths)
148:             );
149:             $response = $this->in($message, null, $defaultPath);
150:             if (strtoupper($response) === 'Q') {
151:                 $this->err('Extract Aborted');
152:                 $this->_stop();
153: 
154:                 return;
155:             }
156:             if (strtoupper($response) === 'D' && count($this->_paths)) {
157:                 $this->out();
158: 
159:                 return;
160:             }
161:             if (strtoupper($response) === 'D') {
162:                 $this->warn('No directories selected. Please choose a directory.');
163:             } elseif (is_dir($response)) {
164:                 $this->_paths[] = $response;
165:                 $defaultPath = 'D';
166:             } else {
167:                 $this->err('The directory path you supplied was not found. Please try again.');
168:             }
169:             $this->out();
170:         }
171:     }
172: 
173:     /**
174:      * Execution method always used for tasks
175:      *
176:      * @return void
177:      */
178:     public function main()
179:     {
180:         if (!empty($this->params['exclude'])) {
181:             $this->_exclude = explode(',', $this->params['exclude']);
182:         }
183:         if (isset($this->params['files']) && !is_array($this->params['files'])) {
184:             $this->_files = explode(',', $this->params['files']);
185:         }
186:         if (isset($this->params['paths'])) {
187:             $this->_paths = explode(',', $this->params['paths']);
188:         } elseif (isset($this->params['plugin'])) {
189:             $plugin = Inflector::camelize($this->params['plugin']);
190:             if (!Plugin::isLoaded($plugin)) {
191:                 throw new MissingPluginException(['plugin' => $plugin]);
192:             }
193:             $this->_paths = [Plugin::classPath($plugin)];
194:             $this->params['plugin'] = $plugin;
195:         } else {
196:             $this->_getPaths();
197:         }
198: 
199:         if (isset($this->params['extract-core'])) {
200:             $this->_extractCore = !(strtolower($this->params['extract-core']) === 'no');
201:         } else {
202:             $response = $this->in('Would you like to extract the messages from the CakePHP core?', ['y', 'n'], 'n');
203:             $this->_extractCore = strtolower($response) === 'y';
204:         }
205: 
206:         if (!empty($this->params['exclude-plugins']) && $this->_isExtractingApp()) {
207:             $this->_exclude = array_merge($this->_exclude, App::path('Plugin'));
208:         }
209: 
210:         if (!empty($this->params['validation-domain'])) {
211:             $this->_validationDomain = $this->params['validation-domain'];
212:         }
213: 
214:         if ($this->_extractCore) {
215:             $this->_paths[] = CAKE;
216:         }
217: 
218:         if (isset($this->params['output'])) {
219:             $this->_output = $this->params['output'];
220:         } elseif (isset($this->params['plugin'])) {
221:             $this->_output = $this->_paths[0] . 'Locale';
222:         } else {
223:             $message = "What is the path you would like to output?\n[Q]uit";
224:             while (true) {
225:                 $response = $this->in($message, null, rtrim($this->_paths[0], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'Locale');
226:                 if (strtoupper($response) === 'Q') {
227:                     $this->err('Extract Aborted');
228:                     $this->_stop();
229: 
230:                     return;
231:                 }
232:                 if ($this->_isPathUsable($response)) {
233:                     $this->_output = $response . DIRECTORY_SEPARATOR;
234:                     break;
235:                 }
236: 
237:                 $this->err('');
238:                 $this->err(
239:                     '<error>The directory path you supplied was ' .
240:                     'not found. Please try again.</error>'
241:                 );
242:                 $this->out();
243:             }
244:         }
245: 
246:         if (isset($this->params['merge'])) {
247:             $this->_merge = !(strtolower($this->params['merge']) === 'no');
248:         } else {
249:             $this->out();
250:             $response = $this->in('Would you like to merge all domain strings into the default.pot file?', ['y', 'n'], 'n');
251:             $this->_merge = strtolower($response) === 'y';
252:         }
253: 
254:         $this->_markerError = $this->param('marker-error');
255:         $this->_relativePaths = $this->param('relative-paths');
256: 
257:         if (empty($this->_files)) {
258:             $this->_searchFiles();
259:         }
260: 
261:         $this->_output = rtrim($this->_output, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
262:         if (!$this->_isPathUsable($this->_output)) {
263:             $this->err(sprintf('The output directory %s was not found or writable.', $this->_output));
264:             $this->_stop();
265: 
266:             return;
267:         }
268: 
269:         $this->_extract();
270:     }
271: 
272:     /**
273:      * Add a translation to the internal translations property
274:      *
275:      * Takes care of duplicate translations
276:      *
277:      * @param string $domain The domain
278:      * @param string $msgid The message string
279:      * @param array $details Context and plural form if any, file and line references
280:      * @return void
281:      */
282:     protected function _addTranslation($domain, $msgid, $details = [])
283:     {
284:         $context = isset($details['msgctxt']) ? $details['msgctxt'] : '';
285: 
286:         if (empty($this->_translations[$domain][$msgid][$context])) {
287:             $this->_translations[$domain][$msgid][$context] = [
288:                 'msgid_plural' => false
289:             ];
290:         }
291: 
292:         if (isset($details['msgid_plural'])) {
293:             $this->_translations[$domain][$msgid][$context]['msgid_plural'] = $details['msgid_plural'];
294:         }
295: 
296:         if (isset($details['file'])) {
297:             $line = isset($details['line']) ? $details['line'] : 0;
298:             $this->_translations[$domain][$msgid][$context]['references'][$details['file']][] = $line;
299:         }
300:     }
301: 
302:     /**
303:      * Extract text
304:      *
305:      * @return void
306:      */
307:     protected function _extract()
308:     {
309:         $this->out();
310:         $this->out();
311:         $this->out('Extracting...');
312:         $this->hr();
313:         $this->out('Paths:');
314:         foreach ($this->_paths as $path) {
315:             $this->out('   ' . $path);
316:         }
317:         $this->out('Output Directory: ' . $this->_output);
318:         $this->hr();
319:         $this->_extractTokens();
320:         $this->_buildFiles();
321:         $this->_writeFiles();
322:         $this->_paths = $this->_files = $this->_storage = [];
323:         $this->_translations = $this->_tokens = [];
324:         $this->out();
325:         if ($this->_countMarkerError) {
326:             $this->err("{$this->_countMarkerError} marker error(s) detected.");
327:             $this->err(" => Use the --marker-error option to display errors.");
328:         }
329: 
330:         $this->out('Done.');
331:     }
332: 
333:     /**
334:      * Gets the option parser instance and configures it.
335:      *
336:      * @return \Cake\Console\ConsoleOptionParser
337:      */
338:     public function getOptionParser()
339:     {
340:         $parser = parent::getOptionParser();
341:         $parser->setDescription(
342:             'CakePHP Language String Extraction:'
343:         )->addOption('app', [
344:             'help' => 'Directory where your application is located.'
345:         ])->addOption('paths', [
346:             'help' => 'Comma separated list of paths.'
347:         ])->addOption('merge', [
348:             'help' => 'Merge all domain strings into the default.po file.',
349:             'choices' => ['yes', 'no']
350:         ])->addOption('relative-paths', [
351:             'help' => 'Use relative paths in the .pot file',
352:             'boolean' => true,
353:             'default' => false,
354:         ])->addOption('output', [
355:             'help' => 'Full path to output directory.'
356:         ])->addOption('files', [
357:             'help' => 'Comma separated list of files.'
358:         ])->addOption('exclude-plugins', [
359:             'boolean' => true,
360:             'default' => true,
361:             'help' => 'Ignores all files in plugins if this command is run inside from the same app directory.'
362:         ])->addOption('plugin', [
363:             'help' => 'Extracts tokens only from the plugin specified and puts the result in the plugin\'s Locale directory.',
364:             'short' => 'p',
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:                     $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:                     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:             } 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);
752:             } else {
753:                 $this->err($this->_tokens[$count], false);
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);
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: 
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