1<?php 2/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ 3 4/** 5 * Contains the Translation2_Admin_Container_gettext 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 * @author Michael Wallner <mike@php.net> 34 * @copyright 2004-2007 Lorenzo Alberton, Michael Wallner 35 * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) 36 * @version CVS: $Id: gettext.php 305985 2010-12-05 22:55:33Z clockwerx $ 37 * @link http://pear.php.net/package/Translation2 38 */ 39 40/** 41 * require Translation2_Container_gettext class 42 */ 43require_once 'Translation2/Container/gettext.php'; 44 45/** 46 * Storage driver for storing/fetching data to/from a gettext file 47 * 48 * This storage driver requires the gettext extension 49 * 50 * @category Internationalization 51 * @package Translation2 52 * @author Lorenzo Alberton <l.alberton@quipo.it> 53 * @author Michael Wallner <mike@php.net> 54 * @copyright 2004-2007 Lorenzo Alberton, Michael Wallner 55 * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) 56 * @version CVS: $Id: gettext.php 305985 2010-12-05 22:55:33Z clockwerx $ 57 * @link http://pear.php.net/package/Translation2 58 */ 59class Translation2_Admin_Container_gettext extends Translation2_Container_gettext 60{ 61 // {{{ class vars 62 63 var $_bulk = false; 64 var $_queue = array(); 65 var $_fields = array('name', 'meta', 'error_text', 'encoding'); 66 67 // }}} 68 // {{{ addLang() 69 70 /** 71 * Creates a new entry in the langs_avail .ini file. 72 * 73 * @param array $langData language data 74 * @param string $path path to gettext data dir 75 * 76 * @return mixed Returns true on success or PEAR_Error on failure. 77 */ 78 function addLang($langData, $path = null) 79 { 80 if (!isset($path) || !is_string($path)) { 81 $path = $this->_domains[$this->options['default_domain']]; 82 } 83 84 $path .= '/'. $langData['lang_id'] . '/LC_MESSAGES'; 85 86 if (!is_dir($path)) { 87 include_once 'System.php'; 88 if (!System::mkdir(array('-p', $path))) { 89 return $this->raiseError(sprintf( 90 'Cannot create new language in path "%s"', $path 91 ), 92 TRANSLATION2_ERROR_CANNOT_CREATE_DIR 93 ); 94 } 95 } 96 97 return true; 98 } 99 100 // }}} 101 // {{{ addLangToList() 102 103 /** 104 * Creates a new entry in the langsAvail .ini file. 105 * If the file doesn't exist yet, it is created. 106 * 107 * @param array $langData array('lang_id' => 'en', 108 * 'name' => 'english', 109 * 'meta' => 'some meta info', 110 * 'error_text' => 'not available' 111 * 'encoding' => 'iso-8859-1', 112 * ); 113 * 114 * @return true|PEAR_Error on failure 115 */ 116 function addLangToList($langData) 117 { 118 if (PEAR::isError($changed = $this->_updateLangData($langData))) { 119 return $changed; 120 } 121 return $changed ? $this->_writeLangsAvailFile() : true; 122 } 123 124 // }}} 125 // {{{ add() 126 127 /** 128 * Add a new entry in the strings domain. 129 * 130 * @param string $stringID string ID 131 * @param string $pageID page/group ID 132 * @param array $strings Associative array with string translations. 133 * Sample format: array('en' => 'sample', 'it' => 'esempio') 134 * 135 * @return true|PEAR_Error on failure 136 */ 137 function add($stringID, $pageID, $strings) 138 { 139 if (!isset($pageID)) { 140 $pageID = $this->options['default_domain']; 141 } 142 143 $langs = array_intersect(array_keys($strings), $this->getLangs('ids')); 144 145 if (!count($langs)) { 146 return true; // really? 147 } 148 149 if ($this->_bulk) { 150 foreach ($strings as $lang => $string) { 151 if (in_array($lang, $langs)) { 152 $this->_queue['add'][$pageID][$lang][$stringID] = $string; 153 } 154 } 155 return true; 156 } else { 157 $add = array(); 158 foreach ($strings as $lang => $string) { 159 if (in_array($lang, $langs)) { 160 $add[$pageID][$lang][$stringID] = $string; 161 } 162 } 163 return $this->_add($add); 164 } 165 } 166 167 // }}} 168 // {{{ remove() 169 170 /** 171 * Remove an entry from the domain. 172 * 173 * @param string $stringID string ID 174 * @param string $pageID page/group ID 175 * 176 * @return true|PEAR_Error on failure 177 */ 178 function remove($stringID, $pageID) 179 { 180 if (!isset($pageID)) { 181 $pageID = $this->options['default_domain']; 182 } 183 184 if ($this->_bulk) { 185 $this->_queue['remove'][$pageID][$stringID] = true; 186 return true; 187 } else { 188 $tmp = array($pageID => array($stringID => true)); 189 return $this->_remove($tmp); 190 } 191 192 } 193 194 // }}} 195 // {{{ removePage 196 197 /** 198 * Remove all the strings in the given page/group (domain) 199 * 200 * @param string $pageID page/group ID 201 * @param string $path path to gettext data dir 202 * 203 * @return mixed true on success, PEAR_Error on failure 204 */ 205 function removePage($pageID = null, $path = null) 206 { 207 if (!isset($pageID)) { 208 $pageID = $this->options['default_domain']; 209 } 210 211 if (!isset($path)) { 212 if (!empty($this->_domains[$pageID])) { 213 $path = $this->_domains[$pageID]; 214 } else { 215 $path = $this->_domains[$this->options['default_domain']]; 216 } 217 } 218 219 if (PEAR::isError($e = $this->_removeDomain($pageID))) { 220 return $e; 221 } 222 223 $this->fetchLangs(); 224 foreach ($this->langs as $langID => $lang) { 225 $domain_file = $path .'/'. $langID .'/LC_MESSAGES/'. $pageID .'.'; 226 if (!@unlink($domain_file.'mo') || !@unlink($domain_file.'po')) { 227 return $this->raiseError('Cannot delete page ' . $pageID. ' (file '.$domain_file.'.*)', 228 TRANSLATION2_ERROR 229 ); 230 } 231 } 232 233 return true; 234 } 235 236 // }}} 237 // {{{ update() 238 239 /** 240 * Update 241 * 242 * Alias for Translation2_Admin_Container_gettext::add() 243 * 244 * @param string $stringID string ID 245 * @param string $pageID page/group ID 246 * @param array $strings strings 247 * 248 * @return mixed 249 * @access public 250 * @see add() 251 */ 252 function update($stringID, $pageID, $strings) 253 { 254 return $this->add($stringID, $pageID, $strings); 255 } 256 257 // }}} 258 // {{{ removeLang() 259 260 /** 261 * Remove Language 262 * 263 * @param string $langID language ID 264 * @param bool $force (unused) 265 * 266 * @return true|PEAR_Error 267 * @access public 268 */ 269 function removeLang($langID, $force = false) 270 { 271 include_once 'System.php'; 272 foreach ((array) $this->_domains as $domain => $path) { 273 if (is_dir($fp = $path .'/'. $langID)) { 274 if (PEAR::isError($e = System::rm(array('-rf', $fp))) || !$e) { 275 return $e ? $e : PEAR::raiseError(sprintf( 276 'Could not remove language "%s" from domain "%s" '. 277 'in path "%s" (probably insufficient permissions)', 278 $langID, $domain, $path 279 ), 280 TRANSLATION2_ERROR 281 ); 282 } 283 } 284 } 285 return true; 286 } 287 288 // }}} 289 // {{{ updateLang() 290 291 /** 292 * Update the lang info in the langs_avail file 293 * 294 * @param array $langData language data 295 * 296 * @return mixed Returns true on success or PEAR_Error on failure. 297 * @access public 298 */ 299 function updateLang($langData) 300 { 301 if (PEAR::isError($changed = $this->_updateLangData($langData))) { 302 return $changed; 303 } 304 return $changed ? $this->_writeLangsAvailFile() : true; 305 } 306 307 // }}} 308 // {{{ getPageNames() 309 310 /** 311 * Get a list of all the domains 312 * 313 * @return array 314 * @access public 315 */ 316 function getPageNames() 317 { 318 return array_keys($this->_domains); 319 } 320 321 // }}} 322 // {{{ begin() 323 324 /** 325 * Begin 326 * 327 * @return void 328 * @access public 329 */ 330 function begin() 331 { 332 $this->_bulk = true; 333 } 334 335 // }}} 336 // {{{ commit() 337 338 /** 339 * Commit 340 * 341 * @return true|PEAR_Error on failure. 342 * @access public 343 */ 344 function commit() 345 { 346 $this->_bulk = false; 347 if (isset($this->_queue['remove'])) { 348 if (PEAR::isError($e = $this->_remove($this->_queue['remove']))) { 349 return $e; 350 } 351 } 352 if (isset($this->_queue['add'])) { 353 if (PEAR::isError($e = $this->_add($this->_queue['add']))) { 354 return $e; 355 } 356 } 357 return true; 358 } 359 360 // }}} 361 // {{{ _add() 362 363 /** 364 * Add 365 * 366 * @param array &$bulk array('pageID' => array([languages])) 367 * 368 * @return true|PEAR_Error on failure. 369 * @access private 370 */ 371 function _add(&$bulk) 372 { 373 include_once 'File/Gettext.php'; 374 $gtFile = &File_Gettext::factory($this->options['file_type']); 375 $langs = $this->getLangs('array'); 376 377 foreach ((array) $bulk as $pageID => $languages) { 378 //create the new domain on demand 379 if (!isset($this->_domains[$pageID])) { 380 if (PEAR::isError($e = $this->_addDomain($pageID))) { 381 return $e; 382 } 383 } 384 $path = $this->_domains[$pageID]; 385 if ($path[strlen($path)-1] != '/' && $path[strlen($path)-1] != '\\') { 386 $path .= '/'; 387 } 388 $file = '/LC_MESSAGES/'. $pageID .'.'. $this->options['file_type']; 389 390 foreach ($languages as $lang => $strings) { 391 392 if (is_file($path . $lang . $file)) { 393 if (PEAR::isError($e = $gtFile->load($path . $lang . $file))) { 394 return $e; 395 } 396 } 397 398 if (!isset($gtFile->meta['Content-Type'])) { 399 $gtFile->meta['Content-Type'] = 'text/plain; charset='; 400 if (isset($langs[$lang]['encoding'])) { 401 $gtFile->meta['Content-Type'] .= $langs[$lang]['encoding']; 402 } else { 403 $gtFile->meta['Content-Type'] .= $this->options['default_encoding']; 404 } 405 } 406 407 foreach ($strings as $stringID => $string) { 408 $gtFile->strings[$stringID] = $string; 409 } 410 411 if (PEAR::isError($e = $gtFile->save($path . $lang . $file))) { 412 return $e; 413 } 414 415 //refresh cache 416 $this->cachedDomains[$lang][$pageID] = $gtFile->strings; 417 } 418 } 419 420 $bulk = null; 421 return true; 422 } 423 424 // }}} 425 // {{{ _remove() 426 427 /** 428 * Remove 429 * 430 * @param array &$bulk array('pageID' => array([languages])) 431 * 432 * @return true|PEAR_Error on failure. 433 * @access private 434 */ 435 function _remove(&$bulk) 436 { 437 include_once 'File/Gettext.php'; 438 $gtFile = &File_Gettext::factory($this->options['file_type']); 439 440 foreach ($this->getLangs('ids') as $lang) { 441 foreach ((array) $bulk as $pageID => $stringIDs) { 442 $file = sprintf( 443 '%s/%s/LC_MESSAGES/%s.%s', 444 $this->_domains[$pageID], 445 $lang, 446 $pageID, 447 $this->options['file_type'] 448 ); 449 450 if (is_file($file)) { 451 if (PEAR::isError($e = $gtFile->load($file))) { 452 return $e; 453 } 454 455 foreach (array_keys($stringIDs) as $stringID) { 456 unset($gtFile->strings[$stringID]); 457 } 458 459 if (PEAR::isError($e = $gtFile->save($file))) { 460 return $e; 461 } 462 463 //refresh cache 464 $this->cachedDomains[$lang][$pageID] = $gtFile->strings; 465 } 466 } 467 } 468 469 $bulk = null; 470 return true; 471 } 472 473 // }}} 474 // {{{ _addDomain() 475 476 /** 477 * Add the path-to-the-new-domain to the domains-path-INI-file 478 * 479 * @param string $pageID domain name 480 * 481 * @return true|PEAR_Error on failure 482 * @access private 483 */ 484 function _addDomain($pageID) 485 { 486 $domain_path = count($this->_domains) ? reset($this->_domains) : 'locale/'; 487 488 if (!is_resource($f = fopen($this->options['domains_path_file'], 'a'))) { 489 return $this->raiseError(sprintf( 490 'Cannot write to domains path INI file "%s"', 491 $this->options['domains_path_file'] 492 ), 493 TRANSLATION2_ERROR_CANNOT_WRITE_FILE 494 ); 495 } 496 497 $CRLF = $this->options['carriage_return']; 498 499 while (true) { 500 if (@flock($f, LOCK_EX)) { 501 fwrite($f, $CRLF . $pageID . ' = ' . $domain_path . $CRLF); 502 @flock($f, LOCK_UN); 503 fclose($f); 504 break; 505 } 506 } 507 508 $this->_domains[$pageID] = $domain_path; 509 510 return true; 511 } 512 513 // }}} 514 // {{{ _removeDomain() 515 516 /** 517 * Remove the path-to-the-domain from the domains-path-INI-file 518 * 519 * @param string $pageID domain name 520 * 521 * @return true|PEAR_Error on failure 522 * @access private 523 */ 524 function _removeDomain($pageID) 525 { 526 $domain_path = count($this->_domains) ? reset($this->_domains) : 'locale/'; 527 528 if (!is_resource($f = fopen($this->options['domains_path_file'], 'r+'))) { 529 return $this->raiseError(sprintf( 530 'Cannot write to domains path INI file "%s"', 531 $this->options['domains_path_file'] 532 ), 533 TRANSLATION2_ERROR_CANNOT_WRITE_FILE 534 ); 535 } 536 537 $CRLF = $this->options['carriage_return']; 538 539 while (true) { 540 if (@flock($f, LOCK_EX)) { 541 $pages = file($this->options['domains_path_file']); 542 foreach ($pages as $page) { 543 if (preg_match('/^'.$pageID.'\s*=/', $page)) { 544 //skip 545 continue; 546 } 547 fwrite($f, $page . $CRLF); 548 } 549 fflush($f); 550 ftruncate($f, ftell($f)); 551 @flock($f, LOCK_UN); 552 fclose($f); 553 break; 554 } 555 } 556 557 unset($this->_domains[$pageID]); 558 559 return true; 560 } 561 562 // }}} 563 // {{{ _writeLangsAvailFile() 564 565 /** 566 * Write the langs_avail INI file 567 * 568 * @return true|PEAR_Error on failure. 569 * @access private 570 */ 571 function _writeLangsAvailFile() 572 { 573 if (PEAR::isError($langs = $this->getLangs())) { 574 return $langs; 575 } 576 577 if (!is_resource($f = fopen($this->options['langs_avail_file'], 'w'))) { 578 return $this->raiseError(sprintf( 579 'Cannot write to available langs INI file "%s"', 580 $this->options['langs_avail_file'] 581 ), 582 TRANSLATION2_ERROR_CANNOT_WRITE_FILE 583 ); 584 } 585 $CRLF = $this->options['carriage_return']; 586 587 @flock($f, LOCK_EX); 588 589 foreach ($langs as $id => $data) { 590 fwrite($f, '['. $id .']'. $CRLF); 591 foreach ($this->_fields as $k) { 592 if (isset($data[$k])) { 593 fwrite($f, $k . ' = ' . $data[$k] . $CRLF); 594 } 595 } 596 fwrite($f, $CRLF); 597 } 598 599 @flock($f, LOCK_UN); 600 fclose($f); 601 return true; 602 } 603 604 // }}} 605 // {{{ _updateLangData() 606 607 /** 608 * Update Lang Data 609 * 610 * @param array $langData language data 611 * 612 * @return true|PEAR_Error on failure. 613 * @access private 614 */ 615 function _updateLangData($langData) 616 { 617 if (PEAR::isError($langs = $this->getLangs())) { 618 return $langs; 619 } 620 621 $lang = &$langs[$langData['lang_id']]; 622 $changed = false; 623 foreach ($this->_fields as $k) { 624 if ( isset($langData[$k]) && 625 (!isset($lang[$k]) || $langData[$k] != $lang[$k])) { 626 $lang[$k] = $langData[$k]; 627 $changed = true; 628 } 629 } 630 631 if ($changed) { 632 $lang['id'] = $langData['lang_id']; 633 $this->langs = $langs; 634 } 635 return $changed; 636 } 637 638 // }}} 639} 640?>