ErrorMsgs['required'] = 'Field is required';
$this->ErrorMsgs['unique'] = 'Field value must be unique';
$this->ErrorMsgs['value_out_of_range'] = 'Field is out of range, possible values from %s to %s';
$this->ErrorMsgs['length_out_of_range'] = 'Field is out of range';
$this->ErrorMsgs['bad_type'] = 'Incorrect data format, please use %s';
$this->ErrorMsgs['bad_date_format'] = 'Incorrect date format, please use (%s) ex. (%s)';
}
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
*
* @access public
*/
function SetDefaultValues()
{
parent::SetDefaultValues();
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->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_msg = $this->Application->Phrase($error_label);
if( !($error_msg && getArrayValue($this->ErrorMsgs,$pseudo)) )
{
$this->ErrorMsgs[$pseudo] = $error_msg;
}
$this->FieldErrors[$field]['pseudo'] = $pseudo;
}
/**
* 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
* @return void
* @see kDBItem::Load()
* @see kDBItem::Update()
* @see kDBItem::Delete()
*/
function GetKeyClause($method=null)
{
return '`'.$this->TableName.'`.'.$this->IDField.' = '.$this->Conn->qstr($this->ID);
}
/**
* Loads item from the database by given id
*
* @access public
* @param int $id Primery Key Id to load
* @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 (is_array($id)) {
$keys = $id;
foreach ($keys as $field => $value) {
$sqls[] = '`'.$this->TableName.'`.'.$field.' = '.$this->Conn->qstr($value);
}
$keys_sql = '('.implode(') AND (', $sqls).')';
}
if (isset($id_field_name)) $this->SetIDField($id_field_name);
if (!isset($id) && !isset($keys_sql)) return false;
if( !$this->raiseEvent('OnBeforeItemLoad',$id) ) return false;
$this->ID = $id;
$q = $this->GetSelectSQL().' WHERE '.(isset($keys_sql) ? $keys_sql : $this->GetKeyClause('load'));
if ($this->DisplayQueries) {
echo get_class($this)." Load SQL: $q
";
}
$this->FieldValues = array_merge_recursive2( $this->FieldValues, $this->Conn->GetRow($q) );
if ($this->FieldValues === false) {
//Error handling could be here
return false;
}
if (isset($keys_sql)) {
$this->setID($this->FieldValues[$this->IDField]);
}
else {
$this->setID($id);
}
$this->UpdateFormattersSubFields(); // used for updating separate virtual date/time fields from DB timestamp (for example)
$this->raiseEvent('OnAfterItemLoad');
$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 'virtual' field
// $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->DisplayQueries) echo "Sql: $sql
";
if ($this->Conn->ChangeQuery($sql) === false) { //Executing query and checking results
if ($this->DisplayQueries)
{
echo "Error executing statement: ".$adodbConnection->ErrorMsg()."
";
}
return false;
}
$affected = $this->Conn->getAffectedRows();
if (!$system_update && $affected == 1){
$this->setModifiedFlag();
}
$this->raiseEvent('OnAfterItemUpdate');
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);
// 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 ".$this->Prefix.", FieldErrors follow (look at items with 'pseudo' key set)
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;
}
function HasErrors()
{
$global_res = false;
foreach ($this->Fields as $field => $params) {
// If Formatter has set some error messages during values parsing
if (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)
{
$where[] = '`'.$unique_field.'` = '.$this->Conn->qstr( $this->GetDBField($unique_field) );
}
$sql = 'SELECT COUNT(*) FROM %s WHERE ('.implode(') AND (',$where).') AND ('.$this->IDField.' <> '.(int)$this->ID.')';
$res_temp = $this->Conn->GetOne( sprintf($sql, $this->TableName ) );
$res_live = $this->Conn->GetOne( sprintf($sql, kTempTablesHandler::GetLiveName($this->TableName) ) );
$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 = '-∞';
if ( !isset($max_val) ) $max_val = '∞';
$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)
{
if( !isset($this->FieldErrors[$field]) ) return '';
$err = getArrayValue($this->FieldErrors[$field], 'pseudo');
if( isset($this->Fields[$field]['error_msgs'][$err]) )
{
$msg = $this->Fields[$field]['error_msgs'][$err];
$msg = $this->Application->ReplaceLanguageTags($msg);
}
else
{
if( !isset($this->ErrorMsgs[$err]) ) return $err;
$msg = $this->ErrorMsgs[$err];
}
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;
$fields_sql .= sprintf('`%s`, ',$field_name); //Adding field name to fields block of Insert statement
//Adding field' value to Values block of Insert statement, escaping it with ADODB' 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']).', ';
}
else {
$values_sql .= 'NULL, ';
}
}
else
{
$values_sql .= sprintf('%s, ',$this->Conn->qstr($this->FieldValues[$field_name], 0));
}
}
//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)
{
if($this->DisplayQueries)
{
echo "Error executing statement: ".$this->Conn->getErrorMsg().'
';
}
return false;
}
$insert_id = $this->Conn->getInsertID();
if($insert_id == 0) $insert_id = $this->FieldValues[$this->IDField];
$this->setID($insert_id);
if (!$system_create){
$this->setModifiedFlag();
}
$this->raiseEvent('OnAfterItemCreate');
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');
if ($this->DisplayQueries)
{
echo get_class($this).' Delete SQL: '.$q.'
';
}
$ret = $this->Conn->ChangeQuery($q);
$this->setModifiedFlag();
$this->raiseEvent('OnAfterItemDelete');
return $ret;
}
/**
* 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);
if (getArrayValue($master, 'ForeignKey') && isset($foreign_key)) {
$query .= ' AND '.$master['ForeignKey'].' = '.$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)
{
if( !isset($id) ) $id = $this->GetID();
$event = new kEvent( Array('name'=>$name,'prefix'=>$this->Prefix,'special'=>$this->Special) );
$event->setEventParam('id', $id);
$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;
}
}
?>