<?php
/**
* @version	$Id: multilanguage_helper.php 15328 2012-05-06 11:21:07Z 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!');

	/**
	 * Performs action on multilingual fields
	 *
	 */
	class kMultiLanguageHelper extends kHelper {

		/**
		 * Determines, that language info should be requeried
		 *
		 * @var bool
		 * @access protected
		 */
		protected $initMade = false;

		/**
		 * Maximal language id
		 *
		 * @var int
		 */
		protected $languageCount = 0;
		/**
		 * Languages created in system
		 *
		 * @var Array
		 */
		protected $languagesIDs = Array ();

		/**
		 * Structure of table, that is currently processed
		 *
		 * @var Array
		 */
		var $curStructure = Array();

		/**
		 * Field, to get structure information from
		 *
		 * @var string
		 */
		var $curSourceField = false;

		/**
		 * Indexes used in table of 32
		 *
		 * @var int
		 */
		var $curIndexCount = 0;

		/**
		 * Fields from config, that are currently used
		 *
		 * @var Array
		 */
		var $curFields = Array();

		public function resetState()
		{
			$this->initMade = false;
		}

		/**
		 * Updates language count in system (always is divisible by 5)
		 *
		 */
		protected function _queryLanguages()
		{
			if ( !$this->initMade ) {
				$this->languagesIDs = $this->getActualLanguages();
				$this->languageCount = max(max($this->languagesIDs), 5);
				$this->initMade = true;
			}
		}

		/**
		 * Returns language ids, that can be used
		 *
		 * @return Array
		 */
		protected function getActualLanguages()
		{
			$cache_key = 'actual_language_ids[%LangSerial%]';
			$ret = $this->Application->getCache($cache_key);

			if ( $ret === false ) {
				$this->Conn->nextQueryCachable = true;
				$sql = 'SELECT ' . $this->Application->getUnitOption('lang', 'IDField') . '
						FROM ' . $this->Application->getUnitOption('lang', 'TableName');
				$ret = $this->Conn->GetCol($sql);

				$this->Application->setCache($cache_key, $ret);
			}

			return $ret;
		}

		/**
		 * Checks if language with specified id is created
		 *
		 * @param int $language_id
		 * @return bool
		 */
		protected function LanguageFound($language_id)
		{
			return in_array($language_id, $this->languagesIDs) || $language_id <= 5;
		}

		/**
		 * Returns list of processable languages
		 *
		 * @return Array
		 */
		public function getLanguages()
		{
			$cache_key = 'processable_language_ids[%LangSerial%]';
			$ret = $this->Application->getCache($cache_key);

			if ( $ret === false ) {
				$ret = Array ();
				$this->_queryLanguages();

				for ($language_id = 1; $language_id <= $this->languageCount; $language_id++) {
					if ( $this->LanguageFound($language_id) ) {
						$ret[] = $language_id;
					}
				}

				$this->Application->setCache($cache_key, $ret);
			}

			return $ret;
		}

		function scanTable($mask)
		{
			$i = 0;
			$fields_found = 0;
			$fields = array_keys($this->curStructure);

			foreach ($fields as $field_name) {
				if (preg_match($mask, $field_name)) {
					$fields_found++;
				}
			}
			return $fields_found;
		}

		function readTableStructure($table_name, $refresh = false)
		{
//			if ($refresh || !getArrayValue($structure_status, $prefix.'.'.$table_name)) {
				$this->curStructure = $this->Conn->Query('DESCRIBE '.$table_name, 'Field');
				$this->curIndexCount = count($this->Conn->Query('SHOW INDEXES FROM '.$table_name));
//			}
		}

		/**
		 * Creates missing multilingual fields for all unit configs, registered in system
		 *
		 * @param bool $reread_configs
		 * @return void
		 * @access public
		 */
		public function massCreateFields($reread_configs = true)
		{
			if ( $reread_configs ) {
				$this->Application->UnitConfigReader->ReReadConfigs();
			}

			foreach ($this->Application->UnitConfigReader->configData as $prefix => $config_data) {
				$this->createFields($prefix);
			}
		}

		/**
		 * Creates missing multilanguage fields in table by specified prefix
		 *
		 * @param string $prefix
		 * @param bool $refresh Forces config field structure to be re-read from database
		 * @return void
		 */
		function createFields($prefix, $refresh = false)
		{
			if ( $refresh && preg_match('/(.*)-cdata$/', $prefix, $regs) ) {
				// call main item config to clone cdata table
				$this->Application->UnitConfigReader->loadConfig($regs[1]);
				$this->Application->UnitConfigReader->runAfterConfigRead($prefix);
			}

			$table_name = $this->Application->getUnitOption($prefix, 'TableName');
			$this->curFields = $this->Application->getUnitOption($prefix, 'Fields');

			if ( !($table_name && $this->curFields) || ($table_name && !$this->Conn->TableFound($table_name, kUtil::constOn('IS_INSTALL'))) ) {
				// invalid config found or prefix not found
				return ;
			}

			$this->_queryLanguages();

			$sqls = Array ();
			$this->readTableStructure($table_name, $refresh);

			foreach ($this->curFields as $field_name => $field_options) {
				if ( getArrayValue($field_options, 'formatter') == 'kMultiLanguage' ) {
					if ( isset($field_options['master_field']) ) {
						unset($this->curFields[$field_name]);
						continue;
					}

					$this->setSourceField($field_name);
					if ( $this->languageCount > 0 ) {
						// `l77_Name` VARCHAR( 255 ) NULL DEFAULT '0';
						$field_mask = Array ();
						$field_mask['name'] = 'l%s_' . $field_name;
						$field_mask['null'] = getArrayValue($field_options, 'not_null') ? 'NOT NULL' : 'NULL';

						if ( $this->curSourceField ) {
							$default_value = $this->getFieldParam('Default') != 'NULL'
									? $this->Conn->qstr($this->getFieldParam('Default'))
									: $this->getFieldParam('Default');
							$field_mask['type'] = $this->getFieldParam('Type');
						}
						else {
							$default_value = is_null($field_options['default']) ? 'NULL'
									: $this->Conn->qstr($field_options['default']);
							$field_mask['type'] = $field_options['db_type'];
						}

						$field_mask['default'] = ($field_mask['null'] == 'NOT NULL' && $default_value == 'NULL') ? '' : 'DEFAULT ' . $default_value;

						if ( strtoupper($field_mask['type']) == 'TEXT' ) {
							// text fields in mysql doesn't have default value
							$field_mask = $field_mask['name'] . ' ' . $field_mask['type'] . ' ' . $field_mask['null'];
						}
						else {
							$field_mask = $field_mask['name'] . ' ' . $field_mask['type'] . ' ' . $field_mask['null'] . ' ' . $field_mask['default'];
						}

						$alter_sqls = $this->generateAlterSQL($field_mask, 1, $this->languageCount);
						if ( $alter_sqls ) {
							$sqls[] = 'ALTER TABLE ' . $table_name . ' ' . $alter_sqls;
						}
					}
				}
			}

			foreach ($sqls as $sql_query) {
				$this->Conn->Query($sql_query);
			}
		}

		/**
		 * Creates missing multilanguage fields in table by specified prefix
		 *
		 * @param string $prefix
		 * @param int $src_language
		 * @param int $dst_language
		 * @return void
		 * @access public
		 */
		public function copyMissingData($prefix, $src_language, $dst_language)
		{
			$table_name = $this->Application->getUnitOption($prefix, 'TableName');
			$this->curFields = $this->Application->getUnitOption($prefix, 'Fields');

			if ( !($table_name && $this->curFields) || ($table_name && !$this->Conn->TableFound($table_name, kUtil::constOn('IS_INSTALL'))) ) {
				// invalid config found or prefix not found
				return ;
			}

			foreach ($this->curFields as $field_name => $field_options) {
				$formatter = isset($field_options['formatter']) ? $field_options['formatter'] : '';

				if ( ($formatter == 'kMultiLanguage') && !isset($field_options['master_field']) ) {
					$sql = 'UPDATE ' . $table_name . '
							SET l' . $dst_language . '_' . $field_name . ' = l' . $src_language . '_' . $field_name . '
							WHERE l' . $dst_language . '_' . $field_name . ' = "" OR l' . $dst_language . '_' . $field_name . ' IS NULL';
					$this->Conn->Query($sql);
				}
			}
		}

		function deleteField($prefix, $custom_id)
		{
			$table_name = $this->Application->getUnitOption($prefix, 'TableName');
			$sql = 'DESCRIBE '.$table_name.' "l%_cust_'.$custom_id.'"';
			$fields = $this->Conn->GetCol($sql);

			$sql = 'ALTER TABLE '.$table_name.' ';
			$sql_template = 'DROP COLUMN %s, ';

			foreach ($fields as $field_name) {
				$sql .= sprintf($sql_template, $field_name);
			}

			$this->Conn->Query( substr($sql, 0, -2) );
		}

		/**
		 * Returns parameter requested of current source field
		 *
		 * @param string $param_name
		 * @return string
		 */
		function getFieldParam($param_name)
		{
			return $this->curStructure[$this->curSourceField][$param_name];
		}

		/**
		 * Detects field name to create other fields from
		 *
		 * @param string $field_name
		 */
		function setSourceField($field_name)
		{
			$ret = $this->scanTable('/^l[\d]+_'.preg_quote($field_name, '/').'$/');
			if (!$ret) {
				// no multilingual fields at all (but we have such field without language prefix)
				$original_found = $this->scanTable('/^'.preg_quote($field_name, '$/').'/');
				$this->curSourceField = $original_found ? $field_name : false;
			}
			else {
				$this->curSourceField = 'l1_'.$field_name;
			}
		}

		/**
		 * Returns ALTER statement part for adding required fields to table
		 *
		 * @param string $field_mask sql mask for creating field with correct definition (type & size)
		 * @param int $start_index add new fields starting from this index
		 * @param int $create_count create this much new multilingual field translations
		 * @return string
		 */
		function generateAlterSQL($field_mask, $start_index, $create_count)
		{
			static $single_lang = null;
			if (!isset($single_lang)) {
				// if single language mode, then create indexes only on primary columns
				$table_name = $this->Application->getUnitOption('lang', 'TableName');
				$sql = 'SELECT COUNT(*)
						FROM '.$table_name.'
						WHERE Enabled = 1';
				// if language count = 0, then assume it's multi language mode
				$single_lang = $this->Conn->GetOne($sql) == 1;
			}

			$ret = '';
			$ml_field = preg_replace('/l(.*?)_(.*?) (.*)/', '\\2', $field_mask);

			$i_count = $start_index + $create_count;
			while ($start_index < $i_count) {

				if (isset($this->curStructure['l'.$start_index.'_'.$ml_field]) || (!$this->LanguageFound($start_index)) ) {
					$start_index++;
					continue;
				}

				$prev_index = $start_index - 1;
				do {
					list($prev_field,$type) = explode(' ', sprintf($field_mask, $prev_index) );
				} while ($prev_index > 0 && !$this->LanguageFound($prev_index--));

				if (substr($prev_field, 0, 3) == 'l0_') {
					$prev_field = substr($prev_field, 3, strlen($prev_field));
					if (!$this->curSourceField) {
						// get field name before this one
						$fields = array_keys($this->curFields);
//						$prev_field = key(end($this->curStructure));
						$prev_field = $fields[array_search($prev_field, $fields) - 1];
						if (getArrayValue($this->curFields[$prev_field], 'formatter') == 'kMultiLanguage') {
							$prev_field = 'l'.$this->languageCount.'_'.$prev_field;
						}
					}
				}

				$field_expression = sprintf($field_mask, $start_index);
				$ret .= 'ADD COLUMN '.$field_expression.' AFTER `'.$prev_field.'`, ';

				if ($this->curIndexCount < 32 && ($start_index == $this->Application->GetDefaultLanguageId() || !$single_lang)) {
					// create index for primary language column + for all others (if multiple languages installed)
					list($field_name, $field_params) = explode(' ', $field_expression, 2);

					$index_type = isset($this->curFields[$ml_field]['index_type']) ? $this->curFields[$prev_field]['index_type'] : 'string';

					$ret .= $index_type == 'string' ? 'ADD INDEX (`'.$field_name.'` (5) ), ' : 'ADD INDEX (`'.$field_name.'`), ';
					$this->curIndexCount++;
				}

				$start_index++;
			}
			return preg_replace('/, $/', ';', $ret);
		}

		/**
		 * Returns phrase based on given number
		 *
		 * @param int $number
		 * @param Array $forms
		 * @param bool $allow_editing
		 * @param bool $use_admin
		 * @return string
		 * @access public
		 */
		public function getPluralPhrase($number, $forms, $allow_editing = true, $use_admin = false)
		{
			// normalize given forms
			if ( !array_key_exists('phrase5', $forms) ) {
				$forms['phrase5'] = $forms['phrase2'];
			}

			$phrase_type = $this->getPluralPhraseType($number);

			return $this->Application->Phrase($forms['phrase' . $phrase_type], $allow_editing, $use_admin);
		}

		/**
		 * Returns phrase type based on given number
		 *
		 * @param int $number
		 * @return int
		 * @access protected
		 */
		protected function getPluralPhraseType($number)
		{
			$last_digit = substr($number, -1);
			$last_but_one_digit = strlen($number) > 1 ? substr($number, -2, 1) : false;
			$phrase_type = '5';

			if ( $last_but_one_digit != 1 ) {
				if ( $last_digit == 1 ) {
					$phrase_type = '1';
				}
				elseif ( $last_digit >= 2 && $last_digit <= 4 ) {
					$phrase_type = '2';
				}
			}

			return $phrase_type;
		}
	}
