includes/lang.php
changeset 1227 bdac73ed481e
parent 1081 745200a9cc2a
child 1327 e8f4dea267c8
equal deleted inserted replaced
1226:de56132c008d 1227:bdac73ed481e
    19  * @license GNU General Public License
    19  * @license GNU General Public License
    20  */
    20  */
    21 
    21 
    22 class Language
    22 class Language
    23 {
    23 {
    24   
    24 	
    25   /**
    25 	/**
    26    * The numerical ID of the loaded language.
    26  	* The numerical ID of the loaded language.
    27    * @var int
    27  	* @var int
    28    */
    28  	*/
    29   
    29 	
    30   var $lang_id;
    30 	var $lang_id;
    31   
    31 	
    32   /**
    32 	/**
    33    * The ISO-639-3 code for the loaded language. This should be grabbed directly from the database.
    33  	* The ISO-639-3 code for the loaded language. This should be grabbed directly from the database.
    34    * @var string
    34  	* @var string
    35    */
    35  	*/
    36   
    36 	
    37   var $lang_code;
    37 	var $lang_code;
    38 
    38 
    39   /**
    39 	/**
    40    * Used to track when a language was last changed, to allow browsers to cache language data
    40  	* Used to track when a language was last changed, to allow browsers to cache language data
    41    * @var int
    41  	* @var int
    42    */
    42  	*/
    43   
    43 	
    44   var $lang_timestamp;
    44 	var $lang_timestamp;
    45   
    45 	
    46   /**
    46 	/**
    47    * Will be an object that holds an instance of the class configured with the site's default language. Only instanciated when needed.
    47  	* Will be an object that holds an instance of the class configured with the site's default language. Only instanciated when needed.
    48    * @var object
    48  	* @var object
    49    */
    49  	*/
    50   
    50 	
    51   var $default;
    51 	var $default;
    52   
    52 	
    53   /**
    53 	/**
    54    * The list of loaded strings.
    54  	* The list of loaded strings.
    55    * @var array
    55  	* @var array
    56    * @access private
    56  	* @access private
    57    */
    57  	*/
    58   
    58 	
    59   var $strings = array();
    59 	var $strings = array();
    60   
    60 	
    61   /**
    61 	/**
    62    * Switch for debug mode. If true, will show an asterisk after localized strings. This
    62  	* Switch for debug mode. If true, will show an asterisk after localized strings. This
    63    * can be useful if you're localizing a component and need to see what's already done.
    63  	* can be useful if you're localizing a component and need to see what's already done.
    64    * @var bool
    64  	* @var bool
    65    */
    65  	*/
    66   
    66 	
    67   var $debug = false;
    67 	var $debug = false;
    68   
    68 	
    69   /**
    69 	/**
    70    * List of available filters to pass variables through.
    70  	* List of available filters to pass variables through.
    71    * @var array
    71  	* @var array
    72    * @access private
    72  	* @access private
    73    */
    73  	*/
    74   
    74 	
    75   protected $filters = array();
    75 	protected $filters = array();
    76   
    76 	
    77   /**
    77 	/**
    78    * Constructor.
    78  	* Constructor.
    79    * @param int|string Language ID or code to load.
    79  	* @param int|string Language ID or code to load.
    80    */
    80  	*/
    81   
    81 	
    82   function __construct($lang)
    82 	function __construct($lang)
    83   {
    83 	{
    84     global $db, $session, $paths, $template, $plugins; // Common objects
    84 		global $db, $session, $paths, $template, $plugins; // Common objects
    85     
    85 		
    86     if ( defined('IN_ENANO_INSTALL') && ( !defined('ENANO_CONFIG_FETCHED') || ( defined('IN_ENANO_UPGRADE') && !defined('IN_ENANO_UPGRADE_POST') ) ) )
    86 		if ( defined('IN_ENANO_INSTALL') && ( !defined('ENANO_CONFIG_FETCHED') || ( defined('IN_ENANO_UPGRADE') && !defined('IN_ENANO_UPGRADE_POST') ) ) )
    87     {
    87 		{
    88       // special case for the Enano installer: it will load its own strings from a JSON file and just use this API for fetching
    88 			// special case for the Enano installer: it will load its own strings from a JSON file and just use this API for fetching
    89       // and templatizing them.
    89 			// and templatizing them.
    90       // 1.1.4 fix: this was still being called after main API startup from installer payload
    90 			// 1.1.4 fix: this was still being called after main API startup from installer payload
    91       $this->lang_id   = 1;
    91 			$this->lang_id   = 1;
    92       $this->lang_code = $lang;
    92 			$this->lang_code = $lang;
    93       return true;
    93 			return true;
    94     }
    94 		}
    95     if ( is_string($lang) )
    95 		if ( is_string($lang) )
    96     {
    96 		{
    97       $sql_col = 'lang_code=\'' . $db->escape($lang) . '\'';
    97 			$sql_col = 'lang_code=\'' . $db->escape($lang) . '\'';
    98     }
    98 		}
    99     else if ( is_int($lang) )
    99 		else if ( is_int($lang) )
   100     {
   100 		{
   101       $sql_col = 'lang_id=' . $lang . '';
   101 			$sql_col = 'lang_id=' . $lang . '';
   102     }
   102 		}
   103     else
   103 		else
   104     {
   104 		{
   105       $db->_die('lang.php - attempting to pass invalid value to constructor');
   105 			$db->_die('lang.php - attempting to pass invalid value to constructor');
   106     }
   106 		}
   107     
   107 		
   108     $lang_default = ( $x = getConfig('default_language') ) ? intval($x) : '0';
   108 		$lang_default = ( $x = getConfig('default_language') ) ? intval($x) : '0';
   109     
   109 		
   110     $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 ASC LIMIT 1;");
   110 		$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 ASC LIMIT 1;");
   111     
   111 		
   112     if ( !$q )
   112 		if ( !$q )
   113       $db->_die('lang.php - main select query');
   113 			$db->_die('lang.php - main select query');
   114     
   114 		
   115     if ( $db->numrows() < 1 )
   115 		if ( $db->numrows() < 1 )
   116       $db->_die('lang.php - There are no languages installed');
   116 			$db->_die('lang.php - There are no languages installed');
   117     
   117 		
   118     $row = $db->fetchrow();
   118 		$row = $db->fetchrow();
   119     
   119 		
   120     $this->lang_id   = intval( $row['lang_id'] );
   120 		$this->lang_id   = intval( $row['lang_id'] );
   121     $this->lang_code = $row['lang_code'];
   121 		$this->lang_code = $row['lang_code'];
   122     $this->lang_timestamp = $row['last_changed'];
   122 		$this->lang_timestamp = $row['last_changed'];
   123     
   123 		
   124     $this->register_filter('htmlsafe', 'htmlspecialchars');
   124 		$this->register_filter('htmlsafe', 'htmlspecialchars');
   125     $this->register_filter('urlencode', 'urlencode');
   125 		$this->register_filter('urlencode', 'urlencode');
   126     $this->register_filter('rawurlencode', 'rawurlencode');
   126 		$this->register_filter('rawurlencode', 'rawurlencode');
   127     
   127 		
   128     $code = $plugins->setHook('lang_init');
   128 		$code = $plugins->setHook('lang_init');
   129     foreach ( $code as $cmd )
   129 		foreach ( $code as $cmd )
   130     {
   130 		{
   131       eval($cmd);
   131 			eval($cmd);
   132     }
   132 		}
   133   }
   133 	}
   134   
   134 	
   135   /**
   135 	/**
   136    * Fetches language strings from the database, or a cache file if it's available.
   136  	* Fetches language strings from the database, or a cache file if it's available.
   137    * @param bool If true (default), allows the cache to be used.
   137  	* @param bool If true (default), allows the cache to be used.
   138    */
   138  	*/
   139   
   139 	
   140   function fetch($allow_cache = true)
   140 	function fetch($allow_cache = true)
   141   {
   141 	{
   142     global $db, $session, $paths, $template, $plugins; // Common objects
   142 		global $db, $session, $paths, $template, $plugins; // Common objects
   143     
   143 		
   144     // Attempt to load the strings from a cache file
   144 		// Attempt to load the strings from a cache file
   145     $loaded = false;
   145 		$loaded = false;
   146     
   146 		
   147     if ( $allow_cache )
   147 		if ( $allow_cache )
   148     {
   148 		{
   149       // Load the cache manager
   149 			// Load the cache manager
   150       global $cache;
   150 			global $cache;
   151       
   151 			
   152       if ( $cached = $cache->fetch("lang_{$this->lang_id}") )
   152 			if ( $cached = $cache->fetch("lang_{$this->lang_id}") )
   153       {
   153 			{
   154         $this->merge($cached);
   154 				$this->merge($cached);
   155         $loaded = true;
   155 				$loaded = true;
   156       }
   156 			}
   157     }
   157 		}
   158     if ( !$loaded )
   158 		if ( !$loaded )
   159     {
   159 		{
   160       // No cache file - select and retrieve from the database
   160 			// No cache file - select and retrieve from the database
   161       $q = $db->sql_unbuffered_query("SELECT string_category, string_name, string_content FROM " . table_prefix . "language_strings WHERE lang_id = {$this->lang_id};");
   161 			$q = $db->sql_unbuffered_query("SELECT string_category, string_name, string_content FROM " . table_prefix . "language_strings WHERE lang_id = {$this->lang_id};");
   162       if ( !$q )
   162 			if ( !$q )
   163         $db->_die('lang.php - selecting language string data');
   163 				$db->_die('lang.php - selecting language string data');
   164       if ( $row = $db->fetchrow() )
   164 			if ( $row = $db->fetchrow() )
   165       {
   165 			{
   166         $strings = array();
   166 				$strings = array();
   167         do
   167 				do
   168         {
   168 				{
   169           $cat =& $row['string_category'];
   169 					$cat =& $row['string_category'];
   170           if ( !is_array(@$strings[$cat]) )
   170 					if ( !is_array(@$strings[$cat]) )
   171           {
   171 					{
   172             $strings[$cat] = array();
   172 						$strings[$cat] = array();
   173           }
   173 					}
   174           $strings[$cat][ $row['string_name'] ] = $row['string_content'];
   174 					$strings[$cat][ $row['string_name'] ] = $row['string_content'];
   175         }
   175 				}
   176         while ( $row = $db->fetchrow() );
   176 				while ( $row = $db->fetchrow() );
   177         // all done fetching
   177 				// all done fetching
   178         $this->merge($strings);
   178 				$this->merge($strings);
   179         $this->regen_caches(false);
   179 				$this->regen_caches(false);
   180       }
   180 			}
   181       /*
   181 			/*
   182       else
   182 			else
   183       {
   183 			{
   184         if ( !defined('ENANO_ALLOW_LOAD_NOLANG') )
   184 				if ( !defined('ENANO_ALLOW_LOAD_NOLANG') )
   185           $db->_die('lang.php - No strings for language ' . $this->lang_code);
   185 					$db->_die('lang.php - No strings for language ' . $this->lang_code);
   186       }
   186 			}
   187       */
   187 			*/
   188     }
   188 		}
   189   }
   189 	}
   190   
   190 	
   191   /**
   191 	/**
   192    * Loads a file from the disk cache (treated as PHP) and merges it into RAM.
   192  	* Loads a file from the disk cache (treated as PHP) and merges it into RAM.
   193    * @param string File to load
   193  	* @param string File to load
   194    */
   194  	*/
   195   
   195 	
   196   function load_cache_file($file)
   196 	function load_cache_file($file)
   197   {
   197 	{
   198     global $db, $session, $paths, $template, $plugins; // Common objects
   198 		global $db, $session, $paths, $template, $plugins; // Common objects
   199     
   199 		
   200     if ( !file_exists($file) )
   200 		if ( !file_exists($file) )
   201       $db->_die('lang.php - requested cache file doesn\'t exist');
   201 			$db->_die('lang.php - requested cache file doesn\'t exist');
   202     
   202 		
   203     @include($file);
   203 		@include($file);
   204         
   204 				
   205     if ( !isset($lang_cache) || ( isset($lang_cache) && !is_array($lang_cache) ) )
   205 		if ( !isset($lang_cache) || ( isset($lang_cache) && !is_array($lang_cache) ) )
   206       $db->_die('lang.php - the cache file is invalid (didn\'t set $lang_cache as an array)');
   206 			$db->_die('lang.php - the cache file is invalid (didn\'t set $lang_cache as an array)');
   207     
   207 		
   208     $this->merge($lang_cache);
   208 		$this->merge($lang_cache);
   209   }
   209 	}
   210   
   210 	
   211   /**
   211 	/**
   212    * Loads a JSON language file and parses the strings into RAM. Will use the cache if possible, but stays far away from the database,
   212  	* Loads a JSON language file and parses the strings into RAM. Will use the cache if possible, but stays far away from the database,
   213    * which we assume doesn't exist yet.
   213  	* which we assume doesn't exist yet.
   214    */
   214  	*/
   215   
   215 	
   216   function load_file($file)
   216 	function load_file($file)
   217   {
   217 	{
   218     global $db, $session, $paths, $template, $plugins; // Common objects
   218 		global $db, $session, $paths, $template, $plugins; // Common objects
   219     
   219 		
   220     if ( !file_exists($file) )
   220 		if ( !file_exists($file) )
   221     {
   221 		{
   222       if ( defined('IN_ENANO_INSTALL') )
   222 			if ( defined('IN_ENANO_INSTALL') )
   223       {
   223 			{
   224         die('lang.php - requested JSON file (' . htmlspecialchars($file) . ') doesn\'t exist');
   224 				die('lang.php - requested JSON file (' . htmlspecialchars($file) . ') doesn\'t exist');
   225       }
   225 			}
   226       else
   226 			else
   227       {
   227 			{
   228         $db->_die('lang.php - requested JSON file doesn\'t exist');
   228 				$db->_die('lang.php - requested JSON file doesn\'t exist');
   229       }
   229 			}
   230     }
   230 		}
   231     
   231 		
   232     $contents = trim(@file_get_contents($file));
   232 		$contents = trim(@file_get_contents($file));
   233     if ( empty($contents) )
   233 		if ( empty($contents) )
   234       $db->_die('lang.php - empty language file...');
   234 			$db->_die('lang.php - empty language file...');
   235     
   235 		
   236     // Trim off all text before and after the starting and ending braces
   236 		// Trim off all text before and after the starting and ending braces
   237     $contents = preg_replace('/^([^{]+)\{/', '{', $contents);
   237 		$contents = preg_replace('/^([^{]+)\{/', '{', $contents);
   238     $contents = preg_replace('/\}([^}]+)$/', '}', $contents);
   238 		$contents = preg_replace('/\}([^}]+)$/', '}', $contents);
   239     $contents = trim($contents);
   239 		$contents = trim($contents);
   240     
   240 		
   241     if ( empty($contents) )
   241 		if ( empty($contents) )
   242       $db->_die('lang.php - no meat to the language file...');
   242 			$db->_die('lang.php - no meat to the language file...');
   243     
   243 		
   244     $checksum = md5($contents);
   244 		$checksum = md5($contents);
   245     if ( file_exists("./cache/lang_json_{$checksum}.php") )
   245 		if ( file_exists("./cache/lang_json_{$checksum}.php") )
   246     {
   246 		{
   247       $this->load_cache_file("./cache/lang_json_{$checksum}.php");
   247 			$this->load_cache_file("./cache/lang_json_{$checksum}.php");
   248     }
   248 		}
   249     else
   249 		else
   250     {
   250 		{
   251       // Correct syntax to be nice to the json parser
   251 			// Correct syntax to be nice to the json parser
   252     
   252 		
   253       // eliminate comments
   253 			// eliminate comments
   254       $contents = preg_replace(array(
   254 			$contents = preg_replace(array(
   255               // eliminate single line comments in '// ...' form
   255 							// eliminate single line comments in '// ...' form
   256               '#^\s*//(.+)$#m',
   256 							'#^\s*//(.+)$#m',
   257               // eliminate multi-line comments in '/* ... */' form, at start of string
   257 							// eliminate multi-line comments in '/* ... */' form, at start of string
   258               '#^\s*/\*(.+)\*/#Us',
   258 							'#^\s*/\*(.+)\*/#Us',
   259               // eliminate multi-line comments in '/* ... */' form, at end of string
   259 							// eliminate multi-line comments in '/* ... */' form, at end of string
   260               '#/\*(.+)\*/\s*$#Us'
   260 							'#/\*(.+)\*/\s*$#Us'
   261             ), '', $contents);
   261 						), '', $contents);
   262       
   262 			
   263       $contents = preg_replace('/([,\{\[])([\s]*?)([a-z0-9_]+)([\s]*?):/', '\\1\\2"\\3" :', $contents);
   263 			$contents = preg_replace('/([,\{\[])([\s]*?)([a-z0-9_]+)([\s]*?):/', '\\1\\2"\\3" :', $contents);
   264       
   264 			
   265       try
   265 			try
   266       {
   266 			{
   267         $langdata = enano_json_decode($contents);
   267 				$langdata = enano_json_decode($contents);
   268       }
   268 			}
   269       catch(Zend_Json_Exception $e)
   269 			catch(Zend_Json_Exception $e)
   270       {
   270 			{
   271         $db->_die('lang.php - Exception caught by JSON parser</p><pre>' . htmlspecialchars(print_r($e, true)) . '</pre><p>');
   271 				$db->_die('lang.php - Exception caught by JSON parser</p><pre>' . htmlspecialchars(print_r($e, true)) . '</pre><p>');
   272         exit;
   272 				exit;
   273       }
   273 			}
   274     
   274 		
   275       if ( !is_array($langdata) )
   275 			if ( !is_array($langdata) )
   276         $db->_die('lang.php - invalid language file');
   276 				$db->_die('lang.php - invalid language file');
   277       
   277 			
   278       if ( !isset($langdata['categories']) || !isset($langdata['strings']) )
   278 			if ( !isset($langdata['categories']) || !isset($langdata['strings']) )
   279         $db->_die('lang.php - language file does not contain the proper items');
   279 				$db->_die('lang.php - language file does not contain the proper items');
   280       
   280 			
   281       $this->merge($langdata['strings']);
   281 			$this->merge($langdata['strings']);
   282       
   282 			
   283       $lang_file = "./cache/lang_json_{$checksum}.php";
   283 			$lang_file = "./cache/lang_json_{$checksum}.php";
   284       
   284 			
   285       $handle = @fopen($lang_file, 'w');
   285 			$handle = @fopen($lang_file, 'w');
   286       if ( !$handle )
   286 			if ( !$handle )
   287         // Couldn't open the file. Silently fail and let the strings come from RAM.
   287 				// Couldn't open the file. Silently fail and let the strings come from RAM.
   288         return false;
   288 				return false;
   289         
   289 				
   290       // The file's open, that means we should be good.
   290 			// The file's open, that means we should be good.
   291       fwrite($handle, '<?php
   291 			fwrite($handle, '<?php
   292 // This file was generated automatically by Enano. You should not edit this file because any changes you make
   292 // This file was generated automatically by Enano. You should not edit this file because any changes you make
   293 // to it will not be visible in the ACP and all changes will be lost upon any changes to strings in the admin panel.
   293 // to it will not be visible in the ACP and all changes will be lost upon any changes to strings in the admin panel.
   294 
   294 
   295 $lang_cache = ');
   295 $lang_cache = ');
   296       
   296 			
   297       $exported = $this->var_export_string($this->strings);
   297 			$exported = $this->var_export_string($this->strings);
   298       if ( empty($exported) )
   298 			if ( empty($exported) )
   299         // Ehh, that's not good
   299 				// Ehh, that's not good
   300         $db->_die('lang.php - load_file(): var_export_string() failed');
   300 				$db->_die('lang.php - load_file(): var_export_string() failed');
   301       
   301 			
   302       fwrite($handle, $exported . '; ?>');
   302 			fwrite($handle, $exported . '; ?>');
   303       
   303 			
   304       // Clean up
   304 			// Clean up
   305       unset($exported, $langdata);
   305 			unset($exported, $langdata);
   306       
   306 			
   307       // Done =)
   307 			// Done =)
   308       fclose($handle);
   308 			fclose($handle);
   309     }
   309 		}
   310   }
   310 	}
   311   
   311 	
   312   /**
   312 	/**
   313    * Merges a standard language assoc array ($arr[cat][stringid]) with the master in RAM.
   313  	* Merges a standard language assoc array ($arr[cat][stringid]) with the master in RAM.
   314    * @param array
   314  	* @param array
   315    */
   315  	*/
   316   
   316 	
   317   function merge($strings)
   317 	function merge($strings)
   318   {
   318 	{
   319     // This is stupidly simple.
   319 		// This is stupidly simple.
   320     foreach ( $strings as $cat_id => $contents )
   320 		foreach ( $strings as $cat_id => $contents )
   321     {
   321 		{
   322       if ( !isset($this->strings[$cat_id]) || ( isset($this->strings[$cat_id]) && !is_array($this->strings[$cat_id]) ) )
   322 			if ( !isset($this->strings[$cat_id]) || ( isset($this->strings[$cat_id]) && !is_array($this->strings[$cat_id]) ) )
   323         $this->strings[$cat_id] = array();
   323 				$this->strings[$cat_id] = array();
   324       foreach ( $contents as $string_id => $string )
   324 			foreach ( $contents as $string_id => $string )
   325       {
   325 			{
   326         $this->strings[$cat_id][$string_id] = $string;
   326 				$this->strings[$cat_id][$string_id] = $string;
   327       }
   327 			}
   328     }
   328 		}
   329   }
   329 	}
   330   
   330 	
   331   /**
   331 	/**
   332    * Imports a JSON-format language file into the database and merges with current strings.
   332  	* Imports a JSON-format language file into the database and merges with current strings.
   333    * @param string Path to the JSON file to load
   333  	* @param string Path to the JSON file to load
   334    * @param bool If true, only imports new strings and skips those that already exist. Defaults to false (import all strings)
   334  	* @param bool If true, only imports new strings and skips those that already exist. Defaults to false (import all strings)
   335    * @param bool Enable debugging output, makes the process over CLI more interesting
   335  	* @param bool Enable debugging output, makes the process over CLI more interesting
   336    */
   336  	*/
   337   
   337 	
   338   function import($file, $skip_existing = false, $debug = false)
   338 	function import($file, $skip_existing = false, $debug = false)
   339   {
   339 	{
   340     global $db, $session, $paths, $template, $plugins; // Common objects
   340 		global $db, $session, $paths, $template, $plugins; // Common objects
   341     
   341 		
   342     if ( !file_exists($file) )
   342 		if ( !file_exists($file) )
   343       $db->_die('lang.php - can\'t import language file: string file doesn\'t exist');
   343 			$db->_die('lang.php - can\'t import language file: string file doesn\'t exist');
   344     
   344 		
   345     if ( $this->lang_id == 0 )
   345 		if ( $this->lang_id == 0 )
   346       $db->_die('lang.php - BUG: trying to perform import when $lang->lang_id == 0');
   346 			$db->_die('lang.php - BUG: trying to perform import when $lang->lang_id == 0');
   347     
   347 		
   348     if ( $debug )
   348 		if ( $debug )
   349       $br = ( isset($_SERVER['REQUEST_URI']) ) ? '<br />' : '';
   349 			$br = ( isset($_SERVER['REQUEST_URI']) ) ? '<br />' : '';
   350     
   350 		
   351     if ( $debug )
   351 		if ( $debug )
   352       echo "Importing file: $file$br\n  Checking file...$br\n";
   352 			echo "Importing file: $file$br\n  Checking file...$br\n";
   353     
   353 		
   354     $contents = trim(@file_get_contents($file));
   354 		$contents = trim(@file_get_contents($file));
   355     
   355 		
   356     if ( empty($contents) )
   356 		if ( empty($contents) )
   357       $db->_die('lang.php - can\'t load the contents of the language file');
   357 			$db->_die('lang.php - can\'t load the contents of the language file');
   358     
   358 		
   359     if ( $debug )
   359 		if ( $debug )
   360       echo "  Cleaning up JSON$br\n";
   360 			echo "  Cleaning up JSON$br\n";
   361     
   361 		
   362     // Trim off all text before and after the starting and ending braces
   362 		// Trim off all text before and after the starting and ending braces
   363     $contents = preg_replace('/^([^{]+)\{/', '{', $contents);
   363 		$contents = preg_replace('/^([^{]+)\{/', '{', $contents);
   364     $contents = preg_replace('/\}([^}]+)$/', '}', $contents);
   364 		$contents = preg_replace('/\}([^}]+)$/', '}', $contents);
   365     
   365 		
   366     // Correct syntax to be nice to the json parser
   366 		// Correct syntax to be nice to the json parser
   367     $contents = enano_clean_json($contents);
   367 		$contents = enano_clean_json($contents);
   368     
   368 		
   369     if ( $debug )
   369 		if ( $debug )
   370       echo "  Decoding JSON stream$br\n";
   370 			echo "  Decoding JSON stream$br\n";
   371     
   371 		
   372     try
   372 		try
   373     {
   373 		{
   374       $langdata = enano_json_decode($contents);
   374 			$langdata = enano_json_decode($contents);
   375     }
   375 		}
   376     catch(Zend_Json_Exception $e)
   376 		catch(Zend_Json_Exception $e)
   377     {
   377 		{
   378       $db->_die('lang.php - Exception caught by JSON parser</p><pre>' . htmlspecialchars(print_r($e, true)) . '</pre><p>');
   378 			$db->_die('lang.php - Exception caught by JSON parser</p><pre>' . htmlspecialchars(print_r($e, true)) . '</pre><p>');
   379       exit;
   379 			exit;
   380     }
   380 		}
   381     
   381 		
   382     if ( !is_array($langdata) )
   382 		if ( !is_array($langdata) )
   383     {
   383 		{
   384       $db->_die('lang.php - invalid or non-well-formed language file');
   384 			$db->_die('lang.php - invalid or non-well-formed language file');
   385     }
   385 		}
   386     
   386 		
   387     if ( $debug )
   387 		if ( $debug )
   388       echo "  Starting string import$br\n";
   388 			echo "  Starting string import$br\n";
   389     
   389 		
   390     return $this->import_array($langdata, $skip_existing, $debug);
   390 		return $this->import_array($langdata, $skip_existing, $debug);
   391   }
   391 	}
   392   
   392 	
   393   /**
   393 	/**
   394    * Imports a JSON-format language file into the database and merges with current strings.
   394  	* Imports a JSON-format language file into the database and merges with current strings.
   395    * @param string Path to plugin file
   395  	* @param string Path to plugin file
   396    */
   396  	*/
   397   
   397 	
   398   function import_plugin($file)
   398 	function import_plugin($file)
   399   {
   399 	{
   400     global $db, $session, $paths, $template, $plugins; // Common objects
   400 		global $db, $session, $paths, $template, $plugins; // Common objects
   401     
   401 		
   402     if ( !file_exists($file) )
   402 		if ( !file_exists($file) )
   403       $db->_die('lang.php - can\'t import language file: string file doesn\'t exist');
   403 			$db->_die('lang.php - can\'t import language file: string file doesn\'t exist');
   404     
   404 		
   405     if ( $this->lang_id == 0 )
   405 		if ( $this->lang_id == 0 )
   406       $db->_die('lang.php - BUG: trying to perform import when $lang->lang_id == 0');
   406 			$db->_die('lang.php - BUG: trying to perform import when $lang->lang_id == 0');
   407     
   407 		
   408     $block = pluginLoader::parse_plugin_blocks($file, 'language');
   408 		$block = pluginLoader::parse_plugin_blocks($file, 'language');
   409     if ( !is_array($block) )
   409 		if ( !is_array($block) )
   410       return false;
   410 			return false;
   411     if ( !isset($block[0]) )
   411 		if ( !isset($block[0]) )
   412       return false;
   412 			return false;
   413     
   413 		
   414     $contents =& $block[0]['value'];
   414 		$contents =& $block[0]['value'];
   415     
   415 		
   416     // Trim off all text before and after the starting and ending braces
   416 		// Trim off all text before and after the starting and ending braces
   417     $contents = enano_trim_json($contents);
   417 		$contents = enano_trim_json($contents);
   418     
   418 		
   419     // Correct syntax to be nice to the json parser
   419 		// Correct syntax to be nice to the json parser
   420     $contents = enano_clean_json($contents);
   420 		$contents = enano_clean_json($contents);
   421     
   421 		
   422     try
   422 		try
   423     {
   423 		{
   424       $langdata = enano_json_decode($contents);
   424 			$langdata = enano_json_decode($contents);
   425     }
   425 		}
   426     catch(Zend_Json_Exception $e)
   426 		catch(Zend_Json_Exception $e)
   427     {
   427 		{
   428       $db->_die('lang.php - Exception caught by JSON parser</p><pre>' . htmlspecialchars(print_r($e, true)) . '</pre><p>');
   428 			$db->_die('lang.php - Exception caught by JSON parser</p><pre>' . htmlspecialchars(print_r($e, true)) . '</pre><p>');
   429       exit;
   429 			exit;
   430     }
   430 		}
   431     
   431 		
   432     if ( !is_array($langdata) )
   432 		if ( !is_array($langdata) )
   433     {
   433 		{
   434       $db->_die('lang.php - invalid or non-well-formed language file');
   434 			$db->_die('lang.php - invalid or non-well-formed language file');
   435     }
   435 		}
   436     
   436 		
   437     // Does the plugin support the current language?
   437 		// Does the plugin support the current language?
   438     if ( isset($langdata[$this->lang_code]) )
   438 		if ( isset($langdata[$this->lang_code]) )
   439     {
   439 		{
   440       // Yes, import that
   440 			// Yes, import that
   441       return $this->import_array($langdata[$this->lang_code]);
   441 			return $this->import_array($langdata[$this->lang_code]);
   442     }
   442 		}
   443     
   443 		
   444     // Just import the first language we run across.
   444 		// Just import the first language we run across.
   445     $supported_langs = array_keys($langdata);
   445 		$supported_langs = array_keys($langdata);
   446     
   446 		
   447     if ( !isset($supported_langs[0]) )
   447 		if ( !isset($supported_langs[0]) )
   448     {
   448 		{
   449       $db->_die('lang.php - plugin has an invalid or corrupt language block');
   449 			$db->_die('lang.php - plugin has an invalid or corrupt language block');
   450     }
   450 		}
   451     
   451 		
   452     $first_lang = $supported_langs[0];
   452 		$first_lang = $supported_langs[0];
   453     
   453 		
   454     return $this->import_array($langdata[$first_lang], false, true);
   454 		return $this->import_array($langdata[$first_lang], false, true);
   455   }
   455 	}
   456   
   456 	
   457   /**
   457 	/**
   458    * Performs the actual import of string data.
   458  	* Performs the actual import of string data.
   459    * @param array Parsed JSON object, should be in the form of an array
   459  	* @param array Parsed JSON object, should be in the form of an array
   460    * @param bool If true, only imports new strings and skips those that already exist. Defaults to false.
   460  	* @param bool If true, only imports new strings and skips those that already exist. Defaults to false.
   461    * @param bool Enable debugging output
   461  	* @param bool Enable debugging output
   462    * @access private
   462  	* @access private
   463    */
   463  	*/
   464   
   464 	
   465   protected function import_array($langdata, $skip_existing = false, $debug = false) 
   465 	protected function import_array($langdata, $skip_existing = false, $debug = false) 
   466   {
   466 	{
   467     global $db, $session, $paths, $template, $plugins; // Common objects
   467 		global $db, $session, $paths, $template, $plugins; // Common objects
   468     
   468 		
   469     if ( !isset($langdata['categories']) || !isset($langdata['strings']) )
   469 		if ( !isset($langdata['categories']) || !isset($langdata['strings']) )
   470       $db->_die('lang.php - language file does not contain the proper items');
   470 			$db->_die('lang.php - language file does not contain the proper items');
   471     
   471 		
   472     if ( $debug )
   472 		if ( $debug )
   473       $br = ( isset($_SERVER['REQUEST_URI']) ) ? '<br />' : '';
   473 			$br = ( isset($_SERVER['REQUEST_URI']) ) ? '<br />' : '';
   474     
   474 		
   475     $insert_list = array();
   475 		$insert_list = array();
   476     $delete_list = array();
   476 		$delete_list = array();
   477     
   477 		
   478     foreach ( $langdata['categories'] as $category )
   478 		foreach ( $langdata['categories'] as $category )
   479     {
   479 		{
   480       if ( isset($langdata['strings'][$category]) )
   480 			if ( isset($langdata['strings'][$category]) )
   481       {
   481 			{
   482         if ( $debug )
   482 				if ( $debug )
   483         {
   483 				{
   484           $desc = ( isset($langdata['strings']['meta'][$category]) ) ? $langdata['strings']['meta'][$category] : $this->get("meta_$category");
   484 					$desc = ( isset($langdata['strings']['meta'][$category]) ) ? $langdata['strings']['meta'][$category] : $this->get("meta_$category");
   485           echo "  Indexing category: $category ({$desc})$br\n";
   485 					echo "  Indexing category: $category ({$desc})$br\n";
   486         }
   486 				}
   487         foreach ( $langdata['strings'][$category] as $string_name => $string_value )
   487 				foreach ( $langdata['strings'][$category] as $string_name => $string_value )
   488         {
   488 				{
   489           // should we skip this string?
   489 					// should we skip this string?
   490           if ( isset($this->strings[$category]) && $skip_existing )
   490 					if ( isset($this->strings[$category]) && $skip_existing )
   491           {
   491 					{
   492             if ( isset($this->strings[$category][$string_name]) )
   492 						if ( isset($this->strings[$category][$string_name]) )
   493             {
   493 						{
   494               // already exists, skip
   494 							// already exists, skip
   495               if ( $debug )
   495 							if ( $debug )
   496                 echo "    Skipping string (already exists): {$category}_{$string_name}$br\n";
   496 								echo "    Skipping string (already exists): {$category}_{$string_name}$br\n";
   497               continue;
   497 							continue;
   498             }
   498 						}
   499           }
   499 					}
   500           $string_name = $db->escape($string_name);
   500 					$string_name = $db->escape($string_name);
   501           $string_value = $db->escape($string_value);
   501 					$string_value = $db->escape($string_value);
   502           $category_name = $db->escape($category);
   502 					$category_name = $db->escape($category);
   503           $insert_list[] = "({$this->lang_id}, '$category_name', '$string_name', '$string_value')";
   503 					$insert_list[] = "({$this->lang_id}, '$category_name', '$string_name', '$string_value')";
   504           $delete_list[] = "( lang_id = {$this->lang_id} AND string_category = '$category_name' AND string_name = '$string_name' )";
   504 					$delete_list[] = "( lang_id = {$this->lang_id} AND string_category = '$category_name' AND string_name = '$string_name' )";
   505         }
   505 				}
   506       }
   506 			}
   507     }
   507 		}
   508     
   508 		
   509     if ( $debug )
   509 		if ( $debug )
   510     {
   510 		{
   511       echo "  Running deletion of old strings...";
   511 			echo "  Running deletion of old strings...";
   512       $start = microtime_float();
   512 			$start = microtime_float();
   513     }
   513 		}
   514     $delete_list = implode(" OR\n  ", $delete_list);
   514 		$delete_list = implode(" OR\n  ", $delete_list);
   515     
   515 		
   516     if ( !empty($delete_list) )
   516 		if ( !empty($delete_list) )
   517     {
   517 		{
   518       $sql = "DELETE FROM " . table_prefix . "language_strings WHERE $delete_list;";
   518 			$sql = "DELETE FROM " . table_prefix . "language_strings WHERE $delete_list;";
   519       
   519 			
   520       // Free some memory
   520 			// Free some memory
   521       unset($delete_list);
   521 			unset($delete_list);
   522       
   522 			
   523       // Run the query
   523 			// Run the query
   524       $q = $db->sql_query($sql);
   524 			$q = $db->sql_query($sql);
   525       if ( !$q )
   525 			if ( !$q )
   526         $db->_die('lang.php - couldn\'t kill off them old strings');
   526 				$db->_die('lang.php - couldn\'t kill off them old strings');
   527     }
   527 		}
   528     
   528 		
   529     if ( $debug )
   529 		if ( $debug )
   530     {
   530 		{
   531       $time = round(microtime_float() - $start, 5);
   531 			$time = round(microtime_float() - $start, 5);
   532       echo "({$time}s)$br\n";
   532 			echo "({$time}s)$br\n";
   533     }
   533 		}
   534     
   534 		
   535     if ( !empty($insert_list) )
   535 		if ( !empty($insert_list) )
   536     {
   536 		{
   537       if ( $debug )
   537 			if ( $debug )
   538       {
   538 			{
   539         echo "  Inserting strings...";
   539 				echo "  Inserting strings...";
   540         $start = microtime_float();
   540 				$start = microtime_float();
   541       }
   541 			}
   542       $insert_list = implode(",\n  ", $insert_list);
   542 			$insert_list = implode(",\n  ", $insert_list);
   543       $sql = "INSERT INTO " . table_prefix . "language_strings(lang_id, string_category, string_name, string_content) VALUES\n  $insert_list;";
   543 			$sql = "INSERT INTO " . table_prefix . "language_strings(lang_id, string_category, string_name, string_content) VALUES\n  $insert_list;";
   544       
   544 			
   545       // Free some memory
   545 			// Free some memory
   546       unset($insert_list);
   546 			unset($insert_list);
   547       
   547 			
   548       // Run the query
   548 			// Run the query
   549       $q = $db->sql_query($sql);
   549 			$q = $db->sql_query($sql);
   550       if ( !$q )
   550 			if ( !$q )
   551         $db->_die('lang.php - couldn\'t insert strings in import()');
   551 				$db->_die('lang.php - couldn\'t insert strings in import()');
   552       
   552 			
   553       if ( $debug )
   553 			if ( $debug )
   554       {
   554 			{
   555         $time = round(microtime_float() - $start, 5);
   555 				$time = round(microtime_float() - $start, 5);
   556         echo "({$time}s)$br\n";
   556 				echo "({$time}s)$br\n";
   557       }
   557 			}
   558     }
   558 		}
   559     
   559 		
   560     // YAY! done!
   560 		// YAY! done!
   561     // This will regenerate the cache file if possible.
   561 		// This will regenerate the cache file if possible.
   562     if ( $debug )
   562 		if ( $debug )
   563       echo "  Regenerating cache file$br\n";
   563 			echo "  Regenerating cache file$br\n";
   564     $this->regen_caches();
   564 		$this->regen_caches();
   565     
   565 		
   566     return true;
   566 		return true;
   567   }
   567 	}
   568   
   568 	
   569   /**
   569 	/**
   570    * Refetches the strings and writes out the cache file.
   570  	* Refetches the strings and writes out the cache file.
   571    */
   571  	*/
   572   
   572 	
   573   function regen_caches($refetch = true)
   573 	function regen_caches($refetch = true)
   574   {
   574 	{
   575     global $db, $session, $paths, $template, $plugins; // Common objects
   575 		global $db, $session, $paths, $template, $plugins; // Common objects
   576     
   576 		
   577     // Refresh the strings in RAM to the latest copies in the DB
   577 		// Refresh the strings in RAM to the latest copies in the DB
   578     if ( $refetch )
   578 		if ( $refetch )
   579       $this->fetch(false);
   579 			$this->fetch(false);
   580     
   580 		
   581     // Load the cache manager
   581 		// Load the cache manager
   582     global $cache;
   582 		global $cache;
   583     
   583 		
   584     // Store it
   584 		// Store it
   585     $cache->store("lang_{$this->lang_id}", $this->strings, -1);
   585 		$cache->store("lang_{$this->lang_id}", $this->strings, -1);
   586     
   586 		
   587     // Update timestamp in database
   587 		// Update timestamp in database
   588     $q = $db->sql_query('UPDATE ' . table_prefix . 'language SET last_changed = ' . time() . ' WHERE lang_id = ' . $this->lang_id . ';');
   588 		$q = $db->sql_query('UPDATE ' . table_prefix . 'language SET last_changed = ' . time() . ' WHERE lang_id = ' . $this->lang_id . ';');
   589     if ( !$q )
   589 		if ( !$q )
   590       $db->_die('lang.php - updating timestamp on language');
   590 			$db->_die('lang.php - updating timestamp on language');
   591     
   591 		
   592     return true;
   592 		return true;
   593   }
   593 	}
   594   
   594 	
   595   /**
   595 	/**
   596    * Calls var_export() on whatever, and returns the function's output.
   596  	* Calls var_export() on whatever, and returns the function's output.
   597    * @param mixed Whatever you want var_exported. Usually an array.
   597  	* @param mixed Whatever you want var_exported. Usually an array.
   598    * @return string
   598  	* @return string
   599    */
   599  	*/
   600   
   600 	
   601   static function var_export_string($val)
   601 	static function var_export_string($val)
   602   {
   602 	{
   603     ob_start();
   603 		ob_start();
   604     var_export($val);
   604 		var_export($val);
   605     $contents = ob_get_contents();
   605 		$contents = ob_get_contents();
   606     ob_end_clean();
   606 		ob_end_clean();
   607     return $contents;
   607 		return $contents;
   608   }
   608 	}
   609   
   609 	
   610   /**
   610 	/**
   611    * Registers a filter, a function that strings can be passed through to change the string somehow (e.g. htmlspecialchars)
   611  	* Registers a filter, a function that strings can be passed through to change the string somehow (e.g. htmlspecialchars)
   612    * @param string Filter name. Lowercase alphanumeric (htmlsafe)
   612  	* @param string Filter name. Lowercase alphanumeric (htmlsafe)
   613    * @param callback Function to call.
   613  	* @param callback Function to call.
   614    * @return bool True on success, false if some error occurred
   614  	* @return bool True on success, false if some error occurred
   615    */
   615  	*/
   616   
   616 	
   617   public function register_filter($filter_name, $filter_function)
   617 	public function register_filter($filter_name, $filter_function)
   618   {
   618 	{
   619     if ( !is_string($filter_function) && !is_array($filter_function) )
   619 		if ( !is_string($filter_function) && !is_array($filter_function) )
   620     {
   620 		{
   621       return false;
   621 			return false;
   622     }
   622 		}
   623     if ( ( is_string($filter_function) && !function_exists($filter_function) ) || ( is_array($filter_function) && !method_exists(@$filter_function[0], @$filter_function[1]) ) )
   623 		if ( ( is_string($filter_function) && !function_exists($filter_function) ) || ( is_array($filter_function) && !method_exists(@$filter_function[0], @$filter_function[1]) ) )
   624     {
   624 		{
   625       return false;
   625 			return false;
   626     }
   626 		}
   627     if ( !preg_match('/^[a-z0-9_]+$/', $filter_name) )
   627 		if ( !preg_match('/^[a-z0-9_]+$/', $filter_name) )
   628     {
   628 		{
   629       return false;
   629 			return false;
   630     }
   630 		}
   631     $this->filters[$filter_name] = $filter_function;
   631 		$this->filters[$filter_name] = $filter_function;
   632     return true;
   632 		return true;
   633   }
   633 	}
   634   
   634 	
   635   /**
   635 	/**
   636    * 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
   636  	* 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
   637    * in the default language. If even then the string can't be found, this function will return what was passed to it.
   637  	* in the default language. If even then the string can't be found, this function will return what was passed to it.
   638    *
   638  	*
   639    * 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
   639  	* 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
   640    * of 'foo' => 'foo substitute'.
   640  	* of 'foo' => 'foo substitute'.
   641    *
   641  	*
   642    * @param string ID of the string to fetch. This will always be in the format of category_stringid.
   642  	* @param string ID of the string to fetch. This will always be in the format of category_stringid.
   643    * @param array Optional. Associative array of substitutions.
   643  	* @param array Optional. Associative array of substitutions.
   644    * @return string
   644  	* @return string
   645    */
   645  	*/
   646   
   646 	
   647   function get($string_id, $substitutions = false)
   647 	function get($string_id, $substitutions = false)
   648   {
   648 	{
   649     if ( !is_array($substitutions) )
   649 		if ( !is_array($substitutions) )
   650       $substitutions = array();
   650 			$substitutions = array();
   651     // if this isn't a valid language string ID, just return the string unprocessed.
   651 		// if this isn't a valid language string ID, just return the string unprocessed.
   652     if ( !preg_match('/^([a-z0-9]+)((_[a-z0-9]+)+)$/', $string_id) )
   652 		if ( !preg_match('/^([a-z0-9]+)((_[a-z0-9]+)+)$/', $string_id) )
   653       return $string_id;
   653 			return $string_id;
   654     return $this->substitute($this->get_uncensored($string_id), $substitutions);
   654 		return $this->substitute($this->get_uncensored($string_id), $substitutions);
   655   }
   655 	}
   656   
   656 	
   657   /**
   657 	/**
   658    * The same as get(), but does not perform any substitution or filtering. Used in get() (of course) and in the admin panel, where
   658  	* The same as get(), but does not perform any substitution or filtering. Used in get() (of course) and in the admin panel, where
   659    * strings are updated only if they were changed.
   659  	* strings are updated only if they were changed.
   660    *
   660  	*
   661    * @param string ID of the string to fetch. This will always be in the format of category_stringid.
   661  	* @param string ID of the string to fetch. This will always be in the format of category_stringid.
   662    * @param array Optional. Associative array of substitutions.
   662  	* @param array Optional. Associative array of substitutions.
   663    * @return string
   663  	* @return string
   664    */
   664  	*/
   665   
   665 	
   666   function get_uncensored($string_id, $substitutions = false)
   666 	function get_uncensored($string_id, $substitutions = false)
   667   {
   667 	{
   668     global $db, $session, $paths, $template, $plugins; // Common objects
   668 		global $db, $session, $paths, $template, $plugins; // Common objects
   669     
   669 		
   670     // Extract the category and string ID
   670 		// Extract the category and string ID
   671     $category = substr($string_id, 0, ( strpos($string_id, '_') ));
   671 		$category = substr($string_id, 0, ( strpos($string_id, '_') ));
   672     $string_name = substr($string_id, ( strpos($string_id, '_') + 1 ));
   672 		$string_name = substr($string_id, ( strpos($string_id, '_') + 1 ));
   673     $found = false;
   673 		$found = false;
   674     if ( isset($this->strings[$category]) && isset($this->strings[$category][$string_name]) )
   674 		if ( isset($this->strings[$category]) && isset($this->strings[$category][$string_name]) )
   675     {
   675 		{
   676       $found = true;
   676 			$found = true;
   677       $string = $this->strings[$category][$string_name];
   677 			$string = $this->strings[$category][$string_name];
   678     }
   678 		}
   679     if ( !$found )
   679 		if ( !$found )
   680     {
   680 		{
   681       // Ehh, the string wasn't found. Rerun fetch() and try again.
   681 			// Ehh, the string wasn't found. Rerun fetch() and try again.
   682       // Or if it's the installer, no use in refetching, so just fail.
   682 			// Or if it's the installer, no use in refetching, so just fail.
   683       if ( defined('IN_ENANO_INSTALL') || ( defined('ENANO_INSTALLED') && !@$db->_conn ) )
   683 			if ( defined('IN_ENANO_INSTALL') || ( defined('ENANO_INSTALLED') && !@$db->_conn ) )
   684       {
   684 			{
   685         return $string_id;
   685 				return $string_id;
   686       }
   686 			}
   687       $this->fetch();
   687 			$this->fetch();
   688       profiler_log('Language(' . $this->lang_code . '): refetched due to missing string: ' . $string_id);
   688 			profiler_log('Language(' . $this->lang_code . '): refetched due to missing string: ' . $string_id);
   689       if ( isset($this->strings[$category]) && isset($this->strings[$category][$string_name]) )
   689 			if ( isset($this->strings[$category]) && isset($this->strings[$category][$string_name]) )
   690       {
   690 			{
   691         $found = true;
   691 				$found = true;
   692         $string = $this->strings[$category][$string_name];
   692 				$string = $this->strings[$category][$string_name];
   693       }
   693 			}
   694       if ( !$found )
   694 			if ( !$found )
   695       {
   695 			{
   696         // STILL not found. Check the default language.
   696 				// STILL not found. Check the default language.
   697         $lang_default = ( $x = getConfig('default_language') ) ? intval($x) : $this->lang_id;
   697 				$lang_default = ( $x = getConfig('default_language') ) ? intval($x) : $this->lang_id;
   698         if ( $lang_default != $this->lang_id )
   698 				if ( $lang_default != $this->lang_id )
   699         {
   699 				{
   700           if ( !is_object($this->default) )
   700 					if ( !is_object($this->default) )
   701             $this->default = new Language($lang_default);
   701 						$this->default = new Language($lang_default);
   702           return $this->default->get_uncensored($string_id);
   702 					return $this->default->get_uncensored($string_id);
   703         }
   703 				}
   704       }
   704 			}
   705     }
   705 		}
   706     if ( !$found )
   706 		if ( !$found )
   707     {
   707 		{
   708       // Alright, it's nowhere. Return the input, grumble grumble...
   708 			// Alright, it's nowhere. Return the input, grumble grumble...
   709       return $string_id;
   709 			return $string_id;
   710     }
   710 		}
   711     // Found it!
   711 		// Found it!
   712     return $string;
   712 		return $string;
   713   }
   713 	}
   714   
   714 	
   715   /**
   715 	/**
   716    * Processes substitutions.
   716  	* Processes substitutions.
   717    * @param string
   717  	* @param string
   718    * @param array
   718  	* @param array
   719    * @return string
   719  	* @return string
   720    */
   720  	*/
   721   
   721 	
   722   function substitute($string, $subs)
   722 	function substitute($string, $subs)
   723   {
   723 	{
   724     preg_match_all('/%this\.([a-z0-9_]+)((?:\|(?:[a-z0-9_]+))*)%/', $string, $matches);
   724 		preg_match_all('/%this\.([a-z0-9_]+)((?:\|(?:[a-z0-9_]+))*)%/', $string, $matches);
   725     if ( count($matches[0]) > 0 )
   725 		if ( count($matches[0]) > 0 )
   726     {
   726 		{
   727       foreach ( $matches[1] as $i => $string_id )
   727 			foreach ( $matches[1] as $i => $string_id )
   728       {
   728 			{
   729         $result = $this->get($string_id);
   729 				$result = $this->get($string_id);
   730         $string = str_replace($matches[0][$i], $this->process_filters($result, $matches[2][$i]), $string);
   730 				$string = str_replace($matches[0][$i], $this->process_filters($result, $matches[2][$i]), $string);
   731       }
   731 			}
   732     }
   732 		}
   733     preg_match_all('/%config\.([a-z0-9_]+)((?:\|(?:[a-z0-9_]+))*)%/', $string, $matches);
   733 		preg_match_all('/%config\.([a-z0-9_]+)((?:\|(?:[a-z0-9_]+))*)%/', $string, $matches);
   734     if ( count($matches[0]) > 0 )
   734 		if ( count($matches[0]) > 0 )
   735     {
   735 		{
   736       foreach ( $matches[1] as $i => $string_id )
   736 			foreach ( $matches[1] as $i => $string_id )
   737       {
   737 			{
   738         $result = getConfig($string_id, '');
   738 				$result = getConfig($string_id, '');
   739         $string = str_replace($matches[0][$i], $this->process_filters($result, $matches[2][$i]), $string);
   739 				$string = str_replace($matches[0][$i], $this->process_filters($result, $matches[2][$i]), $string);
   740       }
   740 			}
   741     }
   741 		}
   742     preg_match_all('/%([a-z0-9_]+)((?:\|(?:[a-z0-9_]+))*)%/', $string, $matches);
   742 		preg_match_all('/%([a-z0-9_]+)((?:\|(?:[a-z0-9_]+))*)%/', $string, $matches);
   743     if ( count($matches[0]) > 0 )
   743 		if ( count($matches[0]) > 0 )
   744     {
   744 		{
   745       foreach ( $matches[1] as $i => $string_id )
   745 			foreach ( $matches[1] as $i => $string_id )
   746       {
   746 			{
   747         if ( isset($subs[$string_id]) )
   747 				if ( isset($subs[$string_id]) )
   748         {
   748 				{
   749           $string = str_replace($matches[0][$i], $this->process_filters($subs[$string_id], $matches[2][$i]), $string);
   749 					$string = str_replace($matches[0][$i], $this->process_filters($subs[$string_id], $matches[2][$i]), $string);
   750         }
   750 				}
   751       }
   751 			}
   752     }
   752 		}
   753     return ( $this->debug ) ? "$string*" : $string;
   753 		return ( $this->debug ) ? "$string*" : $string;
   754   }
   754 	}
   755   
   755 	
   756   /**
   756 	/**
   757    * Processes filters to a language string.
   757  	* Processes filters to a language string.
   758    * @param string Unprocessed string
   758  	* @param string Unprocessed string
   759    * @param string Filter list (format: |filter1|filter2|filter3, initial pipe is important); can also be an array if you so desire
   759  	* @param string Filter list (format: |filter1|filter2|filter3, initial pipe is important); can also be an array if you so desire
   760    * @return string
   760  	* @return string
   761    */
   761  	*/
   762   
   762 	
   763   function process_filters($string, $filters)
   763 	function process_filters($string, $filters)
   764   {
   764 	{
   765     if ( !empty($filters) )
   765 		if ( !empty($filters) )
   766     {
   766 		{
   767       $filters = trim($filters, '|');
   767 			$filters = trim($filters, '|');
   768       $filters = explode('|', $filters);
   768 			$filters = explode('|', $filters);
   769       foreach ( $filters as $filter )
   769 			foreach ( $filters as $filter )
   770       {
   770 			{
   771         if ( isset($this->filters[$filter]) )
   771 				if ( isset($this->filters[$filter]) )
   772         {
   772 				{
   773           $result = call_user_func($this->filters[$filter], $string);
   773 					$result = call_user_func($this->filters[$filter], $string);
   774           if ( is_string($result) )
   774 					if ( is_string($result) )
   775           {
   775 					{
   776             $string = $result;
   776 						$string = $result;
   777           }
   777 					}
   778         }
   778 				}
   779       }
   779 			}
   780     }
   780 		}
   781     return $string;
   781 		return $string;
   782   }
   782 	}
   783   
   783 	
   784 } // class Language
   784 } // class Language
   785 
   785 
   786 ?>
   786 ?>