|
| 1 | +<?php |
| 2 | + |
| 3 | +/** |
| 4 | + * |
| 5 | + * EPV :: The phpBB Forum Extension Pre Validator. |
| 6 | + * |
| 7 | + * @copyright (c) 2017 phpBB Limited <https://www.phpbb.com> |
| 8 | + * @license GNU General Public License, version 2 (GPL-2.0) |
| 9 | + * |
| 10 | + */ |
| 11 | + |
| 12 | +namespace Phpbb\Epv\Tests\Tests; |
| 13 | + |
| 14 | +use Phpbb\Epv\Output\OutputInterface; |
| 15 | +use Phpbb\Epv\Tests\BaseTest; |
| 16 | +use PhpParser\Error; |
| 17 | +use PhpParser\Node\Expr\Array_; |
| 18 | +use PhpParser\Node\Expr\ArrayItem; |
| 19 | +use PhpParser\Node\Expr\Assign; |
| 20 | +use PhpParser\Node\Expr\FuncCall; |
| 21 | +use PhpParser\Node\Scalar\String_; |
| 22 | +use PhpParser\Parser; |
| 23 | +use PhpParser\ParserFactory; |
| 24 | + |
| 25 | +class epv_test_validate_languages extends BaseTest |
| 26 | +{ |
| 27 | + /** |
| 28 | + * @var Parser |
| 29 | + */ |
| 30 | + private $parser; |
| 31 | + |
| 32 | + /** |
| 33 | + * @param bool $debug if debug is enabled |
| 34 | + * @param OutputInterface $output |
| 35 | + * @param string $basedir |
| 36 | + * @param string $namespace |
| 37 | + * @param boolean $titania |
| 38 | + * @param string $opendir |
| 39 | + */ |
| 40 | + public function __construct($debug, OutputInterface $output, $basedir, $namespace, $titania, $opendir) |
| 41 | + { |
| 42 | + parent::__construct($debug, $output, $basedir, $namespace, $titania, $opendir); |
| 43 | + |
| 44 | + $this->directory = true; |
| 45 | + $this->parser = (new ParserFactory)->create(ParserFactory::PREFER_PHP7); |
| 46 | + } |
| 47 | + |
| 48 | + /** |
| 49 | + * @param array $files |
| 50 | + * |
| 51 | + * @return void |
| 52 | + */ |
| 53 | + public function validateDirectory(array $files) |
| 54 | + { |
| 55 | + $langs = []; |
| 56 | + $expected_keys = []; |
| 57 | + $expected_files = []; |
| 58 | + |
| 59 | + foreach ($files as $file) |
| 60 | + { |
| 61 | + if (preg_match('#^' . $this->basedir . 'language/([a-z_]+?)/(.+\.php)$#', $file, $matches) === 1) |
| 62 | + { |
| 63 | + $language = $matches[1]; // language, e.g. "en" |
| 64 | + $relative_filename = $matches[2]; // file name relative to language's base dir, e.g. "info_acp_ext.php" |
| 65 | + $expected_files[$relative_filename] = $relative_filename; |
| 66 | + |
| 67 | + try |
| 68 | + { |
| 69 | + $keys = $this->load_language_keys($file); |
| 70 | + $langs[$language][$relative_filename] = $keys; |
| 71 | + |
| 72 | + $lang_keys = isset($expected_keys[$relative_filename]) ? $expected_keys[$relative_filename] : []; |
| 73 | + $expected_keys[$relative_filename] = array_unique(array_merge($lang_keys, $keys)); |
| 74 | + } |
| 75 | + catch (Error $e) |
| 76 | + { |
| 77 | + $this->output->addMessage(OutputInterface::FATAL, 'PHP parse error in file ' . $file->getSaveFilename() . '. Message: ' . $e->getMessage()); |
| 78 | + } |
| 79 | + } |
| 80 | + } |
| 81 | + |
| 82 | + foreach ($langs as $lang_name => $file_contents) |
| 83 | + { |
| 84 | + // Check for missing language files |
| 85 | + foreach (array_diff($expected_files, array_keys($file_contents)) as $missing_file) |
| 86 | + { |
| 87 | + $this->output->addMessage(OutputInterface::NOTICE, sprintf("Language %s is missing the language file %s", $lang_name, $missing_file)); |
| 88 | + } |
| 89 | + |
| 90 | + // Check for missing language keys |
| 91 | + foreach ($file_contents as $relative_filename => $present_keys) |
| 92 | + { |
| 93 | + foreach (array_diff($expected_keys[$relative_filename], $present_keys) as $missing_key) |
| 94 | + { |
| 95 | + $this->output->addMessage(OutputInterface::WARNING, sprintf("Language file %s/%s is missing the language key %s", $lang_name, $relative_filename, $missing_key)); |
| 96 | + } |
| 97 | + } |
| 98 | + } |
| 99 | + } |
| 100 | + |
| 101 | + /** |
| 102 | + * This method scans through all global-scoped calls to array_merge |
| 103 | + * and extracts all string keys of all array arguments. |
| 104 | + * |
| 105 | + * @param string $filename File name to a phpBB language file |
| 106 | + * @return array |
| 107 | + * @throws Error |
| 108 | + */ |
| 109 | + protected function load_language_keys($filename) |
| 110 | + { |
| 111 | + $contents = @file_get_contents($filename); |
| 112 | + |
| 113 | + $keys = []; |
| 114 | + |
| 115 | + $nodes = $this->parser->parse($contents); |
| 116 | + |
| 117 | + foreach ($nodes as $node) |
| 118 | + { |
| 119 | + if ($node instanceof Assign && $node->expr instanceof FuncCall) |
| 120 | + { |
| 121 | + /** @var FuncCall $expr */ |
| 122 | + $expr = $node->expr; |
| 123 | + |
| 124 | + if ($expr->name->getFirst() === 'array_merge') |
| 125 | + { |
| 126 | + for ($i = 1; $i < sizeof($expr->args); $i++) |
| 127 | + { |
| 128 | + /** @var Array_ $array */ |
| 129 | + $array = $expr->args[$i]->value; |
| 130 | + |
| 131 | + if (!($array instanceof Array_)) |
| 132 | + { |
| 133 | + throw new Error(sprintf('Expected argument %d of array_merge() to be %s, got %s', $i + 1, Array_::class, get_class($array)), $array->getLine()); |
| 134 | + } |
| 135 | + |
| 136 | + foreach ($array->items as $item) |
| 137 | + { |
| 138 | + /** @var ArrayItem $item */ |
| 139 | + if ($item->key instanceof String_) |
| 140 | + { |
| 141 | + $keys[] = $item->key->value; |
| 142 | + } |
| 143 | + else |
| 144 | + { |
| 145 | + $this->output->addMessage(OutputInterface::NOTICE, 'Language key is not a string value in ' . substr($filename, strlen($this->basedir)) . ' on line ' . $item->key->getLine()); |
| 146 | + } |
| 147 | + } |
| 148 | + } |
| 149 | + } |
| 150 | + } |
| 151 | + } |
| 152 | + |
| 153 | + return $keys; |
| 154 | + } |
| 155 | + |
| 156 | + /** |
| 157 | + * |
| 158 | + * @return String |
| 159 | + */ |
| 160 | + public function testName() |
| 161 | + { |
| 162 | + return 'Test languages'; |
| 163 | + } |
| 164 | +} |
0 commit comments