Elgg  Version master
ReleaseCleaner.php
Go to the documentation of this file.
1 <?php
2 
3 namespace Elgg\I18n;
4 
6 use Elgg\Includer;
8 
15 
19  private $codes;
20 
24  public $log = [];
25 
31  public function __construct(array $codes = []) {
32  if (empty($codes)) {
33  $codes = _elgg_services()->locale->getLanguageCodes();
34  }
35 
36  $this->codes = $codes;
37  }
38 
46  public function cleanInstallation(string $dir): void {
47  $dir = Paths::sanitize($dir, false);
48 
49  if (is_dir("{$dir}/install/languages")) {
50  $this->cleanLanguagesDir("{$dir}/install/languages");
51  }
52 
53  if (is_dir("{$dir}/languages")) {
54  $this->cleanLanguagesDir("{$dir}/languages");
55  }
56 
57  $mods = new \DirectoryIterator("{$dir}/mod");
58 
59  foreach ($mods as $mod) {
60  if ($mod->isDot() || !$mod->isDir()) {
61  continue;
62  }
63 
64  if (!in_array($mod->getFilename(), Plugins::BUNDLED_PLUGINS)) {
65  // not a core plugin
66  continue;
67  }
68 
69  if (is_dir("{$mod->getPathname()}/languages")) {
70  // only process plugins which have translations
71  $this->cleanLanguagesDir("{$mod->getPathname()}/languages");
72  }
73  }
74  }
75 
83  public function cleanLanguagesDir(string $dir): void {
84  $dir = Paths::sanitize($dir, false);
85 
86  $files = new \DirectoryIterator($dir);
87  foreach ($files as $file) {
88  if ($file->isDot() || !$file->isFile()) {
89  continue;
90  }
91 
92  if ($file->getExtension() !== 'php') {
93  continue;
94  }
95 
96  $code = $file->getBasename('.php');
97  if (!in_array($code, $this->codes)) {
98  $code = $this->normalizeLanguageCode($code);
99 
100  if (in_array($code, $this->codes)) {
101  // rename file to lowercase
102  rename($file->getPathname(), "{$dir}/{$code}.php");
103  $this->log[] = "Renamed {$file->getPathname()} to {$code}.php";
104  } else {
105  unlink($file->getPathname());
106  $this->log[] = "Removed {$file->getPathname()}";
107  }
108  }
109 
110  if ($code !== 'en' && file_exists("{$dir}/{$code}.php")) {
111 // $this->detectAdditionalKeys($dir, $code);
112 // $this->detectIdenticalTranslations($dir, $code);
113  $this->cleanupMissingTranslationParameters($dir, $code);
114  $this->cleanupEmptyTranslations("{$dir}/{$code}.php");
115  $this->cleanupTranslationsThreshold($dir, $code);
116  }
117  }
118  }
119 
128  protected function cleanupMissingTranslationParameters(string $directory, string $language_code): void {
129  $english = Includer::includeFile("{$directory}/en.php");
130  $translation = Includer::includeFile("{$directory}/{$language_code}.php");
131 
132  foreach ($english as $key => $value) {
133  $english_matches = preg_match_all('/%[a-zA-Z]/m', $value);
134  if (!array_key_exists($key, $translation) || $english_matches === false) {
135  continue;
136  }
137 
138  $translation_matches = preg_match_all('/%[a-zA-Z]/m', $translation[$key]);
139  if ($translation_matches !== false && $english_matches === $translation_matches) {
140  continue;
141  }
142 
143  $file_contents = file_get_contents("{$directory}/{$language_code}.php");
144 
145  $pattern = '/^\s*[\'"]' . $key . '[\'"] => [\'"]' . preg_quote($translation[$key], '/') . '[\'"],{0,1}\R/m';
146  $count = 0;
147  $file_contents = preg_replace($pattern, '', $file_contents, -1, $count);
148  if ($count < 1) {
149  // try to add slashes for quotes
150  $pattern = '/^\s*[\'"]' . $key . '[\'"] => [\'"]' . preg_quote(addslashes($translation[$key]), '/') . '[\'"],{0,1}\R/m';
151  $count = 0;
152  $file_contents = preg_replace($pattern, '', $file_contents, -1, $count);
153  }
154 
155  if ($count > 0) {
156  file_put_contents("{$directory}/{$language_code}.php", $file_contents);
157  } else {
158  $this->log[] = "Unable to repair mismatch in translation argument count in {$directory}/{$language_code}.php for the key '{$key}'";
159  }
160  }
161  }
162 
170  protected function cleanupEmptyTranslations(string $translation_file): void {
171  $contents = file_get_contents($translation_file);
172  if (empty($contents)) {
173  return;
174  }
175 
176  $pattern = '/^\s*[\'"].*[\'"] => [\'"]{2},{0,1}\R/m';
177  $count = 0;
178  $contents = preg_replace($pattern, '', $contents, -1, $count);
179 
180  if ($count > 0) {
181  // something was changed
182  file_put_contents($translation_file, $contents);
183 
184  $translations = Includer::includeFile($translation_file);
185  if (!empty($translations)) {
186  $this->log[] = "Cleaned empty translations from {$translation_file}";
187  } else {
188  unlink($translation_file);
189 
190  $this->log[] = "Removed empty translation file {$translation_file}";
191  }
192  }
193  }
194 
204  protected function cleanupTranslationsThreshold(string $directory, string $language_code, int $min_percentage = 50): void {
205  if (!file_exists("{$directory}/{$language_code}.php")) {
206  // can happen if cleanupEmptyTranslations() removed the file
207  return;
208  }
209 
210  $english = Includer::includeFile("{$directory}/en.php");
211  $translation = Includer::includeFile("{$directory}/{$language_code}.php");
212 
213  if (empty($translation)) {
214  unlink("{$directory}/{$language_code}.php");
215 
216  $this->log[] = "Removed empty translation file {$directory}/{$language_code}.php";
217  return;
218  }
219 
220  $trans_percentage = (count($translation) / count($english)) * 100;
221  if ($trans_percentage < $min_percentage) {
222  unlink("{$directory}/{$language_code}.php");
223  $trans_percentage = round($trans_percentage, 2);
224 
225  $this->log[] = "Removed translation file {$directory}/{$language_code}.php below minimal threshold ({$trans_percentage} < {$min_percentage})";
226  }
227  }
228 
236  protected function normalizeLanguageCode(string $code): string {
237  $code = strtolower($code);
238  return preg_replace('~[^a-z0-9]~', '_', $code);
239  }
240 
249  protected function detectAdditionalKeys(string $directory, string $language_code): void {
250  $english = Includer::includeFile("{$directory}/en.php");
251  $translation = Includer::includeFile("{$directory}/{$language_code}.php");
252 
253  foreach ($translation as $key => $value) {
254  if (array_key_exists($key, $english)) {
255  continue;
256  }
257 
258  $this->log[] = "The translation key '{$key}' exists in the '{$language_code}' translation but not in English";
259  }
260  }
261 
270  protected function detectIdenticalTranslations(string $directory, string $language_code): void {
271  $english = Includer::includeFile("{$directory}/en.php");
272  $translation = Includer::includeFile("{$directory}/{$language_code}.php");
273 
274  foreach ($translation as $key => $value) {
275  if (!array_key_exists($key, $english)) {
276  // shouldn't happen
277  continue;
278  }
279 
280  if (strlen($key) < 3) {
281  // probably a language code
282  continue;
283  }
284 
285  if ($english[$key] !== $value) {
286  continue;
287  }
288 
289  $this->log[] = "The translation key '{$key}' in the '{$language_code}' translation is identical to the English translation";
290  }
291  }
292 }
log($level, $message, array $context=[])
Log a message.
Definition: Loggable.php:58
$count
Definition: ban.php:24
Persistent, installation-wide key-value storage.
Definition: Plugins.php:27
Removes invalid language files from an installation.
__construct(array $codes=[])
Constructor.
cleanupMissingTranslationParameters(string $directory, string $language_code)
Try to cleanup translations with a different argument count than English as this can cause failed tra...
detectIdenticalTranslations(string $directory, string $language_code)
Detect identical translations, this could be due to a wrong Transifex import.
cleanLanguagesDir(string $dir)
Clean up a languages dir.
cleanInstallation(string $dir)
Clean up within an installation.
cleanupEmptyTranslations(string $translation_file)
Remove empty translations from a translation file.
cleanupTranslationsThreshold(string $directory, string $language_code, int $min_percentage=50)
Remove translation files with translations below a threshold.
detectAdditionalKeys(string $directory, string $language_code)
Detect translation keys that (still) exist in a translation but no longer in the English translation.
normalizeLanguageCode(string $code)
Normalize a language code (e.g.
Allow executing scripts without $this context or local vars.
Definition: Includer.php:10
Find Elgg and project paths.
Definition: Paths.php:8
_elgg_services()
Get the global service provider.
Definition: elgglib.php:347
$value
Definition: generic.php:51
foreach(array_keys($combine_languages) as $language) $translations
if(!empty($title) &&!empty($icon_name)) if(!empty($title)) if(!empty($menu)) if(!empty($header)) if(!empty($body)) $contents
Definition: message.php:73
if(! $plugin instanceof \ElggPlugin) $file_contents
if($container instanceof ElggGroup && $container->guid !=elgg_get_page_owner_guid()) $key
Definition: summary.php:44
if(parse_url(elgg_get_site_url(), PHP_URL_PATH) !=='/') if(file_exists(elgg_get_root_path() . 'robots.txt'))
Set robots.txt.
Definition: robots.php:10