Index: branches/5.2.x/core/units/helpers/language_import_helper.php =================================================================== --- branches/5.2.x/core/units/helpers/language_import_helper.php (revision 15237) +++ branches/5.2.x/core/units/helpers/language_import_helper.php (revision 15238) @@ -1,1223 +1,1222 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ /** * Language pack format version description * * v1 * ========== * All language properties are separate nodes inside <LANGUAGE> node. There are * two more nodes PHRASES and EVENTS for phrase and email event translations. * * v2 * ========== * All data, that will end up in Language table is now attributes of LANGUAGE node * and is name exactly as field name, that will be used to store that data. * * v4 * ========== * Hint & Column translation added to each phrase translation * * v5 * ========== * Use separate xml nodes for subject, headers, html & plain translations * * v6 * ========== * Added e-mail design templates * */ defined('FULL_PATH') or die('restricted access!'); define('LANG_OVERWRITE_EXISTING', 1); define('LANG_SKIP_EXISTING', 2); class LanguageImportHelper extends kHelper { /** * Current Language in import * * @var LanguagesItem */ var $lang_object = null; /** * Current user's IP address * * @var string */ var $ip_address = ''; /** * Event type + name mapping to id (from system) * * @var Array */ var $events_hash = Array (); /** * Language pack import mode * * @var int */ var $import_mode = LANG_SKIP_EXISTING; /** * Language IDs, that were imported * * @var Array */ var $_languages = Array (); /** * Temporary table names to perform import on * * @var Array */ var $_tables = Array (); /** * Phrase types allowed for import/export operations * * @var Array */ var $phrase_types_allowed = Array (); /** * Encoding, used for language pack exporting * * @var string */ var $_exportEncoding = 'base64'; /** * Exported data limits (all or only specified ones) * * @var Array */ var $_exportLimits = Array ( 'phrases' => false, 'emailevents' => false, 'country-state' => false, ); /** * Debug language pack import process * * @var bool */ var $_debugMode = false; /** * Latest version of language pack format. Versions are not backwards compatible! * * @var int */ var $_latestVersion = 6; /** * Prefix-based serial numbers, that should be changed after import is finished * * @var Array */ var $changedPrefixes = Array (); public function __construct() { parent::__construct(); // "core/install/english.lang", phrase count: 3318, xml parse time on windows: 10s, insert time: 0.058s set_time_limit(0); ini_set('memory_limit', -1); $this->lang_object = $this->Application->recallObject('lang.import', null, Array ('skip_autoload' => true)); if (!(defined('IS_INSTALL') && IS_INSTALL)) { // perform only, when not in installation mode $this->_updateEventsCache(); } $this->ip_address = getenv('HTTP_X_FORWARDED_FOR') ? getenv('HTTP_X_FORWARDED_FOR') : getenv('REMOTE_ADDR'); // $this->_debugMode = $this->Application->isDebugMode(); } /** * Performs import of given language pack (former Parse method) * * @param string $filename * @param string $phrase_types * @param Array $module_ids * @param int $import_mode * @return bool */ function performImport($filename, $phrase_types, $module_ids, $import_mode = LANG_SKIP_EXISTING) { // define the XML parsing routines/functions to call based on the handler path if (!file_exists($filename) || !$phrase_types /*|| !$module_ids*/) { return false; } if ($this->_debugMode) { $start_time = microtime(true); $this->Application->Debugger->appendHTML(__CLASS__ . '::' . __FUNCTION__ . '("' . $filename . '")'); } if (defined('IS_INSTALL') && IS_INSTALL) { // new events could be added during module upgrade $this->_updateEventsCache(); } $this->_initImportTables(); $phrase_types = explode('|', substr($phrase_types, 1, -1) ); // $module_ids = explode('|', substr($module_ids, 1, -1) ); $this->phrase_types_allowed = array_flip($phrase_types); $this->import_mode = $import_mode; $this->_parseXML($filename); // copy data from temp tables to live foreach ($this->_languages as $language_id) { $this->_performUpgrade($language_id, 'phrases', 'PhraseKey', Array ('l%s_Translation', 'l%s_HintTranslation', 'l%s_ColumnTranslation', 'PhraseType')); $this->_performUpgrade($language_id, 'emailevents', 'EventId', Array ('l%s_Subject', 'Headers', 'l%s_HtmlBody', 'l%s_PlainTextBody')); $this->_performUpgrade($language_id, 'country-state', 'CountryStateId', Array ('l%s_Name')); } $this->_initImportTables(true); $this->changedPrefixes = array_unique($this->changedPrefixes); foreach ($this->changedPrefixes as $prefix) { $this->Application->incrementCacheSerial($prefix); } if ($this->_debugMode) { $this->Application->Debugger->appendHTML(__CLASS__ . '::' . __FUNCTION__ . '("' . $filename . '"): ' . (microtime(true) - $start_time)); } return true; } /** * Creates XML file with exported language data (former Create method) * * @param string $filename filename to export into * @param Array $phrase_types phrases types to export from modules passed in $module_ids * @param Array $language_ids IDs of languages to export * @param Array $module_ids IDs of modules to export phrases from */ function performExport($filename, $phrase_types, $language_ids, $module_ids) { $fp = fopen($filename,'w'); if (!$fp || !$phrase_types || !$module_ids || !$language_ids) { return false; } $phrase_types = explode('|', substr($phrase_types, 1, -1) ); $module_ids = explode('|', substr($module_ids, 1, -1) ); $ret = '<LANGUAGES Version="' . $this->_latestVersion . '">' . "\n"; $export_fields = $this->_getExportFields(); // get languages $sql = 'SELECT * FROM ' . $this->Application->getUnitOption('lang','TableName') . ' WHERE LanguageId IN (' . implode(',', $language_ids) . ')'; $languages = $this->Conn->Query($sql, 'LanguageId'); // get phrases $phrase_modules = $module_ids; array_push($phrase_modules, ''); // for old language packs without module $phrase_modules = $this->Conn->qstrArray($phrase_modules); // apply phrase selection limit if ($this->_exportLimits['phrases']) { $escaped_phrases = $this->Conn->qstrArray($this->_exportLimits['phrases']); $limit_where = 'Phrase IN (' . implode(',', $escaped_phrases) . ')'; } else { $limit_where = 'TRUE'; } $sql = 'SELECT * FROM ' . $this->Application->getUnitOption('phrases','TableName') . ' WHERE PhraseType IN (' . implode(',', $phrase_types) . ') AND Module IN (' . implode(',', $phrase_modules) . ') AND ' . $limit_where . ' ORDER BY Phrase'; $phrases = $this->Conn->Query($sql, 'PhraseId'); // email events $module_sql = preg_replace('/(.*),/U', 'INSTR(Module,\'\\1\') OR ', implode(',', $module_ids) . ','); // apply event selection limit if ($this->_exportLimits['emailevents']) { $escaped_email_events = $this->Conn->qstrArray($this->_exportLimits['emailevents']); $limit_where = '`Event` IN (' . implode(',', $escaped_email_events) . ')'; } else { $limit_where = 'TRUE'; } $sql = 'SELECT * FROM ' . $this->Application->getUnitOption('emailevents', 'TableName') . ' WHERE `Type` IN (' . implode(',', $phrase_types) . ') AND (' . substr($module_sql, 0, -4) . ') AND ' . $limit_where . ' ORDER BY `Event`, `Type`'; $events = $this->Conn->Query($sql, 'EventId'); if ( in_array('Core', $module_ids) ) { if ($this->_exportLimits['country-state']) { $escaped_countries = $this->Conn->qstrArray($this->_exportLimits['country-state']); $limit_where = '`IsoCode` IN (' . implode(',', $escaped_countries) . ')'; } else { $limit_where = 'TRUE'; } $country_table = $this->Application->getUnitOption('country-state', 'TableName'); // countries $sql = 'SELECT * FROM ' . $country_table . ' WHERE Type = ' . DESTINATION_TYPE_COUNTRY . ' AND ' . $limit_where . ' ORDER BY `IsoCode`'; $countries = $this->Conn->Query($sql, 'CountryStateId'); // states $sql = 'SELECT state.* FROM ' . $country_table . ' state JOIN ' . $country_table . ' country ON country.CountryStateId = state.StateCountryId WHERE state.Type = ' . DESTINATION_TYPE_STATE . ' AND ' . str_replace('`IsoCode`', 'country.`IsoCode`', $limit_where) . ' ORDER BY state.`IsoCode`'; $states = $this->Conn->Query($sql, 'CountryStateId'); foreach ($states as $state_id => $state_data) { $country_id = $state_data['StateCountryId']; if ( !array_key_exists('States', $countries[$country_id]) ) { $countries[$country_id]['States'] = Array (); } $countries[$country_id]['States'][] = $state_id; } } foreach ($languages as $language_id => $language_info) { // language $ret .= "\t" . '<LANGUAGE Encoding="' . $this->_exportEncoding . '"'; foreach ($export_fields as $export_field) { $ret .= ' ' . $export_field . '="' . htmlspecialchars($language_info[$export_field]) . '"'; } $ret .= '>' . "\n"; // filename replacements $replacements = $language_info['FilenameReplacements']; if ( $replacements ) { $ret .= "\t\t" . '<REPLACEMENTS>' . $this->_exportConvert($replacements) . '</REPLACEMENTS>' . "\n"; } // e-mail design templates if ( $language_info['HtmlEmailTemplate'] || $language_info['TextEmailTemplate'] ) { $ret .= "\t\t" . '<EMAILDESIGNS>' . "\n"; if ( $language_info['HtmlEmailTemplate'] ) { $ret .= "\t\t\t" . '<HTML>' . $this->_exportConvert($language_info['HtmlEmailTemplate']) . '</HTML>' . "\n"; } if ( $language_info['TextEmailTemplate'] ) { $ret .= "\t\t\t" . '<TEXT>' . $this->_exportConvert($language_info['TextEmailTemplate']) . '</TEXT>' . "\n"; } $ret .= "\t\t" . '</EMAILDESIGNS>' . "\n"; } // phrases if ($phrases) { $ret .= "\t\t" . '<PHRASES>' . "\n"; foreach ($phrases as $phrase_id => $phrase) { $translation = $phrase['l' . $language_id . '_Translation']; $hint_translation = $phrase['l' . $language_id . '_HintTranslation']; $column_translation = $phrase['l' . $language_id . '_ColumnTranslation']; if (!$translation) { // phrase is not translated on given language continue; } if ( $this->_exportEncoding == 'base64' ) { $hint_translation = base64_encode($hint_translation); $column_translation = base64_encode($column_translation); } else { $hint_translation = htmlspecialchars($hint_translation); $column_translation = htmlspecialchars($column_translation); } $attributes = Array ( 'Label="' . $phrase['Phrase'] . '"', 'Module="' . $phrase['Module'] . '"', 'Type="' . $phrase['PhraseType'] . '"' ); if ( $phrase['l' . $language_id . '_HintTranslation'] ) { $attributes[] = 'Hint="' . $hint_translation . '"'; } if ( $phrase['l' . $language_id . '_ColumnTranslation'] ) { $attributes[] = 'Column="' . $column_translation . '"'; } $ret .= "\t\t\t" . '<PHRASE ' . implode(' ', $attributes) . '>' . $this->_exportConvert($translation) . '</PHRASE>' . "\n"; } $ret .= "\t\t" . '</PHRASES>' . "\n"; } // email events if ($events) { $ret .= "\t\t" . '<EVENTS>' . "\n"; foreach ($events as $event_data) { $fields_hash = Array ( 'HEADERS' => $event_data['Headers'], 'SUBJECT' => $event_data['l' . $language_id . '_Subject'], 'HTMLBODY' => $event_data['l' . $language_id . '_HtmlBody'], 'PLAINTEXTBODY' => $event_data['l' . $language_id . '_PlainTextBody'], ); $data = ''; foreach ($fields_hash as $xml_node => $xml_content) { if ( $xml_content ) { $data .= "\t\t\t\t" . '<' . $xml_node . '>' . $this->_exportConvert($xml_content) . '</' . $xml_node . '>' . "\n"; } } if ( $data ) { $ret .= "\t\t\t" . '<EVENT Event="' . $event_data['Event'] . '" Type="' . $event_data['Type'] . '">' . "\n" . $data . "\t\t\t" . '</EVENT>' . "\n"; } } $ret .= "\t\t" . '</EVENTS>' . "\n"; } if (in_array('Core', $module_ids) && $countries) { $ret .= "\t\t" . '<COUNTRIES>' . "\n"; foreach ($countries as $country_data) { $translation = $country_data['l' . $language_id . '_Name']; if (!$translation) { // country is not translated on given language continue; } $data = $this->_exportEncoding == 'base64' ? base64_encode($translation) : $translation; if (array_key_exists('States', $country_data)) { $ret .= "\t\t\t" . '<COUNTRY Iso="' . $country_data['IsoCode'] . '" Translation="' . $data . '">' . "\n"; foreach ($country_data['States'] as $state_id) { $translation = $states[$state_id]['l' . $language_id . '_Name']; if (!$translation) { // state is not translated on given language continue; } $data = $this->_exportEncoding == 'base64' ? base64_encode($translation) : $translation; $ret .= "\t\t\t\t" . '<STATE Iso="' . $states[$state_id]['IsoCode'] . '" Translation="' . $data . '"/>' . "\n"; } $ret .= "\t\t\t" . '</COUNTRY>' . "\n"; } else { $ret .= "\t\t\t" . '<COUNTRY Iso="' . $country_data['IsoCode'] . '" Translation="' . $data . '"/>' . "\n"; } } $ret .= "\t\t" . '</COUNTRIES>' . "\n"; } $ret .= "\t" . '</LANGUAGE>' . "\n"; } $ret .= '</LANGUAGES>'; fwrite($fp, $ret); fclose($fp); return true; } /** * Converts string before placing into export file * * @param string $string * @return string * @access protected */ protected function _exportConvert($string) { return $this->_exportEncoding == 'base64' ? base64_encode($string) : '<![CDATA[' . $string . ']]>'; } /** * Sets language pack encoding (not charset) used during export * * @param string $encoding */ function setExportEncoding($encoding) { $this->_exportEncoding = $encoding; } /** * Sets language pack data limit for export * * @param string $prefix * @param string $data */ function setExportLimit($prefix, $data = null) { if ( !isset($data) ) { $key_field = $prefix == 'phrases' ? 'Phrase' : 'Event'; $ids = $this->getExportIDs($prefix); $sql = 'SELECT ' . $key_field . ' FROM ' . $this->Application->getUnitOption($prefix, 'TableName') . ' WHERE ' . $this->Application->getUnitOption($prefix, 'IDField') . ' IN (' . $ids . ')'; $rs = $this->Conn->QueryRaw($sql); if ( $this->Conn->RowCount($rs) ) { $data = ''; while ( ($row = $this->Conn->GetNextRow($rs)) ) { $data .= ',' . $row[$key_field]; } $data = substr($data, 1); } $this->Conn->Destroy($rs); } if ( !is_array($data) ) { $data = str_replace(',', "\n", $data); $data = preg_replace("/\n+/", "\n", str_replace("\r", '', trim($data))); $data = $data ? array_map('trim', explode("\n", $data)) : Array (); } $this->_exportLimits[$prefix] = $data; } /** * Performs upgrade of given language pack part * * @param int $language_id * @param string $prefix * @param string $unique_field * @param Array $data_fields */ function _performUpgrade($language_id, $prefix, $unique_field, $data_fields) { $live_records = $this->_getTableData($language_id, $prefix, $unique_field, $data_fields[0], false); $temp_records = $this->_getTableData($language_id, $prefix, $unique_field, $data_fields[0], true); if (!$temp_records) { // no data for given language return ; } // perform insert for records, that are missing in live table $to_insert = array_diff($temp_records, $live_records); if ($to_insert) { $to_insert = $this->Conn->qstrArray($to_insert); $sql = 'INSERT INTO ' . $this->Application->getUnitOption($prefix, 'TableName') . ' SELECT * FROM ' . $this->_tables[$prefix] . ' WHERE ' . $unique_field . ' IN (' . implode(',', $to_insert) . ')'; $this->Conn->Query($sql); // new records were added $this->changedPrefixes[] = $prefix; } // perform update for records, that are present in live table $to_update = array_diff($temp_records, $to_insert); if ($to_update) { $to_update = $this->Conn->qstrArray($to_update); $sql = 'UPDATE ' . $this->Application->getUnitOption($prefix, 'TableName') . ' live SET '; foreach ($data_fields as $index => $data_field) { $data_field = sprintf($data_field, $language_id); $sql .= ' live.' . $data_field . ' = ( SELECT temp' . $index . '.' . $data_field . ' FROM ' . $this->_tables[$prefix] . ' temp' . $index . ' WHERE temp' . $index . '.' . $unique_field . ' = live.' . $unique_field . ' ),'; } $sql = substr($sql, 0, -1); // cut last comma $where_clause = Array ( // this won't make any difference, but just in case $unique_field . ' IN (' . implode(',', $to_update) . ')', ); if ($this->import_mode == LANG_SKIP_EXISTING) { // empty OR not set $data_field = sprintf($data_fields[0], $language_id); $where_clause[] = '(' . $data_field . ' = "") OR (' . $data_field . ' IS NULL)'; } if ($where_clause) { $sql .= "\n" . 'WHERE (' . implode(') AND (', $where_clause) . ')'; } $this->Conn->Query($sql); if ($this->Conn->getAffectedRows() > 0) { // existing records were updated $this->changedPrefixes[] = $prefix; } } } /** * Returns data from given table used for language pack upgrade * * @param int $language_id * @param string $prefix * @param string $unique_field * @param string $data_field * @param bool $temp_mode * @return Array */ function _getTableData($language_id, $prefix, $unique_field, $data_field, $temp_mode = false) { $data_field = sprintf($data_field, $language_id); $table_name = $this->Application->getUnitOption($prefix, 'TableName'); if ($temp_mode) { // for temp table get only records, that have contents on given language (not empty and isset) $sql = 'SELECT ' . $unique_field . ' FROM ' . $this->Application->GetTempName($table_name, 'prefix:' . $prefix) . ' WHERE (' . $data_field . ' <> "") AND (' . $data_field . ' IS NOT NULL)'; } else { // for live table get all records, no matter on what language $sql = 'SELECT ' . $unique_field . ' FROM ' . $table_name; } return $this->Conn->GetCol($sql); } function _parseXML($filename) { if ( $this->_debugMode ) { $start_time = microtime(true); $this->Application->Debugger->appendHTML(__CLASS__ . '::' . __FUNCTION__ . '("' . $filename . '")'); } $languages = simplexml_load_file($filename); if ( $languages === false) { // invalid language pack contents return false; } // PHP 5.3 version would be: $languages->count() if ( count($languages->children()) ) { $this->_processLanguages($languages); } if ( $this->_debugMode ) { $this->Application->Debugger->appendHTML(__CLASS__ . '::' . __FUNCTION__ . '("' . $filename . '"): ' . (microtime(true) - $start_time)); } return true; } /** * Creates temporary tables, used during language import * * @param bool $drop_only */ function _initImportTables($drop_only = false) { $this->_tables['phrases'] = $this->_prepareTempTable('phrases', $drop_only); $this->_tables['emailevents'] = $this->_prepareTempTable('emailevents', $drop_only); $this->_tables['country-state'] = $this->_prepareTempTable('country-state', $drop_only); } /** * Create temp table for prefix, if table already exists, then delete it and create again * * @param string $prefix * @param bool $drop_only * @return string Name of created temp table * @access protected */ protected function _prepareTempTable($prefix, $drop_only = false) { $id_field = $this->Application->getUnitOption($prefix, 'IDField'); $table = $this->Application->getUnitOption($prefix,'TableName'); $temp_table = $this->Application->GetTempName($table); $sql = 'DROP TABLE IF EXISTS %s'; $this->Conn->Query( sprintf($sql, $temp_table) ); if (!$drop_only) { $sql = 'CREATE TABLE ' . $temp_table . ' SELECT * FROM ' . $table . ' WHERE 0'; $this->Conn->Query($sql); $sql = 'ALTER TABLE %1$s CHANGE %2$s %2$s INT(11) NOT NULL DEFAULT "0"'; $this->Conn->Query( sprintf($sql, $temp_table, $id_field) ); switch ($prefix) { case 'phrases': $unique_field = 'PhraseKey'; break; case 'emailevents': $unique_field = 'EventId'; break; case 'country-state': $unique_field = 'CountryStateId'; break; default: throw new Exception('Unknown prefix "<strong>' . $prefix . '</strong>" during language pack import'); break; } $sql = 'ALTER TABLE ' . $temp_table . ' ADD UNIQUE (' . $unique_field . ')'; $this->Conn->Query($sql); } return $temp_table; } /** * Prepares mapping between event name+type and their ids in database * */ function _updateEventsCache() { $sql = 'SELECT EventId, CONCAT(Event,"_",Type) AS EventMix FROM ' . TABLE_PREFIX . 'EmailEvents'; $this->events_hash = $this->Conn->GetCol($sql, 'EventMix'); } /** * Returns language fields to be exported * * @return Array */ function _getExportFields() { return Array ( 'PackName', 'LocalName', 'DateFormat', 'TimeFormat', 'InputDateFormat', 'InputTimeFormat', 'DecimalPoint', 'ThousandSep', 'Charset', 'UnitSystem', 'Locale', 'UserDocsUrl' ); } /** * Processes parsed XML * * @param SimpleXMLElement $languages */ function _processLanguages($languages) { $version = (int)$languages['Version']; if ( !$version ) { // version missing -> guess it if ( $languages->DATEFORMAT->getName() ) { $version = 1; } elseif ( (string)$languages->LANGUAGE['Charset'] != '' ) { $version = 2; } } if ( $version == 1 ) { $field_mapping = Array ( 'DATEFORMAT' => 'DateFormat', 'TIMEFORMAT' => 'TimeFormat', 'INPUTDATEFORMAT' => 'InputDateFormat', 'INPUTTIMEFORMAT' => 'InputTimeFormat', 'DECIMAL' => 'DecimalPoint', 'THOUSANDS' => 'ThousandSep', 'CHARSET' => 'Charset', 'UNITSYSTEM' => 'UnitSystem', 'DOCS_URL' => 'UserDocsUrl', ); } else { $export_fields = $this->_getExportFields(); } foreach ($languages as $language_node) { $language_id = false; $fields_hash = Array ( 'PackName' => (string)$language_node['PackName'], 'LocalName' => (string)$language_node['PackName'], 'Encoding' => (string)$language_node['Encoding'], 'Charset' => 'utf-8', 'SynchronizationModes' => Language::SYNCHRONIZE_DEFAULT, - 'HtmlEmailTemplate' => '$body', ); if ( $version > 1 ) { foreach ($export_fields as $export_field) { if ( (string)$language_node[$export_field] ) { $fields_hash[$export_field] = (string)$language_node[$export_field]; } } } $container_nodes = Array ('PHRASES', 'EVENTS', 'COUNTRIES'); foreach ($language_node as $sub_node) { /* @var $sub_node SimpleXMLElement */ if ( in_array($sub_node->getName(), $container_nodes) ) { // PHP 5.3 version would be: !$sub_node->count() if ( !count($sub_node->children()) ) { continue; } if ( !$language_id ) { $language_id = $this->_processLanguage($fields_hash); } } switch ($sub_node->getName()) { case 'PHRASES': $this->_processPhrases($sub_node, $language_id, $fields_hash['Encoding']); break; case 'EVENTS': $this->_processEvents($sub_node, $language_id, $fields_hash['Encoding']); break; case 'COUNTRIES': $this->_processCountries($sub_node, $language_id, $fields_hash['Encoding']); break; case 'REPLACEMENTS': // added since v2 $replacements = (string)$sub_node; if ( $fields_hash['Encoding'] != 'plain' ) { $replacements = base64_decode($replacements); } $fields_hash['FilenameReplacements'] = $replacements; break; case 'EMAILDESIGNS': // added since v6 $this->_decodeEmailDesignTemplate($fields_hash, 'HtmlEmailTemplate', (string)$sub_node->HTML); $this->_decodeEmailDesignTemplate($fields_hash, 'TextEmailTemplate', (string)$sub_node->TEXT); break; default: if ( $version == 1 ) { $fields_hash[$field_mapping[$sub_node->Name]] = (string)$sub_node; } break; } } } } /** * Decodes e-mail template design from language pack * * @param Array $fields_hash * @param string $field * @param string $design_template */ protected function _decodeEmailDesignTemplate(&$fields_hash, $field, $design_template) { if ( $fields_hash['Encoding'] != 'plain' ) { $design_template = base64_decode($design_template); } if ( $design_template ) { $fields_hash[$field] = $design_template; } } /** * Performs phases import * * @param SimpleXMLElement $phrases * @param int $language_id * @param string $language_encoding */ function _processPhrases($phrases, $language_id, $language_encoding) { static $other_translations = Array (); if ( $this->Application->isDebugMode() ) { $this->Application->Debugger->profileStart('L[' . $language_id . ']P', 'Language: ' . $language_id . '; Phrases Import'); } foreach ($phrases as $phrase_node) { /* @var $phrase_node SimpleXMLElement */ $phrase_key = mb_strtoupper($phrase_node['Label']); $fields_hash = Array ( 'Phrase' => (string)$phrase_node['Label'], 'PhraseKey' => $phrase_key, 'PhraseType' => (int)$phrase_node['Type'], 'Module' => (string)$phrase_node['Module'] ? (string)$phrase_node['Module'] : 'Core', 'LastChanged' => TIMENOW, 'LastChangeIP' => $this->ip_address, ); $translation = (string)$phrase_node; $hint_translation = (string)$phrase_node['Hint']; $column_translation = (string)$phrase_node['Column']; if ( array_key_exists($fields_hash['PhraseType'], $this->phrase_types_allowed) ) { if ( $language_encoding != 'plain' ) { $translation = base64_decode($translation); $hint_translation = base64_decode($hint_translation); $column_translation = base64_decode($column_translation); } if ( array_key_exists($phrase_key, $other_translations) ) { $other_translations[$phrase_key]['l' . $language_id . '_Translation'] = $translation; $other_translations[$phrase_key]['l' . $language_id . '_HintTranslation'] = $hint_translation; $other_translations[$phrase_key]['l' . $language_id . '_ColumnTranslation'] = $column_translation; } else { $other_translations[$phrase_key] = Array ( 'l' . $language_id . '_Translation' => $translation, 'l' . $language_id . '_HintTranslation' => $hint_translation, 'l' . $language_id . '_ColumnTranslation' => $column_translation, ); } $fields_hash = array_merge($fields_hash, $other_translations[$phrase_key]); $this->Conn->doInsert($fields_hash, $this->_tables['phrases'], 'REPLACE', false); } } if ( $this->Application->isDebugMode() ) { $this->Application->Debugger->profileFinish('L[' . $language_id . ']P', 'Language: ' . $language_id . '; Phrases Import'); } $this->Conn->doInsert($fields_hash, $this->_tables['phrases'], 'REPLACE'); } /** * Performs email event import * * @param SimpleXMLElement $events * @param int $language_id * @param string $language_encoding */ function _processEvents($events, $language_id, $language_encoding) { static $other_translations = Array (); if ( $this->Application->isDebugMode() ) { $this->Application->Debugger->profileStart('L[' . $language_id . ']E', 'Language: ' . $language_id . '; Events Import'); } $email_message_helper = $this->Application->recallObject('kEmailMessageHelper'); /* @var $email_message_helper kEmailMessageHelper */ foreach ($events as $event_node) { /* @var $event_node SimpleXMLElement */ $message_type = (string)$event_node['MessageType']; $event_id = $this->_getEventId((string)$event_node['Event'], (int)$event_node['Type']); if ( !$event_id ) { continue; } $fields_hash = Array ( 'EventId' => $event_id, 'Event' => (string)$event_node['Event'], 'Type' => (int)$event_node['Type'], ); if ( $message_type == '' ) { $parsed = $email_message_helper->parseTemplate($event_node, ''); $parsed = array_map($language_encoding == 'plain' ? 'rtrim' : 'base64_decode', $parsed); } else { $template = $language_encoding == 'plain' ? rtrim($event_node) : base64_decode($event_node); $parsed = $email_message_helper->parseTemplate($template, $message_type); } if ( array_key_exists($event_id, $other_translations) ) { $other_translations[$event_id]['l' . $language_id . '_Subject'] = $parsed['Subject']; $other_translations[$event_id]['l' . $language_id . '_HtmlBody'] = $parsed['HtmlBody']; $other_translations[$event_id]['l' . $language_id . '_PlainTextBody'] = $parsed['PlainTextBody']; } else { $other_translations[$event_id] = Array ( 'l' . $language_id . '_Subject' => $parsed['Subject'], 'l' . $language_id . '_HtmlBody' => $parsed['HtmlBody'], 'l' . $language_id . '_PlainTextBody' => $parsed['PlainTextBody'], ); } if ( $parsed['Headers'] ) { $other_translations[$event_id]['Headers'] = $parsed['Headers']; } elseif ( !$parsed['Headers'] && !array_key_exists('Headers', $other_translations[$event_id]) ) { $other_translations[$event_id]['Headers'] = $parsed['Headers']; } $fields_hash = array_merge($fields_hash, $other_translations[$event_id]); $this->Conn->doInsert($fields_hash, $this->_tables['emailevents'], 'REPLACE', false); } if ( $this->Application->isDebugMode() ) { $this->Application->Debugger->profileFinish('L[' . $language_id . ']E', 'Language: ' . $language_id . '; Events Import'); } if ( isset($fields_hash) ) { // at least one email event in language pack was found in database $this->Conn->doInsert($fields_hash, $this->_tables['emailevents'], 'REPLACE'); } } /** * Performs country_state translation import * * @param SimpleXMLElement $country_states * @param int $language_id * @param string $language_encoding * @param bool $process_states * @return void */ function _processCountries($country_states, $language_id, $language_encoding, $process_states = false) { static $other_translations = Array (); foreach ($country_states as $country_state_node) { /* @var $country_state_node SimpleXMLElement */ if ( $process_states ) { $country_state_id = $this->_getStateId((string)$country_states['Iso'], (string)$country_state_node['Iso']); } else { $country_state_id = $this->_getCountryId((string)$country_state_node['Iso']); } if ( !$country_state_id ) { continue; } if ( $language_encoding == 'plain' ) { $translation = rtrim($country_state_node['Translation']); } else { $translation = base64_decode($country_state_node['Translation']); } $fields_hash = Array ('CountryStateId' => $country_state_id); if ( array_key_exists($country_state_id, $other_translations) ) { $other_translations[$country_state_id]['l' . $language_id . '_Name'] = $translation; } else { $other_translations[$country_state_id] = Array ('l' . $language_id . '_Name' => $translation); } $fields_hash = array_merge($fields_hash, $other_translations[$country_state_id]); $this->Conn->doInsert($fields_hash, $this->_tables['country-state'], 'REPLACE', false); // PHP 5.3 version would be: $country_state_node->count() if ( !$process_states && count($country_state_node->children()) ) { $this->_processCountries($country_state_node, $language_id, $language_encoding, true); } } $this->Conn->doInsert($fields_hash, $this->_tables['country-state'], 'REPLACE'); } /** * Creates/updates language based on given fields and returns it's id * * @param Array $fields_hash * @return int */ function _processLanguage($fields_hash) { // 1. get language from database $sql = 'SELECT ' . $this->lang_object->IDField . ' FROM ' . $this->lang_object->TableName . ' WHERE PackName = ' . $this->Conn->qstr($fields_hash['PackName']); $language_id = $this->Conn->GetOne($sql); if ($language_id) { // 2. language found -> update, when allowed $this->lang_object->Load($language_id); if ($this->import_mode == LANG_OVERWRITE_EXISTING) { // update live language record based on data from xml $this->lang_object->SetFieldsFromHash($fields_hash); $this->lang_object->Update(); } } else { // 3. language not found -> create $this->lang_object->SetFieldsFromHash($fields_hash); $this->lang_object->SetDBField('Enabled', STATUS_ACTIVE); if ($this->lang_object->Create()) { $language_id = $this->lang_object->GetID(); if (defined('IS_INSTALL') && IS_INSTALL) { // language created during install becomes admin interface language $this->lang_object->setPrimary(true, true); } } } // 4. collect ID of every processed language if (!in_array($language_id, $this->_languages)) { $this->_languages[] = $language_id; } return $language_id; } /** * Returns event id based on it's name and type * * @param string $event_name * @param string $event_type * @return int */ function _getEventId($event_name, $event_type) { $cache_key = $event_name . '_' . $event_type; return array_key_exists($cache_key, $this->events_hash) ? $this->events_hash[$cache_key] : 0; } /** * Returns country id based on it's 3letter ISO code * * @param string $iso * @return int */ function _getCountryId($iso) { static $cache = null; if (!isset($cache)) { $sql = 'SELECT CountryStateId, IsoCode FROM ' . TABLE_PREFIX . 'CountryStates WHERE Type = ' . DESTINATION_TYPE_COUNTRY; $cache = $this->Conn->GetCol($sql, 'IsoCode'); } return array_key_exists($iso, $cache) ? $cache[$iso] : false; } /** * Returns state id based on 3letter country ISO code and 2letter state ISO code * * @param string $country_iso * @param string $state_iso * @return int */ function _getStateId($country_iso, $state_iso) { static $cache = null; if (!isset($cache)) { $sql = 'SELECT CountryStateId, CONCAT(StateCountryId, "-", IsoCode) AS IsoCode FROM ' . TABLE_PREFIX . 'CountryStates WHERE Type = ' . DESTINATION_TYPE_STATE; $cache = $this->Conn->GetCol($sql, 'IsoCode'); } $country_id = $this->_getCountryId($country_iso); return array_key_exists($country_id . '-' . $state_iso, $cache) ? $cache[$country_id . '-' . $state_iso] : false; } /** * Returns comma-separated list of IDs, that will be exported * * @param string $prefix * @return string * @access public */ public function getExportIDs($prefix) { $ids = $this->Application->RecallVar($prefix . '_selected_ids'); if ( $ids ) { // some records were selected in grid return $ids; } $tag_params = Array ( 'grid' => $prefix == 'phrases' ? 'Phrases' : 'Emails', 'skip_counting' => 1, 'per_page' => -1 ); $list = $this->Application->recallObject($prefix, $prefix . '_List', $tag_params); /* @var $list kDBList */ $sql = $list->getCountSQL($list->GetSelectSQL()); $sql = str_replace('COUNT(*) AS count', $list->TableName . '.' . $list->IDField, $sql); $ids = ''; $rs = $this->Conn->QueryRaw($sql); if ( $this->Conn->RowCount($rs) ) { while ( ($row = $this->Conn->GetNextRow($rs)) ) { $ids .= ',' . $row[$list->IDField]; } $ids = substr($ids, 1); } $this->Conn->Destroy($rs); return $ids; } } \ No newline at end of file Index: branches/5.2.x/core/units/languages/languages_config.php =================================================================== --- branches/5.2.x/core/units/languages/languages_config.php (revision 15237) +++ branches/5.2.x/core/units/languages/languages_config.php (revision 15238) @@ -1,273 +1,273 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); $config = Array ( 'Prefix' => 'lang', 'ItemClass' => Array ('class' => 'LanguagesItem', 'file' => 'languages_item.php', 'build_event' => 'OnItemBuild'), 'ListClass' => Array ('class' => 'kDBList', 'file' => '', 'build_event' => 'OnListBuild'), 'EventHandlerClass' => Array ('class' => 'LanguagesEventHandler', 'file' => 'languages_event_handler.php', 'build_event' => 'OnBuild'), 'TagProcessorClass' => Array ('class' => 'LanguagesTagProcessor', 'file' => 'languages_tag_processor.php', 'build_event' => 'OnBuild'), 'AutoLoad' => true, 'Hooks' => Array ( Array ( 'Mode' => hAFTER, 'Conditional' => false, 'HookToPrefix' => 'lang', 'HookToSpecial' => '*', 'HookToEvent' => Array('OnSave', 'OnMassDelete'), 'DoPrefix' => '', 'DoSpecial' => '', 'DoEvent' => 'OnScheduleTopFrameReload', ), ), 'QueryString' => Array ( 1 => 'id', 2 => 'Page', 3 => 'PerPage', 4 => 'event', 5 => 'mode', ), 'IDField' => 'LanguageId', 'StatusField' => Array ('Enabled', 'PrimaryLang'), // field, that is affected by Approve/Decline events 'TitleField' => 'PackName', // field, used in bluebar when editing existing item 'TitlePresets' => Array ( 'default' => Array ( 'new_status_labels' => Array ('lang' => '!la_title_Adding_Language!'), 'edit_status_labels' => Array ('lang' => '!la_title_Editing_Language!'), ), 'languages_list' => Array ( 'prefixes' => Array ('lang_List'), 'format' => "!la_title_Configuration! - !la_title_LanguagePacks!", 'toolbar_buttons' => Array ( 'new_item', 'edit', 'delete', 'export', 'import', 'setprimary', 'refresh', 'view', 'dbl-click' ), ), 'languages_edit_general' => Array ('prefixes' => Array ('lang'), 'format' => "#lang_status# '#lang_titlefield#' - !la_title_General!"), 'phrases_list' => Array ( 'prefixes' => Array ('lang', 'phrases_List'), 'format' => "#lang_status# '#lang_titlefield#' - !la_title_Labels!" ), 'phrase_edit' => Array ( 'prefixes' => Array ('phrases'), 'new_status_labels' => Array ('phrases' => '!la_title_Adding_Phrase!'), 'edit_status_labels' => Array ('phrases' => '!la_title_Editing_Phrase!'), 'format' => "#phrases_status# '#phrases_titlefield#'", ), 'import_language' => Array ( 'prefixes' => Array ('phrases.import'), 'format' => "!la_title_InstallLanguagePackStep1!", ), 'import_language_step2' => Array ( 'prefixes' => Array ('phrases.import'), 'format' => "!la_title_InstallLanguagePackStep2!", ), 'export_language' => Array ( 'prefixes' => Array ('phrases.export'), 'format' => "!la_title_ExportLanguagePackStep1!", ), 'export_language_results' => Array ( 'prefixes' => Array(), 'format' => "!la_title_ExportLanguagePackResults!", ), 'events_list' => Array ( 'prefixes' => Array ('lang', 'emailevents_List'), 'format' => "#lang_status# '#lang_titlefield#' - !la_title_EmailEvents!", ), 'email_messages_edit' => Array ( 'prefixes' => Array ('lang', 'emailevents'), 'format' => "#lang_status# '#lang_titlefield#' - !la_title_EditingEmailEvent! '#emailevents_titlefield#'", 'toolbar_buttons' => Array ('select', 'cancel', 'prev', 'next'), ), // for separate language list 'languages_list_st' => Array ( 'prefixes' => Array ('lang_List'), 'format' => "!la_title_LanguagesManagement!", ), ), 'EditTabPresets' => Array ( 'Default' => Array ( 'general' => Array ('title' => 'la_tab_General', 't' => 'regional/languages_edit', 'priority' => 1), 'labels' => Array ('title' => 'la_tab_Labels', 't' => 'regional/languages_edit_phrases', 'priority' => 2), 'email_events' => Array ('title' => 'la_tab_EmailEvents', 't' => 'regional/languages_edit_email_events', 'priority' => 3), ), ), 'PermSection' => Array ('main' => 'in-portal:configure_lang'), 'Sections' => Array ( 'in-portal:configure_lang' => Array ( 'parent' => 'in-portal:website_setting_folder', 'icon' => 'conf_regional', 'label' => 'la_tab_Regional', 'url' => Array ('t' => 'regional/languages_list', 'pass' => 'm'), 'permissions' => Array ('view', 'add', 'edit', 'delete', 'advanced:set_primary', 'advanced:import', 'advanced:export'), 'priority' => 4, 'type' => stTREE, ), // "Lang. Management" /*'in-portal:lang_management' => Array ( 'parent' => 'in-portal:system', 'icon' => 'core:settings_general', 'label' => 'la_title_LangManagement', 'url' => Array ('t' => 'languages/language_list', 'pass' => 'm'), 'permissions' => Array ('view', 'add', 'edit', 'delete'), 'perm_prefix' => 'lang', 'priority' => 10.03, 'show_mode' => smSUPER_ADMIN, 'type' => stTREE, ),*/ ), 'TableName' => TABLE_PREFIX . 'Languages', 'AutoDelete' => true, 'AutoClone' => true, 'ListSQLs' => Array ('' => 'SELECT * FROM %s'), 'ItemSQLs' => Array ('' => 'SELECT * FROM %s'), 'ListSortings' => Array ( '' => Array ( 'Sorting' => Array ('Priority' => 'desc', 'PackName' => 'asc'), ), ), 'Fields' => Array ( 'LanguageId' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), 'PackName' => Array ( 'type' => 'string', 'formatter' => 'kOptionsFormatter', 'options_sql' => 'SELECT %s FROM ' . TABLE_PREFIX . 'Languages ORDER BY PackName', 'option_title_field' => 'PackName', 'option_key_field' => 'PackName', 'not_null' => 1, 'required' => 1, 'default' => '' ), 'LocalName' => Array ( 'type' => 'string', 'formatter' => 'kOptionsFormatter', 'options_sql' => 'SELECT %s FROM ' . TABLE_PREFIX . 'Languages ORDER BY PackName', 'option_title_field' => 'LocalName', 'option_key_field' => 'LocalName', 'not_null' => 1, 'required' => 1, 'default' => '' ), 'Enabled' => Array ( 'type' => 'int', 'formatter' => 'kOptionsFormatter', 'options' => Array (0 => 'la_Disabled', 1 => 'la_Active'), 'use_phrases' => 1, 'not_null' => 1, 'default' => 1 ), 'PrimaryLang' => Array( 'type' => 'int', 'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Yes', 0 => 'la_No'), 'use_phrases' => 1, 'not_null' => 1, 'default' => 0 ), 'AdminInterfaceLang' => Array ( 'type' => 'int', 'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Yes', 0 => 'la_No'), 'use_phrases' => 1, 'not_null' => 1, 'default' => 0 ), 'Priority' => Array ('type' => 'int', 'not_null' => 1, 'default' => 0), 'IconURL' => Array ('type' => 'string', 'max_len' => 255, 'default' => NULL), 'IconDisabledURL' => Array ('type' => 'string', 'max_len' => 255, 'default' => NULL), 'InputDateFormat' => Array ( 'type' => 'string', 'formatter' => 'kOptionsFormatter', 'options' => Array ('m/d/Y' => 'mm/dd/yyyy', 'd/m/Y' => 'dd/mm/yyyy', 'm.d.Y' => 'mm.dd.yyyy', 'd.m.Y' => 'dd.mm.yyyy'), 'not_null' => 1, 'required' => 1, 'default' => 'm/d/Y' ), 'InputTimeFormat' => Array ( 'type' => 'string', 'formatter' => 'kOptionsFormatter', 'options' => Array ('g:i:s A' => 'g:i:s A', 'g:i A' => 'g:i A', 'H:i:s' => 'H:i:s', 'H:i' => 'H:i'), 'not_null' => '1', 'required' => 1, 'default' => 'g:i:s A', ), 'DateFormat' => Array ('type' => 'string', 'not_null' => 1, 'required' => 1, 'default' => 'm/d/Y'), 'TimeFormat' => Array ('type' => 'string', 'not_null' => 1, 'required' => 1, 'default' => 'g:i:s A'), 'DecimalPoint' => Array ('type' => 'string', 'not_null' => 1, 'required' => 1, 'default' => '.'), 'ThousandSep' => Array ('type' => 'string', 'not_null' => 1, 'default' => ''), 'Charset' => Array ('type' => 'string', 'not_null' => '1', 'required' => 1, 'default' => 'utf-8'), 'UnitSystem' => Array ( 'type' => 'int', 'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_Metric', 2 => 'la_US_UK'), 'use_phrases' => 1, 'not_null' => 1, 'default' => 1 ), 'FilenameReplacements' => Array ('type' => 'string', 'default' => NULL), 'Locale' => Array ( 'type' => 'string', 'formatter' => 'kOptionsFormatter', 'options_sql' => " SELECT CONCAT(LocaleName, ' ' ,'\/',Locale,'\/') AS Name, Locale FROM " . TABLE_PREFIX . 'LocalesList ORDER BY LocaleId', 'option_title_field' => 'Name', 'option_key_field' => 'Locale', 'not_null' => 1, 'default' => 'en-US', ), 'UserDocsUrl' => Array ('type' => 'string', 'max_len' => 255, 'not_null' => 1, 'default' => ''), 'SynchronizationModes' => Array ( 'type' => 'string', 'max_len' => 255, 'formatter' => 'kOptionsFormatter', 'options' => Array (1 => 'la_opt_SynchronizeToOthers', 2 => 'la_opt_SynchronizeFromOthers'), 'use_phrases' => 1, 'multiple' => 1, 'not_null' => 1, 'default' => '' ), 'HtmlEmailTemplate' => Array ( 'type' => 'string', 'error_msgs' => Array ('parsing_error' => '!la_error_ParsingError!', 'body_missing' => '!la_error_EmailTemplateBodyMissing!'), - 'default' => NULL + 'default' => '$body' ), 'TextEmailTemplate' => Array ( 'type' => 'string', 'error_msgs' => Array ('parsing_error' => '!la_error_ParsingError!', 'body_missing' => '!la_error_EmailTemplateBodyMissing!'), 'default' => NULL ), ), 'VirtualFields' => Array ( 'CopyLabels' => Array ('type' => 'int', 'default' => 0), 'CopyFromLanguage' => Array ( 'type' => 'int', 'formatter' => 'kOptionsFormatter', 'options_sql' => 'SELECT %s FROM ' . TABLE_PREFIX . 'Languages ORDER BY PackName', 'option_title_field' => 'PackName', 'option_key_field' => 'LanguageId', 'default' => '', ), ), 'Grids' => Array( 'Default' => Array ( 'Icons' => Array ( 'default' => 'icon16_item.png', '0_0' => 'icon16_disabled.png', '0_1' => 'icon16_disabled.png', '1_0' => 'icon16_item.png', '1_1' => 'icon16_primary.png', ), 'Fields' => Array( 'LanguageId' => Array ('title' => 'column:la_fld_Id', 'data_block' => 'grid_checkbox_td', 'filter_block' => 'grid_range_filter', 'width' => 50, ), 'PackName' => Array ('filter_block' => 'grid_options_filter', 'width' => 150, ),// 'PrimaryLang' => Array ('title' => 'la_col_IsPrimaryLanguage', 'filter_block' => 'grid_options_filter', 'width' => 150, ), 'AdminInterfaceLang' => Array ('filter_block' => 'grid_options_filter', 'width' => 150, ), 'Charset' => Array ('filter_block' => 'grid_like_filter', 'width' => 100, ), 'Priority' => Array ('filter_block' => 'grid_like_filter', 'width' => 60, ), 'Enabled' => Array ('filter_block' => 'grid_options_filter', 'width' => 80, ), 'SynchronizationModes' => Array ('filter_block' => 'grid_picker_filter', 'width' => 120, 'format' => ', ', 'hidden' => 1), ), ), /*'LangManagement' => Array ( 'Icons' => Array ( 'default' => 'icon16_item.png', '0_0' => 'icon16_disabled.png', '0_1' => 'icon16_disabled.png', '1_0' => 'icon16_item.png', '1_1' => 'icon16_primary.png', ), 'Fields' => Array ( 'LanguageId' => Array ('title' => 'column:la_fld_Id', 'data_block' => 'grid_checkbox_td', 'filter_block' => 'grid_range_filter', 'width' => 60), 'PackName' => Array ('title' => 'column:la_fld_Language', 'filter_block' => 'grid_options_filter', 'width' => 120), 'LocalName' => Array ('title' => 'column:la_fld_Prefix', 'filter_block' => 'grid_options_filter', 'width' => 120), 'IconURL' => Array ('title' => 'column:la_fld_Image', 'filter_block' => 'grid_empty_filter', 'width' => 80), ), ),*/ ), ); \ No newline at end of file Index: branches/5.2.x/core/units/languages/languages_event_handler.php =================================================================== --- branches/5.2.x/core/units/languages/languages_event_handler.php (revision 15237) +++ branches/5.2.x/core/units/languages/languages_event_handler.php (revision 15238) @@ -1,790 +1,789 @@ <?php /** * @version $Id$ * @package In-Portal * @copyright Copyright (C) 1997 - 2009 Intechnic. All rights reserved. * @license GNU/GPL * In-Portal is Open Source software. * This means that this software may have been modified pursuant * the GNU General Public License, and as distributed it includes * or is derivative of works licensed under the GNU General Public License * or other free or open source software licenses. * See http://www.in-portal.org/license for copyright notices and details. */ defined('FULL_PATH') or die('restricted access!'); class LanguagesEventHandler extends kDBEventHandler { /** * Allows to override standard permission mapping * * @return void * @access protected * @see kEventHandler::$permMapping */ protected function mapPermissions() { parent::mapPermissions(); $permissions = Array ( 'OnChangeLanguage' => Array ('self' => true), 'OnSetPrimary' => Array ('self' => 'advanced:set_primary|add|edit'), 'OnImportLanguage' => Array ('self' => 'advanced:import'), 'OnExportLanguage' => Array ('self' => 'advanced:export'), 'OnExportProgress' => Array ('self' => 'advanced:export'), 'OnReflectMultiLingualFields' => Array ('self' => 'view'), 'OnSynchronizeLanguages' => Array ('self' => 'edit'), ); $this->permMapping = array_merge($this->permMapping, $permissions); } /** * Checks user permission to execute given $event * * @param kEvent $event * @return bool * @access public */ public function CheckPermission(kEvent $event) { if ( $event->Name == 'OnItemBuild' ) { // check permission without using $event->getSection(), // so first cache rebuild won't lead to "ldefault_Name" field being used return true; } return parent::CheckPermission($event); } /** * Allows to get primary language object * * @param kEvent $event * @return int * @access public */ public function getPassedID(kEvent $event) { if ( $event->Special == 'primary' ) { return $this->Application->GetDefaultLanguageId(); } return parent::getPassedID($event); } /** * [HOOK] Updates table structure on new language adding/removing language * * @param kEvent $event */ function OnReflectMultiLingualFields($event) { if ($this->Application->GetVar('ajax') == 'yes') { $event->status = kEvent::erSTOP; } if (is_object($event->MasterEvent)) { if ($event->MasterEvent->status != kEvent::erSUCCESS) { // only rebuild when all fields are validated return ; } if (($event->MasterEvent->Name == 'OnSave') && !$this->Application->GetVar('new_language')) { // only rebuild during new language adding return ; } } $ml_helper = $this->Application->recallObject('kMultiLanguageHelper'); /* @var $ml_helper kMultiLanguageHelper */ $ml_helper->massCreateFields(); $event->SetRedirectParam('action_completed', 1); } /** * Allows to set selected language as primary * * @param kEvent $event */ function OnSetPrimary($event) { if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) { $event->status = kEvent::erFAIL; return; } $this->StoreSelectedIDs($event); $ids = $this->getSelectedIDs($event); if ($ids) { $id = array_shift($ids); $object = $event->getObject( Array('skip_autoload' => true) ); /* @var $object LanguagesItem */ $object->Load($id); $object->copyMissingData( $object->setPrimary() ); } } /** * [HOOK] Reset primary status of other languages if we are saving primary language * * @param kEvent $event */ function OnUpdatePrimary($event) { if ($event->MasterEvent->status != kEvent::erSUCCESS) { return ; } $object = $event->getObject( Array('skip_autoload' => true) ); /* @var $object LanguagesItem */ $object->SwitchToLive(); // set primary for each languages, that have this checkbox checked $ids = explode(',', $event->MasterEvent->getEventParam('ids')); foreach ($ids as $id) { $object->Load($id); if ($object->GetDBField('PrimaryLang')) { $object->copyMissingData( $object->setPrimary(true, false) ); } if ($object->GetDBField('AdminInterfaceLang')) { $object->setPrimary(true, true); } } // if no primary language left, then set primary last language (not to load again) from edited list $sql = 'SELECT '.$object->IDField.' FROM '.$object->TableName.' WHERE PrimaryLang = 1'; $primary_language = $this->Conn->GetOne($sql); if (!$primary_language) { $object->setPrimary(false, false); // set primary language } $sql = 'SELECT '.$object->IDField.' FROM '.$object->TableName.' WHERE AdminInterfaceLang = 1'; $primary_language = $this->Conn->GetOne($sql); if (!$primary_language) { $object->setPrimary(false, true); // set admin interface language } } /** * Prefills options with dynamic values * * @param kEvent $event * @return void * @access protected */ protected function OnAfterConfigRead(kEvent $event) { parent::OnAfterConfigRead($event); $fields = $this->Application->getUnitOption($event->Prefix, 'Fields'); // set dynamic hints for options in date format fields $options = $fields['InputDateFormat']['options']; if ($options) { foreach ($options as $i => $v) { $options[$i] = $v . ' (' . adodb_date($i) . ')'; } $fields['InputDateFormat']['options'] = $options; } $options = $fields['InputTimeFormat']['options']; if ($options) { foreach ($options as $i => $v) { $options[$i] = $v . ' (' . adodb_date($i) . ')'; } $fields['InputTimeFormat']['options'] = $options; } $this->Application->setUnitOption($event->Prefix, 'Fields', $fields); } /** * Occurs before creating item * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemCreate(kEvent $event) { parent::OnBeforeItemCreate($event); $this->_itemChanged($event); } /** * Occurs before updating item * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemUpdate(kEvent $event) { parent::OnBeforeItemUpdate($event); $object = $event->getObject(); /* @var $object kDBItem */ $status_fields = $this->Application->getUnitOption($event->Prefix, 'StatusField'); $status_field = array_shift($status_fields); if ( $object->GetDBField('PrimaryLang') == 1 && $object->GetDBField($status_field) == 0 ) { $object->SetDBField($status_field, 1); } $this->_itemChanged($event); } /** * Dynamically changes required fields * * @param kEvent $event * @return void * @access protected */ protected function _itemChanged(kEvent $event) { $this->setRequired($event); } /** * Dynamically changes required fields * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemValidate(kEvent $event) { parent::OnBeforeItemValidate($event); $object = $event->getObject(); /* @var $object kDBItem */ $email_message_helper = $this->Application->recallObject('kEmailMessageHelper'); /* @var $email_message_helper kEmailMessageHelper */ $email_message_helper->parseField($object, 'HtmlEmailTemplate'); $email_message_helper->parseField($object, 'TextEmailTemplate'); $check_field = $object->GetDBField('TextEmailTemplate') ? 'TextEmailTemplate' : 'HtmlEmailTemplate'; $check_value = $object->GetDBField($check_field); if ( $check_value && strpos($check_value, '$body') === false ) { $object->SetError($check_field, 'body_missing'); } } /** * Dynamically changes required fields * * @param kEvent $event * @return void * @access protected */ protected function setRequired(kEvent $event) { $object = $event->getObject(); /* @var $object kDBItem */ $object->setRequired('HtmlEmailTemplate', !$object->GetDBField('TextEmailTemplate')); $object->setRequired('TextEmailTemplate', !$object->GetDBField('HtmlEmailTemplate')); } /** * Shows only enabled languages on front * * @param kEvent $event * @return void * @access protected * @see kDBEventHandler::OnListBuild() */ protected function SetCustomQuery(kEvent $event) { parent::SetCustomQuery($event); $object = $event->getObject(); /* @var $object kDBList */ if ( in_array($event->Special, Array ('enabled', 'selected', 'available')) ) { $object->addFilter('enabled_filter', '%1$s.Enabled = ' . STATUS_ACTIVE); } // site domain language picker if ( $event->Special == 'selected' || $event->Special == 'available' ) { $edit_picker_helper = $this->Application->recallObject('EditPickerHelper'); /* @var $edit_picker_helper EditPickerHelper */ $edit_picker_helper->applyFilter($event, 'Languages'); } // apply domain-based language filtering $languages = $this->Application->siteDomainField('Languages'); if ( strlen($languages) ) { $languages = explode('|', substr($languages, 1, -1)); $object->addFilter('domain_filter', '%1$s.LanguageId IN (' . implode(',', $languages) . ')'); } } /** * Copy labels from another language * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemCreate(kEvent $event) { parent::OnAfterItemCreate($event); $object = $event->getObject(); /* @var $object kDBItem */ $src_language = $object->GetDBField('CopyFromLanguage'); if ( $object->GetDBField('CopyLabels') && $src_language ) { $dst_language = $object->GetID(); // 1. schedule data copy after OnSave event is executed $var_name = $event->getPrefixSpecial() . '_copy_data' . $this->Application->GetVar('m_wid'); $pending_actions = $this->Application->RecallVar($var_name, Array ()); if ( $pending_actions ) { $pending_actions = unserialize($pending_actions); } $pending_actions[$src_language] = $dst_language; $this->Application->StoreVar($var_name, serialize($pending_actions)); $object->SetDBField('CopyLabels', 0); } } /** * Saves language from temp table to live * * @param kEvent $event * @return void * @access protected */ protected function OnSave(kEvent $event) { parent::OnSave($event); if ( $event->status != kEvent::erSUCCESS ) { return; } $var_name = $event->getPrefixSpecial() . '_copy_data' . $this->Application->GetVar('m_wid'); $pending_actions = $this->Application->RecallVar($var_name, Array ()); if ( $pending_actions ) { $pending_actions = unserialize($pending_actions); } // create multilingual columns for phrases & email events table first (actual for 6+ language) $ml_helper = $this->Application->recallObject('kMultiLanguageHelper'); /* @var $ml_helper kMultiLanguageHelper */ $ml_helper->createFields('phrases'); $ml_helper->createFields('emailevents'); foreach ($pending_actions as $src_language => $dst_language) { // phrases import $sql = 'UPDATE ' . $this->Application->getUnitOption('phrases', 'TableName') . ' SET l' . $dst_language . '_Translation = l' . $src_language . '_Translation'; $this->Conn->Query($sql); // events import $sql = 'UPDATE ' . $this->Application->getUnitOption('emailevents', 'TableName') . ' SET l' . $dst_language . '_Subject = l' . $src_language . '_Subject, l' . $dst_language . '_HtmlBody = l' . $src_language . '_HtmlBody, l' . $dst_language . '_PlainTextBody = l' . $src_language . '_PlainTextBody'; $this->Conn->Query($sql); } $this->Application->RemoveVar($var_name); $event->CallSubEvent('OnReflectMultiLingualFields'); $event->CallSubEvent('OnUpdatePrimary'); } /** * Prepare temp tables for creating new item * but does not create it. Actual create is * done in OnPreSaveCreated * * @param kEvent $event * @return void * @access protected */ protected function OnPreCreate(kEvent $event) { parent::OnPreCreate($event); $object = $event->getObject(); /* @var $object kDBItem */ $object->SetDBField('CopyLabels', 1); $sql = 'SELECT ' . $object->IDField . ' FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . ' WHERE PrimaryLang = 1'; $primary_lang_id = $this->Conn->GetOne($sql); $object->SetDBField('CopyFromLanguage', $primary_lang_id); $object->SetDBField('SynchronizationModes', Language::SYNCHRONIZE_DEFAULT); - $object->SetDBField('HtmlEmailTemplate', '$body'); $this->setRequired($event); } /** * Sets dynamic required fields * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemLoad(kEvent $event) { parent::OnAfterItemLoad($event); $object = $event->getObject(); /* @var $object kDBItem */ $this->setRequired($event); } /** * Sets new language mark * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeDeleteFromLive(kEvent $event) { parent::OnBeforeDeleteFromLive($event); $id_field = $this->Application->getUnitOption($event->Prefix, 'IDField'); $sql = 'SELECT ' . $id_field . ' FROM ' . $this->Application->getUnitOption($event->Prefix, 'TableName') . ' WHERE ' . $id_field . ' = ' . $event->getEventParam('id'); $id = $this->Conn->GetOne($sql); if ( !$id ) { $this->Application->SetVar('new_language', 1); } } function OnChangeLanguage($event) { $language_id = $this->Application->GetVar('language'); $language_field = $this->Application->isAdmin ? 'AdminLanguage' : 'FrontLanguage'; $this->Application->SetVar('m_lang', $language_id); // set new language for this session $this->Application->Session->SetField('Language', $language_id); // remember last user language if ($this->Application->RecallVar('user_id') == USER_ROOT) { $this->Application->StorePersistentVar($language_field, $language_id); } else { $object = $this->Application->recallObject('u.current'); /* @var $object kDBItem */ $object->SetDBField($language_field, $language_id); $object->Update(); } // without this language change in admin will cause erase of last remembered tree section $this->Application->SetVar('skip_last_template', 1); } /** * Parse language XML file into temp tables and redirect to progress bar screen * * @param kEvent $event */ function OnImportLanguage($event) { if ($this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1)) { $event->status = kEvent::erFAIL; return; } $items_info = $this->Application->GetVar('phrases_import'); if ($items_info) { list ($id, $field_values) = each($items_info); $object = $this->Application->recallObject('phrases.import', 'phrases', Array('skip_autoload' => true)); /* @var $object kDBItem */ $object->setID($id); $object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values)); if (!$object->Validate()) { $event->status = kEvent::erFAIL; return ; } $filename = $object->GetField('LangFile', 'full_path'); if (!filesize($filename)) { $object->SetError('LangFile', 'la_empty_file', 'la_EmptyFile'); $event->status = kEvent::erFAIL; } $language_import_helper = $this->Application->recallObject('LanguageImportHelper'); /* @var $language_import_helper LanguageImportHelper */ $language_import_helper->performImport( $filename, $object->GetDBField('PhraseType'), $object->GetDBField('Module'), $object->GetDBField('ImportOverwrite') ? LANG_OVERWRITE_EXISTING : LANG_SKIP_EXISTING ); // delete uploaded language pack after import is finished unlink($filename); $event->SetRedirectParam('opener', 'u'); } } /** * Stores ids of selected languages and redirects to export language step 1 * * @param kEvent $event */ function OnExportLanguage($event) { if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; return; } $this->Application->setUnitOption('phrases', 'AutoLoad', false); $this->StoreSelectedIDs($event); $this->Application->StoreVar('export_language_ids', implode(',', $this->getSelectedIDs($event))); $event->setRedirectParams( Array ( 'phrases.export_event' => 'OnNew', 'pass' => 'all,phrases.export', 'export_mode' => $event->Prefix, ) ); } /** * Saves selected languages to xml file passed * * @param kEvent $event */ function OnExportProgress($event) { $items_info = $this->Application->GetVar('phrases_export'); if ( $items_info ) { list($id, $field_values) = each($items_info); $object = $this->Application->recallObject('phrases.export', null, Array ('skip_autoload' => true)); /* @var $object kDBItem */ $object->setID($id); $object->SetFieldsFromHash($field_values, $this->getRequestProtectedFields($field_values)); if ( !$object->Validate() ) { $event->status = kEvent::erFAIL; return; } $file_helper = $this->Application->recallObject('FileHelper'); /* @var $file_helper FileHelper */ $file_helper->CheckFolder(EXPORT_PATH); if ( !is_writable(EXPORT_PATH) ) { $event->status = kEvent::erFAIL; $object->SetError('LangFile', 'write_error', 'la_ExportFolderNotWritable'); return; } if ( substr($field_values['LangFile'], -5) != '.lang' ) { $field_values['LangFile'] .= '.lang'; } $filename = EXPORT_PATH . '/' . $field_values['LangFile']; $language_import_helper = $this->Application->recallObject('LanguageImportHelper'); /* @var $language_import_helper LanguageImportHelper */ if ( $object->GetDBField('DoNotEncode') ) { $language_import_helper->setExportEncoding('plain'); } $data_types = Array ( 'phrases' => 'ExportPhrases', 'emailevents' => 'ExportEmailEvents', 'country-state' => 'ExportCountries' ); $export_mode = $this->Application->GetVar('export_mode'); $allowed_data_types = explode('|', substr($field_values['ExportDataTypes'], 1, -1)); if ( $export_mode == $event->Prefix ) { foreach ($data_types as $prefix => $export_limit_field) { $export_limit = in_array($prefix, $allowed_data_types) ? $field_values[$export_limit_field] : '-'; $language_import_helper->setExportLimit($prefix, $export_limit); } } else { foreach ($data_types as $prefix => $export_limit_field) { $export_limit = in_array($prefix, $allowed_data_types) ? null : '-'; $language_import_helper->setExportLimit($prefix, $export_limit); } } $lang_ids = explode(',', $this->Application->RecallVar('export_language_ids')); $language_import_helper->performExport($filename, $field_values['PhraseType'], $lang_ids, $field_values['Module']); } $event->redirect = 'regional/languages_export_step2'; $event->SetRedirectParam('export_file', $field_values['LangFile']); } /** * Returns to previous template in opener stack * * @param kEvent $event * @return void * @access protected */ protected function OnGoBack(kEvent $event) { $event->SetRedirectParam('opener', 'u'); } function OnScheduleTopFrameReload($event) { $this->Application->StoreVar('RefreshTopFrame',1); } /** * Do now allow deleting current language * * @param kEvent $event * @return void * @access protected */ protected function OnBeforeItemDelete(kEvent $event) { parent::OnBeforeItemDelete($event); $object = $event->getObject(); /* @var $object kDBItem */ if ( $object->GetDBField('PrimaryLang') || $object->GetDBField('AdminInterfaceLang') || $object->GetID() == $this->Application->GetVar('m_lang') ) { $event->status = kEvent::erFAIL; } } /** * Deletes phrases and email events on given language * * @param kEvent $event * @return void * @access protected */ protected function OnAfterItemDelete(kEvent $event) { parent::OnAfterItemDelete($event); $object = $event->getObject(); /* @var $object kDBItem */ // clean EmailEvents table $fields_hash = Array ( 'l' . $object->GetID() . '_Subject' => NULL, 'l' . $object->GetID() . '_HtmlBody' => NULL, 'l' . $object->GetID() . '_PlainTextBody' => NULL, ); $this->Conn->doUpdate($fields_hash, $this->Application->getUnitOption('emailevents', 'TableName'), 1); // clean Phrases table $fields_hash = Array ( 'l' . $object->GetID() . '_Translation' => NULL, 'l' . $object->GetID() . '_HintTranslation' => NULL, 'l' . $object->GetID() . '_ColumnTranslation' => NULL, ); $this->Conn->doUpdate($fields_hash, $this->Application->getUnitOption('phrases', 'TableName'), 1); } /** * Copy missing phrases across all system languages (starting from primary) * * @param kEvent $event * @return void * @access protected */ protected function OnSynchronizeLanguages($event) { if ( $this->Application->CheckPermission('SYSTEM_ACCESS.READONLY', 1) ) { $event->status = kEvent::erFAIL; return; } $source_languages = $target_languages = Array (); // get language list with primary language first $sql = 'SELECT SynchronizationModes, LanguageId FROM ' . TABLE_PREFIX . 'Languages WHERE SynchronizationModes <> "" ORDER BY PrimaryLang DESC'; $languages = $this->Conn->GetCol($sql, 'LanguageId'); foreach ($languages as $language_id => $synchronization_modes) { $synchronization_modes = explode('|', substr($synchronization_modes, 1, -1)); if ( in_array(Language::SYNCHRONIZE_TO_OTHERS, $synchronization_modes) ) { $source_languages[] = $language_id; } if ( in_array(Language::SYNCHRONIZE_FROM_OTHERS, $synchronization_modes) ) { $target_languages[] = $language_id; } } foreach ($source_languages as $source_id) { foreach ($target_languages as $target_id) { if ( $source_id == $target_id ) { continue; } $sql = 'UPDATE ' . TABLE_PREFIX . 'LanguageLabels SET l' . $target_id . '_Translation = l' . $source_id . '_Translation WHERE COALESCE(l' . $target_id . '_Translation, "") = "" AND COALESCE(l' . $source_id . '_Translation, "") <> ""'; $this->Conn->Query($sql); } } } } \ No newline at end of file