diff --git a/Admin.php b/Translation2/Admin.php similarity index 100% rename from Admin.php rename to Translation2/Admin.php diff --git a/Translation2/Admin/Container/dataobjectsimple.php b/Translation2/Admin/Container/dataobjectsimple.php new file mode 100644 index 0000000..7a158c9 --- /dev/null +++ b/Translation2/Admin/Container/dataobjectsimple.php @@ -0,0 +1,241 @@ + + * @copyright 2004-2007 Alan Knowles + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ + +/** + * require Translation2_Container_dataobjectsimple class + */ +require_once 'Translation2/Container/dataobjectsimple.php'; + +/** + * Storage driver for storing/fetching data to/from a database + * + * This storage driver can use all databases which are supported + * by PEAR::DB_DataObject to fetch data. + * + * Database Structure: + * + *
+ * // meta data etc. not supported
+ *
+ * table: translations
+ *  id          // not null primary key autoincrement..
+ *  string_id   // translation id
+ *  page        // indexed varchar eg. (mytemplate.html)
+ *  lang        // index varchar (eg. en|fr|.....)
+ *  translation // the translated value in language lang.
+ * 
+ * + * @category Internationalization + * @package Translation2 + * @author Alan Knowles + * @copyright 2004-2007 Alan Knowles + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @link http://pear.php.net/package/Translation2 + */ +class Translation2_Admin_Container_dataobjectsimple extends Translation2_Container_dataobjectsimple +{ + // {{{ addLang() + + /** + * Creates a new table to store the strings in this language. + * If the table is shared with other langs, it is ALTERed to + * hold strings in this lang too. + * + * @param array $langData array('lang_id' => 'en', + * 'table_name' => 'i18n', + * 'name' => 'english', + * 'meta' => 'some meta info', + * 'error_text' => 'not available'); + * @param array $options DB_DataObject options + * + * @return true|PEAR_Error + */ + function addLang($langData, $options = array()) + { + $do = DB_DataObject::factory($this->options['table']); + $do->lang = $langData['lang_id']; + if (!$do->find()) { + $do->insert(); + } + } + + // }}} + // {{{ addLangToList() + + /** + * Creates a new entry in the langsAvail table. + * If the table doesn't exist yet, it is created. + * + * @param array $langData array('lang_id' => 'en', + * 'table_name' => 'i18n', + * 'name' => 'english', + * 'meta' => 'some meta info', + * 'error_text' => 'not available'); + * + * @return true|PEAR_Error + */ + function addLangToList($langData) + { + return true; + } + + // }}} + // {{{ add() + + /** + * Add a new entry in the strings table. + * + * @param string $string string + * @param string $pageID page/group ID + * @param array $stringArray Associative array with string translations. + * Sample format: array('en' => 'sample', 'it' => 'esempio') + * + * @return true|PEAR_Error + */ + function add($string, $pageID, $stringArray) + { + //look up the string id first.. + $do = DB_DataObject::factory($this->options['table']); + $do->lang = '-'; + $do->translation = $string; + $do->page = $page; + if ($do->find(true)) { + $stringID = $do->string_id; + } else { + // insert it and use the 'id' as the string id + $stringID = $do->insert(); + $do->string_id = $stringID; + $do->update(); + } + + foreach ($stringArray as $lang=>$value) { + $do = DB_DataObject::factory($this->options['table']); + $do->string_id = $stringID; + $do->page = $pageID; + $do->lang = $lang; + if ($do->find(true)) { + $do->translation = $value; + $do->update(); + continue; + } + $do->translation = $value; + $do->insert(); + } + + return true; + } + + // }}} + // {{{ update() + + /** + * Update an existing entry in the strings table. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param array $stringArray Associative array with string translations. + * Sample format: array('en' => 'sample', 'it' => 'esempio') + * + * @return true|PEAR_Error + */ + function update($stringID, $pageID, $stringArray) + { + $this->add($stringID, $pageID, $stringArray); + return true; + } + + // }}} + // {{{ remove() + + /** + * Remove an entry from the strings table. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * + * @return true|PEAR_Error + */ + function remove($stringID, $pageID) + { + // get the string id + $do = DB_DataObject::factory($this->options['table']); + $do->page = $pageID; + $do->translation = $stringID; + // we don't have the base language translation.. + if (!$do->find()) { + return ''; + } + + while ($do->fetch()) { + $do2 = DB_DataObject::factory($this->options['table']); + $do2->get($do->id); + $do2->delete(); + } + return true; + } + + // }}} + // {{{ removePage() + + /** + * Remove all the strings in the given page/group + * + * @param string $pageID page/group ID + * + * @return mixed true on success, PEAR_Error on failure + */ + function removePage($pageID = null) + { + $do = DB_DataObject::factory($this->options['table']); + $do->page = $pageID; + // we don't have the base language translation.. + if (!$do->find()) { + return ''; + } + + while ($do->fetch()) { + $do2 = DB_DataObject::factory($this->options['table']); + $do2->get($do->id); + $do2->delete(); + } + return true; + } + + // }}} +} +?> \ No newline at end of file diff --git a/Translation2/Admin/Container/db.php b/Translation2/Admin/Container/db.php new file mode 100644 index 0000000..525abfe --- /dev/null +++ b/Translation2/Admin/Container/db.php @@ -0,0 +1,721 @@ + + * @author Ian Eure + * @copyright 2004-2007 Lorenzo Alberton, Ian Eure + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ + +/** + * require Translation2_Container_db class + */ +require_once 'Translation2/Container/db.php'; + +/** + * Storage driver for storing/fetching data to/from a database + * + * This storage driver can use all databases which are supported + * by the PEAR::DB abstraction layer to store and fetch data. + * + * @category Internationalization + * @package Translation2 + * @author Lorenzo Alberton + * @author Ian Eure + * @copyright 2004-2007 Lorenzo Alberton, Ian Eure + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @link http://pear.php.net/package/Translation2 + */ +class Translation2_Admin_Container_db extends Translation2_Container_db +{ + + // {{{ class vars + + // }}} + // {{{ addLang() + + /** + * Creates a new table to store the strings in this language. + * If the table is shared with other langs, it is ALTERed to + * hold strings in this lang too. + * + * @param array $langData language data + * @param array $options options + * + * @return true|PEAR_Error + */ + function addLang($langData, $options = array()) + { + $tables = $this->db->getListOf('tables'); + if (PEAR::isError($tables)) { + return $tables; + } + + $lang_col = $this->_getLangCol($langData['lang_id']); + + if (in_array($langData['table_name'], $tables)) { + // table exists + $query = sprintf('ALTER TABLE %s ADD %s%s TEXT', + $this->db->quoteIdentifier($langData['table_name']), + $this->db->phptype == 'mssql' ? '' : 'COLUMN ', + $this->db->quoteIdentifier($lang_col) + ); + ++$this->_queries; + return $this->db->query($query); + } + + //table does not exist + $queries = array(); + $queries[] = sprintf('CREATE TABLE %s ( ' + .'%s VARCHAR(%d) default NULL, ' + .'%s TEXT NOT NULL, ' + .'%s TEXT )', + $this->db->quoteIdentifier($langData['table_name']), + $this->db->quoteIdentifier($this->options['string_page_id_col']), + (int)$this->options['string_page_id_col_length'], + $this->db->quoteIdentifier($this->options['string_id_col']), + $this->db->quoteIdentifier($lang_col) + ); + $mysqlClause = ($this->db->phptype == 'mysql') ? '(255)' : ''; + + $index_name = sprintf('%s_%s_%s_index', + $langData['table_name'], + $this->options['string_page_id_col'], + $this->options['string_id_col'] + ); + $queries[] = sprintf('CREATE UNIQUE INDEX %s ON %s (%s, %s%s)', + $this->db->quoteIdentifier($index_name), + $this->db->quoteIdentifier($langData['table_name']), + $this->db->quoteIdentifier($this->options['string_page_id_col']), + $this->db->quoteIdentifier($this->options['string_id_col']), + $mysqlClause + ); + + $index_name = sprintf('%s_%s_index', + $langData['table_name'], + $this->options['string_page_id_col'] + ); + $queries[] = sprintf('CREATE INDEX %s ON %s (%s)', + $this->db->quoteIdentifier($index_name), + $this->db->quoteIdentifier($langData['table_name']), + $this->db->quoteIdentifier($this->options['string_page_id_col']) + ); + + $index_name = sprintf('%s_%s_index', + $langData['table_name'], + $this->options['string_id_col'] + ); + $queries[] = sprintf('CREATE INDEX %s ON %s (%s%s)', + $this->db->quoteIdentifier($index_name), + $this->db->quoteIdentifier($langData['table_name']), + $this->db->quoteIdentifier($this->options['string_id_col']), + $mysqlClause + ); + + foreach ($queries as $query) { + ++$this->_queries; + $res = $this->db->query($query); + if (PEAR::isError($res)) { + return $res; + } + } + return true; + } + + // }}} + // {{{ addLangToList() + + /** + * Creates a new entry in the langsAvail table. + * If the table doesn't exist yet, it is created. + * + * @param array $langData array('lang_id' => 'en', + * 'table_name' => 'i18n', + * 'name' => 'english', + * 'meta' => 'some meta info', + * 'error_text' => 'not available', + * 'encoding' => 'iso-8859-1'); + * + * @return true|PEAR_Error + */ + function addLangToList($langData) + { + $tables = $this->db->getListOf('tables'); + if (PEAR::isError($tables)) { + return $tables; + } + + if (!in_array($this->options['langs_avail_table'], $tables)) { + $queries = array(); + $queries[] = sprintf('CREATE TABLE %s (' + .'%s VARCHAR(16), ' + .'%s VARCHAR(200), ' + .'%s TEXT, ' + .'%s VARCHAR(250), ' + .'%s VARCHAR(16) )', + $this->db->quoteIdentifier($this->options['langs_avail_table']), + $this->db->quoteIdentifier($this->options['lang_id_col']), + $this->db->quoteIdentifier($this->options['lang_name_col']), + $this->db->quoteIdentifier($this->options['lang_meta_col']), + $this->db->quoteIdentifier($this->options['lang_errmsg_col']), + $this->db->quoteIdentifier($this->options['lang_encoding_col']) + ); + $queries[] = sprintf('CREATE UNIQUE INDEX %s_%s_index ON %s (%s)', + $this->options['langs_avail_table'], + $this->options['lang_id_col'], + $this->db->quoteIdentifier($this->options['langs_avail_table']), + $this->db->quoteIdentifier($this->options['lang_id_col']) + ); + + foreach ($queries as $query) { + ++$this->_queries; + $res = $this->db->query($query); + if (PEAR::isError($res)) { + return $res; + } + } + } + + $query = sprintf('INSERT INTO %s (%s, %s, %s, %s, %s) VALUES (%s, %s, %s, %s, %s)', + $this->db->quoteIdentifier($this->options['langs_avail_table']), + $this->db->quoteIdentifier($this->options['lang_id_col']), + $this->db->quoteIdentifier($this->options['lang_name_col']), + $this->db->quoteIdentifier($this->options['lang_meta_col']), + $this->db->quoteIdentifier($this->options['lang_errmsg_col']), + $this->db->quoteIdentifier($this->options['lang_encoding_col']), + $this->db->quote($langData['lang_id']), + $this->db->quote($langData['name']), + $this->db->quote($langData['meta']), + $this->db->quote($langData['error_text']), + $this->db->quote($langData['encoding']) + ); + + ++$this->_queries; + $success = $this->db->query($query); + $this->options['strings_tables'][$langData['lang_id']] = $langData['table_name']; + return $success; + } + + // }}} + // {{{ removeLang() + + /** + * Remove the lang from the langsAvail table and drop the strings table. + * If the strings table holds other langs and $force==false, then + * only the lang column is dropped. If $force==true the whole + * table is dropped without any check + * + * @param string $langID language ID + * @param boolean $force if true, the whole table is dropped without checks + * + * @return true|PEAR_Error + */ + function removeLang($langID, $force) + { + //remove from langsAvail + $query = sprintf('DELETE FROM %s WHERE %s = %s', + $this->db->quoteIdentifier($this->options['langs_avail_table']), + $this->db->quoteIdentifier($this->options['lang_id_col']), + $this->db->quote($langID) + ); + ++$this->_queries; + $res = $this->db->query($query); + if (PEAR::isError($res)) { + return $res; + } + + $lang_table = $this->_getLangTable($langID); + if ($force) { + //remove the whole table + ++$this->_queries; + return $this->db->query('DROP TABLE ' . $this->db->quoteIdentifier($lang_table)); + } + + //drop only the column for this lang + $query = sprintf('ALTER TABLE %s DROP COLUMN %s', + $lang_table, + $this->_getLangCol($langID) + ); + ++$this->_queries; + return $this->db->query($query); + } + + // }}} + // {{{ updateLang() + + /** + * Update the lang info in the langsAvail table + * + * @param array $langData language data + * + * @return true|PEAR_Error + */ + function updateLang($langData) + { + $allFields = array( + //'lang_id' => 'lang_id_col', + 'name' => 'lang_name_col', + 'meta' => 'lang_meta_col', + 'error_text' => 'lang_errmsg_col', + 'encoding' => 'lang_encoding_col', + ); + $updateFields = array_keys($langData); + $langSet = array(); + foreach ($allFields as $field => $col) { + if (in_array($field, $updateFields)) { + $langSet[] = $this->db->quoteIdentifier($this->options[$col]) . ' = ' . + $this->db->quote($langData[$field]); + } + } + $query = sprintf('UPDATE %s SET %s WHERE %s=%s', + $this->db->quoteIdentifier($this->options['langs_avail_table']), + implode(', ', $langSet), + $this->db->quoteIdentifier($this->options['lang_id_col']), + $this->db->quote($langData['lang_id']) + ); + + ++$this->_queries; + $success = $this->db->query($query); + $this->fetchLangs(); //update memory cache + return $success; + } + + // }}} + // {{{ add() + + /** + * Add a new entry in the strings table. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param array $stringArray Associative array with string translations. + * Sample format: array('en' => 'sample', 'it' => 'esempio') + * + * @return true|PEAR_Error + */ + function add($stringID, $pageID, $stringArray) + { + $langs = array_intersect( + array_keys($stringArray), + $this->getLangs('ids') + ); + + if (!count($langs)) { + //return error: no valid lang provided + return true; + } + + // Langs may be in different tables - we need to split up queries along + // table lines, so we can keep DB traffic to a minimum. + + $unquoted_stringID = $stringID; + $unquoted_pageID = $pageID; + $stringID = $this->db->quote($stringID); + $pageID = is_null($pageID) ? 'NULL' : $this->db->quote($pageID); + // Loop over the tables we need to insert into. + foreach ($this->_tableLangs($langs) as $table => $tableLangs) { + $exists = $this->_recordExists($unquoted_stringID, $unquoted_pageID, $table); + if (PEAR::isError($exists)) { + return $exists; + } + $func = $exists ? '_getUpdateQuery' : '_getInsertQuery'; + $query = $this->$func($table, $tableLangs, $stringID, $pageID, $stringArray); + + ++$this->_queries; + $res = $this->db->query($query); + if (PEAR::isError($res)) { + return $res; + } + } + + return true; + } + + // }}} + // {{{ update() + + /** + * Update an existing entry in the strings table. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param array $stringArray Associative array with string translations. + * Sample format: array('en' => 'sample', 'it' => 'esempio') + * + * @return true|PEAR_Error + */ + function update($stringID, $pageID, $stringArray) + { + return $this->add($stringID, $pageID, $stringArray); + } + + // }}} + // {{{ _getInsertQuery() + + /** + * Build a SQL query to INSERT a record + * + * @param string $table table name + * @param array &$tableLangs tables containing the languages + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param array &$stringArray array of strings + * + * @return string INSERT query + * @access private + */ + function _getInsertQuery($table, &$tableLangs, $stringID, $pageID, &$stringArray) + { + $tableCols = $this->_getLangCols($tableLangs); + $langData = array(); + foreach ($tableLangs as $lang) { + $langData[$lang] = $this->db->quote($stringArray[$lang]); + } + foreach (array_keys($tableCols) as $k) { + $tableCols[$k] = $this->db->quoteIdentifier($tableCols[$k]); + } + + return sprintf('INSERT INTO %s (%s, %s, %s) VALUES (%s, %s, %s)', + $this->db->quoteIdentifier($table), + $this->db->quoteIdentifier($this->options['string_id_col']), + $this->db->quoteIdentifier($this->options['string_page_id_col']), + implode(', ', $tableCols), + $stringID, + $pageID, + implode(', ', $langData) + ); + } + + // }}} + // {{{ _getUpdateQuery() + + /** + * Build a SQL query to UPDATE a record + * + * @param string $table table name + * @param array &$tableLangs tables containing the languages + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param array &$stringArray array of strings + * + * @return string UPDATE query + * @access private + */ + function _getUpdateQuery($table, &$tableLangs, $stringID, $pageID, &$stringArray) + { + $tableCols = $this->_getLangCols($tableLangs); + $langSet = array(); + foreach ($tableLangs as $lang) { + $langSet[] = $this->db->quoteIdentifier($tableCols[$lang]) . ' = ' . + $this->db->quote($stringArray[$lang]); + } + + return sprintf('UPDATE %s SET %s WHERE %s = %s AND %s = %s', + $this->db->quoteIdentifier($table), + implode(', ', $langSet), + $this->db->quoteIdentifier($this->options['string_id_col']), + $stringID, + $this->db->quoteIdentifier($this->options['string_page_id_col']), + $pageID + ); + } + + // }}} + // {{{ remove() + + /** + * Remove an entry from the strings table. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * + * @return true|PEAR_Error + */ + function remove($stringID, $pageID) + { + $tables = array_unique($this->_getLangTables()); + + $stringID = $this->db->quote($stringID); + // get the tables and skip the non existent ones + $dbTables = $this->db->getListOf('tables'); + foreach ($tables as $table) { + if (!in_array($table, $dbTables)) { + continue; + } + $query = sprintf('DELETE FROM %s WHERE %s = %s AND %s', + $this->db->quoteIdentifier($table), + $this->db->quoteIdentifier($this->options['string_id_col']), + $stringID, + $this->db->quoteIdentifier($this->options['string_page_id_col']) + ); + if (is_null($pageID)) { + $query .= ' IS NULL'; + } else { + $query .= ' = ' . $this->db->quote($pageID); + } + + ++$this->_queries; + $res = $this->db->query($query); + if (PEAR::isError($res)) { + return $res; + } + } + + return true; + } + + // }}} + // {{{ removePage + + /** + * Remove all the strings in the given page/group + * + * @param string $pageID page/group ID + * + * @return mixed true on success, PEAR_Error on failure + */ + function removePage($pageID = null) + { + $tables = array_unique($this->_getLangTables()); + + // get the tables and skip the non existent ones + $dbTables = $this->db->getListOf('tables'); + foreach ($tables as $table) { + if (!in_array($table, $dbTables)) { + continue; + } + $query = sprintf('DELETE FROM %s WHERE %s', + $this->db->quoteIdentifier($table, true), + $this->db->quoteIdentifier($this->options['string_page_id_col']) + ); + if (is_null($pageID)) { + $query .= ' IS NULL'; + } else { + $query .= ' = ' . $this->db->quote($pageID, 'text'); + } + + ++$this->_queries; + $res = $this->db->query($query); + if (PEAR::isError($res)) { + return $res; + } + } + + return true; + } + + // }}} + // {{{ getPageNames() + + /** + * Get a list of all the pageIDs in any table. + * + * @return array + */ + function getPageNames() + { + $pages = array(); + foreach ($this->_getLangTables() as $table) { + $query = sprintf('SELECT DISTINCT %s FROM %s', + $this->db->quoteIdentifier($this->options['string_page_id_col']), + $this->db->quoteIdentifier($table) + ); + ++$this->_queries; + $res = $this->db->getCol($query); + if (PEAR::isError($res)) { + return $res; + } + $pages = array_merge($pages, $res); + } + return array_unique($pages); + } + + // }}} + // {{{ _tableLangs() + + /** + * Get table -> language mapping + * + * The key of the array is the table that a language is stored in; + * the value is an /array/ of languages stored in that table. + * + * @param array $langs Languages to get mapping for + * + * @return array Table -> language mapping + * @access private + * @see Translation2_Container_DB::_getLangTable() + */ + function &_tableLangs($langs) + { + $tables = array(); + foreach ($langs as $lang) { + $table = $this->_getLangTable($lang); + $tables[$table][] = $lang; + } + return $tables; + } + + // }}} + // {{{ _getLangTables() + + /** + * Get tables for languages + * + * This is like _getLangTable(), but it returns an array of the tables for + * multiple languages. + * + * @param array $langs Languages to get tables for + * + * @return array + * @access private + */ + function &_getLangTables($langs = null) + { + $tables = array(); + $langs = !is_array($langs) ? $this->getLangs('ids') : $langs; + foreach ($langs as $lang) { + $tables[] = $this->_getLangTable($lang); + } + $tables = array_unique($tables); + return $tables; + } + + // }}} + // {{{ _getLangCols() + + /** + * Get table columns strings are stored in + * + * This is like _getLangCol(), except it returns an array which contains + * the mapping for multiple languages. + * + * @param array $langs Languages to get mapping for + * + * @return array Language -> column mapping + * @access private + * @see Translation2_Container_DB::_getLangCol() + */ + function &_getLangCols($langs) + { + $cols = array(); + foreach ($langs as $lang) { + $cols[$lang] = $this->_getLangCol($lang); + } + return $cols; + } + + // }}} + // {{{ _recordExists() + + /** + * Check if there's already a record in the table with the + * given (pageID, stringID) pair. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param string $table table name + * + * @return boolean + * @access private + */ + function _recordExists($stringID, $pageID, $table) + { + $stringID = $this->db->quote($stringID, 'text'); + $pageID = is_null($pageID) ? ' IS NULL' : ' = ' . $this->db->quote($pageID); + $query = sprintf('SELECT COUNT(*) FROM %s WHERE %s=%s AND %s%s', + $this->db->quoteIdentifier($table), + $this->db->quoteIdentifier($this->options['string_id_col']), + $stringID, + $this->db->quoteIdentifier($this->options['string_page_id_col']), + $pageID + ); + ++$this->_queries; + $res = $this->db->getOne($query); + if (PEAR::isError($res)) { + return $res; + } + return ($res > 0); + } + + // }}} + // {{{ _filterStringsByTable() + + /** + * Get only the strings for the langs in the given table + * + * @param array $stringArray Associative array with string translations. + * Sample format: array('en' => 'sample', 'it' => 'esempio') + * @param string $table table name + * + * @return array + * @access private + */ + function &_filterStringsByTable($stringArray, $table) + { + $strings = array(); + foreach ($stringArray as $lang => $string) { + if ($table == $this->_getLangTable($lang)) { + $strings[$lang] = $string; + } + } + return $strings; + } + + // }}} + // {{{ _getLangsInTable() + + /** + * Get the languages sharing the given table + * + * @param string $table table name + * + * @return array + */ + function &_getLangsInTable($table) + { + $this->fetchLangs(); // force cache refresh + $langsInTable = array(); + foreach (array_keys($this->langs) as $lang) { + if ($table == $this->_getLangTable($lang)) { + $langsInTable[] = $lang; + } + } + return $langsInTable; + } + + // }}} +} +?> \ No newline at end of file diff --git a/Translation2/Admin/Container/gettext.php b/Translation2/Admin/Container/gettext.php new file mode 100644 index 0000000..d66f55e --- /dev/null +++ b/Translation2/Admin/Container/gettext.php @@ -0,0 +1,640 @@ + + * @author Michael Wallner + * @copyright 2004-2007 Lorenzo Alberton, Michael Wallner + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ + +/** + * require Translation2_Container_gettext class + */ +require_once 'Translation2/Container/gettext.php'; + +/** + * Storage driver for storing/fetching data to/from a gettext file + * + * This storage driver requires the gettext extension + * + * @category Internationalization + * @package Translation2 + * @author Lorenzo Alberton + * @author Michael Wallner + * @copyright 2004-2007 Lorenzo Alberton, Michael Wallner + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ +class Translation2_Admin_Container_gettext extends Translation2_Container_gettext +{ + // {{{ class vars + + var $_bulk = false; + var $_queue = array(); + var $_fields = array('name', 'meta', 'error_text', 'encoding'); + + // }}} + // {{{ addLang() + + /** + * Creates a new entry in the langs_avail .ini file. + * + * @param array $langData language data + * @param string $path path to gettext data dir + * + * @return mixed Returns true on success or PEAR_Error on failure. + */ + function addLang($langData, $path = null) + { + if (!isset($path) || !is_string($path)) { + $path = $this->_domains[$this->options['default_domain']]; + } + + $path .= '/'. $langData['lang_id'] . '/LC_MESSAGES'; + + if (!is_dir($path)) { + include_once 'System.php'; + if (!System::mkdir(array('-p', $path))) { + return $this->raiseError(sprintf( + 'Cannot create new language in path "%s"', $path + ), + TRANSLATION2_ERROR_CANNOT_CREATE_DIR + ); + } + } + + return true; + } + + // }}} + // {{{ addLangToList() + + /** + * Creates a new entry in the langsAvail .ini file. + * If the file doesn't exist yet, it is created. + * + * @param array $langData array('lang_id' => 'en', + * 'name' => 'english', + * 'meta' => 'some meta info', + * 'error_text' => 'not available' + * 'encoding' => 'iso-8859-1', + * ); + * + * @return true|PEAR_Error on failure + */ + function addLangToList($langData) + { + if (PEAR::isError($changed = $this->_updateLangData($langData))) { + return $changed; + } + return $changed ? $this->_writeLangsAvailFile() : true; + } + + // }}} + // {{{ add() + + /** + * Add a new entry in the strings domain. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param array $strings Associative array with string translations. + * Sample format: array('en' => 'sample', 'it' => 'esempio') + * + * @return true|PEAR_Error on failure + */ + function add($stringID, $pageID, $strings) + { + if (!isset($pageID)) { + $pageID = $this->options['default_domain']; + } + + $langs = array_intersect(array_keys($strings), $this->getLangs('ids')); + + if (!count($langs)) { + return true; // really? + } + + if ($this->_bulk) { + foreach ($strings as $lang => $string) { + if (in_array($lang, $langs)) { + $this->_queue['add'][$pageID][$lang][$stringID] = $string; + } + } + return true; + } else { + $add = array(); + foreach ($strings as $lang => $string) { + if (in_array($lang, $langs)) { + $add[$pageID][$lang][$stringID] = $string; + } + } + return $this->_add($add); + } + } + + // }}} + // {{{ remove() + + /** + * Remove an entry from the domain. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * + * @return true|PEAR_Error on failure + */ + function remove($stringID, $pageID) + { + if (!isset($pageID)) { + $pageID = $this->options['default_domain']; + } + + if ($this->_bulk) { + $this->_queue['remove'][$pageID][$stringID] = true; + return true; + } else { + $tmp = array($pageID => array($stringID => true)); + return $this->_remove($tmp); + } + + } + + // }}} + // {{{ removePage + + /** + * Remove all the strings in the given page/group (domain) + * + * @param string $pageID page/group ID + * @param string $path path to gettext data dir + * + * @return mixed true on success, PEAR_Error on failure + */ + function removePage($pageID = null, $path = null) + { + if (!isset($pageID)) { + $pageID = $this->options['default_domain']; + } + + if (!isset($path)) { + if (!empty($this->_domains[$pageID])) { + $path = $this->_domains[$pageID]; + } else { + $path = $this->_domains[$this->options['default_domain']]; + } + } + + if (PEAR::isError($e = $this->_removeDomain($pageID))) { + return $e; + } + + $this->fetchLangs(); + foreach ($this->langs as $langID => $lang) { + $domain_file = $path .'/'. $langID .'/LC_MESSAGES/'. $pageID .'.'; + if (!@unlink($domain_file.'mo') || !@unlink($domain_file.'po')) { + return $this->raiseError('Cannot delete page ' . $pageID. ' (file '.$domain_file.'.*)', + TRANSLATION2_ERROR + ); + } + } + + return true; + } + + // }}} + // {{{ update() + + /** + * Update + * + * Alias for Translation2_Admin_Container_gettext::add() + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param array $strings strings + * + * @return mixed + * @access public + * @see add() + */ + function update($stringID, $pageID, $strings) + { + return $this->add($stringID, $pageID, $strings); + } + + // }}} + // {{{ removeLang() + + /** + * Remove Language + * + * @param string $langID language ID + * @param bool $force (unused) + * + * @return true|PEAR_Error + * @access public + */ + function removeLang($langID, $force = false) + { + include_once 'System.php'; + foreach ((array) $this->_domains as $domain => $path) { + if (is_dir($fp = $path .'/'. $langID)) { + if (PEAR::isError($e = System::rm(array('-rf', $fp))) || !$e) { + return $e ? $e : PEAR::raiseError(sprintf( + 'Could not remove language "%s" from domain "%s" '. + 'in path "%s" (probably insufficient permissions)', + $langID, $domain, $path + ), + TRANSLATION2_ERROR + ); + } + } + } + return true; + } + + // }}} + // {{{ updateLang() + + /** + * Update the lang info in the langs_avail file + * + * @param array $langData language data + * + * @return mixed Returns true on success or PEAR_Error on failure. + * @access public + */ + function updateLang($langData) + { + if (PEAR::isError($changed = $this->_updateLangData($langData))) { + return $changed; + } + return $changed ? $this->_writeLangsAvailFile() : true; + } + + // }}} + // {{{ getPageNames() + + /** + * Get a list of all the domains + * + * @return array + * @access public + */ + function getPageNames() + { + return array_keys($this->_domains); + } + + // }}} + // {{{ begin() + + /** + * Begin + * + * @return void + * @access public + */ + function begin() + { + $this->_bulk = true; + } + + // }}} + // {{{ commit() + + /** + * Commit + * + * @return true|PEAR_Error on failure. + * @access public + */ + function commit() + { + $this->_bulk = false; + if (isset($this->_queue['remove'])) { + if (PEAR::isError($e = $this->_remove($this->_queue['remove']))) { + return $e; + } + } + if (isset($this->_queue['add'])) { + if (PEAR::isError($e = $this->_add($this->_queue['add']))) { + return $e; + } + } + return true; + } + + // }}} + // {{{ _add() + + /** + * Add + * + * @param array &$bulk array('pageID' => array([languages])) + * + * @return true|PEAR_Error on failure. + * @access private + */ + function _add(&$bulk) + { + include_once 'File/Gettext.php'; + $gtFile = &File_Gettext::factory($this->options['file_type']); + $langs = $this->getLangs('array'); + + foreach ((array) $bulk as $pageID => $languages) { + //create the new domain on demand + if (!isset($this->_domains[$pageID])) { + if (PEAR::isError($e = $this->_addDomain($pageID))) { + return $e; + } + } + $path = $this->_domains[$pageID]; + if ($path[strlen($path)-1] != '/' && $path[strlen($path)-1] != '\\') { + $path .= '/'; + } + $file = '/LC_MESSAGES/'. $pageID .'.'. $this->options['file_type']; + + foreach ($languages as $lang => $strings) { + + if (is_file($path . $lang . $file)) { + if (PEAR::isError($e = $gtFile->load($path . $lang . $file))) { + return $e; + } + } + + if (!isset($gtFile->meta['Content-Type'])) { + $gtFile->meta['Content-Type'] = 'text/plain; charset='; + if (isset($langs[$lang]['encoding'])) { + $gtFile->meta['Content-Type'] .= $langs[$lang]['encoding']; + } else { + $gtFile->meta['Content-Type'] .= $this->options['default_encoding']; + } + } + + foreach ($strings as $stringID => $string) { + $gtFile->strings[$stringID] = $string; + } + + if (PEAR::isError($e = $gtFile->save($path . $lang . $file))) { + return $e; + } + + //refresh cache + $this->cachedDomains[$lang][$pageID] = $gtFile->strings; + } + } + + $bulk = null; + return true; + } + + // }}} + // {{{ _remove() + + /** + * Remove + * + * @param array &$bulk array('pageID' => array([languages])) + * + * @return true|PEAR_Error on failure. + * @access private + */ + function _remove(&$bulk) + { + include_once 'File/Gettext.php'; + $gtFile = &File_Gettext::factory($this->options['file_type']); + + foreach ($this->getLangs('ids') as $lang) { + foreach ((array) $bulk as $pageID => $stringIDs) { + $file = sprintf( + '%s/%s/LC_MESSAGES/%s.%s', + $this->_domains[$pageID], + $lang, + $pageID, + $this->options['file_type'] + ); + + if (is_file($file)) { + if (PEAR::isError($e = $gtFile->load($file))) { + return $e; + } + + foreach (array_keys($stringIDs) as $stringID) { + unset($gtFile->strings[$stringID]); + } + + if (PEAR::isError($e = $gtFile->save($file))) { + return $e; + } + + //refresh cache + $this->cachedDomains[$lang][$pageID] = $gtFile->strings; + } + } + } + + $bulk = null; + return true; + } + + // }}} + // {{{ _addDomain() + + /** + * Add the path-to-the-new-domain to the domains-path-INI-file + * + * @param string $pageID domain name + * + * @return true|PEAR_Error on failure + * @access private + */ + function _addDomain($pageID) + { + $domain_path = count($this->_domains) ? reset($this->_domains) : 'locale/'; + + if (!is_resource($f = fopen($this->options['domains_path_file'], 'a'))) { + return $this->raiseError(sprintf( + 'Cannot write to domains path INI file "%s"', + $this->options['domains_path_file'] + ), + TRANSLATION2_ERROR_CANNOT_WRITE_FILE + ); + } + + $CRLF = $this->options['carriage_return']; + + while (true) { + if (@flock($f, LOCK_EX)) { + fwrite($f, $CRLF . $pageID . ' = ' . $domain_path . $CRLF); + @flock($f, LOCK_UN); + fclose($f); + break; + } + } + + $this->_domains[$pageID] = $domain_path; + + return true; + } + + // }}} + // {{{ _removeDomain() + + /** + * Remove the path-to-the-domain from the domains-path-INI-file + * + * @param string $pageID domain name + * + * @return true|PEAR_Error on failure + * @access private + */ + function _removeDomain($pageID) + { + $domain_path = count($this->_domains) ? reset($this->_domains) : 'locale/'; + + if (!is_resource($f = fopen($this->options['domains_path_file'], 'r+'))) { + return $this->raiseError(sprintf( + 'Cannot write to domains path INI file "%s"', + $this->options['domains_path_file'] + ), + TRANSLATION2_ERROR_CANNOT_WRITE_FILE + ); + } + + $CRLF = $this->options['carriage_return']; + + while (true) { + if (@flock($f, LOCK_EX)) { + $pages = file($this->options['domains_path_file']); + foreach ($pages as $page) { + if (preg_match('/^'.$pageID.'\s*=/', $page)) { + //skip + continue; + } + fwrite($f, $page . $CRLF); + } + fflush($f); + ftruncate($f, ftell($f)); + @flock($f, LOCK_UN); + fclose($f); + break; + } + } + + unset($this->_domains[$pageID]); + + return true; + } + + // }}} + // {{{ _writeLangsAvailFile() + + /** + * Write the langs_avail INI file + * + * @return true|PEAR_Error on failure. + * @access private + */ + function _writeLangsAvailFile() + { + if (PEAR::isError($langs = $this->getLangs())) { + return $langs; + } + + if (!is_resource($f = fopen($this->options['langs_avail_file'], 'w'))) { + return $this->raiseError(sprintf( + 'Cannot write to available langs INI file "%s"', + $this->options['langs_avail_file'] + ), + TRANSLATION2_ERROR_CANNOT_WRITE_FILE + ); + } + $CRLF = $this->options['carriage_return']; + + @flock($f, LOCK_EX); + + foreach ($langs as $id => $data) { + fwrite($f, '['. $id .']'. $CRLF); + foreach ($this->_fields as $k) { + if (isset($data[$k])) { + fwrite($f, $k . ' = ' . $data[$k] . $CRLF); + } + } + fwrite($f, $CRLF); + } + + @flock($f, LOCK_UN); + fclose($f); + return true; + } + + // }}} + // {{{ _updateLangData() + + /** + * Update Lang Data + * + * @param array $langData language data + * + * @return true|PEAR_Error on failure. + * @access private + */ + function _updateLangData($langData) + { + if (PEAR::isError($langs = $this->getLangs())) { + return $langs; + } + + $lang = &$langs[$langData['lang_id']]; + $changed = false; + foreach ($this->_fields as $k) { + if ( isset($langData[$k]) && + (!isset($lang[$k]) || $langData[$k] != $lang[$k])) { + $lang[$k] = $langData[$k]; + $changed = true; + } + } + + if ($changed) { + $lang['id'] = $langData['lang_id']; + $this->langs = $langs; + } + return $changed; + } + + // }}} +} +?> \ No newline at end of file diff --git a/Translation2/Admin/Container/mdb.php b/Translation2/Admin/Container/mdb.php new file mode 100644 index 0000000..d010ae5 --- /dev/null +++ b/Translation2/Admin/Container/mdb.php @@ -0,0 +1,714 @@ + + * @author Ian Eure + * @copyright 2004-2007 Lorenzo Alberton, Ian Eure + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ + +/** + * require Translation2_Container_mdb class + */ +require_once 'Translation2/Container/mdb.php'; + +/** + * Storage driver for storing/fetching data to/from a database + * + * This storage driver can use all databases which are supported + * by the PEAR::MDB abstraction layer to store and fetch data. + * + * @category Internationalization + * @package Translation2 + * @author Lorenzo Alberton + * @author Ian Eure + * @copyright 2004-2007 Lorenzo Alberton, Ian Eure + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @link http://pear.php.net/package/Translation2 + */ +class Translation2_Admin_Container_mdb extends Translation2_Container_mdb +{ + // {{{ addLang() + + /** + * Creates a new table to store the strings in this language. + * If the table is shared with other langs, it is ALTERed to + * hold strings in this lang too. + * + * @param array $langData language data + * @param array $options options + * + * @return true|PEAR_Error + */ + function addLang($langData, $options = array()) + { + $tables = $this->db->listTables(); + if (PEAR::isError($tables)) { + return $tables; + } + + $lang_col = $this->_getLangCol($langData['lang_id']); + + if (in_array($langData['table_name'], $tables)) { + //table exists + $query = sprintf('ALTER TABLE %s ADD %s%s TEXT', + $this->db->quoteIdentifier($langData['table_name']), + $this->db->phptype == 'mssql' ? '' : 'COLUMN ', + $this->db->quoteIdentifier($lang_col) + ); + ++$this->_queries; + return $this->db->query($query); + } + + //table does not exist + $queries = array(); + $queries[] = sprintf('CREATE TABLE %s ( ' + .'%s VARCHAR(%d) default NULL, ' + .'%s TEXT NOT NULL, ' + .'%s TEXT )', + $this->db->quoteIdentifier($langData['table_name']), + $this->db->quoteIdentifier($this->options['string_page_id_col']), + (int)$this->options['string_page_id_col_length'], + $this->db->quoteIdentifier($this->options['string_id_col']), + $this->db->quoteIdentifier($lang_col) + ); + $mysqlClause = ($this->db->phptype == 'mysql') ? '(255)' : ''; + + $index_name = sprintf('%s_%s_%s_index', + $langData['table_name'], + $this->options['string_page_id_col'], + $this->options['string_id_col'] + ); + $queries[] = sprintf('CREATE UNIQUE INDEX %s ON %s (%s, %s%s)', + $this->db->quoteIdentifier($index_name), + $this->db->quoteIdentifier($langData['table_name']), + $this->db->quoteIdentifier($this->options['string_page_id_col']), + $this->db->quoteIdentifier($this->options['string_id_col']), + $mysqlClause + ); + + $index_name = sprintf('%s_%s_index', + $langData['table_name'], + $this->options['string_page_id_col'] + ); + $queries[] = sprintf('CREATE INDEX %s ON %s (%s)', + $this->db->quoteIdentifier($index_name), + $this->db->quoteIdentifier($langData['table_name']), + $this->db->quoteIdentifier($this->options['string_page_id_col']) + ); + + $index_name = sprintf('%s_%s_index', + $langData['table_name'], + $this->options['string_id_col'] + ); + $queries[] = sprintf('CREATE INDEX %s ON %s (%s%s)', + $this->db->quoteIdentifier($index_name), + $this->db->quoteIdentifier($langData['table_name']), + $this->db->quoteIdentifier($this->options['string_id_col']), + $mysqlClause + ); + + foreach ($queries as $query) { + ++$this->_queries; + $res = $this->db->query($query); + if (PEAR::isError($res)) { + return $res; + } + } + return true; + } + + // }}} + // {{{ addLangToList() + + /** + * Creates a new entry in the langsAvail table. + * If the table doesn't exist yet, it is created. + * + * @param array $langData array('lang_id' => 'en', + * 'table_name' => 'i18n', + * 'name' => 'english', + * 'meta' => 'some meta info', + * 'error_text' => 'not available', + * 'encoding' => 'iso-8859-1'); + * + * @return true|PEAR_Error + */ + function addLangToList($langData) + { + $tables = $this->db->listTables(); + if (PEAR::isError($tables)) { + return $tables; + } + + if (!in_array($this->options['langs_avail_table'], $tables)) { + $queries = array(); + $queries[] = sprintf('CREATE TABLE %s (' + .'%s VARCHAR(16), ' + .'%s VARCHAR(200), ' + .'%s TEXT, ' + .'%s VARCHAR(250), ' + .'%s VARCHAR(16) )', + $this->db->quoteIdentifier($this->options['langs_avail_table']), + $this->db->quoteIdentifier($this->options['lang_id_col']), + $this->db->quoteIdentifier($this->options['lang_name_col']), + $this->db->quoteIdentifier($this->options['lang_meta_col']), + $this->db->quoteIdentifier($this->options['lang_errmsg_col']), + $this->db->quoteIdentifier($this->options['lang_encoding_col']) + ); + $queries[] = sprintf('CREATE UNIQUE INDEX %s_%s_index ON %s (%s)', + $this->options['langs_avail_table'], + $this->options['lang_id_col'], + $this->db->quoteIdentifier($this->options['langs_avail_table']), + $this->db->quoteIdentifier($this->options['lang_id_col']) + ); + + foreach ($queries as $query) { + ++$this->_queries; + $res = $this->db->query($query); + if (PEAR::isError($res)) { + return $res; + } + } + } + + $query = sprintf('INSERT INTO %s (%s, %s, %s, %s, %s) VALUES (%s, %s, %s, %s, %s)', + $this->db->quoteIdentifier($this->options['langs_avail_table']), + $this->db->quoteIdentifier($this->options['lang_id_col']), + $this->db->quoteIdentifier($this->options['lang_name_col']), + $this->db->quoteIdentifier($this->options['lang_meta_col']), + $this->db->quoteIdentifier($this->options['lang_errmsg_col']), + $this->db->quoteIdentifier($this->options['lang_encoding_col']), + $this->db->getTextValue($langData['lang_id']), + $this->db->getTextValue($langData['name']), + $this->db->getTextValue($langData['meta']), + $this->db->getTextValue($langData['error_text']), + $this->db->getTextValue($langData['encoding']) + ); + + ++$this->_queries; + $success = $this->db->query($query); + $this->options['strings_tables'][$langData['lang_id']] = $langData['table_name']; + return $success; + } + + // }}} + // {{{ removeLang() + + /** + * Remove the lang from the langsAvail table and drop the strings table. + * If the strings table holds other langs and $force==false, then + * only the lang column is dropped. If $force==true the whole + * table is dropped without any check + * + * @param string $langID language ID + * @param boolean $force if true, drop the whole table without further checks + * + * @return true|PEAR_Error + */ + function removeLang($langID, $force) + { + //remove from langsAvail + $query = sprintf('DELETE FROM %s WHERE %s = %s', + $this->db->quoteIdentifier($this->options['langs_avail_table']), + $this->db->quoteIdentifier($this->options['lang_id_col']), + $this->db->getTextValue($langID) + ); + ++$this->_queries; + $res = $this->db->query($query); + if (PEAR::isError($res)) { + return $res; + } + + $lang_table = $this->_getLangTable($langID); + if ($force) { + //remove the whole table + ++$this->_queries; + return $this->db->query('DROP TABLE ' . $this->db->quoteIdentifier($lang_table)); + } + + //drop only the column for this lang + $query = sprintf('ALTER TABLE %s DROP COLUMN %s', + $this->db->quoteIdentifier($lang_table), + $this->db->quoteIdentifier($this->_getLangCol($langID)) + ); + ++$this->_queries; + return $this->db->query($query); + } + + // }}} + // {{{ updateLang() + + /** + * Update the lang info in the langsAvail table + * + * @param array $langData array of language data + * + * @return true|PEAR_Error + */ + function updateLang($langData) + { + $allFields = array( + //'lang_id' => 'lang_id_col', + 'name' => 'lang_name_col', + 'meta' => 'lang_meta_col', + 'error_text' => 'lang_errmsg_col', + 'encoding' => 'lang_encoding_col', + ); + $updateFields = array_keys($langData); + $langSet = array(); + foreach ($allFields as $field => $col) { + if (in_array($field, $updateFields)) { + $langSet[] = $this->db->quoteIdentifier($this->options[$col]) . ' = ' . + $this->db->getTextValue($langData[$field]); + } + } + $query = sprintf('UPDATE %s SET %s WHERE %s=%s', + $this->db->quoteIdentifier($this->options['langs_avail_table']), + implode(', ', $langSet), + $this->db->quoteIdentifier($this->options['lang_id_col']), + $this->db->getTextValue($langData['lang_id']) + ); + + ++$this->_queries; + $success = $this->db->query($query); + $this->fetchLangs(); //update memory cache + return $success; + } + + // }}} + // {{{ add() + + /** + * Add a new entry in the strings table. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param array $stringArray Associative array with string translations. + * Sample format: array('en' => 'sample', 'it' => 'esempio') + * + * @return true|PEAR_Error + */ + function add($stringID, $pageID, $stringArray) + { + $langs = array_intersect( + array_keys($stringArray), + $this->getLangs('ids') + ); + + if (!count($langs)) { + //return error: no valid lang provided + return true; + } + + // Langs may be in different tables - we need to split up queries along + // table lines, so we can keep DB traffic to a minimum. + + $unquoted_stringID = $stringID; + $unquoted_pageID = $pageID; + $stringID = $this->db->getTextValue($stringID); + $pageID = is_null($pageID) ? 'NULL' : $this->db->getTextValue($pageID); + // Loop over the tables we need to insert into. + foreach ($this->_tableLangs($langs) as $table => $tableLangs) { + $exists = $this->_recordExists($unquoted_stringID, $unquoted_pageID, $table); + if (PEAR::isError($exists)) { + return $exists; + } + $func = $exists ? '_getUpdateQuery' : '_getInsertQuery'; + $query = $this->$func($table, $tableLangs, $stringID, $pageID, $stringArray); + + ++$this->_queries; + $res = $this->db->query($query); + if (PEAR::isError($res)) { + return $res; + } + } + + return true; + } + + // }}} + // {{{ update() + + /** + * Update an existing entry in the strings table. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param array $stringArray Associative array with string translations. + * Sample format: array('en' => 'sample', 'it' => 'esempio') + * + * @return true|PEAR_Error + */ + function update($stringID, $pageID, $stringArray) + { + return $this->add($stringID, $pageID, $stringArray); + } + + // }}} + // {{{ _getInsertQuery() + + /** + * Build a SQL query to INSERT a record + * + * @param string $table table name + * @param array &$tableLangs tables containing the languages + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param array &$stringArray array of strings + * + * @return string INSERT query + * @access private + */ + function _getInsertQuery($table, &$tableLangs, $stringID, $pageID, &$stringArray) + { + $tableCols = $this->_getLangCols($tableLangs); + $langData = array(); + foreach ($tableLangs as $lang) { + $langData[$lang] = $this->db->getTextValue($stringArray[$lang]); + } + foreach (array_keys($tableCols) as $k) { + $tableCols[$k] = $this->db->quoteIdentifier($tableCols[$k]); + } + + return sprintf('INSERT INTO %s (%s, %s, %s) VALUES (%s, %s, %s)', + $this->db->quoteIdentifier($table), + $this->db->quoteIdentifier($this->options['string_id_col']), + $this->db->quoteIdentifier($this->options['string_page_id_col']), + implode(', ', $tableCols), + $stringID, + $pageID, + implode(', ', $langData) + ); + } + + // }}} + // {{{ _getUpdateQuery() + + /** + * Build a SQL query to UPDATE a record + * + * @param string $table table name + * @param array &$tableLangs tables containing the languages + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param array &$stringArray array of strings + * + * @return string UPDATE query + * @access private + */ + function _getUpdateQuery($table, &$tableLangs, $stringID, $pageID, &$stringArray) + { + $tableCols = $this->_getLangCols($tableLangs); + $langSet = array(); + foreach ($tableLangs as $lang) { + $langSet[] = $this->db->quoteIdentifier($tableCols[$lang]) . ' = ' . + $this->db->getTextValue($stringArray[$lang]); + } + + return sprintf('UPDATE %s SET %s WHERE %s = %s AND %s = %s', + $this->db->quoteIdentifier($table), + implode(', ', $langSet), + $this->db->quoteIdentifier($this->options['string_id_col']), + $stringID, + $this->db->quoteIdentifier($this->options['string_page_id_col']), + $pageID + ); + } + + // }}} + // {{{ remove() + + /** + * Remove an entry from the strings table. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * + * @return boolean|PEAR_Error + */ + function remove($stringID, $pageID) + { + $tables = array_unique($this->_getLangTables()); + + $stringID = $this->db->getTextValue($stringID); + // get the tables and skip the non existent ones + $dbTables = $this->db->listTables(); + foreach ($tables as $table) { + if (!in_array($table, $dbTables)) { + continue; + } + $query = sprintf('DELETE FROM %s WHERE %s = %s AND %s', + $this->db->quoteIdentifier($table), + $this->db->quoteIdentifier($this->options['string_id_col']), + $stringID, + $this->db->quoteIdentifier($this->options['string_page_id_col']) + ); + if (is_null($pageID)) { + $query .= ' IS NULL'; + } else { + $query .= ' = ' . $this->db->getTextValue($pageID); + } + + ++$this->_queries; + $res = $this->db->query($query); + if (PEAR::isError($res)) { + return $res; + } + } + + return true; + } + + // }}} + // {{{ removePage + + /** + * Remove all the strings in the given page/group + * + * @param string $pageID page/group ID + * + * @return mixed true on success, PEAR_Error on failure + */ + function removePage($pageID = null) + { + $tables = array_unique($this->_getLangTables()); + + // get the tables and skip the non existent ones + $dbTables = $this->db->listTables(); + foreach ($tables as $table) { + if (!in_array($table, $dbTables)) { + continue; + } + $query = sprintf('DELETE FROM %s WHERE %s', + $this->db->quoteIdentifier($table, true), + $this->db->quoteIdentifier($this->options['string_page_id_col'], true) + ); + if (is_null($pageID)) { + $query .= ' IS NULL'; + } else { + $query .= ' = ' . $this->db->getTextValue($pageID); + } + + ++$this->_queries; + $res = $this->db->query($query); + if (PEAR::isError($res)) { + return $res; + } + } + + return true; + } + + // }}} + // {{{ getPageNames() + + /** + * Get a list of all the pageIDs in any table. + * + * @return array + */ + function getPageNames() + { + $pages = array(); + foreach ($this->_getLangTables() as $table) { + $query = sprintf('SELECT DISTINCT %s FROM %s', + $this->db->quoteIdentifier($this->options['string_page_id_col']), + $this->db->quoteIdentifier($table) + ); + ++$this->_queries; + $res = $this->db->getCol($query); + if (PEAR::isError($res)) { + return $res; + } + $pages = array_merge($pages, $res); + } + return array_unique($pages); + } + + // }}} + // {{{ _tableLangs() + + /** + * Get table -> language mapping + * + * The key of the array is the table that a language is stored in; + * the value is an /array/ of languages stored in that table. + * + * @param array $langs Languages to get mapping for + * + * @return array Table -> language mapping + * @access private + * @see Translation2_Container_MDB::_getLangTable() + */ + function &_tableLangs($langs) + { + $tables = array(); + foreach ($langs as $lang) { + $table = $this->_getLangTable($lang); + $tables[$table][] = $lang; + } + return $tables; + } + + // }}} + // {{{ _getLangTables() + + /** + * Get tables for languages + * + * This is like _getLangTable(), but it returns an array of the tables for + * multiple languages. + * + * @param array $langs Languages to get tables for + * + * @return array + * @access private + */ + function &_getLangTables($langs = null) + { + $tables = array(); + $langs = !is_array($langs) ? $this->getLangs('ids') : $langs; + foreach ($langs as $lang) { + $tables[] = $this->_getLangTable($lang); + } + $tables = array_unique($tables); + return $tables; + } + + // }}} + // {{{ _getLangCols() + + /** + * Get table columns strings are stored in + * + * This is like _getLangCol(), except it returns an array which contains + * the mapping for multiple languages. + * + * @param array $langs Languages to get mapping for + * + * @return array Language -> column mapping + * @access private + * @see Translation2_Container_MDB::_getLangCol() + */ + function &_getLangCols($langs) + { + $cols = array(); + foreach ($langs as $lang) { + $cols[$lang] = $this->_getLangCol($lang); + } + return $cols; + } + + // }}} + // {{{ _recordExists() + + /** + * Check if there's already a record in the table with the + * given (pageID, stringID) pair. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param string $table table name + * + * @return boolean + * @access private + */ + function _recordExists($stringID, $pageID, $table) + { + $stringID = $this->db->getTextValue($stringID); + $pageID = is_null($pageID) ? ' IS NULL' : ' = ' . $this->db->getTextValue($pageID); + $query = sprintf('SELECT COUNT(*) FROM %s WHERE %s=%s AND %s%s', + $this->db->quoteIdentifier($table), + $this->db->quoteIdentifier($this->options['string_id_col']), + $stringID, + $this->db->quoteIdentifier($this->options['string_page_id_col']), + $pageID + ); + ++$this->_queries; + $res = $this->db->getOne($query); + if (PEAR::isError($res)) { + return $res; + } + return ($res > 0); + } + + // }}} + // {{{ _filterStringsByTable() + + /** + * Get only the strings for the langs in the given table + * + * @param array $stringArray Associative array with string translations. + * Sample format: array('en' => 'sample', 'it' => 'esempio') + * @param string $table table name + * + * @return array strings + * @access private + */ + function &_filterStringsByTable($stringArray, $table) + { + $strings = array(); + foreach ($stringArray as $lang => $string) { + if ($table == $this->_getLangTable($lang)) { + $strings[$lang] = $string; + } + } + return $strings; + } + + // }}} + // {{{ _getLangsInTable() + + /** + * Get the languages sharing the given table + * + * @param string $table table name + * + * @return array + */ + function &_getLangsInTable($table) + { + $this->fetchLangs(); // force cache refresh + $langsInTable = array(); + foreach (array_keys($this->langs) as $lang) { + if ($table == $this->_getLangTable($lang)) { + $langsInTable[] = $lang; + } + } + return $langsInTable; + } + + // }}} +} +?> \ No newline at end of file diff --git a/Translation2/Admin/Container/mdb2.php b/Translation2/Admin/Container/mdb2.php new file mode 100644 index 0000000..4d3da17 --- /dev/null +++ b/Translation2/Admin/Container/mdb2.php @@ -0,0 +1,758 @@ + + * @copyright 2004-2007 Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ + +/** + * require Translation2_Container_mdb2 class + */ +require_once 'Translation2/Container/mdb2.php'; + +/** + * Storage driver for storing/fetching data to/from a database + * + * This storage driver can use all databases which are supported + * by the PEAR::MDB2 abstraction layer to store and fetch data. + * + * @category Internationalization + * @package Translation2 + * @author Lorenzo Alberton + * @copyright 2004-2007 Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @link http://pear.php.net/package/Translation2 + */ +class Translation2_Admin_Container_mdb2 extends Translation2_Container_mdb2 +{ + // {{{ + + /** + * Fetch the table names from the db + * + * @access private + * @return array|PEAR_Error + */ + function _fetchTableNames() + { + $this->db->loadModule('Manager'); + return $this->db->manager->listTables(); + } + + // }}} + // {{{ addLang() + + /** + * Creates a new table to store the strings in this language. + * If the table is shared with other langs, it is ALTERed to + * hold strings in this lang too. + * + * @param array $langData array('lang_id' => 'en', + * 'table_name' => 'i18n', + * 'name' => 'english', + * 'meta' => 'some meta info', + * 'error_text' => 'not available'); + * @param array $options array('charset' => 'utf8', + * 'collation' => 'utf8_general_ci'); + * + * @return true|PEAR_Error + */ + function addLang($langData, $options = array()) + { + $tables = $this->_fetchTableNames(); + if (PEAR::isError($tables)) { + return $tables; + } + + $lang_col = $this->_getLangCol($langData['lang_id']); + $charset = empty($options['charset']) ? null : $options['charset']; + $collation = empty($options['collation']) ? null : $options['collation']; + $this->db->loadModule('Manager'); + + if (in_array($langData['table_name'], $tables)) { + //table exists + $table_changes = array( + 'add' => array( + $lang_col => array( + 'type' => 'text', + 'charset' => $charset, + 'collation' => $collation, + ) + ) + ); + ++$this->_queries; + return $this->db->manager->alterTable($langData['table_name'], $table_changes, false); + } + + //table does not exist + $table_definition = array( + $this->options['string_page_id_col'] => array( + 'type' => 'text', + 'length' => $this->options['string_page_id_col_length'], + 'default' => null, + 'charset' => $charset, + 'collation' => $collation, + ), + $this->options['string_id_col'] => array( + 'type' => 'text', + 'notnull' => 1, + 'charset' => $charset, + 'collation' => $collation, + ), + $lang_col => array( + 'type' => 'text', + 'charset' => $charset, + 'collation' => $collation, + ), + ); + ++$this->_queries; + $table_options = array( + 'charset' => $charset, + 'collate' => $collation, + ); + $res = $this->db->manager->createTable($langData['table_name'], $table_definition, $table_options); + if (PEAR::isError($res)) { + return $res; + } + $mysqlClause = ($this->db->phptype == 'mysql') ? '(255)' : ''; + + $constraint_name = $langData['table_name'] + .'_'. $this->options['string_page_id_col'] + .'_'. $this->options['string_id_col']; + $constraint_definition = array( + 'fields' => array( + $this->options['string_page_id_col'] => array(), + $this->options['string_id_col'].$mysqlClause => array(), + ), + 'unique' => true, + ); + ++$this->_queries; + $res = $this->db->manager->createConstraint($langData['table_name'], $constraint_name, $constraint_definition); + if (PEAR::isError($res)) { + return $res; + } + + $index_name = $langData['table_name'] .'_'. $this->options['string_page_id_col']; + $index_definition = array( + 'fields' => array($this->options['string_page_id_col'] => array()) + ); + ++$this->_queries; + $res = $this->db->manager->createIndex($langData['table_name'], $index_name, $index_definition); + if (PEAR::isError($res)) { + return $res; + } + + $index_name = $langData['table_name'] .'_'. $this->options['string_id_col']; + $index_definition = array( + 'fields' => array($this->options['string_id_col'] => array('length' => 255)) + ); + ++$this->_queries; + $res = $this->db->manager->createIndex($langData['table_name'], $index_name, $index_definition); + if (PEAR::isError($res)) { + return $res; + } + + return true; + } + + // }}} + // {{{ addLangToList() + + /** + * Creates a new entry in the langsAvail table. + * If the table doesn't exist yet, it is created. + * + * @param array $langData array('lang_id' => 'en', + * 'table_name' => 'i18n', + * 'name' => 'english', + * 'meta' => 'some meta info', + * 'error_text' => 'not available', + * 'encoding' => 'iso-8859-1'); + * + * @return true|PEAR_Error + */ + function addLangToList($langData) + { + $tables = $this->_fetchTableNames(); + if (PEAR::isError($tables)) { + return $tables; + } + + if (!in_array($this->options['langs_avail_table'], $tables)) { + $queries = array(); + $queries[] = sprintf('CREATE TABLE %s (' + .'%s VARCHAR(16), ' + .'%s VARCHAR(200), ' + .'%s TEXT, ' + .'%s VARCHAR(250), ' + .'%s VARCHAR(16) )', + $this->db->quoteIdentifier($this->options['langs_avail_table'], true), + $this->db->quoteIdentifier($this->options['lang_id_col'], true), + $this->db->quoteIdentifier($this->options['lang_name_col'], true), + $this->db->quoteIdentifier($this->options['lang_meta_col'], true), + $this->db->quoteIdentifier($this->options['lang_errmsg_col'], true), + $this->db->quoteIdentifier($this->options['lang_encoding_col'], true) + ); + $queries[] = sprintf('CREATE UNIQUE INDEX %s_%s_index ON %s (%s)', + $this->db->quoteIdentifier($this->options['langs_avail_table'], true), + $this->db->quoteIdentifier($this->options['lang_id_col'], true), + $this->db->quoteIdentifier($this->options['langs_avail_table'], true), + $this->db->quoteIdentifier($this->options['lang_id_col'], true) + ); + + foreach ($queries as $query) { + ++$this->_queries; + $res = $this->db->exec($query); + if (PEAR::isError($res)) { + return $res; + } + } + } + + $query = sprintf('INSERT INTO %s (%s, %s, %s, %s, %s) VALUES (%s, %s, %s, %s, %s)', + $this->db->quoteIdentifier($this->options['langs_avail_table'], true), + $this->db->quoteIdentifier($this->options['lang_id_col'], true), + $this->db->quoteIdentifier($this->options['lang_name_col'], true), + $this->db->quoteIdentifier($this->options['lang_meta_col'], true), + $this->db->quoteIdentifier($this->options['lang_errmsg_col'], true), + $this->db->quoteIdentifier($this->options['lang_encoding_col'], true), + $this->db->quote($langData['lang_id']), + $this->db->quote($langData['name']), + $this->db->quote($langData['meta']), + $this->db->quote($langData['error_text']), + $this->db->quote($langData['encoding']) + ); + + ++$this->_queries; + $res = $this->db->exec($query); + $this->options['strings_tables'][$langData['lang_id']] = $langData['table_name']; + if (PEAR::isError($res)) { + return $res; + } + return true; + } + + // }}} + // {{{ removeLang() + + /** + * Remove the lang from the langsAvail table and drop the strings table. + * If the strings table holds other langs and $force==false, then + * only the lang column is dropped. If $force==true the whole + * table is dropped without any check + * + * @param string $langID language ID + * @param boolean $force if true, the whole table is dropped without checks + * + * @return true|PEAR_Error + */ + function removeLang($langID, $force) + { + //remove from langsAvail + $query = sprintf('DELETE FROM %s WHERE %s = %s', + $this->db->quoteIdentifier($this->options['langs_avail_table'], true), + $this->db->quoteIdentifier($this->options['lang_id_col'], true), + $this->db->quote($langID, 'text') + ); + ++$this->_queries; + $res = $this->db->exec($query); + if (PEAR::isError($res)) { + return $res; + } + + $this->db->loadModule('Manager'); + $lang_table = $this->_getLangTable($langID); + if ($force) { + //remove the whole table + ++$this->_queries; + return $this->db->manager->dropTable($lang_table); + } + + //drop only the column for this lang + $table_changes = array( + 'remove' => array($this->_getLangCol($langID) => array()) + ); + ++$this->_queries; + return $this->db->manager->alterTable($lang_table, $table_changes, false); + } + + // }}} + // {{{ updateLang() + + /** + * Update the lang info in the langsAvail table + * + * @param array $langData language data + * + * @return true|PEAR_Error + */ + function updateLang($langData) + { + $allFields = array( + //'lang_id' => 'lang_id_col', + 'name' => 'lang_name_col', + 'meta' => 'lang_meta_col', + 'error_text' => 'lang_errmsg_col', + 'encoding' => 'lang_encoding_col', + ); + $updateFields = array_keys($langData); + $langSet = array(); + foreach ($allFields as $field => $col) { + if (in_array($field, $updateFields)) { + $langSet[] = $this->db->quoteIdentifier($this->options[$col], true) . ' = ' . + $this->db->quote($langData[$field]); + } + } + $query = sprintf('UPDATE %s SET %s WHERE %s=%s', + $this->db->quoteIdentifier($this->options['langs_avail_table'], true), + implode(', ', $langSet), + $this->db->quoteIdentifier($this->options['lang_id_col'], true), + $this->db->quote($langData['lang_id']) + ); + + ++$this->_queries; + $res = $this->db->exec($query); + $this->fetchLangs(); //update memory cache + if (PEAR::isError($res)) { + return $res; + } + return true; + } + + // }}} + // {{{ add() + + /** + * Add a new entry in the strings table. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param array $stringArray Associative array with string translations. + * Sample format: array('en' => 'sample', 'it' => 'esempio') + * + * @return true|PEAR_Error + */ + function add($stringID, $pageID, $stringArray) + { + $langs = array_intersect( + array_keys($stringArray), + $this->getLangs('ids') + ); + + if (!count($langs)) { + //return error: no valid lang provided + return true; + } + + // Langs may be in different tables - we need to split up queries along + // table lines, so we can keep DB traffic to a minimum. + + $unquoted_stringID = $stringID; + $unquoted_pageID = $pageID; + $stringID = $this->db->quote($stringID, 'text'); + $pageID = is_null($pageID) ? 'NULL' : $this->db->quote($pageID, 'text'); + // Loop over the tables we need to insert into. + foreach ($this->_tableLangs($langs) as $table => $tableLangs) { + $exists = $this->_recordExists($unquoted_stringID, $unquoted_pageID, $table); + if (PEAR::isError($exists)) { + return $exists; + } + $func = $exists ? '_getUpdateQuery' : '_getInsertQuery'; + $query = $this->$func($table, $tableLangs, $stringID, $pageID, $stringArray); + + ++$this->_queries; + $res = $this->db->exec($query); + if (PEAR::isError($res)) { + return $res; + } + } + + return true; + } + + // }}} + // {{{ update() + + /** + * Update an existing entry in the strings table. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param array $stringArray Associative array with string translations. + * Sample format: array('en' => 'sample', 'it' => 'esempio') + * + * @return true|PEAR_Error + */ + function update($stringID, $pageID, $stringArray) + { + return $this->add($stringID, $pageID, $stringArray); + } + + // }}} + // {{{ _getInsertQuery() + + /** + * Build a SQL query to INSERT a record + * + * @param string $table table name + * @param array &$tableLangs tables containing the languages + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param array &$stringArray array of strings + * + * @return string INSERT query + * @access private + */ + function _getInsertQuery($table, &$tableLangs, $stringID, $pageID, &$stringArray) + { + $tableCols = $this->_getLangCols($tableLangs); + $langData = array(); + foreach ($tableLangs as $lang) { + $langData[$lang] = $this->db->quote($stringArray[$lang], 'text'); + } + foreach (array_keys($tableCols) as $k) { + $tableCols[$k] = $this->db->quoteIdentifier($tableCols[$k], true); + } + + return sprintf('INSERT INTO %s (%s, %s, %s) VALUES (%s, %s, %s)', + $this->db->quoteIdentifier($table, true), + $this->db->quoteIdentifier($this->options['string_id_col'], true), + $this->db->quoteIdentifier($this->options['string_page_id_col'], true), + implode(', ', $tableCols), + $stringID, + $pageID, + implode(', ', $langData) + ); + } + + // }}} + // {{{ _getUpdateQuery() + + /** + * Build a SQL query to UPDATE a record + * + * @param string $table table name + * @param array &$tableLangs tables containing the languages + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param array &$stringArray array of strings + * + * @return string UPDATE query + * @access private + */ + function _getUpdateQuery($table, &$tableLangs, $stringID, $pageID, &$stringArray) + { + $tableCols = $this->_getLangCols($tableLangs); + $langSet = array(); + foreach ($tableLangs as $lang) { + $langSet[] = $this->db->quoteIdentifier($tableCols[$lang], true) . ' = ' . + $this->db->quote($stringArray[$lang], 'text'); + } + + return sprintf('UPDATE %s SET %s WHERE %s = %s AND %s = %s', + $this->db->quoteIdentifier($table, true), + implode(', ', $langSet), + $this->db->quoteIdentifier($this->options['string_id_col'], true), + $stringID, + $this->db->quoteIdentifier($this->options['string_page_id_col'], true), + $pageID + ); + } + + // }}} + // {{{ remove() + + /** + * Remove an entry from the strings table. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * + * @return mixed true on success, PEAR_Error on failure + */ + function remove($stringID, $pageID) + { + $tables = array_unique($this->_getLangTables()); + + $stringID = $this->db->quote($stringID, 'text'); + // get the tables and skip the non existent ones + $dbTables = $this->_fetchTableNames(); + foreach ($tables as $table) { + if (!in_array($table, $dbTables)) { + continue; + } + $query = sprintf('DELETE FROM %s WHERE %s = %s AND %s', + $this->db->quoteIdentifier($table, true), + $this->db->quoteIdentifier($this->options['string_id_col'], true), + $stringID, + $this->db->quoteIdentifier($this->options['string_page_id_col'], true) + ); + if (is_null($pageID)) { + $query .= ' IS NULL'; + } else { + $query .= ' = ' . $this->db->quote($pageID, 'text'); + } + + ++$this->_queries; + $res = $this->db->exec($query); + if (PEAR::isError($res)) { + return $res; + } + } + + return true; + } + + // }}} + // {{{ removePage + + /** + * Remove all the strings in the given page/group + * + * @param string $pageID page/group ID + * + * @return mixed true on success, PEAR_Error on failure + */ + function removePage($pageID = null) + { + $tables = array_unique($this->_getLangTables()); + + // get the tables and skip the non existent ones + $dbTables = $this->_fetchTableNames(); + foreach ($tables as $table) { + if (!in_array($table, $dbTables)) { + continue; + } + $query = sprintf('DELETE FROM %s WHERE %s', + $this->db->quoteIdentifier($table, true), + $this->db->quoteIdentifier($this->options['string_page_id_col'], true) + ); + if (is_null($pageID)) { + $query .= ' IS NULL'; + } else { + $query .= ' = ' . $this->db->quote($pageID, 'text'); + } + + ++$this->_queries; + $res = $this->db->exec($query); + if (PEAR::isError($res)) { + return $res; + } + } + + return true; + } + + // }}} + // {{{ getPageNames() + + /** + * Get a list of all the pageIDs in any table. + * + * @return array + */ + function getPageNames() + { + $pages = array(); + foreach ($this->_getLangTables() as $table) { + $query = sprintf('SELECT DISTINCT %s FROM %s', + $this->db->quoteIdentifier($this->options['string_page_id_col'], true), + $this->db->quoteIdentifier($table, true) + ); + ++$this->_queries; + $res = $this->db->queryCol($query); + if (PEAR::isError($res)) { + return $res; + } + $pages = array_merge($pages, $res); + } + return array_unique($pages); + } + + // }}} + // {{{ _tableLangs() + + /** + * Get table -> language mapping + * + * The key of the array is the table that a language is stored in; + * the value is an /array/ of languages stored in that table. + * + * @param array $langs Languages to get mapping for + * + * @return array Table -> language mapping + * @access private + * @see Translation2_Container_MDB2::_getLangTable() + */ + function &_tableLangs($langs) + { + $tables = array(); + foreach ($langs as $lang) { + $table = $this->_getLangTable($lang); + $tables[$table][] = $lang; + } + return $tables; + } + + // }}} + // {{{ _getLangTables() + + /** + * Get tables for languages + * + * This is like _getLangTable(), but it returns an array of the tables for + * multiple languages. + * + * @param array $langs Languages to get tables for + * + * @return array + * @access private + */ + function &_getLangTables($langs = null) + { + $tables = array(); + $langs = !is_array($langs) ? $this->getLangs('ids') : $langs; + foreach ($langs as $lang) { + $tables[] = $this->_getLangTable($lang); + } + $tables = array_unique($tables); + return $tables; + } + + // }}} + // {{{ _getLangCols() + + /** + * Get table columns strings are stored in + * + * This is like _getLangCol(), except it returns an array which contains + * the mapping for multiple languages. + * + * @param array $langs Languages to get mapping for + * + * @return array Language -> column mapping + * @access private + * @see Translation2_Container_DB::_getLangCol() + */ + function &_getLangCols($langs) + { + $cols = array(); + foreach ($langs as $lang) { + $cols[$lang] = $this->_getLangCol($lang); + } + return $cols; + } + + // }}} + // {{{ _recordExists() + + /** + * Check if there's already a record in the table with the + * given (pageID, stringID) pair. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param string $table table name + * + * @return boolean|PEAR_Error + * @access private + */ + function _recordExists($stringID, $pageID, $table) + { + $stringID = $this->db->quote($stringID, 'text'); + $pageID = is_null($pageID) ? ' IS NULL' : ' = ' . $this->db->quote($pageID, 'text'); + $query = sprintf('SELECT COUNT(*) FROM %s WHERE %s=%s AND %s%s', + $this->db->quoteIdentifier($table, true), + $this->db->quoteIdentifier($this->options['string_id_col'], true), + $stringID, + $this->db->quoteIdentifier($this->options['string_page_id_col'], true), + $pageID + ); + ++$this->_queries; + $res = $this->db->queryOne($query); + if (PEAR::isError($res)) { + return $res; + } + return ($res > 0); + } + + // }}} + // {{{ _filterStringsByTable() + + /** + * Get only the strings for the langs in the given table + * + * @param array $stringArray Associative array with string translations. + * Sample format: array('en' => 'sample', 'it' => 'esempio') + * @param string $table table name + * + * @return array strings + * @access private + */ + function &_filterStringsByTable($stringArray, $table) + { + $strings = array(); + foreach ($stringArray as $lang => $string) { + if ($table == $this->_getLangTable($lang)) { + $strings[$lang] = $string; + } + } + return $strings; + } + + // }}} + // {{{ _getLangsInTable() + + /** + * Get the languages sharing the given table + * + * @param string $table table name + * + * @return array + */ + function &_getLangsInTable($table) + { + $this->fetchLangs(); // force cache refresh + $langsInTable = array(); + foreach (array_keys($this->langs) as $lang) { + if ($table == $this->_getLangTable($lang)) { + $langsInTable[] = $lang; + } + } + return $langsInTable; + } + + // }}} +} +?> \ No newline at end of file diff --git a/Translation2/Admin/Container/xml.php b/Translation2/Admin/Container/xml.php new file mode 100644 index 0000000..8f99326 --- /dev/null +++ b/Translation2/Admin/Container/xml.php @@ -0,0 +1,408 @@ + + * @author Olivier Guilyardi + * @copyright 2004-2007 Lorenzo Alberton, Olivier Guilyardi + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ + +/** + * require Translation2_Container_xml class + */ +require_once 'Translation2/Container/xml.php'; + +require_once 'XML/Util.php'; + +/** + * Storage driver for storing/fetching data to/from a XML file + * + * @category Internationalization + * @package Translation2 + * @author Lorenzo Alberton + * @author Olivier Guilyardi + * @copyright 2004-2007 Lorenzo Alberton, Olivier Guilyardi + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @link http://pear.php.net/package/Translation2 + */ +class Translation2_Admin_Container_xml extends Translation2_Container_xml +{ + // {{{ class vars + + /** + * Whether _saveData() is already registered at shutdown or not + * @var boolean + */ + var $_isScheduledSaving = false; + + // }}} + // {{{ addLang() + + /** + * Does nothing (here for compatibility with the container interface) + * + * @param array $langData language data + * @param array $options language options + * + * @return true|PEAR_Error + */ + function addLang($langData, $options = array()) + { + return true; + } + + // }}} + // {{{ addLangToList() + + /** + * Creates a new entry in the section + * + * @param array $langData array('lang_id' => 'en', + * 'name' => 'english', + * 'meta' => 'some meta info', + * 'error_text' => 'not available', + * 'encoding' => 'iso-8859-1', + * ); + * + * @return true|PEAR_Error + */ + function addLangToList($langData) + { + $validInput = array( + 'name' => '', + 'meta' => '', + 'error_text' => '', + 'encoding' => 'iso-8859-1', + ); + + foreach ($validInput as $key => $val) { + if (isset($langData[$key])) $validInput[$key] = $langData[$key]; + } + + $this->_data['languages'][$langData['lang_id']] = $validInput; + return $this->_scheduleSaving(); + } + + // }}} + // {{{ updateLang() + + /** + * Update the lang info in the langsAvail table + * + * @param array $langData array [@see addLangToList()] + * + * @return true|PEAR_Error + */ + function updateLang($langData) + { + $allFields = array( //'lang_id', + 'name', 'meta', 'error_text', 'encoding', + ); + foreach ($allFields as $field) { + if (isset($this->_data['languages'][$langData['lang_id']][$field])) { + $this->_data['languages'][$langData['lang_id']][$field] = $langData[$field]; + } + } + $success = $this->_scheduleSaving(); + $this->fetchLangs(); //update memory cache + return $success; + } + + // }}} + // {{{ add() + + /** + * Add a new entry in the strings table. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param array $stringArray Associative array with string translations. + * Sample format: array('en' => 'sample', 'it' => 'esempio') + * + * @return true|PEAR_Error + */ + function add($stringID, $pageID, $stringArray) + { + $langs = array_intersect( + array_keys($stringArray), + $this->getLangs('ids') + ); + + $pageID = is_null($pageID) ? '#NULL' : $pageID; + $pageID = empty($pageID) ? '#EMPTY' : $pageID; + + if (!array_key_exists($pageID, $this->_data['pages'])) { + $this->_data['pages'][$pageID] = array(); + } + if (!array_key_exists($stringID, $this->_data['pages'][$pageID])) { + $this->_data['pages'][$pageID][$stringID] = array(); + } + foreach ($langs as $lang) { + $this->_data['pages'][$pageID][$stringID][$lang] = $stringArray[$lang]; + } + + return $this->_scheduleSaving(); + } + + // }}} + // {{{ update() + + /** + * Update an existing entry in the strings table. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param array $stringArray Associative array with string translations. + * Sample format: array('en' => 'sample', 'it' => 'esempio') + * + * @return true|PEAR_Error + */ + function update($stringID, $pageID, $stringArray) + { + return $this->add($stringID, $pageID, $stringArray); + } + + // }}} + // {{{ remove() + + /** + * Remove an entry from the strings table. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * + * @return true|PEAR_Error + */ + function remove($stringID, $pageID) + { + $pageID = is_null($pageID) ? '#NULL' : $pageID; + $pageID = empty($pageID) ? '#EMPTY' : $pageID; + + unset ($this->_data['pages'][$pageID][$stringID]); + if (!count($this->_data['pages'][$pageID])) { + unset ($this->_data['pages'][$pageID]); + } + + return $this->_scheduleSaving(); + } + + // }}} + // {{{ removeLang() + + /** + * Remove all the entries for the given lang from the strings table. + * + * @param string $langID language ID + * @param boolean $force (ignored) + * + * @return true|PEAR_Error + */ + function removeLang($langID, $force = true) + { + // remove lang metadata + unset($this->_data['languages'][$langID]); + + // remove the entries + foreach (array_keys($this->_data['pages']) as $pageID) { + foreach (array_keys($this->_data['pages'][$pageID]) as $stringID) { + if (array_key_exists($langID, $this->_data['pages'][$pageID][$stringID])) { + unset($this->_data['pages'][$pageID][$stringID][$langID]); + } + } + } + return $this->_scheduleSaving(); + } + + // }}} + // {{{ removePage() + + /** + * Remove all the strings in the given page/group + * + * @param string $pageID page/group ID + * + * @return true|PEAR_Error + */ + function removePage($pageID = null) + { + $pageID = is_null($pageID) ? '#NULL' : $pageID; + $pageID = empty($pageID) ? '#EMPTY' : $pageID; + + unset ($this->_data['pages'][$pageID]); + + return $this->_scheduleSaving(); + } + + // }}} + // {{{ getPageNames() + + /** + * Get a list of all the pageIDs. + * + * @return array + */ + function getPageNames() + { + $pages = array_keys($this->_data['pages']); + $k = array_search('#NULL', $pages); + if ($k !== false && !is_null($k)) { + $pages[$k] = null; + } + $k = array_search('#EMPTY', $pages); + if ($k !== false && !is_null($k)) { + $pages[$k] = ''; + } + return $pages; + } + + // }}} + // {{{ _scheduleSaving() + + /** + * Prepare data saving + * + * This methods registers _saveData() as a PEAR shutdown function. This + * is to avoid saving multiple times if the programmer makes several + * changes. + * + * @return true|PEAR_Error + * @access private + * @see Translation2_Admin_Container_xml::_saveData() + */ + function _scheduleSaving() + { + if ($this->options['save_on_shutdown']) { + if (!$this->_isScheduledSaving) { + // save the changes on shutdown + register_shutdown_function(array(&$this, '_saveData')); + $this->_isScheduledSaving = true; + } + return true; + } + + // save the changes now + return $this->_saveData(); + } + + // }}} + // {{{ _saveData() + + /** + * Serialize and save the updated tranlation data to the XML file + * + * @return boolean | PEAR_Error + * @access private + * @see Translation2_Admin_Container_xml::_scheduleSaving() + */ + function _saveData() + { + if ($this->options['save_on_shutdown']) { + $data =& $this->_data; + } else { + $data = $this->_data; + } + + $this->_convertEncodings('to_xml', $data); + $this->_convertLangEncodings('to_xml', $data); + + // Serializing + + $xml = "\n\n" . + "\n\n" . + "\n" . + " \n"; + + foreach ($data['languages'] as $lang => $spec) { + extract ($spec); + $xml .= " \n" . + " " . + ($name ? ' ' . XML_Util::replaceEntities($name) . ' ' : '') . + "\n" . + " " . + ($meta ? ' ' . XML_Util::replaceEntities($meta) . ' ' : "") . + "\n" . + " " . + ($error_text + ? ' ' . XML_Util::replaceEntities($error_text) . ' ' + : "") . + "\n" . + " " . ($encoding ? " $encoding " : "") . + "\n" . + " \n"; + } + + $xml .= " \n" . + " \n"; + + foreach ($data['pages'] as $page => $strings) { + $xml .= " \n"; + foreach ($strings as $str_id => $translations) { + $xml .= " \n"; + foreach ($translations as $lang => $str) { + $xml .= " " . + XML_Util::replaceEntities($str) . " \n"; + } + $xml .= " \n"; + } + $xml .= " \n"; + } + + $xml .= " \n" . + "\n"; + + unset ($data); + + // Saving + + if (!$f = fopen ($this->_filename, 'w')) { + return $this->raiseError(sprintf( + 'Unable to open the XML file ("%s") for writing', + $this->_filename + ), + TRANSLATION2_ERROR_CANNOT_WRITE_FILE, + PEAR_ERROR_TRIGGER, + E_USER_ERROR + ); + } + @flock($f, LOCK_EX); + fwrite ($f, $xml); + //@flock($f, LOCK_UN); + fclose ($f); + return true; + } + + // }}} +} +?> \ No newline at end of file diff --git a/Translation2/Admin/Decorator.php b/Translation2/Admin/Decorator.php new file mode 100644 index 0000000..5a04433 --- /dev/null +++ b/Translation2/Admin/Decorator.php @@ -0,0 +1,221 @@ + + * @copyright 2004-2007 Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ + +/** + * Load Translation2_Decorator class + */ +require_once 'Translation2/Decorator.php'; + +/** + * Decorates a Translation2_Admin class. + * + * Create a subclass of this class for your own "decoration". + * + * @category Internationalization + * @package Translation2 + * @author Lorenzo Alberton + * @copyright 2004-2007 Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @link http://pear.php.net/package/Translation2 + * @abstract + * @todo Don't allow stacking on top of regular Decorators, + * since that will break things. + */ +class Translation2_Admin_Decorator extends Translation2_Decorator +{ + // {{{ addLang() + + /** + * Prepare the storage container for a new lang. + * If the langsAvail table doesn't exist yet, it is created. + * + * @param array $langData array('lang_id' => 'en', + * 'table_name' => 'i18n', + * 'name' => 'english', + * 'meta' => 'some meta info', + * 'error_text' => 'not available'); + * @param array $options array('charset' => 'utf8', + * 'collation' => 'utf8_general_ci'); + * + * @return mixed true on success, PEAR_Error on failure + * @see Translation2_Admin::addLang() + */ + function addLang($langData, $options = array()) + { + return $this->translation2->addLang($langData, $options); + } + + // }}} + // {{{ removeLang() + + /** + * Remove the lang from the langsAvail table and drop the strings table. + * If the strings table holds other langs and $force==false, then + * only the lang column is dropped. If $force==true the whole + * table is dropped without any check + * + * @param string $langID language ID + * @param boolean $force remove the language info without further checks + * + * @return mixed true on success, PEAR_Error on failure + * @see Translation2_Admin::removeLang() + */ + function removeLang($langID = null, $force = false) + { + return $this->translation2->removeLang($langID, $force); + } + + // }}} + // {{{ updateLang() + + /** + * Update the lang info in the langsAvail table + * + * @param array $langData array containing language info + * + * @return mixed true on success, PEAR_Error on failure + * @see Translation2_Admin::updateLang() + */ + function updateLang($langData) + { + return $this->translation2->updateLang($langData); + } + + // }}} + // {{{ add() + + /** + * Add a new translation + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param array $stringArray Associative array with string translations. + * Sample format: array('en' => 'sample', 'it' => 'esempio') + * + * @return mixed true on success, PEAR_Error on failure + * @see Translation2_Admin::add() + */ + function add($stringID, $pageID, $stringArray) + { + return $this->translation2->add($stringID, $pageID, $stringArray); + } + + // }}} + // {{{ update() + + /** + * Update an existing translation + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param array $stringArray Associative array with string translations. + * Sample format: array('en' => 'sample', 'it' => 'esempio') + * + * @return mixed true on success, PEAR_Error on failure + * @see Translation2_Admin::update() + */ + function update($stringID, $pageID, $stringArray) + { + return $this->translation2->update($stringID, $pageID, $stringArray); + } + + // }}} + // {{{ remove() + + /** + * Remove a translated string + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * + * @return mixed true on success, PEAR_Error on failure + * @see Translation2_Admin::remove() + */ + function remove($stringID, $pageID = null) + { + return $this->translation2->remove($stringID, $pageID); + } + + // }}} + // {{{ removePage + + /** + * Remove all the strings in the given page/group + * + * @param string $pageID page/group ID + * + * @return mixed true on success, PEAR_Error on failure + * @see Translation2_Admin::removePage() + */ + function removePage($pageID = null) + { + return $this->translation2->removePager($pageID); + } + + // }}} + // {{{ getPageNames() + + /** + * Get a list of all the pageIDs in any table. + * + * @return array + * @see Translation2_Admin::getPageNames() + */ + function getPageNames() + { + return $this->translation2->getPageNames(); + } + + // }}} + // {{{ cleanCache() + + /** + * If you use the CacheLiteFunction decorator, you may want to invalidate + * the cache after a change in the data base. + * + * @return void + * @see Translation2_Admin::cleanCache() + */ + function cleanCache() + { + return $this->translation2->cleanCache(); + } + + // }}} +} +?> \ No newline at end of file diff --git a/Translation2/Admin/Decorator/Autoadd.php b/Translation2/Admin/Decorator/Autoadd.php new file mode 100644 index 0000000..4c8e2a5 --- /dev/null +++ b/Translation2/Admin/Decorator/Autoadd.php @@ -0,0 +1,112 @@ + + * @author Ian Eure + * @copyright 2004-2007 Lorenzo Alberton, Ian Eure + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ + +/** + * Load Translation2_Decorator class + */ +require_once 'Translation2/Admin/Decorator.php'; + +/** + * Automatically add requested strings + * + * This Decorator will add strings to a language when a request for them to be + * translated happens. The 'autoaddlang' option must be set to the language the + * strings will be added as. + * + * Example: + *
+ * $tr =& Translation2_Admin::factory(...);
+ * $tr->setLang('en');
+ * $tr =& $tr->getAdminDecorator('Autoadd');
+ * $tr->setOption('autoaddlang', 'en');
+ * ...
+ * $tr->get('Entirely new string', 'samplePage', 'de');
+ * 
+ * + * 'Entirely new string' will be added to the English language table. + * + * @category Internationalization + * @package Translation2 + * @author Ian Eure + * @copyright 2004-2007 Ian Eure + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @link http://pear.php.net/package/Translation2 + * @since 2.0.0beta3 + */ +class Translation2_Admin_Decorator_Autoadd extends Translation2_Admin_Decorator +{ + /** + * Language to add strings in + * + * @var string + */ + var $autoaddlang = ''; + + /** + * Get a translated string + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param string $langID language ID + * + * @return string + * @see Translation2::get() + */ + function get($stringID, $pageID = TRANSLATION2_DEFAULT_PAGEID, $langID = null) + { + $pageID = ($pageID == TRANSLATION2_DEFAULT_PAGEID ? $this->translation2->currentPageID : $pageID); + $string = $this->translation2->get($stringID, $pageID, $langID); + if (PEAR::isError($string) + || !strlen($string) + && !empty($this->autoaddlang) + && $langID == $this->autoaddlang) { + // Make sure we add a stub for all languages we know about. + $langs = array(); + foreach ($this->translation2->getLangs('ids') as $lang) { + $langs[$lang] = ''; + } + $langs[$this->autoaddlang] = $stringID; + + // Add the string + $this->translation2->add($stringID, $pageID, $langs); + } + return $string; + } +} +?> \ No newline at end of file diff --git a/Container.php b/Translation2/Container.php similarity index 100% rename from Container.php rename to Translation2/Container.php diff --git a/Translation2/Container/dataobjectsimple.php b/Translation2/Container/dataobjectsimple.php new file mode 100644 index 0000000..d1bac9f --- /dev/null +++ b/Translation2/Container/dataobjectsimple.php @@ -0,0 +1,255 @@ + + * @copyright 2004-2008 Alan Knowles + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ + +/** + * require Translation2_Container class and DB_DataObjects + */ +require_once 'Translation2/Container.php'; +require_once 'DB/DataObject.php'; + +/** + * Simple storage driver for fetching data from a db with DB_DataObject + * + * This storage driver can use all databases which are supported + * by the PEAR::DB abstraction layer to fetch data. + * + * Database Structure: + *
+ *  // meta data etc. not supported yet...
+ *
+ *  create table translations (
+ *     id int(11) auto_increment not null primary key,
+ *     string_id int(11),
+ *     page varchar(128),
+ *     lang varchar(10),
+ *     translation text
+ *     );
+ * alter table translations add index page (page);
+ * alter table translations add index lang (lang);
+ * alter table translations add index string_id (string_id);
+ * 
+ * + * - then just run the dataobjects createtables script. + * + * @category Internationalization + * @package Translation2 + * @author Alan Knowles + * @copyright 2004-2008 Alan Knowles + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ +class Translation2_Container_dataobjectsimple extends Translation2_Container +{ + // {{{ init + + /** + * Initialize the container + * + * @param string $table table name + * + * @return boolean true + */ + function init($table = null) + { + $this->_setDefaultOptions(); + if (!empty($table)) { + $this->options['table'] = $table; + } + return true; + } + + // }}} + // {{{ _setDefaultOptions() + + /** + * Set some default options + * + * @return void + * @access private + */ + function _setDefaultOptions() + { + $this->options['table'] = 'translations'; + } + + // }}} + // {{{ fetchLangs() + + /** + * Fetch the available langs if they're not cached yet. + * + * @return void + */ + function fetchLangs() + { + $do = DB_DataObject::factory($this->options['table']); + $do->selectAdd(); + $do->selectAdd('distinct lang'); + $do->find(); + + $ret = array(); + while ($do->fetch()) { + $l = $do->lang; + $ret[$l] = array( + 'id' => $l, + 'name' => $l, + 'meta' => '', + 'error_text' => '', + ); + } + $this->langs = $ret; + } + + // }}} + // {{{ getPage() + + /** + * Returns an array of the strings in the selected page + * + * @param string $pageID page/group ID + * @param string $langID language ID + * + * @return array + */ + function getPage($pageID = null, $langID = null) + { + $langID = $this->_getLangID($langID); + if (PEAR::isError($langID)) { + return $langID; + } + + // First get the array of string IDs + $do = DB_DataObject::factory($this->options['table']); + $do->lang = '-'; + $do->page = $pageID; + $do->find(); + + $stringIDs = array(); + while ($do->fetch()) { + $stringIDs[$do->string_id] = $do->translation; + } + + // Now get the array of strings + $do = DB_DataObject::factory($this->options['table']); + $do->page = $pageID; + $do->lang = $langID; + + $do->find(); + $translations = array(); + while ($do->fetch()) { + $translations[$do->string_id] = $do->translation; + } + + // Construct an associative array of stringIDs and translations + $strings = array(); + foreach ($translations as $key => $value) { + $strings[$stringIDs[$key]] = $value; + } + + return $strings; + } + + // }}} + // {{{ getOne() + + /** + * Get a single item from the container, without caching the whole page + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param string $langID language ID + * + * @return string + */ + function getOne($string, $pageID = null, $langID = null) + { + $langID = $langID ? $langID : (isset($this->currentLang['id']) ? $this->currentLang['id'] : '-'); + // get the string id + $do = DB_DataObject::factory($this->options['table']); + $do->lang = '-'; + $do->page = $pageID; + $do->translation = $string; + // we dont have the base language translation.. + if (!$do->find(true)) { + return ''; + } + $stringID = $do->string_id; + + $do = DB_DataObject::factory($this->options['table']); + $do->lang = $langID; + $do->page = $pageID; + $do->string_id = $stringID; + //print_r($do); + $do->selectAdd(); + $do->selectAdd('translation'); + if (!$do->find(true)) { + return ''; + } + return $do->translation; + + } + + // }}} + // {{{ getStringID() + + /** + * Get the stringID for the given string + * + * @param string $string string + * @param string $pageID page/group ID + * + * @return string + */ + function getStringID($string, $pageID = null) + { + // get the english version... + + $do = DB_DataObject::factory($this->options['table']); + $do->lang = $this->currentLang['id']; + $do->page = $pageID; + $do->translation = $string; + if ($do->find(true)) { + return ''; + } + return $do->string_id; + } + + // }}} +} +?> \ No newline at end of file diff --git a/Translation2/Container/db.php b/Translation2/Container/db.php new file mode 100644 index 0000000..8143023 --- /dev/null +++ b/Translation2/Container/db.php @@ -0,0 +1,359 @@ + + * @author Ian Eure + * @copyright 2004-2008 Lorenzo Alberton, Ian Eure + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ + +/** + * require Translation2_Container class + */ +require_once 'Translation2/Container.php'; + +/** + * Storage driver for fetching data from a database + * + * This storage driver can use all databases which are supported + * by the PEAR::DB abstraction layer to fetch data. + * + * @category Internationalization + * @package Translation2 + * @author Lorenzo Alberton + * @author Ian Eure + * @copyright 2004-2008 Lorenzo Alberton, Ian Eure + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ +class Translation2_Container_db extends Translation2_Container +{ + // {{{ class vars + + /** + * DB object + * @var object + */ + var $db = null; + + /** + * query counter + * @var integer + * @access private + */ + var $_queries = 0; + + // }}} + // {{{ init + + /** + * Initialize the container + * + * @param mixed &$db string DSN or object DB instance + * + * @return boolean|PEAR_Error object if something went wrong + */ + function init(&$db) + { + $this->_setDefaultOptions(); + if (PEAR::isError($err = $this->_connect($db))) { + return $err; + } + return true; + } + + // }}} + // {{{ _connect() + + /** + * Connect to database by using the given DSN string + * + * @param mixed &$db string DSN or object DB instance + * + * @return boolean|PEAR_Error object if something went wrong + * @access private + */ + function _connect(&$db) + { + if (is_object($db) && is_a($db, 'DB_Common')) { + // Passed an existing instance + $this->db =& $db; + } else if (is_string($db) || is_array($db)) { + // Passed a DSN + include_once 'DB.php'; + $this->db =& DB::connect($db); + } else { + // Passed something invalid + return PEAR::raiseError('The given dsn was not valid in file ' + . __FILE__ . ' at line ' . __LINE__, + TRANSLATION2_ERROR_CANNOT_CONNECT, + PEAR_ERROR_RETURN); + } + + if (PEAR::isError($this->db)) { + return $this->db; + } + return true; + } + + // }}} + // {{{ _setDefaultOptions() + + /** + * Set some default options + * + * @access private + * @return void + */ + function _setDefaultOptions() + { + $this->options['langs_avail_table'] = 'langs'; + $this->options['lang_id_col'] = 'id'; + $this->options['lang_name_col'] = 'name'; + $this->options['lang_meta_col'] = 'meta'; + $this->options['lang_errmsg_col'] = 'error_text'; + $this->options['lang_encoding_col'] = 'encoding'; + + $this->options['strings_default_table'] = 'i18n'; + $this->options['strings_tables'] = array(); // 'lang_id' => 'table_name' + $this->options['string_id_col'] = 'id'; + $this->options['string_page_id_col'] = 'page_id'; + $this->options['string_page_id_col_length'] = 50; + $this->options['string_text_col'] = '%s'; // col_name if one table per lang is used, + // or a pattern (i.e. "tr_%s" => "tr_EN_US") + } + + // }}} + // {{{ setCharset() + + /** + * Set charset used to read/store the translations + * + * @param string $charset character set (encoding) + * + * @return PEAR_Error on failure + */ + function setCharset($charset) + { + if (in_array('setcharset', array_map('strtolower', get_class_methods($this->db)))) { + return $this->db->setCharset($charset); + } + return $this->db->query('SET NAMES ' .$this->db->quoteSmart($charset)); + } + + // }}} + // {{{ fetchLangs() + + /** + * Fetch the available langs if they're not cached yet. + * + * @return PEAR_Error on failure + */ + function fetchLangs() + { + $query = sprintf('SELECT %s AS id, %s AS name, %s AS meta, %s AS error_text, %s AS encoding FROM %s', + $this->db->quoteIdentifier($this->options['lang_id_col']), + $this->db->quoteIdentifier($this->options['lang_name_col']), + $this->db->quoteIdentifier($this->options['lang_meta_col']), + $this->db->quoteIdentifier($this->options['lang_errmsg_col']), + $this->db->quoteIdentifier($this->options['lang_encoding_col']), + $this->db->quoteIdentifier($this->options['langs_avail_table']) + ); + + ++$this->_queries; + $res = $this->db->getAll($query, DB_FETCHMODE_ASSOC); + if (PEAR::isError($res)) { + return $res; + } + foreach ($res as $row) { + $row = array_change_key_case($row, CASE_LOWER); + $this->langs[$row['id']] = $row; + } + } + + // }}} + // {{{ getPage() + + /** + * Returns an array of the strings in the selected page + * + * @param string $pageID page/group ID + * @param string $langID language ID + * + * @return array + */ + function getPage($pageID = null, $langID = null) + { + $langID = $this->_getLangID($langID); + if (PEAR::isError($langID)) { + return $langID; + } + $lang_col = $this->_getLangCol($langID); + $table = $this->_getLangTable($langID); + + $query = sprintf('SELECT %s, %s FROM %s WHERE %s ', + $this->db->quoteIdentifier($this->options['string_id_col']), + $this->db->quoteIdentifier($lang_col), + $this->db->quoteIdentifier($table), + $this->db->quoteIdentifier($this->options['string_page_id_col']) + ); + + if (is_null($pageID)) { + $query .= 'IS NULL'; + } else { + $query .= ' = ' . $this->db->quote($pageID); + } + + ++$this->_queries; + $res = $this->db->getAssoc($query); + return $res; + } + + // }}} + // {{{ getOne() + + /** + * Get a single item from the container + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param string $langID language ID + * + * @return string + */ + function getOne($stringID, $pageID = null, $langID = null) + { + $langID = $this->_getLangID($langID); + if (PEAR::isError($langID)) { + return $langID; + } + $lang_col = $this->_getLangCol($langID); + $table = $this->_getLangTable($langID); + + $query = sprintf('SELECT %s FROM %s WHERE %s = %s AND %s', + $this->db->quoteIdentifier($lang_col), + $this->db->quoteIdentifier($table), + $this->db->quoteIdentifier($this->options['string_id_col']), + $this->db->quote($stringID), + $this->db->quoteIdentifier($this->options['string_page_id_col']) + ); + + if (is_null($pageID)) { + $query .= ' IS NULL'; + } else { + $query .= ' = ' . $this->db->quote($pageID); + } + + ++$this->_queries; + return $this->db->getOne($query); + } + + // }}} + // {{{ getStringID() + + /** + * Get the stringID for the given string + * + * @param string $string string + * @param string $pageID page/group ID + * + * @return string + */ + function getStringID($string, $pageID = null) + { + $lang_col = $this->_getLangCol($this->currentLang['id']); + $table = $this->_getLangTable($this->currentLang['id']); + $query = sprintf('SELECT %s FROM %s WHERE %s = %s AND %s', + $this->db->quoteIdentifier($this->options['string_id_col']), + $this->db->quoteIdentifier($table), + $this->db->quoteIdentifier($lang_col), + $this->db->quote($string), + $this->db->quoteIdentifier($this->options['string_page_id_col']) + ); + if (is_null($pageID)) { + $query .= ' IS NULL'; + } else { + $query .= ' = ' . $this->db->quote($pageID); + } + ++$this->_queries; + return $this->db->getOne($query); + } + + // }}} + // {{{ _getLangTable() + + /** + * Get the table a language is stored in + * + * @param string $langID language ID + * + * @return string table $langID is stored in + * @access private + */ + function _getLangTable($langID) + { + if (isset($this->options['strings_tables'][$langID])) { + return $this->options['strings_tables'][$langID]; + } + return str_replace('%s', $langID, $this->options['strings_default_table']); + } + + // }}} + // {{{ _getLangCol() + + /** + * Get the column a language's string is stored in + * + * @param string $langID Language + * + * @return string column $langID is stored in + * @access private + */ + function _getLangCol($langID) + { + static $cols; + if (!isset($cols[$langID])) { + if (isset($this->options['string_text_col']) && + !empty($this->options['string_text_col'])) { + $cols[$langID] = str_replace('%s', $langID, $this->options['string_text_col']); + } else { + $cols[$langID] = $langID; + } + } + return $cols[$langID]; + } + + // }}} +} +?> \ No newline at end of file diff --git a/Translation2/Container/gettext.php b/Translation2/Container/gettext.php new file mode 100644 index 0000000..e39230b --- /dev/null +++ b/Translation2/Container/gettext.php @@ -0,0 +1,351 @@ + + * @author Michael Wallner + * @copyright 2004-2008 Lorenzo Alberton, Michael Wallner + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ + +/** + * require Translation2_Container class + */ +require_once 'Translation2/Container.php'; + +/** + * require I18Nv2 for locale handling + */ +require_once 'I18Nv2.php'; + +/** + * Storage driver for fetching data with gettext + * + * This storage driver requires the gettext extension + * and the PEAR::I18Nv2 class for locale handling + * + * @category Internationalization + * @package Translation2 + * @author Lorenzo Alberton + * @author Michael Wallner + * @copyright 2004-2008 Lorenzo Alberton, Michael Wallner + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + * @see /docs/gettext_readme.txt for an usage example + */ +class Translation2_Container_gettext extends Translation2_Container +{ + // {{{ class vars + + /** + * domain bindings + * @var array + * @access private + */ + var $_domains = array(); + + /** + * @var array + * @access private + */ + var $cachedDomains = array(); + + /** + * @var boolean + * @access private + */ + var $_native = false; + + // }}} + // {{{ init + + /** + * Initialize the container + * + * @param array $options gettext parameters + * + * @return boolean|PEAR_Error object if domains INI file doesn't exist + */ + function init($options) + { + $this->_setDefaultOptions(); + $this->_parseOptions($options); + $this->_native = ( + function_exists('gettext') && + (strtolower($this->options['file_type']) == 'mo') && + !$this->options['blank_on_missing'] + ); + + $this->_domains = @parse_ini_file($this->options['domains_path_file']); + + if (!$this->_domains) { + return $this->raiseError(sprintf( + 'Cannot find domains INI file "%s" [%s on line %d]', + $this->options['domains_path_file'], __FILE__, __LINE__ + ), + TRANSLATION2_ERROR_CANNOT_FIND_FILE + ); + } + + if ($this->_native) { + foreach ((array) $this->_domains as $domain => $path) { + bindtextdomain($domain, $path); + } + textdomain($this->options['default_domain']); + } + $this->setLang($this->options['default_lang']); + + return true; + } + + // }}} + // {{{ _setDefaultOptions() + + /** + * Set some default options + * + * @access private + * @return void + */ + function _setDefaultOptions() + { + $this->options['langs_avail_file'] = 'langs.ini'; + $this->options['domains_path_file'] = 'domains.ini'; + $this->options['default_domain'] = 'messages'; + $this->options['carriage_return'] = "\n"; + $this->options['file_type'] = 'mo'; + $this->options['default_lang'] = 'en'; + $this->options['default_encoding'] = 'iso-8859-1'; + $this->options['blank_on_missing'] = false; + } + + // }}} + // {{{ _switchLang() + + /** + * Set the given langID + * + * @param string $langID new langID + * + * @return string previous langID + * @access private + */ + function _switchLang($langID) + { + $langID = $this->_getLangID($langID); + $oldLang = $this->currentLang['id']; + $this->setLang($langID); + return $oldLang; + } + + // }}} + // {{{ fetchLangs() + + /** + * Fetch the available langs if they're not cached yet. + * + * @return void + */ + function fetchLangs() + { + $this->langs = @parse_ini_file($this->options['langs_avail_file'], true); + foreach ((array) $this->langs as $id => $lang) { + $this->langs[$id]['id'] = $id; + } + } + + // }}} + // {{{ setLang() + + /** + * Sets the current lang + * + * @param string $langID language ID + * + * @return array language data + */ + function setLang($langID) + { + if (!PEAR::isError($langData = parent::setLang($langID))) { + I18Nv2::setLocale($langID); + } + return $langData; + } + + // }}} + // {{{ getPage() + + /** + * Get all the strings from a domain (parsing the .mo file) + * + * @param string $pageID page/group ID + * @param string $langID language ID + * + * @return array|PEAR_Error + */ + function getPage($pageID = null, $langID = null) + { + $oldLang = $this->_switchLang($langID); + $curLang = $this->currentLang['id']; + + if (empty($pageID) || $pageID == TRANSLATION2_DEFAULT_PAGEID) { + $pageID = $this->options['default_domain']; + } + + if (isset($this->cachedDomains[$curLang][$pageID])) { + $this->_switchLang($oldLang); + return $this->cachedDomains[$curLang][$pageID]; + } + + if (!isset($this->_domains[$pageID])) { + $this->_switchLang($oldLang); + return $this->raiseError(sprintf( + 'The domain "%s" was not specified in the domains INI '. + 'file "%s" [%s on line %d]', $pageID, + $this->options['domains_path_file'], __FILE__, __LINE__ + ), + TRANSLATION2_ERROR_DOMAIN_NOT_SET + ); + } + + include_once 'File/Gettext.php'; + $gtFile = &File_Gettext::factory($this->options['file_type']); + + $path = $this->_domains[$pageID] .'/'. $curLang .'/LC_MESSAGES/'; + $file = $path . $pageID .'.'. $this->options['file_type']; + + if (PEAR::isError($e = $gtFile->load($file))) { + if (is_file($file)) { + $this->_switchLang($oldLang); + return $this->raiseError(sprintf( + '%s [%s on line %d]', $e->getMessage(), __FILE__, __LINE__ + ), + TRANSLATION2_ERROR, PEAR_ERROR_RETURN + ); + } + $this->_switchLang($oldLang); + return $this->raiseError(sprintf( + 'Cannot find file "%s" [%s on line %d]', + $file, __FILE__, __LINE__ + ), + TRANSLATION2_ERROR_CANNOT_FIND_FILE, PEAR_ERROR_RETURN + ); + } + + $this->cachedDomains[$curLang][$pageID] = $gtFile->strings; + $this->_switchLang($oldLang); + return $gtFile->strings; + } + + // }}} + // {{{ getOne() + + /** + * Get a single item from the container, without caching the whole page + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param string $langID language ID + * + * @return string + */ + function getOne($stringID, $pageID = null, $langID = null) + { + // native mode + if ($this->_native) { + $oldLang = $this->_switchLang($langID); + $curLang = $this->currentLang['id']; + + if (empty($pageID) || $pageID == TRANSLATION2_DEFAULT_PAGEID) { + $pageID = $this->options['default_domain']; + } + + $string = dgettext($pageID, $stringID); + + $this->_switchLang($oldLang); + return $string; + } + + // use File_Gettext + $page = $this->getPage($pageID, $langID); + if (PEAR::isError($page = $this->getPage($pageID, $langID))) { + if ($page->getCode() == TRANSLATION2_ERROR_CANNOT_FIND_FILE) { + $page = array(); + } else { + return $this->raiseError($page->getMessage(), $page->getCode()); + } + } + + // return original string if there's no translation available + if (isset($page[$stringID]) && strlen($page[$stringID])) { + return $page[$stringID]; + } else if (false == $this->options['blank_on_missing']) { + return $stringID; + } else { + return ''; + } + } + + // }}} + // {{{ getStringID() + + /** + * Get the stringID for the given string + * + * @param string $string string + * @param string $pageID page/group ID + * + * @return string|PEAR_Error + */ + function getStringID($string, $pageID = null) + { + if (empty($pageID) || $pageID == TRANSLATION2_DEFAULT_PAGEID) { + $pageID = $this->options['default_domain']; + } + + if (!array_key_exists($pageID, $this->_domains)) { + return $this->raiseError(sprintf( + 'The domain "%s" was not specified in the domains '. + 'INI file "%s" [%s on line %d]', $pageID, + $this->options['domains_path_file'], __FILE__, __LINE__ + ), + TRANSLATION2_ERROR_DOMAIN_NOT_SET + ); + } + + return array_search($string, $this->getPage($pageID)); + } + + // }}} +} +?> \ No newline at end of file diff --git a/Translation2/Container/mdb.php b/Translation2/Container/mdb.php new file mode 100644 index 0000000..26c77ec --- /dev/null +++ b/Translation2/Container/mdb.php @@ -0,0 +1,339 @@ + + * @copyright 2004-2008 Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ + +/** + * require Translation2_Container class + */ +require_once 'Translation2/Container.php'; + +/** + * Storage driver for fetching data from a database + * + * This storage driver can use all databases which are supported + * by the PEAR::MDB abstraction layer to fetch data. + * + * @category Internationalization + * @package Translation2 + * @author Lorenzo Alberton + * @copyright 2004-2008 Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ +class Translation2_Container_mdb extends Translation2_Container +{ + + // {{{ class vars + + /** + * MDB object + * @var object + */ + var $db = null; + + /** + * query counter + * @var integer + * @access private + */ + var $_queries = 0; + + // }}} + // {{{ init + + /** + * Initialize the container + * + * @param string &$db Connection data or MDB object + * + * @return boolean|PEAR_Error object if something went wrong + */ + function init(&$db) + { + $this->_setDefaultOptions(); + if (PEAR::isError($err = $this->_connect($db))) { + return $err; + } + return true; + } + + // }}} + // {{{ _connect() + + /** + * Connect to database by using the given DSN string + * + * @param mixed &$db DSN string | array | mdb object + * + * @return boolean|PEAR_Error on error + * @access private + */ + function _connect(&$db) + { + if (is_object($db) && is_a($db, 'MDB_Common')) { + $this->db = &$db; + } elseif (is_string($db) || is_array($db)) { + include_once 'MDB.php'; + $this->db =& MDB::connect($db); + } elseif (is_object($db) && MDB::isError($db)) { + return PEAR::raiseError($db->getMessage(), $db->code); + } else { + return PEAR::raiseError('The given dsn was not valid in file ' + . __FILE__ . ' at line ' . __LINE__, + TRANSLATION2_ERROR_CANNOT_CONNECT, + PEAR_ERROR_RETURN); + } + + if (PEAR::isError($this->db)) { + return $this->db; + } + return true; + } + + // }}} + // {{{ _setDefaultOptions() + + /** + * Set some default options + * + * @access private + * @return void + */ + function _setDefaultOptions() + { + $this->options['langs_avail_table'] = 'langs'; + $this->options['lang_id_col'] = 'id'; + $this->options['lang_name_col'] = 'name'; + $this->options['lang_meta_col'] = 'meta'; + $this->options['lang_errmsg_col'] = 'error_text'; + $this->options['lang_encoding_col'] = 'encoding'; + + $this->options['strings_default_table'] = 'i18n'; + $this->options['strings_tables'] = array(); // 'lang_id' => 'table_name' + $this->options['string_id_col'] = 'id'; + $this->options['string_page_id_col'] = 'page_id'; + $this->options['string_page_id_col_length'] = 50; + $this->options['string_text_col'] = '%s'; // col_name if one table per lang is used, + // or a pattern (i.e. "tr_%s" => "tr_EN_US") + } + + // }}} + // {{{ fetchLangs() + + /** + * Fetch the available langs if they're not cached yet. + * + * @return PEAR_Error on error + */ + function fetchLangs() + { + $query = sprintf('SELECT %s AS id, %s AS name, %s AS meta, %s AS error_text, %s AS encoding FROM %s', + $this->db->quoteIdentifier($this->options['lang_id_col']), + $this->db->quoteIdentifier($this->options['lang_name_col']), + $this->db->quoteIdentifier($this->options['lang_meta_col']), + $this->db->quoteIdentifier($this->options['lang_errmsg_col']), + $this->db->quoteIdentifier($this->options['lang_encoding_col']), + $this->db->quoteIdentifier($this->options['langs_avail_table']) + ); + + ++$this->_queries; + $res = $this->db->getAll($query, null, array(), null, MDB_FETCHMODE_ASSOC); + if (PEAR::isError($res)) { + return $res; + } + foreach ($res as $row) { + $row = array_change_key_case($row, CASE_LOWER); + $this->langs[$row['id']] = $row; + } + } + + // }}} + // {{{ getPage() + + /** + * Returns an array of the strings in the selected page + * + * @param string $pageID page/group ID + * @param string $langID language ID + * + * @return array|PEAR_Error on error + */ + function getPage($pageID = null, $langID = null) + { + $langID = $this->_getLangID($langID); + if (PEAR::isError($langID)) { + return $langID; + } + $lang_col = $this->_getLangCol($langID); + $table = $this->_getLangTable($langID); + + $query = sprintf('SELECT %s, %s FROM %s WHERE %s ', + $this->db->quoteIdentifier($this->options['string_id_col']), + $this->db->quoteIdentifier($lang_col), + $this->db->quoteIdentifier($table), + $this->db->quoteIdentifier($this->options['string_page_id_col']) + ); + + if (is_null($pageID)) { + $query .= 'IS NULL'; + } else { + $query .= ' = ' . $this->db->getTextValue($pageID); + } + + ++$this->_queries; + $res = $this->db->getAssoc($query); + return $res; + } + + // }}} + // {{{ getOne() + + /** + * Get a single item from the container + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param string $langID language ID + * + * @return string + */ + function getOne($stringID, $pageID = null, $langID = null) + { + $langID = $this->_getLangID($langID); + if (PEAR::isError($langID)) { + return $langID; + } + $lang_col = $this->_getLangCol($langID); + $table = $this->_getLangTable($langID); + + $query = sprintf('SELECT %s FROM %s WHERE %s = %s AND %s', + $this->db->quoteIdentifier($lang_col), + $this->db->quoteIdentifier($table), + $this->db->quoteIdentifier($this->options['string_id_col']), + $this->db->getTextValue($stringID), + $this->db->quoteIdentifier($this->options['string_page_id_col']) + ); + + if (is_null($pageID)) { + $query .= ' IS NULL'; + } else { + $query .= ' = ' . $this->db->getTextValue($pageID); + } + + ++$this->_queries; + return $this->db->getOne($query); + } + + // }}} + // {{{ getStringID() + + /** + * Get the stringID for the given string + * + * @param string $string string + * @param string $pageID page/group ID + * + * @return string + */ + function getStringID($string, $pageID = null) + { + $lang_col = $this->_getLangCol($this->currentLang['id']); + $table = $this->_getLangTable($this->currentLang['id']); + $query = sprintf('SELECT %s FROM %s WHERE %s = %s AND %s', + $this->db->quoteIdentifier($this->options['string_id_col']), + $this->db->quoteIdentifier($table), + $this->db->quoteIdentifier($lang_col), + $this->db->getTextValue($string), + $this->db->quoteIdentifier($this->options['string_page_id_col']) + ); + if (is_null($pageID)) { + $query .= ' IS NULL'; + } else { + $query .= ' = ' . $this->db->getTextValue($pageID); + } + ++$this->_queries; + return $this->db->getOne($query); + } + + // }}} + // {{{ _getLangTable() + + /** + * Get the table a language is stored in + * + * @param string $langID language ID + * + * @return string table $langID is stored in + * @access private + */ + function _getLangTable($langID) + { + if (isset($this->options['strings_tables'][$langID])) { + return $this->options['strings_tables'][$langID]; + } + return str_replace('%s', $langID, $this->options['strings_default_table']); + } + + // }}} + // {{{ _getLangCol() + + /** + * Get the column a language's string is stored in + * + * @param string $langID language ID + * + * @return string column $langID is stored in + * @access private + */ + function _getLangCol($langID) + { + static $cols; + if (!isset($cols[$langID])) { + if (isset($this->options['string_text_col']) && + !empty($this->options['string_text_col'])) { + $cols[$langID] = str_replace('%s', $langID, $this->options['string_text_col']); + } else { + $cols[$langID] = $langID; + } + } + return $cols[$langID]; + } + + // }}} +} +?> \ No newline at end of file diff --git a/Translation2/Container/mdb2.php b/Translation2/Container/mdb2.php new file mode 100644 index 0000000..69303d2 --- /dev/null +++ b/Translation2/Container/mdb2.php @@ -0,0 +1,362 @@ + + * @copyright 2004-2008 Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ + +/** + * require Translation2_Container class + */ +require_once 'Translation2/Container.php'; + +/** + * Storage driver for fetching data from a database + * + * This storage driver can use all databases which are supported + * by the PEAR::MDB2 abstraction layer to fetch data. + * + * @category Internationalization + * @package Translation2 + * @author Lorenzo Alberton + * @copyright 2004-2008 Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ +class Translation2_Container_mdb2 extends Translation2_Container +{ + // {{{ class vars + + /** + * MDB2 object + * @var object + */ + var $db = null; + + /** + * query counter + * @var integer + * @access private + */ + var $_queries = 0; + + // }}} + // {{{ init + + /** + * Initialize the container + * + * @param string &$db Connection data or MDB2 object + * + * @return boolean|PEAR_Error object if something went wrong + */ + function init(&$db) + { + $this->_setDefaultOptions(); + if (PEAR::isError($err = $this->_connect($db))) { + return $err; + } + return true; + } + + // }}} + // {{{ _connect() + + /** + * Connect to database by using the given DSN string + * + * @param mixed &$db DSN string | array | MDB2 object + * + * @return boolean|PEAR_Error on error + * @access private + */ + function _connect(&$db) + { + if (is_object($db) && is_a($db, 'MDB2_Driver_Common')) { + $this->db = &$db; + } elseif (is_string($db) || is_array($db)) { + include_once 'MDB2.php'; + $this->db =& MDB2::connect($db); + } elseif (is_object($db) && MDB2::isError($db)) { + return PEAR::raiseError($db->getMessage(), $db->code); + } else { + return PEAR::raiseError('The given dsn was not valid in file ' + . __FILE__ . ' at line ' . __LINE__, + TRANSLATION2_ERROR_CANNOT_CONNECT, + PEAR_ERROR_RETURN); + } + + if (PEAR::isError($this->db)) { + return $this->db; + } + return true; + } + + // }}} + // {{{ _setDefaultOptions() + + /** + * Set some default options + * + * @return void + * @access private + */ + function _setDefaultOptions() + { + $this->options['langs_avail_table'] = 'langs'; + $this->options['lang_id_col'] = 'id'; + $this->options['lang_name_col'] = 'name'; + $this->options['lang_meta_col'] = 'meta'; + $this->options['lang_errmsg_col'] = 'error_text'; + $this->options['lang_encoding_col'] = 'encoding'; + + $this->options['strings_default_table'] = 'i18n'; + $this->options['strings_tables'] = array(); // 'lang_id' => 'table_name' + $this->options['string_id_col'] = 'id'; + $this->options['string_page_id_col'] = 'page_id'; + $this->options['string_page_id_col_length'] = 50; + $this->options['string_text_col'] = '%s'; // col_name if one table per lang is used, + // or a pattern (i.e. "tr_%s" => "tr_EN_US") + } + + // }}} + // {{{ setCharset() + + /** + * Set charset used to read/store the translations + * + * @param string $charset character set (encoding) + * + * @return PEAR_Error on error + */ + function setCharset($charset) + { + return $this->db->setCharset($charset); + } + + // }}} + // {{{ fetchLangs() + + /** + * Fetch the available langs if they're not cached yet. + * + * @return PEAR_Error on error + */ + function fetchLangs() + { + $query = sprintf('SELECT %s AS id, %s AS name, %s AS meta, %s AS error_text, %s AS encoding FROM %s', + $this->db->quoteIdentifier($this->options['lang_id_col'], true), + $this->db->quoteIdentifier($this->options['lang_name_col'], true), + $this->db->quoteIdentifier($this->options['lang_meta_col'], true), + $this->db->quoteIdentifier($this->options['lang_errmsg_col'], true), + $this->db->quoteIdentifier($this->options['lang_encoding_col'], true), + $this->db->quoteIdentifier($this->options['langs_avail_table'], true) + ); + + ++$this->_queries; + $res = $this->db->queryAll($query, null, MDB2_FETCHMODE_ASSOC); + if (PEAR::isError($res)) { + return $res; + } + foreach ($res as $row) { + $row = array_change_key_case($row, CASE_LOWER); + $this->langs[$row['id']] = $row; + } + } + + // }}} + // {{{ getPage() + + /** + * Returns an array of the strings in the selected page + * + * @param string $pageID page/group ID + * @param string $langID language ID + * + * @return array + */ + function getPage($pageID = null, $langID = null) + { + $langID = $this->_getLangID($langID); + if (PEAR::isError($langID)) { + return $langID; + } + $lang_col = $this->_getLangCol($langID); + $table = $this->_getLangTable($langID); + + $query = sprintf('SELECT %s, %s FROM %s WHERE %s ', + $this->db->quoteIdentifier($this->options['string_id_col'], true), + $this->db->quoteIdentifier($lang_col, true), + $this->db->quoteIdentifier($table, true), + $this->db->quoteIdentifier($this->options['string_page_id_col'], true) + ); + + if (is_null($pageID)) { + $query .= 'IS NULL'; + } else { + $query .= ' = ' . $this->db->quote($pageID, 'text'); + } + + ++$this->_queries; + $res = $this->db->query($query); + if (PEAR::isError($res)) { + return $res; + } + + $strings = array(); + while (list($key, $value) = $res->fetchRow(MDB2_FETCHMODE_ORDERED)) { + $strings[$key] = $value; + } + $res->free(); + return $strings; + } + + // }}} + // {{{ getOne() + + /** + * Get a single item from the container + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param string $langID language ID + * + * @return string + */ + function getOne($stringID, $pageID = null, $langID = null) + { + $langID = $this->_getLangID($langID); + if (PEAR::isError($langID)) { + return $langID; + } + $lang_col = $this->_getLangCol($langID); + $table = $this->_getLangTable($langID); + + $query = sprintf('SELECT %s FROM %s WHERE %s = %s AND %s', + $this->db->quoteIdentifier($lang_col, true), + $this->db->quoteIdentifier($table, true), + $this->db->quoteIdentifier($this->options['string_id_col'], true), + $this->db->quote($stringID, 'text'), + $this->db->quoteIdentifier($this->options['string_page_id_col'], true) + ); + + if (is_null($pageID)) { + $query .= ' IS NULL'; + } else { + $query .= ' = ' . $this->db->quote($pageID, 'text'); + } + + ++$this->_queries; + return $this->db->queryOne($query); + } + + // }}} + // {{{ getStringID() + + /** + * Get the stringID for the given string + * + * @param string $string string + * @param string $pageID page/group ID + * + * @return string + */ + function getStringID($string, $pageID = null) + { + $lang_col = $this->_getLangCol($this->currentLang['id']); + $table = $this->_getLangTable($this->currentLang['id']); + $query = sprintf('SELECT %s FROM %s WHERE %s = %s AND %s', + $this->db->quoteIdentifier($this->options['string_id_col'], true), + $this->db->quoteIdentifier($table, true), + $this->db->quoteIdentifier($lang_col, true), + $this->db->quote($string, 'text'), + $this->db->quoteIdentifier($this->options['string_page_id_col'], true) + ); + if (is_null($pageID)) { + $query .= ' IS NULL'; + } else { + $query .= ' = ' . $this->db->quote($pageID, 'text'); + } + ++$this->_queries; + return $this->db->queryOne($query); + } + + // }}} + // {{{ _getLangTable() + + /** + * Get the table a language is stored in + * + * @param string $langID language ID + * + * @return string table $langID is stored in + * @access private + */ + function _getLangTable($langID) + { + if (isset($this->options['strings_tables'][$langID])) { + return $this->options['strings_tables'][$langID]; + } + return str_replace('%s', $langID, $this->options['strings_default_table']); + } + + // }}} + // {{{ _getLangCol() + + /** + * Get the column a language's string is stored in + * + * @param string $langID language ID + * + * @return string column $langID is stored in + * @access private + */ + function _getLangCol($langID) + { + static $cols; + if (!isset($cols[$langID])) { + if (isset($this->options['string_text_col']) && + !empty($this->options['string_text_col'])) { + $cols[$langID] = str_replace('%s', $langID, $this->options['string_text_col']); + } else { + $cols[$langID] = $langID; + } + } + return $cols[$langID]; + } + + // }}} +} +?> \ No newline at end of file diff --git a/Translation2/Container/xml.php b/Translation2/Container/xml.php new file mode 100644 index 0000000..8fab9cb --- /dev/null +++ b/Translation2/Container/xml.php @@ -0,0 +1,500 @@ + + * @author Olivier Guilyardi + * @copyright 2004-2008 Lorenzo Alberton, Olivier Guilyardi + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ + +/** + * require Translation2_Container class + */ +require_once 'Translation2/Container.php'; +/** + * require XML_Unserializer class + */ +require_once 'XML/Unserializer.php'; +/** + * Document Type Definition + */ +define('TRANSLATION2_DTD', + "\n" . + "\n" . + "\n" . + "\n" . + "\n" . + "\n" . + "\n" . + "\n" . + "\n" . + "\n" . + "\n" . + "\n" . + "\n" . + "\n" . + "\n" +); + +/** + * Storage driver for fetching data from a XML file + * + * Example file : + *
+ * 
+ * 
+ *     
+ *         
+ *              English 
+ *              Custom meta data
+ *              Non disponible en français 
+ *              iso-8859-1 
+ *         
+ *         
+ *     
+ *     
+ *         
+ *             
+ *                  Chat 
+ *                 
+ *             
+ *             
+ *         
+ *         
+ *     
+ * 
+ * 
+ * + * @category Internationalization + * @package Translation2 + * @author Lorenzo Alberton + * @author Olivier Guilyardi + * @copyright 2004-2008 Lorenzo Alberton, Olivier Guilyardi + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @link http://pear.php.net/package/Translation2 + */ +class Translation2_Container_xml extends Translation2_Container +{ + // {{{ class vars + + /** + * Unserialized XML data + * @var object + */ + var $_data = null; + + /** + * XML file name + * @var string + */ + var $_filename; + + // }}} + // {{{ init + + /** + * Initialize the container + * + * @param array $options - 'filename': Path to the XML file + * + * @return boolean|PEAR_Error object if something went wrong + */ + function init($options) + { + $this->_filename = $options['filename']; + unset($options['filename']); + $this->_setDefaultOptions(); + $this->_parseOptions($options); + + return $this->_loadFile(); + } + + // }}} + // {{{ _loadFile() + + /** + * Load an XML file into memory, and eventually decode the strings from UTF-8 + * + * @return boolean|PEAR_Error + * @access private + */ + function _loadFile() + { + $keyAttr = array ( + 'lang' => 'id', + 'page' => 'key', + 'string' => 'key', + 'tr' => 'lang' + ); + if (!$fp = @fopen($this->_filename, 'r')) { + return new PEAR_Error ("Can\'t read from the XML source: {$this->_filename}"); + } + @flock($fp, LOCK_SH); + $unserializer = &new XML_Unserializer (array('keyAttribute' => $keyAttr)); + if (PEAR::isError($status = $unserializer->unserialize($this->_filename, true))) { + fclose($fp); + return $status; + } + fclose($fp); + + // unserialize data + $this->_data = $unserializer->getUnserializedData(); + $this->fixEmptySets($this->_data); + $this->_fixDuplicateEntries(); + + // Handle default language settings. + // This allows, for example, to rapidly write the meta data as: + // + // + // + + $defaults = array( + 'name' => '', + 'meta' => '', + 'error_text' => '', + 'encoding' => 'iso-8859-1' + ); + + foreach ($this->_data['languages'] as $lang_id => $settings) { + if (empty($settings)) { + $this->_data['languages'][$lang_id] = $defaults; + } else { + $this->_data['languages'][$lang_id] = + array_merge($defaults, $this->_data['languages'][$lang_id]); + } + } + + // convert lang metadata from UTF-8 + if (PEAR::isError($e = $this->_convertLangEncodings('from_xml', $this->_data))) { + return $e; + } + + // convert encodings of the translated strings from xml (somehow heavy) + return $this->_convertEncodings('from_xml', $this->_data); + } + + // }}} + // {{{ _convertEncodings() + + /** + * Convert strings to/from XML unique charset (UTF-8) + * + * @param string $direction ['from_xml' | 'to_xml'] + * @param array &$data Data buffer to operate on + * + * @return boolean|PEAR_Error + */ + function _convertEncodings($direction, &$data) + { + if ($direction == 'from_xml') { + $source_encoding = 'UTF-8'; + } else { + $target_encoding = 'UTF-8'; + } + + foreach ($data['pages'] as $page_id => $page_content) { + foreach ($page_content as $str_id => $translations) { + foreach ($translations as $lang => $str) { + if ($direction == 'from_xml') { + $target_encoding = + strtoupper($data['languages'][$lang]['encoding']); + } else { + $source_encoding = + strtoupper($data['languages'][$lang]['encoding']); + } + if ($target_encoding != $source_encoding) { + $res = iconv($source_encoding, $target_encoding, $str); + if ($res === false) { + $msg = 'Encoding conversion error ' . + "(source encoding: $source_encoding, ". + "target encoding: $target_encoding, ". + "processed string: \"$str\""; + return $this->raiseError($msg, + TRANSLATION2_ERROR_ENCODING_CONVERSION, + PEAR_ERROR_RETURN, + E_USER_WARNING); + } + $data['pages'][$page_id][$str_id][$lang] = $res; + } + } + } + } + return true; + } + + // }}} + // {{{ _convertLangEncodings() + + /** + * Convert lang data to/from XML unique charset (UTF-8) + * + * @param string $direction ['from_xml' | 'to_xml'] + * @param array &$data Data buffer to operate on + * + * @return boolean|PEAR_Error + */ + function _convertLangEncodings($direction, &$data) + { + static $fields = array('name', 'meta', 'error_text'); + + if ($direction == 'from_xml') { + $source_encoding = 'UTF-8'; + } else { + $target_encoding = 'UTF-8'; + } + + foreach ($data['languages'] as $lang_id => $lang) { + if ($direction == 'from_xml') { + $target_encoding = strtoupper($lang['encoding']); + } else { + $source_encoding = strtoupper($lang['encoding']); + } + //foreach (array_keys($lang) as $field) { + foreach ($fields as $field) { + if ($target_encoding != $source_encoding && !empty($lang[$field])) { + $res = iconv($source_encoding, $target_encoding, $lang[$field]); + if ($res === false) { + $msg = 'Encoding conversion error ' . + "(source encoding: $source_encoding, ". + "target encoding: $target_encoding, ". + "processed string: \"$lang[$field]\""; + return $this->raiseError($msg, + TRANSLATION2_ERROR_ENCODING_CONVERSION, + PEAR_ERROR_RETURN, + E_USER_WARNING); + } + $data['languages'][$lang_id][$field] = $res; + } + } + } + return true; + } + + // }}} + // {{{ _fixDuplicateEntries() + + /** + * Remove duplicate entries from the xml data + * + * @return void + */ + function _fixDuplicateEntries() + { + foreach ($this->_data['pages'] as $pagename => $pagedata) { + foreach ($pagedata as $stringname => $stringvalues) { + if (is_array(array_pop($stringvalues))) { + $this->_data['pages'][$pagename][$stringname] = + call_user_func_array(array($this, '_merge'), $stringvalues); + } + } + } + } + + // }}} + // {{{ fixEmptySets() + + /** + * Turn empty strings returned by XML_Unserializer into empty arrays + * + * Note: this method is public because called statically by the t2xmlchk.php + * script. It is not meant to be called by user-space code. + * + * @param array &$data array of languages/pages + * + * @return void + * @access public + * @static + */ + function fixEmptySets(&$data) + { + if (PEAR::isError($this->_data) && ($this->_data->code == XML_UNSERIALIZER_ERROR_NO_UNSERIALIZATION)) { + //empty file... create skeleton + $this->_data = array( + 'languages' => array(), + 'pages' => array(), + ); + } + if (is_string($data['languages']) and trim($data['languages']) == '') { + $data['languages'] = array(); + } + if (is_string($data['pages']) and trim($data['pages']) == '') { + $data['pages'] = array(); + } else { + foreach ($data['pages'] as $pageName => $strings) { + //if (is_string($strings) and trim($strings) == '') { + if (is_string($strings)) { + $data['pages'][$pageName] = array(); + } else { + foreach ($strings as $stringName => $translations) { + if (is_string($translations) and trim($translations) == '') { + $data['pages'][$pageName][$stringName] = array(); + } + } + } + } + } + } + + // }}} + // {{{ _merge() + + /** + * Wrapper for array_merge() + * + * @param array $arr1 reference + * + * @return array + */ + function _merge() + { + $return = array(); + foreach (func_get_args() as $arg) { + $return = array_merge($return, $arg); + } + return $return; + } + + // }}} + // {{{ _setDefaultOptions() + + /** + * Set some default options + * + * @return void + * @access private + */ + function _setDefaultOptions() + { + //save changes on shutdown or in real time? + $this->options['save_on_shutdown'] = true; + } + + // }}} + // {{{ fetchLangs() + + /** + * Fetch the available langs + * + * @return void + */ + function fetchLangs() + { + $res = array(); + foreach ($this->_data['languages'] as $id => $spec) { + $spec['id'] = $id; + $res[$id] = $spec; + } + $this->langs = $res; + } + + // }}} + // {{{ getPage() + + /** + * Returns an array of the strings in the selected page + * + * @param string $pageID page/group ID + * @param string $langID language ID + * + * @return array + */ + function getPage($pageID = null, $langID = null) + { + $langID = $this->_getLangID($langID); + if (PEAR::isError($langID)) { + return $langID; + } + $pageID = (is_null($pageID)) ? '#NULL' : $pageID; + $pageID = (empty($pageID) && (0 !== $pageID)) ? '#EMPTY' : $pageID; + + $result = array(); + foreach ($this->_data['pages'][$pageID] as $str_id => $translations) { + $result[$str_id] = isset($translations[$langID]) + ? $translations[$langID] + : null; + } + + return $result; + } + + // }}} + // {{{ getOne() + + /** + * Get a single item from the container + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param string $langID language ID + * + * @return string + */ + function getOne($stringID, $pageID = null, $langID = null) + { + $langID = $this->_getLangID($langID); + if (PEAR::isError($langID)) { + return $langID; + } + $pageID = (is_null($pageID)) ? '#NULL' : $pageID; + return isset($this->_data['pages'][$pageID][$stringID][$langID]) + ? $this->_data['pages'][$pageID][$stringID][$langID] + : null; + } + + // }}} + // {{{ getStringID() + + /** + * Get the stringID for the given string + * + * @param string $string string + * @param string $pageID page/group ID + * + * @return string + */ + function getStringID($string, $pageID = null) + { + $pageID = (is_null($pageID)) ? '#NULL' : $pageID; + + foreach ($this->_data['pages'][$pageID] as $stringID => $translations) { + if (array_search($string, $translations) !== false) { + return $stringID; + } + } + + return ''; + } + + // }}} +} +?> \ No newline at end of file diff --git a/Decorator.php b/Translation2/Decorator.php similarity index 100% rename from Decorator.php rename to Translation2/Decorator.php diff --git a/Translation2/Decorator/CacheLiteFunction.php b/Translation2/Decorator/CacheLiteFunction.php new file mode 100644 index 0000000..227d8e4 --- /dev/null +++ b/Translation2/Decorator/CacheLiteFunction.php @@ -0,0 +1,452 @@ + + * @copyright 2004-2007 Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ + +/** + * Load Translation2 decorator base class + * and Cache_Lite_Function class + */ +require_once 'Translation2/Decorator.php'; +require_once 'Cache/Lite/Function.php'; + +/** + * Decorator to cache fetched data using the Cache_Lite_Function class. + * + * @category Internationalization + * @package Translation2 + * @author Lorenzo Alberton + * @copyright 2004-2007 Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ +class Translation2_Decorator_CacheLiteFunction extends Translation2_Decorator +{ + // {{{ class vars + + /** + * Cache_Lite_Function object + * @var object + */ + var $cacheLiteFunction = null; + + /** + * @var int (default 1) + * @access private + */ + var $tempVarNameGenerator = 1; + + /** + * @var string + * @access private + */ + var $tempVarName = null; + + /** + * Cache lifetime (in seconds) + * @var int $lifeTime + * @access private + */ + var $lifeTime = 3600; + + /** + * Directory where to put the cache files + * (make sure to add a trailing slash) + * @var string $cacheDir + * @access private + */ + var $cacheDir = '/tmp/'; + + /** + * Enable / disable fileLocking. Can avoid cache corruption under bad + * circumstances. + * @var string $cacheDir + * @access private + */ + var $fileLocking = true; + + /** + * Enable / disable caching + * (can be very useful to debug cached scripts) + * @var boolean $caching + */ + var $caching = true; + + /** + * Frequency of cache cleaning. + * Higher values mean lower cleaning probability. + * Set 0 to disable. Set 1 to clean at every request. + * @var boolean $caching + */ + var $cleaningFrequency = 0; + + /** + * Name of default cache group. + * @var string $defaultGroup + */ + var $defaultGroup = 'Translation2'; + + // }}} + // {{{ _prepare() + + /** + * Istanciate a new Cache_Lite_Function object + * and get the name for an unused global variable, + * needed by Cache_Lite_Function + * + * @return void + * @access private + */ + function _prepare() + { + if (is_null($this->cacheLiteFunction)) { + $cache_options = array( + 'caching' => $this->caching, + 'cacheDir' => $this->cacheDir, + 'lifeTime' => $this->lifeTime, + 'fileLocking' => $this->fileLocking, + 'defaultGroup' => $this->defaultGroup, + + ); + $this->cacheLiteFunction = new Cache_Lite_Function($cache_options); + } + + $this->_cleanCache(); + } + + // }}} + // {{{ setLang() + + /** + * Set default lang + * + * Set the language that shall be used when retrieving strings. + * + * @param string $langID language code (for instance, 'en' or 'it') + * + * @return void + */ + function setLang($langID) + { + // WITHOUT THIS, IT DOESN'T WORK + global $translation2_storage_cachelitefunction_temp; + //generate temp variable + $translation2_storage_cachelitefunction_temp = $this->translation2->storage; + + $this->_prepare(); + $res = $this->cacheLiteFunction->call( + 'translation2_storage_cachelitefunction_temp->setLang', $langID); + if (PEAR::isError($res)) { + return $res; + } + $this->translation2->lang = $res; + + } + + // }}} + // {{{ setCacheOption() + + /** + * Set a Cache_Lite option + * + * Passes a Cache_Lite option forward to the Cache_Lite object + * See Cache_Lite constructor for available options + * + * @param string $name name of the option + * @param string $value new value of the option + * + * @return self + * @access public + * @see Cache_Lite::setOption() + */ + function setCacheOption($name, $value) + { + $this->_prepare(); + $this->cacheLiteFunction->setOption($name, $value); + return $this; + } + + // }}} + // {{{ getLang() + + /** + * get lang info + * + * Get some extra information about the language (its full name, + * the localized error text, ...) + * + * @param string $langID language ID + * @param string $format ['name', 'meta', 'error_text', 'array'] + * + * @return mixed [string | array], depending on $format + */ + function getLang($langID = null, $format = 'name') + { + $langs = $this->getLangs('array'); + + if (is_null($langID)) { + if (!isset($this->lang['id']) || !array_key_exists($this->lang['id'], $langs)) { + $msg = 'Translation2::getLang(): unknown language "'.$langID.'".' + .' Use Translation2::setLang() to set a default language.'; + return $this->storage->raiseError($msg, TRANSLATION2_ERROR_UNKNOWN_LANG); + } + $langID = $this->lang['id']; + } + + if ($format == 'array') { + return $langs[$langID]; + } elseif (isset($langs[$langID][$format])) { + return $langs[$langID][$format]; + } elseif (isset($langs[$langID]['name'])) { + return $langs[$langID]['name']; + } + $msg = 'Translation2::getLang(): unknown language "'.$langID.'".' + .' Use Translation2::setLang() to set a default language.'; + return $this->storage->raiseError($msg, TRANSLATION2_ERROR_UNKNOWN_LANG); + } + + // }}} + // {{{ getLangs() + + /** + * get langs + * + * Get some extra information about the languages (their full names, + * the localized error text, their codes, ...) + * + * @param string $format ['ids', 'names', 'array'] + * + * @return array + */ + function getLangs($format = 'name') + { + // WITHOUT THIS, IT DOESN'T WORK + global $translation2_cachelitefunction_temp; + //generate temp variable + $translation2_cachelitefunction_temp = $this->translation2; + + $this->_prepare(); + return $this->cacheLiteFunction->call('translation2_cachelitefunction_temp->getLangs', + $format); + } + + // }}} + // {{{ getRaw() + + /** + * Get translated string (as-is) + * + * First check if the string is cached, if not => fetch the page + * from the container and cache it for later use. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param string $langID language ID + * @param string $defaultText Text to display when the strings in both + * the default and the fallback lang are empty + * + * @return string + */ + function getRaw($stringID, $pageID = TRANSLATION2_DEFAULT_PAGEID, $langID = null, $defaultText = '') + { + // WITHOUT THIS, IT DOESN'T WORK + global $translation2_cachelitefunction_temp; + //generate temp variable + $translation2_cachelitefunction_temp = $this->translation2; + + if ($pageID == TRANSLATION2_DEFAULT_PAGEID) { + $pageID = $this->translation2->currentPageID; + } + $langID = empty($langID) ? $this->translation2->lang['id'] : $langID; + + $this->_prepare(); + + return $this->cacheLiteFunction->call('translation2_cachelitefunction_temp->getRaw', + $stringID, $pageID, $langID, $defaultText); + } + + // }}} + // {{{ get() + + /** + * Get translated string + * + * First check if the string is cached, if not => fetch the page + * from the container and cache it for later use. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param string $langID language ID + * @param string $defaultText Text to display when the strings in both + * the default and the fallback lang are empty + * + * @return string + */ + function get($stringID, $pageID = TRANSLATION2_DEFAULT_PAGEID, $langID = null, $defaultText = '') + { + // WITHOUT THIS, IT DOESN'T WORK + global $translation2_cachelitefunction_temp; + //generate temp variable + $translation2_cachelitefunction_temp = $this->translation2->storage; + + if ($pageID == TRANSLATION2_DEFAULT_PAGEID) { + $pageID = $this->translation2->currentPageID; + } + $langID = empty($langID) ? $this->translation2->lang['id'] : $langID; + + $this->_prepare(); + + $string = $this->cacheLiteFunction->call('translation2_cachelitefunction_temp->getOne', + $stringID, $pageID, $langID); + if (empty($string)) { + return $defaultText; + } + return $this->translation2->_replaceParams($string); + } + + // }}} + // {{{ getRawPage() + + /** + * Get the array of strings in a page + * + * First check if the strings are cached, if not => fetch the page + * from the container and cache it for later use. + * + * @param string $pageID page/group ID + * @param string $langID language ID + * + * @return array + */ + function getRawPage($pageID = TRANSLATION2_DEFAULT_PAGEID, $langID = null) + { + // WITHOUT THIS, IT DOESN'T WORK + global $translation2_cachelitefunction_temp; + //generate temp variable + $translation2_cachelitefunction_temp = $this->translation2; + + if ($pageID == TRANSLATION2_DEFAULT_PAGEID) { + $pageID = $this->translation2->currentPageID; + } + $langID = empty($langID) ? $this->translation2->lang['id'] : $langID; + + $this->_prepare(); + + return $this->cacheLiteFunction->call('translation2_cachelitefunction_temp->getRawPage', + $pageID, $langID); + } + + // }}} + // {{{ getPage() + + /** + * Same as getRawPage, but resort to fallback language and + * replace parameters when needed + * + * @param string $pageID page/group ID + * @param string $langID language ID + * + * @return array + */ + function getPage($pageID = TRANSLATION2_DEFAULT_PAGEID, $langID = null) + { + // WITHOUT THIS, IT DOESN'T WORK + global $translation2_cachelitefunction_temp; + //generate temp variable + $translation2_cachelitefunction_temp = $this->translation2; + + if ($pageID == TRANSLATION2_DEFAULT_PAGEID) { + $pageID = $this->translation2->currentPageID; + } + $langID = empty($langID) ? $this->translation2->lang['id'] : $langID; + + $this->_prepare(); + + return $this->cacheLiteFunction->call('translation2_cachelitefunction_temp->getPage', + $pageID, $langID); + } + + // }}} + // {{{ getStringID() + + /** + * Get translated string + * + * @param string $string This is NOT the stringID, this is a real string. + * The method will search for its matching stringID, + * and then it will return the associate string in the + * selected language. + * @param string $pageID page/group ID + * + * @return string + */ + function getStringID($string, $pageID=TRANSLATION2_DEFAULT_PAGEID) + { + // WITHOUT THIS, IT DOESN'T WORK + global $translation2_cachelitefunction_temp; + //generate temp variable + $translation2_cachelitefunction_temp = $this->translation2; + + if ($pageID == TRANSLATION2_DEFAULT_PAGEID) { + $pageID = $this->translation2->currentPageID; + } + $this->_prepare(); + + return $this->cacheLiteFunction->call('translation2_cachelitefunction_temp->getStringID', + $string, $pageID); + } + + // }}} + // {{{ _cleanCache() + + /** + * Statistically purge the cache + * + * @return void + */ + function _cleanCache() + { + if ($this->cleaningFrequency > 0) { + if (mt_rand(1, $this->cleaningFrequency) == 1) { + $this->cacheLiteFunction->clean($this->defaultGroup); + } + } + } + + // }}} +} +?> \ No newline at end of file diff --git a/Translation2/Decorator/CacheMemory.php b/Translation2/Decorator/CacheMemory.php new file mode 100644 index 0000000..e9eaab1 --- /dev/null +++ b/Translation2/Decorator/CacheMemory.php @@ -0,0 +1,229 @@ + + * @copyright 2004-2007 Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ + +/** + * Load Translation2 decorator base class + */ +require_once 'Translation2/Decorator.php'; + +/** + * Allows redefinition of alternate key for empty pageID + */ +if (!defined('TRANSLATION2_EMPTY_PAGEID_KEY')) { + define('TRANSLATION2_EMPTY_PAGEID_KEY', 'array_key_4_empty_pageID'); +} +/** + * Allows redefinition of alternate key for null pageID + */ +if (!defined('TRANSLATION2_NULL_PAGEID_KEY')) { + define('TRANSLATION2_NULL_PAGEID_KEY', 'array_key_4_null_pageID'); +} + +/** + * Decorator to cache fetched data in memory + * + * @category Internationalization + * @package Translation2 + * @author Lorenzo Alberton + * @copyright 2004-2007 Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ +class Translation2_Decorator_CacheMemory extends Translation2_Decorator +{ + // {{{ class vars + + /** + * Translated strings array + * Used for cache purposes. + * No parameter substitution or fallback langs here. + * @var array + * @access protected + */ + var $rawData = array(); + + /** + * set prefetch on/off + * @var boolean + * @access protected + */ + var $prefetch = true; + + // }}} + // {{{ _getPageIDKey() + + /** + * return a valid array key based on pageID value + * + * @param mixed $pageID (string or null) + * + * @return string + * @access private + */ + function _getPageIDKey($pageID) + { + if (is_null($pageID)) { + return TRANSLATION2_NULL_PAGEID_KEY; + } + if (empty($pageID)) { + return TRANSLATION2_EMPTY_PAGEID_KEY; + } + if ($pageID == TRANSLATION2_DEFAULT_PAGEID) { + return $this->translation2->currentPageID; + } + return $pageID; + } + + // }}} + // {{{ getRaw() + + /** + * Get translated string (as-is) + * + * First check if the string is cached, if not => fetch the page + * from the container and cache it for later use. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param string $langID language ID + * @param string $defaultText Text to display when the strings in both + * the default and the fallback lang are empty + * + * @return string + */ + function getRaw($stringID, $pageID = TRANSLATION2_DEFAULT_PAGEID, $langID = null, $defaultText = null) + { + $pageID_key = $this->_getPageIDKey($pageID); + $langID_key = empty($langID) ? $this->translation2->lang['id'] : $langID; + + if (!array_key_exists($langID_key, $this->rawData)) { + $this->rawData[$langID_key] = array(); + } + + if ($this->prefetch) { + $this->getRawPage($pageID, $langID); + } + if (array_key_exists($pageID_key, $this->rawData[$langID_key])) { + if (PEAR::isError($this->rawData[$langID_key][$pageID_key])) { + return $this->rawData[$langID_key][$pageID_key]; + } + $str = (isset($this->rawData[$langID_key][$pageID_key][$stringID]) ? + $this->rawData[$langID_key][$pageID_key][$stringID] : ''); //empty string or null value? + } else { + $str = $this->translation2->getRaw($stringID, $pageID, $langID, $defaultText); + } + return $str; + } + + // }}} + // {{{ get() + + /** + * Get translated string + * + * First check if the string is cached, if not => fetch the page + * from the container and cache it for later use. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param string $langID language ID + * @param string $defaultText Text to display when the strings in both + * the default and the fallback lang are empty + * + * @return string + */ + function get($stringID, $pageID = TRANSLATION2_DEFAULT_PAGEID, $langID = null, $defaultText = null) + { + $str = $this->getRaw($stringID, $pageID, $langID, $defaultText); + return $this->_replaceParams($str); + } + + // }}} + // {{{ getRawPage() + + /** + * Get the array of strings in a page + * + * First check if the strings are cached, if not => fetch the page + * from the container and cache it for later use. + * + * @param string $pageID page/group ID + * @param string $langID language ID + * + * @return array + */ + function getRawPage($pageID = TRANSLATION2_DEFAULT_PAGEID, $langID = null) + { + $pageID_key = $this->_getPageIDKey($pageID); + $langID_key = empty($langID) ? $this->translation2->lang['id'] : $langID; + + if (!array_key_exists($langID_key, $this->rawData)) { + $this->rawData[$langID_key] = array(); + } + if (!array_key_exists($pageID_key, $this->rawData[$langID_key])) { + $this->rawData[$langID_key][$pageID_key] = + $this->translation2->getRawPage($pageID, $langID); + } + return $this->rawData[$langID_key][$pageID_key]; + } + + // }}} + // {{{ getPage() + + /** + * Same as getRawPage, but resort to fallback language and + * replace parameters when needed + * + * @param string $pageID page/group ID + * @param string $langID language ID + * + * @return array + */ + function getPage($pageID = TRANSLATION2_DEFAULT_PAGEID, $langID = null) + { + $pageID_key = $this->_getPageIDKey($pageID); + $langID_key = empty($langID) ? $this->translation2->lang['id'] : $langID; + + $this->getRawPage($pageID, $langID); + return $this->_replaceParams($this->rawData[$langID_key][$pageID_key]); + } + + // }}} +} +?> \ No newline at end of file diff --git a/Translation2/Decorator/DefaultText.php b/Translation2/Decorator/DefaultText.php new file mode 100644 index 0000000..4ba46a9 --- /dev/null +++ b/Translation2/Decorator/DefaultText.php @@ -0,0 +1,196 @@ + + * @author Rolf 'Red' Ochsenbein + * @copyright 2004-2007 Lorenzo Alberton, Rolf 'Red' Ochsenbein + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ + +/** + * Load Translation2 decorator base class + */ +require_once 'Translation2/Decorator.php'; + +/** + * Decorator to provide a fallback text for empty strings. + * + * If the string is empty, return the defaultText parameter. + * If the defaultText parameter is empty too, then return + * "$emptyPostfix.$outputString.$emptyPrefix", the three variables + * being class properties you can set to a custom string. + * + * @category Internationalization + * @package Translation2 + * @author Lorenzo Alberton + * @author Rolf 'Red' Ochsenbein + * @copyright 2004-2007 Lorenzo Alberton, Rolf 'Red' Ochsenbein + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ +class Translation2_Decorator_DefaultText extends Translation2_Decorator +{ + // {{{ class vars + + /** + * String appended to the returned string when the string is empty + * and it's replaced by its $stringID. It can be used to mark unreplaced + * strings. + * @var string + * @access protected + */ + var $emptyPostfix = ''; + + /** + * String prepended to the returned string when the string is empty + * and it's replaced by its $stringID. It can be used to mark unreplaced + * strings. + * @var string + * @access protected + */ + var $emptyPrefix = ''; + + /** + * String to output when there was no translation + * %stringID% will be replaced with the stringID + * %stringID_url% will replaced with a urlencoded stringID + * %url% will be replaced with the targeted url + * @var string + * @access protected + */ + //var $outputString = '%stringID%(T)'; + var $outputString = '%stringID%'; + + /** + * Targeted URL of strings without translations + * @var string + * @access protected + */ + var $url = '#'; + + // }}} + // {{{ get() + + /** + * Get translated string + * + * If the string is empty, return the $defaultText if not empty, + * the $stringID otherwise. + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param string $langID language ID + * @param string $defaultText Text to display when the string is empty + * + * @return string + */ + function get($stringID, $pageID = TRANSLATION2_DEFAULT_PAGEID, $langID = null, $defaultText = '') + { + if ($pageID == TRANSLATION2_DEFAULT_PAGEID) { + $pageID = $this->translation2->currentPageID; + } + $str = $this->translation2->get($stringID, $pageID, $langID); + if (!empty($str)) { + return $str; + } + if (!empty($defaultText)) { + return $this->_replaceParams($defaultText); + } + + $search = array( + '%stringID%', + '%stringID_url%', + '%pageID_url%', + '%url%' + ); + $replace = array( + $stringID, + urlencode($stringID), + urlencode($pageID), + $this->url + ); + return $this->_replaceParams( + $this->emptyPrefix + .str_replace($search, $replace, $this->outputString) + .$this->emptyPostfix + ); + //$str = (empty($defaultText) ? $this->emptyPrefix.$stringID.$this->emptyPostfix : $defaultText); + } + + // }}} + // {{{ getPage() + + /** + * Replace empty strings with their $stringID + * + * @param string $pageID page/group ID + * @param string $langID language ID + * + * @return array + */ + function getPage($pageID = TRANSLATION2_DEFAULT_PAGEID, $langID = null) + { + $data = $this->translation2->getPage($pageID, $langID); + return $this->replaceEmptyStringsWithKeys($data); + } + + // }}} + // {{{ getStringID + + /** + * Get the stringID for the given string. This method is the reverse of get(). + * If the requested string is unknown to the system, + * the requested string will be returned. + * + * @param string $string This is NOT the stringID, this is a real string. + * The method will search for its matching stringID, + * and then it will return the associate string in the + * selected language. + * @param string $pageID page/group ID + * + * @return string + */ + function &getStringID($string, $pageID = TRANSLATION2_DEFAULT_PAGEID) + { + if ($pageID == TRANSLATION2_DEFAULT_PAGEID) { + $pageID = $this->translation2->currentPageID; + } + $stringID = $this->storage->getStringID($string, $pageID); + if (empty($stringID)) { + $stringID = $string; + } + return $stringID; + } +} +?> \ No newline at end of file diff --git a/Translation2/Decorator/ErrorText.php b/Translation2/Decorator/ErrorText.php new file mode 100644 index 0000000..386a183 --- /dev/null +++ b/Translation2/Decorator/ErrorText.php @@ -0,0 +1,108 @@ + + * @copyright 2004-2007 Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ + +/** + * Load Translation2 decorator base class + */ +require_once 'Translation2/Decorator.php'; + +/** + * Decorator to provide a fallback text for empty strings. + * + * @category Internationalization + * @package Translation2 + * @author Lorenzo Alberton + * @copyright 2004-2007 Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ +class Translation2_Decorator_ErrorText extends Translation2_Decorator +{ + // {{{ get() + + /** + * Get translated string + * + * If the string is empty, return the error message + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param string $langID language ID + * @param string $defaultText Text to display when the string is empty + * + * @return string + */ + function get($stringID, $pageID = TRANSLATION2_DEFAULT_PAGEID, $langID = null, $defaultText = '') + { + $str = $this->translation2->get($stringID, $pageID, $langID, $defaultText); + if (empty($str)) { + $str = $this->translation2->getLang(null, 'error_text'); + } + return $str; + } + + // }}} + // {{{ getPage() + + /** + * Same as getRawPage, but resort to fallback language and + * replace parameters when needed + * + * @param string $pageID page/group ID + * @param string $langID language ID + * + * @return array + */ + function getPage($pageID = TRANSLATION2_DEFAULT_PAGEID, $langID = null) + { + $data = $this->translation2->getPage($pageID, $langID); + if (PEAR::isError($data)) { + return $data; + } + $error_text = str_replace('"', '\"', $this->translation2->getLang(null, 'error_text')); + array_walk( + $data, + create_function('&$w', 'if (empty($w)) $w = "'.$error_text.'";') + ); + return $data; + } + + // }}} +} +?> \ No newline at end of file diff --git a/Translation2/Decorator/Iconv.php b/Translation2/Decorator/Iconv.php new file mode 100644 index 0000000..e97da41 --- /dev/null +++ b/Translation2/Decorator/Iconv.php @@ -0,0 +1,156 @@ + + * @author Sergey Korotkov + * @copyright 2004-2007 Lorenzo Alberton, Sergey Korotkov + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ + +/** + * Load Translation2 decorator base class + */ +require_once 'Translation2/Decorator.php'; + +/** + * Translation2 Iconv Decorator + * + * Decorator to change the encoding of the stored translation to the + * one given in the 'encoding' option. + * + * + * $tr->setOptions(array('encoding' => 'UTF-8')); + * + * + * @category Internationalization + * @package Translation2 + * @author Lorenzo Alberton + * @author Sergey Korotkov + * @copyright 2004-2007 Lorenzo Alberton, Sergey Korotkov + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + * @see http://www.php.net/htmlentities for a list of available encodings. + */ +class Translation2_Decorator_Iconv extends Translation2_Decorator +{ + // {{{ class vars + + /** + * @var string + * @access private + */ + var $encoding = 'ISO-8859-1'; + + /** + * @var array + * @access private + */ + var $lang_encodings; + + // }}} + // {{{ _getEncoding() + + /** + * Get the encoding for the given langID + * + * @param string $langID language ID + * + * @return string encoding + * @access private + */ + function _getEncoding($langID = null) + { + if (!is_array($this->lang_encodings)) { + $this->lang_encodings = array(); + foreach ($this->translation2->getLangs('encodings') as $langID => $encoding) { + $this->lang_encodings[$langID] = $encoding; + } + } + if (!is_null($langID) && isset($this->lang_encodings[$langID])) { + return $this->lang_encodings[$langID]; + } + return $this->lang['encoding']; + } + + // }}} + // {{{ get() + + /** + * Get the translated string, in the new encoding + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param string $langID language ID + * @param string $defaultText Text to display when the string is empty + * + * @return string + */ + function get($stringID, $pageID = TRANSLATION2_DEFAULT_PAGEID, $langID = null, $defaultText = null) + { + $str = $this->translation2->get($stringID, $pageID, $langID, $defaultText); + if (PEAR::isError($str) || empty($str)) { + return $str; + } + return iconv($this->_getEncoding($langID), $this->encoding, $str); + } + + // }}} + // {{{ getPage() + + /** + * Same as getRawPage, but apply transformations when needed + * + * @param string $pageID page/group ID + * @param string $langID language ID + * + * @return array + */ + function getPage($pageID = TRANSLATION2_DEFAULT_PAGEID, $langID = null) + { + $data = $this->translation2->getPage($pageID, $langID); + if (PEAR::isError($data)) { + return $data; + } + $input_encoding = $this->_getEncoding($langID); + foreach (array_keys($data) as $k) { + if (!empty($data[$k])) { + $data[$k] = iconv($input_encoding, $this->encoding, $data[$k]); + } + } + return $data; + } + + // }}} +} +?> \ No newline at end of file diff --git a/Translation2/Decorator/Lang.php b/Translation2/Decorator/Lang.php new file mode 100644 index 0000000..6cc6710 --- /dev/null +++ b/Translation2/Decorator/Lang.php @@ -0,0 +1,149 @@ + + * @copyright 2004-2007 Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ + +/** + * Load Translation2 decorator base class + */ +require_once 'Translation2/Decorator.php'; + +/** + * Decorator to provide a fallback language for empty strings. + * + * @category Internationalization + * @package Translation2 + * @author Lorenzo Alberton + * @copyright 2004-2007 Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ +class Translation2_Decorator_Lang extends Translation2_Decorator +{ + // {{{ class vars + + /** + * fallback lang + * @var string + * @access protected + */ + var $fallbackLang; + + // }}} + // {{{ setOption() + + /** + * set Decorator option (intercept 'fallbackLang' option). + * I don't know why it's needed, but it doesn't work without. + * + * @param string $option option name + * @param mixed $value option value + * + * @return self + */ + function setOption($option, $value=null) + { + if ($option == 'fallbackLang') { + $this->fallbackLang = $value; + return $this; + } + return parent::setOption($option, $value); + } + + // }}} + // {{{ get() + + /** + * Get translated string + * + * If the string is empty, check the fallback language + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param string $langID language ID + * @param string $defaultText Text to display when the strings in both + * the default and the fallback lang are empty + * + * @return string + */ + function get($stringID, $pageID = TRANSLATION2_DEFAULT_PAGEID, $langID = null, $defaultText = '') + { + $str = $this->translation2->get($stringID, $pageID, $langID, $defaultText); + if (empty($str)) { + $str = $this->translation2->get($stringID, $pageID, $this->fallbackLang); + } + return $str; + } + + // }}} + // {{{ getPage() + + /** + * Same as getRawPage, but resort to fallback language and + * replace parameters when needed + * + * @param string $pageID page/group ID + * @param string $langID language ID + * + * @return array + */ + function getPage($pageID = TRANSLATION2_DEFAULT_PAGEID, $langID = null) + { + $data1 = $this->translation2->getPage($pageID, $langID); + if (PEAR::isError($data1)) { + return $data1; + } + $data2 = $this->translation2->getPage($pageID, $this->fallbackLang); + if (PEAR::isError($data2)) { + return $data2; + } + foreach ($data1 as $key => $val) { + if (empty($val)) { + $data1[$key] = $data2[$key]; + } + } + // append keys when fallback lang contains more than current + $diff = array_diff(array_keys($data2), array_keys($data1)); + foreach ($diff as $key) { + $data1[$key] = $data2[$key]; + } + return $data1; + } + + // }}} +} +?> \ No newline at end of file diff --git a/Translation2/Decorator/SpecialChars.php b/Translation2/Decorator/SpecialChars.php new file mode 100644 index 0000000..d08894f --- /dev/null +++ b/Translation2/Decorator/SpecialChars.php @@ -0,0 +1,125 @@ + + * @copyright 2004-2007 Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ + +/** + * Load Translation2 decorator base class + */ +require_once 'Translation2/Decorator.php'; + +/** + * Decorator to replace special chars with the matching html entities. + * + * You can set the charset to use (the default being 'ISO-8859-1'): + * + * $tr->setOptions(array('charset' => 'UTF-8')); + * + * + * @category Internationalization + * @package Translation2 + * @author Lorenzo Alberton + * @copyright 2004-2007 Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + * @see http://www.php.net/htmlentities for a list of available charsets. + */ +class Translation2_Decorator_SpecialChars extends Translation2_Decorator +{ + // {{{ class vars + + /** + * @var string + * @access protected + */ + var $charset = 'ISO-8859-1'; + + // }}} + // {{{ get() + + /** + * Get translated string + * + * replace special chars with the matching html entities + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param string $langID language ID + * @param string $defaultText Text to display when the string is empty + * + * @return string + */ + function get($stringID, $pageID=TRANSLATION2_DEFAULT_PAGEID, $langID=null, $defaultText=null) + { + $str = $this->translation2->get($stringID, $pageID, $langID, $defaultText); + if (PEAR::isError($str)) { + return $str; + } + if (!empty($str)) { + $str = htmlentities($str, ENT_QUOTES, $this->charset); + } + return $str; + } + + // }}} + // {{{ getPage() + + /** + * Same as getRawPage, but apply transformations when needed + * + * @param string $pageID page/group ID + * @param string $langID language ID + * + * @return array + */ + function getPage($pageID=TRANSLATION2_DEFAULT_PAGEID, $langID=null) + { + $data = $this->translation2->getPage($pageID, $langID); + if (PEAR::isError($data)) { + return $data; + } + foreach ($data as $key => $val) { + if (!empty($val)) { + $data[$key] = htmlentities($val, ENT_QUOTES, $this->charset); + } + } + return $data; + } + + // }}} +} +?> \ No newline at end of file diff --git a/Translation2/Decorator/UTF8.php b/Translation2/Decorator/UTF8.php new file mode 100644 index 0000000..c834bea --- /dev/null +++ b/Translation2/Decorator/UTF8.php @@ -0,0 +1,114 @@ + + * @copyright 2004-2007 Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ + +/** + * Load Translation2 decorator base class + */ +require_once 'Translation2/Decorator.php'; + +/** + * Decorator to convert UTF-8 strings to ISO-8859-1 + * + * @category Internationalization + * @package Translation2 + * @author Lorenzo Alberton + * @copyright 2004-2007 Lorenzo Alberton + * @license http://www.debian.org/misc/bsd.license BSD License (3 Clause) + * @version CVS: $Id$ + * @link http://pear.php.net/package/Translation2 + */ +class Translation2_Decorator_UTF8 extends Translation2_Decorator +{ + // {{{ get() + + /** + * Get translated string + * + * Decode the UTF-8 string to ISO-8859-1 + * + * @param string $stringID string ID + * @param string $pageID page/group ID + * @param string $langID language ID + * @param string $defaultText Text to display when the strings in both + * the default and the fallback lang are empty + * + * @return string + */ + function get($stringID, $pageID = TRANSLATION2_DEFAULT_PAGEID, $langID = null, $defaultText = null) + { + $str = $this->translation2->get($stringID, $pageID, $langID); + if (PEAR::isError($str)) { + return $str; + } + if (!empty($str)) { + $str = utf8_decode($str); //decodes an UTF-8 string to ISO-8859-1 + } + return $str; + } + + // }}} + // {{{ getPage() + + /** + * Same as getRawPage, but resort to fallback language and + * replace parameters when needed + * + * Decode each UTF-8 string in the group to ISO-8859-1 + * + * @param string $pageID page/group ID + * @param string $langID language ID + * + * @return array + */ + function getPage($pageID = TRANSLATION2_DEFAULT_PAGEID, $langID = null) + { + $data = $this->translation2->getPage($pageID, $langID); + if (PEAR::isError($data)) { + return $data; + } + foreach ($data as $key => $val) { + if (!empty($val)) { + $data[$key] = utf8_decode($val); + } + } + return $data; + } + + // }}} +} +?> \ No newline at end of file diff --git a/package.xml b/package.xml index 313eb22..de173b6 100644 --- a/package.xml +++ b/package.xml @@ -57,7 +57,7 @@ Req #16787 default 'latin1_swedish_ci' db created with collation set t utf8_unicode_ci - quipo - + @@ -124,7 +124,7 @@ utf8_unicode_ci - quipo - +