summaryrefslogtreecommitdiffstats
path: root/codebase/Dhtmlx/Connector/Connector.php
diff options
context:
space:
mode:
Diffstat (limited to 'codebase/Dhtmlx/Connector/Connector.php')
-rw-r--r--codebase/Dhtmlx/Connector/Connector.php646
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( '&amp;' , '&quot;', '&apos;' , '&lt;' , '&gt;', '&apos;' ), $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