fix image text
[pear] / Translation2.php
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  */
42 require_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  */
49 if (!defined('TRANSLATION2_DEFAULT_PAGEID')) {
50     define('TRANSLATION2_DEFAULT_PAGEID', 'translation2_default_pageID');
51 }
52 /**
53  * Class Error codes
54  */
55 define('TRANSLATION2_ERROR',                      -1);
56 define('TRANSLATION2_ERROR_METHOD_NOT_SUPPORTED', -2);
57 define('TRANSLATION2_ERROR_CANNOT_CONNECT',       -3);
58 define('TRANSLATION2_ERROR_CANNOT_FIND_FILE',     -4);
59 define('TRANSLATION2_ERROR_DOMAIN_NOT_SET',       -5);
60 define('TRANSLATION2_ERROR_INVALID_PATH',         -6);
61 define('TRANSLATION2_ERROR_CANNOT_CREATE_DIR',    -7);
62 define('TRANSLATION2_ERROR_CANNOT_WRITE_FILE',    -8);
63 define('TRANSLATION2_ERROR_UNKNOWN_LANG',         -9);
64 define('TRANSLATION2_ERROR_ENCODING_CONVERSION', -10);
65 define('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  */
81 class 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 __construct()
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     static 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     static  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 ?>