Untitled diff

Created Diff never expires
15 removals
Lines
Total
Removed
Words
Total
Removed
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
728 lines
18 additions
Lines
Total
Added
Words
Total
Added
To continue using this feature, upgrade to
Diffchecker logo
Diffchecker Pro
731 lines
<?php
<?php
/**
/**
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* CakePHP(tm) : Rapid Development Framework (https://cakephp.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
*
*
* Licensed under The MIT License
* Licensed under The MIT License
* For full copyright and license information, please see the LICENSE.txt
* For full copyright and license information, please see the LICENSE.txt
* Redistributions of files must retain the above copyright notice.
* Redistributions of files must retain the above copyright notice.
*
*
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @copyright Copyright (c) Cake Software Foundation, Inc. (https://cakefoundation.org)
* @link https://cakephp.org CakePHP(tm) Project
* @link https://cakephp.org CakePHP(tm) Project
* @since 1.2.0
* @since 1.2.0
* @license https://opensource.org/licenses/mit-license.php MIT License
* @license https://opensource.org/licenses/mit-license.php MIT License
*/
*/
namespace Cake\Shell\Task;
namespace Cake\Shell\Task;


use Cake\Console\Shell;
use Cake\Console\Shell;
use Cake\Core\App;
use Cake\Core\App;
use Cake\Core\Plugin;
use Cake\Core\Plugin;
use Cake\Filesystem\File;
use Cake\Filesystem\File;
use Cake\Filesystem\Folder;
use Cake\Filesystem\Folder;
use Cake\Utility\Inflector;
use Cake\Utility\Inflector;


/**
/**
* Language string extractor
* Language string extractor
*/
*/
class ExtractTask extends Shell
class ExtractTask extends Shell
{
{


/**
/**
* Paths to use when looking for strings
* Paths to use when looking for strings
*
*
* @var array
* @var array
*/
*/
protected $_paths = [];
protected $_paths = [];


/**
/**
* Files from where to extract
* Files from where to extract
*
*
* @var array
* @var array
*/
*/
protected $_files = [];
protected $_files = [];


/**
/**
* Merge all domain strings into the default.pot file
* Merge all domain strings into the default.pot file
*
*
* @var bool
* @var bool
*/
*/
protected $_merge = false;
protected $_merge = false;


/**
/**
* Use relative paths in the pot files rather than full path
*
* @var bool
*/
protected $_relativePaths = false;

/**
* Current file being processed
* Current file being processed
*
*
* @var string|null
* @var string|null
*/
*/
protected $_file;
protected $_file;


/**
/**
* Contains all content waiting to be write
* Contains all content waiting to be write
*
*
* @var array
* @var array
*/
*/
protected $_storage = [];
protected $_storage = [];


/**
/**
* Extracted tokens
* Extracted tokens
*
*
* @var array
* @var array
*/
*/
protected $_tokens = [];
protected $_tokens = [];


/**
/**
* Extracted strings indexed by domain.
* Extracted strings indexed by domain.
*
*
* @var array
* @var array
*/
*/
protected $_translations = [];
protected $_translations = [];


/**
/**
* Destination path
* Destination path
*
*
* @var string|null
* @var string|null
*/
*/
protected $_output;
protected $_output;


/**
/**
* An array of directories to exclude.
* An array of directories to exclude.
*
*
* @var array
* @var array
*/
*/
protected $_exclude = [];
protected $_exclude = [];


/**
/**
* Holds the validation string domain to use for validation messages when extracting
* Holds the validation string domain to use for validation messages when extracting
*
*
* @var string
* @var string
*/
*/
protected $_validationDomain = 'default';
protected $_validationDomain = 'default';


/**
/**
* Holds whether this call should extract the CakePHP Lib messages
* Holds whether this call should extract the CakePHP Lib messages
*
*
* @var bool
* @var bool
*/
*/
protected $_extractCore = false;
protected $_extractCore = false;


/**
/**
* No welcome message.
* No welcome message.
*
*
* @return void
* @return void
*/
*/
protected function _welcome()
protected function _welcome()
{
{
}
}


/**
/**
* Method to interact with the User and get path selections.
* Method to interact with the User and get path selections.
*
*
* @return void
* @return void
*/
*/
protected function _getPaths()
protected function _getPaths()
{
{
$defaultPath = APP;
$defaultPath = APP;
while (true) {
while (true) {
$currentPaths = count($this->_paths) > 0 ? $this->_paths : ['None'];
$currentPaths = count($this->_paths) > 0 ? $this->_paths : ['None'];
$message = sprintf(
$message = sprintf(
"Current paths: %s\nWhat is the path you would like to extract?\n[Q]uit [D]one",
"Current paths: %s\nWhat is the path you would like to extract?\n[Q]uit [D]one",
implode(', ', $currentPaths)
implode(', ', $currentPaths)
);
);
$response = $this->in($message, null, $defaultPath);
$response = $this->in($message, null, $defaultPath);
if (strtoupper($response) === 'Q') {
if (strtoupper($response) === 'Q') {
$this->err('Extract Aborted');
$this->err('Extract Aborted');
$this->_stop();
$this->_stop();


return;
return;
}
}
if (strtoupper($response) === 'D' && count($this->_paths)) {
if (strtoupper($response) === 'D' && count($this->_paths)) {
$this->out();
$this->out();


return;
return;
}
}
if (strtoupper($response) === 'D') {
if (strtoupper($response) === 'D') {
$this->warn('No directories selected. Please choose a directory.');
$this->warn('No directories selected. Please choose a directory.');
} elseif (is_dir($response)) {
} elseif (is_dir($response)) {
$this->_paths[] = $response;
$this->_paths[] = $response;
$defaultPath = 'D';
$defaultPath = 'D';
} else {
} else {
$this->err('The directory path you supplied was not found. Please try again.');
$this->err('The directory path you supplied was not found. Please try again.');
}
}
$this->out();
$this->out();
}
}
}
}


/**
/**
* Execution method always used for tasks
* Execution method always used for tasks
*
*
* @return void
* @return void
*/
*/
public function main()
public function main()
{
{
if (!empty($this->params['exclude'])) {
if (!empty($this->params['exclude'])) {
$this->_exclude = explode(',', $this->params['exclude']);
$this->_exclude = explode(',', $this->params['exclude']);
}
}
if (isset($this->params['files']) && !is_array($this->params['files'])) {
if (isset($this->params['files']) && !is_array($this->params['files'])) {
$this->_files = explode(',', $this->params['files']);
$this->_files = explode(',', $this->params['files']);
}
}
if (isset($this->params['paths'])) {
if (isset($this->params['paths'])) {
$this->_paths = explode(',', $this->params['paths']);
$this->_paths = explode(',', $this->params['paths']);
} elseif (isset($this->params['plugin'])) {
} elseif (isset($this->params['plugin'])) {
$plugin = Inflector::camelize($this->params['plugin']);
$plugin = Inflector::camelize($this->params['plugin']);
if (!Plugin::loaded($plugin)) {
if (!Plugin::loaded($plugin)) {
Plugin::load($plugin);
Plugin::load($plugin);
}
}
$this->_paths = [Plugin::classPath($plugin)];
$this->_paths = [Plugin::classPath($plugin)];
$this->params['plugin'] = $plugin;
$this->params['plugin'] = $plugin;
} else {
} else {
$this->_getPaths();
$this->_getPaths();
}
}


if (isset($this->params['extract-core'])) {
if (isset($this->params['extract-core'])) {
$this->_extractCore = !(strtolower($this->params['extract-core']) === 'no');
$this->_extractCore = !(strtolower($this->params['extract-core']) === 'no');
} else {
} else {
$response = $this->in('Would you like to extract the messages from the CakePHP core?', ['y', 'n'], 'n');
$response = $this->in('Would you like to extract the messages from the CakePHP core?', ['y', 'n'], 'n');
$this->_extractCore = strtolower($response) === 'y';
$this->_extractCore = strtolower($response) === 'y';
}
}


if (!empty($this->params['exclude-plugins']) && $this->_isExtractingApp()) {
if (!empty($this->params['exclude-plugins']) && $this->_isExtractingApp()) {
$this->_exclude = array_merge($this->_exclude, App::path('Plugin'));
$this->_exclude = array_merge($this->_exclude, App::path('Plugin'));
}
}


if (!empty($this->params['validation-domain'])) {
if (!empty($this->params['validation-domain'])) {
$this->_validationDomain = $this->params['validation-domain'];
$this->_validationDomain = $this->params['validation-domain'];
}
}


if ($this->_extractCore) {
if ($this->_extractCore) {
$this->_paths[] = CAKE;
$this->_paths[] = CAKE;
}
}


if (isset($this->params['output'])) {
if (isset($this->params['output'])) {
$this->_output = $this->params['output'];
$this->_output = $this->params['output'];
} elseif (isset($this->params['plugin'])) {
} elseif (isset($this->params['plugin'])) {
$this->_output = $this->_paths[0] . 'Locale';
$this->_output = $this->_paths[0] . 'Locale';
} else {
} else {
$message = "What is the path you would like to output?\n[Q]uit";
$message = "What is the path you would like to output?\n[Q]uit";
while (true) {
while (true) {
$response = $this->in($message, null, rtrim($this->_paths[0], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'Locale');
$response = $this->in($message, null, rtrim($this->_paths[0], DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'Locale');
if (strtoupper($response) === 'Q') {
if (strtoupper($response) === 'Q') {
$this->err('Extract Aborted');
$this->err('Extract Aborted');
$this->_stop();
$this->_stop();


return;
return;
}
}
if ($this->_isPathUsable($response)) {
if ($this->_isPathUsable($response)) {
$this->_output = $response . DIRECTORY_SEPARATOR;
$this->_output = $response . DIRECTORY_SEPARATOR;
break;
break;
}
}


$this->err('');
$this->err('');
$this->err(
$this->err(
'<error>The directory path you supplied was ' .
'<error>The directory path you supplied was ' .
'not found. Please try again.</error>'
'not found. Please try again.</error>'
);
);
$this->out();
$this->out();
}
}
}
}


if (isset($this->params['merge'])) {
if (isset($this->params['merge'])) {
$this->_merge = !(strtolower($this->params['merge']) === 'no');
$this->_merge = !(strtolower($this->params['merge']) === 'no');
} else {
} else {
$this->out();
$this->out();
$response = $this->in('Would you like to merge all domain strings into the default.pot file?', ['y', 'n'], 'n');
$response = $this->in('Would you like to merge all domain strings into the default.pot file?', ['y', 'n'], 'n');
$this->_merge = strtolower($response) === 'y';
$this->_merge = strtolower($response) === 'y';
}
}


if (isset($this->params['relative-paths'])) {
$this->_relativePaths = !(strtolower($this->params['relative-paths']) === 'no');
}

if (empty($this->_files)) {
if (empty($this->_files)) {
$this->_searchFiles();
$this->_searchFiles();
}
}


$this->_output = rtrim($this->_output, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
$this->_output = rtrim($this->_output, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
if (!$this->_isPathUsable($this->_output)) {
if (!$this->_isPathUsable($this->_output)) {
$this->err(sprintf('The output directory %s was not found or writable.', $this->_output));
$this->err(sprintf('The output directory %s was not found or writable.', $this->_output));
$this->_stop();
$this->_stop();


return;
return;
}
}


$this->_extract();
$this->_extract();
}
}


/**
/**
* Add a translation to the internal translations property
* Add a translation to the internal translations property
*
*
* Takes care of duplicate translations
* Takes care of duplicate translations
*
*
* @param string $domain The domain
* @param string $domain The domain
* @param string $msgid The message string
* @param string $msgid The message string
* @param array $details Context and plural form if any, file and line references
* @param array $details Context and plural form if any, file and line references
* @return void
* @return void
*/
*/
protected function _addTranslation($domain, $msgid, $details = [])
protected function _addTranslation($domain, $msgid, $details = [])
{
{
$context = isset($details['msgctxt']) ? $details['msgctxt'] : '';
$context = isset($details['msgctxt']) ? $details['msgctxt'] : '';


if (empty($this->_translations[$domain][$msgid][$context])) {
if (empty($this->_translations[$domain][$msgid][$context])) {
$this->_translations[$domain][$msgid][$context] = [
$this->_translations[$domain][$msgid][$context] = [
'msgid_plural' => false
'msgid_plural' => false
];
];
}
}


if (isset($details['msgid_plural'])) {
if (isset($details['msgid_plural'])) {
$this->_translations[$domain][$msgid][$context]['msgid_plural'] = $details['msgid_plural'];
$this->_translations[$domain][$msgid][$context]['msgid_plural'] = $details['msgid_plural'];
}
}


if (isset($details['file'])) {
if (isset($details['file'])) {
$line = isset($details['line']) ? $details['line'] : 0;
$line = isset($details['line']) ? $details['line'] : 0;
$this->_translations[$domain][$msgid][$context]['references'][$details['file']][] = $line;
$this->_translations[$domain][$msgid][$context]['references'][$details['file']][] = $line;
}
}
}
}


/**
/**
* Extract text
* Extract text
*
*
* @return void
* @return void
*/
*/
protected function _extract()
protected function _extract()
{
{
$this->out();
$this->out();
$this->out();
$this->out();
$this->out('Extracting...');
$this->out('Extracting...');
$this->hr();
$this->hr();
$this->out('Paths:');
$this->out('Paths:');
foreach ($this->_paths as $path) {
foreach ($this->_paths as $path) {
$this->out(' ' . $path);
$this->out(' ' . $path);
}
}
$this->out('Output Directory: ' . $this->_output);
$this->out('Output Directory: ' . $this->_output);
$this->hr();
$this->hr();
$this->_extractTokens();
$this->_extractTokens();
$this->_buildFiles();
$this->_buildFiles();
$this->_writeFiles();
$this->_writeFiles();
$this->_paths = $this->_files = $this->_storage = [];
$this->_paths = $this->_files = $this->_storage = [];
$this->_translations = $this->_tokens = [];
$this->_translations = $this->_tokens = [];
$this->out();
$this->out();
$this->out('Done.');
$this->out('Done.');
}
}


/**
/**
* Gets the option parser instance and configures it.
* Gets the option parser instance and configures it.
*
*
* @return \Cake\Console\ConsoleOptionParser
* @return \Cake\Console\ConsoleOptionParser
*/
*/
public function getOptionParser()
public function getOptionParser()
{
{
$parser = parent::getOptionParser();
$parser = parent::getOptionParser();
$parser->setDescription(
$parser->setDescription(
'CakePHP Language String Extraction:'
'CakePHP Language String Extraction:'
)->addOption('app', [
)->addOption('app', [
'help' => 'Directory where your application is located.'
'help' => 'Directory where your application is located.'
])->addOption('paths', [
])->addOption('paths', [
'help' => 'Comma separated list of paths.'
'help' => 'Comma separated list of paths.'
])->addOption('relative-paths', [
'help' => 'Use relative paths in the .pot file',
'choices' => ['yes', 'no']
])->addOption('merge', [
])->addOption('merge', [
'help' => 'Merge all domain strings into the default.po file.',
'help' => 'Merge all domain strings into the default.po file.',
'choices' => ['yes', 'no']
'choices' => ['yes', 'no']
])->addOption('output', [
])->addOption('output', [
'help' => 'Full path to output directory.'
'help' => 'Full path to output directory.'
])->addOption('files', [
])->addOption('files', [
'help' => 'Comma separated list of files.'
'help' => 'Comma separated list of files.'
])->addOption('exclude-plugins', [
])->addOption('exclude-plugins', [
'boolean' => true,
'boolean' => true,
'default' => true,
'default' => true,
'help' => 'Ignores all files in plugins if this command is run inside from the same app directory.'
'help' => 'Ignores all files in plugins if this command is run inside from the same app directory.'
])->addOption('plugin', [
])->addOption('plugin', [
'help' => 'Extracts tokens only from the plugin specified and puts the result in the plugin\'s Locale directory.'
'help' => 'Extracts tokens only from the plugin specified and puts the result in the plugin\'s Locale directory.'
])->addOption('ignore-model-validation', [
])->addOption('ignore-model-validation', [
'boolean' => true,
'boolean' => true,
'default' => false,
'default' => false,
'help' => 'Ignores validation messages in the $validate property.' .
'help' => 'Ignores validation messages in the $validate property.' .
' If this flag is not set and the command is run from the same app directory,' .
' If this flag is not set and the command is run from the same app directory,' .
' all messages in model validation rules will be extracted as tokens.'
' all messages in model validation rules will be extracted as tokens.'
])->addOption('validation-domain', [
])->addOption('validation-domain', [
'help' => 'If set to a value, the localization domain to be used for model validation messages.'
'help' => 'If set to a value, the localization domain to be used for model validation messages.'
])->addOption('exclude', [
])->addOption('exclude', [
'help' => 'Comma separated list of directories to exclude.' .
'help' => 'Comma separated list of directories to exclude.' .
' Any path containing a path segment with the provided values will be skipped. E.g. test,vendors'
' Any path containing a path segment with the provided values will be skipped. E.g. test,vendors'
])->addOption('overwrite', [
])->addOption('overwrite', [
'boolean' => true,
'boolean' => true,
'default' => false,
'default' => false,
'help' => 'Always overwrite existing .pot files.'
'help' => 'Always overwrite existing .pot files.'
])->addOption('extract-core', [
])->addOption('extract-core', [
'help' => 'Extract messages from the CakePHP core libs.',
'help' => 'Extract messages from the CakePHP core libs.',
'choices' => ['yes', 'no']
'choices' => ['yes', 'no']
])->addOption('no-location', [
])->addOption('no-location', [
'boolean' => true,
'boolean' => true,
'default' => false,
'default' => false,
'help' => 'Do not write file locations for each extracted message.',
'help' => 'Do not write file locations for each extracted message.',
]);
]);


return $parser;
return $parser;
}
}


/**
/**
* Extract tokens out of all files to be processed
* Extract tokens out of all files to be processed
*
*
* @return void
* @return void
*/
*/
protected function _extractTokens()
protected function _extractTokens()
{
{
/** @var \Cake\Shell\Helper\ProgressHelper $progress */
/** @var \Cake\Shell\Helper\ProgressHelper $progress */
$progress = $this->helper('progress');
$progress = $this->helper('progress');
$progress->init(['total' => count($this->_files)]);
$progress->init(['total' => count($this->_files)]);
$isVerbose = $this->param('verbose');
$isVerbose = $this->param('verbose');


foreach ($this->_files as $file) {
foreach ($this->_files as $file) {
$this->_file = $file;
$this->_file = $file;
if ($isVerbose) {
if ($isVerbose) {
$this->out(sprintf('Processing %s...', $file), 1, Shell::VERBOSE);
$this->out(sprintf('Processing %s...', $file), 1, Shell::VERBOSE);
}
}


$code = file_get_contents($file);
$code = file_get_contents($file);
$allTokens = token_get_all($code);
$allTokens = token_get_all($code);


$this->_tokens = [];
$this->_tokens = [];
foreach ($allTokens as $token) {
foreach ($allTokens as $token) {
if (!is_array($token) || ($token[0] !== T_WHITESPACE && $token[0] !== T_INLINE_HTML)) {
if (!is_array($token) || ($token[0] !== T_WHITESPACE && $token[0] !== T_INLINE_HTML)) {
$this->_tokens[] = $token;
$this->_tokens[] = $token;
}
}
}
}
unset($allTokens);
unset($allTokens);
$this->_parse('__', ['singular']);
$this->_parse('__', ['singular']);
$this->_parse('__n', ['singular', 'plural']);
$this->_parse('__n', ['singular', 'plural']);
$this->_parse('__d', ['domain', 'singular']);
$this->_parse('__d', ['domain', 'singular']);
$this->_parse('__dn', ['domain', 'singular', 'plural']);
$this->_parse('__dn', ['domain', 'singular', 'plural']);
$this->_parse('__x', ['context', 'singular']);
$this->_parse('__x', ['context', 'singular']);
$this->_parse('__xn', ['context', 'singular', 'plural']);
$this->_parse('__xn', ['context', 'singular', 'plural']);
$this->_parse('__dx', ['domain', 'context', 'singular']);
$this->_parse('__dx', ['domain', 'context', 'singular']);
$this->_parse('__dxn', ['domain', 'context', 'singular', 'plural']);
$this->_parse('__dxn', ['domain', 'context', 'singular', 'plural']);


if (!$isVerbose) {
if (!$isVerbose) {
$progress->increment(1);
$progress->increment(1);
$progress->draw();
$progress->draw();
}
}
}
}
}
}


/**
/**
* Parse tokens
* Parse tokens
*
*
* @param string $functionName Function name that indicates translatable string (e.g: '__')
* @param string $functionName Function name that indicates translatable string (e.g: '__')
* @param array $map Array containing what variables it will find (e.g: domain, singular, plural)
* @param array $map Array containing what variables it will find (e.g: domain, singular, plural)
* @return void
* @return void
*/
*/
protected function _parse($functionName, $map)
protected function _parse($functionName, $map)
{
{
$count = 0;
$count = 0;
$tokenCount = count($this->_tokens);
$tokenCount = count($this->_tokens);


while (($tokenCount - $count) > 1) {
while (($tokenCount - $count) > 1) {
$countToken = $this->_tokens[$count];
$countToken = $this->_tokens[$count];
$firstParenthesis = $this->_tokens[$count + 1];
$firstParenthesis = $this->_tokens[$count + 1];
if (!is_array($countToken)) {
if (!is_array($countToken)) {
$count++;
$count++;
continue;
continue;
}
}


list($type, $string, $line) = $countToken;
list($type, $string, $line) = $countToken;
if (($type == T_STRING) && ($string === $functionName) && ($firstParenthesis === '(')) {
if (($type == T_STRING) && ($string === $functionName) && ($firstParenthesis === '(')) {
$position = $count;
$position = $count;
$depth = 0;
$depth = 0;


while (!$depth) {
while (!$depth) {
if ($this->_tokens[$position] === '(') {
if ($this->_tokens[$position] === '(') {
$depth++;
$depth++;
} elseif ($this->_tokens[$position] === ')') {
} elseif ($this->_tokens[$position] === ')') {
$depth--;
$depth--;
}
}
$position++;
$position++;
}
}


$mapCount = count($map);
$mapCount = count($map);
$strings = $this->_getStrings($position, $mapCount);
$strings = $this->_getStrings($position, $mapCount);


if ($mapCount === count($strings)) {
if ($mapCount === count($strings)) {
$singular = null;
$singular = null;
extract(array_combine($map, $strings));
extract(array_combine($map, $strings));
$domain = isset($domain) ? $domain : 'default';
$domain = isset($domain) ? $domain : 'default';
$details = [
$details = [
'file' => $this->_file,
'file' => $this->_file,
'line' => $line,
'line' => $line,
];
];
if ($this->_relativePaths) {
$details['file'] = '.' . str_replace(ROOT, '', $details['file']);
}
if (isset($plural)) {
if (isset($plural)) {
$details['msgid_plural'] = $plural;
$details['msgid_plural'] = $plural;
}
}
if (isset($context)) {
if (isset($context)) {
$details['msgctxt'] = $context;
$details['msgctxt'] = $context;
}
}
$this->_addTranslation($domain, $singular, $details);
$this->_addTranslation($domain, $singular, $details);
} elseif (strpos($this->_file, CAKE_CORE_INCLUDE_PATH) === false) {
} elseif (strpos($this->_file, CAKE_CORE_INCLUDE_PATH) === false) {
$this->_markerError($this->_file, $line, $functionName, $count);
$this->_markerError($this->_file, $line, $functionName, $count);
}
}
}
}
$count++;
$count++;
}
}
}
}


/**
/**
* Build the translate template file contents out of obtained strings
* Build the translate template file contents out of obtained strings
*
*
* @return void
* @return void
*/
*/
protected function _buildFiles()
protected function _buildFiles()
{
{
$paths = $this->_paths;
$paths = $this->_paths;
$paths[] = realpath(APP) . DIRECTORY_SEPARATOR;
$paths[] = realpath(APP) . DIRECTORY_SEPARATOR;


usort($paths, function ($a, $b) {
usort($paths, function ($a, $b) {
return strlen($a) - strlen($b);
return strlen($a) - strlen($b);
});
});


foreach ($this->_translations as $domain => $translations) {
foreach ($this->_translations as $domain => $translations) {
foreach ($translations as $msgid => $contexts) {
foreach ($translations as $msgid => $contexts) {
foreach ($contexts as $context => $details) {
foreach ($contexts as $context => $details) {
$plural = $details['msgid_plural'];
$plural = $details['msgid_plural'];
$files = $details['references'];
$files = $details['references'];
$occurrences = [];
$occurrences = [];
foreach ($files as $file => $lines) {
foreach ($files as $file => $lines) {
$lines = array_unique($lines);
$lines = array_unique($lines);
$occurrences[] = $file . ':' . implode(';', $lines);
$occurrences[] = $file . ':' . implode(';', $lines);
}
}
$occurrences = implode("\n#: ", $occurrences);
$occurrences = implode("\n#: ", $occurrences);
$header = '';
$header = '';
if (!$this->param('no-location')) {
if (!$this->param('no-location')) {
$header = '#: ' . str_replace(DIRECTORY_SEPARATOR, '/', str_replace($paths, '', $occurrences)) . "\n";
$header = '#: ' . str_replace(DIRECTORY_SEPARATOR, '/', str_replace($paths, '', $occurrences)) . "\n";
}
}


$sentence = '';
$sentence = '';
if ($context !== '') {
if ($context !== '') {
$sentence .= "msgctxt \"{$context}\"\n";
$sentence .= "msgctxt \"{$context}\"\n";
}
}
if ($plural === false) {
if ($plural === false) {
$sentence .= "msgid \"{$msgid}\"\n";
$sentence .= "msgid \"{$msgid}\"\n";
$sentence .= "msgstr \"\"\n\n";
$sentence .= "msgstr \"\"\n\n";
} else {
} else {
$sentence .= "msgid \"{$msgid}\"\n";
$sentence .= "msgid \"{$msgid}\"\n";
$sentence .= "msgid_plural \"{$plural}\"\n";
$sentence .= "msgid_plural \"{$plural}\"\n";
$sentence .= "msgstr[0] \"\"\n";
$sentence .= "msgstr[0] \"\"\n";
$sentence .= "msgstr[1] \"\"\n\n";
$sentence .= "msgstr[1] \"\"\n\n";
}
}


if ($domain !== 'default' && $this->_merge) {
if ($domain !== 'default' && $this->_merge) {
$this->_store('default', $header, $sentence);
$this->_store('default', $header, $sentence);
} else {
} else {
$this->_store($domain, $header, $sentence);
$this->_store($domain, $header, $sentence);
}
}
}
}
}
}
}
}
}
}


/**
/**
* Prepare a file to be stored
* Prepare a file to be stored
*
*
* @param string $domain The domain
* @param string $domain The domain
* @param string $header The header content.
* @param string $header The header content.
* @param string $sentence The sentence to store.
* @param string $sentence The sentence to store.
* @return void
* @return void
*/
*/
protected function _store($domain, $header, $sentence)
protected function _store($domain, $header, $sentence)
{
{
if (!isset($this->_storage[$domain])) {
if (!isset($this->_storage[$domain])) {
$this->_storage[$domain] = [];
$this->_storage[$domain] = [];
}
}
if (!isset($this->_storage[$domain][$sentence])) {
if (!isset($this->_storage[$domain][$sentence])) {
$this->_storage[$domain][$sentence] = $header;
$this->_storage[$domain][$sentence] = $header;
} else {
} else {
$this->_storage[$domain][$sentence] .= $header;
$this->_storage[$domain][$sentence] .= $header;
}
}
}
}


/**
/**
* Write the files that need to be stored
* Write the files that need to be stored
*
*
* @return void
* @return void
*/
*/
protected function _writeFiles()
protected function _writeFiles()
{
{
$overwriteAll = false;
$overwriteAll = false;
if (!empty($this->params['overwrite'])) {
if (!empty($this->params['overwrite'])) {
$overwriteAll = true;
$overwriteAll = true;
}
}
foreach ($this->_storage as $domain => $sentences) {
foreach ($this->_storage as $domain => $sentences) {
$output = $this->_writeHeader();
$output = $this->_writeHeader();
foreach ($sentences as $sentence => $header) {
foreach ($sentences as $sentence => $header) {
$output .= $header . $sentence;
$output .= $header . $sentence;
}
}


// Remove vendor prefix if present.
// Remove vendor prefix if present.
$slashPosition = strpos($domain, '/');
$slashPosition = strpos($domain, '/');
if ($slashPosition !== false) {
if ($slashPosition !== false) {
$domain = substr($domain, $slashPosition + 1);
$domain = substr($domain, $slashPosition + 1);
}
}


$filename = str_replace('/', '_', $domain) . '.pot';
$filename = str_replace('/', '_', $domain) . '.pot';
$File = new File($this->_output . $filename);
$File = new File($this->_output . $filename);
$response = '';
$response = '';
while ($overwriteAll === false && $File->exists() && strtoupper($response) !== 'Y') {
while ($overwriteAll === false && $File->exists() && strtoupper($response) !== 'Y') {
$this->out();
$this->out();
$response = $this->in(
$response = $this->in(
sprintf('Error: %s already exists in this location. Overwrite? [Y]es, [N]o, [A]ll', $filename),
sprintf('Error: %s already exists in this location. Overwrite? [Y]es, [N]o, [A]ll', $filename),
['y', 'n', 'a'],
['y', 'n', 'a'],
'y'
'y'
);
);
if (strtoupper($response) === 'N') {
if (strtoupper($response) === 'N') {
$response = '';
$response = '';
while (!$response) {
while (!$response) {
$response = $this->in('What would you like to name this file?', null, 'new_' . $filename);
$response = $this->in('What would you like to name this file?', null, 'new_' . $filename);
$File = new File($this->_output . $response);
$File = new File($this->_output . $response);
$filename = $response;
$filename = $response;
}
}
} elseif (strtoupper($response) === 'A') {
} elseif (strtoupper($response) === 'A') {
$overwriteAll = true;
$overwriteAll = true;
}
}
}
}
$File->write($output);
$File->write($output);
$File->close();
$File->close();
}
}
}
}


/**
/**
* Build the translation template header
* Build the translation template header
*
*
* @return string Translation template header
* @return string Translation template header
*/
*/
protected function _writeHeader()
protected function _writeHeader()
{
{
$output = "# LANGUAGE translation of CakePHP Application\n";
$output = "# LANGUAGE translation of CakePHP Application\n";
$output .= "# Copyright YEAR NAME <EMAIL@ADDRESS>\n";
$output .= "# Copyright YEAR NAME <EMAIL@ADDRESS>\n";
$output .= "#\n";
$output .= "#\n";
$output .= "#, fuzzy\n";
$output .= "#, fuzzy\n";
$output .= "msgid \"\"\n";
$output .= "msgid \"\"\n";
$output .= "msgstr \"\"\n";
$output .= "msgstr \"\"\n";
$output .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
$output .= "\"Project-Id-Version: PROJECT VERSION\\n\"\n";
$output .= '"POT-Creation-Date: ' . date('Y-m-d H:iO') . "\\n\"\n";
$output .= '"POT-Creation-Date: ' . date('Y-m-d H:iO') . "\\n\"\n";
$output .= "\"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\\n\"\n";
$output .= "\"PO-Revision-Date: YYYY-mm-DD HH:MM+ZZZZ\\n\"\n";
$output .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
$output .= "\"Last-Translator: NAME <EMAIL@ADDRESS>\\n\"\n";
$output .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n";
$output .= "\"Language-Team: LANGUAGE <EMAIL@ADDRESS>\\n\"\n";
$output .= "\"MIME-Version: 1.0\\n\"\n";
$output .= "\"MIME-Version: 1.0\\n\"\n";
$output .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
$output .= "\"Content-Type: text/plain; charset=utf-8\\n\"\n";
$output .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
$output .= "\"Content-Transfer-Encoding: 8bit\\n\"\n";
$output .= "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n\n";
$output .= "\"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\\n\"\n\n";


return $output;
return $output;
}
}


/**
/**
* Get the strings from the position forward
* Get the strings from the position forward
*
*
* @param int $position Actual position on tokens array
* @param int $position Actual position on tokens array
* @param int $target Number of strings to extract
* @param int $target Number of strings to extract
* @return array Strings extracted
* @return array Strings extracted
*/
*/
protected function _getStrings(&$position, $target)
protected function _getStrings(&$position, $target)
{
{
$strings = [];
$strings = [];
$count = count($strings);
$count = count($strings);
while ($count < $target && ($this->_tokens[$position] === ',' || $this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING || $this->_tokens[$position][0] == T_LNUMBER)) {
while ($count < $target && ($this->_tokens[$position] === ',' || $this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING || $this->_tokens[$position][0] == T_LNUMBER)) {
$count = count($strings);
$count = count($strings);
if ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING && $this->_tokens[$position + 1] === '.') {
if ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING && $this->_tokens[$position + 1] === '.') {
$string = '';
$string = '';
while ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING || $this->_tokens[$position] === '.') {
while ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING || $this->_tokens[$position] === '.') {
if ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING) {
if ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING) {
$string .= $this->_formatString($this->_tokens[$position][1]);
$string .= $this->_formatString($this->_tokens[$position][1]);
}
}
$position++;
$position++;
}
}
$strings[] = $string;
$strings[] = $string;
} elseif ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING) {
} elseif ($this->_tokens[$position][0] == T_CONSTANT_ENCAPSED_STRING) {
$strings[] = $this->_formatString($this->_tokens[$position][1]);
$strings[] = $this->_formatString($this->_tokens[$position][1]);
} elseif ($this->_tokens[$position][0] == T_LNUMBER) {
} elseif ($this->_tokens[$position][0] == T_LNUMBER) {
$strings[] = $this->_tokens[$position][1];
$strings[] = $this->_tokens[$position][1];
}
}
$position++;
$position++;
}
}


return $strings;
return $strings;
}
}


/**
/**
* Format a string to be added as a translatable string
* Format a string to be added as a translatable string
*
*
* @param string $string String to format
* @param string $string String to format
* @return string Formatted string
* @return string Formatted string
*/
*/
protected function _formatString($string)
protected function _formatString($string)
{
{
$quote = substr($string, 0, 1);
$quote = substr($string, 0, 1);
$string = substr($string, 1, -1);
$string = substr($string, 1, -1);
if ($quote === '"') {
if ($quote === '"') {
$string = stripcslashes($string);
$string = stripcslashes($string);
} else {
} else {
$string = strtr($string, ["\\'" => "'", '\\\\' => '\\']);
$string = strtr($string, ["\\'" => "'", '\\\\' => '\\']);
}
}
$string = str_replace("\r\n", "\n", $string);
$string = str_replace("\r\n", "\n", $string);


return addcslashes($string, "\0..\37\\\"");
return addcslashes($string, "\0..\37\\\"");
}
}


/**
/**
* Indicate an invalid marker on a processed file
* Indicate an invalid marker on a processed file
*
*
* @param string $file File where invalid marker resides
* @param string $file File where invalid marker resides
* @param int $line Line number
* @param int $line Line number
* @param string $marker Marker found
* @param string $marker Marker found
* @param int $count Count
* @param int $count Count
* @return void
* @return void
*/
*/
protected function _markerError($file, $line, $marker, $count)
protected function _markerError($file, $line, $marker, $count)
{
{
$this->err(sprintf("Invalid marker content in %s:%s\n* %s(", $file, $line, $marker));
$this->err(sprintf("Invalid marker content in %s:%s\n* %s(", $file, $line, $marker));
$count += 2;
$count += 2;
$tokenCount = count($this->_tokens);
$tokenCount = count($this->_tokens);
$parenthesis = 1;
$parenthesis = 1;


while ((($tokenCount - $count) > 0) && $parenthesis) {
while ((($tokenCount - $count) > 0) && $parenthesis) {
if (is_array($this->_tokens[$count])) {
if (is_array($this->_tokens[$count])) {
$this->err($this->_tokens[$count][1], false);
$this->err($this->_tokens[$count][1], false);
} else {
} else {
$this->err($this->_tokens[$count], false);
$this->err($this->_tokens[$count], false);
if ($this->_tokens[$count] === '(') {
if ($this->_tokens[$count] === '(') {
$parenthesis++;
$parenthesis++;
}
}


if ($this->_tokens[$count] === ')') {
if ($this->_tokens[$count] === ')') {
$parenthesis--;
$parenthesis--;
}
}
}
}
$count++;
$count++;
}
}
$this->err("\n", true);
$this->err("\n", true);
}
}


/**
/**
* Search files that may contain translatable strings
* Search files that may contain translatable strings
*
*
* @return void
* @return void
*/
*/
protected function _searchFiles()
protected function _searchFiles()
{
{
$pattern = false;
$pattern = false;
if (!empty($this->_exclude)) {
if (!empty($this->_exclude)) {
$exclude = [];
$exclude = [];
foreach ($this->_exclude as $e) {
foreach ($this->_exclude as $e) {
if (DIRECTORY_SEPARATOR !== '\\' && $e[0] !== DIRECTORY_SEPARATOR) {
if (DIRECTORY_SEPARATOR !== '\\' && $e[0] !== DIRECTORY_SEPARATOR) {
$e = DIRECTORY_SEPARATOR . $e;
$e = DIRECTORY_SEPARATOR . $e;
}
}
$exclude[] = preg_quote($e, '/');
$exclude[] = preg_quote
}
$pattern = '/' . implode('|', $exclude) . '/';
}
foreach ($this->_paths as $path) {
$path = realpath($path) . DIRECTORY_SEPARATOR;
$Folder = new Folder($path);
$files = $Folder->findRecursive('.*\.(php|ctp|thtml|inc|tpl)', true);
if (!empty($pattern)) {
$files = preg_grep($pattern, $files, PREG_GREP_INVERT);
$files = array_values($files);
}
$this->_files = array_merge($this->_files, $files);
}
$this->_files = array_unique($this->