Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * Standard string manager.
19
 *
20
 * @package    core
21
 * @copyright  2010 Petr Skoda {@link http://skodak.org}
22
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
defined('MOODLE_INTERNAL') || die();
26
 
27
 
28
/**
29
 * Standard string_manager implementation
30
 *
31
 * Implements string_manager with getting and printing localised strings
32
 *
33
 * @package    core
34
 * @copyright  2010 Petr Skoda {@link http://skodak.org}
35
 * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
36
 */
37
class core_string_manager_standard implements core_string_manager {
38
    /** @var string location of all packs except 'en' */
39
    protected $otherroot;
40
    /** @var string location of all lang pack local modifications */
41
    protected $localroot;
42
    /** @var cache lang string cache - it will be optimised more later */
43
    protected $cache;
44
    /** @var int get_string() counter */
45
    protected $countgetstring = 0;
46
    /** @var array use disk cache */
47
    protected $translist;
48
    /** @var array language aliases to use in the language selector */
49
    protected $transaliases = [];
50
    /** @var cache stores list of available translations */
51
    protected $menucache;
52
    /** @var array list of cached deprecated strings */
53
    protected $cacheddeprecated;
54
 
55
    /**
56
     * Create new instance of string manager
57
     *
58
     * @param string $otherroot location of downloaded lang packs - usually $CFG->dataroot/lang
59
     * @param string $localroot usually the same as $otherroot
60
     * @param array $translist limit list of visible translations
61
     * @param array $transaliases aliases to use for the languages in the language selector
62
     */
63
    public function __construct($otherroot, $localroot, $translist, $transaliases = []) {
64
        $this->otherroot    = $otherroot;
65
        $this->localroot    = $localroot;
66
        if ($translist) {
67
            $this->translist = array_combine($translist, $translist);
68
            $this->transaliases = $transaliases;
69
        } else {
70
            $this->translist = array();
71
        }
72
 
73
        if ($this->get_revision() > 0) {
74
            // We can use a proper cache, establish the cache using the 'String cache' definition.
75
            $this->cache = cache::make('core', 'string');
76
            $this->menucache = cache::make('core', 'langmenu');
77
        } else {
78
            // We only want a cache for the length of the request, create a static cache.
79
            $options = array(
80
                'simplekeys' => true,
81
                'simpledata' => true
82
            );
83
            $this->cache = cache::make_from_params(cache_store::MODE_REQUEST, 'core', 'string', array(), $options);
84
            $this->menucache = cache::make_from_params(cache_store::MODE_REQUEST, 'core', 'langmenu', array(), $options);
85
        }
86
    }
87
 
88
    /**
89
     * Returns list of all explicit parent languages for the given language.
90
     *
91
     * English (en) is considered as the top implicit parent of all language packs
92
     * and is not included in the returned list. The language itself is appended to the
93
     * end of the list. The method is aware of circular dependency risk.
94
     *
95
     * @see self::populate_parent_languages()
96
     * @param string $lang the code of the language
97
     * @return array all explicit parent languages with the lang itself appended
98
     */
99
    public function get_language_dependencies($lang) {
100
        return $this->populate_parent_languages($lang);
101
    }
102
 
103
    /**
104
     * Load all strings for one component
105
     *
106
     * @param string $component The module the string is associated with
107
     * @param string $lang
108
     * @param bool $disablecache Do not use caches, force fetching the strings from sources
109
     * @param bool $disablelocal Do not use customized strings in xx_local language packs
110
     * @return array of all string for given component and lang
111
     */
112
    public function load_component_strings($component, $lang, $disablecache = false, $disablelocal = false) {
113
        global $CFG;
114
 
115
        list($plugintype, $pluginname) = core_component::normalize_component($component);
116
        if ($plugintype === 'core' and is_null($pluginname)) {
117
            $component = 'core';
118
        } else {
119
            $component = $plugintype . '_' . $pluginname;
120
        }
121
 
122
        $cachekey = $lang.'_'.$component.'_'.$this->get_key_suffix();
123
 
124
        $cachedstring = $this->cache->get($cachekey);
125
        if (!$disablecache and !$disablelocal) {
126
            if ($cachedstring !== false) {
127
                return $cachedstring;
128
            }
129
        }
130
 
131
        // No cache found - let us merge all possible sources of the strings.
132
        if ($plugintype === 'core') {
133
            $file = $pluginname;
134
            if ($file === null) {
135
                $file = 'moodle';
136
            }
137
            $string = array();
138
            // First load english pack.
139
            if (!file_exists("$CFG->dirroot/lang/en/$file.php")) {
140
                return array();
141
            }
142
            include("$CFG->dirroot/lang/en/$file.php");
143
            $enstring = $string;
144
 
145
            // And then corresponding local if present and allowed.
146
            if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
147
                include("$this->localroot/en_local/$file.php");
148
            }
149
            // Now loop through all langs in correct order.
150
            $deps = $this->get_language_dependencies($lang);
151
            foreach ($deps as $dep) {
152
                // The main lang string location.
153
                if (file_exists("$this->otherroot/$dep/$file.php")) {
154
                    include("$this->otherroot/$dep/$file.php");
155
                }
156
                if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
157
                    include("$this->localroot/{$dep}_local/$file.php");
158
                }
159
            }
160
 
161
        } else {
162
            if (!$location = core_component::get_plugin_directory($plugintype, $pluginname) or !is_dir($location)) {
163
                return array();
164
            }
165
            if ($plugintype === 'mod') {
166
                // Bloody mod hack.
167
                $file = $pluginname;
168
            } else {
169
                $file = $plugintype . '_' . $pluginname;
170
            }
171
            $string = array();
172
            // First load English pack.
173
            if (!file_exists("$location/lang/en/$file.php")) {
174
                // English pack does not exist, so do not try to load anything else.
175
                return array();
176
            }
177
            include("$location/lang/en/$file.php");
178
            $enstring = $string;
179
            // And then corresponding local english if present.
180
            if (!$disablelocal and file_exists("$this->localroot/en_local/$file.php")) {
181
                include("$this->localroot/en_local/$file.php");
182
            }
183
 
184
            // Now loop through all langs in correct order.
185
            $deps = $this->get_language_dependencies($lang);
186
            foreach ($deps as $dep) {
187
                // Legacy location - used by contrib only.
188
                if (file_exists("$location/lang/$dep/$file.php")) {
189
                    include("$location/lang/$dep/$file.php");
190
                }
191
                // The main lang string location.
192
                if (file_exists("$this->otherroot/$dep/$file.php")) {
193
                    include("$this->otherroot/$dep/$file.php");
194
                }
195
                // Local customisations.
196
                if (!$disablelocal and file_exists("$this->localroot/{$dep}_local/$file.php")) {
197
                    include("$this->localroot/{$dep}_local/$file.php");
198
                }
199
            }
200
        }
201
 
202
        // We do not want any extra strings from other languages - everything must be in en lang pack.
203
        $string = array_intersect_key($string, $enstring);
204
 
205
        if (!$disablelocal) {
206
            // Now we have a list of strings from all possible sources,
207
            // cache it in MUC cache if not already there.
208
            if ($cachedstring === false) {
209
                $this->cache->set($cachekey, $string);
210
            }
211
        }
212
        return $string;
213
    }
214
 
215
    /**
216
     * Parses all deprecated.txt in all plugins lang locations and returns the list of deprecated strings.
217
     *
218
     * Static variable is used for caching, this function is only called in dev environment.
219
     *
220
     * @return array of deprecated strings in the same format they appear in deprecated.txt files: "identifier,component"
221
     *     where component is a normalised component (i.e. "core_moodle", "mod_assign", etc.)
222
     */
223
    protected function load_deprecated_strings() {
224
        global $CFG;
225
 
226
        if ($this->cacheddeprecated !== null) {
227
            return $this->cacheddeprecated;
228
        }
229
 
230
        $this->cacheddeprecated = array();
231
        $content = '';
232
        $filename = $CFG->dirroot . '/lang/en/deprecated.txt';
233
        if (file_exists($filename)) {
234
            $content .= file_get_contents($filename);
235
        }
236
        foreach (core_component::get_plugin_types() as $plugintype => $plugintypedir) {
237
            foreach (core_component::get_plugin_list($plugintype) as $pluginname => $plugindir) {
238
                $filename = $plugindir.'/lang/en/deprecated.txt';
239
                if (file_exists($filename)) {
240
                    $content .= "\n". file_get_contents($filename);
241
                }
242
            }
243
        }
244
 
245
        $strings = preg_split('/\s*\n\s*/', $content, -1, PREG_SPLIT_NO_EMPTY);
246
        $this->cacheddeprecated = array_flip($strings);
247
 
248
        return $this->cacheddeprecated;
249
    }
250
 
251
    /**
252
     * Has string been deprecated?
253
     *
254
     * Usually checked only inside get_string() to display debug warnings.
255
     *
256
     * @param string $identifier The identifier of the string to search for
257
     * @param string $component The module the string is associated with
258
     * @return bool true if deprecated
259
     */
260
    public function string_deprecated($identifier, $component) {
261
        $deprecated = $this->load_deprecated_strings();
262
        list($plugintype, $pluginname) = core_component::normalize_component($component);
263
        $normcomponent = $pluginname ? ($plugintype . '_' . $pluginname) : $plugintype;
264
        return isset($deprecated[$identifier . ',' . $normcomponent]);
265
    }
266
 
267
    /**
268
     * Does the string actually exist?
269
     *
270
     * get_string() is throwing debug warnings, sometimes we do not want them
271
     * or we want to display better explanation of the problem.
272
     * Note: Use with care!
273
     *
274
     * @param string $identifier The identifier of the string to search for
275
     * @param string $component The module the string is associated with
276
     * @return boot true if exists
277
     */
278
    public function string_exists($identifier, $component) {
279
        $lang = current_language();
280
        $string = $this->load_component_strings($component, $lang);
281
        return isset($string[$identifier]);
282
    }
283
 
284
    /**
285
     * Get String returns a requested string
286
     *
287
     * @param string $identifier The identifier of the string to search for
288
     * @param string $component The module the string is associated with
289
     * @param string|object|array $a An object, string or number that can be used
290
     *      within translation strings
291
     * @param string $lang moodle translation language, null means use current
292
     * @return string The String !
293
     */
294
    public function get_string($identifier, $component = '', $a = null, $lang = null) {
295
        global $CFG;
296
 
297
        $this->countgetstring++;
298
        // There are very many uses of these time formatting strings without the 'langconfig' component,
299
        // it would not be reasonable to expect that all of them would be converted during 2.0 migration.
300
        static $langconfigstrs = array(
301
            'strftimedate' => 1,
302
            'strftimedatefullshort' => 1,
303
            'strftimedateshort' => 1,
304
            'strftimedatetime' => 1,
305
            'strftimedatetimeaccurate' => 1,
306
            'strftimedatetimeshort' => 1,
307
            'strftimedatetimeshortaccurate' => 1,
308
            'strftimedaydate' => 1,
309
            'strftimedaydatetime' => 1,
310
            'strftimedayshort' => 1,
311
            'strftimedaytime' => 1,
312
            'strftimemonth' => 1,
313
            'strftimemonthyear' => 1,
314
            'strftimerecent' => 1,
315
            'strftimerecentfull' => 1,
316
            'strftimetime' => 1);
317
 
318
        if (empty($component)) {
319
            if (isset($langconfigstrs[$identifier])) {
320
                $component = 'langconfig';
321
            } else {
322
                $component = 'moodle';
323
            }
324
        }
325
 
326
        if ($lang === null) {
327
            $lang = current_language();
328
        }
329
 
330
        $string = $this->load_component_strings($component, $lang);
331
 
332
        if (!isset($string[$identifier])) {
333
            if ($component === 'pix' or $component === 'core_pix') {
334
                // This component contains only alt tags for emoticons, not all of them are supposed to be defined.
335
                return '';
336
            }
337
            if ($identifier === 'parentlanguage' and ($component === 'langconfig' or $component === 'core_langconfig')) {
338
                // Identifier parentlanguage is a special string, undefined means use English if not defined.
339
                return 'en';
340
            }
341
            // Do not rebuild caches here!
342
            // Devs need to learn to purge all caches after any change or disable $CFG->langstringcache.
343
            if (!isset($string[$identifier])) {
344
                // The string is still missing - should be fixed by developer.
345
                if ($CFG->debugdeveloper) {
346
                    list($plugintype, $pluginname) = core_component::normalize_component($component);
347
                    if ($plugintype === 'core') {
348
                        $file = "lang/en/{$component}.php";
349
                    } else if ($plugintype == 'mod') {
350
                        $file = "mod/{$pluginname}/lang/en/{$pluginname}.php";
351
                    } else {
352
                        $path = core_component::get_plugin_directory($plugintype, $pluginname);
353
                        $file = "{$path}/lang/en/{$plugintype}_{$pluginname}.php";
354
                    }
355
                    debugging("Invalid get_string() identifier: '{$identifier}' or component '{$component}'. " .
356
                    "Perhaps you are missing \$string['{$identifier}'] = ''; in {$file}?", DEBUG_DEVELOPER);
357
                }
358
                return "[[$identifier]]";
359
            }
360
        }
361
 
362
        $string = $string[$identifier];
363
 
364
        if ($a !== null) {
365
            // Process array's and objects (except lang_strings).
366
            if (is_array($a) or (is_object($a) && !($a instanceof Stringable))) {
367
                $a = (array)$a;
368
                $search = array();
369
                $replace = array();
370
                foreach ($a as $key => $value) {
371
                    if (is_int($key)) {
372
                        // We do not support numeric keys - sorry!
373
                        continue;
374
                    }
375
                    if (is_array($value) or (is_object($value) && !($value instanceof Stringable))) {
376
                        // We support just string or lang_string as value.
377
                        continue;
378
                    }
379
                    $search[]  = '{$a->'.$key.'}';
380
                    $replace[] = (string)$value;
381
                }
382
                if ($search) {
383
                    $string = str_replace($search, $replace, $string);
384
                }
385
            } else {
386
                $string = str_replace('{$a}', (string)$a, $string);
387
            }
388
        }
389
 
390
        if ($CFG->debugdeveloper) {
391
            // Display a debugging message if sting exists but was deprecated.
392
            if ($this->string_deprecated($identifier, $component)) {
393
                list($plugintype, $pluginname) = core_component::normalize_component($component);
394
                $normcomponent = $pluginname ? ($plugintype . '_' . $pluginname) : $plugintype;
395
                debugging("String [{$identifier},{$normcomponent}] is deprecated. ".
396
                    'Either you should no longer be using that string, or the string has been incorrectly deprecated, in which case you should report this as a bug. '.
397
                    'Please refer to https://moodledev.io/general/projects/api/string-deprecation', DEBUG_DEVELOPER);
398
            }
399
        }
400
 
401
        return $string;
402
    }
403
 
404
    /**
405
     * Returns information about the core_string_manager performance.
406
     *
407
     * @return array
408
     */
409
    public function get_performance_summary() {
410
        return array(array(
411
            'langcountgetstring' => $this->countgetstring,
412
        ), array(
413
            'langcountgetstring' => 'get_string calls',
414
        ));
415
    }
416
 
417
    /**
418
     * Returns a localised list of all country names, sorted by localised name.
419
     *
420
     * @param bool $returnall return all or just enabled
421
     * @param string $lang moodle translation language, null means use current
422
     * @return array two-letter country code => translated name.
423
     */
424
    public function get_list_of_countries($returnall = false, $lang = null) {
425
        global $CFG;
426
 
427
        if ($lang === null) {
428
            $lang = current_language();
429
        }
430
 
431
        $countries = $this->load_component_strings('core_countries', $lang);
432
        core_collator::asort($countries);
433
 
434
        if (!$returnall and !empty($CFG->allcountrycodes)) {
435
            $enabled = explode(',', $CFG->allcountrycodes);
436
            $return = array();
437
            foreach ($enabled as $c) {
438
                if (isset($countries[$c])) {
439
                    $return[$c] = $countries[$c];
440
                }
441
            }
442
 
443
            if (!empty($return)) {
444
                return $return;
445
            }
446
        }
447
 
448
        return $countries;
449
    }
450
 
451
    /**
452
     * Returns a localised list of languages, sorted by code keys.
453
     *
454
     * @param string $lang moodle translation language, null means use current
455
     * @param string $standard language list standard
456
     *    - iso6392: three-letter language code (ISO 639-2/T) => translated name
457
     *    - iso6391: two-letter language code (ISO 639-1) => translated name
458
     * @return array language code => translated name
459
     */
460
    public function get_list_of_languages($lang = null, $standard = 'iso6391') {
461
        if ($lang === null) {
462
            $lang = current_language();
463
        }
464
 
465
        if ($standard === 'iso6392') {
466
            $langs = $this->load_component_strings('core_iso6392', $lang);
467
            ksort($langs);
468
            return $langs;
469
 
470
        } else if ($standard === 'iso6391') {
471
            $langs2 = $this->load_component_strings('core_iso6392', $lang);
472
            static $mapping = array('aar' => 'aa', 'abk' => 'ab', 'afr' => 'af', 'aka' => 'ak', 'sqi' => 'sq', 'amh' => 'am', 'ara' => 'ar', 'arg' => 'an', 'hye' => 'hy',
473
                'asm' => 'as', 'ava' => 'av', 'ave' => 'ae', 'aym' => 'ay', 'aze' => 'az', 'bak' => 'ba', 'bam' => 'bm', 'eus' => 'eu', 'bel' => 'be', 'ben' => 'bn', 'bih' => 'bh',
474
                'bis' => 'bi', 'bos' => 'bs', 'bre' => 'br', 'bul' => 'bg', 'mya' => 'my', 'cat' => 'ca', 'cha' => 'ch', 'che' => 'ce', 'zho' => 'zh', 'chu' => 'cu', 'chv' => 'cv',
475
                'cor' => 'kw', 'cos' => 'co', 'cre' => 'cr', 'ces' => 'cs', 'dan' => 'da', 'div' => 'dv', 'nld' => 'nl', 'dzo' => 'dz', 'eng' => 'en', 'epo' => 'eo', 'est' => 'et',
476
                'ewe' => 'ee', 'fao' => 'fo', 'fij' => 'fj', 'fin' => 'fi', 'fra' => 'fr', 'fry' => 'fy', 'ful' => 'ff', 'kat' => 'ka', 'deu' => 'de', 'gla' => 'gd', 'gle' => 'ga',
477
                'glg' => 'gl', 'glv' => 'gv', 'ell' => 'el', 'grn' => 'gn', 'guj' => 'gu', 'hat' => 'ht', 'hau' => 'ha', 'heb' => 'he', 'her' => 'hz', 'hin' => 'hi', 'hmo' => 'ho',
478
                'hrv' => 'hr', 'hun' => 'hu', 'ibo' => 'ig', 'isl' => 'is', 'ido' => 'io', 'iii' => 'ii', 'iku' => 'iu', 'ile' => 'ie', 'ina' => 'ia', 'ind' => 'id', 'ipk' => 'ik',
479
                'ita' => 'it', 'jav' => 'jv', 'jpn' => 'ja', 'kal' => 'kl', 'kan' => 'kn', 'kas' => 'ks', 'kau' => 'kr', 'kaz' => 'kk', 'khm' => 'km', 'kik' => 'ki', 'kin' => 'rw',
480
                'kir' => 'ky', 'kom' => 'kv', 'kon' => 'kg', 'kor' => 'ko', 'kua' => 'kj', 'kur' => 'ku', 'lao' => 'lo', 'lat' => 'la', 'lav' => 'lv', 'lim' => 'li', 'lin' => 'ln',
481
                'lit' => 'lt', 'ltz' => 'lb', 'lub' => 'lu', 'lug' => 'lg', 'mkd' => 'mk', 'mah' => 'mh', 'mal' => 'ml', 'mri' => 'mi', 'mar' => 'mr', 'msa' => 'ms', 'mlg' => 'mg',
482
                'mlt' => 'mt', 'mon' => 'mn', 'nau' => 'na', 'nav' => 'nv', 'nbl' => 'nr', 'nde' => 'nd', 'ndo' => 'ng', 'nep' => 'ne', 'nno' => 'nn', 'nob' => 'nb', 'nor' => 'no',
483
                'nya' => 'ny', 'oci' => 'oc', 'oji' => 'oj', 'ori' => 'or', 'orm' => 'om', 'oss' => 'os', 'pan' => 'pa', 'fas' => 'fa', 'pli' => 'pi', 'pol' => 'pl', 'por' => 'pt',
484
                'pus' => 'ps', 'que' => 'qu', 'roh' => 'rm', 'ron' => 'ro', 'run' => 'rn', 'rus' => 'ru', 'sag' => 'sg', 'san' => 'sa', 'sin' => 'si', 'slk' => 'sk', 'slv' => 'sl',
485
                'sme' => 'se', 'smo' => 'sm', 'sna' => 'sn', 'snd' => 'sd', 'som' => 'so', 'sot' => 'st', 'spa' => 'es', 'srd' => 'sc', 'srp' => 'sr', 'ssw' => 'ss', 'sun' => 'su',
486
                'swa' => 'sw', 'swe' => 'sv', 'tah' => 'ty', 'tam' => 'ta', 'tat' => 'tt', 'tel' => 'te', 'tgk' => 'tg', 'tgl' => 'tl', 'tha' => 'th', 'bod' => 'bo', 'tir' => 'ti',
487
                'ton' => 'to', 'tsn' => 'tn', 'tso' => 'ts', 'tuk' => 'tk', 'tur' => 'tr', 'twi' => 'tw', 'uig' => 'ug', 'ukr' => 'uk', 'urd' => 'ur', 'uzb' => 'uz', 'ven' => 've',
488
                'vie' => 'vi', 'vol' => 'vo', 'cym' => 'cy', 'wln' => 'wa', 'wol' => 'wo', 'xho' => 'xh', 'yid' => 'yi', 'yor' => 'yo', 'zha' => 'za', 'zul' => 'zu');
489
            $langs1 = array();
490
            foreach ($mapping as $c2 => $c1) {
491
                $langs1[$c1] = $langs2[$c2];
492
            }
493
            ksort($langs1);
494
            return $langs1;
495
 
496
        } else {
497
            debugging('Unsupported $standard parameter in get_list_of_languages() method: '.$standard);
498
        }
499
 
500
        return array();
501
    }
502
 
503
    /**
504
     * Checks if the translation exists for the language
505
     *
506
     * @param string $lang moodle translation language code
507
     * @param bool $includeall include also disabled translations
508
     * @return bool true if exists
509
     */
510
    public function translation_exists($lang, $includeall = true) {
511
        $translations = $this->get_list_of_translations($includeall);
512
        return isset($translations[$lang]);
513
    }
514
 
515
    /**
516
     * Returns localised list of installed translations
517
     *
518
     * @param bool $returnall return all or just enabled
519
     * @return array moodle translation code => localised translation name
520
     */
521
    public function get_list_of_translations($returnall = false) {
522
        global $CFG;
523
 
524
        $languages = array();
525
 
526
        $cachekey = 'list_'.$this->get_key_suffix();
527
        $cachedlist = $this->menucache->get($cachekey);
528
        if ($cachedlist !== false) {
529
            // The cache content is valid.
530
            if ($returnall or empty($this->translist)) {
531
                return $cachedlist;
532
            }
533
            // Return only enabled translations.
534
            foreach ($cachedlist as $langcode => $langname) {
535
                if (array_key_exists($langcode, $this->translist)) {
536
                    $languages[$langcode] = !empty($this->transaliases[$langcode]) ? $this->transaliases[$langcode] : $langname;
537
                }
538
            }
539
 
540
            // If there are no valid enabled translations, then return all languages.
541
            if (!empty($languages)) {
542
                return $languages;
543
            } else {
544
                return $cachedlist;
545
            }
546
        }
547
 
548
        // Get all languages available in system.
549
        $langdirs = get_list_of_plugins('', 'en', $this->otherroot);
550
        $langdirs["$CFG->dirroot/lang/en"] = 'en';
551
 
552
        // We use left to right mark to demark the shortcodes contained in LTR brackets, but we need to do
553
        // this hacky thing to have the utf8 char until we go php7 minimum and can simply put \u200E in
554
        // a double quoted string.
555
        $lrm = json_decode('"\u200E"');
556
 
557
        // Loop through all langs and get info.
558
        foreach ($langdirs as $lang) {
559
            if (strrpos($lang, '_local') !== false) {
560
                // Just a local tweak of some other lang pack.
561
                continue;
562
            }
563
            if (strrpos($lang, '_utf8') !== false) {
564
                // Legacy 1.x lang pack.
565
                continue;
566
            }
567
            if ($lang !== clean_param($lang, PARAM_SAFEDIR)) {
568
                // Invalid lang pack name!
569
                continue;
570
            }
571
            $string = $this->load_component_strings('langconfig', $lang);
572
            if (!empty($string['thislanguage'])) {
573
                $languages[$lang] = $string['thislanguage'].' '.$lrm.'('. $lang .')'.$lrm;
574
            }
575
        }
576
 
577
        core_collator::asort($languages);
578
 
579
        // Cache the list so that it can be used next time.
580
        $this->menucache->set($cachekey, $languages);
581
 
582
        if ($returnall or empty($this->translist)) {
583
            return $languages;
584
        }
585
 
586
        $cachedlist = $languages;
587
 
588
        // Return only enabled translations.
589
        $languages = array();
590
        foreach ($cachedlist as $langcode => $langname) {
591
            if (isset($this->translist[$langcode])) {
592
                $languages[$langcode] = !empty($this->transaliases[$langcode]) ? $this->transaliases[$langcode] : $langname;
593
            }
594
        }
595
 
596
        // If there are no valid enabled translations, then return all languages.
597
        if (!empty($languages)) {
598
            return $languages;
599
        } else {
600
            return $cachedlist;
601
        }
602
    }
603
 
604
    /**
605
     * Returns localised list of currencies.
606
     *
607
     * @param string $lang moodle translation language, null means use current
608
     * @return array currency code => localised currency name
609
     */
610
    public function get_list_of_currencies($lang = null) {
611
        if ($lang === null) {
612
            $lang = current_language();
613
        }
614
 
615
        $currencies = $this->load_component_strings('core_currencies', $lang);
616
        asort($currencies);
617
 
618
        return $currencies;
619
    }
620
 
621
    /**
622
     * Clears both in-memory and on-disk caches
623
     * @param bool $phpunitreset true means called from our PHPUnit integration test reset
624
     */
625
    public function reset_caches($phpunitreset = false) {
626
        // Clear the on-disk disk with aggregated string files.
627
        $this->cache->purge();
628
        $this->menucache->purge();
629
 
630
        if (!$phpunitreset) {
631
            // Increment the revision counter.
632
            $langrev = get_config('core', 'langrev');
633
            $next = time();
634
            if ($langrev !== false and $next <= $langrev and $langrev - $next < 60*60) {
635
                // This resolves problems when reset is requested repeatedly within 1s,
636
                // the < 1h condition prevents accidental switching to future dates
637
                // because we might not recover from it.
638
                $next = $langrev+1;
639
            }
640
            set_config('langrev', $next);
641
        }
642
 
643
        // Lang packs use PHP files in dataroot, it is better to invalidate opcode caches.
644
        if (function_exists('opcache_reset')) {
645
            opcache_reset();
646
        }
647
    }
648
 
649
    /**
650
     * Returns cache key suffix, this enables us to store string + lang menu
651
     * caches in local caches on cluster nodes. We can not use prefix because
652
     * it would cause problems when creating subdirs in cache file store.
653
     * @return string
654
     */
655
    protected function get_key_suffix() {
656
        $rev = $this->get_revision();
657
        if ($rev < 0) {
658
            // Simple keys do not like minus char.
659
            $rev = 0;
660
        }
661
 
662
        return $rev;
663
    }
664
 
665
    /**
666
     * Returns string revision counter, this is incremented after any string cache reset.
667
     * @return int lang string revision counter, -1 if unknown
668
     */
669
    public function get_revision() {
670
        global $CFG;
671
        if (empty($CFG->langstringcache)) {
672
            return -1;
673
        }
674
        if (isset($CFG->langrev)) {
675
            return (int)$CFG->langrev;
676
        } else {
677
            return -1;
678
        }
679
    }
680
 
681
    /**
682
     * Helper method that recursively loads all parents of the given language.
683
     *
684
     * @see self::get_language_dependencies()
685
     * @param string $lang language code
686
     * @param array $stack list of parent languages already populated in previous recursive calls
687
     * @return array list of all parents of the given language with the $lang itself added as the last element
688
     */
689
    protected function populate_parent_languages($lang, array $stack = array()) {
690
 
691
        // English does not have a parent language.
692
        if ($lang === 'en') {
693
            return $stack;
694
        }
695
 
696
        // Prevent circular dependency (and thence the infinitive recursion loop).
697
        if (in_array($lang, $stack)) {
698
            return $stack;
699
        }
700
 
701
        // Load language configuration and look for the explicit parent language.
702
        if (!file_exists("$this->otherroot/$lang/langconfig.php")) {
703
            return $stack;
704
        }
705
        $string = array();
706
        include("$this->otherroot/$lang/langconfig.php");
707
 
708
        if (empty($string['parentlanguage']) or $string['parentlanguage'] === 'en') {
709
            return array_merge(array($lang), $stack);
710
 
711
        }
712
 
713
        $parentlang = $string['parentlanguage'];
714
        return $this->populate_parent_languages($parentlang, array_merge(array($lang), $stack));
715
    }
716
}