Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
20.61% covered (danger)
20.61%
27 / 131
11.11% covered (danger)
11.11%
2 / 18
CRAP
0.00% covered (danger)
0.00%
0 / 1
tao_models_classes_LanguageService
20.61% covered (danger)
20.61%
27 / 131
11.11% covered (danger)
11.11%
2 / 18
1058.24
0.00% covered (danger)
0.00%
0 / 1
 createLanguage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getLanguageByCode
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 getCode
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 getAvailableLanguagesByUsage
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 isLanguageAvailable
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
2
 addTranslationsForLanguage
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 generateAll
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 generateClientBundles
43.75% covered (danger)
43.75%
14 / 32
0.00% covered (danger)
0.00%
0 / 1
23.42
 generateServerBundles
50.00% covered (danger)
50.00%
2 / 4
0.00% covered (danger)
0.00%
0 / 1
2.50
 getServerBundle
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 buildServerBundle
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
 getDefaultLanguageByUsage
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 filterLanguage
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getExistingLanguageUri
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
20
 getLanguageFiles
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 1
72
 getLanguageDefinition
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 isRtlLanguage
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getClientLibConfigRegistry
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; under version 2
7 * of the License (non-upgradable).
8 *
9 * This program 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 this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 *
18 * Copyright (c) 2002-2008 (original work) Public Research Centre Henri Tudor & University of Luxembourg
19 *                         (under the project TAO & TAO2);
20 *               2008-2010 (update and modification) Deutsche Institut für Internationale Pädagogische Forschung
21 *                         (under the project TAO-TRANSFER);
22 *               2009-2012 (update and modification) Public Research Centre Henri Tudor
23 *                         (under the project TAO-SUSTAIN & TAO-DEV);
24 *
25 */
26
27use oat\generis\model\OntologyRdf;
28use oat\tao\helpers\translation\TranslationBundle;
29use oat\generis\model\data\ModelManager;
30use oat\tao\helpers\translation\rdf\RdfPack;
31use oat\generis\model\kernel\persistence\file\FileIterator;
32use oat\tao\model\ClientLibConfigRegistry;
33use oat\tao\model\ClientLibRegistry;
34
35/**
36 * Short description of class tao_models_classes_LanguageService
37 *
38 * @access public
39 * @author Joel Bout, <joel.bout@tudor.lu>
40 * @package tao
41 */
42class tao_models_classes_LanguageService extends tao_models_classes_GenerisService
43{
44    // --- ASSOCIATIONS ---
45    public const TRANSLATION_PREFIX = __CLASS__ . ':all';
46
47    // --- ATTRIBUTES ---
48    public const CLASS_URI_LANGUAGES = 'http://www.tao.lu/Ontologies/TAO.rdf#Languages';
49    public const CLASS_URI_LANGUAGES_USAGES = 'http://www.tao.lu/Ontologies/TAO.rdf#LanguagesUsages';
50    public const PROPERTY_LANGUAGE_USAGES = 'http://www.tao.lu/Ontologies/TAO.rdf#LanguageUsages';
51    public const PROPERTY_LANGUAGE_ORIENTATION = 'http://www.tao.lu/Ontologies/TAO.rdf#LanguageOrientation';
52    public const PROPERTY_LANGUAGE_VERTICAL_WRITING_MODE =
53        'http://www.tao.lu/Ontologies/TAO.rdf#LanguageVerticalWritingMode';
54    public const INSTANCE_LANGUAGE_USAGE_GUI = 'http://www.tao.lu/Ontologies/TAO.rdf#LanguageUsageGUI';
55    public const INSTANCE_LANGUAGE_USAGE_DATA = 'http://www.tao.lu/Ontologies/TAO.rdf#LanguageUsageData';
56    public const INSTANCE_ORIENTATION_LTR = 'http://www.tao.lu/Ontologies/TAO.rdf#OrientationLeftToRight';
57    public const INSTANCE_ORIENTATION_RTL = 'http://www.tao.lu/Ontologies/TAO.rdf#OrientationRightToLeft';
58    public const INSTANCE_VERTICAL_WRITING_MODE_RL = 'http://www.tao.lu/Ontologies/TAO.rdf#WritingModeVerticalRl';
59    public const INSTANCE_VERTICAL_WRITING_MODE_LR = 'http://www.tao.lu/Ontologies/TAO.rdf#WritingModeVerticalLr';
60    // --- OPERATIONS ---
61
62    /**
63     * Short description of method createLanguage
64     *
65     * @access public
66     * @author Joel Bout, <joel.bout@tudor.lu>
67     * @param  string $code
68     * @return core_kernel_classes_Resource
69     * @throws common_exception_Error   Not implemented in this class yet.
70     */
71    public function createLanguage($code)
72    {
73        throw new common_exception_Error(__METHOD__ . ' not yet implemented in ' . __CLASS__);
74    }
75
76    /**
77     * Short description of method getLanguageByCode
78     *
79     * @access public
80     * @author Joel Bout, <joel.bout@tudor.lu>
81     * @param  string $code
82     * @return core_kernel_classes_Resource|null
83     */
84    public function getLanguageByCode($code)
85    {
86        $returnValue = null;
87
88
89        $langClass = new core_kernel_classes_Class(static::CLASS_URI_LANGUAGES);
90        $langs = $langClass->searchInstances([
91            OntologyRdf::RDF_VALUE => $code
92        ], [
93            'like' => false
94        ]);
95        if (count($langs) == 1) {
96            $returnValue = current($langs);
97        } else {
98            common_Logger::w('Could not find language with code ' . $code);
99        }
100
101
102        return $returnValue;
103    }
104
105    /**
106     * Short description of method getCode
107     *
108     * @access public
109     * @author Joel Bout, <joel.bout@tudor.lu>
110     * @param  core_kernel_classes_Resource $language
111     * @return string
112     */
113    public function getCode(core_kernel_classes_Resource $language)
114    {
115        $returnValue = (string) '';
116        $valueProperty = new core_kernel_classes_Property(OntologyRdf::RDF_VALUE);
117        $returnValue = $language->getUniquePropertyValue($valueProperty);
118        return (string) $returnValue;
119    }
120
121    /**
122     * Short description of method getAvailableLanguagesByUsage
123     *
124     * @access public
125     * @author Joel Bout, <joel.bout@tudor.lu>
126     * @param  core_kernel_classes_Resource $usage
127     * @return array
128     */
129    public function getAvailableLanguagesByUsage(core_kernel_classes_Resource $usage)
130    {
131        $returnValue = [];
132        $langClass = new core_kernel_classes_Class(static::CLASS_URI_LANGUAGES);
133        $returnValue = $langClass->searchInstances([
134            static::PROPERTY_LANGUAGE_USAGES => $usage->getUri()
135        ], [
136            'like' => false
137        ]);
138        return (array) $returnValue;
139    }
140
141    /**
142     * Checks the language availability in the given context(usage).
143     *
144     * @param string                       $code    The language code to check. (for example: en-US)
145     * @param core_kernel_classes_Resource $usage   The context of the availability.
146     *
147     * @return bool
148     */
149    public function isLanguageAvailable($code, core_kernel_classes_Resource $usage)
150    {
151        $langClass = new core_kernel_classes_Class(static::CLASS_URI_LANGUAGES);
152        $result = $langClass->searchInstances(
153            [
154                OntologyRdf::RDF_VALUE => $code,
155                static::PROPERTY_LANGUAGE_USAGES => $usage->getUri(),
156            ],
157            ['like' => false]
158        );
159
160        return !empty($result);
161    }
162
163    public function addTranslationsForLanguage(core_kernel_classes_Resource $language)
164    {
165        $langCode = $this->getCode($language);
166        $rdf = ModelManager::getModel()->getRdfInterface();
167
168        $extensions = common_ext_ExtensionsManager::singleton()->getInstalledExtensions();
169        foreach ($extensions as $extension) {
170            $pack = new RdfPack($langCode, $extension);
171            foreach ($pack as $triple) {
172                $rdf->add($triple);
173            }
174        }
175    }
176
177    /**
178     * Regenerates client and server translation
179     * @return string[] list of client files regenerated
180     */
181    public function generateAll($checkPreviousBundle = false)
182    {
183        $this->generateServerBundles();
184        $files = $this->generateClientBundles($checkPreviousBundle);
185        return $files;
186    }
187
188    /**
189     *
190     * @author Lionel Lecaque, lionel@taotesting.com
191     *
192     * @param bool $checkPreviousBundle
193     *
194     * @return array
195     */
196    public function generateClientBundles($checkPreviousBundle = false)
197    {
198        $returnValue = [];
199
200        $extensions = array_map(
201            function ($extension) {
202                return $extension->getId();
203            },
204            common_ext_ExtensionsManager::singleton()->getInstalledExtensions()
205        );
206        // lookup for languages into tao
207        $languages = tao_helpers_translation_Utils::getAvailableLanguages();
208        $path = ROOT_PATH . 'tao/views/locales/';
209        $generated = 0;
210        $generate = true;
211        foreach ($languages as $langCode) {
212            try {
213                $bundle = new TranslationBundle($langCode, $extensions, ROOT_PATH, TAO_VERSION);
214
215                if ($checkPreviousBundle) {
216                    $currentBundle = $path . $langCode . '.json';
217                    if (file_exists($currentBundle)) {
218                        $bundleData = json_decode(file_get_contents($currentBundle), true);
219                        if ($bundleData['serial'] === $bundle->getSerial()) {
220                            $generate = false;
221                        }
222                    }
223                    if ($generate) {
224                        $file = $bundle->generateTo($path, false);
225                    }
226                } else {
227                    $file = $bundle->generateTo($path);
228                }
229                if ($file) {
230                    $generated++;
231                    $returnValue[] = $file;
232                } else {
233                    if ($generate) {
234                        common_Logger::e('Failure generating message.js for lang ' . $langCode);
235                    } else {
236                        common_Logger::d('Actual File is more recent, skip ' . $langCode);
237                    }
238                }
239            } catch (common_exception_Error $e) {
240                common_Logger::e('Failure: ' . $e->getMessage());
241            }
242        }
243        common_Logger::i($generated . ' translation bundles have been (re)generated');
244
245        return $returnValue;
246    }
247
248    /**
249     * Generate server translation file, forching a cache overwrite
250     */
251    public function generateServerBundles()
252    {
253        $usage = $this->getResource(self::INSTANCE_LANGUAGE_USAGE_GUI);
254        foreach ($this->getAvailableLanguagesByUsage($usage) as $language) {
255            $langCode = $this->getCode($language);
256            $this->buildServerBundle($langCode);
257        }
258    }
259
260    /**
261     * Returns the translation strings for a given language
262     * Conflicting translations get resolved by order of dependencies
263     * @param string $langCode
264     * @return array translation strings
265     */
266    public function getServerBundle($langCode)
267    {
268        $cache = $this->getServiceLocator()->get(common_cache_Cache::SERVICE_ID);
269        try {
270            $translations = $cache->get(self::TRANSLATION_PREFIX . $langCode);
271        } catch (common_cache_NotFoundException $ex) {
272            $translations = $this->buildServerBundle($langCode);
273        }
274        return $translations;
275    }
276
277    /**
278     * Rebuild the translation cache from the POs situated in each installed extension
279     * @param string $langCode
280     * @return array translation
281     */
282    protected function buildServerBundle($langCode)
283    {
284        $extensions = common_ext_ExtensionsManager::singleton()->getInstalledExtensions();
285        $extensions = helpers_ExtensionHelper::sortByDependencies($extensions);
286        $translations = [];
287        foreach ($extensions as $extension) {
288            $file = $extension->getDir() . 'locales' . DIRECTORY_SEPARATOR . $langCode . DIRECTORY_SEPARATOR
289                . 'messages.po';
290            $new = l10n::getPoFile($file);
291            if (is_array($new)) {
292                $translations = array_merge($translations, $new);
293            }
294        }
295        $cache = $this->getServiceLocator()->get(common_cache_Cache::SERVICE_ID);
296        $cache->put($translations, self::TRANSLATION_PREFIX . $langCode);
297        return $translations;
298    }
299
300    /**
301     * Short description of method getDefaultLanguageByUsage
302     *
303     * @access public
304     * @author Joel Bout, <joel.bout@tudor.lu>
305     * @param  core_kernel_classes_Resource $usage
306     * @return core_kernel_classes_Resource
307     * @throws common_exception_Error   Not implemented in this class yet.
308     */
309    public function getDefaultLanguageByUsage(core_kernel_classes_Resource $usage)
310    {
311        throw new common_exception_Error(__METHOD__ . ' not yet implemented in ' . __CLASS__);
312    }
313
314    /**
315     * Filter a value of a language to transform it into uri
316     *
317     * If it's an uri, returns it
318     * If it's a language code returns the associated uri
319     * Else returns the default language uri
320     *
321     * @param $value
322     * @return string
323     * @throws common_exception_Error
324     */
325    public static function filterLanguage($value)
326    {
327        $uri = self::getExistingLanguageUri($value);
328        /** @noinspection NullPointerExceptionInspection */
329        return $uri !== null
330            ? $uri
331            : self::singleton()->getLanguageByCode(DEFAULT_LANG)->getUri();
332    }
333
334    /**
335     * @param string $value language code or uri
336     * @return string|null language uri if language found or null otherwise
337     * @throws common_exception_Error
338     */
339    public static function getExistingLanguageUri($value)
340    {
341        if (filter_var($value, FILTER_VALIDATE_URL) !== false) {
342            $langByUri = new \core_kernel_classes_Resource($value);
343            return $langByUri->exists()
344                ? $value
345                : null;
346        }
347
348        $langByCode = self::singleton()->getLanguageByCode($value);
349        return $langByCode !== null
350            ? $langByCode->getUri()
351            : null;
352    }
353
354    /**
355     * Convenience method that returns available language descriptions to be inserted in the
356     * knowledge base.
357     *
358     * @return array of ns => files
359     */
360    private function getLanguageFiles()
361    {
362        $extManager = $this->getServiceLocator()->get(common_ext_ExtensionsManager::SERVICE_ID);
363        $localesPath = $extManager->getExtensionById('tao')->getDir() . 'locales';
364        if (!@is_dir($localesPath) || !@is_readable($localesPath)) {
365            throw new tao_install_utils_Exception("Cannot read 'locales' directory in extenstion 'tao'.");
366        }
367
368        $files = [];
369        $localeDirectories = scandir($localesPath);
370        foreach ($localeDirectories as $localeDir) {
371            $path = $localesPath . '/' . $localeDir;
372            if ($localeDir[0] != '.' && @is_dir($path)) {
373                // Look if the lang.rdf can be read.
374                $languageModelFile = $path . '/lang.rdf';
375                if (@file_exists($languageModelFile) && @is_readable($languageModelFile)) {
376                    $files[] = $languageModelFile;
377                }
378            }
379        }
380        return $files;
381    }
382
383
384    /**
385     * Return the definition of the languages as an RDF iterator
386     * @return AppendIterator
387     */
388    public function getLanguageDefinition()
389    {
390        $model = new AppendIterator();
391        foreach ($this->getLanguageFiles() as $rdfPath) {
392            $iterator = new FileIterator($rdfPath);
393            $model->append($iterator->getIterator());
394        }
395        return $model;
396    }
397
398    public function isRtlLanguage(string $languageCode): bool
399    {
400        $config = $this->getClientLibConfigRegistry()->get('util/locale') ?? [];
401
402        return in_array($languageCode, $config['rtl'] ?? [], true);
403    }
404
405    private function getClientLibConfigRegistry(): ClientLibConfigRegistry
406    {
407        return $this->getServiceLocator()->getContainer()->get(ClientLibConfigRegistry::class);
408    }
409}