diff options
Diffstat (limited to 'codebase/Dhtmlx/Connector/Connector.php')
-rw-r--r-- | codebase/Dhtmlx/Connector/Connector.php | 646 |
1 files changed, 646 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 |