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){
ob_clean();
if ($this->type == "xml")
header("Content-type: text/xml");
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( '&' , '"', ''' , '<' , '>', ''' ), $string);
}
/*! return starting tag for self as XML string
*/
public function to_xml_start(){
$str="- 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 "
";
}
}
/*! 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
private $encoding="utf-8";//!< assigned encoding (UTF-8 by default)
private $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
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(){
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 {
$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
*/
private function xmlentities($string) {
return str_replace( array( '&', '"', "'", '<', '>', '’' ), array( '&' , '"', ''' , '<' , '>', ''' ), $string);
}
/*! 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();
$index++;
}
return $output;
}
/*! output fetched data as XML
@param res
DB resultset
*/
protected function output_as_xml($res){
$start="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();
}
/*! 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() || sizeof($this->request->get_filters()) || sizeof($this->request->get_sort_by()));
}
/*! renders self as xml, starting part
*/
protected function xml_start(){
return "";
}
/*! renders self as xml, ending part
*/
protected function xml_end(){
return "";
}
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);
}
}
?>