diff options
Diffstat (limited to 'codebase/Dhtmlx/Connector')
52 files changed, 5687 insertions, 0 deletions
diff --git a/codebase/Dhtmlx/Connector/Connector.php b/codebase/Dhtmlx/Connector/Connector.php new file mode 100644 index 0000000..de870f3 --- /dev/null +++ b/codebase/Dhtmlx/Connector/Connector.php @@ -0,0 +1,646 @@ +<?php + +namespace DHTMLX\Connector; + +use DHTMLX\Connector\Tools\EventMaster; +use DHTMLX\Connector\Tools\AccessMaster; +use DHTMLX\Connector\Tools\LogMaster; +use DHTMLX\Connector\Output\RenderStrategy; +use DHTMLX\Connector\Output\OutputWriter; +use DHTMLX\Connector\DataStorage\DataConfig; +use DHTMLX\Connector\DataStorage\DataAction; +use DHTMLX\Connector\DataStorage\DataRequestConfig; +use DHTMLX\Connector\DataStorage\MySQLDBDataWrapper; +use DHTMLX\Connector\DataStorage\ArrayDBDataWrapper; +use DHTMLX\Connector\DataStorage\PHPYii2DBDataWrapper; +use DHTMLX\Connector\Data\CommonDataProcessor; +use DHTMLX\Connector\XSSFilter\ConnectorSecurity; +use DHTMLX\Connector\Event\SortInterface; +use DHTMLX\Connector\Event\FilterInterface; + +class Connector { + protected $config;//DataConfig instance + protected $request;//DataRequestConfig instance + protected $names;//!< hash of names for used classes + protected $encoding="utf-8";//!< assigned encoding (UTF-8 by default) + protected $editing=false;//!< flag of edit mode ( response for dataprocessor ) + + public static $filter_var="dhx_filter"; + public static $sort_var="dhx_sort"; + public static $kids_var="dhx_kids"; + + public $model=false; + + private $updating=false;//!< flag of update mode ( response for data-update ) + private $db; //!< db connection resource + protected $dload;//!< flag of dyn. loading mode + public $access; //!< AccessMaster instance + protected $data_separator = "\n"; + + public $sql; //DataWrapper instance + public $event; //EventMaster instance + public $limit=false; + + private $id_seed=0; //!< default value, used to generate auto-IDs + protected $live_update = false; // actions table name for autoupdating + protected $extra_output="";//!< extra info which need to be sent to client side + protected $options=array();//!< hash of OptionsConnector + protected $as_string = false; // render() returns string, don't send result in response + protected $simple = false; // render only data without any other info + protected $filters; + protected $sorts; + protected $mix; + protected $order = false; + + /*! constructor + + Here initilization of all Masters occurs, execution timer initialized + @param db + db connection resource + @param type + string , which hold type of database ( MySQL or Postgre ), optional, instead of short DB name, full name of DataWrapper-based class can be provided + @param item_type + name of class, which will be used for item rendering, optional, DataItem will be used by default + @param data_type + name of class which will be used for dataprocessor calls handling, optional, DataProcessor class will be used by default. + */ + public function __construct($db,$type=false, $item_type=false, $data_type=false, $render_type = false){ + $this->exec_time=microtime(true); + $dsnamespace = "DHTMLX\\Connector\\DataStorage\\"; + if (!$type) $type=$dsnamespace."MySQLDBDataWrapper"; + + //die(var_dump(class_exists("DataConfig",true), new MySQLDBDataWrapper)); + if (class_exists($dsnamespace.$type."DBDataWrapper",true)) + $type.="DBDataWrapper"; + if (!$item_type) $item_type="DHTMLX\\Connector\\Data\\DataItem"; + if (!$data_type) $data_type="DHTMLX\\Connector\\Data\\DataProcessor"; + if (!$render_type) $render_type="DHTMLX\\Connector\\Output\\RenderStrategy"; + + $this->names=array( + "db_class"=>$type, + "item_class"=>$item_type, + "data_class"=>$data_type, + "render_class"=>$render_type + ); + $this->attributes = array(); + $this->filters = array(); + $this->sorts = array(); + $this->mix = array(); + + $this->config = new DataConfig(); + $this->request = new DataRequestConfig(); + $this->event = new EventMaster(); + $this->access = new AccessMaster(); + +// if (!class_exists($this->names["db_class"],false)) +// throw new \Exception("DB class not found: ".$this->names["db_class"]); + $dbClass = $dsnamespace.$this->names["db_class"]; + $this->sql = new $dbClass($db,$this->config); + $this->render = new $this->names["render_class"]($this); + + $this->db=$db;//saved for options connectors, if any + + EventMaster::trigger_static("connectorCreate",$this); + } + + /*! return db connection resource + nested class may neeed to access live connection object + @return + DB connection resource + */ + protected function get_connection(){ + return $this->db; + } + + public function get_config(){ + return new DataConfig($this->config); + } + + public function get_request(){ + return new DataRequestConfig($this->request); + } + + + protected $attributes; + public function add_top_attribute($name, $string){ + $this->attributes[$name] = $string; + } + + //model is a class, which will be used for all data operations + //we expect that it has next methods get, update, insert, delete + //if method was not defined - we will use default logic + public function useModel($model){ + $this->model = $model; + } + + + /*! config connector based on table + + @param table + name of table in DB + @param id + name of id field + @param fields + list of fields names + @param extra + list of extra fields, optional, such fields will not be included in data rendering, but will be accessible in all inner events + @param relation_id + name of field used to define relations for hierarchical data organization, optional + */ + public function render_table($table,$id="",$fields=false,$extra=false,$relation_id=false){ + $this->configure($table,$id,$fields,$extra,$relation_id); + return $this->render(); + } + public function configure($table,$id="",$fields=false,$extra=false,$relation_id=false){ + if ($fields === false){ + //auto-config + $info = $this->sql->fields_list($table); + $fields = implode(",",$info["fields"]); + if ($info["key"]) + $id = $info["key"]; + } + $this->config->init($id,$fields,$extra,$relation_id); + if (strpos(trim($table), " ")!==false) + $this->request->parse_sql($table); + else + $this->request->set_source($table); + } + + public function uuid(){ + return time()."x".$this->id_seed++; + } + + /*! config connector based on sql + + @param sql + sql query used as base of configuration + @param id + name of id field + @param fields + list of fields names + @param extra + list of extra fields, optional, such fields will not be included in data rendering, but will be accessible in all inner events + @param relation_id + name of field used to define relations for hierarchical data organization, optional + */ + public function render_sql($sql,$id,$fields,$extra=false,$relation_id=false){ + $this->config->init($id,$fields,$extra,$relation_id); + $this->request->parse_sql($sql); + return $this->render(); + } + + public function render_array($data, $id, $fields, $extra=false, $relation_id=false){ + $this->configure("-",$id,$fields,$extra,$relation_id); + $this->sql = new ArrayDBDataWrapper($data, $this->config); + return $this->render(); + } + + public function render_complex_sql($sql,$id,$fields,$extra=false,$relation_id=false){ + $this->config->init($id,$fields,$extra,$relation_id); + $this->request->parse_sql($sql, true); + return $this->render(); + } + + /*! render already configured connector + + @param config + configuration of data + @param request + configuraton of request + */ + public function render_connector($config,$request){ + $this->config->copy($config); + $this->request->copy($request); + return $this->render(); + } + + /*! render self + process commands, output requested data as XML + */ + public function render(){ + $this->event->trigger("onInit", $this); + EventMaster::trigger_static("connectorInit",$this); + + if (!$this->as_string) + $this->parse_request(); + $this->set_relation(); + + if ($this->live_update !== false && $this->updating!==false) { + $this->live_update->get_updates(); + } else { + if ($this->editing){ + //die($this->names["data_class"]); + $dp = new $this->names["data_class"]($this,$this->config,$this->request); + $dp->process($this->config,$this->request); + } else { + if (!$this->access->check("read")){ + LogMaster::log("Access control: read operation blocked"); + echo "Access denied"; + die(); + } + $wrap = new SortInterface($this->request); + $this->apply_sorts($wrap); + $this->event->trigger("beforeSort",$wrap); + $wrap->store(); + + $wrap = new FilterInterface($this->request); + $this->apply_filters($wrap); + $this->event->trigger("beforeFilter",$wrap); + $wrap->store(); + + if ($this->model && method_exists($this->model, "get")){ + $this->sql = new ArrayDBDataWrapper(); + $result = new ArrayQueryWrapper(call_user_func(array($this->model, "get"), $this->request)); + $out = $this->output_as_xml($result); + } else { + $out = $this->output_as_xml($this->get_resource()); + + if ($out !== null) return $out; + } + + } + } + $this->end_run(); + } + + + /*! empty call which used for tree-logic + * to prevent code duplicating + */ + protected function set_relation() {} + + /*! gets resource for rendering + */ + protected function get_resource() { + //die(var_dump($this->sql->select($this->request))); + return $this->sql->select($this->request); + } + + + /*! prevent SQL injection through column names + replace dangerous chars in field names + @param str + incoming field name + @return + safe field name + */ + protected function safe_field_name($str){ + return strtok($str, " \n\t;',"); + } + + /*! limit max count of records + connector will ignore any records after outputing max count + @param limit + max count of records + @return + none + */ + public function set_limit($limit){ + $this->limit = $limit; + } + + + public function limit($start, $count, $sort_field=false, $sort_dir=false){ + $this->request->set_limit($start, $count); + if ($sort_field) + $this->request->set_sort($sort_field, $sort_dir); + } + + protected function parse_request_mode(){ + //detect edit mode + if (isset($_GET["editing"])){ + $this->editing=true; + } else if (isset($_POST["ids"])){ + $this->editing=true; + LogMaster::log('While there is no edit mode mark, POST parameters similar to edit mode detected. \n Switching to edit mode ( to disable behavior remove POST[ids]'); + } else if (isset($_GET['dhx_version'])){ + $this->updating = true; + } + } + + /*! parse incoming request, detects commands and modes + */ + protected function parse_request(){ + //set default dyn. loading params, can be reset in child classes + if ($this->dload) + $this->request->set_limit(0,$this->dload); + else if ($this->limit) + $this->request->set_limit(0,$this->limit); + + if (isset($_GET["posStart"]) && isset($_GET["count"])) { + $this->request->set_limit($_GET["posStart"],$_GET["count"]); + } + + $this->parse_request_mode(); + + if ($this->live_update && ($this->updating || $this->editing)){ + $this->request->set_version($_GET["dhx_version"]); + $this->request->set_user($_GET["dhx_user"]); + } + + if (isset($_GET[Connector::$sort_var])) + foreach($_GET[Connector::$sort_var] as $k => $v){ + $k = $this->safe_field_name($k); + $this->request->set_sort($this->resolve_parameter($k),$v); + } + + if (isset($_GET[Connector::$filter_var])) + foreach($_GET[Connector::$filter_var] as $k => $v){ + $k = $this->safe_field_name($k); + if ($v !== "") + $this->request->set_filter($this->resolve_parameter($k),$v); + } + + $this->check_csrf(); + } + + protected function check_csrf(){ + $key = ConnectorSecurity::checkCSRF($this->editing); + if ($key !== "") + $this->add_top_attribute(ConnectorSecurity::$security_var, $key); + } + + /*! convert incoming request name to the actual DB name + @param name + incoming parameter name + @return + name of related DB field + */ + protected function resolve_parameter($name){ + return $name; + } + + + /*! replace xml unsafe characters + + @param string + string to be escaped + @return + escaped string + */ + protected function xmlentities($string) { + return str_replace( array( '&', '"', "'", '<', '>', '’' ), array( '&' , '"', ''' , '<' , '>', ''' ), $string); + } + + public function getRecord($id){ + LogMaster::log("Retreiving data for record: ".$id); + $source = new DataRequestConfig($this->request); + $source->set_filter($this->config->id["name"],$id, "="); + + $res = $this->sql->select($source); + + $temp = $this->data_separator; + $this->data_separator=""; + $output = $this->render_set($res); + $this->data_separato=$temp; + + return $output; + } + + /*! render from DB resultset + @param res + DB resultset + process commands, output requested data as XML + */ + protected function render_set($res){ + return $this->render->render_set($res, $this->names["item_class"], $this->dload, $this->data_separator, $this->config, $this->mix); + } + + /*! output fetched data as XML + @param res + DB resultset + */ + protected function output_as_xml($res){ + $result = $this->render_set($res); + if ($this->simple) return $result; + + $start="<?xml version='1.0' encoding='".$this->encoding."' ?>".$this->xml_start(); + $end=$result.$this->xml_end(); + + if ($this->as_string) return $start.$end; + + $out = new OutputWriter($start, $end); + $this->event->trigger("beforeOutput", $this, $out); + $out->output("", true, $this->encoding); + } + + + /*! end processing + stop execution timer, kill the process + */ + protected function end_run(){ + $time=microtime(true)-$this->exec_time; + LogMaster::log("Done in {$time}s"); + flush(); + die(); + } + + /*! set xml encoding + + methods sets only attribute in XML, no real encoding conversion occurs + @param encoding + value which will be used as XML encoding + */ + public function set_encoding($encoding){ + $this->encoding=$encoding; + } + + /*! enable or disable dynamic loading mode + + @param count + count of rows loaded from server, actual only for grid-connector, can be skiped in other cases. + If value is a false or 0 - dyn. loading will be disabled + */ + public function dynamic_loading($count){ + $this->dload=$count; + } + + /*! enable or disable data reordering + + @param name + name of field, which will be used for order storing, optional + by default 'sortorder' field will be used + */ + public function enable_order($name = true){ + if ($name === true) + $name = "sortorder"; + + $this->sort($name); + $this->access->allow("order"); + $this->request->set_order($name); + $this->order = $name; + } + + /*! enable logging + + @param path + path to the log file. If set as false or empty strig - logging will be disabled + @param client_log + enable output of log data to the client side + */ + public function enable_log($path=true,$client_log=false){ + LogMaster::enable_log($path,$client_log); + } + + /*! provides infor about current processing mode + @return + true if processing dataprocessor command, false otherwise + */ + public function is_select_mode(){ + $this->parse_request_mode(); + return !$this->editing; + } + + public function is_first_call(){ + $this->parse_request_mode(); + return !($this->editing || $this->updating || $this->request->get_start() || isset($_GET['dhx_no_header'])); + + } + + /*! renders self as xml, starting part + */ + protected function xml_start(){ + $attributes = ""; + + if ($this->dload){ + //info for dyn. loadin + if ($pos=$this->request->get_start()) + $attributes .= " pos='".$pos."'"; + else + $attributes .= " total_count='".$this->sql->get_size($this->request)."'"; + } + foreach($this->attributes as $k=>$v) + $attributes .= " ".$k."='".$v."'"; + + return "<data".$attributes.">"; + } + /*! renders self as xml, ending part + */ + protected function xml_end(){ + $this->fill_collections(); + if (isset($this->extra_output)) + return $this->extra_output."</data>"; + else + return "</data>"; + } + + protected function fill_collections($list=""){ + foreach ($this->options as $k=>$v) { + $name = $k; + $this->extra_output.="<coll_options for='{$name}'>"; + if (!is_string($this->options[$name])) + $this->extra_output.=$this->options[$name]->render(); + else + $this->extra_output.=$this->options[$name]; + $this->extra_output.="</coll_options>"; + } + } + + /*! assign options collection to the column + + @param name + name of the column + @param options + array or connector object + */ + public function set_options($name,$options){ + if (is_array($options)){ + $str=""; + foreach($options as $k => $v) + $str.="<item value='".$this->xmlentities($k)."' label='".$this->xmlentities($v)."' />"; + $options=$str; + } + $this->options[$name]=$options; + } + + + public function insert($data) { + $action = new DataAction('inserted', false, $data); + $request = new DataRequestConfig(); + $request->set_source($this->request->get_source()); + + $this->config->limit_fields($data); + $this->sql->insert($action,$request); + $this->config->restore_fields($data); + + return $action->get_new_id(); + } + + public function delete($id) { + $action = new DataAction('deleted', $id, array()); + $request = new DataRequestConfig(); + $request->set_source($this->request->get_source()); + + $this->sql->delete($action,$request); + return $action->get_status(); + } + + public function update($data) { + $action = new DataAction('updated', $data[$this->config->id["name"]], $data); + $request = new DataRequestConfig(); + $request->set_source($this->request->get_source()); + + $this->config->limit_fields($data); + $this->sql->update($action,$request); + $this->config->restore_fields($data); + + return $action->get_status(); + } + + /*! sets actions_table for Optimistic concurrency control mode and start it + @param table_name + name of database table which will used for saving actions + @param url + url used for update notifications + */ + public function enable_live_update($table, $url=false){ + $this->live_update = new DataUpdate($this->sql, $this->config, $this->request, $table,$url); + $this->live_update->set_event($this->event,$this->names["item_class"]); + $this->event->attach("beforeOutput", Array($this->live_update, "version_output")); + $this->event->attach("beforeFiltering", Array($this->live_update, "get_updates")); + $this->event->attach("beforeProcessing", Array($this->live_update, "check_collision")); + $this->event->attach("afterProcessing", Array($this->live_update, "log_operations")); + } + + /*! render() returns result as string or send to response + */ + public function asString($as_string) { + $this->as_string = $as_string; + } + + public function simple_render() { + $this->simple = true; + return $this->render(); + } + + public function filter($name, $value = false, $operation = '=') { + $this->filters[] = array('name' => $name, 'value' => $value, 'operation' => $operation); + } + + public function clear_filter() { + $this->filters = array(); + $this->request->set_filters(array()); + } + + protected function apply_filters($wrap) { + for ($i = 0; $i < count($this->filters); $i++) { + $f = $this->filters[$i]; + $wrap->add($f['name'], $f['value'], $f['operation']); + } + } + + public function sort($name, $direction = false) { + $this->sorts[] = array('name' => $name, 'direction' => $direction); + } + + protected function apply_sorts($wrap) { + for ($i = 0; $i < count($this->sorts); $i++) { + $s = $this->sorts[$i]; + $wrap->add($s['name'], $s['direction']); + } + } + + public function mix($name, $value, $filter=false) { + $this->mix[] = Array('name'=>$name, 'value'=>$value, 'filter'=>$filter); + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/ConvertService.php b/codebase/Dhtmlx/Connector/ConvertService.php new file mode 100644 index 0000000..9fa3261 --- /dev/null +++ b/codebase/Dhtmlx/Connector/ConvertService.php @@ -0,0 +1,71 @@ +<?php + +namespace DHTMLX\Connector; + +use DHTMLX\Connector\Tools\EventMaster; +/* + @author dhtmlx.com + @license GPL, see license.txt +*/ +class ConvertService{ + private $url; + private $type; + private $name; + private $inline; + + public function __construct($url){ + $this->url = $url; + $this->pdf(); + EventMaster::attach_static("connectorInit",array($this, "handle")); + } + public function pdf($name = "data.pdf", $inline = false){ + $this->type = "pdf"; + $this->name = $name; + $this->inline = $inline; + } + public function excel($name = "data.xls", $inline = false){ + $this->type = "excel"; + $this->name = $name; + $this->inline = $inline; + } + public function handle($conn){ + $conn->event->attach("beforeOutput",array($this,"convert")); + } + private function as_file($size, $name, $inline){ + header('Content-Type: application/force-download'); + header('Content-Type: application/octet-stream'); + header('Content-Type: application/download'); + header('Content-Transfer-Encoding: binary'); + + header('Content-Length: '.$size); + if ($inline) + header('Content-Disposition: inline; filename="'.$name.'";'); + else + header('Content-Disposition: attachment; filename="'.basename($name).'";'); + } + public function convert($conn, $out){ + + $str_out = str_replace("<rows>","<rows profile='color'>", $out); + $str_out = str_replace("<head>","<head><columns>", $str_out); + $str_out = str_replace("</head>","</columns></head>", $str_out); + + if ($this->type == "pdf") + header("Content-type: application/pdf"); + else + header("Content-type: application/ms-excel"); + + $handle = curl_init($this->url); + curl_setopt($handle, CURLOPT_POST, true); + curl_setopt($handle, CURLOPT_HEADER, false); + curl_setopt($handle, CURLOPT_RETURNTRANSFER, true); + curl_setopt($handle, CURLOPT_POSTFIELDS, "grid_xml=".urlencode($str_out)); + + + $out->reset(); + $out->set_type("pdf"); + $out->add(curl_exec($handle)); + $this->as_file(strlen((string)$out), $this->name, $this->inline); + + curl_close($handle); + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/Data/CommonDataItem.php b/codebase/Dhtmlx/Connector/Data/CommonDataItem.php new file mode 100644 index 0000000..523cd08 --- /dev/null +++ b/codebase/Dhtmlx/Connector/Data/CommonDataItem.php @@ -0,0 +1,27 @@ +<?php + +namespace DHTMLX\Connector\Data; +/*! DataItem class for DataView component +**/ +class CommonDataItem extends DataItem{ + /*! return self as XML string + */ + function to_xml(){ + if ($this->skip) return ""; + return $this->to_xml_start().$this->to_xml_end(); + } + + function to_xml_start(){ + $str="<item id='".$this->get_id()."' "; + for ($i=0; $i < sizeof($this->config->text); $i++){ + $name=$this->config->text[$i]["name"]; + $str.=" ".$name."='".$this->xmlentities($this->data[$name])."'"; + } + + if ($this->userdata !== false) + foreach ($this->userdata as $key => $value) + $str.=" ".$key."='".$this->xmlentities($value)."'"; + + return $str.">"; + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/Data/CommonDataProcessor.php b/codebase/Dhtmlx/Connector/Data/CommonDataProcessor.php new file mode 100644 index 0000000..8f00b94 --- /dev/null +++ b/codebase/Dhtmlx/Connector/Data/CommonDataProcessor.php @@ -0,0 +1,55 @@ +<?php +namespace DHTMLX\Connector\Data; + +use DHTMLX\Connector\XSSFilter\ConnectorSecurity; +use DHTMLX\Connector\Tools\LogMaster; + +class CommonDataProcessor extends DataProcessor{ + protected function get_post_values($ids){ + if (isset($_GET['action'])){ + $data = array(); + if (isset($_POST["id"])){ + $dataset = array(); + foreach($_POST as $key=>$value) + $dataset[$key] = ConnectorSecurity::filter($value); + + $data[$_POST["id"]] = $dataset; + } + else + $data["dummy_id"] = $_POST; + return $data; + } + return parent::get_post_values($ids); + } + + protected function get_ids(){ + if (isset($_GET['action'])){ + if (isset($_POST["id"])) + return array($_POST['id']); + else + return array("dummy_id"); + } + return parent::get_ids(); + } + + protected function get_operation($rid){ + if (isset($_GET['action'])) + return $_GET['action']; + return parent::get_operation($rid); + } + + public function output_as_xml($results){ + if (isset($_GET['action'])){ + LogMaster::log("Edit operation finished",$results); + ob_clean(); + $type = $results[0]->get_status(); + if ($type == "error" || $type == "invalid"){ + echo "false"; + } else if ($type=="insert"){ + echo "true\n".$results[0]->get_new_id(); + } else + echo "true"; + } else + return parent::output_as_xml($results); + } +};
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/Data/DataAction.php b/codebase/Dhtmlx/Connector/Data/DataAction.php new file mode 100644 index 0000000..8e4b5dd --- /dev/null +++ b/codebase/Dhtmlx/Connector/Data/DataAction.php @@ -0,0 +1,276 @@ +<?php +namespace DHTMLX\Connector\Data; + +use DHTMLX\Connector\Tools\LogMaster; +/*! contain all info related to action and controls customizaton +**/ +class DataAction{ + private $status; //!< cuurent status of record + private $id;//!< id of record + private $data;//!< data hash of record + private $userdata;//!< hash of extra data , attached to record + private $nid;//!< new id value , after operation executed + private $output;//!< custom output to client side code + private $attrs;//!< hash of custtom attributes + private $ready;//!< flag of operation's execution + private $addf;//!< array of added fields + private $delf;//!< array of deleted fields + + + /*! constructor + + @param status + current operation status + @param id + record id + @param data + hash of data + */ + function __construct($status,$id,$data){ + $this->status=$status; + $this->id=$id; + $this->data=$data; + $this->nid=$id; + + $this->output=""; + $this->attrs=array(); + $this->ready=false; + + $this->addf=array(); + $this->delf=array(); + } + + + /*! add custom field and value to DB operation + + @param name + name of field which will be added to DB operation + @param value + value which will be used for related field in DB operation + */ + function add_field($name,$value){ + LogMaster::log("adding field: ".$name.", with value: ".$value); + $this->data[$name]=$value; + $this->addf[]=$name; + } + /*! remove field from DB operation + + @param name + name of field which will be removed from DB operation + */ + function remove_field($name){ + LogMaster::log("removing field: ".$name); + $this->delf[]=$name; + } + + /*! sync field configuration with external object + + @param slave + SQLMaster object + @todo + check , if all fields removed then cancel action + */ + function sync_config($slave){ + foreach ($this->addf as $k => $v) + $slave->add_field($v); + foreach ($this->delf as $k => $v) + $slave->remove_field($v); + } + /*! get value of some record's propery + + @param name + name of record's property ( name of db field or alias ) + @return + value of related property + */ + function get_value($name){ + //die(var_dump($this->data["c0"])); + if (!array_key_exists($name,$this->data)){ + LogMaster::log("Incorrect field name used: ".$name); + LogMaster::log("data",$this->data); + return ""; + } + return $this->data[$name]; + } + /*! set value of some record's propery + + @param name + name of record's property ( name of db field or alias ) + @param value + value of related property + */ + function set_value($name,$value){ + LogMaster::log("change value of: ".$name." as: ".$value); + $this->data[$name]=$value; + } + /*! get hash of data properties + + @return + hash of data properties + */ + function get_data(){ + return $this->data; + } + /*! get some extra info attached to record + deprecated, exists just for backward compatibility, you can use set_value instead of it + @param name + name of userdata property + @return + value of related userdata property + */ + function get_userdata_value($name){ + return $this->get_value($name); + } + /*! set some extra info attached to record + deprecated, exists just for backward compatibility, you can use get_value instead of it + @param name + name of userdata property + @param value + value of userdata property + */ + function set_userdata_value($name,$value){ + return $this->set_value($name,$value); + } + /*! get current status of record + + @return + string with status value + */ + function get_status(){ + return $this->status; + } + /*! assign new status to the record + + @param status + new status value + */ + function set_status($status){ + $this->status=$status; + } + /*! set id + @param id + id value + */ + function set_id($id) { + $this->id = $id; + LogMaster::log("Change id: ".$id); + } + /*! set id + @param id + id value + */ + function set_new_id($id) { + $this->nid = $id; + LogMaster::log("Change new id: ".$id); + } + /*! get id of current record + + @return + id of record + */ + function get_id(){ + return $this->id; + } + /*! sets custom response text + + can be accessed through defineAction on client side. Text wrapped in CDATA, so no extra escaping necessary + @param text + custom response text + */ + function set_response_text($text){ + $this->set_response_xml("<![CDATA[".$text."]]>"); + } + /*! sets custom response xml + + can be accessed through defineAction on client side + @param text + string with XML data + */ + function set_response_xml($text){ + $this->output=$text; + } + /*! sets custom response attributes + + can be accessed through defineAction on client side + @param name + name of custom attribute + @param value + value of custom attribute + */ + function set_response_attribute($name,$value){ + $this->attrs[$name]=$value; + } + /*! check if action finished + + @return + true if action finished, false otherwise + */ + function is_ready(){ + return $this->ready; + } + /*! return new id value + + equal to original ID normally, after insert operation - value assigned for new DB record + @return + new id value + */ + function get_new_id(){ + return $this->nid; + } + + /*! set result of operation as error + */ + function error(){ + $this->status="error"; + $this->ready=true; + } + /*! set result of operation as invalid + */ + function invalid(){ + $this->status="invalid"; + $this->ready=true; + } + /*! confirm successful opeation execution + @param id + new id value, optional + */ + function success($id=false){ + if ($id!==false) + $this->nid = $id; + $this->ready=true; + } + /*! convert DataAction to xml format compatible with client side dataProcessor + @return + DataAction operation report as XML string + */ + function to_xml(){ + $str="<action type='{$this->status}' sid='{$this->id}' tid='{$this->nid}' "; + foreach ($this->attrs as $k => $v) { + $str.=$k."='".$this->xmlentities($v)."' "; + } + $str.=">{$this->output}</action>"; + return $str; + } + + /*! replace xml unsafe characters + + @param string + string to be escaped + @return + escaped string + */ + public function xmlentities($string) { + return str_replace( array( '&', '"', "'", '<', '>', '’' ), array( '&' , '"', ''' , '<' , '>', ''' ), $string); + } + + /*! convert self to string ( for logs ) + + @return + DataAction operation report as plain string + */ + function __toString(){ + return "action:{$this->status}; sid:{$this->id}; tid:{$this->nid};"; + } + + +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/Data/DataItem.php b/codebase/Dhtmlx/Connector/Data/DataItem.php new file mode 100644 index 0000000..b980d9c --- /dev/null +++ b/codebase/Dhtmlx/Connector/Data/DataItem.php @@ -0,0 +1,128 @@ +<?php + +namespace DHTMLX\Connector\Data; +/*! base class for component item representation +**/ +class DataItem{ + protected $data; //!< hash of data + protected $config;//!< DataConfig instance + protected $index;//!< index of element + protected $skip;//!< flag , which set if element need to be skiped during rendering + protected $userdata; + + /*! constructor + + @param data + hash of data + @param config + DataConfig object + @param index + index of element + */ + function __construct($data,$config,$index){ + $this->config=$config; + $this->data=$data; + $this->index=$index; + $this->skip=false; + $this->userdata=false; + } + + //set userdata for the item + function set_userdata($name, $value){ + if ($this->userdata === false) + $this->userdata = array(); + + $this->userdata[$name]=$value; + } + /*! get named value + + @param name + name or alias of field + @return + value from field with provided name or alias + */ + public function get_value($name){ + return $this->data[$name]; + } + /*! set named value + + @param name + name or alias of field + @param value + value for field with provided name or alias + */ + public function set_value($name,$value){ + return $this->data[$name]=$value; + } + /*! get id of element + @return + id of element + */ + public function get_id(){ + $id = $this->config->id["name"]; + if (array_key_exists($id,$this->data)) + return $this->data[$id]; + return false; + } + /*! change id of element + + @param value + new id value + */ + public function set_id($value){ + $this->data[$this->config->id["name"]]=$value; + } + /*! get index of element + + @return + index of element + */ + public function get_index(){ + return $this->index; + } + /*! mark element for skiping ( such element will not be rendered ) + */ + public function skip(){ + $this->skip=true; + } + + /*! return self as XML string + */ + public function to_xml(){ + return $this->to_xml_start().$this->to_xml_end(); + } + + /*! replace xml unsafe characters + + @param string + string to be escaped + @return + escaped string + */ + public function xmlentities($string) { + return str_replace( array( '&', '"', "'", '<', '>', '’' ), array( '&' , '"', ''' , '<' , '>', ''' ), $string); + } + + /*! return starting tag for self as XML string + */ + public function to_xml_start(){ + $str="<item"; + for ($i=0; $i < sizeof($this->config->data); $i++){ + $name=$this->config->data[$i]["name"]; + $db_name=$this->config->data[$i]["db_name"]; + $str.=" ".$name."='".$this->xmlentities($this->data[$name])."'"; + } + //output custom data + if ($this->userdata !== false) + foreach ($this->userdata as $key => $value){ + $str.=" ".$key."='".$this->xmlentities($value)."'"; + } + + return $str.">"; + } + /*! return ending tag for XML string + */ + public function to_xml_end(){ + return "</item>"; + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/Data/DataItemUpdate.php b/codebase/Dhtmlx/Connector/Data/DataItemUpdate.php new file mode 100644 index 0000000..ad97be2 --- /dev/null +++ b/codebase/Dhtmlx/Connector/Data/DataItemUpdate.php @@ -0,0 +1,105 @@ +<?php + +/* + @author dhtmlx.com + @license GPL, see license.txt +*/ + +/*! DataItemUpdate class for realization Optimistic concurrency control + Wrapper for DataItem object + It's used during outputing updates instead of DataItem object + Create wrapper for every data item with update information. +*/ + +namespace DHTMLX\Connector\Data; + +class DataItemUpdate extends DataItem { + + + /*! constructor + @param data + hash of data + @param config + DataConfig object + @param index + index of element + */ + public function __construct($data,$config,$index,$type){ + $this->config=$config; + $this->data=$data; + $this->index=$index; + $this->skip=false; + $this->child = new $type($data, $config, $index); + } + + /*! returns parent_id (for Tree and TreeGrid components) + */ + public function get_parent_id(){ + if (method_exists($this->child, 'get_parent_id')) { + return $this->child->get_parent_id(); + } else { + return ''; + } + } + + + /*! generate XML on the data hash base + */ + public function to_xml(){ + $str= "<update "; + $str .= 'status="'.$this->data['type'].'" '; + $str .= 'id="'.$this->data['dataId'].'" '; + $str .= 'parent="'.$this->get_parent_id().'"'; + $str .= '>'; + $str .= $this->child->to_xml(); + $str .= '</update>'; + return $str; + } + + /*! return starting tag for XML string + */ + public function to_xml_start(){ + $str="<update "; + $str .= 'status="'.$this->data['type'].'" '; + $str .= 'id="'.$this->data['dataId'].'" '; + $str .= 'parent="'.$this->get_parent_id().'"'; + $str .= '>'; + $str .= $this->child->to_xml_start(); + return $str; + } + + /*! return ending tag for XML string + */ + public function to_xml_end(){ + $str = $this->child->to_xml_end(); + $str .= '</update>'; + return $str; + } + + /*! returns false for outputing only current item without child items + */ + public function has_kids(){ + return false; + } + + /*! sets count of child items + @param value + count of child items + */ + public function set_kids($value){ + if (method_exists($this->child, 'set_kids')) { + $this->child->set_kids($value); + } + } + + /*! sets attribute for item + */ + public function set_attribute($name, $value){ + if (method_exists($this->child, 'set_attribute')) { + LogMaster::log("setting attribute: \nname = {$name}\nvalue = {$value}"); + $this->child->set_attribute($name, $value); + } else { + LogMaster::log("set_attribute method doesn't exists"); + } + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/Data/DataProcessor.php b/codebase/Dhtmlx/Connector/Data/DataProcessor.php new file mode 100644 index 0000000..973753a --- /dev/null +++ b/codebase/Dhtmlx/Connector/Data/DataProcessor.php @@ -0,0 +1,253 @@ +<?php +/*! Base DataProcessor handling +**/ +namespace DHTMLX\Connector\Data; +use DHTMLX\Connector\Tools\LogMaster; +use DHTMLX\Connector\XSSFilter\ConnectorSecurity; +use DHTMLX\Connector\DataStorage\DataConfig; + +class DataProcessor{ + protected $connector;//!< Connector instance + protected $config;//!< DataConfig instance + protected $request;//!< DataRequestConfig instance + static public $action_param ="!nativeeditor_status"; + + /*! constructor + + @param connector + Connector object + @param config + DataConfig object + @param request + DataRequestConfig object + */ + function __construct($connector,$config,$request){ + $this->connector= $connector; + $this->config=$config; + $this->request=$request; + } + + /*! convert incoming data name to valid db name + redirect to Connector->name_data by default + @param data + data name from incoming request + @return + related db_name + */ + function name_data($data){ + return $data; + } + /*! retrieve data from incoming request and normalize it + + @param ids + array of extected IDs + @return + hash of data + */ + protected function get_post_values($ids){ + $data=array(); + for ($i=0; $i < sizeof($ids); $i++) + $data[$ids[$i]]=array(); + + foreach ($_POST as $key => $value) { + $details=explode("_",$key,2); + if (sizeof($details)==1) continue; + + $name=$this->name_data($details[1]); + $data[$details[0]][$name]=ConnectorSecurity::filter($value); + } + + return $data; + } + protected function get_ids(){ + if (!isset($_POST["ids"])) + throw new \Exception("Incorrect incoming data, ID of incoming records not recognized"); + return explode(",",$_POST["ids"]); + } + + protected function get_operation($rid){ + if (!isset($_POST[$rid."_".DataProcessor::$action_param])) + throw new \Exception("Status of record [{$rid}] not found in incoming request"); + return $_POST[$rid."_".DataProcessor::$action_param]; + } + /*! process incoming request ( save|update|delete ) + */ + function process(){ + LogMaster::log("DataProcessor object initialized",$_POST); + + $results=array(); + + $ids=$this->get_ids(); + $rows_data=$this->get_post_values($ids); + $failed=false; + + try{ + if ($this->connector->sql->is_global_transaction()) + $this->connector->sql->begin_transaction(); + + for ($i=0; $i < sizeof($ids); $i++) { + $rid = $ids[$i]; + LogMaster::log("Row data [{$rid}]",$rows_data[$rid]); + $status = $this->get_operation($rid); + + $action=new DataAction($status,$rid,$rows_data[$rid]); + + $results[]=$action; + $this->inner_process($action); + } + + } catch(\Exception $e){ + LogMaster::log($e); + $failed=true; + } + + if ($this->connector->sql->is_global_transaction()){ + if (!$failed) + for ($i=0; $i < sizeof($results); $i++) + if ($results[$i]->get_status()=="error" || $results[$i]->get_status()=="invalid"){ + $failed=true; + break; + } + if ($failed){ + for ($i=0; $i < sizeof($results); $i++) + $results[$i]->error(); + $this->connector->sql->rollback_transaction(); + } + else + $this->connector->sql->commit_transaction(); + } + + $this->output_as_xml($results); + } + + /*! converts status string to the inner mode name + + @param status + external status string + @return + inner mode name + */ + protected function status_to_mode($status){ + switch($status){ + case "updated": + return "update"; + break; + case "inserted": + return "insert"; + break; + case "deleted": + return "delete"; + break; + default: + return $status; + break; + } + } + /*! process data updated request received + + @param action + DataAction object + @return + DataAction object with details of processing + */ + protected function inner_process($action){ + + if ($this->connector->sql->is_record_transaction()) + $this->connector->sql->begin_transaction(); + + + + try{ + + $mode = $this->status_to_mode($action->get_status()); + if (!$this->connector->access->check($mode)){ + LogMaster::log("Access control: {$mode} operation blocked"); + $action->error(); + } else { + $check = $this->connector->event->trigger("beforeProcessing",$action); + + + if (!$action->is_ready()) + $this->check_exts($action,$mode); + if ($mode == "insert" && $action->get_status() != "error" && $action->get_status() != "invalid") + $this->connector->sql->new_record_order($action, $this->request); + + $check = $this->connector->event->trigger("afterProcessing",$action); + } + + } catch (\Exception $e){ + LogMaster::log($e); + $action->set_status("error"); + if ($action) + $this->connector->event->trigger("onDBError", $action, $e); + } + + if ($this->connector->sql->is_record_transaction()){ + if ($action->get_status()=="error" || $action->get_status()=="invalid") + $this->connector->sql->rollback_transaction(); + else + $this->connector->sql->commit_transaction(); + } + + return $action; + } + + /*! check if some event intercepts processing, send data to DataWrapper in other case + + @param action + DataAction object + @param mode + name of inner mode ( will be used to generate event names ) + */ + function check_exts($action,$mode){ + $old_config = new DataConfig($this->config); + + + $this->connector->event->trigger("before".$mode,$action); + if ($action->is_ready()) + LogMaster::log("Event code for ".$mode." processed"); + else { + //check if custom sql defined + $sql = $this->connector->sql->get_sql($mode,$action); + if ($sql){ + $this->connector->sql->query($sql); + } + else{ + $action->sync_config($this->config); + + + if ($this->connector->model && method_exists($this->connector->model, $mode)){ + call_user_func(array($this->connector->model, $mode), $action); + LogMaster::log("Model object process action: ".$mode); + } + if (!$action->is_ready()){ + + $method=array($this->connector->sql,$mode); + if (!is_callable($method)) + throw new \Exception("Unknown dataprocessing action: ".$mode); + call_user_func($method,$action,$this->request); + } + } + } + $this->connector->event->trigger("after".$mode,$action); + + $this->config->copy($old_config); + } + + /*! output xml response for dataprocessor + + @param results + array of DataAction objects + */ + function output_as_xml($results){ + LogMaster::log("Edit operation finished",$results); + ob_clean(); + header("Content-type:text/xml"); + echo "<?xml version='1.0' ?>"; + echo "<data>"; + for ($i=0; $i < sizeof($results); $i++) + echo $results[$i]->to_xml(); + echo "</data>"; + } + +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/Data/DataUpdate.php b/codebase/Dhtmlx/Connector/Data/DataUpdate.php new file mode 100644 index 0000000..8fc1c46 --- /dev/null +++ b/codebase/Dhtmlx/Connector/Data/DataUpdate.php @@ -0,0 +1,163 @@ +<?php + +class DataUpdate{ + + protected $table; //!< table , where actions are stored + protected $url; //!< url for notification service, optional + protected $sql; //!< DB wrapper object + protected $config; //!< DBConfig object + protected $request; //!< DBRequestConfig object + protected $event; + protected $item_class; + protected $demu; + + //protected $config;//!< DataConfig instance + //protected $request;//!< DataRequestConfig instance + + /*! constructor + + @param connector + Connector object + @param config + DataConfig object + @param request + DataRequestConfig object + */ + function __construct($sql, $config, $request, $table, $url){ + $this->config= $config; + $this->request= $request; + $this->sql = $sql; + $this->table=$table; + $this->url=$url; + $this->demu = false; + } + + public function set_demultiplexor($path){ + $this->demu = $path; + } + + public function set_event($master, $name){ + $this->event = $master; + $this->item_class = $name; + } + + private function select_update($actions_table, $join_table, $id_field_name, $version, $user) { + $sql = "SELECT * FROM {$actions_table}"; + $sql .= " LEFT OUTER JOIN {$join_table} ON "; + $sql .= "{$actions_table}.DATAID = {$join_table}.{$id_field_name} "; + $sql .= "WHERE {$actions_table}.ID > '{$version}' AND {$actions_table}.USER <> '{$user}'"; + return $sql; + } + + private function get_update_max_version() { + $sql = "SELECT MAX(id) as VERSION FROM {$this->table}"; + $res = $this->sql->query($sql); + $data = $this->sql->get_next($res); + + if ($data == false || $data['VERSION'] == false) + return 1; + else + return $data['VERSION']; + } + + private function log_update_action($actions_table, $dataId, $status, $user) { + $sql = "INSERT INTO {$actions_table} (DATAID, TYPE, USER) VALUES ('{$dataId}', '{$status}', '{$user}')"; + $this->sql->query($sql); + if ($this->demu) + file_get_contents($this->demu); + } + + + + + /*! records operations in actions_table + @param action + DataAction object + */ + public function log_operations($action) { + $type = $this->sql->escape($action->get_status()); + $dataId = $this->sql->escape($action->get_new_id()); + $user = $this->sql->escape($this->request->get_user()); + if ($type!="error" && $type!="invalid" && $type !="collision") { + $this->log_update_action($this->table, $dataId, $type, $user); + } + } + + + /*! return action version in XMl format + */ + public function get_version() { + $version = $this->get_update_max_version(); + return "<userdata name='version'>".$version."</userdata>"; + } + + + /*! adds action version in output XML as userdata + */ + public function version_output($conn, $out) { + $out->add($this->get_version()); + } + + + /*! create update actions in XML-format and sends it to output + */ + public function get_updates() { + $sub_request = new DataRequestConfig($this->request); + $version = $this->request->get_version(); + $user = $this->request->get_user(); + + $sub_request->parse_sql($this->select_update($this->table, $this->request->get_source(), $this->config->id['db_name'], $version, $user)); + $sub_request->set_relation(false); + + $output = $this->render_set($this->sql->select($sub_request), $this->item_class); + + ob_clean(); + header("Content-type:text/xml"); + + echo $this->updates_start(); + echo $this->get_version(); + echo $output; + echo $this->updates_end(); + } + + + protected function render_set($res, $name){ + $output=""; + $index=0; + while ($data=$this->sql->get_next($res)){ + $data = new DataItemUpdate($data,$this->config,$index, $name); + $this->event->trigger("beforeRender",$data); + $output.=$data->to_xml(); + $index++; + } + return $output; + } + + /*! returns update start string + */ + protected function updates_start() { + $start = '<updates>'; + return $start; + } + + /*! returns update end string + */ + protected function updates_end() { + $start = '</updates>'; + return $start; + } + + /*! checks if action version given by client is deprecated + @param action + DataAction object + */ + public function check_collision($action) { + $version = $this->sql->escape($this->request->get_version()); + //$user = $this->sql->escape($this->request->get_user()); + $last_version = $this->get_update_max_version(); + if (($last_version > $version)&&($action->get_status() == 'update')) { + $action->error(); + $action->set_status('collision'); + } + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/Data/GridDataProcessor.php b/codebase/Dhtmlx/Connector/Data/GridDataProcessor.php new file mode 100644 index 0000000..9b487eb --- /dev/null +++ b/codebase/Dhtmlx/Connector/Data/GridDataProcessor.php @@ -0,0 +1,23 @@ +<?php +namespace DHTMLX\Connector\Data; + +/*! DataProcessor class for Grid component +**/ +class GridDataProcessor extends DataProcessor{ + + /*! convert incoming data name to valid db name + converts c0..cN to valid field names + @param data + data name from incoming request + @return + related db_name + */ + function name_data($data){ + if ($data == "gr_id") return $this->config->id["name"]; + $parts=explode("c",$data); + if ($parts[0]=="" && ((string)intval($parts[1]))==$parts[1]) + if (sizeof($this->config->text)>intval($parts[1])) + return $this->config->text[intval($parts[1])]["name"]; + return $data; + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/Data/JSONCommonDataItem.php b/codebase/Dhtmlx/Connector/Data/JSONCommonDataItem.php new file mode 100644 index 0000000..4a69068 --- /dev/null +++ b/codebase/Dhtmlx/Connector/Data/JSONCommonDataItem.php @@ -0,0 +1,29 @@ +<?php +namespace DHTMLX\Connector\Data; + +class JSONCommonDataItem extends DataItem{ + /*! return self as XML string + */ + function to_xml(){ + if ($this->skip) return false; + + $data = array( + 'id' => $this->get_id() + ); + for ($i=0; $i<sizeof($this->config->text); $i++){ + $extra = $this->config->text[$i]["name"]; + $data[$extra]=$this->data[$extra]; + if (is_null($data[$extra])) + $data[$extra] = ""; + } + + if ($this->userdata !== false) + foreach ($this->userdata as $key => $value){ + if ($value === null) + $data[$key]=""; + $data[$key]=$value; + } + + return $data; + } +} diff --git a/codebase/Dhtmlx/Connector/Data/TreeDataItem.php b/codebase/Dhtmlx/Connector/Data/TreeDataItem.php new file mode 100644 index 0000000..561d495 --- /dev/null +++ b/codebase/Dhtmlx/Connector/Data/TreeDataItem.php @@ -0,0 +1,157 @@ +<?php + +namespace DHTMLX\Connector\Data; + +class TreeDataItem extends DataItem +{ + private $im0;//!< image of closed folder + private $im1;//!< image of opened folder + private $im2;//!< image of leaf item + private $check;//!< checked state + private $kids = -1;//!< checked state + private $attrs;//!< collection of custom attributes + + function __construct($data, $config, $index) + { + parent::__construct($data, $config, $index); + + $this->im0 = false; + $this->im1 = false; + $this->im2 = false; + $this->check = false; + $this->attrs = array(); + } + + /*! get id of parent record + + @return + id of parent record + */ + function get_parent_id() + { + return $this->data[$this->config->relation_id["name"]]; + } + + /*! get state of items checkbox + + @return + state of item's checkbox as int value, false if state was not defined + */ + function get_check_state() + { + return $this->check; + } + + /*! set state of item's checkbox + + @param value + int value, 1 - checked, 0 - unchecked, -1 - third state + */ + function set_check_state($value) + { + $this->check = $value; + } + + /*! return count of child items + -1 if there is no info about childs + @return + count of child items + */ + function has_kids() + { + return $this->kids; + } + + /*! sets count of child items + @param value + count of child items + */ + function set_kids($value) + { + $this->kids = $value; + } + + /*! set custom attribute + + @param name + name of the attribute + @param value + new value of the attribute + */ + function set_attribute($name, $value) + { + switch ($name) { + case "id": + $this->set_id($value); + break; + case "text": + $this->data[$this->config->text[0]["name"]] = $value; + break; + case "checked": + $this->set_check_state($value); + break; + case "im0": + $this->im0 = $value; + break; + case "im1": + $this->im1 = $value; + break; + case "im2": + $this->im2 = $value; + break; + case "child": + $this->set_kids($value); + break; + default: + $this->attrs[$name] = $value; + } + } + + + /*! assign image for tree's item + + @param img_folder_closed + image for item, which represents folder in closed state + @param img_folder_open + image for item, which represents folder in opened state, optional + @param img_leaf + image for item, which represents leaf item, optional + */ + function set_image($img_folder_closed, $img_folder_open = false, $img_leaf = false) + { + $this->im0 = $img_folder_closed; + $this->im1 = $img_folder_open ? $img_folder_open : $img_folder_closed; + $this->im2 = $img_leaf ? $img_leaf : $img_folder_closed; + } + + /*! return self as XML string, starting part + */ + function to_xml_start() + { + if ($this->skip) return ""; + + $str1 = "<item id='" . $this->get_id() . "' text='" . $this->xmlentities($this->data[$this->config->text[0]["name"]]) . "' "; + if ($this->has_kids() == true) $str1 .= "child='" . $this->has_kids() . "' "; + if ($this->im0) $str1 .= "im0='" . $this->im0 . "' "; + if ($this->im1) $str1 .= "im1='" . $this->im1 . "' "; + if ($this->im2) $str1 .= "im2='" . $this->im2 . "' "; + if ($this->check) $str1 .= "checked='" . $this->check . "' "; + foreach ($this->attrs as $key => $value) + $str1 .= $key . "='" . $this->xmlentities($value) . "' "; + $str1 .= ">"; + if ($this->userdata !== false) + foreach ($this->userdata as $key => $value) + $str1 .= "<userdata name='" . $key . "'><![CDATA[" . $value . "]]></userdata>"; + + return $str1; + } + + /*! return self as XML string, ending part + */ + function to_xml_end() + { + if ($this->skip) return ""; + return "</item>"; + } + +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/Data/TreeDataProcessor.php b/codebase/Dhtmlx/Connector/Data/TreeDataProcessor.php new file mode 100644 index 0000000..dca6a8f --- /dev/null +++ b/codebase/Dhtmlx/Connector/Data/TreeDataProcessor.php @@ -0,0 +1,25 @@ +<?php +namespace DHTMLX\Connector\Data; + +class TreeDataProcessor extends DataProcessor{ + + function __construct($connector,$config,$request){ + parent::__construct($connector,$config,$request); + $request->set_relation(false); + } + + /*! convert incoming data name to valid db name + converts c0..cN to valid field names + @param data + data name from incoming request + @return + related db_name + */ + function name_data($data){ + if ($data=="tr_pid") + return $this->config->relation_id["db_name"]; + if ($data=="tr_text") + return $this->config->text[0]["db_name"]; + return $data; + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/DataConnector.php b/codebase/Dhtmlx/Connector/DataConnector.php new file mode 100644 index 0000000..dc3498d --- /dev/null +++ b/codebase/Dhtmlx/Connector/DataConnector.php @@ -0,0 +1,84 @@ +<?php +namespace DHTMLX\Connector; + +/*! Connector class for DataView +**/ +class DataConnector extends Connector{ + + /*! constructor + + Here initilization of all Masters occurs, execution timer initialized + @param res + db connection resource + @param type + string , which hold type of database ( MySQL or Postgre ), optional, instead of short DB name, full name of DataWrapper-based class can be provided + @param item_type + name of class, which will be used for item rendering, optional, DataItem will be used by default + @param data_type + name of class which will be used for dataprocessor calls handling, optional, DataProcessor class will be used by default. + */ + public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){ + if (!$item_type) $item_type="DHTMLX\\Connector\\Data\\CommonDataItem"; + if (!$data_type) $data_type="DHTMLX\\Connector\\Data\\CommonDataProcessor"; + + $this->sections = array(); + + if (!$render_type) $render_type="DHTMLX\\Connector\\Output\\RenderStrategy"; + parent::__construct($res,$type,$item_type,$data_type,$render_type); + + } + + protected $sections; + public function add_section($name, $string){ + $this->sections[$name] = $string; + } + + protected function parse_request_mode(){ + if (isset($_GET['action']) && $_GET["action"] != "get") + $this->editing = true; + else + parent::parse_request_mode(); + } + + //parse GET scoope, all operations with incoming request must be done here + protected function parse_request(){ + if (isset($_GET['action'])){ + $action = $_GET['action']; + //simple request mode + if ($action == "get"){ + //data request + if (isset($_GET['id'])){ + //single entity data request + $this->request->set_filter($this->config->id["name"],$_GET['id'],"="); + } else { + //loading collection of items + } + } else { + //data saving + $this->editing = true; + } + parent::check_csrf(); + } else { + if (isset($_GET['editing']) && isset($_POST['ids'])) + $this->editing = true; + parent::parse_request(); + } + + if (isset($_GET["start"]) && isset($_GET["count"])) + $this->request->set_limit($_GET["start"],$_GET["count"]); + + } + + /*! renders self as xml, starting part + */ + protected function xml_start(){ + $start = "<data"; + foreach($this->attributes as $k=>$v) + $start .= " ".$k."='".$v."'"; + $start.= ">"; + + foreach($this->sections as $k=>$v) + $start .= "<".$k.">".$v."</".$k.">\n"; + return $start; + } +};
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/DataStorage/ArrayDBDataWrapper.php b/codebase/Dhtmlx/Connector/DataStorage/ArrayDBDataWrapper.php new file mode 100644 index 0000000..a806161 --- /dev/null +++ b/codebase/Dhtmlx/Connector/DataStorage/ArrayDBDataWrapper.php @@ -0,0 +1,50 @@ +<?php + +namespace DHTMLX\Connector\DataStorage; + +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"]; + + 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"); + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/DataStorage/ArrayQueryWrapper.php b/codebase/Dhtmlx/Connector/DataStorage/ArrayQueryWrapper.php new file mode 100644 index 0000000..c922bd8 --- /dev/null +++ b/codebase/Dhtmlx/Connector/DataStorage/ArrayQueryWrapper.php @@ -0,0 +1,9 @@ +<?php +namespace DHTMLX\Connector\DataStorage; + +class ArrayQueryWrapper{ + public function __construct($data){ + $this->data = $data; + $this->index = 0; + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/DataStorage/DBDataWrapper.php b/codebase/Dhtmlx/Connector/DataStorage/DBDataWrapper.php new file mode 100644 index 0000000..1311866 --- /dev/null +++ b/codebase/Dhtmlx/Connector/DataStorage/DBDataWrapper.php @@ -0,0 +1,456 @@ +<?php +/** + * Created by PhpStorm. + * User: user + * Date: 26.3.15 + * Time: 15.50 + */ + +namespace DHTMLX\Connector\DataStorage; + +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 + if ($rules[$i]["value"]!=""){ + if (!$rules[$i]["operation"]) + array_push($sql,$this->escape_name($rules[$i]["name"])." LIKE '%".$this->escape($rules[$i]["value"])."%'"); + else + array_push($sql,$this->escape_name($rules[$i]["name"])." ".$rules[$i]["operation"]." '".$this->escape($rules[$i]["value"])."'"); + } + + 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"); + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/DataStorage/DataConfig.php b/codebase/Dhtmlx/Connector/DataStorage/DataConfig.php new file mode 100644 index 0000000..c10fd2d --- /dev/null +++ b/codebase/Dhtmlx/Connector/DataStorage/DataConfig.php @@ -0,0 +1,227 @@ +<?php + +namespace DHTMLX\Connector\DataStorage; +/*! 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; $i<sizeof($this->text); $i++) + $str.="{$this->text[$i]['db_name']}({$this->text[$i]['name']}),"; + + $str.="\nExtra:"; + for ($i=0; $i<sizeof($this->data); $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; $i<sizeof($collection); $i++) + if ($collection[$i]["name"] == $name || $collection[$i]["db_name"] == $name) return $i; + return -1; + } + + +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/DataStorage/DataRequestConfig.php b/codebase/Dhtmlx/Connector/DataStorage/DataRequestConfig.php new file mode 100644 index 0000000..bb2c750 --- /dev/null +++ b/codebase/Dhtmlx/Connector/DataStorage/DataRequestConfig.php @@ -0,0 +1,284 @@ +<?php + +namespace DHTMLX\Connector\DataStorage; + +/*! manager of data request +**/ +class DataRequestConfig{ + private $filters; //!< array of filtering rules + private $relation=false; //!< ID or other element used for linking hierarchy + private $sort_by; //!< sorting field + private $start; //!< start of requested data + private $count; //!< length of requested data + + private $order = false; + private $user; + private $version; + + //for render_sql + private $source; //!< souce table or another source destination + private $fieldset; //!< set of data, which need to be retrieved from source + + /*! constructor + + @param proto + DataRequestConfig object, optional, if provided then new request object will copy all properties from provided one + */ + public function __construct($proto=false){ + if ($proto) + $this->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; + } + + /*! 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; + } + + /*! 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; + if (!$this->source) throw new Exception("Source of data can't be empty"); + } + /*! 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]); + } + + } + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/DataStorage/DataWrapper.php b/codebase/Dhtmlx/Connector/DataStorage/DataWrapper.php new file mode 100644 index 0000000..19158cc --- /dev/null +++ b/codebase/Dhtmlx/Connector/DataStorage/DataWrapper.php @@ -0,0 +1,100 @@ +<?php + +namespace DHTMLX\Connector\DataStorage; +/*! Base abstraction class, used for data operations + Class abstract access to data, it is a base class to all DB wrappers +**/ +abstract class DataWrapper{ + protected $connection; + protected $config;//!< DataConfig instance + /*! constructor + @param connection + DB connection + @param config + DataConfig instance + */ + public function __construct($connection = false,$config = false){ + $this->config=$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."); + } +} + diff --git a/codebase/Dhtmlx/Connector/DataStorage/MsSQLDBDataWrapper.php b/codebase/Dhtmlx/Connector/DataStorage/MsSQLDBDataWrapper.php new file mode 100644 index 0000000..1d245b2 --- /dev/null +++ b/codebase/Dhtmlx/Connector/DataStorage/MsSQLDBDataWrapper.php @@ -0,0 +1,78 @@ +<?php + +namespace DHTMLX\Connector\DataStorage; + +use DHTMLX\Connector\Tools\LogMaster; + +class MsSQLDBDataWrapper extends DBDataWrapper +{ + private $last_id = ""; //!< ID of previously inserted record + private $insert_operation = false; //!< flag of insert operation + private $start_from = false; //!< index of start position + + public function query($sql) + { + LogMaster::log($sql); + $res = mssql_query($sql, $this->connection); + if ($this->insert_operation) { + $last = mssql_fetch_assoc($res); + $this->last_id = $last["dhx_id"]; + mssql_free_result($res); + } + if ($this->start_from) + mssql_data_seek($res, $this->start_from); + return $res; + } + + public function get_next($res) + { + return mssql_fetch_assoc($res); + } + + public function get_new_id() + { + /* + MSSQL doesn't support identity or auto-increment fields + Insert SQL returns new ID value, which stored in last_id field + */ + return $this->last_id; + } + + protected function insert_query($data, $request) + { + $sql = parent::insert_query($data, $request); + $this->insert_operation = true; + return $sql . ";SELECT @@IDENTITY AS dhx_id"; + } + + protected function select_query($select, $from, $where, $sort, $start, $count) + { + if (!$from) + return $select; + + $sql = "SELECT "; + if ($count) + $sql .= " TOP " . ($count + $start); + $sql .= " " . $select . " FROM " . $from; + if ($where) $sql .= " WHERE " . $where; + if ($sort) $sql .= " ORDER BY " . $sort; + if ($start && $count) + $this->start_from = $start; + else + $this->start_from = false; + return $sql; + } + + public function escape($data) + { + /* + there is no special escaping method for mssql - use common logic + */ + return str_replace("'", "''", $data); + } + + public function begin_transaction() + { + $this->query("BEGIN TRAN"); + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/DataStorage/MySQLDBDataWrapper.php b/codebase/Dhtmlx/Connector/DataStorage/MySQLDBDataWrapper.php new file mode 100644 index 0000000..3f13492 --- /dev/null +++ b/codebase/Dhtmlx/Connector/DataStorage/MySQLDBDataWrapper.php @@ -0,0 +1,69 @@ +<?php +namespace DHTMLX\Connector\DataStorage; + + +/*! Implementation of DataWrapper for MySQL +**/ +class MySQLDBDataWrapper extends DBDataWrapper{ + protected $last_result; + public function query($sql){ + LogMaster::log($sql); + $res=mysql_query($sql,$this->connection); + if ($res===false) throw new Exception("MySQL operation failed\n".mysql_error($this->connection)); + $this->last_result = $res; + return $res; + } + + public function get_next($res){ + if (!$res) + $res = $this->last_result; + + return mysql_fetch_assoc($res); + } + + public function get_new_id(){ + return mysql_insert_id($this->connection); + } + + public function escape($data){ + return mysql_real_escape_string($data, $this->connection); + } + + public function tables_list() { + $result = mysql_query("SHOW TABLES"); + if ($result===false) throw new Exception("MySQL operation failed\n".mysql_error($this->connection)); + + $tables = array(); + while ($table = mysql_fetch_array($result)) { + $tables[] = $table[0]; + } + return $tables; + } + + public function fields_list($table) { + $result = mysql_query("SHOW COLUMNS FROM `".$table."`"); + if ($result===false) throw new Exception("MySQL operation failed\n".mysql_error($this->connection)); + + $fields = array(); + $id = ""; + while ($field = mysql_fetch_assoc($result)) { + if ($field['Key'] == "PRI") + $id = $field["Field"]; + else + $fields[] = $field["Field"]; + } + return array("fields" => $fields, "key" => $id ); + } + + /*! escape field name to prevent sql reserved words conflict + @param data + unescaped data + @return + escaped data + */ + public function escape_name($data){ + if ((strpos($data,"`")!==false || is_int($data)) || (strpos($data,".")!==false)) + return $data; + return '`'.$data.'`'; + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/DataStorage/PDODBDataWrapper.php b/codebase/Dhtmlx/Connector/DataStorage/PDODBDataWrapper.php new file mode 100644 index 0000000..5a08e57 --- /dev/null +++ b/codebase/Dhtmlx/Connector/DataStorage/PDODBDataWrapper.php @@ -0,0 +1,82 @@ +<?php + +namespace DHTMLX\Connector\DataStorage; + +/*! Implementation of DataWrapper for PDO + +if you plan to use it for Oracle - use Oracle connection type instead +**/ +class PDODBDataWrapper extends DBDataWrapper{ + private $last_result;//!< store result or last operation + + public function query($sql){ + LogMaster::log($sql); + + $res=$this->connection->query($sql); + if ($res===false) { + $message = $this->connection->errorInfo(); + throw new Exception("PDO - sql execution failed\n".$message[2]); + } + + return new PDOResultSet($res); + } + + 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) { + if ($this->connection->getAttribute(PDO::ATTR_DRIVER_NAME)=="pgsql") + $sql.=" OFFSET ".$start." LIMIT ".$count; + else + $sql.=" LIMIT ".$start.",".$count; + } + return $sql; + } + + public function tables_list() { + $result = $this->query("SHOW TABLES"); + if ($result===false) throw new Exception("MySQL operation failed\n".mysql_error($this->connection)); + + $tables = array(); + while ($table = $result->next()) { + $tables[] = $table[0]; + } + return $tables; + } + + public function fields_list($table) { + $result = $this->query("SHOW COLUMNS FROM `".$table."`"); + if ($result===false) throw new Exception("MySQL operation failed\n".mysql_error($this->connection)); + + $fields = array(); + $id = ""; + while ($field = $result->next()) { + if ($field['Key'] == "PRI") + $id = $field["Field"]; + else + $fields[] = $field["Field"]; + } + return array("fields" => $fields, "key" => $id ); + } + + public function get_next($res){ + $data = $res->next(); + return $data; + } + + public function get_new_id(){ + return $this->connection->lastInsertId(); + } + + public function escape($str){ + $res=$this->connection->quote($str); + if ($res===false) //not supported by pdo driver + return str_replace("'","''",$str); + return substr($res,1,-1); + } + +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/DataStorage/PDOResultSet.php b/codebase/Dhtmlx/Connector/DataStorage/PDOResultSet.php new file mode 100644 index 0000000..536f75c --- /dev/null +++ b/codebase/Dhtmlx/Connector/DataStorage/PDOResultSet.php @@ -0,0 +1,18 @@ +<?php + +namespace DHTMLX\Connector\DataStorage; + +class PDOResultSet{ + private $res; + public function __construct($res){ + $this->res = $res; + } + public function next(){ + $data = $this->res->fetch(PDO::FETCH_ASSOC); + if (!$data){ + $this->res->closeCursor(); + return null; + } + return $data; + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/DataStorage/PHPCI2DBDataWrapper.php b/codebase/Dhtmlx/Connector/DataStorage/PHPCI2DBDataWrapper.php new file mode 100755 index 0000000..8819ab5 --- /dev/null +++ b/codebase/Dhtmlx/Connector/DataStorage/PHPCI2DBDataWrapper.php @@ -0,0 +1,58 @@ +<?php
+namespace Dhtmlx\Connector\DataStorage;
+use Dhtmlx\Connector\Connector;
+use Dhtmlx\Connector\Tools\LogMaster;
+
+class PHPCI2DBDataWrapper extends DBDataWrapper {
+
+ public function query($sql){
+ LogMaster::log($sql);
+ $res=$this->connection->query($sql);
+
+ if($res === false)
+ throw new Exception("CI - sql execution failed");
+
+ if (is_object($res))
+ return new PHPCIResultSet($res);
+
+ return new ArrayQueryWrapper(array());
+ }
+
+ public function get_next($res){
+ $data = $res->next();
+ return $data;
+ }
+
+ public function get_new_id(){
+ return $this->connection->insert_id();
+ }
+
+ public function escape($str){
+ return $this->connection->escape_str($str);
+ }
+
+ public function escape_name($data){
+ return $this->connection->protect_identifiers($data);
+ }
+}
+class PHPCIResultSet {
+ private $res;
+ private $start;
+ private $count;
+ public function __construct($res){
+ $this->res = $res;
+ $this->start = $res->current_row;
+ $this->count = $res->num_rows;
+ }
+
+ public function next(){
+ if($this->start != $this->count)
+ return $this->res->row($this->start++, "array");
+ else {
+ $this->res->free_result();
+ return null;
+ }
+ }
+}
+
+?>
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/DataStorage/PHPCIDBDataWrapper.php b/codebase/Dhtmlx/Connector/DataStorage/PHPCIDBDataWrapper.php new file mode 100755 index 0000000..3173557 --- /dev/null +++ b/codebase/Dhtmlx/Connector/DataStorage/PHPCIDBDataWrapper.php @@ -0,0 +1,67 @@ +<?php
+namespace Dhtmlx\Connector\DataStorage;
+use Dhtmlx\Connector\Connector;
+use Dhtmlx\Connector\Tools\LogMaster;
+
+class PHPCIDBDataWrapper extends DBDataWrapper {
+
+ public function query($sql) {
+ LogMaster::log($sql);
+ $res = $this->connection->query($sql);
+
+ if ($res === false)
+ throw new Exception("CI - sql execution failed");
+
+ if(is_object($res))
+ return new PHPCIResultSet($res);
+
+ return new ArrayQueryWrapper(array());
+ }
+
+ public function get_next($res) {
+ return $res->next();
+ }
+
+ public function get_new_id() {
+ return $this->connection->insert_id();
+ }
+
+ public function escape($str) {
+ return $this->connection->escape_str($str);
+ }
+
+ public function escape_name($data) {
+ return $this->connection->protect_identifiers($data);
+ }
+}
+
+class PHPCIResultSet {
+ private $is_result_done = false;
+ private $res;
+ private $start;
+ private $count;
+
+ public function __construct($res){
+ if(is_bool($res))
+ $this->$is_result_done = true;
+ else {
+ $this->res = $res;
+ $this->start = $res->current_row;
+ $this->count = $res->num_rows();
+ }
+ }
+
+ public function next(){
+ if($this->is_result_done)
+ return null;
+
+ if($this->start != $this->count)
+ return $this->res->row($this->start++, "array");
+ else {
+ $this->res->free_result();
+ return null;
+ }
+ }
+}
+
+?>
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/DataStorage/PHPCakeDBDataWrapper.php b/codebase/Dhtmlx/Connector/DataStorage/PHPCakeDBDataWrapper.php new file mode 100755 index 0000000..8297c7f --- /dev/null +++ b/codebase/Dhtmlx/Connector/DataStorage/PHPCakeDBDataWrapper.php @@ -0,0 +1,76 @@ +<?php
+namespace Dhtmlx\Connector\DataStorage;
+use Cake\ORM\TableRegistry;
+
+class PHPCakeDBDataWrapper extends ArrayDBDataWrapper {
+
+ public function select($sql) {
+ if(is_array($this->connection)) //result of findAll
+ $query = $this->connection;
+ else
+ $query = $this->connection->find("all");
+
+ $temp = array();
+ foreach($query as $row)
+ $temp[] = $row->toArray();
+
+ return new ArrayQueryWrapper($temp);
+ }
+
+ protected function getErrorMessage() {
+ $errors = $this->connection->invalidFields();
+ $text = array();
+ foreach ($errors as $key => $value){
+ $text[] = $key." - ".$value[0];
+ }
+ return implode("\n", $text);
+ }
+
+ public function insert($data, $source) {
+ $table = TableRegistry::get($source->get_source());
+ $obj = $table->newEntity();
+ $obj = $this->fillModel($obj, $data);
+ $savedResult = $this->connection->save($obj);
+ $data->success($savedResult->get($this->config->id["db_name"]));
+ }
+
+ public function delete($data, $source) {
+ $table = TableRegistry::get($source->get_source());
+ $obj = $table->get($data->get_id());
+ $this->connection->delete($obj);
+ }
+
+ public function update($data, $source) {
+ $table = TableRegistry::get($source->get_source());
+ $obj = $table->get($data->get_id());
+ $obj = $this->fillModel($obj, $data);
+ $table->save($obj);
+ }
+
+ private function fillModel($obj, $data) {
+ //Map data to model object.
+ for($i = 0; $i < count($this->config->text); $i++) {
+ $step=$this->config->text[$i];
+ $obj->set($step["name"], $data->get_value($step["name"]));
+ }
+
+ if($relation = $this->config->relation_id["db_name"])
+ $obj->set($relation, $data->get_value($relation));
+
+ return $obj;
+ }
+
+ public function escape($str){
+ throw new Exception("Not implemented");
+ }
+
+ public function query($str){
+ throw new Exception("Not implemented");
+ }
+
+ public function get_new_id(){
+ throw new Exception("Not implemented");
+ }
+}
+
+?>
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/DataStorage/PHPYii1DBDataWrapper.php b/codebase/Dhtmlx/Connector/DataStorage/PHPYii1DBDataWrapper.php new file mode 100644 index 0000000..db0f100 --- /dev/null +++ b/codebase/Dhtmlx/Connector/DataStorage/PHPYii1DBDataWrapper.php @@ -0,0 +1,93 @@ +<?php +namespace DHTMLX\Connector\DataStorage; + +class PHPYii1DBDataWrapper extends ArrayDBDataWrapper { + + public function select($sql) { + if(is_array($this->connection)) //result of findAll + $res = $this->connection; + else + $res = $this->connection->find()->all(); + + $temp = array(); + if(sizeof($res)) { + foreach ($res as $obj) + $temp[]=$obj->getAttributes(); + } + + return new ArrayQueryWrapper($temp); + } + + protected function getErrorMessage() { + $errors = $this->connection->getErrors(); + $text = array(); + foreach($errors as $key => $value) + $text[] = $key." - ".$value[0]; + + return implode("\n", $text); + } + + public function insert($data, $source) { + $name = get_class($this->connection); + $obj = new $name(); + $this->fill_model_and_save($obj, $data); + } + + public function delete($data, $source){ + $obj = $this->connection->findOne($data->get_id()); + if($obj->delete()) { + $data->success(); + $data->set_new_id($obj->getPrimaryKey()); + } + else { + $data->set_response_attribute("details", $this->errors_to_string($obj->getErrors())); + $data->invalid(); + } + } + public function update($data, $source) { + $obj = $this->connection->findOne($data->get_id()); + $this->fill_model_and_save($obj, $data); + } + + protected function fill_model_and_save($obj, $data) { + //Map data to model object. + for($i=0; $i < sizeof($this->config->text); $i++) { + $step=$this->config->text[$i]; + $obj->setAttribute($step["name"], $data->get_value($step["name"])); + } + + if($relation = $this->config->relation_id["db_name"]) + $obj->setAttribute($relation, $data->get_value($relation)); + + //Save model. + if($obj->save()) { + $data->success(); + $data->set_new_id($obj->getPrimaryKey()); + } + else { + $data->set_response_attribute("details", $this->errors_to_string($obj->getErrors())); + $data->invalid(); + } + } + + protected function errors_to_string($errors) { + $text = array(); + foreach($errors as $value) + $text[] = implode("\n", $value); + + return implode("\n",$text); + } + + public function escape($str) { + throw new Exception("Not implemented"); + } + + public function query($str) { + throw new Exception("Not implemented"); + } + + public function get_new_id() { + throw new Exception("Not implemented"); + } + +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/DataStorage/PHPYiiDBDataWrapper.php b/codebase/Dhtmlx/Connector/DataStorage/PHPYiiDBDataWrapper.php new file mode 100644 index 0000000..57fb94a --- /dev/null +++ b/codebase/Dhtmlx/Connector/DataStorage/PHPYiiDBDataWrapper.php @@ -0,0 +1,92 @@ +<?php +namespace DHTMLX\Connector\DataStorage; + +class PHPYiiDBDataWrapper extends ArrayDBDataWrapper { + + public function select($sql) { + if(is_array($this->connection)) //result of findAll + $res = $this->connection; + else + $res = $this->connection->find()->all(); + + $temp = array(); + if(sizeof($res)) { + foreach($res as $obj) + $temp[] = $obj->getAttributes(); + } + return new ArrayQueryWrapper($temp); + } + + protected function getErrorMessage() { + $errors = $this->connection->getErrors(); + $text = array(); + foreach($errors as $key => $value) + $text[] = $key." - ".$value[0]; + + return implode("\n", $text); + } + public function insert($data, $source) { + $name = get_class($this->connection); + $obj = new $name(); + $this->fill_model_and_save($obj, $data); + } + + public function delete($data, $source) { + $obj = $this->connection->findOne($data->get_id()); + if($obj->delete()) { + $data->success(); + $data->set_new_id($obj->getPrimaryKey()); + } + else { + $data->set_response_attribute("details", $this->errors_to_string($obj->getErrors())); + $data->invalid(); + } + } + + public function update($data, $source) { + $obj = $this->connection->findOne($data->get_id()); + $this->fill_model_and_save($obj, $data); + } + + protected function fill_model_and_save($obj, $data) { + //Map data to model object. + for($i=0; $i < sizeof($this->config->text); $i++) { + $step=$this->config->text[$i]; + $obj->setAttribute($step["name"], $data->get_value($step["name"])); + } + + if($relation = $this->config->relation_id["db_name"]) + $obj->setAttribute($relation, $data->get_value($relation)); + + //Save model. + if($obj->save()) { + $data->success(); + $data->set_new_id($obj->getPrimaryKey()); + } + else { + $data->set_response_attribute("details", $this->errors_to_string($obj->getErrors())); + $data->invalid(); + } + } + + protected function errors_to_string($errors) { + $text = array(); + foreach($errors as $value) + $text[] = implode("\n", $value); + + return implode("\n",$text); + } + + public function escape($str) { + throw new Exception("Not implemented"); + } + + public function query($str) { + throw new Exception("Not implemented"); + } + + public function get_new_id() { + throw new Exception("Not implemented"); + } + +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/DataStorage/PostgreDBDataWrapper.php b/codebase/Dhtmlx/Connector/DataStorage/PostgreDBDataWrapper.php new file mode 100644 index 0000000..56eaa48 --- /dev/null +++ b/codebase/Dhtmlx/Connector/DataStorage/PostgreDBDataWrapper.php @@ -0,0 +1,70 @@ +<?php + +namespace DHTMLX\Connector\DataStorage; + +use DHTMLX\Connector\Tools\LogMaster; + +class PostgreDBDataWrapper extends DBDataWrapper{ + public function query($sql){ + LogMaster::log($sql); + + $res=pg_query($this->connection,$sql); + if ($res===false) throw new \Exception("Postgre - sql execution failed\n".pg_last_error($this->connection)); + + return $res; + } + + 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.=" OFFSET ".$start." LIMIT ".$count; + return $sql; + } + + public function get_next($res){ + return pg_fetch_assoc($res); + } + + public function get_new_id(){ + $res = pg_query( $this->connection, "SELECT LASTVAL() AS seq"); + $data = pg_fetch_assoc($res); + pg_free_result($res); + return $data['seq']; + } + + public function escape($data){ + //need to use oci_bind_by_name + return pg_escape_string($this->connection,$data); + } + + public function tables_list() { + $sql = "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'"; + $res = pg_query($this->connection, $sql); + $tables = array(); + while ($table = pg_fetch_assoc($res)) { + $tables[] = $table['table_name']; + } + return $tables; + } + + public function fields_list($table) { + $sql = "SELECT * FROM information_schema.constraint_column_usage"; + $result = pg_query($this->connection, $sql); + $field = pg_fetch_assoc($result); + $id = $field['column_name']; + + $sql = "SELECT * FROM information_schema.columns WHERE table_name ='".$table."';"; + $result = pg_query($this->connection, $sql); + $fields = array(); + $id = ""; + while ($field = pg_fetch_assoc($result)) { + $fields[] = $field["column_name"]; + } + return array('fields' => $fields, 'key' => $id ); + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/DistinctOptionsConnector.php b/codebase/Dhtmlx/Connector/DistinctOptionsConnector.php new file mode 100644 index 0000000..273ec6b --- /dev/null +++ b/codebase/Dhtmlx/Connector/DistinctOptionsConnector.php @@ -0,0 +1,18 @@ +<?php +namespace DHTMLX\Connector; + +class DistinctOptionsConnector extends OptionsConnector{ + /*! render self + process commands, return data as XML, not output data to stdout, ignore parameters in incoming request + @return + data as XML string + */ + public function render(){ + if (!$this->init_flag){ + $this->init_flag=true; + return ""; + } + $res = $this->sql->get_variants($this->config->text[0]["db_name"],$this->request); + return $this->render_set($res); + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/Event/EventInterface.php b/codebase/Dhtmlx/Connector/Event/EventInterface.php new file mode 100644 index 0000000..645d89b --- /dev/null +++ b/codebase/Dhtmlx/Connector/Event/EventInterface.php @@ -0,0 +1,37 @@ +<?php +namespace DHTMLX\Connector\Event; + +class EventInterface{ + protected $request; ////!< DataRequestConfig instance + public $rules=array(); //!< array of sorting rules + + /*! constructor + creates a new interface based on existing request + @param request + DataRequestConfig object + */ + public function __construct($request){ + $this->request = $request; + } + + /*! remove all elements from collection + */ + public function clear(){ + array_splice($rules,0); + } + /*! get index by name + + @param name + name of field + @return + index of named field + */ + public function index($name){ + $len = sizeof($this->rules); + for ($i=0; $i < $len; $i++) { + if ($this->rules[$i]["name"]==$name) + return $i; + } + return false; + } +} diff --git a/codebase/Dhtmlx/Connector/Event/FilterInterface.php b/codebase/Dhtmlx/Connector/Event/FilterInterface.php new file mode 100644 index 0000000..e37489d --- /dev/null +++ b/codebase/Dhtmlx/Connector/Event/FilterInterface.php @@ -0,0 +1,32 @@ +<?php + +namespace DHTMLX\Connector\Event; + +/*! Wrapper for collection of filtering rules +**/ +class FilterInterface extends EventInterface{ + /*! constructor + creates a new interface based on existing request + @param request + DataRequestConfig object + */ + public function __construct($request){ + $this->request = $request; + $this->rules = &$request->get_filters_ref(); + } + /*! add new filatering rule + + @param name + name of field + @param value + value to filter by + @param rule + filtering rule + */ + public function add($name,$value,$rule){ + $this->request->set_filter($name,$value,$rule); + } + public function store(){ + $this->request->set_filters($this->rules); + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/Event/SortInterface.php b/codebase/Dhtmlx/Connector/Event/SortInterface.php new file mode 100644 index 0000000..5e6aef4 --- /dev/null +++ b/codebase/Dhtmlx/Connector/Event/SortInterface.php @@ -0,0 +1,31 @@ +<?php + +namespace DHTMLX\Connector\Event; + +class SortInterface extends EventInterface{ + /*! constructor + creates a new interface based on existing request + @param request + DataRequestConfig object + */ + public function __construct($request){ + parent::__construct($request); + $this->rules = &$request->get_sort_by_ref(); + } + /*! add new sorting rule + + @param name + name of field + @param dir + direction of sorting + */ + public function add($name,$dir){ + if ($dir === false) + $this->request->set_sort($name); + else + $this->request->set_sort($name,$dir); + } + public function store(){ + $this->request->set_sort_by($this->rules); + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/GridConfiguration.php b/codebase/Dhtmlx/Connector/GridConfiguration.php new file mode 100644 index 0000000..d67a0b6 --- /dev/null +++ b/codebase/Dhtmlx/Connector/GridConfiguration.php @@ -0,0 +1,445 @@ +<?php +namespace DHTMLX\Connector; + +class GridConfiguration +{ + + /*! attaching header functionality + */ + protected $headerDelimiter = ','; + protected $headerNames = false; + protected $headerAttaches = array(); + protected $footerAttaches = array(); + protected $headerWidthsUnits = 'px'; + + protected $headerIds = false; + protected $headerWidths = false; + protected $headerTypes = false; + protected $headerAlign = false; + protected $headerVAlign = false; + protected $headerSorts = false; + protected $headerColors = false; + protected $headerHidden = false; + protected $headerFormat = false; + + protected $convert_mode = false; + + function __construct($headers = false) + { + if ($headers === false || $headers === true) + $this->headerNames = $headers; + else + $this->setHeader($headers); + } + + /*! brief convert list of parameters to an array + @param param + list of values or array of values + @return array of parameters + */ + private function parse_param_array($param, $check = false, $default = "") + { + if (gettype($param) == 'string') + $param = explode($this->headerDelimiter, $param); + + if ($check) { + for ($i = 0; $i < sizeof($param); $i++) { + if (!array_key_exists($param[$i], $check)) + $param[$i] = $default; + } + } + return $param; + } + + /*! sets delimiter for string arguments in attach header functions (default is ,) + @param headerDelimiter + string delimiter + */ + public function setHeaderDelimiter($headerDelimiter) + { + $this->headerDelimiter = $headerDelimiter; + } + + /*! sets header + @param names + array of names or string of names, delimited by headerDelimiter (default is ,) + */ + public function setHeader($names) + { + if ($names instanceof DataConfig) { + $out = array(); + for ($i = 0; $i < sizeof($names->text); $i++) + $out[] = $names->text[$i]["name"]; + $names = $out; + } + + $this->headerNames = $this->parse_param_array($names); + } + + /*! sets init columns width in pixels + @param wp + array of widths or string of widths, delimited by headerDelimiter (default is ,) + */ + public function setInitWidths($wp) + { + $this->headerWidths = $this->parse_param_array($wp); + $this->headerWidthsUnits = 'px'; + } + + /*! sets init columns width in persents + @param wp + array of widths or string of widths, delimited by headerDelimiter (default is ,) + */ + public function setInitWidthsP($wp) + { + $this->setInitWidths($wp); + $this->headerWidthsUnits = '%'; + } + + /*! sets columns align + @param alStr + array of aligns or string of aligns, delimited by headerDelimiter (default is ,) + */ + public function setColAlign($alStr) + { + $this->headerAlign = $this->parse_param_array($alStr, + array("right" => 1, "left" => 1, "center" => 1, "justify" => 1), + "left"); + } + + /*! sets columns vertical align + @param alStr + array of vertical aligns or string of vertical aligns, delimited by headerDelimiter (default is ,) + */ + public function setColVAlign($alStr) + { + $this->headerVAlign = $this->parse_param_array($alStr, + array("baseline" => 1, "sub" => 1, "super" => 1, "top" => 1, "text-top" => 1, "middle" => 1, "bottom" => 1, "text-bottom" => 1), + "top"); + } + + /*! sets column types + @param typeStr + array of types or string of types, delimited by headerDelimiter (default is ,) + */ + public function setColTypes($typeStr) + { + $this->headerTypes = $this->parse_param_array($typeStr); + } + + /*! sets columns sorting + @param sortStr + array if sortings or string of sortings, delimited by headerDelimiter (default is ,) + */ + public function setColSorting($sortStr) + { + $this->headerSorts = $this->parse_param_array($sortStr); + } + + /*! sets columns colors + @param colorStr + array of colors or string of colors, delimited by headerDelimiter (default is ,) + if (color should not be applied it's value should be null) + */ + public function setColColor($colorStr) + { + $this->headerColors = $this->parse_param_array($colorStr); + } + + /*! sets hidden columns + @param hidStr + array of bool values or string of bool values, delimited by headerDelimiter (default is ,) + */ + public function setColHidden($hidStr) + { + $this->headerHidden = $this->parse_param_array($hidStr); + } + + /*! sets columns id + @param idsStr + array of ids or string of ids, delimited by headerDelimiter (default is ,) + */ + public function setColIds($idsStr) + { + $this->headerIds = $this->parse_param_array($idsStr); + } + + /*! sets number/date format + @param formatArr + array of mask formats for number/dates , delimited by headerDelimiter (default is ,) + */ + public function setColFormat($formatArr) + { + $this->headerFormat = $this->parse_param_array($formatArr); + } + + /*! attaches header + @param values + array of header names or string of header names, delimited by headerDelimiter (default is ,) + @param styles + array of header styles or string of header styles, delimited by headerDelimiter (default is ,) + */ + public function attachHeader($values, $styles = null, $footer = false) + { + $header = array(); + $header['values'] = $this->parse_param_array($values); + if ($styles != null) { + $header['styles'] = $this->parse_param_array($styles); + } else { + $header['styles'] = null; + } + if ($footer) + $this->footerAttaches[] = $header; + else + $this->headerAttaches[] = $header; + } + + /*! attaches footer + @param values + array of footer names or string of footer names, delimited by headerDelimiter (default is ,) + @param styles + array of footer styles or string of footer styles, delimited by headerDelimiter (default is ,) + */ + public function attachFooter($values, $styles = null) + { + $this->attachHeader($values, $styles, true); + } + + private function auto_fill($mode) + { + $headerWidths = array(); + $headerTypes = array(); + $headerSorts = array(); + $headerAttaches = array(); + + for ($i = 0; $i < sizeof($this->headerNames); $i++) { + $headerWidths[] = 100; + $headerTypes[] = "ro"; + $headerSorts[] = "connector"; + $headerAttaches[] = "#connector_text_filter"; + } + if ($this->headerWidths == false) + $this->setInitWidths($headerWidths); + if ($this->headerTypes == false) + $this->setColTypes($headerTypes); + + if ($mode) { + if ($this->headerSorts == false) + $this->setColSorting($headerSorts); + $this->attachHeader($headerAttaches); + } + } + + public function defineOptions($conn) + { + if (!$conn->is_first_call()) return; //render head only for first call + + $config = $conn->get_config(); + $full_header = ($this->headerNames === true); + + if (gettype($this->headerNames) == 'boolean') //auto-config + $this->setHeader($config); + $this->auto_fill($full_header); + + if (isset($_GET["dhx_colls"])) return; + + $fillList = array(); + for ($i = 0; $i < count($this->headerNames); $i++) + if ($this->headerTypes[$i] == "co" || $this->headerTypes[$i] == "coro") + $fillList[$i] = true; + + for ($i = 0; $i < count($this->headerAttaches); $i++) { + for ($j = 0; $j < count($this->headerAttaches[$i]['values']); $j++) { + if ($this->headerAttaches[$i]['values'][$j] == "#connector_select_filter" + || $this->headerAttaches[$i]['values'][$j] == "#select_filter" + ) { + $fillList[$j] = true;; + } + } + } + + $temp = array(); + foreach ($fillList as $k => $v) + $temp[] = $k; + if (count($temp)) + $_GET["dhx_colls"] = implode(",", $temp); + } + + + /*! gets header as array + */ + private function getHeaderArray() + { + $head = Array(); + $head[0] = $this->headerNames; + $head = $this->getAttaches($head, $this->headerAttaches); + return $head; + } + + + /*! get footer as array + */ + private function getFooterArray() + { + $foot = Array(); + $foot = $this->getAttaches($foot, $this->footerAttaches); + return $foot; + } + + + /*! gets array of data with attaches + */ + private function getAttaches($to, $from) + { + for ($i = 0; $i < count($from); $i++) { + $line = $from[$i]['values']; + $to[] = $line; + } + return $to; + } + + + /*! calculates rowspan array according #cspan markers + */ + private function processCspan($data) + { + $rspan = Array(); + for ($i = 0; $i < count($data); $i++) { + $last = 0; + $rspan[$i] = Array(); + for ($j = 0; $j < count($data[$i]); $j++) { + $rspan[$i][$j] = 0; + if ($data[$i][$j] === '#cspan') { + $rspan[$i][$last]++; + } else { + $last = $j; + } + } + } + return $rspan; + } + + + /*! calculates colspan array according #rspan markers + */ + private function processRspan($data) + { + $last = Array(); + $cspan = Array(); + for ($i = 0; $i < count($data); $i++) { + $cspan[$i] = Array(); + for ($j = 0; $j < count($data[$i]); $j++) { + $cspan[$i][$j] = 0; + if (!isset($last[$j])) $last[$j] = 0; + if ($data[$i][$j] === '#rspan') { + $cspan[$last[$j]][$j]++; + } else { + $last[$j] = $i; + } + } + } + return $cspan; + } + + + /*! sets mode of output format: usual mode or convert mode. + * @param mode + * true - convert mode, false - otherwise + */ + public function set_convert_mode($mode) + { + $this->convert_mode = $mode; + } + + + /*! adds header configuration in output XML + */ + public function attachHeaderToXML($conn, $out) + { + if (!$conn->is_first_call()) return; //render head only for first call + + $head = $this->getHeaderArray(); + $foot = $this->getFooterArray(); + $rspan = $this->processRspan($head); + $cspan = $this->processCspan($head); + + $str = '<head>'; + + if ($this->convert_mode) $str .= "<columns>"; + + for ($i = 0; $i < count($this->headerNames); $i++) { + $str .= '<column'; + $str .= ' type="' . $this->headerTypes[$i] . '"'; + $str .= ' width="' . $this->headerWidths[$i] . '"'; + $str .= $this->headerIds ? ' id="' . $this->headerIds[$i] . '"' : ''; + $str .= $this->headerAlign[$i] ? ' align="' . $this->headerAlign[$i] . '"' : ''; + $str .= $this->headerVAlign[$i] ? ' valign="' . $this->headerVAlign[$i] . '"' : ''; + $str .= $this->headerSorts[$i] ? ' sort="' . $this->headerSorts[$i] . '"' : ''; + $str .= $this->headerColors[$i] ? ' color="' . $this->headerColors[$i] . '"' : ''; + $str .= $this->headerHidden[$i] ? ' hidden="' . $this->headerHidden[$i] . '"' : ''; + $str .= $this->headerFormat[$i] ? ' format="' . $this->headerFormat[$i] . '"' : ''; + $str .= $cspan[0][$i] ? ' colspan="' . ($cspan[0][$i] + 1) . '"' : ''; + $str .= $rspan[0][$i] ? ' rowspan="' . ($rspan[0][$i] + 1) . '"' : ''; + $str .= '>' . $this->headerNames[$i] . '</column>'; + } + + if (!$this->convert_mode) { + $str .= '<settings><colwidth>' . $this->headerWidthsUnits . '</colwidth></settings>'; + if ((count($this->headerAttaches) > 0) || (count($this->footerAttaches) > 0)) { + $str .= '<afterInit>'; + } + for ($i = 0; $i < count($this->headerAttaches); $i++) { + $str .= '<call command="attachHeader">'; + $str .= '<param>' . implode(",", $this->headerAttaches[$i]['values']) . '</param>'; + if ($this->headerAttaches[$i]['styles'] != null) { + $str .= '<param>' . implode(",", $this->headerAttaches[$i]['styles']) . '</param>'; + } + $str .= '</call>'; + } + for ($i = 0; $i < count($this->footerAttaches); $i++) { + $str .= '<call command="attachFooter">'; + $str .= '<param>' . implode(",", $this->footerAttaches[$i]['values']) . '</param>'; + if ($this->footerAttaches[$i]['styles'] != null) { + $str .= '<param>' . implode(",", $this->footerAttaches[$i]['styles']) . '</param>'; + } + $str .= '</call>'; + } + if ((count($this->headerAttaches) > 0) || (count($this->footerAttaches) > 0)) { + $str .= '</afterInit>'; + } + } else { + $str .= "</columns>"; + for ($i = 1; $i < count($head); $i++) { + $str .= "<columns>"; + for ($j = 0; $j < count($head[$i]); $j++) { + $str .= '<column'; + $str .= $cspan[$i][$j] ? ' colspan="' . ($cspan[$i][$j] + 1) . '"' : ''; + $str .= $rspan[$i][$j] ? ' rowspan="' . ($rspan[$i][$j] + 1) . '"' : ''; + $str .= '>' . $head[$i][$j] . '</column>'; + } + $str .= "</columns>\n"; + } + } + $str .= '</head>'; + + + if ($this->convert_mode && count($foot) > 0) { + $rspan = $this->processRspan($foot); + $cspan = $this->processCspan($foot); + $str .= "<foot>"; + for ($i = 0; $i < count($foot); $i++) { + $str .= "<columns>"; + for ($j = 0; $j < count($foot[$i]); $j++) { + $str .= '<column'; + $str .= $cspan[$i][$j] ? ' colspan="' . ($cspan[$i][$j] + 1) . '"' : ''; + $str .= $rspan[$i][$j] ? ' rowspan="' . ($rspan[$i][$j] + 1) . '"' : ''; + $str .= '>' . $foot[$i][$j] . '</column>'; + } + $str .= "</columns>\n"; + } + $str .= "</foot>"; + } + + $out->add($str); + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/GridConnector.php b/codebase/Dhtmlx/Connector/GridConnector.php new file mode 100644 index 0000000..b25a95a --- /dev/null +++ b/codebase/Dhtmlx/Connector/GridConnector.php @@ -0,0 +1,125 @@ +<?php + +namespace DHTMLX\Connector; +/*! Connector for the dhtmlxgrid +**/ +class GridConnector extends Connector{ + + /*! constructor + + Here initilization of all Masters occurs, execution timer initialized + @param res + db connection resource + @param type + string , which hold type of database ( MySQL or Postgre ), optional, instead of short DB name, full name of DataWrapper-based class can be provided + @param item_type + name of class, which will be used for item rendering, optional, DataItem will be used by default + @param data_type + name of class which will be used for dataprocessor calls handling, optional, DataProcessor class will be used by default. + */ + public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){ + if (!$item_type) $item_type="DHTMLX\\Connector\\GridDataItem"; + if (!$data_type) $data_type="DHTMLX\\Connector\\Data\\GridDataProcessor"; + if (!$render_type) $render_type="DHTMLX\\Connector\\Output\\RenderStrategy"; + parent::__construct($res,$type,$item_type,$data_type,$render_type); + } + + + protected function parse_request(){ + parent::parse_request(); + + if (isset($_GET["dhx_colls"])) + $this->fill_collections($_GET["dhx_colls"]); + } + protected function resolve_parameter($name){ + if (intval($name).""==$name) + return $this->config->text[intval($name)]["db_name"]; + return $name; + } + + /*! replace xml unsafe characters + + @param string + string to be escaped + @return + escaped string + */ + protected function xmlentities($string) { + return str_replace( array( '&', '"', "'", '<', '>', '’' ), array( '&' , '"', ''' , '<' , '>', ''' ), $string); + } + + /*! assign options collection to the column + + @param name + name of the column + @param options + array or connector object + */ + public function set_options($name,$options){ + if (is_array($options)){ + $str=""; + foreach($options as $k => $v) + $str.="<item value='".$this->xmlentities($k)."' label='".$this->xmlentities($v)."' />"; + $options=$str; + } + $this->options[$name]=$options; + } + /*! generates xml description for options collections + + @param list + comma separated list of column names, for which options need to be generated + */ + protected function fill_collections($list=""){ + $names=explode(",",$list); + for ($i=0; $i < sizeof($names); $i++) { + $name = $this->resolve_parameter($names[$i]); + if (!array_key_exists($name,$this->options)){ + $this->options[$name] = new DistinctOptionsConnector($this->get_connection(),$this->names["db_class"]); + $c = new DataConfig($this->config); + $r = new DataRequestConfig($this->request); + $c->minimize($name); + + $this->options[$name]->render_connector($c,$r); + } + + $this->extra_output.="<coll_options for='{$names[$i]}'>"; + if (!is_string($this->options[$name])) + $this->extra_output.=$this->options[$name]->render(); + else + $this->extra_output.=$this->options[$name]; + $this->extra_output.="</coll_options>"; + } + } + + /*! renders self as xml, starting part + */ + protected function xml_start(){ + $attributes = ""; + foreach($this->attributes as $k=>$v) + $attributes .= " ".$k."='".$v."'"; + + if ($this->dload){ + if ($pos=$this->request->get_start()) + return "<rows pos='".$pos."'".$attributes.">"; + else + return "<rows total_count='".$this->sql->get_size($this->request)."'".$attributes.">"; + } + else + return "<rows".$attributes.">"; + } + + + /*! renders self as xml, ending part + */ + protected function xml_end(){ + return $this->extra_output."</rows>"; + } + + public function set_config($config = false){ + if (gettype($config) == 'boolean') + $config = new GridConfiguration($config); + + $this->event->attach("beforeOutput", Array($config, "attachHeaderToXML")); + $this->event->attach("onInit", Array($config, "defineOptions")); + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/GridDataItem.php b/codebase/Dhtmlx/Connector/GridDataItem.php new file mode 100644 index 0000000..a405f8b --- /dev/null +++ b/codebase/Dhtmlx/Connector/GridDataItem.php @@ -0,0 +1,120 @@ +<?php + +namespace DHTMLX\Connector; + +use DHTMLX\Connector\Data\DataItem; +/*! DataItem class for Grid component +**/ + +class GridDataItem extends DataItem{ + protected $row_attrs;//!< hash of row attributes + protected $cell_attrs;//!< hash of cell attributes + + function __construct($data,$name,$index=0){ + parent::__construct($data,$name,$index); + + $this->row_attrs=array(); + $this->cell_attrs=array(); + } + /*! set color of row + + @param color + color of row + */ + function set_row_color($color){ + $this->row_attrs["bgColor"]=$color; + } + /*! set style of row + + @param color + color of row + */ + function set_row_style($color){ + $this->row_attrs["style"]=$color; + } + /*! assign custom style to the cell + + @param name + name of column + @param value + css style string + */ + function set_cell_style($name,$value){ + $this->set_cell_attribute($name,"style",$value); + } + /*! assign custom class to specific cell + + @param name + name of column + @param value + css class name + */ + function set_cell_class($name,$value){ + $this->set_cell_attribute($name,"class",$value); + } + /*! set custom cell attribute + + @param name + name of column + @param attr + name of attribute + @param value + value of attribute + */ + function set_cell_attribute($name,$attr,$value){ + if (!array_key_exists($name, $this->cell_attrs)) $this->cell_attrs[$name]=array(); + $this->cell_attrs[$name][$attr]=$value; + } + + /*! set custom row attribute + + @param attr + name of attribute + @param value + value of attribute + */ + function set_row_attribute($attr,$value){ + $this->row_attrs[$attr]=$value; + } + + /*! return self as XML string, starting part + */ + public function to_xml_start(){ + if ($this->skip) return ""; + + $str="<row id='".$this->xmlentities($this->get_id())."'"; + foreach ($this->row_attrs as $k=>$v) + $str.=" ".$k."='".$v."'"; + $str.=">"; + for ($i=0; $i < sizeof($this->config->text); $i++){ + $str.="<cell"; + $name=$this->config->text[$i]["name"]; + $xmlcontent = false; + if (isset($this->cell_attrs[$name])){ + $cattrs=$this->cell_attrs[$name]; + foreach ($cattrs as $k => $v){ + $str.=" ".$k."='".$this->xmlentities($v)."'"; + if ($k == "xmlcontent") + $xmlcontent = true; + } + } + $value = isset($this->data[$name]) ? $this->data[$name] : ''; + if (!$xmlcontent) + $str.="><![CDATA[".$value."]]></cell>"; + else + $str.=">".$value."</cell>"; + } + if ($this->userdata !== false) + foreach ($this->userdata as $key => $value) + $str.="<userdata name='".$key."'><![CDATA[".$value."]]></userdata>"; + + return $str; + } + /*! return self as XML string, ending part + */ + public function to_xml_end(){ + if ($this->skip) return ""; + + return "</row>"; + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/JSONDataConnector.php b/codebase/Dhtmlx/Connector/JSONDataConnector.php new file mode 100644 index 0000000..fd6b3a0 --- /dev/null +++ b/codebase/Dhtmlx/Connector/JSONDataConnector.php @@ -0,0 +1,108 @@ +<?php + +namespace DHTMLX\Connector; + +use DHTMLX\Connector\Output\OutputWriter; + + +class JSONDataConnector extends DataConnector{ + + public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){ + if (!$item_type) $item_type="DHTMLX\\Connector\\Data\\JSONCommonDataItem"; + if (!$data_type) $data_type="DHTMLX\\Connector\\Data\\CommonDataProcessor"; + if (!$render_type) $render_type="DHTMLX\\Connector\\Output\\JSONRenderStrategy"; + $this->data_separator = ",\n"; + parent::__construct($res,$type,$item_type,$data_type,$render_type); + } + + /*! assign options collection to the column + + @param name + name of the column + @param options + array or connector object + */ + public function set_options($name,$options){ + if (is_array($options)){ + $str=array(); + foreach($options as $k => $v) + $str[]='{"id":"'.$this->xmlentities($k).'", "value":"'.$this->xmlentities($v).'"}'; + $options=implode(",",$str); + } + $this->options[$name]=$options; + } + + /*! generates xml description for options collections + + @param list + comma separated list of column names, for which options need to be generated + */ + protected function fill_collections($list=""){ + $options = array(); + foreach ($this->options as $k=>$v) { + $name = $k; + $option="\"{$name}\":["; + if (!is_string($this->options[$name])) + $option.=substr(json_encode($this->options[$name]->render()),1,-1); + else + $option.=$this->options[$name]; + $option.="]"; + $options[] = $option; + } + $this->extra_output .= implode($this->data_separator, $options); + } + + protected function resolve_parameter($name){ + if (intval($name).""==$name) + return $this->config->text[intval($name)]["db_name"]; + return $name; + } + + protected function output_as_xml($res){ + $json = $this->render_set($res); + if ($this->simple) return $json; + $result = json_encode($json); + + $this->fill_collections(); + $is_sections = sizeof($this->sections) && $this->is_first_call(); + if ($this->dload || $is_sections || sizeof($this->attributes) || !empty($this->extra_data)){ + + $attributes = ""; + foreach($this->attributes as $k=>$v) + $attributes .= ", \"".$k."\":\"".$v."\""; + + $extra = ""; + if (!empty($this->extra_output)) + $extra .= ', "collections": {'.$this->extra_output.'}'; + + $sections = ""; + if ($is_sections){ + //extra sections + foreach($this->sections as $k=>$v) + $sections .= ", \"".$k."\":".$v; + } + + $dyn = ""; + if ($this->dload){ + //info for dyn. loadin + if ($pos=$this->request->get_start()) + $dyn .= ", \"pos\":".$pos; + else + $dyn .= ", \"pos\":0, \"total_count\":".$this->sql->get_size($this->request); + } + if ($attributes || $sections || $this->extra_output || $dyn) { + $result = "{ \"data\":".$result.$attributes.$extra.$sections.$dyn."}"; + } + } + + // return as string + if ($this->as_string) return $result; + + // output direct to response + $out = new OutputWriter($result, ""); + $out->set_type("json"); + $this->event->trigger("beforeOutput", $this, $out); + $out->output("", true, $this->encoding); + return null; + } +} diff --git a/codebase/Dhtmlx/Connector/JSONOptionsConnector.php b/codebase/Dhtmlx/Connector/JSONOptionsConnector.php new file mode 100644 index 0000000..6d25354 --- /dev/null +++ b/codebase/Dhtmlx/Connector/JSONOptionsConnector.php @@ -0,0 +1,43 @@ +<?php + +namespace DHTMLX\Connector; +/*! wrapper around options collection, used for comboboxes and filters +**/ +class JSONOptionsConnector extends JSONDataConnector{ + protected $init_flag=false;//!< used to prevent rendering while initialization + public function __construct($res,$type=false,$item_type=false,$data_type=false){ + if (!$item_type) $item_type="JSONCommonDataItem"; + if (!$data_type) $data_type=""; //has not sense, options not editable + parent::__construct($res,$type,$item_type,$data_type); + } + /*! render self + process commands, return data as XML, not output data to stdout, ignore parameters in incoming request + @return + data as XML string + */ + public function render(){ + if (!$this->init_flag){ + $this->init_flag=true; + return ""; + } + $res = $this->sql->select($this->request); + return $this->render_set($res); + } +} + + +class JSONDistinctOptionsConnector extends JSONOptionsConnector{ + /*! render self + process commands, return data as XML, not output data to stdout, ignore parameters in incoming request + @return + data as XML string + */ + public function render(){ + if (!$this->init_flag){ + $this->init_flag=true; + return ""; + } + $res = $this->sql->get_variants($this->config->text[0]["db_name"],$this->request); + return $this->render_set($res); + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/MixedConnector.php b/codebase/Dhtmlx/Connector/MixedConnector.php new file mode 100644 index 0000000..898bfa9 --- /dev/null +++ b/codebase/Dhtmlx/Connector/MixedConnector.php @@ -0,0 +1,24 @@ +<?php +namespace DHTMLX\Connector; + +use DHTMLX\Connector\Connector; + +class MixedConnector extends Connector { + + protected $connectors = array(); + + public function add($name, $conn) { + $this->connectors[$name] = $conn; + } + + public function render() { + $result = "{"; + $parts = array(); + foreach($this->connectors as $name => $conn) { + $conn->asString(true); + $parts[] = "\"".$name."\":".($conn->render())."\n"; + } + $result .= implode(",\n", $parts)."}"; + echo $result; + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/OptionsConnector.php b/codebase/Dhtmlx/Connector/OptionsConnector.php new file mode 100644 index 0000000..4c0ab16 --- /dev/null +++ b/codebase/Dhtmlx/Connector/OptionsConnector.php @@ -0,0 +1,27 @@ +<?php + +namespace DHTMLX\Connector; + +/*! wrapper around options collection, used for comboboxes and filters +**/ +class OptionsConnector extends Connector{ + protected $init_flag=false;//!< used to prevent rendering while initialization + public function __construct($res,$type=false,$item_type=false,$data_type=false){ + if (!$item_type) $item_type="DataItem"; + if (!$data_type) $data_type=""; //has not sense, options not editable + parent::__construct($res,$type,$item_type,$data_type); + } + /*! render self + process commands, return data as XML, not output data to stdout, ignore parameters in incoming request + @return + data as XML string + */ + public function render(){ + if (!$this->init_flag){ + $this->init_flag=true; + return ""; + } + $res = $this->sql->select($this->request); + return $this->render_set($res); + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/Output/JSONRenderStrategy.php b/codebase/Dhtmlx/Connector/Output/JSONRenderStrategy.php new file mode 100644 index 0000000..fddfd3d --- /dev/null +++ b/codebase/Dhtmlx/Connector/Output/JSONRenderStrategy.php @@ -0,0 +1,33 @@ +<?php + +namespace DHTMLX\Connector\Output; + +class JSONRenderStrategy extends RenderStrategy { + + /*! render from DB resultset + @param res + DB resultset + process commands, output requested data as json + */ + public function render_set($res, $name, $dload, $sep, $config, $mix){ + $output=array(); + $index=0; + $conn = $this->conn; + $this->mix($config, $mix); + $conn->event->trigger("beforeRenderSet",$conn,$res,$config); + while ($data=$conn->sql->get_next($res)){ + $data = $this->complex_mix($mix, $data); + $data = new $name($data,$config,$index); + if ($data->get_id()===false) + $data->set_id($conn->uuid()); + $conn->event->trigger("beforeRender",$data); + $item = $data->to_xml(); + if ($item !== false) + $output[]=$item; + $index++; + } + $this->unmix($config, $mix); + return $output; + } + +} diff --git a/codebase/Dhtmlx/Connector/Output/JSONTreeRenderStrategy.php b/codebase/Dhtmlx/Connector/Output/JSONTreeRenderStrategy.php new file mode 100644 index 0000000..04a8672 --- /dev/null +++ b/codebase/Dhtmlx/Connector/Output/JSONTreeRenderStrategy.php @@ -0,0 +1,39 @@ +<?php +namespace DHTMLX\Connector\Output; + +class JSONTreeRenderStrategy extends TreeRenderStrategy { + + public function render_set($res, $name, $dload, $sep, $config,$mix){ + $output=array(); + $index=0; + $conn = $this->conn; + $config_copy = new DataConfig($config); + $this->mix($config, $mix); + while ($data=$conn->sql->get_next($res)){ + $data = $this->complex_mix($mix, $data); + $data = new $name($data,$config,$index); + $conn->event->trigger("beforeRender",$data); + //there is no info about child elements, + //if we are using dyn. loading - assume that it has, + //in normal mode just exec sub-render routine + if ($data->has_kids()===-1 && $dload) + $data->set_kids(true); + $record = $data->to_xml_start(); + if ($data->has_kids()===-1 || ( $data->has_kids()==true && !$dload)){ + $sub_request = new DataRequestConfig($conn->get_request()); + //$sub_request->set_fieldset(implode(",",$config_copy->db_names_list($conn->sql))); + $sub_request->set_relation($data->get_id()); + //$sub_request->set_filters(array()); + $temp = $this->render_set($conn->sql->select($sub_request), $name, $dload, $sep, $config_copy, $mix); + if (sizeof($temp)) + $record["data"] = $temp; + } + if ($record !== false) + $output[] = $record; + $index++; + } + $this->unmix($config, $mix); + return $output; + } + +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/Output/OutputWriter.php b/codebase/Dhtmlx/Connector/Output/OutputWriter.php new file mode 100644 index 0000000..96bb49a --- /dev/null +++ b/codebase/Dhtmlx/Connector/Output/OutputWriter.php @@ -0,0 +1,40 @@ +<?php + +namespace DHTMLX\Connector\Output; + +class OutputWriter{ + private $start; + private $end; + private $type; + + public function __construct($start, $end = ""){ + $this->start = $start; + $this->end = $end; + $this->type = "xml"; + } + public function add($add){ + $this->start.=$add; + } + public function reset(){ + $this->start=""; + $this->end=""; + } + public function set_type($add){ + $this->type=$add; + } + public function output($name="", $inline=true, $encoding=""){ + ob_clean(); + + if ($this->type == "xml"){ + $header = "Content-type: text/xml"; + if ("" != $encoding) + $header.="; charset=".$encoding; + header($header); + } + + echo $this->__toString(); + } + public function __toString(){ + return $this->start.$this->end; + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/Output/RenderStrategy.php b/codebase/Dhtmlx/Connector/Output/RenderStrategy.php new file mode 100644 index 0000000..9431d0d --- /dev/null +++ b/codebase/Dhtmlx/Connector/Output/RenderStrategy.php @@ -0,0 +1,115 @@ +<?php +namespace DHTMLX\Connector\Output; + +use DHTMLX\Connector\Data\DataItem; +use DHTMLX\Connector\GridDataItem; +class RenderStrategy { + + protected $conn = null; + + public function __construct($conn) { + $this->conn = $conn; + } + + /*! adds mix fields into DataConfig + * @param config + * DataConfig object + * @param mix + * mix structure + */ + protected function mix($config, $mix) { + for ($i = 0; $i < count($mix); $i++) { + if ($config->is_field($mix[$i]['name'])===-1) { + $config->add_field($mix[$i]['name']); + } + } + } + + /*! remove mix fields from DataConfig + * @param config + * DataConfig object + * @param mix + * mix structure + */ + protected function unmix($config, $mix) { + for ($i = 0; $i < count($mix); $i++) { + if ($config->is_field($mix[$i]['name'])!==-1) { + $config->remove_field_full($mix[$i]['name']); + } + } + } + + /*! adds mix fields in item + * simple mix adds only strings specified by user + * @param mix + * mix structure + * @param data + * array of selected data + */ + protected function simple_mix($mix, $data) { + // get mix details + for ($i = 0; $i < count($mix); $i++) + $data[$mix[$i]["name"]] = is_object($mix[$i]["value"]) ? "" : $mix[$i]["value"]; + return $data; + } + + /*! adds mix fields in item + * complex mix adds strings specified by user and results of subrequests + * @param mix + * mix structure + * @param data + * array of selected data + */ + protected function complex_mix($mix, $data) { + // get mix details + for ($i = 0; $i < count($mix); $i++) { + $mixname = $mix[$i]["name"]; + if ($mix[$i]['filter'] !== false) { + $subconn = $mix[$i]["value"]; + $filter = $mix[$i]["filter"]; + + // setting relationships + $subconn->clear_filter(); + foreach ($filter as $k => $v) + if (isset($data[$v])) + $subconn->filter($k, $data[$v], "="); + else + throw new Exception('There was no such data field registered as: '.$k); + + $subconn->asString(true); + $data[$mixname]=$subconn->simple_render(); + if (is_array($data[$mixname]) && count($data[$mixname]) == 1) + $data[$mixname] = $data[$mixname][0]; + } else { + $data[$mixname] = $mix[$i]["value"]; + } + } + return $data; + } + + /*! render from DB resultset + @param res + DB resultset + process commands, output requested data as XML + */ + public function render_set($res, $name, $dload, $sep, $config, $mix){ + $output=""; + $index=0; + $conn = $this->conn; + $this->mix($config, $mix); + $conn->event->trigger("beforeRenderSet",$conn,$res,$config); + while ($data=$conn->sql->get_next($res)){ + $data = $this->simple_mix($mix, $data); + + $data = new $name($data,$config,$index); + if ($data->get_id()===false) + $data->set_id($conn->uuid()); + $conn->event->trigger("beforeRender",$data); + $output.=$data->to_xml().$sep; + $index++; + } + $this->unmix($config, $mix); + return $output; + } + +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/Output/TreeRenderStrategy.php b/codebase/Dhtmlx/Connector/Output/TreeRenderStrategy.php new file mode 100644 index 0000000..d66e58e --- /dev/null +++ b/codebase/Dhtmlx/Connector/Output/TreeRenderStrategy.php @@ -0,0 +1,64 @@ +<?php +namespace DHTMLX\Connector\Output; + +use DHTMLX\Connector\DataStorage\DataConfig; + +class TreeRenderStrategy extends RenderStrategy { + + protected $id_swap = array(); + + public function __construct($conn) { + parent::__construct($conn); + $conn->event->attach("afterInsert",array($this,"parent_id_correction_a")); + $conn->event->attach("beforeProcessing",array($this,"parent_id_correction_b")); + } + + public function render_set($res, $name, $dload, $sep, $config, $mix){ + $output=""; + $index=0; + $conn = $this->conn; + $config_copy = new DataConfig($config); + $this->mix($config, $mix); + while ($data=$conn->sql->get_next($res)){ + $data = $this->simple_mix($mix, $data); + $data = new $name($data,$config,$index); + $conn->event->trigger("beforeRender",$data); + //there is no info about child elements, + //if we are using dyn. loading - assume that it has, + //in normal mode juse exec sub-render routine + if ($data->has_kids()===-1 && $dload) + $data->set_kids(true); + $output.=$data->to_xml_start(); + if ($data->has_kids()===-1 || ( $data->has_kids()==true && !$dload)){ + $sub_request = new DataRequestConfig($conn->get_request()); + //$sub_request->set_fieldset(implode(",",$config_copy->db_names_list($conn->sql))); + $sub_request->set_relation($data->get_id()); + $output.=$this->render_set($conn->sql->select($sub_request), $name, $dload, $sep, $config_copy, $mix); + } + $output.=$data->to_xml_end(); + $index++; + } + $this->unmix($config, $mix); + return $output; + } + + /*! store info about ID changes during insert operation + @param dataAction + data action object during insert operation + */ + public function parent_id_correction_a($dataAction){ + $this->id_swap[$dataAction->get_id()]=$dataAction->get_new_id(); + } + + /*! update ID if it was affected by previous operation + @param dataAction + data action object, before any processing operation + */ + public function parent_id_correction_b($dataAction){ + $relation = $this->conn->get_config()->relation_id["db_name"]; + $value = $dataAction->get_value($relation); + + if (array_key_exists($value,$this->id_swap)) + $dataAction->set_value($relation,$this->id_swap[$value]); + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/Tools/AccessMaster.php b/codebase/Dhtmlx/Connector/Tools/AccessMaster.php new file mode 100644 index 0000000..6c84e20 --- /dev/null +++ b/codebase/Dhtmlx/Connector/Tools/AccessMaster.php @@ -0,0 +1,57 @@ +<?php +namespace DHTMLX\Connector\Tools; +/*! Class which handles access rules. +**/ +class AccessMaster{ + private $rules,$local; + /*! constructor + + Set next access right to "allowed" by default : read, insert, update, delete + Basically - all common data operations allowed by default + */ + function __construct(){ + $this->rules=array("read" => true, "insert" => true, "update" => true, "delete" => true); + $this->local=true; + } + /*! change access rule to "allow" + @param name + name of access right + */ + public function allow($name){ + $this->rules[$name]=true; + } + /*! change access rule to "deny" + + @param name + name of access right + */ + public function deny($name){ + $this->rules[$name]=false; + } + + /*! change all access rules to "deny" + */ + public function deny_all(){ + $this->rules=array(); + } + + /*! check access rule + + @param name + name of access right + @return + true if access rule allowed, false otherwise + */ + public function check($name){ + if ($this->local){ + /*! + todo + add referrer check, to prevent access from remote points + */ + } + if (!isset($this->rules[$name]) || !$this->rules[$name]){ + return false; + } + return true; + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/Tools/EventMaster.php b/codebase/Dhtmlx/Connector/Tools/EventMaster.php new file mode 100644 index 0000000..ec849d8 --- /dev/null +++ b/codebase/Dhtmlx/Connector/Tools/EventMaster.php @@ -0,0 +1,107 @@ +<?php + +namespace DHTMLX\Connector\Tools; +/*! Class which allows to assign|fire events. +*/ +class EventMaster{ + private $events;//!< hash of event handlers + private $master; + private static $eventsStatic=array(); + + /*! constructor + */ + function __construct(){ + $this->events=array(); + $this->master = false; + } + /*! Method check if event with such name already exists. + @param name + name of event, case non-sensitive + @return + true if event with such name registered, false otherwise + */ + public function exist($name){ + $name=strtolower($name); + return (isset($this->events[$name]) && sizeof($this->events[$name])); + } + /*! Attach custom code to event. + + Only on event handler can be attached in the same time. If new event handler attached - old will be detached. + + @param name + name of event, case non-sensitive + @param method + function which will be attached. You can use array(class, method) if you want to attach the method of the class. + */ + public function attach($name,$method=false){ + //use class for event handling + if ($method === false){ + $this->master = $name; + return; + } + //use separate functions + $name=strtolower($name); + if (!array_key_exists($name,$this->events)) + $this->events[$name]=array(); + $this->events[$name][]=$method; + } + + public static function attach_static($name, $method){ + $name=strtolower($name); + if (!array_key_exists($name,EventMaster::$eventsStatic)) + EventMaster::$eventsStatic[$name]=array(); + EventMaster::$eventsStatic[$name][]=$method; + } + + public static function trigger_static($name, $method){ + $arg_list = func_get_args(); + $name=strtolower(array_shift($arg_list)); + + if (isset(EventMaster::$eventsStatic[$name])) + foreach(EventMaster::$eventsStatic[$name] as $method){ + if (is_array($method) && !method_exists($method[0],$method[1])) + throw new Exception("Incorrect method assigned to event: ".$method[0].":".$method[1]); + if (!is_array($method) && !function_exists($method)) + throw new Exception("Incorrect function assigned to event: ".$method); + call_user_func_array($method, $arg_list); + } + return true; + } + + /*! Detach code from event + @param name + name of event, case non-sensitive + */ + public function detach($name){ + $name=strtolower($name); + unset($this->events[$name]); + } + /*! Trigger event. + @param name + name of event, case non-sensitive + @param data + value which will be provided as argument for event function, + you can provide multiple data arguments, method accepts variable number of parameters + @return + true if event handler was not assigned , result of event hangler otherwise + */ + public function trigger($name,$data){ + $arg_list = func_get_args(); + $name=strtolower(array_shift($arg_list)); + + if (isset($this->events[$name])) + foreach($this->events[$name] as $method){ + if (is_array($method) && !method_exists($method[0],$method[1])) + throw new Exception("Incorrect method assigned to event: ".$method[0].":".$method[1]); + if (!is_array($method) && !function_exists($method)) + throw new Exception("Incorrect function assigned to event: ".$method); + call_user_func_array($method, $arg_list); + } + + if ($this->master !== false) + if (method_exists($this->master, $name)) + call_user_func_array(array($this->master, $name), $arg_list); + + return true; + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/Tools/LogMaster.php b/codebase/Dhtmlx/Connector/Tools/LogMaster.php new file mode 100755 index 0000000..657516d --- /dev/null +++ b/codebase/Dhtmlx/Connector/Tools/LogMaster.php @@ -0,0 +1,101 @@ +<?php + +namespace DHTMLX\Connector\Tools; +/*! Controls error and debug logging. + Class designed to be used as static object. +**/ +class LogMaster{ + private static $_log=false;//!< logging mode flag + private static $_output=false;//!< output error infor to client flag + private static $session="";//!< all messages generated for current request + + /*! convert array to string representation ( it is a bit more readable than var_dump ) + + @param data + data object + @param pref + prefix string, used for formating, optional + @return + string with array description + */ + private static function log_details($data,$pref=""){ + if (is_array($data)){ + $str=array(""); + foreach($data as $k=>$v) + array_push($str,$pref.$k." => ".LogMaster::log_details($v,$pref."\t")); + return implode("\n",$str); + } + return $data; + } + /*! put record in log + + @param str + string with log info, optional + @param data + data object, which will be added to log, optional + */ + public static function log($str="",$data=""){ + if (LogMaster::$_log){ + $message = $str.LogMaster::log_details($data)."\n\n"; + LogMaster::$session.=$message; + error_log($message,3,LogMaster::$_log); + } + } + + /*! get logs for current request + @return + string, which contains all log messages generated for current request + */ + public static function get_session_log(){ + return LogMaster::$session; + } + + /*! error handler, put normal php errors in log file + + @param errn + error number + @param errstr + error description + @param file + error file + @param line + error line + @param context + error cntext + */ + public static function error_log($errn,$errstr,$file,$line,$context){ + LogMaster::log($errstr." at ".$file." line ".$line); + } + + /*! exception handler, used as default reaction on any error - show execution log and stop processing + + @param exception + instance of Exception + */ + public static function exception_log($exception){ + LogMaster::log("!!!Uncaught Exception\nCode: " . $exception->getCode() . "\nMessage: " . $exception->getMessage()); + if (LogMaster::$_output){ + echo "<pre><xmp>\n"; + echo LogMaster::get_session_log(); + echo "\n</xmp></pre>"; + } + die(); + } + + /*! enable logging + + @param name + path to the log file, if boolean false provided as value - logging will be disabled + @param output + flag of client side output, if enabled - session log will be sent to client side in case of an error. + */ + public static function enable_log($name,$output=false){ + LogMaster::$_log=$name; + LogMaster::$_output=$output; + if ($name){ + set_error_handler(array("DHTMLX\\Connector\\Tools\\LogMaster","error_log"),E_ALL); + set_exception_handler(array("DHTMLX\\Connector\\Tools\\LogMaster","exception_log")); + LogMaster::log("\n\n====================================\nLog started, ".date("d/m/Y h:i:s")."\n===================================="); + } + } +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/TreeConnector.php b/codebase/Dhtmlx/Connector/TreeConnector.php new file mode 100644 index 0000000..b01e399 --- /dev/null +++ b/codebase/Dhtmlx/Connector/TreeConnector.php @@ -0,0 +1,63 @@ +<?php + +namespace DHTMLX\Connector; + +class TreeConnector extends Connector +{ + protected $parent_name = 'id'; + public $rootId = "0"; + + /*! constructor + + Here initilization of all Masters occurs, execution timer initialized + @param res + db connection resource + @param type + string , which hold type of database ( MySQL or Postgre ), optional, instead of short DB name, full name of DataWrapper-based class can be provided + @param item_type + name of class, which will be used for item rendering, optional, DataItem will be used by default + @param data_type + name of class which will be used for dataprocessor calls handling, optional, DataProcessor class will be used by default. + * @param render_type + * name of class which will provides data rendering + */ + public function __construct($res, $type = false, $item_type = false, $data_type = false, $render_type = false) + { + if (!$item_type) $item_type = "TreeDataItem"; + if (!$data_type) $data_type = "TreeDataProcessor"; + if (!$render_type) $render_type = "TreeRenderStrategy"; + parent::__construct($res, $type, $item_type, $data_type, $render_type); + } + + //parse GET scoope, all operations with incoming request must be done here + public function parse_request() + { + parent::parse_request(); + + if (isset($_GET[$this->parent_name])) + $this->request->set_relation($_GET[$this->parent_name]); + else + $this->request->set_relation($this->rootId); + + $this->request->set_limit(0, 0); //netralize default reaction on dyn. loading mode + } + + /*! renders self as xml, starting part + */ + public function xml_start() + { + $attributes = ""; + foreach ($this->attributes as $k => $v) + $attributes .= " " . $k . "='" . $v . "'"; + + return "<tree id='" . $this->request->get_relation() . "'" . $attributes . ">"; + } + + /*! renders self as xml, ending part + */ + public function xml_end() + { + $this->fill_collections(); + return $this->extra_output . "</tree>"; + } +} diff --git a/codebase/Dhtmlx/Connector/XSSFilter/ConnectorSecurity.php b/codebase/Dhtmlx/Connector/XSSFilter/ConnectorSecurity.php new file mode 100644 index 0000000..195e962 --- /dev/null +++ b/codebase/Dhtmlx/Connector/XSSFilter/ConnectorSecurity.php @@ -0,0 +1,67 @@ +<?php +namespace DHTMLX\Connector\XSSFilter; + + +use DHTMLX\Connector\Tools\LogMaster; + +define("DHX_SECURITY_SAFETEXT", 1); +define("DHX_SECURITY_SAFEHTML", 2); +define("DHX_SECURITY_TRUSTED", 3); + +class ConnectorSecurity{ + static public $xss = DHX_SECURITY_SAFETEXT; + static public $security_key = false; + static public $security_var = "dhx_security"; + + static private $filterClass = null; + static function filter($value, $mode = false){ + if ($mode === false) + $mode = ConnectorSecurity::$xss; + + if ($mode == DHX_SECURITY_TRUSTED) + return $value; + if ($mode == DHX_SECURITY_SAFETEXT) + return filter_var($value, FILTER_SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_QUOTES); + if ($mode == DHX_SECURITY_SAFEHTML){ + if (ConnectorSecurity::$filterClass == null) + ConnectorSecurity::$filterClass = new dhxExternalInputClean(); + return ConnectorSecurity::$filterClass->basic($value); + } + throw new Error("Invalid security mode:"+$mode); + } + + static function CSRF_detected(){ + LogMaster::log("[SECURITY] Possible CSRF attack detected", array( + "referer" => $_SERVER["HTTP_REFERER"], + "remote" => $_SERVER["REMOTE_ADDR"] + )); + LogMaster::log("Request data", $_POST); + die(); + } + static function checkCSRF($edit){ + if (ConnectorSecurity::$security_key){ + if (!isset($_SESSION)) + @session_start(); + + if ($edit=== true){ + if (!isset($_POST[ConnectorSecurity::$security_var])) + return ConnectorSecurity::CSRF_detected(); + $master_key = $_SESSION[ConnectorSecurity::$security_var]; + $update_key = $_POST[ConnectorSecurity::$security_var]; + if ($master_key != $update_key) + return ConnectorSecurity::CSRF_detected(); + + return ""; + } + //data loading + if (!array_key_exists(ConnectorSecurity::$security_var,$_SESSION)){ + $_SESSION[ConnectorSecurity::$security_var] = md5(uniqid()); + } + + return $_SESSION[ConnectorSecurity::$security_var]; + } + + return ""; + } + +}
\ No newline at end of file diff --git a/codebase/Dhtmlx/Connector/XSSFilter/dhxExternalInputClean.php b/codebase/Dhtmlx/Connector/XSSFilter/dhxExternalInputClean.php new file mode 100644 index 0000000..4f3b526 --- /dev/null +++ b/codebase/Dhtmlx/Connector/XSSFilter/dhxExternalInputClean.php @@ -0,0 +1,120 @@ +<?php +namespace DHTMLX\Connector\XSSFilter; +//original name was lx_externalinput_clean +//renamed to prevent possible conflicts +class dhxExternalInputClean { + // this basic clean should clean html code from + // lot of possible malicious code for Cross Site Scripting + // use it whereever you get external input + + // you can also set $filterOut to some use html cleaning, but I don't know of any code, which could + // exploit that. But if you want to be sure, set it to eg. array("Tidy","Dom"); + static function basic($string, $filterIn = array("Tidy","Dom","Striptags"), $filterOut = "none") { + $string = self::tidyUp($string, $filterIn); + $string = str_replace(array("&", "<", ">"), array("&amp;", "&lt;", "&gt;"), $string); + + // fix &entitiy\n; + $string = preg_replace('#(&\#*\w+)[\x00-\x20]+;#u', "$1;", $string); + $string = preg_replace('#(&\#x*)([0-9A-F]+);*#iu', "$1$2;", $string); + + $string = html_entity_decode($string, ENT_COMPAT, "UTF-8"); + + // remove any attribute starting with "on" or xmlns + $string = preg_replace('#(<[^>]+[\x00-\x20\"\'\/])(on|xmlns)[^>]*>#iUu', "$1>", $string); + + // remove javascript: and vbscript: protocol + $string = preg_replace('#([a-z]*)[\x00-\x20\/]*=[\x00-\x20\/]*([\`\'\"]*)[\x00-\x20\/]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iUu', '$1=$2nojavascript...', $string); + $string = preg_replace('#([a-z]*)[\x00-\x20\/]*=[\x00-\x20\/]*([\`\'\"]*)[\x00-\x20\/]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iUu', '$1=$2novbscript...', $string); + $string = preg_replace('#([a-z]*)[\x00-\x20\/]*=[\x00-\x20\/]*([\`\'\"]*)[\x00-\x20\/]*-moz-binding[\x00-\x20]*:#Uu', '$1=$2nomozbinding...', $string); + $string = preg_replace('#([a-z]*)[\x00-\x20\/]*=[\x00-\x20\/]*([\`\'\"]*)[\x00-\x20\/]*data[\x00-\x20]*:#Uu', '$1=$2nodata...', $string); + + //remove any style attributes, IE allows too much stupid things in them, eg. + //<span style="width: expression(alert('Ping!'));"></span> + // and in general you really don't want style declarations in your UGC + + $string = preg_replace('#(<[^>]+[\x00-\x20\"\'\/])style[^>]*>#iUu', "$1>", $string); + + //remove namespaced elements (we do not need them...) + $string = preg_replace('#</*\w+:\w[^>]*>#i', "", $string); + + //remove really unwanted tags + do { + $oldstring = $string; + $string = preg_replace('#</*(applet|meta|xml|blink|link|style|script|embed|object|iframe|frame|frameset|ilayer|layer|bgsound|title|base)[^>]*>#i', "", $string); + } while ($oldstring != $string); + + return self::tidyUp($string, $filterOut); + } + + static function tidyUp($string, $filters) { + if (is_array($filters)) { + foreach ($filters as $filter) { + $return = self::tidyUpWithFilter($string, $filter); + if ($return !== false) { + return $return; + } + } + } else { + $return = self::tidyUpWithFilter($string, $filters); + } + // if no filter matched, use the Striptags filter to be sure. + if ($return === false) { + return self::tidyUpModuleStriptags($string); + } else { + return $return; + } + } + + static private function tidyUpWithFilter($string, $filter) { + if (is_callable(array("self", "tidyUpModule" . $filter))) { + return call_user_func(array("self", "tidyUpModule" . $filter), $string); + } + return false; + } + + static private function tidyUpModuleStriptags($string) { + + return strip_tags($string); + } + + static private function tidyUpModuleNone($string) { + return $string; + } + + static private function tidyUpModuleDom($string) { + $dom = new domdocument(); + @$dom->loadHTML("<html><body>" . $string . "</body></html>"); + $string = ''; + foreach ($dom->documentElement->firstChild->childNodes as $child) { + $string .= $dom->saveXML($child); + } + return $string; + } + + static private function tidyUpModuleTidy($string) { + if (class_exists("tidy")) { + $tidy = new tidy(); + $tidyOptions = array("output-xhtml" => true, + "show-body-only" => true, + "clean" => true, + "wrap" => "350", + "indent" => true, + "indent-spaces" => 1, + "ascii-chars" => false, + "wrap-attributes" => false, + "alt-text" => "", + "doctype" => "loose", + "numeric-entities" => true, + "drop-proprietary-attributes" => true, + "enclose-text" => false, + "enclose-block-text" => false + + ); + $tidy->parseString($string, $tidyOptions, "utf8"); + $tidy->cleanRepair(); + return (string) $tidy; + } else { + return false; + } + } +}
\ No newline at end of file |