copy($proto); else{ $start=0; $this->filters=array(); $this->sort_by=array(); } } /*! copy parameters of source object into self @param proto source object */ public function copy($proto){ $this->filters =$proto->get_filters(); $this->sort_by =$proto->get_sort_by(); $this->count =$proto->get_count(); $this->start =$proto->get_start(); $this->source =$proto->get_source(); $this->fieldset =$proto->get_fieldset(); $this->relation =$proto->get_relation(); $this->user = $proto->user; $this->version = $proto->version; $this->action_mode = $proto->action_mode; } /*! convert self to string ( for logs ) @return self as plain string, */ public function __toString(){ $str="Source:{$this->source}\nFieldset:{$this->fieldset}\nWhere:"; for ($i=0; $i < sizeof($this->filters); $i++) $str.=$this->filters[$i]["name"]." ".$this->filters[$i]["operation"]." ".$this->filters[$i]["value"].";"; $str.="\nStart:{$this->start}\nCount:{$this->count}\n"; for ($i=0; $i < sizeof($this->sort_by); $i++) $str.=$this->sort_by[$i]["name"]."=".$this->sort_by[$i]["direction"].";"; $str.="\nRelation:{$this->relation}"; return $str; } public function set_action_mode($action_mode) { $this->action_mode = $action_mode; return $this; } public function get_action_mode() { return $this->action_mode; } /*! returns set of filtering rules @return set of filtering rules */ public function get_filters(){ return $this->filters; } public function &get_filters_ref(){ return $this->filters; } public function set_filters($data){ $this->filters=$data; } public function get_order(){ return $this->order; } public function set_order($order){ $this->order = $order; } public function get_user(){ return $this->user; } public function set_user($user){ $this->user = $user; } public function get_version(){ return $this->version; } public function set_version($version){ $this->version = $version; } /*! returns list of used fields @return list of used fields */ public function get_fieldset(){ return $this->fieldset; } /*! returns name of source table @return name of source table */ public function get_source(){ return $this->source; } /*! returns set of sorting rules @return set of sorting rules */ public function get_sort_by(){ return $this->sort_by; } public function &get_sort_by_ref(){ return $this->sort_by; } public function set_sort_by($data){ $this->sort_by=$data; } /*! returns start index @return start index */ public function get_start(){ return $this->start; } /*! returns count of requested records @return count of requested records */ public function get_count(){ return $this->count; } /*! returns name of relation id @return relation id name */ public function get_relation(){ return $this->relation; } /*! sets sorting rule @param field name of column @param order direction of sorting */ public function set_sort($field,$order=false){ if (!$field && !$order) $this->sort_by=array(); else{ if ($order===false) $this->sort_by[] = $field; else { $order=strtolower($order)=="asc"?"ASC":"DESC"; $this->sort_by[]=array("name"=>$field,"direction" => $order); } } } /*! sets filtering rule @param field name of column @param value value for filtering @param operation operation for filtering, optional , LIKE by default */ public function set_filter($field,$value=false,$operation=false){ if ($value === false) array_push($this->filters,$field); else array_push($this->filters,array("name"=>$field,"value"=>$value,"operation"=>$operation)); } /*! sets list of used fields @param value list of used fields */ public function set_fieldset($value){ $this->fieldset=$value; } /*! sets name of source table @param value name of source table */ public function set_source($value){ if (is_string($value)) $value = trim($value); $this->source = $value; } /*! sets data limits @param start start index @param count requested count of data */ public function set_limit($start,$count){ $this->start=$start; $this->count=$count; } /*! sets name of relation id @param value name of relation id field */ public function set_relation($value){ $this->relation=$value; } /*! parse incoming sql, to fill other properties @param sql incoming sql string */ public function parse_sql($sql, $as_is = false){ if ($as_is){ $this->fieldset = $sql; return; } $sql= preg_replace("/[ \n\t]+limit[\n\t ,0-9]*$/i","",$sql); $data = preg_split("/[ \n\t]+\\_from\\_/i",$sql,2); if (count($data)!=2) $data = preg_split("/[ \n\t]+from/i",$sql,2); $this->fieldset = preg_replace("/^[\s]*select/i","",$data[0],1); //Ignore next type of calls //direct call to stored procedure without FROM if ((count($data) == 1) || //UNION select preg_match("#[ \n\r\t]union[ \n\t\r]#i", $sql)){ $this->fieldset = $sql; return; } $table_data = preg_split("/[ \n\t]+where/i",$data[1],2); /* if sql code contains group_by we will place all sql query in the FROM it will not allow to use any filtering against the query still it is better than just generate incorrect sql commands for any group by query */ if (sizeof($table_data)>1 && !preg_match("#.*group by.*#i",$table_data[1])){ //where construction exists $this->set_source($table_data[0]); $where_data = preg_split("/[ \n\t]+order[ ]+by/i",$table_data[1],2); $this->filters[]=$where_data[0]; if (sizeof($where_data)==1) return; //end of line detected $data=$where_data[1]; } else { $table_data = preg_split("/[ \n\t]+order[ ]+by/i",$data[1],2); $this->set_source($table_data[0]); if (sizeof($table_data)==1) return; //end of line detected $data=$table_data[1]; } if (trim($data)){ //order by construction exists $s_data = preg_split("/\\,/",trim($data)); for ($i=0; $i < count($s_data); $i++) { $data=preg_split("/[ ]+/",trim($s_data[$i]),2); if (sizeof($data)>1) $this->set_sort($data[0],$data[1]); else $this->set_sort($data[0]); } } } } /*! manager of data configuration **/ class DataConfig{ public $id;////!< name of ID field public $relation_id;//!< name or relation ID field public $text;//!< array of text fields public $data;//!< array of all known fields , fields which exists only in this collection will not be included in dataprocessor's operations /*! converts self to the string, for logging purposes **/ public function __toString(){ $str="ID:{$this->id['db_name']}(ID:{$this->id['name']})\n"; $str.="Relation ID:{$this->relation_id['db_name']}({$this->relation_id['name']})\n"; $str.="Data:"; for ($i=0; $itext); $i++) $str.="{$this->text[$i]['db_name']}({$this->text[$i]['name']}),"; $str.="\nExtra:"; for ($i=0; $idata); $i++) $str.="{$this->data[$i]['db_name']}({$this->data[$i]['name']}),"; return $str; } /*! removes un-used fields from configuration @param name name of field , which need to be preserved */ public function minimize($name){ for ($i=0; $i < sizeof($this->text); $i++){ if ($this->text[$i]["db_name"]==$name || $this->text[$i]["name"]==$name){ $this->text[$i]["name"]="value"; $this->data=array($this->text[$i]); $this->text=array($this->text[$i]); return; } } throw new Exception("Incorrect dataset minimization, master field not found."); } public function limit_fields($data){ if (isset($this->full_field_list)) $this->restore_fields(); $this->full_field_list = $this->text; $this->text = array(); for ($i=0; $i < sizeof($this->full_field_list); $i++) { if (array_key_exists($this->full_field_list[$i]["name"],$data)) $this->text[] = $this->full_field_list[$i]; } } public function restore_fields(){ if (isset($this->full_field_list)) $this->text = $this->full_field_list; } /*! initialize inner state by parsing configuration parameters @param id name of id field @param fields name of data field(s) @param extra name of extra field(s) @param relation name of relation field */ public function init($id,$fields,$extra,$relation){ $this->id = $this->parse($id,false); $this->text = $this->parse($fields,true); $this->data = array_merge($this->text,$this->parse($extra,true)); $this->relation_id = $this->parse($relation,false); } /*! parse configuration string @param key key string from configuration @param mode multi names flag @return parsed field name object */ private function parse($key,$mode){ if ($mode){ if (!$key) return array(); $key=explode(",",$key); for ($i=0; $i < sizeof($key); $i++) $key[$i]=$this->parse($key[$i],false); return $key; } $key=explode("(",$key); $data=array("db_name"=>trim($key[0]), "name"=>trim($key[0])); if (sizeof($key)>1) $data["name"]=substr(trim($key[1]),0,-1); return $data; } /*! constructor init public collectons @param proto DataConfig object used as prototype for new one, optional */ public function __construct($proto=false){ if ($proto!==false) $this->copy($proto); else { $this->text=array(); $this->data=array(); $this->id=array("name"=>"dhx_auto_id", "db_name"=>"dhx_auto_id"); $this->relation_id=array("name"=>"", "db_name"=>""); } } /*! copy properties from source object @param proto source object */ public function copy($proto){ $this->id = $proto->id; $this->relation_id = $proto->relation_id; $this->text = $proto->text; $this->data = $proto->data; } /*! returns list of data fields (db_names) @return list of data fields ( ready to be used in SQL query ) */ public function db_names_list($db){ $out=array(); if ($this->id["db_name"]) array_push($out,$db->escape_name($this->id["db_name"])); if ($this->relation_id["db_name"]) array_push($out,$db->escape_name($this->relation_id["db_name"])); for ($i=0; $i < sizeof($this->data); $i++){ if ($this->data[$i]["db_name"]!=$this->data[$i]["name"]) $out[]=$db->escape_name($this->data[$i]["db_name"])." as ".$this->data[$i]["name"]; else $out[]=$db->escape_name($this->data[$i]["db_name"]); } return $out; } /*! add field to dataset config ($text collection) added field will be used in all auto-generated queries @param name name of field @param aliase aliase of field, optional */ public function add_field($name,$aliase=false){ if ($aliase===false) $aliase=$name; //adding to list of data-active fields if ($this->id["db_name"]==$name || $this->relation_id["db_name"] == $name){ LogMaster::log("Field name already used as ID, be sure that it is really necessary."); } if ($this->is_field($name,$this->text)!=-1) throw new Exception('Data field already registered: '.$name); array_push($this->text,array("db_name"=>$name,"name"=>$aliase)); //adding to list of all fields as well if ($this->is_field($name,$this->data)==-1) array_push($this->data,array("db_name"=>$name,"name"=>$aliase)); } /*! remove field from dataset config ($text collection) removed field will be excluded from all auto-generated queries @param name name of field, or aliase of field */ public function remove_field($name){ $ind = $this->is_field($name); if ($ind==-1) throw new Exception('There was no such data field registered as: '.$name); array_splice($this->text,$ind,1); //we not deleting field from $data collection, so it will not be included in data operation, but its data still available } /*! remove field from dataset config ($text and $data collections) removed field will be excluded from all auto-generated queries @param name name of field, or aliase of field */ public function remove_field_full($name){ $ind = $this->is_field($name); if ($ind==-1) throw new Exception('There was no such data field registered as: '.$name); array_splice($this->text,$ind,1); $ind = $this->is_field($name, $this->data); if ($ind==-1) throw new Exception('There was no such data field registered as: '.$name); array_splice($this->data,$ind,1); } /*! check if field is a part of dataset @param name name of field @param collection collection, against which check will be done, $text collection by default @return returns true if field already a part of dataset, otherwise returns true */ public function is_field($name,$collection = false){ if (!$collection) $collection=$this->text; for ($i=0; $iconfig=$config; $this->connection=$connection; } /*! insert record in storage @param data DataAction object @param source DataRequestConfig object */ abstract function insert($data,$source); /*! delete record from storage @param data DataAction object @param source DataRequestConfig object */ abstract function delete($data,$source); /*! update record in storage @param data DataAction object @param source DataRequestConfig object */ abstract function update($data,$source); /*! select record from storage @param source DataRequestConfig object */ abstract function select($source); /*! get size of storage @param source DataRequestConfig object */ abstract function get_size($source); /*! get all variations of field in storage @param name name of field @param source DataRequestConfig object */ abstract function get_variants($name,$source); /*! checks if there is a custom sql string for specified db operation @param name name of DB operation @param data hash of data @return sql string */ public function get_sql($name,$data){ return ""; //custom sql not supported by default } /*! begins DB transaction */ public function begin_transaction(){ throw new Exception("Data wrapper not supports transactions."); } /*! commits DB transaction */ public function commit_transaction(){ throw new Exception("Data wrapper not supports transactions."); } /*! rollbacks DB transaction */ public function rollback_transaction(){ throw new Exception("Data wrapper not supports transactions."); } } /*! Common database abstraction class Class provides base set of methods to access and change data in DB, class used as a base for DB-specific wrappers **/ abstract class DBDataWrapper extends DataWrapper{ private $transaction = false; //!< type of transaction private $sequence=false;//!< sequence name private $sqls = array();//!< predefined sql actions /*! assign named sql query @param name name of sql query @param data sql query text */ public function attach($name,$data){ $name=strtolower($name); $this->sqls[$name]=$data; } /*! replace vars in sql string with actual values @param matches array of field name matches @return value for the var name */ public function get_sql_callback($matches){ return $this->escape($this->temp->get_value($matches[1])); } public function get_sql($name,$data){ $name=strtolower($name); if (!array_key_exists($name,$this->sqls)) return ""; $str = $this->sqls[$name]; $this->temp = $data; //dirty $str = preg_replace_callback('|\{([^}]+)\}|',array($this,"get_sql_callback"),$str); unset ($this->temp); //dirty return $str; } public function new_record_order($action, $source){ $order = $source->get_order(); if ($order){ $table = $source->get_source(); $id = $this->config->id["db_name"]; $idvalue = $action->get_new_id(); $max = $this->queryOne("SELECT MAX($order) as dhx_maxvalue FROM $table"); $dhx_maxvalue = $max["dhx_maxvalue"] + 1; $this->query("UPDATE $table SET $order = $dhx_maxvalue WHERE $id = $idvalue"); } } public function order($data, $source){ //id of moved item $id1 = $this->escape($data->get_value("id")); //id of target item $target = $data->get_value("target"); if (strpos($target, "next:") !== false){ $dropnext = true; $id2 = str_replace("next:", "", $target); } else { $id2 = $target; } $id2 = $this->escape($id2); //for tree like components we need to limit out queries to the affected branch only $relation_select = $relation_update = $relation_sql_out = $relation_sql = ""; if ($this->config->relation_id["name"]){ $relation = $data->get_value($this->config->relation_id["name"]); if ($relation !== false && $relation !== ""){ $relation_sql = " ".$this->config->relation_id["db_name"]." = '".$this->escape($relation)."' AND "; $relation_select = $this->config->relation_id["db_name"]." as dhx_parent, "; $relation_update = " ".$this->config->relation_id["db_name"]." = '".$this->escape($relation)."', "; } } $name = $source->get_order(); $table = $source->get_source(); $idkey = $this->config->id["db_name"]; $source = $this->queryOne("select $relation_select $name as dhx_index from $table where $idkey = '$id1'"); $source_index = $source["dhx_index"] ? $source["dhx_index"] : 0; if ($relation_sql) $relation_sql_out = " ".$this->config->relation_id["db_name"]." = '".$this->escape($source["dhx_parent"])."' AND "; $this->query("update $table set $name = $name - 1 where $relation_sql_out $name >= $source_index"); if ($id2 !== ""){ $target = $this->queryOne("select $name as dhx_index from $table where $idkey = '$id2'"); $target_index = $target["dhx_index"]; if (!$target_index) $target_index = 0; if ($dropnext) $target_index += 1; $this->query("update $table set $name = $name + 1 where $relation_sql $name >= $target_index"); } else { $target = $this->queryOne("select max($name) as dhx_index from $table"); $target_index = ($target["dhx_index"] ? $target["dhx_index"] : 0)+1; } $this->query("update $table set $relation_update $name = $target_index where $idkey = '$id1'"); } public function insert($data,$source){ $sql=$this->insert_query($data,$source); $this->query($sql); $data->success($this->get_new_id()); } public function delete($data,$source){ $sql=$this->delete_query($data,$source); $this->query($sql); $data->success(); } public function update($data,$source){ $sql=$this->update_query($data,$source); $this->query($sql); $data->success(); } public function select($source){ $select=$source->get_fieldset(); if (!$select){ $select=$this->config->db_names_list($this); $select = implode(",",$select); } $where=$this->build_where($source->get_filters(),$source->get_relation()); $sort=$this->build_order($source->get_sort_by()); return $this->query($this->select_query($select,$source->get_source(),$where,$sort,$source->get_start(),$source->get_count())); } public function queryOne($sql){ $res = $this->query($sql); if ($res) return $this->get_next($res); return false; } public function get_size($source){ $count = new DataRequestConfig($source); $count->set_fieldset("COUNT(*) as DHX_COUNT "); $count->set_sort(null); $count->set_limit(0,0); $res=$this->select($count); $data=$this->get_next($res); if (array_key_exists("DHX_COUNT",$data)) return $data["DHX_COUNT"]; else return $data["dhx_count"]; //postgresql } public function get_variants($name,$source){ $count = new DataRequestConfig($source); $count->set_fieldset("DISTINCT ".$this->escape_name($name)." as value"); $sort = new SortInterface($source); $count->set_sort(null); for ($i = 0; $i < count($sort->rules); $i++) { if ($sort->rules[$i]['name'] == $name) $count->set_sort($sort->rules[$i]['name'], $sort->rules[$i]['direction']); } $count->set_limit(0,0); return $this->select($count); } public function sequence($sec){ $this->sequence=$sec; } /*! create an sql string for filtering rules @param rules set of filtering rules @param relation name of relation id field @return sql string with filtering rules */ protected function build_where($rules,$relation=false){ $sql=array(); for ($i=0; $i < sizeof($rules); $i++) if (is_string($rules[$i])) array_push($sql,"(".$rules[$i].")"); else { $filtervalue = $rules[$i]["value"]; $filteroperation = $rules[$i]["operation"]; if ($filtervalue!=""){ if (!$filteroperation) array_push($sql,$this->escape_name($rules[$i]["name"])." LIKE '%".$this->escape($filtervalue)."%'"); else { if ($filteroperation != "IN") $filtervalue = "'".$this->escape($filtervalue)."'"; array_push($sql,$this->escape_name($rules[$i]["name"])." ".$filteroperation." ".$filtervalue); } } } if ($relation !== false && $relation !== ""){ $relsql = $this->escape_name($this->config->relation_id["db_name"])." = '".$this->escape($relation)."'"; if ($relation == "0") $relsql = "( ".$relsql." OR ".$this->escape_name($this->config->relation_id["db_name"])." IS NULL )"; array_push($sql,$relsql); } return implode(" AND ",$sql); } /*! convert sorting rules to sql string @param by set of sorting rules @return sql string for set of sorting rules */ protected function build_order($by){ if (!sizeof($by)) return ""; $out = array(); for ($i=0; $i < sizeof($by); $i++) if (is_string($by[$i])) $out[] = $by[$i]; else if ($by[$i]["name"]) $out[]=$this->escape_name($by[$i]["name"])." ".$by[$i]["direction"]; return implode(",",$out); } /*! generates sql code for select operation @param select list of fields in select @param from table name @param where list of filtering rules @param sort list of sorting rules @param start start index of fetching @param count count of records to fetch @return sql string for select operation */ protected function select_query($select,$from,$where,$sort,$start,$count){ if (!$from) return $select; $sql="SELECT ".$select." FROM ".$from; if ($where) $sql.=" WHERE ".$where; if ($sort) $sql.=" ORDER BY ".$sort; if ($start || $count) $sql.=" LIMIT ".$start.",".$count; return $sql; } /*! generates update sql @param data DataAction object @param request DataRequestConfig object @return sql string, which updates record with provided data */ protected function update_query($data,$request){ $sql="UPDATE ".$request->get_source()." SET "; $temp=array(); for ($i=0; $i < sizeof($this->config->text); $i++) { $step=$this->config->text[$i]; if ($data->get_value($step["name"])===Null) $step_value ="Null"; else $step_value = "'".$this->escape($data->get_value($step["name"]))."'"; $temp[$i]= $this->escape_name($step["db_name"])."=". $step_value; } if ($relation = $this->config->relation_id["db_name"]){ $temp[]= $this->escape_name($relation)."='".$this->escape($data->get_value($relation))."'"; } $sql.=implode(",",$temp)." WHERE ".$this->escape_name($this->config->id["db_name"])."='".$this->escape($data->get_id())."'"; //if we have limited set - set constraints $where=$this->build_where($request->get_filters()); if ($where) $sql.=" AND (".$where.")"; return $sql; } /*! generates delete sql @param data DataAction object @param request DataRequestConfig object @return sql string, which delete record */ protected function delete_query($data,$request){ $sql="DELETE FROM ".$request->get_source(); $sql.=" WHERE ".$this->escape_name($this->config->id["db_name"])."='".$this->escape($data->get_id())."'"; //if we have limited set - set constraints $where=$this->build_where($request->get_filters()); if ($where) $sql.=" AND (".$where.")"; return $sql; } /*! generates insert sql @param data DataAction object @param request DataRequestConfig object @return sql string, which inserts new record with provided data */ protected function insert_query($data,$request){ $temp_n=array(); $temp_v=array(); foreach($this->config->text as $k => $v){ $temp_n[$k]=$this->escape_name($v["db_name"]); if ($data->get_value($v["name"])===Null) $temp_v[$k]="Null"; else $temp_v[$k]="'".$this->escape($data->get_value($v["name"]))."'"; } if ($relation = $this->config->relation_id["db_name"]){ $temp_n[]=$this->escape_name($relation); $temp_v[]="'".$this->escape($data->get_value($relation))."'"; } if ($this->sequence){ $temp_n[]=$this->escape_name($this->config->id["db_name"]); $temp_v[]=$this->sequence; } $sql="INSERT INTO ".$request->get_source()."(".implode(",",$temp_n).") VALUES (".implode(",",$temp_v).")"; return $sql; } /*! sets the transaction mode, used by dataprocessor @param mode mode name */ public function set_transaction_mode($mode){ if ($mode!="none" && $mode!="global" && $mode!="record") throw new Exception("Unknown transaction mode"); $this->transaction=$mode; } /*! returns true if global transaction mode was specified @return true if global transaction mode was specified */ public function is_global_transaction(){ return $this->transaction == "global"; } /*! returns true if record transaction mode was specified @return true if record transaction mode was specified */ public function is_record_transaction(){ return $this->transaction == "record"; } public function begin_transaction(){ $this->query("BEGIN"); } public function commit_transaction(){ $this->query("COMMIT"); } public function rollback_transaction(){ $this->query("ROLLBACK"); } /*! exec sql string @param sql sql string @return sql result set */ abstract public function query($sql); /*! returns next record from result set @param res sql result set @return hash of data */ abstract public function get_next($res); /*! returns new id value, for newly inserted row @return new id value, for newly inserted row */ abstract public function get_new_id(); /*! escape data to prevent sql injections @param data unescaped data @return escaped data */ abstract public function escape($data); /*! escape field name to prevent sql reserved words conflict @param data unescaped data @return escaped data */ public function escape_name($data){ return $data; } /*! get list of tables in the database @return array of table names */ public function tables_list() { throw new Exception("Not implemented"); } /*! returns list of fields for the table in question @param table name of table in question @return array of field names */ public function fields_list($table) { throw new Exception("Not implemented"); } } class ArrayDBDataWrapper extends DBDataWrapper{ public function get_next($res){ if ($res->index < sizeof($res->data)) return $res->data[$res->index++]; } public function select($sql){ if ($this->config->relation_id["db_name"] == "") { if ($sql->get_relation() == "0" || $sql->get_relation() == "") { return new ArrayQueryWrapper($this->connection); } else { return new ArrayQueryWrapper(array()); } } $relation_id = $this->config->relation_id["db_name"]; $result = array(); for ($i = 0; $i < count($this->connection); $i++) { $item = $this->connection[$i]; if (!isset($item[$relation_id])) continue; if ($item[$relation_id] == $sql->get_relation()) $result[] = $item; } return new ArrayQueryWrapper($result); } public function query($sql){ throw new Exception("Not implemented"); } public function escape($value){ throw new Exception("Not implemented"); } public function get_new_id(){ throw new Exception("Not implemented"); } } class ArrayQueryWrapper{ public function __construct($data){ $this->data = $data; $this->index = 0; } } include_once(__DIR__."/db_pdo.php"); ?>