odoo/addons/web_calendar/static/lib/dhtmlxScheduler/codebase/connector/base_connector.php

707 lines
19 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
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){
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( '&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
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( '&amp;' , '&quot;', '&apos;' , '&lt;' , '&gt;', '&apos;' ), $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="<?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();
}
/*! 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 "<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);
}
}
?>