summaryrefslogtreecommitdiffstats
path: root/codebase/connector
diff options
context:
space:
mode:
authorStanislau Wolski <stanislau.wolski@gmail.com>2013-10-18 14:02:16 +0300
committerStanislau Wolski <stanislau.wolski@gmail.com>2013-10-18 14:02:16 +0300
commitc2e1fd58b0c848118cf8554de50f788199d609e2 (patch)
treebd30b3b7de59b34120705465e8628a740cdb513d /codebase/connector
downloadscheduler-c2e1fd58b0c848118cf8554de50f788199d609e2.zip
scheduler-c2e1fd58b0c848118cf8554de50f788199d609e2.tar.gz
scheduler-c2e1fd58b0c848118cf8554de50f788199d609e2.tar.bz2
[add] version 4.0v4.0.0
Diffstat (limited to 'codebase/connector')
-rw-r--r--codebase/connector/base_connector.php926
-rw-r--r--codebase/connector/chart_connector.php18
-rw-r--r--codebase/connector/combo_connector.php94
-rw-r--r--codebase/connector/convert.php69
-rw-r--r--codebase/connector/crosslink_connector.php141
-rw-r--r--codebase/connector/data_connector.php500
-rw-r--r--codebase/connector/dataprocessor.php505
-rw-r--r--codebase/connector/dataview_connector.php74
-rw-r--r--codebase/connector/db_adodb.php72
-rw-r--r--codebase/connector/db_common.php1069
-rw-r--r--codebase/connector/db_excel.php190
-rw-r--r--codebase/connector/db_filesystem.php345
-rw-r--r--codebase/connector/db_mssql.php73
-rw-r--r--codebase/connector/db_mysqli.php56
-rw-r--r--codebase/connector/db_oracle.php88
-rw-r--r--codebase/connector/db_pdo.php75
-rw-r--r--codebase/connector/db_phpcake.php85
-rw-r--r--codebase/connector/db_phpci.php63
-rw-r--r--codebase/connector/db_phpyii.php91
-rw-r--r--codebase/connector/db_postgre.php73
-rw-r--r--codebase/connector/db_sasql.php54
-rw-r--r--codebase/connector/db_sqlite.php34
-rw-r--r--codebase/connector/db_sqlite3.php33
-rw-r--r--codebase/connector/db_sqlsrv.php102
-rw-r--r--codebase/connector/filesystem_item.php19
-rw-r--r--codebase/connector/form_connector.php62
-rw-r--r--codebase/connector/grid_config.php423
-rw-r--r--codebase/connector/grid_connector.php262
-rw-r--r--codebase/connector/keygrid_connector.php48
-rw-r--r--codebase/connector/mixed_connector.php28
-rw-r--r--codebase/connector/options_connector.php45
-rw-r--r--codebase/connector/scheduler_connector.php230
-rw-r--r--codebase/connector/strategy.php500
-rw-r--r--codebase/connector/tools.php267
-rw-r--r--codebase/connector/tree_connector.php229
-rw-r--r--codebase/connector/treedatagroup_connector.php89
-rw-r--r--codebase/connector/treedatamultitable_connector.php91
-rw-r--r--codebase/connector/treegrid_connector.php120
-rw-r--r--codebase/connector/treegridgroup_connector.php46
-rw-r--r--codebase/connector/treegridmultitable_connector.php70
-rw-r--r--codebase/connector/treegroup_connector.php46
-rw-r--r--codebase/connector/treemultitable_connector.php51
-rw-r--r--codebase/connector/update.php266
-rw-r--r--codebase/connector/xss_filter.php199
44 files changed, 7921 insertions, 0 deletions
diff --git a/codebase/connector/base_connector.php b/codebase/connector/base_connector.php
new file mode 100644
index 0000000..ac25b00
--- /dev/null
+++ b/codebase/connector/base_connector.php
@@ -0,0 +1,926 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("tools.php");
+require_once("db_common.php");
+require_once("dataprocessor.php");
+require_once("strategy.php");
+require_once("update.php");
+
+//enable buffering to catch and ignore any custom output before XML generation
+//because of this command, it strongly recommended to include connector's file before any other libs
+//in such case it will handle any extra output from not well formed code of other libs
+ini_set("output_buffering","On");
+ob_start();
+
+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;
+ }
+}
+
+/*! EventInterface
+ Base class , for iterable collections, which are used in 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;
+ }
+}
+/*! Wrapper for collection of sorting rules
+**/
+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);
+ }
+}
+/*! 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);
+ }
+}
+
+/*! 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( '&amp;' , '&quot;', '&apos;' , '&lt;' , '&gt;', '&apos;' ), $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>";
+ }
+}
+
+
+
+
+
+/*! Base connector class
+ This class used as a base for all component specific connectors.
+ Can be used on its own to provide raw data.
+**/
+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 $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;
+
+ /*! 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);
+
+ if (!$type) $type="MySQL";
+ if (class_exists($type."DBDataWrapper",false)) $type.="DBDataWrapper";
+ if (!$item_type) $item_type="DataItem";
+ if (!$data_type) $data_type="DataProcessor";
+ if (!$render_type) $render_type="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"]);
+ $this->sql = new $this->names["db_class"]($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){
+ $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() {
+ 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::$sort_var]))
+ foreach($_GET[Connector::$filter_var] as $k => $v){
+ $k = $this->safe_field_name($k);
+ $this->request->set_filter($this->resolve_parameter($k),$v);
+ }
+
+ $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 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);
+ }
+}
+
+
+/*! 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);
+ }
+}
+
+
+
+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/connector/chart_connector.php b/codebase/connector/chart_connector.php
new file mode 100644
index 0000000..247d1e6
--- /dev/null
+++ b/codebase/connector/chart_connector.php
@@ -0,0 +1,18 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+
+ require_once("dataview_connector.php");
+
+
+/*! Connector class for DataView
+**/
+class ChartConnector extends DataViewConnector{
+ public function __construct($res,$type=false,$item_type=false,$data_type=false){
+ parent::__construct($res,$type,$item_type,$data_type);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/codebase/connector/combo_connector.php b/codebase/connector/combo_connector.php
new file mode 100644
index 0000000..35c66e9
--- /dev/null
+++ b/codebase/connector/combo_connector.php
@@ -0,0 +1,94 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+
+require_once("base_connector.php");
+/*! DataItem class for Combo component
+**/
+class ComboDataItem extends DataItem{
+ private $selected;//!< flag of selected option
+
+ function __construct($data,$config,$index){
+ parent::__construct($data,$config,$index);
+
+ $this->selected=false;
+ }
+ /*! mark option as selected
+ */
+ function select(){
+ $this->selected=true;
+ }
+ /*! return self as XML string, starting part
+ */
+ function to_xml_start(){
+ if ($this->skip) return "";
+
+ return "<option ".($this->selected?"selected='true'":"")."value='".$this->get_id()."'><![CDATA[".$this->data[$this->config->text[0]["name"]]."]]>";
+ }
+ /*! return self as XML string, ending part
+ */
+ function to_xml_end(){
+ if ($this->skip) return "";
+ return "</option>";
+ }
+}
+
+/*! Connector for the dhtmlxCombo
+**/
+class ComboConnector extends Connector{
+ private $filter; //!< filtering mask from incoming request
+ private $position; //!< position from incoming request
+
+ /*! 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){
+ if (!$item_type) $item_type="ComboDataItem";
+ parent::__construct($res,$type,$item_type,$data_type);
+ }
+
+ //parse GET scoope, all operations with incoming request must be done here
+ function parse_request(){
+ parent::parse_request();
+
+ if (isset($_GET["pos"])){
+ if (!$this->dload) //not critical, so just write a log message
+ LogMaster::log("Dyn loading request received, but server side was not configured to process dyn. loading. ");
+ else
+ $this->request->set_limit($_GET["pos"],$this->dload);
+ }
+
+ if (isset($_GET["mask"]))
+ $this->request->set_filter($this->config->text[0]["db_name"],$_GET["mask"]."%","LIKE");
+
+ LogMaster::log($this->request);
+ }
+
+
+ /*! renders self as xml, starting part
+ */
+ public function xml_start(){
+ if ($this->request->get_start())
+ return "<complete add='true'>";
+ else
+ return "<complete>";
+ }
+
+ /*! renders self as xml, ending part
+ */
+ public function xml_end(){
+ return "</complete>";
+ }
+}
+?> \ No newline at end of file
diff --git a/codebase/connector/convert.php b/codebase/connector/convert.php
new file mode 100644
index 0000000..f24922c
--- /dev/null
+++ b/codebase/connector/convert.php
@@ -0,0 +1,69 @@
+<?php
+/*
+ @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/connector/crosslink_connector.php b/codebase/connector/crosslink_connector.php
new file mode 100644
index 0000000..22ad83d
--- /dev/null
+++ b/codebase/connector/crosslink_connector.php
@@ -0,0 +1,141 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("data_connector.php");
+
+class DelayedConnector extends Connector{
+ protected $init_flag=false;//!< used to prevent rendering while initialization
+ private $data_mode=false;//!< flag to separate xml and data request modes
+ private $data_result=false;//<! store results of query
+
+ public function dataMode($name){
+ $this->data_mode = $name;
+ $this->data_result=array();
+ }
+ public function getDataResult(){
+ return $this->data_result;
+ }
+
+ public function render(){
+ if (!$this->init_flag){
+ $this->init_flag=true;
+ return "";
+ }
+ return parent::render();
+ }
+
+ protected function output_as_xml($res){
+ if ($this->data_mode){
+ while ($data=$this->sql->get_next($res)){
+ $this->data_result[]=$data[$this->data_mode];
+ }
+ }
+ else
+ return parent::output_as_xml($res);
+ }
+ protected function end_run(){
+ if (!$this->data_mode)
+ parent::end_run();
+ }
+}
+
+class CrossOptionsConnector extends Connector{
+ public $options, $link;
+ private $master_name, $link_name, $master_value;
+
+ public function __construct($res,$type=false,$item_type=false,$data_type=false){
+ $this->options = new OptionsConnector($res,$type,$item_type,$data_type);
+ $this->link = new DelayedConnector($res,$type,$item_type,$data_type);
+
+ EventMaster::attach_static("connectorInit",array($this, "handle"));
+ }
+ public function handle($conn){
+ if ($conn instanceof DelayedConnector) return;
+ if ($conn instanceof OptionsConnector) return;
+
+ $this->master_name = $this->link->get_config()->id["db_name"];
+ $this->link_name = $this->options->get_config()->id["db_name"];
+
+ $this->link->event->attach("beforeFilter",array($this, "get_only_related"));
+
+ if (isset($_GET["dhx_crosslink_".$this->link_name])){
+ $this->get_links($_GET["dhx_crosslink_".$this->link_name]);
+ die();
+ }
+
+ if (!$this->dload){
+ $conn->event->attach("beforeRender", array($this, "getOptions"));
+ $conn->event->attach("beforeRenderSet", array($this, "prepareConfig"));
+ }
+
+
+ $conn->event->attach("afterProcessing", array($this, "afterProcessing"));
+ }
+ public function prepareConfig($conn, $res, $config){
+ $config->add_field($this->link_name);
+ }
+ public function getOptions($data){
+ $this->link->dataMode($this->link_name);
+
+ $this->get_links($data->get_value($this->master_name));
+
+ $data->set_value($this->link_name, implode(",",$this->link->getDataResult()));
+ }
+ public function get_links($id){
+ $this->master_value = $id;
+ $this->link->render();
+ }
+ public function get_only_related($filters){
+ $index = $filters->index($this->master_name);
+ if ($index!==false){
+ $filters->rules[$index]["value"]=$this->master_value;
+ } else
+ $filters->add($this->master_name, $this->master_value, "=");
+ }
+ public function afterProcessing($action){
+ $status = $action->get_status();
+
+ $master_key = $action->get_value($this->master_name);
+ $link_key = $action->get_value($this->link_name);
+ $link_key = explode(',', $link_key);
+
+ if ($status == "inserted")
+ $master_key = $action->get_new_id();
+
+ switch ($status){
+ case "deleted":
+ $this->link->delete($master_key);
+ break;
+ case "updated":
+ //cross link options not loaded yet, so we can skip update
+ if (!array_key_exists($this->link_name, $action->get_data()))
+ break;
+ //else, delete old options and continue in insert section to add new values
+ $this->link->delete($master_key);
+ case "inserted":
+ for ($i=0; $i < sizeof($link_key); $i++)
+ if ($link_key[$i]!="")
+ $this->link->insert(array(
+ $this->link_name => $link_key[$i],
+ $this->master_name => $master_key
+ ));
+ break;
+ }
+ }
+}
+
+
+class JSONCrossOptionsConnector extends CrossOptionsConnector{
+ public $options, $link;
+ private $master_name, $link_name, $master_value;
+
+ public function __construct($res,$type=false,$item_type=false,$data_type=false){
+ $this->options = new JSONOptionsConnector($res,$type,$item_type,$data_type);
+ $this->link = new DelayedConnector($res,$type,$item_type,$data_type);
+
+ EventMaster::attach_static("connectorInit",array($this, "handle"));
+ }
+}
+?> \ No newline at end of file
diff --git a/codebase/connector/data_connector.php b/codebase/connector/data_connector.php
new file mode 100644
index 0000000..caa5369
--- /dev/null
+++ b/codebase/connector/data_connector.php
@@ -0,0 +1,500 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("base_connector.php");
+
+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);
+ }
+};
+
+/*! 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.">";
+ }
+}
+
+
+/*! 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="CommonDataItem";
+ if (!$data_type) $data_type="CommonDataProcessor";
+
+ $this->sections = array();
+
+ if (!$render_type) $render_type="RenderStrategy";
+ parent::__construct($res,$type,$item_type,$data_type,$render_type);
+
+ }
+
+ protected $sections;
+ public function add_section($name, $string){
+ $this->sections[$name] = $string;
+ }
+
+
+ //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;
+ }
+ } 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 = parent::xml_start();
+
+ foreach($this->sections as $k=>$v)
+ $start .= "<".$k.">".$v."</".$k.">\n";
+ return $start;
+ }
+};
+
+class JSONDataConnector extends DataConnector{
+
+ public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
+ if (!$item_type) $item_type="JSONCommonDataItem";
+ if (!$data_type) $data_type="CommonDataProcessor";
+ if (!$render_type) $render_type="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($this->options[$name]->render(),0,-2);
+ 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;
+ }
+}
+
+class JSONCommonDataItem extends DataItem{
+ /*! return self as XML string
+ */
+ function to_xml(){
+ if ($this->skip) return "";
+
+ $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 ($this->userdata !== false)
+ foreach ($this->userdata as $key => $value)
+ $data[$key]=$value;
+
+ return $data;
+ }
+}
+
+
+/*! 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);
+ }
+}
+
+
+
+class TreeCommonDataItem extends CommonDataItem{
+ protected $kids=-1;
+
+ 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)."'";
+
+ if ($this->kids === true)
+ $str .=" dhx_kids='1'";
+
+ return $str.">";
+ }
+
+ function has_kids(){
+ return $this->kids;
+ }
+
+ function set_kids($value){
+ $this->kids=$value;
+ }
+}
+
+
+class TreeDataConnector extends DataConnector{
+ protected $parent_name = 'parent';
+
+ /*! 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="TreeCommonDataItem";
+ if (!$data_type) $data_type="CommonDataProcessor";
+ 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
+ protected 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("0");
+
+ $this->request->set_limit(0,0); //netralize default reaction on dyn. loading mode
+ }
+
+ /*! renders self as xml, starting part
+ */
+ protected function xml_start(){
+ return "<data parent='".$this->request->get_relation()."'>";
+ }
+}
+
+
+class JSONTreeDataConnector extends TreeDataConnector{
+
+ public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
+ if (!$item_type) $item_type="JSONTreeCommonDataItem";
+ if (!$data_type) $data_type="CommonDataProcessor";
+ if (!$render_type) $render_type="JSONTreeRenderStrategy";
+ parent::__construct($res,$type,$item_type,$data_type,$render_type);
+ }
+
+ protected function output_as_xml($res){
+ $result = $this->render_set($res);
+ if ($this->simple) return $result;
+
+ $data = array();
+ $data["parent"] = $this->request->get_relation();
+ $data["data"] = $result;
+
+ $this->fill_collections();
+ if (!empty($this->options))
+ $data["collections"] = $this->options;
+
+ $data = json_encode($data);
+
+ // return as string
+ if ($this->as_string) return $data;
+
+ // output direct to response
+ $out = new OutputWriter($data, "");
+ $out->set_type("json");
+ $this->event->trigger("beforeOutput", $this, $out);
+ $out->output("", true, $this->encoding);
+ }
+
+ /*! 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[]=Array("id"=>$this->xmlentities($k), "value"=>$this->xmlentities($v));//'{"id":"'.$this->xmlentities($k).'", "value":"'.$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=""){
+ $options = array();
+ foreach ($this->options as $k=>$v) {
+ $name = $k;
+ if (!is_array($this->options[$name]))
+ $option=$this->options[$name]->render();
+ else
+ $option=$this->options[$name];
+ $options[$name] = $option;
+ }
+ $this->options = $options;
+ $this->extra_output .= "'collections':".json_encode($options);
+ }
+
+}
+
+
+class JSONTreeCommonDataItem extends TreeCommonDataItem{
+ /*! return self as XML string
+ */
+ function to_xml_start(){
+ if ($this->skip) return "";
+
+ $data = array( "id" => $this->get_id() );
+ for ($i=0; $i<sizeof($this->config->text); $i++){
+ $extra = $this->config->text[$i]["name"];
+ if (isset($this->data[$extra]))
+ $data[$extra]=$this->data[$extra];
+ }
+
+ if ($this->userdata !== false)
+ foreach ($this->userdata as $key => $value)
+ $data[$key]=$value;
+
+ if ($this->kids === true)
+ $data["dhx_kids"] = 1;
+
+ return $data;
+ }
+
+ function to_xml_end(){
+ return "";
+ }
+}
+
+
+?> \ No newline at end of file
diff --git a/codebase/connector/dataprocessor.php b/codebase/connector/dataprocessor.php
new file mode 100644
index 0000000..e75fc85
--- /dev/null
+++ b/codebase/connector/dataprocessor.php
@@ -0,0 +1,505 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+/*! Base DataProcessor handling
+**/
+
+require_once("xss_filter.php");
+
+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: {$operation} operation blocked");
+ $action->error();
+ } else {
+ $check = $this->connector->event->trigger("beforeProcessing",$action);
+ if (!$action->is_ready())
+ $this->check_exts($action,$mode);
+ $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>";
+ }
+
+}
+
+/*! 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){
+ 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."='".$v."' ";
+ }
+ $str.=">{$this->output}</action>";
+ return $str;
+ }
+ /*! 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/connector/dataview_connector.php b/codebase/connector/dataview_connector.php
new file mode 100644
index 0000000..41b7387
--- /dev/null
+++ b/codebase/connector/dataview_connector.php
@@ -0,0 +1,74 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("base_connector.php");
+
+/*! DataItem class for DataView component
+**/
+class DataViewDataItem extends DataItem{
+ /*! return self as XML string
+ */
+ function to_xml(){
+ if ($this->skip) return "";
+
+ $str="<item id='".$this->get_id()."' >";
+ for ($i=0; $i<sizeof($this->config->text); $i++){
+ $extra = $this->config->text[$i]["name"];
+ $str.="<".$extra."><![CDATA[".$this->data[$extra]."]]></".$extra.">";
+ }
+ return $str."</item>";
+ }
+}
+
+
+/*! Connector class for DataView
+**/
+class DataViewConnector 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){
+ if (!$item_type) $item_type="DataViewDataItem";
+ if (!$data_type) $data_type="DataProcessor";
+ parent::__construct($res,$type,$item_type,$data_type);
+ }
+
+ //parse GET scoope, all operations with incoming request must be done here
+ function parse_request(){
+ parent::parse_request();
+
+ if (isset($_GET["posStart"]) && isset($_GET["count"]))
+ $this->request->set_limit($_GET["posStart"],$_GET["count"]);
+ }
+
+ /*! renders self as xml, starting part
+ */
+ protected function xml_start(){
+ $attributes = "";
+ foreach($this->attributes as $k=>$v)
+ $attributes .= " ".$k."='".$v."'";
+
+ $start.= ">";
+ if ($this->dload){
+ if ($pos=$this->request->get_start())
+ return "<data pos='".$pos."'".$attributes.">";
+ else
+ return "<data total_count='".$this->sql->get_size($this->request)."'".$attributes.">";
+ }
+ else
+ return "<data".$attributes.">";
+ }
+}
+?> \ No newline at end of file
diff --git a/codebase/connector/db_adodb.php b/codebase/connector/db_adodb.php
new file mode 100644
index 0000000..5250c21
--- /dev/null
+++ b/codebase/connector/db_adodb.php
@@ -0,0 +1,72 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("db_common.php");
+/*! Implementation of DataWrapper for PostgreSQL
+**/
+class AdoDBDataWrapper extends DBDataWrapper{
+ protected $last_result;
+ public function query($sql){
+ LogMaster::log($sql);
+ if (is_array($sql)) {
+ $res = $this->connection->SelectLimit($sql['sql'], $sql['numrows'], $sql['offset']);
+ } else {
+ $res = $this->connection->Execute($sql);
+ }
+
+ if ($res===false) throw new Exception("ADODB operation failed\n".$this->connection->ErrorMsg());
+ $this->last_result = $res;
+ return $res;
+ }
+
+ public function get_next($res){
+ if (!$res)
+ $res = $this->last_result;
+
+ if ($res->EOF)
+ return false;
+
+ $row = $res->GetRowAssoc(false);
+ $res->MoveNext();
+ return $row;
+ }
+
+ protected function get_new_id(){
+ return $this->connection->Insert_ID();
+ }
+
+ public function escape($data){
+ return $this->connection->addq($data);
+ }
+
+ /*! 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.'`';
+ }
+
+
+ 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=array("sql"=>$sql,'numrows'=>$count, 'offset'=>$start);
+ }
+ return $sql;
+ }
+
+}
+?> \ No newline at end of file
diff --git a/codebase/connector/db_common.php b/codebase/connector/db_common.php
new file mode 100644
index 0000000..76748e7
--- /dev/null
+++ b/codebase/connector/db_common.php
@@ -0,0 +1,1069 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("tools.php");
+
+/*! 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 $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_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]);
+ }
+
+ }
+ }
+}
+
+/*! 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;
+ }
+
+
+}
+
+/*! 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,$config){
+ $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.");
+ }
+}
+
+/*! Common database abstraction class
+ Class provides base set of methods to access and change data in DB, class used as a base for DB-specific wrappers
+**/
+abstract class DBDataWrapper extends DataWrapper{
+ private $transaction = false; //!< type of transaction
+ private $sequence=false;//!< sequence name
+ private $sqls = array();//!< predefined sql actions
+
+
+ /*! assign named sql query
+ @param name
+ name of sql query
+ @param data
+ sql query text
+ */
+ public function attach($name,$data){
+ $name=strtolower($name);
+ $this->sqls[$name]=$data;
+ }
+ /*! replace vars in sql string with actual values
+
+ @param matches
+ array of field name matches
+ @return
+ value for the var name
+ */
+ public function get_sql_callback($matches){
+ return $this->escape($this->temp->get_value($matches[1]));
+ }
+ public function get_sql($name,$data){
+ $name=strtolower($name);
+ if (!array_key_exists($name,$this->sqls)) return "";
+
+
+ $str = $this->sqls[$name];
+ $this->temp = $data; //dirty
+ $str = preg_replace_callback('|\{([^}]+)\}|',array($this,"get_sql_callback"),$str);
+ unset ($this->temp); //dirty
+ return $str;
+ }
+
+ public function 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)
+ array_push($sql,$this->escape_name($this->config->relation_id["db_name"])." = '".$this->escape($relation)."'");
+ return implode(" AND ",$sql);
+ }
+ /*! convert sorting rules to sql string
+
+ @param by
+ set of sorting rules
+ @return
+ sql string for set of sorting rules
+ */
+ protected function build_order($by){
+ if (!sizeof($by)) return "";
+ $out = array();
+ for ($i=0; $i < sizeof($by); $i++)
+ if (is_string($by[$i]))
+ $out[] = $by[$i];
+ else if ($by[$i]["name"])
+ $out[]=$this->escape_name($by[$i]["name"])." ".$by[$i]["direction"];
+ return implode(",",$out);
+ }
+
+ /*! generates sql code for select operation
+
+ @param select
+ list of fields in select
+ @param from
+ table name
+ @param where
+ list of filtering rules
+ @param sort
+ list of sorting rules
+ @param start
+ start index of fetching
+ @param count
+ count of records to fetch
+ @return
+ sql string for select operation
+ */
+ protected function select_query($select,$from,$where,$sort,$start,$count){
+ if (!$from)
+ return $select;
+
+ $sql="SELECT ".$select." FROM ".$from;
+ if ($where) $sql.=" WHERE ".$where;
+ if ($sort) $sql.=" ORDER BY ".$sort;
+ if ($start || $count) $sql.=" LIMIT ".$start.",".$count;
+ return $sql;
+ }
+ /*! generates update sql
+
+ @param data
+ DataAction object
+ @param request
+ DataRequestConfig object
+ @return
+ sql string, which updates record with provided data
+ */
+ protected function update_query($data,$request){
+ $sql="UPDATE ".$request->get_source()." SET ";
+ $temp=array();
+ for ($i=0; $i < sizeof($this->config->text); $i++) {
+ $step=$this->config->text[$i];
+
+ if ($data->get_value($step["name"])===Null)
+ $step_value ="Null";
+ else
+ $step_value = "'".$this->escape($data->get_value($step["name"]))."'";
+ $temp[$i]= $this->escape_name($step["db_name"])."=". $step_value;
+ }
+ if ($relation = $this->config->relation_id["db_name"]){
+ $temp[]= $this->escape_name($relation)."='".$this->escape($data->get_value($relation))."'";
+ }
+ $sql.=implode(",",$temp)." WHERE ".$this->escape_name($this->config->id["db_name"])."='".$this->escape($data->get_id())."'";
+
+ //if we have limited set - set constraints
+ $where=$this->build_where($request->get_filters());
+ if ($where) $sql.=" AND (".$where.")";
+
+ return $sql;
+ }
+
+ /*! generates delete sql
+
+ @param data
+ DataAction object
+ @param request
+ DataRequestConfig object
+ @return
+ sql string, which delete record
+ */
+ protected function delete_query($data,$request){
+ $sql="DELETE FROM ".$request->get_source();
+ $sql.=" WHERE ".$this->escape_name($this->config->id["db_name"])."='".$this->escape($data->get_id())."'";
+
+ //if we have limited set - set constraints
+ $where=$this->build_where($request->get_filters());
+ if ($where) $sql.=" AND (".$where.")";
+
+ return $sql;
+ }
+
+ /*! generates insert sql
+
+ @param data
+ DataAction object
+ @param request
+ DataRequestConfig object
+ @return
+ sql string, which inserts new record with provided data
+ */
+ protected function insert_query($data,$request){
+ $temp_n=array();
+ $temp_v=array();
+ foreach($this->config->text as $k => $v){
+ $temp_n[$k]=$this->escape_name($v["db_name"]);
+ if ($data->get_value($v["name"])===Null)
+ $temp_v[$k]="Null";
+ else
+ $temp_v[$k]="'".$this->escape($data->get_value($v["name"]))."'";
+ }
+ if ($relation = $this->config->relation_id["db_name"]){
+ $temp_n[]=$this->escape_name($relation);
+ $temp_v[]="'".$this->escape($data->get_value($relation))."'";
+ }
+ if ($this->sequence){
+ $temp_n[]=$this->escape_name($this->config->id["db_name"]);
+ $temp_v[]=$this->sequence;
+ }
+
+ $sql="INSERT INTO ".$request->get_source()."(".implode(",",$temp_n).") VALUES (".implode(",",$temp_v).")";
+
+ return $sql;
+ }
+
+ /*! sets the transaction mode, used by dataprocessor
+
+ @param mode
+ mode name
+ */
+ public function set_transaction_mode($mode){
+ if ($mode!="none" && $mode!="global" && $mode!="record")
+ throw new Exception("Unknown transaction mode");
+ $this->transaction=$mode;
+ }
+ /*! returns true if global transaction mode was specified
+ @return
+ true if global transaction mode was specified
+ */
+ public function is_global_transaction(){
+ return $this->transaction == "global";
+ }
+ /*! returns true if record transaction mode was specified
+ @return
+ true if record transaction mode was specified
+ */
+ public function is_record_transaction(){
+ return $this->transaction == "record";
+ }
+
+
+ public function begin_transaction(){
+ $this->query("BEGIN");
+ }
+ public function commit_transaction(){
+ $this->query("COMMIT");
+ }
+ public function rollback_transaction(){
+ $this->query("ROLLBACK");
+ }
+
+ /*! exec sql string
+
+ @param sql
+ sql string
+ @return
+ sql result set
+ */
+ abstract public function query($sql);
+ /*! returns next record from result set
+
+ @param res
+ sql result set
+ @return
+ hash of data
+ */
+ abstract public function get_next($res);
+ /*! returns new id value, for newly inserted row
+ @return
+ new id value, for newly inserted row
+ */
+ abstract public function get_new_id();
+ /*! escape data to prevent sql injections
+ @param data
+ unescaped data
+ @return
+ escaped data
+ */
+ abstract public function escape($data);
+
+ /*! escape field name to prevent sql reserved words conflict
+ @param data
+ unescaped data
+ @return
+ escaped data
+ */
+ public function escape_name($data){
+ return $data;
+ }
+
+ /*! get list of tables in the database
+
+ @return
+ array of table names
+ */
+ public function tables_list() {
+ throw new Exception("Not implemented");
+ }
+
+ /*! returns list of fields for the table in question
+
+ @param table
+ name of table in question
+ @return
+ array of field names
+ */
+ public function fields_list($table) {
+ throw new Exception("Not implemented");
+ }
+
+}
+
+class ArrayDBDataWrapper extends DBDataWrapper{
+ public function get_next($res){
+ if ($res->index < sizeof($res->data))
+ return $res->data[$res->index++];
+ }
+ public function select($sql){
+ if ($this->config->relation_id["db_name"] == "") {
+ if ($sql->get_relation() == "0" || $sql->get_relation() == "") {
+ return new ArrayQueryWrapper($this->connection);
+ } else {
+ return new ArrayQueryWrapper(array());
+ }
+ }
+
+ $relation_id = $this->config->relation_id["db_name"];
+
+ for ($i = 0; $i < count($this->connection); $i++) {
+ $item = $this->connection[$i];
+ if (!isset($item[$relation_id])) continue;
+ if ($item[$relation_id] == $sql->get_relation())
+ $result[] = $item;
+
+ }
+
+ return new ArrayQueryWrapper($result);
+ }
+ public function query($sql){
+ throw new Exception("Not implemented");
+ }
+ public function escape($value){
+ throw new Exception("Not implemented");
+ }
+ public function get_new_id(){
+ throw new Exception("Not implemented");
+ }
+}
+
+class ArrayQueryWrapper{
+ public function __construct($data){
+ $this->data = $data;
+ $this->index = 0;
+ }
+}
+/*! 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/connector/db_excel.php b/codebase/connector/db_excel.php
new file mode 100644
index 0000000..6c0e347
--- /dev/null
+++ b/codebase/connector/db_excel.php
@@ -0,0 +1,190 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once('db_common.php');
+
+if (!defined('DHX_IGNORE_EMPTY_ROWS')) {
+ define('DHX_IGNORE_EMPTY_ROWS', true);
+}
+
+class ExcelDBDataWrapper extends DBDataWrapper {
+
+ public $emptyLimit = 10;
+ public function excel_data($points){
+ $path = $this->connection;
+ $excel = PHPExcel_IOFactory::createReaderForFile($path);
+ $excel = $excel->load($path);
+ $result = array();
+ $excelWS = $excel->getActiveSheet();
+
+ for ($i=0; $i < sizeof($points); $i++) {
+ $c = array();
+ preg_match("/^([a-zA-Z]+)(\d+)/", $points[$i], $c);
+ if (count($c) > 0) {
+ $col = PHPExcel_Cell::columnIndexFromString($c[1]) - 1;
+ $cell = $excelWS->getCellByColumnAndRow($col, (int)$c[2]);
+ $result[] = $cell->getValue();
+ }
+ }
+
+ return $result;
+ }
+ public function select($source) {
+ $path = $this->connection;
+ $excel = PHPExcel_IOFactory::createReaderForFile($path);
+ $excel->setReadDataOnly(false);
+ $excel = $excel->load($path);
+ $excRes = new ExcelResult();
+ $excelWS = $excel->getActiveSheet();
+ $addFields = true;
+
+ $coords = array();
+ if ($source->get_source() == '*') {
+ $coords['start_row'] = 0;
+ $coords['end_row'] = false;
+ } else {
+ $c = array();
+ preg_match("/^([a-zA-Z]+)(\d+)/", $source->get_source(), $c);
+ if (count($c) > 0) {
+ $coords['start_row'] = (int) $c[2];
+ } else {
+ $coords['start_row'] = 0;
+ }
+ $c = array();
+ preg_match("/:(.+)(\d+)$/U", $source->get_source(), $c);
+ if (count($c) > 0) {
+ $coords['end_row'] = (int) $c[2];
+ } else {
+ $coords['end_row'] = false;
+ }
+ }
+
+ $i = $coords['start_row'];
+ $end = 0;
+ while ((($coords['end_row'] == false)&&($end < $this->emptyLimit))||(($coords['end_row'] !== false)&&($i < $coords['end_row']))) {
+ $r = Array();
+ $emptyNum = 0;
+ for ($j = 0; $j < count($this->config->text); $j++) {
+ $col = PHPExcel_Cell::columnIndexFromString($this->config->text[$j]['name']) - 1;
+ $cell = $excelWS->getCellByColumnAndRow($col, $i);
+ if (PHPExcel_Shared_Date::isDateTime($cell)) {
+ $r[PHPExcel_Cell::stringFromColumnIndex($col)] = PHPExcel_Shared_Date::ExcelToPHP($cell->getValue());
+ } else if ($cell->getDataType() == 'f') {
+ $r[PHPExcel_Cell::stringFromColumnIndex($col)] = $cell->getCalculatedValue();
+ } else {
+ $r[PHPExcel_Cell::stringFromColumnIndex($col)] = $cell->getValue();
+ }
+ if ($r[PHPExcel_Cell::stringFromColumnIndex($col)] == '') {
+ $emptyNum++;
+ }
+ }
+ if ($emptyNum < count($this->config->text)) {
+ $r['id'] = $i;
+ $excRes->addRecord($r);
+ $end = 0;
+ } else {
+ if (DHX_IGNORE_EMPTY_ROWS == false) {
+ $r['id'] = $i;
+ $excRes->addRecord($r);
+ }
+ $end++;
+ }
+ $i++;
+ }
+ return $excRes;
+ }
+
+ public function query($sql) {
+ }
+
+ public function get_new_id() {
+ }
+
+ public function escape($data) {
+ }
+
+ public function get_next($res) {
+ return $res->next();
+ }
+
+}
+
+
+class ExcelResult {
+ private $rows;
+ private $currentRecord = 0;
+
+
+ // add record to output list
+ public function addRecord($file) {
+ $this->rows[] = $file;
+ }
+
+
+ // return next record
+ public function next() {
+ if ($this->currentRecord < count($this->rows)) {
+ $row = $this->rows[$this->currentRecord];
+ $this->currentRecord++;
+ return $row;
+ } else {
+ return false;
+ }
+ }
+
+
+ // sorts records under $sort array
+ public function sort($sort, $data) {
+ if (count($this->files) == 0) {
+ return $this;
+ }
+ // defines fields list if it's need
+ for ($i = 0; $i < count($sort); $i++) {
+ $fieldname = $sort[$i]['name'];
+ if (!isset($this->files[0][$fieldname])) {
+ if (isset($data[$fieldname])) {
+ $fieldname = $data[$fieldname]['db_name'];
+ $sort[$i]['name'] = $fieldname;
+ } else {
+ $fieldname = false;
+ }
+ }
+ }
+
+ // for every sorting field will sort
+ for ($i = 0; $i < count($sort); $i++) {
+ // if field, setted in sort parameter doesn't exist, continue
+ if ($sort[$i]['name'] == false) {
+ continue;
+ }
+ // sorting by current field
+ $flag = true;
+ while ($flag == true) {
+ $flag = false;
+ // checks if previous sorting fields are equal
+ for ($j = 0; $j < count($this->files) - 1; $j++) {
+ $equal = true;
+ for ($k = 0; $k < $i; $k++) {
+ if ($this->files[$j][$sort[$k]['name']] != $this->files[$j + 1][$sort[$k]['name']]) {
+ $equal = false;
+ }
+ }
+ // compares two records in list under current sorting field and sorting direction
+ if (((($this->files[$j][$sort[$i]['name']] > $this->files[$j + 1][$sort[$i]['name']])&&($sort[$i]['direction'] == 'ASC'))||(($this->files[$j][$sort[$i]['name']] < $this->files[$j + 1][$sort[$i]['name']])&&($sort[$i]['direction'] == 'DESC')))&&($equal == true)) {
+ $c = $this->files[$j];
+ $this->files[$j] = $this->files[$j+1];
+ $this->files[$j+1] = $c;
+ $flag = true;
+ }
+ }
+ }
+ }
+ return $this;
+ }
+
+}
+
+
+?> \ No newline at end of file
diff --git a/codebase/connector/db_filesystem.php b/codebase/connector/db_filesystem.php
new file mode 100644
index 0000000..b3d16d2
--- /dev/null
+++ b/codebase/connector/db_filesystem.php
@@ -0,0 +1,345 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once('db_common.php');
+require_once('tree_connector.php');
+
+/*
+Most execution time is a standart functions for workin with FileSystem: is_dir(), dir(), readdir(), stat()
+*/
+
+class FileSystemDBDataWrapper extends DBDataWrapper {
+
+
+ // returns list of files and directories
+ public function select($source) {
+ $relation = $this->getFileName($source->get_relation());
+ // for tree checks relation id and forms absolute path
+ if ($relation == '0') {
+ $relation = '';
+ } else {
+ $path = $source->get_source();
+ }
+ $path = $source->get_source();
+ $path = $this->getFileName($path);
+ $path = realpath($path);
+ if ($path == false) {
+ return new FileSystemResult();
+ }
+
+ if (strpos(realpath($path.'/'.$relation), $path) !== 0) {
+ return new FileSystemResult();
+ }
+ // gets files and directories list
+ $res = $this->getFilesList($path, $relation);
+ // sorts list
+ $res = $res->sort($source->get_sort_by(), $this->config->data);
+ return $res;
+ }
+
+
+ // gets files and directory list
+ private function getFilesList($path, $relation) {
+ $fileSystemTypes = FileSystemTypes::getInstance();
+ LogMaster::log("Query filesystem: ".$path);
+ $dir = opendir($path.'/'.$relation);
+ $result = new FileSystemResult();
+ // forms fields list
+ for ($i = 0; $i < count($this->config->data); $i++) {
+ $fields[] = $this->config->data[$i]['db_name'];
+ }
+ // for every file and directory of folder
+ while ($file = readdir($dir)) {
+ // . and .. should not be in output list
+ if (($file == '.')||($file == '..')) {
+ continue;
+ }
+ $newFile = array();
+ // parse file name as Array('name', 'ext', 'is_dir')
+ $fileNameExt = $this->parseFileName($path.'/'.$relation, $file);
+ // checks if file should be in output array
+ if (!$fileSystemTypes->checkFile($file, $fileNameExt)) {
+ continue;
+ }
+ // takes file stat if it's need
+ if ((in_array('size', $fields))||(in_array('date', $fields))) {
+ $fileInfo = stat($path.'/'.$file);
+ }
+
+ // for every field forms list of fields
+ for ($i = 0; $i < count($fields); $i++) {
+ $field = $fields[$i];
+ switch ($field) {
+ case 'filename':
+ $newFile['filename'] = $file;
+ break;
+ case 'full_filename':
+ $newFile['full_filename'] = $path."/".$file;
+ break;
+ case 'size':
+ $newFile['size'] = $fileInfo['size'];
+ break;
+ case 'extention':
+ $newFile['extention'] = $fileNameExt['ext'];
+ break;
+ case 'name':
+ $newFile['name'] = $fileNameExt['name'];
+ break;
+ case 'date':
+ $newFile['date'] = date("Y-m-d H:i:s", $fileInfo['ctime']);
+ break;
+ }
+ $newFile['relation_id'] = $relation.'/'.$file;
+ $newFile['safe_name'] = $this->setFileName($relation.'/'.$file);
+ $newFile['is_folder'] = $fileNameExt['is_dir'];
+ }
+ // add file in output list
+ $result->addFile($newFile);
+ }
+ return $result;
+ }
+
+
+ // replaces '.' and '_' in id
+ private function setFileName($filename) {
+ $filename = str_replace(".", "{-dot-}", $filename);
+ $filename = str_replace("_", "{-nizh-}", $filename);
+ return $filename;
+ }
+
+
+ // replaces '{-dot-}' and '{-nizh-}' in id
+ private function getFileName($filename) {
+ $filename = str_replace("{-dot-}", ".", $filename);
+ $filename = str_replace("{-nizh-}", "_", $filename);
+ return $filename;
+ }
+
+
+ // parses file name and checks if is directory
+ private function parseFileName($path, $file) {
+ $result = Array();
+ if (is_dir($path.'/'.$file)) {
+ $result['name'] = $file;
+ $result['ext'] = 'dir';
+ $result['is_dir'] = 1;
+ } else {
+ $pos = strrpos($file, '.');
+ $result['name'] = substr($file, 0, $pos);
+ $result['ext'] = substr($file, $pos + 1);
+ $result['is_dir'] = 0;
+ }
+ return $result;
+ }
+
+ public function query($sql) {
+ }
+
+ public function get_new_id() {
+ }
+
+ public function escape($data) {
+ }
+
+ public function get_next($res) {
+ return $res->next();
+ }
+
+}
+
+
+class FileSystemResult {
+ private $files;
+ private $currentRecord = 0;
+
+
+ // add record to output list
+ public function addFile($file) {
+ $this->files[] = $file;
+ }
+
+
+ // return next record
+ public function next() {
+ if ($this->currentRecord < count($this->files)) {
+ $file = $this->files[$this->currentRecord];
+ $this->currentRecord++;
+ return $file;
+ } else {
+ return false;
+ }
+ }
+
+
+ // sorts records under $sort array
+ public function sort($sort, $data) {
+ if (count($this->files) == 0) {
+ return $this;
+ }
+ // defines fields list if it's need
+ for ($i = 0; $i < count($sort); $i++) {
+ $fieldname = $sort[$i]['name'];
+ if (!isset($this->files[0][$fieldname])) {
+ if (isset($data[$fieldname])) {
+ $fieldname = $data[$fieldname]['db_name'];
+ $sort[$i]['name'] = $fieldname;
+ } else {
+ $fieldname = false;
+ }
+ }
+ }
+
+ // for every sorting field will sort
+ for ($i = 0; $i < count($sort); $i++) {
+ // if field, setted in sort parameter doesn't exist, continue
+ if ($sort[$i]['name'] == false) {
+ continue;
+ }
+ // sorting by current field
+ $flag = true;
+ while ($flag == true) {
+ $flag = false;
+ // checks if previous sorting fields are equal
+ for ($j = 0; $j < count($this->files) - 1; $j++) {
+ $equal = true;
+ for ($k = 0; $k < $i; $k++) {
+ if ($this->files[$j][$sort[$k]['name']] != $this->files[$j + 1][$sort[$k]['name']]) {
+ $equal = false;
+ }
+ }
+ // compares two records in list under current sorting field and sorting direction
+ if (((($this->files[$j][$sort[$i]['name']] > $this->files[$j + 1][$sort[$i]['name']])&&($sort[$i]['direction'] == 'ASC'))||(($this->files[$j][$sort[$i]['name']] < $this->files[$j + 1][$sort[$i]['name']])&&($sort[$i]['direction'] == 'DESC')))&&($equal == true)) {
+ $c = $this->files[$j];
+ $this->files[$j] = $this->files[$j+1];
+ $this->files[$j+1] = $c;
+ $flag = true;
+ }
+ }
+ }
+ }
+ return $this;
+ }
+
+}
+
+
+// singleton class for setting file types filter
+class FileSystemTypes {
+
+ static private $instance = NULL;
+ private $extentions = Array();
+ private $extentions_not = Array();
+ private $all = true;
+ private $patterns = Array();
+ // predefined types
+ private $types = Array(
+ 'image' => Array('jpg', 'jpeg', 'gif', 'png', 'tiff', 'bmp', 'psd', 'dir'),
+ 'document' => Array('txt', 'doc', 'docx', 'xls', 'xlsx', 'rtf', 'dir'),
+ 'web' => Array('php', 'html', 'htm', 'js', 'css', 'dir'),
+ 'audio' => Array('mp3', 'wav', 'ogg', 'dir'),
+ 'video' => Array('avi', 'mpg', 'mpeg', 'mp4', 'dir'),
+ 'only_dir' => Array('dir')
+ );
+
+
+ static function getInstance() {
+ if (self::$instance == NULL) {
+ self::$instance = new FileSystemTypes();
+ }
+ return self::$instance;
+ }
+
+ // sets array of extentions
+ public function setExtentions($ext) {
+ $this->all = false;
+ $this->extentions = $ext;
+ }
+
+ // adds one extention in array
+ public function addExtention($ext) {
+ $this->all = false;
+ $this->extentions[] = $ext;
+ }
+
+
+ // adds one extention which will not ouputed in array
+ public function addExtentionNot($ext) {
+ $this->extentions_not[] = $ext;
+ }
+
+
+ // returns array of extentions
+ public function getExtentions() {
+ return $this->extentions;
+ }
+
+ // adds regexp pattern
+ public function addPattern($pattern) {
+ $this->all = false;
+ $this->patterns[] = $pattern;
+ }
+
+ // clear extentions array
+ public function clearExtentions() {
+ $this->all = true;
+ $this->extentions = Array();
+ }
+
+ // clear regexp patterns array
+ public function clearPatterns() {
+ $this->all = true;
+ $this->patterns = Array();
+ }
+
+ // clear all filters
+ public function clearAll() {
+ $this->clearExtentions();
+ $this->clearPatterns();
+ }
+
+ // sets predefined type
+ public function setType($type, $clear = false) {
+ $this->all = false;
+ if ($type == 'all') {
+ $this->all = true;
+ return true;
+ }
+ if (isset($this->types[$type])) {
+ if ($clear) {
+ $this->clearExtentions();
+ }
+ for ($i = 0; $i < count($this->types[$type]); $i++) {
+ $this->extentions[] = $this->types[$type][$i];
+ }
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+
+ // check file under setted filter
+ public function checkFile($filename, $fileNameExt) {
+ if (in_array($fileNameExt['ext'], $this->extentions_not)) {
+ return false;
+ }
+ if ($this->all) {
+ return true;
+ }
+
+ if ((count($this->extentions) > 0)&&(!in_array($fileNameExt['ext'], $this->extentions))) {
+ return false;
+ }
+
+ for ($i = 0; $i < count($this->patterns); $i++) {
+ if (!preg_match($this->patterns[$i], $filename)) {
+ return false;
+ }
+ }
+ return true;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/codebase/connector/db_mssql.php b/codebase/connector/db_mssql.php
new file mode 100644
index 0000000..0acab93
--- /dev/null
+++ b/codebase/connector/db_mssql.php
@@ -0,0 +1,73 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("db_common.php");
+/*! MSSQL implementation of DataWrapper
+**/
+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/connector/db_mysqli.php b/codebase/connector/db_mysqli.php
new file mode 100644
index 0000000..6740a3b
--- /dev/null
+++ b/codebase/connector/db_mysqli.php
@@ -0,0 +1,56 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("db_common.php");
+
+class MySQLiDBDataWrapper extends MySQLDBDataWrapper{
+
+ public function query($sql){
+ LogMaster::log($sql);
+ $res = $this->connection->query($sql);
+ if ($res===false) throw new Exception("MySQL operation failed\n".$this->connection->error);
+ return $res;
+ }
+
+ public function get_next($res){
+ return $res->fetch_assoc();
+ }
+
+ public function get_new_id(){
+ return $this->connection->insert_id;
+ }
+
+ public function escape($data){
+ return $this->connection->real_escape_string($data);
+ }
+
+ public function tables_list() {
+ $result = $this->connection->query("SHOW TABLES");
+ if ($result===false) throw new Exception("MySQL operation failed\n".$this->connection->error);
+
+ $tables = array();
+ while ($table = $result->fetch_array()) {
+ $tables[] = $table[0];
+ }
+ return $tables;
+ }
+
+ public function fields_list($table) {
+ $result = $this->connection->query("SHOW COLUMNS FROM `".$table."`");
+ if ($result===false) throw new Exception("MySQL operation failed\n".$this->connection->error);
+ $fields = array();
+ while ($field = $result->fetch_array()) {
+ if ($field['Key'] == "PRI") {
+ $fields[$field[0]] = 1;
+ } else {
+ $fields[$field[0]] = 0;
+ }
+ }
+ return $fields;
+ }
+
+}
+
+?> \ No newline at end of file
diff --git a/codebase/connector/db_oracle.php b/codebase/connector/db_oracle.php
new file mode 100644
index 0000000..064d55a
--- /dev/null
+++ b/codebase/connector/db_oracle.php
@@ -0,0 +1,88 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("db_common.php");
+/*! Implementation of DataWrapper for Oracle
+**/
+class OracleDBDataWrapper extends DBDataWrapper{
+ private $last_id=""; //id of previously inserted record
+ private $insert_operation=false; //flag of insert operation
+
+ public function query($sql){
+ LogMaster::log($sql);
+ $stm = oci_parse($this->connection,$sql);
+ if ($stm===false) throw new Exception("Oracle - sql parsing failed\n".oci_error($this->connection));
+
+ $out = array(0=>null);
+ if($this->insert_operation){
+ oci_bind_by_name($stm,":outID",$out[0],999);
+ $this->insert_operation=false;
+ }
+
+
+ $mode = ($this->is_record_transaction() || $this->is_global_transaction())?OCI_DEFAULT:OCI_COMMIT_ON_SUCCESS;
+ $res=oci_execute($stm,$mode);
+ if ($res===false) throw new Exception("Oracle - sql execution failed\n".oci_error($this->connection));
+
+ $this->last_id=$out[0];
+
+ return $stm;
+ }
+
+ public function get_next($res){
+ $data = oci_fetch_assoc($res);
+ if ($data){
+ foreach ($data as $k => $v)
+ $data[strtolower($k)] = $v;
+ }
+ return $data;
+ }
+
+ public function get_new_id(){
+ /*
+ Oracle 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." returning ".$this->config->id["db_name"]." into :outID";
+ }
+
+ 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="SELECT * FROM ( select /*+ FIRST_ROWS(".$count.")*/dhx_table.*, ROWNUM rnum FROM (".$sql.") dhx_table where ROWNUM <= ".($count+$start)." ) where rnum >".$start;
+ return $sql;
+ }
+
+ public function escape($data){
+ /*
+ as far as I can see the only way to escape data is by using oci_bind_by_name
+ while it is neat solution in common case, it conflicts with existing SQL building logic
+ fallback to simple escaping
+ */
+ return str_replace("'","''",$data);
+ }
+
+ public function begin_transaction(){
+ //auto-start of transaction
+ }
+ public function commit_transaction(){
+ oci_commit($this->connection);
+ }
+ public function rollback_transaction(){
+ oci_rollback($this->connection);
+ }
+}
+?> \ No newline at end of file
diff --git a/codebase/connector/db_pdo.php b/codebase/connector/db_pdo.php
new file mode 100644
index 0000000..d1ad4d8
--- /dev/null
+++ b/codebase/connector/db_pdo.php
@@ -0,0 +1,75 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("db_common.php");
+/*! 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 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);
+ }
+
+}
+
+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/connector/db_phpcake.php b/codebase/connector/db_phpcake.php
new file mode 100644
index 0000000..97d94eb
--- /dev/null
+++ b/codebase/connector/db_phpcake.php
@@ -0,0 +1,85 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("db_common.php");
+
+//DataProcessor::$action_param ="dhx_editor_status";
+
+/*! Implementation of DataWrapper for PDO
+
+if you plan to use it for Oracle - use Oracle connection type instead
+**/
+class PHPCakeDBDataWrapper extends ArrayDBDataWrapper{
+ public function select($sql){
+ $source = $sql->get_source();
+ if (is_array($source)) //result of find
+ $res = $source;
+ else
+ $res = $this->connection->find("all");
+
+ if (sizeof($res)){
+ $name = get_class($this->connection);
+ $temp = array();
+ for ($i=sizeof($res)-1; $i>=0; $i--)
+ $temp[]=&$res[$i][$name];
+ }
+ 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){
+ $name = get_class($this->connection);
+ $save = array();
+ $temp_data = $data->get_data();
+ unset($temp_data[$this->config->id['db_name']]);
+ unset($temp_data["!nativeeditor_status"]);
+ $save[$name] = $temp_data;
+
+ if ($this->connection->save($save)){
+ $data->success($this->connection->getLastInsertID());
+ } else {
+ $data->set_response_attribute("details", $this->getErrorMessage());
+ $data->invalid();
+ }
+ }
+ public function delete($data,$source){
+ $id = $data->get_id();
+ $this->connection->delete($id);
+ $data->success();
+ }
+ public function update($data,$source){
+ $name = get_class($this->connection);
+ $save = array();
+ $save[$name] = &$data->get_data();
+
+ if ($this->connection->save($save)){
+ $data->success();
+ } else {
+ $data->set_response_attribute("details", $this->getErrorMessage());
+ $data->invalid();
+ }
+ }
+
+
+ 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/connector/db_phpci.php b/codebase/connector/db_phpci.php
new file mode 100644
index 0000000..b9a5879
--- /dev/null
+++ b/codebase/connector/db_phpci.php
@@ -0,0 +1,63 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("db_common.php");
+
+/*! Implementation of DataWrapper for PDO
+
+if you plan to use it for Oracle - use Oracle connection type instead
+**/
+class PHPCIDBDataWrapper 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) {
+ throw new Exception("CI - sql execution failed");
+ }
+
+ return new PHPCIResultSet($res);
+ }
+
+ 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/connector/db_phpyii.php b/codebase/connector/db_phpyii.php
new file mode 100644
index 0000000..f71d61a
--- /dev/null
+++ b/codebase/connector/db_phpyii.php
@@ -0,0 +1,91 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+
+require_once("db_common.php");
+
+class PHPYiiDBDataWrapper extends ArrayDBDataWrapper{
+ public function select($sql){
+ if (is_array($this->connection)) //result of findAll
+ $res = $this->connection;
+ else
+ $res = $this->connection->findAll();
+
+ if (sizeof($res)){
+ $temp = array();
+ foreach ($res as $obj)
+ $temp[]=$obj->getAttributes();
+ }
+ 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){
+ $name = get_class($this->connection);
+ $obj = new $name();
+
+ $this->fill_model_and_save($obj, $data);
+ }
+ public function delete($data,$source){
+ $obj = $this->connection->findByPk($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->findByPk($data->get_id());
+ $this->fill_model_and_save($obj, $data);
+ }
+
+ protected function fill_model_and_save($obj, $data){
+ $values = $data->get_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/connector/db_postgre.php b/codebase/connector/db_postgre.php
new file mode 100644
index 0000000..a7d1598
--- /dev/null
+++ b/codebase/connector/db_postgre.php
@@ -0,0 +1,73 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("db_common.php");
+/*! Implementation of DataWrapper for PostgreSQL
+**/
+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/connector/db_sasql.php b/codebase/connector/db_sasql.php
new file mode 100644
index 0000000..025f5ef
--- /dev/null
+++ b/codebase/connector/db_sasql.php
@@ -0,0 +1,54 @@
+<?php
+require_once("db_common.php");
+/*! SaSQL implementation of DataWrapper
+**/
+class SaSQLDBDataWrapper extends DBDataWrapper{
+ private $last_id=""; //!< ID of previously inserted record
+
+ public function query($sql){
+ LogMaster::log($sql);
+ $res=sasql_query($this->connection, $sql);
+ if ($res===false) throw new Exception("SaSQL operation failed\n".sasql_error($this->connection));
+ $this->last_result = $res;
+ return $res;
+ }
+
+ public function get_next($res){
+ if (!$res)
+ $res = $this->last_result;
+
+ return sasql_fetch_assoc($res);
+ }
+
+ public function get_new_id(){
+ return sasql_insert_id($this->connection);
+ }
+
+ protected function insert_query($data,$request){
+ $sql = parent::insert_query($data,$request);
+ $this->insert_operation=true;
+ return $sql;
+ }
+
+ 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;
+ return $sql;
+ }
+
+ public function escape($data){
+ return sasql_escape_string($this->connection, $data);
+ }
+
+ public function begin_transaction(){
+ $this->query("BEGIN TRAN");
+ }
+}
+?> \ No newline at end of file
diff --git a/codebase/connector/db_sqlite.php b/codebase/connector/db_sqlite.php
new file mode 100644
index 0000000..04df7e5
--- /dev/null
+++ b/codebase/connector/db_sqlite.php
@@ -0,0 +1,34 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("db_common.php");
+/*! SQLite implementation of DataWrapper
+**/
+class SQLiteDBDataWrapper extends DBDataWrapper{
+
+ public function query($sql){
+ LogMaster::log($sql);
+
+ $res = sqlite_query($this->connection,$sql);
+ if ($res === false)
+ throw new Exception("SQLLite - sql execution failed\n".sqlite_error_string(sqlite_last_error($this->connection)));
+
+ return $res;
+ }
+
+ public function get_next($res){
+ $data = sqlite_fetch_array($res, SQLITE_ASSOC);
+ return $data;
+ }
+
+ public function get_new_id(){
+ return sqlite_last_insert_rowid($this->connection);
+ }
+
+ public function escape($data){
+ return sqlite_escape_string($data);
+ }
+}
+?> \ No newline at end of file
diff --git a/codebase/connector/db_sqlite3.php b/codebase/connector/db_sqlite3.php
new file mode 100644
index 0000000..349490b
--- /dev/null
+++ b/codebase/connector/db_sqlite3.php
@@ -0,0 +1,33 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("db_common.php");
+/*! SQLite implementation of DataWrapper
+**/
+class SQLite3DBDataWrapper extends DBDataWrapper{
+
+ public function query($sql){
+ LogMaster::log($sql);
+
+ $res = $this->connection->query($sql);
+ if ($res === false)
+ throw new Exception("SQLLite - sql execution failed\n".$this->connection->lastErrorMsg());
+
+ return $res;
+ }
+
+ public function get_next($res){
+ return $res->fetchArray();
+ }
+
+ public function get_new_id(){
+ return $this->connection->lastInsertRowID();
+ }
+
+ public function escape($data){
+ return $this->connection->escapeString($data);
+ }
+}
+?> \ No newline at end of file
diff --git a/codebase/connector/db_sqlsrv.php b/codebase/connector/db_sqlsrv.php
new file mode 100644
index 0000000..1b27020
--- /dev/null
+++ b/codebase/connector/db_sqlsrv.php
@@ -0,0 +1,102 @@
+<?php
+/*
+This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
+to use it in non-GPL project. Please contact sales@dhtmlx.com for details
+*/
+?><?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("db_common.php");
+/*! MSSQL implementation of DataWrapper
+**/
+class SQLSrvDBDataWrapper 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);
+ if ($this->start_from)
+ $res = sqlsrv_query($this->connection,$sql, array(), array("Scrollable" => SQLSRV_CURSOR_STATIC));
+ else
+ $res = sqlsrv_query($this->connection,$sql);
+
+ if ($res === false){
+ $errors = sqlsrv_errors();
+ $message = Array();
+ foreach($errors as $error)
+ $message[]=$error["SQLSTATE"].$error["code"].$error["message"];
+ throw new Exception("SQLSrv operation failed\n".implode("\n\n", $message));
+ }
+
+ if ($this->insert_operation){
+ sqlsrv_next_result($res);
+ $last = sqlsrv_fetch_array($res);
+ $this->last_id = $last["dhx_id"];
+ sqlsrv_free_stmt($res);
+ }
+ if ($this->start_from)
+ $data = sqlsrv_fetch($res, SQLSRV_SCROLL_ABSOLUTE, $this->start_from-1);
+ return $res;
+ }
+
+ public function get_next($res){
+ $data = sqlsrv_fetch_array($res, SQLSRV_FETCH_ASSOC);
+ if ($data)
+ foreach ($data as $key => $value)
+ if (is_a($value, "DateTime"))
+ $data[$key] = $value->format("Y-m-d H:i");
+ return $data;
+ }
+
+ 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 SCOPE_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(){
+ sqlsrv_begin_transaction($this->connection);
+ }
+ public function commit_transaction(){
+ sqlsrv_commit($this->connection);
+ }
+ public function rollback_transaction(){
+ sqlsrv_rollback($this->connection);
+ }
+}
+?> \ No newline at end of file
diff --git a/codebase/connector/filesystem_item.php b/codebase/connector/filesystem_item.php
new file mode 100644
index 0000000..046ad98
--- /dev/null
+++ b/codebase/connector/filesystem_item.php
@@ -0,0 +1,19 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+
+class FileTreeDataItem extends TreeDataItem {
+
+ function has_kids(){
+ if ($this->data['is_folder'] == '1') {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+}
+
+?> \ No newline at end of file
diff --git a/codebase/connector/form_connector.php b/codebase/connector/form_connector.php
new file mode 100644
index 0000000..5eeea38
--- /dev/null
+++ b/codebase/connector/form_connector.php
@@ -0,0 +1,62 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("base_connector.php");
+
+/*! DataItem class for dhxForm component
+**/
+class FormDataItem extends DataItem{
+ /*! return self as XML string
+ */
+ function to_xml(){
+ if ($this->skip) return "";
+ $str="";
+ for ($i = 0; $i < count($this->config->data); $i++) {
+ $str .= "<".$this->config->data[$i]['name']."><![CDATA[".$this->data[$this->config->data[$i]['name']]."]]></".$this->config->data[$i]['name'].">";
+ }
+ return $str;
+ }
+}
+
+
+/*! Connector class for dhtmlxForm
+**/
+class FormConnector 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){
+ if (!$item_type) $item_type="FormDataItem";
+ if (!$data_type) $data_type="FormDataProcessor";
+ parent::__construct($res,$type,$item_type,$data_type);
+ }
+
+ //parse GET scoope, all operations with incoming request must be done here
+ function parse_request(){
+ parent::parse_request();
+ if (isset($_GET["id"]))
+ $this->request->set_filter($this->config->id["name"],$_GET["id"],"=");
+ else if (!$_POST["ids"])
+ throw new Exception("ID parameter is missed");
+ }
+
+}
+
+/*! DataProcessor class for dhxForm component
+**/
+class FormDataProcessor extends DataProcessor{
+
+}
+?> \ No newline at end of file
diff --git a/codebase/connector/grid_config.php b/codebase/connector/grid_config.php
new file mode 100644
index 0000000..24297b2
--- /dev/null
+++ b/codebase/connector/grid_config.php
@@ -0,0 +1,423 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+
+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/connector/grid_connector.php b/codebase/connector/grid_connector.php
new file mode 100644
index 0000000..9748dee
--- /dev/null
+++ b/codebase/connector/grid_connector.php
@@ -0,0 +1,262 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("base_connector.php");
+require_once("grid_config.php");
+
+//require_once("grid_dataprocessor.php");
+
+/*! 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->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"];
+ if (isset($this->cell_attrs[$name])){
+ $cattrs=$this->cell_attrs[$name];
+ foreach ($cattrs as $k => $v)
+ $str.=" ".$k."='".$this->xmlentities($v)."'";
+ }
+ $value = isset($this->data[$name]) ? $this->data[$name] : '';
+ $str.="><![CDATA[".$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>";
+ }
+}
+/*! 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="GridDataItem";
+ if (!$data_type) $data_type="GridDataProcessor";
+ if (!$render_type) $render_type="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( '&amp;' , '&quot;', '&apos;' , '&lt;' , '&gt;', '&apos;' ), $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"));
+ }
+}
+
+/*! 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/connector/keygrid_connector.php b/codebase/connector/keygrid_connector.php
new file mode 100644
index 0000000..3942ac2
--- /dev/null
+++ b/codebase/connector/keygrid_connector.php
@@ -0,0 +1,48 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("grid_connector.php");
+class KeyGridConnector extends GridConnector{
+ public function __construct($res,$type=false,$item_type=false,$data_type=false){
+ if (!$item_type) $item_type="GridDataItem";
+ if (!$data_type) $data_type="KeyGridDataProcessor";
+ parent::__construct($res,$type,$item_type,$data_type);
+
+ $this->event->attach("beforeProcessing",array($this,"before_check_key"));
+ $this->event->attach("afterProcessing",array($this,"after_check_key"));
+ }
+
+ public function before_check_key($action){
+ if ($action->get_value($this->config->id["name"])=="")
+ $action->error();
+ }
+ public function after_check_key($action){
+ if ($action->get_status()=="inserted" || $action->get_status()=="updated"){
+ $action->success($action->get_value($this->config->id["name"]));
+ $action->set_status("inserted");
+ }
+ }
+};
+
+class KeyGridDataProcessor 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 "__dummy__id__"; //ignore ID
+ $parts=explode("c",$data);
+ if ($parts[0]=="" && intval($parts[1])==$parts[1])
+ return $this->config->text[intval($parts[1])]["name"];
+ return $data;
+ }
+}
+
+
+?> \ No newline at end of file
diff --git a/codebase/connector/mixed_connector.php b/codebase/connector/mixed_connector.php
new file mode 100644
index 0000000..461d6ec
--- /dev/null
+++ b/codebase/connector/mixed_connector.php
@@ -0,0 +1,28 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("base_connector.php");
+
+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/connector/options_connector.php b/codebase/connector/options_connector.php
new file mode 100644
index 0000000..dc72eb2
--- /dev/null
+++ b/codebase/connector/options_connector.php
@@ -0,0 +1,45 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("base_connector.php");
+
+/*! DataItem class for dhxForm:options
+**/
+class OptionsDataItem extends DataItem{
+ /*! return self as XML string
+ */
+ function to_xml(){
+ if ($this->skip) return "";
+ $str ="";
+
+ $str .= "<item value=\"".$this->xmlentities($this->data[$this->config->data[0]['db_name']])."\" label=\"".$this->xmlentities($this->data[$this->config->data[1]['db_name']])."\" />";
+ return $str;
+ }
+}
+
+/*! Connector class for dhtmlxForm:options
+**/
+class SelectOptionsConnector 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){
+ if (!$item_type) $item_type="OptionsDataItem";
+ parent::__construct($res,$type,$item_type,$data_type);
+ }
+
+}
+
+?> \ No newline at end of file
diff --git a/codebase/connector/scheduler_connector.php b/codebase/connector/scheduler_connector.php
new file mode 100644
index 0000000..ee0cd20
--- /dev/null
+++ b/codebase/connector/scheduler_connector.php
@@ -0,0 +1,230 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("base_connector.php");
+require_once("data_connector.php");
+
+/*! DataItem class for Scheduler component
+**/
+class SchedulerDataItem extends DataItem{
+ /*! return self as XML string
+ */
+ function to_xml(){
+ if ($this->skip) return "";
+
+ $str="<event id='".$this->get_id()."' >";
+ $str.="<start_date><![CDATA[".$this->data[$this->config->text[0]["name"]]."]]></start_date>";
+ $str.="<end_date><![CDATA[".$this->data[$this->config->text[1]["name"]]."]]></end_date>";
+ $str.="<text><![CDATA[".$this->data[$this->config->text[2]["name"]]."]]></text>";
+ for ($i=3; $i<sizeof($this->config->text); $i++){
+ $extra = $this->config->text[$i]["name"];
+ $str.="<".$extra."><![CDATA[".$this->data[$extra]."]]></".$extra.">";
+ }
+ if ($this->userdata !== false)
+ foreach ($this->userdata as $key => $value)
+ $str.="<".$key."><![CDATA[".$value."]]></".$key.">";
+
+ return $str."</event>";
+ }
+}
+
+
+/*! Connector class for dhtmlxScheduler
+**/
+class SchedulerConnector extends Connector{
+
+ protected $extra_output="";//!< extra info which need to be sent to client side
+ protected $options=array();//!< hash of OptionsConnector
+
+
+ /*! 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;
+ }
+
+
+ /*! 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 be used for rendering.
+ */
+ public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
+ if (!$item_type) $item_type="SchedulerDataItem";
+ if (!$data_type) $data_type="SchedulerDataProcessor";
+ if (!$render_type) $render_type="RenderStrategy";
+ parent::__construct($res,$type,$item_type,$data_type,$render_type);
+ }
+
+ //parse GET scoope, all operations with incoming request must be done here
+ function parse_request(){
+ parent::parse_request();
+ if (count($this->config->text)){
+ if (isset($_GET["to"]))
+ $this->request->set_filter($this->config->text[0]["name"],$_GET["to"],"<");
+ if (isset($_GET["from"]))
+ $this->request->set_filter($this->config->text[1]["name"],$_GET["from"],">");
+ }
+ }
+}
+
+/*! DataProcessor class for Scheduler component
+**/
+class SchedulerDataProcessor extends DataProcessor{
+ function name_data($data){
+ if ($data=="start_date")
+ return $this->config->text[0]["db_name"];
+ if ($data=="id")
+ return $this->config->id["db_name"];
+ if ($data=="end_date")
+ return $this->config->text[1]["db_name"];
+ if ($data=="text")
+ return $this->config->text[2]["db_name"];
+
+ return $data;
+ }
+}
+
+
+class JSONSchedulerDataItem extends SchedulerDataItem{
+ /*! return self as XML string
+ */
+ function to_xml(){
+ if ($this->skip) return "";
+
+ $obj = array();
+ $obj['id'] = $this->get_id();
+ $obj['start_date'] = $this->data[$this->config->text[0]["name"]];
+ $obj['end_date'] = $this->data[$this->config->text[1]["name"]];
+ $obj['text'] = $this->data[$this->config->text[2]["name"]];
+ for ($i=3; $i<sizeof($this->config->text); $i++){
+ $extra = $this->config->text[$i]["name"];
+ $obj[$extra]=$this->data[$extra];
+ }
+
+ if ($this->userdata !== false)
+ foreach ($this->userdata as $key => $value)
+ $obj[$key]=$value;
+
+ return $obj;
+ }
+}
+
+
+class JSONSchedulerConnector extends SchedulerConnector {
+
+ protected $data_separator = ",";
+
+ /*! 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="JSONSchedulerDataItem";
+ if (!$data_type) $data_type="SchedulerDataProcessor";
+ if (!$render_type) $render_type="JSONRenderStrategy";
+ parent::__construct($res,$type,$item_type,$data_type,$render_type);
+ }
+
+ protected function xml_start() {
+ return '{ "data":';
+ }
+
+ protected function xml_end() {
+ $this->fill_collections();
+ $end = (!empty($this->extra_output)) ? ', "collections": {'.$this->extra_output.'}' : '';
+ foreach ($this->attributes as $k => $v)
+ $end.=", \"".$k."\":\"".$v."\"";
+ $end .= '}';
+ return $end;
+ }
+
+ /*! 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])){
+ $data = json_encode($this->options[$name]->render());
+ $option.=substr($data,1,-1);
+ } else
+ $option.=$this->options[$name];
+ $option.="]";
+ $options[] = $option;
+ }
+ $this->extra_output .= implode($this->data_separator, $options);
+ }
+
+
+ /*! 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;
+
+ $data=$this->xml_start().json_encode($result).$this->xml_end();
+
+ if ($this->as_string) return $data;
+
+ $out = new OutputWriter($data, "");
+ $out->set_type("json");
+ $this->event->trigger("beforeOutput", $this, $out);
+ $out->output("", true, $this->encoding);
+ }
+}
+?> \ No newline at end of file
diff --git a/codebase/connector/strategy.php b/codebase/connector/strategy.php
new file mode 100644
index 0000000..eb579b8
--- /dev/null
+++ b/codebase/connector/strategy.php
@@ -0,0 +1,500 @@
+<?php
+
+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_string($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;
+ }
+
+}
+
+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);
+ $output[]=$data->to_xml();
+ $index++;
+ }
+ $this->unmix($config, $mix);
+ return $output;
+ }
+
+}
+
+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;
+ $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_relation($data->get_id());
+ $output.=$this->render_set($conn->sql->select($sub_request), $name, $dload, $sep, $config);
+ }
+ $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]);
+ }
+}
+
+
+
+class JSONTreeRenderStrategy extends TreeRenderStrategy {
+
+ public function render_set($res, $name, $dload, $sep, $config,$mix){
+ $output=array();
+ $index=0;
+ $conn = $this->conn;
+ $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_relation($data->get_id());
+ $temp = $this->render_set($conn->sql->select($sub_request), $name, $dload, $sep, $config, $mix);
+ if (sizeof($temp))
+ $record["data"] = $temp;
+ }
+ $output[] = $record;
+ $index++;
+ }
+ $this->unmix($config, $mix);
+ return $output;
+ }
+
+}
+
+
+class MultitableTreeRenderStrategy extends TreeRenderStrategy {
+
+ private $level = 0;
+ private $max_level = null;
+ protected $sep = "#";
+
+ public function __construct($conn) {
+ parent::__construct($conn);
+ $conn->event->attach("beforeProcessing", Array($this, 'id_translate_before'));
+ $conn->event->attach("afterProcessing", Array($this, 'id_translate_after'));
+ }
+
+ public function set_separator($sep) {
+ $this->sep = $sep;
+ }
+
+ public function render_set($res, $name, $dload, $sep, $config, $mix){
+ $output="";
+ $index=0;
+ $conn = $this->conn;
+ $this->mix($config, $mix);
+ while ($data=$conn->sql->get_next($res)){
+ $data = $this->simple_mix($mix, $data);
+ $data[$config->id['name']] = $this->level_id($data[$config->id['name']]);
+ $data = new $name($data,$config,$index);
+ $conn->event->trigger("beforeRender",$data);
+ if (($this->max_level !== null)&&($conn->get_level() == $this->max_level)) {
+ $data->set_kids(false);
+ } else {
+ if ($data->has_kids()===-1)
+ $data->set_kids(true);
+ }
+ $output.=$data->to_xml_start();
+ $output.=$data->to_xml_end();
+ $index++;
+ }
+ $this->unmix($config, $mix);
+ return $output;
+ }
+
+
+ public function level_id($id, $level = null) {
+ return ($level === null ? $this->level : $level).$this->sep.$id;
+ }
+
+
+ /*! remove level prefix from id, parent id and set new id before processing
+ @param action
+ DataAction object
+ */
+ public function id_translate_before($action) {
+ $id = $action->get_id();
+ $id = $this->parse_id($id, false);
+ $action->set_id($id);
+ $action->set_value('tr_id', $id);
+ $action->set_new_id($id);
+ $pid = $action->get_value($this->conn->get_config()->relation_id['db_name']);
+ $pid = $this->parse_id($pid, false);
+ $action->set_value($this->conn->get_config()->relation_id['db_name'], $pid);
+ }
+
+
+ /*! add level prefix in id and new id after processing
+ @param action
+ DataAction object
+ */
+ public function id_translate_after($action) {
+ $id = $action->get_id();
+ $action->set_id($this->level_id($id));
+ $id = $action->get_new_id();
+ $action->success($this->level_id($id));
+ }
+
+
+ public function get_level($parent_name) {
+ if ($this->level) return $this->level;
+ if (!isset($_GET[$parent_name])) {
+ if (isset($_POST['ids'])) {
+ $ids = explode(",",$_POST["ids"]);
+ $id = $this->parse_id($ids[0]);
+ $this->level--;
+ }
+ $this->conn->get_request()->set_relation(false);
+ } else {
+ $id = $this->parse_id($_GET[$parent_name]);
+ $_GET[$parent_name] = $id;
+ }
+ return $this->level;
+ }
+
+
+ public function is_max_level() {
+ if (($this->max_level !== null) && ($this->level >= $this->max_level))
+ return true;
+ return false;
+ }
+ public function set_max_level($max_level) {
+ $this->max_level = $max_level;
+ }
+ public function parse_id($id, $set_level = true) {
+ $parts = explode('#', urldecode($id));
+ if (count($parts) === 2) {
+ $level = $parts[0] + 1;
+ $id = $parts[1];
+ } else {
+ $level = 0;
+ $id = '';
+ }
+ if ($set_level) $this->level = $level;
+ return $id;
+ }
+
+}
+
+
+class JSONMultitableTreeRenderStrategy extends MultitableTreeRenderStrategy {
+
+ public function render_set($res, $name, $dload, $sep, $config, $mix){
+ $output=array();
+ $index=0;
+ $conn = $this->conn;
+ $this->mix($config, $mix);
+ while ($data=$conn->sql->get_next($res)){
+ $data = $this->complex_mix($mix, $data);
+ $data[$config->id['name']] = $this->level_id($data[$config->id['name']]);
+ $data = new $name($data,$config,$index);
+ $conn->event->trigger("beforeRender",$data);
+
+ if ($this->is_max_level()) {
+ $data->set_kids(false);
+ } else {
+ if ($data->has_kids()===-1)
+ $data->set_kids(true);
+ }
+ $record = $data->to_xml_start($output);
+ $output[] = $record;
+ $index++;
+ }
+ $this->unmix($config, $mix);
+ return $output;
+ }
+
+}
+
+
+class GroupRenderStrategy extends RenderStrategy {
+
+ protected $id_postfix = '__{group_param}';
+
+ public function __construct($conn) {
+ parent::__construct($conn);
+ $conn->event->attach("beforeProcessing", Array($this, 'check_id'));
+ $conn->event->attach("onInit", Array($this, 'replace_postfix'));
+ }
+
+ public function render_set($res, $name, $dload, $sep, $config, $mix, $usemix = false){
+ $output="";
+ $index=0;
+ $conn = $this->conn;
+ if ($usemix) $this->mix($config, $mix);
+ while ($data=$conn->sql->get_next($res)){
+ if (isset($data[$config->id['name']])) {
+ $this->simple_mix($mix, $data);
+ $has_kids = false;
+ } else {
+ $data[$config->id['name']] = $data['value'].$this->id_postfix;
+ $data[$config->text[0]['name']] = $data['value'];
+ $has_kids = true;
+ }
+ $data = new $name($data,$config,$index);
+ $conn->event->trigger("beforeRender",$data);
+ if ($has_kids === false) {
+ $data->set_kids(false);
+ }
+
+ 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))&&($has_kids == true)){
+ $sub_request = new DataRequestConfig($conn->get_request());
+ $sub_request->set_relation(str_replace($this->id_postfix, "", $data->get_id()));
+ $output.=$this->render_set($conn->sql->select($sub_request), $name, $dload, $sep, $config, $mix, true);
+ }
+ $output.=$data->to_xml_end();
+ $index++;
+ }
+ if ($usemix) $this->unmix($config, $mix);
+ return $output;
+ }
+
+ public function check_id($action) {
+ if (isset($_GET['editing'])) {
+ $config = $this->conn->get_config();
+ $id = $action->get_id();
+ $pid = $action->get_value($config->relation_id['name']);
+ $pid = str_replace($this->id_postfix, "", $pid);
+ $action->set_value($config->relation_id['name'], $pid);
+ if (!empty($pid)) {
+ return $action;
+ } else {
+ $action->error();
+ $action->set_response_text("This record can't be updated!");
+ return $action;
+ }
+ } else {
+ return $action;
+ }
+ }
+
+ public function replace_postfix() {
+ if (isset($_GET['id'])) {
+ $_GET['id'] = str_replace($this->id_postfix, "", $_GET['id']);
+ }
+ }
+
+ public function get_postfix() {
+ return $this->id_postfix;
+ }
+
+}
+
+
+class JSONGroupRenderStrategy extends GroupRenderStrategy {
+
+ public function render_set($res, $name, $dload, $sep, $config, $mix, $usemix = false){
+ $output=array();
+ $index=0;
+ $conn = $this->conn;
+ if ($usemix) $this->mix($config, $mix);
+ while ($data=$conn->sql->get_next($res)){
+ if (isset($data[$config->id['name']])) {
+ $data = $this->complex_mix($mix, $data);
+ $has_kids = false;
+ } else {
+ $data[$config->id['name']] = $data['value'].$this->id_postfix;
+ $data[$config->text[0]['name']] = $data['value'];
+ $has_kids = true;
+ }
+ $data = new $name($data,$config,$index);
+ $conn->event->trigger("beforeRender",$data);
+ if ($has_kids === false) {
+ $data->set_kids(false);
+ }
+
+ 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))&&($has_kids == true)){
+ $sub_request = new DataRequestConfig($conn->get_request());
+ $sub_request->set_relation(str_replace($this->id_postfix, "", $data->get_id()));
+ $temp = $this->render_set($conn->sql->select($sub_request), $name, $dload, $sep, $config, $mix, true);
+ if (sizeof($temp))
+ $record["data"] = $temp;
+ }
+ $output[] = $record;
+ $index++;
+ }
+ if ($usemix) $this->unmix($config, $mix);
+ return $output;
+ }
+
+}
+
+
+?> \ No newline at end of file
diff --git a/codebase/connector/tools.php b/codebase/connector/tools.php
new file mode 100644
index 0000000..9383c75
--- /dev/null
+++ b/codebase/connector/tools.php
@@ -0,0 +1,267 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+
+/*! 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;
+ }
+}
+
+/*! 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;
+ }
+}
+
+/*! 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("LogMaster","error_log"),E_ALL);
+ set_exception_handler(array("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/connector/tree_connector.php b/codebase/connector/tree_connector.php
new file mode 100644
index 0000000..6383ff7
--- /dev/null
+++ b/codebase/connector/tree_connector.php
@@ -0,0 +1,229 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("base_connector.php");
+
+/*! DataItem class for Tree component
+**/
+
+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>";
+ }
+
+}
+
+require_once("filesystem_item.php");
+
+/*! Connector for the dhtmlxtree
+**/
+class TreeConnector extends Connector{
+ protected $parent_name = 'id';
+
+ /*! 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("0");
+
+ $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(){
+ return "</tree>";
+ }
+}
+
+
+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/connector/treedatagroup_connector.php b/codebase/connector/treedatagroup_connector.php
new file mode 100644
index 0000000..336915a
--- /dev/null
+++ b/codebase/connector/treedatagroup_connector.php
@@ -0,0 +1,89 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("data_connector.php");
+
+class TreeDataGroupConnector extends TreeDataConnector{
+
+ public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
+ if (!$render_type) $render_type="GroupRenderStrategy";
+ parent::__construct($res,$type,$item_type,$data_type,$render_type);
+ }
+
+ /*! if not isset $_GET[id] then it's top level
+ */
+ protected function set_relation() {
+ if (!isset($_GET[$this->parent_name])) $this->request->set_relation(false);
+ }
+
+ /*! if it's first level then distinct level
+ * else select by parent
+ */
+ protected function get_resource() {
+ $resource = null;
+ if (isset($_GET[$this->parent_name]))
+ $resource = $this->sql->select($this->request);
+ else
+ $resource = $this->sql->get_variants($this->config->relation_id['name'], $this->request);
+ return $resource;
+ }
+
+
+ /*! renders self as xml, starting part
+ */
+ public function xml_start(){
+ if (isset($_GET[$this->parent_name])) {
+ return "<data parent='".$_GET[$this->parent_name].$this->render->get_postfix()."'>";
+ } else {
+ return "<data parent='0'>";
+ }
+ }
+
+}
+
+
+
+
+class JSONTreeDataGroupConnector extends JSONTreeDataConnector{
+
+ public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
+ if (!$render_type) $render_type="JSONGroupRenderStrategy";
+ parent::__construct($res,$type,$item_type,$data_type,$render_type);
+ }
+
+ /*! if not isset $_GET[id] then it's top level
+ */
+ protected function set_relation() {
+ if (!isset($_GET[$this->parent_name])) $this->request->set_relation(false);
+ }
+
+ /*! if it's first level then distinct level
+ * else select by parent
+ */
+ protected function get_resource() {
+ $resource = null;
+ if (isset($_GET[$this->parent_name]))
+ $resource = $this->sql->select($this->request);
+ else
+ $resource = $this->sql->get_variants($this->config->relation_id['name'], $this->request);
+ return $resource;
+ }
+
+
+ /*! renders self as xml, starting part
+ */
+ public function xml_start(){
+ if (isset($_GET[$this->parent_name])) {
+ return "<data parent='".$_GET[$this->parent_name].$this->render->get_postfix()."'>";
+ } else {
+ return "<data parent='0'>";
+ }
+ }
+
+}
+
+
+
+?> \ No newline at end of file
diff --git a/codebase/connector/treedatamultitable_connector.php b/codebase/connector/treedatamultitable_connector.php
new file mode 100644
index 0000000..8dba8c6
--- /dev/null
+++ b/codebase/connector/treedatamultitable_connector.php
@@ -0,0 +1,91 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("data_connector.php");
+
+class TreeDataMultitableConnector extends TreeDataConnector{
+
+ protected $parent_name = 'parent';
+
+ public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
+ if (!$data_type) $data_type="TreeDataProcessor";
+ if (!$render_type) $render_type="MultitableTreeRenderStrategy";
+ parent::__construct($res,$type,$item_type,$data_type,$render_type);
+ }
+
+ public function render(){
+ $this->dload = true;
+ return parent::render();
+ }
+
+ /*! sets relation for rendering */
+ protected function set_relation() {
+ if (!isset($_GET[$this->parent_name]))
+ $this->request->set_relation(false);
+ }
+
+ public function xml_start(){
+ if (isset($_GET[$this->parent_name])) {
+ return "<data parent='".$this->render->level_id($_GET[$this->parent_name], $this->render->get_level() - 1)."'>";
+ } else {
+ return "<data parent='0'>";
+ }
+ }
+
+ /*! set maximum level of tree
+ @param max_level
+ maximum level
+ */
+ public function setMaxLevel($max_level) {
+ $this->render->set_max_level($max_level);
+ }
+
+ public function get_level() {
+ return $this->render->get_level($this->parent_name);
+ }
+
+}
+
+
+
+
+
+
+class JSONTreeDataMultitableConnector extends TreeDataMultitableConnector{
+
+ public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
+ if (!$item_type) $item_type="JSONTreeCommonDataItem";
+ if (!$data_type) $data_type="CommonDataProcessor";
+ if (!$render_type) $render_type="JSONMultitableTreeRenderStrategy";
+ parent::__construct($res,$type,$item_type,$data_type,$render_type);
+ }
+
+ protected function output_as_xml($res){
+ $result = $this->render_set($res);
+ if ($this->simple) return $result;
+
+ $data = array();
+ if (isset($_GET['parent']))
+ $data["parent"] = $this->render->level_id($_GET[$this->parent_name], $this->render->get_level() - 1);
+ else
+ $data["parent"] = "0";
+ $data["data"] = $result;
+
+ $result = json_encode($data);
+ if ($this->as_string) return $result;
+
+ $out = new OutputWriter($result, "");
+ $out->set_type("json");
+ $this->event->trigger("beforeOutput", $this, $out);
+ $out->output("", true, $this->encoding);
+ }
+
+ public function xml_start(){
+ return '';
+ }
+}
+
+
+?> \ No newline at end of file
diff --git a/codebase/connector/treegrid_connector.php b/codebase/connector/treegrid_connector.php
new file mode 100644
index 0000000..f074879
--- /dev/null
+++ b/codebase/connector/treegrid_connector.php
@@ -0,0 +1,120 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("grid_connector.php");
+
+/*! DataItem class for TreeGrid component
+**/
+class TreeGridDataItem extends GridDataItem{
+ private $kids=-1;//!< checked state
+
+ function __construct($data,$config,$index){
+ parent::__construct($data,$config,$index);
+ $this->im0=false;
+ }
+ /*! return id of parent record
+
+ @return
+ id of parent record
+ */
+ function get_parent_id(){
+ return $this->data[$this->config->relation_id["name"]];
+ }
+ /*! assign image to treegrid's item
+ longer description
+ @param img
+ relative path to the image
+ */
+ function set_image($img){
+ $this->set_cell_attribute($this->config->text[0]["name"],"image",$img);
+ }
+
+ /*! 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;
+ if ($value)
+ $this->set_row_attribute("xmlkids",$value);
+ }
+}
+/*! Connector for dhtmlxTreeGrid
+**/
+class TreeGridConnector extends GridConnector{
+ protected $parent_name = 'id';
+
+ /*! 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="TreeGridDataItem";
+ if (!$data_type) $data_type="TreeGridDataProcessor";
+ if (!$render_type) $render_type="TreeRenderStrategy";
+ parent::__construct($res,$type,$item_type,$data_type,$render_type);
+ }
+
+ /*! process treegrid specific options in incoming request */
+ 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("0");
+
+ $this->request->set_limit(0,0); //netralize default reaction on dyn. loading mode
+ }
+
+ /*! renders self as xml, starting part
+ */
+ protected function xml_start(){
+ return "<rows parent='".$this->request->get_relation()."'>";
+ }
+}
+
+/*! DataProcessor class for Grid component
+**/
+class TreeGridDataProcessor extends GridDataProcessor{
+
+ 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=="gr_pid")
+ return $this->config->relation_id["name"];
+ else return parent::name_data($data);
+ }
+}
+?> \ No newline at end of file
diff --git a/codebase/connector/treegridgroup_connector.php b/codebase/connector/treegridgroup_connector.php
new file mode 100644
index 0000000..cd8fdf2
--- /dev/null
+++ b/codebase/connector/treegridgroup_connector.php
@@ -0,0 +1,46 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("treegrid_connector.php");
+
+class TreeGridGroupConnector extends TreeGridConnector{
+
+ public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
+ if (!$render_type) $render_type="GroupRenderStrategy";
+ parent::__construct($res,$type,$item_type,$data_type,$render_type);
+ }
+
+ /*! if not isset $_GET[id] then it's top level
+ */
+ protected function set_relation() {
+ if (!isset($_GET[$this->parent_name])) $this->request->set_relation(false);
+ }
+
+ /*! if it's first level then distinct level
+ * else select by parent
+ */
+ protected function get_resource() {
+ $resource = null;
+ if (isset($_GET[$this->parent_name]))
+ $resource = $this->sql->select($this->request);
+ else
+ $resource = $this->sql->get_variants($this->config->relation_id['name'], $this->request);
+ return $resource;
+ }
+
+
+ /*! renders self as xml, starting part
+ */
+ protected function xml_start(){
+ if (isset($_GET[$this->parent_name])) {
+ return "<rows parent='".$_GET[$this->parent_name].$this->render->get_postfix()."'>";
+ } else {
+ return "<rows parent='0'>";
+ }
+ }
+
+}
+
+?> \ No newline at end of file
diff --git a/codebase/connector/treegridmultitable_connector.php b/codebase/connector/treegridmultitable_connector.php
new file mode 100644
index 0000000..c380ef6
--- /dev/null
+++ b/codebase/connector/treegridmultitable_connector.php
@@ -0,0 +1,70 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("treegrid_connector.php");
+
+class TreeGridMultitableConnector extends TreeGridConnector{
+
+ public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
+ $data_type="TreeGridMultitableDataProcessor";
+ if (!$render_type) $render_type="MultitableTreeRenderStrategy";
+ parent::__construct($res,$type,$item_type,$data_type,$render_type);
+ $this->render->set_separator("%23");
+ }
+
+ public function render(){
+ $this->dload = true;
+ return parent::render();
+ }
+
+ /*! sets relation for rendering */
+ protected function set_relation() {
+ if (!isset($_GET['id']))
+ $this->request->set_relation(false);
+ }
+
+ public function xml_start(){
+ if (isset($_GET['id'])) {
+ return "<rows parent='".$this->render->level_id($_GET['id'], $this->get_level() - 1)."'>";
+ } else {
+ return "<rows parent='0'>";
+ }
+ }
+
+ /*! set maximum level of tree
+ @param max_level
+ maximum level
+ */
+ public function setMaxLevel($max_level) {
+ $this->render->set_max_level($max_level);
+ }
+
+ public function get_level() {
+ return $this->render->get_level($this->parent_name);
+ }
+
+
+}
+
+
+class TreeGridMultitableDataProcessor extends DataProcessor {
+
+ function name_data($data){
+ if ($data=="gr_pid")
+ return $this->config->relation_id["name"];
+ if ($data=="gr_id")
+ return $this->config->id["name"];
+ preg_match('/^c([%\d]+)$/', $data, $data_num);
+ if (!isset($data_num[1])) return $data;
+ $data_num = $data_num[1];
+ if (isset($this->config->data[$data_num]["db_name"])) {
+ return $this->config->data[$data_num]["db_name"];
+ }
+ return $data;
+ }
+
+}
+
+?> \ No newline at end of file
diff --git a/codebase/connector/treegroup_connector.php b/codebase/connector/treegroup_connector.php
new file mode 100644
index 0000000..5266d0b
--- /dev/null
+++ b/codebase/connector/treegroup_connector.php
@@ -0,0 +1,46 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("tree_connector.php");
+
+class TreeGroupConnector extends TreeConnector{
+
+ public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
+ if (!$render_type) $render_type="GroupRenderStrategy";
+ parent::__construct($res,$type,$item_type,$data_type,$render_type);
+ }
+
+ /*! if not isset $_GET[id] then it's top level
+ */
+ protected function set_relation() {
+ if (!isset($_GET[$this->parent_name])) $this->request->set_relation(false);
+ }
+
+ /*! if it's first level then distinct level
+ * else select by parent
+ */
+ protected function get_resource() {
+ $resource = null;
+ if (isset($_GET[$this->parent_name]))
+ $resource = $this->sql->select($this->request);
+ else
+ $resource = $this->sql->get_variants($this->config->relation_id['name'], $this->request);
+ return $resource;
+ }
+
+
+ /*! renders self as xml, starting part
+ */
+ public function xml_start(){
+ if (isset($_GET[$this->parent_name])) {
+ return "<tree id='".$_GET[$this->parent_name].$this->render->get_postfix()."'>";
+ } else {
+ return "<tree id='0'>";
+ }
+ }
+
+}
+
+?> \ No newline at end of file
diff --git a/codebase/connector/treemultitable_connector.php b/codebase/connector/treemultitable_connector.php
new file mode 100644
index 0000000..09bb19b
--- /dev/null
+++ b/codebase/connector/treemultitable_connector.php
@@ -0,0 +1,51 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("tree_connector.php");
+
+class TreeMultitableConnector extends TreeConnector{
+
+ protected $parent_name = 'id';
+
+ public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
+ if (!$data_type) $data_type="TreeDataProcessor";
+ if (!$render_type) $render_type="MultitableTreeRenderStrategy";
+ parent::__construct($res,$type,$item_type,$data_type,$render_type);
+ }
+
+ public function render(){
+ $this->dload = true;
+ return parent::render();
+ }
+
+ /*! sets relation for rendering */
+ protected function set_relation() {
+ if (!isset($_GET[$this->parent_name]))
+ $this->request->set_relation(false);
+ }
+
+ public function xml_start(){
+ if (isset($_GET[$this->parent_name])) {
+ return "<tree id='".($this->render->level_id($_GET[$this->parent_name], $this->get_level() - 1))."'>";
+ } else {
+ return "<tree id='0'>";
+ }
+ }
+
+ /*! set maximum level of tree
+ @param max_level
+ maximum level
+ */
+ public function setMaxLevel($max_level) {
+ $this->render->set_max_level($max_level);
+ }
+
+ public function get_level() {
+ return $this->render->get_level($this->parent_name);
+ }
+
+}
+
+?> \ No newline at end of file
diff --git a/codebase/connector/update.php b/codebase/connector/update.php
new file mode 100644
index 0000000..dacc211
--- /dev/null
+++ b/codebase/connector/update.php
@@ -0,0 +1,266 @@
+<?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.
+*/
+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");
+ }
+ }
+}
+
+
+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/connector/xss_filter.php b/codebase/connector/xss_filter.php
new file mode 100644
index 0000000..ed0a309
--- /dev/null
+++ b/codebase/connector/xss_filter.php
@@ -0,0 +1,199 @@
+<?php
+
+// +----------------------------------------------------------------------+
+// | Copyright (c) 2001-2008 Liip AG |
+// +----------------------------------------------------------------------+
+// | Licensed under the Apache License, Version 2.0 (the "License"); |
+// | you may not use this file except in compliance with the License. |
+// | You may obtain a copy of the License at |
+// | http://www.apache.org/licenses/LICENSE-2.0 |
+// | Unless required by applicable law or agreed to in writing, software |
+// | distributed under the License is distributed on an "AS IS" BASIS, |
+// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
+// | implied. See the License for the specific language governing |
+// | permissions and limitations under the License. |
+// +----------------------------------------------------------------------+
+// | Author: Christian Stocker <christian.stocker@liip.ch> |
+// +----------------------------------------------------------------------+
+
+
+//original name was lx_externalinput_clean
+//renamed to prevent possible conflicts
+class dhx_externalinput_clean {
+ // 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("&amp;", "&lt;", "&gt;"), array("&amp;amp;", "&amp;lt;", "&amp;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;
+ }
+ }
+}
+
+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 dhx_externalinput_clean();
+ 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