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