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; } } ?>