<?php
/**
* @version	$Id: kbase.php 13545 2010-05-11 19:19:58Z 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!');

/**
* Base
*
* Desciption
* @package kernel4
*/
class kBase {
	/**
	* Holds reference to global KernelApplication instance
	* @access public
	* @var kApplication
	*/
	var $Application;

	/**
	 * Prefix, that was used
	 * to create an object
	 *
	 * @var string
	 * @access public
	 */
	var $Prefix='';

	/**
	 * Special, that was used
	 * to create an object
	 *
	 * @var string
	 * @access public
	 */
	var $Special='';

	var $OriginalParams;

	/**
	 * Set's application
	 *
	 * @return kBase
	 * @access public
	 */
	function kBase()
	{
		$this->Application =& kApplication::Instance();
	}

	/**
	 * Create new instance of object
	 *
	 * @return kBase
	 */
	function &makeClass()
	{
		$object = new kBase();
		return $object;
	}

	/**
	 * Set's prefix and special
	 *
	 * @param string $prefix
	 * @param string $special
	 * @access public
	 */
	function Init($prefix,$special,$event_params=null)
	{
		$prefix=explode('_',$prefix,2);
		$this->Prefix=$prefix[0];
		$this->Special=$special;
		$this->OriginalParams = $event_params;
	}

	/**
	 * Returns joined prefix
	 * and special if any
	 *
	 * @param bool $from_submit if true, then joins prefix & special by "_", uses  "." otherwise
	 * @return string
	 * @access protected
	 */
	function getPrefixSpecial($from_submit = false)
	{
		$separator = !$from_submit ? '.' : '_';
		$ret = $this->Prefix.$separator.$this->Special;
		return rtrim($ret, $separator);
	}

	function &getProperty($property_name)
	{
		return $this->$property_name;
	}

	function setProperty($property_name, &$property_value)
	{
		$this->$property_name =& $property_value;
	}
}

	class kHelper extends kBase {

		/**
		* Connection to database
		*
		* @var kDBConnection
		* @access public
		*/
		var $Conn;

		function kHelper()
		{
			parent::kBase();
			$this->Conn =& $this->Application->GetADODBConnection();
		}

		function InitHelper()
		{

		}

		/**
		 * Append prefix and special to tag
		 * params (get them from tagname) like
		 * they were really passed as params
		 *
		 * @param string $prefix_special
		 * @param Array $tag_params
		 * @return Array
		 * @access protected
		 */
		function prepareTagParams($prefix_special, $tag_params = Array())
		{
			$parts = explode('.', $prefix_special);

			$ret = $tag_params;
			$ret['Prefix'] = $parts[0];
			$ret['Special'] = count($parts) > 1 ? $parts[1] : '';
			$ret['PrefixSpecial'] = $prefix_special;

			return $ret;
		}
	}

class kDBBase extends kBase {
	/**
	* Connection to database
	*
	* @var kDBConnection
	* @access public
	*/
	var $Conn;

	/**
	* Description
	*
	* @var string Name of primary key field for the item
	* @access public
	*/
	var $IDField;

	/**
	* Holds SELECT, FROM, JOIN parts of SELECT query
	*
	* @var string
	* @access public
	*/
	var $SelectClause;

	/**
	* Fields allowed to be set (from table + virtual)
	*
	* @var Array
	* @access private
	*/
	var $Fields = Array();

	/**
	 * Holds custom field names for item
	 *
	 * @var Array
	 */
	var $customFields = Array();

	/**
	 * All virtual field names
	 *
	 * @var Array
	 * @access private
	 */
	var $VirtualFields = Array();

	/**
	 * Fields that need to be queried using custom expression, e.g. IF(...) AS value
	 *
	 * @var Array
	 * @access private
	 */
	var $CalculatedFields = Array();


	/**
	 * Calculated fields, that contain aggregated functions, e.g. COUNT, SUM, etc.
	 *
	 * @var Array
	 */
	var $AggregatedCalculatedFields = Array();

	/**
	* Description
	*
	* @var string Item' database table name, without prefix
	* @access public
	*/
	var $TableName;

	/**
	 * Allows to determine object's table status ('temp' - temp table, '' - live table)
	 *
	 * @var string
	 * @access public
	 */
	var $mode='';

	function kDBBase()
	{
		parent::kBase();
		$this->Conn =& $this->Application->GetADODBConnection();
	}

	/**
	* Set current item' database table name
	*
	* @access public
	* @param string $table_name
	* @return void
	*/
	function setTableName($table_name)
	{
		$this->TableName = $table_name;
	}

	/**
	 * Set object' TableName to Live table from config
	 *
	 * @access public
	 */
	function SwitchToLive()
	{
		$this->TableName = $this->Application->getUnitOption($this->Prefix, 'TableName');
		$this->mode = '';
	}

	/**
	 * Set object' TableName to Temp table from config
	 *
	 * @access public
	 */
	function SwitchToTemp()
	{
		$this->TableName = $this->Application->getUnitOption($this->Prefix, 'TableName');
		$this->SetTableName( $this->Application->GetTempName($this->TableName, 'prefix:'.$this->Prefix) );
		$this->mode = 't';
	}

	/**
	 * Checks if object uses temp table
	 *
	 * @return bool
	 */
	function IsTempTable()
	{
		return $this->Application->IsTempTable($this->TableName);
	}

	/**
	* Sets SELECT part of list' query
	*
	* @access public
	* @param string $sql SELECT and FROM [JOIN] part of the query up to WHERE
	* @return void
	*/
	function SetSelectSQL($sql)
	{
		$this->SelectClause = $sql;
	}

	function GetSelectSQL($base_query = null)
	{
		if (!isset($base_query)) {
			$base_query = $this->SelectClause;
		}
		$query = str_replace( Array('%1$s','%s'), $this->TableName, $base_query);
		$query = $this->replaceModePrefix($query);
		return $query;
	}


	/**
	 * Returns required mixing of aggregated & non-aggregated calculated fields
	 *
	 * @param int $aggregated 0 - having + aggregated, 1 - having only, 2 - aggregated only
	 * @return Array
	 */
	function getCalculatedFields($aggregated = 0)
	{
		switch ($aggregated) {
			case 0:
				$fields = array_merge($this->CalculatedFields, $this->AggregatedCalculatedFields);
				break;

			case 1:
				$fields = $this->CalculatedFields;
				break;

			case 2:
				$fields = $this->AggregatedCalculatedFields;
				break;

			default:
				$fields = Array();
				break;
		}

		return $fields;
	}

	/**
	 * Insert calculated fields sql into query in place of %2$s,
	 * return processed query.
	 *
	 * @param string $query
	 * @param int $aggregated 0 - having + aggregated, 1 - having only, 2 - aggregated only
	 * @return string
	 */
	function addCalculatedFields($query, $aggregated = 1)
	{
		$fields = $this->getCalculatedFields($aggregated);
		if ($fields) {
			$sql = Array ();
			$fields = str_replace('%2$s', $this->Application->GetVar('m_lang'), $fields);

			foreach ($fields as $field_name => $field_expression) {
				$sql[] = '('.$field_expression.') AS `'.$field_name.'`';
			}
			$sql = implode(',',$sql);
			return $this->Application->ReplaceLanguageTags( str_replace('%2$s', ','.$sql, $query) );
		}
		else {
			return str_replace('%2$s', '', $query);
		}
	}

	/**
	 * Allows substables to be in same mode as main item (e.g. LEFT JOINED ones)
	 *
	 * @param string $query
	 * @return string
	 */
	function replaceModePrefix($query)
	{
		$live_table = substr($this->Application->GetLiveName($this->TableName), strlen(TABLE_PREFIX));
		if (preg_match('/'.preg_quote(TABLE_PREFIX, '/').'(.*)'.preg_quote($live_table, '/').'/', $this->TableName, $rets)) {
			// will only happen, when table has a prefix (like in K4)
			return str_replace('%3$s', $rets[1], $query);
		}

		// will happen, when K3 table without prefix is used
		return $query;
	}

	/**
	 * Adds calculated field declaration to object.
	 *
	 * @param string $name
	 * @param string $sql_clause
	 */
	function addCalculatedField($name, $sql_clause)
	{
		$this->CalculatedFields[$name] = $sql_clause;
	}

	/**
	* Sets ID Field name used as primary key for loading items
	*
	* @access public
	* @param string $field_name
	* @return void
	* @see kDBBase::IDField
	*/
	function setIDField($field_name)
	{
		$this->IDField = $field_name;
	}

	/**
	 * Performs initial object configuration
	 *
	 * @param bool $populate_ml_fields create all ml fields from db in config or not
	 */
	function Configure($populate_ml_fields = false)
	{
		$this->setTableName( $this->Application->getUnitOption($this->Prefix, 'TableName') );
		$this->setIDField( $this->Application->getUnitOption($this->Prefix, 'IDField') );

		$this->defineFields();

		$this->ApplyFieldModifiers(); // should be called only after all fields definitions been set
		$this->prepareConfigOptions(); // this should go last, but before setDefaultValues, order is significant!

		$this->SetDefaultValues($populate_ml_fields);
	}

	/**
	 * Add field definitions from all possible sources (DB Fields, Virtual Fields, Calcualted Fields, e.t.c.)
	 *
	 */
	function defineFields()
	{
		$this->setConfigFields( $this->Application->getUnitOption($this->Prefix, 'Fields') );
		$this->setCustomFields( $this->Application->getUnitOption($this->Prefix, 'CustomFields', Array()) );
		$this->setVirtualFields( $this->Application->getUnitOption($this->Prefix, 'VirtualFields') );
		$this->setCalculatedFields( $this->Application->getUnitOption($this->Prefix, 'CalculatedFields', Array()) );
		$this->setAggragatedCalculatedFields( $this->Application->getUnitOption($this->Prefix, 'AggregatedCalculatedFields', Array()) );
	}

	function setCalculatedFields($fields)
	{
		$this->CalculatedFields = isset($fields[$this->Special]) ? $fields[$this->Special] : (isset($fields['']) ? $fields[''] : Array());
	}

	function setAggragatedCalculatedFields($fields)
	{
		$this->AggregatedCalculatedFields = isset($fields[$this->Special]) ? $fields[$this->Special] : (isset($fields['']) ? $fields[''] : Array());
	}

	/**
	 * Set's field names from table
	 * from config
	 *
	 * @param Array $fields
	 * @access public
	 */
	function setCustomFields($fields)
	{
		$this->customFields = $fields;
	}

	/**
	 * Set's field names from table
	 * from config
	 *
	 * @param Array $fields
	 * @access public
	 */
	function setConfigFields($fields)
	{
		$this->Fields = $fields;
	}

	/**
	 * Override field options with ones defined in submit via "field_modfiers" array (common for all prefixes)
	 *
	 * @access private
	 * @author Alex
	 */
	function ApplyFieldModifiers($field_modifiers = null)
	{
		$allowed_modifiers = Array('required', 'multiple');

		if ($this->Application->isAdminUser) {
			// can change upload dir on the fly (admin only!)
			$allowed_modifiers[] = 'upload_dir';
		}

		if (!isset($field_modifiers)) {
			$field_modifiers = $this->Application->GetVar('field_modifiers');
			if (!$field_modifiers) {
				// no field modifiers
				return false;
			}

			$field_modifiers = getArrayValue($field_modifiers, $this->getPrefixSpecial());
		}

		if (!$field_modifiers) {
			// no field modifiers for current prefix_special
			return false;
		}

		foreach ($field_modifiers as $field => $field_options)
		{
			foreach ($field_options as $option_name => $option_value)
			{
				if ( !in_array(strtolower($option_name), $allowed_modifiers) ) continue;
				$this->Fields[$field][$option_name] = $option_value;
			}
		}
	}

	/**
	 * Set fields (+options) for fields that physically doesn't exist in database
	 *
	 * @param Array $fields
	 * @access public
	 */
	function setVirtualFields($fields)
	{
		if($fields)
		{
			$this->VirtualFields = $fields;
			$this->Fields = array_merge($this->VirtualFields, $this->Fields);
//			$this->Fields = array_merge_recursive2($this->VirtualFields, $this->Fields);
		}
	}

	function SetDefaultValues($populate_ml_fields = false)
	{
		foreach($this->Fields as $field => $options)
		{
			if( isset($options['default']) && $options['default'] === '#NOW#')
			{
				$this->Fields[$field]['default'] = adodb_mktime();
			}
		}
	}

	function SetFieldOptions($field, $options)
	{
		$this->Fields[$field] = $options;
	}

	function GetFieldOptions($field)
	{
		if (isset($this->Fields[$field])) {
			$options_prepared = array_key_exists('options_prepared', $this->Fields[$field]) ? $this->Fields[$field]['options_prepared'] : false;
			if (!$options_prepared) {
				$this->PrepareFieldOptions($field);
				$this->Fields[$field]['options_prepared'] = true;
			}

			return $this->Fields[$field];
		}
		else {
			return Array();
		}
	}

	/**
	 * Returns formatted field value
	 *
	 * @param string $name
	 * @param string $format
	 *
	 * @return string
	 * @access public
	 */
	function GetField($name, $format = null)
	{
		$options = $this->GetFieldOptions($name);

		if (array_key_exists('formatter', $options)) {
			$formatter_class = $options['formatter'];
			$value = ($formatter_class == 'kMultiLanguage') && !preg_match('/^l[0-9]+_/', $name) ? '' : $this->GetDBField($name);

			$formatter =& $this->Application->recallObject($formatter_class);
			return $formatter->Format($value, $name, $this, $format);
		}

		return $this->GetDBField($name);
	}

	function HasField($name)
	{

	}

	function GetFieldValues()
	{

	}

	function UpdateFormattersSubFields($fields=null)
	{
		if (!is_array($fields)) {
			$fields = array_keys($this->Fields);
		}
		foreach ($fields as $field) {
			if ( isset($this->Fields[$field]['formatter']) ) {
				$formatter =& $this->Application->recallObject($this->Fields[$field]['formatter']);
				$formatter->UpdateSubFields($field, $this->GetDBField($field), $this->Fields[$field], $this);
			}
		}
	}

	function prepareConfigOptions()
	{
		foreach (array_keys($this->Fields) as $field_name)
		{
//			$this->PrepareFieldOptions($field_name);
			$this->PrepareOptions($field_name);
		}
	}

	/**
	 * Escapes fields only, not expressions
	 *
	 * @param string $field_expr
	 * @return string
	 */
	function escapeField($field_expr)
	{
		return preg_match('/[.(]/', $field_expr) ? $field_expr : '`'.$field_expr.'`';
	}

	/**
	 * Replaces current language id in given field options
	 *
	 * @param string $field_name
	 * @param Array $field_option_names
	 */
	function _replaceLanguageId($field_name, $field_option_names)
	{
		// don't use GetVar('m_lang') since it's always equals to default language on editing form in admin
		$current_language_id = $this->Application->Phrases->LanguageId;
		$primary_language_id = $this->Application->GetDefaultLanguageId();

		$field_options =& $this->Fields[$field_name];
		foreach ($field_option_names as $option_name) {
			$field_options[$option_name] = str_replace('%2$s', $current_language_id, $field_options[$option_name]);
			$field_options[$option_name] = str_replace('%3$s', $primary_language_id, $field_options[$option_name]);
		}
	}

	function PrepareFieldOptions($field_name)
	{
		$field_options =& $this->Fields[$field_name];
		if (array_key_exists('options_sql', $field_options) ) {
			// get options based on given sql
			$replace_options = Array ('option_title_field', 'option_key_field', 'options_sql');
			$this->_replaceLanguageId($field_name, $replace_options);

			$select_clause = $this->escapeField($field_options['option_title_field']) . ',' . $this->escapeField($field_options['option_key_field']);
			$sql = sprintf($field_options['options_sql'], $select_clause);

			if (array_key_exists('serial_name', $field_options)) {
				// try to cache option sql on serial basis
				$cache_key = 'sql_' . crc32($sql) . '[%' . $field_options['serial_name'] . '%]';
				$dynamic_options = $this->Application->getCache($cache_key);

				if ($dynamic_options === false) {
					$this->Conn->nextQueryCachable = true;
					$dynamic_options = $this->Conn->GetCol($sql, preg_replace('/^.*?\./', '', $field_options['option_key_field']));
					$this->Application->setCache($cache_key, $dynamic_options);
				}
			}
			else {
				// don't cache options sql
				$dynamic_options = $this->Conn->GetCol($sql, preg_replace('/^.*?\./', '', $field_options['option_key_field']));
			}

			$options_hash = array_key_exists('options', $field_options) ? $field_options['options'] : Array ();
			$field_options['options'] = array_merge_recursive2($options_hash, $dynamic_options);
		}
	}

	function PrepareOptions($field_name)
	{
		if ( isset($this->Fields[$field_name]['formatter']) )
		{
			$formatter =& $this->Application->recallObject( $this->Fields[$field_name]['formatter'] );
			$formatter->PrepareOptions($field_name, $this->Fields[$field_name], $this);
		}
	}

	/**
	 * Returns unformatted field value
	 *
	 * @param string $field
	 * @return string
	 * @access public
	 */
	function GetDBField($field)
	{

	}

	/**
	 * Returns ID of currently processed record
	 *
	 * @return int
	 * @access public
	 */
	function GetID()
	{
		return $this->GetDBField($this->IDField);
	}

	/**
	 * Allows kDBTagProcessor.SectionTitle to detect if it's editing or new item creation
	 *
	 * @return bool
	 */
	function IsNewItem()
	{
		return $this->GetID() ? false : true;
	}

	/**
	 * Returns parent table information
	 *
	 * @param bool $from_temp load parent item from temp table
	 * @param string $special special of main item
	 * @param bool $guess_special if object retrieved with specified special is not loaded, then try not to use special
	 * @return Array
	 */
	function getLinkedInfo($special = '', $guess_special = false)
	{
		$parent_prefix = $this->Application->getUnitOption($this->Prefix, 'ParentPrefix');
		if ($parent_prefix) {
			// if this is linked table, then set id from main table
			$table_info = Array (
				'TableName' => $this->Application->getUnitOption($this->Prefix,'TableName'),
				'IdField' => $this->Application->getUnitOption($this->Prefix,'IDField'),
				'ForeignKey' => $this->Application->getUnitOption($this->Prefix,'ForeignKey'),
				'ParentTableKey' => $this->Application->getUnitOption($this->Prefix,'ParentTableKey'),
				'ParentPrefix' => $parent_prefix
			);

			if (is_array($table_info['ForeignKey'])) {
					$table_info['ForeignKey'] = getArrayValue($table_info, 'ForeignKey', $parent_prefix);
			}

			if (is_array($table_info['ParentTableKey'])) {
				$table_info['ParentTableKey'] = getArrayValue($table_info, 'ParentTableKey', $parent_prefix);
			}

			$main_object =& $this->Application->recallObject($parent_prefix.'.'.$special, null, Array ('raise_warnings' => 0));
			/* @var $main_object kDBItem */

			if (!$main_object->isLoaded() && $guess_special) {
				$main_object =& $this->Application->recallObject($parent_prefix);
			}

			return array_merge($table_info, Array('ParentId'=> $main_object->GetDBField( $table_info['ParentTableKey'] ) ) );
		}

		return false;
	}

	/**
	 * Returns true if item was queried/loaded
	 *
	 * @return bool
	 */
	function isLoaded()
	{
		return false;
	}

	/**
	 * Returns specified field value from all selected rows.
	 * Don't affect current record index
	 *
	 * @param string $field
	 * @return Array
	 */
	function GetCol($field)
	{
		return Array ();
	}

}