PageRenderTime 49ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/includes/lang.php

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