Commit e88a5d9c by Alexander Makarov

Fixes #4823 and #6005: `yii message` accuracy and error handling were improved…

Fixes #4823 and #6005: `yii message` accuracy and error handling were improved by using PHP tokenizer instead of regular expressions. Removed eval() as well.
parent acbf0ccc
...@@ -5,6 +5,7 @@ Yii Framework 2 Change Log ...@@ -5,6 +5,7 @@ Yii Framework 2 Change Log
----------------------- -----------------------
- Bug #4471: `yii\caching\ApcCache::getValues()` now returns array in case of APC is installed but not enabled in CLI mode (samdark, cebe) - Bug #4471: `yii\caching\ApcCache::getValues()` now returns array in case of APC is installed but not enabled in CLI mode (samdark, cebe)
- Bug #4823: `yii message` accuracy and error handling were improved (samdark)
- Bug #4889: Application was getting into redirect loop when user wasn't allowed accessing login page. Now shows 403 (samdark) - Bug #4889: Application was getting into redirect loop when user wasn't allowed accessing login page. Now shows 403 (samdark)
- Bug #5402: Debugger was not loading when there were closures in asset classes (samdark) - Bug #5402: Debugger was not loading when there were closures in asset classes (samdark)
- Bug #5452: Errors occurring after the response is sent are not displayed (qiangxue) - Bug #5452: Errors occurring after the response is sent are not displayed (qiangxue)
......
...@@ -244,40 +244,103 @@ class MessageController extends Controller ...@@ -244,40 +244,103 @@ class MessageController extends Controller
*/ */
protected function extractMessages($fileName, $translator) protected function extractMessages($fileName, $translator)
{ {
echo "Extracting messages from $fileName...\n"; $coloredFileName = Console::ansiFormat($fileName, [Console::FG_CYAN]);
$this->stdout("Extracting messages from $coloredFileName...\n");
$subject = file_get_contents($fileName); $subject = file_get_contents($fileName);
$messages = []; $messages = [];
if (!is_array($translator)) { if (!is_array($translator)) {
$translator = [$translator]; $translator = [$translator];
} }
foreach ($translator as $currentTranslator) { foreach ($translator as $currentTranslator) {
$n = preg_match_all( $translatorTokens = token_get_all('<?php ' . $currentTranslator);
'/\b' . $currentTranslator . '\s*\(\s*(\'.*?(?<!\\\\)\'|".*?(?<!\\\\)")\s*,\s*(\'.*?(?<!\\\\)\'|".*?(?<!\\\\)")\s*[,\)]/s', array_shift($translatorTokens);
$subject,
$matches, $translatorTokensCount = count($translatorTokens);
PREG_SET_ORDER $matchedTokensCount = 0;
); $buffer = [];
for ($i = 0; $i < $n; ++$i) {
$category = substr($matches[$i][1], 1, -1); $tokens = token_get_all($subject);
$message = $matches[$i][2]; foreach ($tokens as $token) {
try { // finding out translator call
$messages[$category][] = eval("return {$message};"); // use eval to eliminate quote escape if ($matchedTokensCount < $translatorTokensCount) {
} catch (ErrorException $e) { if ($this->tokensEqual($token, $translatorTokens[$matchedTokensCount])) {
$category = Console::ansiFormat($category, [Console::FG_CYAN]); $matchedTokensCount++;
$message = Console::ansiFormat($message, [Console::FG_CYAN]); } else {
$fileName = Console::ansiFormat($fileName, [Console::FG_CYAN]); $matchedTokensCount = 0;
$error = Console::ansiFormat($e->getMessage(), [Console::FG_RED]); }
} elseif ($matchedTokensCount === $translatorTokensCount) {
// translator found
// end of translator call or end of something that we can't extract
if ($this->tokensEqual(')', $token)) {
if (isset($buffer[0][0], $buffer[1], $buffer[2][0]) && $buffer[0][0] === T_CONSTANT_ENCAPSED_STRING && $buffer[1] === ',' && $buffer[2][0] === T_CONSTANT_ENCAPSED_STRING) {
// is valid call we can extract
$category = stripcslashes($buffer[0][1]);
$category = mb_substr($category, 1, mb_strlen($category) - 2);
$message = stripcslashes($buffer[2][1]);
$message = mb_substr($message, 1, mb_strlen($message) - 2);
$messages[$category][] = $message;
} else {
// invalid call or dynamic call we can't extract
$this->stdout("Failed parsing $fileName, $message in $category category:\n" . $error . "\n"); $line = Console::ansiFormat($this->getLine($buffer), [Console::FG_CYAN]);
Yii::$app->end(self::EXIT_CODE_ERROR); $skipping = Console::ansiFormat('Skipping line', [Console::FG_YELLOW]);
$this->stdout("$skipping $line. Make sure both category and message are static strings.\n");
}
// prepare for the next match
$matchedTokensCount = 0;
$buffer = [];
} elseif ($token !== '(' && isset($token[0]) && !in_array($token[0], [T_WHITESPACE, T_COMMENT])) {
// ignore comments, whitespaces and beginning of function call
$buffer[] = $token;
} }
} }
} }
}
$this->stdout("\n");
return $messages; return $messages;
} }
/** /**
* Finds out if two PHP tokens are equal
*
* @param array|string $a
* @param array|string $b
* @return boolean
*/
protected function tokensEqual($a, $b)
{
if (is_string($a) && is_string($b)) {
return $a === $b;
} elseif (isset($a[0], $a[1], $b[0], $b[1])) {
return $a[0] === $b[0] && $a[1] == $b[1];
}
return false;
}
/**
* Finds out a line of the first non-char PHP token found
*
* @param array $tokens
* @return int|string
*/
protected function getLine($tokens)
{
foreach ($tokens as $token) {
if (isset($token[2])) {
return $token[2];
}
}
return 'unknown';
}
/**
* Writes messages into PHP files * Writes messages into PHP files
* *
* @param array $messages * @param array $messages
...@@ -293,7 +356,8 @@ class MessageController extends Controller ...@@ -293,7 +356,8 @@ class MessageController extends Controller
$path = dirname($file); $path = dirname($file);
FileHelper::createDirectory($path); FileHelper::createDirectory($path);
$msgs = array_values(array_unique($msgs)); $msgs = array_values(array_unique($msgs));
echo "Saving messages to $file...\n"; $coloredFileName = Console::ansiFormat($file, [Console::FG_CYAN]);
echo "Saving messages to $coloredFileName...\n";
$this->saveMessagesCategoryToPHP($msgs, $file, $overwrite, $removeUnused, $sort, $category); $this->saveMessagesCategoryToPHP($msgs, $file, $overwrite, $removeUnused, $sort, $category);
} }
} }
...@@ -315,7 +379,7 @@ class MessageController extends Controller ...@@ -315,7 +379,7 @@ class MessageController extends Controller
sort($messages); sort($messages);
ksort($existingMessages); ksort($existingMessages);
if (array_keys($existingMessages) == $messages) { if (array_keys($existingMessages) == $messages) {
echo "Nothing new in \"$category\" category... Nothing to save.\n"; echo "Nothing new in \"$category\" category... Nothing to save.\n\n";
return; return;
} }
$merged = []; $merged = [];
...@@ -385,7 +449,7 @@ return $array; ...@@ -385,7 +449,7 @@ return $array;
EOD; EOD;
file_put_contents($fileName, $content); file_put_contents($fileName, $content);
echo "Saved.\n"; echo "Saved.\n\n";
} }
/** /**
......
...@@ -86,7 +86,7 @@ abstract class BaseMessageControllerTest extends TestCase ...@@ -86,7 +86,7 @@ abstract class BaseMessageControllerTest extends TestCase
protected function createSourceFile($content) protected function createSourceFile($content)
{ {
$fileName = $this->sourcePath . DIRECTORY_SEPARATOR . md5(uniqid()) . '.php'; $fileName = $this->sourcePath . DIRECTORY_SEPARATOR . md5(uniqid()) . '.php';
file_put_contents($fileName, $content); file_put_contents($fileName, "<?php\n" . $content);
return $fileName; return $fileName;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment