summaryrefslogtreecommitdiffstats
path: root/codebase
diff options
context:
space:
mode:
authorMaksim <max@dhtmlx.com>2011-07-18 18:21:10 +0300
committerMaksim <max@dhtmlx.com>2011-07-18 18:21:10 +0300
commit5c2afae6cf595c981155ac68501ac2d1af77db54 (patch)
treeb07de6379fb3eb540df732a57061cafd05288599 /codebase
downloadconnector-php-5c2afae6cf595c981155ac68501ac2d1af77db54.zip
connector-php-5c2afae6cf595c981155ac68501ac2d1af77db54.tar.gz
connector-php-5c2afae6cf595c981155ac68501ac2d1af77db54.tar.bz2
* import of php connector v1.2
Diffstat (limited to 'codebase')
-rw-r--r--codebase/base_connector.php736
-rw-r--r--codebase/chart_connector.php18
-rw-r--r--codebase/combo_connector.php94
-rw-r--r--codebase/connector.js145
-rw-r--r--codebase/convert.php65
-rw-r--r--codebase/crosslink_connector.php123
-rw-r--r--codebase/data_connector.php163
-rw-r--r--codebase/dataprocessor.php493
-rw-r--r--codebase/dataview_connector.php69
-rw-r--r--codebase/db_common.php975
-rw-r--r--codebase/db_excel.php187
-rw-r--r--codebase/db_filesystem.php345
-rw-r--r--codebase/db_mssql.php70
-rw-r--r--codebase/db_mysqli.php56
-rw-r--r--codebase/db_oracle.php88
-rw-r--r--codebase/db_pdo.php69
-rw-r--r--codebase/db_postgre.php70
-rw-r--r--codebase/db_sqlite.php34
-rw-r--r--codebase/db_sqlsrv.php86
-rw-r--r--codebase/filesystem_item.php19
-rw-r--r--codebase/form_connector.php62
-rw-r--r--codebase/grid_config.php298
-rw-r--r--codebase/grid_connector.php273
-rw-r--r--codebase/keygrid_connector.php48
-rw-r--r--codebase/options_connector.php45
-rw-r--r--codebase/scheduler_connector.php125
-rw-r--r--codebase/tools.php254
-rw-r--r--codebase/tree_connector.php278
-rw-r--r--codebase/treegrid_connector.php165
-rw-r--r--codebase/treegridgroup_connector.php122
-rw-r--r--codebase/treegridmultitable_connector.php166
-rw-r--r--codebase/treegroup_connector.php118
-rw-r--r--codebase/treemultitable_connector.php152
-rw-r--r--codebase/update.php266
34 files changed, 6277 insertions, 0 deletions
diff --git a/codebase/base_connector.php b/codebase/base_connector.php
new file mode 100644
index 0000000..76351b9
--- /dev/null
+++ b/codebase/base_connector.php
@@ -0,0 +1,736 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("tools.php");
+require_once("db_common.php");
+require_once("dataprocessor.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){
+ $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
+ /*! 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;
+ }
+ /*! 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
+ */
+ protected 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"];
+ $str.=" ".$name."='".$this->xmlentities($this->data[$name])."'";
+ }
+ 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 )
+ 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
+
+ /*! 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){
+ $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";
+
+ $this->names=array(
+ "db_class"=>$type,
+ "item_class"=>$item_type,
+ "data_class"=>$data_type,
+ );
+
+ $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->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->config);
+ }
+
+
+ /*! 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);
+ $this->request->set_source($table);
+ }
+
+ protected 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();
+ }
+
+ /*! 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);
+
+ $this->parse_request();
+
+ 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->event->trigger("beforeSort",$wrap);
+ $wrap->store();
+
+ $wrap = new FilterInterface($this->request);
+ $this->event->trigger("beforeFilter",$wrap);
+ $wrap->store();
+
+ $this->output_as_xml( $this->sql->select($this->request) );
+ }
+ }
+ $this->end_run();
+ }
+
+ /*! 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;
+ }
+
+ 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);
+
+ $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["dhx_sort"]))
+ foreach($_GET["dhx_sort"] as $k => $v){
+ $k = $this->safe_field_name($k);
+ $this->request->set_sort($this->resolve_parameter($k),$v);
+ }
+
+ if (isset($_GET["dhx_filter"]))
+ foreach($_GET["dhx_filter"] as $k => $v){
+ $k = $this->safe_field_name($k);
+ $this->request->set_filter($this->resolve_parameter($k),$v);
+ }
+
+
+ }
+
+ /*! 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){
+ $output="";
+ $index=0;
+ $this->event->trigger("beforeRenderSet",$this,$res,$this->config);
+ while ($data=$this->sql->get_next($res)){
+ $data = new $this->names["item_class"]($data,$this->config,$index);
+ if ($data->get_id()===false)
+ $data->set_id($this->uuid());
+ $this->event->trigger("beforeRender",$data);
+ $output.=$data->to_xml().$this->data_separator;
+ $index++;
+ }
+ return $output;
+ }
+
+ /*! output fetched data as XML
+ @param res
+ DB resultset
+ */
+ protected function output_as_xml($res){
+ $start="<?xml version='1.0' encoding='".$this->encoding."' ?>".$this->xml_start();
+ $end=$this->render_set($res).$this->xml_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(){
+ return "<data>";
+ }
+ /*! renders self as xml, ending part
+ */
+ protected function xml_end(){
+ return "</data>";
+ }
+
+
+ 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"));
+ }
+}
+
+
+/*! 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);
+ }
+}
+
+?>
diff --git a/codebase/chart_connector.php b/codebase/chart_connector.php
new file mode 100644
index 0000000..247d1e6
--- /dev/null
+++ b/codebase/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/combo_connector.php b/codebase/combo_connector.php
new file mode 100644
index 0000000..35c66e9
--- /dev/null
+++ b/codebase/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.js b/codebase/connector.js
new file mode 100644
index 0000000..c89a654
--- /dev/null
+++ b/codebase/connector.js
@@ -0,0 +1,145 @@
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+if (window.dhtmlXGridObject){
+ dhtmlXGridObject.prototype._init_point_connector=dhtmlXGridObject.prototype._init_point;
+ dhtmlXGridObject.prototype._init_point=function(){
+ //make separate config array for each grid
+ this._con_f_used = [].concat(this._con_f_used);
+ dhtmlXGridObject.prototype._con_f_used=[];
+
+ var clear_url=function(url){
+ url=url.replace(/(\?|\&)connector[^\f]*/g,"");
+ return url+(url.indexOf("?")!=-1?"&":"?")+"connector=true"+(this.hdr.rows.length > 0 ? "&dhx_no_header=1":"");
+ };
+ var combine_urls=function(url){
+ return clear_url.call(this,url)+(this._connector_sorting||"")+(this._connector_filter||"");
+ };
+ var sorting_url=function(url,ind,dir){
+ this._connector_sorting="&dhx_sort["+ind+"]="+dir;
+ return combine_urls.call(this,url);
+ };
+ var filtering_url=function(url,inds,vals){
+ for (var i=0; i<inds.length; i++)
+ inds[i]="dhx_filter["+inds[i]+"]="+encodeURIComponent(vals[i]);
+ this._connector_filter="&"+inds.join("&");
+ return combine_urls.call(this,url);
+ };
+ this.attachEvent("onCollectValues",function(ind){
+ if (this._con_f_used[ind]){
+ if (typeof(this._con_f_used[ind]) == "object")
+ return this._con_f_used[ind];
+ else
+ return false;
+ }
+ return true;
+ });
+ this.attachEvent("onDynXLS",function(){
+ this.xmlFileUrl=combine_urls.call(this,this.xmlFileUrl);
+ return true;
+ });
+ this.attachEvent("onBeforeSorting",function(ind,type,dir){
+ if (type=="connector"){
+ var self=this;
+ this.clearAndLoad(sorting_url.call(this,this.xmlFileUrl,ind,dir),function(){
+ self.setSortImgState(true,ind,dir);
+ });
+ return false;
+ }
+ return true;
+ });
+ this.attachEvent("onFilterStart",function(a,b){
+ if (this._con_f_used.length){
+ this.clearAndLoad(filtering_url.call(this,this.xmlFileUrl,a,b));
+ return false;
+ }
+ return true;
+ });
+ this.attachEvent("onXLE",function(a,b,c,xml){
+ if (!xml) return;
+ });
+
+ if (this._init_point_connector) this._init_point_connector();
+ };
+ dhtmlXGridObject.prototype._con_f_used=[];
+ dhtmlXGridObject.prototype._in_header_connector_text_filter=function(t,i){
+ if (!this._con_f_used[i])
+ this._con_f_used[i]=1;
+ return this._in_header_text_filter(t,i);
+ };
+ dhtmlXGridObject.prototype._in_header_connector_select_filter=function(t,i){
+ if (!this._con_f_used[i])
+ this._con_f_used[i]=2;
+ return this._in_header_select_filter(t,i);
+ };
+ dhtmlXGridObject.prototype.load_connector=dhtmlXGridObject.prototype.load;
+ dhtmlXGridObject.prototype.load=function(url, call, type){
+ if (!this._colls_loaded && this.cellType){
+ var ar=[];
+ for (var i=0; i < this.cellType.length; i++)
+ if (this.cellType[i].indexOf("co")==0 || this._con_f_used[i]==2) ar.push(i);
+ if (ar.length)
+ arguments[0]+=(arguments[0].indexOf("?")!=-1?"&":"?")+"connector=true&dhx_colls="+ar.join(",");
+ }
+ return this.load_connector.apply(this,arguments);
+ };
+ dhtmlXGridObject.prototype._parseHead_connector=dhtmlXGridObject.prototype._parseHead;
+ dhtmlXGridObject.prototype._parseHead=function(url, call, type){
+ this._parseHead_connector.apply(this,arguments);
+ if (!this._colls_loaded){
+ var cols = this.xmlLoader.doXPath("./coll_options", arguments[0]);
+ for (var i=0; i < cols.length; i++){
+ var f = cols[i].getAttribute("for");
+ var v = [];
+ var combo=null;
+ if (this.cellType[f] == "combo")
+ combo = this.getColumnCombo(f);
+ else if (this.cellType[f].indexOf("co")==0)
+ combo=this.getCombo(f);
+
+ var os = this.xmlLoader.doXPath("./item",cols[i]);
+ var opts = [];
+ for (var j=0; j<os.length; j++){
+ var val=os[j].getAttribute("value");
+
+ if (combo){
+ var lab=os[j].getAttribute("label")||val;
+
+ if (combo.addOption)
+ opts.push([val, lab]);
+ else
+ combo.put(val,lab);
+
+ v[v.length]=lab;
+ } else
+ v[v.length]=val;
+ }
+ if (opts.length)
+ combo.addOption(opts);
+
+ if (this._con_f_used[f*1])
+ this._con_f_used[f*1]=v;
+ };
+ this._colls_loaded=true;
+ }
+ };
+
+
+
+
+}
+
+if (window.dataProcessor){
+ dataProcessor.prototype.init_original=dataProcessor.prototype.init;
+ dataProcessor.prototype.init=function(obj){
+ this.init_original(obj);
+ obj._dataprocessor=this;
+
+ this.setTransactionMode("POST",true);
+ this.serverProcessor+=(this.serverProcessor.indexOf("?")!=-1?"&":"?")+"editing=true";
+ };
+};
+dhtmlxError.catchError("LoadXML",function(a,b,c){
+ alert(c[0].responseText);
+});
diff --git a/codebase/convert.php b/codebase/convert.php
new file mode 100644
index 0000000..65adea0
--- /dev/null
+++ b/codebase/convert.php
@@ -0,0 +1,65 @@
+<?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){
+
+ 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_replace("<rows>","<rows profile='color'>",str_replace("</head>", "</columns></head>", str_replace("<head>", "<head><columns>",$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/crosslink_connector.php b/codebase/crosslink_connector.php
new file mode 100644
index 0000000..f92d0c5
--- /dev/null
+++ b/codebase/crosslink_connector.php
@@ -0,0 +1,123 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+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":
+ $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;
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/codebase/data_connector.php b/codebase/data_connector.php
new file mode 100644
index 0000000..042a20b
--- /dev/null
+++ b/codebase/data_connector.php
@@ -0,0 +1,163 @@
+<?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"]))
+ $data[$_POST["id"]] = $_POST;
+ 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 "";
+
+ $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 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){
+ if (!$item_type) $item_type="CommonDataItem";
+ if (!$data_type) $data_type="CommonDataProcessor";
+ parent::__construct($res,$type,$item_type,$data_type);
+ }
+
+ protected function parse_request_mode(){
+ //do nothing, at least for now
+ }
+
+ //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();
+ }
+ }
+
+ /*! renders self as xml, starting part
+ */
+ protected function xml_start(){
+ return "<data>";
+ }
+};
+
+class JSONDataConnector extends DataConnector{
+ public function __construct($res,$type=false,$item_type=false,$data_type=false){
+ if (!$item_type) $item_type="JSONCommonDataItem";
+ if (!$data_type) $data_type="CommonDataProcessor";
+ $this->data_separator = ",\n";
+ parent::__construct($res,$type,$item_type,$data_type);
+ }
+
+ protected function output_as_xml($res){
+ $start = "[\n";
+ $end = substr($this->render_set($res),0,-2)."\n]";
+
+ $out = new OutputWriter($start, $end);
+ $out->set_type("json");
+ $this->event->trigger("beforeOutput", $this, $out);
+ $out->output("", true, $this->encoding);
+ }
+}
+
+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];
+ }
+ return json_encode($data);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/codebase/dataprocessor.php b/codebase/dataprocessor.php
new file mode 100644
index 0000000..348c8e2
--- /dev/null
+++ b/codebase/dataprocessor.php
@@ -0,0 +1,493 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+/*! Base DataProcessor handling
+**/
+class DataProcessor{
+ protected $connector;//!< Connector instance
+ protected $config;//!< DataConfig instance
+ protected $request;//!< DataRequestConfig instance
+
+ /*! 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]=$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."_!nativeeditor_status"]))
+ throw new Exception("Status of record [{$rid}] not found in incoming request");
+ return $_POST[$rid."_!nativeeditor_status"];
+ }
+ /*! 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){
+ $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){
+ $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);
+ $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 = $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/dataview_connector.php b/codebase/dataview_connector.php
new file mode 100644
index 0000000..0c4ca0c
--- /dev/null
+++ b/codebase/dataview_connector.php
@@ -0,0 +1,69 @@
+<?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(){
+ if ($this->dload){
+ if ($pos=$this->request->get_start())
+ return "<data pos='".$pos."'>";
+ else
+ return "<data total_count='".$this->sql->get_size($this->request)."'>";
+ }
+ else
+ return "<data>";
+ }
+}
+?> \ No newline at end of file
diff --git a/codebase/db_common.php b/codebase/db_common.php
new file mode 100644
index 0000000..a37d54d
--- /dev/null
+++ b/codebase/db_common.php
@@ -0,0 +1,975 @@
+<?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{
+ $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,$operation=false){
+ 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){
+ $this->source=trim($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){
+ $sql= preg_replace("/[ \n\t]+limit[\n ,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);
+
+ $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
+ }
+
+ /*! 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
+ */
+ private 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 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 ($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){
+ $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(),$request->get_relation());
+ 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(),$request->get_relation());
+ 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 protected 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 protected 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");
+ }
+
+}
+/*! 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);
+ }
+
+ protected 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/db_excel.php b/codebase/db_excel.php
new file mode 100644
index 0000000..09db5b0
--- /dev/null
+++ b/codebase/db_excel.php
@@ -0,0 +1,187 @@
+<?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 = $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 ($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;
+ }
+
+ protected function query($sql) {
+ }
+
+ protected 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/db_filesystem.php b/codebase/db_filesystem.php
new file mode 100644
index 0000000..7817be5
--- /dev/null
+++ b/codebase/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;
+ }
+
+ protected function query($sql) {
+ }
+
+ protected 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/db_mssql.php b/codebase/db_mssql.php
new file mode 100644
index 0000000..e082c03
--- /dev/null
+++ b/codebase/db_mssql.php
@@ -0,0 +1,70 @@
+<?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);
+ }
+
+ protected 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){
+ $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/db_mysqli.php b/codebase/db_mysqli.php
new file mode 100644
index 0000000..806fcda
--- /dev/null
+++ b/codebase/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();
+ }
+
+ protected 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/db_oracle.php b/codebase/db_oracle.php
new file mode 100644
index 0000000..5dcbd71
--- /dev/null
+++ b/codebase/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){
+ if (array_key_exists("VALUE",$data))
+ $data["value"]=$data["VALUE"];
+ if (array_key_exists("LABEL",$data))
+ $data["label"]=$data["LABEL"];
+ }
+
+ return $data;
+ }
+
+ protected 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){
+ $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/db_pdo.php b/codebase/db_pdo.php
new file mode 100644
index 0000000..281b23d
--- /dev/null
+++ b/codebase/db_pdo.php
@@ -0,0 +1,69 @@
+<?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) throw new Exception("PDO - sql execution failed\n".$this->connection->errorInfo());
+
+ return new PDOResultSet($res);
+ }
+
+ protected function select_query($select,$from,$where,$sort,$start,$count){
+ $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;
+ }
+
+ protected 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/db_postgre.php b/codebase/db_postgre.php
new file mode 100644
index 0000000..f944714
--- /dev/null
+++ b/codebase/db_postgre.php
@@ -0,0 +1,70 @@
+<?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){
+ $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);
+ }
+
+ protected 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/db_sqlite.php b/codebase/db_sqlite.php
new file mode 100644
index 0000000..cd8de9d
--- /dev/null
+++ b/codebase/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;
+ }
+
+ protected 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/db_sqlsrv.php b/codebase/db_sqlsrv.php
new file mode 100644
index 0000000..1dfd4ef
--- /dev/null
+++ b/codebase/db_sqlsrv.php
@@ -0,0 +1,86 @@
+<?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 ($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);
+ foreach ($data as $key => $value)
+ if (is_a($value, "DateTime"))
+ $data[$key] = $value->format("Y-m-d H:i");
+ return $data;
+ }
+
+ protected 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){
+ $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/filesystem_item.php b/codebase/filesystem_item.php
new file mode 100644
index 0000000..046ad98
--- /dev/null
+++ b/codebase/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/form_connector.php b/codebase/form_connector.php
new file mode 100644
index 0000000..f89cf03
--- /dev/null
+++ b/codebase/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]['db_name']."><![CDATA[".$this->data[$this->config->data[$i]['db_name']]."]]></".$this->config->data[$i]['db_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/grid_config.php b/codebase/grid_config.php
new file mode 100644
index 0000000..5bb99c5
--- /dev/null
+++ b/codebase/grid_config.php
@@ -0,0 +1,298 @@
+<?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;
+
+ 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);
+ }
+
+ /*! adds header configuration in output XML
+ */
+ public function attachHeaderToXML($conn, $out) {
+ if (!$conn->is_first_call()) return; //render head only for first call
+
+
+ $str = '<head>';
+ 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 .= '>'.$this->headerNames[$i].'</column>';
+ }
+ $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>';
+ }
+ $str .= '</head>';
+
+ $out->add($str);
+ }
+}
+
+?> \ No newline at end of file
diff --git a/codebase/grid_connector.php b/codebase/grid_connector.php
new file mode 100644
index 0000000..2fe043e
--- /dev/null
+++ b/codebase/grid_connector.php
@@ -0,0 +1,273 @@
+<?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
+ protected $userdata;
+
+ function __construct($data,$name,$index=0){
+ parent::__construct($data,$name,$index);
+
+ $this->row_attrs=array();
+ $this->cell_attrs=array();
+ $this->userdata=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 userdata section for the item
+
+ @param name
+ name of userdata
+ @param value
+ value of userdata
+ */
+ function set_userdata($name, $value){
+ $this->userdata[$name]=$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)."'";
+ }
+ $str.="><![CDATA[".$this->data[$name]."]]></cell>";
+ }
+ 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{
+ protected $extra_output="";//!< extra info which need to be sent to client side
+ private $options=array();//!< hash of OptionsConnector
+
+ /*! 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="GridDataItem";
+ if (!$data_type) $data_type="GridDataProcessor";
+ parent::__construct($res,$type,$item_type,$data_type);
+ }
+
+
+ protected function parse_request(){
+ parent::parse_request();
+
+ if (isset($_GET["dhx_colls"]))
+ $this->fill_collections($_GET["dhx_colls"]);
+
+ if (isset($_GET["posStart"]) && isset($_GET["count"]))
+ $this->request->set_limit($_GET["posStart"],$_GET["count"]);
+ }
+ 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(){
+ if ($this->dload){
+ if ($pos=$this->request->get_start())
+ return "<rows pos='".$pos."'>";
+ else
+ return "<rows total_count='".$this->sql->get_size($this->request)."'>";
+ }
+ else
+ return "<rows>";
+ }
+
+
+ /*! 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])
+ return $this->config->text[intval($parts[1])]["name"];
+ return $data;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/codebase/keygrid_connector.php b/codebase/keygrid_connector.php
new file mode 100644
index 0000000..3942ac2
--- /dev/null
+++ b/codebase/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/options_connector.php b/codebase/options_connector.php
new file mode 100644
index 0000000..dc72eb2
--- /dev/null
+++ b/codebase/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/scheduler_connector.php b/codebase/scheduler_connector.php
new file mode 100644
index 0000000..c36c83f
--- /dev/null
+++ b/codebase/scheduler_connector.php
@@ -0,0 +1,125 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("base_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.">";
+ }
+ return $str."</event>";
+ }
+}
+
+
+/*! Connector class for dhtmlxScheduler
+**/
+class SchedulerConnector extends Connector{
+
+ protected $extra_output="";//!< extra info which need to be sent to client side
+ private $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;
+ }
+ /*! 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(){
+ 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>";
+ }
+ }
+
+ /*! renders self as xml, ending part
+ */
+ protected function xml_end(){
+ $this->fill_collections();
+ return $this->extra_output."</data>";
+ }
+
+
+ /*! 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="SchedulerDataItem";
+ if (!$data_type) $data_type="SchedulerDataProcessor";
+ 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 (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;
+ }
+}
+
+?> \ No newline at end of file
diff --git a/codebase/tools.php b/codebase/tools.php
new file mode 100644
index 0000000..cefe051
--- /dev/null
+++ b/codebase/tools.php
@@ -0,0 +1,254 @@
+<?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 static $eventsStatic=array();
+
+ /*! constructor
+ */
+ function __construct(){
+ $this->events=array();
+ }
+ /*! 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){
+ $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);
+ }
+ 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:m:s")."\n====================================");
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/codebase/tree_connector.php b/codebase/tree_connector.php
new file mode 100644
index 0000000..dff0867
--- /dev/null
+++ b/codebase/tree_connector.php
@@ -0,0 +1,278 @@
+<?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();
+ $this->userdata = 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;
+ }
+ }
+
+ /*! set userdata section for the item
+
+ @param name
+ name of userdata
+ @param value
+ value of userdata
+ */
+ function set_userdata($name, $value){
+ $this->userdata[$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.=">";
+ 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{
+ private $id_swap = array();
+
+ /*! 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="TreeDataItem";
+ if (!$data_type) $data_type="TreeDataProcessor";
+ parent::__construct($res,$type,$item_type,$data_type);
+
+ $this->event->attach("afterInsert",array($this,"parent_id_correction_a"));
+ $this->event->attach("beforeProcessing",array($this,"parent_id_correction_b"));
+ }
+
+ /*! 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->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]);
+ }
+
+
+ public function parse_request(){
+ parent::parse_request();
+
+ if (isset($_GET["id"]))
+ $this->request->set_relation($_GET["id"]);
+ else
+ $this->request->set_relation("0");
+
+ $this->request->set_limit(0,0); //netralize default reaction on dyn. loading mode
+ }
+
+
+
+ protected function render_set($res){
+ $output="";
+ $index=0;
+ while ($data=$this->sql->get_next($res)){
+ $data = new $this->names["item_class"]($data,$this->config,$index);
+ $this->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 && $this->dload)
+ $data->set_kids(true);
+ $output.=$data->to_xml_start();
+ if ($data->has_kids()===-1 || ( $data->has_kids()==true && !$this->dload)){
+ $sub_request = new DataRequestConfig($this->request);
+ $sub_request->set_relation($data->get_id());
+ $output.=$this->render_set($this->sql->select($sub_request));
+ }
+ $output.=$data->to_xml_end();
+ $index++;
+ }
+ return $output;
+ }
+ /*! renders self as xml, starting part
+ */
+ public function xml_start(){
+ return "<tree id='".$this->request->get_relation()."'>";
+ }
+
+ /*! 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/treegrid_connector.php b/codebase/treegrid_connector.php
new file mode 100644
index 0000000..308fcf4
--- /dev/null
+++ b/codebase/treegrid_connector.php
@@ -0,0 +1,165 @@
+<?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{
+ private $id_swap = array();
+
+ /*! 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="TreeGridDataItem";
+ if (!$data_type) $data_type="TreeGridDataProcessor";
+ parent::__construct($res,$type,$item_type,$data_type);
+
+ $this->event->attach("afterInsert",array($this,"parent_id_correction_a"));
+ $this->event->attach("beforeProcessing",array($this,"parent_id_correction_b"));
+ }
+
+ /*! 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->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]);
+ }
+
+ /*! process treegrid specific options in incoming request
+ */
+ public function parse_request(){
+ parent::parse_request();
+
+ if (isset($_GET["id"]))
+ $this->request->set_relation($_GET["id"]);
+ else
+ $this->request->set_relation("0");
+
+ $this->request->set_limit(0,0); //netralize default reaction on dyn. loading mode
+ }
+
+ /*! process treegrid specific options in incoming request
+ */
+ protected function render_set($res){
+ $output="";
+ $index=0;
+ while ($data=$this->sql->get_next($res)){
+ $data = new $this->names["item_class"]($data,$this->config,$index);
+ $this->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 && $this->dload)
+ $data->set_kids(true);
+ $output.=$data->to_xml_start();
+ if ($data->has_kids()===-1 || ( $data->has_kids()==true && !$this->dload)){
+ $sub_request = new DataRequestConfig($this->request);
+ $sub_request->set_relation($data->get_id());
+ $output.=$this->render_set($this->sql->select($sub_request));
+ }
+ $output.=$data->to_xml_end();
+ $index++;
+ }
+ return $output;
+ }
+
+ /*! 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/treegridgroup_connector.php b/codebase/treegridgroup_connector.php
new file mode 100644
index 0000000..b8c725e
--- /dev/null
+++ b/codebase/treegridgroup_connector.php
@@ -0,0 +1,122 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("treegrid_connector.php");
+
+class TreeGridGroupConnector extends TreeGridConnector{
+
+ private $id_postfix = '__{group_param}';
+
+ public function __construct($res,$type=false,$item_type=false,$data_type=false){
+ parent::__construct($res,$type,$item_type,$data_type);
+ $this->event->attach("beforeProcessing", Array($this, 'check_id'));
+ }
+
+
+ public function render(){
+ if (isset($_GET['id'])) {
+ $_GET['id'] = str_replace($this->id_postfix, "", $_GET['id']);
+ }
+ $this->parse_request();
+ if (!isset($_GET['id'])) {
+ $this->request->set_relation(false);
+ }
+
+ if (isset($_GET["editing"]))
+ $this->editing=true;
+ else if (isset($_POST["ids"])){
+ $this->editing=true;
+ } else {
+ $this->editing = false;
+ }
+
+ if ($this->editing){
+ $dp = new $this->names["data_class"]($this,$this->config,$this->request);
+ $dp->process($this->config,$this->request);
+ }
+ else {
+ $wrap = new SortInterface($this->request);
+ $this->event->trigger("beforeSort",$wrap);
+ $wrap->store();
+
+ $wrap = new FilterInterface($this->request);
+ $this->event->trigger("beforeFilter",$wrap);
+ $wrap->store();
+
+ if (isset($_GET['id'])) {
+ $this->output_as_xml( $this->sql->select($this->request) );
+ } else {
+ $relation_id = $this->config->relation_id['name'];
+ $this->output_as_xml( $this->sql->get_variants($this->config->relation_id['name'], $this->request));
+ }
+ }
+ $this->end_run();
+ }
+
+
+ protected function render_set($res){
+ $output="";
+ $index=0;
+ $records = Array();
+ while ($data=$this->sql->get_next($res)){
+ if (isset($data[$this->config->id['name']])) {
+ $has_kids = false;
+ } else {
+ $data[$this->config->id['name']] = $data['value'].$this->id_postfix;
+ $data[$this->config->text[0]['name']] = $data['value'];
+ $has_kids = true;
+ }
+ $data = new $this->names["item_class"]($data,$this->config,$index);
+ $this->event->trigger("beforeRender",$data);
+ if ($has_kids === false) {
+ $data->set_kids(false);
+ }
+
+ if ($data->has_kids()===-1 && $this->dload)
+ $data->set_kids(true);
+ $output.=$data->to_xml_start();
+ if (($data->has_kids()===-1 || ( $data->has_kids()==true && !$this->dload))&&($has_kids == true)){
+ $sub_request = new DataRequestConfig($this->request);
+ $sub_request->set_relation(str_replace($this->id_postfix, "", $data->get_id()));
+ $output.=$this->render_set($this->sql->select($sub_request));
+ }
+ $output.=$data->to_xml_end();
+ $index++;
+ }
+ return $output;
+ }
+
+
+ /*! renders self as xml, starting part
+ */
+ protected function xml_start(){
+ if (isset($_GET['id'])) {
+ return "<rows parent='".$_GET['id'].$this->id_postfix."'>";
+ } else {
+ return "<rows parent='0'>";
+ }
+ }
+
+
+ public function check_id($action) {
+ if (isset($_GET['editing'])) {
+ $id = $action->get_id();
+ $pid = $action->get_value($this->config->relation_id['name']);
+ $pid = str_replace($this->id_postfix, "", $pid);
+ $action->set_value($this->config->relation_id['name'], $pid);
+ if (strpos($id, $this->id_postfix) == false) {
+ return $action;
+ } else {
+ $action->error();
+ $action->set_response_text("This record can't be updated!");
+ return $action;
+ }
+ } else {
+ return $action;
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/codebase/treegridmultitable_connector.php b/codebase/treegridmultitable_connector.php
new file mode 100644
index 0000000..8d16bda
--- /dev/null
+++ b/codebase/treegridmultitable_connector.php
@@ -0,0 +1,166 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("treegrid_connector.php");
+
+class TreeGridMultitableConnector extends TreeGridConnector{
+
+ private $level = 0;
+ private $max_level = null;
+
+ public function __construct($res,$type=false,$item_type=false,$data_type=false){
+ $data_type="TreeGridMultitableDataProcessor";
+ parent::__construct($res,$type,$item_type,$data_type);
+ $this->event->attach("beforeProcessing", Array($this, 'id_translate_before'));
+ $this->event->attach("afterProcessing", Array($this, 'id_translate_after'));
+ }
+
+ public function render(){
+ $this->parse_request();
+ $this->dload = true;
+ if ((isset($_GET["editing"]))||(isset($_POST["ids"]))) {
+ $this->editing=true;
+ } else {
+ $this->editing = false;
+ }
+
+ if ($this->editing){
+ $dp = new $this->names["data_class"]($this,$this->config,$this->request);
+ $dp->process($this->config,$this->request);
+ } else {
+ $wrap = new SortInterface($this->request);
+ $this->event->trigger("beforeSort",$wrap);
+ $wrap->store();
+
+ $wrap = new FilterInterface($this->request);
+ $this->event->trigger("beforeFilter",$wrap);
+ $wrap->store();
+
+ if (isset($_GET['id'])) {
+ $this->output_as_xml( $this->sql->select($this->request) );
+ } else {
+ $this->request->set_relation(false);
+ $this->output_as_xml( $this->sql->select($this->request) );
+ }
+ }
+ $this->end_run();
+ }
+
+
+ protected function render_set($res){
+ $output="";
+ $index=0;
+ $records = Array();
+ while ($data=$this->sql->get_next($res)){
+ $data[$this->config->id['name']] = $this->level.'%23'.$data[$this->config->id['name']];
+ $data = new $this->names["item_class"]($data,$this->config,$index);
+ $this->event->trigger("beforeRender",$data);
+ if (($this->max_level !== null)&&($this->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++;
+ }
+ return $output;
+ }
+
+
+ public function xml_start(){
+ if (isset($_GET['id'])) {
+ return "<rows parent='".($this->level - 1).'%23'.$_GET['id']."'>";
+ } else {
+ return "<rows parent=''>";
+ }
+ }
+
+
+ public function get_level() {
+ if (!isset($_GET['id'])) {
+ if (isset($_POST['ids'])) {
+ $ids = explode(",",$_POST["ids"]);
+ $id = $this->parseId($ids[0]);
+ $this->level--;
+ }
+ $this->request->set_relation(false);
+ } else {
+ $id = $this->parseId($_GET['id']);
+ $_GET['id'] = $id;
+ }
+ return $this->level;
+ }
+
+
+ public function parseId($id, $set_level = true) {
+ $result = Array();
+ preg_match('/^(.+)((#)|(%23))/', $id, $result);
+ if ($set_level === true) {
+ $this->level = $result[1] + 1;
+ }
+ preg_match('/^(.+)((#)|(%23))(.*)$/', $id, $result);
+ $id = $result[5];
+ return $id;
+ }
+
+
+ /*! set maximum level of tree
+ @param max_level
+ maximum level
+ */
+ public function setMaxLevel($max_level) {
+ $this->max_level = $max_level;
+ }
+
+
+ /*! remove level prefix from id, parent id and set new id before processing
+ @param action
+ DataAction object
+ */
+ public function id_translate_before($action) {
+ $this->request->set_relation(false);
+ $id = $action->get_id();
+ $id = $this->parseId($id, false);
+ $action->set_id($id);
+ $action->set_value('gr_id', $id);
+ $action->set_new_id($id);
+ $pid = $action->get_value($this->config->relation_id['db_name']);
+ $pid = $this->parseId($pid, false);
+ $action->set_value($this->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).'%23'.$id);
+ $id = $action->get_new_id();
+ $action->success(($this->level).'%23'.$id);
+ }
+
+}
+
+
+class TreeGridMultitableDataProcessor extends DataProcessor {
+
+ function name_data($data){
+ if ($data=="gr_pid")
+ return $this->config->relation_id["name"];
+ preg_match('/^c(\d+)$/', $data, $data_num);
+ $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/treegroup_connector.php b/codebase/treegroup_connector.php
new file mode 100644
index 0000000..16db63c
--- /dev/null
+++ b/codebase/treegroup_connector.php
@@ -0,0 +1,118 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("tree_connector.php");
+
+class TreeGroupConnector extends TreeConnector{
+
+ private $id_postfix = '__{group_param}';
+
+ public function __construct($res,$type=false,$item_type=false,$data_type=false){
+ parent::__construct($res,$type,$item_type,$data_type);
+ $this->event->attach("beforeProcessing", Array($this, 'check_id'));
+ }
+
+
+ public function render(){
+ if (isset($_GET['id'])) {
+ $_GET['id'] = str_replace($this->id_postfix, "", $_GET['id']);
+ }
+ $this->parse_request();
+ if (!isset($_GET['id'])) {
+ $this->request->set_relation(false);
+ }
+
+ if (isset($_GET["editing"]))
+ $this->editing=true;
+ else if (isset($_POST["ids"])){
+ $this->editing=true;
+ } else {
+ $this->editing = false;
+ }
+
+ if ($this->editing){
+ $dp = new $this->names["data_class"]($this,$this->config,$this->request);
+ $dp->process($this->config,$this->request);
+ }
+ else {
+ $wrap = new SortInterface($this->request);
+ $this->event->trigger("beforeSort",$wrap);
+ $wrap->store();
+
+ $wrap = new FilterInterface($this->request);
+ $this->event->trigger("beforeFilter",$wrap);
+ $wrap->store();
+
+ if (isset($_GET['id'])) {
+ $this->output_as_xml( $this->sql->select($this->request) );
+ } else {
+ $relation_id = $this->config->relation_id['name'];
+ $this->output_as_xml( $this->sql->get_variants($this->config->relation_id['name'], $this->request));
+ }
+ }
+ $this->end_run();
+ }
+
+ protected function render_set($res){
+ $output="";
+ $index=0;
+ $records = Array();
+ while ($data=$this->sql->get_next($res)){
+ if (isset($data[$this->config->id['name']])) {
+ $has_kids = false;
+ } else {
+ $data[$this->config->id['name']] = $data['value'].$this->id_postfix;
+ $data[$this->config->text[0]['name']] = $data['value'];
+ $has_kids = true;
+ }
+ $data = new $this->names["item_class"]($data,$this->config,$index);
+ $this->event->trigger("beforeRender",$data);
+ if ($has_kids === false) {
+ $data->set_kids(false);
+ }
+
+ if ($data->has_kids()===-1 && $this->dload)
+ $data->set_kids(true);
+ $output.=$data->to_xml_start();
+ if (($data->has_kids()===-1 || ( $data->has_kids()==true && !$this->dload))&&($has_kids == true)){
+ $sub_request = new DataRequestConfig($this->request);
+ $sub_request->set_relation(str_replace($this->id_postfix, "", $data->get_id()));
+ $output.=$this->render_set($this->sql->select($sub_request));
+ }
+ $output.=$data->to_xml_end();
+ $index++;
+ }
+ return $output;
+ }
+
+ public function xml_start(){
+ if (isset($_GET['id'])) {
+ return "<tree id='".$_GET['id'].$this->id_postfix."'>";
+ } else {
+ return "<tree id='0'>";
+ }
+ }
+
+
+ public function check_id($action) {
+ if (isset($_GET['editing'])) {
+ $id = $action->get_id();
+ $pid = $action->get_value($this->config->relation_id['name']);
+ $pid = str_replace($this->id_postfix, "", $pid);
+ $action->set_value($this->config->relation_id['name'], $pid);
+ if (strpos($id, $this->id_postfix) == false) {
+ return $action;
+ } else {
+ $action->error();
+ $action->set_response_text("This record can't be updated!");
+ return $action;
+ }
+ } else {
+ return $action;
+ }
+ }
+}
+
+?> \ No newline at end of file
diff --git a/codebase/treemultitable_connector.php b/codebase/treemultitable_connector.php
new file mode 100644
index 0000000..f081689
--- /dev/null
+++ b/codebase/treemultitable_connector.php
@@ -0,0 +1,152 @@
+<?php
+/*
+ @author dhtmlx.com
+ @license GPL, see license.txt
+*/
+require_once("tree_connector.php");
+
+class TreeMultitableConnector extends TreeConnector{
+
+ private $level = 0;
+ private $max_level = null;
+
+ public function __construct($res,$type=false,$item_type=false,$data_type=false){
+ $data_type="TreeDataProcessor";
+ parent::__construct($res,$type,$item_type,$data_type);
+ $this->event->attach("beforeProcessing", Array($this, 'id_translate_before'));
+ $this->event->attach("afterProcessing", Array($this, 'id_translate_after'));
+ }
+
+
+ public function render(){
+ $this->parse_request();
+ $this->dload = true;
+ if ((isset($_GET["editing"]))||(isset($_POST["ids"]))) {
+ $this->editing=true;
+ } else {
+ $this->editing = false;
+ }
+
+ if ($this->editing){
+ $dp = new $this->names["data_class"]($this,$this->config,$this->request);
+ $dp->process($this->config,$this->request);
+ } else {
+ $wrap = new SortInterface($this->request);
+ $this->event->trigger("beforeSort",$wrap);
+ $wrap->store();
+
+ $wrap = new FilterInterface($this->request);
+ $this->event->trigger("beforeFilter",$wrap);
+ $wrap->store();
+
+ if (isset($_GET['id'])) {
+ $this->output_as_xml( $this->sql->select($this->request) );
+ } else {
+ $this->request->set_relation(false);
+ $this->output_as_xml( $this->sql->select($this->request) );
+ }
+ }
+ $this->end_run();
+ }
+
+ protected function render_set($res){
+ $output="";
+ $index=0;
+ $records = Array();
+ while ($data=$this->sql->get_next($res)){
+ $data[$this->config->id['name']] = $this->level.'#'.$data[$this->config->id['name']];
+ $data = new $this->names["item_class"]($data,$this->config,$index);
+ $this->event->trigger("beforeRender",$data);
+ if (($this->max_level !== null)&&($this->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++;
+ }
+ return $output;
+ }
+
+ public function xml_start(){
+ if (isset($_GET['id'])) {
+ return "<tree id='".($this->level - 1).'#'.$_GET['id']."'>";
+ } else {
+ return "<tree id='0'>";
+ }
+ }
+
+
+ public function get_level() {
+ if (!isset($_GET['id'])) {
+ if (isset($_POST['ids'])) {
+ $ids = explode(",",$_POST["ids"]);
+ $id = $this->parseId($ids[0]);
+ $this->level--;
+ }
+ $this->request->set_relation(false);
+ } else {
+ $id = $this->parseId($_GET['id']);
+ $_GET['id'] = $id;
+ }
+ return $this->level;
+ }
+
+
+ public function parseId($id, $set_level = true) {
+ $result = Array();
+ preg_match('/^(.+)#/', $id, $result);
+ if ($set_level === true) {
+ $this->level = $result[1] + 1;
+ }
+ preg_match('/^(.+)#(.*)$/', $id, $result);
+ if (isset($result[2])) {
+ $id = $result[2];
+ } else {
+ $id = '';
+ }
+ return $id;
+ }
+
+
+ /*! set maximum level of tree
+ @param max_level
+ maximum level
+ */
+ public function setMaxLevel($max_level) {
+ $this->max_level = $max_level;
+ }
+
+
+ /*! 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->parseId($id, false);
+ $action->set_id($id);
+ $action->set_value('tr_id', $id);
+ $action->set_new_id($id);
+ $pid = $action->get_value($this->config->relation_id['db_name']);
+ $pid = $this->parseId($pid, false);
+ $action->set_value($this->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 = $action->get_new_id();
+ $action->success(($this->level).'#'.$id);
+ }
+
+}
+
+?> \ No newline at end of file
diff --git a/codebase/update.php b/codebase/update.php
new file mode 100644
index 0000000..4931ad7
--- /dev/null
+++ b/codebase/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() {
+ echo $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