PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 2.15.5
Independent Analytics – WordPress Analytics Plugin v2.15.5
2.15.5 2.15.4 2.15.3 2.15.2 2.15.1 2.15.0 2.14.10 trunk 1.1 1.10 1.10.1 1.11 1.12 1.13 1.14 1.15 1.16 1.17 1.17.1 1.17.2 1.17.3 1.17.4 1.18 1.18.1 1.19.0 1.19.1 1.2 1.20.0 1.21.0 1.22.0 1.22.1 1.23.0 1.23.1 1.24.0 1.24.1 1.25.0 1.25.1 1.26.0 1.27.0 1.28.0 1.28.1 1.28.2 1.28.3 1.29.0 1.3 1.30.0 1.30.1 1.4 1.5 1.6 1.7 1.8 1.9 2.0.0 2.0.1 2.1.4 2.1.5 2.1.6 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.10 2.11.2 2.11.3 2.11.4 2.11.5 2.11.6 2.11.7 2.11.8 2.11.9 2.12.0 2.12.1 2.12.2 2.13.1 2.13.2 2.13.5 2.13.6 2.14.0 2.14.1 2.14.2 2.14.4 2.14.6 2.14.7 2.14.8 2.14.9 2.2.0 2.2.1 2.3.1 2.3.2 2.4.2 2.4.3 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.7.1 2.7.2 2.7.3 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7
independent-analytics / vendor / symfony / translation / Command / XliffLintCommand.php
independent-analytics / vendor / symfony / translation / Command Last commit date
TranslationPullCommand.php 1 month ago TranslationPushCommand.php 1 month ago TranslationTrait.php 2 years ago XliffLintCommand.php 1 month ago
XliffLintCommand.php
227 lines
1 <?php
2
3 /*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11 namespace IAWPSCOPED\Symfony\Component\Translation\Command;
12
13 use IAWPSCOPED\Symfony\Component\Console\Attribute\AsCommand;
14 use IAWPSCOPED\Symfony\Component\Console\CI\GithubActionReporter;
15 use IAWPSCOPED\Symfony\Component\Console\Command\Command;
16 use IAWPSCOPED\Symfony\Component\Console\Completion\CompletionInput;
17 use IAWPSCOPED\Symfony\Component\Console\Completion\CompletionSuggestions;
18 use IAWPSCOPED\Symfony\Component\Console\Exception\RuntimeException;
19 use IAWPSCOPED\Symfony\Component\Console\Input\InputArgument;
20 use IAWPSCOPED\Symfony\Component\Console\Input\InputInterface;
21 use IAWPSCOPED\Symfony\Component\Console\Input\InputOption;
22 use IAWPSCOPED\Symfony\Component\Console\Output\OutputInterface;
23 use IAWPSCOPED\Symfony\Component\Console\Style\SymfonyStyle;
24 use IAWPSCOPED\Symfony\Component\Translation\Exception\InvalidArgumentException;
25 use IAWPSCOPED\Symfony\Component\Translation\Util\XliffUtils;
26 /**
27 * Validates XLIFF files syntax and outputs encountered errors.
28 *
29 * @author Grégoire Pineau <lyrixx@lyrixx.info>
30 * @author Robin Chalas <robin.chalas@gmail.com>
31 * @author Javier Eguiluz <javier.eguiluz@gmail.com>
32 * @internal
33 */
34 #[AsCommand(name: 'lint:xliff', description: 'Lint an XLIFF file and outputs encountered errors')]
35 class XliffLintCommand extends Command
36 {
37 private string $format;
38 private bool $displayCorrectFiles;
39 private ?\Closure $directoryIteratorProvider;
40 private ?\Closure $isReadableProvider;
41 private bool $requireStrictFileNames;
42 public function __construct(string $name = null, callable $directoryIteratorProvider = null, callable $isReadableProvider = null, bool $requireStrictFileNames = \true)
43 {
44 parent::__construct($name);
45 $this->directoryIteratorProvider = null === $directoryIteratorProvider || $directoryIteratorProvider instanceof \Closure ? $directoryIteratorProvider : \Closure::fromCallable($directoryIteratorProvider);
46 $this->isReadableProvider = null === $isReadableProvider || $isReadableProvider instanceof \Closure ? $isReadableProvider : \Closure::fromCallable($isReadableProvider);
47 $this->requireStrictFileNames = $requireStrictFileNames;
48 }
49 /**
50 * {@inheritdoc}
51 */
52 protected function configure()
53 {
54 $this->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN')->addOption('format', null, InputOption::VALUE_REQUIRED, 'The output format')->setHelp(<<<EOF
55 The <info>%command.name%</info> command lints an XLIFF file and outputs to STDOUT
56 the first encountered syntax error.
57
58 You can validates XLIFF contents passed from STDIN:
59
60 <info>cat filename | php %command.full_name% -</info>
61
62 You can also validate the syntax of a file:
63
64 <info>php %command.full_name% filename</info>
65
66 Or of a whole directory:
67
68 <info>php %command.full_name% dirname</info>
69 <info>php %command.full_name% dirname --format=json</info>
70
71 EOF
72 );
73 }
74 protected function execute(InputInterface $input, OutputInterface $output) : int
75 {
76 $io = new SymfonyStyle($input, $output);
77 $filenames = (array) $input->getArgument('filename');
78 $this->format = $input->getOption('format') ?? (GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt');
79 $this->displayCorrectFiles = $output->isVerbose();
80 if (['-'] === $filenames) {
81 return $this->display($io, [$this->validate(\file_get_contents('php://stdin'))]);
82 }
83 if (!$filenames) {
84 throw new RuntimeException('Please provide a filename or pipe file content to STDIN.');
85 }
86 $filesInfo = [];
87 foreach ($filenames as $filename) {
88 if (!$this->isReadable($filename)) {
89 throw new RuntimeException(\sprintf('File or directory "%s" is not readable.', $filename));
90 }
91 foreach ($this->getFiles($filename) as $file) {
92 $filesInfo[] = $this->validate(\file_get_contents($file), $file);
93 }
94 }
95 return $this->display($io, $filesInfo);
96 }
97 private function validate(string $content, string $file = null) : array
98 {
99 $errors = [];
100 // Avoid: Warning DOMDocument::loadXML(): Empty string supplied as input
101 if ('' === \trim($content)) {
102 return ['file' => $file, 'valid' => \true];
103 }
104 $internal = \libxml_use_internal_errors(\true);
105 $document = new \DOMDocument();
106 $document->loadXML($content);
107 if (null !== ($targetLanguage = $this->getTargetLanguageFromFile($document))) {
108 $normalizedLocalePattern = \sprintf('(%s|%s)', \preg_quote($targetLanguage, '/'), \preg_quote(\str_replace('-', '_', $targetLanguage), '/'));
109 // strict file names require translation files to be named '____.locale.xlf'
110 // otherwise, both '____.locale.xlf' and 'locale.____.xlf' are allowed
111 // also, the regexp matching must be case-insensitive, as defined for 'target-language' values
112 // http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html#target-language
113 $expectedFilenamePattern = $this->requireStrictFileNames ? \sprintf('/^.*\\.(?i:%s)\\.(?:xlf|xliff)/', $normalizedLocalePattern) : \sprintf('/^(?:.*\\.(?i:%s)|(?i:%s)\\..*)\\.(?:xlf|xliff)/', $normalizedLocalePattern, $normalizedLocalePattern);
114 if (0 === \preg_match($expectedFilenamePattern, \basename($file))) {
115 $errors[] = ['line' => -1, 'column' => -1, 'message' => \sprintf('There is a mismatch between the language included in the file name ("%s") and the "%s" value used in the "target-language" attribute of the file.', \basename($file), $targetLanguage)];
116 }
117 }
118 foreach (XliffUtils::validateSchema($document) as $xmlError) {
119 $errors[] = ['line' => $xmlError['line'], 'column' => $xmlError['column'], 'message' => $xmlError['message']];
120 }
121 \libxml_clear_errors();
122 \libxml_use_internal_errors($internal);
123 return ['file' => $file, 'valid' => 0 === \count($errors), 'messages' => $errors];
124 }
125 private function display(SymfonyStyle $io, array $files)
126 {
127 switch ($this->format) {
128 case 'txt':
129 return $this->displayTxt($io, $files);
130 case 'json':
131 return $this->displayJson($io, $files);
132 case 'github':
133 return $this->displayTxt($io, $files, \true);
134 default:
135 throw new InvalidArgumentException(\sprintf('The format "%s" is not supported.', $this->format));
136 }
137 }
138 private function displayTxt(SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = \false)
139 {
140 $countFiles = \count($filesInfo);
141 $erroredFiles = 0;
142 $githubReporter = $errorAsGithubAnnotations ? new GithubActionReporter($io) : null;
143 foreach ($filesInfo as $info) {
144 if ($info['valid'] && $this->displayCorrectFiles) {
145 $io->comment('<info>OK</info>' . ($info['file'] ? \sprintf(' in %s', $info['file']) : ''));
146 } elseif (!$info['valid']) {
147 ++$erroredFiles;
148 $io->text('<error> ERROR </error>' . ($info['file'] ? \sprintf(' in %s', $info['file']) : ''));
149 $io->listing(\array_map(function ($error) use($info, $githubReporter) {
150 // general document errors have a '-1' line number
151 $line = -1 === $error['line'] ? null : $error['line'];
152 if ($githubReporter) {
153 $githubReporter->error($error['message'], $info['file'], $line, null !== $line ? $error['column'] : null);
154 }
155 return null === $line ? $error['message'] : \sprintf('Line %d, Column %d: %s', $line, $error['column'], $error['message']);
156 }, $info['messages']));
157 }
158 }
159 if (0 === $erroredFiles) {
160 $io->success(\sprintf('All %d XLIFF files contain valid syntax.', $countFiles));
161 } else {
162 $io->warning(\sprintf('%d XLIFF files have valid syntax and %d contain errors.', $countFiles - $erroredFiles, $erroredFiles));
163 }
164 return \min($erroredFiles, 1);
165 }
166 private function displayJson(SymfonyStyle $io, array $filesInfo)
167 {
168 $errors = 0;
169 \array_walk($filesInfo, function (&$v) use(&$errors) {
170 $v['file'] = (string) $v['file'];
171 if (!$v['valid']) {
172 ++$errors;
173 }
174 });
175 $io->writeln(\json_encode($filesInfo, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES));
176 return \min($errors, 1);
177 }
178 private function getFiles(string $fileOrDirectory)
179 {
180 if (\is_file($fileOrDirectory)) {
181 (yield new \SplFileInfo($fileOrDirectory));
182 return;
183 }
184 foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) {
185 if (!\in_array($file->getExtension(), ['xlf', 'xliff'])) {
186 continue;
187 }
188 (yield $file);
189 }
190 }
191 private function getDirectoryIterator(string $directory)
192 {
193 $default = function ($directory) {
194 return new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS), \RecursiveIteratorIterator::LEAVES_ONLY);
195 };
196 if (null !== $this->directoryIteratorProvider) {
197 return ($this->directoryIteratorProvider)($directory, $default);
198 }
199 return $default($directory);
200 }
201 private function isReadable(string $fileOrDirectory)
202 {
203 $default = function ($fileOrDirectory) {
204 return \is_readable($fileOrDirectory);
205 };
206 if (null !== $this->isReadableProvider) {
207 return ($this->isReadableProvider)($fileOrDirectory, $default);
208 }
209 return $default($fileOrDirectory);
210 }
211 private function getTargetLanguageFromFile(\DOMDocument $xliffContents) : ?string
212 {
213 foreach ($xliffContents->getElementsByTagName('file')[0]->attributes ?? [] as $attribute) {
214 if ('target-language' === $attribute->nodeName) {
215 return $attribute->nodeValue;
216 }
217 }
218 return null;
219 }
220 public function complete(CompletionInput $input, CompletionSuggestions $suggestions) : void
221 {
222 if ($input->mustSuggestOptionValuesFor('format')) {
223 $suggestions->suggestValues(['txt', 'json', 'github']);
224 }
225 }
226 }
227