1<?php 2/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ 3 4/** 5 * Contains the Translation2 base class 6 * 7 * PHP versions 4 and 5 8 * 9 * LICENSE: Redistribution and use in source and binary forms, with or without 10 * modification, are permitted provided that the following conditions are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 3. The name of the author may not be used to endorse or promote products 17 * derived from this software without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR IMPLIED 20 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 21 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 22 * IN NO EVENT SHALL THE FREEBSD PROJECT OR CONTRIBUTORS BE LIABLE FOR ANY 23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 * 30 * @category Internationalization 31 * @package Translation2 32 * @author Lorenzo Alberton <l.alberton@quipo.it> 33 * @copyright 2004-2008 Lorenzo Alberton 34 * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) 35 * @version CVS: $Id: Translation2.php 268999 2008-11-14 16:18:50Z quipo $ 36 * @link http://pear.php.net/package/Translation2 37 */ 38 39/** 40 * require PEAR base class 41 */ 42require_once 'PEAR.php'; 43 44/** 45 * Allows redefinition of the default pageID. 46 * This constant is needed to allow both NULL and EMPTY pageID values 47 * and to have them match 48 */ 49if (!defined('TRANSLATION2_DEFAULT_PAGEID')) { 50 define('TRANSLATION2_DEFAULT_PAGEID', 'translation2_default_pageID'); 51} 52/** 53 * Class Error codes 54 */ 55define('TRANSLATION2_ERROR', -1); 56define('TRANSLATION2_ERROR_METHOD_NOT_SUPPORTED', -2); 57define('TRANSLATION2_ERROR_CANNOT_CONNECT', -3); 58define('TRANSLATION2_ERROR_CANNOT_FIND_FILE', -4); 59define('TRANSLATION2_ERROR_DOMAIN_NOT_SET', -5); 60define('TRANSLATION2_ERROR_INVALID_PATH', -6); 61define('TRANSLATION2_ERROR_CANNOT_CREATE_DIR', -7); 62define('TRANSLATION2_ERROR_CANNOT_WRITE_FILE', -8); 63define('TRANSLATION2_ERROR_UNKNOWN_LANG', -9); 64define('TRANSLATION2_ERROR_ENCODING_CONVERSION', -10); 65define('TRANSLATION2_ERROR_UNSUPPORTED', -11); 66 67/** 68 * Translation2 base class 69 * 70 * This class provides an easy way to retrieve all the strings 71 * for a multilingual site or application from a data source 72 * (i.e. a db, an xml file or a gettext file). 73 * 74 * @category Internationalization 75 * @package Translation2 76 * @author Lorenzo Alberton <l.alberton@quipo.it> 77 * @copyright 2004-2008 Lorenzo Alberton 78 * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) 79 * @link http://pear.php.net/package/Translation2 80 */ 81class Translation2 82{ 83 // {{{ class vars 84 85 /** 86 * Storage object 87 * @var object 88 * @access protected 89 */ 90 var $storage = ''; 91 92 /** 93 * Class options 94 * @var array 95 */ 96 var $options = array(); 97 98 /** 99 * Default lang 100 * @var array 101 * @access protected 102 */ 103 var $lang = array(); 104 105 /** 106 * Current pageID 107 * @var string 108 * @access protected 109 */ 110 var $currentPageID = null; 111 112 /** 113 * Array of parameters for the adapter class 114 * @var array 115 * @access protected 116 */ 117 var $params = array(); 118 119 // }}} 120 // {{{ Constructor 121 122 /** 123 * Constructor 124 */ 125 function Translation2() 126 { 127 if (func_num_args()) { 128 $msg = '<b>Translation2 error:</b>' 129 .' Don\'t use the constructor - use factory()'; 130 trigger_error($msg, E_USER_ERROR); 131 } 132 } 133 134 // }}} 135 // {{{ factory() 136 137 /** 138 * Return a Translation2 instance already initialized 139 * 140 * @param string $driver Type of the storage driver 141 * @param mixed $options Additional options for the storage driver 142 * (example: if you are using DB as the storage 143 * driver, you have to pass the dsn string here) 144 * @param array $params Array of parameters for the adapter class 145 * (i.e. you can set here the mappings between your 146 * table/field names and the ones used by this class) 147 * 148 * @return object Translation2 instance or PEAR_Error on failure 149 * @static 150 */ 151 function & factory($driver, $options = '', $params = array()) 152 { 153 $tr = new Translation2; 154 $tr->storage = Translation2::_storageFactory($driver, $options); 155 if (PEAR::isError($tr->storage)) { 156 return $tr->storage; 157 } 158 $tr->_setDefaultOptions(); 159 $tr->_parseOptions($params); 160 $tr->storage->_parseOptions($params); 161 return $tr; 162 } 163 164 // }}} 165 // {{{ _storageFactory() 166 167 /** 168 * Return a storage driver based on $driver and $options 169 * 170 * @param string $driver Type of storage class to return 171 * @param string $options Optional parameters for the storage class 172 * 173 * @return object Object Storage object 174 * @static 175 * @access private 176 */ 177 function & _storageFactory($driver, $options = '') 178 { 179 $storage_path = 'Translation2/Container/'.strtolower($driver).'.php'; 180 $storage_class = 'Translation2_Container_'.strtolower($driver); 181 include_once $storage_path; 182 $storage = new $storage_class; 183 $err = $storage->init($options); 184 if (PEAR::isError($err)) { 185 return $err; 186 } 187 return $storage; 188 } 189 190 // }}} 191 // {{{ setContainerOptions() 192 193 /** 194 * Set some storage driver options 195 * 196 * @param array $options array of options 197 * 198 * @return void 199 * @access protected 200 */ 201 function setContainerOptions($options) 202 { 203 $this->storage->_parseOptions($options); 204 } 205 206 // }}} 207 // {{{ _setDefaultOptions() 208 209 /** 210 * Set some default options 211 * 212 * @return void 213 * @access private 214 */ 215 function _setDefaultOptions() 216 { 217 $this->options['ParameterPrefix'] = '&&'; 218 $this->options['ParameterPostfix'] = '&&'; 219 $this->options['ParameterAutoFree'] = true; 220 $this->options['prefetch'] = true; 221 } 222 223 // }}} 224 // {{{ _parseOptions() 225 226 /** 227 * Parse options passed to the base class 228 * 229 * @param array $array options 230 * 231 * @return void 232 * @access private 233 */ 234 function _parseOptions($array) 235 { 236 foreach ($array as $key => $value) { 237 if (isset($this->options[$key])) { 238 $this->options[$key] = $value; 239 } 240 } 241 } 242 243 // }}} 244 // {{{ getDecorator() 245 246 /** 247 * Return an instance of a decorator 248 * 249 * This method is used to get a decorator instance. 250 * A decorator can be seen as a filter, i.e. something that can change 251 * or handle the values of the objects/vars that pass through. 252 * 253 * @param string $decorator Name of the decorator 254 * 255 * @return object Decorator object reference 256 */ 257 function & getDecorator($decorator) 258 { 259 $decorator_path = 'Translation2/Decorator/'.$decorator.'.php'; 260 $decorator_class = 'Translation2_Decorator_'.$decorator; 261 include_once $decorator_path; 262 if (func_num_args() > 1) { 263 $obj = func_get_arg(1); 264 $new_decorator = new $decorator_class($obj); 265 } else { 266 $new_decorator = new $decorator_class($this); 267 } 268 return $new_decorator; 269 } 270 271 // }}} 272 // {{{ setCharset() 273 274 /** 275 * Set charset used to read/store the translations 276 * 277 * @param string $charset character set (encoding) 278 * 279 * @return void|PEAR_Error 280 */ 281 function setCharset($charset) 282 { 283 $res = $this->storage->setCharset($charset); 284 if (PEAR::isError($res)) { 285 return $res; 286 } 287 } 288 289 // }}} 290 // {{{ setLang() 291 292 /** 293 * Set default lang 294 * 295 * Set the language that shall be used when retrieving strings. 296 * 297 * @param string $langID language code (for instance, 'en' or 'it') 298 * 299 * @return true|PEAR_Error 300 */ 301 function setLang($langID) 302 { 303 $res = $this->storage->setLang($langID); 304 if (PEAR::isError($res)) { 305 return $res; 306 } 307 $this->lang = $res; 308 return true; 309 } 310 311 // }}} 312 // {{{ setPageID($pageID) 313 314 /** 315 * Set default page 316 * 317 * Set the page (aka "group of strings") that shall be used when retrieving strings. 318 * If you set it, you don't have to state it in each get() call. 319 * 320 * @param string $pageID ID of the default page 321 * 322 * @return self 323 */ 324 function setPageID($pageID = null) 325 { 326 $this->currentPageID = $pageID; 327 return $this; 328 } 329 330 // }}} 331 // {{{ getLang() 332 333 /** 334 * get lang info 335 * 336 * Get some extra information about the language (its full name, 337 * the localized error text, ...) 338 * 339 * @param string $langID language ID 340 * @param string $format ['name', 'meta', 'error_text', 'array'] 341 * 342 * @return mixed [string | array], depending on $format 343 */ 344 function getLang($langID = null, $format = 'name') 345 { 346 if (is_null($langID)) { 347 if (!isset($this->lang['id'])) { 348 $msg = 'Translation2::getLang(): unknown language "'.$langID.'".' 349 .' Use Translation2::setLang() to set a default language.'; 350 return $this->storage->raiseError($msg, TRANSLATION2_ERROR_UNKNOWN_LANG); 351 } 352 $langID = $this->lang['id']; 353 } 354 $lang = $this->storage->getLangData($langID); 355 if ($format == 'array') { 356 return $lang; 357 } elseif (isset($lang[$format])) { 358 return $lang[$format]; 359 } elseif (isset($lang['name'])) { 360 return $lang['name']; 361 } 362 $msg = 'Translation2::getLang(): unknown language "'.$langID.'".' 363 .' Use Translation2::setLang() to set a default language.'; 364 return $this->storage->raiseError($msg, TRANSLATION2_ERROR_UNKNOWN_LANG); 365 } 366 367 // }}} 368 // {{{ getLangs() 369 370 /** 371 * get langs 372 * 373 * Get some extra information about the languages (their full names, 374 * the localized error text, their codes, ...) 375 * 376 * @param string $format ['ids', 'names', 'array'] 377 * 378 * @return array|PEAR_Error 379 */ 380 function getLangs($format = 'name') 381 { 382 return $this->storage->getLangs($format); 383 } 384 385 // }}} 386 // {{{ setParams() 387 388 /** 389 * Set parameters for next string 390 * 391 * Set the replacement for the parameters in the string(s). 392 * Parameter delimiters are customizable. 393 * 394 * @param array $params array of replacement parameters 395 * 396 * @return self 397 */ 398 function setParams($params = null) 399 { 400 if (empty($params)) { 401 $this->params = array(); 402 } elseif (is_array($params)) { 403 $this->params = $params; 404 } else { 405 $this->params = array($params); 406 } 407 return $this; 408 } 409 410 // }}} 411 // {{{ _replaceParams() 412 413 /** 414 * Replace parameters in strings 415 * 416 * @param mixed $strings strings where the replacements must occur 417 * 418 * @return mixed 419 * @access protected 420 */ 421 function _replaceParams($strings) 422 { 423 if (empty($strings) || is_object($strings) || !count($this->params)) { 424 return $strings; 425 } 426 if (is_array($strings)) { 427 foreach ($strings as $key => $string) { 428 $strings[$key] = $this->_replaceParams($string); 429 } 430 } else { 431 if (strpos($strings, $this->options['ParameterPrefix']) !== false) { 432 foreach ($this->params as $name => $value) { 433 $strings = str_replace($this->options['ParameterPrefix'] 434 . $name . $this->options['ParameterPostfix'], 435 $value, 436 $strings); 437 } 438 if ($this->options['ParameterAutoFree']) { 439 $this->params = array(); 440 } 441 } 442 } 443 return $strings; 444 } 445 446 // }}} 447 // {{{ replaceEmptyStringsWithKeys() 448 449 /** 450 * Replace empty strings with their stringID 451 * 452 * @param array $strings array of strings to be replaced if empty 453 * 454 * @return array 455 * @static 456 */ 457 function replaceEmptyStringsWithKeys($strings) 458 { 459 if (!is_array($strings)) { 460 return $strings; 461 } 462 foreach ($strings as $key => $string) { 463 if (empty($string)) { 464 $strings[$key] = $key; 465 } 466 } 467 return $strings; 468 } 469 470 // }}} 471 // {{{ getRaw() 472 473 /** 474 * Get translated string (as-is) 475 * 476 * @param string $stringID ID of the string to be translated 477 * @param string $pageID ID of the page/group containing the string 478 * @param string $langID ID of the language 479 * @param string $defaultText Text to display when the string is empty 480 * 481 * @return string|PEAR_Error 482 */ 483 function getRaw($stringID, $pageID = TRANSLATION2_DEFAULT_PAGEID, $langID = null, $defaultText = '') 484 { 485 $pageID = ($pageID == TRANSLATION2_DEFAULT_PAGEID ? $this->currentPageID : $pageID); 486 $str = $this->storage->getOne($stringID, $pageID, $langID); 487 if (empty($str)) { 488 $str = $defaultText; 489 } 490 return $str; 491 } 492 493 // }}} 494 // {{{ get() 495 496 /** 497 * Get translated string 498 * 499 * First check if the string is cached, if not => fetch the page 500 * from the container and cache it for later use. 501 * If the string is empty, check the fallback language; if 502 * the latter is empty too, then return the $defaultText. 503 * 504 * @param string $stringID ID of the string 505 * @param string $pageID ID of the page/group containing the string 506 * @param string $langID ID of the language 507 * @param string $defaultText Text to display when the string is empty 508 * NB: This parameter is only used in the DefaultText decorator 509 * 510 * @return string 511 */ 512 function get($stringID, $pageID = TRANSLATION2_DEFAULT_PAGEID, $langID = null, $defaultText = '') 513 { 514 $str = $this->getRaw($stringID, $pageID, $langID); 515 if (PEAR::isError($str)) { 516 return $str; 517 } 518 return $this->_replaceParams($str); 519 } 520 521 // }}} 522 // {{{ getRawPage() 523 524 /** 525 * Get the array of strings in a page 526 * 527 * Fetch the page (aka "group of strings) from the container, 528 * without applying any formatting and without replacing the parameters 529 * 530 * @param string $pageID ID of the page/group containing the string 531 * @param string $langID ID of the language 532 * 533 * @return array 534 */ 535 function getRawPage($pageID = TRANSLATION2_DEFAULT_PAGEID, $langID = null) 536 { 537 $pageID = ($pageID == TRANSLATION2_DEFAULT_PAGEID ? $this->currentPageID : $pageID); 538 return $this->storage->getPage($pageID, $langID); 539 } 540 541 // }}} 542 // {{{ getPage() 543 544 /** 545 * Get an entire group of strings 546 * 547 * Same as getRawPage, but resort to fallback language and 548 * replace parameters when needed 549 * 550 * @param string $pageID ID of the page/group containing the string 551 * @param string $langID ID of the language 552 * 553 * @return array 554 */ 555 function getPage($pageID = TRANSLATION2_DEFAULT_PAGEID, $langID = null) 556 { 557 $pageData = $this->getRawPage($pageID, $langID); 558 return $this->_replaceParams($pageData); 559 } 560 561 // }}} 562 // {{{ getStringID() 563 564 /** 565 * Get the stringID for the given string. This method is the reverse of get(). 566 * 567 * @param string $string This is NOT the stringID, this is a real string. 568 * The method will search for its matching stringID, and then 569 * it will return the associate string in the selected language. 570 * @param string $pageID ID of the page/group containing the string 571 * 572 * @return string 573 */ 574 function getStringID($string, $pageID = TRANSLATION2_DEFAULT_PAGEID) 575 { 576 $pageID = ($pageID == TRANSLATION2_DEFAULT_PAGEID ? $this->currentPageID : $pageID); 577 return $this->storage->getStringID($string, $pageID); 578 } 579 580 // }}} 581 // {{{ __clone() 582 583 /** 584 * Clone internal object references 585 * 586 * This method is called automatically by PHP5 587 * 588 * @return void 589 * @access protected 590 */ 591 function __clone() 592 { 593 $this->storage = clone($this->storage); 594 } 595 596 // }}} 597} 598?>