Translation2
[ class tree: Translation2 ] [ index: Translation2 ] [ all elements ]

Source for file gettext.php

Documentation is available at gettext.php

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