includes/lang.php
changeset 205 c4542792db2b
child 209 8a00247d1dee
equal deleted inserted replaced
204:473cc747022a 205:c4542792db2b
       
     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    * Will be an object that holds an instance of the class configured with the site's default language. Only instanciated when needed.
       
    42    * @var object
       
    43    */
       
    44   
       
    45   var $default;
       
    46   
       
    47   /**
       
    48    * The list of loaded strings.
       
    49    * @var array
       
    50    * @access private
       
    51    */
       
    52   
       
    53   var $strings = array();
       
    54   
       
    55   /**
       
    56    * Constructor.
       
    57    * @param int|string Language ID or code to load.
       
    58    */
       
    59   
       
    60   function __construct($lang)
       
    61   {
       
    62     global $db, $session, $paths, $template, $plugins; // Common objects
       
    63     
       
    64     if ( defined('IN_ENANO_INSTALL') )
       
    65     {
       
    66       // 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.
       
    67       $this->lang_id   = LANG_DEFAULT;
       
    68       $this->lang_code = 'neutral';
       
    69       return true;
       
    70     }
       
    71     if ( is_string($lang) )
       
    72     {
       
    73       $sql_col = 'lang_code="' . $db->escape($lang) . '"';
       
    74     }
       
    75     else if ( is_int($lang) )
       
    76     {
       
    77       $sql_col = 'lang_id=' . $lang . '';
       
    78     }
       
    79     else
       
    80     {
       
    81       $db->_die('lang.php - attempting to pass invalid value to constructor');
       
    82     }
       
    83     
       
    84     $lang_default = ( $x = getConfig('default_language') ) ? intval($x) : 1;
       
    85     $q = $db->sql_query("SELECT lang_id, lang_code, ( 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;");
       
    86     
       
    87     if ( !$q )
       
    88       $db->_die('lang.php - main select query');
       
    89     
       
    90     if ( $db->numrows() < 1 )
       
    91       $db->_die('lang.php - There are no languages installed');
       
    92     
       
    93     $row = $db->fetchrow();
       
    94     
       
    95     $this->lang_id   = intval( $row['lang_id'] );
       
    96     $this->lang_code = $row['lang_code'];
       
    97   }
       
    98   
       
    99   /**
       
   100    * PHP 4 constructor.
       
   101    * @param int|string Language ID or code to load.
       
   102    */
       
   103   
       
   104   function Language($lang)
       
   105   {
       
   106     $this->__construct($lang);
       
   107   }
       
   108   
       
   109   /**
       
   110    * Fetches language strings from the database, or a cache file if it's available.
       
   111    * @param bool If true (default), allows the cache to be used.
       
   112    */
       
   113   
       
   114   function fetch($allow_cache = true)
       
   115   {
       
   116     global $db, $session, $paths, $template, $plugins; // Common objects
       
   117     
       
   118     $lang_file = ENANO_ROOT . "/cache/lang_{$this->lang_id}.php";
       
   119     // Attempt to load the strings from a cache file
       
   120     if ( file_exists($lang_file) && $allow_cache )
       
   121     {
       
   122       // Yay! found it
       
   123       $this->load_cache_file($lang_file);
       
   124     }
       
   125     else
       
   126     {
       
   127       // No cache file - select and retrieve from the database
       
   128       $q = $db->sql_unbuffered_query("SELECT string_category, string_name, string_content FROM " . table_prefix . "language_strings WHERE lang_id = {$this->lang_id};");
       
   129       if ( !$q )
       
   130         $db->_die('lang.php - selecting language string data');
       
   131       if ( $row = $db->fetchrow() )
       
   132       {
       
   133         $strings = array();
       
   134         do
       
   135         {
       
   136           $cat =& $row['string_category'];
       
   137           if ( !is_array($strings[$cat]) )
       
   138           {
       
   139             $strings[$cat] = array();
       
   140           }
       
   141           $strings[$cat][ $row['string_name'] ] = $row['string_content'];
       
   142         }
       
   143         while ( $row = $db->fetchrow() );
       
   144         // all done fetching
       
   145         $this->merge($strings);
       
   146       }
       
   147       else
       
   148       {
       
   149         $db->_die('lang.php - No strings for language ' . $this->lang_code);
       
   150       }
       
   151     }
       
   152   }
       
   153   
       
   154   /**
       
   155    * Loads a file from the disk cache (treated as PHP) and merges it into RAM.
       
   156    * @param string File to load
       
   157    */
       
   158   
       
   159   function load_cache_file($file)
       
   160   {
       
   161     global $db, $session, $paths, $template, $plugins; // Common objects
       
   162     
       
   163     // We're using eval() here because it makes handling scope easier.
       
   164     
       
   165     if ( !file_exists($file) )
       
   166       $db->_die('lang.php - requested cache file doesn\'t exist');
       
   167     
       
   168     $contents = file_get_contents($file);
       
   169     $contents = preg_replace('/([\s]*)<\?php/', '', $contents);
       
   170     
       
   171     @eval($contents);
       
   172     
       
   173     if ( !isset($lang_cache) || ( isset($lang_cache) && !is_array($lang_cache) ) )
       
   174       $db->_die('lang.php - the cache file is invalid (didn\'t set $lang_cache as an array)');
       
   175     
       
   176     $this->merge($lang_cache);
       
   177   }
       
   178   
       
   179   /**
       
   180    * Merges a standard language assoc array ($arr[cat][stringid]) with the master in RAM.
       
   181    * @param array
       
   182    */
       
   183   
       
   184   function merge($strings)
       
   185   {
       
   186     // This is stupidly simple.
       
   187     foreach ( $strings as $cat_id => $contents )
       
   188     {
       
   189       if ( !is_array($this->strings[$cat_id]) )
       
   190         $this->strings[$cat_id] = array();
       
   191       foreach ( $contents as $string_id => $string )
       
   192       {
       
   193         $this->strings[$cat_id][$string_id] = $string;
       
   194       }
       
   195     }
       
   196   }
       
   197   
       
   198   /**
       
   199    * Imports a JSON-format language file into the database and merges with current strings.
       
   200    * @param string Path to the JSON file to load
       
   201    */
       
   202   
       
   203   function import($file)
       
   204   {
       
   205     global $db, $session, $paths, $template, $plugins; // Common objects
       
   206     
       
   207     if ( !file_exists($file) )
       
   208       $db->_die('lang.php - can\'t import language file: string file doesn\'t exist');
       
   209     
       
   210     $contents = trim(@file_get_contents($file));
       
   211     
       
   212     if ( empty($contents) )
       
   213       $db->_die('lang.php - can\'t load the contents of the language file');
       
   214     
       
   215     // Trim off all text before and after the starting and ending braces
       
   216     $contents = preg_replace('/^([^{]+)\{/', '{', $contents);
       
   217     $contents = preg_replace('/\}([^}]+)$/', '}', $contents);
       
   218     
       
   219     $json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
       
   220     $langdata = $json->decode($contents);
       
   221     
       
   222     if ( !is_array($langdata) )
       
   223       $db->_die('lang.php - invalid language file');
       
   224     
       
   225     if ( !isset($langdata['categories']) || !isset($langdata['strings']) )
       
   226       $db->_die('lang.php - language file does not contain the proper items');
       
   227     
       
   228     $insert_list = array();
       
   229     $delete_list = array();
       
   230     
       
   231     foreach ( $langdata['categories'] as $category )
       
   232     {
       
   233       if ( isset($langdata['strings'][$category]) )
       
   234       {
       
   235         foreach ( $langdata['strings'][$category] as $string_name => $string_value )
       
   236         {
       
   237           $string_name = $db->escape($string_name);
       
   238           $string_value = $db->escape($string_value);
       
   239           $category_name = $db->escape($category);
       
   240           $insert_list[] = "({$this->lang_id}, '$category_name', '$string_name', '$string_value')";
       
   241           $delete_list[] = "( lang_id = {$this->lang_id} AND string_category = '$category_name' AND string_name = '$string_name' )";
       
   242         }
       
   243       }
       
   244     }
       
   245     
       
   246     $delete_list = implode(" OR\n  ", $delete_list);
       
   247     $sql = "DELETE FROM " . table_prefix . "language_strings WHERE $delete_list;";
       
   248     
       
   249     // Free some memory
       
   250     unset($delete_list);
       
   251     
       
   252     // Run the query
       
   253     $q = $db->sql_query($sql);
       
   254     if ( !$q )
       
   255       $db->_die('lang.php - couldn\'t kill off them old strings');
       
   256     
       
   257     $insert_list = implode(",\n  ", $insert_list);
       
   258     $sql = "INSERT INTO " . table_prefix . "language_strings(lang_id, string_category, string_name, string_content) VALUES\n  $insert_list;";
       
   259     
       
   260     // Free some memory
       
   261     unset($insert_list);
       
   262     
       
   263     // Run the query
       
   264     $q = $db->sql_query($sql);
       
   265     if ( !$q )
       
   266       $db->_die('lang.php - couldn\'t insert strings in import()');
       
   267     
       
   268     // YAY! done!
       
   269     // This will regenerate the cache file if possible.
       
   270     $this->regen_caches();
       
   271   }
       
   272   
       
   273   /**
       
   274    * Refetches the strings and writes out the cache file.
       
   275    */
       
   276   
       
   277   function regen_caches()
       
   278   {
       
   279     global $db, $session, $paths, $template, $plugins; // Common objects
       
   280     
       
   281     $lang_file = ENANO_ROOT . "/cache/lang_{$this->lang_id}.php";
       
   282     
       
   283     // Refresh the strings in RAM to the latest copies in the DB
       
   284     $this->fetch(false);
       
   285     
       
   286     $handle = @fopen($lang_file, 'w');
       
   287     if ( !$handle )
       
   288       // Couldn't open the file. Silently fail and let the strings come from the database.
       
   289       return false;
       
   290       
       
   291     // The file's open, that means we should be good.
       
   292     fwrite($handle, '<?php
       
   293 // This file was generated automatically by Enano. You should not edit this file because any changes you make
       
   294 // to it will not be visible in the ACP and all changes will be lost upon any changes to strings in the admin panel.
       
   295 
       
   296 $lang_cache = ');
       
   297     
       
   298     $exported = $this->var_export_string($this->strings);
       
   299     if ( empty($exported) )
       
   300       // Ehh, that's not good
       
   301       $db->_die('lang.php - var_export_string() failed');
       
   302     
       
   303     fwrite($handle, $exported . '; ?>');
       
   304     
       
   305     // Done =)
       
   306     fclose($handle);
       
   307   }
       
   308   
       
   309   /**
       
   310    * Calls var_export() on whatever, and returns the function's output.
       
   311    * @param mixed Whatever you want var_exported. Usually an array.
       
   312    * @return string
       
   313    */
       
   314   
       
   315   function var_export_string($val)
       
   316   {
       
   317     ob_start();
       
   318     var_export($val);
       
   319     $contents = ob_get_contents();
       
   320     ob_end_clean();
       
   321     return $contents;
       
   322   }
       
   323   
       
   324   /**
       
   325    * 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
       
   326    * in the default language. If even then the string can't be found, this function will return what was passed to it.
       
   327    *
       
   328    * 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
       
   329    * of 'foo' => 'foo substitute'.
       
   330    *
       
   331    * @param string ID of the string to fetch. This will always be in the format of category_stringid.
       
   332    * @param array Optional. Associative array of substitutions.
       
   333    * @return string
       
   334    */
       
   335   
       
   336   function get($string_id, $substitutions = false)
       
   337   {
       
   338     // Extract the category and string ID
       
   339     $category = substr($string_id, 0, ( strpos($string_id, '_') ));
       
   340     $string_name = substr($string_id, ( strpos($string_id, '_') + 1 ));
       
   341     $found = false;
       
   342     if ( isset($this->strings[$category]) && isset($this->strings[$category][$string_name]) )
       
   343     {
       
   344       $found = true;
       
   345       $string = $this->strings[$category][$string_name];
       
   346     }
       
   347     if ( !$found )
       
   348     {
       
   349       // Ehh, the string wasn't found. Rerun fetch() and try again.
       
   350       $this->fetch();
       
   351       if ( isset($this->strings[$category]) && isset($this->strings[$category][$string_name]) )
       
   352       {
       
   353         $found = true;
       
   354         $string = $this->strings[$category][$string_name];
       
   355       }
       
   356       if ( !$found )
       
   357       {
       
   358         // STILL not found. Check the default language.
       
   359         $lang_default = ( $x = getConfig('default_language') ) ? intval($x) : $this->lang_id;
       
   360         if ( $lang_default != $this->lang_id )
       
   361         {
       
   362           if ( !is_object($this->default) )
       
   363             $this->default = new Language($lang_default);
       
   364           return $this->default->get($string_id, $substitutions);
       
   365         }
       
   366       }
       
   367     }
       
   368     if ( !$found )
       
   369     {
       
   370       // Alright, it's nowhere. Return the input, grumble grumble...
       
   371       return $string_id;
       
   372     }
       
   373     // Found it!
       
   374     // Perform substitutions.
       
   375     if ( !is_array($substitutions) )
       
   376       $substitutions = array();
       
   377     return $this->substitute($string, $substitutions);
       
   378   }
       
   379   
       
   380   /**
       
   381    * Processes substitutions.
       
   382    * @param string
       
   383    * @param array
       
   384    * @return string
       
   385    */
       
   386   
       
   387   function substitute($string, $subs)
       
   388   {
       
   389     preg_match_all('/%this\.([a-z0-9_]+)%/', $string, $matches);
       
   390     if ( count($matches[0]) > 0 )
       
   391     {
       
   392       foreach ( $matches[1] as $i => $string_id )
       
   393       {
       
   394         $result = $this->get($string_id);
       
   395         $string = str_replace($matches[0][$i], $result, $string);
       
   396       }
       
   397     }
       
   398     foreach ( $subs as $key => $value )
       
   399     {
       
   400       $string = str_replace("%$key%", $value, $string);
       
   401     }
       
   402     return $string;
       
   403   }
       
   404   
       
   405 } // class Language
       
   406 
       
   407 ?>