<?php
/**
* @version	$Id: language_import_helper.php 12734 2009-10-20 19:28:11Z alex $
* @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!');

	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';

		/**
		 * Debug language pack import process
		 *
		 * @var bool
		 */
		var $_debugMode = false;

		function LanguageImportHelper()
		{
			parent::kHelper();

			// "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 = getmicrotime();
				$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');
				$this->_performUpgrade($language_id, 'emailmessages', 'EventId');
			}

			$this->_initImportTables(true);

			if ($this->_debugMode) {
				$this->Application->Debugger->appendHTML(__CLASS__ . '::' . __FUNCTION__ . '("' . $filename . '"): ' . (getmicrotime() - $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) );

			$this->events_hash = array_flip($this->events_hash);

			$lang_table = $this->Application->getUnitOption('lang','TableName');
			$phrases_table = $this->Application->getUnitOption('phrases','TableName');
			$emailevents_table = $this->Application->getUnitOption('emailmessages','TableName');
			$mainevents_table = $this->Application->getUnitOption('emailevents','TableName');

			$phrase_tpl = "\t\t\t".'<PHRASE Label="%s" Module="%s" Type="%s">%s</PHRASE>'."\n";
			$event_tpl = "\t\t\t".'<EVENT MessageType="%s" Event="%s" Type="%s">%s</EVENT>'."\n";
			$sql = 'SELECT * FROM %s WHERE LanguageId = %s';
			$ret = '<LANGUAGES>'."\n";
			foreach ($language_ids as $language_id) {
				// languages
				$row = $this->Conn->GetRow( sprintf($sql, $lang_table, $language_id) );
				$ret .= "\t".'<LANGUAGE PackName="'.$row['PackName'].'" Encoding="'.$this->_exportEncoding.'"><DATEFORMAT>'.$row['DateFormat'].'</DATEFORMAT>';
				$ret .= '<TIMEFORMAT>'.$row['TimeFormat'].'</TIMEFORMAT><INPUTDATEFORMAT>'.$row['InputDateFormat'].'</INPUTDATEFORMAT>';
				$ret .= '<INPUTTIMEFORMAT>'.$row['InputTimeFormat'].'</INPUTTIMEFORMAT><DECIMAL>'.$row['DecimalPoint'].'</DECIMAL>';
				$ret .= '<THOUSANDS>'.$row['ThousandSep'].'</THOUSANDS><CHARSET>'.$row['Charset'].'</CHARSET><DOCS_URL>'.$row['UserDocsUrl'].'</DOCS_URL>';
				$ret .= '<UNITSYSTEM>'.$row['UnitSystem'].'</UNITSYSTEM>'."\n";

				// phrases
				$phrases_sql = 'SELECT * FROM '.$phrases_table.' WHERE LanguageId = %s AND PhraseType IN (%s) AND Module IN (%s) ORDER BY Phrase';
				if( in_array('In-Portal',$module_ids) ) array_push($module_ids, ''); // for old language packs
				$rows = $this->Conn->Query( sprintf($phrases_sql,$language_id, implode(',',$phrase_types), '\''.implode('\',\'',$module_ids).'\'' ) );
				if($rows)
				{
					$ret .= "\t\t".'<PHRASES>'."\n";
					foreach($rows as $row)
					{
						$data = $this->_exportEncoding == 'base64' ? base64_encode($row['Translation']) : '<![CDATA['.$row['Translation'].']]>';
						$ret .= sprintf($phrase_tpl, $row['Phrase'], $row['Module'], $row['PhraseType'], $data );
					}
					$ret .= "\t\t".'</PHRASES>'."\n";
				}

				// email events
				if ( in_array('In-Portal',$module_ids) ) {
					unset( $module_ids[array_search('',$module_ids)] ); // for old language packs
				}

				$module_sql = preg_replace('/(.*),/U', 'INSTR(Module,\'\\1\') OR ', implode(',', $module_ids).',');
				$module_sql = substr($module_sql, 0, -4);

				$sql = 'SELECT EventId FROM '.$mainevents_table.' WHERE '.$module_sql;
				$event_ids = $this->Conn->GetCol($sql);

				if($event_ids)
				{
					$ret .= "\t\t".'<EVENTS>'."\n";
					$event_sql = '	SELECT em.*
									FROM '.$emailevents_table.' em
									LEFT JOIN '.$mainevents_table.' e ON e.EventId = em.EventId
									WHERE em.LanguageId = %s AND em.EventId IN (%s)
									ORDER BY e.Event, e.Type';
					$rows = $this->Conn->Query( sprintf($event_sql,$language_id, $event_ids ? implode(',',$event_ids) : '' ) );
					foreach($rows as $row)
					{
						if (!array_key_exists($row['EventId'], $this->events_hash)) {
							// don't export existing translations of missing events
							continue;
						}

						list($event_name, $event_type) = explode('_', $this->events_hash[ $row['EventId'] ] );
						$data = $this->_exportEncoding == 'base64' ? base64_encode($row['Template']) : '<![CDATA['.$row['Template'].']]>';
						$ret .= sprintf($event_tpl, $row['MessageType'], $event_name, $event_type, $data );
					}
					$ret .= "\t\t".'</EVENTS>'."\n";
				}
				$ret .= "\t".'</LANGUAGE>'."\n";
			}

			$ret .= '</LANGUAGES>';
			fwrite($fp, $ret);
			fclose($fp);

			return true;
		}

		/**
		 * Sets language pack encoding (not charset) used during export
		 *
		 * @param string $encoding
		 */
		function setExportEncoding($encoding)
		{
			$this->_exportEncoding = $encoding;
		}

		/**
		 * Performs upgrade of given language pack part
		 *
		 * @param int $language_id
		 * @param string $prefix
		 * @param string $unique_field
		 */
		function _performUpgrade($language_id, $prefix, $unique_field)
		{
			// TODO: find a way to compare (intersect,diff) phrases in non-case sensitive way, but keeping original case in result
			$live_records = $this->_getTableData($language_id, $prefix, $unique_field, false);
			$temp_records = $this->_getTableData($language_id, $prefix, $unique_field, true);

			if ($this->import_mode == LANG_OVERWRITE_EXISTING) {
				// remove existing records before copy
				$common_records = array_intersect($temp_records, $live_records);
				if ($common_records) {
					$live_records = array_diff($live_records, $common_records); // remove overlaping records
					$common_records = array_map(Array(&$this->Conn, 'qstr'), $common_records);

					$sql = 'DELETE FROM ' . $this->Application->getUnitOption($prefix, 'TableName') . '
							WHERE (LanguageId = ' . $language_id . ') AND (' . $unique_field . ' IN (' . implode(',', $common_records) . '))';
					$this->Conn->Query($sql);
				}
			}

			$temp_records = array_diff($temp_records, $live_records);

			if (!$temp_records) {
				// no new records found in temp table while comparing it to live table
				return ;
			}

			$temp_records = array_map(Array(&$this->Conn, 'qstr'), $temp_records);

			$sql = 'INSERT INTO ' . $this->Application->getUnitOption($prefix, 'TableName') . '
					SELECT *
					FROM ' . $this->_tables[$prefix] . '
					WHERE (LanguageId = ' . $language_id . ')';

			if ($live_records) {
				// subsctract live records from temp table during coping
				$sql .= ' AND (' . $unique_field . ' IN (' . implode(',', $temp_records) . '))';
			}

			$this->Conn->Query($sql);
		}

		/**
		 * Returns data from given table used for language pack upgrade
		 *
		 * @param int $language_id
		 * @param string $prefix
		 * @param string $unique_field
		 * @param bool $temp_mode
		 * @return Array
		 */
		function _getTableData($language_id, $prefix, $unique_field, $temp_mode = false)
		{
			$table_name = $this->Application->getUnitOption($prefix, 'TableName');

			if ($temp_mode) {
				$table_name = $this->Application->GetTempName($table_name, 'prefix:' . $prefix);
			}

			$sql = 'SELECT ' . $unique_field . '
					FROM ' . $table_name . '
					WHERE LanguageId = ' . $language_id;
			return $this->Conn->GetCol($sql);
		}

		function _parseXML($filename)
		{
			if ($this->_debugMode) {
				$start_time = getmicrotime();
				$this->Application->Debugger->appendHTML(__CLASS__ . '::' . __FUNCTION__ . '("' . $filename . '")');
			}

			$fdata = file_get_contents($filename);

			$xml_parser =& $this->Application->recallObject('kXMLHelper');
			/* @var $xml_parser kXMLHelper */

			$root_node =& $xml_parser->Parse($fdata);
			if (!is_object($root_node) || !is_a($root_node, 'kXMLNode')) {
				// invalid language pack contents
				return false;
			}

			if ($root_node->Children) {
				$this->_processLanguages($root_node->firstChild);
			}

			if ($this->_debugMode) {
				$this->Application->Debugger->appendHTML(__CLASS__ . '::' . __FUNCTION__ . '("' . $filename . '"): ' . (getmicrotime() - $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['emailmessages'] = $this->_prepareTempTable('emailmessages', $drop_only);
		}

		/**
		 * Create temp table for prefix, if table already exists, then delete it and create again
		 *
		 * @param string $prefix
		 */
		function _prepareTempTable($prefix, $drop_only = false)
		{
			$idfield = $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 %s SELECT * FROM %s WHERE 0';
				$this->Conn->Query( sprintf($sql, $temp_table, $table) );

				$sql = 'ALTER TABLE %1$s CHANGE %2$s %2$s INT(11) NOT NULL DEFAULT "0"';
				$this->Conn->Query( sprintf($sql, $temp_table, $idfield) );
			}

			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 . 'Events';
			$this->events_hash = $this->Conn->GetCol($sql, 'EventMix');
		}

		/**
		 * Processes parsed XML
		 *
		 * @param kXMLNode $language_node
		 */
		function _processLanguages(&$language_node)
		{
			$field_mapping = Array (
				'DATEFORMAT' => 'DateFormat',
				'TIMEFORMAT' => 'TimeFormat',
				'INPUTDATEFORMAT' => 'InputDateFormat',
				'INPUTTIMEFORMAT' => 'InputTimeFormat',
				'DECIMAL' => 'DecimalPoint',
				'THOUSANDS' => 'ThousandSep',
				'CHARSET' => 'Charset',
				'UNITSYSTEM' => 'UnitSystem',
				'DOCS_URL' => 'UserDocsUrl',
			);

			do {
				$language_id = false;

				$fields_hash = Array (
					'PackName' => $language_node->Attributes['PACKNAME'],
					'LocalName' => $language_node->Attributes['PACKNAME'],
					'Encoding' => $language_node->Attributes['ENCODING'],
					'Charset' => 'iso-8859-1',
				);

				$sub_node =& $language_node->firstChild;
				/* @var $sub_node kXMLNode */

				do {
					switch ($sub_node->Name) {
						case 'PHRASES':
							if ($sub_node->Children) {
								if (!$language_id) {
									$language_id = $this->_processLanguage($fields_hash);
								}

								if ($this->_debugMode) {
									$start_time = getmicrotime();
								}

								$this->_processPhrases($sub_node->firstChild, $language_id, $fields_hash['Encoding']);

								if ($this->_debugMode) {
									$this->Application->Debugger->appendHTML(__CLASS__ . '::' . '_processPhrases: ' . (getmicrotime() - $start_time));
								}
							}
							break;

						case 'EVENTS':
							if ($sub_node->Children) {
								if (!$language_id) {
									$language_id = $this->_processLanguage($fields_hash);
								}

								$this->_processEvents($sub_node->firstChild, $language_id, $fields_hash['Encoding']);
							}
							break;

						default:
							$fields_hash[ $field_mapping[$sub_node->Name] ] = $sub_node->Data;
							break;
					}
				} while (($sub_node =& $sub_node->NextSibling()));
			} while (($language_node =& $language_node->NextSibling()));
		}

		/**
		 * Performs phases import
		 *
		 * @param kXMLNode $phrase_node
		 * @param int $language_id
		 * @param string $language_encoding
		 */
		function _processPhrases(&$phrase_node, $language_id, $language_encoding)
		{
			do {
				$fields_hash = Array (
					'LanguageId' => $language_id,
					'Phrase' => $phrase_node->Attributes['LABEL'],
					'PhraseKey' => mb_strtoupper($phrase_node->Attributes['LABEL']),
					'PhraseType' => $phrase_node->Attributes['TYPE'],
					'Module' => array_key_exists('MODULE', $phrase_node->Attributes) ? $phrase_node->Attributes['MODULE'] : 'Core',
					'LastChanged' => adodb_mktime(),
					'LastChangeIP' => $this->ip_address,
					'Translation' => $phrase_node->Data,
				);

				if (array_key_exists($fields_hash['PhraseType'], $this->phrase_types_allowed)) {
					if ($language_encoding != 'plain') {
						$fields_hash['Translation'] = base64_decode($fields_hash['Translation']);
					}

					$this->Conn->doInsert($fields_hash, $this->_tables['phrases'], 'INSERT', false);
				}
			} while (($phrase_node =& $phrase_node->NextSibling()));

			$this->Conn->doInsert($fields_hash, $this->_tables['phrases'], 'INSERT');
		}

		/**
		 * Performs email event import
		 *
		 * @param kXMLNode $event_node
		 * @param int $language_id
		 * @param string $language_encoding
		 */
		function _processEvents(&$event_node, $language_id, $language_encoding)
		{
			$email_message_helper =& $this->Application->recallObject('EmailMessageHelper');
			/* @var $email_message_helper EmailMessageHelper */

			do {
				$event_id = $this->_getEventId($event_node->Attributes['EVENT'], $event_node->Attributes['TYPE']);
				if ($event_id) {
					$fields_hash = Array (
						'LanguageId' => $language_id,
						'EventId' => $event_id,
						'MessageType' => $event_node->Attributes['MESSAGETYPE'],
					);

					if ($language_encoding == 'plain') {
						$fields_hash['Template'] = rtrim($event_node->Data);
					}
					else {
						$fields_hash['Template'] = base64_decode($event_node->Data);
					}

					$parsed = $email_message_helper->parseTemplate($fields_hash['Template']);
					$fields_hash['Subject'] = $parsed['Subject'];

					$this->Conn->doInsert($fields_hash, $this->_tables['emailmessages'], 'INSERT', false);
				}
			} while (($event_node =& $event_node->NextSibling()));

			if ($fields_hash) {
				// at least one corresponding event declaration found by email event name+type given in translation
				$this->Conn->doInsert($fields_hash, $this->_tables['emailmessages'], 'INSERT');
			}
		}

		/**
		 * 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;
		}
	}