<?php

/**
* DBItem
*
* Desciption
* @package kernel4
*/
class kDBItem extends kDBBase {

	/**
	* Description
	*
	* @var array Associative array of current item' field values
	* @access public
	*/
	var $FieldValues;

	/**
	 * Unformatted field values, before parse
	 *
	 * @var Array
	 * @access private
	 */
	var $DirtyFieldValues = Array();

	var $FieldErrors;

	var $ErrorMsgs = Array();

	/**
	* If set to true, Update will skip Validation before running
	*
	* @var array Associative array of current item' field values
	* @access public
	*/
	var $IgnoreValidation = false;

	var $Loaded = false;

	/**
	* Holds item' primary key value
	*
	* @var int Value of primary key field for current item
	* @access public
	*/
	var $ID;

	function kDBItem()
	{
		parent::kDBBase();
		$this->ErrorMsgs['required'] = '!la_err_required!'; //'Field is required';
		$this->ErrorMsgs['unique'] = '!la_err_unique!'; //'Field value must be unique';
		$this->ErrorMsgs['value_out_of_range'] = '!la_err_value_out_of_range!'; //'Field is out of range, possible values from %s to %s';
		$this->ErrorMsgs['length_out_of_range'] = '!la_err_length_out_of_range!'; //'Field is out of range';
		$this->ErrorMsgs['bad_type'] = '!la_err_bad_type!'; //'Incorrect data format, please use %s';
		$this->ErrorMsgs['invalid_format'] = '!la_err_invalid_format!'; //'Incorrect data format, please use %s';
		$this->ErrorMsgs['bad_date_format'] = '!la_err_bad_date_format!'; //'Incorrect date format, please use (%s) ex. (%s)';
		$this->ErrorMsgs['primary_lang_required'] = '!la_err_primary_lang_required!';
	}

	function SetDirtyField($field_name, $field_value)
	{
		$this->DirtyFieldValues[$field_name] = $field_value;
	}

	function GetDirtyField($field_name)
	{
		return $this->DirtyFieldValues[$field_name];
	}

	/**
	 * Set's default values for all fields
	 *
	 * @param bool $populate_ml_fields create all ml fields from db in config or not
	 * 
	 * @access public
	 */
	function SetDefaultValues($populate_ml_fields = false)
	{
		parent::SetDefaultValues($populate_ml_fields);
		if ($populate_ml_fields) {
			$this->PopulateMultiLangFields();
		}
		
		foreach ($this->Fields as $field => $params) {
			if ( isset($params['default']) ) {
				$this->SetDBField($field, $params['default']);
			}
			else {
				$this->SetDBField($field, NULL);
			}
		}
	}

	/**
	* Sets current item field value
	* (applies formatting)
	*
	* @access public
	* @param string $name Name of the field
	* @param mixed $value Value to set the field to
	* @return void
	*/
	function SetField($name,$value)
	{
		$options = $this->GetFieldOptions($name);
		$parsed = $value;
		if ($value == '') {
			$parsed = NULL;
		}
		if (isset($options['formatter'])) {
				$formatter =& $this->Application->recallObject($options['formatter']);
//				$parsed = $formatter->Parse($value, $options, $err);
				$parsed = $formatter->Parse($value, $name, $this);
		}
		// this will make sure numeric value is converted to normal representation
		// according to regional format, even when formatter is not set (try seting format to 1.234,56 to understand why)
		elseif (preg_match('#int|integer|double|float|real|numeric#', $options['type'])) {
			$formatter =& $this->Application->recallObject('kFormatter');
			$parsed = $formatter->TypeCast($value, $options);
		}
		$this->SetDBField($name,$parsed);
	}

	/**
	* Sets current item field value
	* (doesn't apply formatting)
	*
	* @access public
	* @param string $name Name of the field
	* @param mixed $value Value to set the field to
	* @return void
	*/
	function SetDBField($name,$value)
	{
		$this->FieldValues[$name] = $value;
		/*if (isset($this->Fields[$name]['formatter'])) {
			$formatter =& $this->Application->recallObject($this->Fields[$name]['formatter']);
			$formatter->UpdateSubFields($name, $value, $this->Fields[$name], $this);
		}*/
	}

	/**
	 * Set's field error, if pseudo passed not found then create it with message text supplied.
	 * Don't owerrite existing pseudo translation.
	 *
	 * @param string $field
	 * @param string $pseudo
	 * @param string $error_label
	 */
	function SetError($field, $pseudo, $error_label = '')
	{
		$error_field = isset($this->Fields[$field]['error_field']) ? $this->Fields[$field]['error_field'] : $field;
		$this->FieldErrors[$error_field]['pseudo'] = $pseudo;

		$error_msg = $error_label ? $this->Application->Phrase($error_label) : '';
		if ($error_label && !getArrayValue($this->ErrorMsgs, $pseudo))
		{
			$this->ErrorMsgs[$pseudo] = $error_msg;
		}
	}

	/**
	* Return current item' field value by field name
	* (doesn't apply formatter)
	*
	* @access public
	* @param string $name field name to return
	* @return mixed
	*/
	function GetDBField($name)
	{
		return $this->FieldValues[$name];
	}

	function HasField($name)
	{
		return isset($this->FieldValues[$name]);
	}

	function GetFieldValues()
	{
		return $this->FieldValues;
	}

	/**
	* Sets item' fields corresponding to elements in passed $hash values.
	*
	* The function sets current item fields to values passed in $hash, by matching $hash keys with field names
	* of current item. If current item' fields are unknown {@link kDBItem::PrepareFields()} is called before acutally setting the fields
	*
	* @access public
	* @param Array $hash
	* @param Array $set_fields Optional param, field names in target object to set, other fields will be skipped
	* @return void
	*/
	function SetFieldsFromHash($hash, $set_fields=null)
	{
		// used in formatter which work with multiple fields together
		foreach($hash as $field_name => $field_value)
		{
			if( eregi("^[0-9]+$", $field_name) || !array_key_exists($field_name,$this->Fields) ) continue;
			if ( is_array($set_fields) && !in_array($field_name, $set_fields) ) continue;
			$this->SetDirtyField($field_name, $field_value);
		}

		// formats all fields using associated formatters
		foreach ($hash as $field_name => $field_value)
		{
			if( eregi("^[0-9]+$", $field_name) || !array_key_exists($field_name,$this->Fields) ) continue;
			if ( is_array($set_fields) && !in_array($field_name, $set_fields) ) continue;
			$this->SetField($field_name,$field_value);
		}
	}

	function SetDBFieldsFromHash($hash, $set_fields=null)
	{
		foreach ($hash as $field_name => $field_value)
		{
			if( eregi("^[0-9]+$", $field_name) || !array_key_exists($field_name,$this->Fields) ) continue;
			if ( is_array($set_fields) && !in_array($field_name, $set_fields) ) continue;

			$this->SetDBField($field_name, $field_value);
		}
	}

	/**
	* Returns part of SQL WHERE clause identifing the record, ex. id = 25
	*
	* @access public
	* @param string $method Child class may want to know who called GetKeyClause, Load(), Update(), Delete() send its names as method
	* @param Array $keys_hash alternative, then item id, keys hash to load item by
	* @return void
	* @see kDBItem::Load()
	* @see kDBItem::Update()
	* @see kDBItem::Delete()
	*/
	function GetKeyClause($method=null, $keys_hash = null)
	{
		if( !isset($keys_hash) ) $keys_hash = Array($this->IDField => $this->ID);

		$ret = '';
		foreach($keys_hash as $field => $value)
		{
			if (!preg_match('/\./', $field)) {
				$ret .= '(`'.$this->TableName.'`.'.$field.' = '.$this->Conn->qstr($value).') AND ';
			}
			else {
				$ret .= '('.$field.' = '.$this->Conn->qstr($value).') AND ';
			}
		}

		return preg_replace('/(.*) AND $/', '\\1', $ret);
	}

	/**
	* Loads item from the database by given id
	*
	* @access public
	* @param mixed $id item id of keys->values hash to load item by
	* @param string $id_field_name Optional parameter to load item by given Id field
	* @return bool True if item has been loaded, false otherwise
	*/
	function Load($id, $id_field_name = null)
	{
		if ( isset($id_field_name) ) $this->SetIDField( $id_field_name );
		$keys_sql = '';
		if( is_array($id) )
		{
			$keys_sql = $this->GetKeyClause('load', $id);
		}
		else
		{
			$this->setID($id);
			$keys_sql = $this->GetKeyClause('load');
		}
		if ( isset($id_field_name) ) $this->setIDField( $this->Application->getUnitOption($this->Prefix, 'IDField') );


		if( ($id === false) || !$keys_sql ) return $this->Clear();

		if( !$this->raiseEvent('OnBeforeItemLoad', $id) ) return false;

		$q = $this->GetSelectSQL().' WHERE '.$keys_sql;

		$field_values = $this->Conn->GetRow($q);
		if($field_values)
		{
			$this->FieldValues = array_merge_recursive2($this->FieldValues, $field_values);
		}
		else
		{
			return $this->Clear();
		}

		if( is_array($id) || isset($id_field_name) ) $this->setID( $this->FieldValues[$this->IDField] );

		$this->UpdateFormattersSubFields(); // used for updating separate virtual date/time fields from DB timestamp (for example)

		$this->raiseEvent('OnAfterItemLoad', $this->GetID() );
		$this->Loaded = true;
		return true;
	}

	/**
	* Builds select sql, SELECT ... FROM parts only
	*
	* @access public
	* @return string
	*/
	function GetSelectSQL()
	{
		$sql = $this->addCalculatedFields($this->SelectClause);
		return parent::GetSelectSQL($sql);
	}

	function UpdateFormattersMasterFields()
	{
		foreach ($this->Fields as $field => $options) {
			if (isset($options['formatter'])) {
				$formatter =& $this->Application->recallObject($options['formatter']);
				$formatter->UpdateMasterFields($field, $this->GetDBField($field), $options, $this);
			}
		}
	}

	function SkipField($field_name, $force_id=false)
	{
		$skip = false;
		$skip = $skip || ( isset($this->VirtualFields[$field_name]) ); //skipping 'virtual' field
		$skip = $skip || ( !getArrayValue($this->FieldValues, $field_name) && getArrayValue($this->Fields[$field_name], 'skip_empty') ); //skipping marked field with 'skip_empty'
//		$skip = $skip || ($field_name == $this->IDField && !$force_id); //skipping Primary Key

//		$table_name = preg_replace("/^(.*)\./", "$1", $field_name);
//		$skip = $skip || ($table_name && ($table_name != $this->TableName)); //skipping field from other tables

		$skip = $skip || ( !isset($this->Fields[$field_name]) ); //skipping field not in Fields (nor virtual, nor real)

		return $skip;
	}

	/**
	* Updates previously loaded record with current item' values
	*
	* @access public
	* @param int Primery Key Id to update
	* @return bool
	*/
	function Update($id=null, $system_update=false)
	{
		if( isset($id) ) $this->setID($id);

		if( !$this->raiseEvent('OnBeforeItemUpdate') ) return false;

		if( !isset($this->ID) ) return false;

		// Validate before updating
		if( !$this->IgnoreValidation && !$this->Validate() ) return false;
	    if( !$this->raiseEvent('OnAfterItemValidate') ) return false;

	   	//Nothing to update
	    if(!$this->FieldValues) return true;

	    $sql = sprintf('UPDATE %s SET ',$this->TableName);
	    foreach ($this->FieldValues as $field_name => $field_value)
	    {
	    	if ($this->SkipField($field_name)) continue;

	    	$real_field_name = eregi_replace("^.*\.", '',$field_name); //removing table names from field names

	    	//Adding part of SET clause for current field, escaping data with ADODB' qstr
	    	if (is_null( $this->FieldValues[$field_name] )) {
	    		if (isset($this->Fields[$field_name]['not_null']) && $this->Fields[$field_name]['not_null']) {
	    			$sql .= '`'.$real_field_name.'` = '.$this->Conn->qstr($this->Fields[$field_name]['default']).', ';
	    		}
	    		else {
	    			$sql .= '`'.$real_field_name.'` = NULL, ';
	    		}
	    	}
	    	else {
	    		$sql.= sprintf('`%s`=%s, ', $real_field_name, $this->Conn->qstr($this->FieldValues[$field_name], 0));
	    	}
	   	}
	   	$sql = ereg_replace(", $", '', $sql); //Removing last comma and space

	    $sql.= sprintf(' WHERE %s', $this->GetKeyClause('update')); //Adding WHERE clause with Primary Key

	    if( $this->Conn->ChangeQuery($sql) === false ) return false;

		$affected = $this->Conn->getAffectedRows();
		if (!$system_update && $affected == 1){
			$this->setModifiedFlag();
		}

		$this->saveCustomFields();
	    $this->raiseEvent('OnAfterItemUpdate');
	    $this->Loaded = true;
	    if ($this->mode != 't') {
			$this->Application->resetCounters($this->TableName);
		}
	    return true;
	}

	/**
	 * Validate all item fields based on
	 * constraints set in each field options
	 * in config
	 *
	 * @return bool
	 * @access private
	 */
	function Validate()
	{
		$this->UpdateFormattersMasterFields(); //order is critical - should be called BEFORE checking errors
		$global_res = true;
		foreach ($this->Fields as $field => $params) {
			$res = true;
			$res = $res && $this->ValidateType($field, $params);
			$res = $res && $this->ValidateRange($field, $params);
			$res = $res && $this->ValidateUnique($field, $params);
			$res = $res && $this->ValidateRequired($field, $params);
			$res = $res && $this->CustomValidation($field, $params);

			// If Formatter has set some error messages during values parsing
			$error_field = isset($params['error_field']) ? $params['error_field'] : $field;
			if (isset($this->FieldErrors[$error_field]['pseudo']) && $this->FieldErrors[$error_field] != '') {
				$global_res = false;
			}

			$global_res = $global_res && $res;
		}

		if (!$global_res &&  $this->Application->isDebugMode() )
		{
			global $debugger;
			$error_msg = "Validation failed in prefix <b>".$this->Prefix."</b>, FieldErrors follow (look at items with 'pseudo' key set)<br>
										You may ignore this notice if submitted data really has a validation error ";
			trigger_error( $error_msg, E_USER_NOTICE);
			$debugger->dumpVars($this->FieldErrors);
		}

		return $global_res;
	}

	/**
	 * Check field value by user-defined alghoritm
	 *
	 * @param string $field field name
	 * @param Array $params field options from config
	 * @return bool
	 */
	function CustomValidation($field, $params)
	{
		return true;
	}

	/**
	 * Check if item has errors
	 *
	 * @param Array $skip_fields fields to skip during error checking
	 * @return bool
	 */
	function HasErrors($skip_fields)
	{
		$global_res = false;

		foreach ($this->Fields as $field => $field_params) {
			// If Formatter has set some error messages during values parsing
			if ( !( in_array($field, $skip_fields) ) &&
				isset($this->FieldErrors[$field]['pseudo']) && $this->FieldErrors[$field] != '') {
				$global_res = true;
			}
		}
		return $global_res;
	}

	/**
	 * Check if value in field matches field type specified in config
	 *
	 * @param string $field field name
	 * @param Array $params field options from config
	 * @return bool
	 */
	function ValidateType($field, $params)
	{
		$res = true;
		$val = $this->FieldValues[$field];
		$error_field = isset($params['error_field']) ? $params['error_field'] : $field;
		if ( 	$val != '' &&
					isset($params['type']) &&
					preg_match("#int|integer|double|float|real|numeric|string#", $params['type'])
				) {
			$res = is_numeric($val);
			if($params['type']=='string' || $res)
			{
				$f = 'is_'.$params['type'];
				settype($val, $params['type']);
				$res = $f($val) && ($val==$this->FieldValues[$field]);
			}
			if (!$res)
			{
				$this->FieldErrors[$error_field]['pseudo'] = 'bad_type';
				$this->FieldErrors[$error_field]['params'] = $params['type'];
			}
		}
		return $res;
	}

	/**
	 * Check if value is set for required field
	 *
	 * @param string $field field name
	 * @param Array $params field options from config
	 * @return bool
	 * @access private
	 */
	function ValidateRequired($field, $params)
	{
		$res = true;
		$error_field = isset($params['error_field']) ? $params['error_field'] : $field;
		if ( getArrayValue($params,'required') )
		{
			$res = ( (string) $this->FieldValues[$field] != '');
		}
		$options = $this->GetFieldOptions($field);
		if (!$res && getArrayValue($options, 'formatter') != 'kUploadFormatter') $this->FieldErrors[$error_field]['pseudo'] = 'required';
		return $res;
	}

	/**
	 * Validates that current record has unique field combination among other table records
	 *
	 * @param string $field field name
	 * @param Array $params field options from config
	 * @return bool
	 * @access private
	 */
	function ValidateUnique($field, $params)
	{
		$res = true;
		$error_field = isset($params['error_field']) ? $params['error_field'] : $field;
		$unique_fields = getArrayValue($params,'unique');
		if($unique_fields !== false)
		{
			$where = Array();
			array_push($unique_fields,$field);
			foreach($unique_fields as $unique_field)
			{
				// if field is not empty or if it is required - we add where condition
				if ($this->GetDBField($unique_field) != '' || $this->Fields[$unique_field]['required']) {
					$where[] = '`'.$unique_field.'` = '.$this->Conn->qstr( $this->GetDBField($unique_field) );
				}
			}
			// This can ONLY happen if all unique fields are empty and not required.
			// In such case we return true, because if unique field is not required there may be numerous empty values
			if (!$where) return true;

			$sql = 'SELECT COUNT(*) FROM %s WHERE ('.implode(') AND (',$where).') AND ('.$this->IDField.' <> '.(int)$this->ID.')';

			$res_temp = $this->Conn->GetOne( str_replace('%s', $this->TableName, $sql) );

			$current_table_only = getArrayValue($params, 'current_table_only'); // check unique record only in current table
			$res_live = $current_table_only ? 0 : $this->Conn->GetOne( str_replace('%s', $this->Application->GetLiveName($this->TableName), $sql) );

			$res = ($res_temp == 0) && ($res_live == 0);

			if(!$res) $this->FieldErrors[$error_field]['pseudo'] = 'unique';
		}
		return $res;
	}

	/**
	 * Check if field value is in range specified in config
	 *
	 * @param string $field field name
	 * @param Array $params field options from config
	 * @return bool
	 * @access private
	 */
	function ValidateRange($field, $params)
	{
		$res = true;
		$val = $this->FieldValues[$field];
		$error_field = isset($params['error_field']) ? $params['error_field'] : $field;

		if ( isset($params['type']) && preg_match("#int|integer|double|float|real#", $params['type']) && strlen($val) > 0 ) {
			if ( isset($params['max_value_inc'])) {
				$res = $res && $val <= $params['max_value_inc'];
				$max_val = $params['max_value_inc'].' (inclusive)';
			}
			if ( isset($params['min_value_inc'])) {
				$res = $res && $val >= $params['min_value_inc'];
				$min_val = $params['min_value_inc'].' (inclusive)';
			}
			if ( isset($params['max_value_exc'])) {
				$res = $res && $val < $params['max_value_exc'];
				$max_val = $params['max_value_exc'].' (exclusive)';
			}
			if ( isset($params['min_value_exc'])) {
				$res = $res && $val > $params['min_value_exc'];
				$min_val = $params['min_value_exc'].' (exclusive)';
			}
		}
		if (!$res) {
			$this->FieldErrors[$error_field]['pseudo'] = 'value_out_of_range';

			if ( !isset($min_val) ) $min_val = '-&infin;';
			if ( !isset($max_val) ) $max_val = '&infin;';

			$this->FieldErrors[$error_field]['params'] = Array( $min_val, $max_val );
			return $res;
		}
		if ( isset($params['max_len'])) {
			$res = $res && strlen($val) <= $params['max_len'];
		}
		if ( isset($params['min_len'])) {
			$res = $res && strlen($val) >= $params['min_len'];
		}
		if (!$res) {
			$this->FieldErrors[$error_field]['pseudo'] = 'length_out_of_range';
			$this->FieldErrors[$error_field]['params'] = Array( getArrayValue($params,'min_len'), getArrayValue($params,'max_len') );
			return $res;
		}
		return $res;
	}

	/**
	 * Return error message for field
	 *
	 * @param string $field
	 * @return string
	 * @access public
	 */
	function GetErrorMsg($field, $force_escape = null)
	{
		if( !isset($this->FieldErrors[$field]) ) return '';

		$err = getArrayValue($this->FieldErrors[$field], 'pseudo');
		if (!$err) return '';
		// if special error msg defined in config
		if( isset($this->Fields[$field]['error_msgs'][$err]) )
		{
			$msg = $this->Fields[$field]['error_msgs'][$err];
		}
		else //fall back to defaults
		{
			if( !isset($this->ErrorMsgs[$err]) ) {
				trigger_error('No user message is defined for pseudo error <b>'.$err.'</b><br>', E_USER_WARNING);
				return $err; //return the pseudo itself
			}
			$msg = $this->ErrorMsgs[$err];
		}
		$msg = $this->Application->ReplaceLanguageTags($msg, $force_escape);

		if ( isset($this->FieldErrors[$field]['params']) )
		{
			return vsprintf($msg, $this->FieldErrors[$field]['params']);
		}
		return $msg;
	}

	/**
	* Creates a record in the database table with current item' values
	*
	* @param mixed $force_id Set to TRUE to force creating of item's own ID or to value to force creating of passed id. Do not pass 1 for true, pass exactly TRUE!
	* @access public
	* @return bool
	*/
	function Create($force_id=false, $system_create=false)
	{
		if( !$this->raiseEvent('OnBeforeItemCreate') ) return false;

		// Validating fields before attempting to create record
		if( !$this->IgnoreValidation && !$this->Validate() )  return false;
		if( !$this->raiseEvent('OnAfterItemValidate') ) return false;

		if (is_int($force_id)) {
			$this->FieldValues[$this->IDField] = $force_id;
		}
		elseif (!$force_id || !is_bool($force_id)) {
			$this->FieldValues[$this->IDField] = $this->generateID();
		}

		$fields_sql = '';
		$values_sql = '';
		foreach ($this->FieldValues as $field_name => $field_value) {
			if ($this->SkipField($field_name, $force_id)) continue;

			//Adding field' value to Values block of Insert statement, escaping it with qstr
			if (is_null( $this->FieldValues[$field_name] )) {
				if (isset($this->Fields[$field_name]['not_null']) && $this->Fields[$field_name]['not_null']) {
					$values_sql .= $this->Conn->qstr($this->Fields[$field_name]['default'], 0);
				}
				else {
					$values_sql .= 'NULL';
				}
			}
			else {
				if ($field_name == $this->IDField && $this->FieldValues[$field_name] == 0) {
					$values_sql .= 'DEFAULT';
				}
				else {
					$values_sql .= $this->Conn->qstr($this->FieldValues[$field_name], 0);
				}
			}
			$fields_sql .= '`'.$field_name.'`, '; //Adding field name to fields block of Insert statement
			$values_sql .= ', ';
		}
		//Cutting last commas and spaces
		$fields_sql = ereg_replace(", $", '', $fields_sql);
		$values_sql = ereg_replace(", $", '', $values_sql);
		$sql = sprintf('INSERT INTO %s (%s) VALUES (%s)', $this->TableName, $fields_sql, $values_sql); //Formatting query

		//Executing the query and checking the result
		if ($this->Conn->ChangeQuery($sql) === false) return false;

		$insert_id = $this->Conn->getInsertID();
		if ($insert_id == 0) {
			// insert into temp table (id is not auto-increment field)
			$insert_id = $this->FieldValues[$this->IDField];
		}
		$this->setID($insert_id);

		if (!$system_create){
			$this->setModifiedFlag();
		}

		$this->saveCustomFields();
		if ($this->mode != 't') {
			$this->Application->resetCounters($this->TableName);
		}
		$this->raiseEvent('OnAfterItemCreate');
		$this->Loaded = true;
		return true;
	}

	/**
	* Deletes the record from databse
	*
	* @access public
	* @return bool
	*/
	function Delete($id = null)
	{
		if( isset($id) ) $this->setID($id);

		if( !$this->raiseEvent('OnBeforeItemDelete') ) return false;

		$q = 'DELETE FROM '.$this->TableName.' WHERE '.$this->GetKeyClause('Delete');

		$ret = $this->Conn->ChangeQuery($q);

		$this->setModifiedFlag();

		$this->raiseEvent('OnAfterItemDelete');
		if ($this->mode != 't') {
			$this->Application->resetCounters($this->TableName);
		}
		return $ret;
	}

	function PopulateMultiLangFields()
	{
		$ml_helper =& $this->Application->recallObject('kMultiLanguageHelper');
		$lang_count = $ml_helper->getLanguageCount();
		foreach ($this->Fields as $field => $options)
		{
			if (isset($options['formatter']) && $options['formatter'] == 'kMultiLanguage' && isset($options['master_field'])) {
				if (preg_match('/^l([0-9]+)_(.*)/', $field, $regs)) {
					$l = $regs[1];
					$name = $regs[2];
					unset($options['required']); // all non-primary language field set to non-required
					for ($i=1; $i<=$lang_count; $i++) {
						if ($i == $l) continue;
						$this->Fields['l'.$i.'_'.$name] = $options;
					}
				}
			}
		}
	}

	/**
	 * Sets new name for item in case if it is beeing copied
	 * in same table
	 *
	 * @param array $master Table data from TempHandler
	 * @param int $foreign_key ForeignKey value to filter name check query by
	 * @access private
	 */
	function NameCopy($master=null, $foreign_key=null)
	{
		$title_field = $this->Application->getUnitOption($this->Prefix, 'TitleField');
		if (!$title_field || isset($this->CalculatedFields[$title_field]) ) return;

		$new_name = $this->GetDBField($title_field);
		$original_checked = false;
		do {
			if ( preg_match('/Copy ([0-9]*) *of (.*)/', $new_name, $regs) ) {
				$new_name = 'Copy '.($regs[1]+1).' of '.$regs[2];
			}
			elseif ($original_checked) {
				$new_name = 'Copy of '.$new_name;
			}

			// if we are cloning in temp table this will look for names in temp table,
			// since object' TableName contains correct TableName (for temp also!)
			// if we are cloning live - look in live
			$query = 'SELECT '.$title_field.' FROM '.$this->TableName.'
								WHERE '.$title_field.' = '.$this->Conn->qstr($new_name);

			$foreign_key_field = getArrayValue($master, 'ForeignKey');
			$foreign_key_field = is_array($foreign_key_field) ? $foreign_key_field[ $master['ParentPrefix'] ] : $foreign_key_field;

			if ($foreign_key_field && isset($foreign_key)) {
				$query .= ' AND '.$foreign_key_field.' = '.$foreign_key;
			}

			$res = $this->Conn->GetOne($query);

			/*// if not found in live table, check in temp table if applicable
			if ($res === false && $object->Special == 'temp') {
				$query = 'SELECT '.$name_field.' FROM '.$this->GetTempName($master['TableName']).'
									WHERE '.$name_field.' = '.$this->Conn->qstr($new_name);
				$res = $this->Conn->GetOne($query);
			}*/

			$original_checked = true;
		} while ($res !== false);
		$this->SetDBField($title_field, $new_name);
	}

	function raiseEvent($name, $id = null, $additional_params = Array())
	{
		if( !isset($id) ) $id = $this->GetID();
		$event = new kEvent( Array('name'=>$name,'prefix'=>$this->Prefix,'special'=>$this->Special) );
		$event->setEventParam('id', $id);

		if ($additional_params) {
			foreach ($additional_params as $ap_name => $ap_value) {
				$event->setEventParam($ap_name, $ap_value);
			}
		}

		$this->Application->HandleEvent($event);
		return $event->status == erSUCCESS ? true : false;
	}

	/**
	 * Set's new ID for item
	 *
	 * @param int $new_id
	 * @access public
	 */
	function setID($new_id)
	{
		$this->ID = $new_id;
		$this->SetDBField($this->IDField, $new_id);
	}

	/**
	 * Generate and set new temporary id
	 *
	 * @access private
	 */
	function setTempID()
	{
		$new_id = (int)$this->Conn->GetOne('SELECT MIN('.$this->IDField.') FROM '.$this->TableName);
		if($new_id > 0) $new_id = 0;
		--$new_id;

		$this->Conn->Query('UPDATE '.$this->TableName.' SET `'.$this->IDField.'` = '.$new_id.' WHERE `'.$this->IDField.'` = '.$this->GetID());
		$this->SetID($new_id);
	}

	/**
	 * Set's modification flag for main prefix of current prefix to true
	 *
	 * @access private
	 * @author Alexey
	 */
	function setModifiedFlag()
	{
		$main_prefix = $this->Application->GetTopmostPrefix($this->Prefix);
		$this->Application->StoreVar($main_prefix.'_modified', '1');
	}

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

	/**
	 * Generates ID for new items before inserting into database
	 *
	 * @return int
	 * @access private
	 */
	function generateID()
	{
		return 0;
	}

	/**
	 * Returns true if item was loaded successfully by Load method
	 *
	 * @return bool
	 */
	function isLoaded()
	{
		return $this->Loaded;
	}

	/**
	 * Checks if field is required
	 *
	 * @param string $field
	 * @return bool
	 */
	function isRequired($field)
	{
		return getArrayValue( $this->Fields[$field], 'required' );
	}

	/**
	 * Sets new required flag to field
	 *
	 * @param string $field
	 * @param bool $is_required
	 */
	function setRequired($field, $is_required = true)
	{
		$this->Fields[$field]['required'] = $is_required;
	}

	function Clear()
	{
		$this->setID(null);
		$this->Loaded = false;
		$this->FieldValues = Array();
		$this->SetDefaultValues();
		$this->FieldErrors = Array();
		return $this->Loaded;
	}

	function Query($force = false)
	{
		if( $this->Application->isDebugMode() )
		{
			$this->Application->Debugger->appendTrace();
		}
		trigger_error('<b>Query</b> method is called in class <b>'.get_class($this).'</b> for prefix <b>'.$this->getPrefixSpecial().'</b>', E_USER_ERROR);
	}

	function saveCustomFields()
	{
		if (!$this->customFields) {
			return true;
		}

		$cdata_key = rtrim($this->Prefix.'-cdata.'.$this->Special, '.');
		$cdata =& $this->Application->recallObject($cdata_key, null, Array('skip_autoload' => true));

		$resource_id = $this->GetDBField('ResourceId');
		$cdata->Load($resource_id, 'ResourceId');
		$cdata->SetDBField('ResourceId', $resource_id);

		$ml_formatter =& $this->Application->recallObject('kMultiLanguage');
		foreach ($this->customFields as $custom_id => $custom_name) {
			$cdata->SetDBField($ml_formatter->LangFieldName('cust_'.$custom_id), $this->GetDBField('cust_'.$custom_name));
		}

		if ($cdata->isLoaded()) {
			$ret = $cdata->Update();
		}
		else {
			 $ret = $cdata->Create();
			 if ($cdata->mode == 't') $cdata->setTempID();
		}

		return $ret;
	}
}



?>