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  }
116  }
117  }
118 
127  protected function cleanupMissingTranslationParameters(string $directory, string $language_code): void {
128  $english = Includer::includeFile("{$directory}/en.php");
129  $translation = Includer::includeFile("{$directory}/{$language_code}.php");
130 
131  foreach ($english as $key => $value) {
132  $english_matches = preg_match_all('/%[a-zA-Z]/m', $value);
133  if (!array_key_exists($key, $translation) || $english_matches === false) {
134  continue;
135  }
136 
137  $translation_matches = preg_match_all('/%[a-zA-Z]/m', $translation[$key]);
138  if ($translation_matches !== false && $english_matches === $translation_matches) {
139  continue;
140  }
141 
142  $file_contents = file_get_contents("{$directory}/{$language_code}.php");
143 
144  $pattern = '/^\s*[\'"]' . $key . '[\'"] => [\'"]' . preg_quote($translation[$key], '/') . '[\'"],{0,1}\R/m';
145  $count = 0;
146  $file_contents = preg_replace($pattern, '', $file_contents, -1, $count);
147  if ($count < 1) {
148  // try to add slashes for quotes
149  $pattern = '/^\s*[\'"]' . $key . '[\'"] => [\'"]' . preg_quote(addslashes($translation[$key]), '/') . '[\'"],{0,1}\R/m';
150  $count = 0;
151  $file_contents = preg_replace($pattern, '', $file_contents, -1, $count);
152  }
153 
154  if ($count > 0) {
155  file_put_contents("{$directory}/{$language_code}.php", $file_contents);
156  } else {
157  $this->log[] = "Unable to repair mismatch in translation argument count in {$directory}/{$language_code}.php for the key '{$key}'";
158  }
159  }
160  }
161 
169  protected function cleanupEmptyTranslations(string $translation_file): void {
170  $contents = file_get_contents($translation_file);
171  if (empty($contents)) {
172  return;
173  }
174 
175  $pattern = '/^\s*[\'"].*[\'"] => [\'"]{2},{0,1}\R/m';
176  $count = 0;
177  $contents = preg_replace($pattern, '', $contents, -1, $count);
178 
179  if ($count > 0) {
180  // something was changed
181  file_put_contents($translation_file, $contents);
182 
183  $translations = Includer::includeFile($translation_file);
184  if (!empty($translations)) {
185  $this->log[] = "Cleaned empty translations from {$translation_file}";
186  } else {
187  unlink($translation_file);
188 
189  $this->log[] = "Removed empty translation file {$translation_file}";
190  }
191  }
192  }
193 
201  protected function normalizeLanguageCode(string $code): string {
202  $code = strtolower($code);
203  return preg_replace('~[^a-z0-9]~', '_', $code);
204  }
205 
214  protected function detectAdditionalKeys(string $directory, string $language_code): void {
215  $english = Includer::includeFile("{$directory}/en.php");
216  $translation = Includer::includeFile("{$directory}/{$language_code}.php");
217 
218  foreach ($translation as $key => $value) {
219  if (array_key_exists($key, $english)) {
220  continue;
221  }
222 
223  $this->log[] = "The translation key '{$key}' exists in the '{$language_code}' translation but not in English";
224  }
225  }
226 
235  protected function detectIdenticalTranslations(string $directory, string $language_code): void {
236  $english = Includer::includeFile("{$directory}/en.php");
237  $translation = Includer::includeFile("{$directory}/{$language_code}.php");
238 
239  foreach ($translation as $key => $value) {
240  if (!array_key_exists($key, $english)) {
241  // shouldn't happen
242  continue;
243  }
244 
245  if (strlen($key) < 3) {
246  // probably a language code
247  continue;
248  }
249 
250  if ($english[$key] !== $value) {
251  continue;
252  }
253 
254  $this->log[] = "The translation key '{$key}' in the '{$language_code}' translation is identical to the English translation";
255  }
256  }
257 }
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:28
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.
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:353
$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