Joomla Platform  13.1
Documentation des API du framework Joomla Platform
 Tout Classes Espaces de nommage Fichiers Fonctions Variables Pages
language.php
Aller à la documentation de ce fichier.
1 <?php
2 /**
3  * @package Joomla.Platform
4  * @subpackage Language
5  *
6  * @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
7  * @license GNU General Public License version 2 or later; see LICENSE
8  */
9 
10 defined('JPATH_PLATFORM') or die;
11 
12 /**
13  * Allows for quoting in language .ini files.
14  */
15 define('_QQ_', '"');
16 
17 /**
18  * Languages/translation handler class
19  *
20  * @package Joomla.Platform
21  * @subpackage Language
22  * @since 11.1
23  */
24 class JLanguage
25 {
26  /**
27  * Array of JLanguage objects
28  *
29  * @var array
30  * @since 11.1
31  */
32  protected static $languages = array();
33 
34  /**
35  * Debug language, If true, highlights if string isn't found.
36  *
37  * @var boolean
38  * @since 11.1
39  */
40  protected $debug = false;
41 
42  /**
43  * The default language, used when a language file in the requested language does not exist.
44  *
45  * @var string
46  * @since 11.1
47  */
48  protected $default = 'en-GB';
49 
50  /**
51  * An array of orphaned text.
52  *
53  * @var array
54  * @since 11.1
55  */
56  protected $orphans = array();
57 
58  /**
59  * Array holding the language metadata.
60  *
61  * @var array
62  * @since 11.1
63  */
64  protected $metadata = null;
65 
66  /**
67  * Array holding the language locale or boolean null if none.
68  *
69  * @var array|boolean
70  * @since 11.1
71  */
72  protected $locale = null;
73 
74  /**
75  * The language to load.
76  *
77  * @var string
78  * @since 11.1
79  */
80  protected $lang = null;
81 
82  /**
83  * A nested array of language files that have been loaded
84  *
85  * @var array
86  * @since 11.1
87  */
88  protected $paths = array();
89 
90  /**
91  * List of language files that are in error state
92  *
93  * @var array
94  * @since 11.1
95  */
96  protected $errorfiles = array();
97 
98  /**
99  * Translations
100  *
101  * @var array
102  * @since 11.1
103  */
104  protected $strings = array();
105 
106  /**
107  * An array of used text, used during debugging.
108  *
109  * @var array
110  * @since 11.1
111  */
112  protected $used = array();
113 
114  /**
115  * Counter for number of loads.
116  *
117  * @var integer
118  * @since 11.1
119  */
120  protected $counter = 0;
121 
122  /**
123  * An array used to store overrides.
124  *
125  * @var array
126  * @since 11.1
127  */
128  protected $override = array();
129 
130  /**
131  * Name of the transliterator function for this language.
132  *
133  * @var string
134  * @since 11.1
135  */
136  protected $transliterator = null;
137 
138  /**
139  * Name of the pluralSuffixesCallback function for this language.
140  *
141  * @var callable
142  * @since 11.1
143  */
144  protected $pluralSuffixesCallback = null;
145 
146  /**
147  * Name of the ignoredSearchWordsCallback function for this language.
148  *
149  * @var callable
150  * @since 11.1
151  */
152  protected $ignoredSearchWordsCallback = null;
153 
154  /**
155  * Name of the lowerLimitSearchWordCallback function for this language.
156  *
157  * @var callable
158  * @since 11.1
159  */
160  protected $lowerLimitSearchWordCallback = null;
161 
162  /**
163  * Name of the uppperLimitSearchWordCallback function for this language.
164  *
165  * @var callable
166  * @since 11.1
167  */
168  protected $upperLimitSearchWordCallback = null;
169 
170  /**
171  * Name of the searchDisplayedCharactersNumberCallback function for this language.
172  *
173  * @var callable
174  * @since 11.1
175  */
176  protected $searchDisplayedCharactersNumberCallback = null;
177 
178  /**
179  * Constructor activating the default information of the language.
180  *
181  * @param string $lang The language
182  * @param boolean $debug Indicates if language debugging is enabled.
183  *
184  * @since 11.1
185  */
186  public function __construct($lang = null, $debug = false)
187  {
188  $this->strings = array();
189 
190  if ($lang == null)
191  {
192  $lang = $this->default;
193  }
194 
195  $this->setLanguage($lang);
196  $this->setDebug($debug);
197 
198  $filename = JPATH_BASE . "/language/overrides/$lang.override.ini";
199 
200  if (file_exists($filename) && $contents = $this->parse($filename))
201  {
202  if (is_array($contents))
203  {
204  // Sort the underlying heap by key values to optimize merging
205  ksort($contents, SORT_STRING);
206  $this->override = $contents;
207  }
208 
209  unset($contents);
210  }
211 
212  // Look for a language specific localise class
213  $class = str_replace('-', '_', $lang . 'Localise');
214  $paths = array();
215 
216  if (defined('JPATH_SITE'))
217  {
218  // Note: Manual indexing to enforce load order.
219  $paths[0] = JPATH_SITE . "/language/overrides/$lang.localise.php";
220  $paths[2] = JPATH_SITE . "/language/$lang/$lang.localise.php";
221  }
222 
223  if (defined('JPATH_ADMINISTRATOR'))
224  {
225  // Note: Manual indexing to enforce load order.
226  $paths[1] = JPATH_ADMINISTRATOR . "/language/overrides/$lang.localise.php";
227  $paths[3] = JPATH_ADMINISTRATOR . "/language/$lang/$lang.localise.php";
228  }
229 
230  ksort($paths);
231  $path = reset($paths);
232 
233  while (!class_exists($class) && $path)
234  {
235  if (file_exists($path))
236  {
237  require_once $path;
238  }
239 
240  $path = next($paths);
241  }
242 
243  if (class_exists($class))
244  {
245  /* Class exists. Try to find
246  * -a transliterate method,
247  * -a getPluralSuffixes method,
248  * -a getIgnoredSearchWords method
249  * -a getLowerLimitSearchWord method
250  * -a getUpperLimitSearchWord method
251  * -a getSearchDisplayCharactersNumber method
252  */
253  if (method_exists($class, 'transliterate'))
254  {
255  $this->transliterator = array($class, 'transliterate');
256  }
257 
258  if (method_exists($class, 'getPluralSuffixes'))
259  {
260  $this->pluralSuffixesCallback = array($class, 'getPluralSuffixes');
261  }
262 
263  if (method_exists($class, 'getIgnoredSearchWords'))
264  {
265  $this->ignoredSearchWordsCallback = array($class, 'getIgnoredSearchWords');
266  }
267 
268  if (method_exists($class, 'getLowerLimitSearchWord'))
269  {
270  $this->lowerLimitSearchWordCallback = array($class, 'getLowerLimitSearchWord');
271  }
272 
273  if (method_exists($class, 'getUpperLimitSearchWord'))
274  {
275  $this->upperLimitSearchWordCallback = array($class, 'getUpperLimitSearchWord');
276  }
277 
278  if (method_exists($class, 'getSearchDisplayedCharactersNumber'))
279  {
280  $this->searchDisplayedCharactersNumberCallback = array($class, 'getSearchDisplayedCharactersNumber');
281  }
282  }
283 
284  $this->load();
285  }
286 
287  /**
288  * Returns a language object.
289  *
290  * @param string $lang The language to use.
291  * @param boolean $debug The debug mode.
292  *
293  * @return JLanguage The Language object.
294  *
295  * @since 11.1
296  */
297  public static function getInstance($lang, $debug = false)
298  {
299  if (!isset(self::$languages[$lang . $debug]))
300  {
301  self::$languages[$lang . $debug] = new JLanguage($lang, $debug);
302  }
303 
304  return self::$languages[$lang . $debug];
305  }
306 
307  /**
308  * Translate function, mimics the php gettext (alias _) function.
309  *
310  * The function checks if $jsSafe is true, then if $interpretBackslashes is true.
311  *
312  * @param string $string The string to translate
313  * @param boolean $jsSafe Make the result javascript safe
314  * @param boolean $interpretBackSlashes Interpret \t and \n
315  *
316  * @return string The translation of the string
317  *
318  * @since 11.1
319  */
320  public function _($string, $jsSafe = false, $interpretBackSlashes = true)
321  {
322  // Detect empty string
323  if ($string == '')
324  {
325  return '';
326  }
327 
328  $key = strtoupper($string);
329 
330  if (isset($this->strings[$key]))
331  {
332  $string = $this->debug ? '**' . $this->strings[$key] . '**' : $this->strings[$key];
333 
334  // Store debug information
335  if ($this->debug)
336  {
337  $caller = $this->getCallerInfo();
338 
339  if (!array_key_exists($key, $this->used))
340  {
341  $this->used[$key] = array();
342  }
343 
344  $this->used[$key][] = $caller;
345  }
346  }
347  else
348  {
349  if ($this->debug)
350  {
351  $caller = $this->getCallerInfo();
352  $caller['string'] = $string;
353 
354  if (!array_key_exists($key, $this->orphans))
355  {
356  $this->orphans[$key] = array();
357  }
358 
359  $this->orphans[$key][] = $caller;
360 
361  $string = '??' . $string . '??';
362  }
363  }
364 
365  if ($jsSafe)
366  {
367  // Javascript filter
368  $string = addslashes($string);
369  }
370  elseif ($interpretBackSlashes)
371  {
372  // Interpret \n and \t characters
373  $string = str_replace(array('\\\\', '\t', '\n'), array("\\", "\t", "\n"), $string);
374  }
375 
376  return $string;
377  }
378 
379  /**
380  * Transliterate function
381  *
382  * This method processes a string and replaces all accented UTF-8 characters by unaccented
383  * ASCII-7 "equivalents".
384  *
385  * @param string $string The string to transliterate.
386  *
387  * @return string The transliteration of the string.
388  *
389  * @since 11.1
390  */
391  public function transliterate($string)
392  {
393  if ($this->transliterator !== null)
394  {
395  return call_user_func($this->transliterator, $string);
396  }
397 
399  $string = JString::strtolower($string);
400 
401  return $string;
402  }
403 
404  /**
405  * Getter for transliteration function
406  *
407  * @return callable The transliterator function
408  *
409  * @since 11.1
410  */
411  public function getTransliterator()
412  {
413  return $this->transliterator;
414  }
415 
416  /**
417  * Set the transliteration function.
418  *
419  * @param callable $function Function name or the actual function.
420  *
421  * @return callable The previous function.
422  *
423  * @since 11.1
424  */
425  public function setTransliterator($function)
426  {
427  $previous = $this->transliterator;
428  $this->transliterator = $function;
429 
430  return $previous;
431  }
432 
433  /**
434  * Returns an array of suffixes for plural rules.
435  *
436  * @param integer $count The count number the rule is for.
437  *
438  * @return array The array of suffixes.
439  *
440  * @since 11.1
441  */
442  public function getPluralSuffixes($count)
443  {
444  if ($this->pluralSuffixesCallback !== null)
445  {
446  return call_user_func($this->pluralSuffixesCallback, $count);
447  }
448  else
449  {
450  return array((string) $count);
451  }
452  }
453 
454  /**
455  * Getter for pluralSuffixesCallback function.
456  *
457  * @return callable Function name or the actual function.
458  *
459  * @since 11.1
460  */
461  public function getPluralSuffixesCallback()
462  {
463  return $this->pluralSuffixesCallback;
464  }
465 
466  /**
467  * Set the pluralSuffixes function.
468  *
469  * @param callable $function Function name or actual function.
470  *
471  * @return callable The previous function.
472  *
473  * @since 11.1
474  */
475  public function setPluralSuffixesCallback($function)
476  {
477  $previous = $this->pluralSuffixesCallback;
478  $this->pluralSuffixesCallback = $function;
479 
480  return $previous;
481  }
482 
483  /**
484  * Returns an array of ignored search words
485  *
486  * @return array The array of ignored search words.
487  *
488  * @since 11.1
489  */
490  public function getIgnoredSearchWords()
491  {
492  if ($this->ignoredSearchWordsCallback !== null)
493  {
494  return call_user_func($this->ignoredSearchWordsCallback);
495  }
496  else
497  {
498  return array();
499  }
500  }
501 
502  /**
503  * Getter for ignoredSearchWordsCallback function.
504  *
505  * @return callable Function name or the actual function.
506  *
507  * @since 11.1
508  */
509  public function getIgnoredSearchWordsCallback()
510  {
511  return $this->ignoredSearchWordsCallback;
512  }
513 
514  /**
515  * Setter for the ignoredSearchWordsCallback function
516  *
517  * @param callable $function Function name or actual function.
518  *
519  * @return callable The previous function.
520  *
521  * @since 11.1
522  */
523  public function setIgnoredSearchWordsCallback($function)
524  {
525  $previous = $this->ignoredSearchWordsCallback;
526  $this->ignoredSearchWordsCallback = $function;
527 
528  return $previous;
529  }
530 
531  /**
532  * Returns a lower limit integer for length of search words
533  *
534  * @return integer The lower limit integer for length of search words (3 if no value was set for a specific language).
535  *
536  * @since 11.1
537  */
538  public function getLowerLimitSearchWord()
539  {
540  if ($this->lowerLimitSearchWordCallback !== null)
541  {
542  return call_user_func($this->lowerLimitSearchWordCallback);
543  }
544  else
545  {
546  return 3;
547  }
548  }
549 
550  /**
551  * Getter for lowerLimitSearchWordCallback function
552  *
553  * @return callable Function name or the actual function.
554  *
555  * @since 11.1
556  */
557  public function getLowerLimitSearchWordCallback()
558  {
559  return $this->lowerLimitSearchWordCallback;
560  }
561 
562  /**
563  * Setter for the lowerLimitSearchWordCallback function.
564  *
565  * @param callable $function Function name or actual function.
566  *
567  * @return callable The previous function.
568  *
569  * @since 11.1
570  */
571  public function setLowerLimitSearchWordCallback($function)
572  {
573  $previous = $this->lowerLimitSearchWordCallback;
574  $this->lowerLimitSearchWordCallback = $function;
575 
576  return $previous;
577  }
578 
579  /**
580  * Returns an upper limit integer for length of search words
581  *
582  * @return integer The upper limit integer for length of search words (20 if no value was set for a specific language).
583  *
584  * @since 11.1
585  */
586  public function getUpperLimitSearchWord()
587  {
588  if ($this->upperLimitSearchWordCallback !== null)
589  {
590  return call_user_func($this->upperLimitSearchWordCallback);
591  }
592  else
593  {
594  return 20;
595  }
596  }
597 
598  /**
599  * Getter for upperLimitSearchWordCallback function
600  *
601  * @return callable Function name or the actual function.
602  *
603  * @since 11.1
604  */
605  public function getUpperLimitSearchWordCallback()
606  {
607  return $this->upperLimitSearchWordCallback;
608  }
609 
610  /**
611  * Setter for the upperLimitSearchWordCallback function
612  *
613  * @param callable $function Function name or the actual function.
614  *
615  * @return callable The previous function.
616  *
617  * @since 11.1
618  */
619  public function setUpperLimitSearchWordCallback($function)
620  {
621  $previous = $this->upperLimitSearchWordCallback;
622  $this->upperLimitSearchWordCallback = $function;
623 
624  return $previous;
625  }
626 
627  /**
628  * Returns the number of characters displayed in search results.
629  *
630  * @return integer The number of characters displayed (200 if no value was set for a specific language).
631  *
632  * @since 11.1
633  */
634  public function getSearchDisplayedCharactersNumber()
635  {
636  if ($this->searchDisplayedCharactersNumberCallback !== null)
637  {
638  return call_user_func($this->searchDisplayedCharactersNumberCallback);
639  }
640  else
641  {
642  return 200;
643  }
644  }
645 
646  /**
647  * Getter for searchDisplayedCharactersNumberCallback function
648  *
649  * @return callable Function name or the actual function.
650  *
651  * @since 11.1
652  */
653  public function getSearchDisplayedCharactersNumberCallback()
654  {
655  return $this->searchDisplayedCharactersNumberCallback;
656  }
657 
658  /**
659  * Setter for the searchDisplayedCharactersNumberCallback function.
660  *
661  * @param callable $function Function name or the actual function.
662  *
663  * @return callable The previous function.
664  *
665  * @since 11.1
666  */
667  public function setSearchDisplayedCharactersNumberCallback($function)
668  {
669  $previous = $this->searchDisplayedCharactersNumberCallback;
670  $this->searchDisplayedCharactersNumberCallback = $function;
671 
672  return $previous;
673  }
674 
675  /**
676  * Checks if a language exists.
677  *
678  * This is a simple, quick check for the directory that should contain language files for the given user.
679  *
680  * @param string $lang Language to check.
681  * @param string $basePath Optional path to check.
682  *
683  * @return boolean True if the language exists.
684  *
685  * @since 11.1
686  */
687  public static function exists($lang, $basePath = JPATH_BASE)
688  {
689  static $paths = array();
690 
691  // Return false if no language was specified
692  if (!$lang)
693  {
694  return false;
695  }
696 
697  $path = $basePath . '/language/' . $lang;
698 
699  // Return previous check results if it exists
700  if (isset($paths[$path]))
701  {
702  return $paths[$path];
703  }
704 
705  // Check if the language exists
706  $paths[$path] = is_dir($path);
707 
708  return $paths[$path];
709  }
710 
711  /**
712  * Loads a single language file and appends the results to the existing strings
713  *
714  * @param string $extension The extension for which a language file should be loaded.
715  * @param string $basePath The basepath to use.
716  * @param string $lang The language to load, default null for the current language.
717  * @param boolean $reload Flag that will force a language to be reloaded if set to true.
718  * @param boolean $default Flag that force the default language to be loaded if the current does not exist.
719  *
720  * @return boolean True if the file has successfully loaded.
721  *
722  * @since 11.1
723  */
724  public function load($extension = 'joomla', $basePath = JPATH_BASE, $lang = null, $reload = false, $default = true)
725  {
726  if (!$lang)
727  {
728  $lang = $this->lang;
729  }
730 
731  $path = self::getLanguagePath($basePath, $lang);
732 
733  $internal = $extension == 'joomla' || $extension == '';
734  $filename = $internal ? $lang : $lang . '.' . $extension;
735  $filename = "$path/$filename.ini";
736 
737  if (isset($this->paths[$extension][$filename]) && !$reload)
738  {
739  // This file has already been tested for loading.
740  $result = $this->paths[$extension][$filename];
741  }
742  else
743  {
744  // Load the language file
745  $result = $this->loadLanguage($filename, $extension);
746 
747  // Check whether there was a problem with loading the file
748  if ($result === false && $default)
749  {
750  // No strings, so either file doesn't exist or the file is invalid
751  $oldFilename = $filename;
752 
753  // Check the standard file name
754  $path = self::getLanguagePath($basePath, $this->default);
755  $filename = $internal ? $this->default : $this->default . '.' . $extension;
756  $filename = "$path/$filename.ini";
757 
758  // If the one we tried is different than the new name, try again
759  if ($oldFilename != $filename)
760  {
761  $result = $this->loadLanguage($filename, $extension, false);
762  }
763  }
764  }
765 
766  return $result;
767  }
768 
769  /**
770  * Loads a language file.
771  *
772  * This method will not note the successful loading of a file - use load() instead.
773  *
774  * @param string $filename The name of the file.
775  * @param string $extension The name of the extension.
776  *
777  * @return boolean True if new strings have been added to the language
778  *
779  * @see JLanguage::load()
780  * @since 11.1
781  */
782  protected function loadLanguage($filename, $extension = 'unknown')
783  {
784  $this->counter++;
785 
786  $result = false;
787  $strings = false;
788 
789  if (file_exists($filename))
790  {
791  $strings = $this->parse($filename);
792  }
793 
794  if ($strings)
795  {
796  if (is_array($strings))
797  {
798  // Sort the underlying heap by key values to optimize merging
799  ksort($strings, SORT_STRING);
800  $this->strings = array_merge($this->strings, $strings);
801  }
802 
803  if (is_array($strings) && count($strings))
804  {
805  // Do not bother with ksort here. Since the originals were sorted, PHP will already have chosen the best heap.
806  $this->strings = array_merge($this->strings, $this->override);
807  $result = true;
808  }
809  }
810 
811  // Record the result of loading the extension's file.
812  if (!isset($this->paths[$extension]))
813  {
814  $this->paths[$extension] = array();
815  }
816 
817  $this->paths[$extension][$filename] = $result;
818 
819  return $result;
820  }
821 
822  /**
823  * Parses a language file.
824  *
825  * @param string $filename The name of the file.
826  *
827  * @return array The array of parsed strings.
828  *
829  * @since 11.1
830  */
831  protected function parse($filename)
832  {
833  if ($this->debug)
834  {
835  // Capture hidden PHP errors from the parsing.
836  $php_errormsg = null;
837  $track_errors = ini_get('track_errors');
838  ini_set('track_errors', true);
839  }
840 
841  $contents = file_get_contents($filename);
842  $contents = str_replace('_QQ_', '"\""', $contents);
843  $strings = @parse_ini_string($contents);
844 
845  if (!is_array($strings))
846  {
847  $strings = array();
848  }
849 
850  if ($this->debug)
851  {
852  // Restore error tracking to what it was before.
853  ini_set('track_errors', $track_errors);
854 
855  // Initialise variables for manually parsing the file for common errors.
856  $blacklist = array('YES', 'NO', 'NULL', 'FALSE', 'ON', 'OFF', 'NONE', 'TRUE');
857  $regex = '/^(|(\[[^\]]*\])|([A-Z][A-Z0-9_\-\.]*\s*=(\s*(("[^"]*")|(_QQ_)))+))\s*(;.*)?$/';
858  $this->debug = false;
859  $errors = array();
860 
861  // Open the file as a stream.
862  $file = new SplFileObject($filename);
863 
864  foreach ($file as $lineNumber => $line)
865  {
866  // Avoid BOM error as BOM is OK when using parse_ini
867  if ($lineNumber == 0)
868  {
869  $line = str_replace("\xEF\xBB\xBF", '', $line);
870  }
871 
872  // Check that the key is not in the blacklist and that the line format passes the regex.
873  $key = strtoupper(trim(substr($line, 0, strpos($line, '='))));
874 
875  // Workaround to reduce regex complexity when matching escaped quotes
876  $line = str_replace('\"', '_QQ_', $line);
877 
878  if (!preg_match($regex, $line) || in_array($key, $blacklist))
879  {
880  $errors[] = $lineNumber;
881  }
882  }
883 
884  // Check if we encountered any errors.
885  if (count($errors))
886  {
887  if (basename($filename) != $this->lang . '.ini')
888  {
889  $this->errorfiles[$filename] = $filename . JText::sprintf('JERROR_PARSING_LANGUAGE_FILE', implode(', ', $errors));
890  }
891  else
892  {
893  $this->errorfiles[$filename] = $filename . '&#160;: error(s) in line(s) ' . implode(', ', $errors);
894  }
895  }
896  elseif ($php_errormsg)
897  {
898  // We didn't find any errors but there's probably a parse notice.
899  $this->errorfiles['PHP' . $filename] = 'PHP parser errors :' . $php_errormsg;
900  }
901 
902  $this->debug = true;
903  }
904 
905  return $strings;
906  }
907 
908  /**
909  * Get a metadata language property.
910  *
911  * @param string $property The name of the property.
912  * @param mixed $default The default value.
913  *
914  * @return mixed The value of the property.
915  *
916  * @since 11.1
917  */
918  public function get($property, $default = null)
919  {
920  if (isset($this->metadata[$property]))
921  {
922  return $this->metadata[$property];
923  }
924 
925  return $default;
926  }
927 
928  /**
929  * Determine who called JLanguage or JText.
930  *
931  * @return array Caller information.
932  *
933  * @since 11.1
934  */
935  protected function getCallerInfo()
936  {
937  // Try to determine the source if none was provided
938  if (!function_exists('debug_backtrace'))
939  {
940  return null;
941  }
942 
943  $backtrace = debug_backtrace();
944  $info = array();
945 
946  // Search through the backtrace to our caller
947  $continue = true;
948 
949  while ($continue && next($backtrace))
950  {
951  $step = current($backtrace);
952  $class = @ $step['class'];
953 
954  // We're looking for something outside of language.php
955  if ($class != 'JLanguage' && $class != 'JText')
956  {
957  $info['function'] = @ $step['function'];
958  $info['class'] = $class;
959  $info['step'] = prev($backtrace);
960 
961  // Determine the file and name of the file
962  $info['file'] = @ $step['file'];
963  $info['line'] = @ $step['line'];
964 
965  $continue = false;
966  }
967  }
968 
969  return $info;
970  }
971 
972  /**
973  * Getter for Name.
974  *
975  * @return string Official name element of the language.
976  *
977  * @since 11.1
978  */
979  public function getName()
980  {
981  return $this->metadata['name'];
982  }
983 
984  /**
985  * Get a list of language files that have been loaded.
986  *
987  * @param string $extension An optional extension name.
988  *
989  * @return array
990  *
991  * @since 11.1
992  */
993  public function getPaths($extension = null)
994  {
995  if (isset($extension))
996  {
997  if (isset($this->paths[$extension]))
998  {
999  return $this->paths[$extension];
1000  }
1001 
1002  return null;
1003  }
1004  else
1005  {
1006  return $this->paths;
1007  }
1008  }
1009 
1010  /**
1011  * Get a list of language files that are in error state.
1012  *
1013  * @return array
1014  *
1015  * @since 11.1
1016  */
1017  public function getErrorFiles()
1018  {
1019  return $this->errorfiles;
1020  }
1021 
1022  /**
1023  * Getter for the language tag (as defined in RFC 3066)
1024  *
1025  * @return string The language tag.
1026  *
1027  * @since 11.1
1028  */
1029  public function getTag()
1030  {
1031  return $this->metadata['tag'];
1032  }
1033 
1034  /**
1035  * Get the RTL property.
1036  *
1037  * @return boolean True is it an RTL language.
1038  *
1039  * @since 11.1
1040  */
1041  public function isRTL()
1042  {
1043  return (bool) $this->metadata['rtl'];
1044  }
1045 
1046  /**
1047  * Set the Debug property.
1048  *
1049  * @param boolean $debug The debug setting.
1050  *
1051  * @return boolean Previous value.
1052  *
1053  * @since 11.1
1054  */
1055  public function setDebug($debug)
1056  {
1057  $previous = $this->debug;
1058  $this->debug = (boolean) $debug;
1059 
1060  return $previous;
1061  }
1062 
1063  /**
1064  * Get the Debug property.
1065  *
1066  * @return boolean True is in debug mode.
1067  *
1068  * @since 11.1
1069  */
1070  public function getDebug()
1071  {
1072  return $this->debug;
1073  }
1074 
1075  /**
1076  * Get the default language code.
1077  *
1078  * @return string Language code.
1079  *
1080  * @since 11.1
1081  */
1082  public function getDefault()
1083  {
1084  return $this->default;
1085  }
1086 
1087  /**
1088  * Set the default language code.
1089  *
1090  * @param string $lang The language code.
1091  *
1092  * @return string Previous value.
1093  *
1094  * @since 11.1
1095  */
1096  public function setDefault($lang)
1097  {
1098  $previous = $this->default;
1099  $this->default = $lang;
1100 
1101  return $previous;
1102  }
1103 
1104  /**
1105  * Get the list of orphaned strings if being tracked.
1106  *
1107  * @return array Orphaned text.
1108  *
1109  * @since 11.1
1110  */
1111  public function getOrphans()
1112  {
1113  return $this->orphans;
1114  }
1115 
1116  /**
1117  * Get the list of used strings.
1118  *
1119  * Used strings are those strings requested and found either as a string or a constant.
1120  *
1121  * @return array Used strings.
1122  *
1123  * @since 11.1
1124  */
1125  public function getUsed()
1126  {
1127  return $this->used;
1128  }
1129 
1130  /**
1131  * Determines is a key exists.
1132  *
1133  * @param string $string The key to check.
1134  *
1135  * @return boolean True, if the key exists.
1136  *
1137  * @since 11.1
1138  */
1139  public function hasKey($string)
1140  {
1141  $key = strtoupper($string);
1142 
1143  return isset($this->strings[$key]);
1144  }
1145 
1146  /**
1147  * Returns a associative array holding the metadata.
1148  *
1149  * @param string $lang The name of the language.
1150  *
1151  * @return mixed If $lang exists return key/value pair with the language metadata, otherwise return NULL.
1152  *
1153  * @since 11.1
1154  */
1155  public static function getMetadata($lang)
1156  {
1157  $path = self::getLanguagePath(JPATH_BASE, $lang);
1158  $file = $lang . '.xml';
1159 
1160  $result = null;
1161 
1162  if (is_file("$path/$file"))
1163  {
1164  $result = self::parseXMLLanguageFile("$path/$file");
1165  }
1166 
1167  if (empty($result))
1168  {
1169  return null;
1170  }
1171 
1172  return $result;
1173  }
1174 
1175  /**
1176  * Returns a list of known languages for an area
1177  *
1178  * @param string $basePath The basepath to use
1179  *
1180  * @return array key/value pair with the language file and real name.
1181  *
1182  * @since 11.1
1183  */
1184  public static function getKnownLanguages($basePath = JPATH_BASE)
1185  {
1186  $dir = self::getLanguagePath($basePath);
1187  $knownLanguages = self::parseLanguageFiles($dir);
1188 
1189  return $knownLanguages;
1190  }
1191 
1192  /**
1193  * Get the path to a language
1194  *
1195  * @param string $basePath The basepath to use.
1196  * @param string $language The language tag.
1197  *
1198  * @return string language related path or null.
1199  *
1200  * @since 11.1
1201  */
1202  public static function getLanguagePath($basePath = JPATH_BASE, $language = null)
1203  {
1204  $dir = $basePath . '/language';
1205 
1206  if (!empty($language))
1207  {
1208  $dir .= '/' . $language;
1209  }
1210 
1211  return $dir;
1212  }
1213 
1214  /**
1215  * Set the language attributes to the given language.
1216  *
1217  * Once called, the language still needs to be loaded using JLanguage::load().
1218  *
1219  * @param string $lang Language code.
1220  *
1221  * @return string Previous value.
1222  *
1223  * @since 11.1
1224  */
1225  public function setLanguage($lang)
1226  {
1227  $previous = $this->lang;
1228  $this->lang = $lang;
1229  $this->metadata = $this->getMetadata($this->lang);
1230 
1231  return $previous;
1232  }
1233 
1234  /**
1235  * Get the language locale based on current language.
1236  *
1237  * @return array The locale according to the language.
1238  *
1239  * @since 11.1
1240  */
1241  public function getLocale()
1242  {
1243  if (!isset($this->locale))
1244  {
1245  $locale = str_replace(' ', '', isset($this->metadata['locale']) ? $this->metadata['locale'] : '');
1246 
1247  if ($locale)
1248  {
1249  $this->locale = explode(',', $locale);
1250  }
1251  else
1252  {
1253  $this->locale = false;
1254  }
1255  }
1256 
1257  return $this->locale;
1258  }
1259 
1260  /**
1261  * Get the first day of the week for this language.
1262  *
1263  * @return integer The first day of the week according to the language
1264  *
1265  * @since 11.1
1266  */
1267  public function getFirstDay()
1268  {
1269  return (int) (isset($this->metadata['firstDay']) ? $this->metadata['firstDay'] : 0);
1270  }
1271 
1272  /**
1273  * Get the weekends days for this language.
1274  *
1275  * @return string The weekend days of the week separated by a comma according to the language
1276  *
1277  * @since 3.2
1278  */
1279  public function getWeekEnd()
1280  {
1281  return (isset($this->metadata['weekEnd']) && $this->metadata['weekEnd']) ? $this->metadata['weekEnd'] : '0,6';
1282  }
1283 
1284  /**
1285  * Searches for language directories within a certain base dir.
1286  *
1287  * @param string $dir directory of files.
1288  *
1289  * @return array Array holding the found languages as filename => real name pairs.
1290  *
1291  * @since 11.1
1292  */
1293  public static function parseLanguageFiles($dir = null)
1294  {
1295  $languages = array();
1296 
1297  $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
1298 
1299  foreach ($iterator as $file)
1300  {
1301  $langs = array();
1302  $fileName = $file->getFilename();
1303 
1304  if (!$file->isFile() || !preg_match("/^([-_A-Za-z]*)\.xml$/", $fileName))
1305  {
1306  continue;
1307  }
1308 
1309  try
1310  {
1311  $metadata = self::parseXMLLanguageFile($file->getRealPath());
1312 
1313  if ($metadata)
1314  {
1315  $lang = str_replace('.xml', '', $fileName);
1316  $langs[$lang] = $metadata;
1317  }
1318 
1319  $languages = array_merge($languages, $langs);
1320  }
1321  catch (RuntimeException $e)
1322  {
1323  }
1324  }
1325 
1326  return $languages;
1327  }
1328 
1329  /**
1330  * Parse XML file for language information.
1331  *
1332  * @param string $path Path to the XML files.
1333  *
1334  * @return array Array holding the found metadata as a key => value pair.
1335  *
1336  * @since 11.1
1337  * @throws RuntimeException
1338  */
1339  public static function parseXMLLanguageFile($path)
1340  {
1341  if (!is_readable($path))
1342  {
1343  throw new RuntimeException('File not found or not readable');
1344  }
1345 
1346  // Try to load the file
1347  $xml = simplexml_load_file($path);
1348 
1349  if (!$xml)
1350  {
1351  return null;
1352  }
1353 
1354  // Check that it's a metadata file
1355  if ((string) $xml->getName() != 'metafile')
1356  {
1357  return null;
1358  }
1359 
1360  $metadata = array();
1361 
1362  foreach ($xml->metadata->children() as $child)
1363  {
1364  $metadata[$child->getName()] = (string) $child;
1365  }
1366 
1367  return $metadata;
1368  }
1369 }