205
+ − 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
209
+ − 84
$lang_default = ( $x = getConfig('default_language') ) ? intval($x) : 'def';
205
+ − 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.
209
+ − 375
// if ( is_array($substitutions) )
+ − 376
// die('<pre>' . print_r($substitutions, true) . '</pre>');
205
+ − 377
if ( !is_array($substitutions) )
+ − 378
$substitutions = array();
+ − 379
return $this->substitute($string, $substitutions);
+ − 380
}
+ − 381
+ − 382
/**
+ − 383
* Processes substitutions.
+ − 384
* @param string
+ − 385
* @param array
+ − 386
* @return string
+ − 387
*/
+ − 388
+ − 389
function substitute($string, $subs)
+ − 390
{
+ − 391
preg_match_all('/%this\.([a-z0-9_]+)%/', $string, $matches);
+ − 392
if ( count($matches[0]) > 0 )
+ − 393
{
+ − 394
foreach ( $matches[1] as $i => $string_id )
+ − 395
{
+ − 396
$result = $this->get($string_id);
+ − 397
$string = str_replace($matches[0][$i], $result, $string);
+ − 398
}
+ − 399
}
209
+ − 400
preg_match_all('/%config\.([a-z0-9_]+)%/', $string, $matches);
+ − 401
if ( count($matches[0]) > 0 )
+ − 402
{
+ − 403
foreach ( $matches[1] as $i => $string_id )
+ − 404
{
+ − 405
$result = getConfig($string_id);
+ − 406
$string = str_replace($matches[0][$i], $result, $string);
+ − 407
}
+ − 408
}
205
+ − 409
foreach ( $subs as $key => $value )
+ − 410
{
209
+ − 411
$subs[$key] = strval($value);
+ − 412
$string = str_replace("%{$key}%", "{$subs[$key]}", $string);
205
+ − 413
}
209
+ − 414
return "[L] $string";
205
+ − 415
}
+ − 416
+ − 417
} // class Language
+ − 418
+ − 419
?>