Commit 2c60486b authored by Romain Courteaud's avatar Romain Courteaud Committed by Jérome Perrin

Migrate job gantt widget.

parent 0e39fb46
......@@ -12,3 +12,4 @@ outputJSON.json
trace*.xls
npm-debug.log
node_modules/
tmp/
......@@ -6,7 +6,7 @@ module.exports = function (grunt) {
var global_config = {
src: "dream/platform/src2/",
lib: "dream/platform/vendor/",
// tmp: "tmp",
tmp: "tmp",
dest: "dream/platform/static/"
};
......@@ -19,6 +19,7 @@ module.exports = function (grunt) {
grunt.loadNpmTasks('grunt-contrib-copy');
grunt.loadNpmTasks('grunt-curl');
grunt.loadNpmTasks('grunt-contrib-less');
grunt.loadNpmTasks('grunt-zip');
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
......@@ -146,6 +147,18 @@ module.exports = function (grunt) {
relative_dest: 'lib/qunit.js',
dest: "<%= global_config.dest %>/<%= copy.qunitjs.relative_dest %>"
},
dhtmlxganttjs: {
src: "<%= global_config.lib %>/dhtmlxgantt.js",
// src: "<%= unzip.dhtmlxgantt.dest %>/codebase/dhtmlxgantt.js",
relative_dest: 'lib/dhtmlxgantt.js',
dest: "<%= global_config.dest %>/<%= copy.dhtmlxganttjs.relative_dest %>"
},
dhtmlxganttcss: {
src: "<%= global_config.lib %>/dhtmlxgantt.css",
// src: "<%= unzip.dhtmlxgantt.dest %>/codebase/dhtmlxgantt.css",
relative_dest: 'lib/dhtmlxgantt.css',
dest: "<%= global_config.dest %>/<%= copy.dhtmlxganttcss.relative_dest %>"
},
qunitcss: {
src: 'node_modules/qunitjs/qunit/qunit.css',
relative_dest: 'lib/qunit.css',
......@@ -210,6 +223,10 @@ module.exports = function (grunt) {
relative_dest: 'lib/jquery.flot.stack.js',
dest: '<%= global_config.dest %>/<%= curl.jqueryflotstack.relative_dest %>'
},
dhtmlxgantt: {
src: 'http://dhtmlx.com/x/download/regular/dhtmlxGantt.zip',
dest: '<%= global_config.tmp %>/dhtmlxGantt.zip'
},
handsontablejs: {
src: 'https://raw.githubusercontent.com/warpech/' +
'jquery-handsontable/v0.10.5/dist/jquery.handsontable.full.js',
......@@ -239,6 +256,13 @@ module.exports = function (grunt) {
}
// qunit: {
// all: ['test/index.html']
},
unzip: {
dhtmlxgantt: {
src: '<%= curl.dhtmlxgantt.dest %>',
dest: '<%= global_config.tmp %>/dhtmlxGantt/'
}
}
});
......@@ -246,7 +270,7 @@ module.exports = function (grunt) {
grunt.registerTask('default', ['all']);
grunt.registerTask('all', ['lint', 'build']);
grunt.registerTask('lint', ['jslint']);
grunt.registerTask('dep', ['curl']);
grunt.registerTask('dep', ['curl', 'unzip']);
// grunt.registerTask('test', ['qunit']);
grunt.registerTask('build', ['concat', 'copy', 'uglify', 'less']);
......
......@@ -8,7 +8,6 @@
<link rel="stylesheet" href="css/skeleton_wide.css">
<link rel="stylesheet" href="css/layout.css">
<link rel="stylesheet" href="css/jquery-ui-1.10.3.custom.min.css">
<link rel="stylesheet" type="text/css" href="lib/dhtmlxgantt/dhtmlxgantt.css">
<link rel="stylesheet" href="css/demo-new.css">
<link rel="stylesheet" href="css/font-awesome.css">
<link rel="shortcut icon" href="favicon.ico" type="image/x-icon">
......@@ -83,7 +82,6 @@
<div id="capacity_graphs"></div>
</div>
<div id="job_gantt" style='width:1320px; height:800px;'></div>
<div id="job_schedule_spreadsheet" style="overflow: scroll;"></div>
</div>
......@@ -103,9 +101,6 @@
<script type="text/javascript" src="lib/jquery.flot.js"></script>
<script type="text/javascript" src="lib/jquery.flot.stack.js"></script>
<!-- gantt chart -->
<script type="text/javascript" src="lib/dhtmlxgantt/dhtmlxgantt.js"></script>
<!-- nice display of dates -->
<script type="text/javascript" src="lib/moment-with-langs.min.js"></script>
......
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("tools.php");
require_once("db_common.php");
require_once("dataprocessor.php");
require_once("strategy.php");
require_once("update.php");
//enable buffering to catch and ignore any custom output before XML generation
//because of this command, it strongly recommended to include connector's file before any other libs
//in such case it will handle any extra output from not well formed code of other libs
ini_set("output_buffering","On");
ob_start();
class OutputWriter{
private $start;
private $end;
private $type;
public function __construct($start, $end = ""){
$this->start = $start;
$this->end = $end;
$this->type = "xml";
}
public function add($add){
$this->start.=$add;
}
public function reset(){
$this->start="";
$this->end="";
}
public function set_type($add){
$this->type=$add;
}
public function output($name="", $inline=true, $encoding=""){
ob_clean();
if ($this->type == "xml"){
$header = "Content-type: text/xml";
if ("" != $encoding)
$header.="; charset=".$encoding;
header($header);
}
echo $this->__toString();
}
public function __toString(){
return $this->start.$this->end;
}
}
/*! EventInterface
Base class , for iterable collections, which are used in event
**/
class EventInterface{
protected $request; ////!< DataRequestConfig instance
public $rules=array(); //!< array of sorting rules
/*! constructor
creates a new interface based on existing request
@param request
DataRequestConfig object
*/
public function __construct($request){
$this->request = $request;
}
/*! remove all elements from collection
*/
public function clear(){
array_splice($rules,0);
}
/*! get index by name
@param name
name of field
@return
index of named field
*/
public function index($name){
$len = sizeof($this->rules);
for ($i=0; $i < $len; $i++) {
if ($this->rules[$i]["name"]==$name)
return $i;
}
return false;
}
}
/*! Wrapper for collection of sorting rules
**/
class SortInterface extends EventInterface{
/*! constructor
creates a new interface based on existing request
@param request
DataRequestConfig object
*/
public function __construct($request){
parent::__construct($request);
$this->rules = &$request->get_sort_by_ref();
}
/*! add new sorting rule
@param name
name of field
@param dir
direction of sorting
*/
public function add($name,$dir){
if ($dir === false)
$this->request->set_sort($name);
else
$this->request->set_sort($name,$dir);
}
public function store(){
$this->request->set_sort_by($this->rules);
}
}
/*! Wrapper for collection of filtering rules
**/
class FilterInterface extends EventInterface{
/*! constructor
creates a new interface based on existing request
@param request
DataRequestConfig object
*/
public function __construct($request){
$this->request = $request;
$this->rules = &$request->get_filters_ref();
}
/*! add new filatering rule
@param name
name of field
@param value
value to filter by
@param rule
filtering rule
*/
public function add($name,$value,$rule){
$this->request->set_filter($name,$value,$rule);
}
public function store(){
$this->request->set_filters($this->rules);
}
}
/*! base class for component item representation
**/
class DataItem{
protected $data; //!< hash of data
protected $config;//!< DataConfig instance
protected $index;//!< index of element
protected $skip;//!< flag , which set if element need to be skiped during rendering
protected $userdata;
/*! constructor
@param data
hash of data
@param config
DataConfig object
@param index
index of element
*/
function __construct($data,$config,$index){
$this->config=$config;
$this->data=$data;
$this->index=$index;
$this->skip=false;
$this->userdata=false;
}
//set userdata for the item
function set_userdata($name, $value){
if ($this->userdata === false)
$this->userdata = array();
$this->userdata[$name]=$value;
}
/*! get named value
@param name
name or alias of field
@return
value from field with provided name or alias
*/
public function get_value($name){
return $this->data[$name];
}
/*! set named value
@param name
name or alias of field
@param value
value for field with provided name or alias
*/
public function set_value($name,$value){
return $this->data[$name]=$value;
}
/*! get id of element
@return
id of element
*/
public function get_id(){
$id = $this->config->id["name"];
if (array_key_exists($id,$this->data))
return $this->data[$id];
return false;
}
/*! change id of element
@param value
new id value
*/
public function set_id($value){
$this->data[$this->config->id["name"]]=$value;
}
/*! get index of element
@return
index of element
*/
public function get_index(){
return $this->index;
}
/*! mark element for skiping ( such element will not be rendered )
*/
public function skip(){
$this->skip=true;
}
/*! return self as XML string
*/
public function to_xml(){
return $this->to_xml_start().$this->to_xml_end();
}
/*! replace xml unsafe characters
@param string
string to be escaped
@return
escaped string
*/
public function xmlentities($string) {
return str_replace( array( '&', '"', "'", '<', '>', '’' ), array( '&amp;' , '&quot;', '&apos;' , '&lt;' , '&gt;', '&apos;' ), $string);
}
/*! return starting tag for self as XML string
*/
public function to_xml_start(){
$str="<item";
for ($i=0; $i < sizeof($this->config->data); $i++){
$name=$this->config->data[$i]["name"];
$db_name=$this->config->data[$i]["db_name"];
$str.=" ".$name."='".$this->xmlentities($this->data[$name])."'";
}
//output custom data
if ($this->userdata !== false)
foreach ($this->userdata as $key => $value){
$str.=" ".$key."='".$this->xmlentities($value)."'";
}
return $str.">";
}
/*! return ending tag for XML string
*/
public function to_xml_end(){
return "</item>";
}
}
/*! Base connector class
This class used as a base for all component specific connectors.
Can be used on its own to provide raw data.
**/
class Connector {
protected $config;//DataConfig instance
protected $request;//DataRequestConfig instance
protected $names;//!< hash of names for used classes
protected $encoding="utf-8";//!< assigned encoding (UTF-8 by default)
protected $editing=false;//!< flag of edit mode ( response for dataprocessor )
public static $filter_var="dhx_filter";
public static $sort_var="dhx_sort";
public $model=false;
private $updating=false;//!< flag of update mode ( response for data-update )
private $db; //!< db connection resource
protected $dload;//!< flag of dyn. loading mode
public $access; //!< AccessMaster instance
protected $data_separator = "\n";
public $sql; //DataWrapper instance
public $event; //EventMaster instance
public $limit=false;
private $id_seed=0; //!< default value, used to generate auto-IDs
protected $live_update = false; // actions table name for autoupdating
protected $extra_output="";//!< extra info which need to be sent to client side
protected $options=array();//!< hash of OptionsConnector
protected $as_string = false; // render() returns string, don't send result in response
protected $simple = false; // render only data without any other info
protected $filters;
protected $sorts;
protected $mix;
protected $order = false;
/*! constructor
Here initilization of all Masters occurs, execution timer initialized
@param db
db connection resource
@param type
string , which hold type of database ( MySQL or Postgre ), optional, instead of short DB name, full name of DataWrapper-based class can be provided
@param item_type
name of class, which will be used for item rendering, optional, DataItem will be used by default
@param data_type
name of class which will be used for dataprocessor calls handling, optional, DataProcessor class will be used by default.
*/
public function __construct($db,$type=false, $item_type=false, $data_type=false, $render_type = false){
$this->exec_time=microtime(true);
if (!$type) $type="MySQL";
if (class_exists($type."DBDataWrapper",false)) $type.="DBDataWrapper";
if (!$item_type) $item_type="DataItem";
if (!$data_type) $data_type="DataProcessor";
if (!$render_type) $render_type="RenderStrategy";
$this->names=array(
"db_class"=>$type,
"item_class"=>$item_type,
"data_class"=>$data_type,
"render_class"=>$render_type
);
$this->attributes = array();
$this->filters = array();
$this->sorts = array();
$this->mix = array();
$this->config = new DataConfig();
$this->request = new DataRequestConfig();
$this->event = new EventMaster();
$this->access = new AccessMaster();
if (!class_exists($this->names["db_class"],false))
throw new Exception("DB class not found: ".$this->names["db_class"]);
$this->sql = new $this->names["db_class"]($db,$this->config);
$this->render = new $this->names["render_class"]($this);
$this->db=$db;//saved for options connectors, if any
EventMaster::trigger_static("connectorCreate",$this);
}
/*! return db connection resource
nested class may neeed to access live connection object
@return
DB connection resource
*/
protected function get_connection(){
return $this->db;
}
public function get_config(){
return new DataConfig($this->config);
}
public function get_request(){
return new DataRequestConfig($this->request);
}
protected $attributes;
public function add_top_attribute($name, $string){
$this->attributes[$name] = $string;
}
//model is a class, which will be used for all data operations
//we expect that it has next methods get, update, insert, delete
//if method was not defined - we will use default logic
public function useModel($model){
$this->model = $model;
}
/*! config connector based on table
@param table
name of table in DB
@param id
name of id field
@param fields
list of fields names
@param extra
list of extra fields, optional, such fields will not be included in data rendering, but will be accessible in all inner events
@param relation_id
name of field used to define relations for hierarchical data organization, optional
*/
public function render_table($table,$id="",$fields=false,$extra=false,$relation_id=false){
$this->configure($table,$id,$fields,$extra,$relation_id);
return $this->render();
}
public function configure($table,$id="",$fields=false,$extra=false,$relation_id=false){
if ($fields === false){
//auto-config
$info = $this->sql->fields_list($table);
$fields = implode(",",$info["fields"]);
if ($info["key"])
$id = $info["key"];
}
$this->config->init($id,$fields,$extra,$relation_id);
if (strpos(trim($table), " ")!==false)
$this->request->parse_sql($table);
else
$this->request->set_source($table);
}
public function uuid(){
return time()."x".$this->id_seed++;
}
/*! config connector based on sql
@param sql
sql query used as base of configuration
@param id
name of id field
@param fields
list of fields names
@param extra
list of extra fields, optional, such fields will not be included in data rendering, but will be accessible in all inner events
@param relation_id
name of field used to define relations for hierarchical data organization, optional
*/
public function render_sql($sql,$id,$fields,$extra=false,$relation_id=false){
$this->config->init($id,$fields,$extra,$relation_id);
$this->request->parse_sql($sql);
return $this->render();
}
public function render_array($data, $id, $fields, $extra=false, $relation_id=false){
$this->configure("-",$id,$fields,$extra,$relation_id);
$this->sql = new ArrayDBDataWrapper($data, $this->config);
return $this->render();
}
public function render_complex_sql($sql,$id,$fields,$extra=false,$relation_id=false){
$this->config->init($id,$fields,$extra,$relation_id);
$this->request->parse_sql($sql, true);
return $this->render();
}
/*! render already configured connector
@param config
configuration of data
@param request
configuraton of request
*/
public function render_connector($config,$request){
$this->config->copy($config);
$this->request->copy($request);
return $this->render();
}
/*! render self
process commands, output requested data as XML
*/
public function render(){
$this->event->trigger("onInit", $this);
EventMaster::trigger_static("connectorInit",$this);
if (!$this->as_string)
$this->parse_request();
$this->set_relation();
if ($this->live_update !== false && $this->updating!==false) {
$this->live_update->get_updates();
} else {
if ($this->editing){
$dp = new $this->names["data_class"]($this,$this->config,$this->request);
$dp->process($this->config,$this->request);
} else {
if (!$this->access->check("read")){
LogMaster::log("Access control: read operation blocked");
echo "Access denied";
die();
}
$wrap = new SortInterface($this->request);
$this->apply_sorts($wrap);
$this->event->trigger("beforeSort",$wrap);
$wrap->store();
$wrap = new FilterInterface($this->request);
$this->apply_filters($wrap);
$this->event->trigger("beforeFilter",$wrap);
$wrap->store();
if ($this->model && method_exists($this->model, "get")){
$this->sql = new ArrayDBDataWrapper();
$result = new ArrayQueryWrapper(call_user_func(array($this->model, "get"), $this->request));
$out = $this->output_as_xml($result);
} else {
$out = $this->output_as_xml($this->get_resource());
if ($out !== null) return $out;
}
}
}
$this->end_run();
}
/*! empty call which used for tree-logic
* to prevent code duplicating
*/
protected function set_relation() {}
/*! gets resource for rendering
*/
protected function get_resource() {
return $this->sql->select($this->request);
}
/*! prevent SQL injection through column names
replace dangerous chars in field names
@param str
incoming field name
@return
safe field name
*/
protected function safe_field_name($str){
return strtok($str, " \n\t;',");
}
/*! limit max count of records
connector will ignore any records after outputing max count
@param limit
max count of records
@return
none
*/
public function set_limit($limit){
$this->limit = $limit;
}
public function limit($start, $count, $sort_field=false, $sort_dir=false){
$this->request->set_limit($start, $count);
if ($sort_field)
$this->request->set_sort($sort_field, $sort_dir);
}
protected function parse_request_mode(){
//detect edit mode
if (isset($_GET["editing"])){
$this->editing=true;
} else if (isset($_POST["ids"])){
$this->editing=true;
LogMaster::log('While there is no edit mode mark, POST parameters similar to edit mode detected. \n Switching to edit mode ( to disable behavior remove POST[ids]');
} else if (isset($_GET['dhx_version'])){
$this->updating = true;
}
}
/*! parse incoming request, detects commands and modes
*/
protected function parse_request(){
//set default dyn. loading params, can be reset in child classes
if ($this->dload)
$this->request->set_limit(0,$this->dload);
else if ($this->limit)
$this->request->set_limit(0,$this->limit);
if (isset($_GET["posStart"]) && isset($_GET["count"])) {
$this->request->set_limit($_GET["posStart"],$_GET["count"]);
}
$this->parse_request_mode();
if ($this->live_update && ($this->updating || $this->editing)){
$this->request->set_version($_GET["dhx_version"]);
$this->request->set_user($_GET["dhx_user"]);
}
if (isset($_GET[Connector::$sort_var]))
foreach($_GET[Connector::$sort_var] as $k => $v){
$k = $this->safe_field_name($k);
$this->request->set_sort($this->resolve_parameter($k),$v);
}
if (isset($_GET[Connector::$filter_var]))
foreach($_GET[Connector::$filter_var] as $k => $v){
$k = $this->safe_field_name($k);
if ($v !== "")
$this->request->set_filter($this->resolve_parameter($k),$v);
}
$this->check_csrf();
}
protected function check_csrf(){
$key = ConnectorSecurity::checkCSRF($this->editing);
if ($key !== "")
$this->add_top_attribute(ConnectorSecurity::$security_var, $key);
}
/*! convert incoming request name to the actual DB name
@param name
incoming parameter name
@return
name of related DB field
*/
protected function resolve_parameter($name){
return $name;
}
/*! replace xml unsafe characters
@param string
string to be escaped
@return
escaped string
*/
protected function xmlentities($string) {
return str_replace( array( '&', '"', "'", '<', '>', '’' ), array( '&amp;' , '&quot;', '&apos;' , '&lt;' , '&gt;', '&apos;' ), $string);
}
public function getRecord($id){
LogMaster::log("Retreiving data for record: ".$id);
$source = new DataRequestConfig($this->request);
$source->set_filter($this->config->id["name"],$id, "=");
$res = $this->sql->select($source);
$temp = $this->data_separator;
$this->data_separator="";
$output = $this->render_set($res);
$this->data_separato=$temp;
return $output;
}
/*! render from DB resultset
@param res
DB resultset
process commands, output requested data as XML
*/
protected function render_set($res){
return $this->render->render_set($res, $this->names["item_class"], $this->dload, $this->data_separator, $this->config, $this->mix);
}
/*! output fetched data as XML
@param res
DB resultset
*/
protected function output_as_xml($res){
$result = $this->render_set($res);
if ($this->simple) return $result;
$start="<?xml version='1.0' encoding='".$this->encoding."' ?>".$this->xml_start();
$end=$result.$this->xml_end();
if ($this->as_string) return $start.$end;
$out = new OutputWriter($start, $end);
$this->event->trigger("beforeOutput", $this, $out);
$out->output("", true, $this->encoding);
}
/*! end processing
stop execution timer, kill the process
*/
protected function end_run(){
$time=microtime(true)-$this->exec_time;
LogMaster::log("Done in {$time}s");
flush();
die();
}
/*! set xml encoding
methods sets only attribute in XML, no real encoding conversion occurs
@param encoding
value which will be used as XML encoding
*/
public function set_encoding($encoding){
$this->encoding=$encoding;
}
/*! enable or disable dynamic loading mode
@param count
count of rows loaded from server, actual only for grid-connector, can be skiped in other cases.
If value is a false or 0 - dyn. loading will be disabled
*/
public function dynamic_loading($count){
$this->dload=$count;
}
/*! enable or disable data reordering
@param name
name of field, which will be used for order storing, optional
by default 'sortorder' field will be used
*/
public function enable_order($name = true){
if ($name === true)
$name = "sortorder";
$this->sort($name);
$this->access->allow("order");
$this->request->set_order($name);
$this->order = $name;
}
/*! enable logging
@param path
path to the log file. If set as false or empty strig - logging will be disabled
@param client_log
enable output of log data to the client side
*/
public function enable_log($path=true,$client_log=false){
LogMaster::enable_log($path,$client_log);
}
/*! provides infor about current processing mode
@return
true if processing dataprocessor command, false otherwise
*/
public function is_select_mode(){
$this->parse_request_mode();
return !$this->editing;
}
public function is_first_call(){
$this->parse_request_mode();
return !($this->editing || $this->updating || $this->request->get_start() || isset($_GET['dhx_no_header']));
}
/*! renders self as xml, starting part
*/
protected function xml_start(){
$attributes = "";
if ($this->dload){
//info for dyn. loadin
if ($pos=$this->request->get_start())
$attributes .= " pos='".$pos."'";
else
$attributes .= " total_count='".$this->sql->get_size($this->request)."'";
}
foreach($this->attributes as $k=>$v)
$attributes .= " ".$k."='".$v."'";
return "<data".$attributes.">";
}
/*! renders self as xml, ending part
*/
protected function xml_end(){
$this->fill_collections();
if (isset($this->extra_output))
return $this->extra_output."</data>";
else
return "</data>";
}
protected function fill_collections($list=""){
foreach ($this->options as $k=>$v) {
$name = $k;
$this->extra_output.="<coll_options for='{$name}'>";
if (!is_string($this->options[$name]))
$this->extra_output.=$this->options[$name]->render();
else
$this->extra_output.=$this->options[$name];
$this->extra_output.="</coll_options>";
}
}
/*! assign options collection to the column
@param name
name of the column
@param options
array or connector object
*/
public function set_options($name,$options){
if (is_array($options)){
$str="";
foreach($options as $k => $v)
$str.="<item value='".$this->xmlentities($k)."' label='".$this->xmlentities($v)."' />";
$options=$str;
}
$this->options[$name]=$options;
}
public function insert($data) {
$action = new DataAction('inserted', false, $data);
$request = new DataRequestConfig();
$request->set_source($this->request->get_source());
$this->config->limit_fields($data);
$this->sql->insert($action,$request);
$this->config->restore_fields($data);
return $action->get_new_id();
}
public function delete($id) {
$action = new DataAction('deleted', $id, array());
$request = new DataRequestConfig();
$request->set_source($this->request->get_source());
$this->sql->delete($action,$request);
return $action->get_status();
}
public function update($data) {
$action = new DataAction('updated', $data[$this->config->id["name"]], $data);
$request = new DataRequestConfig();
$request->set_source($this->request->get_source());
$this->config->limit_fields($data);
$this->sql->update($action,$request);
$this->config->restore_fields($data);
return $action->get_status();
}
/*! sets actions_table for Optimistic concurrency control mode and start it
@param table_name
name of database table which will used for saving actions
@param url
url used for update notifications
*/
public function enable_live_update($table, $url=false){
$this->live_update = new DataUpdate($this->sql, $this->config, $this->request, $table,$url);
$this->live_update->set_event($this->event,$this->names["item_class"]);
$this->event->attach("beforeOutput", Array($this->live_update, "version_output"));
$this->event->attach("beforeFiltering", Array($this->live_update, "get_updates"));
$this->event->attach("beforeProcessing", Array($this->live_update, "check_collision"));
$this->event->attach("afterProcessing", Array($this->live_update, "log_operations"));
}
/*! render() returns result as string or send to response
*/
public function asString($as_string) {
$this->as_string = $as_string;
}
public function simple_render() {
$this->simple = true;
return $this->render();
}
public function filter($name, $value = false, $operation = '=') {
$this->filters[] = array('name' => $name, 'value' => $value, 'operation' => $operation);
}
public function clear_filter() {
$this->filters = array();
$this->request->set_filters(array());
}
protected function apply_filters($wrap) {
for ($i = 0; $i < count($this->filters); $i++) {
$f = $this->filters[$i];
$wrap->add($f['name'], $f['value'], $f['operation']);
}
}
public function sort($name, $direction = false) {
$this->sorts[] = array('name' => $name, 'direction' => $direction);
}
protected function apply_sorts($wrap) {
for ($i = 0; $i < count($this->sorts); $i++) {
$s = $this->sorts[$i];
$wrap->add($s['name'], $s['direction']);
}
}
public function mix($name, $value, $filter=false) {
$this->mix[] = Array('name'=>$name, 'value'=>$value, 'filter'=>$filter);
}
}
/*! wrapper around options collection, used for comboboxes and filters
**/
class OptionsConnector extends Connector{
protected $init_flag=false;//!< used to prevent rendering while initialization
public function __construct($res,$type=false,$item_type=false,$data_type=false){
if (!$item_type) $item_type="DataItem";
if (!$data_type) $data_type=""; //has not sense, options not editable
parent::__construct($res,$type,$item_type,$data_type);
}
/*! render self
process commands, return data as XML, not output data to stdout, ignore parameters in incoming request
@return
data as XML string
*/
public function render(){
if (!$this->init_flag){
$this->init_flag=true;
return "";
}
$res = $this->sql->select($this->request);
return $this->render_set($res);
}
}
class DistinctOptionsConnector extends OptionsConnector{
/*! render self
process commands, return data as XML, not output data to stdout, ignore parameters in incoming request
@return
data as XML string
*/
public function render(){
if (!$this->init_flag){
$this->init_flag=true;
return "";
}
$res = $this->sql->get_variants($this->config->text[0]["db_name"],$this->request);
return $this->render_set($res);
}
}
?>
\ No newline at end of file
<?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
<?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
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
class ConvertService{
private $url;
private $type;
private $name;
private $inline;
public function __construct($url){
$this->url = $url;
$this->pdf();
EventMaster::attach_static("connectorInit",array($this, "handle"));
}
public function pdf($name = "data.pdf", $inline = false){
$this->type = "pdf";
$this->name = $name;
$this->inline = $inline;
}
public function excel($name = "data.xls", $inline = false){
$this->type = "excel";
$this->name = $name;
$this->inline = $inline;
}
public function handle($conn){
$conn->event->attach("beforeOutput",array($this,"convert"));
}
private function as_file($size, $name, $inline){
header('Content-Type: application/force-download');
header('Content-Type: application/octet-stream');
header('Content-Type: application/download');
header('Content-Transfer-Encoding: binary');
header('Content-Length: '.$size);
if ($inline)
header('Content-Disposition: inline; filename="'.$name.'";');
else
header('Content-Disposition: attachment; filename="'.basename($name).'";');
}
public function convert($conn, $out){
$str_out = str_replace("<rows>","<rows profile='color'>", $out);
$str_out = str_replace("<head>","<head><columns>", $str_out);
$str_out = str_replace("</head>","</columns></head>", $str_out);
if ($this->type == "pdf")
header("Content-type: application/pdf");
else
header("Content-type: application/ms-excel");
$handle = curl_init($this->url);
curl_setopt($handle, CURLOPT_POST, true);
curl_setopt($handle, CURLOPT_HEADER, false);
curl_setopt($handle, CURLOPT_RETURNTRANSFER, true);
curl_setopt($handle, CURLOPT_POSTFIELDS, "grid_xml=".urlencode($str_out));
$out->reset();
$out->set_type("pdf");
$out->add(curl_exec($handle));
$this->as_file(strlen((string)$out), $this->name, $this->inline);
curl_close($handle);
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("data_connector.php");
class DelayedConnector extends Connector{
protected $init_flag=false;//!< used to prevent rendering while initialization
private $data_mode=false;//!< flag to separate xml and data request modes
private $data_result=false;//<! store results of query
public function dataMode($name){
$this->data_mode = $name;
$this->data_result=array();
}
public function getDataResult(){
return $this->data_result;
}
public function render(){
if (!$this->init_flag){
$this->init_flag=true;
return "";
}
return parent::render();
}
protected function output_as_xml($res){
if ($this->data_mode){
while ($data=$this->sql->get_next($res)){
$this->data_result[]=$data[$this->data_mode];
}
}
else
return parent::output_as_xml($res);
}
protected function end_run(){
if (!$this->data_mode)
parent::end_run();
}
}
class CrossOptionsConnector extends Connector{
public $options, $link;
private $master_name, $link_name, $master_value;
public function __construct($res,$type=false,$item_type=false,$data_type=false){
$this->options = new OptionsConnector($res,$type,$item_type,$data_type);
$this->link = new DelayedConnector($res,$type,$item_type,$data_type);
EventMaster::attach_static("connectorInit",array($this, "handle"));
}
public function handle($conn){
if ($conn instanceof DelayedConnector) return;
if ($conn instanceof OptionsConnector) return;
$this->master_name = $this->link->get_config()->id["db_name"];
$this->link_name = $this->options->get_config()->id["db_name"];
$this->link->event->attach("beforeFilter",array($this, "get_only_related"));
if (isset($_GET["dhx_crosslink_".$this->link_name])){
$this->get_links($_GET["dhx_crosslink_".$this->link_name]);
die();
}
if (!$this->dload){
$conn->event->attach("beforeRender", array($this, "getOptions"));
$conn->event->attach("beforeRenderSet", array($this, "prepareConfig"));
}
$conn->event->attach("afterProcessing", array($this, "afterProcessing"));
}
public function prepareConfig($conn, $res, $config){
$config->add_field($this->link_name);
}
public function getOptions($data){
$this->link->dataMode($this->link_name);
$this->get_links($data->get_value($this->master_name));
$data->set_value($this->link_name, implode(",",$this->link->getDataResult()));
}
public function get_links($id){
$this->master_value = $id;
$this->link->render();
}
public function get_only_related($filters){
$index = $filters->index($this->master_name);
if ($index!==false){
$filters->rules[$index]["value"]=$this->master_value;
} else
$filters->add($this->master_name, $this->master_value, "=");
}
public function afterProcessing($action){
$status = $action->get_status();
$master_key = $action->get_value($this->master_name);
$link_key = $action->get_value($this->link_name);
$link_key = explode(',', $link_key);
if ($status == "inserted")
$master_key = $action->get_new_id();
switch ($status){
case "deleted":
$this->link->delete($master_key);
break;
case "updated":
//cross link options not loaded yet, so we can skip update
if (!array_key_exists($this->link_name, $action->get_data()))
break;
//else, delete old options and continue in insert section to add new values
$this->link->delete($master_key);
case "inserted":
for ($i=0; $i < sizeof($link_key); $i++)
if ($link_key[$i]!="")
$this->link->insert(array(
$this->link_name => $link_key[$i],
$this->master_name => $master_key
));
break;
}
}
}
class JSONCrossOptionsConnector extends CrossOptionsConnector{
public $options, $link;
private $master_name, $link_name, $master_value;
public function __construct($res,$type=false,$item_type=false,$data_type=false){
$this->options = new JSONOptionsConnector($res,$type,$item_type,$data_type);
$this->link = new DelayedConnector($res,$type,$item_type,$data_type);
EventMaster::attach_static("connectorInit",array($this, "handle"));
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("base_connector.php");
class CommonDataProcessor extends DataProcessor{
protected function get_post_values($ids){
if (isset($_GET['action'])){
$data = array();
if (isset($_POST["id"])){
$dataset = array();
foreach($_POST as $key=>$value)
$dataset[$key] = ConnectorSecurity::filter($value);
$data[$_POST["id"]] = $dataset;
}
else
$data["dummy_id"] = $_POST;
return $data;
}
return parent::get_post_values($ids);
}
protected function get_ids(){
if (isset($_GET['action'])){
if (isset($_POST["id"]))
return array($_POST['id']);
else
return array("dummy_id");
}
return parent::get_ids();
}
protected function get_operation($rid){
if (isset($_GET['action']))
return $_GET['action'];
return parent::get_operation($rid);
}
public function output_as_xml($results){
if (isset($_GET['action'])){
LogMaster::log("Edit operation finished",$results);
ob_clean();
$type = $results[0]->get_status();
if ($type == "error" || $type == "invalid"){
echo "false";
} else if ($type=="insert"){
echo "true\n".$results[0]->get_new_id();
} else
echo "true";
} else
return parent::output_as_xml($results);
}
};
/*! DataItem class for DataView component
**/
class CommonDataItem extends DataItem{
/*! return self as XML string
*/
function to_xml(){
if ($this->skip) return "";
return $this->to_xml_start().$this->to_xml_end();
}
function to_xml_start(){
$str="<item id='".$this->get_id()."' ";
for ($i=0; $i < sizeof($this->config->text); $i++){
$name=$this->config->text[$i]["name"];
$str.=" ".$name."='".$this->xmlentities($this->data[$name])."'";
}
if ($this->userdata !== false)
foreach ($this->userdata as $key => $value)
$str.=" ".$key."='".$this->xmlentities($value)."'";
return $str.">";
}
}
/*! Connector class for DataView
**/
class DataConnector extends Connector{
/*! constructor
Here initilization of all Masters occurs, execution timer initialized
@param res
db connection resource
@param type
string , which hold type of database ( MySQL or Postgre ), optional, instead of short DB name, full name of DataWrapper-based class can be provided
@param item_type
name of class, which will be used for item rendering, optional, DataItem will be used by default
@param data_type
name of class which will be used for dataprocessor calls handling, optional, DataProcessor class will be used by default.
*/
public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
if (!$item_type) $item_type="CommonDataItem";
if (!$data_type) $data_type="CommonDataProcessor";
$this->sections = array();
if (!$render_type) $render_type="RenderStrategy";
parent::__construct($res,$type,$item_type,$data_type,$render_type);
}
protected $sections;
public function add_section($name, $string){
$this->sections[$name] = $string;
}
protected function parse_request_mode(){
if (isset($_GET['action']) && $_GET["action"] != "get")
$this->editing = true;
else
parent::parse_request_mode();
}
//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;
}
parent::check_csrf();
} else {
if (isset($_GET['editing']) && isset($_POST['ids']))
$this->editing = true;
parent::parse_request();
}
if (isset($_GET["start"]) && isset($_GET["count"]))
$this->request->set_limit($_GET["start"],$_GET["count"]);
}
/*! renders self as xml, starting part
*/
protected function xml_start(){
$start = "<data";
foreach($this->attributes as $k=>$v)
$start .= " ".$k."='".$v."'";
$start.= ">";
foreach($this->sections as $k=>$v)
$start .= "<".$k.">".$v."</".$k.">\n";
return $start;
}
};
class JSONDataConnector extends DataConnector{
public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
if (!$item_type) $item_type="JSONCommonDataItem";
if (!$data_type) $data_type="CommonDataProcessor";
if (!$render_type) $render_type="JSONRenderStrategy";
$this->data_separator = ",\n";
parent::__construct($res,$type,$item_type,$data_type,$render_type);
}
/*! assign options collection to the column
@param name
name of the column
@param options
array or connector object
*/
public function set_options($name,$options){
if (is_array($options)){
$str=array();
foreach($options as $k => $v)
$str[]='{"id":"'.$this->xmlentities($k).'", "value":"'.$this->xmlentities($v).'"}';
$options=implode(",",$str);
}
$this->options[$name]=$options;
}
/*! generates xml description for options collections
@param list
comma separated list of column names, for which options need to be generated
*/
protected function fill_collections($list=""){
$options = array();
foreach ($this->options as $k=>$v) {
$name = $k;
$option="\"{$name}\":[";
if (!is_string($this->options[$name]))
$option.=substr(json_encode($this->options[$name]->render()),1,-1);
else
$option.=$this->options[$name];
$option.="]";
$options[] = $option;
}
$this->extra_output .= implode($this->data_separator, $options);
}
protected function resolve_parameter($name){
if (intval($name).""==$name)
return $this->config->text[intval($name)]["db_name"];
return $name;
}
protected function output_as_xml($res){
$json = $this->render_set($res);
if ($this->simple) return $json;
$result = json_encode($json);
$this->fill_collections();
$is_sections = sizeof($this->sections) && $this->is_first_call();
if ($this->dload || $is_sections || sizeof($this->attributes) || !empty($this->extra_data)){
$attributes = "";
foreach($this->attributes as $k=>$v)
$attributes .= ", \"".$k."\":\"".$v."\"";
$extra = "";
if (!empty($this->extra_output))
$extra .= ', "collections": {'.$this->extra_output.'}';
$sections = "";
if ($is_sections){
//extra sections
foreach($this->sections as $k=>$v)
$sections .= ", \"".$k."\":".$v;
}
$dyn = "";
if ($this->dload){
//info for dyn. loadin
if ($pos=$this->request->get_start())
$dyn .= ", \"pos\":".$pos;
else
$dyn .= ", \"pos\":0, \"total_count\":".$this->sql->get_size($this->request);
}
if ($attributes || $sections || $this->extra_output || $dyn) {
$result = "{ \"data\":".$result.$attributes.$extra.$sections.$dyn."}";
}
}
// return as string
if ($this->as_string) return $result;
// output direct to response
$out = new OutputWriter($result, "");
$out->set_type("json");
$this->event->trigger("beforeOutput", $this, $out);
$out->output("", true, $this->encoding);
return null;
}
}
class JSONCommonDataItem extends DataItem{
/*! return self as XML string
*/
function to_xml(){
if ($this->skip) return "";
$data = array(
'id' => $this->get_id()
);
for ($i=0; $i<sizeof($this->config->text); $i++){
$extra = $this->config->text[$i]["name"];
$data[$extra]=$this->data[$extra];
}
if ($this->userdata !== false)
foreach ($this->userdata as $key => $value)
$data[$key]=$value;
return $data;
}
}
/*! wrapper around options collection, used for comboboxes and filters
**/
class JSONOptionsConnector extends JSONDataConnector{
protected $init_flag=false;//!< used to prevent rendering while initialization
public function __construct($res,$type=false,$item_type=false,$data_type=false){
if (!$item_type) $item_type="JSONCommonDataItem";
if (!$data_type) $data_type=""; //has not sense, options not editable
parent::__construct($res,$type,$item_type,$data_type);
}
/*! render self
process commands, return data as XML, not output data to stdout, ignore parameters in incoming request
@return
data as XML string
*/
public function render(){
if (!$this->init_flag){
$this->init_flag=true;
return "";
}
$res = $this->sql->select($this->request);
return $this->render_set($res);
}
}
class JSONDistinctOptionsConnector extends JSONOptionsConnector{
/*! render self
process commands, return data as XML, not output data to stdout, ignore parameters in incoming request
@return
data as XML string
*/
public function render(){
if (!$this->init_flag){
$this->init_flag=true;
return "";
}
$res = $this->sql->get_variants($this->config->text[0]["db_name"],$this->request);
return $this->render_set($res);
}
}
class TreeCommonDataItem extends CommonDataItem{
protected $kids=-1;
function to_xml_start(){
$str="<item id='".$this->get_id()."' ";
for ($i=0; $i < sizeof($this->config->text); $i++){
$name=$this->config->text[$i]["name"];
$str.=" ".$name."='".$this->xmlentities($this->data[$name])."'";
}
if ($this->userdata !== false)
foreach ($this->userdata as $key => $value)
$str.=" ".$key."='".$this->xmlentities($value)."'";
if ($this->kids === true)
$str .=" dhx_kids='1'";
return $str.">";
}
function has_kids(){
return $this->kids;
}
function set_kids($value){
$this->kids=$value;
}
}
class TreeDataConnector extends DataConnector{
protected $parent_name = 'parent';
/*! constructor
Here initilization of all Masters occurs, execution timer initialized
@param res
db connection resource
@param type
string , which hold type of database ( MySQL or Postgre ), optional, instead of short DB name, full name of DataWrapper-based class can be provided
@param item_type
name of class, which will be used for item rendering, optional, DataItem will be used by default
@param data_type
name of class which will be used for dataprocessor calls handling, optional, DataProcessor class will be used by default.
* @param render_type
* name of class which will provides data rendering
*/
public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
if (!$item_type) $item_type="TreeCommonDataItem";
if (!$data_type) $data_type="CommonDataProcessor";
if (!$render_type) $render_type="TreeRenderStrategy";
parent::__construct($res,$type,$item_type,$data_type,$render_type);
}
//parse GET scoope, all operations with incoming request must be done here
protected function parse_request(){
parent::parse_request();
if (isset($_GET[$this->parent_name]))
$this->request->set_relation($_GET[$this->parent_name]);
else
$this->request->set_relation("0");
$this->request->set_limit(0,0); //netralize default reaction on dyn. loading mode
}
/*! renders self as xml, starting part
*/
protected function xml_start(){
$attributes = " parent='".$this->request->get_relation()."' ";
foreach($this->attributes as $k=>$v)
$attributes .= " ".$k."='".$v."'";
return "<data".$attributes.">";
}
}
class JSONTreeDataConnector extends TreeDataConnector{
public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
if (!$item_type) $item_type="JSONTreeCommonDataItem";
if (!$data_type) $data_type="CommonDataProcessor";
if (!$render_type) $render_type="JSONTreeRenderStrategy";
parent::__construct($res,$type,$item_type,$data_type,$render_type);
}
protected function output_as_xml($res){
$result = $this->render_set($res);
if ($this->simple) return $result;
$data = array();
$data["parent"] = $this->request->get_relation();
$data["data"] = $result;
$this->fill_collections();
if (!empty($this->options))
$data["collections"] = $this->options;
foreach($this->attributes as $k=>$v)
$data[$k] = $v;
$data = json_encode($data);
// return as string
if ($this->as_string) return $data;
// output direct to response
$out = new OutputWriter($data, "");
$out->set_type("json");
$this->event->trigger("beforeOutput", $this, $out);
$out->output("", true, $this->encoding);
}
/*! assign options collection to the column
@param name
name of the column
@param options
array or connector object
*/
public function set_options($name,$options){
if (is_array($options)){
$str=array();
foreach($options as $k => $v)
$str[]=Array("id"=>$this->xmlentities($k), "value"=>$this->xmlentities($v));//'{"id":"'.$this->xmlentities($k).'", "value":"'.$this->xmlentities($v).'"}';
$options=$str;
}
$this->options[$name]=$options;
}
/*! generates xml description for options collections
@param list
comma separated list of column names, for which options need to be generated
*/
protected function fill_collections($list=""){
$options = array();
foreach ($this->options as $k=>$v) {
$name = $k;
if (!is_array($this->options[$name]))
$option=$this->options[$name]->render();
else
$option=$this->options[$name];
$options[$name] = $option;
}
$this->options = $options;
$this->extra_output .= "'collections':".json_encode($options);
}
}
class JSONTreeCommonDataItem extends TreeCommonDataItem{
/*! return self as XML string
*/
function to_xml_start(){
if ($this->skip) return "";
$data = array( "id" => $this->get_id() );
for ($i=0; $i<sizeof($this->config->text); $i++){
$extra = $this->config->text[$i]["name"];
if (isset($this->data[$extra]))
$data[$extra]=$this->data[$extra];
}
if ($this->userdata !== false)
foreach ($this->userdata as $key => $value)
$data[$key]=$value;
if ($this->kids === true)
$data["dhx_kids"] = 1;
return $data;
}
function to_xml_end(){
return "";
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
/*! Base DataProcessor handling
**/
require_once("xss_filter.php");
class DataProcessor{
protected $connector;//!< Connector instance
protected $config;//!< DataConfig instance
protected $request;//!< DataRequestConfig instance
static public $action_param ="!nativeeditor_status";
/*! constructor
@param connector
Connector object
@param config
DataConfig object
@param request
DataRequestConfig object
*/
function __construct($connector,$config,$request){
$this->connector= $connector;
$this->config=$config;
$this->request=$request;
}
/*! convert incoming data name to valid db name
redirect to Connector->name_data by default
@param data
data name from incoming request
@return
related db_name
*/
function name_data($data){
return $data;
}
/*! retrieve data from incoming request and normalize it
@param ids
array of extected IDs
@return
hash of data
*/
protected function get_post_values($ids){
$data=array();
for ($i=0; $i < sizeof($ids); $i++)
$data[$ids[$i]]=array();
foreach ($_POST as $key => $value) {
$details=explode("_",$key,2);
if (sizeof($details)==1) continue;
$name=$this->name_data($details[1]);
$data[$details[0]][$name]=ConnectorSecurity::filter($value);
}
return $data;
}
protected function get_ids(){
if (!isset($_POST["ids"]))
throw new Exception("Incorrect incoming data, ID of incoming records not recognized");
return explode(",",$_POST["ids"]);
}
protected function get_operation($rid){
if (!isset($_POST[$rid."_".DataProcessor::$action_param]))
throw new Exception("Status of record [{$rid}] not found in incoming request");
return $_POST[$rid."_".DataProcessor::$action_param];
}
/*! process incoming request ( save|update|delete )
*/
function process(){
LogMaster::log("DataProcessor object initialized",$_POST);
$results=array();
$ids=$this->get_ids();
$rows_data=$this->get_post_values($ids);
$failed=false;
try{
if ($this->connector->sql->is_global_transaction())
$this->connector->sql->begin_transaction();
for ($i=0; $i < sizeof($ids); $i++) {
$rid = $ids[$i];
LogMaster::log("Row data [{$rid}]",$rows_data[$rid]);
$status = $this->get_operation($rid);
$action=new DataAction($status,$rid,$rows_data[$rid]);
$results[]=$action;
$this->inner_process($action);
}
} catch(Exception $e){
LogMaster::log($e);
$failed=true;
}
if ($this->connector->sql->is_global_transaction()){
if (!$failed)
for ($i=0; $i < sizeof($results); $i++)
if ($results[$i]->get_status()=="error" || $results[$i]->get_status()=="invalid"){
$failed=true;
break;
}
if ($failed){
for ($i=0; $i < sizeof($results); $i++)
$results[$i]->error();
$this->connector->sql->rollback_transaction();
}
else
$this->connector->sql->commit_transaction();
}
$this->output_as_xml($results);
}
/*! converts status string to the inner mode name
@param status
external status string
@return
inner mode name
*/
protected function status_to_mode($status){
switch($status){
case "updated":
return "update";
break;
case "inserted":
return "insert";
break;
case "deleted":
return "delete";
break;
default:
return $status;
break;
}
}
/*! process data updated request received
@param action
DataAction object
@return
DataAction object with details of processing
*/
protected function inner_process($action){
if ($this->connector->sql->is_record_transaction())
$this->connector->sql->begin_transaction();
try{
$mode = $this->status_to_mode($action->get_status());
if (!$this->connector->access->check($mode)){
LogMaster::log("Access control: {$operation} operation blocked");
$action->error();
} else {
$check = $this->connector->event->trigger("beforeProcessing",$action);
if (!$action->is_ready())
$this->check_exts($action,$mode);
if ($mode == "insert" && $action->get_status() != "error" && $action->get_status() != "invalid")
$this->connector->sql->new_record_order($action, $this->request);
$check = $this->connector->event->trigger("afterProcessing",$action);
}
} catch (Exception $e){
LogMaster::log($e);
$action->set_status("error");
if ($action)
$this->connector->event->trigger("onDBError", $action, $e);
}
if ($this->connector->sql->is_record_transaction()){
if ($action->get_status()=="error" || $action->get_status()=="invalid")
$this->connector->sql->rollback_transaction();
else
$this->connector->sql->commit_transaction();
}
return $action;
}
/*! check if some event intercepts processing, send data to DataWrapper in other case
@param action
DataAction object
@param mode
name of inner mode ( will be used to generate event names )
*/
function check_exts($action,$mode){
$old_config = new DataConfig($this->config);
$this->connector->event->trigger("before".$mode,$action);
if ($action->is_ready())
LogMaster::log("Event code for ".$mode." processed");
else {
//check if custom sql defined
$sql = $this->connector->sql->get_sql($mode,$action);
if ($sql){
$this->connector->sql->query($sql);
}
else{
$action->sync_config($this->config);
if ($this->connector->model && method_exists($this->connector->model, $mode)){
call_user_func(array($this->connector->model, $mode), $action);
LogMaster::log("Model object process action: ".$mode);
}
if (!$action->is_ready()){
$method=array($this->connector->sql,$mode);
if (!is_callable($method))
throw new Exception("Unknown dataprocessing action: ".$mode);
call_user_func($method,$action,$this->request);
}
}
}
$this->connector->event->trigger("after".$mode,$action);
$this->config->copy($old_config);
}
/*! output xml response for dataprocessor
@param results
array of DataAction objects
*/
function output_as_xml($results){
LogMaster::log("Edit operation finished",$results);
ob_clean();
header("Content-type:text/xml");
echo "<?xml version='1.0' ?>";
echo "<data>";
for ($i=0; $i < sizeof($results); $i++)
echo $results[$i]->to_xml();
echo "</data>";
}
}
/*! contain all info related to action and controls customizaton
**/
class DataAction{
private $status; //!< cuurent status of record
private $id;//!< id of record
private $data;//!< data hash of record
private $userdata;//!< hash of extra data , attached to record
private $nid;//!< new id value , after operation executed
private $output;//!< custom output to client side code
private $attrs;//!< hash of custtom attributes
private $ready;//!< flag of operation's execution
private $addf;//!< array of added fields
private $delf;//!< array of deleted fields
/*! constructor
@param status
current operation status
@param id
record id
@param data
hash of data
*/
function __construct($status,$id,$data){
$this->status=$status;
$this->id=$id;
$this->data=$data;
$this->nid=$id;
$this->output="";
$this->attrs=array();
$this->ready=false;
$this->addf=array();
$this->delf=array();
}
/*! add custom field and value to DB operation
@param name
name of field which will be added to DB operation
@param value
value which will be used for related field in DB operation
*/
function add_field($name,$value){
LogMaster::log("adding field: ".$name.", with value: ".$value);
$this->data[$name]=$value;
$this->addf[]=$name;
}
/*! remove field from DB operation
@param name
name of field which will be removed from DB operation
*/
function remove_field($name){
LogMaster::log("removing field: ".$name);
$this->delf[]=$name;
}
/*! sync field configuration with external object
@param slave
SQLMaster object
@todo
check , if all fields removed then cancel action
*/
function sync_config($slave){
foreach ($this->addf as $k => $v)
$slave->add_field($v);
foreach ($this->delf as $k => $v)
$slave->remove_field($v);
}
/*! get value of some record's propery
@param name
name of record's property ( name of db field or alias )
@return
value of related property
*/
function get_value($name){
if (!array_key_exists($name,$this->data)){
LogMaster::log("Incorrect field name used: ".$name);
LogMaster::log("data",$this->data);
return "";
}
return $this->data[$name];
}
/*! set value of some record's propery
@param name
name of record's property ( name of db field or alias )
@param value
value of related property
*/
function set_value($name,$value){
LogMaster::log("change value of: ".$name." as: ".$value);
$this->data[$name]=$value;
}
/*! get hash of data properties
@return
hash of data properties
*/
function get_data(){
return $this->data;
}
/*! get some extra info attached to record
deprecated, exists just for backward compatibility, you can use set_value instead of it
@param name
name of userdata property
@return
value of related userdata property
*/
function get_userdata_value($name){
return $this->get_value($name);
}
/*! set some extra info attached to record
deprecated, exists just for backward compatibility, you can use get_value instead of it
@param name
name of userdata property
@param value
value of userdata property
*/
function set_userdata_value($name,$value){
return $this->set_value($name,$value);
}
/*! get current status of record
@return
string with status value
*/
function get_status(){
return $this->status;
}
/*! assign new status to the record
@param status
new status value
*/
function set_status($status){
$this->status=$status;
}
/*! set id
@param id
id value
*/
function set_id($id) {
$this->id = $id;
LogMaster::log("Change id: ".$id);
}
/*! set id
@param id
id value
*/
function set_new_id($id) {
$this->nid = $id;
LogMaster::log("Change new id: ".$id);
}
/*! get id of current record
@return
id of record
*/
function get_id(){
return $this->id;
}
/*! sets custom response text
can be accessed through defineAction on client side. Text wrapped in CDATA, so no extra escaping necessary
@param text
custom response text
*/
function set_response_text($text){
$this->set_response_xml("<![CDATA[".$text."]]>");
}
/*! sets custom response xml
can be accessed through defineAction on client side
@param text
string with XML data
*/
function set_response_xml($text){
$this->output=$text;
}
/*! sets custom response attributes
can be accessed through defineAction on client side
@param name
name of custom attribute
@param value
value of custom attribute
*/
function set_response_attribute($name,$value){
$this->attrs[$name]=$value;
}
/*! check if action finished
@return
true if action finished, false otherwise
*/
function is_ready(){
return $this->ready;
}
/*! return new id value
equal to original ID normally, after insert operation - value assigned for new DB record
@return
new id value
*/
function get_new_id(){
return $this->nid;
}
/*! set result of operation as error
*/
function error(){
$this->status="error";
$this->ready=true;
}
/*! set result of operation as invalid
*/
function invalid(){
$this->status="invalid";
$this->ready=true;
}
/*! confirm successful opeation execution
@param id
new id value, optional
*/
function success($id=false){
if ($id!==false)
$this->nid = $id;
$this->ready=true;
}
/*! convert DataAction to xml format compatible with client side dataProcessor
@return
DataAction operation report as XML string
*/
function to_xml(){
$str="<action type='{$this->status}' sid='{$this->id}' tid='{$this->nid}' ";
foreach ($this->attrs as $k => $v) {
$str.=$k."='".$v."' ";
}
$str.=">{$this->output}</action>";
return $str;
}
/*! convert self to string ( for logs )
@return
DataAction operation report as plain string
*/
function __toString(){
return "action:{$this->status}; sid:{$this->id}; tid:{$this->nid};";
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("base_connector.php");
/*! DataItem class for DataView component
**/
class DataViewDataItem extends DataItem{
/*! return self as XML string
*/
function to_xml(){
if ($this->skip) return "";
$str="<item id='".$this->get_id()."' >";
for ($i=0; $i<sizeof($this->config->text); $i++){
$extra = $this->config->text[$i]["name"];
$str.="<".$extra."><![CDATA[".$this->data[$extra]."]]></".$extra.">";
}
return $str."</item>";
}
}
/*! Connector class for DataView
**/
class DataViewConnector extends Connector{
/*! constructor
Here initilization of all Masters occurs, execution timer initialized
@param res
db connection resource
@param type
string , which hold type of database ( MySQL or Postgre ), optional, instead of short DB name, full name of DataWrapper-based class can be provided
@param item_type
name of class, which will be used for item rendering, optional, DataItem will be used by default
@param data_type
name of class which will be used for dataprocessor calls handling, optional, DataProcessor class will be used by default.
*/
public function __construct($res,$type=false,$item_type=false,$data_type=false){
if (!$item_type) $item_type="DataViewDataItem";
if (!$data_type) $data_type="DataProcessor";
parent::__construct($res,$type,$item_type,$data_type);
}
//parse GET scoope, all operations with incoming request must be done here
function parse_request(){
parent::parse_request();
if (isset($_GET["posStart"]) && isset($_GET["count"]))
$this->request->set_limit($_GET["posStart"],$_GET["count"]);
}
/*! renders self as xml, starting part
*/
protected function xml_start(){
$attributes = "";
foreach($this->attributes as $k=>$v)
$attributes .= " ".$k."='".$v."'";
$start.= ">";
if ($this->dload){
if ($pos=$this->request->get_start())
return "<data pos='".$pos."'".$attributes.">";
else
return "<data total_count='".$this->sql->get_size($this->request)."'".$attributes.">";
}
else
return "<data".$attributes.">";
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("db_common.php");
/*! Implementation of DataWrapper for PostgreSQL
**/
class AdoDBDataWrapper extends DBDataWrapper{
protected $last_result;
public function query($sql){
LogMaster::log($sql);
if (is_array($sql)) {
$res = $this->connection->SelectLimit($sql['sql'], $sql['numrows'], $sql['offset']);
} else {
$res = $this->connection->Execute($sql);
}
if ($res===false) throw new Exception("ADODB operation failed\n".$this->connection->ErrorMsg());
$this->last_result = $res;
return $res;
}
public function get_next($res){
if (!$res)
$res = $this->last_result;
if ($res->EOF)
return false;
$row = $res->GetRowAssoc(false);
$res->MoveNext();
return $row;
}
protected function get_new_id(){
return $this->connection->Insert_ID();
}
public function escape($data){
return $this->connection->addq($data);
}
/*! escape field name to prevent sql reserved words conflict
@param data
unescaped data
@return
escaped data
*/
public function escape_name($data){
if ((strpos($data,"`")!==false || is_int($data)) || (strpos($data,".")!==false))
return $data;
return '`'.$data.'`';
}
protected function select_query($select,$from,$where,$sort,$start,$count){
if (!$from)
return $select;
$sql="SELECT ".$select." FROM ".$from;
if ($where) $sql.=" WHERE ".$where;
if ($sort) $sql.=" ORDER BY ".$sort;
if ($start || $count) {
$sql=array("sql"=>$sql,'numrows'=>$count, 'offset'=>$start);
}
return $sql;
}
}
?>
\ No newline at end of file
<?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 $order = false;
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_order(){
return $this->order;
}
public function set_order($order){
$this->order = $order;
}
public function get_user(){
return $this->user;
}
public function set_user($user){
$this->user = $user;
}
public function get_version(){
return $this->version;
}
public function set_version($version){
$this->version = $version;
}
/*! returns list of used fields
@return
list of used fields
*/
public function get_fieldset(){
return $this->fieldset;
}
/*! returns name of source table
@return
name of source table
*/
public function get_source(){
return $this->source;
}
/*! returns set of sorting rules
@return
set of sorting rules
*/
public function get_sort_by(){
return $this->sort_by;
}
public function &get_sort_by_ref(){
return $this->sort_by;
}
public function set_sort_by($data){
$this->sort_by=$data;
}
/*! returns start index
@return
start index
*/
public function get_start(){
return $this->start;
}
/*! returns count of requested records
@return
count of requested records
*/
public function get_count(){
return $this->count;
}
/*! returns name of relation id
@return
relation id name
*/
public function get_relation(){
return $this->relation;
}
/*! sets sorting rule
@param field
name of column
@param order
direction of sorting
*/
public function set_sort($field,$order=false){
if (!$field && !$order)
$this->sort_by=array();
else{
if ($order===false)
$this->sort_by[] = $field;
else {
$order=strtolower($order)=="asc"?"ASC":"DESC";
$this->sort_by[]=array("name"=>$field,"direction" => $order);
}
}
}
/*! sets filtering rule
@param field
name of column
@param value
value for filtering
@param operation
operation for filtering, optional , LIKE by default
*/
public function set_filter($field,$value=false,$operation=false){
if ($value === false)
array_push($this->filters,$field);
else
array_push($this->filters,array("name"=>$field,"value"=>$value,"operation"=>$operation));
}
/*! sets list of used fields
@param value
list of used fields
*/
public function set_fieldset($value){
$this->fieldset=$value;
}
/*! sets name of source table
@param value
name of source table
*/
public function set_source($value){
if (is_string($value))
$value = trim($value);
$this->source = $value;
if (!$this->source) throw new Exception("Source of data can't be empty");
}
/*! sets data limits
@param start
start index
@param count
requested count of data
*/
public function set_limit($start,$count){
$this->start=$start;
$this->count=$count;
}
/*! sets name of relation id
@param value
name of relation id field
*/
public function set_relation($value){
$this->relation=$value;
}
/*! parse incoming sql, to fill other properties
@param sql
incoming sql string
*/
public function parse_sql($sql, $as_is = false){
if ($as_is){
$this->fieldset = $sql;
return;
}
$sql= preg_replace("/[ \n\t]+limit[\n\t ,0-9]*$/i","",$sql);
$data = preg_split("/[ \n\t]+\\_from\\_/i",$sql,2);
if (count($data)!=2)
$data = preg_split("/[ \n\t]+from/i",$sql,2);
$this->fieldset = preg_replace("/^[\s]*select/i","",$data[0],1);
//Ignore next type of calls
//direct call to stored procedure without FROM
if ((count($data) == 1) ||
//UNION select
preg_match("#[ \n\r\t]union[ \n\t\r]#i", $sql)){
$this->fieldset = $sql;
return;
}
$table_data = preg_split("/[ \n\t]+where/i",$data[1],2);
/*
if sql code contains group_by we will place all sql query in the FROM
it will not allow to use any filtering against the query
still it is better than just generate incorrect sql commands for any group by query
*/
if (sizeof($table_data)>1 && !preg_match("#.*group by.*#i",$table_data[1])){ //where construction exists
$this->set_source($table_data[0]);
$where_data = preg_split("/[ \n\t]+order[ ]+by/i",$table_data[1],2);
$this->filters[]=$where_data[0];
if (sizeof($where_data)==1) return; //end of line detected
$data=$where_data[1];
} else {
$table_data = preg_split("/[ \n\t]+order[ ]+by/i",$data[1],2);
$this->set_source($table_data[0]);
if (sizeof($table_data)==1) return; //end of line detected
$data=$table_data[1];
}
if (trim($data)){ //order by construction exists
$s_data = preg_split("/\\,/",trim($data));
for ($i=0; $i < count($s_data); $i++) {
$data=preg_split("/[ ]+/",trim($s_data[$i]),2);
if (sizeof($data)>1)
$this->set_sort($data[0],$data[1]);
else
$this->set_sort($data[0]);
}
}
}
}
/*! manager of data configuration
**/
class DataConfig{
public $id;////!< name of ID field
public $relation_id;//!< name or relation ID field
public $text;//!< array of text fields
public $data;//!< array of all known fields , fields which exists only in this collection will not be included in dataprocessor's operations
/*! converts self to the string, for logging purposes
**/
public function __toString(){
$str="ID:{$this->id['db_name']}(ID:{$this->id['name']})\n";
$str.="Relation ID:{$this->relation_id['db_name']}({$this->relation_id['name']})\n";
$str.="Data:";
for ($i=0; $i<sizeof($this->text); $i++)
$str.="{$this->text[$i]['db_name']}({$this->text[$i]['name']}),";
$str.="\nExtra:";
for ($i=0; $i<sizeof($this->data); $i++)
$str.="{$this->data[$i]['db_name']}({$this->data[$i]['name']}),";
return $str;
}
/*! removes un-used fields from configuration
@param name
name of field , which need to be preserved
*/
public function minimize($name){
for ($i=0; $i < sizeof($this->text); $i++){
if ($this->text[$i]["db_name"]==$name || $this->text[$i]["name"]==$name){
$this->text[$i]["name"]="value";
$this->data=array($this->text[$i]);
$this->text=array($this->text[$i]);
return;
}
}
throw new Exception("Incorrect dataset minimization, master field not found.");
}
public function limit_fields($data){
if (isset($this->full_field_list))
$this->restore_fields();
$this->full_field_list = $this->text;
$this->text = array();
for ($i=0; $i < sizeof($this->full_field_list); $i++) {
if (array_key_exists($this->full_field_list[$i]["name"],$data))
$this->text[] = $this->full_field_list[$i];
}
}
public function restore_fields(){
if (isset($this->full_field_list))
$this->text = $this->full_field_list;
}
/*! initialize inner state by parsing configuration parameters
@param id
name of id field
@param fields
name of data field(s)
@param extra
name of extra field(s)
@param relation
name of relation field
*/
public function init($id,$fields,$extra,$relation){
$this->id = $this->parse($id,false);
$this->text = $this->parse($fields,true);
$this->data = array_merge($this->text,$this->parse($extra,true));
$this->relation_id = $this->parse($relation,false);
}
/*! parse configuration string
@param key
key string from configuration
@param mode
multi names flag
@return
parsed field name object
*/
private function parse($key,$mode){
if ($mode){
if (!$key) return array();
$key=explode(",",$key);
for ($i=0; $i < sizeof($key); $i++)
$key[$i]=$this->parse($key[$i],false);
return $key;
}
$key=explode("(",$key);
$data=array("db_name"=>trim($key[0]), "name"=>trim($key[0]));
if (sizeof($key)>1)
$data["name"]=substr(trim($key[1]),0,-1);
return $data;
}
/*! constructor
init public collectons
@param proto
DataConfig object used as prototype for new one, optional
*/
public function __construct($proto=false){
if ($proto!==false)
$this->copy($proto);
else {
$this->text=array();
$this->data=array();
$this->id=array("name"=>"dhx_auto_id", "db_name"=>"dhx_auto_id");
$this->relation_id=array("name"=>"", "db_name"=>"");
}
}
/*! copy properties from source object
@param proto
source object
*/
public function copy($proto){
$this->id = $proto->id;
$this->relation_id = $proto->relation_id;
$this->text = $proto->text;
$this->data = $proto->data;
}
/*! returns list of data fields (db_names)
@return
list of data fields ( ready to be used in SQL query )
*/
public function db_names_list($db){
$out=array();
if ($this->id["db_name"])
array_push($out,$db->escape_name($this->id["db_name"]));
if ($this->relation_id["db_name"])
array_push($out,$db->escape_name($this->relation_id["db_name"]));
for ($i=0; $i < sizeof($this->data); $i++){
if ($this->data[$i]["db_name"]!=$this->data[$i]["name"])
$out[]=$db->escape_name($this->data[$i]["db_name"])." as ".$this->data[$i]["name"];
else
$out[]=$db->escape_name($this->data[$i]["db_name"]);
}
return $out;
}
/*! add field to dataset config ($text collection)
added field will be used in all auto-generated queries
@param name
name of field
@param aliase
aliase of field, optional
*/
public function add_field($name,$aliase=false){
if ($aliase===false) $aliase=$name;
//adding to list of data-active fields
if ($this->id["db_name"]==$name || $this->relation_id["db_name"] == $name){
LogMaster::log("Field name already used as ID, be sure that it is really necessary.");
}
if ($this->is_field($name,$this->text)!=-1)
throw new Exception('Data field already registered: '.$name);
array_push($this->text,array("db_name"=>$name,"name"=>$aliase));
//adding to list of all fields as well
if ($this->is_field($name,$this->data)==-1)
array_push($this->data,array("db_name"=>$name,"name"=>$aliase));
}
/*! remove field from dataset config ($text collection)
removed field will be excluded from all auto-generated queries
@param name
name of field, or aliase of field
*/
public function remove_field($name){
$ind = $this->is_field($name);
if ($ind==-1) throw new Exception('There was no such data field registered as: '.$name);
array_splice($this->text,$ind,1);
//we not deleting field from $data collection, so it will not be included in data operation, but its data still available
}
/*! remove field from dataset config ($text and $data collections)
removed field will be excluded from all auto-generated queries
@param name
name of field, or aliase of field
*/
public function remove_field_full($name){
$ind = $this->is_field($name);
if ($ind==-1) throw new Exception('There was no such data field registered as: '.$name);
array_splice($this->text,$ind,1);
$ind = $this->is_field($name, $this->data);
if ($ind==-1) throw new Exception('There was no such data field registered as: '.$name);
array_splice($this->data,$ind,1);
}
/*! check if field is a part of dataset
@param name
name of field
@param collection
collection, against which check will be done, $text collection by default
@return
returns true if field already a part of dataset, otherwise returns true
*/
public function is_field($name,$collection = false){
if (!$collection)
$collection=$this->text;
for ($i=0; $i<sizeof($collection); $i++)
if ($collection[$i]["name"] == $name || $collection[$i]["db_name"] == $name) return $i;
return -1;
}
}
/*! Base abstraction class, used for data operations
Class abstract access to data, it is a base class to all DB wrappers
**/
abstract class DataWrapper{
protected $connection;
protected $config;//!< DataConfig instance
/*! constructor
@param connection
DB connection
@param config
DataConfig instance
*/
public function __construct($connection,$config){
$this->config=$config;
$this->connection=$connection;
}
/*! insert record in storage
@param data
DataAction object
@param source
DataRequestConfig object
*/
abstract function insert($data,$source);
/*! delete record from storage
@param data
DataAction object
@param source
DataRequestConfig object
*/
abstract function delete($data,$source);
/*! update record in storage
@param data
DataAction object
@param source
DataRequestConfig object
*/
abstract function update($data,$source);
/*! select record from storage
@param source
DataRequestConfig object
*/
abstract function select($source);
/*! get size of storage
@param source
DataRequestConfig object
*/
abstract function get_size($source);
/*! get all variations of field in storage
@param name
name of field
@param source
DataRequestConfig object
*/
abstract function get_variants($name,$source);
/*! checks if there is a custom sql string for specified db operation
@param name
name of DB operation
@param data
hash of data
@return
sql string
*/
public function get_sql($name,$data){
return ""; //custom sql not supported by default
}
/*! begins DB transaction
*/
public function begin_transaction(){
throw new Exception("Data wrapper not supports transactions.");
}
/*! commits DB transaction
*/
public function commit_transaction(){
throw new Exception("Data wrapper not supports transactions.");
}
/*! rollbacks DB transaction
*/
public function rollback_transaction(){
throw new Exception("Data wrapper not supports transactions.");
}
}
/*! Common database abstraction class
Class provides base set of methods to access and change data in DB, class used as a base for DB-specific wrappers
**/
abstract class DBDataWrapper extends DataWrapper{
private $transaction = false; //!< type of transaction
private $sequence=false;//!< sequence name
private $sqls = array();//!< predefined sql actions
/*! assign named sql query
@param name
name of sql query
@param data
sql query text
*/
public function attach($name,$data){
$name=strtolower($name);
$this->sqls[$name]=$data;
}
/*! replace vars in sql string with actual values
@param matches
array of field name matches
@return
value for the var name
*/
public function get_sql_callback($matches){
return $this->escape($this->temp->get_value($matches[1]));
}
public function get_sql($name,$data){
$name=strtolower($name);
if (!array_key_exists($name,$this->sqls)) return "";
$str = $this->sqls[$name];
$this->temp = $data; //dirty
$str = preg_replace_callback('|\{([^}]+)\}|',array($this,"get_sql_callback"),$str);
unset ($this->temp); //dirty
return $str;
}
public function new_record_order($action, $source){
$order = $source->get_order();
if ($order){
$table = $source->get_source();
$id = $this->config->id["db_name"];
$idvalue = $action->get_new_id();
$max = $this->queryOne("SELECT MAX($order) as maxvalue FROM $table");
$maxvalue = $max["maxvalue"] + 1;
$this->query("UPDATE $table SET $order = $maxvalue WHERE $id = $idvalue");
}
}
public function order($data, $source){
//id of moved item
$id1 = $this->escape($data->get_value("id"));
//id of target item
$target = $data->get_value("target");
if (strpos($target, "next:") !== false){
$dropnext = true;
$id2 = str_replace("next:", "", $target);
} else {
$id2 = $target;
}
$id2 = $this->escape($id2);
//for tree like components we need to limit out queries to the affected branch only
$relation_select = $relation_update = $relation_sql_out = $relation_sql = "";
if ($this->config->relation_id["name"]){
$relation = $data->get_value($this->config->relation_id["name"]);
if ($relation !== false && $relation !== ""){
$relation_sql = " ".$this->config->relation_id["db_name"]." = '".$this->escape($relation)."' AND ";
$relation_select = $this->config->relation_id["db_name"]." as dhx_parent, ";
$relation_update = " ".$this->config->relation_id["db_name"]." = '".$this->escape($relation)."', ";
}
}
$name = $source->get_order();
$table = $source->get_source();
$idkey = $this->config->id["db_name"];
$source = $this->queryOne("select $relation_select $name as dhx_index from $table where $idkey = '$id1'");
$source_index = $source["dhx_index"] ? $source["dhx_index"] : 0;
if ($relation_sql)
$relation_sql_out = " ".$this->config->relation_id["db_name"]." = '".$this->escape($source["dhx_parent"])."' AND ";
$this->query("update $table set $name = $name - 1 where $relation_sql_out $name >= $source_index");
if ($id2 !== ""){
$target = $this->queryOne("select $name as dhx_index from $table where $idkey = '$id2'");
$target_index = $target["dhx_index"];
if (!$target_index)
$target_index = 0;
if ($dropnext)
$target_index += 1;
$this->query("update $table set $name = $name + 1 where $relation_sql $name >= $target_index");
} else {
$target = $this->queryOne("select max($name) as dhx_index from $table");
$target_index = ($target["dhx_index"] ? $target["dhx_index"] : 0)+1;
}
$this->query("update $table set $relation_update $name = $target_index where $idkey = '$id1'");
}
public function insert($data,$source){
$sql=$this->insert_query($data,$source);
$this->query($sql);
$data->success($this->get_new_id());
}
public function delete($data,$source){
$sql=$this->delete_query($data,$source);
$this->query($sql);
$data->success();
}
public function update($data,$source){
$sql=$this->update_query($data,$source);
$this->query($sql);
$data->success();
}
public function select($source){
$select=$source->get_fieldset();
if (!$select){
$select=$this->config->db_names_list($this);
$select = implode(",",$select);
}
$where=$this->build_where($source->get_filters(),$source->get_relation());
$sort=$this->build_order($source->get_sort_by());
return $this->query($this->select_query($select,$source->get_source(),$where,$sort,$source->get_start(),$source->get_count()));
}
public function queryOne($sql){
$res = $this->query($sql);
if ($res)
return $this->get_next($res);
return false;
}
public function get_size($source){
$count = new DataRequestConfig($source);
$count->set_fieldset("COUNT(*) as DHX_COUNT ");
$count->set_sort(null);
$count->set_limit(0,0);
$res=$this->select($count);
$data=$this->get_next($res);
if (array_key_exists("DHX_COUNT",$data)) return $data["DHX_COUNT"];
else return $data["dhx_count"]; //postgresql
}
public function get_variants($name,$source){
$count = new DataRequestConfig($source);
$count->set_fieldset("DISTINCT ".$this->escape_name($name)." as value");
$sort = new SortInterface($source);
$count->set_sort(null);
for ($i = 0; $i < count($sort->rules); $i++) {
if ($sort->rules[$i]['name'] == $name)
$count->set_sort($sort->rules[$i]['name'], $sort->rules[$i]['direction']);
}
$count->set_limit(0,0);
return $this->select($count);
}
public function sequence($sec){
$this->sequence=$sec;
}
/*! create an sql string for filtering rules
@param rules
set of filtering rules
@param relation
name of relation id field
@return
sql string with filtering rules
*/
protected function build_where($rules,$relation=false){
$sql=array();
for ($i=0; $i < sizeof($rules); $i++)
if (is_string($rules[$i]))
array_push($sql,"(".$rules[$i].")");
else
if ($rules[$i]["value"]!=""){
if (!$rules[$i]["operation"])
array_push($sql,$this->escape_name($rules[$i]["name"])." LIKE '%".$this->escape($rules[$i]["value"])."%'");
else
array_push($sql,$this->escape_name($rules[$i]["name"])." ".$rules[$i]["operation"]." '".$this->escape($rules[$i]["value"])."'");
}
if ($relation !== false && $relation !== "")
array_push($sql,$this->escape_name($this->config->relation_id["db_name"])." = '".$this->escape($relation)."'");
return implode(" AND ",$sql);
}
/*! convert sorting rules to sql string
@param by
set of sorting rules
@return
sql string for set of sorting rules
*/
protected function build_order($by){
if (!sizeof($by)) return "";
$out = array();
for ($i=0; $i < sizeof($by); $i++)
if (is_string($by[$i]))
$out[] = $by[$i];
else if ($by[$i]["name"])
$out[]=$this->escape_name($by[$i]["name"])." ".$by[$i]["direction"];
return implode(",",$out);
}
/*! generates sql code for select operation
@param select
list of fields in select
@param from
table name
@param where
list of filtering rules
@param sort
list of sorting rules
@param start
start index of fetching
@param count
count of records to fetch
@return
sql string for select operation
*/
protected function select_query($select,$from,$where,$sort,$start,$count){
if (!$from)
return $select;
$sql="SELECT ".$select." FROM ".$from;
if ($where) $sql.=" WHERE ".$where;
if ($sort) $sql.=" ORDER BY ".$sort;
if ($start || $count) $sql.=" LIMIT ".$start.",".$count;
return $sql;
}
/*! generates update sql
@param data
DataAction object
@param request
DataRequestConfig object
@return
sql string, which updates record with provided data
*/
protected function update_query($data,$request){
$sql="UPDATE ".$request->get_source()." SET ";
$temp=array();
for ($i=0; $i < sizeof($this->config->text); $i++) {
$step=$this->config->text[$i];
if ($data->get_value($step["name"])===Null)
$step_value ="Null";
else
$step_value = "'".$this->escape($data->get_value($step["name"]))."'";
$temp[$i]= $this->escape_name($step["db_name"])."=". $step_value;
}
if ($relation = $this->config->relation_id["db_name"]){
$temp[]= $this->escape_name($relation)."='".$this->escape($data->get_value($relation))."'";
}
$sql.=implode(",",$temp)." WHERE ".$this->escape_name($this->config->id["db_name"])."='".$this->escape($data->get_id())."'";
//if we have limited set - set constraints
$where=$this->build_where($request->get_filters());
if ($where) $sql.=" AND (".$where.")";
return $sql;
}
/*! generates delete sql
@param data
DataAction object
@param request
DataRequestConfig object
@return
sql string, which delete record
*/
protected function delete_query($data,$request){
$sql="DELETE FROM ".$request->get_source();
$sql.=" WHERE ".$this->escape_name($this->config->id["db_name"])."='".$this->escape($data->get_id())."'";
//if we have limited set - set constraints
$where=$this->build_where($request->get_filters());
if ($where) $sql.=" AND (".$where.")";
return $sql;
}
/*! generates insert sql
@param data
DataAction object
@param request
DataRequestConfig object
@return
sql string, which inserts new record with provided data
*/
protected function insert_query($data,$request){
$temp_n=array();
$temp_v=array();
foreach($this->config->text as $k => $v){
$temp_n[$k]=$this->escape_name($v["db_name"]);
if ($data->get_value($v["name"])===Null)
$temp_v[$k]="Null";
else
$temp_v[$k]="'".$this->escape($data->get_value($v["name"]))."'";
}
if ($relation = $this->config->relation_id["db_name"]){
$temp_n[]=$this->escape_name($relation);
$temp_v[]="'".$this->escape($data->get_value($relation))."'";
}
if ($this->sequence){
$temp_n[]=$this->escape_name($this->config->id["db_name"]);
$temp_v[]=$this->sequence;
}
$sql="INSERT INTO ".$request->get_source()."(".implode(",",$temp_n).") VALUES (".implode(",",$temp_v).")";
return $sql;
}
/*! sets the transaction mode, used by dataprocessor
@param mode
mode name
*/
public function set_transaction_mode($mode){
if ($mode!="none" && $mode!="global" && $mode!="record")
throw new Exception("Unknown transaction mode");
$this->transaction=$mode;
}
/*! returns true if global transaction mode was specified
@return
true if global transaction mode was specified
*/
public function is_global_transaction(){
return $this->transaction == "global";
}
/*! returns true if record transaction mode was specified
@return
true if record transaction mode was specified
*/
public function is_record_transaction(){
return $this->transaction == "record";
}
public function begin_transaction(){
$this->query("BEGIN");
}
public function commit_transaction(){
$this->query("COMMIT");
}
public function rollback_transaction(){
$this->query("ROLLBACK");
}
/*! exec sql string
@param sql
sql string
@return
sql result set
*/
abstract public function query($sql);
/*! returns next record from result set
@param res
sql result set
@return
hash of data
*/
abstract public function get_next($res);
/*! returns new id value, for newly inserted row
@return
new id value, for newly inserted row
*/
abstract public function get_new_id();
/*! escape data to prevent sql injections
@param data
unescaped data
@return
escaped data
*/
abstract public function escape($data);
/*! escape field name to prevent sql reserved words conflict
@param data
unescaped data
@return
escaped data
*/
public function escape_name($data){
return $data;
}
/*! get list of tables in the database
@return
array of table names
*/
public function tables_list() {
throw new Exception("Not implemented");
}
/*! returns list of fields for the table in question
@param table
name of table in question
@return
array of field names
*/
public function fields_list($table) {
throw new Exception("Not implemented");
}
}
class ArrayDBDataWrapper extends DBDataWrapper{
public function get_next($res){
if ($res->index < sizeof($res->data))
return $res->data[$res->index++];
}
public function select($sql){
if ($this->config->relation_id["db_name"] == "") {
if ($sql->get_relation() == "0" || $sql->get_relation() == "") {
return new ArrayQueryWrapper($this->connection);
} else {
return new ArrayQueryWrapper(array());
}
}
$relation_id = $this->config->relation_id["db_name"];
for ($i = 0; $i < count($this->connection); $i++) {
$item = $this->connection[$i];
if (!isset($item[$relation_id])) continue;
if ($item[$relation_id] == $sql->get_relation())
$result[] = $item;
}
return new ArrayQueryWrapper($result);
}
public function query($sql){
throw new Exception("Not implemented");
}
public function escape($value){
throw new Exception("Not implemented");
}
public function get_new_id(){
throw new Exception("Not implemented");
}
}
class ArrayQueryWrapper{
public function __construct($data){
$this->data = $data;
$this->index = 0;
}
}
/*! Implementation of DataWrapper for MySQL
**/
class MySQLDBDataWrapper extends DBDataWrapper{
protected $last_result;
public function query($sql){
LogMaster::log($sql);
$res=mysql_query($sql,$this->connection);
if ($res===false) throw new Exception("MySQL operation failed\n".mysql_error($this->connection));
$this->last_result = $res;
return $res;
}
public function get_next($res){
if (!$res)
$res = $this->last_result;
return mysql_fetch_assoc($res);
}
public function get_new_id(){
return mysql_insert_id($this->connection);
}
public function escape($data){
return mysql_real_escape_string($data, $this->connection);
}
public function tables_list() {
$result = mysql_query("SHOW TABLES");
if ($result===false) throw new Exception("MySQL operation failed\n".mysql_error($this->connection));
$tables = array();
while ($table = mysql_fetch_array($result)) {
$tables[] = $table[0];
}
return $tables;
}
public function fields_list($table) {
$result = mysql_query("SHOW COLUMNS FROM `".$table."`");
if ($result===false) throw new Exception("MySQL operation failed\n".mysql_error($this->connection));
$fields = array();
$id = "";
while ($field = mysql_fetch_assoc($result)) {
if ($field['Key'] == "PRI")
$id = $field["Field"];
else
$fields[] = $field["Field"];
}
return array("fields" => $fields, "key" => $id );
}
/*! escape field name to prevent sql reserved words conflict
@param data
unescaped data
@return
escaped data
*/
public function escape_name($data){
if ((strpos($data,"`")!==false || is_int($data)) || (strpos($data,".")!==false))
return $data;
return '`'.$data.'`';
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once('db_common.php');
if (!defined('DHX_IGNORE_EMPTY_ROWS')) {
define('DHX_IGNORE_EMPTY_ROWS', true);
}
class ExcelDBDataWrapper extends DBDataWrapper {
public $emptyLimit = 10;
public function excel_data($points){
$path = $this->connection;
$excel = PHPExcel_IOFactory::createReaderForFile($path);
$excel = $excel->load($path);
$result = array();
$excelWS = $excel->getActiveSheet();
for ($i=0; $i < sizeof($points); $i++) {
$c = array();
preg_match("/^([a-zA-Z]+)(\d+)/", $points[$i], $c);
if (count($c) > 0) {
$col = PHPExcel_Cell::columnIndexFromString($c[1]) - 1;
$cell = $excelWS->getCellByColumnAndRow($col, (int)$c[2]);
$result[] = $cell->getValue();
}
}
return $result;
}
public function select($source) {
$path = $this->connection;
$excel = PHPExcel_IOFactory::createReaderForFile($path);
$excel->setReadDataOnly(false);
$excel = $excel->load($path);
$excRes = new ExcelResult();
$excelWS = $excel->getActiveSheet();
$addFields = true;
$coords = array();
if ($source->get_source() == '*') {
$coords['start_row'] = 0;
$coords['end_row'] = false;
} else {
$c = array();
preg_match("/^([a-zA-Z]+)(\d+)/", $source->get_source(), $c);
if (count($c) > 0) {
$coords['start_row'] = (int) $c[2];
} else {
$coords['start_row'] = 0;
}
$c = array();
preg_match("/:(.+)(\d+)$/U", $source->get_source(), $c);
if (count($c) > 0) {
$coords['end_row'] = (int) $c[2];
} else {
$coords['end_row'] = false;
}
}
$i = $coords['start_row'];
$end = 0;
while ((($coords['end_row'] == false)&&($end < $this->emptyLimit))||(($coords['end_row'] !== false)&&($i < $coords['end_row']))) {
$r = Array();
$emptyNum = 0;
for ($j = 0; $j < count($this->config->text); $j++) {
$col = PHPExcel_Cell::columnIndexFromString($this->config->text[$j]['name']) - 1;
$cell = $excelWS->getCellByColumnAndRow($col, $i);
if (PHPExcel_Shared_Date::isDateTime($cell)) {
$r[PHPExcel_Cell::stringFromColumnIndex($col)] = PHPExcel_Shared_Date::ExcelToPHP($cell->getValue());
} else if ($cell->getDataType() == 'f') {
$r[PHPExcel_Cell::stringFromColumnIndex($col)] = $cell->getCalculatedValue();
} else {
$r[PHPExcel_Cell::stringFromColumnIndex($col)] = $cell->getValue();
}
if ($r[PHPExcel_Cell::stringFromColumnIndex($col)] == '') {
$emptyNum++;
}
}
if ($emptyNum < count($this->config->text)) {
$r['id'] = $i;
$excRes->addRecord($r);
$end = 0;
} else {
if (DHX_IGNORE_EMPTY_ROWS == false) {
$r['id'] = $i;
$excRes->addRecord($r);
}
$end++;
}
$i++;
}
return $excRes;
}
public function query($sql) {
}
public function get_new_id() {
}
public function escape($data) {
}
public function get_next($res) {
return $res->next();
}
}
class ExcelResult {
private $rows;
private $currentRecord = 0;
// add record to output list
public function addRecord($file) {
$this->rows[] = $file;
}
// return next record
public function next() {
if ($this->currentRecord < count($this->rows)) {
$row = $this->rows[$this->currentRecord];
$this->currentRecord++;
return $row;
} else {
return false;
}
}
// sorts records under $sort array
public function sort($sort, $data) {
if (count($this->files) == 0) {
return $this;
}
// defines fields list if it's need
for ($i = 0; $i < count($sort); $i++) {
$fieldname = $sort[$i]['name'];
if (!isset($this->files[0][$fieldname])) {
if (isset($data[$fieldname])) {
$fieldname = $data[$fieldname]['db_name'];
$sort[$i]['name'] = $fieldname;
} else {
$fieldname = false;
}
}
}
// for every sorting field will sort
for ($i = 0; $i < count($sort); $i++) {
// if field, setted in sort parameter doesn't exist, continue
if ($sort[$i]['name'] == false) {
continue;
}
// sorting by current field
$flag = true;
while ($flag == true) {
$flag = false;
// checks if previous sorting fields are equal
for ($j = 0; $j < count($this->files) - 1; $j++) {
$equal = true;
for ($k = 0; $k < $i; $k++) {
if ($this->files[$j][$sort[$k]['name']] != $this->files[$j + 1][$sort[$k]['name']]) {
$equal = false;
}
}
// compares two records in list under current sorting field and sorting direction
if (((($this->files[$j][$sort[$i]['name']] > $this->files[$j + 1][$sort[$i]['name']])&&($sort[$i]['direction'] == 'ASC'))||(($this->files[$j][$sort[$i]['name']] < $this->files[$j + 1][$sort[$i]['name']])&&($sort[$i]['direction'] == 'DESC')))&&($equal == true)) {
$c = $this->files[$j];
$this->files[$j] = $this->files[$j+1];
$this->files[$j+1] = $c;
$flag = true;
}
}
}
}
return $this;
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once('db_common.php');
require_once('tree_connector.php');
/*
Most execution time is a standart functions for workin with FileSystem: is_dir(), dir(), readdir(), stat()
*/
class FileSystemDBDataWrapper extends DBDataWrapper {
// returns list of files and directories
public function select($source) {
$relation = $this->getFileName($source->get_relation());
// for tree checks relation id and forms absolute path
if ($relation == '0') {
$relation = '';
} else {
$path = $source->get_source();
}
$path = $source->get_source();
$path = $this->getFileName($path);
$path = realpath($path);
if ($path == false) {
return new FileSystemResult();
}
if (strpos(realpath($path.'/'.$relation), $path) !== 0) {
return new FileSystemResult();
}
// gets files and directories list
$res = $this->getFilesList($path, $relation);
// sorts list
$res = $res->sort($source->get_sort_by(), $this->config->data);
return $res;
}
// gets files and directory list
private function getFilesList($path, $relation) {
$fileSystemTypes = FileSystemTypes::getInstance();
LogMaster::log("Query filesystem: ".$path);
$dir = opendir($path.'/'.$relation);
$result = new FileSystemResult();
// forms fields list
for ($i = 0; $i < count($this->config->data); $i++) {
$fields[] = $this->config->data[$i]['db_name'];
}
// for every file and directory of folder
while ($file = readdir($dir)) {
// . and .. should not be in output list
if (($file == '.')||($file == '..')) {
continue;
}
$newFile = array();
// parse file name as Array('name', 'ext', 'is_dir')
$fileNameExt = $this->parseFileName($path.'/'.$relation, $file);
// checks if file should be in output array
if (!$fileSystemTypes->checkFile($file, $fileNameExt)) {
continue;
}
// takes file stat if it's need
if ((in_array('size', $fields))||(in_array('date', $fields))) {
$fileInfo = stat($path.'/'.$file);
}
// for every field forms list of fields
for ($i = 0; $i < count($fields); $i++) {
$field = $fields[$i];
switch ($field) {
case 'filename':
$newFile['filename'] = $file;
break;
case 'full_filename':
$newFile['full_filename'] = $path."/".$file;
break;
case 'size':
$newFile['size'] = $fileInfo['size'];
break;
case 'extention':
$newFile['extention'] = $fileNameExt['ext'];
break;
case 'name':
$newFile['name'] = $fileNameExt['name'];
break;
case 'date':
$newFile['date'] = date("Y-m-d H:i:s", $fileInfo['ctime']);
break;
}
$newFile['relation_id'] = $relation.'/'.$file;
$newFile['safe_name'] = $this->setFileName($relation.'/'.$file);
$newFile['is_folder'] = $fileNameExt['is_dir'];
}
// add file in output list
$result->addFile($newFile);
}
return $result;
}
// replaces '.' and '_' in id
private function setFileName($filename) {
$filename = str_replace(".", "{-dot-}", $filename);
$filename = str_replace("_", "{-nizh-}", $filename);
return $filename;
}
// replaces '{-dot-}' and '{-nizh-}' in id
private function getFileName($filename) {
$filename = str_replace("{-dot-}", ".", $filename);
$filename = str_replace("{-nizh-}", "_", $filename);
return $filename;
}
// parses file name and checks if is directory
private function parseFileName($path, $file) {
$result = Array();
if (is_dir($path.'/'.$file)) {
$result['name'] = $file;
$result['ext'] = 'dir';
$result['is_dir'] = 1;
} else {
$pos = strrpos($file, '.');
$result['name'] = substr($file, 0, $pos);
$result['ext'] = substr($file, $pos + 1);
$result['is_dir'] = 0;
}
return $result;
}
public function query($sql) {
}
public function get_new_id() {
}
public function escape($data) {
}
public function get_next($res) {
return $res->next();
}
}
class FileSystemResult {
private $files;
private $currentRecord = 0;
// add record to output list
public function addFile($file) {
$this->files[] = $file;
}
// return next record
public function next() {
if ($this->currentRecord < count($this->files)) {
$file = $this->files[$this->currentRecord];
$this->currentRecord++;
return $file;
} else {
return false;
}
}
// sorts records under $sort array
public function sort($sort, $data) {
if (count($this->files) == 0) {
return $this;
}
// defines fields list if it's need
for ($i = 0; $i < count($sort); $i++) {
$fieldname = $sort[$i]['name'];
if (!isset($this->files[0][$fieldname])) {
if (isset($data[$fieldname])) {
$fieldname = $data[$fieldname]['db_name'];
$sort[$i]['name'] = $fieldname;
} else {
$fieldname = false;
}
}
}
// for every sorting field will sort
for ($i = 0; $i < count($sort); $i++) {
// if field, setted in sort parameter doesn't exist, continue
if ($sort[$i]['name'] == false) {
continue;
}
// sorting by current field
$flag = true;
while ($flag == true) {
$flag = false;
// checks if previous sorting fields are equal
for ($j = 0; $j < count($this->files) - 1; $j++) {
$equal = true;
for ($k = 0; $k < $i; $k++) {
if ($this->files[$j][$sort[$k]['name']] != $this->files[$j + 1][$sort[$k]['name']]) {
$equal = false;
}
}
// compares two records in list under current sorting field and sorting direction
if (((($this->files[$j][$sort[$i]['name']] > $this->files[$j + 1][$sort[$i]['name']])&&($sort[$i]['direction'] == 'ASC'))||(($this->files[$j][$sort[$i]['name']] < $this->files[$j + 1][$sort[$i]['name']])&&($sort[$i]['direction'] == 'DESC')))&&($equal == true)) {
$c = $this->files[$j];
$this->files[$j] = $this->files[$j+1];
$this->files[$j+1] = $c;
$flag = true;
}
}
}
}
return $this;
}
}
// singleton class for setting file types filter
class FileSystemTypes {
static private $instance = NULL;
private $extentions = Array();
private $extentions_not = Array();
private $all = true;
private $patterns = Array();
// predefined types
private $types = Array(
'image' => Array('jpg', 'jpeg', 'gif', 'png', 'tiff', 'bmp', 'psd', 'dir'),
'document' => Array('txt', 'doc', 'docx', 'xls', 'xlsx', 'rtf', 'dir'),
'web' => Array('php', 'html', 'htm', 'js', 'css', 'dir'),
'audio' => Array('mp3', 'wav', 'ogg', 'dir'),
'video' => Array('avi', 'mpg', 'mpeg', 'mp4', 'dir'),
'only_dir' => Array('dir')
);
static function getInstance() {
if (self::$instance == NULL) {
self::$instance = new FileSystemTypes();
}
return self::$instance;
}
// sets array of extentions
public function setExtentions($ext) {
$this->all = false;
$this->extentions = $ext;
}
// adds one extention in array
public function addExtention($ext) {
$this->all = false;
$this->extentions[] = $ext;
}
// adds one extention which will not ouputed in array
public function addExtentionNot($ext) {
$this->extentions_not[] = $ext;
}
// returns array of extentions
public function getExtentions() {
return $this->extentions;
}
// adds regexp pattern
public function addPattern($pattern) {
$this->all = false;
$this->patterns[] = $pattern;
}
// clear extentions array
public function clearExtentions() {
$this->all = true;
$this->extentions = Array();
}
// clear regexp patterns array
public function clearPatterns() {
$this->all = true;
$this->patterns = Array();
}
// clear all filters
public function clearAll() {
$this->clearExtentions();
$this->clearPatterns();
}
// sets predefined type
public function setType($type, $clear = false) {
$this->all = false;
if ($type == 'all') {
$this->all = true;
return true;
}
if (isset($this->types[$type])) {
if ($clear) {
$this->clearExtentions();
}
for ($i = 0; $i < count($this->types[$type]); $i++) {
$this->extentions[] = $this->types[$type][$i];
}
return true;
} else {
return false;
}
}
// check file under setted filter
public function checkFile($filename, $fileNameExt) {
if (in_array($fileNameExt['ext'], $this->extentions_not)) {
return false;
}
if ($this->all) {
return true;
}
if ((count($this->extentions) > 0)&&(!in_array($fileNameExt['ext'], $this->extentions))) {
return false;
}
for ($i = 0; $i < count($this->patterns); $i++) {
if (!preg_match($this->patterns[$i], $filename)) {
return false;
}
}
return true;
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("db_common.php");
/*! MSSQL implementation of DataWrapper
**/
class MsSQLDBDataWrapper extends DBDataWrapper{
private $last_id=""; //!< ID of previously inserted record
private $insert_operation=false; //!< flag of insert operation
private $start_from=false; //!< index of start position
public function query($sql){
LogMaster::log($sql);
$res = mssql_query($sql,$this->connection);
if ($this->insert_operation){
$last = mssql_fetch_assoc($res);
$this->last_id = $last["dhx_id"];
mssql_free_result($res);
}
if ($this->start_from)
mssql_data_seek($res,$this->start_from);
return $res;
}
public function get_next($res){
return mssql_fetch_assoc($res);
}
public function get_new_id(){
/*
MSSQL doesn't support identity or auto-increment fields
Insert SQL returns new ID value, which stored in last_id field
*/
return $this->last_id;
}
protected function insert_query($data,$request){
$sql = parent::insert_query($data,$request);
$this->insert_operation=true;
return $sql.";SELECT @@IDENTITY AS dhx_id";
}
protected function select_query($select,$from,$where,$sort,$start,$count){
if (!$from)
return $select;
$sql="SELECT " ;
if ($count)
$sql.=" TOP ".($count+$start);
$sql.=" ".$select." FROM ".$from;
if ($where) $sql.=" WHERE ".$where;
if ($sort) $sql.=" ORDER BY ".$sort;
if ($start && $count)
$this->start_from=$start;
else
$this->start_from=false;
return $sql;
}
public function escape($data){
/*
there is no special escaping method for mssql - use common logic
*/
return str_replace("'","''",$data);
}
public function begin_transaction(){
$this->query("BEGIN TRAN");
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("db_common.php");
class MySQLiDBDataWrapper extends MySQLDBDataWrapper{
public function query($sql){
LogMaster::log($sql);
$res = $this->connection->query($sql);
if ($res===false) throw new Exception("MySQL operation failed\n".$this->connection->error);
return $res;
}
public function get_next($res){
return $res->fetch_assoc();
}
public function get_new_id(){
return $this->connection->insert_id;
}
public function escape($data){
return $this->connection->real_escape_string($data);
}
public function tables_list() {
$result = $this->connection->query("SHOW TABLES");
if ($result===false) throw new Exception("MySQL operation failed\n".$this->connection->error);
$tables = array();
while ($table = $result->fetch_array()) {
$tables[] = $table[0];
}
return $tables;
}
public function fields_list($table) {
$result = $this->connection->query("SHOW COLUMNS FROM `".$table."`");
if ($result===false) throw new Exception("MySQL operation failed\n".$this->connection->error);
$fields = array();
while ($field = $result->fetch_array()) {
if ($field['Key'] == "PRI") {
$fields[$field[0]] = 1;
} else {
$fields[$field[0]] = 0;
}
}
return $fields;
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("db_common.php");
/*! Implementation of DataWrapper for Oracle
**/
class OracleDBDataWrapper extends DBDataWrapper{
private $last_id=""; //id of previously inserted record
private $insert_operation=false; //flag of insert operation
public function query($sql){
LogMaster::log($sql);
$stm = oci_parse($this->connection,$sql);
if ($stm===false) throw new Exception("Oracle - sql parsing failed\n".oci_error($this->connection));
$out = array(0=>null);
if($this->insert_operation){
oci_bind_by_name($stm,":outID",$out[0],999);
$this->insert_operation=false;
}
$mode = ($this->is_record_transaction() || $this->is_global_transaction())?OCI_DEFAULT:OCI_COMMIT_ON_SUCCESS;
$res=oci_execute($stm,$mode);
if ($res===false) throw new Exception("Oracle - sql execution failed\n".oci_error($this->connection));
$this->last_id=$out[0];
return $stm;
}
public function get_next($res){
$data = oci_fetch_assoc($res);
if ($data){
foreach ($data as $k => $v)
$data[strtolower($k)] = $v;
}
return $data;
}
public function get_new_id(){
/*
Oracle doesn't support identity or auto-increment fields
Insert SQL returns new ID value, which stored in last_id field
*/
return $this->last_id;
}
protected function insert_query($data,$request){
$sql = parent::insert_query($data,$request);
$this->insert_operation=true;
return $sql." returning ".$this->config->id["db_name"]." into :outID";
}
protected function select_query($select,$from,$where,$sort,$start,$count){
if (!$from)
return $select;
$sql="SELECT ".$select." FROM ".$from;
if ($where) $sql.=" WHERE ".$where;
if ($sort) $sql.=" ORDER BY ".$sort;
if ($start || $count)
$sql="SELECT * FROM ( select /*+ FIRST_ROWS(".$count.")*/dhx_table.*, ROWNUM rnum FROM (".$sql.") dhx_table where ROWNUM <= ".($count+$start)." ) where rnum >".$start;
return $sql;
}
public function escape($data){
/*
as far as I can see the only way to escape data is by using oci_bind_by_name
while it is neat solution in common case, it conflicts with existing SQL building logic
fallback to simple escaping
*/
return str_replace("'","''",$data);
}
public function begin_transaction(){
//auto-start of transaction
}
public function commit_transaction(){
oci_commit($this->connection);
}
public function rollback_transaction(){
oci_rollback($this->connection);
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("db_common.php");
/*! Implementation of DataWrapper for PDO
if you plan to use it for Oracle - use Oracle connection type instead
**/
class PDODBDataWrapper extends DBDataWrapper{
private $last_result;//!< store result or last operation
public function query($sql){
LogMaster::log($sql);
$res=$this->connection->query($sql);
if ($res===false) {
$message = $this->connection->errorInfo();
throw new Exception("PDO - sql execution failed\n".$message[2]);
}
return new PDOResultSet($res);
}
protected function select_query($select,$from,$where,$sort,$start,$count){
if (!$from)
return $select;
$sql="SELECT ".$select." FROM ".$from;
if ($where) $sql.=" WHERE ".$where;
if ($sort) $sql.=" ORDER BY ".$sort;
if ($start || $count) {
if ($this->connection->getAttribute(PDO::ATTR_DRIVER_NAME)=="pgsql")
$sql.=" OFFSET ".$start." LIMIT ".$count;
else
$sql.=" LIMIT ".$start.",".$count;
}
return $sql;
}
public function get_next($res){
$data = $res->next();
return $data;
}
public function get_new_id(){
return $this->connection->lastInsertId();
}
public function escape($str){
$res=$this->connection->quote($str);
if ($res===false) //not supported by pdo driver
return str_replace("'","''",$str);
return substr($res,1,-1);
}
}
class PDOResultSet{
private $res;
public function __construct($res){
$this->res = $res;
}
public function next(){
$data = $this->res->fetch(PDO::FETCH_ASSOC);
if (!$data){
$this->res->closeCursor();
return null;
}
return $data;
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("db_common.php");
//DataProcessor::$action_param ="dhx_editor_status";
/*! Implementation of DataWrapper for PDO
if you plan to use it for Oracle - use Oracle connection type instead
**/
class PHPCakeDBDataWrapper extends ArrayDBDataWrapper{
public function select($sql){
$source = $sql->get_source();
if (is_array($source)) //result of find
$res = $source;
else
$res = $this->connection->find("all");
if (sizeof($res)){
$name = get_class($this->connection);
$temp = array();
for ($i=sizeof($res)-1; $i>=0; $i--)
$temp[]=&$res[$i][$name];
}
return new ArrayQueryWrapper($temp);
}
protected function getErrorMessage(){
$errors = $this->connection->invalidFields();
$text = array();
foreach ($errors as $key => $value){
$text[] = $key." - ".$value[0];
}
return implode("\n", $text);
}
public function insert($data,$source){
$name = get_class($this->connection);
$save = array();
$temp_data = $data->get_data();
unset($temp_data[$this->config->id['db_name']]);
unset($temp_data["!nativeeditor_status"]);
$save[$name] = $temp_data;
if ($this->connection->save($save)){
$data->success($this->connection->getLastInsertID());
} else {
$data->set_response_attribute("details", $this->getErrorMessage());
$data->invalid();
}
}
public function delete($data,$source){
$id = $data->get_id();
$this->connection->delete($id);
$data->success();
}
public function update($data,$source){
$name = get_class($this->connection);
$save = array();
$save[$name] = &$data->get_data();
if ($this->connection->save($save)){
$data->success();
} else {
$data->set_response_attribute("details", $this->getErrorMessage());
$data->invalid();
}
}
public function escape($str){
throw new Exception("Not implemented");
}
public function query($str){
throw new Exception("Not implemented");
}
public function get_new_id(){
throw new Exception("Not implemented");
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("db_common.php");
/*! Implementation of DataWrapper for PDO
if you plan to use it for Oracle - use Oracle connection type instead
**/
class PHPCIDBDataWrapper extends DBDataWrapper{
private $last_result;//!< store result or last operation
public function query($sql){
LogMaster::log($sql);
$res=$this->connection->query($sql);
if ($res===false) {
throw new Exception("CI - sql execution failed");
}
return new PHPCIResultSet($res);
}
public function get_next($res){
$data = $res->next();
return $data;
}
public function get_new_id(){
return $this->connection->insert_id();
}
public function escape($str){
return $this->connection->escape_str($str);
}
public function escape_name($data){
return $this->connection->protect_identifiers($data);
}
}
class PHPCIResultSet{
private $res;
private $start;
private $count;
public function __construct($res){
$this->res = $res;
$this->start = $res->current_row;
$this->count = $res->num_rows;
}
public function next(){
if ($this->start != $this->count){
return $this->res->row($this->start++,'array');
} else {
$this->res->free_result();
return null;
}
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("db_common.php");
class PHPYiiDBDataWrapper extends ArrayDBDataWrapper{
public function select($sql){
if (is_array($this->connection)) //result of findAll
$res = $this->connection;
else
$res = $this->connection->findAll();
if (sizeof($res)){
$temp = array();
foreach ($res as $obj)
$temp[]=$obj->getAttributes();
}
return new ArrayQueryWrapper($temp);
}
protected function getErrorMessage(){
$errors = $this->connection->invalidFields();
$text = array();
foreach ($errors as $key => $value){
$text[] = $key." - ".$value[0];
}
return implode("\n", $text);
}
public function insert($data,$source){
$name = get_class($this->connection);
$obj = new $name();
$this->fill_model_and_save($obj, $data);
}
public function delete($data,$source){
$obj = $this->connection->findByPk($data->get_id());
if ($obj->delete()){
$data->success();
$data->set_new_id($obj->getPrimaryKey());
} else {
$data->set_response_attribute("details", $this->errors_to_string($obj->getErrors()));
$data->invalid();
}
}
public function update($data,$source){
$obj = $this->connection->findByPk($data->get_id());
$this->fill_model_and_save($obj, $data);
}
protected function fill_model_and_save($obj, $data){
$values = $data->get_data();
//map data to model object
for ($i=0; $i < sizeof($this->config->text); $i++){
$step=$this->config->text[$i];
$obj->setAttribute($step["name"], $data->get_value($step["name"]));
}
if ($relation = $this->config->relation_id["db_name"])
$obj->setAttribute($relation, $data->get_value($relation));
//save model
if ($obj->save()){
$data->success();
$data->set_new_id($obj->getPrimaryKey());
} else {
$data->set_response_attribute("details", $this->errors_to_string($obj->getErrors()));
$data->invalid();
}
}
protected function errors_to_string($errors){
$text = array();
foreach($errors as $value)
$text[]=implode("\n", $value);
return implode("\n",$text);
}
public function escape($str){
throw new Exception("Not implemented");
}
public function query($str){
throw new Exception("Not implemented");
}
public function get_new_id(){
throw new Exception("Not implemented");
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("db_common.php");
/*! Implementation of DataWrapper for PostgreSQL
**/
class PostgreDBDataWrapper extends DBDataWrapper{
public function query($sql){
LogMaster::log($sql);
$res=pg_query($this->connection,$sql);
if ($res===false) throw new Exception("Postgre - sql execution failed\n".pg_last_error($this->connection));
return $res;
}
protected function select_query($select,$from,$where,$sort,$start,$count){
if (!$from)
return $select;
$sql="SELECT ".$select." FROM ".$from;
if ($where) $sql.=" WHERE ".$where;
if ($sort) $sql.=" ORDER BY ".$sort;
if ($start || $count)
$sql.=" OFFSET ".$start." LIMIT ".$count;
return $sql;
}
public function get_next($res){
return pg_fetch_assoc($res);
}
public function get_new_id(){
$res = pg_query( $this->connection, "SELECT LASTVAL() AS seq");
$data = pg_fetch_assoc($res);
pg_free_result($res);
return $data['seq'];
}
public function escape($data){
//need to use oci_bind_by_name
return pg_escape_string($this->connection,$data);
}
public function tables_list() {
$sql = "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'";
$res = pg_query($this->connection, $sql);
$tables = array();
while ($table = pg_fetch_assoc($res)) {
$tables[] = $table['table_name'];
}
return $tables;
}
public function fields_list($table) {
$sql = "SELECT * FROM information_schema.constraint_column_usage";
$result = pg_query($this->connection, $sql);
$field = pg_fetch_assoc($result);
$id = $field['column_name'];
$sql = "SELECT * FROM information_schema.columns WHERE table_name ='".$table."';";
$result = pg_query($this->connection, $sql);
$fields = array();
$id = "";
while ($field = pg_fetch_assoc($result)) {
$fields[] = $field["column_name"];
}
return array('fields' => $fields, 'key' => $id );
}
}
?>
\ No newline at end of file
<?php
require_once("db_common.php");
/*! SaSQL implementation of DataWrapper
**/
class SaSQLDBDataWrapper extends DBDataWrapper{
private $last_id=""; //!< ID of previously inserted record
public function query($sql){
LogMaster::log($sql);
$res=sasql_query($this->connection, $sql);
if ($res===false) throw new Exception("SaSQL operation failed\n".sasql_error($this->connection));
$this->last_result = $res;
return $res;
}
public function get_next($res){
if (!$res)
$res = $this->last_result;
return sasql_fetch_assoc($res);
}
public function get_new_id(){
return sasql_insert_id($this->connection);
}
protected function insert_query($data,$request){
$sql = parent::insert_query($data,$request);
$this->insert_operation=true;
return $sql;
}
protected function select_query($select,$from,$where,$sort,$start,$count){
if (!$from)
return $select;
$sql="SELECT " ;
if ($count)
$sql.=" TOP ".($count+$start);
$sql.=" ".$select." FROM ".$from;
if ($where) $sql.=" WHERE ".$where;
if ($sort) $sql.=" ORDER BY ".$sort;
return $sql;
}
public function escape($data){
return sasql_escape_string($this->connection, $data);
}
public function begin_transaction(){
$this->query("BEGIN TRAN");
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("db_common.php");
/*! SQLite implementation of DataWrapper
**/
class SQLiteDBDataWrapper extends DBDataWrapper{
public function query($sql){
LogMaster::log($sql);
$res = sqlite_query($this->connection,$sql);
if ($res === false)
throw new Exception("SQLLite - sql execution failed\n".sqlite_error_string(sqlite_last_error($this->connection)));
return $res;
}
public function get_next($res){
$data = sqlite_fetch_array($res, SQLITE_ASSOC);
return $data;
}
public function get_new_id(){
return sqlite_last_insert_rowid($this->connection);
}
public function escape($data){
return sqlite_escape_string($data);
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("db_common.php");
/*! SQLite implementation of DataWrapper
**/
class SQLite3DBDataWrapper extends DBDataWrapper{
public function query($sql){
LogMaster::log($sql);
$res = $this->connection->query($sql);
if ($res === false)
throw new Exception("SQLLite - sql execution failed\n".$this->connection->lastErrorMsg());
return $res;
}
public function get_next($res){
return $res->fetchArray();
}
public function get_new_id(){
return $this->connection->lastInsertRowID();
}
public function escape($data){
return $this->connection->escapeString($data);
}
}
?>
\ No newline at end of file
<?php
/*
This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
to use it in non-GPL project. Please contact sales@dhtmlx.com for details
*/
?><?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("db_common.php");
/*! MSSQL implementation of DataWrapper
**/
class SQLSrvDBDataWrapper extends DBDataWrapper{
private $last_id=""; //!< ID of previously inserted record
private $insert_operation=false; //!< flag of insert operation
private $start_from=false; //!< index of start position
public function query($sql){
LogMaster::log($sql);
if ($this->start_from)
$res = sqlsrv_query($this->connection,$sql, array(), array("Scrollable" => SQLSRV_CURSOR_STATIC));
else
$res = sqlsrv_query($this->connection,$sql);
if ($res === false){
$errors = sqlsrv_errors();
$message = Array();
foreach($errors as $error)
$message[]=$error["SQLSTATE"].$error["code"].$error["message"];
throw new Exception("SQLSrv operation failed\n".implode("\n\n", $message));
}
if ($this->insert_operation){
sqlsrv_next_result($res);
$last = sqlsrv_fetch_array($res);
$this->last_id = $last["dhx_id"];
sqlsrv_free_stmt($res);
}
if ($this->start_from)
$data = sqlsrv_fetch($res, SQLSRV_SCROLL_ABSOLUTE, $this->start_from-1);
return $res;
}
public function get_next($res){
$data = sqlsrv_fetch_array($res, SQLSRV_FETCH_ASSOC);
if ($data)
foreach ($data as $key => $value)
if (is_a($value, "DateTime"))
$data[$key] = $value->format("Y-m-d H:i");
return $data;
}
public function get_new_id(){
/*
MSSQL doesn't support identity or auto-increment fields
Insert SQL returns new ID value, which stored in last_id field
*/
return $this->last_id;
}
protected function insert_query($data,$request){
$sql = parent::insert_query($data,$request);
$this->insert_operation=true;
return $sql.";SELECT SCOPE_IDENTITY() as dhx_id";
}
protected function select_query($select,$from,$where,$sort,$start,$count){
if (!$from)
return $select;
$sql="SELECT " ;
if ($count)
$sql.=" TOP ".($count+$start);
$sql.=" ".$select." FROM ".$from;
if ($where) $sql.=" WHERE ".$where;
if ($sort) $sql.=" ORDER BY ".$sort;
if ($start && $count)
$this->start_from=$start;
else
$this->start_from=false;
return $sql;
}
public function escape($data){
/*
there is no special escaping method for mssql - use common logic
*/
return str_replace("'","''",$data);
}
public function begin_transaction(){
sqlsrv_begin_transaction($this->connection);
}
public function commit_transaction(){
sqlsrv_commit($this->connection);
}
public function rollback_transaction(){
sqlsrv_rollback($this->connection);
}
}
?>
\ No newline at end of file
<?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
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("base_connector.php");
/*! DataItem class for dhxForm component
**/
class FormDataItem extends DataItem{
/*! return self as XML string
*/
function to_xml(){
if ($this->skip) return "";
$str="";
for ($i = 0; $i < count($this->config->data); $i++) {
$str .= "<".$this->config->data[$i]['name']."><![CDATA[".$this->data[$this->config->data[$i]['name']]."]]></".$this->config->data[$i]['name'].">";
}
return $str;
}
}
/*! Connector class for dhtmlxForm
**/
class FormConnector extends Connector{
/*! constructor
Here initilization of all Masters occurs, execution timer initialized
@param res
db connection resource
@param type
string , which hold type of database ( MySQL or Postgre ), optional, instead of short DB name, full name of DataWrapper-based class can be provided
@param item_type
name of class, which will be used for item rendering, optional, DataItem will be used by default
@param data_type
name of class which will be used for dataprocessor calls handling, optional, DataProcessor class will be used by default.
*/
public function __construct($res,$type=false,$item_type=false,$data_type=false){
if (!$item_type) $item_type="FormDataItem";
if (!$data_type) $data_type="FormDataProcessor";
parent::__construct($res,$type,$item_type,$data_type);
}
//parse GET scoope, all operations with incoming request must be done here
function parse_request(){
parent::parse_request();
if (isset($_GET["id"]))
$this->request->set_filter($this->config->id["name"],$_GET["id"],"=");
else if (!$_POST["ids"])
throw new Exception("ID parameter is missed");
}
}
/*! DataProcessor class for dhxForm component
**/
class FormDataProcessor extends DataProcessor{
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("base_connector.php");
require_once("data_connector.php");
/*! DataItem class for Gantt component
**/
class GanttDataItem extends DataItem{
/*! return self as XML string
*/
function to_xml(){
if ($this->skip) return "";
$str="<task id='".$this->get_id()."' >";
$str.="<start_date><![CDATA[".$this->data[$this->config->text[0]["name"]]."]]></start_date>";
$str.="<".$this->config->text[1]["name"]."><![CDATA[".$this->data[$this->config->text[1]["name"]]."]]></".$this->config->text[1]["name"].">";
$str.="<text><![CDATA[".$this->data[$this->config->text[2]["name"]]."]]></text>";
for ($i=3; $i<sizeof($this->config->text); $i++){
$extra = $this->config->text[$i]["name"];
$str.="<".$extra."><![CDATA[".$this->data[$extra]."]]></".$extra.">";
}
if ($this->userdata !== false)
foreach ($this->userdata as $key => $value)
$str.="<".$key."><![CDATA[".$value."]]></".$key.">";
return $str."</task>";
}
}
/*! Connector class for dhtmlxGantt
**/
class GanttConnector extends Connector{
protected $extra_output="";//!< extra info which need to be sent to client side
protected $options=array();//!< hash of OptionsConnector
protected $links_mode = false;
/*! assign options collection to the column
@param name
name of the column
@param options
array or connector object
*/
public function set_options($name,$options){
if (is_array($options)){
$str="";
foreach($options as $k => $v)
$str.="<item value='".$this->xmlentities($k)."' label='".$this->xmlentities($v)."' />";
$options=$str;
}
$this->options[$name]=$options;
}
/*! constructor
Here initilization of all Masters occurs, execution timer initialized
@param res
db connection resource
@param type
string , which hold type of database ( MySQL or Postgre ), optional, instead of short DB name, full name of DataWrapper-based class can be provided
@param item_type
name of class, which will be used for item rendering, optional, DataItem will be used by default
@param data_type
name of class which will be used for dataprocessor calls handling, optional, DataProcessor class will be used by default.
* @param render_type
name of class which will be used for rendering.
*/
public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
if (!$item_type) $item_type="GanttDataItem";
if (!$data_type) $data_type="GanttDataProcessor";
if (!$render_type) $render_type="RenderStrategy";
parent::__construct($res,$type,$item_type,$data_type,$render_type);
$this->event->attach("afterDelete", array($this, "delete_related_links"));
$this->event->attach("afterOrder", array($this, "order_set_parent"));
}
//parse GET scoope, all operations with incoming request must be done here
function parse_request(){
parent::parse_request();
if (isset($_GET["gantt_mode"]) && $_GET["gantt_mode"] == "links")
$this->links_mode = true;
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"],">");
}
}
function order_set_parent($action){
$value = $action->get_id();
$parent = $action->get_value("parent");
$table = $this->request->get_source();
$id = $this->config->id["db_name"];
$this->sql->query("UPDATE $table SET parent = $parent WHERE $id = $value");
}
function delete_related_links($action){
if (isset($this->options["links"])){
$links = $this->options["links"];
$value = $this->sql->escape($action->get_new_id());
$table = $links->get_request()->get_source();
$this->sql->query("DELETE FROM $table WHERE source = '$value'");
$this->sql->query("DELETE FROM $table WHERE target = '$value'");
}
}
public function render_links($table,$id="",$fields=false,$extra=false,$relation_id=false) {
$links = new GanttLinksConnector($this->get_connection(),$this->names["db_class"]);
$links->render_table($table,$id,$fields,$extra);
$this->set_options("links", $links);
}
}
/*! DataProcessor class for Gantt component
**/
class GanttDataProcessor 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=="duration" && $this->config->text[1]["name"] == "duration")
return $this->config->text[1]["db_name"];
if ($data=="end_date" && $this->config->text[1]["name"] == "end_date")
return $this->config->text[1]["db_name"];
if ($data=="text")
return $this->config->text[2]["db_name"];
return $data;
}
}
class JSONGanttDataItem extends GanttDataItem{
/*! return self as XML string
*/
function to_xml(){
if ($this->skip) return "";
$obj = array();
$obj['id'] = $this->get_id();
$obj['start_date'] = $this->data[$this->config->text[0]["name"]];
$obj[$this->config->text[1]["name"]] = $this->data[$this->config->text[1]["name"]];
$obj['text'] = $this->data[$this->config->text[2]["name"]];
for ($i=3; $i<sizeof($this->config->text); $i++){
$extra = $this->config->text[$i]["name"];
$obj[$extra]=$this->data[$extra];
}
if ($this->userdata !== false)
foreach ($this->userdata as $key => $value)
$obj[$key]=$value;
return $obj;
}
}
class JSONGanttConnector extends GanttConnector {
protected $data_separator = ",";
/*! constructor
Here initilization of all Masters occurs, execution timer initialized
@param res
db connection resource
@param type
string , which hold type of database ( MySQL or Postgre ), optional, instead of short DB name, full name of DataWrapper-based class can be provided
@param item_type
name of class, which will be used for item rendering, optional, DataItem will be used by default
@param data_type
name of class which will be used for dataprocessor calls handling, optional, DataProcessor class will be used by default.
*/
public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
if (!$item_type) $item_type="JSONGanttDataItem";
if (!$data_type) $data_type="GanttDataProcessor";
if (!$render_type) $render_type="JSONRenderStrategy";
parent::__construct($res,$type,$item_type,$data_type,$render_type);
}
protected function xml_start() {
return '{ "data":';
}
protected function xml_end() {
$this->fill_collections();
$end = (!empty($this->extra_output)) ? ', "collections": {'.$this->extra_output.'}' : '';
foreach ($this->attributes as $k => $v)
$end.=", \"".$k."\":\"".$v."\"";
$end .= '}';
return $end;
}
/*! assign options collection to the column
@param name
name of the column
@param options
array or connector object
*/
public function set_options($name,$options){
if (is_array($options)){
$str=array();
foreach($options as $k => $v)
$str[]='{"id":"'.$this->xmlentities($k).'", "value":"'.$this->xmlentities($v).'"}';
$options=implode(",",$str);
}
$this->options[$name]=$options;
}
/*! generates xml description for options collections
@param list
comma separated list of column names, for which options need to be generated
*/
protected function fill_collections($list=""){
$options = array();
foreach ($this->options as $k=>$v) {
$name = $k;
$option="\"{$name}\":[";
if (!is_string($this->options[$name])){
$data = json_encode($this->options[$name]->render());
$option.=substr($data,1,-1);
} else
$option.=$this->options[$name];
$option.="]";
$options[] = $option;
}
$this->extra_output .= implode($this->data_separator, $options);
}
/*! output fetched data as XML
@param res
DB resultset
*/
protected function output_as_xml($res){
$result = $this->render_set($res);
if ($this->simple) return $result;
$data=$this->xml_start().json_encode($result).$this->xml_end();
if ($this->as_string) return $data;
$out = new OutputWriter($data, "");
$out->set_type("json");
$this->event->trigger("beforeOutput", $this, $out);
$out->output("", true, $this->encoding);
}
public function render_links($table,$id="",$fields=false,$extra=false,$relation_id=false) {
$links = new JSONGanttLinksConnector($this->get_connection(),$this->names["db_class"]);
$links->render_table($table,$id,$fields,$extra);
$this->set_options("links", $links);
}
/*! render self
process commands, output requested data as XML
*/
public function render(){
$this->event->trigger("onInit", $this);
EventMaster::trigger_static("connectorInit",$this);
if (!$this->as_string)
$this->parse_request();
$this->set_relation();
if ($this->live_update !== false && $this->updating!==false) {
$this->live_update->get_updates();
} else {
if ($this->editing){
if ($this->links_mode && isset($this->options["links"])) {
$this->options["links"]->save();
} else {
$dp = new $this->names["data_class"]($this,$this->config,$this->request);
$dp->process($this->config,$this->request);
}
} else {
if (!$this->access->check("read")){
LogMaster::log("Access control: read operation blocked");
echo "Access denied";
die();
}
$wrap = new SortInterface($this->request);
$this->apply_sorts($wrap);
$this->event->trigger("beforeSort",$wrap);
$wrap->store();
$wrap = new FilterInterface($this->request);
$this->apply_filters($wrap);
$this->event->trigger("beforeFilter",$wrap);
$wrap->store();
if ($this->model && method_exists($this->model, "get")){
$this->sql = new ArrayDBDataWrapper();
$result = new ArrayQueryWrapper(call_user_func(array($this->model, "get"), $this->request));
$out = $this->output_as_xml($result);
} else {
$out = $this->output_as_xml($this->get_resource());
if ($out !== null) return $out;
}
}
}
$this->end_run();
}
}
class GanttLinksConnector extends OptionsConnector {
public function render(){
if (!$this->init_flag){
$this->init_flag=true;
return "";
}
$res = $this->sql->select($this->request);
return $this->render_set($res);
}
public function save() {
$dp = new $this->names["data_class"]($this,$this->config,$this->request);
$dp->process($this->config,$this->request);
}
}
class JSONGanttLinksConnector extends JSONOptionsConnector {
public function render(){
if (!$this->init_flag){
$this->init_flag=true;
return "";
}
$res = $this->sql->select($this->request);
return $this->render_set($res);
}
public function save() {
$dp = new $this->names["data_class"]($this,$this->config,$this->request);
$dp->process($this->config,$this->request);
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
class GridConfiguration{
/*! attaching header functionality
*/
protected $headerDelimiter = ',';
protected $headerNames = false;
protected $headerAttaches = array();
protected $footerAttaches = array();
protected $headerWidthsUnits = 'px';
protected $headerIds = false;
protected $headerWidths = false;
protected $headerTypes = false;
protected $headerAlign = false;
protected $headerVAlign = false;
protected $headerSorts = false;
protected $headerColors = false;
protected $headerHidden = false;
protected $headerFormat = false;
protected $convert_mode = false;
function __construct($headers = false){
if ($headers === false || $headers === true )
$this->headerNames = $headers;
else
$this->setHeader($headers);
}
/*! brief convert list of parameters to an array
@param param
list of values or array of values
@return array of parameters
*/
private function parse_param_array($param, $check=false, $default = ""){
if (gettype($param) == 'string')
$param = explode($this->headerDelimiter, $param);
if ($check){
for ($i=0; $i < sizeof($param); $i++) {
if (!array_key_exists($param[$i],$check))
$param[$i] = $default;
}
}
return $param;
}
/*! sets delimiter for string arguments in attach header functions (default is ,)
@param headerDelimiter
string delimiter
*/
public function setHeaderDelimiter($headerDelimiter) {
$this->headerDelimiter = $headerDelimiter;
}
/*! sets header
@param names
array of names or string of names, delimited by headerDelimiter (default is ,)
*/
public function setHeader($names) {
if ($names instanceof DataConfig){
$out = array();
for ($i=0; $i < sizeof($names->text); $i++)
$out[]=$names->text[$i]["name"];
$names = $out;
}
$this->headerNames = $this->parse_param_array($names);
}
/*! sets init columns width in pixels
@param wp
array of widths or string of widths, delimited by headerDelimiter (default is ,)
*/
public function setInitWidths($wp) {
$this->headerWidths = $this->parse_param_array($wp);
$this->headerWidthsUnits = 'px';
}
/*! sets init columns width in persents
@param wp
array of widths or string of widths, delimited by headerDelimiter (default is ,)
*/
public function setInitWidthsP($wp) {
$this->setInitWidths($wp);
$this->headerWidthsUnits = '%';
}
/*! sets columns align
@param alStr
array of aligns or string of aligns, delimited by headerDelimiter (default is ,)
*/
public function setColAlign($alStr) {
$this->headerAlign = $this->parse_param_array($alStr,
array("right"=>1, "left"=>1, "center"=>1, "justify"=>1),
"left");
}
/*! sets columns vertical align
@param alStr
array of vertical aligns or string of vertical aligns, delimited by headerDelimiter (default is ,)
*/
public function setColVAlign($alStr) {
$this->headerVAlign = $this->parse_param_array($alStr,
array("baseline"=>1, "sub"=>1, "super"=>1, "top"=>1, "text-top"=>1, "middle"=>1, "bottom"=>1, "text-bottom"=>1),
"top");
}
/*! sets column types
@param typeStr
array of types or string of types, delimited by headerDelimiter (default is ,)
*/
public function setColTypes($typeStr) {
$this->headerTypes = $this->parse_param_array($typeStr);
}
/*! sets columns sorting
@param sortStr
array if sortings or string of sortings, delimited by headerDelimiter (default is ,)
*/
public function setColSorting($sortStr) {
$this->headerSorts = $this->parse_param_array($sortStr);
}
/*! sets columns colors
@param colorStr
array of colors or string of colors, delimited by headerDelimiter (default is ,)
if (color should not be applied it's value should be null)
*/
public function setColColor($colorStr) {
$this->headerColors = $this->parse_param_array($colorStr);
}
/*! sets hidden columns
@param hidStr
array of bool values or string of bool values, delimited by headerDelimiter (default is ,)
*/
public function setColHidden($hidStr) {
$this->headerHidden = $this->parse_param_array($hidStr);
}
/*! sets columns id
@param idsStr
array of ids or string of ids, delimited by headerDelimiter (default is ,)
*/
public function setColIds($idsStr) {
$this->headerIds = $this->parse_param_array($idsStr);
}
/*! sets number/date format
@param formatArr
array of mask formats for number/dates , delimited by headerDelimiter (default is ,)
*/
public function setColFormat($formatArr) {
$this->headerFormat = $this->parse_param_array($formatArr);
}
/*! attaches header
@param values
array of header names or string of header names, delimited by headerDelimiter (default is ,)
@param styles
array of header styles or string of header styles, delimited by headerDelimiter (default is ,)
*/
public function attachHeader($values, $styles = null, $footer = false) {
$header = array();
$header['values'] = $this->parse_param_array($values);
if ($styles != null) {
$header['styles'] = $this->parse_param_array($styles);
} else {
$header['styles'] = null;
}
if ($footer)
$this->footerAttaches[] = $header;
else
$this->headerAttaches[] = $header;
}
/*! attaches footer
@param values
array of footer names or string of footer names, delimited by headerDelimiter (default is ,)
@param styles
array of footer styles or string of footer styles, delimited by headerDelimiter (default is ,)
*/
public function attachFooter($values, $styles = null) {
$this->attachHeader($values, $styles, true);
}
private function auto_fill($mode){
$headerWidths = array();
$headerTypes = array();
$headerSorts = array();
$headerAttaches = array();
for ($i=0; $i < sizeof($this->headerNames); $i++) {
$headerWidths[] = 100;
$headerTypes[] = "ro";
$headerSorts[] = "connector";
$headerAttaches[] = "#connector_text_filter";
}
if ($this->headerWidths == false)
$this->setInitWidths($headerWidths);
if ($this->headerTypes == false)
$this->setColTypes($headerTypes);
if ($mode){
if ($this->headerSorts == false)
$this->setColSorting($headerSorts);
$this->attachHeader($headerAttaches);
}
}
public function defineOptions($conn){
if (!$conn->is_first_call()) return; //render head only for first call
$config = $conn->get_config();
$full_header = ($this->headerNames === true);
if (gettype($this->headerNames) == 'boolean') //auto-config
$this->setHeader($config);
$this->auto_fill($full_header);
if (isset($_GET["dhx_colls"])) return;
$fillList = array();
for ($i = 0; $i < count($this->headerNames); $i++)
if ($this->headerTypes[$i] == "co" || $this->headerTypes[$i] == "coro")
$fillList[$i] = true;
for ($i = 0; $i < count($this->headerAttaches); $i++) {
for ($j = 0; $j < count($this->headerAttaches[$i]['values']); $j++) {
if ($this->headerAttaches[$i]['values'][$j] == "#connector_select_filter"
|| $this->headerAttaches[$i]['values'][$j] == "#select_filter") {
$fillList[$j] = true;;
}
}
}
$temp = array();
foreach($fillList as $k => $v)
$temp[] = $k;
if (count($temp))
$_GET["dhx_colls"] = implode(",",$temp);
}
/*! gets header as array
*/
private function getHeaderArray() {
$head = Array();
$head[0] = $this->headerNames;
$head = $this->getAttaches($head, $this->headerAttaches);
return $head;
}
/*! get footer as array
*/
private function getFooterArray() {
$foot = Array();
$foot = $this->getAttaches($foot, $this->footerAttaches);
return $foot;
}
/*! gets array of data with attaches
*/
private function getAttaches($to, $from) {
for ($i = 0; $i < count($from); $i++) {
$line = $from[$i]['values'];
$to[] = $line;
}
return $to;
}
/*! calculates rowspan array according #cspan markers
*/
private function processCspan($data) {
$rspan = Array();
for ($i = 0; $i < count($data); $i++) {
$last = 0;
$rspan[$i] = Array();
for ($j = 0; $j < count($data[$i]); $j++) {
$rspan[$i][$j] = 0;
if ($data[$i][$j] === '#cspan') {
$rspan[$i][$last]++;
} else {
$last = $j;
}
}
}
return $rspan;
}
/*! calculates colspan array according #rspan markers
*/
private function processRspan($data) {
$last = Array();
$cspan = Array();
for ($i = 0; $i < count($data); $i++) {
$cspan[$i] = Array();
for ($j = 0; $j < count($data[$i]); $j++) {
$cspan[$i][$j] = 0;
if (!isset($last[$j])) $last[$j] = 0;
if ($data[$i][$j] === '#rspan') {
$cspan[$last[$j]][$j]++;
} else {
$last[$j] = $i;
}
}
}
return $cspan;
}
/*! sets mode of output format: usual mode or convert mode.
* @param mode
* true - convert mode, false - otherwise
*/
public function set_convert_mode($mode) {
$this->convert_mode = $mode;
}
/*! adds header configuration in output XML
*/
public function attachHeaderToXML($conn, $out) {
if (!$conn->is_first_call()) return; //render head only for first call
$head = $this->getHeaderArray();
$foot = $this->getFooterArray();
$rspan = $this->processRspan($head);
$cspan = $this->processCspan($head);
$str = '<head>';
if ($this->convert_mode) $str .= "<columns>";
for ($i = 0; $i < count($this->headerNames); $i++) {
$str .= '<column';
$str .= ' type="'. $this->headerTypes[$i].'"';
$str .= ' width="'.$this->headerWidths[$i].'"';
$str .= $this->headerIds ? ' id="'.$this->headerIds[$i].'"' : '';
$str .= $this->headerAlign[$i] ? ' align="'.$this->headerAlign[$i].'"' : '';
$str .= $this->headerVAlign[$i] ? ' valign="'.$this->headerVAlign[$i].'"' : '';
$str .= $this->headerSorts[$i] ? ' sort="'.$this->headerSorts[$i].'"' : '';
$str .= $this->headerColors[$i] ? ' color="'.$this->headerColors[$i].'"' : '';
$str .= $this->headerHidden[$i] ? ' hidden="'.$this->headerHidden[$i].'"' : '';
$str .= $this->headerFormat[$i] ? ' format="'.$this->headerFormat[$i].'"' : '';
$str .= $cspan[0][$i] ? ' colspan="'.($cspan[0][$i] + 1).'"' : '';
$str .= $rspan[0][$i] ? ' rowspan="'.($rspan[0][$i] + 1).'"' : '';
$str .= '>'.$this->headerNames[$i].'</column>';
}
if (!$this->convert_mode) {
$str .= '<settings><colwidth>'.$this->headerWidthsUnits.'</colwidth></settings>';
if ((count($this->headerAttaches) > 0)||(count($this->footerAttaches) > 0)) {
$str .= '<afterInit>';
}
for ($i = 0; $i < count($this->headerAttaches); $i++) {
$str .= '<call command="attachHeader">';
$str .= '<param>'.implode(",",$this->headerAttaches[$i]['values']).'</param>';
if ($this->headerAttaches[$i]['styles'] != null) {
$str .= '<param>'.implode(",",$this->headerAttaches[$i]['styles']).'</param>';
}
$str .= '</call>';
}
for ($i = 0; $i < count($this->footerAttaches); $i++) {
$str .= '<call command="attachFooter">';
$str .= '<param>'.implode(",",$this->footerAttaches[$i]['values']).'</param>';
if ($this->footerAttaches[$i]['styles'] != null) {
$str .= '<param>'.implode(",",$this->footerAttaches[$i]['styles']).'</param>';
}
$str .= '</call>';
}
if ((count($this->headerAttaches) > 0)||(count($this->footerAttaches) > 0)) {
$str .= '</afterInit>';
}
} else {
$str .= "</columns>";
for ($i = 1; $i < count($head); $i++) {
$str .= "<columns>";
for ($j = 0; $j < count($head[$i]); $j++) {
$str .= '<column';
$str .= $cspan[$i][$j] ? ' colspan="'.($cspan[$i][$j] + 1).'"' : '';
$str .= $rspan[$i][$j] ? ' rowspan="'.($rspan[$i][$j] + 1).'"' : '';
$str .= '>'.$head[$i][$j].'</column>';
}
$str .= "</columns>\n";
}
}
$str .= '</head>';
if ($this->convert_mode && count($foot) > 0) {
$rspan = $this->processRspan($foot);
$cspan = $this->processCspan($foot);
$str .= "<foot>";
for ($i = 0; $i < count($foot); $i++) {
$str .= "<columns>";
for ($j = 0; $j < count($foot[$i]); $j++) {
$str .= '<column';
$str .= $cspan[$i][$j] ? ' colspan="'.($cspan[$i][$j] + 1).'"' : '';
$str .= $rspan[$i][$j] ? ' rowspan="'.($rspan[$i][$j] + 1).'"' : '';
$str .= '>'.$foot[$i][$j].'</column>';
}
$str .= "</columns>\n";
}
$str .= "</foot>";
}
$out->add($str);
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("base_connector.php");
require_once("grid_config.php");
//require_once("grid_dataprocessor.php");
/*! DataItem class for Grid component
**/
class GridDataItem extends DataItem{
protected $row_attrs;//!< hash of row attributes
protected $cell_attrs;//!< hash of cell attributes
function __construct($data,$name,$index=0){
parent::__construct($data,$name,$index);
$this->row_attrs=array();
$this->cell_attrs=array();
}
/*! set color of row
@param color
color of row
*/
function set_row_color($color){
$this->row_attrs["bgColor"]=$color;
}
/*! set style of row
@param color
color of row
*/
function set_row_style($color){
$this->row_attrs["style"]=$color;
}
/*! assign custom style to the cell
@param name
name of column
@param value
css style string
*/
function set_cell_style($name,$value){
$this->set_cell_attribute($name,"style",$value);
}
/*! assign custom class to specific cell
@param name
name of column
@param value
css class name
*/
function set_cell_class($name,$value){
$this->set_cell_attribute($name,"class",$value);
}
/*! set custom cell attribute
@param name
name of column
@param attr
name of attribute
@param value
value of attribute
*/
function set_cell_attribute($name,$attr,$value){
if (!array_key_exists($name, $this->cell_attrs)) $this->cell_attrs[$name]=array();
$this->cell_attrs[$name][$attr]=$value;
}
/*! set custom row attribute
@param attr
name of attribute
@param value
value of attribute
*/
function set_row_attribute($attr,$value){
$this->row_attrs[$attr]=$value;
}
/*! return self as XML string, starting part
*/
public function to_xml_start(){
if ($this->skip) return "";
$str="<row id='".$this->get_id()."'";
foreach ($this->row_attrs as $k=>$v)
$str.=" ".$k."='".$v."'";
$str.=">";
for ($i=0; $i < sizeof($this->config->text); $i++){
$str.="<cell";
$name=$this->config->text[$i]["name"];
if (isset($this->cell_attrs[$name])){
$cattrs=$this->cell_attrs[$name];
foreach ($cattrs as $k => $v)
$str.=" ".$k."='".$this->xmlentities($v)."'";
}
$value = isset($this->data[$name]) ? $this->data[$name] : '';
$str.="><![CDATA[".$value."]]></cell>";
}
if ($this->userdata !== false)
foreach ($this->userdata as $key => $value)
$str.="<userdata name='".$key."'><![CDATA[".$value."]]></userdata>";
return $str;
}
/*! return self as XML string, ending part
*/
public function to_xml_end(){
if ($this->skip) return "";
return "</row>";
}
}
/*! Connector for the dhtmlxgrid
**/
class GridConnector extends Connector{
/*! constructor
Here initilization of all Masters occurs, execution timer initialized
@param res
db connection resource
@param type
string , which hold type of database ( MySQL or Postgre ), optional, instead of short DB name, full name of DataWrapper-based class can be provided
@param item_type
name of class, which will be used for item rendering, optional, DataItem will be used by default
@param data_type
name of class which will be used for dataprocessor calls handling, optional, DataProcessor class will be used by default.
*/
public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
if (!$item_type) $item_type="GridDataItem";
if (!$data_type) $data_type="GridDataProcessor";
if (!$render_type) $render_type="RenderStrategy";
parent::__construct($res,$type,$item_type,$data_type,$render_type);
}
protected function parse_request(){
parent::parse_request();
if (isset($_GET["dhx_colls"]))
$this->fill_collections($_GET["dhx_colls"]);
}
protected function resolve_parameter($name){
if (intval($name).""==$name)
return $this->config->text[intval($name)]["db_name"];
return $name;
}
/*! replace xml unsafe characters
@param string
string to be escaped
@return
escaped string
*/
protected function xmlentities($string) {
return str_replace( array( '&', '"', "'", '<', '>', '’' ), array( '&amp;' , '&quot;', '&apos;' , '&lt;' , '&gt;', '&apos;' ), $string);
}
/*! assign options collection to the column
@param name
name of the column
@param options
array or connector object
*/
public function set_options($name,$options){
if (is_array($options)){
$str="";
foreach($options as $k => $v)
$str.="<item value='".$this->xmlentities($k)."' label='".$this->xmlentities($v)."' />";
$options=$str;
}
$this->options[$name]=$options;
}
/*! generates xml description for options collections
@param list
comma separated list of column names, for which options need to be generated
*/
protected function fill_collections($list=""){
$names=explode(",",$list);
for ($i=0; $i < sizeof($names); $i++) {
$name = $this->resolve_parameter($names[$i]);
if (!array_key_exists($name,$this->options)){
$this->options[$name] = new DistinctOptionsConnector($this->get_connection(),$this->names["db_class"]);
$c = new DataConfig($this->config);
$r = new DataRequestConfig($this->request);
$c->minimize($name);
$this->options[$name]->render_connector($c,$r);
}
$this->extra_output.="<coll_options for='{$names[$i]}'>";
if (!is_string($this->options[$name]))
$this->extra_output.=$this->options[$name]->render();
else
$this->extra_output.=$this->options[$name];
$this->extra_output.="</coll_options>";
}
}
/*! renders self as xml, starting part
*/
protected function xml_start(){
$attributes = "";
foreach($this->attributes as $k=>$v)
$attributes .= " ".$k."='".$v."'";
if ($this->dload){
if ($pos=$this->request->get_start())
return "<rows pos='".$pos."'".$attributes.">";
else
return "<rows total_count='".$this->sql->get_size($this->request)."'".$attributes.">";
}
else
return "<rows".$attributes.">";
}
/*! renders self as xml, ending part
*/
protected function xml_end(){
return $this->extra_output."</rows>";
}
public function set_config($config = false){
if (gettype($config) == 'boolean')
$config = new GridConfiguration($config);
$this->event->attach("beforeOutput", Array($config, "attachHeaderToXML"));
$this->event->attach("onInit", Array($config, "defineOptions"));
}
}
/*! DataProcessor class for Grid component
**/
class GridDataProcessor extends DataProcessor{
/*! convert incoming data name to valid db name
converts c0..cN to valid field names
@param data
data name from incoming request
@return
related db_name
*/
function name_data($data){
if ($data == "gr_id") return $this->config->id["name"];
$parts=explode("c",$data);
if ($parts[0]=="" && ((string)intval($parts[1]))==$parts[1])
if (sizeof($this->config->text)>intval($parts[1]))
return $this->config->text[intval($parts[1])]["name"];
return $data;
}
}
?>
\ No newline at end of file
<?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
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("base_connector.php");
class MixedConnector extends Connector {
protected $connectors = array();
public function add($name, $conn) {
$this->connectors[$name] = $conn;
}
public function render() {
$result = "{";
$parts = array();
foreach($this->connectors as $name => $conn) {
$conn->asString(true);
$parts[] = "\"".$name."\":".($conn->render())."\n";
}
$result .= implode(",\n", $parts)."}";
echo $result;
}
}
?>
\ No newline at end of file
<?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
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("base_connector.php");
require_once("data_connector.php");
/*! DataItem class for Scheduler component
**/
class SchedulerDataItem extends DataItem{
/*! return self as XML string
*/
function to_xml(){
if ($this->skip) return "";
$str="<event id='".$this->get_id()."' >";
$str.="<start_date><![CDATA[".$this->data[$this->config->text[0]["name"]]."]]></start_date>";
$str.="<end_date><![CDATA[".$this->data[$this->config->text[1]["name"]]."]]></end_date>";
$str.="<text><![CDATA[".$this->data[$this->config->text[2]["name"]]."]]></text>";
for ($i=3; $i<sizeof($this->config->text); $i++){
$extra = $this->config->text[$i]["name"];
$str.="<".$extra."><![CDATA[".$this->data[$extra]."]]></".$extra.">";
}
if ($this->userdata !== false)
foreach ($this->userdata as $key => $value)
$str.="<".$key."><![CDATA[".$value."]]></".$key.">";
return $str."</event>";
}
}
/*! Connector class for dhtmlxScheduler
**/
class SchedulerConnector extends Connector{
protected $extra_output="";//!< extra info which need to be sent to client side
protected $options=array();//!< hash of OptionsConnector
/*! assign options collection to the column
@param name
name of the column
@param options
array or connector object
*/
public function set_options($name,$options){
if (is_array($options)){
$str="";
foreach($options as $k => $v)
$str.="<item value='".$this->xmlentities($k)."' label='".$this->xmlentities($v)."' />";
$options=$str;
}
$this->options[$name]=$options;
}
/*! constructor
Here initilization of all Masters occurs, execution timer initialized
@param res
db connection resource
@param type
string , which hold type of database ( MySQL or Postgre ), optional, instead of short DB name, full name of DataWrapper-based class can be provided
@param item_type
name of class, which will be used for item rendering, optional, DataItem will be used by default
@param data_type
name of class which will be used for dataprocessor calls handling, optional, DataProcessor class will be used by default.
* @param render_type
name of class which will be used for rendering.
*/
public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
if (!$item_type) $item_type="SchedulerDataItem";
if (!$data_type) $data_type="SchedulerDataProcessor";
if (!$render_type) $render_type="RenderStrategy";
parent::__construct($res,$type,$item_type,$data_type,$render_type);
}
//parse GET scoope, all operations with incoming request must be done here
function parse_request(){
parent::parse_request();
if (count($this->config->text)){
if (isset($_GET["to"]))
$this->request->set_filter($this->config->text[0]["name"],$_GET["to"],"<");
if (isset($_GET["from"]))
$this->request->set_filter($this->config->text[1]["name"],$_GET["from"],">");
}
}
}
/*! DataProcessor class for Scheduler component
**/
class SchedulerDataProcessor extends DataProcessor{
function name_data($data){
if ($data=="start_date")
return $this->config->text[0]["db_name"];
if ($data=="id")
return $this->config->id["db_name"];
if ($data=="end_date")
return $this->config->text[1]["db_name"];
if ($data=="text")
return $this->config->text[2]["db_name"];
return $data;
}
}
class JSONSchedulerDataItem extends SchedulerDataItem{
/*! return self as XML string
*/
function to_xml(){
if ($this->skip) return "";
$obj = array();
$obj['id'] = $this->get_id();
$obj['start_date'] = $this->data[$this->config->text[0]["name"]];
$obj['end_date'] = $this->data[$this->config->text[1]["name"]];
$obj['text'] = $this->data[$this->config->text[2]["name"]];
for ($i=3; $i<sizeof($this->config->text); $i++){
$extra = $this->config->text[$i]["name"];
$obj[$extra]=$this->data[$extra];
}
if ($this->userdata !== false)
foreach ($this->userdata as $key => $value)
$obj[$key]=$value;
return $obj;
}
}
class JSONSchedulerConnector extends SchedulerConnector {
protected $data_separator = ",";
/*! constructor
Here initilization of all Masters occurs, execution timer initialized
@param res
db connection resource
@param type
string , which hold type of database ( MySQL or Postgre ), optional, instead of short DB name, full name of DataWrapper-based class can be provided
@param item_type
name of class, which will be used for item rendering, optional, DataItem will be used by default
@param data_type
name of class which will be used for dataprocessor calls handling, optional, DataProcessor class will be used by default.
*/
public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
if (!$item_type) $item_type="JSONSchedulerDataItem";
if (!$data_type) $data_type="SchedulerDataProcessor";
if (!$render_type) $render_type="JSONRenderStrategy";
parent::__construct($res,$type,$item_type,$data_type,$render_type);
}
protected function xml_start() {
return '{ "data":';
}
protected function xml_end() {
$this->fill_collections();
$end = (!empty($this->extra_output)) ? ', "collections": {'.$this->extra_output.'}' : '';
foreach ($this->attributes as $k => $v)
$end.=", \"".$k."\":\"".$v."\"";
$end .= '}';
return $end;
}
/*! assign options collection to the column
@param name
name of the column
@param options
array or connector object
*/
public function set_options($name,$options){
if (is_array($options)){
$str=array();
foreach($options as $k => $v)
$str[]='{"id":"'.$this->xmlentities($k).'", "value":"'.$this->xmlentities($v).'"}';
$options=implode(",",$str);
}
$this->options[$name]=$options;
}
/*! generates xml description for options collections
@param list
comma separated list of column names, for which options need to be generated
*/
protected function fill_collections($list=""){
$options = array();
foreach ($this->options as $k=>$v) {
$name = $k;
$option="\"{$name}\":[";
if (!is_string($this->options[$name])){
$data = json_encode($this->options[$name]->render());
$option.=substr($data,1,-1);
} else
$option.=$this->options[$name];
$option.="]";
$options[] = $option;
}
$this->extra_output .= implode($this->data_separator, $options);
}
/*! output fetched data as XML
@param res
DB resultset
*/
protected function output_as_xml($res){
$result = $this->render_set($res);
if ($this->simple) return $result;
$data=$this->xml_start().json_encode($result).$this->xml_end();
if ($this->as_string) return $data;
$out = new OutputWriter($data, "");
$out->set_type("json");
$this->event->trigger("beforeOutput", $this, $out);
$out->output("", true, $this->encoding);
}
}
?>
\ No newline at end of file
<?php
class RenderStrategy {
protected $conn = null;
public function __construct($conn) {
$this->conn = $conn;
}
/*! adds mix fields into DataConfig
* @param config
* DataConfig object
* @param mix
* mix structure
*/
protected function mix($config, $mix) {
for ($i = 0; $i < count($mix); $i++) {
if ($config->is_field($mix[$i]['name'])===-1) {
$config->add_field($mix[$i]['name']);
}
}
}
/*! remove mix fields from DataConfig
* @param config
* DataConfig object
* @param mix
* mix structure
*/
protected function unmix($config, $mix) {
for ($i = 0; $i < count($mix); $i++) {
if ($config->is_field($mix[$i]['name'])!==-1) {
$config->remove_field_full($mix[$i]['name']);
}
}
}
/*! adds mix fields in item
* simple mix adds only strings specified by user
* @param mix
* mix structure
* @param data
* array of selected data
*/
protected function simple_mix($mix, $data) {
// get mix details
for ($i = 0; $i < count($mix); $i++)
$data[$mix[$i]["name"]] = is_string($mix[$i]["value"]) ? $mix[$i]["value"] : "";
return $data;
}
/*! adds mix fields in item
* complex mix adds strings specified by user and results of subrequests
* @param mix
* mix structure
* @param data
* array of selected data
*/
protected function complex_mix($mix, $data) {
// get mix details
for ($i = 0; $i < count($mix); $i++) {
$mixname = $mix[$i]["name"];
if ($mix[$i]['filter'] !== false) {
$subconn = $mix[$i]["value"];
$filter = $mix[$i]["filter"];
// setting relationships
$subconn->clear_filter();
foreach ($filter as $k => $v)
if (isset($data[$v]))
$subconn->filter($k, $data[$v], "=");
else
throw new Exception('There was no such data field registered as: '.$k);
$subconn->asString(true);
$data[$mixname]=$subconn->simple_render();
if (is_array($data[$mixname]) && count($data[$mixname]) == 1)
$data[$mixname] = $data[$mixname][0];
} else {
$data[$mixname] = $mix[$i]["value"];
}
}
return $data;
}
/*! render from DB resultset
@param res
DB resultset
process commands, output requested data as XML
*/
public function render_set($res, $name, $dload, $sep, $config, $mix){
$output="";
$index=0;
$conn = $this->conn;
$this->mix($config, $mix);
$conn->event->trigger("beforeRenderSet",$conn,$res,$config);
while ($data=$conn->sql->get_next($res)){
$data = $this->simple_mix($mix, $data);
$data = new $name($data,$config,$index);
if ($data->get_id()===false)
$data->set_id($conn->uuid());
$conn->event->trigger("beforeRender",$data);
$output.=$data->to_xml().$sep;
$index++;
}
$this->unmix($config, $mix);
return $output;
}
}
class JSONRenderStrategy extends RenderStrategy {
/*! render from DB resultset
@param res
DB resultset
process commands, output requested data as json
*/
public function render_set($res, $name, $dload, $sep, $config, $mix){
$output=array();
$index=0;
$conn = $this->conn;
$this->mix($config, $mix);
$conn->event->trigger("beforeRenderSet",$conn,$res,$config);
while ($data=$conn->sql->get_next($res)){
$data = $this->complex_mix($mix, $data);
$data = new $name($data,$config,$index);
if ($data->get_id()===false)
$data->set_id($conn->uuid());
$conn->event->trigger("beforeRender",$data);
$output[]=$data->to_xml();
$index++;
}
$this->unmix($config, $mix);
return $output;
}
}
class TreeRenderStrategy extends RenderStrategy {
protected $id_swap = array();
public function __construct($conn) {
parent::__construct($conn);
$conn->event->attach("afterInsert",array($this,"parent_id_correction_a"));
$conn->event->attach("beforeProcessing",array($this,"parent_id_correction_b"));
}
public function render_set($res, $name, $dload, $sep, $config, $mix){
$output="";
$index=0;
$conn = $this->conn;
$config_copy = new DataConfig($config);
$this->mix($config, $mix);
while ($data=$conn->sql->get_next($res)){
$data = $this->simple_mix($mix, $data);
$data = new $name($data,$config,$index);
$conn->event->trigger("beforeRender",$data);
//there is no info about child elements,
//if we are using dyn. loading - assume that it has,
//in normal mode juse exec sub-render routine
if ($data->has_kids()===-1 && $dload)
$data->set_kids(true);
$output.=$data->to_xml_start();
if ($data->has_kids()===-1 || ( $data->has_kids()==true && !$dload)){
$sub_request = new DataRequestConfig($conn->get_request());
$sub_request->set_fieldset(implode(",",$config_copy->db_names_list($conn->sql)));
$sub_request->set_relation($data->get_id());
$output.=$this->render_set($conn->sql->select($sub_request), $name, $dload, $sep, $config_copy, $mix);
}
$output.=$data->to_xml_end();
$index++;
}
$this->unmix($config, $mix);
return $output;
}
/*! store info about ID changes during insert operation
@param dataAction
data action object during insert operation
*/
public function parent_id_correction_a($dataAction){
$this->id_swap[$dataAction->get_id()]=$dataAction->get_new_id();
}
/*! update ID if it was affected by previous operation
@param dataAction
data action object, before any processing operation
*/
public function parent_id_correction_b($dataAction){
$relation = $this->conn->get_config()->relation_id["db_name"];
$value = $dataAction->get_value($relation);
if (array_key_exists($value,$this->id_swap))
$dataAction->set_value($relation,$this->id_swap[$value]);
}
}
class JSONTreeRenderStrategy extends TreeRenderStrategy {
public function render_set($res, $name, $dload, $sep, $config,$mix){
$output=array();
$index=0;
$conn = $this->conn;
$config_copy = new DataConfig($config);
$this->mix($config, $mix);
while ($data=$conn->sql->get_next($res)){
$data = $this->complex_mix($mix, $data);
$data = new $name($data,$config,$index);
$conn->event->trigger("beforeRender",$data);
//there is no info about child elements,
//if we are using dyn. loading - assume that it has,
//in normal mode just exec sub-render routine
if ($data->has_kids()===-1 && $dload)
$data->set_kids(true);
$record = $data->to_xml_start();
if ($data->has_kids()===-1 || ( $data->has_kids()==true && !$dload)){
$sub_request = new DataRequestConfig($conn->get_request());
$sub_request->set_fieldset(implode(",",$config_copy->db_names_list($conn->sql)));
$sub_request->set_relation($data->get_id());
$sub_request->set_filters(array());
$temp = $this->render_set($conn->sql->select($sub_request), $name, $dload, $sep, $config_copy, $mix);
if (sizeof($temp))
$record["data"] = $temp;
}
$output[] = $record;
$index++;
}
$this->unmix($config, $mix);
return $output;
}
}
class MultitableTreeRenderStrategy extends TreeRenderStrategy {
private $level = 0;
private $max_level = null;
protected $sep = "#";
public function __construct($conn) {
parent::__construct($conn);
$conn->event->attach("beforeProcessing", Array($this, 'id_translate_before'));
$conn->event->attach("afterProcessing", Array($this, 'id_translate_after'));
}
public function set_separator($sep) {
$this->sep = $sep;
}
public function render_set($res, $name, $dload, $sep, $config, $mix){
$output="";
$index=0;
$conn = $this->conn;
$this->mix($config, $mix);
while ($data=$conn->sql->get_next($res)){
$data = $this->simple_mix($mix, $data);
$data[$config->id['name']] = $this->level_id($data[$config->id['name']]);
$data = new $name($data,$config,$index);
$conn->event->trigger("beforeRender",$data);
if (($this->max_level !== null)&&($conn->get_level() == $this->max_level)) {
$data->set_kids(false);
} else {
if ($data->has_kids()===-1)
$data->set_kids(true);
}
$output.=$data->to_xml_start();
$output.=$data->to_xml_end();
$index++;
}
$this->unmix($config, $mix);
return $output;
}
public function level_id($id, $level = null) {
return ($level === null ? $this->level : $level).$this->sep.$id;
}
/*! remove level prefix from id, parent id and set new id before processing
@param action
DataAction object
*/
public function id_translate_before($action) {
$id = $action->get_id();
$id = $this->parse_id($id, false);
$action->set_id($id);
$action->set_value('tr_id', $id);
$action->set_new_id($id);
$pid = $action->get_value($this->conn->get_config()->relation_id['db_name']);
$pid = $this->parse_id($pid, false);
$action->set_value($this->conn->get_config()->relation_id['db_name'], $pid);
}
/*! add level prefix in id and new id after processing
@param action
DataAction object
*/
public function id_translate_after($action) {
$id = $action->get_id();
$action->set_id($this->level_id($id));
$id = $action->get_new_id();
$action->success($this->level_id($id));
}
public function get_level($parent_name) {
if ($this->level) return $this->level;
if (!isset($_GET[$parent_name])) {
if (isset($_POST['ids'])) {
$ids = explode(",",$_POST["ids"]);
$id = $this->parse_id($ids[0]);
$this->level--;
}
$this->conn->get_request()->set_relation(false);
} else {
$id = $this->parse_id($_GET[$parent_name]);
$_GET[$parent_name] = $id;
}
return $this->level;
}
public function is_max_level() {
if (($this->max_level !== null) && ($this->level >= $this->max_level))
return true;
return false;
}
public function set_max_level($max_level) {
$this->max_level = $max_level;
}
public function parse_id($id, $set_level = true) {
$parts = explode('#', urldecode($id));
if (count($parts) === 2) {
$level = $parts[0] + 1;
$id = $parts[1];
} else {
$level = 0;
$id = '';
}
if ($set_level) $this->level = $level;
return $id;
}
}
class JSONMultitableTreeRenderStrategy extends MultitableTreeRenderStrategy {
public function render_set($res, $name, $dload, $sep, $config, $mix){
$output=array();
$index=0;
$conn = $this->conn;
$this->mix($config, $mix);
while ($data=$conn->sql->get_next($res)){
$data = $this->complex_mix($mix, $data);
$data[$config->id['name']] = $this->level_id($data[$config->id['name']]);
$data = new $name($data,$config,$index);
$conn->event->trigger("beforeRender",$data);
if ($this->is_max_level()) {
$data->set_kids(false);
} else {
if ($data->has_kids()===-1)
$data->set_kids(true);
}
$record = $data->to_xml_start($output);
$output[] = $record;
$index++;
}
$this->unmix($config, $mix);
return $output;
}
}
class GroupRenderStrategy extends RenderStrategy {
protected $id_postfix = '__{group_param}';
public function __construct($conn) {
parent::__construct($conn);
$conn->event->attach("beforeProcessing", Array($this, 'check_id'));
$conn->event->attach("onInit", Array($this, 'replace_postfix'));
}
public function render_set($res, $name, $dload, $sep, $config, $mix, $usemix = false){
$output="";
$index=0;
$conn = $this->conn;
if ($usemix) $this->mix($config, $mix);
while ($data=$conn->sql->get_next($res)){
if (isset($data[$config->id['name']])) {
$this->simple_mix($mix, $data);
$has_kids = false;
} else {
$data[$config->id['name']] = $data['value'].$this->id_postfix;
$data[$config->text[0]['name']] = $data['value'];
$has_kids = true;
}
$data = new $name($data,$config,$index);
$conn->event->trigger("beforeRender",$data);
if ($has_kids === false) {
$data->set_kids(false);
}
if ($data->has_kids()===-1 && $dload)
$data->set_kids(true);
$output.=$data->to_xml_start();
if (($data->has_kids()===-1 || ( $data->has_kids()==true && !$dload))&&($has_kids == true)){
$sub_request = new DataRequestConfig($conn->get_request());
$sub_request->set_relation(str_replace($this->id_postfix, "", $data->get_id()));
$output.=$this->render_set($conn->sql->select($sub_request), $name, $dload, $sep, $config, $mix, true);
}
$output.=$data->to_xml_end();
$index++;
}
if ($usemix) $this->unmix($config, $mix);
return $output;
}
public function check_id($action) {
if (isset($_GET['editing'])) {
$config = $this->conn->get_config();
$id = $action->get_id();
$pid = $action->get_value($config->relation_id['name']);
$pid = str_replace($this->id_postfix, "", $pid);
$action->set_value($config->relation_id['name'], $pid);
if (!empty($pid)) {
return $action;
} else {
$action->error();
$action->set_response_text("This record can't be updated!");
return $action;
}
} else {
return $action;
}
}
public function replace_postfix() {
if (isset($_GET['id'])) {
$_GET['id'] = str_replace($this->id_postfix, "", $_GET['id']);
}
}
public function get_postfix() {
return $this->id_postfix;
}
}
class JSONGroupRenderStrategy extends GroupRenderStrategy {
public function render_set($res, $name, $dload, $sep, $config, $mix, $usemix = false){
$output=array();
$index=0;
$conn = $this->conn;
if ($usemix) $this->mix($config, $mix);
while ($data=$conn->sql->get_next($res)){
if (isset($data[$config->id['name']])) {
$data = $this->complex_mix($mix, $data);
$has_kids = false;
} else {
$data[$config->id['name']] = $data['value'].$this->id_postfix;
$data[$config->text[0]['name']] = $data['value'];
$has_kids = true;
}
$data = new $name($data,$config,$index);
$conn->event->trigger("beforeRender",$data);
if ($has_kids === false) {
$data->set_kids(false);
}
if ($data->has_kids()===-1 && $dload)
$data->set_kids(true);
$record = $data->to_xml_start();
if (($data->has_kids()===-1 || ( $data->has_kids()==true && !$dload))&&($has_kids == true)){
$sub_request = new DataRequestConfig($conn->get_request());
$sub_request->set_relation(str_replace($this->id_postfix, "", $data->get_id()));
$temp = $this->render_set($conn->sql->select($sub_request), $name, $dload, $sep, $config, $mix, true);
if (sizeof($temp))
$record["data"] = $temp;
}
$output[] = $record;
$index++;
}
if ($usemix) $this->unmix($config, $mix);
return $output;
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
/*! Class which allows to assign|fire events.
*/
class EventMaster{
private $events;//!< hash of event handlers
private $master;
private static $eventsStatic=array();
/*! constructor
*/
function __construct(){
$this->events=array();
$this->master = false;
}
/*! Method check if event with such name already exists.
@param name
name of event, case non-sensitive
@return
true if event with such name registered, false otherwise
*/
public function exist($name){
$name=strtolower($name);
return (isset($this->events[$name]) && sizeof($this->events[$name]));
}
/*! Attach custom code to event.
Only on event handler can be attached in the same time. If new event handler attached - old will be detached.
@param name
name of event, case non-sensitive
@param method
function which will be attached. You can use array(class, method) if you want to attach the method of the class.
*/
public function attach($name,$method=false){
//use class for event handling
if ($method === false){
$this->master = $name;
return;
}
//use separate functions
$name=strtolower($name);
if (!array_key_exists($name,$this->events))
$this->events[$name]=array();
$this->events[$name][]=$method;
}
public static function attach_static($name, $method){
$name=strtolower($name);
if (!array_key_exists($name,EventMaster::$eventsStatic))
EventMaster::$eventsStatic[$name]=array();
EventMaster::$eventsStatic[$name][]=$method;
}
public static function trigger_static($name, $method){
$arg_list = func_get_args();
$name=strtolower(array_shift($arg_list));
if (isset(EventMaster::$eventsStatic[$name]))
foreach(EventMaster::$eventsStatic[$name] as $method){
if (is_array($method) && !method_exists($method[0],$method[1]))
throw new Exception("Incorrect method assigned to event: ".$method[0].":".$method[1]);
if (!is_array($method) && !function_exists($method))
throw new Exception("Incorrect function assigned to event: ".$method);
call_user_func_array($method, $arg_list);
}
return true;
}
/*! Detach code from event
@param name
name of event, case non-sensitive
*/
public function detach($name){
$name=strtolower($name);
unset($this->events[$name]);
}
/*! Trigger event.
@param name
name of event, case non-sensitive
@param data
value which will be provided as argument for event function,
you can provide multiple data arguments, method accepts variable number of parameters
@return
true if event handler was not assigned , result of event hangler otherwise
*/
public function trigger($name,$data){
$arg_list = func_get_args();
$name=strtolower(array_shift($arg_list));
if (isset($this->events[$name]))
foreach($this->events[$name] as $method){
if (is_array($method) && !method_exists($method[0],$method[1]))
throw new Exception("Incorrect method assigned to event: ".$method[0].":".$method[1]);
if (!is_array($method) && !function_exists($method))
throw new Exception("Incorrect function assigned to event: ".$method);
call_user_func_array($method, $arg_list);
}
if ($this->master !== false)
if (method_exists($this->master, $name))
call_user_func_array(array($this->master, $name), $arg_list);
return true;
}
}
/*! Class which handles access rules.
**/
class AccessMaster{
private $rules,$local;
/*! constructor
Set next access right to "allowed" by default : read, insert, update, delete
Basically - all common data operations allowed by default
*/
function __construct(){
$this->rules=array("read" => true, "insert" => true, "update" => true, "delete" => true);
$this->local=true;
}
/*! change access rule to "allow"
@param name
name of access right
*/
public function allow($name){
$this->rules[$name]=true;
}
/*! change access rule to "deny"
@param name
name of access right
*/
public function deny($name){
$this->rules[$name]=false;
}
/*! change all access rules to "deny"
*/
public function deny_all(){
$this->rules=array();
}
/*! check access rule
@param name
name of access right
@return
true if access rule allowed, false otherwise
*/
public function check($name){
if ($this->local){
/*!
todo
add referrer check, to prevent access from remote points
*/
}
if (!isset($this->rules[$name]) || !$this->rules[$name]){
return false;
}
return true;
}
}
/*! Controls error and debug logging.
Class designed to be used as static object.
**/
class LogMaster{
private static $_log=false;//!< logging mode flag
private static $_output=false;//!< output error infor to client flag
private static $session="";//!< all messages generated for current request
/*! convert array to string representation ( it is a bit more readable than var_dump )
@param data
data object
@param pref
prefix string, used for formating, optional
@return
string with array description
*/
private static function log_details($data,$pref=""){
if (is_array($data)){
$str=array("");
foreach($data as $k=>$v)
array_push($str,$pref.$k." => ".LogMaster::log_details($v,$pref."\t"));
return implode("\n",$str);
}
return $data;
}
/*! put record in log
@param str
string with log info, optional
@param data
data object, which will be added to log, optional
*/
public static function log($str="",$data=""){
if (LogMaster::$_log){
$message = $str.LogMaster::log_details($data)."\n\n";
LogMaster::$session.=$message;
error_log($message,3,LogMaster::$_log);
}
}
/*! get logs for current request
@return
string, which contains all log messages generated for current request
*/
public static function get_session_log(){
return LogMaster::$session;
}
/*! error handler, put normal php errors in log file
@param errn
error number
@param errstr
error description
@param file
error file
@param line
error line
@param context
error cntext
*/
public static function error_log($errn,$errstr,$file,$line,$context){
LogMaster::log($errstr." at ".$file." line ".$line);
}
/*! exception handler, used as default reaction on any error - show execution log and stop processing
@param exception
instance of Exception
*/
public static function exception_log($exception){
LogMaster::log("!!!Uncaught Exception\nCode: " . $exception->getCode() . "\nMessage: " . $exception->getMessage());
if (LogMaster::$_output){
echo "<pre><xmp>\n";
echo LogMaster::get_session_log();
echo "\n</xmp></pre>";
}
die();
}
/*! enable logging
@param name
path to the log file, if boolean false provided as value - logging will be disabled
@param output
flag of client side output, if enabled - session log will be sent to client side in case of an error.
*/
public static function enable_log($name,$output=false){
LogMaster::$_log=$name;
LogMaster::$_output=$output;
if ($name){
set_error_handler(array("LogMaster","error_log"),E_ALL);
set_exception_handler(array("LogMaster","exception_log"));
LogMaster::log("\n\n====================================\nLog started, ".date("d/m/Y h:i:s")."\n====================================");
}
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("base_connector.php");
/*! DataItem class for Tree component
**/
class TreeDataItem extends DataItem{
private $im0;//!< image of closed folder
private $im1;//!< image of opened folder
private $im2;//!< image of leaf item
private $check;//!< checked state
private $kids=-1;//!< checked state
private $attrs;//!< collection of custom attributes
function __construct($data,$config,$index){
parent::__construct($data,$config,$index);
$this->im0=false;
$this->im1=false;
$this->im2=false;
$this->check=false;
$this->attrs = array();
}
/*! get id of parent record
@return
id of parent record
*/
function get_parent_id(){
return $this->data[$this->config->relation_id["name"]];
}
/*! get state of items checkbox
@return
state of item's checkbox as int value, false if state was not defined
*/
function get_check_state(){
return $this->check;
}
/*! set state of item's checkbox
@param value
int value, 1 - checked, 0 - unchecked, -1 - third state
*/
function set_check_state($value){
$this->check=$value;
}
/*! return count of child items
-1 if there is no info about childs
@return
count of child items
*/
function has_kids(){
return $this->kids;
}
/*! sets count of child items
@param value
count of child items
*/
function set_kids($value){
$this->kids=$value;
}
/*! set custom attribute
@param name
name of the attribute
@param value
new value of the attribute
*/
function set_attribute($name, $value){
switch($name){
case "id":
$this->set_id($value);
break;
case "text":
$this->data[$this->config->text[0]["name"]]=$value;
break;
case "checked":
$this->set_check_state($value);
break;
case "im0":
$this->im0=$value;
break;
case "im1":
$this->im1=$value;
break;
case "im2":
$this->im2=$value;
break;
case "child":
$this->set_kids($value);
break;
default:
$this->attrs[$name]=$value;
}
}
/*! assign image for tree's item
@param img_folder_closed
image for item, which represents folder in closed state
@param img_folder_open
image for item, which represents folder in opened state, optional
@param img_leaf
image for item, which represents leaf item, optional
*/
function set_image($img_folder_closed,$img_folder_open=false,$img_leaf=false){
$this->im0=$img_folder_closed;
$this->im1=$img_folder_open?$img_folder_open:$img_folder_closed;
$this->im2=$img_leaf?$img_leaf:$img_folder_closed;
}
/*! return self as XML string, starting part
*/
function to_xml_start(){
if ($this->skip) return "";
$str1="<item id='".$this->get_id()."' text='".$this->xmlentities($this->data[$this->config->text[0]["name"]])."' ";
if ($this->has_kids()==true) $str1.="child='".$this->has_kids()."' ";
if ($this->im0) $str1.="im0='".$this->im0."' ";
if ($this->im1) $str1.="im1='".$this->im1."' ";
if ($this->im2) $str1.="im2='".$this->im2."' ";
if ($this->check) $str1.="checked='".$this->check."' ";
foreach ($this->attrs as $key => $value)
$str1.=$key."='".$this->xmlentities($value)."' ";
$str1.=">";
if ($this->userdata !== false)
foreach ($this->userdata as $key => $value)
$str1.="<userdata name='".$key."'><![CDATA[".$value."]]></userdata>";
return $str1;
}
/*! return self as XML string, ending part
*/
function to_xml_end(){
if ($this->skip) return "";
return "</item>";
}
}
require_once("filesystem_item.php");
/*! Connector for the dhtmlxtree
**/
class TreeConnector extends Connector{
protected $parent_name = 'id';
/*! constructor
Here initilization of all Masters occurs, execution timer initialized
@param res
db connection resource
@param type
string , which hold type of database ( MySQL or Postgre ), optional, instead of short DB name, full name of DataWrapper-based class can be provided
@param item_type
name of class, which will be used for item rendering, optional, DataItem will be used by default
@param data_type
name of class which will be used for dataprocessor calls handling, optional, DataProcessor class will be used by default.
* @param render_type
* name of class which will provides data rendering
*/
public function __construct($res,$type=false,$item_type=false,$data_type=false, $render_type=false){
if (!$item_type) $item_type="TreeDataItem";
if (!$data_type) $data_type="TreeDataProcessor";
if (!$render_type) $render_type="TreeRenderStrategy";
parent::__construct($res,$type,$item_type,$data_type,$render_type);
}
//parse GET scoope, all operations with incoming request must be done here
public function parse_request(){
parent::parse_request();
if (isset($_GET[$this->parent_name]))
$this->request->set_relation($_GET[$this->parent_name]);
else
$this->request->set_relation("0");
$this->request->set_limit(0,0); //netralize default reaction on dyn. loading mode
}
/*! renders self as xml, starting part
*/
public function xml_start(){
$attributes = "";
foreach($this->attributes as $k=>$v)
$attributes .= " ".$k."='".$v."'";
return "<tree id='".$this->request->get_relation()."'".$attributes.">";
}
/*! renders self as xml, ending part
*/
public function xml_end(){
$this->fill_collections();
return $this->extra_output."</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
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("data_connector.php");
class TreeDataGroupConnector extends TreeDataConnector{
public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
if (!$render_type) $render_type="GroupRenderStrategy";
parent::__construct($res,$type,$item_type,$data_type,$render_type);
}
/*! if not isset $_GET[id] then it's top level
*/
protected function set_relation() {
if (!isset($_GET[$this->parent_name])) $this->request->set_relation(false);
}
/*! if it's first level then distinct level
* else select by parent
*/
protected function get_resource() {
$resource = null;
if (isset($_GET[$this->parent_name]))
$resource = $this->sql->select($this->request);
else
$resource = $this->sql->get_variants($this->config->relation_id['name'], $this->request);
return $resource;
}
/*! renders self as xml, starting part
*/
public function xml_start(){
if (isset($_GET[$this->parent_name])) {
return "<data parent='".$_GET[$this->parent_name].$this->render->get_postfix()."'>";
} else {
return "<data parent='0'>";
}
}
}
class JSONTreeDataGroupConnector extends JSONTreeDataConnector{
public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
if (!$render_type) $render_type="JSONGroupRenderStrategy";
parent::__construct($res,$type,$item_type,$data_type,$render_type);
}
/*! if not isset $_GET[id] then it's top level
*/
protected function set_relation() {
if (!isset($_GET[$this->parent_name])) $this->request->set_relation(false);
}
/*! if it's first level then distinct level
* else select by parent
*/
protected function get_resource() {
$resource = null;
if (isset($_GET[$this->parent_name]))
$resource = $this->sql->select($this->request);
else
$resource = $this->sql->get_variants($this->config->relation_id['name'], $this->request);
return $resource;
}
/*! renders self as xml, starting part
*/
public function xml_start(){
if (isset($_GET[$this->parent_name])) {
return "<data parent='".$_GET[$this->parent_name].$this->render->get_postfix()."'>";
} else {
return "<data parent='0'>";
}
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("data_connector.php");
class TreeDataMultitableConnector extends TreeDataConnector{
protected $parent_name = 'parent';
public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
if (!$data_type) $data_type="TreeDataProcessor";
if (!$render_type) $render_type="MultitableTreeRenderStrategy";
parent::__construct($res,$type,$item_type,$data_type,$render_type);
}
public function render(){
$this->dload = true;
return parent::render();
}
/*! sets relation for rendering */
protected function set_relation() {
if (!isset($_GET[$this->parent_name]))
$this->request->set_relation(false);
}
public function xml_start(){
if (isset($_GET[$this->parent_name])) {
return "<data parent='".$this->render->level_id($_GET[$this->parent_name], $this->render->get_level() - 1)."'>";
} else {
return "<data parent='0'>";
}
}
/*! set maximum level of tree
@param max_level
maximum level
*/
public function setMaxLevel($max_level) {
$this->render->set_max_level($max_level);
}
public function get_level() {
return $this->render->get_level($this->parent_name);
}
}
class JSONTreeDataMultitableConnector extends TreeDataMultitableConnector{
public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
if (!$item_type) $item_type="JSONTreeCommonDataItem";
if (!$data_type) $data_type="CommonDataProcessor";
if (!$render_type) $render_type="JSONMultitableTreeRenderStrategy";
parent::__construct($res,$type,$item_type,$data_type,$render_type);
}
protected function output_as_xml($res){
$result = $this->render_set($res);
if ($this->simple) return $result;
$data = array();
if (isset($_GET['parent']))
$data["parent"] = $this->render->level_id($_GET[$this->parent_name], $this->render->get_level() - 1);
else
$data["parent"] = "0";
$data["data"] = $result;
$result = json_encode($data);
if ($this->as_string) return $result;
$out = new OutputWriter($result, "");
$out->set_type("json");
$this->event->trigger("beforeOutput", $this, $out);
$out->output("", true, $this->encoding);
}
public function xml_start(){
return '';
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("grid_connector.php");
/*! DataItem class for TreeGrid component
**/
class TreeGridDataItem extends GridDataItem{
private $kids=-1;//!< checked state
function __construct($data,$config,$index){
parent::__construct($data,$config,$index);
$this->im0=false;
}
/*! return id of parent record
@return
id of parent record
*/
function get_parent_id(){
return $this->data[$this->config->relation_id["name"]];
}
/*! assign image to treegrid's item
longer description
@param img
relative path to the image
*/
function set_image($img){
$this->set_cell_attribute($this->config->text[0]["name"],"image",$img);
}
/*! return count of child items
-1 if there is no info about childs
@return
count of child items
*/
function has_kids(){
return $this->kids;
}
/*! sets count of child items
@param value
count of child items
*/
function set_kids($value){
$this->kids=$value;
if ($value)
$this->set_row_attribute("xmlkids",$value);
}
}
/*! Connector for dhtmlxTreeGrid
**/
class TreeGridConnector extends GridConnector{
protected $parent_name = 'id';
/*! constructor
Here initilization of all Masters occurs, execution timer initialized
@param res
db connection resource
@param type
string , which hold type of database ( MySQL or Postgre ), optional, instead of short DB name, full name of DataWrapper-based class can be provided
@param item_type
name of class, which will be used for item rendering, optional, DataItem will be used by default
@param data_type
name of class which will be used for dataprocessor calls handling, optional, DataProcessor class will be used by default.
* @param render_type
* name of class which will provides data rendering
*/
public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
if (!$item_type) $item_type="TreeGridDataItem";
if (!$data_type) $data_type="TreeGridDataProcessor";
if (!$render_type) $render_type="TreeRenderStrategy";
parent::__construct($res,$type,$item_type,$data_type,$render_type);
}
/*! process treegrid specific options in incoming request */
public function parse_request(){
parent::parse_request();
if (isset($_GET[$this->parent_name]))
$this->request->set_relation($_GET[$this->parent_name]);
else
$this->request->set_relation("0");
$this->request->set_limit(0,0); //netralize default reaction on dyn. loading mode
}
/*! renders self as xml, starting part
*/
protected function xml_start(){
return "<rows parent='".$this->request->get_relation()."'>";
}
}
/*! DataProcessor class for Grid component
**/
class TreeGridDataProcessor extends GridDataProcessor{
function __construct($connector,$config,$request){
parent::__construct($connector,$config,$request);
$request->set_relation(false);
}
/*! convert incoming data name to valid db name
converts c0..cN to valid field names
@param data
data name from incoming request
@return
related db_name
*/
function name_data($data){
if ($data=="gr_pid")
return $this->config->relation_id["name"];
else return parent::name_data($data);
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("treegrid_connector.php");
class TreeGridGroupConnector extends TreeGridConnector{
public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
if (!$render_type) $render_type="GroupRenderStrategy";
parent::__construct($res,$type,$item_type,$data_type,$render_type);
}
/*! if not isset $_GET[id] then it's top level
*/
protected function set_relation() {
if (!isset($_GET[$this->parent_name])) $this->request->set_relation(false);
}
/*! if it's first level then distinct level
* else select by parent
*/
protected function get_resource() {
$resource = null;
if (isset($_GET[$this->parent_name]))
$resource = $this->sql->select($this->request);
else
$resource = $this->sql->get_variants($this->config->relation_id['name'], $this->request);
return $resource;
}
/*! renders self as xml, starting part
*/
protected function xml_start(){
if (isset($_GET[$this->parent_name])) {
return "<rows parent='".$_GET[$this->parent_name].$this->render->get_postfix()."'>";
} else {
return "<rows parent='0'>";
}
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("treegrid_connector.php");
class TreeGridMultitableConnector extends TreeGridConnector{
public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
$data_type="TreeGridMultitableDataProcessor";
if (!$render_type) $render_type="MultitableTreeRenderStrategy";
parent::__construct($res,$type,$item_type,$data_type,$render_type);
$this->render->set_separator("%23");
}
public function render(){
$this->dload = true;
return parent::render();
}
/*! sets relation for rendering */
protected function set_relation() {
if (!isset($_GET['id']))
$this->request->set_relation(false);
}
public function xml_start(){
if (isset($_GET['id'])) {
return "<rows parent='".$this->render->level_id($_GET['id'], $this->get_level() - 1)."'>";
} else {
return "<rows parent='0'>";
}
}
/*! set maximum level of tree
@param max_level
maximum level
*/
public function setMaxLevel($max_level) {
$this->render->set_max_level($max_level);
}
public function get_level() {
return $this->render->get_level($this->parent_name);
}
}
class TreeGridMultitableDataProcessor extends DataProcessor {
function name_data($data){
if ($data=="gr_pid")
return $this->config->relation_id["name"];
if ($data=="gr_id")
return $this->config->id["name"];
preg_match('/^c([%\d]+)$/', $data, $data_num);
if (!isset($data_num[1])) return $data;
$data_num = $data_num[1];
if (isset($this->config->data[$data_num]["db_name"])) {
return $this->config->data[$data_num]["db_name"];
}
return $data;
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("tree_connector.php");
class TreeGroupConnector extends TreeConnector{
public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
if (!$render_type) $render_type="GroupRenderStrategy";
parent::__construct($res,$type,$item_type,$data_type,$render_type);
}
/*! if not isset $_GET[id] then it's top level
*/
protected function set_relation() {
if (!isset($_GET[$this->parent_name])) $this->request->set_relation(false);
}
/*! if it's first level then distinct level
* else select by parent
*/
protected function get_resource() {
$resource = null;
if (isset($_GET[$this->parent_name]))
$resource = $this->sql->select($this->request);
else
$resource = $this->sql->get_variants($this->config->relation_id['name'], $this->request);
return $resource;
}
/*! renders self as xml, starting part
*/
public function xml_start(){
if (isset($_GET[$this->parent_name])) {
return "<tree id='".$_GET[$this->parent_name].$this->render->get_postfix()."'>";
} else {
return "<tree id='0'>";
}
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
require_once("tree_connector.php");
class TreeMultitableConnector extends TreeConnector{
protected $parent_name = 'id';
public function __construct($res,$type=false,$item_type=false,$data_type=false,$render_type=false){
if (!$data_type) $data_type="TreeDataProcessor";
if (!$render_type) $render_type="MultitableTreeRenderStrategy";
parent::__construct($res,$type,$item_type,$data_type,$render_type);
}
public function render(){
$this->dload = true;
return parent::render();
}
/*! sets relation for rendering */
protected function set_relation() {
if (!isset($_GET[$this->parent_name]))
$this->request->set_relation(false);
}
public function xml_start(){
if (isset($_GET[$this->parent_name])) {
return "<tree id='".($this->render->level_id($_GET[$this->parent_name], $this->get_level() - 1))."'>";
} else {
return "<tree id='0'>";
}
}
/*! set maximum level of tree
@param max_level
maximum level
*/
public function setMaxLevel($max_level) {
$this->render->set_max_level($max_level);
}
public function get_level() {
return $this->render->get_level($this->parent_name);
}
}
?>
\ No newline at end of file
<?php
/*
@author dhtmlx.com
@license GPL, see license.txt
*/
/*! DataItemUpdate class for realization Optimistic concurrency control
Wrapper for DataItem object
It's used during outputing updates instead of DataItem object
Create wrapper for every data item with update information.
*/
class DataItemUpdate extends DataItem {
/*! constructor
@param data
hash of data
@param config
DataConfig object
@param index
index of element
*/
public function __construct($data,$config,$index,$type){
$this->config=$config;
$this->data=$data;
$this->index=$index;
$this->skip=false;
$this->child = new $type($data, $config, $index);
}
/*! returns parent_id (for Tree and TreeGrid components)
*/
public function get_parent_id(){
if (method_exists($this->child, 'get_parent_id')) {
return $this->child->get_parent_id();
} else {
return '';
}
}
/*! generate XML on the data hash base
*/
public function to_xml(){
$str= "<update ";
$str .= 'status="'.$this->data['type'].'" ';
$str .= 'id="'.$this->data['dataId'].'" ';
$str .= 'parent="'.$this->get_parent_id().'"';
$str .= '>';
$str .= $this->child->to_xml();
$str .= '</update>';
return $str;
}
/*! return starting tag for XML string
*/
public function to_xml_start(){
$str="<update ";
$str .= 'status="'.$this->data['type'].'" ';
$str .= 'id="'.$this->data['dataId'].'" ';
$str .= 'parent="'.$this->get_parent_id().'"';
$str .= '>';
$str .= $this->child->to_xml_start();
return $str;
}
/*! return ending tag for XML string
*/
public function to_xml_end(){
$str = $this->child->to_xml_end();
$str .= '</update>';
return $str;
}
/*! returns false for outputing only current item without child items
*/
public function has_kids(){
return false;
}
/*! sets count of child items
@param value
count of child items
*/
public function set_kids($value){
if (method_exists($this->child, 'set_kids')) {
$this->child->set_kids($value);
}
}
/*! sets attribute for item
*/
public function set_attribute($name, $value){
if (method_exists($this->child, 'set_attribute')) {
LogMaster::log("setting attribute: \nname = {$name}\nvalue = {$value}");
$this->child->set_attribute($name, $value);
} else {
LogMaster::log("set_attribute method doesn't exists");
}
}
}
class DataUpdate{
protected $table; //!< table , where actions are stored
protected $url; //!< url for notification service, optional
protected $sql; //!< DB wrapper object
protected $config; //!< DBConfig object
protected $request; //!< DBRequestConfig object
protected $event;
protected $item_class;
protected $demu;
//protected $config;//!< DataConfig instance
//protected $request;//!< DataRequestConfig instance
/*! constructor
@param connector
Connector object
@param config
DataConfig object
@param request
DataRequestConfig object
*/
function __construct($sql, $config, $request, $table, $url){
$this->config= $config;
$this->request= $request;
$this->sql = $sql;
$this->table=$table;
$this->url=$url;
$this->demu = false;
}
public function set_demultiplexor($path){
$this->demu = $path;
}
public function set_event($master, $name){
$this->event = $master;
$this->item_class = $name;
}
private function select_update($actions_table, $join_table, $id_field_name, $version, $user) {
$sql = "SELECT * FROM {$actions_table}";
$sql .= " LEFT OUTER JOIN {$join_table} ON ";
$sql .= "{$actions_table}.DATAID = {$join_table}.{$id_field_name} ";
$sql .= "WHERE {$actions_table}.ID > '{$version}' AND {$actions_table}.USER <> '{$user}'";
return $sql;
}
private function get_update_max_version() {
$sql = "SELECT MAX(id) as VERSION FROM {$this->table}";
$res = $this->sql->query($sql);
$data = $this->sql->get_next($res);
if ($data == false || $data['VERSION'] == false)
return 1;
else
return $data['VERSION'];
}
private function log_update_action($actions_table, $dataId, $status, $user) {
$sql = "INSERT INTO {$actions_table} (DATAID, TYPE, USER) VALUES ('{$dataId}', '{$status}', '{$user}')";
$this->sql->query($sql);
if ($this->demu)
file_get_contents($this->demu);
}
/*! records operations in actions_table
@param action
DataAction object
*/
public function log_operations($action) {
$type = $this->sql->escape($action->get_status());
$dataId = $this->sql->escape($action->get_new_id());
$user = $this->sql->escape($this->request->get_user());
if ($type!="error" && $type!="invalid" && $type !="collision") {
$this->log_update_action($this->table, $dataId, $type, $user);
}
}
/*! return action version in XMl format
*/
public function get_version() {
$version = $this->get_update_max_version();
return "<userdata name='version'>".$version."</userdata>";
}
/*! adds action version in output XML as userdata
*/
public function version_output($conn, $out) {
$out->add($this->get_version());
}
/*! create update actions in XML-format and sends it to output
*/
public function get_updates() {
$sub_request = new DataRequestConfig($this->request);
$version = $this->request->get_version();
$user = $this->request->get_user();
$sub_request->parse_sql($this->select_update($this->table, $this->request->get_source(), $this->config->id['db_name'], $version, $user));
$sub_request->set_relation(false);
$output = $this->render_set($this->sql->select($sub_request), $this->item_class);
ob_clean();
header("Content-type:text/xml");
echo $this->updates_start();
echo $this->get_version();
echo $output;
echo $this->updates_end();
}
protected function render_set($res, $name){
$output="";
$index=0;
while ($data=$this->sql->get_next($res)){
$data = new DataItemUpdate($data,$this->config,$index, $name);
$this->event->trigger("beforeRender",$data);
$output.=$data->to_xml();
$index++;
}
return $output;
}
/*! returns update start string
*/
protected function updates_start() {
$start = '<updates>';
return $start;
}
/*! returns update end string
*/
protected function updates_end() {
$start = '</updates>';
return $start;
}
/*! checks if action version given by client is deprecated
@param action
DataAction object
*/
public function check_collision($action) {
$version = $this->sql->escape($this->request->get_version());
//$user = $this->sql->escape($this->request->get_user());
$last_version = $this->get_update_max_version();
if (($last_version > $version)&&($action->get_status() == 'update')) {
$action->error();
$action->set_status('collision');
}
}
}
?>
\ No newline at end of file
<?php
// +----------------------------------------------------------------------+
// | Copyright (c) 2001-2008 Liip AG |
// +----------------------------------------------------------------------+
// | Licensed under the Apache License, Version 2.0 (the "License"); |
// | you may not use this file except in compliance with the License. |
// | You may obtain a copy of the License at |
// | http://www.apache.org/licenses/LICENSE-2.0 |
// | Unless required by applicable law or agreed to in writing, software |
// | distributed under the License is distributed on an "AS IS" BASIS, |
// | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
// | implied. See the License for the specific language governing |
// | permissions and limitations under the License. |
// +----------------------------------------------------------------------+
// | Author: Christian Stocker <christian.stocker@liip.ch> |
// +----------------------------------------------------------------------+
//original name was lx_externalinput_clean
//renamed to prevent possible conflicts
class dhx_externalinput_clean {
// this basic clean should clean html code from
// lot of possible malicious code for Cross Site Scripting
// use it whereever you get external input
// you can also set $filterOut to some use html cleaning, but I don't know of any code, which could
// exploit that. But if you want to be sure, set it to eg. array("Tidy","Dom");
static function basic($string, $filterIn = array("Tidy","Dom","Striptags"), $filterOut = "none") {
$string = self::tidyUp($string, $filterIn);
$string = str_replace(array("&amp;", "&lt;", "&gt;"), array("&amp;amp;", "&amp;lt;", "&amp;gt;"), $string);
// fix &entitiy\n;
$string = preg_replace('#(&\#*\w+)[\x00-\x20]+;#u', "$1;", $string);
$string = preg_replace('#(&\#x*)([0-9A-F]+);*#iu', "$1$2;", $string);
$string = html_entity_decode($string, ENT_COMPAT, "UTF-8");
// remove any attribute starting with "on" or xmlns
$string = preg_replace('#(<[^>]+[\x00-\x20\"\'\/])(on|xmlns)[^>]*>#iUu', "$1>", $string);
// remove javascript: and vbscript: protocol
$string = preg_replace('#([a-z]*)[\x00-\x20\/]*=[\x00-\x20\/]*([\`\'\"]*)[\x00-\x20\/]*j[\x00-\x20]*a[\x00-\x20]*v[\x00-\x20]*a[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iUu', '$1=$2nojavascript...', $string);
$string = preg_replace('#([a-z]*)[\x00-\x20\/]*=[\x00-\x20\/]*([\`\'\"]*)[\x00-\x20\/]*v[\x00-\x20]*b[\x00-\x20]*s[\x00-\x20]*c[\x00-\x20]*r[\x00-\x20]*i[\x00-\x20]*p[\x00-\x20]*t[\x00-\x20]*:#iUu', '$1=$2novbscript...', $string);
$string = preg_replace('#([a-z]*)[\x00-\x20\/]*=[\x00-\x20\/]*([\`\'\"]*)[\x00-\x20\/]*-moz-binding[\x00-\x20]*:#Uu', '$1=$2nomozbinding...', $string);
$string = preg_replace('#([a-z]*)[\x00-\x20\/]*=[\x00-\x20\/]*([\`\'\"]*)[\x00-\x20\/]*data[\x00-\x20]*:#Uu', '$1=$2nodata...', $string);
//remove any style attributes, IE allows too much stupid things in them, eg.
//<span style="width: expression(alert('Ping!'));"></span>
// and in general you really don't want style declarations in your UGC
$string = preg_replace('#(<[^>]+[\x00-\x20\"\'\/])style[^>]*>#iUu', "$1>", $string);
//remove namespaced elements (we do not need them...)
$string = preg_replace('#</*\w+:\w[^>]*>#i', "", $string);
//remove really unwanted tags
do {
$oldstring = $string;
$string = preg_replace('#</*(applet|meta|xml|blink|link|style|script|embed|object|iframe|frame|frameset|ilayer|layer|bgsound|title|base)[^>]*>#i', "", $string);
} while ($oldstring != $string);
return self::tidyUp($string, $filterOut);
}
static function tidyUp($string, $filters) {
if (is_array($filters)) {
foreach ($filters as $filter) {
$return = self::tidyUpWithFilter($string, $filter);
if ($return !== false) {
return $return;
}
}
} else {
$return = self::tidyUpWithFilter($string, $filters);
}
// if no filter matched, use the Striptags filter to be sure.
if ($return === false) {
return self::tidyUpModuleStriptags($string);
} else {
return $return;
}
}
static private function tidyUpWithFilter($string, $filter) {
if (is_callable(array("self", "tidyUpModule" . $filter))) {
return call_user_func(array("self", "tidyUpModule" . $filter), $string);
}
return false;
}
static private function tidyUpModuleStriptags($string) {
return strip_tags($string);
}
static private function tidyUpModuleNone($string) {
return $string;
}
static private function tidyUpModuleDom($string) {
$dom = new domdocument();
@$dom->loadHTML("<html><body>" . $string . "</body></html>");
$string = '';
foreach ($dom->documentElement->firstChild->childNodes as $child) {
$string .= $dom->saveXML($child);
}
return $string;
}
static private function tidyUpModuleTidy($string) {
if (class_exists("tidy")) {
$tidy = new tidy();
$tidyOptions = array("output-xhtml" => true,
"show-body-only" => true,
"clean" => true,
"wrap" => "350",
"indent" => true,
"indent-spaces" => 1,
"ascii-chars" => false,
"wrap-attributes" => false,
"alt-text" => "",
"doctype" => "loose",
"numeric-entities" => true,
"drop-proprietary-attributes" => true,
"enclose-text" => false,
"enclose-block-text" => false
);
$tidy->parseString($string, $tidyOptions, "utf8");
$tidy->cleanRepair();
return (string) $tidy;
} else {
return false;
}
}
}
define("DHX_SECURITY_SAFETEXT", 1);
define("DHX_SECURITY_SAFEHTML", 2);
define("DHX_SECURITY_TRUSTED", 3);
class ConnectorSecurity{
static public $xss = DHX_SECURITY_SAFETEXT;
static public $security_key = false;
static public $security_var = "dhx_security";
static private $filterClass = null;
static function filter($value, $mode = false){
if ($mode === false)
$mode = ConnectorSecurity::$xss;
if ($mode == DHX_SECURITY_TRUSTED)
return $value;
if ($mode == DHX_SECURITY_SAFETEXT)
return filter_var($value, FILTER_SANITIZE_STRING, FILTER_FLAG_NO_ENCODE_QUOTES);
if ($mode == DHX_SECURITY_SAFEHTML){
if (ConnectorSecurity::$filterClass == null)
ConnectorSecurity::$filterClass = new dhx_externalinput_clean();
return ConnectorSecurity::$filterClass->basic($value);
}
throw new Error("Invalid security mode:"+$mode);
}
static function CSRF_detected(){
LogMaster::log("[SECURITY] Possible CSRF attack detected", array(
"referer" => $_SERVER["HTTP_REFERER"],
"remote" => $_SERVER["REMOTE_ADDR"]
));
LogMaster::log("Request data", $_POST);
die();
}
static function checkCSRF($edit){
if (ConnectorSecurity::$security_key){
if (!isset($_SESSION))
@session_start();
if ($edit=== true){
if (!isset($_POST[ConnectorSecurity::$security_var]))
return ConnectorSecurity::CSRF_detected();
$master_key = $_SESSION[ConnectorSecurity::$security_var];
$update_key = $_POST[ConnectorSecurity::$security_var];
if ($master_key != $update_key)
return ConnectorSecurity::CSRF_detected();
return "";
}
//data loading
if (!array_key_exists(ConnectorSecurity::$security_var,$_SESSION)){
$_SESSION[ConnectorSecurity::$security_var] = md5(uniqid());
}
return $_SESSION[ConnectorSecurity::$security_var];
}
return "";
}
}
\ No newline at end of file
/*
This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
to use it in non-GPL project. Please contact sales@dhtmlx.com for details
*/
gantt.config.quickinfo_buttons=["icon_delete","icon_edit"],gantt.config.quick_info_detached=!0,gantt.attachEvent("onTaskClick",function(t){return gantt.showQuickInfo(t),!0}),function(){for(var t=["onEmptyClick","onViewChange","onLightbox","onBeforeTaskDelete","onBeforeDrag"],e=function(){return gantt._hideQuickInfo(),!0},n=0;n<t.length;n++)gantt.attachEvent(t[n],e)}(),gantt.templates.quick_info_title=function(t,e,n){return n.text.substr(0,50)},gantt.templates.quick_info_content=function(t,e,n){return n.details||n.text},gantt.templates.quick_info_date=function(t,e,n){return gantt.templates.task_time(t,e,n)},gantt.showQuickInfo=function(t){if(t!=this._quick_info_box_id){this.hideQuickInfo(!0);var e=this._get_event_counter_part(t);e&&(this._quick_info_box=this._init_quick_info(e),this._fill_quick_data(t),this._show_quick_info(e))}},gantt._hideQuickInfo=function(){gantt.hideQuickInfo()},gantt.hideQuickInfo=function(t){var e=this._quick_info_box;if(this._quick_info_box_id=0,e&&e.parentNode){if(gantt.config.quick_info_detached)return e.parentNode.removeChild(e);"auto"==e.style.right?e.style.left="-350px":e.style.right="-350px",t&&e.parentNode.removeChild(e)}},dhtmlxEvent(window,"keydown",function(t){27==t.keyCode&&gantt.hideQuickInfo()}),gantt._show_quick_info=function(t){var e=gantt._quick_info_box;if(gantt.config.quick_info_detached){e.parentNode&&"#document-fragment"!=e.parentNode.nodeName.toLowerCase()||gantt.$task_data.appendChild(e);var n=e.offsetWidth,i=e.offsetHeight,a=this.getScrollState(),s=this.$task.offsetWidth+a.x-n;e.style.left=Math.min(Math.max(a.x,t.left-t.dx*(n-t.width)),s)+"px",e.style.top=t.top-(t.dy?i:-t.height)-25+"px"}else e.style.top="20px",1==t.dx?(e.style.right="auto",e.style.left="-300px",setTimeout(function(){e.style.left="-10px"},1)):(e.style.left="auto",e.style.right="-300px",setTimeout(function(){e.style.right="-10px"},1)),e.className=e.className.replace("dhx_qi_left","").replace("dhx_qi_left","")+" dhx_qi_"+(1==t?"left":"right"),gantt._obj.appendChild(e)},gantt._init_quick_info=function(){if(!this._quick_info_box){var t=this._quick_info_box=document.createElement("div");t.className="dhx_cal_quick_info",gantt.$testmode&&(t.className+=" dhx_no_animate");var e='<div class="dhx_cal_qi_title"><div class="dhx_cal_qi_tcontent"></div><div class="dhx_cal_qi_tdate"></div></div><div class="dhx_cal_qi_content"></div>';e+='<div class="dhx_cal_qi_controls">';for(var n=gantt.config.quickinfo_buttons,i=0;i<n.length;i++)e+='<div class="dhx_qi_big_icon '+n[i]+'" title="'+gantt.locale.labels[n[i]]+"\"><div class='dhx_menu_icon "+n[i]+"'></div><div>"+gantt.locale.labels[n[i]]+"</div></div>";e+="</div>",t.innerHTML=e,dhtmlxEvent(t,"click",function(t){t=t||event,gantt._qi_button_click(t.target||t.srcElement)}),gantt.config.quick_info_detached&&dhtmlxEvent(gantt.$task_data,"scroll",function(){gantt.hideQuickInfo()})}return this._quick_info_box},gantt._qi_button_click=function(t){var e=gantt._quick_info_box;if(t&&t!=e){var n=t.className;if(-1!=n.indexOf("_icon")){var i=gantt._quick_info_box_id;gantt.$click.buttons[n.split(" ")[1].replace("icon_","")](i)}else gantt._qi_button_click(t.parentNode)}},gantt._get_event_counter_part=function(t){for(var e=gantt.getTaskNode(t),n=0,i=0,a=e;a&&"gantt_task"!=a.className;)n+=a.offsetLeft,i+=a.offsetTop,a=a.offsetParent;var s=this.getScrollState();if(a){var r=n+e.offsetWidth/2-s.x>gantt._x/2?1:0,o=i+e.offsetHeight/2-s.y>gantt._y/2?1:0;return{left:n,top:i,dx:r,dy:o,width:e.offsetWidth,height:e.offsetHeight}}return 0},gantt._fill_quick_data=function(t){var e=gantt.getTask(t),n=gantt._quick_info_box;gantt._quick_info_box_id=t;var i=n.firstChild.firstChild;i.innerHTML=gantt.templates.quick_info_title(e.start_date,e.end_date,e);var a=i.nextSibling;a.innerHTML=gantt.templates.quick_info_date(e.start_date,e.end_date,e);var s=n.firstChild.nextSibling;s.innerHTML=gantt.templates.quick_info_content(e.start_date,e.end_date,e)};
\ No newline at end of file
/*
This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
to use it in non-GPL project. Please contact sales@dhtmlx.com for details
*/
gantt._tooltip={},gantt._tooltip_class="gantt_tooltip",gantt.config.tooltip_timeout=30,gantt._create_tooltip=function(){return this._tooltip_html||(this._tooltip_html=document.createElement("div"),this._tooltip_html.className=gantt._tooltip_class),this._tooltip_html},gantt._show_tooltip=function(t,e){if(!gantt.config.touch||gantt.config.touch_tooltip){var n=this._create_tooltip();n.innerHTML=t,gantt.$task_data.appendChild(n);var i=n.offsetWidth+20,a=n.offsetHeight+40,s=this.$task.offsetHeight,r=this.$task.offsetWidth,o=this.getScrollState();e.x+=o.x,e.y+=o.y,e.y=Math.min(Math.max(o.y,e.y),o.y+s-a),e.x=Math.min(Math.max(o.x,e.x),o.x+r-i),n.style.left=e.x+"px",n.style.top=e.y+"px"}},gantt._hide_tooltip=function(){this._tooltip_html&&this._tooltip_html.parentNode&&this._tooltip_html.parentNode.removeChild(this._tooltip_html),this._tooltip_id=0},gantt._is_tooltip=function(t){var e=t.target||t.srcElement;return gantt._is_node_child(e,function(t){return t.className==this._tooltip_class})},gantt._is_task_line=function(t){var e=t.target||t.srcElement;return gantt._is_node_child(e,function(t){return t==this.$task_data})},gantt._is_node_child=function(t,e){for(var n=!1;t&&!n;)n=e.call(gantt,t),t=t.parentNode;return n},gantt._tooltip_pos=function(t){if(t.pageX||t.pageY)var e={x:t.pageX,y:t.pageY};var n=_isIE?document.documentElement:document.body,e={x:t.clientX+n.scrollLeft-n.clientLeft,y:t.clientY+n.scrollTop-n.clientTop},i=gantt._get_position(gantt.$task);return e.x=e.x-i.x,e.y=e.y-i.y,e},gantt.attachEvent("onMouseMove",function(t,e){this.config.tooltip_timeout?(document.createEventObject&&!document.createEvent&&(e=document.createEventObject(e)),clearTimeout(gantt._tooltip_ev_timer),gantt._tooltip_ev_timer=setTimeout(function(){gantt._init_tooltip(t,e)},gantt.config.tooltip_timeout)):gantt._init_tooltip(t,e)}),gantt._init_tooltip=function(t,e){if(!this._is_tooltip(e)&&(t!=this._tooltip_id||this._is_task_line(e))){if(!t)return this._hide_tooltip();this._tooltip_id=t;var n=this.getTask(t),i=this.templates.tooltip_text(n.start_date,n.end_date,n);i||this._hide_tooltip(),this._show_tooltip(i,this._tooltip_pos(e))}},gantt.attachEvent("onMouseLeave",function(t){gantt._is_tooltip(t)||this._hide_tooltip()}),gantt.templates.tooltip_date_format=gantt.date.date_to_str("%Y-%m-%d"),gantt.templates.tooltip_text=function(t,e,n){return"<b>Task:</b> "+n.text+"<br/><b>Start date:</b> "+gantt.templates.tooltip_date_format(t)+"<br/><b>End date:</b> "+gantt.templates.tooltip_date_format(e)};
\ No newline at end of file
gantt.locale={date:{month_full:["January","February","March","April","May","June","July","August","September","October","November","December"],month_short:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],day_full:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],day_short:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]},labels:{new_task:"New task",icon_save:"Save",icon_cancel:"Cancel",icon_details:"Details",icon_edit:"Edit",icon_delete:"Delete",confirm_closing:"",confirm_deleting:"Task will be deleted permanently, are you sure?",section_description:"Description",section_time:"Time period",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
scheduler.locale={date:{month_full:["كانون الثاني","شباط","آذار","نيسان","أيار","حزيران","تموز","آب","أيلول","تشرين الأول","تشرين الثاني","كانون الأول"],month_short:["يناير","فبراير","مارس","أبريل","مايو","يونيو","يوليو","أغسطس","سبتمبر","أكتوبر","نوفمبر","ديسمبر"],day_full:["الأحد","الأثنين","ألثلاثاء","الأربعاء","ألحميس","ألجمعة","السبت"],day_short:["احد","اثنين","ثلاثاء","اربعاء","خميس","جمعة","سبت"]},labels:{dhx_cal_today_button:"اليوم",day_tab:"يوم",week_tab:"أسبوع",month_tab:"شهر",new_event:"حدث جديد",icon_save:"اخزن",icon_cancel:"الغاء",icon_details:"تفاصيل",icon_edit:"تحرير",icon_delete:"حذف",confirm_closing:"التغييرات سوف تضيع, هل انت متأكد؟",confirm_deleting:"الحدث سيتم حذفها نهائيا ، هل أنت متأكد؟",section_description:"الوصف",section_time:"الفترة الزمنية",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
gantt.locale={date:{month_full:["Студзень","Люты","Сакавік","Красавік","Maй","Чэрвень","Ліпень","Жнівень","Верасень","Кастрычнік","Лістапад","Снежань"],month_short:["Студз","Лют","Сак","Крас","Maй","Чэр","Ліп","Жнів","Вер","Каст","Ліст","Снеж"],day_full:["Нядзеля","Панядзелак","Аўторак","Серада","Чацвер","Пятніца","Субота"],day_short:["Нд","Пн","Аўт","Ср","Чцв","Пт","Сб"]},labels:{dhx_cal_today_button:"Сёння",day_tab:"Дзень",week_tab:"Тыдзень",month_tab:"Месяц",new_event:"Новая падзея",icon_save:"Захаваць",icon_cancel:"Адмяніць",icon_details:"Дэталі",icon_edit:"Змяніць",icon_delete:"Выдаліць",confirm_closing:"",confirm_deleting:"Падзея будзе выдалена незваротна, працягнуць?",section_description:"Апісанне",section_time:"Перыяд часу",column_text:"Задача",column_start_date:"Пачатак",column_duration:"Працяг",column_add:"",link:"Сувязь",confirm_link_deleting:"будзе выдалена",link_start:"(пачатак)",link_end:"(канец)",minutes:"Хвiлiна",hours:"Гадзiна",days:"Дзень",weeks:"Тыдзень",months:"Месяц",years:"Год"}};
\ No newline at end of file
scheduler.locale={date:{month_full:["Gener","Febrer","Març","Abril","Maig","Juny","Juliol","Agost","Setembre","Octubre","Novembre","Desembre"],month_short:["Gen","Feb","Mar","Abr","Mai","Jun","Jul","Ago","Set","Oct","Nov","Des"],day_full:["Diumenge","Dilluns","Dimarts","Dimecres","Dijous","Divendres","Dissabte"],day_short:["Dg","Dl","Dm","Dc","Dj","Dv","Ds"]},labels:{dhx_cal_today_button:"Hui",day_tab:"Dia",week_tab:"Setmana",month_tab:"Mes",new_event:"Nou esdeveniment",icon_save:"Guardar",icon_cancel:"Cancel·lar",icon_details:"Detalls",icon_edit:"Editar",icon_delete:"Esborrar",confirm_closing:"",confirm_deleting:"L'esdeveniment s'esborrarà definitivament, continuar ?",section_description:"Descripció",section_time:"Periode de temps",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
scheduler.config.day_date="%M %d日 %D",scheduler.config.default_date="%Y年 %M %d日",scheduler.config.month_date="%Y年 %M",scheduler.locale={date:{month_full:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],month_short:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],day_full:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],day_short:["","","","","","",""]},labels:{dhx_cal_today_button:"今天",day_tab:"",week_tab:"",month_tab:"",new_event:"新建日程",icon_save:"保存",icon_cancel:"关闭",icon_details:"详细",icon_edit:"编辑",icon_delete:"删除",confirm_closing:"请确认是否撤销修改!",confirm_deleting:"是否删除日程?",section_description:"描述",section_time:"时间范围",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
scheduler.locale={date:{month_full:["Leden","Únor","Březen","Duben","Květen","Červen","Červenec","Srpen","Září","Říjen","Listopad","Prosinec"],month_short:["Led","Ún","Bře","Dub","Kvě","Čer","Čec","Srp","Září","Říj","List","Pro"],day_full:["Neděle","Pondělí","Úterý","Středa","Čtvrtek","Pátek","Sobota"],day_short:["Ne","Po","Út","St","Čt","","So"]},labels:{dhx_cal_today_button:"Dnes",day_tab:"Den",week_tab:"Týden",month_tab:"Měsíc",new_event:"Nová událost",icon_save:"Uložit",icon_cancel:"Zpět",icon_details:"Detail",icon_edit:"Edituj",icon_delete:"Smazat",confirm_closing:"",confirm_deleting:"Událost bude trvale smazána, opravdu?",section_description:"Poznámky",section_time:"Doba platnosti",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
scheduler.locale={date:{month_full:["Januar","Februar","Marts","April","Maj","Juni","Juli","August","September","Oktober","November","December"],month_short:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],day_full:["Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag"],day_short:["Søn","Man","Tir","Ons","Tor","Fre","Lør"]},labels:{dhx_cal_today_button:"Idag",day_tab:"Dag",week_tab:"Uge",month_tab:"Måned",new_event:"Ny begivenhed",icon_save:"Gem",icon_cancel:"Fortryd",icon_details:"Detaljer",icon_edit:"Tilret",icon_delete:"Slet",confirm_closing:"Dine rettelser vil gå tabt.. Er dy sikker?",confirm_deleting:"Bigivenheden vil blive slettet permanent. Er du sikker?",section_description:"Beskrivelse",section_time:"Tidsperiode",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
gantt.locale={date:{month_full:[" Januar"," Februar"," März "," April"," Mai"," Juni"," Juli"," August"," September "," Oktober"," November "," Dezember"],month_short:["Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],day_full:["Sonntag","Montag","Dienstag"," Mittwoch"," Donnerstag","Freitag","Samstag"],day_short:["So","Mo","Di","Mi","Do","Fr","Sa"]},labels:{dhx_cal_today_button:"Heute",day_tab:"Tag",week_tab:"Woche",month_tab:"Monat",new_event:"neuer Eintrag",icon_save:"Speichern",icon_cancel:"Abbrechen",icon_details:"Details",icon_edit:"Ändern",icon_delete:"Löschen",confirm_closing:"",confirm_deleting:"Der Eintrag wird gelöscht",section_description:"Beschreibung",section_time:"Zeitspanne",column_text:"Task-Namen",column_start_date:"Startzeit",column_duration:"Dauer",column_add:"",link:"Link",confirm_link_deleting:"werden gelöscht",link_start:"(starten)",link_end:"(ende)",minutes:"Minuten",hours:"Stunden",days:"Tage",weeks:"Wochen",months:"Monate",years:"Jahre"}};
\ No newline at end of file
scheduler.locale={date:{month_full:["Ιανουάριος","Φεβρουάριος","Μάρτιος","Απρίλιος","Μάϊος","Ιούνιος","Ιούλιος","Αύγουστος","Σεπτέμβριος","Οκτώβριος","Νοέμβριος","Δεκέμβριος"],month_short:["ΙΑΝ","ΦΕΒ","ΜΑΡ","ΑΠΡ","ΜΑΙ","ΙΟΥΝ","ΙΟΥΛ","ΑΥΓ","ΣΕΠ","ΟΚΤ","ΝΟΕ","ΔΕΚ"],day_full:["Κυριακή","Δευτέρα","Τρίτη","Τετάρτη","Πέμπτη","Παρασκευή","Κυριακή"],day_short:["ΚΥ","ΔΕ","ΤΡ","ΤΕ","ΠΕ","ΠΑ","ΣΑ"]},labels:{dhx_cal_today_button:"Σήμερα",day_tab:"Ημέρα",week_tab:"Εβδομάδα",month_tab:"Μήνας",new_event:"Νέο έργο",icon_save:"Αποθήκευση",icon_cancel:"Άκυρο",icon_details:"Λεπτομέρειες",icon_edit:"Επεξεργασία",icon_delete:"Διαγραφή",confirm_closing:"",confirm_deleting:"Το έργο θα διαγραφεί οριστικά. Θέλετε να συνεχίσετε;",section_description:"Περιγραφή",section_time:"Χρονική περίοδος",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
scheduler.locale={date:{month_full:["Enero","Febrero","Marzo","Abril","Mayo","Junio","Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"],month_short:["Ene","Feb","Mar","Abr","May","Jun","Jul","Ago","Sep","Oct","Nov","Dic"],day_full:["Domingo","Lunes","Martes","Miércoles","Jueves","Viernes","Sábado"],day_short:["Dom","Lun","Mar","Mié","Jue","Vie","Sáb"]},labels:{dhx_cal_today_button:"Hoy",day_tab:"Día",week_tab:"Semana",month_tab:"Mes",new_event:"Nuevo evento",icon_save:"Guardar",icon_cancel:"Cancelar",icon_details:"Detalles",icon_edit:"Editar",icon_delete:"Eliminar",confirm_closing:"",confirm_deleting:"El evento se borrará definitivamente, ¿continuar?",section_description:"Descripción",section_time:"Período",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
scheduler.locale={date:{month_full:["Tammikuu","Helmikuu","Maaliskuu","Huhtikuu","Toukokuu","Kes&auml;kuu","Hein&auml;kuu","Elokuu","Syyskuu","Lokakuu","Marraskuu","Joulukuu"],month_short:["Tam","Hel","Maa","Huh","Tou","Kes","Hei","Elo","Syy","Lok","Mar","Jou"],day_full:["Sunnuntai","Maanantai","Tiistai","Keskiviikko","Torstai","Perjantai","Lauantai"],day_short:["Su","Ma","Ti","Ke","To","Pe","La"]},labels:{dhx_cal_today_button:"Tänään",day_tab:"Päivä",week_tab:"Viikko",month_tab:"Kuukausi",new_event:"Uusi tapahtuma",icon_save:"Tallenna",icon_cancel:"Peru",icon_details:"Tiedot",icon_edit:"Muokkaa",icon_delete:"Poista",confirm_closing:"",confirm_deleting:"Haluatko varmasti poistaa tapahtuman?",section_description:"Kuvaus",section_time:"Aikajakso",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
gantt.locale={date:{month_full:["Janvier","Février","Mars","Avril","Mai","Juin","Juillet","Août","Septembre","Octobre","Novembre","Décembre"],month_short:["Jan","Fév","Mar","Avr","Mai","Juin","Juil","Aôu","Sep","Oct","Nov","Déc"],day_full:["Dimanche","Lundi","Mardi","Mercredi","Jeudi","Vendredi","Samedi"],day_short:["Dim","Lun","Mar","Mer","Jeu","Ven","Sam"]},labels:{new_task:"Tâche neuve",icon_save:"Enregistrer",icon_cancel:"Annuler",icon_details:"Détails",icon_edit:"Modifier",icon_delete:"Effacer",confirm_closing:"",confirm_deleting:"L'événement sera effacé sans appel, êtes-vous sûr ?",section_description:"Description",section_time:"Période",column_text:"Tâche neuve",column_start_date:"Date initiale",column_duration:"Durée",column_add:"",confirm_link_deleting:"seront supprimées",link_start:"(début)",link_end:"(fin)",minutes:"Minutes",hours:"Heures",days:"Jours",weeks:"Semaine",months:"Mois",years:"Années"}};
\ No newline at end of file
scheduler.locale={date:{month_full:["ינואר","פברואר","מרץ","אפריל","מאי","יוני","יולי","אוגוסט","ספטמבר","אוקטובר","נובמבר","דצמבר"],month_short:["ינו","פבר","מרץ","אפר","מאי","יונ","יול","אוג","ספט","אוק","נוב","דצמ"],day_full:["ראשון","שני","שלישי","רביעי","חמישי","שישי","שבת"],day_short:["א","ב","ג","ד","ה","ו","ש"]},labels:{dhx_cal_today_button:"היום",day_tab:"יום",week_tab:"שבוע",month_tab:"חודש",new_event:"ארוע חדש",icon_save:"שמור",icon_cancel:"בטל",icon_details:"פרטים",icon_edit:"ערוך",icon_delete:"מחק",confirm_closing:"",confirm_deleting:"ארוע ימחק סופית.להמשיך?",section_description:"הסבר",section_time:"תקופה",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
scheduler.locale={date:{month_full:["Január","Február","Március","Április","Május","Június","Július","Augusztus","Szeptember","Október","November","December"],month_short:["Jan","Feb","Már","Ápr","Máj","Jún","Júl","Aug","Sep","Okt","Nov","Dec"],day_full:["Vasárnap","Hétfõ","Kedd","Szerda","Csütörtök","Péntek","szombat"],day_short:["Va","","Ke","Sze","Csü","","Szo"]},labels:{dhx_cal_today_button:"Ma",day_tab:"Nap",week_tab:"Hét",month_tab:"Hónap",new_event:"Új esemény",icon_save:"Mentés",icon_cancel:"Mégse",icon_details:"Részletek",icon_edit:"Szerkesztés",icon_delete:"Törlés",confirm_closing:"",confirm_deleting:"Az esemény törölve lesz, biztosan folytatja?",section_description:"Leírás",section_time:"Idõszak",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
scheduler.locale={date:{month_full:["Januari","Februari","Maret","April","Mei","Juni","Juli","Agustus","September","Oktober","November","Desember"],month_short:["Jan","Feb","Mar","Apr","Mei","Jun","Jul","Ags","Sep","Okt","Nov","Des"],day_full:["Minggu","Senin","Selasa","Rabu","Kamis","Jumat","Sabtu"],day_short:["Ming","Sen","Sel","Rab","Kam","Jum","Sab"]},labels:{dhx_cal_today_button:"Hari Ini",day_tab:"Hari",week_tab:"Minggu",month_tab:"Bulan",new_event:"Acara Baru",icon_save:"Simpan",icon_cancel:"Batal",icon_details:"Detail",icon_edit:"Edit",icon_delete:"Hapus",confirm_closing:"",confirm_deleting:"Acara akan dihapus",section_description:"Keterangan",section_time:"Periode",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
scheduler.locale={date:{month_full:["Gennaio","Febbraio","Marzo","Aprile","Maggio","Giugno","Luglio","Agosto","Settembre","Ottobre","Novembre","Dicembre"],month_short:["Gen","Feb","Mar","Apr","Mag","Giu","Lug","Ago","Set","Ott","Nov","Dic"],day_full:["Domenica","Lunedì","Martedì","Mercoledì","Giovedì","Venerdì","Sabato"],day_short:["Dom","Lun","Mar","Mer","Gio","Ven","Sab"]},labels:{dhx_cal_today_button:"Oggi",day_tab:"Giorno",week_tab:"Settimana",month_tab:"Mese",new_event:"Nuovo evento",icon_save:"Salva",icon_cancel:"Chiudi",icon_details:"Dettagli",icon_edit:"Modifica",icon_delete:"Elimina",confirm_closing:"",confirm_deleting:"L'evento sarà eliminato, siete sicuri?",section_description:"Descrizione",section_time:"Periodo di tempo",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
scheduler.locale={date:{month_full:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],month_short:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],day_full:["日曜日","月曜日","火曜日","水曜日","木曜日","金曜日","土曜日"],day_short:["","","","","","",""]},labels:{dhx_cal_today_button:"今日",day_tab:"",week_tab:"",month_tab:"",new_event:"新イベント",icon_save:"保存",icon_cancel:"キャンセル",icon_details:"詳細",icon_edit:"編集",icon_delete:"削除",confirm_closing:"",confirm_deleting:"イベント完全に削除されます、宜しいですか?",section_description:"デスクリプション",section_time:"期間",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
scheduler.locale={date:{month_full:["Januar","Februar","Mars","April","Mai","Juni","Juli","August","September","Oktober","November","Desember"],month_short:["Jan","Feb","Mar","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Des"],day_full:["Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag"],day_short:["Søn","Mon","Tir","Ons","Tor","Fre","Lør"]},labels:{dhx_cal_today_button:"I dag",day_tab:"Dag",week_tab:"Uke",month_tab:"Måned",new_event:"Ny hendelse",icon_save:"Lagre",icon_cancel:"Avbryt",icon_details:"Detaljer",icon_edit:"Rediger",icon_delete:"Slett",confirm_closing:"",confirm_deleting:"Hendelsen vil bli slettet permanent. Er du sikker?",section_description:"Beskrivelse",section_time:"Tidsperiode",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
scheduler.locale={date:{month_full:["Januari","Februari","Maart","April","Mei","Juni","Juli","Augustus","September","Oktober","November","December"],month_short:["Jan","Feb","mrt","Apr","Mei","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],day_full:["Zondag","Maandag","Dinsdag","Woensdag","Donderdag","Vrijdag","Zaterdag"],day_short:["Zo","Ma","Di","Wo","Do","Vr","Za"]},labels:{dhx_cal_today_button:"Vandaag",day_tab:"Dag",week_tab:"Week",month_tab:"Maand",new_event:"Nieuw item",icon_save:"Opslaan",icon_cancel:"Annuleren",icon_details:"Details",icon_edit:"Edit",icon_delete:"Verwijderen",confirm_closing:"",confirm_deleting:"Item zal permanent worden verwijderd, doorgaan?",section_description:"Beschrijving",section_time:"Tijd periode",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
scheduler.locale={date:{month_full:["Januar","Februar","Mars","April","Mai","Juni","Juli","August","September","Oktober","November","Desember"],month_short:["Jan","Feb","Mar","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Des"],day_full:["Søndag","Mandag","Tirsdag","Onsdag","Torsdag","Fredag","Lørdag"],day_short:["Søn","Man","Tir","Ons","Tor","Fre","Lør"]},labels:{dhx_cal_today_button:"Idag",day_tab:"Dag",week_tab:"Uke",month_tab:"Måned",new_event:"Ny",icon_save:"Lagre",icon_cancel:"Avbryt",icon_details:"Detaljer",icon_edit:"Endre",icon_delete:"Slett",confirm_closing:"Endringer blir ikke lagret, er du sikker?",confirm_deleting:"Oppføringen vil bli slettet, er du sikker?",section_description:"Beskrivelse",section_time:"Tidsperiode",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
scheduler.locale={date:{month_full:["Styczeń","Luty","Marzec","Kwiecień","Maj","Czerwiec","Lipiec","Sierpień","Wrzesień","Październik","Listopad","Grudzień"],month_short:["Sty","Lut","Mar","Kwi","Maj","Cze","Lip","Sie","Wrz","Paź","Lis","Gru"],day_full:["Niedziela","Poniedziałek","Wtorek","Środa","Czwartek","Piątek","Sobota"],day_short:["Nie","Pon","Wto","Śro","Czw","Pią","Sob"]},labels:{dhx_cal_today_button:"Dziś",day_tab:"Dzień",week_tab:"Tydzień",month_tab:"Miesiąc",new_event:"Nowe zdarzenie",icon_save:"Zapisz",icon_cancel:"Anuluj",icon_details:"Szczegóły",icon_edit:"Edytuj",icon_delete:"Usuń",confirm_closing:"",confirm_deleting:"Zdarzenie zostanie usunięte na zawsze, kontynuować?",section_description:"Opis",section_time:"Okres czasu",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
scheduler.locale={date:{month_full:["Janeiro","Fevereiro","Março","Abril","Maio","Junho","Julho","Agosto","Setembro","Outubro","Novembro","Dezembro"],month_short:["Jan","Fev","Mar","Abr","Mai","Jun","Jul","Ago","Set","Out","Nov","Dez"],day_full:["Domingo","Segunda","Terça","Quarta","Quinta","Sexta","Sábado"],day_short:["Dom","Seg","Ter","Qua","Qui","Sex","Sab"]},labels:{dhx_cal_today_button:"Hoje",day_tab:"Dia",week_tab:"Semana",month_tab:"Mês",new_event:"Novo evento",icon_save:"Salvar",icon_cancel:"Cancelar",icon_details:"Detalhes",icon_edit:"Editar",icon_delete:"Deletar",confirm_closing:"",confirm_deleting:"Tem certeza que deseja excluir?",section_description:"Descrição",section_time:"Período de tempo",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
scheduler.locale={date:{month_full:["Ianuarie","Februarie","Martie","Aprilie","Mai","Iunie","Iulie","August","Septembrie","Octombrie","November","December"],month_short:["Ian","Feb","Mar","Apr","Mai","Iun","Iul","Aug","Sep","Oct","Nov","Dec"],day_full:["Duminica","Luni","Marti","Miercuri","Joi","Vineri","Sambata"],day_short:["Du","Lu","Ma","Mi","Jo","Vi","Sa"]},labels:{dhx_cal_today_button:"Astazi",day_tab:"Zi",week_tab:"Saptamana",month_tab:"Luna",new_event:"Eveniment nou",icon_save:"Salveaza",icon_cancel:"Anuleaza",icon_details:"Detalii",icon_edit:"Editeaza",icon_delete:"Sterge",confirm_closing:"Schimbarile nu vor fi salvate, esti sigur?",confirm_deleting:"Evenimentul va fi sters permanent, esti sigur?",section_description:"Descriere",section_time:"Interval",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
gantt.locale={date:{month_full:["Январь","Февраль","Март","Апрель","Maй","Июнь","Июль","Август","Сентябрь","Oктябрь","Ноябрь","Декабрь"],month_short:["Янв","Фев","Maр","Aпр","Maй","Июн","Июл","Aвг","Сен","Окт","Ноя","Дек"],day_full:["Воскресенье","Понедельник","Вторник","Среда","Четверг","Пятница","Суббота"],day_short:["Вс","Пн","Вт","Ср","Чт","Пт","Сб"]},labels:{dhx_cal_today_button:"Сегодня",day_tab:"День",week_tab:"Неделя",month_tab:"Месяц",new_event:"Новое событие",icon_save:"Сохранить",icon_cancel:"Отменить",icon_details:"Детали",icon_edit:"Изменить",icon_delete:"Удалить",confirm_closing:"",confirm_deleting:"Событие будет удалено безвозвратно, продолжить?",section_description:"Описание",section_time:"Период времени",column_text:"Задача",column_start_date:"Начало",column_duration:"Длительность",column_add:"",link:"Связь",confirm_link_deleting:"будет удалена",link_start:" (начало)",link_end:" (конец)",minutes:"Минута",hours:"Час",days:"День",weeks:"Неделя",months:"Месяц",years:"Год"}};
\ No newline at end of file
scheduler.locale={date:{month_full:["Januar","Februar","Marec","April","Maj","Junij","Julij","Avgust","September","Oktober","November","December"],month_short:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],day_full:["Nedelja","Ponedeljek","Torek","Sreda","Četrtek","Petek","Sobota"],day_short:["Ned","Pon","Tor","Sre","Čet","Pet","Sob"]},labels:{dhx_cal_today_button:"Danes",day_tab:"Dan",week_tab:"Teden",month_tab:"Mesec",new_event:"Nov dogodek",icon_save:"Shrani",icon_cancel:"Prekliči",icon_details:"Podrobnosti",icon_edit:"Uredi",icon_delete:"Izbriši",confirm_closing:"",confirm_deleting:"Dogodek bo izbrisan. Želite nadaljevati?",section_description:"Opis",section_time:"Časovni okvir",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
scheduler.locale={date:{month_full:["Január","Február","Marec","Apríl","Máj","Jún","Júl","August","September","Október","November","December"],month_short:["Jan","Feb","Mar","Apr","Máj","Jún","Júl","Aug","Sept","Okt","Nov","Dec"],day_full:["Nedeľa","Pondelok","Utorok","Streda","Štvrtok","Piatok","Sobota"],day_short:["Ne","Po","Ut","St","Št","Pi","So"]},labels:{dhx_cal_today_button:"Dnes",day_tab:"Deň",week_tab:"Týždeň",month_tab:"Mesiac",new_event:"Nová udalosť",icon_save:"Uložiť",icon_cancel:"Späť",icon_details:"Detail",icon_edit:"Edituj",icon_delete:"Zmazať",confirm_closing:"Vaše zmeny nebudú uložené. Skutočne?",confirm_deleting:"Udalosť bude natrvalo vymazaná. Skutočne?",section_description:"Poznámky",section_time:"Doba platnosti",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
scheduler.locale={date:{month_full:["Januari","Februari","Mars","April","Maj","Juni","Juli","Augusti","September","Oktober","November","December"],month_short:["Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"],day_full:["Söndag","Måndag","Tisdag","Onsdag","Torsdag","Fredag","Lördag"],day_short:["Sön","Mån","Tis","Ons","Tor","Fre","Lör"]},labels:{dhx_cal_today_button:"Idag",day_tab:"Dag",week_tab:"Vecka",month_tab:"Månad",new_event:"Ny händelse",icon_save:"Spara",icon_cancel:"Ångra",icon_details:"Detajer",icon_edit:"Ändra",icon_delete:"Ta bort",confirm_closing:"",confirm_deleting:"Är du säker på att du vill ta bort händelsen permanent?",section_description:"Beskrivning",section_time:"Tid",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
scheduler.locale={date:{month_full:["Ocak","Þubat","Mart","Nisan","Mayýs","Haziran","Temmuz","Aðustos","Eylül","Ekim","Kasým","Aralýk"],month_short:["Oca","Þub","Mar","Nis","May","Haz","Tem","Aðu","Eyl","Eki","Kas","Ara"],day_full:["Pazar","Pazartes,","Salý","Çarþamba","Perþembe","Cuma","Cumartesi"],day_short:["Paz","Pts","Sal","Çar","Per","Cum","Cts"]},labels:{dhx_cal_today_button:"Bugün",day_tab:"Gün",week_tab:"Hafta",month_tab:"Ay",new_event:"Uygun",icon_save:"Kaydet",icon_cancel:"Ýptal",icon_details:"Detaylar",icon_edit:"Düzenle",icon_delete:"Sil",confirm_closing:"",confirm_deleting:"Etkinlik silinecek, devam?",section_description:"Açýklama",section_time:"Zaman aralýðý",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
scheduler.locale={date:{month_full:["Січень","Лютий","Березень","Квітень","Травень","Червень","Липень","Серпень","Вересень","Жовтень","Листопад","Грудень"],month_short:["Січ","Лют","Бер","Кві","Тра","Чер","Лип","Сер","Вер","Жов","Лис","Гру"],day_full:["Неділя","Понеділок","Вівторок","Середа","Четвер","П'ятниця","Субота"],day_short:["Нед","Пон","Вів","Сер","Чет","Птн","Суб"]},labels:{dhx_cal_today_button:"Сьогодні",day_tab:"День",week_tab:"Тиждень",month_tab:"Місяць",new_event:"Нова подія",icon_save:"Зберегти",icon_cancel:"Відміна",icon_details:"Деталі",icon_edit:"Редагувати",icon_delete:"Вилучити",confirm_closing:"",confirm_deleting:"Подія вилучиться назавжди. Ви впевнені?",section_description:"Опис",section_time:"Часовий проміжок",column_text:"Task name",column_start_date:"Start time",column_duration:"Duration",column_add:"",link:"Link",confirm_link_deleting:"will be deleted",link_start:" (start)",link_end:" (end)",minutes:"Minutes",hours:"Hours",days:"Days",weeks:"Week",months:"Months",years:"Years"}};
\ No newline at end of file
/*
This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
to use it in non-GPL project. Please contact sales@dhtmlx.com for details
*/
.gantt_grid{background-color:#d6d6d6;border-right:1px solid #d6d6d6!important}.gantt_grid_scale{background-color:#4f4f4f;background-size:5px 5px;background-size:4px 4px;background-position:0 0,2px 2px;background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.05) 25%,transparent 25%,transparent 75%,rgba(255,255,255,.05) 75%,rgba(255,255,255,.05)),linear-gradient(45deg,rgba(255,255,255,.05) 25%,transparent 25%,transparent 75%,rgba(255,255,255,.05) 75%,rgba(255,255,255,.05));background-image:-moz-linear-gradient(45deg,rgba(255,255,255,.05) 25%,transparent 25%,transparent 75%,rgba(255,255,255,.05) 75%,rgba(255,255,255,.05)),linear-gradient(45deg,rgba(255,255,255,.05) 25%,transparent 25%,transparent 75%,rgba(255,255,255,.05) 75%,rgba(255,255,255,.05));background-image:linear-gradient(45deg,rgba(255,255,255,.05) 25%,transparent 25%,transparent 75%,rgba(255,255,255,.05) 75%,rgba(255,255,255,.05)),linear-gradient(45deg,rgba(255,255,255,.05) 25%,transparent 25%,transparent 75%,rgba(255,255,255,.05) 75%,rgba(255,255,255,.05));color:#e1e1e1;font-size:14px}.gantt_grid_scale .gantt_grid_head_cell{border-top:0!important;border-right:1px solid #4f4f4f!important;border-left:1px solid #5b5b5b!important}.gantt_grid_data .gantt_row,.gantt_grid_data .gantt_row.odd{background-color:#dbdbdb;border-top:1px solid #f6f6f6;border-bottom:1px solid #bababa}.gantt_grid_data .gantt_row .gantt_cell,.gantt_grid_data .gantt_row.odd .gantt_cell{border-right:0}.gridHoverStyle{border-top:1px solid #ededed;background-color:#ededed}.gridSelection{background-color:#fff3a1;border-top-color:#fff087}.timelineSelection{background-color:#fff3a1}.gantt_cell .gantt_tree_icon.gantt_folder_open{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAPCAYAAAACsSQRAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODhBMDIwQzhDRUI3MTFFMjg4RUJDNUMzQkZEREM2RDIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODhBMDIwQzlDRUI3MTFFMjg4RUJDNUMzQkZEREM2RDIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4OEEwMjBDNkNFQjcxMUUyODhFQkM1QzNCRkREQzZEMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4OEEwMjBDN0NFQjcxMUUyODhFQkM1QzNCRkREQzZEMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PntuzoIAAACySURBVHjaYmTAAjo6Ov4z4AAVFRWM6GJM6ALt7e0OUCYjFowsDwfIEjDbDRgZGSf8//8fUzEj4wGgeAGQeQFJ+CIj0IALQEl9BjIB0NADTJQYAHWdAxMDFQBVDGEB+ukgxYYAcQNSzCCnh4NY0o89VkOAAbMfV5gBNR0AytuDXAs01AEUxbhcwoAj1c4HaUTi9wMNwWnb/0ERO0xA/z6k0IyP4NgBGpRApgEfgHgBQIABAAcyQOF3e6FiAAAAAElFTkSuQmCC)}.gantt_cell .gantt_tree_icon.gantt_folder_closed{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAPCAYAAAACsSQRAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODhBMDIwQzhDRUI3MTFFMjg4RUJDNUMzQkZEREM2RDIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6ODhBMDIwQzlDRUI3MTFFMjg4RUJDNUMzQkZEREM2RDIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo4OEEwMjBDNkNFQjcxMUUyODhFQkM1QzNCRkREQzZEMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo4OEEwMjBDN0NFQjcxMUUyODhFQkM1QzNCRkREQzZEMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PntuzoIAAACySURBVHjaYmTAAjo6Ov4z4AAVFRWM6GJM6ALt7e0OUCYjFowsDwfIEjDbDRgZGSf8//8fUzEj4wGgeAGQeQFJ+CIj0IALQEl9BjIB0NADTJQYAHWdAxMDFQBVDGEB+ukgxYYAcQNSzCCnh4NY0o89VkOAAbMfV5gBNR0AytuDXAs01AEUxbhcwoAj1c4HaUTi9wMNwWnb/0ERO0xA/z6k0IyP4NgBGpRApgEfgHgBQIABAAcyQOF3e6FiAAAAAElFTkSuQmCC)}.gantt_cell .gantt_tree_icon.gantt_file{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RkQ2NjJFM0ZDRUI3MTFFMkFGNzc4M0YxNUM2NzkwMTIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RkQ2NjJFNDBDRUI3MTFFMkFGNzc4M0YxNUM2NzkwMTIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpGRDY2MkUzRENFQjcxMUUyQUY3NzgzRjE1QzY3OTAxMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpGRDY2MkUzRUNFQjcxMUUyQUY3NzgzRjE1QzY3OTAxMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PhnnqLUAAAExSURBVHjanFPtUcMwDI28AB0BJkg6AWEEJqAbxPxLJkh+phuUDdiAbtAwAYxAJ0jf60k51ecWDt3pZFtPn5akSKjv+3uIBlyLSMW3eZ4niD1423Xdt8dLYjzCqCluEJyNcPJq9+CMJzMGaAtet20rZJ7Bb+eIIhHYw0UGLvIRwBoRplx04CrgWModgwAXhTXj8Usjr73xMAwzJbNInBwU/8ASoqV9LbInYqwc2tJBrZdd8XcaVT4GpFOa52topL1Ks9CGVuGXL/vU40fqZPlGA7E5GX1NvQ7U4sSw1AWdMNIm07CfxIn1K6rcB2sI5yCXhXPyjPO7fuOLNfNikAAi+OnWILEUYFfLICWjXGptO12cyRk20G+sduiq/y7TOXJ2G906R13n0n0nmz2m63wSYAB0aMIyVTQbywAAAABJRU5ErkJggg==)}.chartHeaderBg{background-color:#dbdbdb}.gantt_task .gantt_task_scale .gantt_scale_cell{border-left:1px solid #f6f6f6;border-right:1px solid #bababa;color:#494949;font-size:14px}.taskProgressStyle{border-right:1px solid #1cb3fb;box-shadow:-1px 0 0 1px #04a1eb inset}.gantt_task_line{background-size:4px 4px;background-image:-webkit-linear-gradient(-45deg,rgba(255,255,255,.2) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.2) 50%,rgba(255,255,255,.2) 75%,transparent 75%,transparent);background-image:linear-gradient(135deg,rgba(255,255,255,.2) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.2) 50%,rgba(255,255,255,.2) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(0deg,transparent,transparent)}.gantt_task_line .gantt_task_content{text-align:left;font-weight:700;padding:0 8px;box-sizing:border-box}.gantt_task_link .gantt_link_arrow_right{border-width:5px;margin-top:-2px}.gantt_task_link .gantt_link_arrow_left{border-width:5px;margin-left:-7px;margin-top:-2px}.gantt_task_link .gantt_link_arrow_top{border-width:5px}.gantt_task_link .gantt_link_arrow_down{border-width:5px}.gantt_task_line .gantt_task_progress_drag{bottom:-4px;height:16px;margin-left:-8px;width:16px}.gantt_row.gantt_project,.gantt_row.odd.gantt_project{background-color:#edffef}.gantt_task_row.gantt_project,.gantt_task_row.odd.gantt_project{background-color:#f5fff6}.gantt_task_line.gantt_project{background-color:#65c16f;border:1px solid #3c9445}.gantt_task_line.gantt_project .gantt_task_progress{background-color:#46ad51;border-right:1px solid #5abc65;box-shadow:-1px 0 0 1px #45a94f inset}.dhx_cal_light{background-color:#fff;border:0!important}.dhx_cal_light .dhx_cal_lsection{color:#959595}.dhx_cal_light .dhx_cal_larea{border-bottom:0}.dhx_cal_light .dhx_cal_ltitle{background-color:#4f4f4f;background-size:5px 5px;background-size:4px 4px;background-position:0 0,2px 2px;background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.05) 25%,transparent 25%,transparent 75%,rgba(255,255,255,.05) 75%,rgba(255,255,255,.05)),linear-gradient(45deg,rgba(255,255,255,.05) 25%,transparent 25%,transparent 75%,rgba(255,255,255,.05) 75%,rgba(255,255,255,.05));background-image:-moz-linear-gradient(45deg,rgba(255,255,255,.05) 25%,transparent 25%,transparent 75%,rgba(255,255,255,.05) 75%,rgba(255,255,255,.05)),linear-gradient(45deg,rgba(255,255,255,.05) 25%,transparent 25%,transparent 75%,rgba(255,255,255,.05) 75%,rgba(255,255,255,.05));background-image:linear-gradient(45deg,rgba(255,255,255,.05) 25%,transparent 25%,transparent 75%,rgba(255,255,255,.05) 75%,rgba(255,255,255,.05)),linear-gradient(45deg,rgba(255,255,255,.05) 25%,transparent 25%,transparent 75%,rgba(255,255,255,.05) 75%,rgba(255,255,255,.05));font-size:14px;color:#e1e1e1;position:relative;padding-top:12px;padding-bottom:12px;font-weight:400}.dhx_cal_light .dhx_cal_ltitle .dhx_title{font-size:13px}.dhx_cal_light .dhx_btn_set{margin:5px 10px}.buttonBg{background:#fff}.dhx_btn_set.dhx_save_btn_set{background:#46ad51;text-shadow:0 -1px 0 #307738;color:#fff}.dhx_btn_set.dhx_delete_btn_set{margin-left:0;text-shadow:0 -1px 0 #6f6f6f;background:#ec8e00;text-shadow:0 -1px 0 #a60;color:#fff}.dhx_delete_btn{margin-top:2px;width:20px}.dhx_cal_light_wide{padding-left:0!important;padding-right:0!important}.dhx_cal_light_wide .dhx_cal_larea{border-left:0!important;border-right:0!important}.dhx_cal_ltitle .dhx_cancel_btn{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAMCAYAAABbayygAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYLEgAjh/DRnAAAAMdJREFUGNN1kcsNwjAQRF9iCkgJdJCUkBJQpDkDFUALdBAqAM6WQglQAlxzSglQgAUH1tIKgS9ezzztz0Vd18QYW0lXfpzsFeM49sAGOEpaf0FHYAnsS6evYoy9g3qDAB6Flb4BtYk568Huu6RmBhBCaFNKV4MPrso9hNAClABd1z2ABfB00BNYmEf5NeTrT/wBh2GogDNQOa8CzuZ9QOuvccPkgZqU0gXA7xFgL2nr1pP1ne/xlCEAi0/5nfc4lzT9+cK5pOkNX8hRT8hMyVwAAAAASUVORK5CYII=);width:30px;top:0;right:0;background-position:6px 12px;background-repeat:no-repeat;position:absolute;height:100%}.dhtmlx_popup_button.dhtmlx_ok_button{background:#46ad51;text-shadow:0 -1px 0 #307738;color:#fff;font-weight:700}.dhtmlx_popup_button.dhtmlx_cancel_button{font-weight:700}.dhx_qi_big_icon.icon_edit{color:#454544}.dhx_qi_big_icon.icon_delete{text-shadow:0 -1px 0 #6f6f6f;background:#ec8e00;text-shadow:0 -1px 0 #a60;color:#fff;font-weight:700}.gantt_container{font-family:Arial;font-size:13px;border:1px solid #bababa;position:relative;white-space:nowrap}.gantt_grid{border-right:1px solid #bababa}.gantt_task_scroll{overflow-x:scroll}.gantt_task{position:relative}.gantt_task,.gantt_grid{overflow-x:hidden;overflow-y:hidden;display:inline-block;vertical-align:top}.gantt_grid_scale,.gantt_task_scale{color:#494949;font-size:12px;border-bottom:1px solid #bababa}.gantt_grid_scale{background-color:#4f4f4f;background-size:5px 5px;background-size:4px 4px;background-position:0 0,2px 2px;background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.05) 25%,transparent 25%,transparent 75%,rgba(255,255,255,.05) 75%,rgba(255,255,255,.05)),linear-gradient(45deg,rgba(255,255,255,.05) 25%,transparent 25%,transparent 75%,rgba(255,255,255,.05) 75%,rgba(255,255,255,.05));background-image:-moz-linear-gradient(45deg,rgba(255,255,255,.05) 25%,transparent 25%,transparent 75%,rgba(255,255,255,.05) 75%,rgba(255,255,255,.05)),linear-gradient(45deg,rgba(255,255,255,.05) 25%,transparent 25%,transparent 75%,rgba(255,255,255,.05) 75%,rgba(255,255,255,.05));background-image:linear-gradient(45deg,rgba(255,255,255,.05) 25%,transparent 25%,transparent 75%,rgba(255,255,255,.05) 75%,rgba(255,255,255,.05)),linear-gradient(45deg,rgba(255,255,255,.05) 25%,transparent 25%,transparent 75%,rgba(255,255,255,.05) 75%,rgba(255,255,255,.05));color:#e1e1e1;font-size:14px}.gantt_task_scale{background-color:#dbdbdb}.gantt_scale_line{box-sizing:border-box;-moz-box-sizing:border-box;border-top:1px solid #bababa}.gantt_scale_line:first-child{border-top:0}.gantt_grid_head_cell{display:inline-block;vertical-align:top;border-right:1px solid #bababa;text-align:center;position:relative;cursor:default;height:100%;box-sizing:border-box;-moz-box-sizing:border-box;line-height:33px;-moz-user-select:-moz-none;-webkit-user-select:none;-user-select:none}.gantt_scale_line{clear:both}.gantt_grid_data{width:100%;overflow:hidden}.gantt_row{position:relative;-webkit-user-select:none;-moz-user-select:none;-moz-user-select:-moz-none}.gantt_add,.gantt_grid_head_add{width:100%;height:100%;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYHCygkHejN0gAAAD1JREFUOMtjtN15mYESwIJLwvHo6v/I/P3WoYzY1DExUAiGgQGMtjsvYwQYsWC/dSjjcAmD0YREo0AkFgAASgkTQxnJ6ggAAAAASUVORK5CYII=);background-position:center center;background-repeat:no-repeat;cursor:pointer;position:relative;-moz-opacity:.3;opacity:.3}.gantt_grid_head_cell.gantt_grid_head_add{-moz-opacity:.6;opacity:.6;top:0}.gantt_grid_head_cell.gantt_grid_head_add:hover{-moz-opacity:1;opacity:1}.gantt_grid_data .gantt_row:hover,.gantt_grid_data .gantt_row.odd:hover{border-top:1px solid #ededed;background-color:#ededed}.gantt_grid_data .gantt_row:hover .gantt_add,.gantt_grid_data .gantt_row.odd:hover .gantt_add{-moz-opacity:1;opacity:1}.gantt_task_row,.gantt_row{border-bottom:1px solid #ebebeb}.gantt_row,.gantt_task_row{background-color:#fff}.gantt_row.odd,.gantt_task_row.odd{background-color:#fff}.gantt_row,.gantt_cell,.gantt_task_row,.gantt_task_cell,.gantt_grid_head_cell,.gantt_scale_cell{box-sizing:border-box;-moz-box-sizing:border-box}.gantt_grid_head_cell,.gantt_scale_cell{line-height:inherit}.gantt_cell{display:inline-block;vertical-align:top;border-right:1px solid #ebebeb;padding-left:6px;padding-right:6px;height:100%;overflow:hidden;white-space:nowrap;font-size:13px}.gantt_grid_scale .gantt_last_cell,.gantt_grid_data .gantt_last_cell,.gantt_task_scale .gantt_last_cell,.gantt_task_bg .gantt_last_cell{border-right-width:0}.gantt_task_bg{overflow:hidden}.gantt_scale_cell{display:inline-block;white-space:nowrap;overflow:hidden;border-right:1px solid #bababa;text-align:center;height:100%}.gantt_task_cell{display:inline-block;height:100%;border-right:1px solid #ebebeb}.gantt_ver_scroll{width:0;background-color:transparent;height:1px;overflow-x:hidden;overflow-y:scroll;display:none;position:absolute;right:0}.gantt_ver_scroll>div{width:1px;height:1px}.gantt_hor_scroll{height:0;background-color:transparent;width:100%;clear:both;overflow-x:scroll;overflow-y:hidden;display:none}.gantt_hor_scroll>div{width:5000px;height:1px}.gantt_tree_indent{width:15px;height:100%;display:inline-block}.gantt_tree_content,.gantt_tree_icon{vertical-align:top}.gantt_tree_icon{width:28px;height:100%;display:inline-block;background-repeat:no-repeat;background-position:center center}.gantt_tree_content{height:100%;display:inline-block}.gantt_tree_icon.gantt_open{background-image:url(data:image/gif;base64,R0lGODlhEgASAOMHAMrKyt3d3ejp6d7f3+/v75aWlvf39////wAAAP///////////////////////////yH5BAEKAA8ALAAAAAASABIAAAQt8MlJq704672L/x9WZKNVXieVIipKsVMaS0hdSzL+tuY+9yJXEAUCcY7IJCUCADs=);width:18px;cursor:pointer}.gantt_tree_icon.gantt_close{background-image:url(data:image/gif;base64,R0lGODlhEgASAOMIAMrKyt3d3ejp6d7f3+/v75aWlvf39wAAAP///////////////////////////////yH5BAEKAAYALAAAAAASABIAAAQp0MhJq704672L/x9WZKNVXieVVqvUTm95zLOLkreY7yqOgiCOcEikRAAAOw==);width:18px;cursor:pointer}.gantt_tree_icon.gantt_blank{width:18px}.gantt_tree_icon.gantt_folder_open{background-image:url(data:image/gif;base64,R0lGODlhEgASAJECAJeXl7Gvrf///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTdDRDM3QzVDMDZEMTFFMUJGMzhFMDhCN0RGRjBGQ0YiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTdDRDM3QzZDMDZEMTFFMUJGMzhFMDhCN0RGRjBGQ0YiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1N0NEMzdDM0MwNkQxMUUxQkYzOEUwOEI3REZGMEZDRiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1N0NEMzdDNEMwNkQxMUUxQkYzOEUwOEI3REZGMEZDRiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAASABIAAAIzlI+pywcPm3mhWgkCsjBOvVkimElG9ZlCBlXd+2XjjLKg5GqoeZXqvsOQXK/ijUZTKVUFADs=)}.gantt_tree_icon.gantt_folder_closed{background-image:url(data:image/gif;base64,R0lGODlhEgASAJECAJeXl7Gvrf///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTAyMTU1RTNDMDZEMTFFMUJGNzZCRThBRkFCRjg4MTIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTAyMTU1RTRDMDZEMTFFMUJGNzZCRThBRkFCRjg4MTIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1MDIxNTVFMUMwNkQxMUUxQkY3NkJFOEFGQUJGODgxMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1MDIxNTVFMkMwNkQxMUUxQkY3NkJFOEFGQUJGODgxMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAASABIAAAIwlI+pywcPm3mhWgkCsjBOvVkimElG9ZlCuYIY6TYs+6bmHDO4igfdD3GNhheV0VQAADs=)}.gantt_tree_icon.gantt_file{background-image:url(data:image/gif;base64,R0lGODlhEgASAJECAJeXl7Gvrf///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NzkxQzI4RjZDMDZEMTFFMTgwRjhBQURDQzI3NDU3QUEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NzkxQzI4RjdDMDZEMTFFMTgwRjhBQURDQzI3NDU3QUEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3OTFDMjhGNEMwNkQxMUUxODBGOEFBRENDMjc0NTdBQSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3OTFDMjhGNUMwNkQxMUUxODBGOEFBRENDMjc0NTdBQSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAASABIAAAIylI+pwN16QJiUQiFThRlJm3RRFYSlR5qXMKmXaMDuuMoyOi8n/e6xn8NMHETgh5RaKQsAOw==)}.gantt_grid_head_cell .gantt_sort{position:absolute;right:5px;top:8px;width:7px;height:13px;background-repeat:no-repeat;background-position:center center}.gantt_grid_head_cell .gantt_sort.gantt_asc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAANCAYAAABlyXS1AAAARUlEQVR42mNgQAL1/VP/M2ADIIntF2/9x1AAlrh0C47hCmA60DFYwX88gIFGwNDY5D8uDFbg7hvwHx2jmIBTAlkB0e4BAEjlaNtBWJPnAAAAAElFTkSuQmCC)}.gantt_grid_head_cell .gantt_sort.gantt_desc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAANCAYAAABlyXS1AAAARUlEQVR4nGNgQAKGxib/GbABkIS7b8B/DAUwCRiGK0CXwFBAb1DfP/U/LszwHwi2X7qFgUEArBtdAVwCBmAKMCSQFSDzAWXXaOHsXeqkAAAAAElFTkSuQmCC)}.gantt_inserted,.gantt_updated{font-weight:700}.gantt_deleted{text-decoration:line-through}.gantt_invalid{background-color:FFE0E0}.gantt_error{color:red}.dhtmlx_message_area{position:fixed;right:5px;width:250px;z-index:1000}.dhtmlx-info{min-width:120px;padding:4px 4px 4px 20px;font-family:Arial;z-index:10000;margin:5px;margin-bottom:10px;-webkit-transition:all .5s ease;-moz-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease}.dhtmlx-info.hidden{height:0;padding:0;border-width:0;margin:0;overflow:hidden}.dhtmlx_modal_box{overflow:hidden;display:inline-block;min-width:250px;width:250px;text-align:center;position:fixed;z-index:20000;box-shadow:3px 3px 3px rgba(0,0,0,.07);font-family:Arial;border-radius:6px;border:1px solid #bababa;background:#fff}.dhtmlx_popup_title{border-top-left-radius:6px;border-top-right-radius:6px;border-width:0}.dhtmlx_button,.dhtmlx_popup_button{border:1px solid #bababa;height:30px;line-height:30px;display:inline-block;margin:0 5px;border-radius:4px;background:#fff}.dhtmlx-info,.dhtmlx_popup_button,.dhtmlx_button{user-select:none;-webkit-user-select:none;-moz-user-select:-moz-none;cursor:pointer}.dhtmlx_popup_text{overflow:hidden}.dhtmlx_popup_controls{border-radius:6px;padding:10px}.dhtmlx_popup_button{min-width:100px}div.dhx_modal_cover{background-color:#000;cursor:default;filter:alpha(opacity=20);opacity:.2;position:fixed;z-index:19999;left:0;top:0;width:100%;height:100%;border:0;zoom:1}.dhtmlx-info img,.dhtmlx_modal_box img{float:left;margin-right:20px}.dhtmlx-alert-error,.dhtmlx-confirm-error{border:1px solid red}.dhtmlx_button input,.dhtmlx_popup_button div{border-radius:4px;font-size:14px;-moz-box-sizing:content-box;box-sizing:content-box;padding:0;margin:0;vertical-align:top}.dhtmlx_popup_title{color:#fff;text-shadow:1px 1px #000;height:40px;line-height:40px;font-size:20px}.dhtmlx_popup_text{margin:15px 15px 5px;font-size:14px;color:#000;min-height:30px;border-radius:6px}.dhtmlx-info,.dhtmlx-error{font-size:14px;color:#000;box-shadow:3px 3px 3px rgba(0,0,0,.07);padding:0;background-color:#FFF;border-radius:3px;border:1px solid #fff}.dhtmlx-info div{padding:5px 10px;background-color:#fff;border-radius:3px;border:1px solid #bababa}.dhtmlx-error{background-color:#d81b1b;border:1px solid #ff3c3c;box-shadow:3px 3px 3px rgba(0,0,0,.07)}.dhtmlx-error div{background-color:#d81b1b;border:1px solid #940000;color:#FFF}.gantt_grid div,.gantt_data_area div{-ms-touch-action:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}.gantt_data_area{position:relative;overflow-x:hidden;overflow-y:hidden;-moz-user-select:-moz-none;-webkit-user-select:none;-user-select:none}.gantt_links_area{position:absolute;left:0;top:0}.gantt_task_content,.gantt_task_progress,.gantt_side_content{line-height:inherit;overflow:hidden;height:100%}.gantt_task_content{font-size:13px;color:#fff;position:absolute;white-space:nowrap;text-align:center}.gantt_task_progress{text-align:center;z-index:0;background:#04a4f0;border-right:1px solid #1cb3fb;box-shadow:-1px 0 0 1px #04a1eb inset}.gantt_task_line{-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;position:absolute;-moz-box-sizing:border-box;box-sizing:border-box;background-color:#17b2fb;border:1px solid #049ae1;-webkit-user-select:none;-moz-user-select:none;-moz-user-select:-moz-none}.gantt_task_line.gantt_drag_move div{cursor:move}.gantt_side_content{position:absolute;white-space:nowrap;color:#6e6e6e;bottom:7px;font-size:11px;font-size:12px}.gantt_side_content.gantt_left{right:100%;padding-right:15px}.gantt_side_content.gantt_right{left:100%;padding-left:15px}.gantt_side_content.gantt_link_crossing{bottom:8.75px}.gantt_task_link .gantt_line_wrapper,.gantt_link_arrow{position:absolute;cursor:pointer}.gantt_line_wrapper div{background-color:#ffa011}.gantt_task_link:hover .gantt_line_wrapper div{box-shadow:0 0 5px 0 #ffa011}.gantt_task_link div.gantt_link_arrow{background-color:transparent;border-style:solid;width:0;height:0}.gantt_link_control{position:absolute;width:13px;top:0}.gantt_link_control div{display:none;cursor:pointer;box-sizing:border-box;position:relative;top:50%;margin-top:-7.5px;vertical-align:middle;border:1px solid #929292;-webkit-border-radius:6.5px;-moz-border-radius:6.5px;border-radius:6.5px;height:13px;width:13px;background-color:#f0f0f0}.gantt_link_control div:hover{background-color:#fff}.gantt_link_control.task_left{left:-13px}.gantt_link_control.task_right{right:-13px}.gantt_task_line.gantt_selected .gantt_link_control div,.gantt_task_line:hover .gantt_link_control div{display:block}.gantt_link_target .gantt_link_control div{display:block}.gantt_link_source,.gantt_link_target{box-shadow:0 0 3px #04a4f0}.gantt_link_target.link_start_allow,.gantt_link_target.link_finish_allow{box-shadow:0 0 3px #ffbf5e}.gantt_link_target.link_start_deny,.gantt_link_target.link_finish_deny{box-shadow:0 0 3px #e87e7b}.link_start_allow .gantt_link_control.task_left div,.link_finish_allow .gantt_link_control.task_right div{background-color:#ffbf5e;border-color:#ffa011}.link_start_deny .gantt_link_control.task_left div,.link_finish_deny .gantt_link_control.task_right div{background-color:#e87e7b;border-color:#dd3e3a}.gantt_link_arrow_right{border-width:2.5px 0 2.5px 5px;border-color:transparent transparent transparent #ffa011;margin-top:-1px}.gantt_link_arrow_left{border-width:2.5px 5px 2.5px 0;margin-top:-1px;border-color:transparent #ffa011 transparent transparent}.gantt_link_arrow_top{border-width:0 2.5px 5px;border-color:transparent transparent #ffa011}.gantt_link_arrow_down{border-width:2.5px 5px 0 2.5px;border-color:#ffa011 transparent transparent}.gantt_task_drag,.gantt_task_progress_drag{cursor:w-resize;height:100%;display:none;position:absolute}.gantt_task_line.gantt_selected .gantt_task_progress_drag,.gantt_task_line:hover .gantt_task_progress_drag,.gantt_task_line.gantt_selected .gantt_task_drag,.gantt_task_line:hover .gantt_task_drag{display:block}.gantt_task_drag{width:6px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAACCAYAAAB7Xa1eAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYDDjkw3UJvAwAAABRJREFUCNdj/P//PwM2wASl/6PTAKrrBf4+lD8LAAAAAElFTkSuQmCC);z-index:1;top:0}.gantt_task_drag.task_left{left:0}.gantt_task_drag.task_right{right:0}.gantt_task_progress_drag{height:8px;width:8px;bottom:-4px;margin-left:-4px;background-position:bottom;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAALCAYAAAB24g05AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MkY3Rjk0RUVDMkYzMTFFMkI1OThEQTA3ODU0OTkzMEEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MkY3Rjk0RUZDMkYzMTFFMkI1OThEQTA3ODU0OTkzMEEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyRjdGOTRFQ0MyRjMxMUUyQjU5OERBMDc4NTQ5OTMwQSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyRjdGOTRFREMyRjMxMUUyQjU5OERBMDc4NTQ5OTMwQSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PobPBzIAAADkSURBVHjaYpk2bRoDDsAExL1QdjEQ/8OmiAWHZk4gXqymqhQM4ty6fU8OSMUA8XdiDBAB4k0a6iqWRga6EKcwMQXduHlnL5DpB8Rv0J2JDFSA+JiOtgZcMwiA2CAxkBxUDVYDLEAKgIpV9XQ0MZwFEgPJAZnHoWpRDAgC4n2W5saiQKfjClQGkBxQDciL+6B6wAbkA/EqJwdrTkUFOQZCAKQGpBbIXA3SCzJggo+XK7OEuBgDsQCkFqgHrBfsBT5eHgZSAUwP2IBfv36TbABMDygdtK1Zv6UESLORaAbIhG6AAAMAKN8wE24DXWcAAAAASUVORK5CYII=);background-repeat:no-repeat;z-index:2}.gantt_link_tooltip{box-shadow:3px 3px 3px #888;background-color:#fff;border-left:1px dotted #cecece;border-top:1px dotted #cecece;font-family:Tahoma;font-size:8pt;color:#444;padding:6px;line-height:20px}.gantt_link_direction{height:0;border:0 none #ffa011;border-bottom-style:dashed;border-bottom-width:2px;transform-origin:0 0;-ms-transform-origin:0 0;-webkit-transform-origin:0 0;z-index:2;margin-left:1px;position:absolute}.gantt_grid_data .gantt_row.gantt_selected,.gantt_grid_data .gantt_row.odd.gantt_selected{background-color:#fff3a1;border-top-color:#fff087}.gantt_task_row.gantt_selected{background-color:#fff3a1}.gantt_task_row.gantt_selected .gantt_task_cell{border-right-color:#ffec6e}.gantt_task_line.gantt_selected{box-shadow:0 0 5px #04a4f0}.gantt_task_line.gantt_project.gantt_selected{box-shadow:0 0 5px #46ad51}.dhx_unselectable,.dhx_unselectable div{-webkit-user-select:none;-moz-user-select:none;-moz-user-select:-moz-none}.dhx_cal_light{-webkit-tap-highlight-color:transparent;border-radius:6px;font-family:Arial;font-size:13px;border:1px solid #bababa;color:#494949;font-size:12px;position:absolute;z-index:10001;width:550px;height:250px;box-shadow:3px 3px 3px rgba(0,0,0,.07)}.dhx_cal_light_wide{width:650px}.dhx_cal_light select{font-family:Arial;border:1px solid #bababa;font-size:13px;padding:2px;margin:0}.dhx_cal_ltitle{padding:7px 10px;overflow:hidden;white-space:nowrap;-webkit-border-top-left-radius:6px;-webkit-border-bottom-left-radius:0;-webkit-border-top-right-radius:6px;-webkit-border-bottom-right-radius:0;-moz-border-radius-topleft:6px;-moz-border-radius-bottomleft:0;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:0;border-top-left-radius:6px;border-bottom-left-radius:0;border-top-right-radius:6px;border-bottom-right-radius:0}.dhx_cal_ltitle span{white-space:nowrap}.dhx_cal_lsection{color:#727272;font-weight:700;padding:12px 0 5px 10px}.dhx_cal_lsection .dhx_fullday{float:right;margin-right:5px;font-size:12px;font-weight:400;line-height:20px;vertical-align:top;cursor:pointer}.dhx_cal_lsection{font-size:13px}.dhx_cal_ltext{padding:2px 10px;overflow:hidden}.dhx_cal_ltext textarea{overflow:auto;font-family:Arial;font-size:13px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #bababa;height:100%;width:100%;outline:0!important;resize:none}.dhx_time{font-weight:700}.dhx_cal_light .dhx_title{padding-left:10px}.dhx_cal_larea{border:1px solid #bababa;border-left:0;border-right:0;background-color:#fff;overflow:hidden;height:1px}.dhx_btn_set{margin:10px 7px 5px 10px;padding:5px 15px 5px 10px;float:left;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border-width:0;border-color:#bababa;border-style:solid;height:32px;font-weight:700;background:#fff;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;cursor:pointer}.dhx_btn_set div{float:left;font-size:13px;height:22px;line-height:22px;background-repeat:no-repeat;vertical-align:middle}.dhx_save_btn{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABEAAAAPCAYAAAACsSQRAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QTBBQUI0RDhDRUJEMTFFMjhFM0ZENTlERUM1OEQzRTYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QTBBQUI0RDlDRUJEMTFFMjhFM0ZENTlERUM1OEQzRTYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpBMEFBQjRENkNFQkQxMUUyOEUzRkQ1OURFQzU4RDNFNiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpBMEFBQjREN0NFQkQxMUUyOEUzRkQ1OURFQzU4RDNFNiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PjdPZV0AAACtSURBVHjanJPbDYUgEESvtwJKoQRLoRNL0RK0ArUCS9AOpAMclDVkw0s2mQ9XcpwJY2OM+VWMgDQ9/CsAEtoh9W6skw+S0Akd5hl1J6kADJBwADs9j9MmIszQ5GJo793qf2nwLUYc0E7xONbaBmloZKAsgCCLW0p2qCsBEEQ6F33gcBbg304I1JYA+BWHQFlAqCcclAXEykaguQSQaiyBVEmbm8RfLFgzo3MJMACZSGxiH8QaMAAAAABJRU5ErkJggg==);margin-top:2px;width:21px}.dhx_cancel_btn{margin-top:2px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MDkzMDA3MzlDMzA0MTFFMjg2QTVFMzFEQzgwRkJERDYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MDkzMDA3M0FDMzA0MTFFMjg2QTVFMzFEQzgwRkJERDYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowOTMwMDczN0MzMDQxMUUyODZBNUUzMURDODBGQkRENiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowOTMwMDczOEMzMDQxMUUyODZBNUUzMURDODBGQkRENiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PmYuYOUAAAEdSURBVHjaYvz//z8DJYAFXWDlypU8QKoIiD2A2AwqfAqIdwBxX3h4+Bdk9YzILgBqtgdS84FYEYeF94E4EWjIQZgAE5LmQCB1AKoZZKMPEAtAMYh9GSp3AKjWD8UFQAEhIPshEIOc3wHENUBb/qJ57SyQMoJyPwKxElDNO1gYFEE17wMKVmIJlzNQzeegrjaA6qmBecEbSvfh0GwMxGeBhoPoemQ9MAO0kEIbl2YTqPAFKK2IbMB3AjabYIkRZmQD7kNpMyI0G0PpO8gGbIUFJj7NQDk2INWIrIcJKfBAKcwJqvkcDs0TgFgXGo19KCkRmpDWQdWDEk0NUoCBoq0FqhkE/IEWbKJKUmZEz43QzFSKIzN1481M5ACAAAMAlfl/lCwRpagAAAAASUVORK5CYII=);width:20px}.dhx_delete_btn{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAYAAAAmlE46AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QzQzRjMxM0NDRUJEMTFFMjlCNDZDQzhFQUM0ODRCMEIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QzQzRjMxM0RDRUJEMTFFMjlCNDZDQzhFQUM0ODRCMEIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpDNDNGMzEzQUNFQkQxMUUyOUI0NkNDOEVBQzQ4NEIwQiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpDNDNGMzEzQkNFQkQxMUUyOUI0NkNDOEVBQzQ4NEIwQiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PoU+5FoAAACMSURBVHja7JPdDYAgDIQLE+gmjuAIbOIKjMRIbAAb1BZLUozgz7OXfIbWKxeCAiKCwmNfXnsNP5S4mIkMrSYiEaY2LOFloO6QVF1JamPGc2IvpaeSbqXY4LkOLwU6OTyvgzBasxwnBnivYOGj/sEXg27gbd/JpWZiOf0pV7Anlq9UGqs07hTFC7sAAwCbSNs8SyqFMAAAAABJRU5ErkJggg==);margin-top:2px;width:20px}.dhx_cal_cover{width:100%;height:100%;position:absolute;z-index:10000;top:0;left:0;background-color:#000;opacity:.1;filter:alpha(opacity=10)}.dhx_custom_button{padding:0 3px;font-family:Arial;font-size:13px;font-weight:400;margin-right:5px;margin-top:0;cursor:pointer}.dhx_custom_button div{cursor:pointer;float:left;height:21px;line-height:21px;vertical-align:middle}.dhx_cal_light_wide{width:580px;padding:2px 4px}.dhx_cal_light_wide .dhx_cal_larea{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #bababa}.dhx_cal_light_wide .dhx_cal_lsection{border:0;float:left;text-align:right;width:80px;height:20px;padding:5px 10px 0 0}.dhx_cal_light_wide .dhx_wrap_section{position:relative;padding:10px 0;overflow:hidden;border-bottom:1px solid #ebebeb}.dhx_cal_light_wide .dhx_section_time{overflow:hidden;padding-top:2px!important;padding-right:0;height:20px!important}.dhx_cal_light_wide .dhx_cal_ltext{padding-right:0}.dhx_cal_light_wide .dhx_cal_larea{padding:0 10px;width:100%}.dhx_cal_light_wide .dhx_section_time{background:transparent}.dhx_cal_light_wide .dhx_cal_checkbox label{padding-left:0}.dhx_cal_light_wide .dhx_cal_lsection .dhx_fullday{float:none;margin-right:0;font-weight:700;cursor:pointer}.dhx_cal_light_wide .dhx_custom_button{position:absolute;top:0;right:0;margin-top:2px}.dhx_cal_light_wide .dhx_repeat_right{margin-right:55px}.dhx_cal_light_wide.dhx_cal_light_full{width:738px}.dhx_cal_wide_checkbox input{margin-top:8px;margin-left:14px}.dhx_cal_light input{font-size:13px}.dhx_custom_button{float:right;height:21px;width:90px;-webkit-border-radius:4px;-moz-border-radius:4px;-ms-border-radius:4px;-o-border-radius:4px;border-radius:4px}.dhx_section_time{background-color:#fff;white-space:nowrap;padding:2px 10px 5px;padding-top:2px!important}.dhx_section_time .dhx_time_selects{float:left;height:25px}.dhx_section_time .dhx_time_selects select{height:23px;padding:2px;border:1px solid #bababa}.dhx_gantt_duration{width:100px;height:23px;float:left;white-space:nowrap;margin-left:20px;line-height:23px}.dhx_gantt_duration .dhx_gantt_duration_value,.dhx_gantt_duration .dhx_gantt_duration_dec,.dhx_gantt_duration .dhx_gantt_duration_inc{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;vertical-align:top;height:100%;border:1px solid #bababa}.dhx_gantt_duration .dhx_gantt_duration_value{width:40px;padding:3px 4px;border-left-width:0;border-right-width:0}.dhx_gantt_duration .dhx_gantt_duration_dec,.dhx_gantt_duration .dhx_gantt_duration_inc{width:20px;padding:1px;padding-bottom:3px;background:#fff}.dhx_gantt_duration .dhx_gantt_duration_dec{-moz-border-top-left-radius:4px;-moz-border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;border-top-left-radius:4px;border-bottom-left-radius:4px}.dhx_gantt_duration .dhx_gantt_duration_inc{margin-right:4px;-moz-border-top-right-radius:4px;-moz-border-bottom-right-radius:4px;-webkit-border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:4px}.dhx_cal_quick_info{border:1px solid #bababa;border-radius:6px;position:absolute;z-index:300;box-shadow:3px 3px 3px rgba(0,0,0,.07);background-color:#fff;width:300px;transition:left .5s ease,right .5s;-moz-transition:left .5s ease,right .5s;-webkit-transition:left .5s ease,right .5s;-o-transition:left .5s ease,right .5s}.dhx_no_animate{transition:none;-moz-transition:none;-webkit-transition:none;-o-transition:none}.dhx_cal_quick_info.dhx_qi_left .dhx_qi_big_icon{float:right}.dhx_cal_qi_title{-webkit-border-top-left-radius:6px;-webkit-border-bottom-left-radius:0;-webkit-border-top-right-radius:6px;-webkit-border-bottom-right-radius:0;-moz-border-radius-topleft:6px;-moz-border-radius-bottomleft:0;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:0;border-top-left-radius:6px;border-bottom-left-radius:0;border-top-right-radius:6px;border-bottom-right-radius:0;padding:5px 0 8px 12px;color:#494949;background-color:#dbdbdb;border-bottom:1px solid #bababa}.dhx_cal_qi_tdate{font-size:14px;font-weight:700}.dhx_cal_qi_tcontent{font-size:13px}.dhx_cal_qi_content{padding:16px 8px;font-size:13px;color:#494949;overflow:hidden}.dhx_cal_qi_controls{-webkit-border-top-left-radius:0;-webkit-border-bottom-left-radius:6px;-webkit-border-top-right-radius:0;-webkit-border-bottom-right-radius:6px;-moz-border-radius-topleft:0;-moz-border-radius-bottomleft:6px;-moz-border-radius-topright:0;-moz-border-radius-bottomright:6px;border-top-left-radius:0;border-bottom-left-radius:6px;border-top-right-radius:0;border-bottom-right-radius:6px;padding-left:7px}.dhx_cal_qi_controls .dhx_menu_icon{margin-top:6px;background-repeat:no-repeat}.dhx_cal_qi_controls .dhx_menu_icon.icon_edit{width:20px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH3QYFCjI5ZQj5bAAAAFNJREFUOMvt0zEOACAIA0DkwTymH8bJTRTKZGJXyaWEKPKTCQAH4Ls37cItcDUzsxHNDLZNhCq7Gt1wh9ErV7EjyGAhyGLphlnsClWuS32rn0czAV+vNGrM/LBtAAAAAElFTkSuQmCC)}.dhx_cal_qi_controls .dhx_menu_icon.icon_delete{width:20px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAYAAAAmlE46AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QzQzRjMxM0NDRUJEMTFFMjlCNDZDQzhFQUM0ODRCMEIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QzQzRjMxM0RDRUJEMTFFMjlCNDZDQzhFQUM0ODRCMEIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpDNDNGMzEzQUNFQkQxMUUyOUI0NkNDOEVBQzQ4NEIwQiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpDNDNGMzEzQkNFQkQxMUUyOUI0NkNDOEVBQzQ4NEIwQiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PoU+5FoAAACMSURBVHja7JPdDYAgDIQLE+gmjuAIbOIKjMRIbAAb1BZLUozgz7OXfIbWKxeCAiKCwmNfXnsNP5S4mIkMrSYiEaY2LOFloO6QVF1JamPGc2IvpaeSbqXY4LkOLwU6OTyvgzBasxwnBnivYOGj/sEXg27gbd/JpWZiOf0pV7Anlq9UGqs07hTFC7sAAwCbSNs8SyqFMAAAAABJRU5ErkJggg==)}.dhx_qi_big_icon{font-size:13px;border-radius:4px;font-weight:700;background:#fff;margin:5px 9px 8px 0;min-width:60px;line-height:32px;vertical-align:middle;padding:0 10px 0 5px;cursor:pointer;border:1px solid #bababa}.dhx_cal_qi_controls div{float:left;height:32px;text-align:center;line-height:32px}.gantt_tooltip{box-shadow:3px 3px 3px rgba(0,0,0,.07);background-color:#fff;border-left:1px solid rgba(0,0,0,.07);border-top:1px solid rgba(0,0,0,.07);font-family:Arial;font-size:8pt;color:#494949;padding:10px;position:absolute;z-index:500}.gantt_noselect{-moz-user-select:-moz-none;-webkit-user-select:none;-user-select:none}.gantt_drag_marker{position:absolute;font-family:Arial;font-size:13px}.gantt_drag_marker .gantt_tree_indent,.gantt_drag_marker .gantt_tree_icon.gantt_blank,.gantt_drag_marker .gantt_tree_icon.gantt_open,.gantt_drag_marker .gantt_tree_icon.gantt_close{display:none}.gantt_drag_marker,.gantt_drag_marker .gantt_row.odd{background-color:#fff}.gantt_drag_marker .gantt_row{border-left:1px solid #d2d2d2;border-top:1px solid #d2d2d2}.gantt_drag_marker .gantt_cell{border-color:#d2d2d2}.gantt_row.gantt_over,.gantt_task_row.gantt_over{background-color:#0070fe}.gantt_row.gantt_transparent .gantt_cell{opacity:.7}.gantt_task_row.gantt_transparent{background-color:#dbf3fe}.dhtmlx_popup_button.dhtmlx_delete_button{background:#46ad51;text-shadow:0 -1px 0 #307738;color:#fff;font-weight:700}
\ No newline at end of file
/*
This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
to use it in non-GPL project. Please contact sales@dhtmlx.com for details
*/
.chartHeaderBg{background-color:#f4f2ea}.gridHoverStyle{background-color:#ffebbc!important}.gantt_grid_scale .gantt_grid_head_cell{border-top:0!important;border-right:0!important}.gantt_grid_data .gantt_cell{border-right:0}.gridSelection{background-color:#ffebbc!important}.timelineSelection{background-color:#ffebbc!important}.gantt_task_line .gantt_task_progress_drag{bottom:-4px;height:16px;margin-left:-8px;width:16px}.gantt_task .gantt_task_scale .gantt_scale_cell{border-right:1px solid #cac8bd}.gantt_row.gantt_project .gantt_cell,.gantt_row.odd.gantt_project .gantt_cell{background-color:#edf3ff}.gantt_task_row.gantt_project .gantt_task_cell,.gantt_task_row.odd.gantt_project .gantt_task_cell{background-color:#f5f8ff}.gantt_task_line.gantt_project{background-color:#c7d8f7;border:1px solid #7ba3ed}.gantt_task_line.gantt_project .gantt_task_progress{background-color:#9ab9f1}.dhx_cal_light .dhx_cal_ltitle{padding:7px 10px}.dhx_cal_light .dhx_cal_ltext textarea{border:1px solid #d8d6ce}.dhx_cal_light .dhx_cal_larea{border-color:#d8d6ce!important;background-color:#fcfaf3}.dhx_cal_light .dhx_cal_larea .dhx_section_time{background-color:#fcfaf3}.buttonBg{background:#e0ded7}.dhx_cal_light .dhx_btn_set{height:27px;margin:5px 10px;padding:0 15px 0 10px}.dhx_cal_light .dhx_btn_set div{height:25px;margin-top:0;background-position:center center;line-height:25px}.dhx_btn_set.dhx_save_btn_set{border:1px solid #98d27e;background:#a7d991}.dhx_btn_set.dhx_cancel_btn_set{background:#e0ded7;border:1px solid #cac8bd}.dhx_btn_set.dhx_delete_btn_set{border:1px solid #ffad54;background:#ffb96d}.dhx_cal_light_wide{padding:0!important}.dhx_cal_light_wide .dhx_cal_larea{border-left:0!important;border-right:0!important}.dhx_cal_light_wide .dhx_cal_larea .dhx_cal_lsection{width:90px}.dhx_cal_light_wide .dhx_btn_set{margin:7px 10px}.dhtmlx_popup_button.dhtmlx_ok_button{border:1px solid #98d27e;background:#a7d991}.gantt_data_area .dhx_cal_quick_info{background-color:#f4f2ea}.gantt_data_area .dhx_cal_qi_content{background:#fcfaf3;border-bottom:1px solid #cac8bd}.dhx_qi_big_icon.icon_delete{border-color:#ffad54;background:#ffb96d}.gantt_container{font-family:Verdana;font-size:11px;border:1px solid #cac8bd;position:relative;white-space:nowrap}.gantt_grid{border-right:1px solid #cac8bd}.gantt_task_scroll{overflow-x:scroll}.gantt_task{position:relative}.gantt_task,.gantt_grid{overflow-x:hidden;overflow-y:hidden;display:inline-block;vertical-align:top}.gantt_grid_scale,.gantt_task_scale{color:#3f3f3f;font-size:8pt;border-bottom:1px solid #cac8bd}.gantt_grid_scale{background-color:#f4f2ea}.gantt_task_scale{background-color:#f4f2ea}.gantt_scale_line{box-sizing:border-box;-moz-box-sizing:border-box;border-top:1px solid #cac8bd}.gantt_scale_line:first-child{border-top:0}.gantt_grid_head_cell{display:inline-block;vertical-align:top;border-right:1px solid #cac8bd;text-align:center;position:relative;cursor:default;height:100%;box-sizing:border-box;-moz-box-sizing:border-box;line-height:28px;-moz-user-select:-moz-none;-webkit-user-select:none;-user-select:none}.gantt_scale_line{clear:both}.gantt_grid_data{width:100%;overflow:hidden}.gantt_row{position:relative;-webkit-user-select:none;-moz-user-select:none;-moz-user-select:-moz-none}.gantt_add,.gantt_grid_head_add{width:100%;height:100%;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MDAzOTZDREFDN0FGMTFFMkE1MDhCNkFCRDk3RkY4NTkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MDAzOTZDREJDN0FGMTFFMkE1MDhCNkFCRDk3RkY4NTkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowMDM5NkNEOEM3QUYxMUUyQTUwOEI2QUJEOTdGRjg1OSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowMDM5NkNEOUM3QUYxMUUyQTUwOEI2QUJEOTdGRjg1OSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PnzqpNoAAAEFSURBVHjapJPPDsFAEMbHZqXSi1ZC3Dg7O9bbeA7v4czZE4hLSRwkbiI4cCAN4Uyqs/pPs1vbrTDJHL7Mb77sbr8WeuMu/FOEMQaStplYdgYHFBnKjK1atZqI8+ViZZ2AMmS5x1QxFNHLNVAx1EPMNVAxBBFtvxnfuq4LUKDTTLwXXsFqNprKE1RMM2y+9oe9FV3Bw5++/3svfMRWpS0MV7fFx0Ka2e62kQEhBEbzoTA0DAPqtXqinbMD643IFGkR3KcL1Cib0yA4/DBY4A2u15ug45oFHD05x44sO+kc+FwhI0j4RZBQlUT5kH+0JS4VBpKfqaSVYDDpJ1rz9eNxlxq8BBgAcMHBu6BPeX8AAAAASUVORK5CYII=);background-position:center center;background-repeat:no-repeat;cursor:pointer;position:relative;-moz-opacity:.3;opacity:.3}.gantt_grid_head_cell.gantt_grid_head_add{-moz-opacity:.6;opacity:.6;top:0}.gantt_grid_head_cell.gantt_grid_head_add:hover{-moz-opacity:1;opacity:1}.gantt_grid_data .gantt_row:hover,.gantt_grid_data .gantt_row.odd:hover{background-color:#ffebbc!important}.gantt_grid_data .gantt_row:hover .gantt_add,.gantt_grid_data .gantt_row.odd:hover .gantt_add{-moz-opacity:1;opacity:1}.gantt_task_row,.gantt_row{border-bottom:1px solid #eae9e5}.gantt_row,.gantt_task_row{background-color:#fff}.gantt_row.odd,.gantt_task_row.odd{background-color:#fff}.gantt_row,.gantt_cell,.gantt_task_row,.gantt_task_cell,.gantt_grid_head_cell,.gantt_scale_cell{box-sizing:border-box;-moz-box-sizing:border-box}.gantt_grid_head_cell,.gantt_scale_cell{line-height:inherit}.gantt_cell{display:inline-block;vertical-align:top;border-right:1px solid #eae9e5;padding-left:6px;padding-right:6px;height:100%;overflow:hidden;white-space:nowrap;font-size:8pt}.gantt_grid_scale .gantt_last_cell,.gantt_grid_data .gantt_last_cell,.gantt_task_scale .gantt_last_cell,.gantt_task_bg .gantt_last_cell{border-right-width:0}.gantt_task_bg{overflow:hidden}.gantt_scale_cell{display:inline-block;white-space:nowrap;overflow:hidden;border-right:1px solid #cac8bd;text-align:center;height:100%}.gantt_task_cell{display:inline-block;height:100%;border-right:1px solid #eae9e5}.gantt_ver_scroll{width:0;background-color:transparent;height:1px;overflow-x:hidden;overflow-y:scroll;display:none;position:absolute;right:0}.gantt_ver_scroll>div{width:1px;height:1px}.gantt_hor_scroll{height:0;background-color:transparent;width:100%;clear:both;overflow-x:scroll;overflow-y:hidden;display:none}.gantt_hor_scroll>div{width:5000px;height:1px}.gantt_tree_indent{width:15px;height:100%;display:inline-block}.gantt_tree_content,.gantt_tree_icon{vertical-align:top}.gantt_tree_icon{width:28px;height:100%;display:inline-block;background-repeat:no-repeat;background-position:center center}.gantt_tree_content{height:100%;display:inline-block}.gantt_tree_icon.gantt_open{background-image:url(data:image/gif;base64,R0lGODlhEgASAJEAAP///4SEhAAAAP///yH5BAUUAAMALAAAAAASABIAAAIqnI+py+0Powq01haA3iDgLWwek2mhNi6ZwLLdZ4owcL4kJ5OWJfX+DykAADs=);width:18px;cursor:pointer}.gantt_tree_icon.gantt_close{background-image:url(data:image/gif;base64,R0lGODlhEgASAJEAAP///4SEhAAAAP///yH5BAUUAAMALAAAAAASABIAAAImnI+py+0Powq01haA3iBgvnlMBnafgKLmWK4LCYpvK0+WJeX6DhUAOw==);width:18px;cursor:pointer}.gantt_tree_icon.gantt_blank{width:18px}.gantt_tree_icon.gantt_folder_open{background-image:url(data:image/gif;base64,R0lGODlhEgASAMQfAPXMWPK9N/fipOzLbPrVaP/89frqwv3tuvvprfLES+7PdP7xyP7z0vPAQfvtyMuaIv/32/735OSqFv3de+7ReP7lmf7gh/nv0v/67unFXfb29vK/PenFSaGEPfLnyv///yH5BAEAAB8ALAAAAAASABIAAAWS4Cc+ZGk+YvqZRVtgGiag6sNBThlFWnTQqQcE4sqRFj/VashsYR6HZI0JWSAjDwQCOGIgERULg/EQzJRQsGVCYHTK51qYTQDkFPj4iA4AJHIUgQoGQA8EdX4NhHgKAxeFfQkJGwGLeI6Fkg2UlQ8Dn5hBCZuchKChIxKkGw05Ga8DHlwkqg2tJ1xBtDlKvTUkviEAOw==)}.gantt_tree_icon.gantt_folder_closed{background-image:url(data:image/gif;base64,R0lGODlhEgASAMQfAPvjpP/24fPHVvnUdvzotdSiKv/23vvrw/LCS/C+RfnelvPMaf/88/nbi/rsyerYq/TLYv7y1v7wzeCsLO+8P86dJfvv0MuaIsydKPPOc+SqGKGEPe/AS+zVneWwLf///yH5BAEAAB8ALAAAAAASABIAAAWZ4CdeZGleYvpdA+O+zNUBqHoZOB4EceHUqVtuF+n9VCtdQCI5BC6FA3BkCEQkBALAACVMV9eDVhGJQGnIixigaAwiG6jie2G7BxDLpVJozG13EAICPxUeGRleQQMLgggJUhiHC3pBggKPFJETGZRAF5gJCRSaFxgTC55BCByioz+nEKojGgmtHBwPJAUCELo2J8FfSMTFxsUhADs=)}.gantt_tree_icon.gantt_file{background-image:url(data:image/gif;base64,R0lGODlhEgASAMQVAPv7+2RkZPf39/r6+vj4+Pn5+fT09Pb29vX19fHx8fDw8Onp6fLy8u7u7uzs7PPz8+vr6+rq6uXl5ejo6O/v7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAABUALAAAAAASABIAAAV4YCWOZGkGaKqaVSAMQCwLQnAOj6ErzUTXpQDgQDgcGJTFDzgSEp4HRCKytDUBMKzAoIAsJACrSCjTGhINR5hELg8ECEZifS3HCgQEgj62xwZPAnwtbgMDBQU0g21Yh4iIi3aGkzBihH52lgEEmDIEli0qoigspSYhADs=)}.gantt_grid_head_cell .gantt_sort{position:absolute;right:5px;top:8px;width:7px;height:13px;background-repeat:no-repeat;background-position:center center}.gantt_grid_head_cell .gantt_sort.gantt_asc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAANCAYAAABlyXS1AAAARUlEQVR42mNgQAL1/VP/M2ADIIntF2/9x1AAlrh0C47hCmA60DFYwX88gIFGwNDY5D8uDFbg7hvwHx2jmIBTAlkB0e4BAEjlaNtBWJPnAAAAAElFTkSuQmCC)}.gantt_grid_head_cell .gantt_sort.gantt_desc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAANCAYAAABlyXS1AAAARUlEQVR4nGNgQAKGxib/GbABkIS7b8B/DAUwCRiGK0CXwFBAb1DfP/U/LszwHwi2X7qFgUEArBtdAVwCBmAKMCSQFSDzAWXXaOHsXeqkAAAAAElFTkSuQmCC)}.gantt_inserted,.gantt_updated{font-weight:700}.gantt_deleted{text-decoration:line-through}.gantt_invalid{background-color:FFE0E0}.gantt_error{color:red}.dhtmlx_message_area{position:fixed;right:5px;width:250px;z-index:1000}.dhtmlx-info{min-width:120px;padding:4px 4px 4px 20px;font-family:Verdana;z-index:10000;margin:5px;margin-bottom:10px;-webkit-transition:all .5s ease;-moz-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease}.dhtmlx-info.hidden{height:0;padding:0;border-width:0;margin:0;overflow:hidden}.dhtmlx_modal_box{overflow:hidden;display:inline-block;min-width:250px;width:250px;text-align:center;position:fixed;z-index:20000;box-shadow:3px 3px 3px rgba(0,0,0,.07);font-family:Verdana;border-radius:6px;border:1px solid #cac8bd;background:#fcfaf3}.dhtmlx_popup_title{border-top-left-radius:6px;border-top-right-radius:6px;border-width:0}.dhtmlx_button,.dhtmlx_popup_button{border:1px solid #cac8bd;height:25px;line-height:25px;display:inline-block;margin:0 5px;border-radius:4px;background:#e0ded7}.dhtmlx-info,.dhtmlx_popup_button,.dhtmlx_button{user-select:none;-webkit-user-select:none;-moz-user-select:-moz-none;cursor:pointer}.dhtmlx_popup_text{overflow:hidden}.dhtmlx_popup_controls{border-radius:6px;padding:10px}.dhtmlx_popup_button{min-width:100px}div.dhx_modal_cover{background-color:#000;cursor:default;filter:alpha(opacity=20);opacity:.2;position:fixed;z-index:19999;left:0;top:0;width:100%;height:100%;border:0;zoom:1}.dhtmlx-info img,.dhtmlx_modal_box img{float:left;margin-right:20px}.dhtmlx-alert-error,.dhtmlx-confirm-error{border:1px solid red}.dhtmlx_button input,.dhtmlx_popup_button div{border-radius:4px;font-size:14px;-moz-box-sizing:content-box;box-sizing:content-box;padding:0;margin:0;vertical-align:top}.dhtmlx_popup_title{color:#fff;text-shadow:1px 1px #000;height:40px;line-height:40px;font-size:20px}.dhtmlx_popup_text{margin:15px 15px 5px;font-size:14px;color:#000;min-height:30px;border-radius:6px}.dhtmlx-info,.dhtmlx-error{font-size:14px;color:#000;box-shadow:3px 3px 3px rgba(0,0,0,.07);padding:0;background-color:#FFF;border-radius:3px;border:1px solid #fff}.dhtmlx-info div{padding:5px 10px;background-color:#fff;border-radius:3px;border:1px solid #cac8bd}.dhtmlx-error{background-color:#d81b1b;border:1px solid #ff3c3c;box-shadow:3px 3px 3px rgba(0,0,0,.07)}.dhtmlx-error div{background-color:#d81b1b;border:1px solid #940000;color:#FFF}.gantt_grid div,.gantt_data_area div{-ms-touch-action:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}.gantt_data_area{position:relative;overflow-x:hidden;overflow-y:hidden;-moz-user-select:-moz-none;-webkit-user-select:none;-user-select:none}.gantt_links_area{position:absolute;left:0;top:0}.gantt_task_content,.gantt_task_progress,.gantt_side_content{line-height:inherit;overflow:hidden;height:100%}.gantt_task_content{font-size:11px;color:#3f3f3f;position:absolute;white-space:nowrap;text-align:center}.gantt_task_progress{text-align:center;z-index:0;background:#a7d991}.gantt_task_line{-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;position:absolute;-moz-box-sizing:border-box;box-sizing:border-box;background-color:#e1ffd4;border:1px solid #7fbc64;-webkit-user-select:none;-moz-user-select:none;-moz-user-select:-moz-none}.gantt_task_line.gantt_drag_move div{cursor:move}.gantt_side_content{position:absolute;white-space:nowrap;color:#6e6e6e;bottom:7px;font-size:11px;font-size:10px}.gantt_side_content.gantt_left{right:100%;padding-right:15px}.gantt_side_content.gantt_right{left:100%;padding-left:15px}.gantt_side_content.gantt_link_crossing{bottom:6.75px}.gantt_task_link .gantt_line_wrapper,.gantt_link_arrow{position:absolute;cursor:pointer}.gantt_line_wrapper div{background-color:#ffb96d}.gantt_task_link:hover .gantt_line_wrapper div{box-shadow:0 0 5px 0 #ffb96d}.gantt_task_link div.gantt_link_arrow{background-color:transparent;border-style:solid;width:0;height:0}.gantt_link_control{position:absolute;width:13px;top:0}.gantt_link_control div{display:none;cursor:pointer;box-sizing:border-box;position:relative;top:50%;margin-top:-7.5px;vertical-align:middle;border:1px solid #929292;-webkit-border-radius:6.5px;-moz-border-radius:6.5px;border-radius:6.5px;height:13px;width:13px;background-color:#f0f0f0}.gantt_link_control div:hover{background-color:#fff}.gantt_link_control.task_left{left:-13px}.gantt_link_control.task_right{right:-13px}.gantt_task_line.gantt_selected .gantt_link_control div,.gantt_task_line:hover .gantt_link_control div{display:block}.gantt_link_target .gantt_link_control div{display:block}.gantt_link_source,.gantt_link_target{box-shadow:0 0 3px #a7d991}.gantt_link_target.link_start_allow,.gantt_link_target.link_finish_allow{box-shadow:0 0 3px #ffdeba}.gantt_link_target.link_start_deny,.gantt_link_target.link_finish_deny{box-shadow:0 0 3px #e87e7b}.link_start_allow .gantt_link_control.task_left div,.link_finish_allow .gantt_link_control.task_right div{background-color:#ffdeba;border-color:#ffb96d}.link_start_deny .gantt_link_control.task_left div,.link_finish_deny .gantt_link_control.task_right div{background-color:#e87e7b;border-color:#dd3e3a}.gantt_link_arrow_right{border-width:4px 0 4px 6px;border-color:transparent transparent transparent #ffb96d;margin-top:-1px}.gantt_link_arrow_left{border-width:4px 6px 4px 0;margin-top:-1px;border-color:transparent #ffb96d transparent transparent}.gantt_link_arrow_top{border-width:0 4px 6px;border-color:transparent transparent #ffb96d}.gantt_link_arrow_down{border-width:4px 6px 0 4px;border-color:#ffb96d transparent transparent}.gantt_task_drag,.gantt_task_progress_drag{cursor:w-resize;height:100%;display:none;position:absolute}.gantt_task_line.gantt_selected .gantt_task_progress_drag,.gantt_task_line:hover .gantt_task_progress_drag,.gantt_task_line.gantt_selected .gantt_task_drag,.gantt_task_line:hover .gantt_task_drag{display:block}.gantt_task_drag{width:6px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAACCAYAAACUn8ZgAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6OTgyOEUzQzJDODQ3MTFFMjg3NDRCMUFBMTM1M0U1OTYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6OTgyOEUzQzNDODQ3MTFFMjg3NDRCMUFBMTM1M0U1OTYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo5ODI4RTNDMEM4NDcxMUUyODc0NEIxQUExMzUzRTU5NiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo5ODI4RTNDMUM4NDcxMUUyODc0NEIxQUExMzUzRTU5NiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pj3qIQcAAAAaSURBVHjaYvz//z8DLsAEIhoaGv5jowECDAASbgp+4xzYLgAAAABJRU5ErkJggg==);z-index:1;top:0}.gantt_task_drag.task_left{left:0}.gantt_task_drag.task_right{right:0}.gantt_task_progress_drag{height:8px;width:8px;bottom:-4px;margin-left:-4px;background-position:bottom;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAJCAYAAAAGuM1UAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6Q0JCMkQyRDhDOTFDMTFFMjg3RTFCNUEzNUQwRDMxNjEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6Q0JCMkQyRDlDOTFDMTFFMjg3RTFCNUEzNUQwRDMxNjEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpDQkIyRDJENkM5MUMxMUUyODdFMUI1QTM1RDBEMzE2MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpDQkIyRDJEN0M5MUMxMUUyODdFMUI1QTM1RDBEMzE2MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Ppy3ICwAAADsSURBVHjafFBbaoNQEB1vghtIPlJX4Z/iX/cSsoZQGl80JZvIAvxVf1yNX6EFH+ADK8Y6nblQaGzagXNnmHPODHMVRIR5+L7/Sqm2bfv0i2TDT3ied4zjGBlU7+f8XPzCwrZtsSzLb9PTXQMRfhRFWFUVdl0nURQFco+45xsDNdwwDOVUNuR5LlHXNWZZhsyR5sDahRDC1XXdsSwLFCHgo+/hc5okhusVVFUFTdNgHMfHIAhQ0N2OaZowkbtpGhiG4Qa0RX6OYRicvCU9b2maPqzWa/grKtpE93D5zoZtkiRnyhv4Py6E3ZcAAwDb89Sl5rtPtAAAAABJRU5ErkJggg==);background-repeat:no-repeat;z-index:2}.gantt_link_tooltip{box-shadow:3px 3px 3px #888;background-color:#fff;border-left:1px dotted #cecece;border-top:1px dotted #cecece;font-family:Tahoma;font-size:8pt;color:#444;padding:6px;line-height:20px}.gantt_link_direction{height:0;border:0 none #ffb96d;border-bottom-style:dashed;border-bottom-width:2px;transform-origin:0 0;-ms-transform-origin:0 0;-webkit-transform-origin:0 0;z-index:2;margin-left:1px;position:absolute}.gantt_grid_data .gantt_row.gantt_selected,.gantt_grid_data .gantt_row.odd.gantt_selected{background-color:#ffebbc!important}.gantt_task_row.gantt_selected{background-color:#ffebbc!important}.gantt_task_row.gantt_selected .gantt_task_cell{border-right-color:#ffdc89}.gantt_task_line.gantt_selected{box-shadow:0 0 5px #a7d991}.gantt_task_line.gantt_project.gantt_selected{box-shadow:0 0 5px #9ab9f1}.dhx_unselectable,.dhx_unselectable div{-webkit-user-select:none;-moz-user-select:none;-moz-user-select:-moz-none}.dhx_cal_light{-webkit-tap-highlight-color:transparent;background:#f4f2ea;border-radius:6px;font-family:Verdana;font-size:11px;border:1px solid #cac8bd;color:#3f3f3f;font-size:8pt;position:absolute;z-index:10001;width:550px;height:250px;box-shadow:3px 3px 3px rgba(0,0,0,.07)}.dhx_cal_light_wide{width:650px}.dhx_cal_light select{font-family:Verdana;border:1px solid #cac8bd;font-size:11px;padding:2px;margin:0}.dhx_cal_ltitle{padding:7px 10px;overflow:hidden;white-space:nowrap;-webkit-border-top-left-radius:6px;-webkit-border-bottom-left-radius:0;-webkit-border-top-right-radius:6px;-webkit-border-bottom-right-radius:0;-moz-border-radius-topleft:6px;-moz-border-radius-bottomleft:0;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:0;border-top-left-radius:6px;border-bottom-left-radius:0;border-top-right-radius:6px;border-bottom-right-radius:0}.dhx_cal_ltitle span{white-space:nowrap}.dhx_cal_lsection{color:#727272;font-weight:700;padding:12px 0 5px 10px}.dhx_cal_lsection .dhx_fullday{float:right;margin-right:5px;font-size:12px;font-weight:400;line-height:20px;vertical-align:top;cursor:pointer}.dhx_cal_lsection{font-size:13px}.dhx_cal_ltext{padding:2px 10px;overflow:hidden}.dhx_cal_ltext textarea{overflow:auto;font-family:Verdana;font-size:11px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #cac8bd;height:100%;width:100%;outline:0!important;resize:none}.dhx_time{font-weight:700}.dhx_cal_light .dhx_title{padding-left:10px}.dhx_cal_larea{border:1px solid #cac8bd;border-left:0;border-right:0;background-color:#fff;overflow:hidden;height:1px}.dhx_btn_set{margin:10px 7px 5px 10px;padding:5px 15px 5px 10px;float:left;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border-width:0;border-color:#cac8bd;border-style:solid;height:27px;color:#4f4f4f;background:#e0ded7;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;cursor:pointer}.dhx_btn_set div{float:left;font-size:13px;height:17px;line-height:17px;background-repeat:no-repeat;vertical-align:middle}.dhx_save_btn{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QUUzMDA3NzlDOTFEMTFFMkJBQTNFMTU1NTdFNUNFMTMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QUUzMDA3N0FDOTFEMTFFMkJBQTNFMTU1NTdFNUNFMTMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpBRTMwMDc3N0M5MUQxMUUyQkFBM0UxNTU1N0U1Q0UxMyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpBRTMwMDc3OEM5MUQxMUUyQkFBM0UxNTU1N0U1Q0UxMyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PtHrmoUAAAF4SURBVHjaYvr//z8DNTATA5UA1QxiIUdTQEAAG5CaA8S6QGyyYcOGvyzkGMLIyLgSGC4BHBwcr1RUVBhJdhHQEGagIctAhjAzM/9OSkrqd3Nz+ws3CKjAA0hNA+JcoDO34jIESC0FGhIM4tvb2y8FGjIbyPwPD2wNDQ1RIKUItG09UIM3LkOAOBzEB3rnaF5eXieQ+RYl1jo6Og4oKCicAtrGCjXMA82Q+TBD+Pj4HtfU1NQDmTewRf/jjIyMNmDgfYAatgnJMFDsxIIYrKysX7Ozs9sFBAT240xHQO/t8PT0BGliQHLZdiA3AabGz89vlrm5+RIg8x++BPkzPj5+Msj/UMM4gBTciwYGBttiY2MnAJmfiUnZj4CB2MzFxfUGWVBcXPx6WVlZE0ie6CwiJye3D+il6UCvgZ0PCreCggKQ4adIzWu/w8LCpoO8wsLC8j0yMnKipqbmelh6wQUYQUUADmACxDpADArwl4RSPT6DBqYYAQgwANyusz7jloxAAAAAAElFTkSuQmCC);margin-top:2px;width:21px}.dhx_cancel_btn{margin-top:2px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QjQxN0VEOUZDOTFEMTFFMkFFMjE4MDI4MUJDNDQ1NDkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QjQxN0VEQTBDOTFEMTFFMkFFMjE4MDI4MUJDNDQ1NDkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpCNDE3RUQ5REM5MUQxMUUyQUUyMTgwMjgxQkM0NDU0OSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpCNDE3RUQ5RUM5MUQxMUUyQUUyMTgwMjgxQkM0NDU0OSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pq9E2PUAAAH/SURBVHjaYvz//z8DNQAjTQ0KCAjgAVJFQOwBxGZQ4VNAvAOI+zZs2PCFoEFAQ+wZGRkXAMUVsNrMyPgAKJcANOwgToOAhgQCqXUgtqCg4ANXV9ctzs7Od0D8vXv3quzYsSPw06dP0lDl/kDDNmEYBDRECGjbYyCfS09Pb0dNTc1sNja2M0CpF1C1EjExMfu/fPmiBHXZZ5CrgYa9A/FZYCaysLCU/vnzh0tUVPRGU1NTPVDoNBD/R3ItyKVKAgICj/79+8cIdJksSA9QrBIkz4RkUBCINjU1XQUNWGRDQC4zBhrycMGCBYW2trbLkPWgGPTz50+wk6WlpTegBT6yIaCYXMfJybkRJPf7928ZDIOYmZl/g+jjx4+/xmcISPz27dsfYNowDOLi4gIb8PDhQ1N8hkDVGIBobm7ulxgGAb0EClyGX79+leAzBCjH9vXr13pkPSgGhYeHTwd6DxhUP62ghjzCZggw2icCw0YdGNDfQXrgkQVjGBgYHPHx8Zm9cePGHGhA/gdq/ANkysGUAHELMO3ogji+vr6zQXpwZREFkEErV66M//btmwi2LAIMyzdAlyz09/efDAoufJlW8tWrVx7Tp08PevDggc779+8VYFlGQUHhSmZm5joxMTFQ5n1OTDHCCsT6QAxKJ2JQsVdA/ASIL4J8TrPyCCDAAK8E80CvM3cMAAAAAElFTkSuQmCC);width:20px}.dhx_delete_btn{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QjkzQUQ0MjVDOTFEMTFFMkEwRERFNzQ5NzZCRjlBODgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QjkzQUQ0MjZDOTFEMTFFMkEwRERFNzQ5NzZCRjlBODgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpCOTNBRDQyM0M5MUQxMUUyQTBEREU3NDk3NkJGOUE4OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpCOTNBRDQyNEM5MUQxMUUyQTBEREU3NDk3NkJGOUE4OCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pmbo+yIAAAB9SURBVHjaYvz//z8DNQALPsmAgAC4LRs2bGDEaxLIRdiwv78/kPqfBsNQPk71jDCvIdtOLEB2JRMDlQAjUmCnkaF/FtYwgoUDsTQypprXRg0a0QYhp2yGpqamzefOnfMhpMnIyGhLXV2dL06DgMAYigmBs1CM0yCyAUCAAQAlK4lJjSOjGQAAAABJRU5ErkJggg==);margin-top:2px;width:20px}.dhx_cal_cover{width:100%;height:100%;position:absolute;z-index:10000;top:0;left:0;background-color:#000;opacity:.1;filter:alpha(opacity=10)}.dhx_custom_button{padding:0 3px;font-family:Verdana;font-size:11px;font-weight:400;margin-right:5px;margin-top:0;cursor:pointer}.dhx_custom_button div{cursor:pointer;float:left;height:21px;line-height:21px;vertical-align:middle}.dhx_cal_light_wide{width:580px;padding:2px 4px}.dhx_cal_light_wide .dhx_cal_larea{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #cac8bd}.dhx_cal_light_wide .dhx_cal_lsection{border:0;float:left;text-align:right;width:80px;height:20px;padding:5px 10px 0 0}.dhx_cal_light_wide .dhx_wrap_section{position:relative;padding:10px 0;overflow:hidden;border-bottom:1px solid #eae9e5}.dhx_cal_light_wide .dhx_section_time{overflow:hidden;padding-top:2px!important;padding-right:0;height:20px!important}.dhx_cal_light_wide .dhx_cal_ltext{padding-right:0}.dhx_cal_light_wide .dhx_cal_larea{padding:0 10px;width:100%}.dhx_cal_light_wide .dhx_section_time{background:transparent}.dhx_cal_light_wide .dhx_cal_checkbox label{padding-left:0}.dhx_cal_light_wide .dhx_cal_lsection .dhx_fullday{float:none;margin-right:0;font-weight:700;cursor:pointer}.dhx_cal_light_wide .dhx_custom_button{position:absolute;top:0;right:0;margin-top:2px}.dhx_cal_light_wide .dhx_repeat_right{margin-right:55px}.dhx_cal_light_wide.dhx_cal_light_full{width:738px}.dhx_cal_wide_checkbox input{margin-top:8px;margin-left:14px}.dhx_cal_light input{font-size:11px}.dhx_custom_button{float:right;height:21px;width:90px;-webkit-border-radius:4px;-moz-border-radius:4px;-ms-border-radius:4px;-o-border-radius:4px;border-radius:4px}.dhx_section_time{background-color:#fff;white-space:nowrap;padding:2px 10px 5px;padding-top:2px!important}.dhx_section_time .dhx_time_selects{float:left;height:25px}.dhx_section_time .dhx_time_selects select{height:23px;padding:2px;border:1px solid #cac8bd}.dhx_gantt_duration{width:100px;height:23px;float:left;white-space:nowrap;margin-left:20px;line-height:23px}.dhx_gantt_duration .dhx_gantt_duration_value,.dhx_gantt_duration .dhx_gantt_duration_dec,.dhx_gantt_duration .dhx_gantt_duration_inc{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;vertical-align:top;height:100%;border:1px solid #cac8bd}.dhx_gantt_duration .dhx_gantt_duration_value{width:40px;padding:3px 4px;border-left-width:0;border-right-width:0}.dhx_gantt_duration .dhx_gantt_duration_dec,.dhx_gantt_duration .dhx_gantt_duration_inc{width:20px;padding:1px;padding-bottom:3px;background:#e0ded7}.dhx_gantt_duration .dhx_gantt_duration_dec{-moz-border-top-left-radius:4px;-moz-border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;border-top-left-radius:4px;border-bottom-left-radius:4px}.dhx_gantt_duration .dhx_gantt_duration_inc{margin-right:4px;-moz-border-top-right-radius:4px;-moz-border-bottom-right-radius:4px;-webkit-border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:4px}.dhx_cal_quick_info{border:1px solid #cac8bd;border-radius:6px;position:absolute;z-index:300;box-shadow:3px 3px 3px rgba(0,0,0,.07);background-color:#fcfaf3;width:300px;transition:left .5s ease,right .5s;-moz-transition:left .5s ease,right .5s;-webkit-transition:left .5s ease,right .5s;-o-transition:left .5s ease,right .5s}.dhx_no_animate{transition:none;-moz-transition:none;-webkit-transition:none;-o-transition:none}.dhx_cal_quick_info.dhx_qi_left .dhx_qi_big_icon{float:right}.dhx_cal_qi_title{-webkit-border-top-left-radius:6px;-webkit-border-bottom-left-radius:0;-webkit-border-top-right-radius:6px;-webkit-border-bottom-right-radius:0;-moz-border-radius-topleft:6px;-moz-border-radius-bottomleft:0;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:0;border-top-left-radius:6px;border-bottom-left-radius:0;border-top-right-radius:6px;border-bottom-right-radius:0;padding:5px 0 8px 12px;color:#3f3f3f;background-color:#f4f2ea;border-bottom:1px solid #cac8bd}.dhx_cal_qi_tdate{font-size:14px;font-weight:700}.dhx_cal_qi_tcontent{font-size:11px}.dhx_cal_qi_content{padding:16px 8px;font-size:13px;color:#3f3f3f;overflow:hidden}.dhx_cal_qi_controls{-webkit-border-top-left-radius:0;-webkit-border-bottom-left-radius:6px;-webkit-border-top-right-radius:0;-webkit-border-bottom-right-radius:6px;-moz-border-radius-topleft:0;-moz-border-radius-bottomleft:6px;-moz-border-radius-topright:0;-moz-border-radius-bottomright:6px;border-top-left-radius:0;border-bottom-left-radius:6px;border-top-right-radius:0;border-bottom-right-radius:6px;padding-left:7px}.dhx_cal_qi_controls .dhx_menu_icon{margin-top:3.5px;background-repeat:no-repeat}.dhx_cal_qi_controls .dhx_menu_icon.icon_edit{width:20px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH3QYFDgcMloNXJQAAAFNJREFUOMvt0zEOACAIA0Dkg30rL8TJTRTKZGJXyaWEKPKTCQAH4Ls37cItcDUzsxHNDLZNhCq7Gt1wh9ErV7EjyGAhyGLphlnsClWuS32rn0czAT/KLVk9yshBAAAAAElFTkSuQmCC)}.dhx_cal_qi_controls .dhx_menu_icon.icon_delete{width:20px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABIAAAASCAYAAABWzo5XAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QjkzQUQ0MjVDOTFEMTFFMkEwRERFNzQ5NzZCRjlBODgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QjkzQUQ0MjZDOTFEMTFFMkEwRERFNzQ5NzZCRjlBODgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpCOTNBRDQyM0M5MUQxMUUyQTBEREU3NDk3NkJGOUE4OCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpCOTNBRDQyNEM5MUQxMUUyQTBEREU3NDk3NkJGOUE4OCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pmbo+yIAAAB9SURBVHjaYvz//z8DNQALPsmAgAC4LRs2bGDEaxLIRdiwv78/kPqfBsNQPk71jDCvIdtOLEB2JRMDlQAjUmCnkaF/FtYwgoUDsTQypprXRg0a0QYhp2yGpqamzefOnfMhpMnIyGhLXV2dL06DgMAYigmBs1CM0yCyAUCAAQAlK4lJjSOjGQAAAABJRU5ErkJggg==)}.dhx_qi_big_icon{font-size:13px;border-radius:4px;color:#4f4f4f;background:#e0ded7;margin:5px 9px 8px 0;min-width:60px;line-height:27px;vertical-align:middle;padding:0 10px 0 5px;cursor:pointer;border:1px solid #cac8bd}.dhx_cal_qi_controls div{float:left;height:27px;text-align:center;line-height:27px}.gantt_tooltip{box-shadow:3px 3px 3px rgba(0,0,0,.07);background-color:#fff;border-left:1px solid rgba(0,0,0,.07);border-top:1px solid rgba(0,0,0,.07);font-family:Verdana;font-size:8pt;color:#3f3f3f;padding:10px;position:absolute;z-index:500}.gantt_noselect{-moz-user-select:-moz-none;-webkit-user-select:none;-user-select:none}.gantt_drag_marker{position:absolute;font-family:Verdana;font-size:11px}.gantt_drag_marker .gantt_tree_indent,.gantt_drag_marker .gantt_tree_icon.gantt_blank,.gantt_drag_marker .gantt_tree_icon.gantt_open,.gantt_drag_marker .gantt_tree_icon.gantt_close{display:none}.gantt_drag_marker,.gantt_drag_marker .gantt_row.odd{background-color:#fff}.gantt_drag_marker .gantt_row{border-left:1px solid #d3d1c8;border-top:1px solid #d3d1c8}.gantt_drag_marker .gantt_cell{border-color:#d3d1c8}.gantt_row.gantt_over,.gantt_task_row.gantt_over{background-color:#0070fe}.gantt_row.gantt_transparent .gantt_cell{opacity:.7}.gantt_task_row.gantt_transparent{background-color:#fff}.dhtmlx_popup_button.dhtmlx_delete_button{border:1px solid #98d27e;background:#a7d991}
\ No newline at end of file
/*
This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
to use it in non-GPL project. Please contact sales@dhtmlx.com for details
*/
.gridHoverStyle{background-color:#ffe6b1!important;background-color:#ffebc1;background-image:-webkit-linear-gradient(top,#ffe09d 0,#ffeabb 100%);background-image:-moz-linear-gradient(top,#ffe09d 0,#ffeabb 100%);background-image:-ms-linear-gradient(top,#ffe09d 0,#ffeabb 100%);background-image:linear-gradient(to top,#ffe09d 0,#ffeabb 100%);border-top-color:#ffc341;border-bottom-color:#ffc341}.gridSelection{background-color:#ffe6b1!important;border-bottom-color:#ffc341}.timelineSelection{background-color:#ffe6b1!important;background-color:#ffebc1;background-image:-webkit-linear-gradient(top,#ffe09d 0,#ffeabb 100%);background-image:-moz-linear-gradient(top,#ffe09d 0,#ffeabb 100%);background-image:-ms-linear-gradient(top,#ffe09d 0,#ffeabb 100%);background-image:linear-gradient(to top,#ffe09d 0,#ffeabb 100%);border-top-color:#ffc341;border-bottom-color:#ffc341}.timelineSelection .gantt_task_cell{border-right-color:#ffce65}.dhx_cal_quick_info .dhx_cal_qi_title{background:#fff}.dhx_cal_qi_controls .dhx_qi_big_icon .dhx_menu_icon.icon_delete{margin-top:5px}.gantt_container{font-family:Tahoma;font-size:11px;border:1px solid #a4bed4;position:relative;white-space:nowrap}.gantt_grid{border-right:1px solid #a4bed4}.gantt_task_scroll{overflow-x:scroll}.gantt_task{position:relative}.gantt_task,.gantt_grid{overflow-x:hidden;overflow-y:hidden;display:inline-block;vertical-align:top}.gantt_grid_scale,.gantt_task_scale{color:#42464b;border-bottom:1px solid #a4bed4}.gantt_grid_scale{box-shadow:0 1px 1px #fff inset;background-color:#dfedff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#e4f0ff),color-stop(50%,#dfedff),color-stop(100%,#d5e8ff));background-image:-webkit-linear-gradient(top,#e4f0ff 0,#dfedff 50%,#d5e8ff 100%);background-image:-moz-linear-gradient(top,#e4f0ff 0,#dfedff 60%,#d5e8ff 100%);background-image:-ms-linear-gradient(top,#e4f0ff 0,#dfedff 60%,#d5e8ff 100%);background-image:-o-linear-gradient(top,#e4f0ff 0,#dfedff 60%,#d5e8ff 100%);background-position:0 1px;background-repeat:repeat-x}.gantt_task_scale{box-shadow:0 1px 1px #fff inset;background-color:#dfedff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#e4f0ff),color-stop(50%,#dfedff),color-stop(100%,#d5e8ff));background-image:-webkit-linear-gradient(top,#e4f0ff 0,#dfedff 50%,#d5e8ff 100%);background-image:-moz-linear-gradient(top,#e4f0ff 0,#dfedff 60%,#d5e8ff 100%);background-image:-ms-linear-gradient(top,#e4f0ff 0,#dfedff 60%,#d5e8ff 100%);background-image:-o-linear-gradient(top,#e4f0ff 0,#dfedff 60%,#d5e8ff 100%);background-position:0 1px;background-repeat:repeat-x}.gantt_scale_line{box-sizing:border-box;-moz-box-sizing:border-box;border-top:1px solid #a4bed4}.gantt_scale_line:first-child{border-top:0}.gantt_grid_head_cell{display:inline-block;vertical-align:top;border-right:1px solid #a4bed4;text-align:center;position:relative;cursor:default;height:100%;box-sizing:border-box;-moz-box-sizing:border-box;line-height:25px;-moz-user-select:-moz-none;-webkit-user-select:none;-user-select:none}.gantt_scale_line{clear:both}.gantt_grid_data{width:100%;overflow:hidden}.gantt_row{position:relative;-webkit-user-select:none;-moz-user-select:none;-moz-user-select:-moz-none}.gantt_add,.gantt_grid_head_add{width:100%;height:100%;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NzZCMThBRUFCRTQ0MTFFMkFFMEFGMEFBMzJEN0RBRTIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NzZCMThBRUJCRTQ0MTFFMkFFMEFGMEFBMzJEN0RBRTIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3NkIxOEFFOEJFNDQxMUUyQUUwQUYwQUEzMkQ3REFFMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3NkIxOEFFOUJFNDQxMUUyQUUwQUYwQUEzMkQ3REFFMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PkVHygEAAAEpSURBVHjaYgzeGcyABfxnwA4Y0QVY/v79i1VlsUMxCr/3QC9WdTgN+P//P8Ohr4fAbDtuOwZc6lj+/PmDVeLfv39wTSA2LnU4DQC5AGYAiI1LHRNQ4j861pfSZ3j36x1YEwiD2CAxbGoZbRbZ/K/3qUcx9cPvDwzXv11n+PL3C5jPw8zDoMmlySDAKoCirnFLI8QLID/ufLOTARf48OcDw/Gfx1HE3EXcwa5j+f37N95AwgVAekB64QaATISB97/fM1z5dIXh85/PYD4vCy+DDp8OgyCrIIYBjFoTtDBSnYWyBYO9rj3DsbfHwHwrYSuGg5cPMpy4ewIzFoCmMKLjwzcOMwiwCMBjAcQGiWFTizchweRompDwuiBANoCgCxjFasQoys4AAQYARt4I/K036xQAAAAASUVORK5CYII=);background-position:center center;background-repeat:no-repeat;cursor:pointer;position:relative;-moz-opacity:.3;opacity:.3}.gantt_grid_head_cell.gantt_grid_head_add{-moz-opacity:.6;opacity:.6;top:0}.gantt_grid_head_cell.gantt_grid_head_add:hover{-moz-opacity:1;opacity:1}.gantt_grid_data .gantt_row:hover,.gantt_grid_data .gantt_row.odd:hover{background-color:#ffe6b1!important;background-color:#ffebc1;background-image:-webkit-linear-gradient(top,#ffe09d 0,#ffeabb 100%);background-image:-moz-linear-gradient(top,#ffe09d 0,#ffeabb 100%);background-image:-ms-linear-gradient(top,#ffe09d 0,#ffeabb 100%);background-image:linear-gradient(to top,#ffe09d 0,#ffeabb 100%);border-top-color:#ffc341;border-bottom-color:#ffc341}.gantt_grid_data .gantt_row:hover .gantt_add,.gantt_grid_data .gantt_row.odd:hover .gantt_add{-moz-opacity:1;opacity:1}.gantt_task_row,.gantt_row{border-bottom:1px solid #ebebeb}.gantt_row,.gantt_task_row{background-color:#fff}.gantt_row.odd,.gantt_task_row.odd{background-color:#fff}.gantt_row,.gantt_cell,.gantt_task_row,.gantt_task_cell,.gantt_grid_head_cell,.gantt_scale_cell{box-sizing:border-box;-moz-box-sizing:border-box}.gantt_grid_head_cell,.gantt_scale_cell{line-height:inherit}.gantt_cell{display:inline-block;vertical-align:top;border-right:1px solid #ebebeb;padding-left:6px;padding-right:6px;height:100%;overflow:hidden;white-space:nowrap}.gantt_grid_scale .gantt_last_cell,.gantt_grid_data .gantt_last_cell,.gantt_task_scale .gantt_last_cell,.gantt_task_bg .gantt_last_cell{border-right-width:0}.gantt_task_bg{overflow:hidden}.gantt_scale_cell{display:inline-block;white-space:nowrap;overflow:hidden;border-right:1px solid #a4bed4;text-align:center;height:100%}.gantt_task_cell{display:inline-block;height:100%;border-right:1px solid #ebebeb}.gantt_ver_scroll{width:0;background-color:transparent;height:1px;overflow-x:hidden;overflow-y:scroll;display:none;position:absolute;right:0}.gantt_ver_scroll>div{width:1px;height:1px}.gantt_hor_scroll{height:0;background-color:transparent;width:100%;clear:both;overflow-x:scroll;overflow-y:hidden;display:none}.gantt_hor_scroll>div{width:5000px;height:1px}.gantt_tree_indent{width:15px;height:100%;display:inline-block}.gantt_tree_content,.gantt_tree_icon{vertical-align:top}.gantt_tree_icon{width:28px;height:100%;display:inline-block;background-repeat:no-repeat;background-position:center center}.gantt_tree_content{height:100%;display:inline-block}.gantt_tree_icon.gantt_open{background-image:url(data:image/gif;base64,R0lGODlhEgASAJEAAP///4SEhAAAAP///yH5BAUUAAMALAAAAAASABIAAAIqnI+py+0Powq01haA3iDgLWwek2mhNi6ZwLLdZ4owcL4kJ5OWJfX+DykAADs=);width:18px;cursor:pointer}.gantt_tree_icon.gantt_close{background-image:url(data:image/gif;base64,R0lGODlhEgASAJEAAP///4SEhAAAAP///yH5BAUUAAMALAAAAAASABIAAAImnI+py+0Powq01haA3iBgvnlMBnafgKLmWK4LCYpvK0+WJeX6DhUAOw==);width:18px;cursor:pointer}.gantt_tree_icon.gantt_blank{width:18px}.gantt_tree_icon.gantt_folder_open{background-image:url(data:image/gif;base64,R0lGODlhEgASAMQfAPXMWPK9N/fipOzLbPrVaP/89frqwv3tuvvprfLES+7PdP7xyP7z0vPAQfvtyMuaIv/32/735OSqFv3de+7ReP7lmf7gh/nv0v/67unFXfb29vK/PenFSaGEPfLnyv///yH5BAEAAB8ALAAAAAASABIAAAWS4Cc+ZGk+YvqZRVtgGiag6sNBThlFWnTQqQcE4sqRFj/VashsYR6HZI0JWSAjDwQCOGIgERULg/EQzJRQsGVCYHTK51qYTQDkFPj4iA4AJHIUgQoGQA8EdX4NhHgKAxeFfQkJGwGLeI6Fkg2UlQ8Dn5hBCZuchKChIxKkGw05Ga8DHlwkqg2tJ1xBtDlKvTUkviEAOw==)}.gantt_tree_icon.gantt_folder_closed{background-image:url(data:image/gif;base64,R0lGODlhEgASAMQfAPvjpP/24fPHVvnUdvzotdSiKv/23vvrw/LCS/C+RfnelvPMaf/88/nbi/rsyerYq/TLYv7y1v7wzeCsLO+8P86dJfvv0MuaIsydKPPOc+SqGKGEPe/AS+zVneWwLf///yH5BAEAAB8ALAAAAAASABIAAAWZ4CdeZGleYvpdA+O+zNUBqHoZOB4EceHUqVtuF+n9VCtdQCI5BC6FA3BkCEQkBALAACVMV9eDVhGJQGnIixigaAwiG6jie2G7BxDLpVJozG13EAICPxUeGRleQQMLgggJUhiHC3pBggKPFJETGZRAF5gJCRSaFxgTC55BCByioz+nEKojGgmtHBwPJAUCELo2J8FfSMTFxsUhADs=)}.gantt_tree_icon.gantt_file{background-image:url(data:image/gif;base64,R0lGODlhEgASAMQVAPv7+2RkZPf39/r6+vj4+Pn5+fT09Pb29vX19fHx8fDw8Onp6fLy8u7u7uzs7PPz8+vr6+rq6uXl5ejo6O/v7////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACH5BAEAABUALAAAAAASABIAAAV4YCWOZGkGaKqaVSAMQCwLQnAOj6ErzUTXpQDgQDgcGJTFDzgSEp4HRCKytDUBMKzAoIAsJACrSCjTGhINR5hELg8ECEZifS3HCgQEgj62xwZPAnwtbgMDBQU0g21Yh4iIi3aGkzBihH52lgEEmDIEli0qoigspSYhADs=)}.gantt_grid_head_cell .gantt_sort{position:absolute;right:5px;top:8px;width:7px;height:13px;background-repeat:no-repeat;background-position:center center}.gantt_grid_head_cell .gantt_sort.gantt_asc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAANCAYAAABlyXS1AAAARUlEQVR4nGNgQAKGxib/GbABkIS7b8B/DAUwCRiGK0CXwFBAb1DfP/U/LszwHwi2X7qFgUEArBtdAVwCBmAKMCSQFSDzAWXXaOHsXeqkAAAAAElFTkSuQmCC)}.gantt_grid_head_cell .gantt_sort.gantt_desc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAANCAYAAABlyXS1AAAARUlEQVR42mNgQAL1/VP/M2ADIIntF2/9x1AAlrh0C47hCmA60DFYwX88gIFGwNDY5D8uDFbg7hvwHx2jmIBTAlkB0e4BAEjlaNtBWJPnAAAAAElFTkSuQmCC)}.gantt_inserted,.gantt_updated{font-weight:700}.gantt_deleted{text-decoration:line-through}.gantt_invalid{background-color:FFE0E0}.gantt_error{color:red}.dhtmlx_message_area{position:fixed;right:5px;width:250px;z-index:1000}.dhtmlx-info{min-width:120px;padding:4px 4px 4px 20px;font-family:Tahoma;z-index:10000;margin:5px;margin-bottom:10px;-webkit-transition:all .5s ease;-moz-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease}.dhtmlx-info.hidden{height:0;padding:0;border-width:0;margin:0;overflow:hidden}.dhtmlx_modal_box{overflow:hidden;display:inline-block;min-width:250px;width:250px;text-align:center;position:fixed;z-index:20000;box-shadow:3px 3px 3px rgba(0,0,0,.07);font-family:Tahoma;border-radius:0;border:1px solid #a4bed4;background:#fff}.dhtmlx_popup_title{border-top-left-radius:0;border-top-right-radius:0;border-width:0}.dhtmlx_button,.dhtmlx_popup_button{border:1px solid #a4bed4;height:24px;line-height:24px;display:inline-block;margin:0 5px;border-radius:4px;background-color:#f8f8f8;background-image:-webkit-linear-gradient(top,#e6e6e6 0,#fff 100%);background-image:-moz-linear-gradient(top,#e6e6e6 0,#fff 100%);background-image:-ms-linear-gradient(top,#e6e6e6 0,#fff 100%);background-image:linear-gradient(to top,#e6e6e6 0,#fff 100%)}.dhtmlx-info,.dhtmlx_popup_button,.dhtmlx_button{user-select:none;-webkit-user-select:none;-moz-user-select:-moz-none;cursor:pointer}.dhtmlx_popup_text{overflow:hidden}.dhtmlx_popup_controls{border-radius:6px;padding:10px}.dhtmlx_popup_button{min-width:100px}div.dhx_modal_cover{background-color:#000;cursor:default;filter:alpha(opacity=20);opacity:.2;position:fixed;z-index:19999;left:0;top:0;width:100%;height:100%;border:0;zoom:1}.dhtmlx-info img,.dhtmlx_modal_box img{float:left;margin-right:20px}.dhtmlx-alert-error,.dhtmlx-confirm-error{border:1px solid red}.dhtmlx_button input,.dhtmlx_popup_button div{border-radius:4px;font-size:15px;-moz-box-sizing:content-box;box-sizing:content-box;padding:0;margin:0;vertical-align:top}.dhtmlx_popup_title{color:#fff;text-shadow:1px 1px #000;height:40px;line-height:40px;font-size:20px}.dhtmlx_popup_text{margin:15px 15px 5px;font-size:14px;color:#000;min-height:30px;border-radius:0}.dhtmlx-info,.dhtmlx-error{font-size:14px;color:#000;box-shadow:3px 3px 3px rgba(0,0,0,.07);padding:0;background-color:#FFF;border-radius:3px;border:1px solid #fff}.dhtmlx-info div{padding:5px 10px;background-color:#fff;border-radius:3px;border:1px solid #a4bed4}.dhtmlx-error{background-color:#d81b1b;border:1px solid #ff3c3c;box-shadow:3px 3px 3px rgba(0,0,0,.07)}.dhtmlx-error div{background-color:#d81b1b;border:1px solid #940000;color:#FFF}.gantt_grid div,.gantt_data_area div{-ms-touch-action:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}.gantt_data_area{position:relative;overflow-x:hidden;overflow-y:hidden;-moz-user-select:-moz-none;-webkit-user-select:none;-user-select:none}.gantt_links_area{position:absolute;left:0;top:0}.gantt_task_content,.gantt_task_progress,.gantt_side_content{line-height:inherit;overflow:hidden;height:100%}.gantt_task_content{font-size:12px;color:#1e2022;position:absolute;white-space:nowrap;text-align:center}.gantt_task_progress{text-align:center;z-index:0;background:#5aa0d3;background-color:#82b7de;background-image:-webkit-linear-gradient(top,#abcee8 0,#5aa0d3 36%,#bfdaee 100%);background-image:-moz-linear-gradient(top,#abcee8 0,#5aa0d3 36%,#bfdaee 100%);background-image:-ms-linear-gradient(top,#abcee8 0,#5aa0d3 36%,#bfdaee 100%);background-image:linear-gradient(to top,#abcee8 0,#5aa0d3 36%,#bfdaee 100%)}.gantt_task_line{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0;position:absolute;-moz-box-sizing:border-box;box-sizing:border-box;background-color:#eff6fb;border:1px solid #3588c5;-webkit-user-select:none;-moz-user-select:none;-moz-user-select:-moz-none}.gantt_task_line.gantt_drag_move div{cursor:move}.gantt_side_content{position:absolute;white-space:nowrap;color:#6e6e6e;bottom:7px;font-size:11px}.gantt_side_content.gantt_left{right:100%;padding-right:15px}.gantt_side_content.gantt_right{left:100%;padding-left:15px}.gantt_side_content.gantt_link_crossing{bottom:6.75px}.gantt_task_link .gantt_line_wrapper,.gantt_link_arrow{position:absolute;cursor:pointer}.gantt_line_wrapper div{background-color:#4a8f43}.gantt_task_link:hover .gantt_line_wrapper div{box-shadow:0 0 5px 0 #4a8f43}.gantt_task_link div.gantt_link_arrow{background-color:transparent;border-style:solid;width:0;height:0}.gantt_link_control{position:absolute;width:13px;top:0}.gantt_link_control div{display:none;cursor:pointer;box-sizing:border-box;position:relative;top:50%;margin-top:-7.5px;vertical-align:middle;border:1px solid #929292;-webkit-border-radius:6.5px;-moz-border-radius:6.5px;border-radius:6.5px;height:13px;width:13px;background-color:#f0f0f0}.gantt_link_control div:hover{background-color:#fff}.gantt_link_control.task_left{left:-13px}.gantt_link_control.task_right{right:-13px}.gantt_task_line.gantt_selected .gantt_link_control div,.gantt_task_line:hover .gantt_link_control div{display:block}.gantt_link_target .gantt_link_control div{display:block}.gantt_link_source,.gantt_link_target{box-shadow:0 0 3px #0070fe}.gantt_link_target.link_start_allow,.gantt_link_target.link_finish_allow{box-shadow:0 0 3px #6eb867}.gantt_link_target.link_start_deny,.gantt_link_target.link_finish_deny{box-shadow:0 0 3px #e87e7b}.link_start_allow .gantt_link_control.task_left div,.link_finish_allow .gantt_link_control.task_right div{background-color:#6eb867;border-color:#4a8f43}.link_start_deny .gantt_link_control.task_left div,.link_finish_deny .gantt_link_control.task_right div{background-color:#e87e7b;border-color:#dd3e3a}.gantt_link_arrow_right{border-width:4px 0 4px 8px;border-color:transparent transparent transparent #4a8f43;margin-top:-1px}.gantt_link_arrow_left{border-width:4px 8px 4px 0;margin-top:-1px;border-color:transparent #4a8f43 transparent transparent}.gantt_link_arrow_top{border-width:0 4px 8px;border-color:transparent transparent #4a8f43}.gantt_link_arrow_down{border-width:4px 8px 0 4px;border-color:#4a8f43 transparent transparent}.gantt_task_drag,.gantt_task_progress_drag{cursor:w-resize;height:100%;display:none;position:absolute}.gantt_task_line.gantt_selected .gantt_task_progress_drag,.gantt_task_line:hover .gantt_task_progress_drag,.gantt_task_line.gantt_selected .gantt_task_drag,.gantt_task_line:hover .gantt_task_drag{display:block}.gantt_task_drag{width:6px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAACCAYAAACUn8ZgAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MDEyNDQ5RDdCRTQ5MTFFMjhBQzlGRDA2RDIyNDc5NzEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MDEyNDQ5RDhCRTQ5MTFFMjhBQzlGRDA2RDIyNDc5NzEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowMTI0NDlENUJFNDkxMUUyOEFDOUZEMDZEMjI0Nzk3MSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowMTI0NDlENkJFNDkxMUUyOEFDOUZEMDZEMjI0Nzk3MSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pv8NECcAAAAaSURBVHjaYvz//z8DLsAEIsw6j/3HRgMEGAARnwqNiuFmdQAAAABJRU5ErkJggg==);z-index:1;top:0}.gantt_task_drag.task_left{left:0}.gantt_task_drag.task_right{right:0}.gantt_task_progress_drag{height:8px;width:8px;bottom:-4px;margin-left:-4px;background-position:bottom;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NDNGNzQ1MTZCQkRBMTFFMjlBMjRBRkU0RkNCMTUzNkUiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NDNGNzQ1MTdCQkRBMTFFMjlBMjRBRkU0RkNCMTUzNkUiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo0M0Y3NDUxNEJCREExMUUyOUEyNEFGRTRGQ0IxNTM2RSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo0M0Y3NDUxNUJCREExMUUyOUEyNEFGRTRGQ0IxNTM2RSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PrBLI3EAAACISURBVHjafE07DkMhDAuftQdgYuCWbZ9a0WtwGJC4BgMbYmVGaZLpqWpryZFjOwrACSmlSLzCN1DwrLUik/TtM3yUUnDOiWMMZE3enTNF4gghRO89KKXkABGh9w6ttcPSHp1zsNaCvbcUjDHAHhVemg1rrVxprYWs2ZOMR84ZfoGfXuAP3gIMABorQUvC1invAAAAAElFTkSuQmCC);background-repeat:no-repeat;z-index:2}.gantt_link_tooltip{box-shadow:3px 3px 3px #888;background-color:#fff;border-left:1px dotted #cecece;border-top:1px dotted #cecece;font-family:Tahoma;font-size:8pt;color:#444;padding:6px;line-height:20px}.gantt_link_direction{height:0;border:0 none #4a8f43;border-bottom-style:dashed;border-bottom-width:2px;transform-origin:0 0;-ms-transform-origin:0 0;-webkit-transform-origin:0 0;z-index:2;margin-left:1px;position:absolute}.gantt_grid_data .gantt_row.gantt_selected,.gantt_grid_data .gantt_row.odd.gantt_selected{background-color:#ffe6b1!important;border-bottom-color:#ffc341}.gantt_task_row.gantt_selected{background-color:#ffe6b1!important;background-color:#ffebc1;background-image:-webkit-linear-gradient(top,#ffe09d 0,#ffeabb 100%);background-image:-moz-linear-gradient(top,#ffe09d 0,#ffeabb 100%);background-image:-ms-linear-gradient(top,#ffe09d 0,#ffeabb 100%);background-image:linear-gradient(to top,#ffe09d 0,#ffeabb 100%);border-top-color:#ffc341;border-bottom-color:#ffc341}.gantt_task_row.gantt_selected .gantt_task_cell{border-right-color:#ffb30e}.gantt_task_row.gantt_selected .gantt_task_cell{border-right-color:#ffce65}.gantt_task_line.gantt_selected{box-shadow:0 0 5px #5aa0d3}.gantt_task_line.gantt_project.gantt_selected{box-shadow:0 0 5px #9ab9f1}.dhx_unselectable,.dhx_unselectable div{-webkit-user-select:none;-moz-user-select:none;-moz-user-select:-moz-none}.dhx_cal_light{-webkit-tap-highlight-color:transparent;background-color:#eff6fb;border-radius:0;font-family:Tahoma;font-size:11px;border:1px solid #a4bed4;color:#42464b;position:absolute;z-index:10001;width:550px;height:250px;box-shadow:3px 3px 3px rgba(0,0,0,.07)}.dhx_cal_light_wide{width:650px}.dhx_cal_light select{font-family:Tahoma;border:1px solid #a4bed4;font-size:11px;padding:2px;margin:0}.dhx_cal_ltitle{padding:7px 10px;overflow:hidden;white-space:nowrap;-webkit-border-top-left-radius:0;-webkit-border-bottom-left-radius:0;-webkit-border-top-right-radius:0;-webkit-border-bottom-right-radius:0;-moz-border-radius-topleft:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0;border-top-left-radius:0;border-bottom-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:0}.dhx_cal_ltitle span{white-space:nowrap}.dhx_cal_lsection{color:#727272;font-weight:700;padding:12px 0 5px 10px}.dhx_cal_lsection .dhx_fullday{float:right;margin-right:5px;font-size:12px;font-weight:400;line-height:20px;vertical-align:top;cursor:pointer}.dhx_cal_lsection{font-size:13px}.dhx_cal_ltext{padding:2px 10px;overflow:hidden}.dhx_cal_ltext textarea{overflow:auto;font-family:Tahoma;font-size:11px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #a4bed4;height:100%;width:100%;outline:0!important;resize:none}.dhx_time{font-weight:700}.dhx_cal_light .dhx_title{padding-left:10px}.dhx_cal_larea{border:1px solid #a4bed4;border-left:0;border-right:0;background-color:#fff;overflow:hidden;height:1px}.dhx_btn_set{margin:10px 7px 5px 10px;padding:2px 25px 2px 10px;float:left;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border-width:1px;border-color:#a4bed4;border-style:solid;height:26px;color:#42464b;background-color:#f8f8f8;background-image:-webkit-linear-gradient(top,#e6e6e6 0,#fff 100%);background-image:-moz-linear-gradient(top,#e6e6e6 0,#fff 100%);background-image:-ms-linear-gradient(top,#e6e6e6 0,#fff 100%);background-image:linear-gradient(to top,#e6e6e6 0,#fff 100%);-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;cursor:pointer}.dhx_btn_set div{float:left;font-size:13px;height:20px;line-height:20px;background-repeat:no-repeat;vertical-align:middle}.dhx_save_btn{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QkE5Nzc4RENDMzAzMTFFMjk4QjNBODhDMUM4QUUwNEQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QkE5Nzc4RERDMzAzMTFFMjk4QjNBODhDMUM4QUUwNEQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpCQTk3NzhEQUMzMDMxMUUyOThCM0E4OEMxQzhBRTA0RCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpCQTk3NzhEQkMzMDMxMUUyOThCM0E4OEMxQzhBRTA0RCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pr5Ot2UAAAFoSURBVHjapFO7SgNRED0rxkjEELbcQksLBUMg4EfkG7YV/AFTpBAUfNQisVDQD7CQlBaWwoZ8gNEiFttmg3GTfbF6JtzFTUJYyIHhDnfOnJm5Dy2OYywDjQJnF5ex/dXDweENNtaAzT/jur46IY1D4McHvv3J+nZ7BGNrG436sSaU3ucHqtUqzP1sVcflMizLEl8EwjBEFEXwomwC5DInEeDGaDSC62cTIJc5iUAQBGJukE1A8YkVNYLv++h232WMRUYOuakRlOLTwzU8z1tYPZ/Po1QqJR0kAgQDWTEjwLaIWq0GwzDmJtm2jVarNSvAeeQQXVe6ME1Tgs1mMyXA2GAwQKFQmH8G/X4fjuOgWCxKkP40yMnlcukO1MNgAivoui5B+tMgh3H1DuQa66fnaLfbGA6HMgY7oNGfNnL+v0RN/cbnl9f46qSBSqUiM9J4ZQSvVgl0Oh1pf2d3D4/3d5q27Hf+FWAAc90EKSR5k78AAAAASUVORK5CYII=);margin-top:2px;width:21px}.dhx_cancel_btn{margin-top:2px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QzU4NTQ3NUZDMzAzMTFFMkE0MjRGNTQzQjE0MTNDQkIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QzU4NTQ3NjBDMzAzMTFFMkE0MjRGNTQzQjE0MTNDQkIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpDNTg1NDc1REMzMDMxMUUyQTQyNEY1NDNCMTQxM0NCQiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpDNTg1NDc1RUMzMDMxMUUyQTQyNEY1NDNCMTQxM0NCQiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PkVB3hoAAAEYSURBVHjapJM9CsJAEIUTFStbQRA8gKCNKSIIeohUVraC9oKiGBRsBDvBylbwEkJQCys9gJUgeIOAxDfwEpYNpsmDL7PZ+cn+TMwgCIw0yhgplZPH3bbVuRqYgiYogxe4ABc8wqD69RpbQR6MwQ04TDZoHc6LP/tvC2uw4Fi+Vgcmrct58W9iW4BaYAB80OGSQ8my7xz7jDsAT11Bn3alJYvUa1pp8VGBNu0uIVm2s9fiowJF8OWJ/0sWPRlX1At8eLqlhGRRhXEfvcCJtpeQLOpq8VGBLe04Ibmh+Ld6AY8HWOBVzUCVvio780z/LrxCtQ9EQ+5tBOZElRzeUmmqWCfKlyfAAkfw5vyb7xb9vlrATPs7/wQYAISgQGDaq6hUAAAAAElFTkSuQmCC);width:20px}.dhx_delete_btn{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RTA3M0M1MzJDMzAzMTFFMkE5ODZDRjhENzQ2MUZFNzkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RTA3M0M1MzNDMzAzMTFFMkE5ODZDRjhENzQ2MUZFNzkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpFMDczQzUzMEMzMDMxMUUyQTk4NkNGOEQ3NDYxRkU3OSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpFMDczQzUzMUMzMDMxMUUyQTk4NkNGOEQ3NDYxRkU3OSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pplu0sEAAAErSURBVHja3FOxboMwED0nIQNbJoaOZM0YyMhGqwz8BOQ3mm5I/AHwCayoGVlhZUSMHTIhsSCEhOtzawRIlZDY+qTTs893z6fzmVBKYQ12YhEEweB0HAf3tCxLUFUVXWSeiDGIzW/ynRE9HA7coihCBtd1AVn40TCO2X1ewbthGCBJEiiKAtvtFggh0HUdWJYFfd9zez6f3JckiS1EhEDmeZ623+9BlmWejCaAfWqahou0bQumab7MK9DP5zM9nU5c4Hg8ch4nF0XBOc9zuF6vg/pm3pw0TSdNDcPwp8QsG2LiOIY/BZagqqp1AmP8M4Gvuq5B1/XJqNq2zVnTNMBzjBsLEHxnHBrf91/Z/nPBpW+32+0hPuFODAt79wtbfiwQuLD4x6SCNfgWYAAfQYJFsOV+5AAAAABJRU5ErkJggg==);margin-top:2px;width:20px}.dhx_cal_cover{width:100%;height:100%;position:absolute;z-index:10000;top:0;left:0;background-color:#000;opacity:.1;filter:alpha(opacity=10)}.dhx_custom_button{padding:0 3px;font-family:Tahoma;font-size:11px;font-weight:400;margin-right:5px;margin-top:0;cursor:pointer}.dhx_custom_button div{cursor:pointer;float:left;height:21px;line-height:21px;vertical-align:middle}.dhx_cal_light_wide{width:580px;padding:2px 4px}.dhx_cal_light_wide .dhx_cal_larea{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #a4bed4}.dhx_cal_light_wide .dhx_cal_lsection{border:0;float:left;text-align:right;width:80px;height:20px;padding:5px 10px 0 0}.dhx_cal_light_wide .dhx_wrap_section{position:relative;padding:10px 0;overflow:hidden;border-bottom:1px solid #ebebeb}.dhx_cal_light_wide .dhx_section_time{overflow:hidden;padding-top:2px!important;padding-right:0;height:20px!important}.dhx_cal_light_wide .dhx_cal_ltext{padding-right:0}.dhx_cal_light_wide .dhx_cal_larea{padding:0 10px;width:100%}.dhx_cal_light_wide .dhx_section_time{background:transparent}.dhx_cal_light_wide .dhx_cal_checkbox label{padding-left:0}.dhx_cal_light_wide .dhx_cal_lsection .dhx_fullday{float:none;margin-right:0;font-weight:700;cursor:pointer}.dhx_cal_light_wide .dhx_custom_button{position:absolute;top:0;right:0;margin-top:2px}.dhx_cal_light_wide .dhx_repeat_right{margin-right:55px}.dhx_cal_light_wide.dhx_cal_light_full{width:738px}.dhx_cal_wide_checkbox input{margin-top:8px;margin-left:14px}.dhx_cal_light input{font-size:11px}.dhx_custom_button{float:right;height:21px;width:90px;-webkit-border-radius:4px;-moz-border-radius:4px;-ms-border-radius:4px;-o-border-radius:4px;border-radius:4px}.dhx_section_time{background-color:#fff;white-space:nowrap;padding:2px 10px 5px;padding-top:2px!important}.dhx_section_time .dhx_time_selects{float:left;height:25px}.dhx_section_time .dhx_time_selects select{height:23px;padding:2px;border:1px solid #a4bed4}.dhx_gantt_duration{width:100px;height:23px;float:left;white-space:nowrap;margin-left:20px;line-height:23px}.dhx_gantt_duration .dhx_gantt_duration_value,.dhx_gantt_duration .dhx_gantt_duration_dec,.dhx_gantt_duration .dhx_gantt_duration_inc{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;vertical-align:top;height:100%;border:1px solid #a4bed4}.dhx_gantt_duration .dhx_gantt_duration_value{width:40px;padding:3px 4px;border-left-width:0;border-right-width:0}.dhx_gantt_duration .dhx_gantt_duration_dec,.dhx_gantt_duration .dhx_gantt_duration_inc{width:20px;padding:1px;padding-bottom:3px;background-color:#f8f8f8;background-image:-webkit-linear-gradient(top,#e6e6e6 0,#fff 100%);background-image:-moz-linear-gradient(top,#e6e6e6 0,#fff 100%);background-image:-ms-linear-gradient(top,#e6e6e6 0,#fff 100%);background-image:linear-gradient(to top,#e6e6e6 0,#fff 100%)}.dhx_gantt_duration .dhx_gantt_duration_dec{-moz-border-top-left-radius:4px;-moz-border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;border-top-left-radius:4px;border-bottom-left-radius:4px}.dhx_gantt_duration .dhx_gantt_duration_inc{margin-right:4px;-moz-border-top-right-radius:4px;-moz-border-bottom-right-radius:4px;-webkit-border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:4px}.dhx_cal_quick_info{border:1px solid #a4bed4;border-radius:0;position:absolute;z-index:300;box-shadow:3px 3px 3px rgba(0,0,0,.07);background-color:#fff;width:300px;transition:left .5s ease,right .5s;-moz-transition:left .5s ease,right .5s;-webkit-transition:left .5s ease,right .5s;-o-transition:left .5s ease,right .5s}.dhx_no_animate{transition:none;-moz-transition:none;-webkit-transition:none;-o-transition:none}.dhx_cal_quick_info.dhx_qi_left .dhx_qi_big_icon{float:right}.dhx_cal_qi_title{-webkit-border-top-left-radius:0;-webkit-border-bottom-left-radius:0;-webkit-border-top-right-radius:0;-webkit-border-bottom-right-radius:0;-moz-border-radius-topleft:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0;border-top-left-radius:0;border-bottom-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:0;padding:5px 0 8px 12px;color:#1e2022;box-shadow:0 1px 1px #fff inset;background-color:#dfedff;background:-webkit-gradient(linear,left top,left bottom,color-stop(0%,#e4f0ff),color-stop(50%,#dfedff),color-stop(100%,#d5e8ff));background-image:-webkit-linear-gradient(top,#e4f0ff 0,#dfedff 50%,#d5e8ff 100%);background-image:-moz-linear-gradient(top,#e4f0ff 0,#dfedff 60%,#d5e8ff 100%);background-image:-ms-linear-gradient(top,#e4f0ff 0,#dfedff 60%,#d5e8ff 100%);background-image:-o-linear-gradient(top,#e4f0ff 0,#dfedff 60%,#d5e8ff 100%);background-position:0 1px;background-repeat:repeat-x;border-bottom:1px solid #a4bed4}.dhx_cal_qi_tdate{font-size:14px;font-weight:700}.dhx_cal_qi_tcontent{font-size:11px}.dhx_cal_qi_content{padding:16px 8px;font-size:13px;color:#1e2022;overflow:hidden}.dhx_cal_qi_controls{-webkit-border-top-left-radius:0;-webkit-border-bottom-left-radius:0;-webkit-border-top-right-radius:0;-webkit-border-bottom-right-radius:0;-moz-border-radius-topleft:0;-moz-border-radius-bottomleft:0;-moz-border-radius-topright:0;-moz-border-radius-bottomright:0;border-top-left-radius:0;border-bottom-left-radius:0;border-top-right-radius:0;border-bottom-right-radius:0;padding-left:7px}.dhx_cal_qi_controls .dhx_menu_icon{margin-top:3px;background-repeat:no-repeat}.dhx_cal_qi_controls .dhx_menu_icon.icon_edit{width:20px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH3QYFDhEuX3ujFgAAAFNJREFUOMvt0zEOACAIA0DksTyqn8XJTRTKZGJXyaWEKPKTCQAH4Ls37cItcDUzsxHNDLZNhCq7Gt1wh9ErV7EjyGAhyGLphlnsClWuS32rn0czAV+sNUIROnQoAAAAAElFTkSuQmCC)}.dhx_cal_qi_controls .dhx_menu_icon.icon_delete{width:20px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RTA3M0M1MzJDMzAzMTFFMkE5ODZDRjhENzQ2MUZFNzkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RTA3M0M1MzNDMzAzMTFFMkE5ODZDRjhENzQ2MUZFNzkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpFMDczQzUzMEMzMDMxMUUyQTk4NkNGOEQ3NDYxRkU3OSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpFMDczQzUzMUMzMDMxMUUyQTk4NkNGOEQ3NDYxRkU3OSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pplu0sEAAAErSURBVHja3FOxboMwED0nIQNbJoaOZM0YyMhGqwz8BOQ3mm5I/AHwCayoGVlhZUSMHTIhsSCEhOtzawRIlZDY+qTTs893z6fzmVBKYQ12YhEEweB0HAf3tCxLUFUVXWSeiDGIzW/ynRE9HA7coihCBtd1AVn40TCO2X1ewbthGCBJEiiKAtvtFggh0HUdWJYFfd9zez6f3JckiS1EhEDmeZ623+9BlmWejCaAfWqahou0bQumab7MK9DP5zM9nU5c4Hg8ch4nF0XBOc9zuF6vg/pm3pw0TSdNDcPwp8QsG2LiOIY/BZagqqp1AmP8M4Gvuq5B1/XJqNq2zVnTNMBzjBsLEHxnHBrf91/Z/nPBpW+32+0hPuFODAt79wtbfiwQuLD4x6SCNfgWYAAfQYJFsOV+5AAAAABJRU5ErkJggg==)}.dhx_qi_big_icon{font-size:13px;border-radius:4px;color:#42464b;background-color:#f8f8f8;background-image:-webkit-linear-gradient(top,#e6e6e6 0,#fff 100%);background-image:-moz-linear-gradient(top,#e6e6e6 0,#fff 100%);background-image:-ms-linear-gradient(top,#e6e6e6 0,#fff 100%);background-image:linear-gradient(to top,#e6e6e6 0,#fff 100%);margin:5px 9px 8px 0;min-width:60px;line-height:26px;vertical-align:middle;padding:0 10px 0 5px;cursor:pointer;border:1px solid #a4bed4}.dhx_cal_qi_controls div{float:left;height:26px;text-align:center;line-height:26px}.gantt_tooltip{box-shadow:3px 3px 3px rgba(0,0,0,.07);background-color:#fff;border-left:1px solid rgba(0,0,0,.07);border-top:1px solid rgba(0,0,0,.07);font-family:Tahoma;font-size:8pt;color:#1e2022;padding:10px;position:absolute;z-index:500}.gantt_noselect{-moz-user-select:-moz-none;-webkit-user-select:none;-user-select:none}.gantt_drag_marker{position:absolute;font-family:Tahoma;font-size:11px}.gantt_drag_marker .gantt_tree_indent,.gantt_drag_marker .gantt_tree_icon.gantt_blank,.gantt_drag_marker .gantt_tree_icon.gantt_open,.gantt_drag_marker .gantt_tree_icon.gantt_close{display:none}.gantt_drag_marker,.gantt_drag_marker .gantt_row.odd{background-color:#fff}.gantt_drag_marker .gantt_row{border-left:1px solid #d2d2d2;border-top:1px solid #d2d2d2}.gantt_drag_marker .gantt_cell{border-color:#d2d2d2}.gantt_row.gantt_over,.gantt_task_row.gantt_over{background-color:#0070fe}.gantt_row.gantt_transparent .gantt_cell{opacity:.7}.gantt_task_row.gantt_transparent{background-color:#e4f0ff}
\ No newline at end of file
/*
This software is allowed to use under GPL or you need to obtain Commercial or Enterise License
to use it in non-GPL project. Please contact sales@dhtmlx.com for details
*/
.gridHoverStyle{background-color:#fff3a1}.gridSelection{background-color:#fff3a1}.timelineSelection{background-color:#fff3a1}.gantt_grid_scale .gantt_grid_head_cell{color:#a6a6a6;border-top:0!important;border-right:0!important}.gantt_grid_data .gantt_cell{border-right:0;color:#454545}.gantt_task_link .gantt_link_arrow_right{border-width:6px;margin-top:-3px}.gantt_task_link .gantt_link_arrow_left{border-width:6px;margin-left:-6px;margin-top:-3px}.gantt_task_link .gantt_link_arrow_top{border-width:6px}.gantt_task_link .gantt_link_arrow_down{border-width:6px}.gantt_task_line .gantt_task_progress_drag{bottom:-4px;height:16px;margin-left:-8px;width:16px}.chartHeaderBg{background-color:#fff}.gantt_task .gantt_task_scale .gantt_scale_cell{color:#a6a6a6;border-right:1px solid #ebebeb}.gantt_row.gantt_project,.gantt_row.odd.gantt_project{background-color:#edffef}.gantt_task_row.gantt_project,.gantt_task_row.odd.gantt_project{background-color:#f5fff6}.gantt_task_line.gantt_project{background-color:#65c16f;border:1px solid #3c9445}.gantt_task_line.gantt_project .gantt_task_progress{background-color:#46ad51}.buttonBg{background:#fff}.dhx_cal_light .dhx_btn_set{margin:5px 10px}.dhx_btn_set.dhx_cancel_btn_set{background:#fff;color:#454545;border:1px solid #cecece}.dhx_btn_set.dhx_save_btn_set{background:#3db9d3;text-shadow:0 -1px 0 #248a9f;color:#fff}.dhx_btn_set.dhx_delete_btn_set{text-shadow:0 -1px 0 #6f6f6f;background:#ec8e00;text-shadow:0 -1px 0 #a60;color:#fff}.dhx_delete_btn{margin-top:2px;width:20px}.dhx_cal_light_wide{padding-left:0!important;padding-right:0!important}.dhx_cal_light_wide .dhx_cal_larea{border-left:0!important;border-right:0!important}.dhtmlx_popup_button.dhtmlx_ok_button{background:#3db9d3;text-shadow:0 -1px 0 #248a9f;color:#fff;font-weight:700;border-width:0}.dhtmlx_popup_button.dhtmlx_cancel_button{font-weight:700;color:#454544}.dhx_qi_big_icon.icon_edit{color:#454545;background:#fff}.dhx_qi_big_icon.icon_delete{text-shadow:0 -1px 0 #a60;background:#ec8e00;color:#fff;border-width:0}.gantt_container{font-family:Arial;font-size:13px;border:1px solid #cecece;position:relative;white-space:nowrap}.gantt_grid{border-right:1px solid #cecece}.gantt_task_scroll{overflow-x:scroll}.gantt_task{position:relative}.gantt_task,.gantt_grid{overflow-x:hidden;overflow-y:hidden;display:inline-block;vertical-align:top}.gantt_grid_scale,.gantt_task_scale{color:#6b6b6b;font-size:12px;border-bottom:1px solid #cecece}.gantt_grid_scale{background-color:#fff}.gantt_task_scale{background-color:#fff}.gantt_scale_line{box-sizing:border-box;-moz-box-sizing:border-box;border-top:1px solid #cecece}.gantt_scale_line:first-child{border-top:0}.gantt_grid_head_cell{display:inline-block;vertical-align:top;border-right:1px solid #cecece;text-align:center;position:relative;cursor:default;height:100%;box-sizing:border-box;-moz-box-sizing:border-box;line-height:33px;-moz-user-select:-moz-none;-webkit-user-select:none;-user-select:none}.gantt_scale_line{clear:both}.gantt_grid_data{width:100%;overflow:hidden}.gantt_row{position:relative;-webkit-user-select:none;-moz-user-select:none;-moz-user-select:-moz-none}.gantt_add,.gantt_grid_head_add{width:100%;height:100%;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTQ3MjMyMENDNkI0MTFFMjk4MTI5QTg3MDhFNDVDQTkiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTQ3MjMyMERDNkI0MTFFMjk4MTI5QTg3MDhFNDVDQTkiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1NDcyMzIwQUM2QjQxMUUyOTgxMjlBODcwOEU0NUNBOSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1NDcyMzIwQkM2QjQxMUUyOTgxMjlBODcwOEU0NUNBOSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PshZT8UAAABbSURBVHjaYrTdeZmBEsCER+4wEP+H4sPkGGCDg020ARR7gb4GIAcYDKMDdPnDyAbYkGG5DVW9cIQMvUdBBAuUY4vDz8iAcZinA2zgCHqAYQMseAywJcYFAAEGAM+UFGuohFczAAAAAElFTkSuQmCC);background-position:center center;background-repeat:no-repeat;cursor:pointer;position:relative;-moz-opacity:.3;opacity:.3}.gantt_grid_head_cell.gantt_grid_head_add{-moz-opacity:.6;opacity:.6;top:0}.gantt_grid_head_cell.gantt_grid_head_add:hover{-moz-opacity:1;opacity:1}.gantt_grid_data .gantt_row:hover,.gantt_grid_data .gantt_row.odd:hover{background-color:#fff3a1}.gantt_grid_data .gantt_row:hover .gantt_add,.gantt_grid_data .gantt_row.odd:hover .gantt_add{-moz-opacity:1;opacity:1}.gantt_task_row,.gantt_row{border-bottom:1px solid #ebebeb}.gantt_row,.gantt_task_row{background-color:#fff}.gantt_row.odd,.gantt_task_row.odd{background-color:#fff}.gantt_row,.gantt_cell,.gantt_task_row,.gantt_task_cell,.gantt_grid_head_cell,.gantt_scale_cell{box-sizing:border-box;-moz-box-sizing:border-box}.gantt_grid_head_cell,.gantt_scale_cell{line-height:inherit}.gantt_cell{display:inline-block;vertical-align:top;border-right:1px solid #ebebeb;padding-left:6px;padding-right:6px;height:100%;overflow:hidden;white-space:nowrap;font-size:13px}.gantt_grid_scale .gantt_last_cell,.gantt_grid_data .gantt_last_cell,.gantt_task_scale .gantt_last_cell,.gantt_task_bg .gantt_last_cell{border-right-width:0}.gantt_task_bg{overflow:hidden}.gantt_scale_cell{display:inline-block;white-space:nowrap;overflow:hidden;border-right:1px solid #cecece;text-align:center;height:100%}.gantt_task_cell{display:inline-block;height:100%;border-right:1px solid #ebebeb}.gantt_ver_scroll{width:0;background-color:transparent;height:1px;overflow-x:hidden;overflow-y:scroll;display:none;position:absolute;right:0}.gantt_ver_scroll>div{width:1px;height:1px}.gantt_hor_scroll{height:0;background-color:transparent;width:100%;clear:both;overflow-x:scroll;overflow-y:hidden;display:none}.gantt_hor_scroll>div{width:5000px;height:1px}.gantt_tree_indent{width:15px;height:100%;display:inline-block}.gantt_tree_content,.gantt_tree_icon{vertical-align:top}.gantt_tree_icon{width:28px;height:100%;display:inline-block;background-repeat:no-repeat;background-position:center center}.gantt_tree_content{height:100%;display:inline-block}.gantt_tree_icon.gantt_open{background-image:url(data:image/gif;base64,R0lGODlhEgASALMJAMrKyt3d3ejp6d7f3+/v75aWlvf39////wAAAP///wAAAAAAAAAAAAAAAAAAAAAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6M0I5RTczQjVDMDdBMTFFMTgxRjc4Mzk4M0Q3MjVFQzAiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6M0I5RTczQjZDMDdBMTFFMTgxRjc4Mzk4M0Q3MjVFQzAiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozQjlFNzNCM0MwN0ExMUUxODFGNzgzOTgzRDcyNUVDMCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDozQjlFNzNCNEMwN0ExMUUxODFGNzgzOTgzRDcyNUVDMCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAkALAAAAAASABIAAARJMMlJq704661B+SAIXAVhnKhBFKSZnmuLImhslXPN3ibi+6pdBXc4IIpB2YkGE1IKAoL0ICUInJNCYMDtDgJYiScUGnHO6LQkAgA7);width:18px;cursor:pointer}.gantt_tree_icon.gantt_close{background-image:url(data:image/gif;base64,R0lGODlhEgASALMJAMrKyt3d3ejp6d7f3+/v75aWlvf39wAAAP///////wAAAAAAAAAAAAAAAAAAAAAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MzY0QzNGM0VDMDdBMTFFMUE3MDlCNUM2QjU1NDA5RjgiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MzY0QzNGM0ZDMDdBMTFFMUE3MDlCNUM2QjU1NDA5RjgiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDozNjRDM0YzQ0MwN0ExMUUxQTcwOUI1QzZCNTU0MDlGOCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDozNjRDM0YzREMwN0ExMUUxQTcwOUI1QzZCNTU0MDlGOCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAkALAAAAAASABIAAARDMMlJq704661B+SAIXAVhnKhBFKSZnmv7wqxVzmpd3Uff5zKEUAi0uV4xm4DAbBIEOkohMKhaB4HoxBMKjTjgsFgSAQA7);width:18px;cursor:pointer}.gantt_tree_icon.gantt_blank{width:18px}.gantt_tree_icon.gantt_folder_open{background-image:url(data:image/gif;base64,R0lGODlhEgASAJECAJeXl7Gvrf///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTdDRDM3QzVDMDZEMTFFMUJGMzhFMDhCN0RGRjBGQ0YiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTdDRDM3QzZDMDZEMTFFMUJGMzhFMDhCN0RGRjBGQ0YiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1N0NEMzdDM0MwNkQxMUUxQkYzOEUwOEI3REZGMEZDRiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1N0NEMzdDNEMwNkQxMUUxQkYzOEUwOEI3REZGMEZDRiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAASABIAAAIzlI+pywcPm3mhWgkCsjBOvVkimElG9ZlCBlXd+2XjjLKg5GqoeZXqvsOQXK/ijUZTKVUFADs=)}.gantt_tree_icon.gantt_folder_closed{background-image:url(data:image/gif;base64,R0lGODlhEgASAJECAJeXl7Gvrf///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTAyMTU1RTNDMDZEMTFFMUJGNzZCRThBRkFCRjg4MTIiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTAyMTU1RTRDMDZEMTFFMUJGNzZCRThBRkFCRjg4MTIiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1MDIxNTVFMUMwNkQxMUUxQkY3NkJFOEFGQUJGODgxMiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1MDIxNTVFMkMwNkQxMUUxQkY3NkJFOEFGQUJGODgxMiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAASABIAAAIwlI+pywcPm3mhWgkCsjBOvVkimElG9ZlCuYIY6TYs+6bmHDO4igfdD3GNhheV0VQAADs=)}.gantt_tree_icon.gantt_file{background-image:url(data:image/gif;base64,R0lGODlhEgASAJECAJeXl7Gvrf///wAAACH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjAgNjEuMTM0Nzc3LCAyMDEwLzAyLzEyLTE3OjMyOjAwICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIiB4bWxuczpzdFJlZj0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlUmVmIyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ1M1IFdpbmRvd3MiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NzkxQzI4RjZDMDZEMTFFMTgwRjhBQURDQzI3NDU3QUEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NzkxQzI4RjdDMDZEMTFFMTgwRjhBQURDQzI3NDU3QUEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo3OTFDMjhGNEMwNkQxMUUxODBGOEFBRENDMjc0NTdBQSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo3OTFDMjhGNUMwNkQxMUUxODBGOEFBRENDMjc0NTdBQSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAEAAAIALAAAAAASABIAAAIylI+pwN16QJiUQiFThRlJm3RRFYSlR5qXMKmXaMDuuMoyOi8n/e6xn8NMHETgh5RaKQsAOw==)}.gantt_grid_head_cell .gantt_sort{position:absolute;right:5px;top:8px;width:7px;height:13px;background-repeat:no-repeat;background-position:center center}.gantt_grid_head_cell .gantt_sort.gantt_asc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAANCAYAAABlyXS1AAAARUlEQVR4nGNgQAKGxib/GbABkIS7b8B/DAUwCRiGK0CXwFBAb1DfP/U/LszwHwi2X7qFgUEArBtdAVwCBmAKMCSQFSDzAWXXaOHsXeqkAAAAAElFTkSuQmCC)}.gantt_grid_head_cell .gantt_sort.gantt_desc{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAANCAYAAABlyXS1AAAARUlEQVR42mNgQAL1/VP/M2ADIIntF2/9x1AAlrh0C47hCmA60DFYwX88gIFGwNDY5D8uDFbg7hvwHx2jmIBTAlkB0e4BAEjlaNtBWJPnAAAAAElFTkSuQmCC)}.gantt_inserted,.gantt_updated{font-weight:700}.gantt_deleted{text-decoration:line-through}.gantt_invalid{background-color:FFE0E0}.gantt_error{color:red}.dhtmlx_message_area{position:fixed;right:5px;width:250px;z-index:1000}.dhtmlx-info{min-width:120px;padding:4px 4px 4px 20px;font-family:Arial;z-index:10000;margin:5px;margin-bottom:10px;-webkit-transition:all .5s ease;-moz-transition:all .5s ease;-o-transition:all .5s ease;transition:all .5s ease}.dhtmlx-info.hidden{height:0;padding:0;border-width:0;margin:0;overflow:hidden}.dhtmlx_modal_box{overflow:hidden;display:inline-block;min-width:250px;width:250px;text-align:center;position:fixed;z-index:20000;box-shadow:3px 3px 3px rgba(0,0,0,.07);font-family:Arial;border-radius:6px;border:1px solid #cecece;background:#fff}.dhtmlx_popup_title{border-top-left-radius:6px;border-top-right-radius:6px;border-width:0}.dhtmlx_button,.dhtmlx_popup_button{border:1px solid #cecece;height:30px;line-height:30px;display:inline-block;margin:0 5px;border-radius:4px;background:#fff}.dhtmlx-info,.dhtmlx_popup_button,.dhtmlx_button{user-select:none;-webkit-user-select:none;-moz-user-select:-moz-none;cursor:pointer}.dhtmlx_popup_text{overflow:hidden}.dhtmlx_popup_controls{border-radius:6px;padding:10px}.dhtmlx_popup_button{min-width:100px}div.dhx_modal_cover{background-color:#000;cursor:default;filter:alpha(opacity=20);opacity:.2;position:fixed;z-index:19999;left:0;top:0;width:100%;height:100%;border:0;zoom:1}.dhtmlx-info img,.dhtmlx_modal_box img{float:left;margin-right:20px}.dhtmlx-alert-error,.dhtmlx-confirm-error{border:1px solid red}.dhtmlx_button input,.dhtmlx_popup_button div{border-radius:4px;font-size:14px;-moz-box-sizing:content-box;box-sizing:content-box;padding:0;margin:0;vertical-align:top}.dhtmlx_popup_title{color:#fff;text-shadow:1px 1px #000;height:40px;line-height:40px;font-size:20px}.dhtmlx_popup_text{margin:15px 15px 5px;font-size:14px;color:#000;min-height:30px;border-radius:6px}.dhtmlx-info,.dhtmlx-error{font-size:14px;color:#000;box-shadow:3px 3px 3px rgba(0,0,0,.07);padding:0;background-color:#FFF;border-radius:3px;border:1px solid #fff}.dhtmlx-info div{padding:5px 10px;background-color:#fff;border-radius:3px;border:1px solid #cecece}.dhtmlx-error{background-color:#d81b1b;border:1px solid #ff3c3c;box-shadow:3px 3px 3px rgba(0,0,0,.07)}.dhtmlx-error div{background-color:#d81b1b;border:1px solid #940000;color:#FFF}.gantt_grid div,.gantt_data_area div{-ms-touch-action:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}.gantt_data_area{position:relative;overflow-x:hidden;overflow-y:hidden;-moz-user-select:-moz-none;-webkit-user-select:none;-user-select:none}.gantt_links_area{position:absolute;left:0;top:0}.gantt_task_content,.gantt_task_progress,.gantt_side_content{line-height:inherit;overflow:hidden;height:100%}.gantt_task_content{font-size:12px;color:#fff;position:absolute;white-space:nowrap;text-align:center}.gantt_task_progress{text-align:center;z-index:0;background:#299cb4}.gantt_task_line{-webkit-border-radius:2px;-moz-border-radius:2px;border-radius:2px;position:absolute;-moz-box-sizing:border-box;box-sizing:border-box;background-color:#3db9d3;border:1px solid #2898b0;-webkit-user-select:none;-moz-user-select:none;-moz-user-select:-moz-none}.gantt_task_line.gantt_drag_move div{cursor:move}.gantt_side_content{position:absolute;white-space:nowrap;color:#6e6e6e;bottom:7px;font-size:11px}.gantt_side_content.gantt_left{right:100%;padding-right:15px}.gantt_side_content.gantt_right{left:100%;padding-left:15px}.gantt_side_content.gantt_link_crossing{bottom:8.75px}.gantt_task_link .gantt_line_wrapper,.gantt_link_arrow{position:absolute;cursor:pointer}.gantt_line_wrapper div{background-color:#ffa011}.gantt_task_link:hover .gantt_line_wrapper div{box-shadow:0 0 5px 0 #ffa011}.gantt_task_link div.gantt_link_arrow{background-color:transparent;border-style:solid;width:0;height:0}.gantt_link_control{position:absolute;width:13px;top:0}.gantt_link_control div{display:none;cursor:pointer;box-sizing:border-box;position:relative;top:50%;margin-top:-7.5px;vertical-align:middle;border:1px solid #929292;-webkit-border-radius:6.5px;-moz-border-radius:6.5px;border-radius:6.5px;height:13px;width:13px;background-color:#f0f0f0}.gantt_link_control div:hover{background-color:#fff}.gantt_link_control.task_left{left:-13px}.gantt_link_control.task_right{right:-13px}.gantt_task_line.gantt_selected .gantt_link_control div,.gantt_task_line:hover .gantt_link_control div{display:block}.gantt_link_target .gantt_link_control div{display:block}.gantt_link_source,.gantt_link_target{box-shadow:0 0 3px #3db9d3}.gantt_link_target.link_start_allow,.gantt_link_target.link_finish_allow{box-shadow:0 0 3px #ffbf5e}.gantt_link_target.link_start_deny,.gantt_link_target.link_finish_deny{box-shadow:0 0 3px #e87e7b}.link_start_allow .gantt_link_control.task_left div,.link_finish_allow .gantt_link_control.task_right div{background-color:#ffbf5e;border-color:#ffa011}.link_start_deny .gantt_link_control.task_left div,.link_finish_deny .gantt_link_control.task_right div{background-color:#e87e7b;border-color:#dd3e3a}.gantt_link_arrow_right{border-width:4px 0 4px 6px;border-color:transparent transparent transparent #ffa011;margin-top:-1px}.gantt_link_arrow_left{border-width:4px 6px 4px 0;margin-top:-1px;border-color:transparent #ffa011 transparent transparent}.gantt_link_arrow_top{border-width:0 4px 6px;border-color:transparent transparent #ffa011}.gantt_link_arrow_down{border-width:4px 6px 0 4px;border-color:#ffa011 transparent transparent}.gantt_task_drag,.gantt_task_progress_drag{cursor:w-resize;height:100%;display:none;position:absolute}.gantt_task_line.gantt_selected .gantt_task_progress_drag,.gantt_task_line:hover .gantt_task_progress_drag,.gantt_task_line.gantt_selected .gantt_task_drag,.gantt_task_line:hover .gantt_task_drag{display:block}.gantt_task_drag{width:6px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAACCAYAAAB7Xa1eAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYDDjkw3UJvAwAAABRJREFUCNdj/P//PwM2wASl/6PTAKrrBf4+lD8LAAAAAElFTkSuQmCC);z-index:1;top:0}.gantt_task_drag.task_left{left:0}.gantt_task_drag.task_right{right:0}.gantt_task_progress_drag{height:8px;width:8px;bottom:-4px;margin-left:-4px;background-position:bottom;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAALCAYAAAB24g05AAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MkY3Rjk0RUVDMkYzMTFFMkI1OThEQTA3ODU0OTkzMEEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MkY3Rjk0RUZDMkYzMTFFMkI1OThEQTA3ODU0OTkzMEEiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyRjdGOTRFQ0MyRjMxMUUyQjU5OERBMDc4NTQ5OTMwQSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyRjdGOTRFREMyRjMxMUUyQjU5OERBMDc4NTQ5OTMwQSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PobPBzIAAADkSURBVHjaYpk2bRoDDsAExL1QdjEQ/8OmiAWHZk4gXqymqhQM4ty6fU8OSMUA8XdiDBAB4k0a6iqWRga6EKcwMQXduHlnL5DpB8Rv0J2JDFSA+JiOtgZcMwiA2CAxkBxUDVYDLEAKgIpV9XQ0MZwFEgPJAZnHoWpRDAgC4n2W5saiQKfjClQGkBxQDciL+6B6wAbkA/EqJwdrTkUFOQZCAKQGpBbIXA3SCzJggo+XK7OEuBgDsQCkFqgHrBfsBT5eHgZSAUwP2IBfv36TbABMDygdtK1Zv6UESLORaAbIhG6AAAMAKN8wE24DXWcAAAAASUVORK5CYII=);background-repeat:no-repeat;z-index:2}.gantt_link_tooltip{box-shadow:3px 3px 3px #888;background-color:#fff;border-left:1px dotted #cecece;border-top:1px dotted #cecece;font-family:Tahoma;font-size:8pt;color:#444;padding:6px;line-height:20px}.gantt_link_direction{height:0;border:0 none #ffa011;border-bottom-style:dashed;border-bottom-width:2px;transform-origin:0 0;-ms-transform-origin:0 0;-webkit-transform-origin:0 0;z-index:2;margin-left:1px;position:absolute}.gantt_grid_data .gantt_row.gantt_selected,.gantt_grid_data .gantt_row.odd.gantt_selected{background-color:#fff3a1}.gantt_task_row.gantt_selected{background-color:#fff3a1}.gantt_task_row.gantt_selected .gantt_task_cell{border-right-color:#ffec6e}.gantt_task_line.gantt_selected{box-shadow:0 0 5px #299cb4}.gantt_task_line.gantt_project.gantt_selected{box-shadow:0 0 5px #46ad51}.dhx_unselectable,.dhx_unselectable div{-webkit-user-select:none;-moz-user-select:none;-moz-user-select:-moz-none}.dhx_cal_light{-webkit-tap-highlight-color:transparent;background:#fff;border-radius:6px;font-family:Arial;font-size:13px;border:1px solid #cecece;color:#6b6b6b;font-size:12px;position:absolute;z-index:10001;width:550px;height:250px;box-shadow:3px 3px 3px rgba(0,0,0,.07)}.dhx_cal_light_wide{width:650px}.dhx_cal_light select{font-family:Arial;border:1px solid #cecece;font-size:13px;padding:2px;margin:0}.dhx_cal_ltitle{padding:7px 10px;overflow:hidden;white-space:nowrap;-webkit-border-top-left-radius:6px;-webkit-border-bottom-left-radius:0;-webkit-border-top-right-radius:6px;-webkit-border-bottom-right-radius:0;-moz-border-radius-topleft:6px;-moz-border-radius-bottomleft:0;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:0;border-top-left-radius:6px;border-bottom-left-radius:0;border-top-right-radius:6px;border-bottom-right-radius:0}.dhx_cal_ltitle span{white-space:nowrap}.dhx_cal_lsection{color:#727272;font-weight:700;padding:12px 0 5px 10px}.dhx_cal_lsection .dhx_fullday{float:right;margin-right:5px;font-size:12px;font-weight:400;line-height:20px;vertical-align:top;cursor:pointer}.dhx_cal_lsection{font-size:13px}.dhx_cal_ltext{padding:2px 10px;overflow:hidden}.dhx_cal_ltext textarea{overflow:auto;font-family:Arial;font-size:13px;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #cecece;height:100%;width:100%;outline:0!important;resize:none}.dhx_time{font-weight:700}.dhx_cal_light .dhx_title{padding-left:10px}.dhx_cal_larea{border:1px solid #cecece;border-left:0;border-right:0;background-color:#fff;overflow:hidden;height:1px}.dhx_btn_set{margin:10px 7px 5px 10px;padding:5px 15px 5px 10px;float:left;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;border-width:0;border-color:#cecece;border-style:solid;height:32px;font-weight:700;background:#fff;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;cursor:pointer}.dhx_btn_set div{float:left;font-size:13px;height:22px;line-height:22px;background-repeat:no-repeat;vertical-align:middle}.dhx_save_btn{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MTk1OUU5RDFDMzA0MTFFMkExMUZBQTdDNDAzOUE5RjMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MTk1OUU5RDJDMzA0MTFFMkExMUZBQTdDNDAzOUE5RjMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoxOTU5RTlDRkMzMDQxMUUyQTExRkFBN0M0MDM5QTlGMyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoxOTU5RTlEMEMzMDQxMUUyQTExRkFBN0M0MDM5QTlGMyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PjDroXYAAAEXSURBVHjaYvz//z8DJYCRUgPIAUxAbAnEHiAHMIBcQCwGaRYXF3e6evXqoffv39/dv38/CymaGSUkJBzv3LlzCsj///fv3wdAihkkIQnEvkAshU8zLy+v7a1bt06ANP/79+87kDIAy505cybq06dPr3p7ezuwGQLTfOPGjWP/ESAZLg8kPKBO+g01RBJNszWyZqC6uSgWgIg/f/4shxnS2dnZBjMEqNkSFGBImi8CKTYMA4BYCGjIczRDHC5dunQQSfN7IKWI4UUkjjdMMdCwnw8ePLjwHxV4Yw1gZA5Q47z/2EELzhhCE+ABGvIQWSeQvwcU38QaAML2wHj+C/X3MyAlijeB4ZBoBOIPQGxJKIVSnBsBAgwABddBclWfcZUAAAAASUVORK5CYII=);margin-top:2px;width:21px}.dhx_cancel_btn{margin-top:2px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MDkzMDA3MzlDMzA0MTFFMjg2QTVFMzFEQzgwRkJERDYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MDkzMDA3M0FDMzA0MTFFMjg2QTVFMzFEQzgwRkJERDYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDowOTMwMDczN0MzMDQxMUUyODZBNUUzMURDODBGQkRENiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDowOTMwMDczOEMzMDQxMUUyODZBNUUzMURDODBGQkRENiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PmYuYOUAAAEdSURBVHjaYvz//z8DJYAFXWDlypU8QKoIiD2A2AwqfAqIdwBxX3h4+Bdk9YzILgBqtgdS84FYEYeF94E4EWjIQZgAE5LmQCB1AKoZZKMPEAtAMYh9GSp3AKjWD8UFQAEhIPshEIOc3wHENUBb/qJ57SyQMoJyPwKxElDNO1gYFEE17wMKVmIJlzNQzeegrjaA6qmBecEbSvfh0GwMxGeBhoPoemQ9MAO0kEIbl2YTqPAFKK2IbMB3AjabYIkRZmQD7kNpMyI0G0PpO8gGbIUFJj7NQDk2INWIrIcJKfBAKcwJqvkcDs0TgFgXGo19KCkRmpDWQdWDEk0NUoCBoq0FqhkE/IEWbKJKUmZEz43QzFSKIzN1481M5ACAAAMAlfl/lCwRpagAAAAASUVORK5CYII=);width:20px}.dhx_delete_btn{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MjFENzI3NUNDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MjFENzI3NURDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyMUQ3Mjc1QUMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyMUQ3Mjc1QkMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PmUD0gAAAABvSURBVHjaYvz//z8DIyMjAxYQicReji4J0ofKQNP8HwmgGQbXB8IsWGwDSSwDuioKjY9uBthVjFAXYHUGAQA2kYmBUoAUBpGk0LAwgBvwH+YX4mkwptgLowYMRgOITUyYKRFIN/wnDjQgJySAAAMApryKzL8wjfUAAAAASUVORK5CYII=);margin-top:2px;width:20px}.dhx_cal_cover{width:100%;height:100%;position:absolute;z-index:10000;top:0;left:0;background-color:#000;opacity:.1;filter:alpha(opacity=10)}.dhx_custom_button{padding:0 3px;font-family:Arial;font-size:13px;font-weight:400;margin-right:5px;margin-top:0;cursor:pointer}.dhx_custom_button div{cursor:pointer;float:left;height:21px;line-height:21px;vertical-align:middle}.dhx_cal_light_wide{width:580px;padding:2px 4px}.dhx_cal_light_wide .dhx_cal_larea{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;border:1px solid #cecece}.dhx_cal_light_wide .dhx_cal_lsection{border:0;float:left;text-align:right;width:80px;height:20px;padding:5px 10px 0 0}.dhx_cal_light_wide .dhx_wrap_section{position:relative;padding:10px 0;overflow:hidden;border-bottom:1px solid #ebebeb}.dhx_cal_light_wide .dhx_section_time{overflow:hidden;padding-top:2px!important;padding-right:0;height:20px!important}.dhx_cal_light_wide .dhx_cal_ltext{padding-right:0}.dhx_cal_light_wide .dhx_cal_larea{padding:0 10px;width:100%}.dhx_cal_light_wide .dhx_section_time{background:transparent}.dhx_cal_light_wide .dhx_cal_checkbox label{padding-left:0}.dhx_cal_light_wide .dhx_cal_lsection .dhx_fullday{float:none;margin-right:0;font-weight:700;cursor:pointer}.dhx_cal_light_wide .dhx_custom_button{position:absolute;top:0;right:0;margin-top:2px}.dhx_cal_light_wide .dhx_repeat_right{margin-right:55px}.dhx_cal_light_wide.dhx_cal_light_full{width:738px}.dhx_cal_wide_checkbox input{margin-top:8px;margin-left:14px}.dhx_cal_light input{font-size:13px}.dhx_custom_button{float:right;height:21px;width:90px;-webkit-border-radius:4px;-moz-border-radius:4px;-ms-border-radius:4px;-o-border-radius:4px;border-radius:4px}.dhx_section_time{background-color:#fff;white-space:nowrap;padding:2px 10px 5px;padding-top:2px!important}.dhx_section_time .dhx_time_selects{float:left;height:25px}.dhx_section_time .dhx_time_selects select{height:23px;padding:2px;border:1px solid #cecece}.dhx_gantt_duration{width:100px;height:23px;float:left;white-space:nowrap;margin-left:20px;line-height:23px}.dhx_gantt_duration .dhx_gantt_duration_value,.dhx_gantt_duration .dhx_gantt_duration_dec,.dhx_gantt_duration .dhx_gantt_duration_inc{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box;text-align:center;vertical-align:top;height:100%;border:1px solid #cecece}.dhx_gantt_duration .dhx_gantt_duration_value{width:40px;padding:3px 4px;border-left-width:0;border-right-width:0}.dhx_gantt_duration .dhx_gantt_duration_dec,.dhx_gantt_duration .dhx_gantt_duration_inc{width:20px;padding:1px;padding-bottom:3px;background:#fff}.dhx_gantt_duration .dhx_gantt_duration_dec{-moz-border-top-left-radius:4px;-moz-border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;-webkit-border-bottom-left-radius:4px;border-top-left-radius:4px;border-bottom-left-radius:4px}.dhx_gantt_duration .dhx_gantt_duration_inc{margin-right:4px;-moz-border-top-right-radius:4px;-moz-border-bottom-right-radius:4px;-webkit-border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:4px}.dhx_cal_quick_info{border:1px solid #cecece;border-radius:6px;position:absolute;z-index:300;box-shadow:3px 3px 3px rgba(0,0,0,.07);background-color:#fff;width:300px;transition:left .5s ease,right .5s;-moz-transition:left .5s ease,right .5s;-webkit-transition:left .5s ease,right .5s;-o-transition:left .5s ease,right .5s}.dhx_no_animate{transition:none;-moz-transition:none;-webkit-transition:none;-o-transition:none}.dhx_cal_quick_info.dhx_qi_left .dhx_qi_big_icon{float:right}.dhx_cal_qi_title{-webkit-border-top-left-radius:6px;-webkit-border-bottom-left-radius:0;-webkit-border-top-right-radius:6px;-webkit-border-bottom-right-radius:0;-moz-border-radius-topleft:6px;-moz-border-radius-bottomleft:0;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:0;border-top-left-radius:6px;border-bottom-left-radius:0;border-top-right-radius:6px;border-bottom-right-radius:0;padding:5px 0 8px 12px;color:#454545;background-color:#fff;border-bottom:1px solid #cecece}.dhx_cal_qi_tdate{font-size:14px;font-weight:700}.dhx_cal_qi_tcontent{font-size:13px}.dhx_cal_qi_content{padding:16px 8px;font-size:13px;color:#454545;overflow:hidden}.dhx_cal_qi_controls{-webkit-border-top-left-radius:0;-webkit-border-bottom-left-radius:6px;-webkit-border-top-right-radius:0;-webkit-border-bottom-right-radius:6px;-moz-border-radius-topleft:0;-moz-border-radius-bottomleft:6px;-moz-border-radius-topright:0;-moz-border-radius-bottomright:6px;border-top-left-radius:0;border-bottom-left-radius:6px;border-top-right-radius:0;border-bottom-right-radius:6px;padding-left:7px}.dhx_cal_qi_controls .dhx_menu_icon{margin-top:6px;background-repeat:no-repeat}.dhx_cal_qi_controls .dhx_menu_icon.icon_edit{width:20px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAA7EAAAOxAGVKw4bAAAAB3RJTUUH3QYFCjI5ZQj5bAAAAFNJREFUOMvt0zEOACAIA0DkwTymH8bJTRTKZGJXyaWEKPKTCQAH4Ls37cItcDUzsxHNDLZNhCq7Gt1wh9ErV7EjyGAhyGLphlnsClWuS32rn0czAV+vNGrM/LBtAAAAAElFTkSuQmCC)}.dhx_cal_qi_controls .dhx_menu_icon.icon_delete{width:20px;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6MjFENzI3NUNDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MjFENzI3NURDMzA0MTFFMjhBNjJGQTc3MUIyQzYzNEYiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDoyMUQ3Mjc1QUMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDoyMUQ3Mjc1QkMzMDQxMUUyOEE2MkZBNzcxQjJDNjM0RiIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PmUD0gAAAABvSURBVHjaYvz//z8DIyMjAxYQicReji4J0ofKQNP8HwmgGQbXB8IsWGwDSSwDuioKjY9uBthVjFAXYHUGAQA2kYmBUoAUBpGk0LAwgBvwH+YX4mkwptgLowYMRgOITUyYKRFIN/wnDjQgJySAAAMApryKzL8wjfUAAAAASUVORK5CYII=)}.dhx_qi_big_icon{font-size:13px;border-radius:4px;font-weight:700;background:#fff;margin:5px 9px 8px 0;min-width:60px;line-height:32px;vertical-align:middle;padding:0 10px 0 5px;cursor:pointer;border:1px solid #cecece}.dhx_cal_qi_controls div{float:left;height:32px;text-align:center;line-height:32px}.gantt_tooltip{box-shadow:3px 3px 3px rgba(0,0,0,.07);background-color:#fff;border-left:1px solid rgba(0,0,0,.07);border-top:1px solid rgba(0,0,0,.07);font-family:Arial;font-size:8pt;color:#454545;padding:10px;position:absolute;z-index:500}.gantt_noselect{-moz-user-select:-moz-none;-webkit-user-select:none;-user-select:none}.gantt_drag_marker{position:absolute;font-family:Arial;font-size:13px}.gantt_drag_marker .gantt_tree_indent,.gantt_drag_marker .gantt_tree_icon.gantt_blank,.gantt_drag_marker .gantt_tree_icon.gantt_open,.gantt_drag_marker .gantt_tree_icon.gantt_close{display:none}.gantt_drag_marker,.gantt_drag_marker .gantt_row.odd{background-color:#fff}.gantt_drag_marker .gantt_row{border-left:1px solid #d2d2d2;border-top:1px solid #d2d2d2}.gantt_drag_marker .gantt_cell{border-color:#d2d2d2}.gantt_row.gantt_over,.gantt_task_row.gantt_over{background-color:#0070fe}.gantt_row.gantt_transparent .gantt_cell{opacity:.7}.gantt_task_row.gantt_transparent{background-color:#f8fdfd}.dhtmlx_popup_button.dhtmlx_delete_button{background:#3db9d3;text-shadow:0 -1px 0 #248a9f;color:#fff;font-weight:700;border-width:0}
\ No newline at end of file
......@@ -285,203 +285,6 @@
}
}
function job_gantt_widget(input_data, output_data) {
// TODO: make this a general parameter
// multiplier to get a day from a time unit
var time_multiplier = 24;
// TODO: use dhx_gantt zoom level feature (
// http://dhtmlx.com/docs/products/dhtmlxGantt/02_features.html )
// temporary hack
var now = new Date();
now.setHours(0);
now.setMinutes(0);
now.setSeconds(0);
var start_date,
gantt_data = {
data: [
{
id: "by_order",
text: "By Order",
start_date: start_date,
duration: 0,
project: 1,
open: true
},
{
id: "by_station",
text: "By Station",
start_date: start_date,
duration: 0,
project: 1,
open: true
}
],
link: []
};
start_date = input_data.general.currentDate;
if (start_date !== undefined && start_date !== "") {
start_date = new Date(start_date);
} else {
start_date = new Date(now.getTime())
}
$.each(output_data.elementList.sort(function(a,b) {return a.id < b.id ? -1 : 1}),
function (idx, obj) {
var isVisibleStation = function (station) {
// we should be able to define in the backend which station is visible
return input_data.nodes[station]._class != "Dream.QueueManagedJob" &&
input_data.nodes[station]._class != "Dream.OperatorManagedJob" &&
input_data.nodes[station]._class != "Dream.ExitJobShop" &&
input_data.nodes[station]._class != "Dream.CapacityStationBuffer" &&
input_data.nodes[station]._class != "Dream.CapacityStationExit" &&
input_data.nodes[station]._class != "Dream.Queue"
};
if (obj._class === 'Dream.Job' || obj._class === 'Dream.CapacityProject') {
// find the corresponding input
var input_job = null, input_order = null;
// find the input order and order component for this job
for (var node_id in input_data.nodes) {
var node = input_data.nodes[node_id];
if (node.wip) {
for (var i=0; i<node.wip.length; i++) {
var order = node.wip[i];
if (order.id == obj.id) {
input_job = input_order = order;
}
if (input_job === null && order.componentsList) {
for (var j=0; j<order.componentsList.length; j++){
var component = order.componentsList[j];
if (component.id == obj.id){
input_order = order;
input_job = component;
}
}
}
}
}
}
var duration = 0;
if (input_job == input_order) { // if we are on the order definition
gantt_data.data.push({
id: input_order.id,
text: input_order.name,
project: 1,
open: false,
parent: "by_order"
});
}
var seen_parts = {};
$.each(obj['results']['schedule'], function (i, schedule) {
// Filter intermediate steps in part job shop
if (isVisibleStation(schedule['stationId'])) {
if (schedule['exitTime']) {
duration = 24 * (schedule['exitTime'] - schedule['entranceTime']);
} else {
if (obj['results']['schedule'][i + 1]) {
duration = obj['results']['schedule'][i + 1]['entranceTime'] - schedule['entranceTime'];
} else {
duration = obj['results'].completionTime - schedule['entranceTime'];
}
}
if (duration > 0.0) {
var task_start_date = new Date(start_date.getTime());
// for simulation time unit as days
task_start_date.setTime(task_start_date.getTime() + schedule['entranceTime']*1000*3600*24);
// for simulation time unit as hours
// task_start_date.setTime(task_start_date.getTime() + schedule['entranceTime']*1000*3600);
var job_full_id = input_job.id + "." + input_order.id;
if (seen_parts[job_full_id] === undefined){
gantt_data.data.push({
id: job_full_id,
text: input_job.name,
parent: input_order.id
});
seen_parts[job_full_id] = 1;
}
gantt_data.data.push({
id: input_order.id + '.' + idx + '_' + i,
text: schedule['stationId'],
start_date: task_start_date,
duration: duration,
parent: job_full_id
});
gantt_data.data.push({
id: 'job.' + obj['id'] + '.' + idx + '_' + i,
text: input_order.name + "-" + input_job.name,
start_date: task_start_date,
duration: duration,
parent: schedule['stationId'],
by_station:1
});
}
}
});
} else {
if (isVisibleStation(obj['id'])) {
gantt_data.data.push({
id: obj['id'],
text: obj['id'],
project: 1,
open: false,
parent: "by_station"
});
};
}
});
gantt.templates.task_class = function (start, end, obj) {
return obj.parent ? "sub_task" : "";
};
try {
gantt.clearAll();
} catch (e) {}
var gantt_output_height = 35 * (gantt_data.data.length + 1) + 1;
gantt_data.data.sort(function (a, b) {
// sort gantt data in a chronological order
var result;
if (a.start_date === undefined && b.start_date !== undefined) {
result = 1;
} else if (a.start_date !== undefined && b.start_date === undefined) {
result = - 1;
} else if (a.start_date === undefined && b.start_date === undefined) {
result = 0;
} else if (a.start_date > b.start_date) {
result = 1;
} else if (a.start_date < b.start_date) {
result = -1;
} else {
result = 0;
}
return result;
});
$('#job_gantt').show().dhx_gantt({
data: gantt_data,
readonly: true,
/* for days has simulation time unit
scale_unit: 'day',
step: 7
*/
// for hours has simulation time unit
scale_unit: 'day',
duration_unit: 60*60*1000,
//date_grid: "%H:%i",
date_scale: "%M/%d",
step: 1,
subscales: [{unit:"hour", step:4, date:"%H:%i" }]
});
}
scope.Dream = function (configuration) {
var that = jsonPlumb(),
priv = {};
......
......@@ -26,6 +26,7 @@
key
),
gadget.whoWantToDisplayThisDocumentPage("exit_stat", key),
gadget.whoWantToDisplayThisDocumentPage("job_gantt", key),
gadget.whoWantToDisplayThisDocumentPage("debug_json", key)
]);
})
......@@ -37,7 +38,8 @@
{link: result_list[3], title: "Stations Utilization"},
{link: result_list[4], title: "Queues Statistics"},
{link: result_list[5], title: "Exit Statistics"},
{link: result_list[6], title: "Debug JSON"}
{link: result_list[6], title: "Job Gantt"},
{link: result_list[7], title: "Debug JSON"}
];
});
});
......
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Job Gantt</title>
<link rel="stylesheet" href="../<%= copy.dhtmlxganttcss.relative_dest %>">
<script src="../<%= copy.rsvp.relative_dest %>" type="text/javascript"></script>
<script src="../<%= copy.renderjs.relative_dest %>" type="text/javascript"></script>
<script src="../<%= curl.jquery.relative_dest %>" type="text/javascript"></script>
<script src="../<%= copy.dhtmlxganttjs.relative_dest %>" type="text/javascript"></script>
<script src="document_page_mixin.js" type="text/javascript"></script>
<script src="job_gantt.js" type="text/javascript"></script>
</head>
<body>
<div class="gant_container" style="height: 800px;"></div>
</body>
</html>
/*global console, rJS, RSVP, initDocumentPageMixin, jQuery, gantt */
/*jslint nomen: true */
(function (window, rJS, RSVP, initDocumentPageMixin, $, gantt) {
"use strict";
gantt.templates.task_class = function (start, end, obj) {
return obj.parent ? "sub_task" : "";
};
function job_gantt_widget(all_data) {
var now = new Date(),
start_date,
gantt_data = {
data: [
{
id: "by_order",
text: "By Order",
start_date: start_date,
duration: 0,
project: 1,
open: true
},
{
id: "by_station",
text: "By Station",
start_date: start_date,
duration: 0,
project: 1,
open: true
}
],
link: []
},
input_data = all_data.input,
output_data = all_data.result;
// temporary hack
now.setHours(0);
now.setMinutes(0);
now.setSeconds(0);
start_date = input_data.general.currentDate;
if ((start_date !== undefined) && (start_date !== "")) {
start_date = new Date(start_date);
} else {
start_date = new Date(now.getTime());
}
function isVisibleStation(station) {
// we should be able to define in the backend which
// station is visible
return (input_data.nodes[station]._class !== "Dream.QueueManagedJob") &&
(input_data.nodes[station]._class !== "Dream.OperatorManagedJob") &&
(input_data.nodes[station]._class !== "Dream.ExitJobShop");
}
$.each(
output_data.elementList.sort(function (a, b) {
return a.id < b.id ? -1 : 1;
}),
function (idx, obj) {
var input_job = null,
input_order = null,
i,
j,
node,
node_key,
order,
component,
duration,
seen_parts = {};
if (obj._class === 'Dream.Job') {
// find the corresponding input
// find the input order and order component for this job
for (node_key in input_data.nodes) {
if (input_data.nodes.hasOwnProperty(node_key)) {
node = input_data.nodes[node_key];
if (node.wip) {
for (i = 0; i < node.wip.length; i += 1) {
order = node.wip[i];
if (order.id === obj.id) {
input_job = input_order = order;
}
if (input_job === null) {
for (j = 0; j < order.componentsList.length; j += 1) {
component = order.componentsList[j];
if (component.id === obj.id) {
input_order = order;
input_job = component;
}
}
}
}
}
}
}
duration = 0;
if (input_job === input_order) {
// if we are on the order definition
gantt_data.data.push({
id: input_order.id,
text: input_order.name,
project: 1,
open: false,
parent: "by_order"
});
}
seen_parts = {};
$.each(obj.results.schedule, function (i, schedule) {
var entrance_date,
task_start_date,
job_full_id;
// Filter intermediate steps in part job shop
if (isVisibleStation(schedule.stationId)) {
entrance_date = new Date(start_date.getTime());
entrance_date.setTime(entrance_date.getTime() +
schedule.entranceTime * 1000 * 3600);
if (obj.results.schedule[i + 1]) {
duration = obj.results.schedule[i + 1].entranceTime -
schedule.entranceTime;
} else {
duration = obj.results.completionTime - schedule.entranceTime;
}
if (duration > 0.0) {
task_start_date = new Date(start_date.getTime());
// for simulation time unit as days
// task_start_date.setDate(task_start_date.getDate() +
// schedule['entranceTime']);
// for simulation time unit as days hours
task_start_date.setTime(task_start_date.getTime() +
schedule.entranceTime * 1000 * 3600);
job_full_id = input_job.id + "." + input_order.id;
if (seen_parts[job_full_id] === undefined) {
gantt_data.data.push({
id: job_full_id,
text: input_job.name,
parent: input_order.id
});
seen_parts[job_full_id] = 1;
}
gantt_data.data.push({
id: input_order.id + '.' + idx + '_' + i,
text: schedule.stationId,
start_date: task_start_date,
duration: duration,
parent: job_full_id
});
gantt_data.data.push({
id: 'job.' + obj.id + '.' + idx + '_' + i,
text: input_order.name + "-" + input_job.name,
start_date: task_start_date,
duration: duration,
parent: schedule.stationId,
by_station: 1
});
}
}
});
} else {
if (isVisibleStation(obj.id)) {
gantt_data.data.push({
id: obj.id,
text: obj.id,
project: 1,
open: false,
parent: "by_station"
});
}
}
}
);
// gantt_output_height = 35 * (gantt_data.data.length + 1) + 1;
gantt_data.data.sort(function (a, b) {
// sort gantt data in a chronological order
var result;
if (a.start_date === undefined && b.start_date !== undefined) {
result = 1;
} else if (a.start_date !== undefined && b.start_date === undefined) {
result = -1;
} else if (a.start_date === undefined && b.start_date === undefined) {
result = 0;
} else if (a.start_date > b.start_date) {
result = 1;
} else if (a.start_date < b.start_date) {
result = -1;
} else {
result = 0;
}
return result;
});
return gantt_data;
}
var gadget_klass = rJS(window);
initDocumentPageMixin(gadget_klass);
gadget_klass
/////////////////////////////////////////////////////////////////
// ready
/////////////////////////////////////////////////////////////////
// Init local properties
.ready(function (g) {
g.props = {};
})
// Assign the element to a variable
.ready(function (g) {
return g.getElement()
.push(function (element) {
g.props.element = element;
});
})
/////////////////////////////////////////////////////////////////
// Acquired methods
/////////////////////////////////////////////////////////////////
.declareAcquiredMethod("aq_getAttachment", "jio_getAttachment")
/////////////////////////////////////////////////////////////////
// declared methods
/////////////////////////////////////////////////////////////////
.declareMethod("render", function (options) {
var jio_key = options.id,
gadget = this;
gadget.props.jio_key = jio_key;
return gadget.aq_getAttachment({
"_id": gadget.props.jio_key,
"_attachment": "simulation.json"
})
.push(function (simulation_json) {
gadget.props.result = job_gantt_widget(
// XXX Hardcoded result
JSON.parse(simulation_json)[0]
);
});
})
.declareMethod("startService", function () {
$(this.props.element).find('.gant_container').dhx_gantt({
data: this.props.result,
readonly: true,
/* for days has simulation time unit
scale_unit: 'day',
step: 7
*/
// for hours has simulation time unit
scale_unit: 'day',
duration_unit: 60 * 60 * 1000,
//date_grid: "%H:%i",
date_scale: "%M/%d",
step: 1,
subscales: [{unit: "hour", step: 4, date: "%H:%i"}]
});
return new RSVP.Queue()
.push(function () {
// Infinite wait, until cancelled
return (new RSVP.defer()).promise;
})
.push(undefined, function (error) {
gantt.clearAll();
throw error;
});
});
}(window, rJS, RSVP, initDocumentPageMixin, jQuery, gantt));
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment