ILIAS  trunk Revision v13.0_alpha-62-g3b72d83f2e5
MigratedLanguageFilePaths.php
Go to the documentation of this file.
1<?php
2
19declare(strict_types=1);
20
22
39{
40 private const string LANGUAGE_KEY_FORMAT = '/^[a-z]{2}\z/';
41
45 private const string MODULE_FORMAT = '/^[A-Za-z0-9_-]+\z/';
46
47 private const string SHIPPED_ARTIFACT_DIRECTORY = 'artifacts/language';
48 private const string BUILD_POINTER_FILE = 'current.json';
49 private const string SYSTEM_LOCALE_BUILD_POINTER_FILE = 'current-system.json';
50 private const string BUILD_ID_FORMAT = '/^b-[0-9a-f]{16,64}\z/';
51 private const string OVERLAY_REVISION_FORMAT = '/^r-[0-9a-f]{16,64}\z/';
52 private const string SHIPPED_TEMPLATES = 'components/*/*/lang/*.pot';
53
54 private static ?string $artifact_directory_for_tests = null;
55
70 public static function resolveClientDataDir(?string $ilias_absolute_path = null): ?string
71 {
72 if (defined('CLIENT_DATA_DIR')) {
73 return (string) CLIENT_DATA_DIR;
74 }
75
76 $ilias_absolute_path ??= dirname(__DIR__, 5);
77 $ini_file = rtrim($ilias_absolute_path, '/') . '/ilias.ini.php';
78 if (!is_file($ini_file) || !is_readable($ini_file)) {
79 return null;
80 }
81 $ini = @parse_ini_file($ini_file, true);
82 if (!is_array($ini)) {
83 return null;
84 }
85
86 $data_dir = $ini['clients']['datadir'] ?? null;
87 if (is_string($data_dir) && $data_dir !== '' && !str_starts_with($data_dir, '/')) {
88 $data_dir = rtrim($ilias_absolute_path, '/') . '/' . $data_dir;
89 }
90
91 $client_id = defined('CLIENT_ID') ? (string) CLIENT_ID : ($ini['clients']['default'] ?? null);
92
94 is_string($data_dir) ? $data_dir : null,
95 is_string($client_id) ? $client_id : null
96 );
97 }
98
103 public static function fromDataDirAndClientId(?string $data_dir, ?string $client_id): ?string
104 {
105 if ($data_dir === null || $data_dir === '' || $client_id === null || $client_id === '') {
106 return null;
107 }
108 if ($client_id === '.' || $client_id === '..' || strpbrk($client_id, "/\\\0") !== false) {
109 return null;
110 }
111
112 $client_data_dir = rtrim($data_dir, '/') . '/' . $client_id;
113
114 return is_dir($client_data_dir) ? $client_data_dir : null;
115 }
116
123 public static function shippedBasePath(
124 string $ilias_absolute_path,
125 LanguageFileDirectory $directory,
126 string $lang_key
127 ): string {
128 self::assertValidLanguageKey($lang_key);
129
130 return self::shippedDirectoryPath($ilias_absolute_path, $directory)
131 . sprintf(self::shippedFileNamePattern($directory), $lang_key);
132 }
133
141 public static function findShippedPoFiles(string $ilias_absolute_path, LanguageFileDirectory $directory): array
142 {
143 $pattern = self::shippedFileNamePattern($directory);
144 $base = self::shippedDirectoryPath($ilias_absolute_path, $directory);
145 [$before, $after] = explode('%s', $pattern, 2);
146 $name_format = '/\A' . preg_quote($before, '/') . '([a-z]{2})' . preg_quote($after, '/') . '\.po\z/';
147
148 $files = [];
149 foreach (glob($base . sprintf($pattern, '*') . '.po') ?: [] as $file) {
150 if (is_file($file) && preg_match($name_format, basename($file), $matches) === 1) {
151 $files[$matches[1]] = $file;
152 }
153 }
154 ksort($files, SORT_STRING);
155
156 return $files;
157 }
158
164 public static function shippedTemplatePath(string $ilias_absolute_path, LanguageFileDirectory $directory): string
165 {
166 return self::shippedDirectoryPath($ilias_absolute_path, $directory)
167 . self::templateFileName(self::shippedFileNamePattern($directory), $directory->getPrefix());
168 }
169
175 public static function findShippedTemplates(string $ilias_absolute_path): array
176 {
177 $templates = [];
178 foreach (glob(rtrim($ilias_absolute_path, '/') . '/' . self::SHIPPED_TEMPLATES) ?: [] as $file) {
179 $module = basename($file, '.pot');
180 if (self::isValidModule($module) && is_file($file)) {
181 $templates[$module] ??= dirname($file);
182 }
183 }
184
185 return $templates;
186 }
187
193 public static function isFoundWithoutGraph(string $ilias_absolute_path, LanguageFileDirectory $directory): bool
194 {
195 $template = self::shippedTemplatePath($ilias_absolute_path, $directory);
196
197 return basename($template) === $directory->getPrefix() . '.pot'
198 && fnmatch(rtrim($ilias_absolute_path, '/') . '/' . self::SHIPPED_TEMPLATES, $template, FNM_PATHNAME)
199 && is_file($template);
200 }
201
207 public static function templateFileName(string $pattern, string $module): string
208 {
209 $name = trim(str_replace('%s', '', $pattern), '_-.');
210
211 return ($name === '' ? $module : $name) . '.pot';
212 }
213
214 private static function shippedDirectoryPath(string $ilias_absolute_path, LanguageFileDirectory $directory): string
215 {
216 return rtrim($ilias_absolute_path, '/') . '/' . ltrim($directory->getPath(), '/');
217 }
218
226 public static function shippedFileNamePattern(LanguageFileDirectory $directory): string
227 {
228 $pattern = $directory instanceof NamesShippedLanguageFiles
229 ? $directory->getShippedFileNamePattern()
230 : $directory->getPrefix() . '_%s';
231 self::assertValidShippedFileNamePattern($pattern);
232
233 return $pattern;
234 }
235
240 public static function assertValidShippedFileNamePattern(string $pattern): void
241 {
242 if (
243 substr_count($pattern, '%s') !== 1
244 || str_contains($pattern, '..')
245 || preg_match('/\A[A-Za-z0-9_.\-]*%s[A-Za-z0-9_.\-]*\z/', $pattern) !== 1
246 ) {
247 throw new \InvalidArgumentException(sprintf('"%s" is not a valid file name pattern for shipped language files.', $pattern));
248 }
249 }
250
254 public static function shippedArtifactDirectory(string $ilias_absolute_path): string
255 {
256 return self::$artifact_directory_for_tests
257 ?? rtrim($ilias_absolute_path, '/') . '/' . self::SHIPPED_ARTIFACT_DIRECTORY;
258 }
259
266 public static function useArtifactDirectoryForTests(?string $artifact_directory): void
267 {
268 self::$artifact_directory_for_tests = $artifact_directory;
269 }
270
275 public static function buildPointerFile(string $artifact_directory): string
276 {
277 return self::buildPointerFileOf($artifact_directory, NativeGettext::usesSystemLocale());
278 }
279
281 public static function buildPointerFileOf(string $artifact_directory, bool $system_locale): string
282 {
283 return rtrim($artifact_directory, '/') . '/'
284 . ($system_locale ? self::SYSTEM_LOCALE_BUILD_POINTER_FILE : self::BUILD_POINTER_FILE);
285 }
286
293 public static function buildDirectory(string $artifact_directory, string $build_id): string
294 {
295 if (preg_match(self::BUILD_ID_FORMAT, $build_id) !== 1) {
296 throw new \InvalidArgumentException(sprintf('"%s" is not a valid build id.', $build_id));
297 }
298
299 return rtrim($artifact_directory, '/') . '/' . $build_id;
300 }
301
302 public static function isBuildId(string $name): bool
303 {
304 return preg_match(self::BUILD_ID_FORMAT, $name) === 1;
305 }
306
313 public static function buildMoFile(string $build_directory, string $module, string $lang_key): string
314 {
315 return rtrim($build_directory, '/') . '/' . NativeGettext::CATALOG_DIRECTORY . '/' . self::shippedDomain($module, $lang_key) . '.mo';
316 }
317
324 public static function buildKeysFile(string $build_directory, string $module): string
325 {
326 self::assertValidModule($module);
327
328 return rtrim($build_directory, '/') . '/keys/' . $module . '.php';
329 }
330
335 public static function buildLocaleDirectory(string $build_directory): string
336 {
337 return rtrim($build_directory, '/') . '/locale';
338 }
339
345 public static function shippedDomain(string $module, string $lang_key): string
346 {
347 self::assertValidModule($module);
348 self::assertValidLanguageKey($lang_key);
349
350 return $module . '.' . $lang_key;
351 }
352
358 public static function overlayDomain(string $module, string $lang_key): string
359 {
360 return self::shippedDomain($module, $lang_key) . '.overlay';
361 }
362
371 public static function overlayDirectory(string $client_data_dir, string $module, string $lang_key): string
372 {
373 self::assertValidModule($module);
374 self::assertValidLanguageKey($lang_key);
375
376 return rtrim($client_data_dir, '/') . '/lang/' . $module . '/' . $lang_key;
377 }
378
385 public static function overlayPoFile(string $client_data_dir, LanguageFileDirectory $directory, string $lang_key): string
386 {
387 return self::overlayDirectory($client_data_dir, $directory->getPrefix(), $lang_key)
388 . '/' . sprintf(self::shippedFileNamePattern($directory), $lang_key) . '.po';
389 }
390
394 public static function overlayLockFile(string $overlay_directory): string
395 {
396 return rtrim($overlay_directory, '/') . '/lock';
397 }
398
403 public static function overlayCurrentFile(string $overlay_directory): string
404 {
405 return rtrim($overlay_directory, '/') . '/current';
406 }
407
414 public static function overlayRevisionDirectory(string $overlay_directory, string $revision): string
415 {
416 if (!self::isOverlayRevision($revision)) {
417 throw new \InvalidArgumentException(sprintf('"%s" is not a valid overlay revision.', $revision));
418 }
419
420 return rtrim($overlay_directory, '/') . '/' . $revision;
421 }
422
423 public static function isOverlayRevision(string $name): bool
424 {
425 return preg_match(self::OVERLAY_REVISION_FORMAT, $name) === 1;
426 }
427
434 public static function overlayMoFile(string $revision_directory, string $module, string $lang_key): string
435 {
436 return rtrim($revision_directory, '/') . '/' . NativeGettext::CATALOG_DIRECTORY . '/' . self::overlayDomain($module, $lang_key) . '.mo';
437 }
438
443 public static function overlayKeysFile(string $revision_directory): string
444 {
445 return rtrim($revision_directory, '/') . '/keys.json';
446 }
447
454 public static function takenOverFile(string $client_data_dir, string $lang_key): string
455 {
456 self::assertValidLanguageKey($lang_key);
457
458 return rtrim($client_data_dir, '/') . '/lang/taken-over.' . $lang_key . '.json';
459 }
460
466 public static function takenOverLockFile(string $client_data_dir, string $lang_key): string
467 {
468 self::assertValidLanguageKey($lang_key);
469
470 return rtrim($client_data_dir, '/') . '/lang/.locks/taken-over.' . $lang_key . '.lock';
471 }
472
480 public static function templateLockFile(string $client_data_dir, LanguageFileDirectory $directory): string
481 {
482 $module = $directory->getPrefix();
483 self::assertValidModule($module);
484
485 return rtrim($client_data_dir, '/') . '/lang/' . $module . '/template.lock';
486 }
487
488 public static function isValidModule(string $module): bool
489 {
490 return preg_match(self::MODULE_FORMAT, $module) === 1;
491 }
492
493 public static function isValidLanguageKey(string $lang_key): bool
494 {
495 return preg_match(self::LANGUAGE_KEY_FORMAT, $lang_key) === 1;
496 }
497
501 private static function assertValidModule(string $module): void
502 {
503 if (!self::isValidModule($module)) {
504 throw new \InvalidArgumentException(sprintf('"%s" is not a valid module name.', $module));
505 }
506 }
507
508 private static function assertValidLanguageKey(string $lang_key): void
509 {
510 if (!self::isValidLanguageKey($lang_key)) {
511 throw new \InvalidArgumentException(sprintf('"%s" is not a valid language key.', $lang_key));
512 }
513 }
514}
The single place that knows where the files of a module migrated to PO/MO live:
static takenOverFile(string $client_data_dir, string $lang_key)
Record of the modules whose lng_data changes were taken over (see markLocalChangesTakenOver()): <clie...
static templateLockFile(string $client_data_dir, LanguageFileDirectory $directory)
The lock file serializing the writes of $directory's template (.pot) - shared by all languages,...
static findShippedPoFiles(string $ilias_absolute_path, LanguageFileDirectory $directory)
The shipped .po files of $directory by language key, found via its file name pattern (see shippedFile...
static buildDirectory(string $artifact_directory, string $build_id)
artifacts/language/<build>: one complete build, bound with bindtextdomain() as it is (the C library l...
static overlayRevisionDirectory(string $overlay_directory, string $revision)
One compiled revision of the overlay in $overlay_directory: r-<hash>/, bound with bindtextdomain() as...
static shippedBasePath(string $ilias_absolute_path, LanguageFileDirectory $directory, string $lang_key)
Without extension; named by shippedFileNamePattern().
static buildLocaleDirectory(string $build_directory)
The directory with the locale NativeGettext activates (NativeGettext::LOCALE_NAME): <build>/locale,...
static shippedArtifactDirectory(string $ilias_absolute_path)
The directory Setup's build writes into (see ShippedTranslationsBuild), in the untracked artifacts/.
static buildMoFile(string $build_directory, string $module, string $lang_key)
The compiled catalog of $module for $lang_key: <build>/messages/LC_MESSAGES/<module>.
static overlayMoFile(string $revision_directory, string $module, string $lang_key)
The compiled overlay of $module for $lang_key in $revision_directory: r-<hash>/messages/LC_MESSAGES/<...
static isFoundWithoutGraph(string $ilias_absolute_path, LanguageFileDirectory $directory)
Whether findShippedTemplates() finds $directory's module.
static overlayDomain(string $module, string $lang_key)
The gettext domain of the overlay of $module for $lang_key: "<module>.<lang>.overlay".
static takenOverLockFile(string $client_data_dir, string $lang_key)
Lock for the read-modify-write of takenOverFile().
static resolveClientDataDir(?string $ilias_absolute_path=null)
The client data directory (CLIENT_DATA_DIR) of this installation, null if it cannot be determined or ...
static buildPointerFile(string $artifact_directory)
artifacts/language/current.json (current-system.json where NativeGettext::usesSystemLocale()): names ...
static overlayPoFile(string $client_data_dir, LanguageFileDirectory $directory, string $lang_key)
The overlay .po of $directory's module for $lang_key (the delta with its bookkeeping),...
static overlayDirectory(string $client_data_dir, string $module, string $lang_key)
The overlay of $module for $lang_key lives in <client data dir>/lang/<module>/<lang>/: the ....
static templateFileName(string $pattern, string $module)
The file name of the template (.pot) for the pattern $pattern of $module: the pattern without "%s" an...
static overlayLockFile(string $overlay_directory)
The lock file serializing the writes of the overlay in $overlay_directory.
static useArtifactDirectoryForTests(?string $artifact_directory)
Makes shippedArtifactDirectory() return $artifact_directory (null: the default) - for tests only,...
const string MODULE_FORMAT
A module name as part of a file name and gettext domain "<module>.<lang>": no ".",...
static fromDataDirAndClientId(?string $data_dir, ?string $client_id)
Composes the client data directory from its two parts (e.g.
static overlayKeysFile(string $revision_directory)
The identifiers the overlay revision $revision_directory has a value for: r-<hash>/keys....
static shippedFileNamePattern(LanguageFileDirectory $directory)
The name of $directory's shipped .po without ".po", with "%s" for the language key: its own pattern (...
static buildPointerFileOf(string $artifact_directory, bool $system_locale)
The pointer of the builds made with ($system_locale) or without a system locale.
static buildKeysFile(string $build_directory, string $module)
The identifiers of $module in the build, the same for every language: <build>/keys/<module>....
static shippedDomain(string $module, string $lang_key)
The gettext domain of the shipped state of $module for $lang_key: "<module>.<lang>".
static shippedDirectoryPath(string $ilias_absolute_path, LanguageFileDirectory $directory)
static overlayCurrentFile(string $overlay_directory)
The file naming the revision (see overlayRevisionDirectory()) of the overlay in $overlay_directory th...
static findShippedTemplates(string $ilias_absolute_path)
Module => lang/ directory of its <module>.pot; works without the component graph (Setup).
static shippedTemplatePath(string $ilias_absolute_path, LanguageFileDirectory $directory)
The template (.pot) next to the shipped .po files, see templateFileName().
static usesSystemLocale()
macOS/BSD: activate() uses a SYSTEM_LOCALES entry, their C library ignores LOCPATH.
string $client_id
Definition: class.ilias.php:36
const CLIENT_ID
Definition: constants.php:41
const CLIENT_DATA_DIR
Definition: constants.php:46
if(!isset($arguments[0])) if(count( $arguments) > 3) if(! $remove_from_lang &&isset($arguments[2]) &&!is_dir($arguments[2])) $module
A directory of language files with a path and a prefix for them.
Optional addition to LanguageFileDirectory for shipped .po files not named <prefix>_<lang>....