includes/lang.php
changeset 228 b0a4d179be85
parent 215 94db56b8124f
child 239 0f1b353570a7
equal deleted inserted replaced
197:90b7a52bea45 228:b0a4d179be85
       
     1 <?php
       
     2 
       
     3 /*
       
     4  * Enano - an open-source CMS capable of wiki functions, Drupal-like sidebar blocks, and everything in between
       
     5  * Version 1.1.1
       
     6  * Copyright (C) 2006-2007 Dan Fuhry
       
     7  *
       
     8  * This program is Free Software; you can redistribute and/or modify it under the terms of the GNU General Public License
       
     9  * as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.
       
    10  *
       
    11  * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
       
    12  * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for details.
       
    13  */
       
    14 
       
    15 /**
       
    16  * Language class - processes, stores, and retrieves language strings.
       
    17  * @package Enano
       
    18  * @subpackage Localization
       
    19  * @copyright 2007 Dan Fuhry
       
    20  * @license GNU General Public License
       
    21  */
       
    22 
       
    23 class Language
       
    24 {
       
    25   
       
    26   /**
       
    27    * The numerical ID of the loaded language.
       
    28    * @var int
       
    29    */
       
    30   
       
    31   var $lang_id;
       
    32   
       
    33   /**
       
    34    * The ISO-639-3 code for the loaded language. This should be grabbed directly from the database.
       
    35    * @var string
       
    36    */
       
    37   
       
    38   var $lang_code;
       
    39 
       
    40   /**
       
    41    * Used to track when a language was last changed, to allow browsers to cache language data
       
    42    * @var int
       
    43    */
       
    44   
       
    45   var $lang_timestamp;
       
    46   
       
    47   /**
       
    48    * Will be an object that holds an instance of the class configured with the site's default language. Only instanciated when needed.
       
    49    * @var object
       
    50    */
       
    51   
       
    52   var $default;
       
    53   
       
    54   /**
       
    55    * The list of loaded strings.
       
    56    * @var array
       
    57    * @access private
       
    58    */
       
    59   
       
    60   var $strings = array();
       
    61   
       
    62   /**
       
    63    * Constructor.
       
    64    * @param int|string Language ID or code to load.
       
    65    */
       
    66   
       
    67   function __construct($lang)
       
    68   {
       
    69     global $db, $session, $paths, $template, $plugins; // Common objects
       
    70     
       
    71     if ( defined('IN_ENANO_INSTALL') )
       
    72     {
       
    73       // special case for the Enano installer: it will load its own strings from a JSON file and just use this API for fetching and templatizing them.
       
    74       $this->lang_id   = LANG_DEFAULT;
       
    75       $this->lang_code = 'neutral';
       
    76       return true;
       
    77     }
       
    78     if ( is_string($lang) )
       
    79     {
       
    80       $sql_col = 'lang_code="' . $db->escape($lang) . '"';
       
    81     }
       
    82     else if ( is_int($lang) )
       
    83     {
       
    84       $sql_col = 'lang_id=' . $lang . '';
       
    85     }
       
    86     else
       
    87     {
       
    88       $db->_die('lang.php - attempting to pass invalid value to constructor');
       
    89     }
       
    90     
       
    91     $lang_default = ( $x = getConfig('default_language') ) ? intval($x) : 'def';
       
    92     $q = $db->sql_query("SELECT lang_id, lang_code, last_changed, ( lang_id = $lang_default ) AS is_default FROM " . table_prefix . "language WHERE $sql_col OR lang_id = $lang_default ORDER BY is_default DESC LIMIT 1;");
       
    93     
       
    94     if ( !$q )
       
    95       $db->_die('lang.php - main select query');
       
    96     
       
    97     if ( $db->numrows() < 1 )
       
    98       $db->_die('lang.php - There are no languages installed');
       
    99     
       
   100     $row = $db->fetchrow();
       
   101     
       
   102     $this->lang_id   = intval( $row['lang_id'] );
       
   103     $this->lang_code = $row['lang_code'];
       
   104     $this->lang_timestamp = $row['last_changed'];
       
   105   }
       
   106   
       
   107   /**
       
   108    * PHP 4 constructor.
       
   109    * @param int|string Language ID or code to load.
       
   110    */
       
   111   
       
   112   function Language($lang)
       
   113   {
       
   114     $this->__construct($lang);
       
   115   }
       
   116   
       
   117   /**
       
   118    * Fetches language strings from the database, or a cache file if it's available.
       
   119    * @param bool If true (default), allows the cache to be used.
       
   120    */
       
   121   
       
   122   function fetch($allow_cache = true)
       
   123   {
       
   124     global $db, $session, $paths, $template, $plugins; // Common objects
       
   125     
       
   126     $lang_file = ENANO_ROOT . "/cache/lang_{$this->lang_id}.php";
       
   127     // Attempt to load the strings from a cache file
       
   128     if ( file_exists($lang_file) && $allow_cache )
       
   129     {
       
   130       // Yay! found it
       
   131       $this->load_cache_file($lang_file);
       
   132     }
       
   133     else
       
   134     {
       
   135       // No cache file - select and retrieve from the database
       
   136       $q = $db->sql_unbuffered_query("SELECT string_category, string_name, string_content FROM " . table_prefix . "language_strings WHERE lang_id = {$this->lang_id};");
       
   137       if ( !$q )
       
   138         $db->_die('lang.php - selecting language string data');
       
   139       if ( $row = $db->fetchrow() )
       
   140       {
       
   141         $strings = array();
       
   142         do
       
   143         {
       
   144           $cat =& $row['string_category'];
       
   145           if ( !is_array($strings[$cat]) )
       
   146           {
       
   147             $strings[$cat] = array();
       
   148           }
       
   149           $strings[$cat][ $row['string_name'] ] = $row['string_content'];
       
   150         }
       
   151         while ( $row = $db->fetchrow() );
       
   152         // all done fetching
       
   153         $this->merge($strings);
       
   154       }
       
   155       else
       
   156       {
       
   157         $db->_die('lang.php - No strings for language ' . $this->lang_code);
       
   158       }
       
   159     }
       
   160   }
       
   161   
       
   162   /**
       
   163    * Loads a file from the disk cache (treated as PHP) and merges it into RAM.
       
   164    * @param string File to load
       
   165    */
       
   166   
       
   167   function load_cache_file($file)
       
   168   {
       
   169     global $db, $session, $paths, $template, $plugins; // Common objects
       
   170     
       
   171     // We're using eval() here because it makes handling scope easier.
       
   172     
       
   173     if ( !file_exists($file) )
       
   174       $db->_die('lang.php - requested cache file doesn\'t exist');
       
   175     
       
   176     $contents = file_get_contents($file);
       
   177     $contents = preg_replace('/([\s]*)<\?php/', '', $contents);
       
   178     
       
   179     @eval($contents);
       
   180     
       
   181     if ( !isset($lang_cache) || ( isset($lang_cache) && !is_array($lang_cache) ) )
       
   182       $db->_die('lang.php - the cache file is invalid (didn\'t set $lang_cache as an array)');
       
   183     
       
   184     $this->merge($lang_cache);
       
   185   }
       
   186   
       
   187   /**
       
   188    * Merges a standard language assoc array ($arr[cat][stringid]) with the master in RAM.
       
   189    * @param array
       
   190    */
       
   191   
       
   192   function merge($strings)
       
   193   {
       
   194     // This is stupidly simple.
       
   195     foreach ( $strings as $cat_id => $contents )
       
   196     {
       
   197       if ( !is_array($this->strings[$cat_id]) )
       
   198         $this->strings[$cat_id] = array();
       
   199       foreach ( $contents as $string_id => $string )
       
   200       {
       
   201         $this->strings[$cat_id][$string_id] = $string;
       
   202       }
       
   203     }
       
   204   }
       
   205   
       
   206   /**
       
   207    * Imports a JSON-format language file into the database and merges with current strings.
       
   208    * @param string Path to the JSON file to load
       
   209    */
       
   210   
       
   211   function import($file)
       
   212   {
       
   213     global $db, $session, $paths, $template, $plugins; // Common objects
       
   214     
       
   215     if ( !file_exists($file) )
       
   216       $db->_die('lang.php - can\'t import language file: string file doesn\'t exist');
       
   217     
       
   218     $contents = trim(@file_get_contents($file));
       
   219     
       
   220     if ( empty($contents) )
       
   221       $db->_die('lang.php - can\'t load the contents of the language file');
       
   222     
       
   223     // Trim off all text before and after the starting and ending braces
       
   224     $contents = preg_replace('/^([^{]+)\{/', '{', $contents);
       
   225     $contents = preg_replace('/\}([^}]+)$/', '}', $contents);
       
   226     
       
   227     $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
       
   228     $langdata = $json->decode($contents);
       
   229     
       
   230     if ( !is_array($langdata) )
       
   231       $db->_die('lang.php - invalid language file');
       
   232     
       
   233     if ( !isset($langdata['categories']) || !isset($langdata['strings']) )
       
   234       $db->_die('lang.php - language file does not contain the proper items');
       
   235     
       
   236     $insert_list = array();
       
   237     $delete_list = array();
       
   238     
       
   239     foreach ( $langdata['categories'] as $category )
       
   240     {
       
   241       if ( isset($langdata['strings'][$category]) )
       
   242       {
       
   243         foreach ( $langdata['strings'][$category] as $string_name => $string_value )
       
   244         {
       
   245           $string_name = $db->escape($string_name);
       
   246           $string_value = $db->escape($string_value);
       
   247           $category_name = $db->escape($category);
       
   248           $insert_list[] = "({$this->lang_id}, '$category_name', '$string_name', '$string_value')";
       
   249           $delete_list[] = "( lang_id = {$this->lang_id} AND string_category = '$category_name' AND string_name = '$string_name' )";
       
   250         }
       
   251       }
       
   252     }
       
   253     
       
   254     $delete_list = implode(" OR\n  ", $delete_list);
       
   255     $sql = "DELETE FROM " . table_prefix . "language_strings WHERE $delete_list;";
       
   256     
       
   257     // Free some memory
       
   258     unset($delete_list);
       
   259     
       
   260     // Run the query
       
   261     $q = $db->sql_query($sql);
       
   262     if ( !$q )
       
   263       $db->_die('lang.php - couldn\'t kill off them old strings');
       
   264     
       
   265     $insert_list = implode(",\n  ", $insert_list);
       
   266     $sql = "INSERT INTO " . table_prefix . "language_strings(lang_id, string_category, string_name, string_content) VALUES\n  $insert_list;";
       
   267     
       
   268     // Free some memory
       
   269     unset($insert_list);
       
   270     
       
   271     // Run the query
       
   272     $q = $db->sql_query($sql);
       
   273     if ( !$q )
       
   274       $db->_die('lang.php - couldn\'t insert strings in import()');
       
   275     
       
   276     // YAY! done!
       
   277     // This will regenerate the cache file if possible.
       
   278     $this->regen_caches();
       
   279   }
       
   280   
       
   281   /**
       
   282    * Refetches the strings and writes out the cache file.
       
   283    */
       
   284   
       
   285   function regen_caches()
       
   286   {
       
   287     global $db, $session, $paths, $template, $plugins; // Common objects
       
   288     
       
   289     $lang_file = ENANO_ROOT . "/cache/lang_{$this->lang_id}.php";
       
   290     
       
   291     // Refresh the strings in RAM to the latest copies in the DB
       
   292     $this->fetch(false);
       
   293     
       
   294     $handle = @fopen($lang_file, 'w');
       
   295     if ( !$handle )
       
   296       // Couldn't open the file. Silently fail and let the strings come from the database.
       
   297       return false;
       
   298       
       
   299     // The file's open, that means we should be good.
       
   300     fwrite($handle, '<?php
       
   301 // This file was generated automatically by Enano. You should not edit this file because any changes you make
       
   302 // to it will not be visible in the ACP and all changes will be lost upon any changes to strings in the admin panel.
       
   303 
       
   304 $lang_cache = ');
       
   305     
       
   306     $exported = $this->var_export_string($this->strings);
       
   307     if ( empty($exported) )
       
   308       // Ehh, that's not good
       
   309       $db->_die('lang.php - var_export_string() failed');
       
   310     
       
   311     fwrite($handle, $exported . '; ?>');
       
   312     
       
   313     // Update timestamp in database
       
   314     $q = $db->sql_query('UPDATE ' . table_prefix . 'language SET last_changed = ' . time() . ' WHERE lang_id = ' . $this->lang_id . ';');
       
   315     if ( !$q )
       
   316       $db->_die('lang.php - updating timestamp on language');
       
   317     
       
   318     // Done =)
       
   319     fclose($handle);
       
   320   }
       
   321   
       
   322   /**
       
   323    * Calls var_export() on whatever, and returns the function's output.
       
   324    * @param mixed Whatever you want var_exported. Usually an array.
       
   325    * @return string
       
   326    */
       
   327   
       
   328   function var_export_string($val)
       
   329   {
       
   330     ob_start();
       
   331     var_export($val);
       
   332     $contents = ob_get_contents();
       
   333     ob_end_clean();
       
   334     return $contents;
       
   335   }
       
   336   
       
   337   /**
       
   338    * Fetches a language string from the cache in RAM. If it isn't there, it will call fetch() again and then try. If it still can't find it, it will ask for the string
       
   339    * in the default language. If even then the string can't be found, this function will return what was passed to it.
       
   340    *
       
   341    * This will also templatize strings. If a string contains variables in the format %foo%, you may specify the second parameter as an associative array in the format
       
   342    * of 'foo' => 'foo substitute'.
       
   343    *
       
   344    * @param string ID of the string to fetch. This will always be in the format of category_stringid.
       
   345    * @param array Optional. Associative array of substitutions.
       
   346    * @return string
       
   347    */
       
   348   
       
   349   function get($string_id, $substitutions = false)
       
   350   {
       
   351     // Extract the category and string ID
       
   352     $category = substr($string_id, 0, ( strpos($string_id, '_') ));
       
   353     $string_name = substr($string_id, ( strpos($string_id, '_') + 1 ));
       
   354     $found = false;
       
   355     if ( isset($this->strings[$category]) && isset($this->strings[$category][$string_name]) )
       
   356     {
       
   357       $found = true;
       
   358       $string = $this->strings[$category][$string_name];
       
   359     }
       
   360     if ( !$found )
       
   361     {
       
   362       // Ehh, the string wasn't found. Rerun fetch() and try again.
       
   363       $this->fetch();
       
   364       if ( isset($this->strings[$category]) && isset($this->strings[$category][$string_name]) )
       
   365       {
       
   366         $found = true;
       
   367         $string = $this->strings[$category][$string_name];
       
   368       }
       
   369       if ( !$found )
       
   370       {
       
   371         // STILL not found. Check the default language.
       
   372         $lang_default = ( $x = getConfig('default_language') ) ? intval($x) : $this->lang_id;
       
   373         if ( $lang_default != $this->lang_id )
       
   374         {
       
   375           if ( !is_object($this->default) )
       
   376             $this->default = new Language($lang_default);
       
   377           return $this->default->get($string_id, $substitutions);
       
   378         }
       
   379       }
       
   380     }
       
   381     if ( !$found )
       
   382     {
       
   383       // Alright, it's nowhere. Return the input, grumble grumble...
       
   384       return $string_id;
       
   385     }
       
   386     // Found it!
       
   387     // Perform substitutions.
       
   388     // if ( is_array($substitutions) )
       
   389     //   die('<pre>' . print_r($substitutions, true) . '</pre>');
       
   390     if ( !is_array($substitutions) )
       
   391       $substitutions = array();
       
   392     return $this->substitute($string, $substitutions);
       
   393   }
       
   394   
       
   395   /**
       
   396    * Processes substitutions.
       
   397    * @param string
       
   398    * @param array
       
   399    * @return string
       
   400    */
       
   401   
       
   402   function substitute($string, $subs)
       
   403   {
       
   404     preg_match_all('/%this\.([a-z0-9_]+)%/', $string, $matches);
       
   405     if ( count($matches[0]) > 0 )
       
   406     {
       
   407       foreach ( $matches[1] as $i => $string_id )
       
   408       {
       
   409         $result = $this->get($string_id);
       
   410         $string = str_replace($matches[0][$i], $result, $string);
       
   411       }
       
   412     }
       
   413     preg_match_all('/%config\.([a-z0-9_]+)%/', $string, $matches);
       
   414     if ( count($matches[0]) > 0 )
       
   415     {
       
   416       foreach ( $matches[1] as $i => $string_id )
       
   417       {
       
   418         $result = getConfig($string_id);
       
   419         $string = str_replace($matches[0][$i], $result, $string);
       
   420       }
       
   421     }
       
   422     foreach ( $subs as $key => $value )
       
   423     {
       
   424       $subs[$key] = strval($value);
       
   425       $string = str_replace("%{$key}%", "{$subs[$key]}", $string);
       
   426     }
       
   427     return "L $string";
       
   428   }
       
   429   
       
   430 } // class Language
       
   431 
       
   432 ?>