<?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['action_table_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['action_table_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 $encoding="utf-8";
	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, $options){
        $this->config= $config;
        $this->request= $request;
        $this->sql = $sql;
        $this->table=$table;
        $this->url=$url;
        $this->demu = false;
        $this->options = $options;
	}

    public function set_demultiplexor($path){
        $this->demu = $path;
    }

    public function set_event($master, $name){
        $this->event = $master;
        $this->item_class = $name;
    }

    public function set_encoding($encoding){
        $this->encoding = $encoding;
    }
   	
	protected function select_update($actions_table, $join_table, $id_field_name, $version, $user) {

		if ($this->options["table"] !== false)
			$join_table = $this->options["table"];

		$sql = "SELECT $join_table.*, {$actions_table}.id, {$actions_table}.dataId, {$actions_table}.type as action_table_type, {$actions_table}.user 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;
	}

	protected 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);
	}

    public function get_table() {
        return $this->table;
    }

	/*! 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") {
            $action_mode = $this->request->get_action_mode();
            if(!empty($action_mode))
                $type .= "#".$action_mode;

			$this->log_update_action($this->table, $dataId, $type, $user);
		}
	}


	/*! return action version in XMl format
	*/
	public function get_version() {
		$version = $this->get_update_max_version();
		return "<userdata name='version'>".$version."</userdata>";
	}


	/*! adds action version in output XML as userdata
	*/
	public function version_output($conn, $out) {
		$out->add($this->get_version());
	}


	/*! create update actions in XML-format and sends it to output
	*/
	public function get_updates() {
		$sub_request = new DataRequestConfig($this->request);
		$version =	$this->request->get_version();
		$user = 	$this->request->get_user();

		$sub_request->parse_sql($this->select_update($this->table, $this->request->get_source(), $this->config->id['db_name'], $version, $user));
		$sub_request->set_relation(false);

		$output = $this->render_set($this->sql->select($sub_request), $this->item_class);
        
		ob_clean();
		header("Content-type:text/xml; charset=".$this->encoding);

		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');
		}
	}
}


class JSONDataItemUpdate extends DataItemUpdate {

    public function to_xml() {
        return array(
            "status" => $this->data["action_table_type"],
            "id" => $this->data["dataId"],
            "parent" => $this->get_parent_id(),
            "data" => $this->child->to_xml()
        );
    }

}

class JSONDataUpdate extends DataUpdate {

    /*! adds action version in output XML as userdata
*/
    public function version_output($conn, $out) {
        $outJson = json_decode($out->__toString(), true);
        if(!isset($outJson["userdata"]))
            $outJson["userdata"] = array();

        $outJson["userdata"] = array_merge($outJson["userdata"], $this->get_version());
        $out->reset();
        $out->add(json_encode($outJson));
    }

    /*! return action version in XMl format
    */
    public function get_version() {
        $version = array("version" => $this->get_update_max_version());
        return $version;
    }

    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);

        if(!isset($output["userdata"]))
            $output["userdata"] = array();

        $output["userdata"] = array_merge($output["userdata"], $this->get_version());
        $this->output(json_encode($output));
    }

    protected function render_set($res, $name){
        $output = array();
        $index = 0;
        while($data = $this->sql->get_next($res)) {
            $data = new JSONDataItemUpdate($data, $this->config, $index, $name);
            $this->event->trigger("beforeRender", $data);
            array_push($output, $data->to_xml());
            $index++;
        }

        return array("updates" => $output);
    }

    protected function output($res){
        $out = new OutputWriter($res, "");
        $out->set_type("json");
        $this->event->trigger("beforeOutput", $this, $out);
        $out->output("", true, $this->encoding);
    }

}
	
?>