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;
210
2b283402e4e4
Added language export to JSON page and localization for Javascript using $lang.get(). Localized AJAX login interface.
Dan
diff
changeset
+ − 39
2b283402e4e4
Added language export to JSON page and localization for Javascript using $lang.get(). Localized AJAX login interface.
Dan
diff
changeset
+ − 40
/**
2b283402e4e4
Added language export to JSON page and localization for Javascript using $lang.get(). Localized AJAX login interface.
Dan
diff
changeset
+ − 41
* Used to track when a language was last changed, to allow browsers to cache language data
2b283402e4e4
Added language export to JSON page and localization for Javascript using $lang.get(). Localized AJAX login interface.
Dan
diff
changeset
+ − 42
* @var int
2b283402e4e4
Added language export to JSON page and localization for Javascript using $lang.get(). Localized AJAX login interface.
Dan
diff
changeset
+ − 43
*/
2b283402e4e4
Added language export to JSON page and localization for Javascript using $lang.get(). Localized AJAX login interface.
Dan
diff
changeset
+ − 44
2b283402e4e4
Added language export to JSON page and localization for Javascript using $lang.get(). Localized AJAX login interface.
Dan
diff
changeset
+ − 45
var $lang_timestamp;
205
+ − 46
+ − 47
/**
+ − 48
* Will be an object that holds an instance of the class configured with the site's default language. Only instanciated when needed.
+ − 49
* @var object
+ − 50
*/
+ − 51
+ − 52
var $default;
+ − 53
+ − 54
/**
+ − 55
* The list of loaded strings.
+ − 56
* @var array
+ − 57
* @access private
+ − 58
*/
+ − 59
+ − 60
var $strings = array();
+ − 61
+ − 62
/**
+ − 63
* Constructor.
+ − 64
* @param int|string Language ID or code to load.
+ − 65
*/
+ − 66
+ − 67
function __construct($lang)
+ − 68
{
+ − 69
global $db, $session, $paths, $template, $plugins; // Common objects
+ − 70
+ − 71
if ( defined('IN_ENANO_INSTALL') )
+ − 72
{
+ − 73
// special case for the Enano installer: it will load its own strings from a JSON file and just use this API for fetching and templatizing them.
243
+ − 74
$this->lang_id = 1;
+ − 75
$this->lang_code = $lang;
205
+ − 76
return true;
+ − 77
}
+ − 78
if ( is_string($lang) )
+ − 79
{
+ − 80
$sql_col = 'lang_code="' . $db->escape($lang) . '"';
+ − 81
}
+ − 82
else if ( is_int($lang) )
+ − 83
{
+ − 84
$sql_col = 'lang_id=' . $lang . '';
+ − 85
}
+ − 86
else
+ − 87
{
+ − 88
$db->_die('lang.php - attempting to pass invalid value to constructor');
+ − 89
}
+ − 90
239
0f1b353570a7
Fix a comparison logic SQL error in lang.php; fix attempt to call mysql_real_escape_string() in install without a working DB connection
Dan
diff
changeset
+ − 91
$lang_default = ( $x = getConfig('default_language') ) ? intval($x) : '\'def\'';
210
2b283402e4e4
Added language export to JSON page and localization for Javascript using $lang.get(). Localized AJAX login interface.
Dan
diff
changeset
+ − 92
$q = $db->sql_query("SELECT lang_id, lang_code, last_changed, ( lang_id = $lang_default ) AS is_default FROM " . table_prefix . "language WHERE $sql_col OR lang_id = $lang_default ORDER BY is_default DESC LIMIT 1;");
205
+ − 93
+ − 94
if ( !$q )
+ − 95
$db->_die('lang.php - main select query');
+ − 96
+ − 97
if ( $db->numrows() < 1 )
+ − 98
$db->_die('lang.php - There are no languages installed');
+ − 99
+ − 100
$row = $db->fetchrow();
+ − 101
+ − 102
$this->lang_id = intval( $row['lang_id'] );
+ − 103
$this->lang_code = $row['lang_code'];
210
2b283402e4e4
Added language export to JSON page and localization for Javascript using $lang.get(). Localized AJAX login interface.
Dan
diff
changeset
+ − 104
$this->lang_timestamp = $row['last_changed'];
205
+ − 105
}
+ − 106
+ − 107
/**
+ − 108
* PHP 4 constructor.
+ − 109
* @param int|string Language ID or code to load.
+ − 110
*/
+ − 111
+ − 112
function Language($lang)
+ − 113
{
+ − 114
$this->__construct($lang);
+ − 115
}
+ − 116
+ − 117
/**
+ − 118
* Fetches language strings from the database, or a cache file if it's available.
+ − 119
* @param bool If true (default), allows the cache to be used.
+ − 120
*/
+ − 121
+ − 122
function fetch($allow_cache = true)
+ − 123
{
+ − 124
global $db, $session, $paths, $template, $plugins; // Common objects
+ − 125
+ − 126
$lang_file = ENANO_ROOT . "/cache/lang_{$this->lang_id}.php";
+ − 127
// Attempt to load the strings from a cache file
+ − 128
if ( file_exists($lang_file) && $allow_cache )
+ − 129
{
+ − 130
// Yay! found it
+ − 131
$this->load_cache_file($lang_file);
+ − 132
}
+ − 133
else
+ − 134
{
+ − 135
// No cache file - select and retrieve from the database
+ − 136
$q = $db->sql_unbuffered_query("SELECT string_category, string_name, string_content FROM " . table_prefix . "language_strings WHERE lang_id = {$this->lang_id};");
+ − 137
if ( !$q )
+ − 138
$db->_die('lang.php - selecting language string data');
+ − 139
if ( $row = $db->fetchrow() )
+ − 140
{
+ − 141
$strings = array();
+ − 142
do
+ − 143
{
+ − 144
$cat =& $row['string_category'];
+ − 145
if ( !is_array($strings[$cat]) )
+ − 146
{
+ − 147
$strings[$cat] = array();
+ − 148
}
+ − 149
$strings[$cat][ $row['string_name'] ] = $row['string_content'];
+ − 150
}
+ − 151
while ( $row = $db->fetchrow() );
+ − 152
// all done fetching
+ − 153
$this->merge($strings);
+ − 154
}
+ − 155
else
+ − 156
{
241
c671f3bb8aed
Trying to get lang import to work in the installer; it's not working ATM - cache file is generated with lang_id = 0. Syncing to Nighthawk.
Dan
diff
changeset
+ − 157
if ( !defined('ENANO_ALLOW_LOAD_NOLANG') )
c671f3bb8aed
Trying to get lang import to work in the installer; it's not working ATM - cache file is generated with lang_id = 0. Syncing to Nighthawk.
Dan
diff
changeset
+ − 158
$db->_die('lang.php - No strings for language ' . $this->lang_code);
205
+ − 159
}
+ − 160
}
+ − 161
}
+ − 162
+ − 163
/**
+ − 164
* Loads a file from the disk cache (treated as PHP) and merges it into RAM.
+ − 165
* @param string File to load
+ − 166
*/
+ − 167
+ − 168
function load_cache_file($file)
+ − 169
{
+ − 170
global $db, $session, $paths, $template, $plugins; // Common objects
+ − 171
+ − 172
// We're using eval() here because it makes handling scope easier.
+ − 173
+ − 174
if ( !file_exists($file) )
+ − 175
$db->_die('lang.php - requested cache file doesn\'t exist');
+ − 176
+ − 177
$contents = file_get_contents($file);
+ − 178
$contents = preg_replace('/([\s]*)<\?php/', '', $contents);
+ − 179
+ − 180
@eval($contents);
+ − 181
+ − 182
if ( !isset($lang_cache) || ( isset($lang_cache) && !is_array($lang_cache) ) )
+ − 183
$db->_die('lang.php - the cache file is invalid (didn\'t set $lang_cache as an array)');
+ − 184
+ − 185
$this->merge($lang_cache);
+ − 186
}
+ − 187
+ − 188
/**
243
+ − 189
* Loads a JSON language file and parses the strings into RAM. Will use the cache if possible, but stays far away from the database,
+ − 190
* which we assume doesn't exist yet.
+ − 191
*/
+ − 192
+ − 193
function load_file($file)
+ − 194
{
+ − 195
global $db, $session, $paths, $template, $plugins; // Common objects
+ − 196
+ − 197
if ( !file_exists($file) )
+ − 198
$db->_die('lang.php - requested JSON file doesn\'t exist');
+ − 199
+ − 200
$contents = trim(@file_get_contents($file));
+ − 201
if ( empty($contents) )
+ − 202
$db->_die('lang.php - empty language file...');
+ − 203
+ − 204
// Trim off all text before and after the starting and ending braces
+ − 205
$contents = preg_replace('/^([^{]+)\{/', '{', $contents);
+ − 206
$contents = preg_replace('/\}([^}]+)$/', '}', $contents);
+ − 207
$contents = trim($contents);
+ − 208
+ − 209
if ( empty($contents) )
+ − 210
$db->_die('lang.php - no meat to the language file...');
+ − 211
+ − 212
$checksum = md5($contents);
+ − 213
if ( file_exists("./cache/lang_json_{$checksum}.php") )
+ − 214
{
+ − 215
$this->load_cache_file("./cache/lang_json_{$checksum}.php");
+ − 216
}
+ − 217
else
+ − 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
$this->merge($langdata['strings']);
+ − 229
+ − 230
$lang_file = "./cache/lang_json_{$checksum}.php";
+ − 231
+ − 232
$handle = @fopen($lang_file, 'w');
+ − 233
if ( !$handle )
+ − 234
// Couldn't open the file. Silently fail and let the strings come from RAM.
+ − 235
return false;
+ − 236
+ − 237
// The file's open, that means we should be good.
+ − 238
fwrite($handle, '<?php
+ − 239
// This file was generated automatically by Enano. You should not edit this file because any changes you make
+ − 240
// to it will not be visible in the ACP and all changes will be lost upon any changes to strings in the admin panel.
+ − 241
+ − 242
$lang_cache = ');
+ − 243
+ − 244
$exported = $this->var_export_string($this->strings);
+ − 245
if ( empty($exported) )
+ − 246
// Ehh, that's not good
+ − 247
$db->_die('lang.php - load_file(): var_export_string() failed');
+ − 248
+ − 249
fwrite($handle, $exported . '; ?>');
+ − 250
+ − 251
// Clean up
+ − 252
unset($exported, $langdata);
+ − 253
+ − 254
// Done =)
+ − 255
fclose($handle);
+ − 256
}
+ − 257
}
+ − 258
+ − 259
/**
205
+ − 260
* Merges a standard language assoc array ($arr[cat][stringid]) with the master in RAM.
+ − 261
* @param array
+ − 262
*/
+ − 263
+ − 264
function merge($strings)
+ − 265
{
+ − 266
// This is stupidly simple.
+ − 267
foreach ( $strings as $cat_id => $contents )
+ − 268
{
243
+ − 269
if ( !isset($this->strings[$cat_id]) || ( isset($this->strings[$cat_id]) && !is_array($this->strings[$cat_id]) ) )
205
+ − 270
$this->strings[$cat_id] = array();
+ − 271
foreach ( $contents as $string_id => $string )
+ − 272
{
+ − 273
$this->strings[$cat_id][$string_id] = $string;
+ − 274
}
+ − 275
}
+ − 276
}
+ − 277
+ − 278
/**
+ − 279
* Imports a JSON-format language file into the database and merges with current strings.
+ − 280
* @param string Path to the JSON file to load
+ − 281
*/
+ − 282
+ − 283
function import($file)
+ − 284
{
+ − 285
global $db, $session, $paths, $template, $plugins; // Common objects
+ − 286
+ − 287
if ( !file_exists($file) )
+ − 288
$db->_die('lang.php - can\'t import language file: string file doesn\'t exist');
+ − 289
241
c671f3bb8aed
Trying to get lang import to work in the installer; it's not working ATM - cache file is generated with lang_id = 0. Syncing to Nighthawk.
Dan
diff
changeset
+ − 290
if ( $this->lang_id == 0 )
c671f3bb8aed
Trying to get lang import to work in the installer; it's not working ATM - cache file is generated with lang_id = 0. Syncing to Nighthawk.
Dan
diff
changeset
+ − 291
$db->_die('lang.php - BUG: trying to perform import when $lang->lang_id == 0');
c671f3bb8aed
Trying to get lang import to work in the installer; it's not working ATM - cache file is generated with lang_id = 0. Syncing to Nighthawk.
Dan
diff
changeset
+ − 292
205
+ − 293
$contents = trim(@file_get_contents($file));
+ − 294
+ − 295
if ( empty($contents) )
+ − 296
$db->_die('lang.php - can\'t load the contents of the language file');
+ − 297
+ − 298
// Trim off all text before and after the starting and ending braces
+ − 299
$contents = preg_replace('/^([^{]+)\{/', '{', $contents);
+ − 300
$contents = preg_replace('/\}([^}]+)$/', '}', $contents);
+ − 301
+ − 302
$json = new Services_JSON(SERVICES_JSON_LOOSE_TYPE);
+ − 303
$langdata = $json->decode($contents);
+ − 304
+ − 305
if ( !is_array($langdata) )
+ − 306
$db->_die('lang.php - invalid language file');
+ − 307
+ − 308
if ( !isset($langdata['categories']) || !isset($langdata['strings']) )
+ − 309
$db->_die('lang.php - language file does not contain the proper items');
+ − 310
+ − 311
$insert_list = array();
+ − 312
$delete_list = array();
+ − 313
+ − 314
foreach ( $langdata['categories'] as $category )
+ − 315
{
+ − 316
if ( isset($langdata['strings'][$category]) )
+ − 317
{
+ − 318
foreach ( $langdata['strings'][$category] as $string_name => $string_value )
+ − 319
{
+ − 320
$string_name = $db->escape($string_name);
+ − 321
$string_value = $db->escape($string_value);
+ − 322
$category_name = $db->escape($category);
+ − 323
$insert_list[] = "({$this->lang_id}, '$category_name', '$string_name', '$string_value')";
+ − 324
$delete_list[] = "( lang_id = {$this->lang_id} AND string_category = '$category_name' AND string_name = '$string_name' )";
+ − 325
}
+ − 326
}
+ − 327
}
+ − 328
+ − 329
$delete_list = implode(" OR\n ", $delete_list);
+ − 330
$sql = "DELETE FROM " . table_prefix . "language_strings WHERE $delete_list;";
+ − 331
+ − 332
// Free some memory
+ − 333
unset($delete_list);
+ − 334
+ − 335
// Run the query
+ − 336
$q = $db->sql_query($sql);
+ − 337
if ( !$q )
+ − 338
$db->_die('lang.php - couldn\'t kill off them old strings');
+ − 339
+ − 340
$insert_list = implode(",\n ", $insert_list);
+ − 341
$sql = "INSERT INTO " . table_prefix . "language_strings(lang_id, string_category, string_name, string_content) VALUES\n $insert_list;";
+ − 342
+ − 343
// Free some memory
+ − 344
unset($insert_list);
+ − 345
+ − 346
// Run the query
+ − 347
$q = $db->sql_query($sql);
+ − 348
if ( !$q )
+ − 349
$db->_die('lang.php - couldn\'t insert strings in import()');
+ − 350
+ − 351
// YAY! done!
+ − 352
// This will regenerate the cache file if possible.
+ − 353
$this->regen_caches();
+ − 354
}
+ − 355
+ − 356
/**
+ − 357
* Refetches the strings and writes out the cache file.
+ − 358
*/
+ − 359
+ − 360
function regen_caches()
+ − 361
{
+ − 362
global $db, $session, $paths, $template, $plugins; // Common objects
+ − 363
+ − 364
$lang_file = ENANO_ROOT . "/cache/lang_{$this->lang_id}.php";
+ − 365
+ − 366
// Refresh the strings in RAM to the latest copies in the DB
+ − 367
$this->fetch(false);
+ − 368
+ − 369
$handle = @fopen($lang_file, 'w');
+ − 370
if ( !$handle )
+ − 371
// Couldn't open the file. Silently fail and let the strings come from the database.
+ − 372
return false;
+ − 373
+ − 374
// The file's open, that means we should be good.
+ − 375
fwrite($handle, '<?php
+ − 376
// This file was generated automatically by Enano. You should not edit this file because any changes you make
+ − 377
// to it will not be visible in the ACP and all changes will be lost upon any changes to strings in the admin panel.
+ − 378
+ − 379
$lang_cache = ');
+ − 380
+ − 381
$exported = $this->var_export_string($this->strings);
+ − 382
if ( empty($exported) )
+ − 383
// Ehh, that's not good
+ − 384
$db->_die('lang.php - var_export_string() failed');
+ − 385
+ − 386
fwrite($handle, $exported . '; ?>');
+ − 387
210
2b283402e4e4
Added language export to JSON page and localization for Javascript using $lang.get(). Localized AJAX login interface.
Dan
diff
changeset
+ − 388
// Update timestamp in database
2b283402e4e4
Added language export to JSON page and localization for Javascript using $lang.get(). Localized AJAX login interface.
Dan
diff
changeset
+ − 389
$q = $db->sql_query('UPDATE ' . table_prefix . 'language SET last_changed = ' . time() . ' WHERE lang_id = ' . $this->lang_id . ';');
2b283402e4e4
Added language export to JSON page and localization for Javascript using $lang.get(). Localized AJAX login interface.
Dan
diff
changeset
+ − 390
if ( !$q )
2b283402e4e4
Added language export to JSON page and localization for Javascript using $lang.get(). Localized AJAX login interface.
Dan
diff
changeset
+ − 391
$db->_die('lang.php - updating timestamp on language');
2b283402e4e4
Added language export to JSON page and localization for Javascript using $lang.get(). Localized AJAX login interface.
Dan
diff
changeset
+ − 392
205
+ − 393
// Done =)
+ − 394
fclose($handle);
+ − 395
}
+ − 396
+ − 397
/**
+ − 398
* Calls var_export() on whatever, and returns the function's output.
+ − 399
* @param mixed Whatever you want var_exported. Usually an array.
+ − 400
* @return string
+ − 401
*/
+ − 402
+ − 403
function var_export_string($val)
+ − 404
{
+ − 405
ob_start();
+ − 406
var_export($val);
+ − 407
$contents = ob_get_contents();
+ − 408
ob_end_clean();
+ − 409
return $contents;
+ − 410
}
+ − 411
+ − 412
/**
+ − 413
* 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
+ − 414
* in the default language. If even then the string can't be found, this function will return what was passed to it.
+ − 415
*
+ − 416
* 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
+ − 417
* of 'foo' => 'foo substitute'.
+ − 418
*
+ − 419
* @param string ID of the string to fetch. This will always be in the format of category_stringid.
+ − 420
* @param array Optional. Associative array of substitutions.
+ − 421
* @return string
+ − 422
*/
+ − 423
+ − 424
function get($string_id, $substitutions = false)
+ − 425
{
+ − 426
// Extract the category and string ID
+ − 427
$category = substr($string_id, 0, ( strpos($string_id, '_') ));
+ − 428
$string_name = substr($string_id, ( strpos($string_id, '_') + 1 ));
+ − 429
$found = false;
+ − 430
if ( isset($this->strings[$category]) && isset($this->strings[$category][$string_name]) )
+ − 431
{
+ − 432
$found = true;
+ − 433
$string = $this->strings[$category][$string_name];
+ − 434
}
+ − 435
if ( !$found )
+ − 436
{
+ − 437
// Ehh, the string wasn't found. Rerun fetch() and try again.
243
+ − 438
if ( defined('IN_ENANO_INSTALL') )
+ − 439
{
+ − 440
return $string_id;
+ − 441
}
205
+ − 442
$this->fetch();
+ − 443
if ( isset($this->strings[$category]) && isset($this->strings[$category][$string_name]) )
+ − 444
{
+ − 445
$found = true;
+ − 446
$string = $this->strings[$category][$string_name];
+ − 447
}
+ − 448
if ( !$found )
+ − 449
{
+ − 450
// STILL not found. Check the default language.
+ − 451
$lang_default = ( $x = getConfig('default_language') ) ? intval($x) : $this->lang_id;
+ − 452
if ( $lang_default != $this->lang_id )
+ − 453
{
+ − 454
if ( !is_object($this->default) )
+ − 455
$this->default = new Language($lang_default);
+ − 456
return $this->default->get($string_id, $substitutions);
+ − 457
}
+ − 458
}
+ − 459
}
+ − 460
if ( !$found )
+ − 461
{
+ − 462
// Alright, it's nowhere. Return the input, grumble grumble...
+ − 463
return $string_id;
+ − 464
}
+ − 465
// Found it!
+ − 466
// Perform substitutions.
209
+ − 467
// if ( is_array($substitutions) )
+ − 468
// die('<pre>' . print_r($substitutions, true) . '</pre>');
205
+ − 469
if ( !is_array($substitutions) )
+ − 470
$substitutions = array();
+ − 471
return $this->substitute($string, $substitutions);
+ − 472
}
+ − 473
+ − 474
/**
+ − 475
* Processes substitutions.
+ − 476
* @param string
+ − 477
* @param array
+ − 478
* @return string
+ − 479
*/
+ − 480
+ − 481
function substitute($string, $subs)
+ − 482
{
+ − 483
preg_match_all('/%this\.([a-z0-9_]+)%/', $string, $matches);
+ − 484
if ( count($matches[0]) > 0 )
+ − 485
{
+ − 486
foreach ( $matches[1] as $i => $string_id )
+ − 487
{
+ − 488
$result = $this->get($string_id);
+ − 489
$string = str_replace($matches[0][$i], $result, $string);
+ − 490
}
+ − 491
}
209
+ − 492
preg_match_all('/%config\.([a-z0-9_]+)%/', $string, $matches);
+ − 493
if ( count($matches[0]) > 0 )
+ − 494
{
+ − 495
foreach ( $matches[1] as $i => $string_id )
+ − 496
{
+ − 497
$result = getConfig($string_id);
+ − 498
$string = str_replace($matches[0][$i], $result, $string);
+ − 499
}
+ − 500
}
205
+ − 501
foreach ( $subs as $key => $value )
+ − 502
{
209
+ − 503
$subs[$key] = strval($value);
+ − 504
$string = str_replace("%{$key}%", "{$subs[$key]}", $string);
205
+ − 505
}
243
+ − 506
return "{$string}*";
205
+ − 507
}
+ − 508
+ − 509
} // class Language
+ − 510
+ − 511
?>