Index: /contrib/ProjectManager/ganttchart.php
===================================================================
--- /contrib/ProjectManager/ganttchart.php	(revision 3594)
+++ /contrib/ProjectManager/ganttchart.php	(revision 3594)
@@ -0,0 +1,45 @@
+<?php
+/**
+ * ProjectManager - Gantchart creation
+ *
+ * As ganttcharts contain an image-map and the image, we save the image as a temporary file.
+ * This for performance reasons, it saves a second creation / script-run.
+ * This script reads and output the temporary file/image and unlinks it after.
+ * If the temp. image is not found, it creates a new one. 
+ * It can be used standalone, eg. from SiteMgr.
+ * 
+ * @link http://www.egroupware.org
+ * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @package projectmanager
+ * @copyright (c) 2005 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @version $Id: ganttchart.php 22355 2006-08-26 16:30:45Z ralfbecker $ 
+ */
+
+error_reporting(E_ALL & ~E_NOTICE);
+
+$tmp = $GLOBALS['phpgw_info']['server']['temp_dir'];
+if (!$tmp || !is_dir($tmp) || !is_writable($tmp))
+{
+	@unlink($tmp = tempnam('','test'));	// get the systems temp-dir
+	$tmp = dirname($tmp);
+}
+if (isset($_GET['img']) && is_readable($ganttchart = $tmp.'/'.basename($_GET['img'])))
+{
+	header('Content-type: image/png');
+	readfile($ganttchart);
+	@unlink($ganttchart);
+	exit;
+}
+
+$GLOBALS['phpgw_info'] = array(
+	'flags' => array(
+		'currentapp'	=> 'projectmanager', 
+		'noheader'		=> True,
+		'nonavbar'		=> True
+));
+include('../header.inc.php');
+
+ExecMethod('projectmanager.ganttchart.create');
+
+$GLOBALS['phpgw']->common->phpgw_exit();
Index: /contrib/ProjectManager/inc/class.bopricelist.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.bopricelist.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.bopricelist.inc.php	(revision 3594)
@@ -0,0 +1,387 @@
+<?php
+/**
+ * ProjectManager - Pricelist buisness object
+ *
+ * @link http://www.egroupware.org
+ * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @package projectmanager
+ * @copyright (c) 2005 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @version $Id: class.bopricelist.inc.php 23379 2007-02-05 18:50:53Z nelius_weiss $ 
+ */
+
+include_once(PHPGW_INCLUDE_ROOT.'/projectmanager/inc/class.sopricelist.inc.php');
+
+/**
+ * Pricelist buisness object of the projectmanager
+ */
+class bopricelist extends sopricelist
+{
+	/**
+	 * @var array $timestamps timestaps that need to be adjusted to user-time on reading or saving
+	 */
+	var $timestamps = array(
+		'pl_modified','pl_validsince',
+	);
+	/**
+	 * @var int $tz_offset_s offset in secconds between user and server-time,
+	 *	it need to be add to a server-time to get the user-time or substracted from a user-time to get the server-time
+	 */
+	var $tz_offset_s;
+	/**
+	 * @var int $now actual USER time
+	 */
+	var $now;
+
+	/**
+	 * Constructor, calls the constructor of the extended class
+	 * 
+	 * @param int $pm_id=0 pm_id of the project to use, default 0 (project independent / standard prices)
+	 */
+	function bopricelist($pm_id=0)
+	{
+		$this->sopricelist($pm_id);
+
+		if (!is_object($GLOBALS['phpgw']->datetime))
+		{
+			$GLOBALS['phpgw']->datetime =& CreateObject('phpgwapi.datetime');
+		}
+		$this->tz_offset_s = $GLOBALS['phpgw']->datetime->tz_offset;
+		$this->now = time() + $this->tz_offset_s;
+		
+		if (!is_object($GLOBALS['boprojectmanager']))
+		{
+			CreateObject('projectmanager.boprojectmanager',$pm_id);
+		}
+		$this->project =& $GLOBALS['boprojectmanager'];
+	}
+	
+	/**
+	 * saves the content of data to the db, also checks acl and deletes not longer set prices!
+	 *
+	 * @param array $keys=null if given $keys are copied to data before saveing => allows a save as
+	 * @return int/boolean 0 on success, true on missing acl-rights and errno != 0 else
+	 */
+	function save($keys=null)
+	{
+		if (is_array($keys) && count($keys)) $this->data_merge($keys);
+		
+		if ((int)$this->debug >= 2)
+		{
+			echo "<p>sopricelist::save(".print_r($keys,true).") data=";
+			_debug_array($this->data);
+		}
+		if ($this->data['pl_id'])
+		{
+			$backup =& $this->data;		// would get overwritten by read
+			unset($this->data);
+			$old = $this->read(array(
+				'pl_id' => $backup['pl_id'],
+				'pm_id' => $backup['pm_id'] ? array($backup['pm_id'],0) : 0
+			));
+			$this->data =& $backup;
+			unset($backup);
+		}
+		$need_general = count($old['prices']) > 0 || count($this->data['prices']) > 0;
+		if (!($pricelist_need_save = !$this->data['pl_id']))
+		{
+			$this->data['cat_id'] = (int) $this->data['cat_id'];
+			foreach($this->db_cols as $col => $data)
+			{
+				if (!$old || $this->data[$col] != $old[$col])
+				{
+					$pricelist_need_save = true;
+					break;
+				}
+			}
+		}
+		if ($pricelist_need_save)
+		{
+			// check acl
+			if (!$this->check_acl(PHPGW_ACL_EDIT,$need_general ? 0 : $this->data['pm_id']))
+			{
+				return lang('permission denied !!!').' need_general='.(int)$need_general;
+			}
+			if (($err = parent::save($this->data)))
+			{
+				return $err;
+			}
+		}
+		$prices = array();
+		foreach($this->data['prices'] as $key => $nul)
+		{
+			$price =& $this->data['prices'][$key];
+			$price['pm_id'] = 0;
+			$price['pl_billable'] = $price['pl_customertitle'] = null;
+			if (count($this->data['prices']) == 1) $price['pl_validsince'] = 0;	// no date for first price
+			$prices[] =& $price;
+		}
+		foreach($this->data['project_prices'] as $key => $nul)
+		{
+			$price =& $this->data['project_prices'][$key];
+			foreach(array('pm_id','pl_billable','pl_customertitle') as $key)
+			{
+				if (!isset($price[$key])) $price[$key] = $this->data[$key];
+			}
+			if (count($this->data['project_prices']) == 1) $price['pl_validsince'] = 0;	// no date for first price
+			$prices[] =& $price;
+		}
+		
+		// index prices in old by pm_id and date (!) of validsince
+		$old_prices = array();
+		if ($old)
+		{
+			foreach(array_merge($old['prices'],$old['project_prices']) as $old_price)
+			{
+				$old_prices[(int)$old_price['pm_id']][date('Y-m-d',(int)$old_price['pl_validsince'])] = $old_price;
+			}
+		}
+		foreach($prices as $key => $nul)
+		{
+			$price =& $prices[$key];
+			if (!isset($price['pl_id'])) $price['pl_id'] = $this->data['pl_id'];
+			$old_price = $old_prices[(int)$price['pm_id']][date('Y-m-d',(int)$price['pl_validsince'])];
+			if (!$this->prices_equal($price,$old_price))
+			{
+				// price needs saving, checking acl now
+				if (!$this->check_acl(PHPGW_ACL_EDIT,$price['pm_id']))
+				{
+					return lang('permission denied !!!').' check_acl(PHPGW_ACL_EDIT(pm_id='.(int)$price[pm_id].')';
+				}
+				// maintain time of old price, to not create doublets with different times by users operating in different TZ's
+				if ($old_price) $price['pl_validsince'] = $old_price['pl_validsince'];
+
+				if (($err = parent::save_price($price)))
+				{
+					return $err;
+				}
+			}
+			unset($old_prices[(int)$price['pm_id']][date('Y-m-d',(int)$old_price['pl_validsince'])]);
+		}
+		// check if there are old prices not longer set ==> delete them
+		foreach($old_prices as $pm_id => $prices)
+		{
+			foreach($prices as $price)
+			{
+				if (!$this->check_acl(PHPGW_ACL_DELETE,$price['pm_id']))
+				{
+					return lang('permission denied !!!').' check_acl(PHPGW_ACL_DELETE(pm_id='.(int)$price[pm_id].')';
+				}
+				if (!parent::delete($price))
+				{
+					return lang('Error: deleting price !!!');
+				}
+			}
+		}
+		return 0;
+	}
+	
+	/**
+	 * search elements, reimplemented to use $this->pm_id, if no pm_id given in criteria or filter and join with the prices table
+	 *
+	 * @param array/string $criteria array of key and data cols, OR a SQL query (content for WHERE), fully quoted (!)
+	 * @param boolean $only_keys True returns only keys, False returns all cols
+	 * @param string $order_by fieldnames + {ASC|DESC} separated by colons ','
+	 * @param string/array $extra_cols string or array of strings to be added to the SELECT, eg. "count(*) as num"
+	 * @param string $wildcard appended befor and after each criteria
+	 * @param boolean $empty False=empty criteria are ignored in query, True=empty have to be empty in row
+	 * @param string $op defaults to 'AND', can be set to 'OR' too, then criteria's are OR'ed together
+	 * @param int/boolean $start if != false, return only maxmatch rows begining with start
+	 * @param array $filter if set (!=null) col-data pairs, to be and-ed (!) into the query without wildcards
+	 * @param string/boolean $join=true default join with prices-table or string as in so_sql
+	 * @return array of matching rows (the row is an array of the cols) or False
+	 */
+	function search($criteria,$only_keys=false,$order_by='',$extra_cols='',$wildcard='',$empty=False,$op='AND',$start=false,$filter=null,$join=true)
+	{
+		if (!$this->check_acl(PHPGW_ACL_READ,(int)($criteria['pm_id'] ? $criteria['pm_id'] : $this->pm_id)))
+		{
+			return false;
+		}
+		return parent::search($criteria,$only_keys,$order_by,$extra_cols,$wildcard,$empty,$op,$start,$filter,$join);
+	}
+	
+	/**
+	 * return priceslist of a given project (only bookable or billable price, no general pricelist)
+	 *
+	 * @param int $pm_id project
+	 * @return array/boolean array with pl_id => pl_unit: pl_tiltle (pl_price) pairs or false on error (eg. no ACL)
+	 */
+	function pricelist($pm_id)
+	{
+		//echo "<p>bopricelist::pricelist($pm_id)</p>\n";
+		if (!($prices =& $this->search(array('pm_id' => $pm_id))))
+		{
+			return false;
+		}
+		$options = array();
+		foreach($prices as $price)
+		{
+			$options[$price['pl_id']] = $price['pl_unit'].' '.$price['pl_title'].
+				($price['pl_customertitle'] ? ': '.$price['pl_customertitle'] : '').
+				' ('.$price['pl_price'].')';
+		}
+		return $options;
+	}
+
+	/**
+	 * reads one pricelist-itme specified by $keys, reimplemented to use $this->pm_id, if no pm_id given
+	 *
+	 * @param array $keys array with keys in form internalName => value, may be a scalar value if only one key
+	 * @param string/array $extra_cols string or array of strings to be added to the SELECT, eg. "count(*) as num"
+	 * @param string/boolean $join=true default join with links-table or string as in so_sql
+	 * @return array/boolean data if row could be retrived else False
+	*/
+	function read($keys,$extra_cols='',$join=true)
+	{
+		// check if we have the requested access to all given pricelists
+		foreach(!is_array($keys) || !isset($keys['pm_id']) ? array($this->pm_id) : 
+			(is_array($keys['pm_id']) ? $keys['pm_id'] : array($keys['pm_id'])) as $pm_id)
+		{
+			if (!$this->check_acl(PHPGW_ACL_READ,(int)$pm_id)) return false;
+		}
+		return parent::read($keys,$extra_cols,$join);
+	}
+
+	/**
+	 * delete pricelist-entries and price(s) specified by keys pl_id, pm_id and/or pl_validsince
+	 *
+	 * If the last price of a pricelist-entry gets deleted, the pricelist entry is automatic deleted too!
+	 *
+	 * @param array/int $keys array with keys pm_id, pl_id and/or pl_validsince to delete or integer pm_id
+	 * @return int/boolean number of deleted prices or false if permission denied
+	 */
+	function delete($keys)
+	{
+		if (!$this->check_acl(PHPGW_ACL_EDIT,(int)(is_array($keys) ? $keys['pm_id'] : $keys)))
+		{
+			return false;
+		}
+		return parent::delete($keys);
+	}
+
+	/**
+	 * checks if the user has sufficent rights for a certain action
+	 *
+	 * For project-spez. prices/data you need a PHPGW_ACL_BUDGET right of the project for read or 
+	 * PHPGW_ACL_EDIT_BUDGET for write or delete.
+	 * For general pricelist data you need atm. no extra read rights, but is_admin to write/delete.
+	 *
+	 * @param int $required PHPGW_ACL_{READ|WRITE|DELETE}
+	 * @param int $pm_id=0 project-id for project-spez. prices/data to check, default 0 = general pricelist
+	 * @param array/int $data=null data/id of pricelist-entry, default null = use $this->data ($pm_id is ignored)
+	 * @return boolean true if the user has the rights, false otherwise
+	 */
+	function check_acl($required,$pm_id=0,$data=null)
+	{
+/*		not used atm.
+		if (is_null($data))
+		{
+			$data =& $this->data;
+		}
+		elseif (!is_array($data))
+		{
+			if ((int) $data)
+			{
+				$backup = $this->data;
+				$data = $this->read(array('pm_id'=>(int)$pm_id,'pl_id' => (int)$data));
+				$this->data = $backup;
+			}
+			else
+			{
+				return false;
+			}
+		}
+*/
+		if (!$pm_id)
+		{
+			return $required == PHPGW_ACL_READ || $this->project->is_admin;
+		}
+		return $this->project->check_acl($required == PHPGW_ACL_READ ? PHPGW_ACL_BUDGET : PHPGW_ACL_EDIT_BUDGET,$pm_id);
+	}
+
+	/**
+	 * Compares two prices to check if they are equal
+	 *
+	 * The compared fields depend on the price being project-specific or not
+	 *
+	 * @param array $price
+	 * @param array $price2
+	 * @return boolean true if the two prices are identical, false otherwise or if they are no arrays!
+	 */
+	function prices_equal($price,$price2)
+	{
+		if (!is_array($price) || !is_array($price2)) return false;
+		
+		$to_compare = array('pl_id','pm_id','pl_price','pl_validsince','pl_modified','pl_modifier');
+		
+		if ($price['pm_id'])
+		{
+			$to_compare[] = 'pl_customertitle';
+			$to_compare[] = 'pl_billable';
+		}
+		$equal = true;
+		foreach($to_compare as $key)
+		{
+			switch($key)
+			{
+				case 'pm_id':
+					$equal = (int) $price['pm_id'] == (int) $price2['pm_id'];
+					break;
+				case 'pl_validsince':
+					$equal = date('Y-m-d',(int)$price['pl_validsince']) == date('Y-m-d',(int)$price2['pl_validsince']);
+					break;
+				default:
+					$equal = $price[$key] == $price2[$key];
+					break;
+			}
+			if (!$equal) break;
+		}
+		if ((int)$this->debug >= 3) echo "<p>bopricelist::prices_equal(".print_r($price,true).','.print_r($price2,true).') = '.($equal ? 'true' : "differ in $key: {$price[$key]} != {$price2[$key]}")."</p>\n";
+
+		return $equal;
+	}
+
+	/**
+	 * changes the data from the db-format to your work-format
+	 *
+	 * reimplemented to adjust the timezone of the timestamps (adding $this->tz_offset_s to get user-time)
+	 * Please note, we do NOT call the method of the parent or so_sql !!!
+	 *
+	 * @param array $data if given works on that array and returns result, else works on internal data-array
+	 * @return array with changed data
+	 */
+	function db2data($data=null)
+	{
+		if (!is_array($data))
+		{
+			$data = &$this->data;
+		}
+		foreach($this->timestamps as $name)
+		{
+			if (isset($data[$name]) && $data[$name]) $data[$name] += $this->tz_offset_s;
+		}
+		return $data;
+	}
+
+	/**
+	 * changes the data from your work-format to the db-format
+	 *
+	 * reimplemented to adjust the timezone of the timestamps (subtraction $this->tz_offset_s to get server-time)
+	 * Please note, we do NOT call the method of the parent or so_sql !!!
+	 *
+	 * @param array $data if given works on that array and returns result, else works on internal data-array
+	 * @return array with changed data
+	 */
+	function data2db($data=null)
+	{
+		if ($intern = !is_array($data))
+		{
+			$data = &$this->data;
+		}
+		foreach($this->timestamps as $name)
+		{
+			if (isset($data[$name]) && $data[$name]) $data[$name] -= $this->tz_offset_s;
+		}
+		return $data;
+	}
+}
Index: /contrib/ProjectManager/inc/class.uimilestones.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.uimilestones.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.uimilestones.inc.php	(revision 3594)
@@ -0,0 +1,186 @@
+<?php
+/**
+ * ProjectManager - Milestones user interface
+ *
+ * @link http://www.egroupware.org
+ * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @package projectmanager
+ * @copyright (c) 2005 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @version $Id: class.uimilestones.inc.php 22355 2006-08-26 16:30:45Z ralfbecker $ 
+ */
+
+include_once(PHPGW_INCLUDE_ROOT.'/projectmanager/inc/class.boprojectmanager.inc.php');
+
+/**
+ * Milestones user interface of the projectmanager
+ */
+class uimilestones extends boprojectmanager 
+{
+	/**
+	 * @var array $public_functions Functions to call via menuaction
+	 */
+	var $public_functions = array(
+		'edit'  => true,
+		'view'  => true,
+	);
+	var $tpl;
+	
+	/**
+	 * Constructor, calls the constructor of the extended class
+	 */
+	function uimilestones()
+	{
+		$this->tpl =& CreateObject('etemplate.etemplate');
+
+		if ((int) $_REQUEST['pm_id'])
+		{
+			$pm_id = (int) $_REQUEST['pm_id'];
+		}
+		else
+		{
+			$pm_id = $GLOBALS['phpgw']->session->appsession('pm_id','projectmanager');
+		}
+		if (!$pm_id)
+		{
+			$this->tpl->location(array(
+				'menuaction' => 'projectmanager.uiprojectmanager.index',
+				'msg'        => lang('You need to select a project first'),
+			));
+		}
+		$this->boprojectmanager($pm_id);
+		
+		// check if we have at least read-access to this project
+		if (!$this->check_acl(PHPGW_ACL_READ))
+		{
+			$this->tpl->location(array(
+				'menuaction' => 'projectmanager.uiprojectmanager.index',
+				'msg'        => lang('Permission denied !!!'),
+			));
+		}
+		if (!is_object($this->milestones))
+		{
+			$this->milestones =& CreateObject('projectmanager.somilestones');
+		}
+	}
+	
+	/**
+	 * View a milestone, only calls edit(null,true);
+	 */
+	function view()
+	{
+		$this->edit(null,true);
+	}
+
+	/**
+	 * Edit a milestone
+	 *
+	 * @param array $content=null posted content
+	 * @param boolean $view=false only view the milestone?
+	 */
+	function edit($content=null,$view=false)
+	{
+		$view = $view || $content['view'] || !$this->check_acl(PHPGW_ACL_EDIT);
+		
+		if (is_array($content))
+		{
+			if ($content['pm_id'] != $this->data['pm_id'])
+			{
+				$this->read($content['pm_id']);
+			}
+			$this->milestones->data_merge($content);
+
+			if ($this->check_acl(PHPGW_ACL_EDIT))
+			{
+				if ($content['save'] || $content['apply'])
+				{
+					if ($this->milestones->save() != 0)
+					{
+						$msg = lang('Error: saving milestone');
+						unset($content['save']);
+					}
+					else
+					{
+						$msg = lang('Milestone saved');
+						$js = "opener.location.href='".$GLOBALS['phpgw']->link('/index.php',array(
+							'menuaction' => 'projectmanager.ganttchart.show',
+							'msg'        => $msg,
+						))."';";
+					}
+				}
+				if ($content['delete'] && $content['ms_id'])
+				{
+					if ($this->milestones->delete(array(
+						'pm_id' => $content['pm_id'],
+						'ms_id' => $content['ms_id'],
+					)))
+					{
+						$msg = lang('Milestone deleted');
+						$js = "opener.location.href='".$GLOBALS['phpgw']->link('/index.php',array(
+							'menuaction' => 'projectmanager.ganttchart.show',
+							'msg'        => $msg,
+						))."';";
+					}
+				}
+				if ($content['edit'] && $this->check_acl(PHPGW_ACL_EDIT))
+				{
+					$view = false;
+				}
+			}
+			if ($content['save'] || $content['cancel'] || $content['delete'])
+			{
+				$js .= 'window.close();';
+				echo '<html><body onload="'.$js.'"></body></html>';
+				$GLOBALS['phpgw']->common->phpgw_exit();
+			}
+		}
+		elseif ($_REQUEST['ms_id'])
+		{
+			$this->milestones->read(array(
+				'ms_id' => $_REQUEST['ms_id'],
+			));
+		}
+		else
+		{
+			$this->milestones->data['pm_id'] = $this->data['pm_id'];
+		}
+		$content = $this->milestones->data + array(
+			'msg' => $msg,
+			'js'  => '<script>'.$js.'</script>',
+		);
+
+		if ($view)
+		{
+			$readonlys = array(
+				'edit'     => !$this->check_acl(PHPGW_ACL_EDIT),
+				'save'     => true,
+				'apply'    => true,
+				'delete'   => true,
+				'pm_id'    => true,
+				'ms_title' => true,
+				'ms_date'  => true,
+				'ms_description' => true,
+			);
+			$sel_options = array(
+				'pm_id' => array($this->data['pm_id'] => $this->data['pm_title']),
+			);
+		}
+		else
+		{
+			$readonlys = array(
+				'edit'   => true,
+			);
+			$sel_options = array(
+				'pm_id' => $this->query_list('pm_title','pm_id'),
+			);
+		}
+		$readonlys['delete'] = !$this->milestones->data['ms_id'] || !$this->check_acl(PHPGW_ACL_EDIT);
+
+		$GLOBALS['phpgw_info']['flags']['app_header'] = lang('projectmanager').' - '.($view ? lang('View milestone') : 
+			($this->milestones->data['ms_id'] ? lang('Edit milestone') : lang('Add milestone')));
+		$this->tpl->read('projectmanager.milestone.edit');
+		$this->tpl->exec('projectmanager.uimilestones.edit',$content,$sel_options,$readonlys,$this->milestones->data+array(
+			'view'  => $view,
+		),2);
+	}	 
+}
Index: /contrib/ProjectManager/inc/class.bocustom_fields.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.bocustom_fields.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.bocustom_fields.inc.php	(revision 3594)
@@ -0,0 +1,106 @@
+<?php
+  /**************************************************************************\
+  * eGroupWare - Calendar - Custom fields and sorting                        *
+  * http://www.egroupware.org                                                *
+  * Written by Ralf Becker <RalfBecker@outdoor-training.de>                  *
+  * --------------------------------------------                             *
+  *  This program is free software; you can redistribute it and/or modify it *
+  *  under the terms of the GNU General Public License as published by the   *
+  *  Free Software Foundation; either version 2 of the License, or (at your  *
+  *  option) any later version.                                              *
+  \**************************************************************************/
+
+
+	class bocustom_fields
+	{
+		var $stock_fields = array(
+			'title' => array(
+				'label' => 'Title',
+				'title' => True
+			),
+			
+			'category'    => 'Category',
+			'location'    => 'Location',
+			
+			'priority'    => 'Priority',
+			'accesso'      => 'accesso',
+			'participants'=> 'Contacto',
+'parent'=>'parent',
+			
+			'owner'       => 'Created By',
+'description1' => 'Description1',
+'description' => 'Description',
+'ex_responsable'=> 'External responsable',
+'startdate'   => 'Start Date/Time',
+			'enddate'     => 'End Date/Time',
+'status'       => 'Status',
+'percent'       => 'Percent',
+'enddate2'     => 'End Date2/End Time2',
+			'updated'     => 'Updated',
+			'alarm'       => 'Alarm',
+			'recure_type' => 'Repetition',
+'participants1'=>'Responsable'
+		);
+
+		function bocustom_fields()
+		{
+			$this->config = CreateObject('phpgwapi.config','calendar');
+			$this->config->read_repository();
+
+			$this->fields = &$this->config->config_data['fields'];
+
+			if (!is_array($this->fields)) {
+				$this->fields = array();
+			}
+
+			foreach ($this->fields as $field => $data)	// this can be removed after a while
+			{
+				if (!isset($this->stock_fields[$field]) && $field[0] != '#')
+				{
+					unset($this->fields[$field]);
+					$this->fields['#'.$field] = $data;
+				}
+			}
+
+			foreach($this->stock_fields as $field => $data)
+			{
+				if (!is_array($data))
+				{
+					$data = array('label' => $data);
+				}
+				if (!isset($this->fields[$field]))
+				{
+					$this->fields[$field] = array(
+						'name'     => $field,
+						'title'    => $data['title'],
+						'disabled' => $data['disabled']
+					);
+				}
+				$this->fields[$field]['label']  = $data['label'];
+				$this->fields[$field]['length'] = $data['length'];
+				$this->fields[$field]['shown']  = $data['shown'];
+			}
+		}
+
+		function set($data)
+		{
+			if (is_array($data) && strlen($data['name']) > 0)
+			{
+				if (!isset($this->stock_fields[$name = $data['name']]))
+				{
+					$name = '#'.$name;
+				}
+				$this->fields[$name] = $data;
+			}
+		}
+
+		function save($fields=False)
+		{
+			if ($fields)
+			{
+				$this->fields = $fields;
+			}
+			//echo "<pre>"; print_r($this->config->config_data); echo "</pre>\n";
+			$this->config->save_repository();
+		}
+	}
Index: /contrib/ProjectManager/inc/class.boprojectmanager.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.boprojectmanager.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.boprojectmanager.inc.php	(revision 3594)
@@ -0,0 +1,868 @@
+<?php
+/**
+ * ProjectManager - General business object
+ *
+ * @link http://www.egroupware.org
+ * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @package projectmanager
+ * @copyright (c) 2005/6 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @version $Id: class.boprojectmanager.inc.php 24677 2007-11-22 14:48:38Z ralfbecker $ 
+ */
+
+include_once(PHPGW_INCLUDE_ROOT.'/projectmanager/inc/class.soprojectmanager.inc.php');
+
+define('PHPGW_ACL_BUDGET',PHPGW_ACL_CUSTOM_1);
+define('PHPGW_ACL_EDIT_BUDGET',PHPGW_ACL_CUSTOM_2);
+
+/**
+ * General business object of the projectmanager
+ *
+ * This class does all the timezone-conversation: All function expect user-time and convert them to server-time
+ * before calling the storage object.
+ */
+class boprojectmanager extends soprojectmanager 
+{
+	/**
+	 * Debuglevel: 0 = no debug-messages, 1 = main, 2 = more, 3 = all, 4 = all incl. so_sql, or string with function-name to debug
+	 * 
+	 * @var int/string
+	 */
+	var $debug=false;
+	/**
+	 * File to log debug-messages, ''=echo them
+	 * 
+	 * @var string
+	 */
+	var $logfile='/tmp/pm_log';
+	/**
+	 * Instance of the link-class
+	 * 
+	 * @var bolink
+	 */
+	var $link;
+	/**
+	 * Timestaps that need to be adjusted to user-time on reading or saving
+	 * 
+	 * @var array
+	 */
+	var $timestamps = array(
+		'pm_created','pm_modified','pm_planned_start','pm_planned_end','pm_real_start','pm_real_end',
+	);
+	/**
+	 * Offset in secconds between user and server-time,	it need to be add to a server-time to get the user-time 
+	 * or substracted from a user-time to get the server-time
+	 * 
+	 * @var int
+	 */
+	var $tz_offset_s;
+	/**
+	 * Current time as timestamp in user-time
+	 * 
+	 * @var int
+	 */
+	var $now_su;
+	/**
+	 * Instance of the soconstraints-class
+	 * 
+	 * @var soconstraints
+	 */
+	var $constraints;
+	/**
+	 * Instance of the somilestones-class
+	 * 
+	 * @var somilestones
+	 */
+	var $milestones;
+	/**
+	 * Instance of the soroles-class, not instanciated automatic!
+	 * 
+	 * @var soroles
+	 */
+	var $roles;
+	/**
+	 * Atm. projectmanager-admins are identical to eGW admins, this might change in the future
+	 * 
+	 * @var boolean
+	 */
+	var $is_admin;
+
+	/**
+	 * Constructor, calls the constructor of the extended class
+	 *
+	 * @param int $pm_id id of the project to load, default null
+	 * @param string $instanciate='' comma-separated: constraints,milestones,roles
+	 * @return boprojectmanager
+	 */
+	function boprojectmanager($pm_id=null,$instanciate='')
+	{
+		if ((int) $this->debug >= 3 || $this->debug == 'projectmanager') $this->debug_message(function_backtrace()."\nboprojectmanager::boprojectmanager($pm_id) started");
+
+		if (!is_object($GLOBALS['phpgw']->datetime))
+		{
+			$GLOBALS['phpgw']->datetime =& CreateObject('phpgwapi.datetime');
+		}
+		$this->tz_offset_s = $GLOBALS['phpgw']->datetime->tz_offset;
+		$this->now_su = time() + $this->tz_offset_s;
+		
+		$this->soprojectmanager($pm_id);
+		
+		// save us in $GLOBALS['boprojectselements'] for ExecMethod used in hooks
+		if (!is_object($GLOBALS['boprojectmanager']))
+		{
+			$GLOBALS['boprojectmanager'] =& $this;
+		}
+		// instanciation of link-class has to be after making us globaly availible, as it calls us to get the search_link
+		if (!is_object($GLOBALS['phpgw']->link))
+		{
+			$GLOBALS['phpgw']->link =& CreateObject('phpgwapi.bolink');
+		}
+		$this->link =& $GLOBALS['phpgw']->link;
+		$this->links_table = $this->link->link_table;
+		
+		// atm. projectmanager-admins are identical to eGW admins, this might change in the future
+		$this->is_admin = isset($GLOBALS['phpgw_info']['user']['apps']['admin']);
+
+		if ($instanciate) $this->instanciate($instanciate);
+		
+		if ((int) $this->debug >= 3 || $this->debug == 'projectmanager') $this->debug_message("boprojectmanager::boprojectmanager($pm_id) finished");
+	}
+	
+	/**
+	 * Instanciates some classes which dont get instanciated by default
+	 *
+	 * @param string $instanciate comma-separated: constraints,milestones,roles
+	 * @param string $pre='so' class prefix to use, default so
+	 */
+	function instanciate($instanciate,$pre='so')
+	{
+		foreach(explode(',',$instanciate) as $class)
+		{
+			if (!is_object($this->$class))
+			{
+				$this->$class =& CreateObject('projectmanager.'.$pre.$class);
+			}
+		}		
+	}
+
+	/**
+	 * Summarize the information of all elements of a project: min(start-time), sum(time), avg(completion), ...
+	 *
+	 * This is implemented in the projectelements class, we call it via ExecMethod
+	 *
+	 * @param int/array $pm_id=null int project-id, array of project-id's or null to use $this->pm_id
+	 * @return array/boolean with summary information (keys as for a single project-element), false on error
+	 */
+	function pe_summary($pm_id=null)
+	{
+		if (is_null($pm_id)) $pm_id = $this->data['pm_id'];
+		
+		if (!$pm_id) return array();
+		
+		return ExecMethod('projectmanager.boprojectelements.summary',$pm_id);
+	}
+
+	/**
+	 * update a project after a change in one of it's project-elements
+	 *
+	 * If the data and the exact changes gets supplied (see params), 
+	 * an whole update or even the update itself might be avoided.
+	 * Not used at the moment!
+	 *
+	 * @param int $pm_id=null project-id or null to use $this->data['pm_id']
+	 * @param int $update_necessary=-1 which fields need updating, or'ed PM_ constants from the datasource class
+	 * @param array $data=null data of the project-element if availible
+	 */
+	function update($pm_id=null,$update_necessary=-1,$data=null)
+	{
+		if (!$pm_id)
+		{
+			$pm_id = $this->data['pm_id'];
+		}
+		elseif ($pm_id != $this->data['pm_id'])
+		{
+			// we need to restore it later
+			$save_data = $this->data;
+
+			$this->read(array('pm_id' => $pm_id));
+		}
+		$pe_summary = $this->pe_summary($pm_id);
+
+		if ((int) $this->debug >= 2 || $this->debug == 'update') $this->debug_message("boprojectmanager::update($pm_id) pe_summary=".print_r($pe_summary,true));
+		
+		if (!$this->pe_name2id)
+		{
+			// we need the PM_ id's
+			include_once(PHPGW_INCLUDE_ROOT.'/projectmanager/inc/class.datasource.inc.php');
+			
+			$ds =& new datasource();
+			$this->pe_name2id = $ds->name2id;
+			unset($ds);
+		}		
+		$save_necessary = false;
+		foreach($this->pe_name2id as $name => $id)
+		{
+			$pm_name = str_replace('pe_','pm_',$name);
+			if (!($this->data['pm_overwrite'] & $id) && $this->data[$pm_name] != $pe_summary[$name])
+			{
+				$this->data[$pm_name] = $pe_summary[$name];
+				$save_necessary = true;
+			}
+		}
+		if ($save_necessary)
+		{
+			$this->save(null,false);	// dont touch modification date
+		}
+		// restore $this->data
+		if (is_array($save_data) && $save_data['pm_id'])
+		{
+			$this->data = $save_data;
+		}	
+	}
+	
+	/**
+	 * saves a project
+	 *
+	 * reimplemented to automatic create a project-ID / pm_number, if empty
+	 *
+	 * @param array $keys if given $keys are copied to data before saveing => allows a save as
+	 * @param boolean $touch_modified=true should modification date+user be set, default yes
+	 * @param boolean $do_notify=true should link::notify be called, default yes
+	 * @return int 0 on success and errno != 0 else
+	 */
+	function save($keys=null,$touch_modified=true,$do_notify=true)
+	{
+		if ($keys) $this->data_merge($keys);
+
+		// check if we have a project-ID and generate one if not
+		if (empty($this->data['pm_number']))
+		{
+			$this->generate_pm_number();
+		}
+		// set creation and modification data
+		if (!$this->data['pm_id'])
+		{
+			$this->data['pm_creator'] = $GLOBALS['phpgw_info']['user']['account_id'];
+			$this->data['pm_created'] = $this->now_su;
+		}
+		if ($touch_modified)
+		{
+			$this->data['pm_modifier'] = $GLOBALS['phpgw_info']['user']['account_id'];
+			$this->data['pm_modified'] = $this->now_su;
+		}
+		if ((int) $this->debug >= 1 || $this->debug == 'save') $this->debug_message("boprojectmanager::save(".print_r($keys,true).",".(int)$touch_modified.") data=".print_r($this->data,true));
+
+		if (!($err = parent::save()) && $do_notify)
+		{
+			// notify the link-class about the update, as other apps may be subscribt to it
+			$this->link->notify_update('projectmanager',$this->data['pm_id'],$this->data);
+		}
+		return $err;
+	}
+	
+	/**
+	 * deletes a project identified by $keys or the loaded one, reimplemented to remove the project-elements too
+	 *
+	 * @param array $keys if given array with col => value pairs to characterise the rows to delete
+	 * @return int affected rows, should be 1 if ok, 0 if an error
+	 */
+	function delete($keys=null)
+	{
+		if ((int) $this->debug >= 1 || $this->debug == 'delete') $this->debug_message("boprojectmanager::delete(".print_r($keys,true).") this->data[pm_id] = ".$this->data['pm_id']);
+
+		$pm_id = is_null($keys) ? $this->data['pm_id'] : (is_array($keys) ? $keys['pm_id'] : $keys);
+		
+		if (($ret = parent::delete($keys)) && $pm_id)
+		{
+			ExecMethod('projectmanager.boprojectelements.delete',array('pm_id' => $pm_id));
+
+			// the following is not really necessary, as it's already one in boprojectelements::delete
+			// delete all links to project $pm_id
+			$this->link->unlink(0,'projectmanager',$pm_id);
+
+			$this->instanciate('constraints,milestones,pricelist,roles');
+
+			// delete all constraints of the project
+			$this->constraints->delete(array('pm_id' => $pm_id));
+	
+			// delete all milestones of the project
+			$this->milestones->delete(array('pm_id' => $pm_id));
+			
+			// delete all pricelist items of the project
+			$this->pricelist->delete(array('pm_id' => $pm_id));
+			
+			// delete all project specific roles
+			$this->roles->delete(array('pm_id' => $pm_id));
+		}
+		return $ret;
+	}
+
+	/**
+	 * changes the data from the db-format to your work-format
+	 *
+	 * reimplemented to adjust the timezone of the timestamps (adding $this->tz_offset_s to get user-time)
+	 * Please note, we do NOT call the method of the parent or so_sql !!!
+	 *
+	 * @param array $data if given works on that array and returns result, else works on internal data-array
+	 * @return array with changed data
+	 */
+	function db2data($data=null)
+	{
+		if (!is_array($data))
+		{
+			$data = &$this->data;
+		}
+		foreach($this->timestamps as $name)
+		{
+			if (isset($data[$name]) && $data[$name]) $data[$name] += $this->tz_offset_s;
+		}
+		if (is_numeric($data['pm_completion'])) $data['pm_completion'] .= '%';
+
+		return $data;
+	}
+
+	/**
+	 * changes the data from your work-format to the db-format
+	 *
+	 * reimplemented to adjust the timezone of the timestamps (subtraction $this->tz_offset_s to get server-time)
+	 * Please note, we do NOT call the method of the parent or so_sql !!!
+	 *
+	 * @param array $data if given works on that array and returns result, else works on internal data-array
+	 * @return array with changed data
+	 */
+	function data2db($data=null)
+	{
+		if ($intern = !is_array($data))
+		{
+			$data = &$this->data;
+		}
+		foreach($this->timestamps as $name)
+		{
+			if (isset($data[$name]) && $data[$name]) $data[$name] -= $this->tz_offset_s;
+		}
+		if (substr($data['pm_completion'],-1) == '%') $data['pm_completion'] = (int) round(substr($data['pm_completion'],0,-1));
+
+		return $data;
+	}
+	
+	/**
+	 * generate a project-ID / pm_number in the form P-YYYY-nnnn (YYYY=year, nnnn=incrementing number)
+	 *
+	 * @param boolean $set_data=true set generated number in $this->data, default true
+	 * @param string $parent='' pm_number of parent, if given a /nnnn is added
+	 * @return string the new pm_number
+	 */
+	function generate_pm_number($set_data=true,$parent='')
+	{
+		$n = 1;
+		do
+		{
+			if ($parent)
+			{
+				$pm_number = sprintf('%s/%04d',$parent,$n++);
+			}
+			else
+			{
+				$pm_number = sprintf('P-%04d-%04d',date('Y'),$n++);
+			}
+		}
+		while ($this->not_unique(array('pm_number' => $pm_number)));
+		
+		if ($set_data) $this->data['pm_number'] = $pm_number;
+		
+		return $pm_number;
+	}
+	
+	/**
+	 * checks if the user has enough rights for a certain operation
+	 *
+	 * Rights are given via owner grants or role based acl
+	 *
+	 * @param int $required PHPGW_ACL_READ, PHPGW_ACL_WRITE, PHPGW_ACL_ADD, PHPGW_ACL_DELETE, PHPGW_ACL_BUDGET, PHPGW_ACL_EDIT_BUDGET
+	 * @param array/int $data=null project or project-id to use, default the project in $this->data
+	 * @param boolean $no_cache=false should a cached value be used, if availible, or not
+	 * @return boolean true if the rights are ok, false if not or null if entry not found
+	 */
+	function check_acl($required,$data=0,$no_cache=false)
+	{
+		static $rights = array();
+		$pm_id = (!$data ? $this->data['pm_id'] : (is_array($data) ? $data['pm_id'] : $data));
+		
+		if (!$pm_id)	// new entry, everything allowed, but delete
+		{
+			return $required != PHPGW_ACL_DELETE;
+		}
+		if (!isset($rights[$pm_id]) || $no_cache)	// check if we have a cache entry for $pm_id
+		{
+			if ($data)
+			{
+				if (!is_array($data))
+				{
+					$data_backup =& $this->data; unset($this->data);
+					$data =& $this->read($data);
+					$this->data =& $data_backup; unset($data_backup);
+				
+					if (!$data) return null;	// $pm_id not found ==> no rights
+				}
+			}
+			else
+			{
+				$data =& $this->data;
+			}
+			// rights come from owner grants or role based acl
+			$rights[$pm_id] = (int) $this->grants[$data['pm_creator']] | (int) $data['role_acl'];
+			
+			// for status or times accounting-type (no accounting) remove the budget-rights from everyone
+			if ($data['pm_accounting_type'] == 'status' || $data['pm_accounting_type'] == 'times')
+			{
+				$rights[$pm_id] &= ~(PHPGW_ACL_BUDGET | PHPGW_ACL_EDIT_BUDGET);
+			}
+		}
+		if ((int) $this->debug >= 2 || $this->debug == 'check_acl') $this->debug_message("boprojectmanager::check_acl($required,pm_id=$pm_id) rights[$pm_id]=".$rights[$pm_id]);
+
+		if ($required == PHPGW_ACL_READ)	// read-rights are implied by all other rights
+		{
+			return (boolean) $rights[$pm_id];
+		}
+		if ($required == PHPGW_ACL_BUDGET) $required |= PHPGW_ACL_EDIT_BUDGET;	// EDIT_BUDGET implies BUDGET
+
+		return (boolean) ($rights[$pm_id] & $required);
+	}
+	
+	/**
+	 * Read a project
+	 * 
+	 * reimplemented to add an acl check
+	 *
+	 * @param array $keys
+	 * @return array/boolean array with project, null if project not found or false if no perms to view it
+	 */
+	function read($keys)
+	{
+		if (!parent::read($keys))
+		{
+			return null;
+		}
+		if (!$this->check_acl(PHPGW_ACL_READ))
+		{
+			return false;
+		}
+		return $this->data;
+	}
+	
+	/**
+	 * get title for an project identified by $entry
+	 * 
+	 * Is called as hook to participate in the linking
+	 *
+	 * @param int/array $entry int pm_id or array with project entry
+	 * @param string/boolean string with title, null if project not found or false if no perms to view it
+	 */
+	function link_title( $entry )
+	{
+		if (!is_array($entry))
+		{
+			$entry = $this->read( $entry );
+		}
+		if (!$entry)
+		{
+			return $entry;
+		}
+		return $entry['pm_number'].': '.$entry['pm_title'];
+	}
+
+	/**
+	 * query projectmanager for entries matching $pattern
+	 *
+	 * Is called as hook to participate in the linking
+	 *
+	 * @param string $pattern pattern to search
+	 * @return array with pm_id - title pairs of the matching entries
+	 */
+	function link_query( $pattern )
+	{
+		$criteria = array();
+		foreach(array('pm_number','pm_title','pm_description') as $col)
+		{
+			$criteria[$col] = $pattern;
+		}
+		$result = array();
+		foreach((array) $this->search($criteria,false,'pm_number','','%',false,'OR',false,array('pm_status'=>'active')) as $prj )
+		{
+			if ($prj['pm_id']) $result[$prj['pm_id']] = $this->link_title($prj);
+		}
+		return $result;
+	}
+	
+	/**
+	 * Hook called by link-class to include projectmanager in the appregistry of the linkage
+	 *
+	 * @param array/string $location location and other parameters (not used)
+	 * @return array with method-names
+	 */
+	function search_link($location)
+	{
+		return array(
+			'query' => 'projectmanager.boprojectmanager.link_query',
+			'title' => 'projectmanager.boprojectmanager.link_title',
+			'view'  => array(
+				'menuaction' => 'projectmanager.uiprojectelements.index',
+			),
+			'view_id' => 'pm_id',
+			'notify' => 'projectmanager.boprojectelements.notify',
+			'add' => array(
+				'menuaction' => 'projectmanager.uiprojectmanager.edit',
+			),
+			'add_app'    => 'link_app',
+			'add_id'     => 'link_id',		
+		);
+	}
+	
+	/**
+	 * gets all ancestors of a given project (calls itself recursivly)
+	 *
+	 * A project P is the parent of an other project C, if link_id1=P.pm_id and link_id2=C.pm_id !
+	 * To get all parents of a project C, we use all links to the project, which link_id2=C.pm_id.
+	 *
+	 * @param int $pm_id=0 id or 0 to use $this->pm_id
+	 * @param array $ancestors=array() already identified ancestors, default none
+	 * @return array with ancestors
+	 */
+	function &ancestors($pm_id=0,$ancestors=array())
+	{
+		static $ancestors_cache = array();	// some caching
+
+		if (!$pm_id && !($pm_id = $this->pm_id)) return false;
+		
+		if (!isset($ancestors_cache[$pm_id]))
+		{
+			$ancestors_cache[$pm_id] = array();
+
+			// read all projectmanager entries attached to this one
+			foreach($this->link->get_links('projectmanager',$pm_id,'projectmanager') as $link_id => $data)
+			{
+				// we need to read the complete link, to know if the entry is a child (link_id1 == pm_id)
+				$link = $this->link->get_link($link_id);
+				if ($link['link_id1'] == $pm_id)
+				{
+					continue;	// we are the parent in this link ==> ignore it
+				}
+				$parent = (int) $link['link_id1'];
+				if (!in_array($parent,$ancestors_cache[$pm_id]))
+				{
+					$ancestors_cache[$pm_id][] = $parent;
+					// now we call ourself recursivly to get all parents of the parents
+					$ancestors_cache[$pm_id] =& $this->ancestors($parent,$ancestors_cache[$pm_id]);
+				}			
+			}
+		}
+		//echo "<p>ancestors($pm_id)=".print_r($ancestors_cache[$pm_id],true)."</p>\n";
+		return array_merge($ancestors,$ancestors_cache[$pm_id]);
+	}
+	
+	/**
+	 * gets recursive all children (only projects) of a given project (calls itself recursivly)
+	 *
+	 * A project P is the parent of an other project C, if link_id1=P.pm_id and link_id2=C.pm_id !
+	 * To get all children of a project C, we use all links to the project, which link_id1=C.pm_id.
+	 *
+	 * @param int $pm_id=0 id or 0 to use $this->pm_id
+	 * @param array $children=array() already identified ancestors, default none
+	 * @return array with children
+	 */
+	function &children($pm_id=0,$children=array())
+	{
+		static $children_cache = array();	// some caching
+
+		if (!$pm_id && !($pm_id = $this->pm_id)) return false;
+		
+		if (!isset($children_cache[$pm_id]))
+		{
+			$children_cache[$pm_id] = array();
+
+			// read all projectmanager entries attached to this one
+			foreach($this->link->get_links('projectmanager',$pm_id,'projectmanager') as $link_id => $data)
+			{
+				// we need to read the complete link, to know if the entry is a child (link_id1 == pm_id)
+				$link = $this->link->get_link($link_id);
+				if ($link['link_id1'] != $pm_id)
+				{
+					continue;	// we are NOT the parent in this link ==> ignore it
+				}
+				$child = (int) $link['link_id2'];
+				if (!in_array($child,$children_cache[$pm_id]))
+				{
+					$children_cache[$pm_id][] = $child;
+					// now we call ourself recursivly to get all parents of the parents
+					$children_cache[$pm_id] =& $this->children($child,$children_cache[$pm_id]);
+				}			
+			}
+		}
+		//echo "<p>children($pm_id)=".print_r($children_cache[$pm_id],true)."</p>\n";
+		return array_merge($children,$children_cache[$pm_id]);
+	}
+	
+	/**
+	 * Query the project-tree from the DB, project tree is indexed by a path consisting of pm_id's delimited by slashes (/)
+	 *
+	 * @param array $filter=array('pm_status' => 'active') filter for the search, default active projects
+	 * @param string $filter_op='AND' AND or OR filters together, default AND
+	 * @return array with path => array(pm_id,pm_number,pm_title,pm_parent) pairs
+	 */
+	function get_project_tree($filter = array('pm_status' => 'active'),$filter_op='AND')
+	{
+		$projects = array();
+		$parents = 'mains';
+		// get the children
+		while (($children = $this->search($filter,$GLOBALS['boprojectmanager']->table_name.'.pm_id AS pm_id,pm_number,pm_title,link_id1 AS pm_parent',
+			'pm_status,pm_number','','',false,$filter_op,false,array('subs_or_mains' => $parents))))
+		{
+			//echo $parents == 'mains' ? "Mains" : "Children of ".implode(',',$parents); _debug_array($children);
+			
+			// sort the children behind the parents
+			$parents = $both = array();
+			foreach ($projects as $parent)
+			{
+				$both[$parent['path']] = $parent;
+				
+				foreach($children as $key => $child)
+				{
+					if ($child['pm_parent'] == $parent['pm_id'])
+					{
+						$child['path'] = $parent['path'] . '/' . $child['pm_id'];
+						$both[$child['path']] = $child;
+						$parents[] = $child['pm_id'];
+						unset($children[$key]);
+					}
+				}
+			}
+			// mains or orphans
+			foreach ($children as $child)
+			{
+				$child['path'] = '/' . $child['pm_id'];
+				$both[$child['path']] = $child;
+				$parents[] = $child['pm_id'];
+				
+			}
+			$projects = $both;
+		}
+		//echo "tree"; _debug_array($projects);
+		return $projects;
+	}
+
+	/**
+	 * write a debug-message to the log-file $this->logfile (if set)
+	 *
+	 * @param string $msg
+	 */
+	function log2file($msg)
+	{
+		if ($this->logfile && ($f = @fopen($this->logfile,'a+')))
+		{
+			fwrite($f,date('Y-m-d H:i:s: ').$GLOBALS['phpgw']->common->grab_owner_name($GLOBALS['phpgw_info']['user']['account_id'])."\n");
+			fwrite($f,$msg."\n\n");
+			fclose($f);
+		}
+	}
+	
+	/**
+	 * EITHER echos a (preformatted / no-html) debug-message OR logs it to a file
+	 *
+	 * @param string $msg
+	 */
+	function debug_message($msg)
+	{
+		$msg = 'Backtrace: '.function_backtrace(2)."\n".$msg;
+
+		if (!$this->logfile)
+		{
+			echo '<pre>'.$msg."</pre>\n";
+		}
+		else
+		{
+			$this->log2file($msg);
+		}
+	}
+
+	/**
+	 * Add a timespan to a given datetime, taking into account the availibility and worktimes of the user
+	 *
+	 * ToDo: take exclusivly blocked times (calendar) into account
+	 *
+	 * @param int $start start timestamp (usertime)
+	 * @param int $time working time in minutes to add, 0 advances to the next working time
+	 * @param int $uid user-id
+	 * @return int/boolean end-time or false if it cant be calculated because user has no availibility or worktime
+	 */
+	function date_add($start,$time,$uid)
+	{
+		// we cache the user-prefs with the working times globaly, as they are expensive to read
+		$user_prefs =& $GLOBALS['phpgw_info']['projectmanager']['user_prefs'][$uid];
+		if (!is_array($user_prefs))
+		{
+			if ($uid == $GLOBALS['phpgw_info']['user']['account_id'])
+			{
+				$user_prefs = $GLOBALS['phpgw_info']['user']['preferences']['projectmanager'];
+			}
+			else
+			{
+				$prefs =& CreateObject('phpgwapi.preferences',$uid);
+				$prefs->read_repository();
+				$user_prefs =& $prefs->data['projectmanager'];
+				unset($prefs);
+			}
+			// calculate total weekly worktime
+			for($day=$user_prefs['duration']=0; $day <= 6; ++$day)
+			{
+				$user_prefs['duration'] += $user_prefs['duration_'.$day];
+			}
+		}
+		$availibility = 1.0;
+		if (isset($this->data['pm_members'][$uid]))
+		{
+			$availibility = $this->data['pm_members'][$uid]['member_availibility'] / 100.0;
+		}
+		$general = $this->get_availibility($uid);
+		if (isset($general[$uid]))
+		{
+			$availibility *= $general[$uid] / 100.0;
+		}
+		// if user has no availibility or no working duration ==> fail
+		if (!$availibility || !$user_prefs['duration'])
+		{
+			return false;
+		}
+		$time_s = $time * 60 / $availibility;
+		
+		if (!is_object($this->bocal))
+		{
+			$this->bocal =& CreateObject('calendar.bocal');
+		}
+		$events =& $this->bocal->search(array(
+			'start' => $start,
+			'end'   => $start+max(10*$time,30*24*60*60),
+			'users' => $uid,
+			'show_rejected' => false,
+			'ignore_acl' => true,
+		));
+		if ($events) $event = array_shift($events);
+
+		$end_s = $start;
+		// we use do-while to allow with time=0 to advance to the next working time
+		do {
+			// ignore non-blocking events or events already over
+			while ($event && ($event['non_blocking'] || $event['end'] <= $end_s))
+			{
+				//echo "<p>ignoring event $event[title]: ".date('Y-m-d H:i',$event['start'])."</p>\n";
+				$event = array_shift($events);
+			}
+			$day = date('w',$end_s);	// 0=Sun, 1=Mon, ...
+			$work_start_s = $user_prefs['start_'.$day] * 60;
+			$max_add_s = 60 * $user_prefs['duration_'.$day];
+			$time_of_day_s = $end_s - mktime(0,0,0,date('m',$end_s),date('d',$end_s),date('Y',$end_s));
+			
+			// befor workday starts ==> go to start of workday
+			if ($max_add_s && $time_of_day_s < $work_start_s)
+			{
+				$end_s += $work_start_s - $time_of_day_s;
+			}
+			// after workday ends or non-working day ==> go to start of NEXT workday
+			elseif (!$max_add_s || $time_of_day_s >= $work_start_s+$max_add_s)	// after workday ends
+			{
+				//echo date('D Y-m-d H:i',$end_s)." ==> go to next day: work_start_s=$work_start_s, time_of_day_s=$time_of_day_s, max_add_s=$max_add_s<br>\n";
+				do {
+					$day = ($day+1) % 7;
+					$end_s = mktime($user_prefs['start_'.$day]/60,$user_prefs['start_'.$day]%60,0,date('m',$end_s),date('d',$end_s)+1,date('Y',$end_s));
+				} while (!($max_add_s = 60 * $user_prefs['duration_'.$day]));
+			}
+			// in the working period ==> adjust max_add_s accordingly
+			else
+			{
+				$max_add_s -= $time_of_day_s - $work_start_s;
+			}
+			$add_s = min($max_add_s,$time_s);
+			
+			//echo date('D Y-m-d H:i',$end_s)." + ".($add_s/60/60)."h / ".($time_s/60/60)."h<br>\n";
+			
+			if ($event)
+			{
+				//echo "<p>checking event $event[title] (".date('Y-m-d H:i',$event['start']).") against end_s=$end_s=".date('Y-m-d H:i',$end_s)." + add_s=$add_s</p>\n";
+				if ($end_s+$add_s > $event['start'])	// event overlaps added period
+				{
+					$time_s -= max(0,$event['start'] - $end_s);	// add only time til events starts (if any)
+					$end_s = $event['end'];				// set time for further calculation to event end
+					//echo "<p>==> event overlaps: time_s=$time_s, end_s=$end_s now</p>\n";
+					$event = array_shift($events);		// advance to next event
+					continue;
+				}
+			}
+			$end_s += $add_s;
+			$time_s -= $add_s;
+		} while ($time_s > 0);
+
+		if ((int) $this->debug >= 3 || $this->debug == 'date_add') $this->debug_message("boprojectmanager::date_add($start=".date('D Y-m-d H:i',$start).", $time=".($time/60.0)."h, $uid)=".date('D Y-m-d H:i',$end_s));
+
+		return $end_s;
+	}
+	
+	/**
+	 * Copies a project
+	 *
+	 * @param int $source id of project to copy
+	 * @param int $only_stage=0 0=both stages plus saving the project, 1=copy of the project, 2=copying the element tree
+	 * @param string $parent_number='' number of the parent project, to create a sub-project-number
+	 * @return int/boolean successful copy new pm_id or true if $only_stage==1, false otherwise (eg. permission denied)
+	 */
+	function copy($source,$only_stage=0,$parent_number='')
+	{
+		if ((int) $this->debug >= 1 || $this->debug == 'copy') $this->debug_message("boprojectmanager::copy($source,$only_stage)");
+
+		if ($only_stage == 2)
+		{
+			if (!(int)$this->data['pm_id']) return false;
+
+			$data_backup = $this->data;
+		}
+		if (!$this->read((int) $source) || !$this->check_acl(PHPGW_ACL_READ))
+		{
+			if ((int) $this->debug >= 1 || $this->debug == 'copy') $this->debug_message("boprojectmanager::copy($source,$only_stage) returning false (not found or no perms), data=".print_r($this->data,true));
+			return false;
+		}
+		if ($only_stage == 2)
+		{
+			$this->data = $data_backup;
+			unset($data_backup);
+		}
+		else
+		{
+			// if user has no budget rights on the source, we need to unset the budget fields
+			if ($this->check_acl(PHPGW_ACL_BUDGET))
+			{
+				include_once(PHPGW_INCLUDE_ROOT.'/projectmanager/inc/class.datasource.inc.php');
+				foreach(array(PM_PLANNED_BUDGET => 'pm_planned_budget',PM_USED_BUDGET => 'pm_used_budget') as $id => $key)
+				{
+					unset($this->data[$key]);
+					$this->data['pm_overwrite'] &= ~$id;
+				}
+			}
+			// we unset a view things, as this should be a new project
+			foreach(array('pm_id','pm_number','pm_creator','pm_created','pm_modified','pm_modifier') as $key)
+			{
+				unset($this->data[$key]);
+			}
+			$this->data['pm_status'] = 'active';
+
+			if ($parent_number) $this->generate_pm_number(true,$parent_number);
+
+			if ($only_stage == 1)
+			{
+				return true;
+			}
+			if ($this->save() != 0) return false;
+		}
+		// copying the element tree
+		$elements =& CreateObject('projectmanager.boprojectelements',$this->data['pm_id']);
+		
+		return $elements->copytree((int) $source) ? $elements->pm_id : false;
+	}
+}
Index: /contrib/ProjectManager/inc/class.somilestones.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.somilestones.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.somilestones.inc.php	(revision 3594)
@@ -0,0 +1,84 @@
+<?php
+/**
+ * ProjectManager - Constraints storage object
+ *
+ * @link http://www.egroupware.org
+ * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @package projectmanager
+ * @copyright (c) 2005 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @version $Id: class.somilestones.inc.php 22355 2006-08-26 16:30:45Z ralfbecker $ 
+ */
+
+include_once(PHPGW_INCLUDE_ROOT.'/etemplate/inc/class.so_sql.inc.php');
+
+/**
+ * Milestones storage object of the projectmanager
+ *
+ * Tables: phpgw_pm_milestones
+ */
+class somilestones extends so_sql
+{
+	/**
+	 * Constructor, calls the constructor of the extended class
+	 * 
+	 * It is sufficent to give a ms_id, as they are unique
+	 *
+	 * @param int $pm_id pm_id of the project to use, default null
+	 * @param int $ms_id ms_id of the milestone to load, default null
+	 */
+	function somilestones($pm_id=null,$ms_id=null)
+	{
+		$this->so_sql('projectmanager','phpgw_pm_milestones');
+
+		if ((int) $ms_id)
+		{
+			$this->read($ms_id);
+			$this->pm_id = $this->data['pm_id'];
+		}
+		if ((int) $pm_id) 
+		{
+			$this->pm_id = (int) $pm_id;
+		}
+	}
+	
+	/**
+	 * searches db for rows matching searchcriteria, reimplemented to automatic add $this->pm_id
+	 *
+	 * '*' and '?' are replaced with sql-wildcards '%' and '_'
+	 *
+	 * @param array/string $criteria array of key and data cols, OR a SQL query (content for WHERE), fully quoted (!)
+	 * @param boolean $only_keys=true True returns only keys, False returns all cols
+	 * @param string $order_by='' fieldnames + {ASC|DESC} separated by colons ',', can also contain a GROUP BY (if it contains ORDER BY)
+	 * @param string/array $extra_cols='' string or array of strings to be added to the SELECT, eg. "count(*) as num"
+	 * @param string $wildcard='' appended befor and after each criteria
+	 * @param boolean $empty=false False=empty criteria are ignored in query, True=empty have to be empty in row
+	 * @param string $op='AND' defaults to 'AND', can be set to 'OR' too, then criteria's are OR'ed together
+	 * @param mixed $start=false if != false, return only maxmatch rows begining with start, or array($start,$num)
+	 * @param array $filter=null if set (!=null) col-data pairs, to be and-ed (!) into the query without wildcards
+	 * @param string $join='' sql to do a join, added as is after the table-name, eg. ", table2 WHERE x=y" or 
+	 *	"LEFT JOIN table2 ON (x=y)", Note: there's no quoting done on $join!
+	 * @return array of matching rows (the row is an array of the cols) or False
+	 */
+	function &search($criteria,$only_keys=True,$order_by='ms_date',$extra_cols='',$wildcard='',$empty=False,$op='AND',$start=false,$filter=null,$join='')
+	{
+		if (!$this->pm_id && !isset($criteria['pm_id']) && !isset($filter['pm_id']))
+		{
+			$filter['pm_id'] = $this->pm_id;
+		}
+		return parent::search($criteria,$only_keys,$order_by,$extra_cols,$wildcard,$empty,$op,$start,$filter,$join);
+	}
+	
+	function &titles($keys=array())
+	{
+		$milestones = array();
+		foreach((array)$this->search($keys,'ms_id,ms_date,ms_title') as $milestone)
+		{
+			if (!$milestone) continue;
+
+			$milestones[$milestone['ms_id']] = date($GLOBALS['phpgw_info']['user']['preferences']['common']['dateformat'],$milestone['ms_date']).
+				': '.$milestone['ms_title'];
+		}
+		return $milestones;
+	}
+}
Index: /contrib/ProjectManager/inc/class.boprojectelements.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.boprojectelements.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.boprojectelements.inc.php	(revision 3594)
@@ -0,0 +1,717 @@
+<?php
+/**
+ * ProjectManager - Elements business object
+ *
+ * @link http://www.egroupware.org
+ * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @package projectmanager
+ * @copyright (c) 2005/6 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @version $Id: class.boprojectelements.inc.php 23913 2007-05-22 04:04:13Z lkneschke $ 
+ */
+
+include_once(PHPGW_INCLUDE_ROOT.'/projectmanager/inc/class.soprojectelements.inc.php');
+
+/**
+ * Elements business object of the projectmanager
+ */
+class boprojectelements extends soprojectelements
+{
+	/**
+	 * Debuglevel: 0 = no debug-messages, 1 = main, 2 = more, 3 = all, 4 = all incl. so_sql, or string with function-name to debug
+	 * 
+	 * @var int/string
+	 */
+	var $debug=false;
+	/**
+	 * Instance of the link-class
+	 * 
+	 * @var bolink
+	 */
+	var $link;
+	/**
+	 * Instance of the boprojectmanager-class
+	 * 
+	 * @var boprojectmanager
+	 */
+	var $project;
+	/**
+	 * Summary information of the current project
+	 * 
+	 * @var array
+	 */
+	var $project_summary;
+	/**
+	 * Instance of the soconstraints-class
+	 * 
+	 * @var soconstraints
+	 */
+	var $constraints;
+	/**
+	 * Instance of the somilestones-class
+	 * 
+	 * @var somilestones
+	 */
+	var $milestones;
+	/**
+	 * Instances of the different datasources
+	 * 
+	 * @var array
+	 */
+	var $datasources = array();
+	/**
+	 * Timestaps that need to be adjusted to user-time on reading or saving
+	 * 
+	 * @var array
+	 */
+	var $timestamps = array(
+		'pe_synced','pe_modified','pe_planned_start','pe_real_start','pe_planned_end','pe_real_end',
+	);
+	/**
+	 * Offset in secconds between user and server-time,	it need to be add to a server-time to get the user-time 
+	 * or substracted from a user-time to get the server-time
+	 * 
+	 * @var int
+	 */
+	var $tz_offset_s;
+	/**
+	 * Current time as timestamp in user-time
+	 * 
+	 * @var int
+	 */
+	var $now_su;
+	/**
+	 * Translates filter-values to allowed stati
+	 * 
+	 * @var array
+	 */
+	var $status_filter = array(
+		'all'     => false,
+		'used'    => array('new','regular'),
+		'new'     => 'new',
+		'ignored' => 'ignore',
+	);
+	/**
+	 * Or'ed id's of the values set by the last call to the updated method
+	 * 
+	 * @var int
+	 */
+	var $updated = 0;
+
+	/**
+	 * Constructor, class the constructor of the extended class
+	 * 
+	 * @param int $pm_id pm_id of the project to use, default null
+	 * @param int $pe_id pe_id of the project-element to load, default null
+	 * @return boprojectelements
+	 */
+	function boprojectelements($pm_id=null,$pe_id=null)
+	{
+		if (!is_object($GLOBALS['phpgw']->datetime))
+		{
+			$GLOBALS['phpgw']->datetime =& CreateObject('phpgwapi.datetime');
+		}
+		$this->tz_offset_s = $GLOBALS['phpgw']->datetime->tz_offset;
+		$this->now_su = time() + $this->tz_offset_s;
+		
+		$this->soprojectelements($pm_id,$pe_id);
+		
+		if (!is_object($GLOBALS['phpgw']->link))
+		{
+			$GLOBALS['phpgw']->link =& CreateObject('phpgwapi.bolink');
+		}
+		$this->link =& $GLOBALS['phpgw']->link;
+		$this->links_table = $this->link->link_table;
+
+		$this->project =& CreateObject('projectmanager.boprojectmanager',$pm_id);
+		$this->config =& $this->project->config;
+		
+		$this->project->instanciate('constraints,milestones');
+		$this->constraints =& $this->project->constraints;
+		$this->milestones  =& $this->project->milestones;
+		
+		$this->project_summary = $this->summary();
+		
+		if ((int)$this->debug >= 3 || $this->debug == 'boprojectelements')
+		{
+			$this->debug_message(function_backtrace()."\nboprojectelements::boprojectelements($pm_id,$pe_id) data=".print_r($this->data,true));
+		}
+		// save us in $GLOBALS['boprojectselements'] for ExecMethod used in hooks
+		if (!is_object($GLOBALS['boprojectselements']))
+		{
+			$GLOBALS['boprojectselements'] =& $this;
+		}
+	}
+	
+	/**
+	 * receives notifications from the link-class: new, deleted links to pm entries, or updated content of linked entries
+	 *
+	 * We only process link- & update-notifications to parent-projects!
+	 * A project P is the parent of an other project C, if link_id1=P.pm_id and link_id2=C.pm_id !
+	 *
+	 * @param array $data array with keys type, id, target_app, target_id, link_id, data
+	 */
+	function notify($data)
+	{
+		if ((int) $this->debug >= 2 || $this->debug == 'notify') $this->debug_message("boprojectelements::notify(link_id=$data[link_id], type=$data[type], target=$data[target_app]-$data[target_id])");
+
+		switch($data['type'])
+		{
+			case 'link':
+			case 'update':
+				// for projectmanager we need to check the direction of the link
+				if ($data['target_app'] == 'projectmanager')
+				{
+					$link = $this->link->get_link($data['link_id']);
+					if ($link['link_id2'] == $data['id'])
+					{
+						return;	// this is a notification to a child / subproject --> ignore it
+					}
+					// for new links we need to make sure the new child is not an ancestor of us
+					if ($data['type'] == 'link')
+					{
+						if (($ancestors = $this->project->ancestors($data['id'])) && in_array($data['target_id'],$ancestors))
+						{
+							if ((int) $this->debug >= 2 || $this->debug == 'notify') $this->debug_message("boprojectelements::notify: cant use pm_id=$data[target_id] as child as it's one of our (pm_id=$data[id]) ancestors=".print_r($ancestors,true));
+							return;	// the link is not used as an project-element, thought it's still a regular link
+						}
+						if ((int) $this->debug >= 3 || $this->debug == 'notify') $this->debug_message("boprojectelements::notify: ancestors($data[id])=".print_r($ancestors,true));
+					}
+				}
+				$this->update($data['target_app'],$data['target_id'],$data['link_id'],$data['id']);
+				break;
+
+			case 'unlink':
+				$this->delete(array('pm_id' => $data['id'],'pe_id' => $data['link_id']));
+				break;
+				
+		}
+	}
+
+	/**
+	 * Updates / creates a project-element with the data of it's datasource
+	 *
+	 * Sets additionally $this->updated with the or'ed id's of the updated values
+	 *
+	 * ToDo: if end-date changed, update elements which have "us" as start-constrain
+	 *
+	 * @param string $app appname
+	 * @param string $id id of $app as used by the link-class and the datasource
+	 * @param int $pe_id=0 element- / link-id or 0 to only read and return the entry, but not save it!
+	 * @param int $pm_id=null project-id, default $this->pm_id
+	 * @param boolean $update_project=true update the data in the project if necessary
+	 * @return array/boolean the updated project-element or false on error (eg. no read access)
+	 */
+	function &update($app,$id,$pe_id=0,$pm_id=null,$update_project=true)
+	{
+		if (!$pm_id) $pm_id = $this->pm_id;
+		
+		if ((int) $this->debug >= 2 || $this->debug == 'update') $this->debug_message("boprojectelements::update(app='$app',id='$id',pe_id=$pe_id,pm_id=$pm_id)");
+
+		if (!$app || !$id || !(int) $pm_id)
+		{
+			return false;
+		}
+		$this->init();
+		$need_save_anyway = false;
+		// check if entry already exists and set basic values if not
+		if (!$pe_id || ($need_save_anyway = !$this->read(array('pm_id'=>$pm_id,'pe_id'=>$pe_id))))
+		{
+			$this->data['pm_id'] = $pm_id;
+			$this->data['pe_id'] = $pe_id;
+			$this->data['pe_overwrite'] = 0;		// none set so far
+			
+			// only set status if it's not set by the datasource
+			if (!isset($this->data['pe_status'])) 
+			{
+				$this->data['pe_status']= 'new';
+			}
+			// if user linking has no ADD rights, the entry is set to ignored
+			if (!$this->check_acl(PHPGW_ACL_ADD,array('pm_id'=>$pm_id)))
+			{
+				$this->data['pe_status']= 'ignore';
+			}
+		}
+		$datasource =& $this->datasource($app);
+		$this->updated = 0;
+
+		if (!($data = $datasource->read($id,$this->data)))
+		{
+			return false;	// eg. no read access, so I cant update
+		}
+		foreach($data as $name => $value)
+		{
+			if (isset($datasource->name2id[$name]) && !($this->data['pe_overwrite'] & $datasource->name2id[$name]) &&
+				$this->data[$name] != $value)
+			{
+				//if ((int) $pe_id) echo "<p>boprojectelements::update($app,$id,$pe_id,$pm_id) $name updated: '{$this->data[$name]}' != '$value'</p>\n";
+				$this->data[$name] = $value;
+				$this->updated |= $datasource->name2id[$name];
+			}
+		}
+		$this->data['pe_synced'] = $this->now_su;
+		
+		if((int) $pe_id && ($need_save_anyway || $this->updated))
+		{
+			$this->save(null,false,$update_project ? $this->updated & ~PM_TITLE & ~PM_DETAILS & ~PM_RESOURCES : 0);	// dont set modified, only synced
+		}
+		return $this->data;
+	}
+
+	/**
+	 * sync all project-elements
+	 *
+	 * The sync of the elements is done by calling the update-method for each (not ignored) element
+	 * in the order of their planned starts and after that calling the projects update methode only 
+	 * once if necessary!
+	 *
+	 * @param int $pm_id=null id of project to use, default null=use $this->pm_id
+	 * @return int number of updated elements
+	 */
+	function &sync_all($pm_id=null)
+	{
+		if (!is_array($GLOBALS['phpgw_info']['flags']['projectmanager']['sync_all_pm_id_visited']))
+		{
+			$GLOBALS['phpgw_info']['flags']['projectmanager']['sync_all_pm_id_visited'] = array();
+		}
+		if (!$pm_id && !($pm_id = $this->pm_id)) return 0;
+		
+		if ((int) $this->debug >= 2 || $this->debug == 'sync_all') $this->debug_message("boprojectelements::sync_all(pm_id=$pm_id)");
+
+		if ($GLOBALS['phpgw_info']['flags']['projectmanager']['sync_all_pm_id_visited'][$pm_id])	// project already visited
+		{
+			if ((int) $this->debug >= 2 || $this->debug == 'sync_all') $this->debug_message("boprojectelements::sync_all(pm_id=$pm_id) stoped recursion, as pm_id in (".implode(',',array_keys($GLOBALS['phpgw_info']['flags']['projectmanager']['sync_all_pm_id_visited'])).")");
+			return 0;							// no further recursion, might lead to an infinit loop
+		}
+		$GLOBALS['phpgw_info']['flags']['projectmanager']['sync_all_pm_id_visited'][$pm_id] = true;
+		
+		$save_project = $this->project->data;
+		
+		$updated = $update_project = 0;
+		++$GLOBALS['phpgw_info']['flags']['projectmanager']['pm_ds_ignore_elements'];
+		foreach((array) $this->search(array('pm_id'=>$pm_id,"pe_status != 'ignore'"),false,'pe_planned_start') as $data)
+		{
+			$this->update($data['pe_app'],$data['pe_app_id'],$data['pe_id'],$pm_id,false);
+			
+			$update_project |= $this->updated & ~PM_TITLE;
+			if ($this->updated) $updated++;
+		}
+		--$GLOBALS['phpgw_info']['flags']['projectmanager']['pm_ds_ignore_elements'];
+		if ($update_project)
+		{
+			$this->project->update($pm_id,$update_project);
+		}
+		if ($this->project->data['pm_id'] != $save_project['pm_id']) $this->project->data =& $save_project;
+
+		unset($GLOBALS['phpgw_info']['flags']['projectmanager']['sync_all_pm_id_visited'][$pm_id]);
+
+		return $updated;
+	}
+
+	/**
+	 * checks if the user has enough rights for a certain operation
+	 *
+	 * The rights on a project-element depend on the rigths on the parent-project:
+	 *	- One can only read an element, if he can read the project (any rights, at least READ on the project)
+	 *	- Adding, editing and deleting of elements require the ADD right of the project (deleting requires the element to exist pe_id!=0)
+	 *	- reading or editing of budgets require the concerned rights of the project
+	 *
+	 * @param int $required PHPGW_ACL_READ, PHPGW_ACL_WRITE, PHPGW_ACL_ADD, PHPGW_ACL_DELETE, PHPGW_ACL_BUDGET or PHPGW_ACL_EDIT_BUDGET
+	 * @param array/int $data=null project-element or pe_id to use, default the project-element in $this->data
+	 * @return boolean true if the rights are ok, false if not
+	 */
+	function check_acl($required,$data=0)
+	{
+		$pe_id = is_array($data) ? $data['pe_id'] : ($data ? $data : $this->data['pe_id']);
+		$pm_id = is_array($data) ? $data['pm_id'] : ($data ? 0 : $this->data['pm_id']);
+		
+		if (!$pe_id && (!$pm_id || $required == PHPGW_ACL_DELETE))
+		{
+			return false;
+		}
+		if (!$pm_id)
+		{
+			$data_backup =& $this->data; unset($this->data);
+			$data =& $this->read($pe_id);
+			$this->data =& $data_backup; unset($data_backup);
+		
+			if (!$data) return false;	// not found ==> no rights
+			
+			$pm_id = $data['pm_id'];
+		}
+		if ($required == PHPGW_ACL_EDIT ||$required ==  PHPGW_ACL_DELETE)
+		{
+			$required = PHPGW_ACL_ADD;	// edit or delete of elements is handled by the ADD right of the project
+		}
+		return $this->project->check_acl($required,$pm_id);
+	}
+	
+	/**
+	 * Get reference to instance of the datasource used for $app
+	 *
+	 * The class has to be named datasource_$app and is search first in the App's inc-dir and then in the one of 
+	 * ProjectManager. If it's not found PM's datasource baseclass is used.
+	 *
+	 * @param string $app appname
+	 * @return object
+	 */
+	function &datasource($app)
+	{
+		if (!isset($this->datasources[$app]))
+		{		
+			if (!file_exists($classfile = PHPGW_INCLUDE_ROOT.'/'.$app.'/inc/class.'.($class='datasource_'.$app).'.inc.php') &&
+				!file_exists($classfile = PHPGW_INCLUDE_ROOT.'/projectmanager/inc/class.'.($class='datasource_'.$app).'.inc.php'))
+			{
+				$classfile = PHPGW_INCLUDE_ROOT.'/projectmanager/inc/class.'.($class='datasource').'.inc.php';
+			}
+			include_once($classfile);
+			$this->datasources[$app] =& new $class($app);
+			// make the project availible for the datasource
+			$this->datasources[$app]->project =& $this->project;
+		}
+		return $this->datasources[$app];	
+	}
+
+	/**
+	 * changes the data from the db-format to your work-format
+	 *
+	 * reimplemented to adjust the timezone of the timestamps (adding $this->tz_offset_s to get user-time)
+	 * Please note, we do NOT call the method of the parent or so_sql !!!
+	 *
+	 * @param array $data if given works on that array and returns result, else works on internal data-array
+	 * @return array with changed data
+	 */
+	function db2data($data=null)
+	{
+		if (!is_array($data))
+		{
+			$data = &$this->data;
+		}
+		foreach($this->timestamps as $name)
+		{
+			if (isset($data[$name]) && $data[$name]) $data[$name] += $this->tz_offset_s;
+		}
+		if (is_numeric($data['pe_completion'])) $data['pe_completion'] .= '%';
+		if ($data['pe_app']) $data['pe_icon'] = $data['pe_app'].'/navbar';
+		if ($data['pe_resources']) $data['pe_resources'] = explode(',',$data['pe_resources']);
+
+		return $data;
+	}
+
+	/**
+	 * changes the data from your work-format to the db-format
+	 *
+	 * reimplemented to adjust the timezone of the timestamps (subtraction $this->tz_offset_s to get server-time)
+	 * Please note, we do NOT call the method of the parent or so_sql !!!
+	 *
+	 * @param array $data if given works on that array and returns result, else works on internal data-array
+	 * @return array with changed data
+	 */
+	function data2db($data=null)
+	{
+		if ($intern = !is_array($data))
+		{
+			$data = &$this->data;
+		}
+		foreach($this->timestamps as $name)
+		{
+			if (isset($data[$name]))
+			{
+				if ($data[$name])
+				{
+					$data[$name] -= $this->tz_offset_s;
+				}
+				else
+				{
+					$data[$name] = null;	// so it's not used for min or max dates
+				}
+			}
+		}
+		if (substr($data['pe_completion'],-1) == '%') $data['pe_completion'] = (int) substr($data['pe_completion'],0,-1);
+
+		if (is_array($data['pe_resources']))
+		{
+			$data['pe_resources'] = count($data['pe_resources']) ? implode(',',$data['pe_resources']) : null;
+		}
+		return $data;
+	}
+	
+	/**
+	 * saves an project-element, reimplemented from SO, to save the remark in the link, if $keys['update_remark']
+	 *
+	 * @param array $keys=null if given $keys are copied to data before saveing => allows a save as
+	 * @param boolean $touch_modified=true should modification date+user be set, default yes
+	 * @param int $update_project=-1 update the data in the project (or'ed PM_ id's), default -1=everything
+	 * @return int 0 on success and errno != 0 else
+	 */
+	function save($keys=null,$touch_modified=true,$update_project=-1)
+	{
+		if ((int) $this->debug >= 1 || $this->debug == 'save') $this->debug_message("boprojectelements::save(".print_r($keys,true).','.(int)$touch_modified.",$update_project) data=".print_r($this->data,true));
+
+		if ($keys['update_remark'] || $this->data['update_remark'])
+		{
+			unset($keys['update_remark']);
+			unset($this->data['update_remark']);
+			$this->link->update_remark($this->data['pe_id'],$this->data['pe_remark']);
+		}
+		if ($keys) $this->data_merge($keys);
+		
+		if ($touch_modified || !$this->data['pe_modified'] || !$this->data['pe_modifier'])
+		{
+			$this->data['pe_modified'] = $this->now_su;
+			$this->data['pe_modifier'] = $GLOBALS['phpgw_info']['user']['account_id'];
+		}
+		if (!$this->data['pm_id']) $this->data['pm_id'] = $this->pm_id;
+
+		if (!($err = parent::save()))
+		{
+			if (is_array($this->data['pe_constraints']))
+			{
+				$this->constraints->save(array(
+					'pm_id' => $this->data['pm_id'],
+					'pe_id' => $this->data['pe_id'],
+				) + $this->data['pe_constraints']);
+			}
+			if ($update_project) 
+			{
+				$this->project->update($this->data['pm_id'],$update_project,$this->data);
+			}
+		}
+		return $err;
+	}
+
+	/**
+	 * deletes a project-element or all project-elements of a project, reimplemented to remove the link too
+	 *
+	 * @param array/int $keys if given array with pm_id and/or pe_id or just an integer pe_id
+	 * @return int affected rows, should be 1 if ok, 0 if an error
+	 */
+	function delete($keys=null)
+	{
+		if (!is_array($keys) && (int) $keys)
+		{
+			$keys = array('pe_id' => (int) $keys);
+		}
+		if (!is_null($keys))
+		{
+			$pm_id = $keys['pm_id'];
+			$pe_id = $keys['pe_id'];
+		}
+		else
+		{
+			$pe_id = $this->data['pe_id'];
+			$pm_id = $this->data['pm_id'];
+		}
+		$ret = parent::delete($keys);
+		
+		if ($pe_id)
+		{
+			// delete one link
+			$this->link->unlink($pe_id);
+			// update the project
+			$this->project->update($pm_id);
+			
+			$this->constraints->delete(array('pe_id' => $pe_id));
+		}
+		elseif ($pm_id)
+		{
+			// delete all links to project $pm_id
+			$this->link->unlink(0,'projectmanager',$pm_id);
+		}	
+		return $ret;
+	}
+	
+	/**
+	 * reads row matched by key and puts all cols in the data array, reimplemented to also read the constraints
+	 *
+	 * @param array $keys array with keys in form internalName => value, may be a scalar value if only one key
+	 * @param string/array $extra_cols string or array of strings to be added to the SELECT, eg. "count(*) as num"
+	 * @param string $join='' sql to do a join, added as is after the table-name, eg. ", table2 WHERE x=y" or 
+	 * @return array/boolean data if row could be retrived else False
+	*/
+	function read($keys,$extra_cols='',$join=true)
+	{
+		if (!($data = parent::read($keys,$extra_cols,$join)))
+		{
+			return false;
+		}
+		$this->data['pe_constraints'] = $this->constraints->read(array(
+			'pm_id' => $this->data['pm_id'],
+			'pe_id' => $this->data['pe_id'],
+		));
+		return $this->data;
+	}
+	
+	/**
+	 * reads the titles of all project-elements specified by $keys
+	 *
+	 * @param array $keys keys of elements to read, default empty = all of the project the class is instanciated for
+	 * @return array with pe_id => lang(pe_app): pe_title pairs
+	 */
+	function &titles($keys=array())
+	{
+		$titles = array();
+		foreach((array) $this->search(array(),'pe_id,pe_title','pe_app,pe_title','','',false,'AND',false,$keys) as $element)
+		{
+			if ($element) $titles[$element['pe_id']] = lang($element['pe_app']).': '.$element['pe_title'];
+		}
+		return $titles;
+	}
+
+	/**
+	 * echos a (preformatted / no-html) debug-message and evtl. log it to a file
+	 *
+	 * It uses the debug_message method of boprojectmanager
+	 *
+	 * @param string $msg
+	 */
+	function debug_message($msg)
+	{
+		$this->project->debug_message($msg);
+	}
+
+	/**
+	 * Copies the elementtree from an other project
+	 *
+	 * This is done by calling the copy method of the datasource (if existent) and then calling update with the (new) app_id
+	 *
+	 * @param int $source
+	 * @return boolean true on success, false otherwise
+	 */
+	function copytree($source)
+	{
+		if ((int) $this->debug >= 2 || $this->debug == 'copytree') $this->debug_message("boprojectelements::copytree($source) this->pm_id=$this->pm_id");
+
+		$elements =& $this->search(array('pm_id' => $source),false,'pe_planned_start');
+		if (!$elements) return true;
+		
+		foreach($elements as $element)
+		{
+			$ds =& $this->datasource($element['pe_app']);
+			
+			if (method_exists($ds,'copy'))
+			{
+				if ((int) $this->debug >= 3 || $this->debug == 'copytree') $this->debug_message("copying $element[pe_app]:$element[pe_app_id] $element[pe_title]");
+				list($app_id,$link_id) = $ds->copy($element,$this->pm_id,$this->project->data);
+			}
+			else	// no copy method, we just link again with that entry
+			{
+				if ((int) $this->debug >= 3 || $this->debug == 'copytree') $this->debug_message("linking $element[pe_app]:$element[pe_app_id] $element[pe_title]");
+				$app_id = $element['pe_app_id'];
+				$link_id = $this->link->link('projectmanager',$this->pm_id,$element['pe_app'],$app_id,$element['pe_remark'],0,0,1);
+			}
+			if ((int) $this->debug >= 3 || $this->debug == 'copytree') $this->debug_message("calling update($element[pe_app],$app_id,$link_id,$this->pm_id,false);");
+
+			if (!$app_id || !$link_id) continue;	// something went wrong, eg. element no longer exists
+
+			$this->update($element['pe_app'],$app_id,$link_id,$this->pm_id,false);	// false=no update of project itself => done once at the end
+
+			// copy evtl. overwriten content from the element
+			if (($need_save = $element['pe_overwrite'] != 0))
+			{
+				foreach($ds->name2id as $name => $id)
+				{
+					if ($element['pe_overwrite'] & $id)
+					{
+						$this->data[$name] = $element[$name];
+					}
+				}
+				$this->data['pe_overwrite'] = $element['pe_overwrite'];
+			}
+			// copy other element data
+			foreach(array('pl_id','pe_cost_per_time','cat_id','pe_share','pe_status') as $name)
+			{
+				if ($name == 'pe_status' && $element['pe_status'] != 'ignore') continue;	// only copy ignored
+
+				if ($this->data[$name] != $element[$name])
+				{
+					$this->data[$name] = $element[$name];
+					$need_save = true;
+				}
+			}	
+			if ($need_save) $this->save(null,true,false);
+		}
+		// now we do one update of our project
+		if ((int) $this->debug >= 3 || $this->debug == 'copytree') $this->debug_message("calling project->update() this->pm_id=$this->pm_id");
+		$this->project->update();
+
+		return true;
+	}
+
+	/**
+	 * Search elements
+	 * 
+	 * Reimplemented to cumulate eg. timesheets in also included infologs, if $filter['cumulate'] is true.
+	 *
+	 * @param array/string $criteria array of key and data cols, OR a SQL query (content for WHERE), fully quoted (!)
+	 * @param boolean $only_keys True returns only keys, False returns all cols
+	 * @param string $order_by fieldnames + {ASC|DESC} separated by colons ','
+	 * @param string/array $extra_cols string or array of strings to be added to the SELECT, eg. "count(*) as num"
+	 * @param string $wildcard appended befor and after each criteria
+	 * @param boolean $empty False=empty criteria are ignored in query, True=empty have to be empty in row
+	 * @param string $op defaults to 'AND', can be set to 'OR' too, then criteria's are OR'ed together
+	 * @param int/boolean $start if != false, return only maxmatch rows begining with start
+	 * @param array $filter if set (!=null) col-data pairs, to be and-ed (!) into the query without wildcards
+	 * @param string/boolean $join=true default join with links-table or string as in so_sql
+	 * @return array of matching rows (the row is an array of the cols) or False
+	 */
+	function search($criteria,$only_keys=True,$order_by='',$extra_cols='',$wildcard='',$empty=False,$op='AND',$start=false,$filter=null,$join=true)
+	{
+		if ($this->pm_id && (!isset($filter['pm_id']) || !$filter['pm_id']))
+		{
+			$filter['pm_id'] = $this->pm_id;
+		}
+		if ($filter['cumulate'])
+		{
+			$cumulate = array();
+			foreach((array)$GLOBALS['phpgw']->hooks->process(array(
+				'location' => 'pm_cumulate',
+				'pm_id' => $filter['pm_id'],
+			)) as $app => $data)
+			{
+				if (is_array($data)) $cumulate += $data;
+			}
+			if ($cumulate)
+			{
+				//echo "<p align=right>cumulate-filter: ".$this->db->expression($this->table_name,'NOT ',array('pe_id' => array_keys($cumulate)))."</p>\n";
+				$filter[] = $this->db->expression($this->table_name,'NOT (',array('pe_id' => array_keys($cumulate)),')');
+			}
+		}
+		$rows = parent::search($criteria,$only_keys,$order_by,$extra_cols,$wildcard,$empty,$op,$start,$filter,$join);
+		
+		if ($rows && $cumulate)
+		{
+			// get the pe_id of all returned rows
+			$row_pe_ids = array();
+			foreach($rows as $k => $row)
+			{
+				$row_pe_ids[$k] = $row['pe_id'];
+			}
+			// get pe_id's of to cumulate entries which are in $rows
+			$cumulate_in = array();
+			foreach($cumulate as $pe_id => $data)
+			{
+				if (in_array($data['other_id'],$row_pe_ids))
+				{
+					$cumulate_in[$pe_id] = $data['other_id'];
+				}
+			}
+			if ($cumulate_in)	// do we have something (timesheets) to cumulate
+			{
+				foreach(parent::search(array('pe_id' => array_keys($cumulate_in)),false) as $to_cumulate)
+				{
+					// get the row, where the entry cumulates
+					if (($k = array_search($cumulate_in[$to_cumulate['pe_id']],$row_pe_ids)) !== false)
+					{
+						//echo "kumulated in ".$rows[$k]['pe_title']; _debug_array($rows);
+						foreach(array('pe_planned_time','pe_used_time','pe_planned_budget','pe_used_budget') as $name)
+						{
+							if ($to_cumulate[$name]) $rows[$k][$name] += $to_cumulate[$name];
+						}
+						//echo "-->"; _debug_array($rows);
+					}
+				}
+			}
+		}
+		return $rows;
+	}}
Index: /contrib/ProjectManager/inc/class.datasource.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.datasource.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.datasource.inc.php	(revision 3594)
@@ -0,0 +1,325 @@
+<?php
+/**
+ * ProjectManager - DataSource baseclass
+ *
+ * @link http://www.egroupware.org
+ * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @package projectmanager
+ * @copyright (c) 2005 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @version $Id: class.datasource.inc.php 22355 2006-08-26 16:30:45Z ralfbecker $ 
+ */
+
+/**
+ * constants for the different types of data
+ *
+ * or'ed together eg. for phpgw_pm_eletemts.pe_overwrite
+ */
+/** int percentage completion 0-100 */
+define('PM_COMPLETION',1);
+/** int seconds planned time */
+define('PM_PLANNED_TIME',2);
+/** int seconds used time */
+define('PM_USED_TIME',4);
+/** double planned budget */
+define('PM_PLANNED_BUDGET',8);
+/** double planned budget */
+define('PM_USED_BUDGET',16);
+/** int timestamp planned start-date */
+define('PM_PLANNED_START',32);
+/** int timestamp real start-date */
+define('PM_REAL_START',64);		
+/** int timestamp planned end-date */
+define('PM_PLANNED_END',128);
+/** int timestamp real end-date */
+define('PM_REAL_END',256);
+/** array with (int) user- or (string) resource-ids */
+define('PM_RESOURCES',512);
+/** string title */
+define('PM_TITLE',1024);
+/** string details */
+define('PM_DETAILS',2048);
+/** int pl_id */
+define('PM_PRICELIST_ID',4096);
+/** double price */
+define('PM_UNITPRICE',8192);
+/** double planned quantity */
+define('PM_PLANNED_QUANTITY',16384);
+/** double used quantity */
+define('PM_USED_QUANTITY',32768);
+/** all data-types or'ed together, need to be changed if new data-types get added */
+define('PM_ALL_DATA',65535);
+
+/**
+ * DataSource baseclass of the ProjectManager
+ *
+ * This is the baseclass of all DataSources, each spezific DataSource extends it and implement 
+ * an own get method.
+ *
+ * The read method of this class sets (if not set by the get method) the planned start- and endtime:
+ *  - planned start from the end of a start constrain or the project start-time
+ *  - planned end from the planned time and a start-time
+ *  - real or planned start and end from each other
+ *
+ * @package projectmanager
+ * @author RalfBecker-AT-outdoor-training.de
+ * @copyright (c) 2005 by RalfBecker-AT-outdoor-training.de
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ */
+class datasource
+{
+	/**
+	 * @var string $type type of the datasource, eg. name of the supported app
+	 */
+	var $type;
+	/**
+	 * @var bolink-object $link instance of the link-class
+	 */
+	var $link;
+	/**
+	 * @var object $bo bo-object of the used app
+	 */
+	var $bo;
+	/**
+	 * @var int $valid valid data-types of that source (or'ed PM_ constants)
+	 */
+	var $valid = 0;
+	/**
+	 * @var array $name2id translated names / array-keys to the numeric ids PM_*
+	 */
+	var $name2id = array(
+		'pe_completion'     => PM_COMPLETION,
+		'pe_planned_time'   => PM_PLANNED_TIME,
+		'pe_used_time'      => PM_USED_TIME,
+		'pe_planned_budget' => PM_PLANNED_BUDGET,
+		'pe_used_budget'    => PM_USED_BUDGET,
+		'pe_planned_start'  => PM_PLANNED_START,
+		'pe_real_start'     => PM_REAL_START,
+		'pe_planned_end'    => PM_PLANNED_END,
+		'pe_real_end'       => PM_REAL_END,
+		'pe_title'          => PM_TITLE,
+		'pe_resources'		=> PM_RESOURCES,
+		'pe_details'		=> PM_DETAILS,
+		'pl_id'				=> PM_PRICELIST_ID,
+		'pe_unitprice'      => PM_UNITPRICE,
+		'pe_planned_quantity' => PM_PLANNED_QUANTITY,
+		'pe_used_quantity'  => PM_USED_QUANTITY,		
+	);
+	/**
+	 * @var boprojectelements-object $bo_pe pe object to read other pe's (eg. for constraints)
+	 */
+	var $bo_pe;
+
+	/**
+	 * Constructor
+	 *
+	 * @param string $type=null type of the datasource
+	 */
+	function datasource($type=null)
+	{
+		$this->type = $type;
+
+		if (!is_object($GLOBALS['phpgw']->link))
+		{
+			$GLOBALS['phpgw']->link =& CreateObject('phpgwapi.bolink');
+		}
+		$this->link =& $GLOBALS['phpgw']->link;
+	}
+	
+	/**
+	 * get an item from the underlaying app and convert applying data ia a datasource array
+	 *
+	 * A datasource array can contain values for the keys: completiton, {planned|used}_time, {planned|used}_budget,
+	 *	{planned|real}_start, {planned|real}_end and pe_status
+	 * Not set values mean they are not supported by the datasource.
+	 *
+	 * Reimplent this function for spezial datasource types (not read!)
+	 * 
+	 * @param mixed $data_id id as used in the link-class for that app, or complete entry as array
+	 * @return array/boolean array with the data supported by that source or false on error (eg. not found, not availible)
+	 */
+	function get($data_id)
+	{
+		if (($title = $this->link->title($this->type,$data_id)))
+		{
+			return array(
+				'pe_title'  => $title,
+				'pe_status' => 'ignore',	// not supported datasources are ignored, as they contain no values, eg. addressbook
+			);
+		}
+		return false;
+	}
+	
+	/**
+	 * read an item from a datasource (via the get methode) and try to set (guess) some not supported values
+	 *
+	 * A datasource array can contain values for the keys: completiton, {planned|used}_time, {planned|used}_budget,
+	 *	{planned|real}_start, {planned|real}_end
+	 * Not set values mean they are not supported by the datasource.
+	 * 
+	 * @param mixed $data_id id as used in the link-class for that app, or complete entry as array
+	 * @param array $pe_data data of the project-element or null, eg. to use the constraints
+	 * @return array/boolean array with the data supported by that source or false on error (eg. not found, not availible)
+	 */
+	function read($data_id,$pe_data=null)
+	{
+		$ds = $this->get($data_id);
+		
+		//echo "<p>datasource::read($data_id,$pe_data) ds="; _debug_array($ds);
+		
+		if ($ds)
+		{
+			// setting a not set planned start from a contrains
+			if ((!$ds['pe_planned_start'] || $ds['ignore_planned_start']) && !is_null($pe_data) && $pe_data['pe_constraints']['start'])
+			{
+				//echo "start-constr."; _debug_array($pe_data['pe_constraints']['start']);
+				$start = 0;
+				if (!is_object($this->bo_pe))
+				{
+					$this->bo_pe =& CreateObject('projectmanager.boprojectelements',$pe_data['pm_id']);
+				}
+				foreach($pe_data['pe_constraints']['start'] as $start_pe_id)
+				{
+					if ($this->bo_pe->read(array('pm_id'=>$pe_data['pm_id'],'pe_id'=>$start_pe_id)) &&
+						$start < $this->bo_pe->data['pe_planned_end'])
+					{
+						$start = $this->bo_pe->data['pe_planned_end'];
+						//echo "startdate from startconstrain with"; _debug_array($this->bo_pe->data);
+					}
+				}
+				if ($start)
+				{
+					$ds['pe_planned_start'] = $this->project->date_add($start,0,$ds['pe_resources'][0]);
+					//echo "<p>$ds[pe_title] set planned start to ".date('D Y-m-d H:i',$ds['pe_planned_start'])."</p>\n";
+					unset($ds['ignore_planned_start']);
+				}
+			}
+			// setting the planned start from the real-start
+			if (!$ds['pe_planned_start'] && !$ds['ignore_planned_start'] && $ds['pe_real_start'])
+			{
+				$ds['pe_planned_start'] = $ds['pe_real_start'];
+			}
+			// setting the planned start from the projects start
+			if ((!$ds['pe_planned_start'] || $ds['ignore_planned_start']) && $pe_data['pm_id'])
+			{
+				if (!is_object($this->bo_pe))
+				{
+					$this->bo_pe =& CreateObject('projectmanager.boprojectelements',$pe_data['pm_id']);
+				}
+				if ($this->bo_pe->pm_id != $pe_data['pm_id'])
+				{
+					$this->bo_pe->boprojectelements($pe_data['pm_id']);
+				}
+				if ($this->bo_pe->project->data['pm_planned_start'] || $this->bo_pe->project->data['pm_real_start'])
+				{
+					$ds['pe_planned_start'] = $this->bo_pe->project->data['pm_planned_start'] ? 
+						$this->bo_pe->project->data['pm_planned_start'] : $this->bo_pe->project->data['pm_real_start'];
+					unset($ds['ignore_planned_start']);
+				}
+			}
+			// calculating the planned end-date from the planned time
+			if ((!$ds['pe_planned_end'] || $ds['ignore_planned_end']) && $ds['pe_planned_time'])
+			{
+				if ($ds['pe_planned_start'] && is_object($this->project))
+				{
+					$ds['pe_planned_end'] = $this->project->date_add($ds['pe_planned_start'],$ds['pe_planned_time'],$ds['pe_resources'][0]);
+					//echo "<p>$ds[pe_title] set planned end to ".date('D Y-m-d H:i',$ds['pe_planned_end'])."</p>\n";
+					unset($ds['ignore_planned_end']);
+				}
+			}
+			// setting real or planned start- or end-date, from each other if not set
+			foreach(array('start','end') as $name)
+			{
+				if ((!isset($ds['pe_real_'.$name]) || $ds['ignore_real_'.$name]) && isset($ds['pe_planned_'.$name]) /*&&
+				// setting the real dates only if the completion is more then 0% (if supported by the datasource)
+					(!isset($ds['pe_completion']) || $ds['pe_completion'] > 0)*/)
+				{
+					$ds['pe_real_'.$name] = $ds['pe_planned_'.$name];
+				}
+				elseif (!isset($ds['pe_planned_'.$name]) && isset($ds['pe_real_'.$name]))
+				{
+					$ds['pe_planned_'.$name] = $ds['pe_real_'.$name];
+				}
+			}
+			// try calculating a (second) completion from the times
+			if (!empty($ds['pe_used_time']) && (int) $ds['pe_planned_time'] > 0)
+			{
+				$compl_by_time = $ds['pe_used_time'] / $ds['pe_planned_time'];
+
+				// if no completion is given by the datasource use the calculated one
+				if (!isset($ds['pe_completion']))
+				{
+					$ds['pe_completion'] = $compl_by_time;
+				}
+				elseif ($compl_by_time < $ds['pe_completion'])
+				{
+					$ds['warning']['completion_by_time'] = $compl_by_time;
+				}
+			}
+			// try calculating a (second) completion from the budget
+			if(!empty($ds['pe_used_budget']) && $ds['pe_planned_budget'] > 0)
+			{
+				$compl_by_budget = $ds['pe_used_budget'] / $ds['pe_planned_budget'];
+			
+				// if no completion is given by the datasource use the calculated one
+				if (!isset($ds['pe_completion']))
+				{
+					$ds['pe_completion'] = $compl_by_budget;
+				}
+				elseif ($compl_by_budget < $ds['pe_completion'])
+				{
+					$ds['warning']['completion_by_budget'] = $compl_by_budget;
+				}
+			}
+			// setting quantity from time, if not given by the ds
+			foreach(array(
+				'pe_planned_time' => 'pe_planned_quantity',
+				'pe_used_time'    => 'pe_used_quantity',
+			) as $time => $quantity)
+			{
+				if (!isset($ds[$quantity]) && isset($ds[$time]))
+				{
+					$ds[$quantity] = $ds[$time] / 60.0;	// time is in min, quantity in h
+				}
+			}
+			// setting the budget from unitprice and quantity
+			if (isset($ds['pe_unitprice']))
+			{
+				foreach(array(
+					'pe_planned_quantity' => 'pe_planned_budget',
+					'pe_used_quantity'    => 'pe_used_budget',
+				) as $quantity => $budget)
+				{
+					if (!isset($ds[$budget]) && isset($ds[$quantity]))
+					{
+						$ds[$budget] = $ds[$quantity] * $ds['pe_unitprice'];
+					}
+				}
+			}
+				
+		}
+		return $ds;
+	}
+	
+	/**
+	 * reading the not-overwritten values from the element-data
+	 *
+	 * Can be used instead of read, if there's no read-access to the datasource itself
+	 *
+	 * @param array $data element data
+	 * @return array
+	 */
+	function element_values($data)
+	{
+		$values = array();
+
+		foreach($this->name2id as $name => $id)
+		{
+			if (!($data['pe_overwrite'] & $id))
+			{
+				$values[$name] = $data[$name];
+			}
+		}
+		return $values;
+	}
+}
Index: /contrib/ProjectManager/inc/class.uipricelist.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.uipricelist.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.uipricelist.inc.php	(revision 3594)
@@ -0,0 +1,357 @@
+<?php
+/**
+ * ProjectManager - Pricelist user interface
+ *
+ * @link http://www.egroupware.org
+ * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @package projectmanager
+ * @copyright (c) 2005 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @version $Id: class.uipricelist.inc.php 22355 2006-08-26 16:30:45Z ralfbecker $ 
+ */
+
+include_once(PHPGW_INCLUDE_ROOT.'/projectmanager/inc/class.bopricelist.inc.php');
+include_once(PHPGW_INCLUDE_ROOT.'/etemplate/inc/class.uietemplate.inc.php');
+
+/**
+ * Pricelist user interface of the projectmanager
+ */
+class uipricelist extends bopricelist
+{
+	/**
+	 * Functions callable via menuaction
+	 *
+	 * @var unknown_type
+	 */
+	var $public_functions = array(
+		'index' => true,
+		'view'  => true,
+		'edit'  => true,
+	);
+	var $billable_lables = array('bookable','billable');
+	/**
+	 * Constructor, calls the constructor of the extended class
+	 *
+	 * @param int $pm_id=null project to use
+	 * @return uipricelist
+	 */
+	function uipricelist($pm_id=null)
+	{
+		if (!is_null($pm_id) || isset($_REQUEST['pm_id']))
+		{
+			if (is_null($pm_id)) $pm_id = (int) $_REQUEST['pm_id'];
+			$GLOBALS['phpgw']->session->appsession('pm_id','projectmanager',$pm_id);
+		}
+		else
+		{
+			$pm_id = (int) $GLOBALS['phpgw']->session->appsession('pm_id','projectmanager');
+		}
+		$this->bopricelist($pm_id);	// sets $this->pm_id
+	}
+	
+	function view()
+	{
+		return $this->edit(null,true);
+	}
+	
+	function edit($content=null,$view=false,$msg='')
+	{
+		$tpl =& new etemplate('projectmanager.pricelist.edit');
+		$tabs = 'price|project|description';
+		
+		if (!is_array($content))
+		{
+			if (($pl_id = (int) $_GET['pl_id']) && $this->read(array(
+				'pl_id' => $pl_id,
+				'pm_id' => $this->pm_id ? array($this->pm_id,0) : 0,
+			)))
+			{
+				
+				// perms are checked later, see view_...prices
+			}
+			else	// add new price
+			{
+				$pl_id = 0;
+				$view = false;
+				$this->data = array(
+					'prices' => array(),
+					'project_prices' => array(),
+					'pm_id' => $this->pm_id,
+				);
+			}
+			// no READ or EDIT/ADD rights ==> close the popup
+			if (!$this->check_acl($view ? PHPGW_ACL_READ : PHPGW_ACL_EDIT) && 
+				!($this->pm_id && $this->check_acl($view ? PHPGW_ACL_READ : PHPGW_ACL_EDIT,$this->pm_id)))
+			{
+				$js = "alert('".lang('Permission denied !!!')."'); window.close();";
+				$GLOBALS['phpgw']->common->phpgw_header();
+				echo "<script>\n$js\n</script>\n";
+				$GLOBALS['phpgw']->common->phpgw_exit();
+			}
+			if (count($this->data['project_prices'])) $content[$tabs] = 'project';	// open project tab
+			$pm_id = count($this->data['project_prices']) ? $this->data['project_prices'][0]['pm_id'] : $this->pm_id;
+		}
+		else
+		{
+			$this->data = $content;
+			foreach(array('view','button','delete_price','delete_project_price',$tabs) as $key)
+			{
+				unset($this->data[$key]);
+			}
+			$pl_id = $content['pl_id'];
+			$pm_id = $content['pm_id'];
+			// copy only non-empty and not deleted prices to $this->data[*prices]
+			foreach(array('prices' => 1,'project_prices' => 3) as $name => $row)
+			{
+				$this->data[$name] = array();
+				$delete =& $content[$name == 'prices' ? 'delete_price' : 'delete_project_price'];
+				while (isset($content[$name][$row]))
+				{
+					$price = $content[$name][$row];
+					if ($price['pl_price'] && !$delete[$row])
+					{
+						$this->data[$name][] = $price;
+					}
+					++$row;
+				}
+			}
+			if (!$this->data['pl_unit']) $this->data['pl_unit'] = lang('h');
+
+			list($button) = @each($content['button']);
+			switch($button)
+			{
+				case 'save':
+				case 'apply':
+					if (!($err = $this->save()))
+					{
+						$msg = lang('Price saved');
+					}
+					else
+					{
+						$msg = lang('Error: saving the price (%1) !!!',$err);
+						$button = 'apply';	// dont close the window
+					}
+					$js = "window.opener.location.href='".$GLOBALS['phpgw']->link('/index.php',array(
+						'menuaction' => 'projectmanager.uipricelist.index',
+						'msg' => $msg,
+					))."';";
+					if ($button == 'apply') break;
+					// fall through
+				case 'cancel':
+					$js .= 'window.close();';
+					echo '<html><body onload="'.$js.'"></body></html>';
+					$GLOBALS['phpgw']->common->phpgw_exit();
+					break;
+					
+				case 'edit':
+					$view = false;	// acl is ensured later, see $view_*prices
+					break;
+			}
+		}
+		$view_prices = $view || count($this->data['prices']) && !$this->check_acl(PHPGW_ACL_EDIT);
+		$view_project_prices = $view || !$this->check_acl(PHPGW_ACL_EDIT,$this->pm_id);
+		$view = $view || $view_prices && $view_project_prices;	// nothing to edit => no rights
+
+		$content = $this->data + array(
+			'msg' => $msg,
+			'js'  => $js ? "<script>\n".$js."\n</script>" : '',
+			'view' => $view,
+			'view_prices' => $view_prices,
+			'view_project_prices' => $view_project_prices,
+			$tabs => $content[$tabs],
+		);
+		// adjust index and add empty price-lines for adding new prices
+		$content['prices'] = array_merge(array(1),$view_prices ? array() : array(array('pl_price'=>'')),$this->data['prices']);
+		$content['project_prices'] = array_merge(array(1,2,3),$view_project_prices ? array() : array(array('pl_price'=>'')),$this->data['project_prices']);
+
+		$preserv = $this->data + array(
+			'view'  => $view,
+		);
+		if (!$this->data['pm_id']) $preserv['pm_id'] = $this->pm_id;
+
+		$readonlys = array(
+			'button[delete]' => !$pl_id || !$this->check_acl(PHPGW_ACL_EDIT_BUDGET,$pl_id),
+		);
+		// preserv the "real" prices, with there new keys
+		foreach(array('prices','project_prices') as $name)
+		{
+			unset($preserv[$name]);
+			foreach($content[$name] as $key => $price)
+			{
+				if (is_array($price) && count($price) > 1)
+				{
+					$preserv[$name][$key] = $price;
+				}
+			}
+		}
+		// set general data and price readonly, if $view or price belongs to general pricelist and no edit there
+		if ($view || count($this->data['prices']) && !$this->check_acl(PHPGW_ACL_EDIT))
+		{
+			foreach($this->db_cols as $name => $data)
+			{
+				$readonlys[$name] = true;
+			}
+			for($n = 0; $n <= count($this->data['prices']); ++$n)
+			{
+				$readonlys['prices['.(1+$n).'][pl_price]'] = 
+				$readonlys['prices['.(1+$n).'][pl_validsince]'] = true;
+			}
+		}
+		// set project-spez. prices readonly, if view or no edit-rights there 
+		if ($view || !$this->check_acl(PHPGW_ACL_EDIT,$this->pm_id))
+		{
+			foreach(array('pl_billable','pl_customertitle') as $name)
+			{
+				$readonlys[$name] = true;
+			}
+			for($n = 0; $n <= count($this->data['project_prices']); ++$n)
+			{
+				$readonlys['project_prices['.(3+$n).'][pl_price]'] = 
+				$readonlys['project_prices['.(3+$n).'][pl_validsince]'] = true;
+			}
+		}
+		$readonlys['button[save]'] = $readonlys['button[apply]'] = $view;
+		$readonlys['button[edit]'] = !$view || !$this->check_acl(PHPGW_ACL_EDIT) && !$this->check_acl(PHPGW_ACL_EDIT,$this->pm_id);
+		
+		if (!$this->pm_id)	// no project tab for the general pricelist
+		{
+			$readonlys[$tabs]['project'] = true;
+		}
+		// no general price tab, if there are none and no rights to edit the general pricelist
+		if (!count($this->data['prices']) && !$this->check_acl(PHPGW_ACL_EDIT))
+		{
+			$readonlys[$tabs]['price'] = true;
+		}
+		$GLOBALS['phpgw_info']['flags']['app_header'] = lang('projectmanager').' - '.
+			($view ? lang('View price') :  ($pl_id ? lang('Edit price') : lang('Add price'))) .
+			($this->pm_id ? ': ' . $this->project->data['pm_number'] . ': ' .$this->project->data['pm_title'] : '');
+
+		//_debug_array($content);
+		//_debug_array($readonlys);
+		return $tpl->exec('projectmanager.uipricelist.edit',$content,array(
+			'pl_billable' => $this->billable_lables,
+		),$readonlys,$preserv,2);
+	}
+	
+	/**
+	 * query pricelist for nextmatch
+	 *
+	 * reimplemented from so_sql to disable action-buttons based on the acl and make some modification on the data
+	 *
+	 * @param array $query
+	 * @param array &$rows returned rows/cups
+	 * @param array &$readonlys eg. to disable buttons based on acl
+	 */
+	function get_rows(&$query,&$rows,&$readonlys)
+	{
+		$GLOBALS['phpgw']->session->appsession('pricelist','projectmanager',$query);
+	
+		if ($query['cat_id'])
+		{
+			$query['col_filter']['cat_id'] = $query['cat_id'];
+		}
+		if ($query['col_filter']['pm_id'] === '' || !$this->check_acl(PHPGW_ACL_READ,$query['col_filter']['pm_id']))
+		{
+			unset($query['col_filter']['pm_id']);
+		}
+		elseif ($query['col_filter']['pm_id'] != $this->pm_id)
+		{
+			$this->uipricelist($query['col_filter']['pm_id']);
+		}
+		if ($query['col_filter']['pl_billable'] === '') unset($query['col_filter']['pl_billable']);
+
+		$total = parent::get_rows($query,$rows,$readonlys,true);
+
+		$readonlys = array();
+		foreach($rows as $n => $val)
+		{
+			$row =& $rows[$n];
+			if (!$this->check_acl(PHPGW_ACL_EDIT) && !($this->pm_id && $this->check_acl(PHPGW_ACL_EDIT,$this->pm_id)))
+			{
+				$readonlys["edit[$row[pl_id]]"] = true;
+			}
+			// we only delete prices from the shown pricelist, not inhirited ones or onces from the general list
+			if ($row['pm_id'] != $this->pm_id || !$this->check_acl(PHPGW_ACL_EDIT,$this->pm_id))
+			{
+				$readonlys["delete[$row[pm_id]:$row[pl_id]]"] = true;
+			}
+		}
+		$rows['standard_only'] = !$this->pm_id;
+
+		return $total;		
+	}
+
+	function index($content=null,$msg='')
+	{
+		while (!$this->check_acl(PHPGW_ACL_READ,$this->pm_id))
+		{
+			if ($this->pm_id)	// try falling back to the general pricelist
+			{
+				$GLOBALS['phpgw']->session->appsession('pm_id','projectmanager',$_REQUEST['pm_id'] = $this->pm_id = 0);
+			}
+			else
+			{
+				$GLOBALS['phpgw']->redirect_link('/index.php',array(
+					'menuaction' => 'projectmanager.uiprojectmanager.index',
+					'msg' => lang('Permission denied !!!'),
+				));
+			}
+		}
+		$tpl =& new etemplate('projectmanager.pricelist.list');
+
+		if (!is_array($content))
+		{
+			$content = array();
+		}
+		elseif($content['nm']['rows']['delete'])
+		{
+			list($id) = @each($content['nm']['rows']['delete']);
+			list($pm_id,$pl_id) = explode(':',$id);
+
+			if ($pl_id && $this->delete(array('pm_id' => $pm_id,'pl_id' => $pl_id)))
+			{
+				$msg = lang('Price deleted');
+			}
+			else
+			{
+				$msg = lang('Permission denied !!!');
+			}
+		}
+		$content['msg'] = $msg ? $msg : $_GET['msg'];
+		$content['nm'] = $GLOBALS['phpgw']->session->appsession('pricelist','projectmanager');
+		if (!is_array($content['nm']))
+		{
+			$content['nm'] = array(
+				'get_rows'       =>	'projectmanager.uipricelist.get_rows',
+				'no_filter'      => true,
+				'no_filter2'     => true,
+				'order'          =>	'pl_title',// IO name of the column to sort after (optional for the sortheaders)
+				'sort'           =>	'DESC',// IO direction of the sort: 'ASC' or 'DESC'
+			);
+		}
+		$content['nm']['col_filter']['pm_id'] = $this->pm_id;
+
+		$GLOBALS['phpgw_info']['flags']['app_header'] = lang('projectmanager').' - '.($this->pm_id ? 
+			lang('Pricelist') .': ' . $this->project->data['pm_number'] . ': ' .$this->project->data['pm_title'] : 
+			lang('General pricelist'));
+
+		$projects = array();
+		foreach((array)$this->project->search(array(
+			'pm_status' => 'active',
+			'pm_id'     => $this->pm_id,		// active or the current one
+		),$this->project->table_name.'.pm_id AS pm_id,pm_number,pm_title','pm_number','','',False,'OR',false,array('pm_accounting_type' => 'pricelist')) as $project)
+		{
+			$projects[$project['pm_id']] = $project['pm_number'].': '.$project['pm_title'];
+		}
+		$projects[0] = lang('General pricelist');
+		
+		$readonlys = array(
+			// show add button only, if user has rights to add a new price
+			'add' => !$this->check_acl(PHPGW_ACL_EDIT,$this->pm_id),
+		);
+		return $tpl->exec('projectmanager.uipricelist.index',$content,array(
+			'pl_billable' => $this->billable_lables,
+			'pm_id' => $projects,
+		),$readonlys);
+	}
+}
Index: /contrib/ProjectManager/inc/class.soinfolog__.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.soinfolog__.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.soinfolog__.inc.php	(revision 3594)
@@ -0,0 +1,214 @@
+<?php
+  /**************************************************************************\
+  * eGroupWare - infolog                                                    *
+  * http://www.egroupware.org                                                *
+  * Based on Webinfolog by Craig Knudsen <cknudsen@radix.net>               *
+  *          http://www.radix.net/~cknudsen                                  *
+  * Modified by Mark Peters <skeeter@phpgroupware.org>                       *
+  * --------------------------------------------                             *
+  *  This program is free software; you can redistribute it and/or modify it *
+  *  under the terms of the GNU General Public License as published by the   *
+  *  Free Software Foundation; either version 2 of the License, or (at your  *
+  *  option) any later version.                                              *
+  \**************************************************************************/
+
+
+	if (@$GLOBALS['phpgw_info']['flags']['included_classes']['soinfolog__'])
+	{
+		return;
+	}
+
+	$GLOBALS['phpgw_info']['flags']['included_classes']['soinfolog__'] = True;
+
+	/*	include(PHPGW_SERVER_ROOT.'/infolog/setup/setup.inc.php');	*/
+
+	if(extension_loaded('mcal') == False)
+	{
+		define('MCAL_RECUR_NONE',0);
+		define('MCAL_RECUR_DAILY',1);
+		define('MCAL_RECUR_WEEKLY',2);
+		define('MCAL_RECUR_MONTHLY_MDAY',3);
+		define('MCAL_RECUR_MONTHLY_WDAY',4);
+		define('MCAL_RECUR_YEARLY',5);
+		
+		define('MCAL_M_SUNDAY',1);
+		define('MCAL_M_MONDAY',2);
+		define('MCAL_M_TUESDAY',4);
+		define('MCAL_M_WEDNESDAY',8);
+		define('MCAL_M_THURSDAY',16);
+		define('MCAL_M_FRIDAY',32);
+		define('MCAL_M_SATURDAY',64);
+		
+		define('MCAL_M_WEEKDAYS',62);
+		define('MCAL_M_WEEKEND',65);
+		define('MCAL_M_ALLDAYS',127);
+	}
+
+	define('MSG_DELETED',0);
+	define('MSG_MODIFIED',1);
+	define('MSG_ADDED',2);
+	define('MSG_REJECTED',3);
+	define('MSG_TENTATIVE',4);
+	define('MSG_ACCEPTED',5);
+	define('MSG_ALARM',6);
+
+	define('REJECTED',0);
+	define('NO_RESPONSE',1);
+	define('TENTATIVE',2);
+	define('ACCEPTED',3);
+
+	class soinfolog__
+	{
+		var $event;
+		var $stream;
+		var $user;
+		var $users_status;
+		var $debug = False;
+	//	var $debug = True;
+
+		function soinfolog__()
+		{
+		}
+
+		function maketime($time)
+		{
+			return mktime($time['hour'],$time['min'],$time['sec'],$time['month'],$time['mday'],$time['year']);
+		}
+
+		function get_cached_event()
+		{
+			return $this->event;
+		}
+
+		function event_init()
+		{
+			$this->event = Array();
+			$this->add_attribute('owner',(int)$this->user);
+		}
+
+		function set_category($category='')
+		{
+			$this->add_attribute('category',$category);
+		}
+
+		function set_title($title='')
+		{
+			$this->add_attribute('title',$title);
+		}
+
+		function set_description($description='')
+		{
+			$this->add_attribute('description',$description);
+		}
+
+function set_description1($description1='')
+		{
+			$this->add_attribute('description1',$description1);
+		}
+
+function set_participants1($participants1='')
+		{
+			$this->add_attribute('participants1',$participants1);
+		}
+
+		function set_ex_participants($ex_participants='')
+		{
+			$this->add_attribute('ex_participants',$ex_participants);
+		}
+
+		function set_date($element,$year,$month,$day=0,$hour=0,$min=0,$sec=0)
+		{
+			$this->add_attribute($element,(int)$year,'year');
+			$this->add_attribute($element,(int)$month,'month');
+			$this->add_attribute($element,(int)$day,'mday');
+			$this->add_attribute($element,(int)$hour,'hour');
+			$this->add_attribute($element,(int)$min,'min');
+			$this->add_attribute($element,(int)$sec,'sec');
+			$this->add_attribute($element,0,'alarm');
+		}
+
+		function set_start($year,$month,$day=0,$hour=0,$min=0,$sec=0)
+		{
+			$this->set_date('start',$year,$month,$day,$hour,$min,$sec);
+		}
+
+		function set_end($year,$month,$day=0,$hour=0,$min=0,$sec=0)
+		{
+			$this->set_date('end',$year,$month,$day,$hour,$min,$sec);
+		}
+
+		function set_alarm($alarm)
+		{
+			$this->add_attribute('alarm',(int)$alarm);
+		}
+
+		function set_class($class)
+		{
+			$this->add_attribute('public',$class);
+		}
+
+		function set_common_recur($year=0,$month=0,$day=0,$interval=0)
+		{
+			$this->add_attribute('recur_interval',(int)$interval);
+			$this->set_date('recur_enddate',$year,$month,$day,0,0,0);
+			$this->add_attribute('recur_data',0);
+		}
+
+		function set_recur_none()
+		{
+			$this->set_common_recur(0,0,0,0);
+			$this->add_attribute('recur_type',MCAL_RECUR_NONE);
+		}
+
+		function set_recur_daily($year,$month,$day,$interval)
+		{
+			$this->set_common_recur((int)$year,(int)$month,(int)$day,$interval);
+			$this->add_attribute('recur_type',MCAL_RECUR_DAILY);
+		}
+
+		function set_recur_weekly($year,$month,$day,$interval,$weekdays)
+		{
+			$this->set_common_recur((int)$year,(int)$month,(int)$day,$interval);
+			$this->add_attribute('recur_type',MCAL_RECUR_WEEKLY);
+			$this->add_attribute('recur_data',(int)$weekdays);
+		}
+
+		function set_recur_monthly_mday($year,$month,$day,$interval)
+		{
+			$this->set_common_recur((int)$year,(int)$month,(int)$day,$interval);
+			$this->add_attribute('recur_type',MCAL_RECUR_MONTHLY_MDAY);
+		}
+		
+		function set_recur_monthly_wday($year,$month,$day,$interval)
+		{
+			$this->set_common_recur((int)$year,(int)$month,(int)$day,$interval);
+			$this->add_attribute('recur_type',MCAL_RECUR_MONTHLY_WDAY);
+		}
+		
+		function set_recur_yearly($year,$month,$day,$interval)
+		{
+			$this->set_common_recur((int)$year,(int)$month,(int)$day,$interval);
+			$this->add_attribute('recur_type',MCAL_RECUR_YEARLY);
+		}
+
+		function fetch_current_stream_event()
+		{
+			return $this->fetch_event($this->event['id']);
+		}
+		
+		function add_attribute($attribute,$value,$element='**(**')
+		{
+			if(is_array($value))
+			{
+				reset($value);
+			}
+			if($element!='**(**')
+			{
+				$this->event[$attribute][$element] = $value;
+			}
+			else
+			{
+				$this->event[$attribute] = $value;
+			}
+		}
+	}
Index: /contrib/ProjectManager/inc/class.soinfolog_.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.soinfolog_.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.soinfolog_.inc.php	(revision 3594)
@@ -0,0 +1,72 @@
+<?php
+  /**************************************************************************\
+  * eGroupWare - infolog                                                    *
+  * http://www.egroupware.org                                                *
+  * Based on Webinfolog by Craig Knudsen <cknudsen@radix.net>               *
+  *          http://www.radix.net/~cknudsen                                  *
+  * Modified by Mark Peters <skeeter@phpgroupware.org>                       *
+  * --------------------------------------------                             *
+  *  This program is free software; you can redistribute it and/or modify it *
+  *  under the terms of the GNU General Public License as published by the   *
+  *  Free Software Foundation; either version 2 of the License, or (at your  *
+  *  option) any later version.                                              *
+  \**************************************************************************/
+
+
+	if(@$GLOBALS['phpgw_info']['server']['infolog_type'] == 'mcal' &&
+		extension_loaded('mcal') == False)
+	{
+		$GLOBALS['phpgw_info']['server']['infolog_type'] = 'sql';
+	}
+/* This will be elminated when ical is fully implemented */
+	else
+	{
+		$GLOBALS['phpgw_info']['server']['infolog_type'] = 'sql';
+	}
+
+
+function get_event_ids()
+		{
+			$from = $where = ' ';
+			
+
+			$sql = 'SELECT DISTINCT  info_id, info_type,
+info_from,info_subject,info_owner,info_datemodified,info_startdate, info_enddate,info_uid,
+info_priority  '
+					. 'FROM phpgw_infolog';
+					//. $from;
+					//. 'WHERE (phpgw_cal_user.info_id = phpgw_cal.info_id) '
+					//. $where . $extra;
+
+//echo "SQL : ".$sql ."<br>\n";
+
+			if($this->debug)
+			{
+				echo "FULL SQL : ".$sql."<br>\n";
+			}
+
+			$this->stream->query($sql,__LINE__,__FILE__);
+
+			$retval = Array();
+			if($this->stream->num_rows() == 0)
+			{
+				if($this->debug)
+				{
+					echo "No records found!<br>\n";
+				}
+				return $retval;
+			}
+
+			while($this->stream->next_record())
+			{
+				$retval[] = (int)$this->stream->f('info_id');
+			}
+			if($this->debug)
+			{
+				echo "Records found!<br>\n";
+			}
+			return $retval;
+		}
+	include(PHPGW_INCLUDE_ROOT.'/infolog/inc/class.soinfolog__.inc.php');
+	include(PHPGW_INCLUDE_ROOT.'/infolog/inc/class.soinfolog_'.$GLOBALS['phpgw_info']['server']['infolog_type'].'.inc.php');
+?>
Index: /contrib/ProjectManager/inc/class.soinfolog_sqlorig.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.soinfolog_sqlorig.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.soinfolog_sqlorig.inc.php	(revision 3594)
@@ -0,0 +1,1135 @@
+<?php
+  /**************************************************************************\
+  * eGroupWare - infolog                                                    *
+  * http://www.eGroupWare.org                                                *
+  * Maintained and further developed by RalfBecker@outdoor-training.de       *
+  * Based on Webinfolog by Craig Knudsen <cknudsen@radix.net>               *
+  *          http://www.radix.net/~cknudsen                                  *
+  * Originaly modified by Mark Peters <skeeter@phpgroupware.org>             *
+  * --------------------------------------------                             *
+  *  This program is free software; you can redistribute it and/or modify it *
+  *  under the terms of the GNU General Public License as published by the   *
+  *  Free Software Foundation; either version 2 of the License, or (at your  *
+  *  option) any later version.                                              *
+  \**************************************************************************/
+
+
+	if (@$GLOBALS['phpgw_info']['flags']['included_classes']['soinfolog_'])
+	{
+		return;
+	}
+
+	$GLOBALS['phpgw_info']['flags']['included_classes']['soinfolog_'] = True;
+
+	class soinfolog_ extends soinfolog__
+	{
+		var $deleted_events = Array();
+
+		var $cal_event;
+		var $today = Array('raw','day','month','year','full','dow','dm','bd');
+
+
+		function soinfolog_()
+		{
+			$this->soinfolog__();
+
+			if (!is_object($GLOBALS['phpgw']->asyncservice))
+			{
+				$GLOBALS['phpgw']->asyncservice = CreateObject('phpgwapi.asyncservice');
+			}
+			$this->async = &$GLOBALS['phpgw']->asyncservice;
+		}
+
+		function open($infolog='',$user='',$passwd='',$options='')
+		{
+			if($user=='')
+			{
+	//			settype($user,'integer');
+				$this->user = $GLOBALS['phpgw_info']['user']['account_id'];
+			}
+			elseif(is_int($user))
+			{
+				$this->user = $user;
+			}
+			elseif(is_string($user))
+			{
+				$this->user = $GLOBALS['phpgw']->accounts->name2id($user);
+			}
+
+			$this->stream = $GLOBALS['phpgw']->db;
+			return $this->stream;
+		}
+
+		function popen($infolog='',$user='',$passwd='',$options='')
+		{
+			return $this->open($infolog,$user,$passwd,$options);
+		}
+
+		function reopen($infolog,$options='')
+		{
+			return $this->stream;
+		}
+
+		function close($options='')
+		{
+			return True;
+		}
+
+		function create_infolog($infolog='')
+		{
+			return $infolog;
+		}
+
+		function rename_infolog($old_name='',$new_name='')
+		{
+			return $new_name;
+		}
+
+		function delete_infolog($infolog='')
+		{
+			$this->stream->query('SELECT info_id FROM phpgw_infolog WHERE info_id='.(int)$infolog,__LINE__,__FILE__);
+			if($this->stream->num_rows())
+			{
+				while($this->stream->next_record())
+				{
+					$this->delete_event((int)$this->stream->f('info_id'));
+				}
+				$this->expunge();
+			}
+			//$this->stream->lock(array('phpgw_cal_user'));
+			//$this->stream->query('DELETE FROM phpgw_cal_user WHERE cal_login='.(int)$infolog,__LINE__,__FILE__);
+			//$this->stream->unlock();
+
+			return $infolog;
+		}
+
+		/*!
+		@function read_alarms
+		@abstract read the alarms of a infolog-event specified by $info_id
+		@returns array of alarms with alarm-id as key
+		@note the alarm-id is a string of 'cal:'.$info_id.':'.$alarm_nr, it is used as the job-id too
+		*/
+		function read_alarms($info_id)
+		{
+			$alarms = array();
+
+			if ($jobs = $this->async->read('cal:'.(int)$info_id.':%'))
+			{
+				foreach($jobs as $id => $job)
+				{
+					$alarm         = $job['data'];	// text, enabled
+					$alarm['id']   = $id;
+					$alarm['time'] = $job['next'];
+
+					$alarms[$id] = $alarm;
+				}
+			}
+			return $alarms;
+		}
+
+		/*!
+		@function read_alarm
+		@abstract read a single alarm specified by it's $id
+		@returns array with data of the alarm
+		@note the alarm-id is a string of 'cal:'.$info_id.':'.$alarm_nr, it is used as the job-id too
+		*/
+		function read_alarm($id)
+		{
+			if (!($jobs = $this->async->read($id)))
+			{
+				return False;
+			}
+			list($id,$job) = each($jobs);
+			$alarm         = $job['data'];	// text, enabled
+			$alarm['id']   = $id;
+			$alarm['time'] = $job['next'];
+
+			//echo "<p>read_alarm('$id')="; print_r($alarm); echo "</p>\n";
+			return $alarm;
+		}
+
+/* Funcao save_alarm modificada para gerar alarmes repetidos, caso seja marcado um evento igualmente repetido. A funcao recebe qual o tipo
+de repeticao escolhido pelo usuario (diario, semanal, mensal, anual) e insere alarmes nas respectivas repeticoes do evento. */
+		/*!
+		@function save_alarm
+		@abstract saves a new or updated alarm
+		@syntax save_alarm($info_id,$alarm,$id=False)
+		@param $info_id Id of the infolog-entry
+		@param $alarm array with fields: text, owner, enabled, ..
+		@returns the id of the alarm
+		*/
+		function save_alarm($info_id,$alarm)
+		{
+
+			if (!($id = $alarm['id']))
+			{
+				$alarm['time'] -= $GLOBALS['phpgw']->datetime->tz_offset;	// time should be stored in server timezone
+				$alarms = $this->read_alarms($info_id);	// find a free alarm#
+
+				if($alarm['repeat'] == 1) // repeticao do tipo "Diariamente";
+				{
+					$n = 0;
+
+					while(@isset($alarms[$id]));
+					{
+
+						$init_alarm = $alarm['init_rept'];
+						$end_alarm = $alarm['end_rept'];
+
+						if($end_alarm != 0)
+						{
+							while($init_alarm <= $end_alarm)
+							{
+								$id = 'cal:'.(int)$info_id.':'.$n;
+								$n++;
+								
+								$alarm['info_id'] = $info_id;		// we need the back-reference
+
+								unset($alarm['repeat']);
+								unset($alarm['init_rept']);
+								unset($alarm['end_rept']);
+								unset($alarm['rpt_wdays']);
+
+								if (!$this->async->set_timer($alarm['time'],$id,'infolog.boinfolog.send_alarm',$alarm))
+								{
+									return False;
+								}
+
+								$alarm['time'] += 86400;
+								$init_alarm += 86400;
+							}
+						}
+					}
+
+				}elseif($alarm['repeat'] == 2) { // repeticao do tipo "Semanalmente";
+
+					$n = 0;
+
+					$init_alarm = $data_atual = $alarm['init_rept'];
+					$end_alarm = $alarm['end_rept'];
+
+					$rpt_alarm_wdays = $alarm['rpt_wdays'];
+
+					$divisor = 64;
+					$quociente = 0;
+					$resto = 0;
+
+					$dia_semana = date("w",$init_alarm);
+
+					switch($dia_semana)
+					{
+						case 0:
+							$dia = array(
+									0 => 'domingo',
+									1 => 'segunda',
+									2 => 'terca',
+									3 => 'quarta',
+									4 => 'quinta',
+									5 => 'sexta',
+									6 => 'sabado'
+							);
+							break;
+						case 1:
+							$dia = array(
+									0 => 'segunda',
+									1 => 'terca',
+									2 => 'quarta',
+									3 => 'quinta',
+									4 => 'sexta',
+									5 => 'sabado',
+									6 => 'domingo'
+							);
+							break;
+						case 2:
+							$dia = array(
+									0 => 'terca',
+									1 => 'quarta',
+									2 => 'quinta',
+									3 => 'sexta',
+									4 => 'sabado',
+									5 => 'domingo',
+									6 => 'segunda'
+							);
+							break;
+						case 3:
+							$dia = array(
+									0 => 'quarta',
+									1 => 'quinta',
+									2 => 'sexta',
+									3 => 'sabado',
+									4 => 'domingo',
+									5 => 'segunda',
+									6 => 'terca'
+							);
+							break;
+						case 4:
+							$dia = array(
+									0 => 'quinta',
+									1 => 'sexta',
+									2 => 'sabado',
+									3 => 'domingo',
+									4 => 'segunda',
+									5 => 'terca',
+									6 => 'quarta'
+							);
+							break;
+						case 5:
+							$dia = array(
+									0 => 'sexta',
+									1 => 'sabado',
+									2 => 'domingo',
+									3 => 'segunda',
+									4 => 'terca',
+									5 => 'quarta',
+									6 => 'quinta'
+							);
+							break;
+						case 6:
+							$dia = array(
+									0 => 'sabado',
+									1 => 'domingo',
+									2 => 'segunda',
+									3 => 'terca',
+									4 => 'quarta',
+									5 => 'quinta',
+									6 => 'sexta'
+							);
+							break;
+					}
+
+
+					$dias_semana = array(
+								64 => 'sabado',
+								32 => 'sexta',
+								16 => 'quinta',
+								8 => 'quarta',
+								4 => 'terca',
+								2 => 'segunda',
+								1 => 'domingo'
+							);
+
+					$result = array();
+					do
+					{
+
+						$resto = ($rpt_alarm_wdays % $divisor);
+						$quociente = floor($rpt_alarm_wdays / $divisor);
+
+						if($quociente == 1)
+						{
+							$result[] = $dias_semana[$divisor];
+
+							$divisor = $divisor / 2;
+							$rpt_alarm_wdays = $resto;
+
+						}else {
+
+							while($rpt_alarm_wdays < $divisor)
+							{
+								$divisor = $divisor / 2;
+							}
+
+							$resto = ($rpt_alarm_wdays % $divisor);
+							$quociente = floor($rpt_alarm_wdays / $divisor);
+
+							if($quociente == 1)
+							{
+
+								$result[] = $dias_semana[$divisor];
+
+								$divisor = $divisor / 2;
+								$rpt_alarm_wdays = $resto;
+							}
+						}
+
+					}
+					while($resto != 0);
+
+					krsort($result);
+
+					$week_num = 0;
+					$y = 0;
+
+					while(@isset($alarms[$id]));
+					{
+
+						if($end_alarm != 0)
+						{
+
+							while($data_atual <= $end_alarm)
+							{
+
+								foreach($dia as $index => $value)
+								{
+									if(in_array($value,$result))
+									{
+
+										$nova_data = $init_alarm + (86400 * ($index + (7 * $y)));
+
+										if($nova_data == $init_alarm){
+
+											continue;
+										}
+
+										$id = 'cal:'.(int)$info_id.':'.$n;
+										$n++;
+
+										$alarm['info_id'] = $info_id;		// we need the back-reference
+
+										unset($alarm['repeat']);
+										unset($alarm['init_rept']);
+										unset($alarm['end_rept']);
+										unset($alarm['rpt_wdays']);
+										
+										$data_atual = $nova_data;
+
+										if($data_atual > $end_alarm){
+											if (!$this->async->set_timer($alarm['time'],$id,'infolog.boinfolog.send_alarm',$alarm))
+											{
+												return False;
+											}
+											break;
+										}
+
+										if (!$this->async->set_timer($alarm['time'],$id,'infolog.boinfolog.send_alarm',$alarm))
+										{
+											return False;
+										}
+											$alarm['time'] = $nova_data - $alarm['offset'] - $GLOBALS['phpgw']->datetime->tz_offset; 
+									}
+								}
+								$y++;
+							}
+						}
+					}
+
+				}elseif($alarm['repeat'] == 3) { // repeticao do tipo "Mensalmente (por data)";
+
+					$n = 0;
+
+					$init_alarm = $alarm['init_rept'];
+					$end_alarm = $alarm['end_rept'];
+					
+					while(@isset($alarms[$id]));
+					{
+
+						if($end_alarm != 0)
+						{
+							while($init_alarm <= $end_alarm)
+							{
+
+								$next_month = date("n",$init_alarm) + 1;
+								$next_alarm = mktime(date("G",$alarm['time']),date("i",$alarm['time']),date("s",$alarm['time']),$next_month,date("j",$alarm['time']),date("Y",$alarm['time']));
+
+								$id = 'cal:'.(int)$info_id.':'.$n;
+								$n++;
+
+								$alarm['info_id'] = $info_id;		// we need the back-reference
+
+								unset($alarm['repeat']);
+								unset($alarm['init_rept']);
+								unset($alarm['end_rept']);
+								unset($alarm['rpt_wdays']);
+
+								if (!$this->async->set_timer($alarm['time'],$id,'infolog.boinfolog.send_alarm',$alarm))
+								{
+									return False;
+								}
+
+								$alarm['time'] = $next_alarm;
+								$init_alarm = $next_alarm;
+
+							}
+						}
+					}
+
+				}elseif($alarm['repeat'] == 5) { // repeticao do tipo "Anualmente";
+
+					$n = 0;
+
+					$init_alarm = $alarm['init_rept'];
+					$end_alarm = $alarm['end_rept'];
+					
+					while(@isset($alarms[$id]));
+					{
+
+
+						if($end_alarm != 0)
+						{
+							while($init_alarm < $end_alarm)
+							{
+
+								$next_year = date("Y",$init_alarm) + 1;
+								$next_alarm = mktime(date("G",$alarm['time']),date("i",$alarm['time']),date("s",$alarm['time']),date("n",$alarm['time']),date("j",$alarm['time']),$next_year);
+
+								$id = 'cal:'.(int)$info_id.':'.$n;
+								$n++;
+
+								$alarm['info_id'] = $info_id;		// we need the back-reference
+
+								unset($alarm['repeat']);
+								unset($alarm['init_rept']);
+								unset($alarm['end_rept']);
+								unset($alarm['rpt_wdays']);
+
+								if (!$this->async->set_timer($alarm['time'],$id,'infolog.boinfolog.send_alarm',$alarm))
+								{
+									return False;
+								}
+
+								$alarm['time'] = $next_alarm;
+								$init_alarm = $next_alarm;
+							}
+						}
+					}
+
+				}else {
+					$alarm['time'] -= $GLOBALS['phpgw']->datetime->tz_offset;	// time should be stored in server timezone
+					$n = count($alarms);
+					while(@isset($alarms[$id]));
+					{
+
+						$id = 'cal:'.(int)$info_id.':'.$n;
+
+						unset($alarm['repeat']);
+						unset($alarm['init_rept']);
+						unset($alarm['end_rept']);
+						unset($alarm['rpt_wdays']);
+
+						$alarm['info_id'] = $info_id;		// we need the back-reference
+						if (!$this->async->set_timer($alarm['time'],$id,'infolog.boinfolog.send_alarm',$alarm))
+						{
+							return False;
+						}
+
+						++$n;
+					}
+				}
+
+			}
+			else
+			{
+				$this->async->cancel_timer($id);
+			}
+			$alarm['time'] -= $GLOBALS['phpgw']->datetime->tz_offset;	// time should be stored in server timezone
+			return $id;
+		}
+
+		/*!
+		@function delete_alarms($info_id)
+		@abstract delete all alarms of a infolog-entry
+		@returns the number of alarms deleted
+		*/
+		function delete_alarms($info_id)
+		{
+			$alarms = $this->read_alarms($info_id);
+
+			foreach($alarms as $id => $alarm)
+			{
+				$this->async->cancel_timer($id);
+			}
+			return count($alarms);
+		}
+
+		/*!
+		@function delete_alarm($id)
+		@abstract delete one alarms identified by its id
+		@returns the number of alarms deleted
+		*/
+		function delete_alarm($id)
+		{
+			return $this->async->cancel_timer($id);
+		}
+
+		function fetch_event($event_id,$options='')
+		{
+			if(!isset($this->stream))
+			{
+				return False;
+			}
+
+			$event_id = (int)$event_id;
+
+			$this->stream->lock(array('phpgw_infolog'/* OLD-ALARM,'phpgw_cal_alarm'*/));
+
+			$this->stream->query('SELECT * FROM phpgw_infolog WHERE info_id='.$event_id,__LINE__,__FILE__);
+
+			if($this->stream->num_rows() > 0)
+			{
+				$this->event_init();
+//echo "<br>aqui paso"."SELECT * FROM phpgw_infolog WHERE info_id='".$event_id;
+//echo "<br>Aa".__LINE__.",".__FILE__;
+				$this->stream->next_record();
+				// Load the infolog event data from the db into $event structure
+				// Use http://www.php.net/manual/en/function.mcal-fetch-event.php as the reference
+				$this->add_attribute('owner',(int)$this->stream->f('owner'));
+				$this->add_attribute('id',(int)$this->stream->f('info_id'));
+				$this->add_attribute('type',$this->stream->f('info_type'));
+				$this->set_class((int)$this->stream->f('info_access'));
+				$this->set_category($this->stream->f('info_cat'));
+				$this->set_title(stripslashes($GLOBALS['phpgw']->strip_html($this->stream->f('info_subject'))));
+				$this->set_description(stripslashes($GLOBALS['phpgw']->strip_html($this->stream->f('info_des'))));
+				$this->set_ex_participants(stripslashes($GLOBALS['phpgw']->strip_html($this->stream->f('info_custom_from'))));
+				$this->add_attribute('uid',$GLOBALS['phpgw']->strip_html($this->stream->f('info_uid')));
+				$this->add_attribute('location',stripslashes($GLOBALS['phpgw']->strip_html($this->stream->f('info_location'))));
+				$this->add_attribute('reference',(int)$this->stream->f('info_status'));
+
+				// This is the preferred method once everything is normalized...
+				//$this->event->alarm = (int)$this->stream->f('alarm');
+				// But until then, do it this way...
+				//Legacy Support (New)
+
+				$datetime = $GLOBALS['phpgw']->datetime->localdates($this->stream->f('datetime'));
+				$this->set_start($datetime['year'],$datetime['month'],$datetime['day'],$datetime['hour'],$datetime['minute'],$datetime['second']);
+
+				$datetime = $GLOBALS['phpgw']->datetime->localdates($this->stream->f('mdatetime'));
+				$this->set_date('modtime',$datetime['year'],$datetime['month'],$datetime['day'],$datetime['hour'],$datetime['minute'],$datetime['second']);
+
+				$datetime = $GLOBALS['phpgw']->datetime->localdates($this->stream->f('edatetime'));
+				$this->set_end($datetime['year'],$datetime['month'],$datetime['day'],$datetime['hour'],$datetime['minute'],$datetime['second']);
+
+			//Legacy Support
+				$this->add_attribute('priority',(int)$this->stream->f('priority'));
+				if($this->stream->f('cal_group') || $this->stream->f('groups') != 'NULL')
+				{
+					$groups = explode(',',$this->stream->f('groups'));
+					for($j=1;$j<count($groups) - 1;$j++)
+					{
+						$this->add_attribute('groups',$groups[$j],$j-1);
+					}
+				}
+
+				
+
+			
+			}
+			else
+			{
+				$this->event = False;
+			}
+
+			$this->stream->unlock();
+
+			if ($this->event)
+			{
+				$this->event['alarm'] = $this->read_alarms($event_id);
+
+				if($this->event['reference'])
+				{
+					$this->event['alarm'] += $this->read_alarms($event_id);
+				}
+			}
+echo "<br>aqui pasollll". $this->event."<br>";
+			return $this->event;
+		}
+
+		function list_events($startYear,$startMonth,$startDay,$endYear=0,$endMonth=0,$endDay=0,$extra='',$tz_offset=0,$owner_id=0)
+		{
+
+echo "entro?";
+			if(!isset($this->stream))
+			{
+				return False;
+			}
+
+			$datetime = mktime(0,0,0,$startMonth,$startDay,$startYear) - $tz_offset;
+
+			$user_where = ' AND (phpgw_infolog.info_owner in (';
+			if(is_array($owner_id) && count($owner_id))
+			{
+				$user_where .= implode(',',$owner_id);
+			}
+			else
+			{
+				$user_where .= $this->user;
+			}
+			$member_groups = $GLOBALS['phpgw']->accounts->membership($this->user);
+			@reset($member_groups);
+			while($member_groups != False && list($key,$group_info) = each($member_groups))
+			{
+				$member[] = $group_info['account_id'];
+			}
+			@reset($member);
+	//		$user_where .= ','.implode(',',$member);
+			$user_where .= ')) ';
+
+			if($this->debug)
+			{
+				echo '<!-- '.$user_where.' -->'."\n";
+			}
+
+		//	$startDate = 'AND ( ( (phpgw_cal.datetime >= '.$datetime.') ';
+
+			$enddate = '';
+			/*if($endYear != 0 && $endMonth != 0 && $endDay != 0)
+			{
+				$edatetime = mktime(23,59,59,(int)$endMonth,(int)$endDay,(int)$endYear) - $tz_offset;
+				$endDate .= 'AND (phpgw_cal.edatetime <= '.$edatetime.') ) '
+					. 'OR ( (phpgw_cal.datetime <= '.$datetime.') '
+					. 'AND (phpgw_cal.edatetime >= '.$edatetime.') ) '
+					. 'OR ( (phpgw_cal.datetime >= '.$datetime.') '
+					. 'AND (phpgw_cal.datetime <= '.$edatetime.') '
+					. 'AND (phpgw_cal.edatetime >= '.$edatetime.') ) '
+					. 'OR ( (phpgw_cal.datetime <= '.$datetime.') '
+					. 'AND (phpgw_cal.edatetime >= '.$datetime.') '
+					. 'AND (phpgw_cal.edatetime <= '.$edatetime.') ';
+			}*/
+			$endDate .= ') ) ';
+
+			$order_by = 'ORDER BY phpgw_infolog.info_datemodified ASC,  phpgw_infolog.priority ASC';
+			if($this->debug)
+			{
+				echo "SQL : ".$user_where.$startDate.$endDate.$extra."<br>\n";
+			}
+
+			return $this->get_event_ids(False,$user_where.$startDate.$endDate.$extra.$order_by);
+		}
+
+		function append_event()
+		{
+			$this->save_event($this->event);
+			$this->send_update(MSG_ADDED,$this->event->participants,'',$this->event);
+			return $this->event['id'];
+		}
+
+		function store_event()
+		{
+			return $this->save_event(&$this->event);
+		}
+
+		function delete_event($event_id)
+		{
+			$this->deleted_events[] = $event_id;
+		}
+
+		function snooze($event_id)
+		{
+		//Turn off an alarm for an event
+		//Returns true.
+		}
+
+		function list_alarms($begin_year='',$begin_month='',$begin_day='',$end_year='',$end_month='',$end_day='')
+		{
+		//Return a list of events that has an alarm triggered at the given datetime
+		//Returns an array of event ID's
+		}
+
+		// The function definition doesn't look correct...
+		// Need more information for this function
+		function next_recurrence($weekstart,$next)
+		{
+	//		return next_recurrence (int stream, int weekstart, array next);
+		}
+
+		function expunge()
+		{
+			if(count($this->deleted_events) <= 0)
+			{
+				return 1;
+			}
+			$this_event = $this->event;
+			$locks = Array(
+				'phpgw_infolog'
+	// OLD-ALARM			'phpgw_cal_alarm'
+			);
+			$this->stream->lock($locks);
+			foreach($this->deleted_events as $info_id)
+			{
+				foreach ($locks as $table)
+				{
+					$this->stream->query('DELETE FROM '.$table.' WHERE info_id='.$info_id,__LINE__,__FILE__);
+				}
+			}
+			$this->stream->unlock();
+
+			foreach($this->deleted_events as $info_id)
+			{
+				$this->delete_alarms($info_id);
+			}
+			$this->deleted_events = array();
+
+			$this->event = $this_event;
+			return 1;
+		}
+
+		/***************** Local functions for SQL based infolog *****************/
+
+		function get_event_ids($search_repeats=False,$extra='',$search_extra=False)
+		{
+			$from = $where = ' ';
+			/*if($search_repeats)
+			{
+				$from  = ', phpgw_cal_repeats ';
+				$where = 'AND (phpgw_cal_repeats.info_id = phpgw_cal.info_id) ';
+			}
+			if($search_extra)
+			{
+				$from  .= 'LEFT JOIN phpgw_cal_extra ON phpgw_cal_extra.info_id = phpgw_cal.info_id ';
+			}*/
+
+			$sql = 'SELECT DISTINCT  info_id, info_type,
+info_from,info_subject,info_owner,info_datemodified,info_startdate, info_enddate,info_uid,
+info_priority  '
+					. 'FROM phpgw_infolog'
+					. $from;
+					//. 'WHERE (phpgw_cal_user.info_id = phpgw_cal.info_id) '
+					//. $where . $extra;
+
+//echo "SQL : ".$sql ."<br>\n";
+
+			if($this->debug)
+			{
+				echo "FULL SQL : ".$sql."<br>\n";
+			}
+
+			$this->stream->query($sql,__LINE__,__FILE__);
+
+			$retval = Array();
+			if($this->stream->num_rows() == 0)
+			{
+				if($this->debug)
+				{
+					echo "No records found!<br>\n";
+				}
+				return $retval;
+			}
+
+			while($this->stream->next_record())
+			{
+				$retval[] = (int)$this->stream->f('info_id');
+			}
+			if($this->debug)
+			{
+				echo "Records found!<br>\n";
+			}
+			return $retval;
+		}
+
+		function save_event(&$event)
+		{
+
+$edatetime = mktime(23,59,59,(int)$endMonth,(int)$endDay,(int)$endYear) - $tz_offset;
+
+			$GLOBALS['infolog']->boinfolog = CreateObject('infolog.boinfolog');
+
+			$locks = Array(
+				'phpgw_infolog'
+	// OLD-ALARM			'phpgw_cal_alarm'
+			);
+			$this->stream->lock($locks);
+			if($event['id'] == 0)
+			{
+				if(!$event['uid'])
+				{
+					if ($GLOBALS['phpgw_info']['server']['hostname'] != '')
+					{
+						$id_suffix = $GLOBALS['phpgw_info']['server']['hostname'];
+					}
+					else
+					{
+						$id_suffix = $GLOBALS['phpgw']->common->randomstring(3).'local';
+					}
+					$parts = Array(
+						0 => 'title',
+						1 => 'description',
+						2 => 'ex_participants'
+					);
+					@reset($parts);
+					while(list($key,$field) = each($parts))
+					{
+						$part[$key] = substr($GLOBALS['phpgw']->crypto->encrypt($event[$field]),0,20);
+						if(!$GLOBALS['phpgw']->crypto->enabled)
+						{
+							$part[$key] = bin2hex(unserialize($part[$key]));
+						}
+					}
+					$event['uid'] = $part[0].'-'.$part[1].'@'.$id_suffix;
+				}
+
+
+
+
+				$this->stream->query('INSERT INTO phpgw_infolog(info_type,info_from,info_addr,info_subject,info_des,info_owner,info_responsible,info_cat,info_datemodified) '
+					. "values('".$event['title']."','". $event['participants']."','','". $this->stream->db_addslashes($event['description1'])."','". $this->stream->db_addslashes($event['description'])."',". $event['owner'].",'". $event['owner']."',0,".$edatetime.")",__LINE__,__FILE__);
+				$event['id'] = $this->stream->get_last_insert_id('phpgw_cal','info_id');
+				$last_status = true;
+			}
+
+			$date = $this->maketime($event['start']) - $GLOBALS['phpgw']->datetime->tz_offset;
+			$enddate = $this->maketime($event['end']) - $GLOBALS['phpgw']->datetime->tz_offset;
+			$today = time() - $GLOBALS['phpgw']->datetime->tz_offset;
+
+			if($event['recur_type'] != MCAL_RECUR_NONE)
+			{
+				$type = 'M';
+			}
+			else
+			{
+				if ($event['type'] == 'hourAppointment')
+					$type = 'H';
+				else if($event['type'] == 'privateHiddenFields'){
+					$type = 'P';
+				}else
+					$type = 'E';
+			}
+
+			$sql = 'UPDATE phpgw_cal SET '
+					. 'owner='.(int)$event['owner'].', '
+					. 'datetime='.(int)$date.', '
+					. 'mdatetime='.(int)$today.', '
+					. 'edatetime='.(int)$enddate.', '
+					. 'priority='.(int)$event['priority'].', '
+					. "category='".$this->stream->db_addslashes($event['category'])."', "
+					. "cal_type='".$this->stream->db_addslashes($type)."', "
+					. 'is_public='.(int)$event['public'].', '
+					. "title='".$this->stream->db_addslashes($event['title'])."', "
+					. "description='".$this->stream->db_addslashes($event['description'])."', "
+					. "ex_participants='".$this->stream->db_addslashes($event['ex_participants'])."', "
+					. "location='".$this->stream->db_addslashes($event['location'])."', "
+					. ($event['groups']?"groups='".(count($event['groups'])>1?implode(',',$event['groups']):','.$event['groups'][0].',')."', ":'')
+					. 'reference='.(int)$event['reference'].' '
+					. ',last_status = '.($last_status ? "'N'" : "'U'").',last_update = '.time()."000". ' '
+					. 'WHERE info_id='.(int)$event['id'];
+
+			$this->stream->query($sql,__LINE__,__FILE__);
+
+			$this->stream->query('DELETE FROM phpgw_infolog WHERE info_id='.(int)$event['id'],__LINE__,__FILE__);
+
+			@reset($event['participants']);
+			/*while (list($key,$value) = @each($event['participants']))
+			{
+				if((int)$key == $event['owner'])
+				{
+					$value = 'A';
+				}
+				$this->stream->query('INSERT INTO phpgw_cal_user(info_id,cal_login,cal_status) '
+					. 'VALUES('.(int)$event['id'].','.(int)$key.",'".$this->stream->db_addslashes($value)."')",__LINE__,__FILE__);
+			}*/
+
+			/*if($event['recur_type'] != MCAL_RECUR_NONE)
+			{
+				if($event['recur_enddate']['month'] != 0 && $event['recur_enddate']['mday'] != 0 && $event['recur_enddate']['year'] != 0)
+				{
+					$end = $this->maketime($event['recur_enddate']) - $GLOBALS['phpgw']->datetime->tz_offset;
+				}
+				else
+				{
+					$end = 0;
+				}
+
+				$this->stream->query('SELECT count(info_id) FROM phpgw_cal_repeats WHERE info_id='.(int)$event['id'],__LINE__,__FILE__);
+				$this->stream->next_record();
+				$num_rows = $this->stream->f(0);
+				if($num_rows == 0)
+				{
+					$this->stream->query('INSERT INTO phpgw_cal_repeats(info_id,recur_type,recur_enddate,recur_data,recur_interval) '
+						.'VALUES('.(int)$event['id'].','.$event['recur_type'].','.(int)$end.','.$event['recur_data'].','.$event['recur_interval'].')',__LINE__,__FILE__);
+				}
+				else
+				{
+					$this->stream->query('UPDATE phpgw_cal_repeats '
+						. 'SET recur_type='.$event['recur_type'].', '
+						. 'recur_enddate='.(int)$end.', '
+						. 'recur_data='.$event['recur_data'].', '
+						. 'recur_interval='.$event['recur_interval'].', '
+						. "recur_exception='".(count($event['recur_exception'])>1?implode(',',$event['recur_exception']):(count($event['recur_exception'])==1?$event['recur_exception'][0]:''))."' "
+						. 'WHERE info_id='.$event['id'],__LINE__,__FILE__);
+				}
+
+			}
+			else
+			{
+				$this->stream->query('DELETE FROM phpgw_cal_repeats WHERE info_id='.$event['id'],__LINE__,__FILE__);
+			}*/
+			// Custom fields
+			/*$this->stream->query('DELETE FROM phpgw_cal_extra WHERE info_id='.$event['id'],__LINE__,__FILE__);
+
+			foreach($event as $name => $value)
+			{
+				if ($name[0] == '#' && strlen($value))
+				{
+					$this->stream->query('INSERT INTO phpgw_cal_extra (info_id,cal_extra_name,cal_extra_value) '
+					. 'VALUES('.$event['id'].",'".addslashes(substr($name,1))."','".addslashes($value)."')",__LINE__,__FILE__);
+				}
+			}*/
+	/*
+			$alarmcount = count($event['alarm']);
+			if ($alarmcount > 1)
+			{
+				// this should never happen, $event['alarm'] should only be set
+				// if creating a new event and uiinfolog only sets up 1 alarm
+				// the user must use "Alarm Management" to create/establish multiple
+				// alarms or to edit/change an alarm
+				echo '<!-- how did this happen, too many alarms -->'."\n";
+				$this->stream->unlock();
+				return True;
+			}
+
+			if ($alarmcount == 1)
+			{
+
+				list($key,$alarm) = @each($event['alarm']);
+
+				$this->stream->query('INSERT INTO phpgw_cal_alarm(info_id,cal_owner,cal_time,cal_text,alarm_enabled) VALUES('.$event['id'].','.$event['owner'].','.$alarm['time'].",'".$alarm['text']."',".$alarm['enabled'].')',__LINE__,__FILE__);
+				$this->stream->query('SELECT LAST_INSERT_ID()');
+				$this->stream->next_record();
+				$alarm['id'] = $this->stream->f(0);
+			}
+	*/
+			print_debug('Event Saved: ID #',$event['id']);
+
+			$this->stream->unlock();
+
+			if (is_array($event['alarm']))
+			{
+				foreach ($event['alarm'] as $alarm)	// this are all new alarms
+				{
+					$this->save_alarm($event['id'],$alarm);
+				}
+			}
+			$GLOBALS['phpgw_info']['cal_new_event_id'] = $event['id'];
+			$this->event = $event;
+			return True;
+		}
+
+		function get_alarm($info_id)
+		{
+	/* OLD-ALARM
+			$this->stream->query('SELECT cal_time, cal_text FROM phpgw_cal_alarm WHERE info_id='.$id.' AND cal_owner='.$this->user,__LINE__,__FILE__);
+			if($this->stream->num_rows())
+			{
+				while($this->stream->next_record())
+				{
+					$alarm[$this->stream->f('cal_time')] = $this->stream->f('cal_text');
+				}
+				@reset($alarm);
+				return $alarm;
+			}
+			else
+			{
+				return False;
+			}
+	*/
+			$alarms = $this->read_alarms($info_id);
+			$ret = False;
+
+			foreach($alarms as $alarm)
+			{
+				if ($alarm['owner'] == $this->user || !$alarm['owner'])
+				{
+					$ret[$alarm['time']] = $alarm['text'];
+				}
+			}
+			return $ret;
+		}
+
+		function set_status($id,$owner,$status)
+		{
+			$status_code_short = Array(
+				REJECTED =>	'R',
+				NO_RESPONSE	=> 'U',
+				TENTATIVE	=>	'T',
+				ACCEPTED	=>	'A'
+			);
+
+			$this->stream->query("UPDATE phpgw_infolog SET info_status='".$status_code_short[$status]."' WHERE info_id=".$id." AND info_owner=".$owner,__LINE__,__FILE__);
+	/* OLD-ALARM
+			if ($status == 'R')
+			{
+				$this->stream->query('UPDATE phpgw_cal_alarm set alarm_enabled=0 where info_id='.$id.' and cal_owner='.$owner,__LINE__,__FILE__);
+			}
+	*/
+			return True;
+		}
+
+	// End of ICal style support.......
+
+		function group_search($owner=0)
+		{
+			$owner = ($owner==$GLOBALS['phpgw_info']['user']['account_id']?0:$owner);
+			/*$groups = substr($GLOBALS['phpgw']->common->sql_search('phpgw_cal.groups',(int)$owner),4);
+			if (!$groups)
+			{
+				return '';
+			}
+			else
+			{
+				return "(phpgw_cal.is_public=2 AND (". $groups .')) ';
+			}*/
+return '';
+		}
+
+		function splittime_($time)
+		{
+			$temp = array('hour','minute','second','ampm');
+			$time = strrev($time);
+			$second = (int)strrev(substr($time,0,2));
+			$minute = (int)strrev(substr($time,2,2));
+			$hour   = (int)strrev(substr($time,4));
+			$temp['second'] = (int)$second;
+			$temp['minute'] = (int)$minute;
+			$temp['hour']   = (int)$hour;
+			$temp['ampm']   = '  ';
+
+			return $temp;
+		}
+
+		function date_to_epoch($d)
+		{
+			return $this->localdates(mktime(0,0,0,(int)(substr($d,4,2)),(int)(substr($d,6,2)),(int)(substr($d,0,4))));
+		}
+
+		function list_dirty_events($lastmod=-1,$repeats=false)
+		{
+			/*if(!isset($this->stream))
+			{
+				return False;
+			}
+			$lastmod = (int)  $lastmod;
+			$repeats = (bool) $repeats;
+
+			$user_where = " AND phpgw_cal_user.cal_login = $this->user";
+
+			$member_groups = $GLOBALS['phpgw']->accounts->membership($this->user);
+			@reset($member_groups);
+			while($member_groups != False && list($key,$group_info) = each($member_groups))
+			{
+				$member[] = $group_info['account_id'];
+			}
+			@reset($member);
+	//		$user_where .= ','.implode(',',$member);
+			//$user_where .= ')) ';
+
+			if($this->debug)
+			{
+				echo '<!-- '.$user_where.' -->'."\n";
+			}
+
+			if($lastmod > 0)
+			{
+				$wheremod = "AND mdatetime = $lastmod";
+			}
+
+			$order_by = ' ORDER BY phpgw_cal.info_id ASC';
+			if($this->debug)
+			{
+				echo "SQL : ".$user_where.$wheremod.$extra."<br>\n";
+			}
+			return $this->get_event_ids($repeats,$user_where.$wheremod.$extra.$order_by);*/
+return '';
+		}
+
+	/* OLD-ALARM
+		function add_alarm($eventid,$alarm,$owner)
+		{
+			$this->stream->query('INSERT INTO phpgw_cal_alarm(info_id,cal_owner,cal_time,cal_text,alarm_enabled) VALUES('.$eventid.','.$owner.','.$alarm['time'].",'".$alarm['text']."',1)",__LINE__,__FILE__);
+			$this->stream->query('SELECT LAST_INSERT_ID()');
+			$this->stream->next_record();
+			return($this->stream->f(0));
+		}
+		function delete_alarm($alarmid)
+		{
+			$this->stream->query('DELETE FROM phpgw_cal_alarm WHERE alarm_id='.$alarmid,__LINE__,__FILE__);
+		}
+	*/
+	}
Index: /contrib/ProjectManager/inc/class.ganttchart.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.ganttchart.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.ganttchart.inc.php	(revision 3594)
@@ -0,0 +1,826 @@
+<?php
+/**
+ * ProjectManager - Gantchart creation
+ *
+ * @link http://www.egroupware.org
+ * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @package projectmanager
+ * @copyright (c) 2005 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @version $Id: class.ganttchart.inc.php 24310 2007-07-16 10:08:15Z ralfbecker $ 
+ */
+
+include_once(PHPGW_INCLUDE_ROOT.'/projectmanager/inc/class.boprojectelements.inc.php');
+
+// JPGraph does not work, if that got somehow set, so unset it
+if (isset($GLOBALS['php_errormsg'])) { unset ($GLOBALS['php_errormsg']); }
+
+// check if the admin installed a recent JPGraph parallel to eGroupWare
+if(file_exists(PHPGW_SERVER_ROOT . '/../jpgraph/src/jpgraph.php'))
+{
+	if (!isset($GLOBALS['phpgw_info']['apps']['projectmanager']['config']))
+	{
+		// read the pm ganttchart configuration and set some default if there's none
+		require_once(PHPGW_API_INC.'/class.config.inc.php');
+		$pm_config = new config('projectmanager');
+		$GLOBALS['phpgw_info']['apps']['projectmanager']['config'] = $pm_config->read_repository();
+		//_debug_array($GLOBALS['phpgw_info']['apps']['projectmanager']['config']);
+		unset($pm_config);
+		foreach(array(
+			'TTF_DIR'          => '',
+			'LANGUAGE_CHARSET' => 'iso-8859-1',
+			'GANTT_FONT'       => 15, //FF_ARIAL
+			'GANTT_FONT_FILE'  => 'arial.ttf',
+			'GANTT_STYLE'      => 9002, //FS_BOLD,
+			'GANTT_CHAR_ENCODE'=> false,
+		) as $name => $default)
+		{
+			if (isset($GLOBALS['phpgw_info']['apps']['projectmanager']['config'][$name]))
+			{
+				define($name,$GLOBALS['phpgw_info']['apps']['projectmanager']['config'][$name]);
+			}
+			elseif($default)
+			{
+				define($name,$default);
+			}
+		}
+	}
+	if (!defined('TTF_DIR'))
+	{
+		// using the OS font dir if we can find it, otherwise fall back to our bundled Vera font
+		foreach(array(
+			'/usr/X11R6/lib/X11/fonts/truetype/',	// linux / *nix default
+			'/usr/share/fonts/ja/TrueType/',		// japanese fonts
+			'/usr/share/fonts/msttcorefonts/', 		// manual for install this fonts search at http://www.aditus.nu/jpgraph/jpdownload.php
+			'C:/windows/fonts/',					// windows default
+			// add your location here ...
+			PHPGW_SERVER_ROOT.'/projectmanager/inc/ttf-bitstream-vera-1.10/',	// our bundled Vera font
+		) as $dir)
+		{
+			if (@is_dir($dir) && (is_readable($dir.GANTT_FONT_FILE) || is_readable($dir.'Vera.ttf')))
+			{
+				define('TTF_DIR',$dir);
+				unset($dir);
+				break;
+			}
+		}
+	}
+	if (!defined('MBTTF_DIR')) define('MBTTF_DIR',TTF_DIR);
+
+	include(PHPGW_SERVER_ROOT . '/../jpgraph/src/jpgraph.php');
+	include(PHPGW_SERVER_ROOT . '/../jpgraph/src/jpgraph_gantt.php');
+}
+else
+{
+	include(PHPGW_SERVER_ROOT . '/projectmanager/inc/jpgraph-1.5.2/src/jpgraph.php');
+	include(PHPGW_SERVER_ROOT . '/projectmanager/inc/jpgraph-1.5.2/src/jpgraph_gantt.php');
+	define('TTF_DIR',PHPGW_SERVER_ROOT.'/projectmanager/inc/ttf-bitstream-vera-1.10/');
+	define('GANTT_FONT',FF_VERA);
+	define('GANTT_STYLE',FS_BOLD);
+	define('LANGUAGE_CHARSET','iso-8859-1');
+	define('GANTT_CHAR_ENCODE',true);
+}
+
+// some constanst for pre php4.3
+if (!defined('PHP_SHLIB_SUFFIX'))
+{
+	define('PHP_SHLIB_SUFFIX',strtoupper(substr(PHP_OS, 0,3)) == 'WIN' ? 'dll' : 'so');
+}
+if (!defined('PHP_SHLIB_PREFIX'))
+{
+	define('PHP_SHLIB_PREFIX',PHP_SHLIB_SUFFIX == 'dll' ? 'php_' : '');
+}
+
+/**
+ * ProjectManager: Ganttchart creation
+ *
+ * @package projectmanager
+ * @author RalfBecker-AT-outdoor-training.de
+ * @copyright (c) 2005 by RalfBecker-AT-outdoor-training.de
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ */
+class ganttchart extends boprojectelements  
+{
+	/**
+	 * @var array $public_functions Functions to call via menuaction
+	 */
+	var $public_functions = array(
+		'create' => true,
+		'show'   => true,
+	);
+	/**
+	 * true if JPGraph version > 1.13
+	 * 
+	 * @var boolean
+	 */
+	var $modernJPGraph;
+	/**
+	 * Charset used internaly by eGW, $GLOBALS['phpgw']->translation->charset()
+	 *
+	 * @var string
+	 */
+	var $charset;
+	/**
+	 * Font used for the Gantt Chart, in the form used by JPGraphs SetFont method
+	 * 
+	 * @var string
+	 */
+	var $gantt_font = GANTT_FONT;
+	/**
+	 * Charset used by the above font
+	 * 
+	 * @var string
+	 */
+	var $gantt_charset = LANGUAGE_CHARSET;
+	/**
+	 * Should non-ascii chars be encoded
+	 *
+	 * @var boolean
+	 */
+	var $gantt_char_encode = GANTT_CHAR_ENCODE;
+
+	var $debug;
+	var $scale_start,$scale_end;
+	var $tmpl;
+	var $prefs;
+
+	/**
+	 * Constructor, calls the constructor of the extended class
+	 */
+	function ganttchart()
+	{
+		$this->tmpl =& CreateObject('etemplate.etemplate');
+
+		$php_extension = 'gd';
+		if (!extension_loaded($php_extension) && (!function_exists('dl') || 
+			!dl(PHP_SHLIB_PREFIX.$php_extension.'.'.PHP_SHLIB_SUFFIX)) ||
+			!function_exists('imagecopyresampled'))
+		{
+			$this->tmpl->Location(array(
+				'menuaction' => 'projectmanager.uiprojectmanager.index',
+				'msg'        => lang("Necessary PHP extentions %1 not loaded and can't be loaded !!!",$php_extension.' ('.PHP_SHLIB_PREFIX.$php_extension.'.'.PHP_SHLIB_SUFFIX.')'),
+			));
+		}
+		$this->modernJPGraph = version_compare('1.13',JPG_VERSION) < 0;
+		//echo "version=".JPG_VERSION.", modernJPGraph=".(int)$this->modernJPGraph; exit;
+		error_log("JPG_VERSION=".JPG_VERSION.", modernJPGraph=".(int)$this->modernJPGraph);
+
+		if (isset($_REQUEST['pm_id']))
+		{
+			$pm_id = (int) $_REQUEST['pm_id'];
+			$GLOBALS['phpgw']->session->appsession('pm_id','projectmanager',$pm_id);
+		}
+		else
+		{
+			$pm_id = $GLOBALS['phpgw']->session->appsession('pm_id','projectmanager');
+		}
+		if (!$pm_id)
+		{
+			$this->tmpl->Location(array(
+				'menuaction' => 'projectmanager.uiprojectmanager.index',
+				'msg'        => lang('You need to select a project first'),
+			));
+		}
+		$this->boprojectelements($pm_id);
+		
+		// check if we have at least read-access to this project
+		if (!$this->project->check_acl(PHPGW_ACL_READ))
+		{
+			$GLOBALS['phpgw']->redirect_link('/index.php',array(
+				'menuaction' => 'projectmanager.uiprojectmanager.index',
+				'msg'        => lang('Permission denied !!!'),
+			));
+		}
+		$this->charset = $GLOBALS['phpgw']->translation->charset();
+		$this->prefs =& $GLOBALS['phpgw_info']['user']['preferences'];
+
+		// check if a arial font is availible and set FF_VERA (or bundled font) if not
+		if (!is_readable((FF_MINCHO <= GANTT_FONT && GANTT_FONT <= FF_PGOTHI ? MBTTF_DIR : TTF_DIR).GANTT_FONT_FILE))
+		{
+			$this->gantt_font = FF_VERA;
+		}
+	}
+	
+	/**
+	 * Converts text from eGW's internal encoding to something understood by JPGraph / GD
+	 *
+	 * The only working thing I found so far is numeric html-entities from iso-8859-1.
+	 * If you found other encoding do work, please let mit know: RalfBecker-AT-outdoor-training.de
+	 * It would be nice if we could use the full utf-8 charset, if supported by the used font.
+	 * 
+	 * @param string $text
+	 * @return string
+	 */
+	function text_encode($text)
+	{
+		// convert to the charset used for the gantchart
+		$text = $GLOBALS['phpgw']->translation->convert($text,$this->charset,$this->gantt_charset);
+
+		// convert everything above ascii to nummeric html entities
+		// not sure if this is necessary for non iso-8859-1 charsets, try to comment it out if you have problems
+		if ($this->gantt_char_encode) $text = preg_replace('/[^\x00-\x7F]/e', '"&#".ord("$0").";"',$text);
+		
+		return $text;
+	}
+	
+	/**
+	 * Try to guess a locale supported by the server, with fallback to 'en_EN' and 'C'
+	 *
+	 * @return string
+	 */
+	function guess_locale()
+	{
+		$lang = $this->prefs['common']['lang'];
+		$country = $this->prefs['common']['country'];
+		
+		if (strlen($lang) == 2)
+		{
+			$country_from_lang = strtoupper($lang);
+		}
+		else
+		{
+			list($lang,$country_from_lang) = explode('-',$lang);
+			$country_from_lang = strtoupper($country_from_lang);
+		}
+		if (setlocale(LC_ALL,$locale=$lang.'_'.$country)) return $locale;
+		if (setlocale(LC_ALL,$locale=$lang.'_'.$country_from_lang)) return $locale;
+		if (setlocale(LC_ALL,$locale=$lang)) return $locale;
+		if (setlocale(LC_ALL,$locale='en_EN')) return $locale;
+		
+		return 'C';
+	}
+
+	/**
+	 * create a new JPGraph Ganttchart object and setup a fitting scale
+	 *
+	 * @param string $title
+	 * @param string $subtile
+	 * @param int $start startdate of the ganttchart
+	 * @param int $end enddate of the ganttchart
+	 * @return object GantGraph
+	 */
+	function &new_gantt($title,$subtitle,$start,$end,$width=940)
+	{
+		// create new graph object
+		$graph =& new GanttGraph($width,-1,'auto');
+		
+		$graph->SetShadow();
+		$graph->SetBox();	
+
+		// set the start and end date
+		$graph->SetDateRange(date('Y-m-d',$start), date('Y-m-d',$end));
+		
+		// some localizations
+		if ($this->modernJPGraph)
+		{
+			$graph->scale->SetDateLocale($this->guess_locale());
+		}
+		elseif ($this->prefs['common']['lang'] == 'de')
+		{
+			$graph->scale->SetDateLocale(LOCALE_DE);	// others use english
+		}		
+		// set start-day of the week from the cal-pref weekdaystarts
+		$weekdaystarts2day = array(
+			'Sunday'   => 0,
+			'Monday'   => 1,
+			'Saturday' => 6,
+		);
+		$weekdaystarts = $this->prefs['calendar']['weekdaystarts'];
+		if ($this->modernJPGraph && isset($weekdaystarts2day[$weekdaystarts]))
+		{
+			$graph->scale->SetWeekStart($weekdaystarts2day[$weekdaystarts]);
+		}
+		// get and remember the real start- & enddates, to clip the bar for old JPGraph versions
+		$this->scale_start = $graph->scale->iStartDate;
+		$this->scale_end   = $graph->scale->iEndDate;
+
+		$days = round(($this->scale_end - $this->scale_start) / 86400);
+		$month = $days / 31;
+		//echo date('Y-m-d',$this->scale_start).' - '.date('Y-m-d',$this->scale_end).' '.$month;
+		// 2 weeks and less: day (weekday date) and hour headers, only possible with modern JPGraph
+		if($this->modernJPGraph && $days <= 14)
+		{
+			$graph->ShowHeaders(GANTT_HDAY | GANTT_HHOUR);
+			$graph->scale->day->SetStyle($days < 7 ? DAYSTYLE_LONGDAYDATE1 : DAYSTYLE_SHORTDATE3);
+			$graph->scale->hour->SetStyle($this->prefs['common']['timeformat'] == 12 ? HOURSTYLE_HAMPM : HOURSTYLE_H24);
+			foreach(array(8=>6,5=>4,3=>2,2=>1) as $max => $int)
+			{
+				if ($days >= $max) break;
+			}
+			//echo "days=$days => $int ($max)";
+			$graph->scale->hour->SetIntervall($int);
+		}
+		// 1.5 month and less: month (with year), week and day (date) headers
+		elseif($month <= 1.5)
+		{
+			$graph->ShowHeaders(GANTT_HMONTH | GANTT_HWEEK | GANTT_HDAY);
+			$graph->scale->month->SetStyle(MONTHSTYLE_LONGNAMEYEAR4);
+			if ($this->modernJPGraph) $graph->scale->day->SetStyle(DAYSTYLE_SHORTDATE4);
+		}
+		// 2.5 month and less: month (with year), week (week and startday) and day headers
+		elseif($month <= 2.5)
+		{
+			$graph->ShowHeaders(GANTT_HMONTH | GANTT_HWEEK | GANTT_HDAY);
+			if ($this->modernJPGraph) $graph->scale->week->SetStyle(WEEKSTYLE_FIRSTDAYWNBR);
+			$graph->scale->month->SetStyle(MONTHSTYLE_LONGNAMEYEAR4);
+		}
+		// 6 month and less: year, month and week (with weeknumber) headers
+		elseif($month <= 6) // half year
+		{
+			$graph->ShowHeaders(GANTT_HYEAR | GANTT_HMONTH | GANTT_HWEEK);
+			$graph->scale->month->SetStyle(MONTHSTYLE_LONGNAME);
+		}
+		// more then 6 month: only year and month headers
+		else
+		{
+			$graph->ShowHeaders(GANTT_HYEAR | GANTT_HMONTH);
+		}
+		// Change the font scale 
+		$graph->scale->week->SetFont($this->gantt_font,FS_NORMAL,8);
+		$graph->scale->year->SetFont($this->gantt_font,GANTT_STYLE,10);
+		
+		// Title & subtitle
+		$graph->title->Set($this->text_encode($title));
+		$graph->title->SetFont($this->gantt_font,GANTT_STYLE,12);
+		$graph->subtitle->Set($this->text_encode($subtitle));
+		$graph->subtitle->SetFont($this->gantt_font,FS_NORMAL,10);
+		
+		return $graph;
+	}
+	
+	/**
+	 * Ganttbar for a project
+	 *
+	 * @param array $pm project or project-element data array
+	 * @param int $level hierarchy level, 0=main project
+	 * @param int $line line-number of the gantchart, starting with 0
+	 * @param boolean $planned_times=false show planned or real start- and end-dates
+	 * @return object GanttBar
+	 */
+	function &project2bar($pm,$level,$line,$planned_times=false)
+	{
+		if ($pm['pe_id'])
+		{
+			$pe =& $pm;
+		}
+		else
+		{
+			$pe = array(
+				'pm_id' => $pm['pm_id'],
+				'pe_id' => 0,
+			);
+			foreach($pm as $key => $val)
+			{
+				if ($key != 'pm_id') $pe[str_replace('pm_','pe_',$key)] =& $pm[$key];
+			}
+		}
+		$bar =& $this->element2bar($pe,$level,$line,$planned_times);
+
+		// set project-specific attributes: bold, solid bar, ...
+		$bar->title->SetFont($this->gantt_font,GANTT_STYLE,!$level ? 9 : 8);
+		$bar->SetPattern(BAND_SOLID,"#9999FF");
+		
+		if ($this->modernJPGraph && !$pe['pe_id'])	// main-project
+		{
+			$link = $GLOBALS['phpgw']->link('/index.php',array(
+				'menuaction' => 'projectmanager.uiprojectmanager.view',
+				'pm_id'      => $pe['pm_id'],
+			));
+			$title = lang('View this project');
+			
+			$bar->SetCSIMTarget($link,$title);
+			$bar->title->SetCSIMTarget($link,$title);
+		}
+		return $bar;
+	}
+	
+	/**
+	 * Ganttbar for a project-element
+	 *
+	 * @param array $pe projectelement-data array
+	 * @param int $level hierarchy level, 0=main project
+	 * @param int $line line-number of the gantchart, starting with 0
+	 * @param boolean $planned_times=false show planned or real start- and end-dates
+	 * @return object GanttBar
+	 */
+	function &element2bar($pe,$level,$line,$planned_times=false)
+	{
+		// create a shorter title (removes dates from calendar-titles and project-numbers from sub-projects
+		if ($pe['pe_app'] == 'calendar' || $pe['pe_app'] == 'projectmanager')
+		{
+			list(,$title) = explode(': ',$pe['pe_title'],2);
+		}
+		if (!$title) $title = $pe['pe_title'];
+		
+		if ((int) $this->debug >= 1) 
+		{
+			echo "<p>GanttBar($line,'".($level ? str_repeat(' ',$level) : '').
+				$this->text_encode($title).'   '."','".
+				date('Y-m-d H:i',$pe['pe_start'])."','".date('Y-m-d H:i',$pe['pe_end'])."','".
+				round($pe['pe_completion']).'%'."',0.5)</p>\n";
+		}		
+		if (!$this->modernJPGraph)	// for an old JPGraph we have to clip the bar ourself
+		{
+			if ($pe['pe_start'] < $this->scale_start) $pe['pe_start'] = $this->scale_start;
+			if ($pe['pe_end'] > $this->scale_end) $pe['pe_end'] = $this->scale_end-1;
+		}
+		$bar =& new GanttBar($line,($level ? str_repeat(' ',$level) : '').
+			$this->text_encode($title).
+			($level ? '  ' : ''),	// fix for wrong length calculation in JPGraph
+			date('Y-m-d'.($this->modernJPGraph ? ' H:i' : ''),$pe['pe_start']),
+			date('Y-m-d'.($this->modernJPGraph ? ' H:i' : ''),$pe['pe_end']),
+			round($pe['pe_completion']).'%',0.5);
+			
+		$bar->progress->Set($pe['pe_completion']/100);
+		$bar->progress->SetHeight(0.5);
+		
+		if ($this->modernJPGraph && $pe['pe_id'])
+		{
+			$bar->SetCSIMTarget('@600x450'.$GLOBALS['phpgw']->link('/index.php',array(	// @ = popup
+				'menuaction' => 'projectmanager.uiprojectelements.view',
+				'pm_id'      => $pe['pm_id'],
+				'pe_id'      => $pe['pe_id'],
+			)),$pe['pe_remark'] ? $pe['pe_remark'] : lang('View this project-element'));
+			
+			if (($popup = $GLOBALS['phpgw']->link->is_popup($pe['pe_app']))) $popup = '@'.$popup;
+			$bar->title->SetCSIMTarget($popup.$GLOBALS['phpgw']->link('/index.php',$this->link->view($pe['pe_app'],$pe['pe_app_id'])),
+				lang('View this element in %1',lang($pe['pe_app'])));
+		}
+		$bar->title->SetFont($this->gantt_font,FS_NORMAL,!$level ? 9 : 8);
+
+		return $bar;
+	}
+
+	/**
+	 * Milestone
+	 *
+	 * @param array $milestone data-array
+	 * @param int $level hierarchy level, 0=main project
+	 * @param int $line line-number of the gantchart, starting with 0
+	 * @return object MileStone
+	 */
+	function &milestone2bar($milestone,$level,$line)
+	{
+		if ((int) $this->debug >= 1) 
+		{
+		 	echo "<p>MileStone($line,'$milestone[ms_title],".
+		 		date('Y-m-d',$milestone['ms_date']).','.
+				date($this->prefs['common']['dateformat'],$milestone['ms_date']).")</p>\n";
+		}
+		$ms =& new MileStone($line,($level ? str_repeat(' ',$level) : '').
+			$this->text_encode($milestone['ms_title']),
+			date('Y-m-d',$milestone['ms_date']),
+			date($this->prefs['common']['dateformat'],$milestone['ms_date']));
+		
+		$ms->title->SetFont($this->gantt_font,FS_ITALIC,8);
+		$ms->title->SetColor('blue');
+		$ms->mark->SetColor('black');
+		$ms->mark->SetFillColor('blue');
+
+		if ($this->modernJPGraph)
+		{
+			$link = $GLOBALS['phpgw']->link('/index.php',array(
+				'menuaction' => 'projectmanager.uimilestones.view',
+				'pm_id'      => $milestone['pm_id'],
+				'ms_id'      => $milestone['ms_id'],
+			));
+			$title = lang('View this milestone');
+			$ms->SetCSIMTarget('@600x450'.$link,$title);	// @ = popup
+			$ms->title->SetCSIMTarget('@600x450'.$link,$title);
+		}
+		return $ms;
+	}
+
+	/**
+	 * Adds all elements of project $pm_id to the ganttchart, calls itself recursive for subprojects
+	 *
+	 * @param int $pm_id project-id
+	 * @param array $params
+	 * @param int &$line line-number of the gantchart, starting with 0, gets incremented
+	 * @param array &$bars bars are added here, with their pe_id as key
+	 * @param int $level hierarchy level starting with 1, function stops if $level > $params['deepth']
+	 */
+	function add_elements($pm_id,$params,&$line,&$bars,$level=1)
+	{
+		static $filter=false;
+		static $extra_cols;
+
+		if (!$filter)	// we do this only once for all shown projects
+		{
+			// defining start- and end-times depending on $params['planned_times'] and the availible data
+			foreach(array('start','end') as $var)
+			{
+				if ($params['planned_times'])
+				{
+					$$var = "CASE WHEN pe_planned_$var IS NULL THEN pe_real_$var ELSE pe_planned_$var END";
+				}
+				else
+				{
+					$$var = "CASE WHEN pe_real_$var IS NULL THEN pe_planned_$var ELSE pe_real_$var END";
+				}
+			}
+			$filter = array(
+				"pe_status != 'ignore'",
+				"$start IS NOT NULL",
+				"$end IS NOT NULL",
+				"$start <= ".(int)$this->scale_end,	// starts befor the end of our period AND
+				"$end >= ".(int)$this->scale_start,	// ends after the start of our period
+				'cumulate' => true,
+			);
+			switch ($params['filter'])
+			{
+				case 'not':
+					$filter['pe_completion'] = 0;
+					break;
+				case 'ongoing':
+					$filter[] = 'pe_completion!=100';
+					break;
+				case 'done':
+					$filter['pe_completion'] = 100;
+					break;
+			}
+			if ($params['pe_resources'])
+			{
+				$filter['pe_resources'] = $params['pe_resources'];
+			}
+			$extra_cols = array(
+				$start.' AS pe_start',
+				$end.' AS pe_end',
+			);
+		}
+		$filter['pm_id'] = $pm_id;	// this is NOT static
+
+		$pe_id2line = array();
+		foreach((array) $this->search(array(),false,'pe_start,pe_end',$extra_cols,
+			'',false,'AND',false,$filter) as $pe)
+		{
+			//echo "$line: ".print_r($pe,true)."<br>\n";
+			if (!$pe) continue;
+			
+			$pe_id = $pe['pe_id'];
+			$pe_id2line[$pe_id] = $line;	// need to remember the line to draw the constraints
+			$pes[$pe_id] = $pe;
+
+			if ($pe['pe_app'] == 'projectmanager')
+			{
+				$bars[$pe_id] =& $this->project2bar($pe,$level,$line++,$params['planned_times']);
+			}
+			else
+			{
+				$bars[$pe_id] =& $this->element2bar($pe,$level,$line++,$params['planned_times']);
+			}
+			// if we should display further levels, we call ourself recursive
+			if ($pe['pe_app'] == 'projectmanager' && $level < $params['depth'])
+			{
+				$this->add_elements($pe['pe_app_id'],$params,$line,$bars,$level+1);
+			}
+		}
+		if ($params['constraints'] && $this->modernJPGraph)		// the old jpgraph does not support constrains
+		{
+			// adding milestones
+			foreach((array)$this->milestones->search(array(),'pm_id,ms_id,ms_title,ms_date','ms_date','','',false,'AND',false,array(
+				'pm_id' => $pm_id,
+				(int)$this->scale_start.' <= ms_date',
+				'ms_date <= '.(int)$this->scale_end, 
+			)) as $milestone)
+			{
+				if (!$milestone || !($ms_id = $milestone['ms_id'])) continue;
+
+				$ms_id2line[$ms_id] = $line;
+				$milestones[$ms_id] = $milestone;
+				$bars[-$ms_id] =& $this->milestone2bar($milestone,$level,$line++);
+			}
+			// adding the constraints to the bars
+			foreach((array)$this->constraints->search(array('pm_id'=>$pm_id)) as $constraint)
+			{
+				$pe_id = $constraint['pe_id_end'];	// start of the array at the end of this pe
+				if (isset($bars[$pe_id]))
+				{
+					$bar =& $bars[$pe_id];
+					
+					if ($constraint['pe_id_start'] && isset($pe_id2line[$constraint['pe_id_start']]))
+					{
+						// show violated constrains in red
+						$color = $pes[$constraint['pe_id_start']]['pe_start'] >= $pes[$pe_id]['pe_end'] ? 'black' : 'red';
+						$bar->SetConstrain($pe_id2line[$constraint['pe_id_start']],CONSTRAIN_ENDSTART,$color);
+					}
+					if ($constraint['ms_id'] && isset($ms_id2line[$constraint['ms_id']]))
+					{
+						// show violated constrains in red
+						$color = $milestones[$constraint['ms_id']]['ms_date'] >= $pes[$pe_id]['pe_end'] ? 'black' : 'red';
+						$bar->SetConstrain($ms_id2line[$constraint['ms_id']],CONSTRAIN_ENDSTART,$color);
+					}
+				}
+			}
+		}
+	}
+
+	/**
+	 * Create a ganttchart
+	 *
+	 * @param array $params=null params, if (default) null, use them from the URL
+	 * @param string $filename='' filename to store the chart or (default) empty to send it direct to the browser
+	 */
+	function create($params=null,$filename='',$imagemap='ganttchar')
+	{
+		if (!$params) $params = $this->url2params($params);
+
+		$title = lang('project overview').': '.(is_numeric($params['pm_id']) ? $this->project->data['pm_title'] : '');
+		$subtitle = lang('from %1 to %2',date($this->prefs['common']['dateformat'],$params['start']),date($this->prefs['common']['dateformat'],$params['end']));
+		// create new graph-object and set scale_{start|end}
+		$graph =& $this->new_gantt($title,$subtitle,$params['start'],$params['end'],$params['width']);
+
+		if ($params['start'] < $this->now_su && $this->now_su < $params['end'])
+		{
+			// add a vertical line to mark today
+			$graph->add(new GanttVLine($this->now_su-24*60*60,
+				date($GLOBALS['phpgw_info']['user']['preferences']['common']['dateformat'],$this->now_su)));
+		}
+		$line = 0;
+		$bars = array();
+		foreach(explode(',',$params['pm_id']) as $pm_id)
+		{
+			if ($pm_id != $this->project->data['pm_id'])
+			{
+				if (!$this->project->read($pm_id) || !$this->project->check_acl(PHPGW_ACL_READ))
+				{
+					continue;
+				}
+			}
+			$graph->Add($this->project2bar($this->project->data,0,$line++,$params['planned_times']));
+	
+			if ($params['depth'] > 0)
+			{
+				$this->add_elements($this->project->data['pm_id'],$params,$line,$bars);
+			}
+		}
+		foreach($bars as $pe_id => $bar)
+		{
+			$graph->Add($bar);
+		}
+		if (!$this->debug) $graph->Stroke($filename);
+		
+		if ($filename && $imagemap) return $graph->GetHTMLImageMap($imagemap);
+	}
+	
+	/**
+	 * read the ganttchart params from the URL
+	 *
+	 * @param array $params already set parameters, default empty array
+	 * @return array with params
+	 */
+	function url2params($params = array())
+	{
+		if ((int) $this->debug >= 1) echo "<p>ganttchart::url2params(".print_r($params,true).")</p>\n";
+		
+		if (!count($params))
+		{
+			if (!($params = $GLOBALS['phpgw']->session->appsession('ganttchart','projectmanager')))
+			{
+				$params = array(				// some defaults, if called the first time
+					'constraints' => true,
+				);
+			}			
+			// check if project changed => not use start and end
+			if ($params['pm_id'] != $this->project->data['pm_id'])
+			{
+				$params['pm_id'] = $this->project->data['pm_id'];
+				unset($params['start']);
+				unset($params['end']);
+			}
+		}
+		$data =& $this->project->data;
+		foreach(array('start','end') as $var)
+		{
+			// set used start- and end-times of the project
+			if ($params['planned_times'] && $data['pe_planned_'.$var] || !$data['pe_real_'.$var])
+			{
+				$data['pm_'.$var] = $data['pm_planned_'.$var];
+			}
+			else
+			{
+				$data['pm_'.$var] = $data['pm_real_'.$var];
+			}
+			// set start- and end-times of the ganttchart
+			if (isset($_GET[$var]))
+			{
+				$params[$var] = $_GET[$var];
+			}
+			elseif (isset($params[$var]) && $params[$var])
+			{
+				// already set
+			}
+			elseif ($data['pm_id'] && $data['pm_'.$var])
+			{
+				$params[$var] = $data['pm_'.$var];
+			}
+			else
+			{
+				$params[$var] = $var == 'start' ? date('Y-m-1') : date('Y-m-1',time()+61*24*60*60);
+			}
+			$params[$var] = is_numeric($params[$var]) ? (int) $params[$var] : strtotime($params[$var]);
+			
+			if ((int) $this->debug >= 1) echo "<p>$var=".$params[$var].'='.date('Y-m-d',$params[$var])."</p>\n";
+		}
+		if ((int) $_GET['width'])
+		{
+			$params['width'] = (int) $_GET['width'];
+		}
+		else
+		{
+			$params['width'] = $this->tmpl->innerWidth - 
+				($this->prefs['common']['auto_hide_sidebox'] ? 60 : 245);
+		}
+		if (!isset($params['pm_id']) && $this->project->data['pm_id'])
+		{
+			$params['pm_id'] = $this->project->data['pm_id'];
+		}
+		if (!isset($params['depth'])) $params['depth'] = 1;
+		
+		if ($_GET['pm_id'] && !is_numeric($_GET['pm_id']))
+		{
+			$params['pm_id'] = $_GET['pm_id'];
+		}
+		$GLOBALS['phpgw']->session->appsession('ganttchart','projectmanager',$params);
+		if ((int) $this->debug >= 1) _debug_array($params);
+
+		return $params;		
+	}
+	
+	/**
+	 * Return message to install a new jpgraph version
+	 *
+	 * @static 
+	 * @return string/boolean message or false if a new version is installed
+	 */
+	function msg_install_new_jpgraph()
+	{
+		return version_compare('1.13',JPG_VERSION) < 0 ? false :
+			lang('You are using the old version of JPGraph, bundled with eGroupWare. It has only limited functionality.').
+			"<br />\n".lang('Please download a recent version from %1 and install it in %2.',
+			'<a href="http://www.aditus.nu/jpgraph/jpdownload.php" target="_blank">www.aditus.nu/jpgraph</a>',
+			realpath(PHPGW_SERVER_ROOT.'/..').SEP.'jpgraph');
+	}
+
+	/**
+	 * Shows a ganttchart
+	 *
+	 * As ganttcharts contain an image-map and the image, we save the image as a temporary file.
+	 * This for performance reasons, it saves a second creation / script-run.
+	 * projectmanager/ganttchart.php reads and output the temporary file/image and unlinks it after.
+	 */
+	function show($content=array(),$msg='')
+	{
+		if ($content['sync_all'] && $this->project->check_acl(PHPGW_ACL_ADD))
+		{
+			$msg = lang('%1 element(s) updated',$this->sync_all());
+			unset($content['sync_all']);
+		}		
+		// run $_GET[msg] through htmlspecialchars, as we output it raw, to allow the link in the jpgraph message.
+		if (!$msg) $msg = $this->tmpl->html->htmlspecialchars($_GET['msg']);
+
+		if (!$GLOBALS['phpgw']->session->appsession('ganttchart','projectmanager') && !$this->modernJPGraph)
+		{
+			$msg .= $this->msg_install_new_jpgraph();
+		}
+		unset($content['update']);
+		$content = $this->url2params($content);
+	
+		$tmp = $GLOBALS['phpgw_info']['server']['temp_dir'];
+		if (!is_dir($tmp) || !is_writable($tmp))
+		{
+			$tmp = '';
+		}
+		$img = tempnam($tmp,'ganttchart');
+		$img_name = basename($img);
+		$map = $this->create($content,$img,'ganttchart');
+		// replace the regular links with popups
+		$map = preg_replace('/href="@(\d+)x(\d+)([^"]+)"/i','href="#" onclick="phpgw_openWindowCentered2(\'\\3\',\'_blank\',\'dependent=yes,width=\\1,height=\\2,scrollbars=yes,status=yes\'); return false;"'
+		,$map);
+		
+		$content['ganttchart'] = $GLOBALS['phpgw']->link('/projectmanager/ganttchart.php',$content+array(
+			'img'   => $img_name,
+		));
+		$content['map'] = $map;
+		$content['msg'] = $msg;
+		
+		$sel_options = array(
+			'depth' => array(
+				0  => '0: '.lang('Mainproject only'),
+				1  => '1: '.lang('Project-elements'),
+				2  => '2: '.lang('Elements of elements'),
+				3  => '3: '.lang('Elements of elements'),
+				99 => lang('Everything recursive'),
+			),
+			'filter' => array(
+				''        => lang('All'),
+				'not'     => lang('Not started (0%)'),
+				'ongoing' => lang('0ngoing (0 < % < 100)'),
+				'done'    => lang('Done (100%)'),
+			),
+		);
+		$GLOBALS['phpgw_info']['flags']['app_header'] = lang('projectmanager').' - '.lang('Ganttchart').': '.$this->project->data['pm_title'];;
+		$this->tmpl->read('projectmanager.ganttchart');
+		return $this->tmpl->exec('projectmanager.ganttchart.show',$content,$sel_options,'',array('pm_id'=>$content['pm_id']));
+	}
+}
Index: /contrib/ProjectManager/inc/class.sopricelist.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.sopricelist.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.sopricelist.inc.php	(revision 3594)
@@ -0,0 +1,317 @@
+<?php
+/**
+ * ProjectManager - Pricelist storage object
+ *
+ * @link http://www.egroupware.org
+ * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @package projectmanager
+ * @copyright (c) 2005 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @version $Id: class.sopricelist.inc.php 23134 2006-12-27 11:08:15Z ralfbecker $ 
+ */
+
+include_once(PHPGW_INCLUDE_ROOT.'/etemplate/inc/class.so_sql.inc.php');
+
+/**
+ * Pricelist storage object of the projectmanager
+ *
+ * Tables: phpgw_pm_pricelist, phpgw_pm_prices
+ */
+class sopricelist extends so_sql
+{
+	/**
+	 * @var string $prices_table table name of the prices table
+	 */
+	var $prices_table = 'phpgw_pm_prices';
+	/**
+	 * @var string $prices_join default join with the prices table
+	 */
+	var $prices_join = 'JOIN phpgw_pm_prices p ON phpgw_pm_pricelist.pl_id=p.pl_id';
+	/**
+	 * @var array $links_extracols extracolumns from the links table
+	 */
+	var $prices_extracols = array('pm_id','pl_validsince','pl_price','pl_customertitle','pl_modifier','pl_modified','pl_billable');
+	/**
+	 * @var int $pm_id project we work on or 0 for standard pricelist only
+	 */
+	var $pm_id;
+
+	/**
+	 * Constructor, calls the constructor of the extended class
+	 * 
+	 * @param int $pm_id=0 pm_id of the project to use, default 0 (project independent / standard prices)
+	 */
+	function sopricelist($pm_id=0)
+	{
+		$this->so_sql('projectmanager','phpgw_pm_pricelist');	// sets $this->table_name
+
+		$this->pm_id = (int) $pm_id;
+	}
+	
+	/**
+	 * reads one pricelist-itme specified by $keys, reimplemented to use $this->pm_id, if no pm_id given
+	 *
+	 * @param array $keys array with keys in form internalName => value, may be a scalar value if only one key
+	 * @param string/array $extra_cols string or array of strings to be added to the SELECT, eg. "count(*) as num"
+	 * @param string/boolean $join=true default join with links-table or string as in so_sql
+	 * @return array/boolean data if row could be retrived else False
+	*/
+	function read($keys,$extra_cols='',$join=true)
+	{
+		if (!is_array($keys)) $keys = array('pl_id' => (int) $keys);
+
+		if ($join === true)	// add join with links-table and extra-columns
+		{
+			// just pl_id would be ambigues
+			$keys[] = $this->table_name.'.pl_id='.(int)$keys['pl_id'];
+			unset($keys['pl_id']);
+			
+			if (isset($keys['pl_validsince']))
+			{
+				$keys[] = 'pl_validsince <= '.(int)$keys['pl_validsince'];
+				unset($keys['pl_validsince']);
+			}
+			if (isset($keys['pm_id']))
+			{
+				$keys[] = $this->db->expression($this->prices_table,array('pm_id' => $keys['pm_id']));
+				unset($keys['pm_id']);
+			}
+			$join = $this->prices_join;
+			
+			if (!$extra_cols) $extra_cols = $this->prices_extracols;
+			
+			// we use search as the join might return multiple columns, which we put in the prices and project_prices array
+			if (!($prices = $this->search(false,false,'pm_id DESC,pl_validsince DESC',$extra_cols,'',false,'AND',false,$keys,$join)))
+			{
+				return false;
+			}
+			list(,$this->data) = each($prices);
+			$this->data['prices'] = $this->data['project_prices'] = array();			
+			
+			foreach($prices as $price)
+			{
+				if ($price['pm_id'])
+				{
+					$this->data['project_prices'][] = $price;
+				}
+				else
+				{
+					$this->data['prices'][] = $price;
+				}
+			}
+			return $this->data;
+		}
+		return parent::read($keys,$extra_cols,$join);
+	}
+	
+	/**
+	 * sql to define a priority depending on the relation-ship of the project the price was defined for to the given one
+	 *
+	 * The given project $pm_id has the highest priority, if $use_standard the standard-pricelist has priority 0
+	 *
+	 * @param int $pm_id=0 project to use, default $this->pm_id
+	 * @param string $col='pm_id' column-name to use, default 'pm_id', can be changed to use a table-name/-alias too
+	 * @param boolean $use_general=true should the general-pricelist be included too (pm_id=0)
+	 * @return string sql with CASE WHEN
+	 */
+	function sql_priority($pm_id=0,$col='pm_id',$use_general=true)
+	{
+		if (!$pm_id) $pm_id = $this->pm_id;
+		
+		$ancestors = array($pm_id);
+		
+		if (!($ancestors = $this->project->ancestors($pm_id,$ancestors)))
+		{
+			echo "<p>sql_priority($pm_id,$col,$use_general) ancestory returnd false</p>\n";
+			return false;
+		}
+		$sql = 'CASE '.$col;
+		foreach($ancestors as $n => $pm_id)
+		{
+			$sql .= ' WHEN '.$pm_id.' THEN '.(count($ancestors)-$n);
+		}
+		if ($use_general)
+		{
+			$sql .= ' WHEN 0 THEN 0';
+		}
+		$sql .= ' END';
+	
+		return $sql;
+	}
+
+	/**
+	 * search elements, reimplemented to use $this->pm_id, if no pm_id given in criteria or filter and join with the prices table
+	 *
+	 * @param array/string $criteria array of key and data cols, OR a SQL query (content for WHERE), fully quoted (!)
+	 * @param boolean $only_keys True returns only keys, False returns all cols
+	 * @param string $order_by fieldnames + {ASC|DESC} separated by colons ','
+	 * @param string/array $extra_cols string or array of strings to be added to the SELECT, eg. "count(*) as num"
+	 * @param string $wildcard appended befor and after each criteria
+	 * @param boolean $empty False=empty criteria are ignored in query, True=empty have to be empty in row
+	 * @param string $op defaults to 'AND', can be set to 'OR' too, then criteria's are OR'ed together
+	 * @param int/boolean $start if != false, return only maxmatch rows begining with start
+	 * @param array $filter if set (!=null) col-data pairs, to be and-ed (!) into the query without wildcards
+	 * @param string/boolean $join=true default join with prices-table or string as in so_sql
+	 * @return array of matching rows (the row is an array of the cols) or False
+	 */
+	function search($criteria,$only_keys=false,$order_by='pl_title',$extra_cols='',$wildcard='',$empty=False,$op='AND',$start=false,$filter=null,$join=true)
+	{
+		if ($join === true)	// add join with prices-table
+		{
+			$join = $this->prices_join;
+			
+			if (isset($filter['pl_validsince']))
+			{
+				$filter[] = 'pl_validsince <= '.($validsince=(int)$filter['pl_validsince']);
+				unset($filter['pl_validsince']);
+			}
+			else
+			{
+				$validsince = time();
+			}
+			if (!$extra_cols)
+			{
+				$extra_cols = $this->prices_extracols;
+			}
+			else
+			{
+				$extra_cols = array_merge($this->prices_extracols,
+					is_array($extra_cols) ? $extra_cols : explode(',',$extra_cols));
+			}
+			if (($no_general = isset($criteria['pm_id'])))
+			{
+				$pm_id = $criteria['pm_id'];
+				unset($criteria['pm_id']);
+			}
+			else
+			{
+				$pm_id = isset($filter['pm_id']) ? $filter['pm_id'] : $this->pm_id;
+				unset($filter['pm_id']);
+			}
+			if ($this->db->capabilities['sub_queries'])
+			{
+				$filter[] = "pl_validsince = (SELECT MAX(t.pl_validsince) FROM $this->prices_table t WHERE p.pl_id=t.pl_id AND p.pm_id=t.pm_id AND t.pl_validsince <= $validsince)";
+
+				if (!$order_by) $order_by = 'pl_title';
+
+				if ($pm_id)
+				{
+					$filter[] = $this->sql_priority($pm_id,'p.pm_id').' = (select MAX('.$this->sql_priority($pm_id,'m.pm_id').
+						") FROM $this->prices_table m WHERE m.pl_id=p.pl_id)";
+
+					if ($no_general) $filter[] = 'pm_id != 0 AND pl_billable IN (0,1)';
+				}
+				else
+				{
+					$filter['pm_id'] = 0;
+				}
+			}
+			else	// mysql 4.0 or less
+			{
+				// ToDo: avoid subqueries (eg. use a join) or do it manual inside php
+				//echo "<p>Pricelist needs a DB capabal of subqueries (eg. MySQL 4.1+) !!!</p>\n";
+				$filter['pm_id'] = $this->project->ancestors($this->pm_id,array(0,(int)$this->pm_id));
+				$filter[] = 'pl_validsince <= '.$validsince;
+			}
+			$prices_filter = array();
+			foreach($extra_cols as $col)
+			{
+				if (isset($filter[$col]))
+				{
+					$prices_filter[$col] = $filter[$col];
+					unset($filter[$col]);
+				}
+			}
+			if (count($prices_filter))
+			{
+				$filter[] = $this->db->expression($this->prices_table,$prices_filter);
+			}
+			// do we run a search
+			if ($op == 'OR' && $wildcard == '%' && $criteria['pl_id'] && $criteria['pl_id'] == $criteria['pl_title'])
+			{
+				foreach(array('pl_customertitle','pl_price') as $col)
+				{
+					$criteria[] = $col. ' LIKE '.$this->db->quote('%'.$criteria['pl_id'].'%');
+				}
+				unset($criteria['pl_id']);	// its ambigues
+			}
+		}
+		// include sub-categories in the search
+		if ($filter['cat_id'])
+		{
+			if (!is_object($GLOBALS['phpgw']->categories))
+			{
+				$GLOBALS['phpgw']->categories =& CreateObject('phpgwapi.categories');
+			}
+			$filter['cat_id'] = $GLOBALS['phpgw']->categories->return_all_children($filter['cat_id']);
+		}
+		return parent::search($criteria,$only_keys,$order_by,$extra_cols,$wildcard,$empty,$op,$start,$filter,$join);
+	}
+	
+	/**
+	 * saves a single price 
+	 *
+	 * @param array $price price-data
+	 * @param boolean $touch_modified=true should the modififcation date and user be set
+	 * @param int 0 on success, db::Errno otherwise
+	 */
+	function save_price(&$price,$touch_modified=true)
+	{
+		//echo "<p>sopricelist::save_price(".print_r($price,true).",$touch_modified)</p>\n";
+		$price = $this->data2db($price);
+
+		if ($touch_modified)
+		{
+			$price['pl_modified'] = time();
+			$price['pl_modifier'] = $GLOBALS['phpgw_info']['user']['account_id'];
+		}
+		$this->db->insert($this->prices_table,$price,array(
+			'pm_id' => $price['pm_id'],
+			'pl_id' => $price['pl_id'],
+			'pl_validsince' => $price['pl_validsince'],
+		),__LINE__,__FILE__);
+		
+		$price = $this->db2data($price);
+
+		return $this->db->Errno;
+	}
+	
+	/**
+	 * delete pricelist-entries and price(s) specified by keys pl_id, pm_id and/or pl_validsince
+	 *
+	 * If the last price of a pricelist-entry gets deleted, the pricelist entry is automatic deleted too!
+	 *
+	 * @param array/int $keys array with keys pm_id, pl_id and/or pl_validsince to delete or integer pm_id
+	 * @return int number of deleted prices
+	 */
+	function delete($keys)
+	{
+		//echo "<p>sopricelist::delete(".print_r($keys,true).",$touch_modified)</p>\n";
+		if (!is_array($keys) && (int) $keys) $keys = array('pm_id' => (int) $keys);
+		
+		$keys = $this->data2db($keys);	// adjust the validsince timestampt to server-time
+
+		$where = array();
+		foreach(array('pm_id','pl_id','pl_validsince') as $key)
+		{
+			if (isset($keys[$key])) $where[$key] = $keys[$key];
+		}
+		if (!$where) return false;
+
+		$this->db->delete($this->prices_table,$where,__LINE__,__FILE__);
+		$deleted = $this->db->affected_rows();
+
+		// check for pricelist items with no prices and delete them too
+		$this->db->select($this->table_name,$this->table_name.'.pl_id',$this->prices_table.'.pl_id IS NULL',__LINE__,__FILE__,false,'',false,0,"LEFT JOIN $this->prices_table ON $this->table_name.pl_id=$this->prices_table.pl_id");
+		$pl_ids = array();
+		while($this->db->next_record())
+		{
+			$pl_ids[] = $this->db->f(0);
+		}
+		if ($pl_ids)
+		{
+			$this->db->delete($this->table_name,array('pl_id' => $pl_ids),__LINE__,__FILE__);
+		}
+		return $deleted;
+	}
+}
Index: /contrib/ProjectManager/inc/class.uiprojectelements.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.uiprojectelements.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.uiprojectelements.inc.php	(revision 3594)
@@ -0,0 +1,643 @@
+<?php
+/**
+ * ProjectManager - UI to list and edit project-elments
+ *
+ * @link http://www.egroupware.org
+ * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @package projectmanager
+ * @copyright (c) 2005/6 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @version $Id: class.uiprojectelements.inc.php 23913 2007-05-22 04:04:13Z lkneschke $ 
+ */
+
+include_once(PHPGW_INCLUDE_ROOT.'/projectmanager/inc/class.boprojectelements.inc.php');
+
+/**
+ * ProjectManage UI: list and edit projects-elements
+ */
+class uiprojectelements extends boprojectelements  
+{
+	/**
+	 * Functions to call via menuaction
+	 * 
+	 * @var array
+	 */
+	var $public_functions = array(
+		'index' => true,
+		'edit'  => true,
+		'view'  => true,
+	);
+	/**
+	 * Instance of the etemplate class
+	 * 
+	 * @var etemplate
+	 */
+	var $tpl;
+	/**
+	 * Labels for status-filter
+	 * 
+	 * @var array
+	 */
+	var $status_labels;
+	/**
+	 * Config-settings for projectmanager
+	 * 
+	 * @var array
+	 */
+	var $config;
+
+	/**
+	 * Constructor, calls the constructor of the extended class
+	 *
+	 * @return uiprojectelements
+	 */
+	function uiprojectelements()
+	{
+		$this->tpl =& CreateObject('etemplate.etemplate');
+
+		if ((int) $_REQUEST['pm_id'])
+		{
+			$pm_id = (int) $_REQUEST['pm_id'];
+			// store the current project (only for index, as popups may be called by other parent-projects)
+		}
+		else
+		{
+			$pm_id = $GLOBALS['phpgw']->session->appsession('pm_id','projectmanager');
+		}
+		if (!$pm_id)
+		{
+			$this->tpl->location(array(
+				'menuaction' => 'projectmanager.uiprojectmanager.index',
+				'msg'        => lang('You need to select a project first'),
+			));
+		}
+		$this->boprojectelements($pm_id);
+	
+		// check if we have at least read-access to this project
+		if (!$this->project->check_acl(PHPGW_ACL_READ))
+		{
+			$this->tpl->location(array(
+				'menuaction' => 'projectmanager.uiprojectmanager.index',
+				'msg'        => lang('Permission denied !!!'),
+			));
+		}
+		
+		$this->status_labels = array(
+			'all'     => lang('all'),
+			'used'    => lang('used'),
+			'new'     => lang('new'),
+			'ignored' => lang('ignored'),
+		);
+	}
+	
+	
+	/**
+	 * View a project-element, just calls edit with view-param set
+	 */
+	function view()
+	{
+		$this->edit(null,true);
+	}
+	
+	/**
+	 * Edit or view a project-element
+	 *
+	 * @var array $content content-array if called by process-exec
+	 * @var boolean $view only view project, default false, only used on first call !is_array($content)
+	 */
+	function edit($content=null,$view=false)
+	{
+		if ((int) $this->debug >= 1 || $this->debug == 'edit') $this->debug_message("uiprojectelements::edit(".print_r($content,true).",$view)");
+
+		if (is_array($content))
+		{
+			$this->data = $content['data'];
+			$update_necessary = $save_necessary = 0;
+			$datasource = $this->datasource($this->data['pe_app']);
+			if (($ds_read_from_element = !($ds = $datasource->read($this->data['pe_app_id'],$this->data))))
+			{
+				$ds = $datasource->element_values($this->data);
+			}
+			if (!$content['view'])
+			{
+				if ($content['pe_completion'] !== '') $content['pe_completion'] .= '%';
+
+				foreach($datasource->name2id as $name => $id)
+				{
+					//echo "checking $name=$id (overwrite={$this->data['pe_overwrite']}&$id == ".($this->data['pe_overwrite']&$id?'true':'false')."), content='{$content[$name]}'<br>\n";
+					// check if update is necessary, because a field has be set or changed
+					if ($content[$name] && ($content[$name] != $this->data[$name] || !($this->data['pe_overwrite'] & $id)))
+					{
+						//echo "need to update $name as content[$name] changed to '".$content[$name]."' != '".$this->data[$name]."'<br>\n";
+						$this->data[$name] = $content[$name];
+						$this->data['pe_overwrite'] |= $id;
+						$update_necessary |= $id;
+					}
+					// check if a field is no longer set, or it's not set and datasource changed 
+					// => set it from the datasource
+					elseif (($this->data['pe_overwrite'] & $id) && !$content[$name] ||
+						    !($this->data['pe_overwrite'] & $id) && (int)$this->data[$name] != (int)$ds[$name])
+					{
+						//echo "need to update $name as content[$name] is unset or datasource changed cont='".$content[$name]."', data='".$this->data[$name]."', ds='".$ds[$name]."'<br>\n";
+						// if we have a change in the datasource, set pe_synced
+						if ($this->data[$name] != $ds[$name])
+						{
+							$this->data['pe_synced'] = $this->now_su;
+						}
+						$this->data[$name] = $ds[$name];
+						$this->data['pe_overwrite'] &= ~$id;
+						$update_necessary |= $id;
+					}
+				}
+				$content['cat_id'] = (int) $content['cat_id'];	// as All='' and cat_id column is int
+				
+				// calculate the new summary and if a percentage give the share in hours
+				//echo "<p>project_summary[pe_total_shares]={$this->project_summary['pe_total_shares']}, old_pe_share={$content['old_pe_share']}, old_default_share=$content[old_default_share], content[pe_share]={$content['pe_share']}</p>\n";
+				$planned_time = $content['pe_planned_time'] ? $content['pe_planned_time'] : $ds['pe_planned_time'];
+				$default_share = $planned_time && $this->project->data['pm_accounting_type'] != 'status' ? $planned_time : $this->default_share;
+
+				$this->project_summary['pe_total_shares'] -= round((string) $content['old_pe_share'] !== '' ? $content['old_pe_share'] : $content['old_default_share']);
+				if (substr($content['pe_share'],-1) == '%' || $this->project->data['pm_accounting_type'] == 'status')
+				{
+					//echo "<p>project_summary[pe_total_shares]={$this->project_summary['pe_total_shares']}</p>\n";
+					if ((float) $content['pe_share'] == 100 || !$this->project_summary['pe_total_shares'])
+					{
+						$content['pe_share'] = $this->default_share;
+					}
+					else
+					{
+						$content['pe_share'] = round($this->project_summary['pe_total_shares'] * (float) $content['pe_share'] / 
+							(100 - (float) $content['pe_share']),1);
+					}
+					//echo "<p>pe_share={$content['pe_share']}</p>\n";
+				}
+				$this->project_summary['pe_total_shares'] += round((string) $content['pe_share'] !== '' ? $content['pe_share'] : $default_share);
+				//echo "<p>project_summary[pe_total_shares]={$this->project_summary['pe_total_shares']}, default_share=$default_share, content[pe_share]={$content['pe_share']}</p>\n";
+
+				foreach(array('pe_status','cat_id','pe_remark','pe_constraints','pe_share') as $name)
+				{
+					if ($name == 'pe_constraints')
+					{
+						foreach($content['pe_constraints'] as $type => $data)
+						{
+							if (!$data)
+							{
+								unset($content['pe_constraints'][$type]);	// ignore not set constraints
+							}
+							elseif (!is_array($data))
+							{
+								$content['pe_constraints'][$type] = explode(',',$data);		// otherwise it's detected as change
+							}
+						}
+					}
+					if ($content[$name] != $this->data[$name] || 
+						$name == 'pe_share' && $content[$name] !== $this->data[$name])	// for pe_share we differ between 0 and empty!
+					{
+						//echo "need to update $name as content[$name] changed to '".print_r($content[$name],true)."' != '".print_r($this->data[$name],true)."'<br>\n";
+						$this->data[$name] = $content[$name];
+						$save_necessary = true;
+
+						if ($name == 'pe_remark') $this->data['update_remark'] = true;
+					}
+				}
+			}
+			//echo "uiprojectelements::edit(): save_necessary=".(int)$save_necessary.", update_necessary=$update_necessary, data="; _debug_array($this->data);
+
+			$view = $content['view'] && !($content['edit'] && $this->check_acl(PHPGW_ACL_EDIT));
+
+			if (($content['save'] || $content['apply']) && $this->check_acl(PHPGW_ACL_EDIT))
+			{
+				if ($update_necessary || $save_necessary)
+				{
+					if ($this->save(null,true,$update_necessary) != 0)
+					{
+						$msg = lang('Error: saving the project-element (%1) !!!',$this->db->Error);
+						unset($content['save']);	// dont exit
+					}
+					else
+					{
+						$msg = lang('Project-Element saved');
+						$js = "opener.location.href='".$GLOBALS['phpgw']->link('/index.php',array(
+							'menuaction' => $content['caller'] ? $content['caller'] : 'projectmanager.uiprojectelements.index',
+							'msg'        => $msg,
+						))."';";
+					}
+				}
+				else
+				{
+					$msg = lang('no save necessary');
+				}
+			}
+			if ($content['delete'] && $this->check_acl(PHPGW_ACL_DELETE))
+			{
+				// all delete are done by index
+				$js = "opener.location.href='".$GLOBALS['phpgw']->link('/index.php',array(
+					'menuaction' => $content['caller'] ? $content['caller'] : 'projectmanager.uiprojectelements.index',
+					'delete'     => $this->data['pe_id'],
+				))."';";
+				/*
+				return $this->index(array('nm'=>array('rows'=>array(
+					'delete' => array($this->data['pe_id']=>true)
+				))));
+				*/
+			}
+			if ($content['save'] || $content['cancel'] || $content['delete'])
+			{
+				$js .= 'window.close();';
+				echo '<html><body onload="'.$js.'"></body></html>';
+				$GLOBALS['phpgw']->common->phpgw_exit();
+				/*
+				$this->tpl->location(array(
+					'menuaction' => 'projectmanager.uiprojectelements.index',
+					'msg'        => $msg,
+				));
+				*/
+			}
+		}
+		else
+		{
+			if ((int) $_GET['pe_id'])
+			{
+				$this->read((int) $_GET['pe_id']);
+			}
+			if ($this->data['pe_id'])
+			{
+				if (!$this->check_acl(PHPGW_ACL_READ))
+				{
+					$this->tpl->location(array(
+						'menuaction' => 'projectmanager.uiprojectelements.index',
+						'msg' => lang('Permission denied !!!'),
+					));
+				}
+				if (!$this->check_acl(PHPGW_ACL_EDIT)) $view = true;
+			}
+			$js = 'window.focus();';
+
+			$datasource = $this->datasource($this->data['pe_app']);
+			if (($ds_read_from_element = !($ds = $datasource->read($this->data['pe_app_id'],$this->data))))
+			{
+				$ds = $datasource->element_values($this->data);
+			}
+			else
+			{
+				$this->data['pe_title'] = $ds['pe_title'];	// updating the title, not all datasources do it automatic
+			}
+		}
+		$planned_time = $this->data['pe_planned_time'] ? $this->data['pe_planned_time'] : $ds['pe_planned_time'];
+		$default_share = $planned_time && $this->project->data['pm_accounting_type'] != 'status' ? $planned_time : $this->default_share;
+
+		if ($ds_read_from_element && !$view)
+		{
+			$msg .= lang('No READ access to the datasource: removing overwritten values will just empty them !!!');
+		}
+		$preserv = $this->data + array(
+			'view' => $view,
+			'data' => $this->data,
+			'caller' => !$content['caller'] && preg_match('/menuaction=([^&]+)/',$_SERVER['HTTP_REFERER'],$matches) ?
+				 $matches[1] : $content['caller'],
+			'old_pe_share' => $this->data['pe_share'],
+			'old_default_share' => $default_share,
+		);
+		unset($preserv['pe_resources']);	// otherwise we cant detect no more resources set
+
+		foreach($datasource->name2id as $name => $id)
+		{
+			if ($id != PM_TITLE && !($this->data['pe_overwrite'] & $id)) 	// empty not explicitly set values
+			{
+				$this->data[$name] = '';
+			}
+		}
+		$js .= "\nfunction calc_budget(form) {
+			form['exec[pe_used_budget]'].value = form['exec[pe_used_quantity]'].value.replace(/,/,'.') * form['exec[pe_unitprice]'].value.replace(/,/,'.');
+			if (form['exec[pe_used_budget]'].value == '0') form['exec[pe_used_budget]'].value = '';
+			form['exec[pe_planned_budget]'].value = form['exec[pe_planned_quantity]'].value.replace(/,/,'.') * form['exec[pe_unitprice]'].value.replace(/,/,'.');
+			if (form['exec[pe_planned_budget]'].value == '0') form['exec[pe_planned_budget]'].value = '';
+		}";
+		$tabs = 'dates|times|budget|constraints|resources|details';
+		$content = $this->data + array(
+			'ds'  => $ds,
+			'msg' => $msg,
+			'js'  => '<script>'.$js.'</script>',
+			'default_share' => $default_share,
+			'duration_format' => $this->config['duration_format'],
+			'no_times' => $this->project->data['pm_accounting_type'] == 'status',
+			$tabs => $content[$tabs],
+			'no_pricelist' => $this->project->data['pm_accounting_type'] != 'pricelist',
+			'planned_quantity_blur' => $this->data['pe_planned_time'] ? $this->data['pe_planned_time'] / 60 : $ds['pe_planned_quantity'],
+			'used_quantity_blur' => $this->data['pe_used_time'] ? $this->data['pe_used_time'] / 60 : $ds['pe_used_quantity'],
+		);
+		// calculate percentual shares
+		$content['default_total'] = $content['share_total'] = $this->project_summary['pe_total_shares'];
+		if ((string) $this->data['pe_share'] !== '')
+		{
+			if ($this->project_summary['pe_total_shares'])
+			{
+				$content['share_percentage'] = round(100.0 * $this->data['pe_share'] / $this->project_summary['pe_total_shares'],1) . '%';
+			}
+			$content['default_total'] += $default_share - $this->data['pe_share'];
+		}
+		if ($content['default_total'])
+		{
+			$content['default_percentage'] = round(100.0 * $default_share / $content['default_total'],1) . '%';
+		}
+		if ($this->project->data['pm_accounting_type'] == 'status')
+		{
+			$content['pe_share'] = $content['share_percentage'];
+		}
+		//_debug_array($content);
+		$sel_options = array(
+			'pe_constraints' => $this->titles(array(	// only titles of elements displayed in a gantchart
+				"pe_status != 'ignore'",
+//				'(pe_planned_start IS NOT NULL OR pe_real_start IS NOT NULL)',
+//				'(pe_planned_end IS NOT NULL OR pe_real_end IS NOT NULL)',
+				'pe_id != '.(int)$this->data['pe_id'],	// dont show own title	
+			)),
+			'milestone'     => $this->milestones->titles(array('pm_id' => $this->data['pm_id'])),
+		);
+		$readonlys = array(
+			'delete' => !$this->data['pe_id'] || !$this->check_acl(PHPGW_ACL_DELETE),
+			'edit' => !$view || !$this->check_acl(PHPGW_ACL_EDIT),
+		);
+		// disable the times tab, if accounting-type status
+		$readonlys[$tabs]['times'] = $this->project->data['pm_accounting_type'] == 'status';
+		// check if user has the necessary rights to view or edit the budget
+		$readonlys[$tabs]['budget'] = !$this->check_acl(PHPGW_ACL_BUDGET);
+		if (!$this->check_acl(PHPGW_ACL_EDIT_BUDGET))
+		{
+			foreach(array('pe_planned_budget','pe_used_budget','pl_id','pe_unitprice','pe_planned_quantity','pe_used_quantity') as $key)
+			{
+				$readonlys[$key] = true;
+			}
+		}
+		if ($view)
+		{
+			foreach($this->db_cols as $name)
+			{
+				$readonlys[$name] = true;
+			}
+			$readonlys['pe_remark'] = true;
+			$readonlys['save'] = $readonlys['apply'] = true;
+			$readonlys['pe_constraints[start]'] = $readonlys['pe_constraints[end]'] = $readonlys['pe_constraints[milestone]'] = true;
+		}
+		$GLOBALS['phpgw_info']['flags']['app_header'] = lang('projectmanager') . ' - ' . 
+			($this->data['pm_id'] ? ($view ? lang('View project-elements') : lang('Edit project-elements')) : lang('Add project-elements'));
+		$this->tpl->read('projectmanager.elements.edit');
+		$this->tpl->exec('projectmanager.uiprojectelements.edit',$content,$sel_options,$readonlys,$preserv,2);
+	}
+
+	/**
+	 * query projects for nextmatch in the projects-list
+	 *
+	 * reimplemented from so_sql to disable action-buttons based on the acl and make some modification on the data
+	 *
+	 * @param array &$query_in
+	 * @param array &$rows returned rows/cups
+	 * @param array &$readonlys eg. to disable buttons based on acl
+	 */
+	function get_rows(&$query_in,&$rows,&$readonlys)
+	{
+		$GLOBALS['phpgw']->session->appsession('projectelements_list','projectmanager',$query=$query_in);
+		
+		if ($this->status_filter[$query['filter']])
+		{
+			$query['col_filter']['pe_status'] = $this->status_filter[$query['filter']];
+		}
+		else
+		{
+			unset($query['col_filter']['pe_status']);
+		}
+		if ($query['cat_id'])
+		{
+			$query['col_filter']['cat_id'] = $query['cat_id'];
+			$query_in['link_add']['extra'] = array('cat_id' => $query['cat_id']);
+		}
+		else
+		{
+			unset($query['col_filter']['cat_id']);
+			unset($query_in['link_add']['extra']);
+		}
+		if (!$query['col_filter']['pe_resources'])
+		{
+			unset($query['col_filter']['pe_resources']);
+		}
+		if ($query['filter2'] & 2)	// show sub-elements (elements of sub-projects)
+		{
+			$query['col_filter']['pm_id'] = $this->project->children($this->pm_id,array($this->pm_id));
+			if (count($query['col_filter']['pm_id']) <= 1) $query['col_filter']['pm_id'] = $this->pm_id;
+			// dont show the sub-projects
+			$query['col_filter'][] = "link_app1!='projectmanager'";
+		}
+		// cumulate eg. timesheets in also included infologs
+		$query['col_filter']['cumulate'] = !($query['filter2'] & 4);
+		$total = parent::get_rows($query,$rows,$readonlys,true);
+		unset($query['col_filter']['cumulate']);
+
+		// adding the project itself always as first line
+		$self = $this->update('projectmanager',$this->pm_id);
+		$self['pe_app']    = 'projectmanager';
+		$self['pe_app_id'] = $this->pm_id;
+		$self['pe_icon']   = 'projectmanager/navbar';
+		$self['pe_modified'] = $this->project->data['pm_modified'];
+		$self['pe_modifier'] = $this->project->data['pm_modifier'];
+		$rows = array_merge(array($self),$rows);
+		
+		$readonlys = array();
+		foreach($rows as $n => $val)
+		{
+			$row =& $rows[$n];
+			if ($n && !$this->check_acl(PHPGW_ACL_EDIT,$row))
+			{
+				$readonlys["edit[$row[pe_id]]"] = true;
+			}
+			if ($n && !$this->check_acl(PHPGW_ACL_DELETE,$row))
+			{
+				$readonlys["delete[$row[pe_id]]"] = true;
+			}
+			if (!$n)
+			{
+				// no link for own project
+				if (!$this->project->check_acl(PHPGW_ACL_EDIT,$this->project->data))
+				{
+					$readonlys['edit'] = true;
+				}
+			}
+			else
+			{
+				$row['link'] = array(
+					'app'  => $row['pe_app'],
+					'id'   => $row['pe_app_id'],
+					'title'=> $row['pe_title'],
+					'help' => $row['pe_app'] == 'projectmanager' ? lang("Select this project and show it's elements") : 
+						lang('View this element in %1',lang($row['pe_app'])),
+				);
+			}
+			if (!($query['filter2']&1)) unset($row['pe_details']);
+			
+			// add project-title for elements from sub-projects
+			if (($query['filter2']&2) && $row['pm_id'] != $this->pm_id)
+			{
+				$row['pm_title'] = $this->project->link_title($row['pm_id']);
+				$row['pm_link'] = array(
+					'app'  => 'projectmanager',
+					'id'   => $row['pm_id'],
+					'title'=> $this->project->link_title($row['pm_id']),
+					'help' => lang("Select this project and show it's elements"),
+				);
+			}
+			$row['pe_completion_icon'] = $row['pe_completion'] == 100 ? 'done' : $row['pe_completion'];
+			
+			$custom_app_icons[$row['pe_app']][] = $row['pe_app_id'];
+		}
+		if ($GLOBALS['phpgw_info']['user']['preferences']['projectmanager']['show_custom_app_icons'])
+		{
+			$custom_app_icons['location'] = 'pm_custom_app_icons';
+			$custom_app_icons = $GLOBALS['phpgw']->hooks->process($custom_app_icons);
+			unset($row);	// it's used as reference before !!!
+			foreach($rows as $n => $row)
+			{
+				if (isset($custom_app_icons[$row['pe_app']][$row['pe_app_id']]))
+				{
+					$rows[$n]['pe_completion_icon'] = $custom_app_icons[$row['pe_app']][$row['pe_app_id']];
+				}
+			}
+		}
+		if (!$this->project->check_acl(PHPGW_ACL_BUDGET))
+		{
+			$rows['no_pe_used_budget_pe_planned_budget'] = true;
+		}
+		if ($this->project->data['pm_accounting_type'] == 'status')
+		{
+			$rows['no_pe_used_time_pe_planned_time'] = true;
+		}
+		// disable time & budget columns if pm is configures for status or status and time only
+		if ($this->config['accounting_types'] == 'status')
+		{
+			$rows['no_pm_used_time_pm_planned_time'] = true;
+			$rows['no_pm_used_budget_pm_planned_budget'] = true;
+			$query_in['options-selectcols']['pm_used_time'] = $query_in['options-selectcols']['pm_planned_time'] = false;
+			$query_in['options-selectcols']['pm_used_budget'] = $query_in['options-selectcols']['pm_planned_budget'] = false;
+		}
+		if ($this->config['accounting_types'] == 'status,times')
+		{
+			$rows['no_pm_used_budget_pm_planned_budget'] = true;
+			$query_in['options-selectcols']['pm_used_budget'] = $query_in['options-selectcols']['pm_planned_budget'] = false;
+		}
+		$rows['duration_format'] = ','.$this->config['duration_format'].',,1';
+		if ($query['cat_id']) $rows['no_cat_id'] = true;
+		// calculate the filter-specific summary if we have a filter, beside the default pe_status=used=array(new,regular)
+		if (array_diff(array_keys($query['col_filter']),array(0,'pe_status','pm_id')) || !is_array($query['col_filter']['pe_status']))
+		{
+			$rows += $this->summary(null,$query['col_filter']);
+		}
+		if ((int)$this->debug >= 2 || $this->debug == 'get_rows')
+		{
+			$this->debug_message("uiprojectelements::get_rows(".print_r($query,true).") total=$total, rows =".print_r($rows,true)."readonlys=".print_r($readonlys,true));
+		}
+		return $total;		
+	}
+
+	/**
+	 * List existing projects-elements
+	 *
+	 * @param array $content=null
+	 * @param string $msg=''
+	 */
+	function index($content=null,$msg='')
+	{
+		if ((int) $this->debug >= 1 || $this->debug == 'index') $this->debug_message("uiprojectelements::index(".print_r($content,true).",$msg)");
+
+		// store the current project (only for index, as popups may be called by other parent-projects)
+		$GLOBALS['phpgw']->session->appsession('pm_id','projectmanager',$this->project->data['pm_id']);
+
+		if ($_GET['msg']) $msg = $_GET['msg'];
+
+		if ($content['nm']['rows']['edit'])
+		{
+			$this->tpl->location(array(
+				'menuaction' => 'projectmanager.uiprojectmanager.edit',
+				'pm_id'      => $this->pm_id,
+			));
+		}
+		elseif ($content['sync_all'] && $this->project->check_acl(PHPGW_ACL_ADD))
+		{
+			$msg = lang('%1 element(s) updated',$this->sync_all());
+		}
+		elseif((int) $_GET['delete'] || $content['nm']['rows']['delete'])
+		{
+			if ($content['nm']['rows']['delete'])
+			{
+				list($pe_id) = each($content['nm']['rows']['delete']);
+			}
+			else
+			{
+				$pe_id = (int) $_GET['delete'];
+			}
+			if ($this->read($pe_id) && !$this->check_acl(PHPGW_ACL_DELETE))
+			{
+				$msg = lang('Permission denied !!!');
+			}
+			else
+			{
+				$msg = $this->delete($pe_id) ? lang('Project-Element deleted') : 
+					lang('Error: deleting project-element !!!');
+			}
+		}
+		$content = array(
+			'nm' => $GLOBALS['phpgw']->session->appsession('projectelements_list','projectmanager'),
+			'msg'      => $msg,
+		);
+		if (!is_array($content['nm']))
+		{
+			$content['nm'] = array(
+				'get_rows'       =>	'projectmanager.uiprojectelements.get_rows',
+				'filter'         => 'used',// I initial value for the filter
+				'filter_label'   => lang('Filter'),// I  label for filter    (optional)
+				'options-filter' => $this->status_labels,
+				'filter_no_lang' => True,// I  set no_lang for filter (=dont translate the options)
+				'options-filter2' => array(
+					0 => 'No details',
+					1 => 'Details',
+					2 => 'Subelements',
+					3 => 'Details of subelements',
+					4 => 'Cumulated elements too',
+					5 => 'Details of cumulated',
+				),
+				'col_filter' => array('pe_resources' => 0),	// default value, to suppress loop
+				'order'          =>	'pe_modified',// IO name of the column to sort after (optional for the sortheaders)
+				'sort'           =>	'DESC',// IO direction of the sort: 'ASC' or 'DESC'
+				'default_cols'   => '!cat_id',
+			);
+		}
+		// add "buttons" only with add-rights
+		if ($this->project->check_acl(PHPGW_ACL_ADD))
+		{
+			$content['nm']['header_right'] = 'projectmanager.elements.list.add';
+			$content['nm']['header_left']  = 'projectmanager.elements.list.add-new';
+		}
+		else
+		{
+			unset($content['nm']['header_right']);
+			unset($content['nm']['header_left']);
+			$readonlys['sync_all'] = true;
+		}			
+		$content['nm']['link_to'] = array(
+			'to_id'    => $this->pm_id,
+			'to_app'   => 'projectmanager',
+			'no_files' => true,
+			'search_label' => 'Add existing',
+			'link_label'   => 'Add',
+		);
+		$content['nm']['link_add'] = array(
+			'to_id'    => $this->pm_id,
+			'to_app'   => 'projectmanager',
+			'add_app'  => 'infolog',
+		);
+		// set id for automatic linking via quick add
+		$GLOBALS['phpgw_info']['flags']['currentid'] = $this->pm_id;
+
+		$GLOBALS['phpgw_info']['flags']['app_header'] = lang('projectmanager').' - '.lang('Elementlist') .
+			': ' . $this->project->data['pm_number'] . ': ' .$this->project->data['pm_title'] ;
+		$this->tpl->read('projectmanager.elements.list');
+		$this->tpl->exec('projectmanager.uiprojectelements.index',$content,'',$readonlys);
+	}
+}
Index: /contrib/ProjectManager/inc/class.pm_admin_prefs_sidebox_hooks.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.pm_admin_prefs_sidebox_hooks.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.pm_admin_prefs_sidebox_hooks.inc.php	(revision 3594)
@@ -0,0 +1,391 @@
+<?php
+/**************************************************************************\
+* eGroupWare - ProjectManager: Admin-, Preferences- and SideboxMenu-Hooks  *
+* http://www.eGroupWare.org                                                *
+* Written and (c) 2005 by Ralf Becker <RalfBecker@outdoor-training.de>     *
+* -------------------------------------------------------                  *
+*  This program is free software; you can redistribute it and/or modify it *
+*  under the terms of the GNU General Public License as published by the   *
+*  Free Software Foundation; either version 2 of the License, or (at your  *
+*  option) any later version.                                              *
+\**************************************************************************/
+
+/* $Id: class.pm_admin_prefs_sidebox_hooks.inc.php 23852 2007-05-09 16:29:32Z lkneschke $ */
+
+class pm_admin_prefs_sidebox_hooks
+{
+	var $public_functions = array(
+//		'check_set_default_prefs' => true,
+	);
+	var $weekdays = array(
+		1 => 'monday',
+		2 => 'tuesday',
+		3 => 'wednesday',
+		4 => 'thursday',
+		5 => 'friday',
+		6 => 'saturday',
+		0 => 'sunday',
+	);
+	var $config = array();
+
+	function pm_admin_prefs_sidebox_hooks()
+	{
+		$config =& CreateObject('phpgwapi.config','projectmanager');
+		$config->read_repository();
+		$this->config =& $config->config_data;
+		unset($config);
+	}
+
+	/**
+	 * hooks to build projectmanager's sidebox-menu plus the admin and preferences sections
+	 *
+	 * @param string/array $args hook args
+	 */
+	function all_hooks($args)
+	{
+		$appname = 'projectmanager';
+		$location = is_array($args) ? $args['location'] : $args;
+		//echo "<p>ranking_admin_prefs_sidebox_hooks::all_hooks(".print_r($args,True).") appname='$appname', location='$location'</p>\n";
+
+		if ($location == 'sidebox_menu')
+		{
+			// project-dropdown in sidebox menu
+			if (!is_object($GLOBALS['phpgw']->html))
+			{
+				$GLOBALS['phpgw']->html =& CreateObject('phpgwapi.html');
+			}
+			if (!is_object($GLOBALS['boprojectmanager']))
+			{
+				// dont assign it to $GLOBALS['boprojectmanager'], as the constructor does it!!!
+				CreateObject('projectmanager.uiprojectmanager');
+			}
+			if (isset($_REQUEST['pm_id']))
+			{
+				$GLOBALS['phpgw']->session->appsession('pm_id','projectmanager',$pm_id = (int) $_REQUEST['pm_id']);
+			}
+			else
+			{
+				$pm_id = (int) $GLOBALS['phpgw']->session->appsession('pm_id','projectmanager');
+			}
+			$file = array(
+				'Projectlist' => $GLOBALS['phpgw']->link('/index.php',array(
+					'menuaction' => 'projectmanager.uiprojectmanager.index' )),
+				array(
+					'text' => 'Elementlist',
+					'link' => $pm_id ? $GLOBALS['phpgw']->link('/index.php',array(
+						'menuaction' => 'projectmanager.uiprojectelements.index', 
+					)) : False,
+				),
+				array(
+					'text' => 'Ganttchart',
+					'link' => $pm_id ? $GLOBALS['phpgw']->link('/index.php',array(
+						'menuaction' => 'projectmanager.ganttchart.show',
+					)) : False,
+				),
+			);
+			// show pricelist menuitem only if we use pricelists
+			if (!$this->config['accounting_types'] || in_array('pricelist',explode(',',$this->config['accounting_types'])))
+			{
+				// menuitem links to project-spezific priclist only if user has rights and it is used
+				// to not always instanciate the priclist class, this code dublicats bopricelist::check_acl(PHPGW_ACL_READ),
+				// specialy the always existing READ right for the general pricelist!!!
+				$file['Pricelist'] = $GLOBALS['phpgw']->link('/index.php',array(
+					'menuaction' => 'projectmanager.uipricelist.index',
+					'pm_id' => $pm_id && $GLOBALS['boprojectmanager']->check_acl(PHPGW_ACL_BUDGET,$pm_id) &&
+						 $GLOBALS['boprojectmanager']->data['pm_accounting_type'] == 'pricelist' ? $pm_id : 0,
+				));
+			}
+
+			// include the filter of the projectlist into the projectlist, eg. if you watch the list of templates, include them in the tree
+			$filter = array('pm_status' => 'active');
+			$list_filter = $GLOBALS['phpgw']->session->appsession('project_list','projectmanager');
+			if ($_GET['menuaction'] == 'projectmanager.uiprojectmanager.index' && isset($_POST['exec']['nm']['filter2']))
+			{
+				//echo "<p align=right>set pm_status={$_POST['exec']['nm']['filter2']}</p>\n";
+				$list_filter['pm_status'] = $_POST['exec']['nm']['filter2'];	// necessary as uiprojectmanager::get_rows is not yet executed
+			}
+			if(in_array($list_filter['filter2'],array('nonactive','archive','template')))
+			{
+				$filter['pm_status'] = array('active',$list_filter['filter2']);
+			}
+			switch($_GET['menuaction'])
+			{
+				case 'projectmanager.ganttchart.show':
+				case 'projectmanager.uipricelist.index':
+					$selbox_action = $_GET['menuaction'];
+					break;
+				default:
+					$selbox_action = 'projectmanager.uiprojectelements.index';
+					break;
+			}
+			$select_link = $GLOBALS['phpgw']->link('/index.php',array('menuaction' => $selbox_action)).'&pm_id=';
+			
+			// show the project-selection as tree or -selectbox
+			// $_POST['user']['show_projectselection'] is used to give the user immediate feedback, if he changes the prefs
+			$type = isset($_POST['user']['show_projectselection']) ? $_POST['user']['show_projectselection'] :
+				$GLOBALS['phpgw_info']['user']['preferences']['projectmanager']['show_projectselection'];
+			if (substr($type,-5) == 'title')
+			{
+				$label = 'pm_title';
+				$title = 'pm_number';
+			}
+			else
+			{
+				$label = 'pm_number';
+				$title = 'pm_title';
+			}
+			if (substr($type,0,9) == 'selectbox')
+			{
+				$projectlist =& $this->_project_selectbox($pm_id,$filter,$select_link,$label,$title);
+			}
+			else
+			{
+				$projectlist =& $this->_project_tree($pm_id,$filter,$select_link,$label,$title);
+			}
+			if ($projectlist) $file[] =& $projectlist;
+
+			display_sidebox($appname,$GLOBALS['phpgw_info']['apps'][$appname]['title'].' '.lang('Menu'),$file);
+		}
+
+		if ($GLOBALS['phpgw_info']['user']['apps']['preferences'] && $location != 'admin')
+		{
+			$file = array(
+				'Preferences'     => $GLOBALS['phpgw']->link('/index.php','menuaction=preferences.uisettings.index&appname='.$appname),
+				'Grant Access'    => $GLOBALS['phpgw']->link('/index.php','menuaction=preferences.uiaclprefs.index&acl_app='.$appname),
+				'Edit Categories' => $GLOBALS['phpgw']->link('/index.php','menuaction=preferences.uicategories.index&cats_app=' . $appname . '&cats_level=True&global_cats=True')
+			);
+			if ($location == 'preferences')
+			{
+				display_section($appname,$file);
+			}
+			else
+			{
+				display_sidebox($appname,lang('Preferences'),$file);
+			}
+		}
+
+		if ($GLOBALS['phpgw_info']['user']['apps']['admin'] && $location != 'preferences')
+		{
+			$file = Array(
+				'Site configuration' => $GLOBALS['phpgw']->link('/index.php','menuaction=projectmanager.admin.config'),
+				'Custom fields' => $GLOBALS['phpgw']->link('/index.php','menuaction=admin.customfields.edit&appname=projectmanager'),
+				'Global Categories'  => $GLOBALS['phpgw']->link('/index.php',array(
+					'menuaction' => 'admin.uicategories.index',
+					'appname'    => $appname,
+					'global_cats'=> True)),
+			);
+			if ($location == 'admin')
+			{
+				display_section($appname,$file);
+			}
+			else
+			{
+				display_sidebox($appname,lang('Admin'),$file);
+			}
+		}
+	}
+	
+	/**
+	 * Show the project-selection as tree
+	 *
+	 * @param int $pm_id current active project
+	 * @param array $filter filter for the project-list
+	 * @param string $select_link link to load the elementslist of an appended project (pm_id)
+	 * @param string $label column to use as label
+	 * @param string $title column to use as title (tooltip)
+	 * @return array suitable for the sidebox-menu
+	 */
+	function &_project_tree($pm_id,$filter,$select_link,$label,$title)
+	{
+		$selected_project = false;
+		$projects = array();
+		foreach($GLOBALS['boprojectmanager']->get_project_tree($filter) as $project)
+		{
+			$projects[$project['path']] = array(
+				'label' => $project[$label],
+				'title' => $project[$title],
+			);
+			if (!$selected_project && $pm_id == $project['pm_id']) $selected_project = $project['path'];
+		}
+		if ($_GET['menuaction'] == 'projectmanager.uipricelist.index')
+		{
+			$projects['general'] = array(
+				'label' => lang('General pricelist'),
+				'image' => 'kfm_home.png',
+			);
+			if (!$pm_id) $selected_project = 'general';	
+		}
+		if (!$projects)	// show project-tree only if it's not empty
+		{
+			return null;
+		}
+		$tree = $GLOBALS['phpgw']->html->tree($projects,$selected_project,false,'load_project');
+		// hack for stupid ie (cant set it as a class!)
+		if ($GLOBALS['phpgw']->html->user_agent == 'msie') $tree = str_replace('id="foldertree"','id="foldertree" style="overflow: auto; width: 198px;"',$tree);
+		
+		return array(
+			'text' => "<script>function load_project(_nodeId) { location.href='$select_link'+_nodeId.substr(_nodeId.lastIndexOf('/')+1,99); }</script>\n".$tree,
+			'no_lang' => True,
+			'link' => False,
+			'icon' => False,
+		);
+	}
+	
+	/**
+	 * Show the project-selection as selectbox
+	 *
+	 * @param int $pm_id current active project
+	 * @param array $filter filter for the project-list
+	 * @param string $select_link link to load the elementslist of an appended project (pm_id)
+	 * @param string $label column to use as label
+	 * @param string $title column to use as title (tooltip)
+	 * @return array suitable for the sidebox-menu
+	 */
+	function &_project_selectbox($pm_id,$filter,$select_link,$label,$title)
+	{
+		$projects = array();
+		foreach((array)$GLOBALS['boprojectmanager']->search(array(
+			'pm_status' => 'active',
+			'pm_id'     => $pm_id,        // active or the current one
+		),$GLOBALS['boprojectmanager']->table_name.'.pm_id AS pm_id,pm_number,pm_title','pm_number','','',False,'OR') as $project)
+		{
+			$projects[$project['pm_id']] = $project[$label].($label == 'pm_number' ? ': '.$project[$title] : ' ('.$project[$title].')');
+		}
+		if ($_GET['menuaction'] == 'projectmanager.uipricelist.index')
+		{
+			$projects[0] = lang('General pricelist');
+		}
+		elseif (!$pm_id) 
+		{
+			$projects[0] = lang('Select a project');
+		}
+		return array(
+			'text' => $GLOBALS['phpgw']->html->select('pm_id',$pm_id,$projects,true,' style="width: 100%;"'.
+				' onchange="location.href=\''.$select_link.'\'+this.value;" title="'.$GLOBALS['phpgw']->html->htmlspecialchars(
+				$pm_id && isset($projects[$pm_id]) ? $projects[$pm_id] : lang('Select a project')).'"'),
+			'no_lang' => True,
+			'link' => False
+		);
+	}
+	
+	/**
+	 * populates $GLOBALS['settings'] for the preferences
+	 */
+	function settings()
+	{
+		$this->check_set_default_prefs();
+		
+		$start = array();
+		for($i = 0; $i < 24*60; $i += 30)
+		{
+			if ($GLOBALS['phpgw_info']['user']['preferences']['common']['timeformat'] == 12)
+			{
+				if (!($hour = ($i / 60) % 12)) 
+				{
+					$hour = 12;
+				}
+				$start[$i] = sprintf('%01d:%02d %s',$hour,$i % 60,$i < 12*60 ? 'am' : 'pm');
+			}
+			else
+			{
+				$start[$i] = sprintf('%01d:%02d',$i/60,$i % 60);
+			}
+		}
+		$duration = array(0 => lang('not working'));
+		for($i = 30; $i <= 24*60; $i += 30)
+		{
+			$duration[$i] = sprintf('%3.1lf',$i / 60.0).' '.lang('hours');
+		}
+		foreach($this->weekdays as $day => $label)
+		{
+			$GLOBALS['settings']['duration_'.$day] = array(
+				'type'   => 'select',
+				'label'  => lang('Working duration on %1',lang($label)),
+				'run_lang' => -1,
+				'name'   => 'duration_'.$day,
+				'values' => $duration,
+				'help'   => 'How long do you work on the given day.',
+				'xmlrpc' => True,
+				'admin'  => !$this->config['allow_change_workingtimes'],
+			);
+			$GLOBALS['settings']['start_'.$day] = array(
+				'type'   => 'select',
+				'label'  => lang('Start working on %1',lang($label)),
+				'run_lang' => -1,
+				'name'   => 'start_'.$day,
+				'values' => $start,
+				'help'   => 'At which time do you start working on the given day.',
+				'xmlrpc' => True,
+				'admin'  => !$this->config['allow_change_workingtimes'],
+			);
+		}
+		$GLOBALS['settings']['show_custom_app_icons'] = array(
+			'type'   => 'check',
+			'label'  => 'Show status icons of the datasources',
+			'name'   => 'show_custom_app_icons',
+			'help'   => 'Should Projectmanager display the status icons of the datasource (eg. InfoLog) or just a progressbar with the numerical status (faster).',
+			'xmlrpc' => True,
+			'admin'  => False,
+		);
+		$GLOBALS['settings']['show_projectselection'] = array(
+			'type'   => 'select',
+			'label'  => 'Show the project selection as',
+			'name'   => 'show_projectselection',
+			'values' => array(
+				'tree_with_number'      => lang('Tree with %1',lang('Project ID')),
+				'tree_with_title'       => lang('Tree with %1',lang('Title')),
+				'selectbox_with_number' => lang('Selectbox with %1',lang('Project ID').': '.lang('Title')),
+				'selectbox_with_title'  => lang('Selectbox with %1',lang('Title').' ('.lang('Project ID').')'),
+			),
+			'help'   => 'How should the project selection in the menu be displayed: A tree gives a better overview, a selectbox might perform better.',
+			'xmlrpc' => True,
+			'admin'  => False,
+		);
+		return true;	// otherwise prefs say it cant find the file ;-)
+	}
+	
+	/**
+	 * Check if reasonable default preferences are set and set them if not
+	 *
+	 * It sets a flag in the app-session-data to be called only once per session
+	 */
+	function check_set_default_prefs()
+	{
+		if ($GLOBALS['phpgw']->session->appsession('default_prefs_set','projectmanager'))
+		{
+			return;
+		}
+		$GLOBALS['phpgw']->session->appsession('default_prefs_set','projectmanager','set');
+
+		$default_prefs =& $GLOBALS['phpgw']->preferences->default['projectmanager'];
+
+		$defaults = array(
+			'start_1' => 9*60,
+			'duration_1' => 8*60,
+			'start_2' => 9*60,
+			'duration_2' => 8*60,
+			'start_3' => 9*60,
+			'duration_3' => 8*60,
+			'start_4' => 9*60,
+			'duration_4' => 8*60,
+			'start_5' => 9*60,
+			'duration_5' => 6*60,
+			'duration_6' => 0,
+			'duration_0' => 0,
+			'show_custom_app_icons' => '0',
+			'show_tree'  => 'pm_number',
+		);
+		foreach($defaults as $var => $default)
+		{
+			if (!isset($default_prefs[$var]) || (string)$default_prefs[$var] === '')
+			{
+				$GLOBALS['phpgw']->preferences->add('projectmanager',$var,$default,'default');
+				$need_save = True;
+			}
+		}
+		if ($need_save)
+		{
+			$GLOBALS['phpgw']->preferences->save_repository(False,'default');
+		}
+	}
+}
Index: /contrib/ProjectManager/inc/inc.DBAccess.php
===================================================================
--- /contrib/ProjectManager/inc/inc.DBAccess.php	(revision 3594)
+++ /contrib/ProjectManager/inc/inc.DBAccess.php	(revision 3594)
@@ -0,0 +1,70 @@
+<?php
+///////////////////////////////////////////////////////////////////////
+// We dont use ths db class any more, and use the egroupware db      //
+// class instead, rewritten by Shang Wenbin <wbsh@realss.com>        //
+///////////////////////////////////////////////////////////////////////
+
+//include $settings->_ADOdbPath . "adodb.inc.php";
+
+class DatabaseAccess
+{
+	var $db;
+	/**
+	 * just copy the existent class $GLOBALS['phpgw']->db 
+	 */
+	function connect()
+	{
+		copyobj($GLOBALS['phpgw']->db,$this->db);
+	}
+
+	/**
+	 * always return true
+	 */
+	function ensureConnected()
+	{
+		if(!is_object($this->db))
+		{
+			$this->connect();
+		}
+		return true;
+	}
+
+	function getResultArray($queryStr)
+	{
+		$resArr = array();
+
+		$res = $this->db->query($queryStr,__LINE__,__FILE__);
+		$resArr = $res->GetArray();
+		$res->Close();
+		return $resArr;
+	}
+
+	/**
+	 * seems only used for update query
+	 */
+	function getResult($queryStr)
+	{
+		return $this->db->query($queryStr,__LINE__,__FILE__);
+	}
+
+	/**
+	 * get the last insert id
+	 * @param string $table
+	 * @param string $column
+	 * @return int
+	 */
+	function getInsertID($table,$column)
+	{
+		return $this->db->get_last_insert_id($table,$column);
+	}
+
+}
+
+
+$db = new DatabaseAccess();
+$db->connect();
+
+$GLOBALS['mydms']->db = new DatabaseAccess();
+$GLOBALS['mydms']->db->connect();
+
+?>
Index: /contrib/ProjectManager/inc/script_numero_cuenta.txt
===================================================================
--- /contrib/ProjectManager/inc/script_numero_cuenta.txt	(revision 3594)
+++ /contrib/ProjectManager/inc/script_numero_cuenta.txt	(revision 3594)
@@ -0,0 +1,256 @@
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000113793' where cedula='7926355' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000113803' where cedula='7958174' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228120000113816' where cedula='8038480' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228140000113829' where cedula='8041825' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228140000113832' where cedula='8338780' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228160000113845' where cedula='11229295' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000113858' where cedula='11320369' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000113861' where cedula='11406249' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000113874' where cedula='11408777' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000113887' where cedula='11506160' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000113890' where cedula='81698552' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228120000113900' where cedula='84413903' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228140000113913' where cedula='2810277' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228160000113926' where cedula='3250034' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000113939' where cedula='4429681' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000113942' where cedula='4582360' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000113955' where cedula='4758883' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000113968' where cedula='4772108' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000113971' where cedula='4774380' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000113984' where cedula='4820263' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000113997' where cedula='4856104' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000114006' where cedula='4973561' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000114019' where cedula='5219374' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000114022' where cedula='5220619' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000114035' where cedula='5575040' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000114048' where cedula='5577493' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000114051' where cedula='5580603' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228170000114064' where cedula='5594003' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000114077' where cedula='6080507' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000114080' where cedula='6100028' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000114093' where cedula='6109260' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000114103' where cedula='6113898' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000114116' where cedula='6194595' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000114129' where cedula='6221812' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000114132' where cedula='6352749' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228170000114145' where cedula='6363152' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000114158' where cedula='6368859' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000114161' where cedula='6486571' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000114174' where cedula='6553111' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228120000114187' where cedula='6653611' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228120000114190' where cedula='6728453' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000114200' where cedula='6822378' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000114213' where cedula='6828344' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228170000114226' where cedula='6856891' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000114239' where cedula='6860830' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000114242' where cedula='6864804' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000114255' where cedula='6889104' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228120000114268' where cedula='19153864' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228120000114271' where cedula='12625103' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228140000114284' where cedula='12717890' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228160000114297' where cedula='12741177' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228170000114307' where cedula='12761640' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228170000114310' where cedula='12762418' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000114323' where cedula='12796289' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000114336' where cedula='12797249' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228120000114349' where cedula='12912855' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228120000114352' where cedula='12918544' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228140000114365' where cedula='9095604' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228160000114378' where cedula='9118766' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228160000114381' where cedula='9248024' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000114394' where cedula='9249088' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000114404' where cedula='9415414' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000114417' where cedula='9806139' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000114420' where cedula='9812096' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228120000114433' where cedula='9996929' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228140000114446' where cedula='10009973' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228160000114459' where cedula='10170645' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228160000114462' where cedula='10335636' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000114475' where cedula='10353016' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000114488' where cedula='10372074' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000114491' where cedula='10376371' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000114501' where cedula='10379280' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228120000114514' where cedula='10381246' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228140000114527' where cedula='10486679' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228140000114530' where cedula='10548951' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228160000114543' where cedula='10555777' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000114556' where cedula='10689565' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000114569' where cedula='10693161' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000114572' where cedula='10778297' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000114585' where cedula='10817376' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000114598' where cedula='10864180' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228140000114608' where cedula='10870497' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228140000114611' where cedula='10943800' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228160000114624' where cedula='11004462' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000114637' where cedula='11029794' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000114640' where cedula='11056838' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000114653' where cedula='11165193' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000114666' where cedula='11199355' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000114679' where cedula='11199462' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000114682' where cedula='11199743' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000114695' where cedula='22762378' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228160000114705' where cedula='14454363' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000114718' where cedula='14500755' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000114721' where cedula='14531667' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000114734' where cedula='14535337' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000114747' where cedula='14547409' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000114750' where cedula='14571516' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000114763' where cedula='14583428' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000114776' where cedula='14680173' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228170000114789' where cedula='8676785' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228170000114792' where cedula='11639225' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000114802' where cedula='11648776' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000114815' where cedula='11675761' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000114828' where cedula='11676096' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000114831' where cedula='11677178' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000114844' where cedula='11738234' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000114857' where cedula='11763573' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000114860' where cedula='11766242' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228170000114873' where cedula='11863236' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000114886' where cedula='11916859' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000114899' where cedula='11924076' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000114909' where cedula='11925077' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000114912' where cedula='11942473' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000114925' where cedula='12041659' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000114938' where cedula='12061468' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000114941' where cedula='12063137' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228170000114954' where cedula='12096201' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000114967' where cedula='12112414' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000114970' where cedula='12174357' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000114983' where cedula='12210763' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228120000114996' where cedula='12255841' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228170000115005' where cedula='12260072' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000115018' where cedula='12322520' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000115021' where cedula='12385331' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000115034' where cedula='12387210' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228120000115047' where cedula='12403482' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228120000115050' where cedula='12453731' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228140000115063' where cedula='12477299' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228160000115076' where cedula='12499231' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000115089' where cedula='12500753' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000115092' where cedula='12561886' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000115102' where cedula='12572737' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000115115' where cedula='12616742' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228120000115128' where cedula='23160926' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228120000115131' where cedula='16094107' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228140000115144' where cedula='16146308' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228160000115157' where cedula='16214357' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228160000115160' where cedula='16227457' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000115173' where cedula='16259510' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000115186' where cedula='16268653' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000115199' where cedula='16283902' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228120000115209' where cedula='8586919' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228120000115212' where cedula='12950936' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228140000115225' where cedula='12956854' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228160000115238' where cedula='13066595' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228160000115241' where cedula='13134818' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000115254' where cedula='13138186' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000115267' where cedula='13289600' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000115270' where cedula='13339053' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000115283' where cedula='13359242' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000115296' where cedula='13377787' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228140000115306' where cedula='13379323' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228160000115319' where cedula='13385178' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228160000115322' where cedula='13495364' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000115335' where cedula='13525276' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000115348' where cedula='13533812' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000115351' where cedula='13542168' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000115364' where cedula='13564680' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000115377' where cedula='13612376' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000115380' where cedula='13802811' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000115393' where cedula='13811593' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228160000115403' where cedula='13833479' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000115416' where cedula='13864369' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000115429' where cedula='13885358' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000115432' where cedula='13945770' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000115445' where cedula='13990646' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000115458' where cedula='13999261' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000115461' where cedula='14016449' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000115474' where cedula='14016906' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228170000115487' where cedula='14139089' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228170000115490' where cedula='14143729' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000115500' where cedula='14157158' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000115513' where cedula='14164527' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000115526' where cedula='14172125' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000115539' where cedula='14294169' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000115542' where cedula='14445448' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000115555' where cedula='8365220' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228170000115568' where cedula='17969651' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228170000115571' where cedula='18032528' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000115584' where cedula='18110377' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000115597' where cedula='18443853' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000115607' where cedula='18467554' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000115610' where cedula='18995083' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000115623' where cedula='8378160' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000115636' where cedula='14760144' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228170000115649' where cedula='14805334' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228170000115652' where cedula='14891676' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000115665' where cedula='14906581' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000115678' where cedula='14934077' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000115681' where cedula='14934962' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228120000115694' where cedula='14955867' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000115704' where cedula='15168232' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000115717' where cedula='15369852' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000115720' where cedula='15394182' and estatus='A' and  cod_tipo_personal='01';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228170000115733' where cedula='15420182' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000115746' where cedula='15420667' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000115759' where cedula='15487572' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000115762' where cedula='15504397' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228120000115775' where cedula='15540409' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228140000115788' where cedula='15585177' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228140000115791' where cedula='15598559' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000115801' where cedula='15612282' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228170000115814' where cedula='15646010' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000115827' where cedula='15665367' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000115830' where cedula='15711476' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000115843' where cedula='15801819' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228120000115856' where cedula='15930294' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228140000115869' where cedula='16004005' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228140000115872' where cedula='16006187' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228160000115885' where cedula='16006779' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000115898' where cedula='16021128' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000115908' where cedula='16021316' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000115911' where cedula='16023358' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000115924' where cedula='16023702' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228120000115937' where cedula='16029444' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228120000115940' where cedula='16030721' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228140000115953' where cedula='16033829' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228160000115966' where cedula='16060054' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000115979' where cedula='16085695' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000115982' where cedula='16322310' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000115995' where cedula='16393773' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228140000116004' where cedula='16436387' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228160000116017' where cedula='16472029' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228160000116020' where cedula='16472049' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000116033' where cedula='16472720' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000116046' where cedula='16554473' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000116059' where cedula='16564392' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000116062' where cedula='16618444' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000116075' where cedula='16662623' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000116088' where cedula='16673346' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000116091' where cedula='16677315' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228160000116101' where cedula='16713571' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228180000116114' where cedula='16725946' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000116127' where cedula='16815551' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000116130' where cedula='16820457' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000116143' where cedula='16820843' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000116156' where cedula='16877640' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000116169' where cedula='16879285' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000116172' where cedula='16888049' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228170000116185' where cedula='16953128' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000116198' where cedula='17075241' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000116208' where cedula='17079811' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228100000116211' where cedula='17115660' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000116224' where cedula='17119734' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000116237' where cedula='17148917' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000116240' where cedula='17158531' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000116253' where cedula='17285691' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228170000116266' where cedula='17368223' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000116279' where cedula='17384358' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228190000116282' where cedula='17400519' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000116295' where cedula='17532193' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228110000116305' where cedula='17561735' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000116318' where cedula='17625647' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228130000116321' where cedula='17642581' and estatus='A';
+update trabajador set id_banco_nomina=31, cuenta_nomina='01020228150000116334' where cedula='17967835' and estatus='A';
+
Index: /contrib/ProjectManager/inc/class.projectmanager_widget.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.projectmanager_widget.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.projectmanager_widget.inc.php	(revision 3594)
@@ -0,0 +1,135 @@
+<?php
+/**
+ * ProjectManager - eTemplate widgets
+ *
+ * @link http://www.egroupware.org
+ * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @package projectmanager
+ * @copyright (c) 2005 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @version $Id: class.projectmanager_widget.inc.php 24486 2007-10-02 13:40:49Z ralfbecker $ 
+ */
+
+/**
+ * ProjectManager: eTemplate widgets
+ *
+ * The Select Price Widget show the pricelist of the project with pm_id=$content['pm_id']!!!
+ */
+class projectmanager_widget
+{
+	/** 
+	 * @var array $public_functions exported methods of this class
+	 */
+	var $public_functions = array(
+		'pre_process' => True,
+	);
+	/**
+	 * @var array $human_name availible extensions and there names for the editor
+	 */
+	var $human_name = array(
+		'projectmanager-select'    => 'Select Project',
+		'projectmanager-pricelist' => 'Select Price',
+	);
+
+	/**
+	 * Constructor of the extension
+	 *
+	 * @param string $ui '' for html
+	 */
+	function projectmanager_widget($ui)
+	{
+		$this->ui = $ui;
+	}
+
+	/**
+	 * pre-processing of the extension
+	 *
+	 * This function is called before the extension gets rendered
+	 *
+	 * @param string $name form-name of the control
+	 * @param mixed &$value value / existing content, can be modified
+	 * @param array &$cell array with the widget, can be modified for ui-independent widgets 
+	 * @param array &$readonlys names of widgets as key, to be made readonly
+	 * @param mixed &$extension_data data the extension can store persisten between pre- and post-process
+	 * @param object &$tmpl reference to the template we belong too
+	 * @return boolean true if extra label is allowed, false otherwise
+	 */
+	function pre_process($name,&$value,&$cell,&$readonlys,&$extension_data,&$tmpl)
+	{
+		static $pricelist = array();
+		// check if user has rights to run projectmanager
+		if (!$GLOBALS['phpgw_info']['user']['apps']['projectmanager'])
+		{
+			$cell = $tmpl->empty_cell();
+			$value = '';
+			return false;
+		}
+		$extension_data['type'] = $cell['type'];
+
+		switch ($cell['type'])
+		{
+			case 'projectmanager-select':
+				if (!is_object($GLOBALS['boprojectmanager']))
+				{
+					CreateObject('projectmanager.boprojectmanager');	// assigns itselft to $GLOBALS['boprojectmanager']
+				}
+				$cell['sel_options'] = $GLOBALS['boprojectmanager']->link_query('');
+				if ($value && !isset($cell['sel_options'][$value]) && ($title = $GLOBALS['boprojectmanager']->link_title($value)))
+				{
+					$cell['sel_options'][$value] = $title;
+				}
+				if (!$cell['help']) $cell['help'] = /*lang(*/ 'Select a project' /*)*/;
+				break;
+
+			case 'projectmanager-pricelist':			// rows, pm_id-var, price-var
+				list($rows,$pm_id_var,$price_var) = explode(',',$cell['size']);
+				if (!$pm_id_var) $pm_id_var = 'pm_id';	// where are the pm_id(s) storered
+				$pm_ids = $tmpl->content[$pm_id_var];
+				$cell['sel_options'] = array();
+				foreach((array) $pm_ids as $pm_id)
+				{
+					// some caching for the pricelist, in case it's needed multiple times
+					if (!isset($pricelist[$pm_id]))
+					{
+						if (!is_object($this->pricelist))
+						{
+							require_once(PHPGW_INCLUDE_ROOT.'/projectmanager/inc/class.bopricelist.inc.php');
+							$this->pricelist =& new bopricelist();
+						}
+						$pricelist[$pm_id] = $this->pricelist->pricelist($pm_id);
+					}
+					if (!is_array($pricelist[$pm_id])) continue;
+
+					foreach($pricelist[$pm_id] as $pl_id => $label) 
+					{
+						if (!isset($cell['sel_options'][$pl_id]))
+						{
+							$cell['sel_options'][$pl_id] = $label;
+						}
+						// if pl_id already used as index, we use pl_id-price as index
+						elseif (preg_match('/\(([0-9.,]+)\)$/',$label,$matches) && 
+								!isset($cell['sel_options'][$pl_id.'-'.$matches[1]]))
+						{
+							$cell['sel_options'][$pl_id.'-'.$matches[1]] = $label;
+						}
+					}
+				}
+				// check if we have a match with pl_id & price --> use it
+				if ($price_var && ($price = $tmpl->content[$price_var]) && isset($cell['sel_options'][$value.'-'.$price]))
+				{
+					$value .= '-'.$price;
+				}
+				$cell['size'] = $rows;	// as the other options are not understood by the select-widget
+
+				if (!$cell['help']) $cell['help'] = /*lang(*/ 'Select a price' /*)*/;
+				break;
+		}
+		$cell['no_lang'] = True;
+		$cell['type'] = 'select';
+		if ($rows > 1)
+		{
+			unset($cell['sel_options']['']);
+		}
+		return True;	// extra Label Ok
+	}
+}
Index: /contrib/ProjectManager/inc/hook_admin.inc.php
===================================================================
--- /contrib/ProjectManager/inc/hook_admin.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/hook_admin.inc.php	(revision 3594)
@@ -0,0 +1,27 @@
+<?php
+	/**************************************************************************\
+	* eGroupWare                                                               *
+	* http://www.egroupware.org                                                *
+	* Written by Joseph Engo <jengo@phpgroupware.org>                          *
+	* --------------------------------------------                             *
+	*  This program is free software; you can redistribute it and/or modify it *
+	*  under the terms of the GNU General Public License as published by the   *
+	*  Free Software Foundation; either version 2 of the License, or (at your  *
+	*  option) any later version.                                              *
+	\**************************************************************************/
+
+{
+// Only Modify the $file and $title variables.....
+	$title = $appname;
+	$file = Array(
+		'Site Configuration' => $GLOBALS['phpgw']->link('/index.php','menuaction=admin.uiconfig.index&appname=' . $appname),
+		'Custom fields and sorting' => $GLOBALS['phpgw']->link('/index.php','menuaction=calendar.uicustom_fields.index'),
+		'Calendar Holiday Management' => $GLOBALS['phpgw']->link('/index.php','menuaction=calendar.uiholiday.admin'),
+		'Import CSV-File' => $GLOBALS['phpgw']->link('/calendar/csv_import.php'),
+		'Global Categories' => $GLOBALS['phpgw']->link('/index.php','menuaction=admin.uicategories.index&appname=calendar'),
+		'Grant Access by Group' => $GLOBALS['phpgw']->link('/index.php','menuaction=calendar.uigroup_access.index')
+	);
+//Do not modify below this line
+	display_section($appname,$title,$file);
+}
+?>
Index: /contrib/ProjectManager/inc/class.soprojectelements.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.soprojectelements.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.soprojectelements.inc.php	(revision 3594)
@@ -0,0 +1,233 @@
+<?php
+/**
+ * ProjectManager - Elements storage object
+ *
+ * @link http://www.egroupware.org
+ * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @package projectmanager
+ * @copyright (c) 2005/6 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @version $Id: class.soprojectelements.inc.php 25098 2008-03-17 08:49:22Z ralfbecker $ 
+ */
+
+include_once(PHPGW_INCLUDE_ROOT.'/etemplate/inc/class.so_sql.inc.php');
+
+/**
+ * Elements storage object of the projectmanager
+ *
+ * Tables: phpgw_pm_elements, phpgw_links
+ *
+ * A project P is the parent of an other project C, if link_id1=P.pm_id and link_id2=C.pm_id !
+ */
+class soprojectelements extends so_sql
+{
+	/**
+	 * Table name 'phpgw_links'
+	 * 
+	 * @var string
+	 */
+	var $links_table = 'phpgw_links';
+	/**
+	 * Join in the links table
+	 *
+	 *  @var string
+	 */
+	var $links_join = ',phpgw_links WHERE pe_id=link_id';
+	/**
+	 * Extracolumns from the links table
+	 * 
+	 * @var array
+	 */
+	var $links_extracols = array(
+		// postgres 8.3 requires cast as link_idx is varchar and pm_id an integer, the cast should be no problem for other DB's
+		"CASE WHEN link_app1='projectmanager' AND link_id1=CAST(pm_id AS CHAR) THEN link_app2 ELSE link_app1 END AS pe_app",
+		"CASE WHEN link_app1='projectmanager' AND link_id1=CAST(pm_id AS CHAR) THEN link_id2 ELSE link_id1 END AS pe_app_id",
+		'link_remark AS pe_remark',
+	);
+	/**
+	 * Default share in minutes (on the whole project), used if no planned time AND no pe_share set
+	 * 
+	 * @var int
+	 */
+	var $default_share = 240; // minutes
+	/**
+	 * Id of the project
+	 *
+	 * @var int
+	 */
+	var $pm_id;
+
+	/**
+	 * Constructor, calls the constructor of the extended class
+	 * 
+	 * It is sufficent to give just the pe_id, as it is unique!
+	 *
+	 * @param int $pm_id pm_id of the project to use, default null
+	 * @param int $pe_id pe_id of the project-element to load, default null
+	 * @return soprojectelements
+	 */
+	function soprojectelements($pm_id=null,$pe_id=null)
+	{
+		$this->so_sql('projectmanager','phpgw_pm_elements');
+
+		if ((int) $pm_id || (int) $pe_id) 
+		{
+			$this->pm_id = (int) $pm_id;
+			
+			if ((int) $pe_id)
+			{
+				if ($this->read($pe_id)) $this->pm_id = $this->data['pm_id'];
+			}
+		}
+	}
+	
+	/**
+	 * Summarize the information of all elements of a project: min(start-time), sum(time), avg(completion), ...
+	 *
+	 * @param int/array $pm_id=null int project-id, array of project-id's or null to use $this->pm_id
+	 * @param array $filter=array() columname => value pairs to filter, eg. '
+	 * @return array/boolean with summary information (keys as for a single project-element), false on error
+	 */
+	function summary($pm_id=null,$filter=array())
+	{
+		if (is_null($pm_id)) $pm_id = $this->pm_id;
+
+		if ($this->project->data['pm_id'] != $pm_id)
+		{
+			$save_data = $this->project->data;
+			$this->project->read($pm_id);
+		}
+		if ($this->project->data['pm_accounting_type'] == 'status')	// we dont have a times!
+		{
+			$share = "CASE WHEN pe_share IS NULL THEN $this->default_share ELSE pe_share END";
+		}
+		else
+		{
+			$share = "CASE WHEN pe_share IS NULL AND pe_planned_time IS NULL THEN $this->default_share WHEN pe_share IS NULL THEN pe_planned_time ELSE pe_share END";
+		}
+		if ($save_data) $this->project->data = $save_data;
+
+		if (!isset($filter['pm_id'])) $filter['pm_id'] = $pm_id;
+		if (!isset($filter['pe_status'])) $filter[] = "pe_status != 'ignore'";
+		// fix some special filters: resources, cats
+		$filter = $this->_fix_filter($filter);
+
+		$this->db->select($this->table_name,array(
+			"SUM(pe_completion * ($share)) AS pe_sum_completion_shares",
+			"SUM(CASE WHEN pe_completion IS NULL THEN NULL ELSE ($share) END) AS pe_total_shares",
+//			'AVG(pe_completion) AS pe_completion',
+			'SUM(pe_used_time) AS pe_used_time',
+			'SUM(pe_planned_time) AS pe_planned_time',
+			'SUM(pe_used_budget) AS pe_used_budget',
+			'SUM(pe_planned_budget) AS pe_planned_budget',
+			'MIN(pe_real_start) AS pe_real_start',
+			'MIN(pe_planned_start) AS pe_planned_start',
+			'MAX(pe_real_end) AS pe_real_end',
+			'MAX(pe_planned_end) AS pe_planned_end',
+		),$filter,__LINE__,__FILE__,false,'',false,0,$this->links_join);
+		
+		if (!($data = $this->db->row(true)))
+		{
+			return false;
+		}
+		if ($data['pe_total_shares'])
+		{
+			$data['pe_completion'] = round($data['pe_sum_completion_shares'] / $data['pe_total_shares'],1);
+		}
+		return $this->db2data($data);
+	}
+
+	/**
+	 * search elements, reimplemented to join in some information from the links table and fix some filters
+	 *
+	 * @param array/string $criteria array of key and data cols, OR a SQL query (content for WHERE), fully quoted (!)
+	 * @param boolean $only_keys True returns only keys, False returns all cols
+	 * @param string $order_by fieldnames + {ASC|DESC} separated by colons ','
+	 * @param string/array $extra_cols string or array of strings to be added to the SELECT, eg. "count(*) as num"
+	 * @param string $wildcard appended befor and after each criteria
+	 * @param boolean $empty False=empty criteria are ignored in query, True=empty have to be empty in row
+	 * @param string $op defaults to 'AND', can be set to 'OR' too, then criteria's are OR'ed together
+	 * @param int/boolean $start if != false, return only maxmatch rows begining with start
+	 * @param array $filter if set (!=null) col-data pairs, to be and-ed (!) into the query without wildcards
+	 * @param string/boolean $join=true default join with links-table or string as in so_sql
+	 * @return array of matching rows (the row is an array of the cols) or False
+	 */
+	function search($criteria,$only_keys=True,$order_by='',$extra_cols='',$wildcard='',$empty=False,$op='AND',$start=false,$filter=null,$join=true)
+	{
+		if ($join === true)	// add join with links-table and extra-columns
+		{
+			$join = $this->links_join;
+			
+			if (!$extra_cols)
+			{
+				$extra_cols = $this->links_extracols;
+			}
+			else
+			{
+				$extra_cols = array_merge($this->links_extracols,
+					is_array($extra_cols) ? $extra_cols : explode(',',$extra_cols));
+			}
+			$order_by = "(link_app1='projectmanager' AND link_app2='projectmanager') DESC".($order_by ? ','.$order_by : '');
+		}
+		// fix some special filters: resources, cats
+		$filter = $this->_fix_filter($filter);
+
+		return parent::search($criteria,$only_keys,$order_by,$extra_cols,$wildcard,$empty,$op,$start,$filter,$join);
+	}
+	
+	/**
+	 * Fix some special filters: resources, cats, ...
+	 *
+	 * @param array $filter
+	 * @return array
+	 */
+	function _fix_filter($filter)
+	{
+		// handle search for a single resource in comma-separated pe_resources column
+		if (isset($filter['pe_resources']))
+		{
+			if ($filter['pe_resources'])
+			{
+				$filter[] = $this->db->concat("','",'pe_resources',"','").' LIKE '.$this->db->quote('%,'.$filter['pe_resources'].',%');
+			}
+			unset($filter['pe_resources']);
+		}
+		// include sub-categories in the search
+		if ($filter['cat_id'])
+		{
+			if (!is_object($GLOBALS['phpgw']->categories))
+			{
+				$GLOBALS['phpgw']->categories =& CreateObject('phpgwapi.categories');
+			}
+			$filter['cat_id'] = $GLOBALS['phpgw']->categories->return_all_children($filter['cat_id']);
+		}
+		// remove pseudo filter
+		unset($filter['cumulate']);
+		
+		return $filter;
+	}
+
+	/**
+	 * reads one project-element specified by $keys, reimplemented to use $this->pm_id, if no pm_id given
+	 *
+	 * @param array $keys array with keys in form internalName => value, may be a scalar value if only one key
+	 * @param string/array $extra_cols string or array of strings to be added to the SELECT, eg. "count(*) as num"
+	 * @param string/boolean $join=true default join with links-table or string as in so_sql
+	 * @return array/boolean data if row could be retrived else False
+	*/
+	function read($keys,$extra_cols='',$join=true)
+	{
+		if ($this->pm_id && !isset($keys['pm_id']))
+		{
+			if (!is_array($keys) && (int) $keys) $keys = array('pe_id' => (int) $keys);
+			$keys['pm_id'] = $this->pm_id;
+		}
+		if ($join === true)	// add join with links-table and extra-columns
+		{
+			$join = $this->links_join;
+			
+			if (!$extra_cols) $extra_cols = $this->links_extracols;
+		}
+		return parent::read($keys,$extra_cols,$join);
+	}
+}
Index: /contrib/ProjectManager/inc/class.soconstraints.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.soconstraints.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.soconstraints.inc.php	(revision 3594)
@@ -0,0 +1,259 @@
+<?php
+/**
+ * ProjectManager - Constraints storage object
+ *
+ * @link http://www.egroupware.org
+ * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @package projectmanager
+ * @copyright (c) 2005 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @version $Id: class.soconstraints.inc.php 22355 2006-08-26 16:30:45Z ralfbecker $ 
+ */
+
+include_once(PHPGW_INCLUDE_ROOT.'/etemplate/inc/class.so_sql.inc.php');
+
+/**
+ * Constraints storage object of the projectmanager
+ *
+ * There are 3 constraint-types:
+ * - start:     PE has to start after an other PE (own pe_id is in pe_id_start)
+ * - end:       PE has to end before the start an other PE (own pe_id is in pe_id_end)
+ * - milestone: PE has to end before a milestone (own pe_id is in pe_id_end)
+ *
+ * Tables: phpgw_pm_constraints
+ */
+class soconstraints extends so_sql
+{
+	/**
+	 * Constructor, calls the constructor of the extended class
+	 * 
+	 * @param int $pm_id pm_id of the project to use, default null
+	 */
+	function soconstraints($pm_id=null)
+	{
+		$this->so_sql('projectmanager','phpgw_pm_constraints');
+
+		if ((int) $pm_id) 
+		{
+			$this->pm_id = (int) $pm_id;
+		}
+	}
+
+	/**
+	 * searches db for rows matching searchcriteria, reimplemented to automatic add $this->pm_id
+	 *
+	 * '*' and '?' are replaced with sql-wildcards '%' and '_'
+	 *
+	 * @param array/string $criteria array of key and data cols, OR a SQL query (content for WHERE), fully quoted (!)
+	 * @param boolean $only_keys=true True returns only keys, False returns all cols
+	 * @param string $order_by='' fieldnames + {ASC|DESC} separated by colons ',', can also contain a GROUP BY (if it contains ORDER BY)
+	 * @param string/array $extra_cols='' string or array of strings to be added to the SELECT, eg. "count(*) as num"
+	 * @param string $wildcard='' appended befor and after each criteria
+	 * @param boolean $empty=false False=empty criteria are ignored in query, True=empty have to be empty in row
+	 * @param string $op='AND' defaults to 'AND', can be set to 'OR' too, then criteria's are OR'ed together
+	 * @param mixed $start=false if != false, return only maxmatch rows begining with start, or array($start,$num)
+	 * @param array $filter=null if set (!=null) col-data pairs, to be and-ed (!) into the query without wildcards
+	 * @param string $join='' sql to do a join, added as is after the table-name, eg. ", table2 WHERE x=y" or 
+	 *	"LEFT JOIN table2 ON (x=y)", Note: there's no quoting done on $join!
+	 * @return array of matching rows (the row is an array of the cols) or False
+	 */
+	function &search($criteria,$only_keys=True,$order_by='',$extra_cols='',$wildcard='',$empty=False,$op='AND',$start=false,$filter=null,$join='')
+	{
+		if (!$this->pm_id && !isset($criteria['pm_id']) && !isset($filter['pm_id']))
+		{
+			$filter['pm_id'] = $this->pm_id;
+		}
+		if (isset($criteria['pe_id']) && (int)$criteria['pe_id'])
+		{
+			$pe_id = (int) $criteria['pe_id'];
+			unset($criteria['pe_id']);
+		}
+		if (isset($filter['pe_id']) && (int)$filter['pe_id'])
+		{
+			$pe_id = (int) $filter['pe_id'];
+			unset($filter['pe_id']);
+		}
+		if ($pe_id)
+		{
+			$filter[] = "(pe_id_end=$pe_id OR pe_id_start=$pe_id)";
+
+			if ($extra_cols && !is_array($extra_cols)) $extra_cols = explode(',',$extra_cols);
+			// defines 3 constrain-types: milestone, start and end
+			$extra_cols[] = "CASE WHEN ms_id != 0 THEN 'milestone' WHEN pe_id_start=$pe_id THEN 'start' ELSE 'end' END AS constraint_type";
+			if (!$order_by) $order_by = 'constraint_type';
+		}
+		return parent::search($criteria,$only_keys,$order_by,$extra_cols,$wildcard,$empty,$op,$start,$filter,$join);
+	}
+	
+	/**
+	 * reads all constraints of a milestone (ms_id given), an element (pe_id given) or a project (pm_id given)
+	 *
+	 * It calls allways search to retrive the data. The form of the data returned depends on the given keys!
+	 *
+	 * @param array $keys array with keys in form internalName => value, may be a scalar value if only one key
+	 * @param string/array $extra_cols string or array of strings to be added to the SELECT, eg. "count(*) as num"
+	 * @param string $join='' sql to do a join, added as is after the table-name, eg. ", table2 WHERE x=y" or 
+	 * @return array/boolean milestones: array with pe_id's, element: array with subarrays for start, end, milestone, 
+	 *	or same as search($keys) would return
+	*/
+	function read($keys,$extra_cols='',$join='')
+	{
+		if (!$search =& $this->search($criteria,$only_keys=True,$order_by='',$extra_cols='',$wildcard='',$empty=False,$op='AND',$start=false,$keys))
+		{
+			return false;
+		}
+		$ret = array();
+
+		if ((int) $keys['ms_id'])
+		{
+			foreach($search as $row)
+			{
+				$ret[] = $row['pe_id_end'];
+			}
+		}
+		elseif ((int) $keys['pe_id'])
+		{
+			foreach($search as $row)
+			{
+				switch($row['constraint_type'])
+				{
+					case 'milestone':
+						$ret['milestone'][] = $row['ms_id'];
+						break;
+					case 'start':
+						$ret['start'][] = $row['pe_id_end'];
+						break;
+					case 'end':
+						$ret['end'][] = $row['pe_id_start'];
+						break;
+				}
+			}
+		}
+		else
+		{
+			$ret =& $search;
+		}
+		if ($this->debug)
+		{
+			echo "<p>soconstraints::read(".print_r($keys,true).",'$extra_cols','$join')</p>\n";
+			_debug_array($ret);
+		}
+		return $ret;
+	}
+
+	/**
+	 * saves the given data to the db
+	 *
+	 * @param array $data with either data for one row or null, or 
+	 *	for the constraints of an elements the keys pe_id, start, end, milestone, or 
+	 *	for the constraints of a milestone the keys ms_id, pe_id (pm_id can be given or is taken from $this->pm_id)
+	 * @return int 0 on success and errno != 0 else
+	 */
+	function save($data=null)
+	{
+		if ($this->debug) { echo "<p>soconstraints::save:"; _debug_array($data); }
+
+		// constraints of an element?
+		if ($data['pe_id'])
+		{
+			$pm_id = $data['pm_id'] ? $data['pm_id'] : $this->pm_id;
+			unset($data['pm_id']);
+			$pe_id = $data['pe_id'];
+			unset($data['pe_id']);
+
+			$this->delete(array(
+				'pm_id' => $pm_id,
+				'pe_id' => $pe_id,
+			));
+			foreach($data as $type => $ids)
+			{
+				foreach(is_array($ids) ? $ids : explode(',',$ids) as $id)
+				{
+					if (!$id) continue;
+
+					switch($type)
+					{
+						case 'milestone':
+							$row = array(
+								'pe_id_end'   => $pe_id,
+								'pe_id_start' => 0,
+								'ms_id'       => $id,
+							);
+							break;
+						case 'start':
+							$row = array(
+								'pe_id_end'   => $id,
+								'pe_id_start' => $pe_id,
+								'ms_id'       => 0,
+							);
+							break;
+						case 'end':
+							$row = array(
+								'pe_id_end'   => $pe_id,
+								'pe_id_start' => $id,
+								'ms_id'       => 0,
+							);
+							break;
+					}
+					$row['pm_id'] = $pm_id;
+
+					if (($err = parent::save($row)))
+					{
+						return $err;
+					}
+				}
+			}
+			return 0;
+		}
+		// constraints of a milestone
+		if ($data['ms_id'] && is_array($data['pe_id']))
+		{
+			$keys = array(
+				'pm_id'       => $data['pm_id'] ? $data['pm_id'] : $this->pm_id,
+				'pe_id_start' => 0,
+				'ms_id'       => $data['ms_id'],
+			);
+			$this->delete($keys);
+
+			foreach($data['pe_id'] as $pe_id);
+			{
+				$keys['pe_id_end'] = $pe_id;
+				
+				if (($err = parent::save($keys)))
+				{
+					return $err;
+				}
+			}
+			return 0;
+		}
+		return parent::save($data);
+	}		
+		
+	/**
+	 * reimplented to delete all constraints from a project-element if a pe_id is given
+	 *
+	 * @param array/int $keys if given array with col => value pairs to characterise the rows to delete or pe_id
+	 * @return int affected rows, should be 1 if ok, 0 if an error
+	 */
+	function delete($keys=null)
+	{
+		if ($this->debug) echo "<p>soconstraints::delete(".print_r($keys,true).")</p>\n";
+
+		if (is_numeric($keys) || is_array($keys) && (int) $keys['pe_id'])
+		{
+			if (is_array($keys))
+			{
+				$pe_id = (int) $keys['pe_id'];
+				unset($keys['pe_id']);
+			}
+			else
+			{
+				$pe_id = (int) $keys;
+				$keys = array();
+			}
+			$keys[] = "(pe_id_end=$pe_id OR pe_id_start=$pe_id)";		
+			return $this->db->delete($this->table_name,$keys,__LINE__,__FILE__);
+		}
+		return parent::delete($keys);
+	}
+}
Index: /contrib/ProjectManager/inc/class.boinfolog.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.boinfolog.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.boinfolog.inc.php	(revision 3594)
@@ -0,0 +1,4416 @@
+<?php
+  /**************************************************************************\
+  * eGroupWare - infolog                                                    *
+  * http://www.eGroupWare.org                                                *
+  * Maintained and further developed by RalfBecker@outdoor-training.de       *
+  * Based on Webinfolog by Craig Knudsen <cknudsen@radix.net>               *
+  *          http://www.radix.net/~cknudsen                                  *
+  * Originaly modified by Mark Peters <skeeter@phpgroupware.org>             *
+  * --------------------------------------------                             *
+  *  This program is free software; you can redistribute it and/or modify it *
+  *  under the terms of the GNU General Public License as published by the   *
+  *  Free Software Foundation; either version 2 of the License, or (at your  *
+  *  option) any later version.                                              *
+  \**************************************************************************/
+
+
+	class boinfolog
+	{
+
+var $db;
+		var $public_functions = Array(
+			'read_entry'      => True,
+			'delete_entry'    => True,
+			'delete_infolog' => True,
+			'change_owner'    => True,
+			'update'          => True,
+			'check_set_default_prefs' => True,
+			'store_to_cache'  => True,
+			'export_event'    => True,
+			'send_alarm'      => True,
+			'reinstate'       => True
+		);
+
+		var $soap_functions = Array(
+			'read_entry' => Array(
+				'in' => Array(
+					'int'
+				),
+				'out' => Array(
+					'SOAPStruct'
+				)
+			),
+			'delete_entry' => Array(
+				'in' => Array(
+					'int'
+				),
+				'out' => Array(
+					'int'
+				)
+			),
+			'delete_infolog' => Array(
+				'in' => Array(
+					'int'
+				),
+				'out' => Array(
+					'int'
+				)
+			),
+			'change_owner' => Array(
+				'in' => Array(
+					'array'
+				),
+				'out' => Array(
+					'int'
+				)
+			),
+			'update' => Array(
+				'in' => Array(
+					'array',
+					'array',
+					'array',
+					'array',
+					'array'
+				),
+				'out' => Array(
+					'array'
+				)
+			),
+			'store_to_cache'	=> Array(
+				'in' => Array(
+					'struct'
+				),
+				'out' => Array(
+					'SOAPStruct'
+				)
+			),
+			'store_to_cache'	=> Array(
+				'in' => Array(
+					'array'
+				),
+				'out' => Array(
+					'string'
+				)
+			),
+			'categories' => array(
+				'in'  => array('bool'),
+				'out' => array('array')
+			),
+		);
+
+		var $debug = False;
+//		var $debug = True;
+
+		var $so;
+		var $so1;
+		var $ex_participants;
+		var $cached_events;
+		var $repeating_events;
+		var $day;
+		var $month;
+		var $year;
+		var $prefs;
+
+		var $owner;
+		var $holiday_color;
+		var $printer_friendly = False;
+
+		var $cached_holidays;
+
+		var $g_owner = 0;
+
+		var $filter;
+		var $cat_id;
+		var $users_timeformat;
+
+		var $modified;
+		var $deleted;
+		var $added;
+
+		var $is_group = False;
+
+		var $soap = False;
+
+		var $use_session = False;
+
+		var $today;
+		var $debug_string;
+
+		var $sortby;
+		var $num_months;
+		var $xmlrpc = False;	// not called via xmlrpc
+       	var $async = false;
+
+		function boinfolog($session=0)
+		{
+
+			if(!is_object($GLOBALS['phpgw']->datetime))
+			{
+				$GLOBALS['phpgw']->datetime = createobject('phpgwapi.date_time');
+			}
+
+			$this->cat = CreateObject('phpgwapi.categories');
+
+$this->resources = CreateObject('phpgwapi.resources');
+
+$this->priority = CreateObject('phpgwapi.priority');
+
+			$this->grants = $GLOBALS['phpgw']->acl->get_grants('infolog');
+			@reset($this->grants);
+			if(DEBUG_APP)
+			{
+				if(floor(phpversion()) >= 4)
+				{
+					$this->debug_string = '';
+					ob_start();
+				}
+
+				foreach($this->grants as $grantor => $rights)
+				{
+					print_debug('Grantor',$grantor);
+					print_debug('Rights',$rights);
+				}
+			}
+
+			print_debug('Read use_session',$session);
+
+			if($session)
+			{
+				$this->read_sessiondata();
+				$this->use_session = True;
+			}
+			print_debug('BO Filter',$this->filter);
+			print_debug('Owner',$this->owner);
+
+			if ($GLOBALS['argv']) {
+			  $this->async = true;
+			  $this->load_lang();
+			}
+
+			$this->prefs['infolog']    = $GLOBALS['phpgw_info']['user']['preferences']['infolog'];
+			$this->check_set_default_prefs();
+
+			$owner = get_var('owner',array('GET','POST'),$GLOBALS['owner']);
+
+			ereg('menuaction=([a-zA-Z.]+)',$_SERVER['HTTP_REFERER'],$regs);
+			$from = $regs[1];
+			if ((substr($_SERVER['PHP_SELF'],-8) == 'home.php' && substr($this->prefs['infolog']['defaultinfolog'],0,7) == 'planner'
+				|| $_GET['menuaction'] == 'infolog.uiinfolog.planner' &&
+				$from  != 'infolog.uiinfolog.planner' && !$this->save_owner)
+				&& (int)$this->prefs['infolog']['planner_start_with_group'] > 0)
+			{
+				// entering planner for the first time ==> saving owner in save_owner, setting owner to default
+				//
+				$this->save_owner = $this->owner;
+				$owner = 'g_'.$this->prefs['infolog']['planner_start_with_group'];
+			}
+			elseif ($_GET['menuaction'] != 'infolog.uiinfolog.planner' &&
+				$this->save_owner)
+			{
+				// leaving planner with an unchanged user/owner ==> setting owner back to save_owner
+				//
+				$owner = (int)(isset($_GET['owner']) ? $_GET['owner'] : $this->save_owner);
+				unset($this->save_owner);
+			}
+			elseif (!empty($owner) && $owner != $this->owner && $from == 'infolog.uiinfolog.planner')
+			{
+				// user/owner changed within planner ==> forgetting save_owner
+				//
+				unset($this->save_owner);
+			}
+
+			if(isset($owner) && $owner!='' && substr($owner,0,2) == 'g_')
+			{
+				$this->set_owner_to_group(substr($owner,2));
+			}
+			elseif(isset($owner) && $owner!='')
+			{
+				$this->owner = (int)$owner;
+			}
+			elseif(!@isset($this->owner) || !@$this->owner)
+			{
+				$this->owner = (int)$GLOBALS['phpgw_info']['user']['account_id'];
+			}
+			elseif(isset($this->owner) && $GLOBALS['phpgw']->accounts->get_type($this->owner) == 'g')
+			{
+				$this->set_owner_to_group((int)$this->owner);
+			}
+
+			$this->prefs['common']    = $GLOBALS['phpgw_info']['user']['preferences']['common'];
+
+			if ($this->prefs['common']['timeformat'] == '12')
+			{
+				$this->users_timeformat = 'h:ia';
+			}
+			else
+			{
+				$this->users_timeformat = 'H:i';
+			}
+			$this->holiday_color = (substr($GLOBALS['phpgw_info']['theme']['bg07'],0,1)=='#'?'':'#').$GLOBALS['phpgw_info']['theme']['bg07'];
+
+			$friendly = (isset($_GET['friendly'])?$_GET['friendly']:'');
+			$friendly = ($friendly=='' && isset($_POST['friendly'])?$_POST['friendly']:$friendly);
+
+			$this->printer_friendly = ((int)$friendly == 1?True:False);
+
+			if(isset($_POST['filter'])) { $this->filter = $_POST['filter']; }
+			if(isset($_POST['sortby'])) { $this->sortby = $_POST['sortby']; }
+			if(isset($_POST['cat_id'])) { $this->cat_id = $_POST['cat_id']; }
+
+			if(!isset($this->filter))
+			{
+				$this->filter = ' '.$this->prefs['infolog']['defaultfilter'].' ';
+			}
+
+			if(!isset($this->sortby))
+			{
+				$this->sortby = $this->prefs['infolog']['defaultinfolog'] == 'planner_user' ? 'user' : 'category';
+			}
+
+			if($GLOBALS['phpgw']->accounts->get_type($this->owner)=='g')
+			{
+				$this->filter = ' all ';
+			}
+
+			$this->so = CreateObject('infolog.soinfolog',
+				Array(
+					'owner'		=> $this->owner,
+					'filter'	=> $this->filter,
+					'category'	=> $this->cat_id,
+					'g_owner'	=> $this->g_owner
+				)
+			);
+			$this->rpt_day = array(	// need to be after creation of soinfolog
+				MCAL_M_SUNDAY    => 'Sunday',
+				MCAL_M_MONDAY    => 'Monday',
+				MCAL_M_TUESDAY   => 'Tuesday',
+				MCAL_M_WEDNESDAY => 'Wednesday',
+				MCAL_M_THURSDAY  => 'Thursday',
+				MCAL_M_FRIDAY    => 'Friday',
+				MCAL_M_SATURDAY  => 'Saturday'
+			);
+			if($this->bo->prefs['infolog']['weekdaystarts'] != 'Sunday')
+			{
+				$mcals = array_keys($this->rpt_day);
+				$days  = array_values($this->rpt_day);
+				$this->rpt_day = array();
+				list($n) = $found = array_keys($days,$this->prefs['infolog']['weekdaystarts']);
+				for ($i = 0; $i < 7; ++$i,++$n)
+				{
+					$this->rpt_day[$mcals[$n % 7]] = $days[$n % 7];
+				}
+			}
+			$this->rpt_type = Array(
+				MCAL_RECUR_NONE		=> 'None',
+				MCAL_RECUR_DAILY	=> 'Daily',
+				MCAL_RECUR_WEEKLY	=> 'Weekly',
+				MCAL_RECUR_MONTHLY_WDAY	=> 'Monthly (by day)',
+				MCAL_RECUR_MONTHLY_MDAY	=> 'Monthly (by date)',
+				MCAL_RECUR_YEARLY	=> 'Yearly'
+			);
+
+			$localtime = $GLOBALS['phpgw']->datetime->users_localtime;
+
+			$date = (isset($GLOBALS['date'])?$GLOBALS['date']:'');
+			$date = (isset($_GET['date'])?$_GET['date']:$date);
+			$date = ($date=='' && isset($_POST['date'])?$_POST['date']:$date);
+
+			$year = (isset($_GET['year'])?$_GET['year']:'');
+			$year = ($year=='' && isset($_POST['year'])?$_POST['year']:$year);
+
+			$month = (isset($_GET['month'])?$_GET['month']:'');
+			$month = ($month=='' && isset($_POST['month'])?$_POST['month']:$month);
+
+			$day = (isset($_GET['day'])?$_GET['day']:'');
+			$day = ($day=='' && isset($_POST['day'])?$_POST['day']:'');
+
+			$num_months = (isset($_GET['num_months'])?$_GET['num_months']:'');
+			$num_months = ($num_months=='' && isset($_POST['num_months'])?$_POST['num_months']:$num_months);
+
+			if(isset($date) && $date!='')
+			{
+				$this->year  = (int)(substr($date,0,4));
+				$this->month = (int)(substr($date,4,2));
+				$this->day   = (int)(substr($date,6,2));
+			}
+			else
+			{
+				if(isset($year) && $year!='')
+				{
+					$this->year = $year;
+				}
+				else
+				{
+					$this->year = date('Y',$localtime);
+				}
+				if(isset($month) && $month!='')
+				{
+					$this->month = $month;
+				}
+				else
+				{
+					$this->month = date('m',$localtime);
+				}
+				if(isset($day) && $day!='')
+				{
+					$this->day = $day;
+				}
+				else
+				{
+					$this->day = date('d',$localtime);
+				}
+			}
+
+			if(isset($num_months) && $num_months!='')
+			{
+				$this->num_months = $num_months;
+			}
+			elseif($this->num_months == 0)
+			{
+				$this->num_months = 1;
+			}
+
+			$this->today = date('Ymd',$GLOBALS['phpgw']->datetime->users_localtime);
+
+			if(DEBUG_APP)
+			{
+				print_debug('BO Filter','('.$this->filter.')');
+				print_debug('Owner',$this->owner);
+				print_debug('Today',$this->today);
+				if(floor(phpversion()) >= 4)
+				{
+					$this->debug_string .= ob_get_contents();
+					ob_end_clean();
+				}
+			}
+
+$evento=$this->restore_from_appsession();
+			$this->xmlrpc = is_object($GLOBALS['server']) && $GLOBALS['server']->last_method;
+		}
+
+	  function load_lang() {
+	    if(!$_SESSION['phpgw_info']['infolog']['langAlarm']) 
+	      {
+		$array_keys = array();
+		$fn = '../../infolog/setup/phpgw_alarm_'.$GLOBALS['phpgw_info']['user']['preferences']['common']['lang'].'.lang';			
+		echo $fn;
+		if (file_exists($fn)){
+		  $fp = fopen($fn,'r');
+		  while ($data = fgets($fp,16000))	{
+		    list($message_id,$app_name,$null,$content) = explode("\t",substr($data,0,-1));			
+		    $_SESSION['phpgw_info']['infolog']['langAlarm'][$message_id] =  $content;
+		  }
+		  fclose($fp);
+		}
+	      }
+	  }
+		function list_methods($_type='xmlrpc')
+		{
+			/*
+			  This handles introspection or discovery by the logged in client,
+			  in which case the input might be an array.  The server always calls
+			  this function to fill the server dispatch map using a string.
+			*/
+			if (is_array($_type))
+			{
+				$_type = $_type['type'];
+			}
+			switch($_type)
+			{
+				case 'xmlrpc':
+					$xml_functions = array(
+						'list_methods' => array(
+							'function'  => 'list_methods',
+							'signature' => array(array(xmlrpcStruct,xmlrpcString)),
+							'docstring' => lang('Read this list of methods.')
+						),
+						'read' => array(
+							'function'  => 'read_entry',
+							'signature' => array(array(xmlrpcStruct,xmlrpcInt)),
+							'docstring' => lang('Read a single entry by passing the id and fieldlist.')
+						),
+						'read_entry' => array(	// deprecated, use read
+							'function'  => 'read_entry',
+							'signature' => array(array(xmlrpcStruct,xmlrpcInt)),
+							'docstring' => lang('Read a single entry by passing the id and fieldlist.')
+						),
+						'write' => array(
+							'function'  => 'update',
+							'signature' => array(array(xmlrpcStruct,xmlrpcStruct)),
+							'docstring' => lang('Add or update a single entry by passing the fields.')
+						),
+						'add_entry' => array(	// deprecated, use write
+							'function'  => 'update',
+							'signature' => array(array(xmlrpcStruct,xmlrpcStruct)),
+							'docstring' => lang('Add a single entry by passing the fields.')
+						),
+						'update_entry' => array(	// deprecated, use write
+							'function'  => 'update',
+							'signature' => array(array(xmlrpcStruct,xmlrpcStruct)),
+							'docstring' => lang('Update a single entry by passing the fields.')
+						),
+						'delete' => array(
+							'function'  => 'delete_entry',
+							'signature' => array(array(xmlrpcInt,xmlrpcInt)),
+							'docstring' => lang('Delete a single entry by passing the id.')
+						),
+						'delete_entry' => array(	// deprecated, use delete
+							'function'  => 'delete_entry',
+							'signature' => array(array(xmlrpcInt,xmlrpcInt)),
+							'docstring' => lang('Delete a single entry by passing the id.')
+						),
+						'delete_infolog' => array(
+							'function'  => 'delete_infolog',
+							'signature' => array(array(xmlrpcInt,xmlrpcInt)),
+							'docstring' => lang('Delete an entire users infolog.')
+						),
+						'change_owner' => array(
+							'function'  => 'change_owner',
+							'signature' => array(array(xmlrpcInt,xmlrpcStruct)),
+							'docstring' => lang('Change all events for $params[\'old_owner\'] to $params[\'new_owner\'].')
+						),
+						'search' => array(
+							'function'  => 'store_to_cache',
+							'signature' => array(array(xmlrpcStruct,xmlrpcStruct)),
+							'docstring' => lang('Read a list of entries.')
+						),
+						'store_to_cache' => array(	// deprecated, use search
+							'function'  => 'store_to_cache',
+							'signature' => array(array(xmlrpcStruct,xmlrpcStruct)),
+							'docstring' => lang('Read a list of entries.')
+						),
+						'export_event' => array(
+							'function'  => 'export_event',
+							'signature' => array(array(xmlrpcString,xmlrpcStruct)),
+							'docstring' => lang('Export a list of entries in iCal format.')
+						),
+						'categories' => array(
+							'function'  => 'categories',
+							'signature' => array(array(xmlrpcStruct,xmlrpcStruct)),
+							'docstring' => lang('List all categories.')
+						),
+					);
+					return $xml_functions;
+					break;
+				case 'soap':
+					return $this->soap_functions;
+					break;
+				default:
+					return array();
+					break;
+			}
+		}
+
+		function set_owner_to_group($owner)
+		{
+			print_debug('infolog::boinfolog::set_owner_to_group:owner',$owner);
+			$this->owner = (int)$owner;
+			$this->is_group = True;
+			$this->g_owner = Array();
+			$members = $GLOBALS['phpgw']->accounts->member($owner);
+			if (is_array($members))
+			{
+				foreach($members as $user)
+				{
+					// use only members which gave the user a read-grant
+					if ($this->check_perms(PHPGW_ACL_READ,0,$user['account_id']))
+					{
+						$this->g_owner[] = $user['account_id'];
+					}
+				}
+			}
+			//echo "<p>".function_backtrace().": set_owner_to_group($owner) = ".print_r($this->g_owner,True)."</p>\n";
+		}
+
+		function member_of_group($owner=0)
+		{
+			$owner = ($owner==0?$GLOBALS['phpgw_info']['user']['account_id']:$owner);
+			$group_owners = $GLOBALS['phpgw']->accounts->membership();
+			while($group_owners && list($index,$group_info) = each($group_owners))
+			{
+				if($this->owner == $group_info['account_id'])
+				{
+					return True;
+				}
+			}
+			return False;
+		}
+
+		function save_sessiondata($data='')
+		{
+			if ($this->use_session)
+			{
+				if (!is_array($data))
+				{
+					$data = array(
+						'filter'     => $this->filter,
+						'cat_id'     => $this->cat_id,
+						'owner'      => $this->owner,
+						'save_owner' => $this->save_owner,
+						'year'       => $this->year,
+						'month'      => $this->month,
+						'day'        => $this->day,
+						'date'       => $this->date,
+						'sortby'     => $this->sortby,
+						'num_months' => $this->num_months,
+						'return_to'  => $this->return_to
+					);
+				}
+				if($this->debug)
+				{
+					if(floor(phpversion()) >= 4)
+					{
+						ob_start();
+					}
+					echo '<!-- '."\n".'Save:'."\n"._debug_array($data,False)."\n".' -->'."\n";
+					if(floor(phpversion()) >= 4)
+					{
+						$this->debug_string .= ob_get_contents();
+						ob_end_clean();
+					}
+				}
+				$GLOBALS['phpgw']->session->appsession('session_data','infolog',$data);
+			}
+		}
+
+		function read_sessiondata()
+		{
+			$data = $GLOBALS['phpgw']->session->appsession('session_data','infolog');
+			print_debug('Read',_debug_array($data,False));
+
+			$this->filter = $data['filter'];
+			$this->cat_id = $data['cat_id'];
+			$this->sortby = $data['sortby'];
+			$this->owner  = (int)$data['owner'];
+			$this->save_owner = (int)$data['save_owner'];
+			$this->year   = (int)$data['year'];
+			$this->month  = (int)$data['month'];
+			$this->day    = (int)$data['day'];
+			$this->num_months = (int)$data['num_months'];
+			$this->return_to = $data['return_to'];
+		}
+
+		function read_entry()
+		{
+
+			$event = $this->so->read_entry($id);
+				echo $event;
+			
+			
+			return False;
+		}
+
+		function delete_single($param)
+		{
+			if($this->check_perms(PHPGW_ACL_DELETE,(int)$param['id']))
+			{
+				$temp_event = $this->get_cached_event();
+				$event = $this->read_entry((int)$param['id']);
+//				if($this->owner == $event['owner'])
+//				{
+				$exception_time = mktime($event['start']['hour'],$event['start']['min'],0,$param['month'],$param['day'],$param['year']) - $GLOBALS['phpgw']->datetime->tz_offset;
+				$event['recur_exception'][] = (int)$exception_time;
+				$this->so->cal->event = $event;
+//				print_debug('exception time',$event['recur_exception'][count($event['recur_exception']) -1]);
+//				print_debug('count event exceptions',count($event['recur_exception']));
+				$this->so->add_entry($event);
+				$cd = 16;
+
+				$this->so->cal->event = $temp_event;
+				unset($temp_event);
+			}
+			else
+			{
+				$cd = 60;
+			}
+//			}
+			return $cd;
+		}
+
+		function delete_entry($id)
+		{
+			if (is_array($id) && count($id) == 1)
+			{
+				list(,$id) = each($id);
+			}
+			if($this->check_perms(PHPGW_ACL_DELETE,$id))
+			{
+				$this->so->delete_entry($id);
+
+				if ($this->xmlrpc)
+				{
+					$this->so->expunge($id);
+				}
+				return $this->xmlrpc ? True : 16;
+			}
+			if ($this->xmlrpc)
+			{
+				$GLOBALS['server']->xmlrpc_error($GLOBALS['xmlrpcerr']['no_access'],$GLOBALS['xmlrpcstr']['no_access']);
+			}
+			return 60;
+		}
+
+		function reinstate($params='')
+		{
+			if($this->check_perms(PHPGW_ACL_EDIT,$params['cal_id']) && isset($params['reinstate_index']))
+			{
+				$event = $this->so->read_entry($params['cal_id']);
+				@reset($params['reinstate_index']);
+				print_debug('Count of reinstate_index',count($params['reinstate_index']));
+				if(count($params['reinstate_index']) > 1)
+				{
+					while(list($key,$value) = each($params['reinstate_index']))
+					{
+						print_debug('reinstate_index ['.$key.']',(int)$value);
+						print_debug('exception time',$event['recur_exception'][(int)$value]);
+						unset($event['recur_exception'][(int)$value]);
+						print_debug('count event exceptions',count($event['recur_exception']));
+					}
+				}
+				else
+				{
+					print_debug('reinstate_index[0]',(int)$params['reinstate_index'][0]);
+					print_debug('exception time',$event['recur_exception'][(int)$params['reinstate_index'][0]]);
+					unset($event['recur_exception'][(int)$params['reinstate_index'][0]]);
+					print_debug('count event exceptions',count($event['recur_exception']));
+				}
+				$this->so->cal->event = $event;
+				$this->so->add_entry($event);
+				return 42;
+			}
+			else
+			{
+				return 43;
+			}
+		}
+
+		function delete_infolog($owner)
+		{
+			if($GLOBALS['phpgw_info']['user']['apps']['admin'])
+			{
+				$this->so->delete_infolog($owner);
+			}
+		}
+
+		function change_owner($params='')
+		{
+			if($GLOBALS['phpgw_info']['server']['infolog_type'] == 'sql')
+			{
+				if(is_array($params))
+				{
+					$this->so->change_owner($params['old_owner'],$params['new_owner']);
+				}
+			}
+		}
+
+		function expunge()
+		{
+			reset($this->so->cal->deleted_events);
+			while(list($i,$event_id) = each($this->so->cal->deleted_events))
+			{
+				$event = $this->so->read_entry($event_id);
+				if(!$this->ex_participants)
+					$this->ex_participants = html_entity_decode($event['ex_participants']);			
+				if($this->check_perms(PHPGW_ACL_DELETE,$event))
+				{
+					$this->send_update(MSG_DELETED,$event['participants'],$event);
+				}
+				else
+				{
+					unset($this->so->cal->deleted_events[$i]);
+				}
+			}
+			$this->so->expunge();
+		}
+
+		function search_keywords($keywords)
+		{
+			$type = $GLOBALS['phpgw']->accounts->get_type($this->owner);
+
+			if($type == 'g')
+			{
+				$members = $GLOBALS['phpgw']->acl->get_ids_for_location($this->owner, 1, 'phpgw_group');
+			}
+			else
+			{
+				$members = array_keys($this->grants);
+
+				if (!in_array($this->owner,$members))
+				{
+					$members[] = $this->owner;
+				}
+			}
+			foreach($members as $n => $uid)
+			{
+				if (!($this->grants[$uid] & PHPGW_ACL_READ))
+				{
+					unset($members[$n]);
+				}
+			}
+
+
+			return $this->so->list_events_keyword($keywords,$members);
+		}
+
+		function update($params='')
+		{
+
+			if($params['from_mobile']) {
+				$ui_return = "mobile.ui_mobileinfolog.edit";
+				$ui_index = "mobile.ui_mobileinfolog.index";
+				$ui_overlap = "mobile.ui_mobileinfolog.overlap";
+			}
+			else {
+				$ui_return = "infolog.uiinfolog.edit";
+				$ui_index = "infolog.uiinfolog.index";		
+				$ui_overlap = "infolog.uiinfolog.overlap";
+			}
+
+			if(!is_object($GLOBALS['phpgw']->datetime))
+			{
+				$GLOBALS['phpgw']->datetime = createobject('phpgwapi.date_time');
+			}
+			$l_cal = (@isset($params['info_type']) && $params['info_type']?$params['info_type']:$_POST['info_type']);
+			$l_participants = (@$params['participants']?$params['participants']:$_POST['participants']);
+			$this->ex_participants = (@$params['ex_participants']?$params['ex_participants']:$_POST['ex_participants']);
+			$l_categories = (@$params['categories']?$params['categories']:$_POST['categories']);
+			$l_start = (@isset($params['start']) && $params['start']?$params['start']:$_POST['start']);
+			$l_end = (@isset($params['end']) && $params['end']?$params['end']:$_POST['end']);
+			$l_recur_enddate = (@isset($params['recur_enddate']) && $params['recur_enddate']?$params['recur_enddate']:$_POST['recur_enddate']);
+
+			
+			$send_to_ui = True;
+			//if ((!is_array($l_start) || !is_array($l_end)) && !isset($_GET['readsess']))	// xmlrpc call
+			if ($this->xmlrpc)	// xmlrpc call
+			{
+
+				$send_to_ui = False;
+
+				$l_cal = $params;	// no extra array
+
+				foreach(array('start','end','recur_enddate') as $name)
+				{
+					$var = 'l_'.$name;
+					$$var = $GLOBALS['server']->iso86012date($params[$name]);
+					unset($l_cal[$name]);
+				}
+
+				if (!is_array($l_participants) || !count($l_participants))
+				{
+					$l_participants = array($GLOBALS['phpgw_info']['user']['account_id'].'A');
+				}
+				else
+				{
+					$l_participants = array();
+					foreach($params['participants'] as $user => $data)
+					{
+						$l_participants[] = $user.$data['status'];
+					}
+				}
+
+				unset($l_cal['participants']);
+
+				if (!is_object($GLOBALS['phpgw']->categories))
+				{
+					$GLOBALS['phpgw']->categories = CreateObject('phpgwapi.categories');
+				}
+
+				$l_categories = $GLOBALS['server']->xmlrpc2cats($params['category']);
+				unset($l_cal['category']);
+
+				// using access={public|private} in all modules via xmlrpc
+				$l_cal['public'] = $params['access'] != 'private';
+				unset($l_cal['access']);
+/*
+				$fp = fopen('/tmp/xmlrpc.log','a+');
+				ob_start();
+				echo "\nboinfolog::update("; print_r($params); echo ")\n";
+				//echo "\nl_start="; print_r($l_start);
+				//echo "\nl_end="; print_r($l_end);
+				fwrite($fp,ob_get_contents());
+				ob_end_clean();
+				fclose($fp);
+*/
+			}
+
+			print_debug('ID',$l_cal['id']);
+
+			// don't wrap to the next day for no time
+			if ($l_end['hour'] == 24 && $l_end['min'] == 0)
+			{
+				$l_end['hour'] = 23;
+				$l_end['min'] = 59;
+			}
+
+			if(isset($_GET['readsess']))
+			{
+
+				$event = $this->restore_from_appsession();
+				$event['title'] = stripslashes($event['title']);
+				$event['description'] = stripslashes($event['description']);
+$event['description1'] = stripslashes($event['description1']);
+$event['participants1'] = stripslashes($event['participants1']);
+				$event['ex_participants'] = stripslashes($event['ex_participants']);
+				$datetime_check = $this->validate_update($event);
+				if($datetime_check)
+				{
+					ExecMethod($ui_return,
+						Array(
+							'cd'		=> $datetime_check,
+							'readsess'	=> 1
+						)
+					);
+					if(!$params['from_mobile'])
+						$GLOBALS['phpgw']->common->phpgw_exit(True);
+					else
+						return;
+				}
+				$overlapping_events = False;
+			}
+			else
+			{
+
+				if((!$l_cal['id'] && !$this->check_perms(PHPGW_ACL_ADD)) ||
+				   ($l_cal['id'] && !$this->check_perms(PHPGW_ACL_EDIT,$l_cal['id'])))
+				{
+
+					if ($this->xmlrpc)
+					{
+						$GLOBALS['server']->xmlrpc_error($GLOBALS['xmlrpcerr']['no_access'],$GLOBALS['xmlrpcstr']['no_access']);
+					}
+					if (!$send_to_ui)
+					{
+						return array(($l_cal['id']?1:2) => 'permission denied');
+					}
+
+					ExecMethod($ui_index);
+
+
+					if(!$params['from_mobile'])
+						$GLOBALS['phpgw']->common->phpgw_exit();
+					else
+						return;
+				}
+
+				print_debug('Prior to fix_update_time()');
+
+				$this->fix_update_time($l_start);
+
+				$this->fix_update_time($l_end);
+
+				if(!isset($l_cal['ex_participants']))
+				{
+					$l_cal['ex_participants'] = $this->ex_participants;
+				}
+
+				if(!isset($l_categories))
+				{
+					$l_categories = 0;
+				}
+
+				$is_public = ($l_cal['type'] != 'private' && $l_cal['type'] != 'privateHiddenFields');
+
+				$this->so->event_init();
+
+			$this->add_attribute('uid',$l_cal['uid']);
+
+				$this->add_attribute('type',$l_cal['type']);
+
+				if($l_cal['ex_participants']) {
+					$this->add_attribute('ex_participants',$l_cal['ex_participants']);
+				}
+				if(count($l_categories) >= 2)
+				{
+					$this->so->set_category(implode(',',$l_categories));
+				}
+				else
+				{
+					$this->so->set_category(strval($l_categories[0]));
+				}
+				$this->so->set_title($l_cal['title']);
+				$this->so->set_description($l_cal['description']);
+$this->so->set_description1($l_cal['description1']);
+$this->so->set_participants1($l_cal['participants1']);
+				$this->so->set_ex_participants($l_cal['ex_participants']);
+				$this->so->set_start($l_start['year'],$l_start['month'],$l_start['mday'],$l_start['hour'],$l_start['min'],0);
+				$this->so->set_end($l_end['year'],$l_end['month'],$l_end['mday'],$l_end['hour'],$l_end['min'],0);
+				$this->so->set_class($is_public);
+				$this->so->add_attribute('reference',(@isset($l_cal['reference']) && $l_cal['reference']?$l_cal['reference']:0));
+				$this->so->add_attribute('location',(@isset($l_cal['location']) && $l_cal['location']?$l_cal['location']:''));
+				if($l_cal['id'])
+				{
+					$this->so->add_attribute('id',$l_cal['id']);
+				}
+
+				if($l_cal['rpt_use_end'] != 'y')
+				{
+					$l_recur_enddate['year'] = 0;
+					$l_recur_enddate['month'] = 0;
+					$l_recur_enddate['mday'] = 0;
+				}
+				elseif (isset($l_recur_enddate['str']))
+				{
+					$l_recur_enddate = $this->jscal->input2date($l_recur_enddate['str'],False,'mday');
+				}
+
+				switch((int)$l_cal['recur_type'])
+				{
+
+
+					case MCAL_RECUR_NONE:
+						$this->so->set_recur_none();
+
+						$repetido = (int)$l_cal['recur_type']; // recebe o tipo de repeticao;
+
+						break;
+					case MCAL_RECUR_DAILY:
+						$this->so->set_recur_daily((int)$l_recur_enddate['year'],(int)$l_recur_enddate['month'],(int)$l_recur_enddate['mday'],(int)$l_cal['recur_interval']);
+
+						$repetido = (int)$l_cal['recur_type']; // recebe o tipo de repeticao;
+
+						$tmp_init = explode("/",$_POST['start']['str']); // recebe a data inicial da repeticao;
+						$init_rept = mktime($_POST['start']['hour'],$_POST['start']['min'],0,intval($tmp_init[1]),$tmp_init[0],$tmp_init[2]); // transforma a data inicial + hora + minuto em UNIX timestamp;
+
+						if($l_cal['rpt_use_end'] == 'y') // verifica se foi indicada data final da repeticao, se sim:
+						{
+
+							$tmp_end = explode("/",$_POST['recur_enddate']['str']); // recebe data final da repeticao;
+							$end_rept = mktime($_POST['start']['hour'],$_POST['start']['min'],0,intval($tmp_end[1]),$tmp_end[0],$tmp_end[2]); // transforma a data inicial + hora e minuto de inicio da repeticao em UNIX timestamp;
+
+						}else { // se nao existe data final da repeticao (agendamento infinito):
+
+							$end_rept = 0; // variavel recebe zero e nao sera adicionado nenhum alarme para o agendamento;
+						}
+						break;
+					case MCAL_RECUR_WEEKLY:
+
+						$repetido = (int)$l_cal['recur_type']; // recebe o tipo de repeticao;
+
+						$tmp_init = explode("/",$_POST['start']['str']); // recebe a data inicial da repeticao;
+						$init_rept = mktime($_POST['start']['hour'],$_POST['start']['min'],0,intval($tmp_init[1]),$tmp_init[0],$tmp_init[2]); // transforma a data inicial + hora + minuto em UNIX timestamp;
+
+						if($l_cal['rpt_use_end'] == 'y') // verifica se foi indicada data final da repeticao, se sim:
+						{
+
+							$tmp_end = explode("/",$_POST['recur_enddate']['str']); // recebe data final da repeticao;
+							$end_rept = mktime($_POST['start']['hour'],$_POST['start']['min'],0,intval($tmp_end[1]),$tmp_end[0],$tmp_end[2]); // transforma a data inicial + hora e minuto de inicio da repeticao em UNIX timestamp;
+
+						}else { // se nao existe data final da repeticao (agendamento infinito):
+
+							$end_rept = 0; // variavel recebe zero e nao sera adicionado nenhum alarme para o agendamento;
+						}
+
+						foreach(array('rpt_sun','rpt_mon','rpt_tue','rpt_wed','rpt_thu','rpt_fri','rpt_sat') as $rpt_day)
+						{
+							$l_cal['recur_data'] += (int)$l_cal[$rpt_day];
+						}
+						if (is_array($l_cal['rpt_day']))
+						{
+							foreach ($l_cal['rpt_day'] as $mask)
+							{
+								$l_cal['recur_data'] |= (int)$mask;
+								$rpt_wdays = $l_cal['recur_data']; // recebe os dias da semana (somatorio) para repetir o alarme;
+							}
+						}
+						$this->so->set_recur_weekly((int)$l_recur_enddate['year'],(int)$l_recur_enddate['month'],(int)$l_recur_enddate['mday'],(int)$l_cal['recur_interval'],$l_cal['recur_data']);
+						break;
+					case MCAL_RECUR_MONTHLY_MDAY:
+						$this->so->set_recur_monthly_mday((int)$l_recur_enddate['year'],(int)$l_recur_enddate['month'],(int)$l_recur_enddate['mday'],(int)$l_cal['recur_interval']);
+
+						$repetido = (int)$l_cal['recur_type']; // recebe o tipo de repeticao;
+
+						$tmp_init = explode("/",$_POST['start']['str']); // recebe a data inicial da repeticao;
+						$init_rept = mktime($_POST['start']['hour'],$_POST['start']['min'],0,intval($tmp_init[1]),$tmp_init[0],$tmp_init[2]); // transforma a data inicial + hora + minuto em UNIX timestamp;
+
+						if($l_cal['rpt_use_end'] == 'y') // verifica se foi indicada data final da repeticao, se sim:
+						{
+
+							$tmp_end = explode("/",$_POST['recur_enddate']['str']); // recebe data final da repeticao;
+							$end_rept = mktime($_POST['start']['hour'],$_POST['start']['min'],0,intval($tmp_end[1]),$tmp_end[0],$tmp_end[2]); // transforma a data inicial + hora e minuto de inicio da repeticao em UNIX timestamp;
+
+						}else { // se nao existe data final da repeticao (agendamento infinito):
+
+							$end_rept = 0; // variavel recebe zero e nao sera adicionado nenhum alarme para o agendamento;
+						}
+						break;
+					case MCAL_RECUR_MONTHLY_WDAY:
+						$this->so->set_recur_monthly_wday((int)$l_recur_enddate['year'],(int)$l_recur_enddate['month'],(int)$l_recur_enddate['mday'],(int)$l_cal['recur_interval']);
+						break;
+					case MCAL_RECUR_YEARLY:
+						$this->so->set_recur_yearly((int)$l_recur_enddate['year'],(int)$l_recur_enddate['month'],(int)$l_recur_enddate['mday'],(int)$l_cal['recur_interval']);
+
+						$repetido = (int)$l_cal['recur_type']; // recebe o tipo de repeticao;
+
+						$tmp_init = explode("/",$_POST['start']['str']); // recebe a data inicial da repeticao;
+						$init_rept = mktime($_POST['start']['hour'],$_POST['start']['min'],0,intval($tmp_init[1]),$tmp_init[0],$tmp_init[2]); // transforma a data inicial + hora + minuto em UNIX timestamp;
+
+						if($l_cal['rpt_use_end'] == 'y') // verifica se foi indicada data final da repeticao, se sim:
+						{
+
+							$tmp_end = explode("/",$_POST['recur_enddate']['str']); // recebe data final da repeticao;
+							$end_rept = mktime($_POST['start']['hour'],$_POST['start']['min'],0,intval($tmp_end[1]),$tmp_end[0],$tmp_end[2]); // transforma a data inicial + hora e minuto de inicio da repeticao em UNIX timestamp;
+
+						}else { // se nao existe data final da repeticao (agendamento infinito):
+
+							$end_rept = 0; // variavel recebe zero e nao sera adicionado nenhum alarme para o agendamento;
+						}
+						break;
+				}
+
+				if($l_participants)
+				{
+					$parts = $l_participants;
+					$minparts = min($l_participants);
+					$part = Array();
+					for($i=0;$i<count($parts);$i++)
+					{
+						if (($accept_type = substr($parts[$i],-1,1)) == '0' || (int)$accept_type > 0)
+						{
+							$accept_type = 'U';
+						}
+						$acct_type = $GLOBALS['phpgw']->accounts->get_type((int)$parts[$i]);
+						if($acct_type == 'u')
+						{
+							$part[(int)$parts[$i]] = $accept_type;
+						}
+						elseif($acct_type == 'g')
+						{
+							$part[(int)$parts[$i]] = $accept_type;
+							$groups[] = $parts[$i];
+							/* This pulls ALL users of a group and makes them as participants to the event */
+							/* I would like to turn this back into a group thing. */
+							$acct = CreateObject('phpgwapi.accounts',(int)$parts[$i]);
+							$members = $acct->member((int)$parts[$i]);
+							unset($acct);
+							if($members == False)
+							{
+								continue;
+							}
+							while($member = each($members))
+							{
+								$part[$member[1]['account_id']] = $accept_type;
+							}
+						}
+					}
+				}
+				else
+				{
+					$part = False;
+				}
+
+				if($part)
+				{
+					@reset($part);
+					while(list($key,$accept_type) = each($part))
+					{
+						$this->so->add_attribute('participants',$accept_type,(int)$key);
+					}
+				}
+
+				if($groups)
+				{
+					@reset($groups);
+					$this->so->add_attribute('groups',(int)$group_id);
+				}
+
+				$event = $this->get_cached_event();
+				if(!is_int($minparts))
+				{
+					$minparts = $this->owner;
+				}
+				if(!@isset($event['participants'][$l_cal['owner']]))
+				{
+					$this->so->add_attribute('owner',$minparts);
+				}
+				else
+				{
+					$this->so->add_attribute('owner',$l_cal['owner']);
+				}
+				$this->so->add_attribute('priority',$l_cal['priority']);
+
+				foreach($l_cal as $name => $value)
+				{
+					if ($name[0] == '#')	// Custom field
+					{
+						$this->so->add_attribute($name,stripslashes($value));
+					}
+				}
+				if (isset($_POST['preserved']) && is_array($preserved = unserialize(stripslashes($_POST['preserved']))))
+				{
+					foreach($preserved as $name => $value)
+					{
+						switch($name)
+						{
+							case 'owner':
+								$this->so->add_attribute('participants',$value,$l_cal['owner']);
+								break;
+							default:
+								$this->so->add_attribute($name,str_replace(array('&amp;','&quot;','&lt;','&gt;'),array('&','"','<','>'),$value));
+						}
+					}
+				}
+				$event = $this->get_cached_event();
+
+				if ($l_cal['alarmdays'] > 0 || $l_cal['alarmhours'] > 0 ||
+						$l_cal['alarmminutes'] > 0)
+				{
+					$offset = ($l_cal['alarmdays'] * 24 * 3600) +
+						($l_cal['alarmhours'] * 3600) + ($l_cal['alarmminutes'] * 60);
+
+					$time = $this->maketime($event['start']) - $offset;
+
+					$event['alarm'][] = Array(
+						'time'    => $time,
+						'offset'  => $offset,
+						'owner'   => $this->owner,
+						'enabled' => 1,
+						'repeat'  => $repetido, // para repetir alarme;
+						'init_rept' => $init_rept, // inicio repeticao;
+						'end_rept' => $end_rept, // fim repeticao;
+						'rpt_wdays' => $rpt_wdays // dias repeticao da semana;
+					);
+				}
+
+				$this->store_to_appsession($event);
+				$datetime_check = $this->validate_update($event);
+				print_debug('bo->validated_update() returnval',$datetime_check);
+
+				/*if($datetime_check)
+				{
+
+
+					if (!$send_to_ui)
+					{
+						return array($datetime_check => 'invalid input data');
+					}
+
+
+					ExecMethod($ui_return,
+						Array(
+							'cd'		=> $datetime_check,
+							'readsess'	=> 1
+						)
+					);
+
+					if(!$params['from_mobile'])
+						$GLOBALS['phpgw']->common->phpgw_exit(True);
+					else
+						return;
+
+				}/*/
+				if($event['id'])
+				{
+					$event_ids[] = $event['id'];
+				}
+				if($event['reference'])
+				{
+					$event_ids[] = $event['reference'];
+				}
+
+				$overlapping_events = $this->overlap(
+					$this->maketime($event['start']),
+					$this->maketime($event['end']),
+					$event['participants'],
+					$event['owner'],
+					$event_ids
+				);
+			}
+			if(!$this->ex_participants)
+				$this->ex_participants = $event['ex_participants'];			
+			if($overlapping_events)
+			{
+
+
+				if($send_to_ui)
+				{
+					$event['ex_participants'] = $this->ex_participants;
+					unset($GLOBALS['phpgw_info']['flags']['noheader']);
+					unset($GLOBALS['phpgw_info']['flags']['nonavbar']);
+					ExecMethod($ui_overlap,
+						Array(
+							'o_events'	=> $overlapping_events,
+							'this_event'	=> $event
+						)
+					);
+					if(!$params['from_mobile'])
+						$GLOBALS['phpgw']->common->phpgw_exit(True);
+					else
+						return;
+				}
+				else
+				{
+					return $overlapping_events;
+				}
+			}
+			else
+			{
+
+
+
+				if(!$event['id'])
+				{
+
+
+
+					$this->so->add_entry($event);
+					$this->send_update(MSG_ADDED,$event['participants'],'',$this->get_cached_event());
+					print_debug('New Event ID',$event['id']);
+				}
+				else
+				{
+
+					print_debug('Updating Event ID',$event['id']);
+					$new_event = $event;
+					$old_event = $this->read_entry($event['id']);
+					// if old event has alarm and the start-time changed => update them
+					//echo "<p>checking ".count($old_event['alarm'])." alarms of event #$event[id] start moved from ".print_r($old_event['start'],True)." to ".print_r($event['start'],True)."</p>\n";
+					if ($old_event['alarm'] &&
+						$this->maketime($old_event['start']) != $this->maketime($event['start']))
+					{
+						$this->so->delete_alarms($old_event['id']);
+						foreach($old_event['alarm'] as $id => $alarm)
+						{
+							$alarm['time'] = $this->maketime($event['start']) - $alarm['offset'];
+							$event['alarm'][] = $alarm;
+						}
+						//echo "updated alarms<pre>".print_r($event['alarm'],True)."</pre>\n";
+					}
+					$this->so->cal->event = $event;
+					$this->so->add_entry($event);
+					$this->prepare_recipients($new_event,$old_event);
+				}
+
+				$date = sprintf("%04d%02d%02d",$event['start']['year'],$event['start']['month'],$event['start']['mday']);
+				if($send_to_ui)
+				{
+					$this->read_sessiondata();
+					if ($this->return_to)
+					{
+						$GLOBALS['phpgw']->redirect_link('/index.php','menuaction='.$this->return_to);
+						$GLOBALS['phpgw']->common->phpgw_exit();
+					}
+					Execmethod($ui_index);
+				}
+				else
+				{
+					return (int)$event['id'];
+				}
+			}
+			return True;
+		}
+
+		/* Private functions */
+		function read_holidays($year=0)
+		{
+			if(!$year)
+			{
+				$year = $this->year;
+			}
+			$holiday = CreateObject('infolog.boholiday');
+			$holiday->prepare_read_holidays($year,$this->owner);
+			$this->cached_holidays = $holiday->read_holiday();
+			unset($holiday);
+		}
+
+		function user_is_a_member($event,$user)
+		{
+			@reset($event['participants']);
+			$uim = False;
+			$security_equals = $GLOBALS['phpgw']->accounts->membership($user);
+			while(!$uim && $event['participants'] && $security_equals && list($participant,$status) = each($event['participants']))
+			{
+				if($GLOBALS['phpgw']->accounts->get_type($participant) == 'g')
+				{
+					@reset($security_equals);
+					while(list($key,$group_info) = each($security_equals))
+					{
+						if($group_info['account_id'] == $participant)
+						{
+							return True;
+							$uim = True;
+						}
+					}
+				}
+			}
+			return $uim;
+		}
+
+		function maketime($time)
+		{
+//echo "tttt".$time['month']."  ".$time['mday']."  ".$time['year']."<br>";
+			return mktime(intval($time['hour']),intval($time['min']),intval($time['sec']),intval($time['month']),intval($time['mday']),intval($time['year']));
+		}
+
+function maketime2($hora,$min,$seg,$dia,$mes,$ano)
+		{
+
+
+			return mktime(intval($hora),intval($min),intval($seg),intval($mes),intval($dia),intval($ano));
+		}
+		/*!
+		@function time2array
+		@abstract returns a date-array suitable for the start- or endtime of an event from a timestamp
+		@syntax time2array($time,$alarm=0)
+		@param $time the timestamp for the values of the array
+		@param $alarm (optional) alarm field of the array, defaults to 0
+		@author ralfbecker
+		*/
+		function time2array($time,$alarm = 0)
+		{
+			return array(
+				'year'  => (int)(date('Y',$time)),
+				'month' => (int)(date('m',$time)),
+				'mday'  => (int)(date('d',$time)),
+				'hour'  => (int)(date('H',$time)),
+				'min'   => (int)(date('i',$time)),
+				'sec'   => (int)(date('s',$time)),
+				'alarm' => (int)($alarm)
+			);
+		}
+
+		/*!
+		@function set_recur_date
+		@abstract set the start- and enddates of a recuring event for a recur-date
+		@syntax set_recur_date(&$event,$date)
+		@param $event the event which fields to set (has to be the original event for start-/end-times)
+		@param $date  the recuring date in form 'Ymd', eg. 20030226
+		@author ralfbecker
+		*/
+		function set_recur_date(&$event,$date)
+		{
+			$org_start = $this->maketime($event['start']);
+			$org_end   = $this->maketime($event['end']);
+			$start = mktime($event['start']['hour'],$event['start']['min'],0,substr($date,4,2),substr($date,6,2),substr($date,0,4));
+			$end   = $org_end + $start - $org_start;
+			$event['start'] = $this->time2array($start);
+			$event['end']   = $this->time2array($end);
+		}
+
+		function fix_update_time(&$time_param)
+		{
+			if (isset($time_param['str']))
+			{
+				if (!is_object($this->jscal))
+				{
+					$this->jscal = CreateObject('phpgwapi.jscalendar');
+				}
+				$time_param += $this->jscal->input2date($time_param['str'],False,'mday');
+				unset($time_param['str']);
+			}
+			if ($this->prefs['common']['timeformat'] == '12')
+			{
+				if ($time_param['ampm'] == 'pm')
+				{
+					if ($time_param['hour'] <> 12)
+					{
+						$time_param['hour'] += 12;
+					}
+				}
+				elseif ($time_param['ampm'] == 'am')
+				{
+					if ($time_param['hour'] == 12)
+					{
+						$time_param['hour'] -= 12;
+					}
+				}
+
+				if($time_param['hour'] > 24)
+				{
+					$time_param['hour'] -= 12;
+				}
+			}
+		}
+
+		function validate_update($event)
+		{
+			$error = 0;
+			// do a little form verifying
+			if (!count($event['participants']))
+			{
+				$error = 43;
+			}
+			elseif ($event['title'] == '')
+			{
+				$error = 40;
+			}
+			elseif (($GLOBALS['phpgw']->datetime->time_valid($event['start']['hour'],$event['start']['min'],0) == False) || ($GLOBALS['phpgw']->datetime->time_valid($event['end']['hour'],$event['end']['min'],0) == False))
+			{
+				$error = 41;
+			}
+			elseif (($GLOBALS['phpgw']->datetime->date_valid($event['start']['year'],$event['start']['month'],$event['start']['mday']) == False) || ($GLOBALS['phpgw']->datetime->date_valid($event['end']['year'],$event['end']['month'],$event['end']['mday']) == False) || ($GLOBALS['phpgw']->datetime->date_compare($event['start']['year'],$event['start']['month'],$event['start']['mday'],$event['end']['year'],$event['end']['month'],$event['end']['mday']) == 1))
+			{
+				$error = 42;
+			}
+			elseif ($GLOBALS['phpgw']->datetime->date_compare($event['start']['year'],$event['start']['month'],$event['start']['mday'],$event['end']['year'],$event['end']['month'],$event['end']['mday']) == 0)
+			{
+				if ($GLOBALS['phpgw']->datetime->time_compare($event['start']['hour'],$event['start']['min'],0,$event['end']['hour'],$event['end']['min'],0) == 1)
+				{
+					$error = 42;
+				}
+			}
+			return $error;
+		}
+
+		/*!
+		@function participants_not_rejected($participants,$event)
+		@abstract checks if any of the $particpants participates in $event and has not rejected it
+		*/
+		function participants_not_rejected($participants,$event)
+		{
+			//echo "participants_not_rejected()<br>participants =<pre>"; print_r($participants); echo "</pre><br>event[participants]=<pre>"; print_r($event['participants']); echo "</pre>\n";
+			foreach($participants as $uid => $status)
+			{
+				//echo "testing event[participants][uid=$uid] = '".$event['participants'][$uid]."'<br>\n";
+				if (isset($event['participants'][$uid]) && $event['participants'][$uid] != 'R' &&
+					$status != 'R')
+				{
+					return True;	// found not rejected participant in event
+				}
+			}
+			return False;
+		}
+
+		function overlap($starttime,$endtime,$participants,$owner=0,$id=0,$restore_cache=False)
+		{
+//			$retval = Array();
+//			$ok = False;
+
+/* This needs some attention.. by commenting this chunk of code it will fix bug #444265 */
+
+			if($restore_cache)
+			{
+				$temp_cache_events = $this->cached_events;
+			}
+
+//			$temp_start = (int)$GLOBALS['phpgw']->common->show_date($starttime,'Ymd');
+//			$temp_start_time = (int)($GLOBALS['phpgw']->common->show_date($starttime,'Hi');
+//			$temp_end = (int)$GLOBALS['phpgw']->common->show_date($endtime,'Ymd');
+//			$temp_end_time = (int)$GLOBALS['phpgw']->common->show_date($endtime,'Hi');
+			$temp_start = (int)(date('Ymd',$starttime));
+			$temp_start_time = (int)(date('Hi',$starttime));
+			$temp_end = (int)(date('Ymd',$endtime));
+			$temp_end_time = (int)(date('Hi',$endtime));
+			if($this->debug)
+			{
+				echo '<!-- Temp_Start: '.$temp_start.' -->'."\n";
+				echo '<!-- Temp_End: '.$temp_end.' -->'."\n";
+			}
+
+			$users = Array();
+			if(count($participants))
+			{
+				while(list($user,$status) = each($participants))
+				{
+					$users[] = $user;
+				}
+			}
+			else
+			{
+				$users[] = $this->owner;
+			}
+
+			$possible_conflicts = $this->store_to_cache(
+				Array(
+					'smonth'	=> substr(strval($temp_start),4,2),
+					'sday'	=> substr(strval($temp_start),6,2),
+					'syear'	=> substr(strval($temp_start),0,4),
+					'emonth'	=> substr(strval($temp_end),4,2),
+					'eday'	=> substr(strval($temp_end),6,2),
+					'eyear'	=> substr(strval($temp_end),0,4),
+					'owner'	=> $users
+				)
+			);
+
+			if($this->debug)
+			{
+				echo '<!-- Possible Conflicts ('.($temp_start - 1).'): '.count($possible_conflicts[$temp_start - 1]).' -->'."\n";
+				echo '<!-- Possible Conflicts ('.$temp_start.'): '.count($possible_conflicts[$temp_start]).' '.count($id).' -->'."\n";
+			}
+
+			if($possible_conflicts[$temp_start] || $possible_conflicts[$temp_end])
+			{
+				if($temp_start == $temp_end)
+				{
+					if($this->debug)
+					{
+						echo '<!-- Temp_Start == Temp_End -->'."\n";
+					}
+					@reset($possible_conflicts[$temp_start]);
+					while(list($key,$event) = each($possible_conflicts[$temp_start]))
+					{
+						$found = False;
+						if($id)
+						{
+							@reset($id);
+							while(list($key,$event_id) = each($id))
+							{
+								if($this->debug)
+								{
+									echo '<!-- $id['.$key.'] = '.$id[$key].' = '.$event_id.' -->'."\n";
+									echo '<!-- '.$event['id'].' == '.$event_id.' -->'."\n";
+								}
+								if($event['id'] == $event_id)
+								{
+									$found = True;
+								}
+							}
+						}
+						if($this->debug)
+						{
+							echo '<!-- Item found: '.$found.' -->'."<br>\n";
+						}
+						if(!$found)
+						{
+							if($this->debug)
+							{
+								echo '<!-- Checking event id #'.$event['id'];
+							}
+							$temp_event_start = sprintf("%d%02d",$event['start']['hour'],$event['start']['min']);
+							$temp_event_end = sprintf("%d%02d",$event['end']['hour'],$event['end']['min']);
+//							if((($temp_start_time <= $temp_event_start) && ($temp_end_time >= $temp_event_start) && ($temp_end_time <= $temp_event_end)) ||
+							if(($temp_start_time <= $temp_event_start &&
+								$temp_end_time > $temp_event_start &&
+								$temp_end_time <= $temp_event_end ||
+								$temp_start_time >= $temp_event_start &&
+								$temp_start_time < $temp_event_end &&
+								$temp_end_time >= $temp_event_end ||
+								$temp_start_time <= $temp_event_start &&
+								$temp_end_time >= $temp_event_end ||
+								$temp_start_time >= $temp_event_start &&
+								$temp_end_time <= $temp_event_end) &&
+								$this->participants_not_rejected($participants,$event))
+							{
+								if($this->debug)
+								{
+									echo ' Conflicts';
+								}
+								$retval[] = $event['id'];
+							}
+							if($this->debug)
+							{
+								echo ' -->'."\n";
+							}
+						}
+					}
+				}
+			}
+			else
+			{
+				$retval = False;
+			}
+
+			if($restore_cache)
+			{
+				$this->cached_events = $temp_cache_events;
+			}
+
+			return $retval;
+		}
+
+		/*!
+		@function check_perms( )
+		@syntax check_perms($needed,$event=0,$other=0)
+		@abstract Checks if the current user has the necessary ACL rights
+		@author ralfbecker
+		@discussion The check is performed on an event or general on the cal of an other user
+		@param $needed necessary ACL right: PHPGW_ACL_{READ|EDIT|DELETE}
+		@param $event event as array or the event-id or 0 for general check
+		@param $other uid to check (if event==0) or 0 to check against $this->owner
+		@note Participating in an event is considered as haveing read-access on that event, \
+			even if you have no general read-grant from that user.
+		*/
+		function check_perms($needed,$event=0,$other=0)
+		{
+			$event_in = $event;
+			
+			if (is_int($event) && $event == 0)
+			{
+				$owner = $other > 0 ? $other : $this->owner;
+			}
+			else
+			{
+				if (!is_array($event))
+				{
+					$event = $this->so->read_entry((int) $event);
+				}
+				if (!is_array($event))
+				{
+					if ($this->xmlrpc)
+					{
+						$GLOBALS['server']->xmlrpc_error($GLOBALS['xmlrpcerr']['not_exist'],$GLOBALS['xmlrpcstr']['not_exist']);
+					}
+					return False;
+				}
+				$owner = $event['owner'];
+				$private = $event['public'] == False || $event['public'] == 0;
+			}
+			$user = $GLOBALS['phpgw_info']['user']['account_id'];
+			$grants = $this->grants[$owner];
+
+			if (is_array($event) && $needed == PHPGW_ACL_READ)
+			{
+				// Check if the $user is one of the participants or has a read-grant from one of them
+				//
+if($event['participants']){
+				foreach($event['participants'] as $uid => $accept)
+				{
+					if ($this->grants[$uid] & PHPGW_ACL_READ || $uid == $user)
+					{
+						$grants |= PHPGW_ACL_READ;
+						// if the $user is one of the participants read-grant private (restricted) too.
+						if($uid == $user || ($this->grants[$uid] & PHPGW_ACL_PRIVATE)) {
+							$grants |= PHPGW_ACL_PRIVATE;
+						}
+						//break;
+					}
+				}
+}
+			}
+
+			if ($GLOBALS['phpgw']->accounts->get_type($owner) == 'g' && $needed == PHPGW_ACL_ADD)
+			{
+				$access = False;	// a group can't be the owner of an event
+			}
+			else
+			{
+				$access = $user == $owner || $grants & $needed && (!$private || $grants & PHPGW_ACL_PRIVATE);
+			}
+			//echo "<p>".function_backtrace()." check_perms($needed,$event_id,$other) for user $user and needed_acl $needed: event='$event[title]': owner=$owner, private=$private, grants=$grants ==> access=$access</p>\n";
+
+			return $access;
+		}
+
+
+		function display_status($user_status)
+		{
+			if(@$this->prefs['infolog']['display_status'] && $user_status)
+			{
+				$user_status = substr($this->get_long_status($user_status),0,1);
+
+				return ' ('.$user_status.')';
+			}
+			else
+			{
+				return '';
+			}
+		}
+
+		function get_long_status($status_short)
+		{
+			switch ($status_short)
+			{
+				case 'A':
+					$status = lang('Accepted');
+					break;
+				case 'R':
+					$status = lang('Rejected');
+					break;
+				case 'T':
+					$status = lang('Tentative');
+					break;
+				case 'U':
+					$status = lang('No Response');
+					break;
+			}
+			return $status;
+		}
+
+		function is_private($event,$owner)
+		{
+			if($owner == 0)
+			{
+				$owner = $this->owner;
+			}
+			if ($owner == $GLOBALS['phpgw_info']['user']['account_id'] || ($event['public']==1) || ($this->check_perms(PHPGW_ACL_PRIVATE,$event) && $event['public']==0) || $event['owner'] == $GLOBALS['phpgw_info']['user']['account_id'])
+			{
+				return False;
+			}
+			elseif($event['public'] == 0)
+			{
+				return True;
+			}
+			elseif($event['public'] == 2)
+			{
+				$is_private = True;
+				$groups = $GLOBALS['phpgw']->accounts->membership($owner);
+				while (list($key,$group) = each($groups))
+				{
+					if (strpos(' '.implode(',',$event['groups']).' ',$group['account_id']))
+					{
+						return False;
+					}
+				}
+			}
+			else
+			{
+				return False;
+			}
+
+			return $is_private;
+		}
+
+		function get_short_field($event,$is_private=True,$field='')
+		{
+
+
+			if($is_private)
+			{
+				return lang('private');
+			}
+
+// cut off too long titles
+			elseif(strlen($event[$field]) > 19 && !$this->printer_friendly && $field=="title")
+//			elseif(strlen($event[$field]) > 19 && $this->printer_friendly)
+			{
+// we dont use currently 160304
+//				return substr($event[$field], 0 , 19) . '&nbsp;...';
+				return $event[$field];
+			}
+			else
+			{
+				return $event[$field];
+			}
+		}
+
+		function long_date($first,$last=0)
+		{
+			if (!is_array($first))
+			{
+				$first = $this->time2array($raw = $first);
+				$first['raw'] = $raw;
+				$first['day'] = $first['mday'];
+			}
+			if ($last && !is_array($last))
+			{
+				$last = $this->time2array($raw = $last);
+				$last['raw'] = $raw;
+				$last['day'] = $last['mday'];
+			}
+			$datefmt = $this->prefs['common']['dateformat'];
+
+			$month_before_day = strtolower($datefmt[0]) == 'm' ||
+				strtolower($datefmt[2]) == 'm' && $datefmt[4] == 'd';
+
+			for ($i = 0; $i < 5; $i += 2)
+			{
+				switch($datefmt[$i])
+				{
+					case 'd':
+						$range .= $first['day'] . ($datefmt[1] == '.' ? '.' : '');
+						if ($first['month'] != $last['month'] || $first['year'] != $last['year'])
+						{
+							if (!$month_before_day)
+							{
+								$range .= ' '.lang(strftime('%B',$first['raw']));
+							}
+							if ($first['year'] != $last['year'] && $datefmt[0] != 'Y')
+							{
+								$range .= ($datefmt[0] != 'd' ? ', ' : ' ') . $first['year'];
+							}
+							if (!$last)
+							{
+								return $range;
+							}
+							$range .= ' - ';
+
+							if ($first['year'] != $last['year'] && $datefmt[0] == 'Y')
+							{
+								$range .= $last['year'] . ', ';
+							}
+
+							if ($month_before_day)
+							{
+								$range .= lang(strftime('%B',$last['raw']));
+							}
+						}
+						else
+						{
+							$range .= ' - ';
+						}
+						$range .= ' ' . $last['day'] . ($datefmt[1] == '.' ? '.' : '');
+						break;
+					case 'm':
+					case 'M':
+						$range .= ' '.lang(strftime('%B',$month_before_day ? $first['raw'] : $last['raw'])) . ' ';
+						break;
+					case 'Y':
+						$range .= ($datefmt[0] == 'm' ? ', ' : ' ') . ($datefmt[0] == 'Y' ? $first['year'].($datefmt[2] == 'd' ? ', ' : ' ') : $last['year'].' ');
+						break;
+				}
+			}
+			return $range;
+		}
+
+		function get_week_label()
+		{
+			$first = $GLOBALS['phpgw']->datetime->gmtdate($GLOBALS['phpgw']->datetime->get_weekday_start($this->year, $this->month, $this->day));
+			$last = $GLOBALS['phpgw']->datetime->gmtdate($first['raw'] + 518400);
+
+			return ($this->long_date($first,$last));
+		}
+
+		function normalizeminutes(&$minutes)
+		{
+			$hour = 0;
+			$min = (int)$minutes;
+			if($min >= 60)
+			{
+				$hour += $min / 60;
+				$min %= 60;
+			}
+			settype($minutes,'integer');
+			$minutes = $min;
+			return $hour;
+		}
+
+		function splittime($time,$follow_24_rule=True)
+		{
+			$temp = array('hour','minute','second','ampm');
+			$time = strrev($time);
+			$second = (int)(strrev(substr($time,0,2)));
+			$minute = (int)(strrev(substr($time,2,2)));
+			$hour   = (int)(strrev(substr($time,4)));
+			$hour += $this->normalizeminutes($minute);
+			$temp['second'] = $second;
+			$temp['minute'] = $minute;
+			$temp['hour']   = $hour;
+			$temp['ampm']   = '  ';
+			if($follow_24_rule == True)
+			{
+				if ($this->prefs['common']['timeformat'] == '24')
+				{
+					return $temp;
+				}
+
+				$temp['ampm'] = 'am';
+
+				if ((int)$temp['hour'] > 12)
+				{
+					$temp['hour'] = (int)((int)$temp['hour'] - 12);
+					$temp['ampm'] = 'pm';
+				}
+				elseif ((int)$temp['hour'] == 12)
+				{
+					$temp['ampm'] = 'pm';
+				}
+			}
+			return $temp;
+		}
+
+		function get_exception_array($exception_str='')
+		{
+			$exception = Array();
+			if(strpos(' '.$exception_str,','))
+			{
+				$exceptions = explode(',',$exception_str);
+				for($exception_count=0;$exception_count<count($exceptions);$exception_count++)
+				{
+					$exception[] = (int)$exceptions[$exception_count];
+				}
+			}
+			elseif($exception_str != '')
+			{
+				$exception[] = (int)$exception_str;
+			}
+			return $exception;
+		}
+
+		function build_time_for_display($fixed_time)
+		{
+			$time = $this->splittime($fixed_time);
+			$str = $time['hour'].':'.((int)$time['minute']<=9?'0':'').$time['minute'];
+
+			if ($this->prefs['common']['timeformat'] == '12')
+			{
+				$str .= ' ' . $time['ampm'];
+			}
+
+			return $str;
+		}
+
+		function sort_event($event,$date)
+		{
+			$inserted = False;
+			if(isset($event['recur_exception']))
+			{
+				$event_time = mktime($event['start']['hour'],$event['start']['min'],0,(int)(substr($date,4,2)),(int)(substr($date,6,2)),(int)(substr($date,0,4))) - $GLOBALS['phpgw']->datetime->tz_offset;
+				while($inserted == False && list($key,$exception_time) = each($event['recur_exception']))
+				{
+					if($this->debug)
+					{
+						echo '<!-- checking exception datetime '.$exception_time.' to event datetime '.$event_time.' -->'."\n";
+					}
+					if($exception_time == $event_time)
+					{
+						$inserted = True;
+					}
+				}
+			}
+			if($this->cached_events[$date] && $inserted == False)
+			{
+
+				if($this->debug)
+				{
+					echo '<!-- Cached Events found for '.$date.' -->'."\n";
+				}
+				$year = substr($date,0,4);
+				$month = substr($date,4,2);
+				$day = substr($date,6,2);
+
+				if($this->debug)
+				{
+					echo '<!-- Date : '.$date.' Count : '.count($this->cached_events[$date]).' -->'."\n";
+				}
+
+				for($i=0;$i<count($this->cached_events[$date]);$i++)
+				{
+					if($this->cached_events[$date][$i]['id'] == $event['id'] || $this->cached_events[$date][$i]['reference'] == $event['id'])
+					{
+						if($this->debug)
+						{
+							echo '<!-- Item already inserted! -->'."\n";
+						}
+						$inserted = True;
+						break;
+					}
+					/* This puts all spanning events across multiple days up at the top. */
+					if($this->cached_events[$date][$i]['recur_type'] == MCAL_RECUR_NONE)
+					{
+						if($this->cached_events[$date][$i]['start']['mday'] != $day && $this->cached_events[$date][$i]['end']['mday'] >= $day)
+						{
+							continue;
+						}
+					}
+					if(date('Hi',mktime($event['start']['hour'],$event['start']['min'],$event['start']['sec'],$month,$day,$year)) < date('Hi',mktime($this->cached_events[$date][$i]['start']['hour'],$this->cached_events[$date][$i]['start']['min'],$this->cached_events[$date][$i]['start']['sec'],$month,$day,$year)))
+					{
+						for($j=count($this->cached_events[$date]);$j>=$i;$j--)
+						{
+							$this->cached_events[$date][$j] = $this->cached_events[$date][$j-1];
+						}
+						if($this->debug)
+						{
+							echo '<!-- Adding event ID: '.$event['id'].' to cached_events -->'."\n";
+						}
+						$inserted = True;
+						$this->cached_events[$date][$i] = $event;
+						break;
+					}
+				}
+			}
+			if(!$inserted)
+			{
+				if($this->debug)
+				{
+					echo '<!-- Adding event ID: '.$event['id'].' to cached_events -->'."\n";
+				}
+				$this->cached_events[$date][] = $event;
+			}
+		}
+
+		function check_repeating_events($datetime)
+		{
+			@reset($this->repeating_events);
+			$search_date_full = date('Ymd',$datetime);
+			$search_date_year = date('Y',$datetime);
+			$search_date_month = date('m',$datetime);
+			$search_date_day = date('d',$datetime);
+			$search_date_dow = date('w',$datetime);
+			$search_beg_day = mktime(0,0,0,$search_date_month,$search_date_day,$search_date_year);
+			if($this->debug)
+			{
+				echo '<!-- Search Date Full = '.$search_date_full.' -->'."\n";
+			}
+			$repeated = $this->repeating_events;
+			$r_events = count($repeated);
+			for ($i=0;$i<$r_events;$i++)
+			{
+				$rep_events = $this->repeating_events[$i];
+				$id = $rep_events['id'];
+				$event_beg_day = mktime(0,0,0,$rep_events['start']['month'],$rep_events['start']['mday'],$rep_events['start']['year']);
+				if($rep_events['recur_enddate']['month'] != 0 && $rep_events['recur_enddate']['mday'] != 0 && $rep_events['recur_enddate']['year'] != 0)
+				{
+					$event_recur_time = $this->maketime($rep_events['recur_enddate']);
+				}
+				else
+				{
+					$event_recur_time = mktime(0,0,0,1,1,2030);
+				}
+				$end_recur_date = date('Ymd',$event_recur_time);
+				$full_event_date = date('Ymd',$event_beg_day);
+
+				if($this->debug)
+				{
+					echo '<!-- check_repeating_events - Processing ID - '.$id.' -->'."\n";
+					echo '<!-- check_repeating_events - Recurring End Date - '.$end_recur_date.' -->'."\n";
+				}
+
+				// only repeat after the beginning, and if there is an rpt_end before the end date
+				if (($search_date_full > $end_recur_date) || ($search_date_full < $full_event_date))
+				{
+					continue;
+				}
+
+				if ($search_date_full == $full_event_date)
+				{
+					$this->sort_event($rep_events,$search_date_full);
+					continue;
+				}
+				else
+				{
+					$freq = $rep_events['recur_interval'];
+					$freq = $freq ? $freq : 1;
+					$type = $rep_events['recur_type'];
+					switch($type)
+					{
+						case MCAL_RECUR_DAILY:
+							if($this->debug)
+							{
+								echo '<!-- check_repeating_events - MCAL_RECUR_DAILY - '.$id.' -->'."\n";
+							}
+							
+							if ($freq == 1 && $rep_events['recur_enddate']['month'] != 0 && $rep_events['recur_enddate']['mday'] != 0 && $rep_events['recur_enddate']['year'] != 0 && $search_date_full <= $end_recur_date)
+							{
+								$this->sort_event($rep_events,$search_date_full);
+							}
+							elseif (floor(($search_beg_day - $event_beg_day)/86400) % ($freq ? $freq : 1))
+							{
+								continue;
+							}
+							else
+							{
+								$this->sort_event($rep_events,$search_date_full);
+							}
+							break;
+						case MCAL_RECUR_WEEKLY:
+							if (floor(($search_beg_day - $event_beg_day)/604800)  % ($freq ? $freq : 1))
+							{
+								continue;
+							}
+							$check = 0;
+							switch($search_date_dow)
+							{
+								case 0:
+									$check = MCAL_M_SUNDAY;
+									break;
+								case 1:
+									$check = MCAL_M_MONDAY;
+									break;
+								case 2:
+									$check = MCAL_M_TUESDAY;
+									break;
+								case 3:
+									$check = MCAL_M_WEDNESDAY;
+									break;
+								case 4:
+									$check = MCAL_M_THURSDAY;
+									break;
+								case 5:
+									$check = MCAL_M_FRIDAY;
+									break;
+								case 6:
+									$check = MCAL_M_SATURDAY;
+									break;
+							}
+							if ($rep_events['recur_data'] & $check)
+							{
+								$this->sort_event($rep_events,$search_date_full);
+							}
+							break;
+						case MCAL_RECUR_MONTHLY_WDAY:
+							if ((($search_date_year - $rep_events['start']['year']) * 12 + $search_date_month - $rep_events['start']['month']) % $freq)
+							{
+								continue;
+							}
+
+							if (($GLOBALS['phpgw']->datetime->day_of_week($rep_events['start']['year'],$rep_events['start']['month'],$rep_events['start']['mday']) == $GLOBALS['phpgw']->datetime->day_of_week($search_date_year,$search_date_month,$search_date_day)) &&
+								(ceil($rep_events['start']['mday']/7) == ceil($search_date_day/7)))
+							{
+								$this->sort_event($rep_events,$search_date_full);
+							}
+							break;
+						case MCAL_RECUR_MONTHLY_MDAY:
+							if ((($search_date_year - $rep_events['start']['year']) * 12 + $search_date_month - $rep_events['start']['month'])  % ($freq ? $freq : 1))
+							{
+								continue;
+							}
+							if ($search_date_day == $rep_events['start']['mday'])
+							{
+								$this->sort_event($rep_events,$search_date_full);
+							}
+							break;
+						case MCAL_RECUR_YEARLY:
+							if (($search_date_year - $rep_events['start']['year']) % ($freq ? $freq : 1))
+							{
+								continue;
+							}
+							if (date('dm',$datetime) == date('dm',$event_beg_day))
+							{
+								$this->sort_event($rep_events,$search_date_full);
+							}
+							break;
+					}
+				}
+			}	// end for loop
+		}	// end function
+
+		function store_to_cache($params)
+		{
+			if(!is_array($params))
+			{
+				return False;
+			}
+			if (isset($params['start']) && ($datearr = $GLOBALS['server']->iso86012date($params['start'])))
+			{
+				$syear = $datearr['year'];
+				$smonth = $datearr['month'];
+				$sday = $datearr['mday'];
+				$this->xmlrpc = True;
+			}
+			else
+			{
+				$syear = $params['syear'];
+				$smonth = $params['smonth'];
+				$sday = $params['sday'];
+			}
+			if (isset($params['end']) && ($datearr = $GLOBALS['server']->iso86012date($params['end'])))
+			{
+				$eyear = $datearr['year'];
+				$emonth = $datearr['month'];
+				$eday = $datearr['mday'];
+				$this->xmlrpc = True;
+			}
+			else
+			{
+				$eyear = (isset($params['eyear'])?$params['eyear']:0);
+				$emonth = (isset($params['emonth'])?$params['emonth']:0);
+				$eday = (isset($params['eday'])?$params['eday']:0);
+			}
+			if (!isset($params['owner']) && @$this->xmlrpc)
+			{
+				$owner_id = $GLOBALS['phpgw_info']['user']['user_id'];
+			}
+			else
+			{
+				$owner_id = (isset($params['owner'])?$params['owner']:0);
+				if($owner_id==0 && $this->is_group)
+				{
+					unset($owner_id);
+					$owner_id = $this->g_owner;
+					if($this->debug)
+					{
+						echo '<!-- owner_id in ('.implode(',',$owner_id).') -->'."\n";
+					}
+				}
+			}
+			if(!$eyear && !$emonth && !$eday)
+			{
+				$edate = mktime(23,59,59,$smonth + 1,$sday + 1,$syear);
+				$eyear = date('Y',$edate);
+				$emonth = date('m',$edate);
+				$eday = date('d',$edate);
+			}
+			else
+			{
+				if(!$eyear)
+				{
+					$eyear = $syear;
+				}
+				//Tratamento do valor final (mes) da pesquisa de eventos feita em $this->so->list_events.
+				//Se $emonth nao tem valor, recebe o valor de $smonth (que recebe $params['smonth']) e soma 1.
+				//O valor $params['emonth'] indica o mes final para a pesquisa de eventos, e passou a ser
+				//informado na a impressao de eventos mensais. Mudancas feitas em class.uiinfolog.inc.php, 
+				//function display_month_print();
+				if(!$emonth)
+				{
+					$emonth = $smonth + 1;
+					if($emonth > 12)
+					{
+						$emonth = 1;
+						$eyear++;
+					}
+				}
+				if(!$eday)
+				{
+					$eday = $sday + 1;
+				}
+				$edate = mktime(23,59,59,$emonth,$eday,$eyear);
+			}
+			//echo "<p>boinfolog::store_to_cache(".print_r($params,True).") syear=$syear, smonth=$smonth, sday=$sday, eyear=$eyear, emonth=$emonth, eday=$eday, xmlrpc='$param[xmlrpc]'</p>\n";
+			if($this->debug)
+			{
+				echo '<!-- Start Date : '.sprintf("%04d%02d%02d",$syear,$smonth,$sday).' -->'."\n";
+				echo '<!-- End   Date : '.sprintf("%04d%02d%02d",$eyear,$emonth,$eday).' -->'."\n";
+			}
+			//A variavel $month_print recebe o parametro 'saux' com o mes de inicio da pesquisa de eventos por
+			//$this->so->list_events. O valor do mes final da pesquisa e tratado no codigo acima;
+			//$month_ini = $params['saux'];
+
+			if($owner_id)
+			{
+				$cached_event_ids = $this->so->list_events($syear,$smonth,$sday,$eyear,$emonth,$eday,$owner_id);
+				$cached_event_ids_repeating = $this->so->list_repeated_events($syear,$smonth,$sday,$eyear,$emonth,$eday,$owner_id);
+			}
+			else
+			{
+				$cached_event_ids = $this->so->list_events($syear,$smonth,$sday,$eyear,$emonth,$eday);
+				$cached_event_ids_repeating = $this->so->list_repeated_events($syear,$smonth,$sday,$eyear,$emonth,$eday);
+			}
+
+			$c_cached_ids = count($cached_event_ids);
+			$c_cached_ids_repeating = count($cached_event_ids_repeating);
+
+			if($this->debug)
+			{
+				echo '<!-- events cached : '.$c_cached_ids.' : for : '.sprintf("%04d%02d%02d",$syear,$smonth,$sday).' -->'."\n";
+				echo '<!-- repeating events cached : '.$c_cached_ids_repeating.' : for : '.sprintf("%04d%02d%02d",$syear,$smonth,$sday).' -->'."\n";
+			}
+
+			$this->cached_events = Array();
+
+			if($c_cached_ids == 0 && $c_cached_ids_repeating == 0)
+			{
+				return;
+			}
+
+			$cache_start = (int)(sprintf("%04d%02d%02d",$syear,$smonth,$sday));
+			$cached_event=$this->get_cached_event();
+			if($c_cached_ids)
+			{
+				for($i=0;$i<$c_cached_ids;$i++)
+				{
+					$event = $this->so->read_entry($cached_event_ids[$i]);
+					if ($event['recur_type'])
+					{
+						continue;	// fetch recuring events only in 2. loop
+					}
+					$startdate = (int)(date('Ymd',$this->maketime($event['start'])));
+					$enddate = (int)(date('Ymd',$this->maketime($event['end'])));
+					$this->cached_events[$startdate][] = $event;
+					if($startdate != $enddate)
+					{
+						$start['year'] = (int)(substr($startdate,0,4));
+						$start['month'] = (int)(substr($startdate,4,2));
+						$start['mday'] = (int)(substr($startdate,6,2));
+						for($j=$startdate,$k=0;$j<=$enddate;$k++,$j=(int)(date('Ymd',mktime(0,0,0,$start['month'],$start['mday'] + $k,$start['year']))))
+						{
+							$c_evt_day = count($this->cached_events[$j]) - 1;
+							if($c_evt_day < 0)
+							{
+								$c_evt_day = 0;
+							}
+							if($this->debug)
+							{
+								echo '<!-- Date: '.$j.' Count : '.$c_evt_day.' -->'."\n";
+							}
+							if($this->cached_events[$j][$c_evt_day]['id'] != $event['id'])
+							{
+								if($this->debug)
+								{
+									echo '<!-- Adding Event for Date: '.$j.' -->'."\n";
+								}
+								$this->cached_events[$j][] = $event;
+							}
+							if ($j >= $cache_start && (@$params['no_doubles'] || @$this->xmlrpc))
+							{
+								break;	// add event only once on it's startdate
+							}
+						}
+					}
+				}
+			}
+
+			$this->repeating_events = Array();
+			if($c_cached_ids_repeating)
+			{
+				for($i=0;$i<$c_cached_ids_repeating;$i++)
+				{
+					$this->repeating_events[$i] = $this->so->read_entry($cached_event_ids_repeating[$i]);
+					if($this->debug)
+					{
+						echo '<!-- Cached Events ID: '.$cached_event_ids_repeating[$i].' ('.sprintf("%04d%02d%02d",$this->repeating_events[$i]['start']['year'],$this->repeating_events[$i]['start']['month'],$this->repeating_events[$i]['start']['mday']).') -->'."\n";
+					}
+				}
+				for($date=mktime(0,0,0,$smonth,$sday,$syear);$date<=$edate;$date += 86400)
+				{
+					if($this->debug)
+					{
+						$search_date = date('Ymd',$date);
+						echo '<!-- Calling check_repeating_events('.$search_date.') -->'."\n";
+					}
+					$this->check_repeating_events($date);
+					if($this->debug)
+					{
+						echo '<!-- Total events found matching '.$search_date.' = '.count($this->cached_events[$search_date]).' -->'."\n";
+						for($i=0;$i<count($this->cached_events[$search_date]);$i++)
+						{
+							echo '<!-- Date: '.$search_date.' ['.$i.'] = '.$this->cached_events[$search_date][$i]['id'].' -->'."\n";
+						}
+					}
+				}
+			}
+			$retval = Array();
+			for($j=date('Ymd',mktime(0,0,0,$smonth,$sday,$syear)),$k=0;$j<=date('Ymd',mktime(0,0,0,$emonth,$eday,$eyear));$k++,$j=date('Ymd',mktime(0,0,0,$smonth,$sday + $k,$syear)))
+			{
+				if(is_array($this->cached_events[$j]))
+				{
+					if ($this->xmlrpc)
+					{
+						foreach($this->cached_events[$j] as $event)
+						{
+							$retval[] = $this->xmlrpc_prepare($event);
+						}
+					}
+					else
+					{
+						$retval[$j] = $this->cached_events[$j];
+					}
+				}
+			}
+			//echo "store_to_cache(".print_r($params,True).")=<pre>".print_r($retval,True)."</pre>\n";
+			$this->so->cal->event = $cached_event;
+			return $retval;
+		}
+
+		function xmlrpc_prepare(&$event)
+		{
+			$event['rights'] = $this->grants[$event['owner']];
+
+			foreach(array('start','end','modtime','recur_enddate') as $name)
+			{
+				if (isset($event[$name]))
+				{
+					$event[$name] = $GLOBALS['server']->date2iso8601($event[$name]);
+				}
+			}
+			if (is_array($event['recur_exception']))
+			{
+				foreach($event['recur_exception'] as $key => $timestamp)
+				{
+					$event['recur_exception'][$key] = $GLOBALS['server']->date2iso8601($timestamp);
+				}
+			}
+			static $user_cache = array();
+
+			if (!is_object($GLOBALS['phpgw']->perferences))
+			{
+				$GLOBALS['phpgw']->perferences = CreateObject('phpgwapi.preferences');
+			}
+			foreach($event['participants'] as $user_id => $status)
+			{
+				if (!isset($user_cache[$user_id]))
+				{
+					$user_cache[$user_id] = array(
+						'name'   => $GLOBALS['phpgw']->common->grab_owner_name($user_id),
+						'email'  => $GLOBALS['phpgw']->perferences->email_address($user_id)
+					);
+				}
+				$event['participants'][$user_id] = $user_cache[$user_id] + array(
+					'status' => $status,
+				);
+			}
+			if (is_array($event['alarm']))
+			{
+				foreach($event['alarm'] as $id => $alarm)
+				{
+					$event['alarm'][$id]['time'] = $GLOBALS['server']->date2iso8601($alarm['time']);
+					if ($alarm['owner'] != $GLOBALS['phpgw_info']['user']['account_id'])
+					{
+						unset($event['alarm'][$id]);
+					}
+				}
+			}
+			$event['category'] = $GLOBALS['server']->cats2xmlrpc(explode(',',$event['category']));
+
+			// using access={public|privat} in all modules via xmlrpc
+			$event['access'] = $event['public'] ? 'public' : 'privat';
+			unset($event['public']);
+
+			return $event;
+		}
+
+		/* Begin Appsession Data */
+		function store_to_appsession($event)
+		{
+			$GLOBALS['phpgw']->session->appsession('entry','infolog',$event);
+		}
+
+		function restore_from_appsession()
+		{
+//echo "quees";
+			$this->event_init();
+			$event = $GLOBALS['phpgw']->session->appsession('entry','infolog');
+//echo "quees".$event;
+			$this->so->cal->event = $event;
+			return $event;
+		}
+		/* End Appsession Data */
+
+		/* Begin of SO functions */
+		function get_cached_event()
+		{
+			return $this->so->get_cached_event();
+		}
+
+		function add_attribute($var,$value,$index='**(**')
+		{
+			$this->so->add_attribute($var,$value,$index);
+		}
+
+		function event_init()
+		{
+			$this->so->event_init();
+		}
+
+		function set_start($year,$month,$day=0,$hour=0,$min=0,$sec=0)
+		{
+			$this->so->set_start($year,$month,$day,$hour,$min,$sec);
+		}
+
+		function set_end($year,$month,$day=0,$hour=0,$min=0,$sec=0)
+		{
+			$this->so->set_end($year,$month,$day,$hour,$min,$sec);
+		}
+
+		function set_title($title='')
+		{
+			$this->so->set_title($title);
+		}
+
+		function set_description($description='')
+		{
+			$this->so->set_description($description);
+		}
+
+		function set_description1($description1='')
+		{
+			$this->so->set_description1($description1);
+		}
+function set_participants1($participants1='')
+		{
+			$this->so->set_participants1($participants1);
+		}
+		function set_ex_participants($ex_participants='')
+		{
+			$this->so->set_ex_participants($ex_participants);
+		}
+
+		function set_class($class)
+		{
+			$this->so->set_class($class);
+		}
+
+		function set_category($category='')
+		{
+			$this->so->set_category($category);
+		}
+
+		function set_alarm($alarm)
+		{
+			$this->so->set_alarm($alarm);
+		}
+
+		function set_recur_none()
+		{
+			$this->so->set_recur_none();
+		}
+
+		function set_recur_daily($year,$month,$day,$interval)
+		{
+			$this->so->set_recur_daily($year,$month,$day,$interval);
+		}
+
+		function set_recur_weekly($year,$month,$day,$interval,$weekdays)
+		{
+			$this->so->set_recur_weekly($year,$month,$day,$interval,$weekdays);
+		}
+
+		function set_recur_monthly_mday($year,$month,$day,$interval)
+		{
+			$this->so->set_recur_monthly_mday($year,$month,$day,$interval);
+		}
+
+		function set_recur_monthly_wday($year,$month,$day,$interval)
+		{
+			$this->so->set_recur_monthly_wday($year,$month,$day,$interval);
+		}
+
+		function set_recur_yearly($year,$month,$day,$interval)
+		{
+			$this->so->set_recur_yearly($year,$month,$day,$interval);
+		}
+		/* End of SO functions */
+
+		function prepare_matrix($interval,$increment,$part,$fulldate)
+		{
+			for($h=0;$h<24;$h++)
+			{
+				for($m=0;$m<$interval;$m++)
+				{
+					$index = (($h * 10000) + (($m * $increment) * 100));
+					$time_slice[$index]['marker'] = '&nbsp';
+					$time_slice[$index]['description'] = '';
+$time_slice[$index]['description1'] = '';
+				}
+			}
+			foreach($this->cached_events[$fulldate] as $event)
+			{
+				if ($event['participants'][$part] == 'R')
+				{
+					continue;	// dont show rejected invitations, as they are free time
+				}
+				$eventstart = $GLOBALS['phpgw']->datetime->localdates($this->maketime($event['start']) - $GLOBALS['phpgw']->datetime->tz_offset);
+				$eventend = $GLOBALS['phpgw']->datetime->localdates($this->maketime($event['end']) - $GLOBALS['phpgw']->datetime->tz_offset);
+				$start = ($eventstart['hour'] * 10000) + ($eventstart['minute'] * 100);
+				$starttemp = $this->splittime("$start",False);
+				$subminute = 0;
+				for($m=0;$m<$interval;$m++)
+				{
+					$minutes = $increment * $m;
+					if((int)$starttemp['minute'] > $minutes && (int)$starttemp['minute'] < ($minutes + $increment))
+					{
+						$subminute = ($starttemp['minute'] - $minutes) * 100;
+					}
+				}
+				$start -= $subminute;
+				$end =  ($eventend['hour'] * 10000) + ($eventend['minute'] * 100);
+				$endtemp = $this->splittime("$end",False);
+				$addminute = 0;
+				for($m=0;$m<$interval;$m++)
+				{
+					$minutes = ($increment * $m);
+					if($endtemp['minute'] < ($minutes + $increment) && $endtemp['minute'] > $minutes)
+					{
+						$addminute = ($minutes + $increment - $endtemp['minute']) * 100;
+					}
+				}
+				$end += $addminute;
+				$starttemp = $this->splittime("$start",False);
+				$endtemp = $this->splittime("$end",False);
+
+				for($h=$starttemp['hour'];$h<=$endtemp['hour'];$h++)
+				{
+					$startminute = 0;
+					$endminute = $interval;
+					$hour = $h * 10000;
+					if($h == (int)$starttemp['hour'])
+					{
+						$startminute = ($starttemp['minute'] / $increment);
+					}
+					if($h == (int)$endtemp['hour'])
+					{
+						$endminute = ($endtemp['minute'] / $increment);
+					}
+					$private = $this->is_private($event,$part);
+					$time_display = $GLOBALS['phpgw']->common->show_date($eventstart['raw'],$this->users_timeformat).'-'.$GLOBALS['phpgw']->common->show_date($eventend['raw'],$this->users_timeformat);
+					$time_description = '('.$time_display.') '.$this->get_short_field($event,$private,'title').$this->display_status($event['participants'][$part]);
+					for($m=$startminute;$m<$endminute;$m++)
+					{
+						$index = ($hour + (($m * $increment) * 100));
+						$time_slice[$index]['marker'] = '-';
+						$time_slice[$index]['description'] = $time_description;
+						$time_slice[$index]['id'] = $event['id'];
+					}
+				}
+			}
+			return $time_slice;
+		}
+
+		/*!
+		@function set_status
+		@abstract set the participant response $status for event $cal_id and notifies the owner of the event
+		*/
+		function set_status($cal_id,$status)
+		{
+			$status2msg = array(
+				REJECTED  => MSG_REJECTED,
+				TENTATIVE => MSG_TENTATIVE,
+				ACCEPTED  => MSG_ACCEPTED
+			);
+			if (!isset($status2msg[$status]))
+			{
+				return False;
+			}
+			$event = $this->so->read_entry($cal_id);
+			$account_id = $GLOBALS['phpgw_info']['user']['account_id'];
+			if(($status2msg[$status] == "5" && $event['participants'][$account_id] == "A") ||
+			 ($status2msg[$status] == "3" && $event['participants'][$account_id] == "R")) {
+				return True;
+			}
+			$this->so->set_status($cal_id,$status);
+			$event = $this->so->read_entry($cal_id);
+			$this->send_update($status2msg[$status],$event['participants'],$event);
+
+		}
+
+		/*!
+		@function update_requested
+		@abstract checks if $userid has requested (in $part_prefs) updates for $msg_type
+		@syntax update_requested($userid,$part_prefs,$msg_type,$old_event,$new_event)
+		@param $userid numerical user-id
+		@param $part_prefs preferces of the user $userid
+		@param $msg_type type of the notification: MSG_ADDED, MSG_MODIFIED, MSG_ACCEPTED, ...
+		@param $old_event Event before the change
+		@param $new_event Event after the change
+		@returns 0 = no update requested, > 0 update requested
+		*/
+		function update_requested($userid,$part_prefs,$msg_type,$old_event,$new_event)
+		{
+			if ($msg_type == MSG_ALARM)
+			{
+				return True;	// always True for now
+			}
+			$want_update = 0;
+
+			// the following switch fall-through all cases, as each included the following too
+			//
+			$msg_is_response = $msg_type == MSG_REJECTED || $msg_type == MSG_ACCEPTED || $msg_type == MSG_TENTATIVE;
+
+			switch($ru = $part_prefs['infolog']['receive_updates'])
+			{
+				case 'responses':
+					if ($msg_is_response)
+					{
+						++$want_update;
+					}
+				case 'modifications':
+					if ($msg_type == MSG_MODIFIED)
+					{
+						++$want_update;
+					}
+				case 'time_change_4h':
+				case 'time_change':
+					$diff = max(abs($this->maketime($old_event['start'])-$this->maketime($new_event['start'])),
+						abs($this->maketime($old_event['end'])-$this->maketime($new_event['end'])));
+					$check = $ru == 'time_change_4h' ? 4 * 60 * 60 - 1 : 0;
+					if ($msg_type == MSG_MODIFIED && $diff > $check)
+					{
+						++$want_update;
+					}
+				case 'add_cancel':
+					if ($old_event['owner'] == $userid && $msg_is_response ||
+						$msg_type == MSG_DELETED || $msg_type == MSG_ADDED)
+					{
+						++$want_update;
+					}
+					break;
+				case 'no':
+					break;
+			}
+			//echo "<p>boinfolog::update_requested(user=$userid,pref=".$part_prefs['infolog']['receive_updates'] .",msg_type=$msg_type,".($old_event?$old_event['title']:'False').",".($old_event?$old_event['title']:'False').") = $want_update</p>\n";
+			return $want_update > 0;
+		}
+
+	function create_vcard($event_array)
+	{
+	if(!is_array($event_array))
+		return null;
+	$tmpattach="BEGIN:Vinfolog\n"
+	."PRODID:-//Expresso Livre//infolog//EN\n"
+	."VERSION:1.0\n";
+		foreach ($event_array as $event)
+		{
+			// It translates int to string
+			if (!is_object($event) && !is_array($event) || !array_key_exists  ('end', $event))
+				$event = $event_array;
+			if ( $event['end']['month'] < 10 )
+				$end_event_month="0".$event['end']['month'];
+			else
+				$end_event_month=$event['end']['month'];
+			if ( $event['start']['month'] < 10 )
+				$start_event_month="0".$event['start']['month'];
+			else
+				$start_event_month=$event['start']['month'];
+			if ( $event['end']['mday'] < 10 )
+				$end_event_day="0".$event['end']['mday'];
+			else
+				$end_event_day=$event['end']['mday'];
+			if ( $event['start']['mday'] < 10 )
+				$start_event_day="0".$event['start']['mday'];
+			else
+				$start_event_day=$event['start']['mday'];
+			if ( $event['start']['hour'] < 10)
+				$start_event_hour="0".$event['start']['hour'];
+			else
+				$start_event_hour=$event['start']['hour'];
+			if ( $event['end']['hour'] < 10)
+				$end_event_hour="0".$event['end']['hour'];
+			else
+				$end_event_hour=$event['end']['hour'];
+				
+			if ( $event['start']['min'] < 10)
+				$start_event_min="0".$event['start']['min'];
+			else
+				$start_event_min=$event['start']['min'];
+			if ( $event['end']['min'] < 10)
+				$end_event_min="0".$event['end']['min'];
+			else
+				$end_event_min=$event['end']['min'];	
+		
+
+			$tmpattach.="BEGIN:VEVENT\n"
+			."DTSTART:".$event['start'][year].$start_event_month.$start_event_day."T".$start_event_hour.$start_event_min."00Z\n"
+			."DTEND:".$event[end][year].$end_event_month.$end_event_day."T".$end_event_hour.$end_event_min."00Z\n"
+			."UID:Expresso-".$event[id].$event[uid]."\n"
+			."LAST-MODIFIED:".time()."\n"
+			."DESCRIPTION:".$event[description]."\n"
+."DESCRIPTION1:".$event[description1]."\n"
+."PARTICIPANTS1:".$event[participants1]."\n"
+			."SUMMARY:".$event[title]."\n"
+			."LOCATION:".$event[location]."\n"
+			."END:VEVENT"."\n";
+		}
+			$tmpattach.="END:Vinfolog\n";
+			return $tmpattach;
+}
+
+		/*!
+		@function send_update
+		@abstract sends update-messages to certain participants of an event
+		@syntax send_update($msg_type,$to_notify,$old_event,$new_event=False)
+		@param $msg_type type of the notification: MSG_ADDED, MSG_MODIFIED, MSG_ACCEPTED, ...
+		@param $to_notify array with numerical user-ids as keys (!) (value is not used)
+		@param $old_event Event before the change
+		@param $new_event Event after the change
+		*/
+		function send_update($msg_type,$to_notify,$old_event,$new_event=False,$user=False)
+		{
+			
+			//echo "<p>boinfolog::send_update(type=$msg_type,to_notify="; print_r($to_notify); echo ", old_event="; print_r($old_event); echo ", new_event="; print_r($new_event); echo ", user=$user)</p>\n";
+			if (!is_array($to_notify))
+			{
+				$to_notify = array();
+			}
+			$owner = $old_event ? $old_event['owner'] : $new_event['owner'];
+			if ($owner && !isset($to_notify[$owner]) && $msg_type != MSG_ALARM)
+			{
+				$to_notify[$owner] = 'owner';	// always include the event-owner
+			}
+			$version = $GLOBALS['phpgw_info']['apps']['infolog']['version'];
+
+			$GLOBALS['phpgw_info']['user']['preferences'] = $GLOBALS['phpgw']->preferences->create_email_preferences();
+			$sender = $GLOBALS['phpgw_info']['user']['email'];
+
+			$temp_tz_offset = $this->prefs['common']['tz_offset'];
+			$temp_timeformat = $this->prefs['common']['timeformat'];
+			$temp_dateformat = $this->prefs['common']['dateformat'];
+
+			$tz_offset = ((60 * 60) * (int)$temp_tz_offset);
+
+			if($old_event != False)
+			{
+				$t_old_start_time = $this->maketime($old_event['start']);
+				if($t_old_start_time < (time() - 86400))
+				{
+					return False;
+				}
+			}
+
+			$temp_user = $GLOBALS['phpgw_info']['user'];
+
+			if (!$user)
+			{
+				$user = $this->owner;
+			}
+			$GLOBALS['phpgw_info']['user']['preferences'] = $GLOBALS['phpgw']->preferences->create_email_preferences($user);
+
+			$event = $msg_type == MSG_ADDED || $msg_type == MSG_MODIFIED ? $new_event : $old_event;
+			if($old_event != False)
+			{
+				$old_starttime = $t_old_start_time - $GLOBALS['phpgw']->datetime->tz_offset;
+			}
+			$starttime = $this->maketime($event['start']) - $GLOBALS['phpgw']->datetime->tz_offset;
+			$endtime   = $this->maketime($event['end']) - $GLOBALS['phpgw']->datetime->tz_offset;
+
+			switch($msg_type)
+			{
+				case MSG_DELETED:
+					$action = lang('Canceled');
+					$msg = 'Canceled';
+					$msgtype = '"infolog";';
+					$method = 'cancel';
+					$typesend = 1;
+					break;
+				case MSG_MODIFIED:
+					$action = lang('Modified');
+					$msg = 'Modified';
+					$msgtype = '"infolog"; Version="'.$version.'"; Id="'.$new_event['id'].'"';
+					$method = 'request';
+					$typesend = 2;
+					break;
+				case MSG_ADDED:
+					$action = lang('Added');
+					$msg = 'Added';
+					$msgtype = '"infolog"; Version="'.$version.'"; Id="'.$new_event['id'].'"';
+					$method = 'request';
+					$typesend = 3;
+					break;
+				case MSG_REJECTED:
+					$action = lang('Rejected');
+					$msg = 'Response';
+					$msgtype = '"infolog";';
+					$method = 'reply';
+					$typesend = 4;
+					break;
+				case MSG_TENTATIVE:
+					$action = lang('Tentative');
+					$msg = 'Response';
+					$msgtype = '"infolog";';
+					$method = 'reply';
+					$typesend = 5;
+					break;
+				case MSG_ACCEPTED:
+					$action = lang('Accepted');
+					$msg = 'Response';
+					$msgtype = '"infolog";';
+					$method = 'reply';
+					$typesend = 6;
+					break;
+				case MSG_ALARM:
+					$action = lang('Alarm');
+					$msg = 'Alarm';
+					$msgtype = '"infolog";';
+					$method = 'publish';	// duno if thats right
+					$typesend = 7;
+					break;
+				default:
+					$method = 'publish';
+					$typesend = 8;
+			}
+			$notify_msg = $this->prefs['infolog']['notify'.$msg];
+			if (empty($notify_msg))
+			{
+				$notify_msg = $this->prefs['infolog']['notifyAdded'];	// use a default
+			}
+			$details = array(			// event-details for the notify-msg
+				'id'          => $msg_type == MSG_ADDED ? $new_event['id'] : $old_event['id'],
+				'action'      => $action,
+			);
+			$event_arr = $this->event2array($event);
+			foreach($event_arr as $key => $val)
+			{
+				$details[$key] = $val['data'];
+			}
+			
+			$details['participants'] = implode("\n",$details['participants']);
+
+			$details['link'] = $GLOBALS['phpgw_info']['server']['webserver_url'].'/index.php?menuaction=infolog.uiinfolog.view&cal_id='.$event['id'];
+			// if url is only a path, try guessing the rest ;-)
+			if ($GLOBALS['phpgw_info']['server']['webserver_url'][0] == '/')
+			{
+				$details['link'] = ($GLOBALS['phpgw_info']['server']['enforce_ssl'] ? 'https://' : 'http://').
+					($GLOBALS['phpgw_info']['server']['hostname'] ? $GLOBALS['phpgw_info']['server']['hostname'] : 'localhost').
+					$details['link'];
+			}
+			
+			//Seta o email usando phpmailer
+			define('PHPGW_INCLUDE_ROOT','../');	
+			define('PHPGW_API_INC','../phpgwapi/inc');	
+			include_once(PHPGW_API_INC.'/class.phpmailer.inc.php');
+			$mail = new PHPMailer();
+			$mail->IsSMTP();
+			$boemailadmin = CreateObject('emailadmin.bo');
+			$emailadmin_profile = $boemailadmin->getProfileList();
+			$emailadmin = $boemailadmin->getProfile($emailadmin_profile[0]['profileID']);
+		
+			$mail->Host = $emailadmin['smtpServer'];
+			$mail->Port = $emailadmin['smtpPort'];
+			$mail->From = $GLOBALS['phpgw']->preferences->values['email'];
+			$mail->FromName = $GLOBALS['phpgw_info']['user']['fullname'];
+			$mail->IsHTML(true);
+
+			// Aqui ï¿œ enviado o email
+			foreach($to_notify as $userid => $statusid) 
+			{
+			 	$mail->ClearAttachments();
+			 	
+				$userid = (int)$userid;
+
+				if ($statusid == 'R' || $GLOBALS['phpgw']->accounts->get_type($userid) == 'g')
+				{
+					continue;	// dont notify rejected participants
+				}
+				if($userid != $GLOBALS['phpgw_info']['user']['account_id'] ||  $msg_type == MSG_ALARM)
+				{
+					print_debug('Msg Type',$msg_type);
+					print_debug('UserID',$userid);
+
+					$preferences = CreateObject('phpgwapi.preferences',$userid);
+					$part_prefs = $preferences->read_repository();
+
+					if (!$this->update_requested($userid,$part_prefs,$msg_type,$old_event,$new_event))
+					{
+						continue;
+					}
+					$GLOBALS['phpgw']->accounts->get_account_name($userid,$lid,$details['to-firstname'],$details['to-lastname']);
+					$details['to-fullname'] = $GLOBALS['phpgw']->common->display_fullname('',$details['to-firstname'],$details['to-lastname']);
+
+					$to = $preferences->email_address($userid);
+					
+					if (empty($to) || $to[0] == '@' || $to[0] == '$')	// we have no valid email-address
+					{
+						//echo "<p>boinfolog::send_update: Empty email adress for user '".$details['to-fullname']."' ==> ignored !!!</p>\n";
+						continue;
+					}
+					print_debug('Email being sent to',$to);
+
+					$GLOBALS['phpgw_info']['user']['preferences']['common']['tz_offset'] = $part_prefs['common']['tz_offset'];
+					$GLOBALS['phpgw_info']['user']['preferences']['common']['timeformat'] = $part_prefs['common']['timeformat'];
+					$GLOBALS['phpgw_info']['user']['preferences']['common']['dateformat'] = $part_prefs['common']['dateformat'];
+
+					$GLOBALS['phpgw']->datetime->tz_offset = ((60 * 60) * (int)$GLOBALS['phpgw_info']['user']['preferences']['common']['tz_offset']);
+
+					if($old_starttime)
+					{
+						$details['olddate'] = $GLOBALS['phpgw']->common->show_date($old_starttime);
+					}
+					$details['startdate'] = $GLOBALS['phpgw']->common->show_date($starttime);
+					$details['enddate']   = $GLOBALS['phpgw']->common->show_date($endtime);
+					
+					
+					list($subject,$body1) = split("\n",$GLOBALS['phpgw']->preferences->parse_notify($notify_msg,$details),2);
+					
+					switch($part_prefs['infolog']['update_format'])
+ 					{
+						case  'extended':
+							//$body .= "\n\n".lang('Event Details follow').":\n";
+							$body = '';
+							$body .= "<br>".lang('Event Details follow')." :: ";
+							foreach($event_arr as $key => $val)
+							{
+								// titulo
+								if($key =='title')
+								{
+									$var1 = $val['field'];
+									$vardata1 = $details[$key];
+								}
+								//descricao
+								if($key =='description')
+								{
+									$var2 = $val['field'];
+									$vardata2 = $details[$key];
+								}
+if($key =='description1')
+								{
+									$var2 = $val['field'];
+									$vardata2 = $details[$key];
+								}
+
+if($key =='participants1')
+								{
+									$var2 = $val['field'];
+									$vardata2 = $details[$key];
+								}
+
+								//dt inicio
+								if($key =='startdate')
+								{
+									switch(trim($part_prefs['common']['dateformat']))
+									{
+										
+										case ($part_prefs['common']['dateformat'] === "m/d/Y" || $part_prefs['common']['dateformat'] === "m-d-Y" || $part_prefs['common']['dateformat'] === "m.d.Y"):
+											$var3 = $val['field'];
+											$vardata3 = $details[$key];
+											$newmounth3 = substr($vardata3,0,2);
+											$newday3 = substr($vardata3,3,2);
+											$newyear3 = substr($vardata3,6,4);
+											$newall3 =$newyear3.$newmounth3.$newday3;
+											break;
+											
+										case	($part_prefs['common']['dateformat'] === "Y/d/m" || $part_prefs['common']['dateformat'] === "Y-d-m" || $part_prefs['common']['dateformat'] === "Y.d.m"):
+
+											$var3 = $val['field'];
+											$vardata3 = $details[$key];
+											$newyear3 = substr($vardata3,0,4);
+											$newday3 = substr($vardata3,5,2);
+											$newmounth3 = substr($vardata3,8,2);
+											$newall3 =$newyear3.$newmounth3.$newday3;
+											break;
+
+										case ($part_prefs['common']['dateformat'] === "Y/m/d" || $part_prefs['common']['dateformat'] === "Y-m-d" || $part_prefs['common']['dateformat'] === "Y.m.d"):
+
+											$var3 = $val['field'];
+											$vardata3 = $details[$key];
+											$newyear3 = substr($vardata3,0,4);
+											$newmounth3 = substr($vardata3,5,2);
+											$newday3 = substr($vardata3,8,2);
+											$newall3 =$newyear3.$newmounth3.$newday3;
+											break;
+
+										case ($part_prefs['common']['dateformat'] === "d/m/Y" || $part_prefs['common']['dateformat'] === "d-m-Y" || $part_prefs['common']['dateformat'] === "d.m.Y" || $part_prefs['common']['dateformat'] === "d-M-Y"):
+										
+											$var3 = $val['field'];
+											$vardata3 = $details[$key];
+											$newday3 = substr($vardata3,0,2);
+											$newmounth3 = substr($vardata3,3,2);
+											$newyear3 = substr($vardata3,6,4);
+											$newall3 =$newyear3.$newmounth3.$newday3;
+											break;
+									
+									}
+									
+								}
+								//dt final
+								if($key =='enddate')
+								{
+									$var4 = $val['field'];
+									$vardata4 = $details[$key];
+								}
+								//localizacao
+								if($key =='location')
+								{
+									$var8 = $val['field'];
+									$vardata8 = $details[$key];
+								}
+								//participantes
+								if($key =='participants')
+								{
+									$var5 = $val['field'];
+									foreach($val['data'] as $NewKey => $NewVal)
+									{
+										//Research inside of ldap ( Pesquisa dentro do ldap )
+										$newvalue = $this->so->search_uidNumber($to);
+										foreach($newvalue as $tmp)
+										{
+											$tmpmail = $tmp['mail'][0];
+											$tmpuid = $tmp['uidnumber'][0];
+											if( trim($tmpmail) == trim($to) & trim($tmpuid) == trim($NewKey))
+											{
+													if($typesend == 3)
+													{
+
+														$lang1 = lang("To See Commitment");
+														$varbuttom = "<form action=".$GLOBALS['phpgw_info']['server']['webserver_url']."/index.php?menuaction=infolog.uiinfolog.view&cal_id=$event[id]&date=$newall3' method='POST'>
+																		  		  <input type='submit' value='$lang1'>
+																		  		   </form>";
+														$lang2 = lang("To accept");
+														$varbuttom1 ="<input type='submit' value='$lang2' onClick='javascript:window.open(\"".$GLOBALS['phpgw_info']['server']['webserver_url']."/index.php?menuaction=infolog.uiinfolog.set_action&cal_id=$event[id]&action=3&response=1\",\"frontpage\",\"height=100,width=400,statusbar=no,toolbar=no,scrollbars=no,menubar=no,left=300,top=200\")'>";
+
+														$lang3 = lang("To reject");
+														$varbuttom2 ="<input type='submit' value='$lang3' onClick='javascript:window.open(\"".$GLOBALS['phpgw_info']['server']['webserver_url']."/index.php?menuaction=infolog.uiinfolog.set_action&cal_id=$event[id]&action=0&response=0\",\"frontpage\",\"height=100,width=400,statusbar=no,toolbar=no,scrollbars=no,menubar=no,left=300,top=200\")'>";
+														
+														$lang4 = lang("Alarm");
+														$varbuttom3 = "<form action=".$GLOBALS['phpgw_info']['server']['webserver_url']."/index.php?menuaction=infolog.uialarm.manager method='POST'>
+																		  		  <input type='submit' value='$lang4'>
+																		  		  <input type='hidden' name='cal_id' value=$event[id]>
+																		  		   </form>";
+													}
+													else
+													{
+															$varbuttom  = "";
+															$varbuttom1 = "";
+															$varbuttom2 = "";
+															$varbuttom3 = "";
+													}
+											}
+											// It only mounts variavel with the name of the participants of the list ( Monta a variavel somente com o nome dos participantes da lista)
+											if($typesend == 3)
+											{
+												list($tmpvardata5,$tmp2vardata5) = explode("(",$NewVal);
+												$vardata5 = $tmpvardata5."<br>";
+											}
+											else
+											{
+												$vardata5 = $NewVal."<br>";
+											}
+										
+										}
+										$vardata6 .= $vardata5;
+										unset($vardata5);
+									}
+								}		
+							}
+							//To mount the message as text/html (Para montar a mensagem como text/html - /phpgwapi/inc/class.send.inc.php )
+							$content_type = "text/html";
+							//It mounts the body of the message (Monta o corpo da mensagem)
+							
+							// A constante PHPGW_APP_TPL nao existe para envio de alarmes (cront, asyncservice).
+							define ("PHPGW_APP_TPL",PHPGW_API_INC . "/../../infolog/templates/".$GLOBALS['phpgw_info']['user']['preferences']['common']['template_set']."");
+							
+							$body = CreateObject('phpgwapi.Template',PHPGW_APP_TPL);
+							$body->set_file(Array('infolog' => 'body_email.tpl'));
+							$body->set_block('infolog','list');
+							$var = Array(
+								'script'			=> $script,
+								'subject'			=> $body1,
+								'var1'				=> $var1,
+								'vardata1'			=> $vardata1,
+								'var2'				=> $var2,
+								'vardata2'			=> $vardata2,
+								'var3'				=> $var3,
+								'vardata3'			=> $vardata3,
+								'var4'				=> $var4,
+								'vardata4'			=> $vardata4,
+								'var5'				=> $var5,
+								'vardata6'			=> $vardata6,
+								'var8'				=> $var8,
+								'vardata8'			=> $vardata8,							
+								'varbuttom'			=> $varbuttom,
+								'varbuttom1'		=> $varbuttom1,
+								'varbuttom2'		=> $varbuttom2,
+								'varbuttom3'		=> $varbuttom3 
+								
+							);
+							$body->set_var($var);
+							$tmpbody = $body->fp('out','list');
+														
+							break;
+
+						case 'ical':
+							$content_type = "infolog; method=$method; name=infolog.ics";
+/*							if ($body != '')
+							{
+								$boundary = '----Message-Boundary';
+								$body .= "\n\n\n$boundary\nContent-type: text/$content_type\n".
+									"Content-Disposition: inline\nContent-transfer-encoding: 7BIT\n\n";
+								$content_type = '';
+							}
+*/
+							$body = ExecMethod('infolog.boiinfolog.export',array(
+								'l_event_id'  => $event['id'],
+								'method'      => $method,
+								'chunk_split' => False
+							));
+							break;
+					}
+					$mail->AddAddress($to);
+					$mail->Body = $tmpbody;
+					$mail->From = $sender;
+					$mail->FromName = $GLOBALS['phpgw_info']['user']['fullname'];
+					$mail->Sender = $mail->From;
+					$mail->SenderName = $mail->FromName;
+					$mail->Subject = $subject;
+					unset($vardata5);
+					unset($vardata6);
+				}
+			}
+
+			//Inicializa variï¿œvel de retorno.
+			$returncode=true;			
+			if(count($mail->to)) {				
+				// Envia aviso a todos os participantes.
+				if(!$mail->Send()) {				
+					$returncode = false;
+					$errorInfo['participants'] = $mail->ErrorInfo;
+				}
+				else
+					$returncode =  true;
+			}
+			
+			if(count($to_notify) && $this->ex_participants){
+				$mail->ClearAllRecipients();
+				$var = explode(",",trim($this->ex_participants));
+				$to = array();
+				if(!$subject) {
+					$details['startdate'] = $GLOBALS['phpgw']->common->show_date($starttime);
+					$details['enddate']   = $GLOBALS['phpgw']->common->show_date($endtime);
+					list($subject,$body1) = split("\n",$GLOBALS['phpgw']->preferences->parse_notify($notify_msg,$details),2);
+				}
+				
+				foreach($var as $index => $ex_participant){
+					$ex_participant = trim($ex_participant);
+					$ex_participant = preg_replace('#"(.*)" <(.*)\@(.*)\.(.*)>#','\\2@\\3.\\4',$ex_participant);
+						if($ex_participant)
+							$to[] = $ex_participant;
+				}		
+				
+				foreach($to as $i => $to_array)
+					$mail->AddAddress($to_array);
+				
+				$_body = explode("<hr size='1' width='100%'>",$tmpbody);
+				$tmpbody = $_body[0] ? $_body[0] : $subject ;
+				$tmpbody.= "<br><b>".lang("external participants").":: </b> ".htmlentities($this->ex_participants);
+				$tmpbody.= "<br>".lang("Summary").": ".$this->so->cal->event[title]."<br>";
+				$tmpbody.= "<br>".lang("Start time").": ".$GLOBALS['phpgw']->common->show_date($starttime)."<br>".lang("End date").": ".$GLOBALS['phpgw']->common->show_date($endtime)."<br>";
+				$tmpbody.= "<br><br><hr size='1' width='100%'><font color='red'>"
+				.lang("This message was sent by server. You must send a message to sender to confirm this event")."<br>"
+				.lang("This is an external event. Even if it added to your expresso its can be changed any time at all")."</font><br>";
+				
+				if ($GLOBALS['boinfolog']->so->cal->event[start][month] > 10)
+					$event_month=$GLOBALS['boinfolog']->so->cal->event[start][month];
+				else
+					$event_month="0".$GLOBALS['boinfolog']->so->cal->event[start][month];
+				//attach extern vcard			
+				// define('context','$GLOBALS.boinfolog.so.cal.event');
+				$tmpattach = $this->create_vcard($GLOBALS['boinfolog']->so->cal->event);
+				if($tmpattach){					
+					$tmpbody .="<a href='../index.php?menuaction=infolog.uiinfolog.add&date="
+					.$GLOBALS['boinfolog']->so->cal->event[start][year]
+				.$event_month
+				.$GLOBALS['boinfolog']->so->cal->event[start][mday]
+					."&hour=".$GLOBALS['boinfolog']->so->cal->event[start][hour]
+					."&minute=".$GLOBALS['boinfolog']->so->cal->event[start][min]
+					."&title=".$GLOBALS['boinfolog']->so->cal->event['title']
+					."&description=".$GLOBALS['boinfolog']->so->cal->event['description']
+."&description1=".$GLOBALS['boinfolog']->so->cal->event['description1']
+."&participants1=".$GLOBALS['boinfolog']->so->cal->event['participants1']
+					."&location=".$GLOBALS['boinfolog']->so->cal->event['location']."'>"
+					."<h2>".lang("Add to my expresso")."</h2>";
+					$tempdir = $GLOBALS['phpgw_info']['server']['temp_dir'] . SEP;
+					srand((double)microtime()*1000000);
+					$random_number = rand(100000000,999999999);
+					$newfilename = md5(time() . getenv("REMOTE_ADDR") . $random_number );
+					$filename = $tempdir . $newfilename;
+					$attach_fd = fopen($filename,"w+");
+					fwrite($attach_fd,$tmpattach);
+					$mail->AddAttachment($filename, "extern.vcard", "base64", "text/plain"); // "application/octet-stream"
+					fclose($attach_fd);
+				}
+				$mail->From = $sender;
+				$mail->FromName = $GLOBALS['phpgw_info']['user']['fullname'];
+				$mail->Sender = $mail->From;
+				$mail->SenderName = $mail->FromName;
+				$mail->Subject = lang("External event from Expresso");
+				$mail->Body = $tmpbody;
+																		
+				if(!$mail->Send()) 
+				{
+					$returncode=false;
+					$errorInfo['ex_participants'] = $mail->ErrorInfo;
+				}
+				else
+				{
+					$returncode=true;
+				}
+			}
+
+
+			if((is_int($this->user) && $this->user != $temp_user['account_id']) ||
+				(is_string($this->user) && $this->user != $temp_user['account_lid']))
+			{
+				$GLOBALS['phpgw_info']['user'] = $temp_user;
+			}	
+
+			$GLOBALS['phpgw_info']['user']['preferences']['common']['tz_offset'] = $temp_tz_offset;
+			$GLBOALS['phpgw']->datetime->tz_offset = ((60 * 60) * $temp_tz_offset);
+			$GLOBALS['phpgw_info']['user']['preferences']['common']['timeformat'] = $temp_timeformat;
+			$GLOBALS['phpgw_info']['user']['preferences']['common']['dateformat'] = $temp_dateformat;
+			
+			// Notifica por email o criador do compromisso, com as possï¿œveis falhas.						
+			if($errorInfo) {
+				$tmpbody = "<font color='red'>".lang("The following commitment had problems for DELIVERING the NOTIFICATION messages.").".</font><br>";
+				$tmpbody.= "<br>".lang("Summary").": ".$this->so->cal->event[title]."<br>";
+				$tmpbody.= "<br>".lang("Start time").": ".$GLOBALS['phpgw']->common->show_date($starttime)."<br>".lang("End date").": ".$GLOBALS['phpgw']->common->show_date($endtime)."<br>";				
+				$tmpbody.= "<br><u>".lang("Failed to delivery")."</u><br>";
+				$failed = false;				
+				if(strstr($errorInfo['participants'],"recipients_failed")){
+					$failed	= explode("recipients_failed",$errorInfo['participants']);
+					$tmpbody.= lang("to").": ".$failed[1];
+				}
+				if(strstr($errorInfo['ex_participants'],"recipients_failed")){
+					$failed = explode("recipients_failed",$errorInfo['ex_participants']);
+					$tmpbody.= lang("to").": ".$failed[1];
+				}			
+				if(!$failed) {
+					$tmpbody.= lang("Description").":<br>";
+$tmpbody.= lang("Description1").":<br>";
+$tmpbody.= lang("participants1").":<br>";
+					$tmpbody.= $errorInfo['participants'];
+					$tmpbody.= "<br>".$errorInfo['ex_participants'];
+				}
+				$tmpbody.= "<br>".lang("Subject").": ".$subject;
+				// Reinicializa o objeto, devido ao erro anterior na entrega.
+				
+				$mail = new PHPMailer();
+				$mail->IsSMTP();
+				$mail->Host = $emailadmin['smtpServer'];
+				$mail->Port = $emailadmin['smtpPort'];
+				$mail->From = $sender;
+				$mail->FromName = $GLOBALS['phpgw_info']['user']['fullname'];
+				$mail->Sender = $mail->From;
+				$mail->SenderName = $mail->FromName;
+				$mail->IsHTML(True);
+				$mail->Subject = lang("infolog event")." - ".lang("email notification");							
+				$mail->Body = $tmpbody;					
+				$mail->AddAddress($sender);
+				if(!$mail->Send())			
+					$returncode = false;
+				else
+					$returncode =  true;
+			}
+			return $returncode;
+		}
+		
+		function send_alarm($alarm)
+		{
+			//echo "<p>boinfolog::send_alarm("; print_r($alarm); echo ")</p>\n";
+			
+			$GLOBALS['phpgw_info']['user']['account_id'] = $this->owner = $alarm['owner'];
+
+			if (!$alarm['enabled'] || !$alarm['owner'] || !$alarm['cal_id'] || !($event = $this->so->read_entry($alarm['cal_id'])))
+			{
+				return False;	// event not found
+			}
+			if ($alarm['all'])
+			{
+				$to_notify = $event['participants'];
+			}
+			elseif ($this->check_perms(PHPGW_ACL_READ,$event))	// checks agains $this->owner set to $alarm[owner]
+			{
+				$to_notify[$alarm['owner']] = 'A';
+			}
+			else
+			{
+				return False;	// no rights
+			}
+			return $this->send_update(MSG_ALARM,$to_notify,$event,False,$alarm['owner']);
+		}
+
+		function get_alarms($event_id)
+		{
+			return $this->so->get_alarm($event_id);
+		}
+
+		function alarm_today($event,$today,$starttime)
+		{
+			$found = False;
+			@reset($event['alarm']);
+			$starttime_hi = $GLOBALS['phpgw']->common->show_date($starttime,'Hi');
+			$t_appt['month'] =$GLOBALS['phpgw']->common->show_date($today,'m');
+			$t_appt['mday'] = $GLOBALS['phpgw']->common->show_date($today,'d');
+			$t_appt['year'] = $GLOBALS['phpgw']->common->show_date($today,'Y');
+			$t_appt['hour'] = $GLOBALS['phpgw']->common->show_date($starttime,'H');
+			$t_appt['min']  = $GLOBALS['phpgw']->common->show_date($starttime,'i');
+			$t_appt['sec']  = 0;
+			$t_time = $this->maketime($t_appt) - $GLOBALS['phpgw']->datetime->tz_offset;
+			$y_time = $t_time - 86400;
+			$tt_time = $t_time + 86399;
+			print_debug('T_TIME',$t_time.' : '.$GLOBALS['phpgw']->common->show_date($t_time));
+			print_debug('Y_TIME',$y_time.' : '.$GLOBALS['phpgw']->common->show_date($y_time));
+			print_debug('TT_TIME',$tt_time.' : '.$GLOBALS['phpgw']->common->show_date($tt_time));
+			while(list($key,$alarm) = each($event['alarm']))
+			{
+				if($alarm['enabled'])
+				{
+					print_debug('TIME',$alarm['time'].' : '.$GLOBALS['phpgw']->common->show_date($alarm['time']).' ('.$event['id'].')');
+					if($event['recur_type'] != MCAL_RECUR_NONE)   /* Recurring Event */
+					{
+						print_debug('Recurring Event');
+						if($alarm['time'] > $y_time && $GLOBALS['phpgw']->common->show_date($alarm['time'],'Hi') < $starttime_hi && $alarm['time'] < $t_time)
+						{
+							$found = True;
+						}
+					}
+					elseif($alarm['time'] > $y_time && $alarm['time'] < $t_time)
+					{
+						$found = True;
+					}
+				}
+			}
+			print_debug('Found',$found);
+			return $found;
+		}
+
+		function prepare_recipients(&$new_event,$old_event)
+		{
+						
+			// Find modified and deleted users.....
+			while(list($old_userid,$old_status) = each($old_event['participants']))
+			{
+				if(isset($new_event['participants'][$old_userid]))
+				{
+					print_debug('Modifying event for user',$old_userid);
+					$this->modified[(int)$old_userid] = $new_status;
+				}
+				else
+				{
+					print_debug('Deleting user from the event',$old_userid);
+					$this->deleted[(int)$old_userid] = $old_status;
+				}
+			}
+			// Find new users.....
+			while(list($new_userid,$new_status) = each($new_event['participants']))
+			{
+				if(!isset($old_event['participants'][$new_userid]))
+				{
+					print_debug('Adding event for user',$new_userid);
+					$this->added[$new_userid] = 'U';
+					$new_event['participants'][$new_userid] = 'U';
+				}
+			}
+			
+			if(count($this->added) > 0 || count($this->modified) > 0 || count($this->deleted) > 0)
+			{
+				if(count($this->added) > 0)
+				{
+					$this->ex_participants = '';
+					$this->send_update(MSG_ADDED,$this->added,'',$new_event);
+				}
+				if(count($this->modified) > 0)
+				{
+					$this->send_update(MSG_MODIFIED,$this->modified,$old_event,$new_event);
+				}
+				if(count($this->deleted) > 0)
+				{
+					$this->ex_participants = '';
+					$this->send_update(MSG_DELETED,$this->deleted,$old_event);
+				}
+			}
+		}
+
+		function remove_doubles_in_cache($firstday,$lastday)
+		{
+			$already_moved = Array();
+			for($v=$firstday;$v<=$lastday;$v++)
+			{
+				if (!$this->cached_events[$v])
+				{
+					continue;
+				}
+				$cached = $this->cached_events[$v];
+				$this->cached_events[$v] = array();
+				while (list($g,$event) = each($cached))
+				{
+					$end = date('Ymd',$this->maketime($event['end']));
+					print_debug('EVENT',_debug_array($event,False));
+					print_debug('start',$start);
+					print_debug('v',$v);
+
+					if (!isset($already_moved[$event['id']]) || $event['recur_type'] && $v > $end)
+					{
+						$this->cached_events[$v][] = $event;
+						$already_moved[$event['id']] = 1;
+						print_debug('Event moved');
+					}
+				}
+			}
+		}
+
+		function get_dirty_entries($lastmod=-1)
+		{
+			$events = false;
+			$event_ids = $this->so->cal->list_dirty_events($lastmod);
+			if(is_array($event_ids))
+			{
+				foreach($event_ids as $key => $id)
+				{
+					$events[$id] = $this->so->cal->fetch_event($id);
+				}
+			}
+			unset($event_ids);
+
+			$rep_event_ids = $this->so->cal->list_dirty_events($lastmod,$true);
+			if(is_array($rep_event_ids))
+			{
+				foreach($rep_event_ids as $key => $id)
+				{
+					$events[$id] = $this->so->cal->fetch_event($id);
+				}
+			}
+			unset($rep_event_ids);
+
+			return $events;
+		}
+
+		function _debug_array($data)
+		{
+			echo '<br>UI:';
+			_debug_array($data);
+		}
+
+		/*!
+		@function rejected_no_show
+		@abstract checks if event is rejected from user and he's not the owner and dont want rejected
+		@param $event to check
+		@returns True if event should not be shown
+		*/
+		function rejected_no_show($event)
+		{
+			$ret = !$this->prefs['infolog']['show_rejected'] &&
+				$event['owner'] != $this->owner &&
+				$event['participants'][$this->owner] == 'R';
+			//echo "<p>rejected_no_show($event[title])='$ret': user=$this->owner, event-owner=$event[owner], status='".$event['participants'][$this->owner]."', show_rejected='".$this->prefs['infolog']['show_rejected']."'</p>\n";
+			return $ret;
+		}
+
+		/* This is called only by list_cals().  It was moved here to remove fatal error in php5 beta4 */
+		function list_cals_add($id,&$users,&$groups)
+		{
+			$name = $GLOBALS['phpgw']->common->grab_owner_name($id);
+			if (($type = $GLOBALS['phpgw']->accounts->get_type($id)) == 'g')
+			{
+				$arr = &$groups;
+			}
+			else
+			{
+				$arr = &$users;
+			}
+			$arr[$name] = Array(
+				'grantor' => $id,
+				'value'   => ($type == 'g' ? 'g_' : '') . $id,
+				'name'    => $name
+			);
+		}
+
+		/*!
+		@function list_cals
+		@abstract generate list of user- / group-infologs for the selectbox in the header
+		@returns alphabeticaly sorted array with groups first and then users
+		*/
+		function list_cals()
+		{
+			$users = $groups = array();
+			foreach($this->grants as $id => $rights)
+			{
+				$this->list_cals_add($id,$users,$groups);
+			}
+			
+			//by JakJr, melhora de performance na abertura da agenda
+			/*if ($memberships = $GLOBALS['phpgw']->accounts->membership($GLOBALS['phpgw_info']['user']['account_id']))
+			{
+				foreach($memberships as $group_info)
+				{
+					$this->list_cals_add($group_info['account_id'],$users,$groups);
+
+					if ($account_perms = $GLOBALS['phpgw']->acl->get_ids_for_location($group_info['account_id'],PHPGW_ACL_READ,'infolog'))
+					{
+						foreach($account_perms as $id)
+						{
+							$this->list_cals_add($id,$users,$groups);
+						}
+					}
+				}
+			}*/
+			uksort($users,'strnatcasecmp');
+			uksort($groups,'strnatcasecmp');
+
+			return $users + $groups;	// users first and then groups, both alphabeticaly
+		}
+
+	  function translate($key,$vars=false, $not_found='*' )
+	  {
+    	    if ($this->async)
+	      return $GLOBALS['phpgw']->translation->translate_async($key, $vars);
+	    return lang($key, $vars);
+	  }
+
+		/*!
+		@function event2array
+		@abstract create array with name, translated name and readable content of each attributes of an event
+		@syntax event2array($event,$sep='<br>')
+		@param $event event to use
+		@returns array of attributes with fieldname as key and array with the 'field'=translated name \
+			'data' = readable content (for participants this is an array !)
+		*/
+		function event2array($event)
+		{
+			
+			
+		  $var['title'] = Array(
+				'field'		=> $this->translate('Title'),
+				'data'		=> $event['title']
+			);
+
+			// Some browser add a \n when its entered in the database. Not a big deal
+			// this will be printed even though its not needed.
+			$var['description'] = Array(
+				'field'	=> $this->translate('Description'),
+				'data'	=> $event['description']
+			);
+$var['description1'] = Array(
+				'field'	=> $this->translate('Description1'),
+				'data'	=> $event['description1']
+			);
+
+
+$var['participants1'] = Array(
+				'field'	=> $this->translate('participants1'),
+				'data'	=> $event['participants1']
+			);
+
+			$var['ex_participants'] = Array(
+				'field'	=> $this->translate('External Participants'),
+				'data'	=> $event['ex_participants']
+			);
+			
+			$cats = Array();
+			$this->cat->categories($this->bo->owner,'infolog');
+			if(strpos($event['category'],','))
+			{
+				$cats = explode(',',$event['category']);
+			}
+			else
+			{
+				$cats[] = $event['category'];
+			}
+			foreach($cats as $cat_id)
+			{
+				list($cat) = $this->cat->return_single($cat_id);
+				$cat_string[] = $cat['name'];
+			}
+			$var['category'] = Array(
+				'field'	=> $this->translate('Category'),
+				'data'	=> implode(', ',$cat_string)
+			);
+
+			$var['location'] = Array(
+				'field'	=> $this->translate('Location'),
+				'data'	=> $event['location']
+			);
+
+			$var['startdate'] = Array(
+				'field'	=> $this->translate('Start Date/Time'),
+				'data'	=> $GLOBALS['phpgw']->common->show_date($this->maketime($event['start']) - $GLOBALS['phpgw']->datetime->tz_offset),
+			);
+
+			$var['enddate'] = Array(
+				'field'	=> $this->translate('End Date/Time'),
+				'data'	=> $GLOBALS['phpgw']->common->show_date($this->maketime($event['end']) - $GLOBALS['phpgw']->datetime->tz_offset)
+			);
+
+			$pri = Array(
+				1	=> lang('Low'),
+				2	=> lang('Normal'),
+				3	=> lang('High')
+			);
+			$var['priority'] = Array(
+				'field'	=> lang('Priority'),
+				'data'	=> $pri[$event['priority']]
+			);
+
+			$var['owner'] = Array(
+				'field'	=> lang('Created By'),
+				'data'	=> $GLOBALS['phpgw']->common->grab_owner_name($event['owner'])
+			);
+
+			$var['updated'] = Array(
+				'field'	=> lang('Updated'),
+				'data'	=> $GLOBALS['phpgw']->common->show_date($this->maketime($event['modtime']) - $GLOBALS['phpgw']->datetime->tz_offset)
+			);
+
+			$var['access'] = Array(
+				'field'	=> lang('Access'),
+				'data'	=> $event['public'] ? lang('Public') : lang('Private')
+			);
+
+			if(@isset($event['groups'][0]))
+			{
+				$cal_grps = '';
+				for($i=0;$i<count($event['groups']);$i++)
+				{
+					if($GLOBALS['phpgw']->accounts->exists($event['groups'][$i]))
+					{
+						$cal_grps .= ($i>0?'<br>':'').$GLOBALS['phpgw']->accounts->id2name($event['groups'][$i]);
+					}
+				}
+
+				$var['groups'] = Array(
+					'field'	=> lang('Groups'),
+					'data'	=> $cal_grps
+				);
+			}
+
+			$participants = array();
+			foreach($event['participants'] as $user => $short_status)
+			{
+				if($GLOBALS['phpgw']->accounts->exists($user))
+				{
+					$participants[$user] = $GLOBALS['phpgw']->common->grab_owner_name($user).' ('.$this->get_long_status($short_status).')';
+				}
+			}
+			$var['participants'] = Array(
+				'field'	=> $this->translate('Participants'),
+				'data'	=> $participants
+			);
+
+			// Repeated Events
+			if($event['recur_type'] != MCAL_RECUR_NONE)
+			{
+				$str = lang($this->rpt_type[$event['recur_type']]);
+
+				$str_extra = array();
+				if ($event['recur_enddate']['mday'] != 0 && $event['recur_enddate']['month'] != 0 && $event['recur_enddate']['year'] != 0)
+				{
+					$recur_end = $this->maketime($event['recur_enddate']);
+					if($recur_end != 0)
+					{
+						$recur_end -= $GLOBALS['phpgw']->datetime->tz_offset;
+						$str_extra[] = lang('ends').': '.lang($GLOBALS['phpgw']->common->show_date($recur_end,'l')).', '.$this->long_date($recur_end).' ';
+					}
+				}
+				// only weekly uses the recur-data (days) !!!
+				if($event['recur_type'] == MCAL_RECUR_WEEKLY)
+				{
+					$repeat_days = array();
+					foreach ($this->rpt_day as $mcal_mask => $dayname)
+					{
+						if ($event['recur_data'] & $mcal_mask)
+						{
+							$repeat_days[] = lang($dayname);
+						}
+					}
+					if(count($repeat_days))
+					{
+						$str_extra[] = lang('days repeated').': '.implode(', ',$repeat_days);
+					}
+				}
+				if($event['recur_interval'] != 0)
+				{
+					$str_extra[] = lang('Interval').': '.$event['recur_interval'];
+				}
+
+				if(count($str_extra))
+				{
+					$str .= ' ('.implode(', ',$str_extra).')';
+				}
+
+				$var['recure_type'] = Array(
+					'field'	=> lang('Repetition'),
+					'data'	=> $str,
+				);
+			}
+
+			if (!isset($this->fields))
+			{
+				$this->custom_fields = CreateObject('infolog.bocustom_fields');
+				$this->fields = &$this->custom_fields->fields;
+				$this->stock_fields = &$this->custom_fields->stock_fields;
+			}
+			foreach($this->fields as $field => $data)
+			{
+				if (!$data['disabled'])
+				{
+					if (isset($var[$field]))
+					{
+						$sorted[$field] = $var[$field];
+					}
+					elseif (!isset($this->stock_fields[$field]) && strlen($event[$field]))	// Custom field
+					{
+						$lang = lang($name = substr($field,1));
+						$sorted[$field] = array(
+							'field' => $lang == $name.'*' ? $name : $lang,
+							'data'  => $event[$field]
+						);
+					}
+				}
+				unset($var[$field]);
+			}
+			foreach($var as $name => $v)
+			{
+				$sorted[$name] = $v;
+
+			}
+			return $sorted;
+		}
+
+		/*!
+		@function check_set_default_prefs
+		@abstract sets the default prefs, if they are not already set (on a per pref. basis)
+		@note It sets a flag in the app-session-data to be called only once per session
+		*/
+		function check_set_default_prefs()
+		{
+			if (($set = $GLOBALS['phpgw']->session->appsession('default_prefs_set','infolog')))
+			{
+				return;
+			}
+			$GLOBALS['phpgw']->session->appsession('default_prefs_set','infolog','set');
+
+			//$default_prefs = $GLOBALS['phpgw']->preferences->default['infolog']; jakjr
+
+			$subject = $this->translate('infolog Event') . ' - $$action$$: $$startdate$$ $$title$$'."\n";
+			$defaults = array(
+				'defaultinfolog' => 'week',
+				'mainscreen_showevents' => '0',
+				'summary'         => 'no',
+				'receive_updates' => 'no',
+				'update_format'   => 'extended',	// leave it to extended for now, as iCal kills the message-body
+				'notifyAdded'     => $subject . $this->translate ('You have a meeting scheduled for %1',array('$$startdate$$')),
+				'notifyCanceled'  => $subject . $this->translate ('Your meeting scheduled for %1 has been canceled',array('$$startdate$$')),
+				'notifyModified'  => $subject . $this->translate ('Your meeting that had been scheduled for %1 has been rescheduled to %2',array('$$olddate$$','$$startdate$$')),
+				'notifyResponse'  => $subject . $this->translate ('On %1 %2 %3 your meeting request for %4', array('$$date$$','$$fullname$$','$$action$$','$$startdate$$')),
+				'notifyAlarm'     => $this->translate('Alarm for %1 at %2 in %3',array('$$title$$','$$startdate$$','$$location$$')) . "\n" . $this->translate('Here is your requested alarm.'),
+				'show_rejected'   => '0',
+				'hide_event_conflict'   => '0',
+				'display_status'  => '1',
+				'weekdaystarts'   => 'Monday',
+				'workdaystarts'   => '9',
+				'workdayends'     => '17',
+				'interval'        => '30',
+				'defaultlength'   => '60',
+				'planner_start_with_group' => $GLOBALS['phpgw']->accounts->name2id('Default'),
+				'planner_intervals_per_day'=> '4',
+				'defaultfilter'   => 'all',
+				'default_private' => '0',
+				'display_minicals'=> '1',
+				'print_black_white'=>'0'
+			);
+			foreach($defaults as $var => $default)
+			{
+				if (!isset($default_prefs[$var]) || $default_prefs[$var] == '')
+				{
+					$GLOBALS['phpgw']->preferences->add('infolog',$var,$default,'default');
+					$need_save = True;
+				}
+			}
+			if ($need_save)
+			{
+				$prefs = $GLOBALS['phpgw']->preferences->save_repository(False,'default');
+				$this->prefs['infolog'] = $prefs['infolog'];
+			}
+			if ($this->prefs['infolog']['send_updates'] && !isset($this->prefs['infolog']['receive_updates']))
+			{
+				$this->prefs['infolog']['receive_updates'] = $this->prefs['infolog']['send_updates'];
+				$GLOBALS['phpgw']->preferences->add('infolog','receive_updates',$this->prefs['infolog']['send_updates']);
+				$GLOBALS['phpgw']->preferences->delete('infolog','send_updates');
+				$prefs = $GLOBALS['phpgw']->preferences->save_repository();
+			}
+		}
+
+		// return array with all infolog categories (for xmlrpc)
+		function categories($complete = False)
+		{
+			return $GLOBALS['server']->categories($complete);
+		}
+/******************************************/
+
+function insert($params='')
+		{
+
+			if($params['from_mobile']) {
+				$ui_return = "mobile.ui_mobileinfolog.edit";
+				$ui_index = "mobile.ui_mobileinfolog.index";
+				$ui_overlap = "mobile.ui_mobileinfolog.overlap";
+			}
+			else {
+				$ui_return = "infolog.uiinfolog.edit";
+				$ui_index = "infolog.uiinfolog.index";		
+				$ui_overlap = "infolog.uiinfolog.overlap";
+			}
+
+			if(!is_object($GLOBALS['phpgw']->datetime))
+			{
+				$GLOBALS['phpgw']->datetime = createobject('phpgwapi.date_time');
+			}
+			//$l_cal = (@isset($params['info_type']) && $params['info_type']?$params['info_type']:$_POST['info_type']);
+			$l_participants = (@$params['participants']?$params['participants']:$_POST['participants']);
+			$this->ex_participants = (@$params['ex_participants']?$params['ex_participants']:$_POST['ex_participants']);
+			$l_categories = (@$params['categories']?$params['categories']:$_POST['categories']);
+			$l_start = (@isset($params['start']) && $params['start']?$params['start']:$_POST['start']);
+			$l_end = (@isset($params['end']) && $params['end']?$params['end']:$_POST['end']);
+			$l_recur_enddate = (@isset($params['recur_enddate']) && $params['recur_enddate']?$params['recur_enddate']:$_POST['recur_enddate']);
+
+			
+			$send_to_ui = True;
+			//if ((!is_array($l_start) || !is_array($l_end)) && !isset($_GET['readsess']))	// xmlrpc call
+			if ($this->xmlrpc)	// xmlrpc call
+			{
+
+				$send_to_ui = False;
+
+				$l_cal = $params;	// no extra array
+
+				foreach(array('start','end','recur_enddate') as $name)
+				{
+					$var = 'l_'.$name;
+					$$var = $GLOBALS['server']->iso86012date($params[$name]);
+					unset($l_cal[$name]);
+				}
+
+				if (!is_array($l_participants) || !count($l_participants))
+				{
+					$l_participants = array($GLOBALS['phpgw_info']['user']['account_id'].'A');
+				}
+				else
+				{
+					$l_participants = array();
+					foreach($params['participants'] as $user => $data)
+					{
+						$l_participants[] = $user.$data['status'];
+					}
+				}
+
+				unset($l_cal['participants']);
+
+				if (!is_object($GLOBALS['phpgw']->categories))
+				{
+					$GLOBALS['phpgw']->categories = CreateObject('phpgwapi.categories');
+				}
+
+				$l_categories = $GLOBALS['server']->xmlrpc2cats($params['category']);
+				unset($l_cal['category']);
+
+				// using access={public|private} in all modules via xmlrpc
+				$l_cal['public'] = $params['access'] != 'private';
+				unset($l_cal['access']);
+/*
+				$fp = fopen('/tmp/xmlrpc.log','a+');
+				ob_start();
+				echo "\nboinfolog::update("; print_r($params); echo ")\n";
+				//echo "\nl_start="; print_r($l_start);
+				//echo "\nl_end="; print_r($l_end);
+				fwrite($fp,ob_get_contents());
+				ob_end_clean();
+				fclose($fp);
+*/
+			}
+
+			print_debug('ID',$l_cal['id']);
+
+			// don't wrap to the next day for no time
+			if ($l_end['hour'] == 24 && $l_end['min'] == 0)
+			{
+				$l_end['hour'] = 23;
+				$l_end['min'] = 59;
+			}
+
+			if(isset($_GET['readsess']))
+			{
+
+				$event = $this->restore_from_appsession();
+				$event['title'] = stripslashes($event['title']);
+				$event['description'] = stripslashes($event['description']);
+$event['description1'] = stripslashes($event['description1']);
+$event['participants1'] = stripslashes($event['participants1']);
+				$event['ex_participants'] = stripslashes($event['ex_participants']);
+				$datetime_check = $this->validate_update($event);
+				if($datetime_check)
+				{
+					ExecMethod($ui_return,
+						Array(
+							'cd'		=> $datetime_check,
+							'readsess'	=> 1
+						)
+					);
+					if(!$params['from_mobile'])
+						$GLOBALS['phpgw']->common->phpgw_exit(True);
+					else
+						return;
+				}
+				$overlapping_events = False;
+			}
+			else
+			{
+
+				if((!$l_cal['id'] && !$this->check_perms(PHPGW_ACL_ADD)) ||
+				   ($l_cal['id'] && !$this->check_perms(PHPGW_ACL_EDIT,$l_cal['id'])))
+				{
+
+					if ($this->xmlrpc)
+					{
+						$GLOBALS['server']->xmlrpc_error($GLOBALS['xmlrpcerr']['no_access'],$GLOBALS['xmlrpcstr']['no_access']);
+					}
+					if (!$send_to_ui)
+					{
+						return array(($l_cal['id']?1:2) => 'permission denied');
+					}
+
+					ExecMethod($ui_index);
+
+
+					if(!$params['from_mobile'])
+						$GLOBALS['phpgw']->common->phpgw_exit();
+					else
+						return;
+				}
+
+				print_debug('Prior to fix_update_time()');
+
+				$this->fix_update_time($l_start);
+
+				$this->fix_update_time($l_end);
+
+				if(!isset($l_cal['ex_participants']))
+				{
+					$l_cal['ex_participants'] = $this->ex_participants;
+				}
+
+				if(!isset($l_categories))
+				{
+					$l_categories = 0;
+				}
+
+				$is_public = ($l_cal['type'] != 'private' && $l_cal['type'] != 'privateHiddenFields');
+
+				$this->so->event_init();
+
+			$this->add_attribute('uid',$l_cal['uid']);
+
+				$this->add_attribute('type',$l_cal['type']);
+
+				if($l_cal['ex_participants']) {
+					$this->add_attribute('ex_participants',$l_cal['ex_participants']);
+				}
+				if(count($l_categories) >= 2)
+				{
+					$this->so->set_category(implode(',',$l_categories));
+				}
+				else
+				{
+					$this->so->set_category(strval($l_categories[0]));
+				}
+				$this->so->set_title($l_cal['title']);
+				$this->so->set_description($l_cal['description']);
+$this->so->set_description1($l_cal['description1']);
+$this->so->set_participants1($l_cal['participants1']);
+				$this->so->set_ex_participants($l_cal['ex_participants']);
+				$this->so->set_start($l_start['year'],$l_start['month'],$l_start['mday'],$l_start['hour'],$l_start['min'],0);
+				$this->so->set_end($l_end['year'],$l_end['month'],$l_end['mday'],$l_end['hour'],$l_end['min'],0);
+				$this->so->set_class($is_public);
+				$this->so->add_attribute('reference',(@isset($l_cal['reference']) && $l_cal['reference']?$l_cal['reference']:0));
+				$this->so->add_attribute('location',(@isset($l_cal['location']) && $l_cal['location']?$l_cal['location']:''));
+				if($l_cal['id'])
+				{
+					$this->so->add_attribute('id',$l_cal['id']);
+				}
+
+				if($l_cal['rpt_use_end'] != 'y')
+				{
+					$l_recur_enddate['year'] = 0;
+					$l_recur_enddate['month'] = 0;
+					$l_recur_enddate['mday'] = 0;
+				}
+				elseif (isset($l_recur_enddate['str']))
+				{
+					$l_recur_enddate = $this->jscal->input2date($l_recur_enddate['str'],False,'mday');
+				}
+
+				switch((int)$l_cal['recur_type'])
+				{
+
+
+					case MCAL_RECUR_NONE:
+						$this->so->set_recur_none();
+
+						$repetido = (int)$l_cal['recur_type']; // recebe o tipo de repeticao;
+
+						break;
+					case MCAL_RECUR_DAILY:
+						$this->so->set_recur_daily((int)$l_recur_enddate['year'],(int)$l_recur_enddate['month'],(int)$l_recur_enddate['mday'],(int)$l_cal['recur_interval']);
+
+						$repetido = (int)$l_cal['recur_type']; // recebe o tipo de repeticao;
+
+						$tmp_init = explode("/",$_POST['start']['str']); // recebe a data inicial da repeticao;
+						$init_rept = mktime($_POST['start']['hour'],$_POST['start']['min'],0,intval($tmp_init[1]),$tmp_init[0],$tmp_init[2]); // transforma a data inicial + hora + minuto em UNIX timestamp;
+
+						if($l_cal['rpt_use_end'] == 'y') // verifica se foi indicada data final da repeticao, se sim:
+						{
+
+							$tmp_end = explode("/",$_POST['recur_enddate']['str']); // recebe data final da repeticao;
+							$end_rept = mktime($_POST['start']['hour'],$_POST['start']['min'],0,intval($tmp_end[1]),$tmp_end[0],$tmp_end[2]); // transforma a data inicial + hora e minuto de inicio da repeticao em UNIX timestamp;
+
+						}else { // se nao existe data final da repeticao (agendamento infinito):
+
+							$end_rept = 0; // variavel recebe zero e nao sera adicionado nenhum alarme para o agendamento;
+						}
+						break;
+					case MCAL_RECUR_WEEKLY:
+
+						$repetido = (int)$l_cal['recur_type']; // recebe o tipo de repeticao;
+
+						$tmp_init = explode("/",$_POST['start']['str']); // recebe a data inicial da repeticao;
+						$init_rept = mktime($_POST['start']['hour'],$_POST['start']['min'],0,intval($tmp_init[1]),$tmp_init[0],$tmp_init[2]); // transforma a data inicial + hora + minuto em UNIX timestamp;
+
+						if($l_cal['rpt_use_end'] == 'y') // verifica se foi indicada data final da repeticao, se sim:
+						{
+
+							$tmp_end = explode("/",$_POST['recur_enddate']['str']); // recebe data final da repeticao;
+							$end_rept = mktime($_POST['start']['hour'],$_POST['start']['min'],0,intval($tmp_end[1]),$tmp_end[0],$tmp_end[2]); // transforma a data inicial + hora e minuto de inicio da repeticao em UNIX timestamp;
+
+						}else { // se nao existe data final da repeticao (agendamento infinito):
+
+							$end_rept = 0; // variavel recebe zero e nao sera adicionado nenhum alarme para o agendamento;
+						}
+
+						foreach(array('rpt_sun','rpt_mon','rpt_tue','rpt_wed','rpt_thu','rpt_fri','rpt_sat') as $rpt_day)
+						{
+							$l_cal['recur_data'] += (int)$l_cal[$rpt_day];
+						}
+						if (is_array($l_cal['rpt_day']))
+						{
+							foreach ($l_cal['rpt_day'] as $mask)
+							{
+								$l_cal['recur_data'] |= (int)$mask;
+								$rpt_wdays = $l_cal['recur_data']; // recebe os dias da semana (somatorio) para repetir o alarme;
+							}
+						}
+						$this->so->set_recur_weekly((int)$l_recur_enddate['year'],(int)$l_recur_enddate['month'],(int)$l_recur_enddate['mday'],(int)$l_cal['recur_interval'],$l_cal['recur_data']);
+						break;
+					case MCAL_RECUR_MONTHLY_MDAY:
+						$this->so->set_recur_monthly_mday((int)$l_recur_enddate['year'],(int)$l_recur_enddate['month'],(int)$l_recur_enddate['mday'],(int)$l_cal['recur_interval']);
+
+						$repetido = (int)$l_cal['recur_type']; // recebe o tipo de repeticao;
+
+						$tmp_init = explode("/",$_POST['start']['str']); // recebe a data inicial da repeticao;
+						$init_rept = mktime($_POST['start']['hour'],$_POST['start']['min'],0,intval($tmp_init[1]),$tmp_init[0],$tmp_init[2]); // transforma a data inicial + hora + minuto em UNIX timestamp;
+
+						if($l_cal['rpt_use_end'] == 'y') // verifica se foi indicada data final da repeticao, se sim:
+						{
+
+							$tmp_end = explode("/",$_POST['recur_enddate']['str']); // recebe data final da repeticao;
+							$end_rept = mktime($_POST['start']['hour'],$_POST['start']['min'],0,intval($tmp_end[1]),$tmp_end[0],$tmp_end[2]); // transforma a data inicial + hora e minuto de inicio da repeticao em UNIX timestamp;
+
+						}else { // se nao existe data final da repeticao (agendamento infinito):
+
+							$end_rept = 0; // variavel recebe zero e nao sera adicionado nenhum alarme para o agendamento;
+						}
+						break;
+					case MCAL_RECUR_MONTHLY_WDAY:
+						$this->so->set_recur_monthly_wday((int)$l_recur_enddate['year'],(int)$l_recur_enddate['month'],(int)$l_recur_enddate['mday'],(int)$l_cal['recur_interval']);
+						break;
+					case MCAL_RECUR_YEARLY:
+						$this->so->set_recur_yearly((int)$l_recur_enddate['year'],(int)$l_recur_enddate['month'],(int)$l_recur_enddate['mday'],(int)$l_cal['recur_interval']);
+
+						$repetido = (int)$l_cal['recur_type']; // recebe o tipo de repeticao;
+
+						$tmp_init = explode("/",$_POST['start']['str']); // recebe a data inicial da repeticao;
+						$init_rept = mktime($_POST['start']['hour'],$_POST['start']['min'],0,intval($tmp_init[1]),$tmp_init[0],$tmp_init[2]); // transforma a data inicial + hora + minuto em UNIX timestamp;
+
+						if($l_cal['rpt_use_end'] == 'y') // verifica se foi indicada data final da repeticao, se sim:
+						{
+
+							$tmp_end = explode("/",$_POST['recur_enddate']['str']); // recebe data final da repeticao;
+							$end_rept = mktime($_POST['start']['hour'],$_POST['start']['min'],0,intval($tmp_end[1]),$tmp_end[0],$tmp_end[2]); // transforma a data inicial + hora e minuto de inicio da repeticao em UNIX timestamp;
+
+						}else { // se nao existe data final da repeticao (agendamento infinito):
+
+							$end_rept = 0; // variavel recebe zero e nao sera adicionado nenhum alarme para o agendamento;
+						}
+						break;
+				}
+
+				if($l_participants)
+				{
+					$parts = $l_participants;
+					$minparts = min($l_participants);
+					$part = Array();
+					for($i=0;$i<count($parts);$i++)
+					{
+						if (($accept_type = substr($parts[$i],-1,1)) == '0' || (int)$accept_type > 0)
+						{
+							$accept_type = 'U';
+						}
+						$acct_type = $GLOBALS['phpgw']->accounts->get_type((int)$parts[$i]);
+						if($acct_type == 'u')
+						{
+							$part[(int)$parts[$i]] = $accept_type;
+						}
+						elseif($acct_type == 'g')
+						{
+							$part[(int)$parts[$i]] = $accept_type;
+							$groups[] = $parts[$i];
+							/* This pulls ALL users of a group and makes them as participants to the event */
+							/* I would like to turn this back into a group thing. */
+							$acct = CreateObject('phpgwapi.accounts',(int)$parts[$i]);
+							$members = $acct->member((int)$parts[$i]);
+							unset($acct);
+							if($members == False)
+							{
+								continue;
+							}
+							while($member = each($members))
+							{
+								$part[$member[1]['account_id']] = $accept_type;
+							}
+						}
+					}
+				}
+				else
+				{
+					$part = False;
+				}
+
+				if($part)
+				{
+					@reset($part);
+					while(list($key,$accept_type) = each($part))
+					{
+						$this->so->add_attribute('participants',$accept_type,(int)$key);
+					}
+				}
+
+				if($groups)
+				{
+					@reset($groups);
+					$this->so->add_attribute('groups',(int)$group_id);
+				}
+
+				$event = $this->get_cached_event();
+				if(!is_int($minparts))
+				{
+					$minparts = $this->owner;
+				}
+				if(!@isset($event['participants'][$l_cal['owner']]))
+				{
+					$this->so->add_attribute('owner',$minparts);
+				}
+				else
+				{
+					$this->so->add_attribute('owner',$l_cal['owner']);
+				}
+				$this->so->add_attribute('priority',$l_cal['priority']);
+
+				foreach($l_cal as $name => $value)
+				{
+					if ($name[0] == '#')	// Custom field
+					{
+						$this->so->add_attribute($name,stripslashes($value));
+					}
+				}
+				if (isset($_POST['preserved']) && is_array($preserved = unserialize(stripslashes($_POST['preserved']))))
+				{
+					foreach($preserved as $name => $value)
+					{
+						switch($name)
+						{
+							case 'owner':
+								$this->so->add_attribute('participants',$value,$l_cal['owner']);
+								break;
+							default:
+								$this->so->add_attribute($name,str_replace(array('&amp;','&quot;','&lt;','&gt;'),array('&','"','<','>'),$value));
+						}
+					}
+				}
+				$event = $this->get_cached_event();
+
+				if ($l_cal['alarmdays'] > 0 || $l_cal['alarmhours'] > 0 ||
+						$l_cal['alarmminutes'] > 0)
+				{
+					$offset = ($l_cal['alarmdays'] * 24 * 3600) +
+						($l_cal['alarmhours'] * 3600) + ($l_cal['alarmminutes'] * 60);
+
+					$time = $this->maketime($event['start']) - $offset;
+
+					$event['alarm'][] = Array(
+						'time'    => $time,
+						'offset'  => $offset,
+						'owner'   => $this->owner,
+						'enabled' => 1,
+						'repeat'  => $repetido, // para repetir alarme;
+						'init_rept' => $init_rept, // inicio repeticao;
+						'end_rept' => $end_rept, // fim repeticao;
+						'rpt_wdays' => $rpt_wdays // dias repeticao da semana;
+					);
+				}
+
+				$this->store_to_appsession($event);
+				$datetime_check = $this->validate_update($event);
+				print_debug('bo->validated_update() returnval',$datetime_check);
+
+				/*if($datetime_check)
+				{
+
+
+					if (!$send_to_ui)
+					{
+						return array($datetime_check => 'invalid input data');
+					}
+
+
+					ExecMethod($ui_return,
+						Array(
+							'cd'		=> $datetime_check,
+							'readsess'	=> 1
+						)
+					);
+
+					if(!$params['from_mobile'])
+						$GLOBALS['phpgw']->common->phpgw_exit(True);
+					else
+						return;
+
+				}/*/
+				if($event['id'])
+				{
+					$event_ids[] = $event['id'];
+				}
+				if($event['reference'])
+				{
+					$event_ids[] = $event['reference'];
+				}
+
+				$overlapping_events = $this->overlap(
+					$this->maketime($event['start']),
+					$this->maketime($event['end']),
+					$event['participants'],
+					$event['owner'],
+					$event_ids
+				);
+			}
+			if(!$this->ex_participants)
+				$this->ex_participants = $event['ex_participants'];			
+			if($overlapping_events)
+			{
+
+
+				if($send_to_ui)
+				{
+					$event['ex_participants'] = $this->ex_participants;
+					unset($GLOBALS['phpgw_info']['flags']['noheader']);
+					unset($GLOBALS['phpgw_info']['flags']['nonavbar']);
+					ExecMethod($ui_overlap,
+						Array(
+							'o_events'	=> $overlapping_events,
+							'this_event'	=> $event
+						)
+					);
+					if(!$params['from_mobile'])
+						$GLOBALS['phpgw']->common->phpgw_exit(True);
+					else
+						return;
+				}
+				else
+				{
+					return $overlapping_events;
+				}
+			}
+			else
+			{
+
+
+
+				if(!$event['id'])
+				{
+
+
+
+					$this->so->add_entry($event);
+					$this->send_update(MSG_ADDED,$event['participants'],'',$this->get_cached_event());
+					print_debug('New Event ID',$event['id']);
+				}
+				else
+				{
+
+					print_debug('Updating Event ID',$event['id']);
+					$new_event = $event;
+					$old_event = $this->read_entry($event['id']);
+					// if old event has alarm and the start-time changed => update them
+					//echo "<p>checking ".count($old_event['alarm'])." alarms of event #$event[id] start moved from ".print_r($old_event['start'],True)." to ".print_r($event['start'],True)."</p>\n";
+					if ($old_event['alarm'] &&
+						$this->maketime($old_event['start']) != $this->maketime($event['start']))
+					{
+						$this->so->delete_alarms($old_event['id']);
+						foreach($old_event['alarm'] as $id => $alarm)
+						{
+							$alarm['time'] = $this->maketime($event['start']) - $alarm['offset'];
+							$event['alarm'][] = $alarm;
+						}
+						//echo "updated alarms<pre>".print_r($event['alarm'],True)."</pre>\n";
+					}
+					$this->so->cal->event = $event;
+					$this->so->add_entry($event);
+					$this->prepare_recipients($new_event,$old_event);
+				}
+
+				$date = sprintf("%04d%02d%02d",$event['start']['year'],$event['start']['month'],$event['start']['mday']);
+				if($send_to_ui)
+				{
+					$this->read_sessiondata();
+					if ($this->return_to)
+					{
+						$GLOBALS['phpgw']->redirect_link('/index.php','menuaction='.$this->return_to);
+						$GLOBALS['phpgw']->common->phpgw_exit();
+					}
+					Execmethod($ui_index);
+				}
+				else
+				{
+					return (int)$event['id'];
+				}
+			}
+			return True;
+		}
+
+
+/***********************************************/
+	}
+?>
Index: /contrib/ProjectManager/inc/class.soinfolog_sql.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.soinfolog_sql.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.soinfolog_sql.inc.php	(revision 3594)
@@ -0,0 +1,86 @@
+<?php
+  /**************************************************************************\
+  * eGroupWare - infolog                                                    *
+  * http://www.eGroupWare.org                                                *
+  * Maintained and further developed by RalfBecker@outdoor-training.de       *
+  * Based on Webinfolog by Craig Knudsen <cknudsen@radix.net>               *
+  *          http://www.radix.net/~cknudsen                                  *
+  * Originaly modified by Mark Peters <skeeter@phpgroupware.org>             *
+  * --------------------------------------------                             *
+  *  This program is free software; you can redistribute it and/or modify it *
+  *  under the terms of the GNU General Public License as published by the   *
+  *  Free Software Foundation; either version 2 of the License, or (at your  *
+  *  option) any later version.                                              *
+  \**************************************************************************/
+
+
+	if (@$GLOBALS['phpgw_info']['flags']['included_classes']['soinfolog_'])
+	{
+		return;
+	}
+
+	$GLOBALS['phpgw_info']['flags']['included_classes']['soinfolog_'] = True;
+
+	class soinfolog_ extends soinfolog__
+	{
+		var $deleted_events = Array();
+
+		var $cal_event;
+		var $today = Array('raw','day','month','year','full','dow','dm','bd');
+
+
+		function soinfolog_()
+		{
+			$this->soinfolog__();
+
+			if (!is_object($GLOBALS['phpgw']->asyncservice))
+			{
+				$GLOBALS['phpgw']->asyncservice = CreateObject('phpgwapi.asyncservice');
+			}
+			$this->async = &$GLOBALS['phpgw']->asyncservice;
+		}
+
+		function get_event_ids()
+		{
+			$from = $where = ' ';
+			
+
+			$sql = 'SELECT DISTINCT  info_id, info_type,
+info_from,info_subject,info_owner,info_datemodified,info_startdate, info_enddate,info_uid,
+info_priority  '
+					. 'FROM phpgw_infolog';
+					//. $from;
+					//. 'WHERE (phpgw_cal_user.info_id = phpgw_cal.info_id) '
+					//. $where . $extra;
+
+//echo "SQL : ".$sql ."<br>\n";
+
+			if($this->debug)
+			{
+				echo "FULL SQL : ".$sql."<br>\n";
+			}
+
+			$this->stream->query($sql,__LINE__,__FILE__);
+
+			$retval = Array();
+			if($this->stream->num_rows() == 0)
+			{
+				if($this->debug)
+				{
+					echo "No records found!<br>\n";
+				}
+				return $retval;
+			}
+
+			while($this->stream->next_record())
+			{
+				$retval[] = (int)$this->stream->f('info_id');
+			}
+			if($this->debug)
+			{
+				echo "Records found!<br>\n";
+			}
+			return $retval;
+		}
+
+	}
Index: /contrib/ProjectManager/inc/Documento no guardado 1
===================================================================
--- /contrib/ProjectManager/inc/Documento no guardado 1	(revision 3594)
+++ /contrib/ProjectManager/inc/Documento no guardado 1	(revision 3594)
@@ -0,0 +1,660 @@
+
+
+function search()
+		{
+
+
+			
+
+			unset($GLOBALS['phpgw_info']['flags']['noheader']);
+			unset($GLOBALS['phpgw_info']['flags']['nonavbar']);
+			$GLOBALS['phpgw_info']['flags']['app_header'] = $GLOBALS['phpgw_info']['apps']['infolog']['title'].' - '.lang('Search Results');
+			$GLOBALS['phpgw']->common->phpgw_header();
+
+			$error = '';
+
+			$matches = 0;
+
+			// There is currently a problem searching in with repeated events.
+			// It spits back out the date it was entered.  I would like to to say that
+			// it is a repeated event.
+
+			// This has been solved by the little icon indicator for recurring events.
+
+$event = $this->bo->read_entry($info_id);
+
+			$event_ids = $this->bo->search_keywords($_POST['keywords']);
+
+			foreach($event_ids as $key => $id)
+			{
+			
+
+				$event = $this->bo->read_entry($id);
+
+				
+				$datetime = $this->bo->maketime($event['start']) - $GLOBALS['phpgw']->datetime->tz_offset;
+
+				$info[strval($event['id'])] = array(
+					'tr_color'	=> $GLOBALS['phpgw']->nextmatchs->alternate_row_color(),
+					'date'		=> $GLOBALS['phpgw']->common->show_date($datetime),
+					'link'		=> $this->link_to_entry($event,$event['start']['month'],$event['start']['mday'],$event['start']['year'])
+				);
+
+
+
+if(!$this->bo->check_perms(PHPGW_ACL_READ,$event))
+				{
+					continue;
+				}
+
+			}
+			$matches = count($event_ids);
+
+			if ($matches == 1)
+			{
+				$quantity = lang('1 match found').'.';
+			}
+			elseif ($matches > 0)
+			{
+				$quantity = lang('%1 matches found',$matches).'.';
+			}
+			else
+			{
+				echo '<b>'.lang('Error').':</b>'.lang('no matches found');
+				return;
+			}
+
+			$p = $GLOBALS['phpgw']->template;
+			$p->set_file(
+				Array(
+					'search_form'	=> 'search.tpl'
+				)
+			);
+			$p->set_block('search_form','search','search');
+			$p->set_block('search_form','search_list_header','search_list_header');
+			$p->set_block('search_form','search_list','search_list');
+			$p->set_block('search_form','search_list_footer','search_list_footer');
+
+			$var = Array(
+				'th_bg'		=> $this->theme['th_bg'],
+				'search_text'	=> lang('Search Results'),
+				'quantity'	=> $quantity
+			);
+			$p->set_var($var);
+
+			if($matches > 0)
+			{
+				$p->parse('rows','search_list_header',True);
+			}
+
+//if($info){
+			foreach($info as $id => $data)
+			{
+				$p->set_var($data);
+				$p->parse('rows','search_list',True);
+			}
+//	}
+			if($matches > 0)
+			{
+				$p->parse('rows','search_list_footer',True);
+			}
+
+			$p->pparse('out','search');
+		}
+
+
+
+
+
+
+function month()
+		{
+			echo $this->printer_friendly($this->get_month(),lang('Monthview'));
+			$GLOBALS['phpgw']->common->phpgw_footer();
+		}
+
+function get_month()
+		{
+			
+			
+		}
+
+
+
+function get_todos(&$todo_label)
+		{
+			$todos_from_hook = $GLOBALS['phpgw']->hooks->process(array(
+				'location'  => 'infolog_include_todos',
+				'year'      => $this->bo->year,
+				'month'     => $this->bo->month,
+				'day'       => $this->bo->day,
+				'owner'     => $this->bo->owner	// num. id of the user, not necessary current user
+			));
+
+			if(is_array($todo_label))
+			{
+				list($label,$showall)=$todo_label;
+			}
+			else
+			{
+				$label=$todo_label;
+				$showall=true;
+			}
+			$maxshow = (int)$GLOBALS['phpgw_info']['user']['preferences']['infolog']['mainscreen_maxshow'];
+			if($maxshow<=0)
+			{
+				$maxshow=10;
+			}
+			//print_debug("get_todos(): label=$label; showall=$showall; max=$maxshow");
+
+			$content = $todo_label = '';
+			if (is_array($todos_from_hook) && count($todos_from_hook))
+			{
+				$todo_label = !empty($label)?$label:lang("open ToDo's:");
+
+				foreach($todos_from_hook as $todos)
+				{
+					$i = 0;
+					if (is_array($todos) && count($todos))
+					{
+						foreach($todos as $todo)
+						{
+							if(!$showall && ($i++>$maxshow))
+							{
+								break;
+							}
+							$icons = '';
+							foreach($todo['icons'] as $name => $app)
+							{
+								$icons .= ($icons?' ':'').$GLOBALS['phpgw']->html->image($app,$name,lang($name),'border="0" width="15" height="15"');
+							}
+							$class = $class == 'row_on' ? 'row_off' : 'row_on';
+
+//NDEE <tr starts here
+							$content .= " <tr id=\"debug\" class=\"$class\">\n  <td valign=\"top\" width=\"15%\"nowrap>".
+								($this->bo->printer_friendly?$icons:$GLOBALS['phpgw']->html->a_href($icons,$todo['view'])).
+								"</td>\n  <td>".($this->bo->printer_friendly?$todo['title']:
+								$GLOBALS['phpgw']->html->a_href($todo['title'],$todo['view']))."</td>\n </tr>\n";
+						}
+					}
+				}
+			}
+			if (!empty($content))
+			{
+				return "<table border=\"0\" width=\"100%\">\n$content</table>\n";
+			}
+			return False;
+		}
+
+
+
+
+
+
+/****************************************/
+
+
+
+
+
+
+function link_to_entry($event,$month,$day,$year)
+		{
+
+
+
+			$str = '';
+			$is_private = !$event['public'] && !$this->bo->check_perms(PHPGW_ACL_READ,$event);
+			$viewable = !$this->bo->printer_friendly && $this->bo->check_perms(PHPGW_ACL_READ,$event);
+
+			$starttime = $this->bo->maketime($event['start']) - $GLOBALS['phpgw']->datetime->tz_offset;
+			$endtime = $this->bo->maketime($event['end']) - $GLOBALS['phpgw']->datetime->tz_offset;
+			$rawdate = mktime(0,0,0,$month,$day,$year);
+			$rawdate_offset = $rawdate - $GLOBALS['phpgw']->datetime->tz_offset;
+			$nextday = mktime(0,0,0,$month,$day + 1,$year) - $GLOBALS['phpgw']->datetime->tz_offset;
+			if ((int)$GLOBALS['phpgw']->common->show_date($starttime,'Hi') && $starttime == $endtime)
+			{
+				$time = $GLOBALS['phpgw']->common->show_date($starttime,$this->bo->users_timeformat);
+			}
+			elseif ($starttime <= $rawdate_offset && $endtime >= $nextday - 60)
+			{
+				$time = '[ '.lang('All Day').' ]';
+			}
+			elseif ((int)$GLOBALS['phpgw']->common->show_date($starttime,'Hi') || $starttime != $endtime)
+			{
+				if($starttime < $rawdate_offset && $event['recur_type'] == MCAL_RECUR_NONE)
+				{
+					$start_time = $GLOBALS['phpgw']->common->show_date($rawdate_offset,$this->bo->users_timeformat);
+				}
+				else
+				{
+					$start_time = $GLOBALS['phpgw']->common->show_date($starttime,$this->bo->users_timeformat);
+				}
+
+				if($endtime >= ($rawdate_offset + 86400))
+				{
+					$end_time = $GLOBALS['phpgw']->common->show_date(mktime(23,59,59,$month,$day,$year) - $GLOBALS['phpgw']->datetime->tz_offset,$this->bo->users_timeformat);
+				}
+				else
+				{
+					$end_time = $GLOBALS['phpgw']->common->show_date($endtime,$this->bo->users_timeformat);
+				}
+				$time = $start_time.'-'.$end_time;
+			}
+			else
+			{
+				$time = '';
+			}
+			
+			$texttitle = $texttime = $textdesc = $textlocation = $textstatus = '';
+
+			
+			
+			if(!$is_private)
+			{
+				//$text .= $this->bo->display_status($event['users_status']);
+				
+				// split text for better display by templates, also see $texttime $texttitle $textdesc $textlocation
+
+				$textstatus=$this->bo->display_status($event['users_status']); 
+				
+			}
+
+			/*
+			$text = '<nobr>&nbsp;'.$time.'&nbsp;</nobr> '.$this->bo->get_short_field($event,$is_private,'title').$text.
+				(!$is_private && $event['description'] ? ': <i>'.$this->bo->get_short_field($event,$is_private,'description').'</i>':'').
+				$GLOBALS['phpgw']->browser->br;
+			*/
+			
+			$texttime=$time;
+			$texttitle=$this->bo->get_short_field($event,$is_private,'title');
+
+			$textdesc=(!$is_private && $event['description'] ? $this->bo->get_short_field($event,$is_private,'info_des'):'');
+echo "descrip".$textdesc."<br>".$event.".";
+			// added $textlocation but this must be activated in the actual pict_link.tpl file of the used template set
+			$textlocation=$this->bo->get_short_field($event,$is_private,'info_cat');
+
+			if ($viewable)
+			{
+				$date = sprintf('%04d%02d%02d',$year,$month,$day);
+				$this->link_tpl->set_var('link_link',$this->page('view','&cal_id='.$event['id'].'&date='.$date));
+				$this->link_tpl->set_var('lang_view',lang('View this entry'));
+				$this->link_tpl->set_var('desc', $textdesc);
+				$this->link_tpl->set_var('location', $textlocation);
+				$this->link_tpl->parse('picture','link_open',True);
+			}
+			if (!$is_private)
+			{
+				if($event['priority'] == 3)
+				{
+					$picture[] = Array(
+						'pict'	=> $GLOBALS['phpgw']->common->image('calendar','high'),
+						'width'	=> 16,
+						'height'=> 16,
+						'title' => lang('high priority')
+					);
+				}
+				if($event['recur_type'] == MCAL_RECUR_NONE)
+				{
+					$picture[] = Array(
+						'pict'	=> $GLOBALS['phpgw']->common->image('calendar','circle'),
+						'width'	=> 9,
+						'height'=> 9,
+						'title' => lang('single event')
+					);
+				}
+				else
+				{
+					$picture[] = Array(
+						'pict'	=> $GLOBALS['phpgw']->common->image('calendar','recur'),
+						'width'	=> 12,
+						'height'=> 12,
+						'title' => lang('recurring event')
+					);
+				}
+			}
+
+
+foreach($event as $dato)
+
+
+
+
+			$participants = $this->planner_participants($event['participants']);
+
+			if(count($event['participants']) > 1)
+			{
+				$picture[] = Array(
+					'pict'	=> $GLOBALS['phpgw']->common->image('calendar','multi_3'),
+					'width'	=> 14,
+					'height'=> 14,
+					'title' => $participants
+				);
+			}
+			else
+			{
+				$picture[] = Array(
+					'pict'	=>  $GLOBALS['phpgw']->common->image('calendar','single'),
+					'width'	=> 14,
+					'height'=> 14,
+					'title' => $participants
+				);
+			}
+			if($event['public'] == 0)
+			{
+				$picture[] = Array(
+					'pict'	=> $GLOBALS['phpgw']->common->image('calendar','private'),
+					'width'	=> 13,
+					'height'=> 13,
+					'title' => lang('private')
+				);
+			}
+			if(@isset($event['alarm']) && count($event['alarm']) >= 1 && !$is_private)
+			{
+				// if the alarm is to go off the day before the event
+				// the icon does not show up because of 'alarm_today'
+				// - TOM
+				if($this->bo->alarm_today($event,$rawdate_offset,$starttime))
+				{
+					$picture[] = Array(
+						'pict'  => $GLOBALS['phpgw']->common->image('calendar','alarm'),
+						'width' => 13,
+						'height'=> 13,
+						'title' => lang('alarm')
+					);
+				}
+			}
+
+			$description = $this->bo->get_short_field($event,$is_private,'description');
+			for($i=0;$i<count($picture);$i++)
+			{
+				$var = Array(
+					'pic_image' => $picture[$i]['pict'],
+					'width'     => $picture[$i]['width'],
+					'height'    => $picture[$i]['height'],
+					'title'     => $picture[$i]['title']
+				);
+				$this->output_template_array($this->link_tpl,'picture','pict',$var);
+			}
+			if ($texttitle)
+			{
+				$var = Array(
+					'text' => $text,
+					'time'=> $texttime,
+					'title'=> $texttitle,
+					'users_status'=>$textstatus,
+					'desc'=> 'dessccccc'.$textdesc,
+					'location'=> "<br><b>Local:</b> ".$textlocation
+				);
+				$this->output_template_array($this->link_tpl,'picture','link_text',$var);
+			}
+
+			if ($viewable)
+			{
+				$this->link_tpl->parse('picture','link_close',True);
+			}
+			$str = $this->link_tpl->fp('out','link_pict');
+			$this->link_tpl->set_var('picture','');
+			$this->link_tpl->set_var('out','');
+			unset($p);
+			return $str;
+		}
+
+
+
+function planner_participants($parts)
+		{
+			static $id2lid;
+
+			$names = '';
+			while (list($id,$status) = each($parts))
+			{
+				$status = substr($this->bo->get_long_status($status),0,1);
+
+				if (!isset($id2lid[$id]))
+				{
+					$id2lid[$id] = $GLOBALS['phpgw']->common->grab_owner_name($id);
+				}
+				if (strlen($names))
+				{
+					$names .= ",\n";
+				}
+				$names .= $id2lid[$id]." ($status)";
+			}
+			if($this->debug)
+			{
+				echo '<!-- Inside participants() : '.$names.' -->'."\n";
+			}
+			return $names;
+		}
+
+
+function view($vcal_id=0,$cal_date=0)
+		{
+
+
+
+			unset($GLOBALS['phpgw_info']['flags']['noheader']);
+			unset($GLOBALS['phpgw_info']['flags']['nonavbar']);
+			$GLOBALS['phpgw_info']['flags']['app_header'] = $GLOBALS['phpgw_info']['apps']['infolog']['title'].' - '.lang('View');
+			$GLOBALS['phpgw_info']['flags']['noappheader'] = True;
+			$GLOBALS['phpgw']->common->phpgw_header();
+
+			$cal_id = get_var('info_id',array('GET','POST'),$vcal_id);
+
+			$date = $cal_date ? $cal_date : 0;
+			$date = $date ? $date : (int)$_GET['date'];
+	
+			// First, make sure they have permission to this entry
+			if ($cal_id < 1)
+			{
+				echo '<center>'.lang('Invalid entry id.').'</center>'."\n";
+				$GLOBALS['phpgw']->common->phpgw_exit(True);
+			}
+
+			if(!$this->bo->check_perms(PHPGW_ACL_READ,$cal_id))
+			{
+				echo '<center>'.lang('You do not have permission to read this record!').'</center>'."\n";
+				$GLOBALS['phpgw']->common->phpgw_exit(True);
+			}
+			
+			$event = $this->bo->read_entry($cal_id);
+
+			if(!isset($event['id']))
+			{
+				echo '<center>'.lang('Sorry, this event does not exist').'.'.'</center>'."\n";
+				$GLOBALS['phpgw']->common->phpgw_exit(True);
+			}
+
+			$this->bo->repeating_events = Array();
+			$this->bo->cached_events = Array();
+			$this->bo->repeating_events[0] = $event;
+			$datetime = mktime(0,0,0,$this->bo->month,$this->bo->day,$this->bo->year) - $GLOBALS['phpgw']->datetime->tz_offset;
+			$this->bo->check_repeating_events($datetime);
+			$check_date = $GLOBALS['phpgw']->common->show_date($datetime,'Ymd');
+			if(is_array($this->bo->cached_events[$check_date][0]) &&
+				$this->bo->cached_events[$check_date][0]['id'] == $event['id'])
+			{
+				$starttime = $this->bo->maketime($event['start']);
+				$endtime = $this->bo->maketime($event['end']);
+				$event['start']['month'] = $this->bo->month;
+				$event['start']['mday'] = $this->bo->day;
+				$event['start']['year'] = $this->bo->year;
+				$temp_end =  $this->bo->maketime($event['start']) + ($endtime - $starttime);
+				$event['end']['month'] = date('m',$temp_end);
+				$event['end']['mday'] = date('d',$temp_end);
+				$event['end']['year'] = date('Y',$temp_end);
+			}
+
+			if(!$this->view_event($event,True))
+			{
+				echo '<center>'.lang('You do not have permission to read this record!').'</center>';
+				$GLOBALS['phpgw']->common->phpgw_exit(True);
+			}
+						
+			$p = $GLOBALS['phpgw']->template;
+			$p->set_file(
+				Array(
+					'form_button'	=> 'form_button_script.tpl'
+				)
+			);
+
+			$button_left = $button_center = $button_right = '';
+
+			if($this->bo->check_perms(PHPGW_ACL_EDIT,$event))
+			{
+				if($event['recur_type'] != MCAL_RECUR_NONE)
+				{
+					$var = Array(
+						'action_url_button'	=> $this->page('edit','&cal_id='.$cal_id),
+						'action_text_button'	=> lang('Edit Single'),
+						'action_confirm_button'	=> '',
+						'action_extra_field'	=> $this->html->input_hidden(array(
+							'edit_type' => 'single',
+							'date' => sprintf('%04d%02d%02d',$this->bo->year,$this->bo->month,$this->bo->day)
+						))
+					);
+					$p->set_var($var);
+					$button_left .= '<td>'.$p->fp('button','form_button').'</td>';
+
+					$var = Array(
+						'action_url_button'	=> $this->page('edit','&cal_id='.$cal_id),
+						'action_text_button'	=> lang('Edit Series'),
+						'action_confirm_button'	=> '',
+						'action_extra_field'	=> $this->html->input_hidden('edit_type','series')
+					);
+					$p->set_var($var);
+					$button_left .= '<td>'.$p->fp('button','form_button').'</td>';
+				}
+				else
+				{
+					$var = Array(
+						'action_url_button'	=> $this->page('edit','&cal_id='.$cal_id),
+						'action_text_button'	=> lang('Edit'),
+						'action_confirm_button'	=> '',
+						'action_extra_field'	=> ''
+					);
+					$p->set_var($var);
+					$button_left .= '<td>'.$p->fp('button','form_button').'</td>';
+				}
+
+				$var = Array(
+					'action_url_button'	=> $GLOBALS['phpgw']->link('/index.php','menuaction=calendar.uialarm.manager'),
+					'action_text_button'	=> lang('Alarm Management'),
+					'action_confirm_button'	=> '',
+					'action_extra_field'	=> $this->html->input_hidden(array(
+						'cal_id' => $cal_id,
+						'return_to' => $this->bo->return_to
+					))
+				);
+				$p->set_var($var);
+				$button_center .= '<td>'.$p->fp('button','form_button').'</td>';
+			}
+
+			if ($this->bo->check_perms(PHPGW_ACL_DELETE,$event))
+			{
+				if($event['recur_type'] != MCAL_RECUR_NONE)
+				{
+					$var = Array(
+						'action_url_button'	=> $this->page('delete','&cal_id='.$cal_id),
+						'action_text_button'	=> lang('Delete Single'),
+						'action_confirm_button'	=> "onClick=\"return confirm('".lang('Are you sure want to delete this single occurence?\\nThis will delete this entry for all users.')."')\"",
+						'action_extra_field'	=> $this->html->input_hidden(array(
+							'delete_type' => 'single',
+							'date' => sprintf('%04d%02d%02d',$this->bo->year,$this->bo->month,$this->bo->day)
+						))
+					);
+					$p->set_var($var);
+					$button_right .= '<td>'.$p->fp('button','form_button').'</td>';
+
+					$var = Array(
+						'action_url_button'	=> $this->page('delete','&cal_id='.$cal_id),
+						'action_text_button'	=> lang('Delete Series'),
+						'action_confirm_button'	=> "onClick=\"return confirm('".lang('Are you sure want to delete this entry?\\nThis will delete this entry for all users.')."')\"",
+						'action_extra_field'	=> $this->html->input_hidden('delete_type','series')
+					);
+					$p->set_var($var);
+					$button_right .= '<td>'.$p->fp('button','form_button').'</td>';
+
+					if($event['recur_exception'])
+					{
+						$var = Array(
+							'action_url_button'	=> $this->page('reinstate_list','&cal_id='.$cal_id),
+							'action_text_button'	=> lang('Reinstate'),
+							'action_confirm_button'	=> '',
+							'action_extra_field'	=> ''
+						);
+						$p->set_var($var);
+						$button_center .= '<td>'.$p->fp('button','form_button').'</td>';
+					}
+				}
+				else
+				{
+					$var = Array(
+						'action_url_button'	=> $this->page('delete','&cal_id='.$cal_id),
+						'action_text_button'	=> lang('Delete'),
+						'action_confirm_button'	=> "onClick=\"return confirm('".lang('Are you sure want to delete this entry?\\nThis will delete this entry for all users.')."')\"",
+						'action_extra_field'	=> ''
+					);
+					$p->set_var($var);
+					$button_right .= '<td>'.$p->fp('button','form_button').'</td>';
+				}
+			}
+			else
+			{
+				// allow me (who I am logged in as) to set up an alarm
+				// if I am a participant, but not the owner
+				reset($event['participants']);
+				while (list($user,$short_status) = each($event['participants']))
+				{
+					if ($GLOBALS['phpgw_info']['user']['account_id'] == $user)
+					{
+						$var = Array(
+							'action_url_button'	=> $GLOBALS['phpgw']->link('/index.php','menuaction=calendar.uialarm.manager'),
+							'action_text_button'	=> lang('Alarm Management'),
+							'action_confirm_button'	=> '',
+							'action_extra_field'	=> $this->html->input_hidden(array(
+								'cal_id'    => $cal_id,
+								'return_to' => $this->bo->return_to
+							))
+						);
+						$p->set_var($var);
+						echo $p->fp('out','form_button');
+					}
+				}
+			}
+
+			$var = Array(
+				'action_url_button'	=> $this->page('export'),
+				'action_text_button'	=> lang('Export'),
+				'action_confirm_button'	=> '',
+				'action_extra_field'	=> $this->html->input_hidden('cal_id',$cal_id)
+			);
+			$p->set_var($var);
+			$button_center .= '<td>'.$p->fp('button','form_button').'</td>';
+
+			if ($this->bo->return_to)
+			{
+				$var = Array(
+					'action_url_button'	=> $GLOBALS['phpgw']->link('/index.php','menuaction='.$this->bo->return_to),
+					'action_text_button'	=> lang('Done'),
+					'action_confirm_button'	=> '',
+					'action_extra_field'	=> ''
+				);
+				$p->set_var($var);
+				$button_left .= '<td>'.$p->fp('button','form_button').'</td>';
+			}
+			$p->set_var(array(
+				'button_left'	=> $button_left,
+				'button_center'	=> $button_center,
+				'button_right'	=> $button_right
+			));
+			$p->pfp('phpgw_body','view_event');
+
+			$GLOBALS['phpgw']->hooks->process(array(
+				'location' => 'calendar_view',
+				'cal_id'   => $cal_id
+			));
+			
+			$GLOBALS['phpgw']->common->phpgw_footer();
+		}
Index: /contrib/ProjectManager/inc/class.uiroles.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.uiroles.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.uiroles.inc.php	(revision 3594)
@@ -0,0 +1,176 @@
+<?php
+/**
+ * ProjectManager - Roles user interface
+ *
+ * @link http://www.egroupware.org
+ * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @package projectmanager
+ * @copyright (c) 2005 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @version $Id: class.uiroles.inc.php 24830 2007-12-17 22:41:34Z ralfbecker $ 
+ */
+
+include_once(PHPGW_INCLUDE_ROOT.'/projectmanager/inc/class.boprojectmanager.inc.php');
+include_once(PHPGW_INCLUDE_ROOT.'/etemplate/inc/class.uietemplate.inc.php');
+
+define('PHPGW_ACL_ROLES',PHPGW_ACL_EDIT);	// maybe this gets an own ACL later
+
+/**
+ * ProjectManager UI: roles
+ */
+class uiroles extends boprojectmanager  
+{
+	/**
+	 * Functions to call via menuaction
+	 * 
+	 * @var array
+	 */
+	var $public_functions = array(
+		'roles' => true,
+	);
+	var $acl2id = array(
+		'read'   => PHPGW_ACL_READ,
+		'edit'   => PHPGW_ACL_EDIT,
+		'delete' => PHPGW_ACL_DELETE,
+		'add'    => PHPGW_ACL_ADD,
+		'budget' => PHPGW_ACL_BUDGET,
+		'edit_budget' => PHPGW_ACL_EDIT_BUDGET,
+	);
+	/**
+	 * Instance of the boprojectmanger class
+	 *
+	 * @var boprojectmanager
+	 */
+	var $project;
+
+	/**
+	 * Constructor, calls the constructor of the extended class
+	 * 
+	 * @return uiroles
+	 */
+	function uiroles()
+	{
+		$this->boprojectmanager(null,'roles');
+	}
+	
+	/**
+	 * Create and edit roles
+	 *
+	 * @param array $content=null
+	 */
+	function roles($content=null)
+	{
+		$tpl =& new etemplate('projectmanager.roles');
+		
+		$pm_id = is_array($content) ? $content['pm_id'] : (int) $_REQUEST['pm_id'];
+		
+		$only = !!$pm_id;
+		if (!($project_rights = $this->check_acl(PHPGW_ACL_ROLES,$pm_id)) || !$this->is_admin)
+		{
+			$only = $project_rights ? 1 : 0;
+			$readonlys['1[pm_id]'] = true;
+			
+			if (!$project_rights && !$this->is_admin)
+			{
+				$readonlys['edit'] = $readonlys['apply'] = true;
+			}
+		}
+		$role_to_edit = array('pm_id' => $only);
+		$js = 'window.focus();';
+
+		if (($content['save'] || $content['apply']) && (!$pm_id && $this->is_admin || $pm_id && $project_rights))
+		{
+			if (!$content[1]['role_title'])
+			{
+				$role_to_edit = $content[1];
+				$msg = lang('Title must not be empty');
+			}
+			else
+			{
+				$role = array(
+					'role_id'          => (int) $content[1]['role_id'],
+					'role_title'       => $content[1]['role_title'],
+					'role_description' => $content[1]['role_description'],
+					'pm_id'            => $content[1]['pm_id'] || !$this->is_admin ? $pm_id : 0,
+					'role_acl'         => 0,
+				);
+				foreach($this->acl2id as $acl => $id)
+				{
+					if ($content[1]['acl_'.$acl]) $role['role_acl'] |= $id;
+				}
+				if ($this->roles->save($role) == 0)
+				{
+					$msg = lang('Role saved');
+					
+					$js = 'opener.document.eTemplate.submit();';
+
+					if ($content['save']) $js .= 'window.close();';
+				}
+				else
+				{
+					$msg = lang('Error: saving role !!!');
+				}
+			}
+		}
+		if ($content['delete'] || $content['edit'])
+		{
+			list($role) = $content['delete'] ? each($content['delete']) : each($content['edit']);
+			if(!($role = $this->roles->read($role)) ||
+				$role['pm_id'] && !$this->check_acl(PHPGW_ACL_ROLES,$role['pm_id']) ||
+				!$role['pm_id'] && !$this->is_admin)
+			{
+				$msg = lang('Permission denied !!!');
+			}
+			elseif ($content['delete'])
+			{
+				if ($this->roles->delete($role))
+				{
+					$msg = lang('Role deleted');
+					$js = 'opener.document.eTemplate.submit();';
+				}
+				else
+				{
+					$msg = lang('Error: deleting role !!!');
+				}
+			}
+			else	// edit an existing role
+			{
+				$role_to_edit = $role;
+				foreach($this->acl2id as $acl => $id)
+				{
+					$role_to_edit['acl_'.$acl] = $role['role_acl'] & $id;
+				}
+			}
+		}
+		if (($view = !($pm_id && $project_rights) && !$this->is_admin))
+		{
+			$readonlys['save'] = $readonlys['apply'] = true;
+		}
+		$content = array(
+			'pm_id' => $pm_id,
+			'msg'   => $msg,
+			'view'  => !($pm_id && $project_rights) && !$this->is_admin,
+			'js'    => '<script>'.$js.'</script>',
+			1       => $role_to_edit,
+		);
+		$n = 2;
+		foreach((array)$this->roles->search(array(),false,'pm_id DESC,role_acl DESC','','',false,'AND',false,array('pm_id'=>array(0,$pm_id))) as $role)
+		{
+			foreach($this->acl2id as $acl => $id)
+			{
+				$role['acl_'.$acl] = $role['role_acl'] & $id;
+			}
+			$content[$n++] = $role;
+			
+			$readonlys['delete['.$role['role_id'].']'] = $readonlys['edit['.$role['role_id'].']'] =
+				!$role['pm_id'] && !$this->is_admin || $role['pm_id'] && !$this->check_acl(PHPGW_ACL_ROLES,$role['pm_id']);
+		}
+		$GLOBALS['phpgw_info']['flags']['app_header'] = lang('projectmanager').' - '.lang('Add or edit roles and their ACL');
+		$tpl->exec('projectmanager.uiroles.roles',$content,array(
+			'pm_id' => $this->query_list(),
+		),$readonlys,array(
+			'pm_id' => $pm_id,
+			1       => array('role_id' => $content[1]['role_id']),
+		),2);
+	}		
+}
Index: /contrib/ProjectManager/inc/class.soroles.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.soroles.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.soroles.inc.php	(revision 3594)
@@ -0,0 +1,50 @@
+<?php
+/**
+ * ProjectManager - Roles storage object
+ *
+ * @link http://www.egroupware.org
+ * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @package projectmanager
+ * @copyright (c) 2005 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @version $Id: class.soroles.inc.php 22355 2006-08-26 16:30:45Z ralfbecker $ 
+ */
+
+include_once(PHPGW_INCLUDE_ROOT.'/etemplate/inc/class.so_sql.inc.php');
+
+/**
+ * Roles storage object of the projectmanager
+ *
+ * Tables: phpgw_pm_roles
+ */
+class soroles extends so_sql
+{
+	/**
+	 * Constructor, calls the constructor of the extended class
+	 * 
+	 * @param int $pm_id pm_id of the project to use, default null
+	 */
+	function soroles($pm_id=null)
+	{
+		$this->so_sql('projectmanager','phpgw_pm_roles');
+
+		if ((int) $pm_id) 
+		{
+			$this->pm_id = (int) $pm_id;
+		}
+	}
+	
+	/**
+	 * reimplemented to set some defaults and order by 'pm_id DESC,role_acl DESC'
+	 *
+	 * @param string $value_col column-name for the values of the array, can also be an expression aliased with AS
+	 * @param string $key_col='' column-name for the keys, default '' = same as $value_col: returns a distinct list
+	 * @param array $filter=array() to filter the entries
+	 * @param string $order='' order, default '' = same as $value_col
+	 * @return array with key_col => value_col pairs
+	 */
+	function query_list($value_col='role_title',$key_col='role_id',$filter=array(),$order='pm_id DESC,role_acl DESC')
+	{
+		return parent::query_list($value_col,$key_col,$filter,$order);
+	}
+}
Index: /contrib/ProjectManager/inc/class.admin.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.admin.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.admin.inc.php	(revision 3594)
@@ -0,0 +1,362 @@
+<?php
+/**
+ * ProjectManager - Adminstration
+ *
+ * @link http://www.egroupware.org
+ * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @package projectmanager
+ * @copyright (c) 2005 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @version $Id: class.admin.inc.php 23852 2007-05-09 16:29:32Z lkneschke $ 
+ */
+
+require_once(PHPGW_INCLUDE_ROOT.'/etemplate/inc/class.uietemplate.inc.php');
+
+/**
+ * ProjectManager: Administration
+ */
+class admin
+{
+	/**
+	 * @var array $public_functions Functions to call via menuaction
+	 */
+	var $public_functions = array(
+		'config' => true,
+	);
+	var $accounting_types;
+	var $duration_units;
+	var $fonts;
+
+	/**
+	 * Constructor, calls the constructor of the extended class
+	 */
+	function admin()
+	{
+		if (!$GLOBALS['phpgw_info']['user']['apps']['admin'])
+		{
+			$GLOBALS['phpgw']->common->redirect_link('/index.php',array(
+				'menuaction' => 'projectmanager.uiprojectmanger.index',
+				'msg'        => lang('Permission denied !!!'),
+			));
+		}
+		$this->config =& CreateObject('phpgwapi.config','projectmanager');
+		$this->config->read_repository();
+
+		$this->accounting_types = array(
+			'status' => lang('No accounting, only status'),
+			'times'  => lang('No accounting, only times and status'),
+			'budget' => lang('Budget (no pricelist)'),
+			'pricelist' => lang('Budget and pricelist'),
+		);
+		$this->duration_units = array(
+			'd' => 'days',
+			'h' => 'hours',
+		);
+		$this->fonts = $this->get_fonts();
+	}
+	
+	/**
+	 * Edit the site configuration
+	 *
+	 * @param array $content=null
+	 */
+	function config($content=null)
+	{
+		$tpl =& new etemplate('projectmanager.config');
+		
+		if ($content['save'] || $content['apply'])
+		{
+			$content['GANTT_FONT_FILE'] = $this->get_font_file($content['GANTT_FONT'],$content['GANTT_STYLE']);
+			foreach(array('duration_units','hours_per_workday','accounting_types','allow_change_workingtimes',
+				'GANTT_FONT','LANGUAGE_CHARSET','GANTT_STYLE','GANTT_CHAR_ENCODE','GANTT_FONT_FILE') as $name)
+			{
+				$this->config->config_data[$name] = $content[$name];
+			}
+			$this->config->save_repository();
+			$msg = lang('Site configuration saved');
+		}
+		if ($content['cancel'] || $content['save'])
+		{
+			$tpl->location(array(
+				'menuaction' => 'projectmanager.uiprojectmanager.index',
+				'msg' => $msg,
+			));
+		}
+		include_once(PHPGW_INCLUDE_ROOT.'/projectmanager/inc/class.ganttchart.inc.php');
+
+		$content = $this->config->config_data;
+		if (!$content['duration_units']) $content['duration_units'] = array_keys($this->duration_units);
+		if (!$content['hours_per_workday']) $content['hours_per_workday'] = 8;
+		if (!$content['accounting_types']) $content['accounting_types'] = array_keys($this->accounting_types);
+		
+		// Ganttchart config
+		$content['jpg_msg'] = ganttchart::msg_install_new_jpgraph();
+		if (!$content['GANTT_FONT']) $content['GANTT_FONT'] = GANTT_FONT;
+		if (!$content['LANGUAGE_CHARSET']) $content['LANGUAGE_CHARSET'] = LANGUAGE_CHARSET;
+		if (!$content['GANTT_STYLE']) $content['GANTT_STYLE'] = GANTT_STYLE;
+		if (!isset($content['GANTT_CHAR_ENCODE'])) $content['GANTT_CHAR_ENCODE'] = GANTT_CHAR_ENCODE;
+		if (is_readable(TTF_DIR.$content['GANTT_FONT_FILE']))
+		{
+			$content['font_msg'] = TTF_DIR.$content['GANTT_FONT_FILE'];
+			$content['font_msg_class'] = '';
+		}
+		else
+		{
+			$content['font_msg'] = lang("Fontfile '%1' not found!!!",$content['GANTT_FONT_FILE']);
+			$content['font_msg_class'] = 'redItalic';
+		}
+		$content['msg'] = $msg;
+
+		$GLOBALS['phpgw_info']['flags']['app_header'] = lang('projectmanager').' - '.lang('Site configuration');
+		$tpl->exec('projectmanager.admin.config',$content,array(
+			'duration_units'   => $this->duration_units,
+			'accounting_types' => $this->accounting_types,
+			'allow_change_workingtimes' => array('no','yes'),
+			'GANTT_FONT' => $this->get_fonts(),
+			'GANTT_STYLE' => array(
+				FS_NORMAL => lang('normal'),
+				FS_BOLD   => lang('bold'),
+				FS_ITALIC => lang('italic'),
+				FS_BOLDITALIC => lang('bold').' & '.lang('italic'),
+			),
+			'GANTT_CHAR_ENCODE' => array(
+				1  => 'yes',
+				'' => 'No',
+			)
+		));
+	}
+	
+	/**
+	 * Get font id - name pairs the installed jpgraph understands
+	 *
+	 * @return array
+	 */
+	function get_fonts()
+	{
+		$fonts = array();
+		foreach(array(
+			// MS web iniative
+			FF_ARIAL     => 'Arial (MS)',
+			FF_COMIC     => 'Comic (MS)',
+			FF_COURIER   => 'Courier (MS)',
+			FF_GEORGIA   => 'Georgia (MS)',
+			FF_TIMES     => 'Times (MS)',
+			FF_TREBUCHE  => 'Trebuche (MS)',
+			FF_VERDANA   => 'Verdana (MS)',
+			
+			// Gnome Vera font
+			// Available from http://www.gnome.org/fonts/
+			FF_VERA      => 'Vera (Gnome)',
+			FF_VERAMONO  => 'Veramono (Gnome)',
+			FF_VERASERIF => 'Veraserif (Gnome)',
+			
+			// Chinese font
+			FF_SIMSUN    => 'Simsun (Chinese)',
+			FF_CHINESE   => 'Chinese (Chinese)',
+			FF_BIG5      => 'Big5 (Chinese)',
+			
+			// Japanese font
+			FF_MINCHO    => 'Mincho (Japanese)',
+			FF_PMINCHO   => 'PMincho (Japanese)',
+			FF_GOTHIC    => 'Gothic (Japanese)',
+			FF_PGOTHIC   => 'PGothic (Japanese)',
+			
+			// Hebrew fonts
+			FF_DAVID     => 'David (Hebrew)',
+			FF_MIRIAM    => 'Miriam (Hebrew)',
+			FF_AHRON     => 'Ahron (Hebrew)',
+			
+			// Extra fonts
+			// Download fonts from 
+			// http://www.webfontlist.com
+			// http://www.webpagepublicity.com/free-fonts.html
+			
+			FF_SPEEDO    => 'Speedo',		// This font is also known as Bauer (Used for gauge fascia)
+			FF_DIGITAL   => 'Digital',		// Digital readout font
+			FF_COMPUTER  => 'Computer',		// The classic computer font
+			FF_CALCULATOR=> 'Calculator',	// Triad font
+		) as $font => $label)
+		{
+			if (is_int($font))
+			{
+				$fonts[$font] = $label;
+			}
+		}
+		return $fonts;
+	}
+	
+	/**
+	 * Get the path of the given font or an error-message
+	 *
+	 * @param int $font
+	 * @param int $style
+	 * @return string
+	 */
+	function get_font_file($font,$style)
+	{
+		if(file_exists(PHPGW_SERVER_ROOT . '/../jpgraph/src/jpg-config.inc.php'))
+		{
+			//echo "including jpg-config.inc.php";
+			require_once(PHPGW_SERVER_ROOT . '/../jpgraph/src/jpg-config.inc.php');
+			if(file_exists(PHPGW_SERVER_ROOT . '/../jpgraph/src/jpgraph_ttf.inc.php'))
+			{
+				//echo "including jpgraph_ttf.inc.php";
+				require_once(PHPGW_SERVER_ROOT . '/../jpgraph/src/jpgraph_ttf.inc.php');
+			}
+		}
+		elseif(file_exists(PHPGW_SERVER_ROOT . '/../jpgraph/src/jpg-config.inc'))
+		{
+			//echo "including jpg-config.inc";
+			require_once(PHPGW_SERVER_ROOT . '/../jpgraph/src/jpg-config.inc');
+		}
+		else
+		{
+			//echo "falling back to vera";
+			return 'Vera.ttf';
+		}
+		if (!defined('FF_ARIAL'))
+		{
+			DEFINE("FF_COURIER",10);
+			DEFINE("FF_VERDANA",11);
+			DEFINE("FF_TIMES",12);
+			DEFINE("FF_COMIC",14);
+			DEFINE("FF_ARIAL",15);
+			DEFINE("FF_GEORGIA",16);
+			DEFINE("FF_TREBUCHE",17);
+			
+			// Gnome Vera font
+			// Available from http://www.gnome.org/fonts/
+			DEFINE("FF_VERA",18);
+			DEFINE("FF_VERAMONO",19);
+			DEFINE("FF_VERASERIF",20);
+			
+			// Chinese font
+			DEFINE("FF_SIMSUN",30);
+			DEFINE("FF_CHINESE",31);
+			DEFINE("FF_BIG5",31);
+			
+			// Japanese font
+			DEFINE("FF_MINCHO",40);
+			DEFINE("FF_PMINCHO",41);
+			DEFINE("FF_GOTHIC",42);
+			DEFINE("FF_PGOTHIC",43);
+			
+			// TTF Font styles
+			DEFINE("FS_NORMAL",9001);
+			DEFINE("FS_BOLD",9002);
+			DEFINE("FS_ITALIC",9003);
+			DEFINE("FS_BOLDIT",9004);
+			DEFINE("FS_BOLDITALIC",9004);
+		}
+		// File names for available fonts from jpgraph_ttf
+		$font_files=array(
+		    FF_COURIER => array(FS_NORMAL	=>'cour.ttf', 
+					FS_BOLD		=>'courbd.ttf', 
+					FS_ITALIC	=>'couri.ttf', 
+					FS_BOLDITALIC	=>'courbi.ttf' ),
+		    FF_GEORGIA => array(FS_NORMAL	=>'georgia.ttf', 
+					FS_BOLD		=>'georgiab.ttf', 
+					FS_ITALIC	=>'georgiai.ttf', 
+					FS_BOLDITALIC	=>'' ),
+		    FF_TREBUCHE	=>array(FS_NORMAL	=>'trebuc.ttf', 
+					FS_BOLD		=>'trebucbd.ttf',   
+					FS_ITALIC	=>'trebucit.ttf', 
+					FS_BOLDITALIC	=>'trebucbi.ttf' ),
+		    FF_VERDANA 	=> array(FS_NORMAL	=>'verdana.ttf', 
+					FS_BOLD		=>'verdanab.ttf',  
+					FS_ITALIC	=>'verdanai.ttf', 
+					FS_BOLDITALIC	=>'' ),
+		    FF_TIMES =>   array(FS_NORMAL	=>'times.ttf',   
+					FS_BOLD		=>'timesbd.ttf',   
+					FS_ITALIC	=>'timesi.ttf',   
+					FS_BOLDITALIC	=>'timesbi.ttf' ),
+		    FF_COMIC =>   array(FS_NORMAL	=>'comic.ttf',   
+					FS_BOLD		=>'comicbd.ttf',   
+					FS_ITALIC	=>'',         
+					FS_BOLDITALIC	=>'' ),
+		    FF_ARIAL =>   array(FS_NORMAL	=>'arial.ttf',   
+					FS_BOLD		=>'arialbd.ttf',   
+					FS_ITALIC	=>'ariali.ttf',   
+					FS_BOLDITALIC	=>'arialbi.ttf' ) ,
+		    FF_VERA =>    array(FS_NORMAL	=>'Vera.ttf',   
+					FS_BOLD		=>'VeraBd.ttf',   
+					FS_ITALIC	=>'VeraIt.ttf',   
+					FS_BOLDITALIC	=>'VeraBI.ttf' ),
+		    FF_VERAMONO	=> array(FS_NORMAL	=>'VeraMono.ttf', 
+					 FS_BOLD	=>'VeraMoBd.ttf', 
+					 FS_ITALIC	=>'VeraMoIt.ttf', 
+					 FS_BOLDITALIC	=>'VeraMoBI.ttf' ),
+		    FF_VERASERIF=> array(FS_NORMAL	=>'VeraSe.ttf', 
+					  FS_BOLD	=>'VeraSeBd.ttf', 
+					  FS_ITALIC	=>'', 
+					  FS_BOLDITALIC	=>'' ) ,
+	
+		    /* Chinese fonts */
+		    FF_SIMSUN 	=>  array(FS_NORMAL	=>'simsun.ttc',  
+					  FS_BOLD	=>'simhei.ttf',   
+					  FS_ITALIC	=>'',   
+					  FS_BOLDITALIC	=>'' ),
+		    FF_CHINESE 	=>   array(FS_NORMAL	=>CHINESE_TTF_FONT, 
+					  FS_BOLD	=>'', 
+					  FS_ITALIC	=>'', 
+					  FS_BOLDITALIC	=>'' ),
+	
+		    /* Japanese fonts */
+	 	    FF_MINCHO 	=>  array(FS_NORMAL	=>MINCHO_TTF_FONT,  
+					  FS_BOLD	=>'',   
+					  FS_ITALIC	=>'',   
+					  FS_BOLDITALIC	=>'' ),
+	 	    FF_PMINCHO 	=>  array(FS_NORMAL	=>PMINCHO_TTF_FONT,  
+					  FS_BOLD	=>'',   
+					  FS_ITALIC	=>'',  
+					  FS_BOLDITALIC	=>'' ),    
+	 	    FF_GOTHIC  	=>  array(FS_NORMAL	=>GOTHIC_TTF_FONT,  
+					  FS_BOLD	=>'',   
+					  FS_ITALIC	=>'',   
+					  FS_BOLDITALIC	=>'' ),    
+	 	    FF_PGOTHIC 	=>  array(FS_NORMAL	=>PGOTHIC_TTF_FONT,  
+					  FS_BOLD	=>'',   
+					  FS_ITALIC	=>'',  
+					  FS_BOLDITALIC	=>'' ),    
+	 	    FF_MINCHO 	=>  array(FS_NORMAL	=>PMINCHO_TTF_FONT,  
+					  FS_BOLD	=>'',   
+					  FS_ITALIC	=>'',  
+					  FS_BOLDITALIC	=>'' ),   
+	
+		    /* Hebrew fonts */
+		    FF_DAVID 	=>  array(FS_NORMAL	=>'DAVIDNEW.TTF',  
+					  FS_BOLD	=>'',   
+					  FS_ITALIC	=>'',  
+					  FS_BOLDITALIC	=>'' ),   
+	
+		    FF_MIRIAM 	=>  array(FS_NORMAL	=>'MRIAMY.TTF',  
+					  FS_BOLD	=>'',   
+					  FS_ITALIC	=>'',  
+					  FS_BOLDITALIC	=>'' ),   
+	
+		    FF_AHRON 	=>  array(FS_NORMAL	=>'ahronbd.ttf',  
+					  FS_BOLD	=>'',   
+					  FS_ITALIC	=>'',  
+					  FS_BOLDITALIC	=>'' ),   
+	
+		    /* Misc fonts */
+	 	    FF_DIGITAL =>   array(FS_NORMAL	=>'DIGIRU__.TTF',  
+					  FS_BOLD	=>'Digirtu_.ttf', 
+					  FS_ITALIC	=>'Digir___.ttf', 
+					  FS_BOLDITALIC	=>'DIGIRT__.TTF' ),   
+	 	    FF_SPEEDO =>    array(FS_NORMAL	=>'Speedo.ttf',  
+					  FS_BOLD	=>'',   
+					  FS_ITALIC	=>'',   
+					  FS_BOLDITALIC	=>'' ),   
+	 	    FF_COMPUTER  =>  array(FS_NORMAL	=>'COMPUTER.TTF',  
+					  FS_BOLD	=>'',   
+					  FS_ITALIC	=>'',   
+					  FS_BOLDITALIC	=>'' ),   
+	 	    FF_CALCULATOR => array(FS_NORMAL	=>'Triad_xs.ttf',  
+					  FS_BOLD	=>'',   
+					  FS_ITALIC	=>'',   
+					  FS_BOLDITALIC	=>'' ),   
+		);
+		return $font_files[$font][$style];
+	}
+}
Index: /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_dir.php
===================================================================
--- /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_dir.php	(revision 3594)
+++ /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_dir.php	(revision 3594)
@@ -0,0 +1,31 @@
+<?php
+/*=======================================================================
+// File: 	JPGRAPH_DIR.PHP
+// Description:	Specification of file directories for JpGraph
+// Created: 	22/11/2001
+// Author:	Johan Persson (johanp@aditus.nu)
+// Ver:		$Id: jpgraph_dir.php 22009 2006-07-06 02:39:11Z ralfbecker $
+//
+// License:	This code is released under GPL 2.0
+// Copyright (C) 2001 Johan Persson
+//========================================================================
+*/
+
+//------------------------------------------------------------------
+// Manifest Constants that control varius aspect of JpGraph
+//------------------------------------------------------------------
+// The full absolute name of directory to be used as a cache. This directory MUST
+// be readable and writable for PHP. Must end with '/'
+DEFINE("CACHE_DIR","/tmp/jpgraph_cache/");
+
+// The URL relative name where the cache can be found, i.e
+// under what HTTP directory can the cache be found. Normally
+// you would probably assign an alias in apache configuration
+// for the cache directory. 
+DEFINE("APACHE_CACHE_DIR","/jpgraph_cache/");
+
+// Directory for TTF fonts. Must end with '/'
+#DEFINE("TTF_DIR","/usr/local/fonts/ttf/");
+DEFINE("TTF_DIR",EGW_SERVER_ROOT."/projectmanager/inc/ttf-bitstream-vera-1.10/");
+
+?>
Index: /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph.php
===================================================================
--- /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph.php	(revision 3594)
+++ /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph.php	(revision 3594)
@@ -0,0 +1,5137 @@
+<?php
+/*=======================================================================
+// File:	JPGRAPH.PHP
+// Description:	PHP4 Graph Plotting library. Base module.
+// Created: 	2001-01-08
+// Author:	Johan Persson (johanp@aditus.nu)
+// Ver:		$Id: jpgraph.php 21908 2006-06-21 18:30:15Z ralfbecker $
+//
+// License:	This code is released under GPL 2.0 
+// Copyright (C) 2001 Johan Persson 
+//========================================================================
+*/
+
+// Get all directory paths
+include ( "jpgraph_dir.php" );
+
+// What group should the cached file belong to
+// (Set to "" will give the default group for the "PHP-user")
+// Please note that the Apache user must be a member of the
+// specified group since otherwise it is impossible for Apache
+// to set the specified group.
+DEFINE("CACHE_FILE_GROUP","");
+
+// What permissions should the cached file have
+// (Set to "" will give the default persmissions for the "PHP-user")
+DEFINE("CACHE_FILE_MOD",0664);
+
+// Should we try to find an image in the cache before generating it? 
+// Set this define to false to bypass the reading of the cache and always
+// regenerate the image. Note that even if reading the cache is 
+// disabled the cached will still be updated with the newly generated
+// image. Set also "USE_CACHE" below.
+DEFINE("READ_CACHE",false);
+
+// Should the cache be used at all? By setting this to false no
+// files will be generated in the cache directory.  
+// The difference from READ_CACHE being that setting READ_CACHE to
+// false will still create the image in the cache directory
+// just not use it. By setting USE_CACHE=false no files will even
+// be generated in the cache directory.
+DEFINE("USE_CACHE",false);
+
+// If the color palette is full should JpGraph try to allocate
+// the closest match? If you plan on using background image or
+// gradient fills it might be a good idea to enable this.
+// If not you will otherwise get an error saying that the color palette is 
+// exhausted. The drawback of using approximations is that the colors 
+// might not be exactly what you specified. 
+// Note1: This does only apply to paletted images, not truecolor 
+// images since they don't have the limitations of maximum number
+// of colors.
+DEFINE("USE_APPROX_COLORS",true);
+
+// Should usage of deprecated functions and parameters give a fatal error?
+// (Useful to check if code is future proof.)
+DEFINE("ERR_DEPRECATED",true);
+
+// Should the time taken to generate each picture be branded to the lower
+// left in corner in each generated image? Useful for performace measurements
+// generating graphs
+DEFINE("BRAND_TIMING",false);
+
+// What format should be used for the timing string?
+DEFINE("BRAND_TIME_FORMAT","Generated in: %01.3fs");
+
+// Decide if we should use the bresenham circle algorithm or the
+// built in Arc(). Bresenham gives better visual apperance of circles 
+// but is more CPU intensive and slower then the built in Arc() function
+// in GD. Turned off by default for speed
+DEFINE("USE_BRESENHAM",false);
+
+// Deafult graphic format set to "auto" which will automtically
+// choose the best available format in the order png,gif,jpg
+// (The supported format depends on what your PHP installation supports)
+DEFINE("DEFAULT_GFORMAT","auto");
+
+// Special unicode language support
+DEFINE("LANGUAGE_CYRILLIC",false);
+
+// Enable some extra debug information to be shown. 
+// (Should only be used if your first name is Johan)
+DEFINE("JPG_DEBUG",false);
+
+// Should the image be a truecolor image? 
+// Note 1: Can only be used with GD 2.0.2 and above.
+// Note 2: GD 2.0.1 + PHP 4.0.6 on Win32 crashes when trying to use 
+// trucolor. Truecolor support is to be considered alpha since GD 2.x
+// is still not considered stable (especially on Win32). 
+// Note 1: MUST be enabled to get background images working with GD2
+// Note 2: If enabled then truetype fonts will look very ugly
+// => You can't have both background images and truetype fonts in the same
+// image until these bugs has been fixed in GD 2.01  
+DEFINE('USE_TRUECOLOR',true);
+
+//------------------------------------------------------------------
+// Constants which are used as parameters for the method calls
+//------------------------------------------------------------------
+
+// TTF Font families
+DEFINE("FF_COURIER",10);
+DEFINE("FF_VERDANA",11);
+DEFINE("FF_TIMES",12);
+DEFINE("FF_HANDWRT",13);
+DEFINE("FF_COMIC",14);
+DEFINE("FF_ARIAL",15);
+DEFINE("FF_BOOK",16);
+DEFINE("FF_VERA",17);
+
+// TTF Font styles
+DEFINE("FS_NORMAL",1);
+DEFINE("FS_BOLD",2);
+DEFINE("FS_ITALIC",3);
+DEFINE("FS_BOLDIT",4);
+
+//Definitions for internal font, new style
+DEFINE("FF_FONT0",1);
+DEFINE("FF_FONT1",2);
+DEFINE("FF_FONT2",4);
+
+//Definitions for internal font, old style
+// (Only defined here to be able to generate an error mesage
+// when used)
+DEFINE("FONT0",99);			// Deprecated from 1.2
+DEFINE("FONT1",98);			// Deprecated from 1.2
+DEFINE("FONT1_BOLD",97);	// Deprecated from 1.2
+DEFINE("FONT2",96);			// Deprecated from 1.2
+DEFINE("FONT2_BOLD",95); 	// Deprecated from 1.2
+
+// Tick density
+DEFINE("TICKD_DENSE",1);
+DEFINE("TICKD_NORMAL",2);
+DEFINE("TICKD_SPARSE",3);
+DEFINE("TICKD_VERYSPARSE",4);
+
+// Side for ticks and labels. 
+DEFINE("SIDE_LEFT",-1);
+DEFINE("SIDE_RIGHT",1);
+DEFINE("SIDE_DOWN",-1);
+DEFINE("SIDE_UP",1);
+
+// Legend type stacked vertical or horizontal
+DEFINE("LEGEND_VERT",0);
+DEFINE("LEGEND_HOR",1);
+
+// Mark types for plot marks
+DEFINE("MARK_SQUARE",1);
+DEFINE("MARK_UTRIANGLE",2);
+DEFINE("MARK_DTRIANGLE",3);
+DEFINE("MARK_DIAMOND",4);
+DEFINE("MARK_CIRCLE",5);
+DEFINE("MARK_FILLEDCIRCLE",6);
+DEFINE("MARK_CROSS",7);
+DEFINE("MARK_STAR",8);
+DEFINE("MARK_X",9);
+
+// Styles for gradient color fill
+DEFINE("GRAD_VER",1);
+DEFINE("GRAD_HOR",2);
+DEFINE("GRAD_MIDHOR",3);
+DEFINE("GRAD_MIDVER",4);
+DEFINE("GRAD_CENTER",5);
+DEFINE("GRAD_WIDE_MIDVER",6);
+DEFINE("GRAD_WIDE_MIDHOR",7);
+
+// Inline defines
+DEFINE("INLINE_YES",1);
+DEFINE("INLINE_NO",0);
+
+// Format for background images
+DEFINE("BGIMG_FILLPLOT",1);
+DEFINE("BGIMG_FILLFRAME",2);
+DEFINE("BGIMG_COPY",3);
+DEFINE("BGIMG_CENTER",4);
+
+// Depth of objects
+DEFINE("DEPTH_BACK",0);
+DEFINE("DEPTH_FRONT",1);
+
+// Direction
+DEFINE("VERTICAL",1);
+DEFINE("HORIZONTAL",0);
+
+// Constants for types of static bands in plot area
+DEFINE("BAND_RDIAG",1);	// Right diagonal lines
+DEFINE("BAND_LDIAG",2); // Left diagonal lines
+DEFINE("BAND_SOLID",3); // Solid one color
+DEFINE("BAND_LVERT",4); // Vertical lines
+DEFINE("BAND_LHOR",5);  // Horizontal lines
+DEFINE("BAND_VLINE",4); // Vertical lines
+DEFINE("BAND_HLINE",5);  // Horizontal lines
+DEFINE("BAND_3DPLANE",6);  // "3D" Plane
+DEFINE("BAND_HVCROSS",7);  // Vertical/Hor crosses
+DEFINE("BAND_DIAGCROSS",8); // Diagonal crosses
+
+
+
+//
+// First of all set up a default error handler
+//
+
+//=============================================================
+// The default trivial error handler.
+// A production quality error handler should generate an image
+// containing the text of the error. 
+//=============================================================
+class JpGraphErrObject {
+    function JpGraphErrObject() {
+	// Empty. Reserved for future use
+    }
+
+    // If aHalt is true then execution can't continue. Typical used for
+    // fatal errors
+    function Raise($aMsg,$aHalt=true) {
+	if( $aHalt )
+	    die($aMsg);
+	else 
+	    echo $aMsg."<p>";
+    }
+}
+
+//
+// A wrapper class that is used to access the specified error object
+// (to hide the global error parameter and avoid having a GLOBAL directive
+// in all methods.
+//
+class JpGraphError {
+    function Install($aErrObject) {
+	GLOBAL $__jpg_err;
+	$__jpg_err = $aErrObject;
+    }
+    function Raise($aMsg,$aHalt=true){
+	GLOBAL $__jpg_err;
+	$tmp = new $__jpg_err;
+	$tmp->Raise($aMsg,$aHalt);
+    }
+}
+
+//
+// ... and install the default error handler
+//
+JpGraphError::Install("JpGraphErrObject");
+
+//
+//Check if there were any warnings, perhaps some wrong includes by the
+//user
+//
+if( isset($GLOBALS['php_errormsg']) ) {
+    JpGraphError::Raise("<b>General PHP error:</b><br>".$GLOBALS['php_errormsg']);
+}
+
+//
+// Check what version of the GD library is being used
+//
+if(function_exists('imagecopyresampled') ) {
+    $gd2 = true;
+    $copyfunc = "imagecopyresampled";
+} elseif(function_exists('imagecopyresized')) {
+    $copyfunc = "imagecopyresized";
+    $gd2 = false;
+}
+else {
+    JpGraphError::Raise("<b>JpGraph Error:</b> Your PHP installation does not 
+	have the required GD library.
+	Please see the PHP documentation on how to install and enable the GD library.");
+}
+
+
+// Usefull mathematical function
+function sign($a) {if( $a>=0) return 1; else return -1;}
+
+// Utility function to generate an image name based on the filename we
+// are running from AND assuming we use auto detection of graphic format
+// (top level), i.e it is safe to call this function
+// from a script that uses JpGraph
+function GenImgName() {
+    $supported = imagetypes();
+    if( $supported & IMG_PNG )
+	$img_format="png";
+    elseif( $supported & IMG_GIF )
+	$img_format="gif";
+    elseif( $supported & IMG_JPG )
+	$img_format="jpeg";
+    if( !isset($_SERVER['PHP_SELF']) )
+	JpGraphError::Raise("<b>JpGraph Error:</b> Can't access PHP_SELF, PHP global variable. You can't run PHP from command line
+		if you want to use the 'auto' naming of cache or image files.");
+    $fname=basename($_SERVER['PHP_SELF']);
+    // Replace the ".php" extension with the image format extension
+    return substr($fname,0,strlen($fname)-4).".".$img_format;
+}
+
+
+class LanguageConv {
+
+//  Translate iso encoding to unicode
+    function iso2uni ($isoline){
+	for ($i=0; $i < strlen($isoline); $i++){
+	    $thischar=substr($isoline,$i,1);
+	    $charcode=ord($thischar);
+	    $uniline.=($charcode>175) ? "&#" . (1040+($charcode-176)). ";" : $thischar;
+	}
+	return $uniline;
+    }
+
+    function ToCyrillic($aTxt) {
+	$koistring = $aTxt;
+	$isostring = convert_cyr_string($koistring, "k", "i");
+	$unistring = LanguageConv::iso2uni($isostring);
+	$this->t = $unistring;
+	return $aTxt;
+    }
+}
+
+//===================================================
+// CLASS JpgTimer
+// Description: General timing utility class to handle
+// timne measurement of generating graphs. Multiple
+// timers can be started by pushing new on a stack.
+//===================================================
+class JpgTimer {
+    var $start;
+    var $idx;	
+//---------------
+// CONSTRUCTOR
+    function JpgTimer() {
+	$this->idx=0;
+    }
+
+//---------------
+// PUBLIC METHODS	
+
+    // Push a new timer start on stack
+    function Push() {
+	list($ms,$s)=explode(" ",microtime());	
+	$this->start[$this->idx++]=floor($ms*1000) + 1000*$s;	
+    }
+
+    // Pop the latest timer start and return the diff with the
+    // current time
+    function Pop() {
+	assert($this->idx>0);
+	list($ms,$s)=explode(" ",microtime());	
+	$etime=floor($ms*1000) + (1000*$s);
+	$this->idx--;
+	return $etime-$this->start[$this->idx];
+    }
+} // Class
+
+
+//===================================================
+// CLASS Graph
+// Description: Main class to handle graphs
+//===================================================
+class Graph {
+    var $cache=null;		// Cache object (singleton)
+    var $img=null;			// Img object (singleton)
+    var $plots=array();	// Array of all plot object in the graph (for Y 1 axis)
+    var $y2plots=array();// Array of all plot object in the graph (for Y 2 axis)
+    var $xscale=null;		// X Scale object (could be instance of LinearScale or LogScale
+    var $yscale=null,$y2scale=null;
+    var $cache_name;		// File name to be used for the current graph in the cache directory
+    var $xgrid=null;		// X Grid object (linear or logarithmic)
+    var $ygrid=null,$y2grid=null; //dito for Y
+    var $doframe=true,$frame_color=array(0,0,0), $frame_weight=1;	// Frame around graph
+    var $boxed=false, $box_color=array(0,0,0), $box_weight=1;		// Box around plot area
+    var $doshadow=false,$shadow_width=4,$shadow_color=array(102,102,102);	// Shadow for graph
+    var $xaxis=null;		// X-axis (instane of Axis class)
+    var $yaxis=null, $y2axis=null;	// Y axis (instance of Axis class)
+    var $margin_color=array(198,198,198);	// Margin coor of graph
+    var $plotarea_color=array(255,255,255);	// Plot area color
+    var $title,$subtitle; 	// Title and subtitle text object
+    var $axtype="linlin";	// Type of axis
+    var $xtick_factor;	// Factot to determine the maximum number of ticks depending on the plot with
+    var $texts=null;		// Text object to ge shown in the graph
+    var $lines=null;
+    var $bands=null;
+    var $text_scale_off=0;	// Text scale offset in world coordinates
+    var $background_image="",$background_image_type=-1,$background_image_format="png";
+    var $background_image_bright=0,$background_image_contr=0,$background_image_sat=0;
+    var $image_bright=0, $image_contr=0, $image_sat=0;
+    var $inline;
+    var $showcsim=0,$csimcolor="red"; //debug stuff, draw the csim boundaris on the image if <>0
+    var $grid_depth=DEPTH_BACK;	// Draw grid under all plots as default
+//---------------
+// CONSTRUCTOR
+
+    // aWIdth 		Width in pixels of image
+    // aHeight  	Height in pixels of image
+    // aCachedName	Name for image file in cache directory 
+    //  aTimeOut	Timeout in minutes for image in cache
+    // aInline		If true the image is streamed back in the call to Stroke()
+    //             If false the image is just created in the cache
+    function Graph($aWidth=300,$aHeight=200,$aCachedName="",$aTimeOut=0,$aInline=true) {
+		
+	// If timing is used create a new timing object
+	if( BRAND_TIMING ) {
+	    global $tim;
+	    $tim = new JpgTimer();
+	    $tim->Push();
+	}
+		
+	// Automtically generate the image file name based on the name of the script that
+	// generates the graph
+	if( $aCachedName=="auto" )
+	    $aCachedName=GenImgName();
+			
+	// Should the image be streamed back to the browser or only to the cache?
+	$this->inline=$aInline;
+		
+	$this->img 		=  new RotImage($aWidth,$aHeight);
+	$this->cache 	=  new ImgStreamCache($this->img);
+	$this->cache->SetTimeOut($aTimeOut);
+	$this->title 	=  new Text();
+	$this->subtitle =  new Text();
+	$this->legend 	=  new Legend();
+		
+	// If the cached version exist just read it directly from the
+	// cache, stream it back to browser and exit
+	if( $aCachedName!="" && READ_CACHE && $aInline )
+	    if( $this->cache->GetAndStream($aCachedName) ) {
+		exit();
+	    }
+				
+	$this->cache_name = $aCachedName;
+	$this->SetTickDensity(); // Normal density
+    }
+//---------------
+// PUBLIC METHODS	
+
+    // Should the grid be in front or back of the plot?
+    function SetGridDepth($aDepth) {
+	$this->grid_depth=$aDepth;
+    }
+	
+    // Specify graph angle 0-360 degrees.
+    function SetAngle($aAngle) {
+	$this->img->SetAngle($aAngle);
+    }
+	
+    // Add a plot object to the graph
+    function Add(&$aPlot) {
+	if( $aPlot == null )
+	    JpGraphError::Raise("<b><b>JpGraph Error:</b></b> Graph::Add() You tried to add a null plot to the graph.");				
+	$this->plots[] = &$aPlot;
+    }
+	
+    // Add plot to second Y-scale
+    function AddY2(&$aPlot) {
+	if( $aPlot == null )
+	    JpGraphError::Raise("<b><b>JpGraph Error:</b></b> Graph::AddY2() You tried to add a null plot to the graph.");				
+	$this->y2plots[] = &$aPlot;
+    }
+	
+    // Add text object to the graph
+    function AddText(&$aTxt) {
+	if( $aTxt == null )
+	    JpGraphError::Raise("<b><b>JpGraph Error:</b></b> Graph::AddText() You tried to add a null text to the graph.");		
+	if( is_array($aTxt) ) {
+	    for($i=0; $i<count($aTxt); ++$i )
+		$this->texts[]=&$aTxt[$i];
+	}
+	else
+	    $this->texts[] = &$aTxt;
+    }
+	
+    // Add a line object (class PlotLine) to the graph
+    function AddLine(&$aLine) {
+	if( $aLine == null )
+	    JpGraphError::Raise("<b><b>JpGraph Error:</b></b> Graph::AddLine() You tried to add a null line to the graph.");		
+	if( is_array($aLine) ) {
+	    for($i=0; $i<count($aLine); ++$i )
+		$this->lines[]=&$aLine[$i];
+	}
+	else
+	    $this->lines[] = &$aLine;
+    }
+
+    // Add vertical or horizontal band
+    function AddBand(&$aBand) {
+	if( $aBand == null )
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Graph::AddBand() You tried to add a null band to the graph.");
+	if( is_array($aBand) ) {
+	    for($i=0; $i<count($aBand); ++$i )
+		$this->bands[] = &$aBand[$i];
+	}
+	else
+	    $this->bands[] = &$aBand;
+    }
+
+	
+    // Specify a background image
+    function SetBackgroundImage($aFileName,$aBgType=BKIMG_FILLPLOT,$aImgFormat="png") {
+
+	if( $GLOBALS["gd2"] && !USE_TRUECOLOR ) {
+	    JpGraphError::Raise("<b>JpGraph Error:</b>You are using GD 2.x and are 
+trying to use a background images on a non truecolor image. <br>
+To use
+background images with GD 2.x you <b>must</b> enable truecolor by setting the
+USE_TRUECOLOR constant to TRUE. <br>
+<b>Note:</b> Due to a bug in GD 2.0.1 
+using any truetype fonts with truecolor images will result in very
+poor quality fonts.");
+	}
+
+	$this->background_image = $aFileName;
+	$this->background_image_type=$aBgType;
+	$this->background_image_format=$aImgFormat;
+    }
+	
+    // Adjust brightness and constrast for background image
+    function AdjBackgroundImage($aBright,$aContr=0,$aSat=0) {
+	$this->background_image_bright=$aBright;
+	$this->background_image_contr=$aContr;
+	$this->background_image_sat=$aSat;
+    }
+	
+    // Adjust brightness and constrast for image
+    function AdjImage($aBright,$aContr=0,$aSat=0) {
+	$this->image_bright=$aBright;
+	$this->image_contr=$aContr;
+	$this->image_sat=$aSat;
+    }
+	
+    // Set a frame around the plot area
+    function SetBox($aDrawPlotFrame=true,$aPlotFrameColor=array(0,0,0),$aPlotFrameWeight=1) {
+	$this->boxed = $aDrawPlotFrame;
+	$this->box_weight = $aPlotFrameWeight;
+	$this->box_color = $aPlotFrameColor;
+    }
+	
+    // Specify color for the plotarea (not the margins)
+    function SetColor($aColor) {
+	$this->plotarea_color=$aColor;
+    }
+	
+    // Specify color for the margins (all areas outside the plotarea)
+    function SetMarginColor($aColor) {
+	$this->margin_color=$aColor;
+    }
+	
+    // Set a frame around the entire image
+    function SetFrame($aDrawImgFrame=true,$aImgFrameColor=array(0,0,0),$aImgFrameWeight=1) {
+	$this->doframe = $aDrawImgFrame;
+	$this->frame_color = $aImgFrameColor;
+	$this->frame_weight = $aImgFrameWeight;
+    }
+		
+    // Set the shadow around the whole image
+    function SetShadow($aShowShadow=true,$aShadowWidth=5,$aShadowColor=array(102,102,102)) {
+	$this->doshadow = $aShowShadow;
+	$this->shadow_color = $aShadowColor;
+	$this->shadow_width = $aShadowWidth;
+    }
+
+    // Specify x,y scale. Note that if you manually specify the scale
+    // you must also specify the tick distance with a call to Ticks::Set()
+    function SetScale($aAxisType,$aYMin=1,$aYMax=1,$aXMin=1,$aXMax=1) {
+	$this->axtype = $aAxisType;
+
+	$yt=substr($aAxisType,-3,3);
+	if( $yt=="lin" )
+	    $this->yscale = new LinearScale($aYMin,$aYMax);
+	elseif( $yt == "int" ) {
+	    $this->yscale = new LinearScale($aYMin,$aYMax);
+	    $this->yscale->SetIntScale();
+	}
+	elseif( $yt=="log" )
+	    $this->yscale = new LogScale($aYMin,$aYMax);
+	else
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Unknown scale specification for Y-scale. ($axtype)");
+			
+	$xt=substr($aAxisType,0,3);
+	if( $xt == "lin" || $xt == "tex" )
+	    $this->xscale = new LinearScale($aXMin,$aXMax,"x");
+	elseif( $xt == "int" ) {
+	    $this->xscale = new LinearScale($aXMin,$aXMax,"x");
+	    $this->xscale->SetIntScale();
+	}
+	elseif( $xt == "log" )
+	    $this->xscale = new LogScale($aXMin,$aXMax,"x");
+	else
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Unknown scale specification for X-scale. ($aAxisType)");
+
+	$this->xscale->Init($this->img);
+	$this->yscale->Init($this->img);						
+					
+	$this->xaxis = new Axis($this->img,$this->xscale);
+	$this->yaxis = new Axis($this->img,$this->yscale);
+	$this->xgrid = new Grid($this->xaxis);
+	$this->ygrid = new Grid($this->yaxis);	
+	$this->ygrid->Show();			
+    }
+	
+    // Specify secondary Y scale
+    function SetY2Scale($aAxisType="lin",$aY2Min=1,$aY2Max=1) {
+	if( $aAxisType=="lin" ) 
+	    $this->y2scale = new LinearScale($aY2Min,$aY2Max);
+	elseif( $aAxisType=="log" ) {
+	    $this->y2scale = new LogScale($aY2Min,$aY2Max);
+	}
+	else JpGraphError::Raise("JpGraph: Unsupported Y2 axis type: $axtype<br>");
+			
+	$this->y2scale->Init($this->img);	
+	$this->y2axis = new Axis($this->img,$this->y2scale);
+	$this->y2axis->scale->ticks->SetDirection(SIDE_LEFT); 
+	$this->y2axis->SetLabelPos(SIDE_RIGHT); 
+		
+	// Deafult position is the max x-value
+	$this->y2axis->SetPos($this->xscale->GetMaxVal());
+	$this->y2grid = new Grid($this->y2axis);							
+    }
+	
+    // Specify density of ticks when autoscaling 'normal', 'dense', 'sparse', 'verysparse'
+    // The dividing factor have been determined heuristically according to my aesthetic 
+    // sense (or lack off) y.m.m.v !
+    function SetTickDensity($aYDensity=TICKD_NORMAL,$aXDensity=TICKD_NORMAL) {
+	$this->xtick_factor=30;
+	$this->ytick_factor=25;		
+	switch( $aYDensity ) {
+	    case TICKD_DENSE:
+		$this->ytick_factor=12;			
+	    break;
+	    case TICKD_NORMAL:
+		$this->ytick_factor=25;			
+	    break;
+	    case TICKD_SPARSE:
+		$this->ytick_factor=40;			
+	    break;
+	    case TICKD_VERYSPARSE:
+		$this->ytick_factor=100;			
+	    break;		
+	    default:
+		JpGraphError::Raise("JpGraph: Unsupported Tick density: $densy");
+	}
+	switch( $aXDensity ) {
+	    case TICKD_DENSE:
+		$this->xtick_factor=18;							
+	    break;
+	    case TICKD_NORMAL:
+		$this->xtick_factor=30;			
+	    break;
+	    case TICKD_SPARSE:
+		$this->xtick_factor=45;					
+	    break;
+	    case TICKD_VERYSPARSE:
+		$this->xtick_factor=60;								
+	    break;		
+	    default:
+		JpGraphError::Raise("JpGraph: Unsupported Tick density: $densx");
+	}		
+    }
+	
+    // Get a string of all image map areas	
+    function GetCSIMareas() {
+	$csim="";
+	foreach ($this->plots as $p) {
+	    $csim.= $p->GetCSIMareas();
+	}		
+	return $csim;
+    }
+	
+    // Get a complete <MAP>..</MAP> tag for the final image map
+    function GetHTMLImageMap($aMapName) {
+	$im = "<MAP NAME=\"$aMapName\">\n";
+	$im .= $this->GetCSIMareas();
+	$im .= "</MAP>"; 
+	return $im;
+    }
+
+    // Stroke the graph
+    // $aStrokeFileName	If != "" the image will be written to this file and NOT
+    // streamed back to the browser
+    function Stroke($aStrokeFileName="") {		
+		
+	// Do any pre-stroke adjustment that is needed by the different plot types
+	// (i.e bar plots want's to add an offset to the x-labels etc)
+	for($i=0; $i<count($this->plots)	; ++$i ) {
+	    $this->plots[$i]->PreStrokeAdjust($this);
+	    $this->plots[$i]->Legend($this);
+	}
+		
+	// Any plots on the second Y scale?
+	if( $this->y2scale != null ) {
+	    for($i=0; $i<count($this->y2plots)	; ++$i ) {
+		$this->y2plots[$i]->PreStrokeAdjust($this);
+		$this->y2plots[$i]->Legend($this);
+	    }
+	}
+		
+	// Bail out if any of the Y-axis not been specified and
+	// has no plots. (This means it is impossible to do autoscaling and
+	// no other scale was given so we can't possible draw anything). If you use manual
+	// scaling you also have to supply the tick steps as well.
+	if( (!$this->yscale->IsSpecified() && count($this->plots)==0) ||
+	    ($this->y2scale!=null && !$this->y2scale->IsSpecified() && count($this->y2plots)==0) ) {
+	    JpGraphError::Raise("<strong>JpGraph: Can't draw unspecified Y-scale.</strong><br>
+				You have either:
+				<br>* Specified an Y axis for autoscaling but have not supplied any plots
+				<br>* Specified a scale manually but have forgot to specify the tick steps");
+	}
+		
+	// Bail out if no plots and no specified X-scale
+	if( (!$this->xscale->IsSpecified() && count($this->plots)==0 && count($this->y2plots)==0) )
+	    JpGraphError::Raise("<strong>JpGraph: Can't draw unspecified X-scale.</strong><br>No plots.<br>");
+
+	//Check if we should autoscale y-axis
+	if( !$this->yscale->IsSpecified() && count($this->plots)>0 ) {
+	    list($min,$max) = $this->GetPlotsYMinMax($this->plots);
+	    $this->yscale->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
+	}
+
+	if( $this->y2scale != null) 
+	    if( !$this->y2scale->IsSpecified() && count($this->y2plots)>0 ) {
+		list($min,$max) = $this->GetPlotsYMinMax($this->y2plots);
+		$this->y2scale->AutoScale($this->img,$min,$max,$this->img->plotheight/$this->ytick_factor);
+	    }			
+				
+	//Check if we should autoscale x-axis
+	if( !$this->xscale->IsSpecified() ) {
+	    if( substr($this->axtype,0,4) == "text" ) {
+		$max=0;
+		foreach( $this->plots as $p ) 
+		    $max=max($max,$p->numpoints-1);
+		$min=0;
+		$this->xscale->Update($this->img,$min,$max);
+		$this->xscale->ticks->Set($this->xaxis->tick_step,1);
+		$this->xscale->ticks->SupressMinorTickMarks();
+	    }
+	    else {
+		list($min,$ymin) = $this->plots[0]->Min();
+		list($max,$ymax) = $this->plots[0]->Max();
+		foreach( $this->plots as $p ) {
+		    list($xmin,$ymin) = $p->Min();
+		    list($xmax,$ymax) = $p->Max();			
+		    $min = Min($xmin,$min);
+		    $max = Max($xmax,$max);
+		}
+		$this->xscale->AutoScale($this->img,$min,$max,$this->img->plotwidth/$this->xtick_factor);
+	    }
+			
+	    //Adjust position of y-axis and y2-axis to minimum/maximum of x-scale
+	    $this->yaxis->SetPos($this->xscale->GetMinVal());
+	    if( $this->y2axis != null ) {
+		$this->y2axis->SetPos($this->xscale->GetMaxVal());
+		$this->y2axis->SetTitleSide(SIDE_RIGHT);
+	    }
+	}		
+		
+	// If we have a negative values and x-axis position is at 0
+	// we need to supress the first and possible the last tick since
+	// they will be drawn on top of the y-axis (and possible y2 axis)
+	// The test below might seem strange the reasone being that if
+	// the user hasn't specified a value for position this will not
+	// be set until we do the stroke for the axis so as of now it
+	// is undefined.
+		
+	if( !$this->xaxis->pos && $this->yscale->GetMinVal() < 0 ) {
+	    $this->yscale->ticks->SupressZeroLabel(false);
+	    $this->xscale->ticks->SupressFirst();
+	    if( $this->y2axis != null ) {
+		$this->xscale->ticks->SupressLast();
+	    }
+	}
+		
+	$this->StrokePlotArea();
+		
+	// Stroke axis
+	$this->xaxis->Stroke($this->yscale);
+	$this->yaxis->Stroke($this->xscale);		
+
+	// Stroke bands
+	if( $this->bands != null ) 
+	    for($i=0; $i<count($this->bands); ++$i) {
+				// Stroke all bands that asks to be in the background
+		if( $this->bands[$i]->depth == DEPTH_BACK )
+		    $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale);
+	    }
+
+	if( $this->grid_depth == DEPTH_BACK ) {
+	    $this->ygrid->Stroke();
+	    $this->xgrid->Stroke();
+	}
+				
+	// Stroke Y2-axis
+	if( $this->y2axis != null ) {		
+	    $this->y2axis->Stroke($this->xscale); 				
+	    $this->y2grid->Stroke();
+	}
+		
+	$oldoff=$this->xscale->off;
+	if(substr($this->axtype,0,4)=="text") {
+	    $this->xscale->off += 
+		 ceil($this->xscale->scale_factor*$this->text_scale_off*$this->xscale->ticks->minor_step);
+	}
+
+	// Stroke all plots for Y1 axis
+	for($i=0; $i<count($this->plots)	; ++$i ) {
+	    $this->plots[$i]->Stroke($this->img,$this->xscale,$this->yscale);
+	    $this->plots[$i]->StrokeMargin($this->img);
+	}						
+		
+	// Stroke all plots for Y2 axis
+	if( $this->y2scale != null )
+	    for($i=0; $i< count($this->y2plots); ++$i ) {	
+		$this->y2plots[$i]->Stroke($this->img,$this->xscale,$this->y2scale);
+	    }		
+
+	$this->xscale->off=$oldoff;
+		
+	if( $this->grid_depth == DEPTH_FRONT ) {
+	    $this->ygrid->Stroke();
+	    $this->xgrid->Stroke();
+	}
+
+	// Stroke bands
+	if( $this->bands!= null )
+	    for($i=0; $i<count($this->bands); ++$i) {
+				// Stroke all bands that asks to be in the foreground
+		if( $this->bands[$i]->depth == DEPTH_FRONT )
+		    $this->bands[$i]->Stroke($this->img,$this->xscale,$this->yscale);
+	    }
+
+	// Stroke any lines added
+	if( $this->lines != null ) {
+	    for($i=0; $i<count($this->lines); ++$i) {
+		$this->lines[$i]->Stroke($this->img,$this->xscale,$this->yscale);
+	    }
+	}
+		
+	// Finally draw the axis again since some plots may have nagged
+	// the axis in the edges.
+	$this->yaxis->Stroke($this->xscale);
+	$this->xaxis->Stroke($this->yscale);
+		
+	if( $this->y2scale != null) 
+	    $this->y2axis->Stroke($this->xscale); 	
+		
+	$this->StrokePlotBox();
+		
+	// The titles and legends never gets rotated so make sure
+	// that the angle is 0 before stroking them				
+	$aa = $this->img->SetAngle(0);
+	$this->StrokeTitles();
+	$this->legend->Stroke($this->img);		
+	$this->StrokeTexts();	
+	$this->img->SetAngle($aa);	
+			
+	// Draw an outline around the image map	
+	if(JPG_DEBUG)
+	    $this->DisplayClientSideaImageMapAreas();		
+
+	// Adjust the appearance of the image
+	$this->AdjustSaturationBrightnessContrast();
+		
+	// Finally stream the generated picture					
+	$this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,$aStrokeFileName);		
+    }
+
+//---------------
+// PRIVATE METHODS	
+    // Private helper function for backgound image
+    function LoadBkgImage($aImgFormat="png",$aBright=0,$aContr=0) {		
+	$f = "imagecreatefrom".$aImgFormat;
+	$imgtag = $aImgFormat;
+	if( $aImgFormat == "jpeg" )
+	    $imgtag = "jpg";
+	if( !strstr($this->background_image,$imgtag) && strstr($this->background_image,".") )
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Background image seems to be of different type (has different file extension)
+			than specified imagetype. <br>Specified: '".$aImgFormat."'<br>File: '".$this->background_image."'");
+	$img = $f($this->background_image);
+	if( !$img ) {
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Can't read background image: '".$this->background_image."'");   
+	}
+	return $img;
+    }	
+
+    // Private
+    // Stroke the plot area with either a solid color or a background image
+    function StrokePlotArea() {
+	// Copy in background image
+	if( $this->background_image != "" ) {
+	    $bkgimg = $this->LoadBkgImage($this->background_image_format);
+	    $this->img->_AdjBrightContrast($bkgimg,$this->background_image_bright,
+	    $this->background_image_contr);							  
+	    $this->img->_AdjSat($bkgimg,$this->background_image_sat);
+	    $bw = ImageSX($bkgimg);
+	    $bh = ImageSY($bkgimg);
+
+	    $aa = $this->img->SetAngle(0);
+		
+	    switch( $this->background_image_type ) {
+		case BGIMG_FILLPLOT: // Resize to just fill the plotarea
+		    $this->StrokeFrame();
+		$GLOBALS["copyfunc"]($this->img->img,$bkgimg,
+		$this->img->left_margin,$this->img->top_margin,
+		0,0,$this->img->plotwidth,$this->img->plotheight,
+		$bw,$bh);
+		break;
+		case BGIMG_FILLFRAME: // Fill the whole area from upper left corner, resize to just fit
+		    $GLOBALS["copyfunc"]($this->img->img,$bkgimg,
+		    0,0,0,0,
+		    $this->img->width,$this->img->height,
+		    $bw,$bh);
+		$this->StrokeFrame();
+		break;
+		case BGIMG_COPY: // Just copy the image from left corner, no resizing
+		    $GLOBALS["copyfunc"]($this->img->img,$bkgimg,
+		    0,0,0,0,
+		    $bw,$bh,
+		    $bw,$bh);
+		$this->StrokeFrame();
+		break;
+		case BGIMG_CENTER: // Center original image in the plot area
+		    $centerx = round($this->img->plotwidth/2+$this->img->left_margin-$bw/2);
+		$centery = round($this->img->plotheight/2+$this->img->top_margin-$bh/2);
+		$GLOBALS["copyfunc"]($this->img->img,$bkgimg,
+		$centerx,$centery,
+		0,0,
+		$bw,$bh,
+		$bw,$bh);
+		$this->StrokeFrame();
+		break;
+		default:
+		    JpGraphError::Raise("<b>JpGraph Error:</b> Unknown background image layout");
+	    }			
+	    $this->img->SetAngle($aa);										  			
+	}
+	else {				
+	    $aa = $this->img->SetAngle(0);
+	    $this->StrokeFrame();
+	    $this->img->SetAngle($aa);			
+
+	    $this->img->PushColor($this->plotarea_color);
+
+	    // Note: To be consistent we really should take a possible shadow
+	    // into account. However, that causes some problem for the LinearScale class
+	    // since in the current design it does not have any links to class Graph which
+	    // means it has no way of compensating for the adjusted plotarea in case of a 
+	    // shadow. So, until I redesign LinearScale we can't compensate for this.
+	    // So just set the two adjustment parameters to zero for now.
+	    $boxadj = 0; //$this->doframe ? $this->frame_weight : 0 ;
+	    $adj = 0; //$this->doshadow ? $this->shadow_width : 0 ;
+
+	    $this->img->FilledRectangle($this->img->left_margin+$boxadj,
+	      $this->img->top_margin+$boxadj,
+	      $this->img->width-$this->img->right_margin-$adj-2*$boxadj,
+	      $this->img->height-$this->img->bottom_margin-$adj-2*$boxadj);	
+
+	    $this->img->PopColor();
+	}	
+	$this->img->SetAngle($aa);
+    }	
+	
+	
+    function StrokePlotBox() {
+	// Should we draw a box around the plot area?
+	if( $this->boxed ) {
+	    $this->img->SetLineWeight($this->box_weight);
+	    $this->img->SetColor($this->box_color);
+	    $this->img->Rectangle(
+		$this->img->left_margin,$this->img->top_margin,
+		$this->img->width-$this->img->right_margin,
+		$this->img->height-$this->img->bottom_margin);
+	}						
+    }		
+
+    function StrokeTitles() {
+	// Stroke title
+	$this->title->Center($this->img->left_margin,$this->img->width-$this->img->right_margin,5);
+	$this->title->Stroke($this->img);
+		
+	// ... and subtitle
+	$this->subtitle->Center($this->img->left_margin,$this->img->width-$this->img->right_margin,
+	7+$this->title->GetFontHeight($this->img));
+	$this->subtitle->Stroke($this->img);
+    }
+
+    function StrokeTexts() {
+	// Stroke any user added text objects
+	if( $this->texts != null ) {
+	    for($i=0; $i<count($this->texts); ++$i) {
+		$this->texts[$i]->Stroke($this->img);
+	    }
+	}
+    }
+
+    function DisplayClientSideaImageMapAreas() {
+	// Debug stuff - display the outline of the image map areas
+	foreach ($this->plots as $p) {
+	    $csim.= $p->GetCSIMareas();
+	}
+	$csim.= $this->legend->GetCSIMareas();
+	if (preg_match_all("/area shape=\"(\w+)\" coords=\"([0-9\, ]+)\"/", $csim, $coords)) {
+	    $this->img->SetColor($this->csimcolor);
+	    for ($i=0; $i<count($coords[0]); $i++) {
+		if ($coords[1][$i]=="poly") {
+		    preg_match_all('/\s*([0-9]+)\s*,\s*([0-9]+)\s*,*/',$coords[2][$i],$pts);
+		    $this->img->SetStartPoint($pts[1][count($pts[0])-1],$pts[2][count($pts[0])-1]);
+		    for ($j=0; $j<count($pts[0]); $j++) {
+			$this->img->LineTo($pts[1][$j],$pts[2][$j]);
+		    }
+		} else if ($coords[1][$i]=="rect") {
+		    $pts = preg_split('/,/', $coords[2][$i]);
+		    $this->img->SetStartPoint($pts[0],$pts[1]);
+		    $this->img->LineTo($pts[2],$pts[1]);
+		    $this->img->LineTo($pts[2],$pts[3]);
+		    $this->img->LineTo($pts[0],$pts[3]);
+		    $this->img->LineTo($pts[0],$pts[1]);					
+		}
+	    }
+	}
+    }
+
+    function AdjustSaturationBrightnessContrast() {
+	// Adjust the brightness and contrast of the image
+	if( $this->image_contr || $this->image_bright )
+	    $this->img->AdjBrightContrast($this->image_bright,$this->image_contr);
+	if( $this->image_sat )											 
+	    $this->img->AdjSat($this->image_sat);
+    }
+
+    // Text scale offset in world coordinates
+    function SetTextScaleOff($aOff) {
+	$this->text_scale_off = $aOff;
+    }
+	
+    // Get min and max values for all included plots
+    function GetPlotsYMinMax(&$aPlots) {
+	list($xmax,$max) = $aPlots[0]->Max();
+	list($xmin,$min) = $aPlots[0]->Min();
+	for($i=0; $i<count($aPlots); ++$i ) {
+	    list($xmax,$ymax)=$aPlots[$i]->Max();
+	    list($xmin,$ymin)=$aPlots[$i]->Min();
+	    if (!is_string($ymax) || $ymax != "") $max=max($max,$ymax);
+	    if (!is_string($ymin) || $ymin != "") $min=min($min,$ymin);
+	}
+	if( $min == "" ) $min = 0;
+	if( $max == "" ) $max = 0;
+	if( $min == 0 && $max == 0 ) {
+	    // Special case if all values are 0
+	    $min=0;$max=1;			
+	}
+	return array($min,$max);
+    }
+
+    // Draw a frame around the image
+    function StrokeFrame() {
+	if( !$this->doframe ) return;
+	if( $this->doshadow ) {
+	    $this->img->SetColor($this->frame_color);			
+	    if( $this->background_image_type <= 1 ) 
+		$c = $this->margin_color;
+	    else
+		$c = false;
+	    $this->img->ShadowRectangle(0,0,$this->img->width,$this->img->height,
+					$c,$this->shadow_width);
+	}
+	else {
+	    $this->img->SetLineWeight($this->frame_weight);
+	    if( $this->background_image_type <= 1 ) {
+		$this->img->SetColor($this->margin_color);
+		$this->img->FilledRectangle(1,1,$this->img->width-2,$this->img->height-2);		
+	    }
+	    $this->img->SetColor($this->frame_color);
+	    $this->img->Rectangle(0,0,$this->img->width-1,$this->img->height-1);		
+	}
+    }
+} // Class
+
+
+//===================================================
+// CLASS TTF
+// Description: Handle TTF font names
+//===================================================
+class TTF {
+    var $font_fam;
+//---------------
+// CONSTRUCTOR
+    function TTF() {
+	// Base file names for available fonts
+	$this->font_fam=array(
+	    FF_COURIER => TTF_DIR."courier",
+	    FF_VERDANA => TTF_DIR."verdana",
+	    FF_TIMES => TTF_DIR."times",
+	    FF_HANDWRT => TTF_DIR."handwriting",
+	    FF_COMIC => TTF_DIR."comic",
+	    FF_ARIAL => TTF_DIR."arial",
+	    FF_BOOK => TTF_DIR."bookant",
+	    FF_VERA => TTF_DIR."Vera");
+    }
+
+//---------------
+// PUBLIC METHODS	
+    // Create the TTF file from the font specification
+    function File($fam,$style=FS_NORMAL) {
+	$f=$this->font_fam[$fam];
+	if( !$f ) JpGraphError::Raise("<b>JpGraph Error:</b> Unknown TTF font family.");
+	switch( $style ) {
+	    case FS_NORMAL:
+		break;
+	    case FS_BOLD: $f .= "Bd";
+		break;
+	    case FS_ITALIC: $f .= "It";
+		break;
+	    case FS_BOLDIT: $f .= "BI";
+		break;
+	    default:
+		JpGraphError::Raise("<b>JpGraph Error:</b> Unknown TTF Style.");
+	}
+	$f .= ".ttf";
+	
+	// Check that file exist
+	if( !file_exists($f) ) 
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Can't open font file \"$f\". Wrong directory?");
+		
+	return $f;
+    }
+} // Class
+
+//===================================================
+// CLASS LineProperty
+// Description: Holds properties for a line
+//===================================================
+class LineProperty {
+    var $iWeight=1, $iColor="black",$iStyle="solid";
+    var $iShow=true;
+	
+//---------------
+// PUBLIC METHODS	
+    function SetColor($aColor) {
+	$this->iColor = $aColor;
+    }
+	
+    function SetWeight($aWeight) {
+	$this->iWeight = $aWeight;
+    }
+	
+    function SetStyle($aStyle) {
+	$this->iStyle = $aStyle;
+    }
+		
+    function Show($aShow=true) {
+	$this->iShow=$aShow;
+    }
+	
+    function Stroke($aImg,$aX1,$aY1,$aX2,$aY2) {
+	if( $this->iShow ) {
+	    $aImg->SetColor($this->iColor);
+	    $aImg->SetLineWeight($this->iWeight);
+	    $aImg->SetLineStyle($this->iStyle);			
+	    $aImg->StyleLine($aX1,$aY1,$aX2,$aY2);
+	}
+    }
+}
+
+
+
+//===================================================
+// CLASS Text
+// Description: Arbitrary text object that can be added to the graph
+//===================================================
+class Text {
+    var $t,$x=0,$y=0,$halign="left",$valign="top",$color=array(0,0,0);
+    var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12,$hide=false,$dir=0;
+    var $boxed=false;	// Should the text be boxed
+    var $paragraph_align="left";
+
+//---------------
+// CONSTRUCTOR
+
+    // Create new text at absolute pixel coordinates
+    function Text($aTxt="",$aXAbsPos=0,$aYAbsPos=0) {
+	$this->t = $aTxt;
+	$this->x = $aXAbsPos;
+	$this->y = $aYAbsPos;
+    }
+//---------------
+// PUBLIC METHODS	
+    // Set the string in the text object
+    function Set($aTxt) {
+	$this->t = $aTxt;
+    }
+	
+    // Specify the position and alignment for the text object
+    function Pos($aXAbsPos=0,$aYAbsPos=0,$aHAlign="left",$aVAlign="top") {
+	$this->x = $aXAbsPos;
+	$this->y = $aYAbsPos;
+	$this->halign = $aHAlign;
+	$this->valign = $aVAlign;
+    }
+	
+    // Specify alignment for the text
+    function Align($aHAlign,$aVAlign="top") {
+	$this->halign = $aHAlign;
+	$this->valign = $aVAlign;
+    }		
+
+    // Specifies the alignment for a multi line text
+    function ParagraphAlign($aAlign) {
+	$this->paragraph_align = $aAlign;
+    }
+	
+    // Specify that the text should be boxed. fcolor=frame color, bcolor=border color,
+    // $shadow=drop shadow should be added around the text.
+    function SetBox($aFrameColor=array(255,255,255),$aBorderColor=array(0,0,0),$aShadow=false) {
+	if( $aFrameColor==false )
+	    $this->boxed=false;
+	else
+	    $this->boxed=true;
+	$this->fcolor=$aFrameColor;
+	$this->bcolor=$aBorderColor;
+	$this->shadow=$aShadow;
+    }
+	
+    // Hide the text
+    function Hide($aHide=true) {
+	$this->hide=$aHide;
+    }
+	
+    // This looks ugly since it's not a very orthogonal design 
+    // but I added this "inverse" of Hide() to harmonize
+    // with some classes which I designed more recently (especially) 
+    // jpgraph_gantt
+    function Show($aShow=true) {
+	$this->hide=!$aShow;
+    }
+	
+    // Specify font
+    function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
+	$this->font_family=$aFamily;
+	$this->font_style=$aStyle;
+	$this->font_size=$aSize;
+    }
+			
+    // Center the text between $left and $right coordinates
+    function Center($aLeft,$aRight,$aYAbsPos=false) {
+	$this->x = $aLeft + ($aRight-$aLeft	)/2;
+	$this->halign = "center";
+	if( is_numeric($aYAbsPos) )
+	    $this->y = $aYAbsPos;		
+    }
+	
+    // Set text color
+    function SetColor($aColor) {
+	$this->color = $aColor;
+    }
+	
+    // Orientation of text. Note only TTF fonts can have an arbitrary angle
+    function SetOrientation($aDirection=0) {
+	if( is_numeric($aDirection) )
+	    $this->dir=$aDirection;	
+	elseif( $aDirection=="h" )
+	    $this->dir = 0;
+	elseif( $aDirection=="v" )
+	    $this->dir = 90;
+	else JpGraphError::Raise("<b>JpGraph Error:</b> Invalid direction specified for text.");
+    }
+	
+    // Total width of text
+    function GetWidth(&$aImg) {
+	$aImg->SetFont($this->font_family,$this->font_style,$this->font_size);		
+	return $aImg->GetTextWidth($this->t);
+    }
+	
+    // Hight of font
+    function GetFontHeight(&$aImg) {
+	$aImg->SetFont($this->font_family,$this->font_style,$this->font_size);		
+	return $aImg->GetFontHeight();
+    }
+
+    function GetTextHeight(&$aImg) {
+	$aImg->SetFont($this->font_family,$this->font_style,$this->font_size);		
+	return $aImg->GetTextHeight($this->t);
+    }
+	
+    // Display text in image
+    function Stroke(&$aImg,$x=-1,$y=-1) {
+	if( $x>-1 ) $this->x = $x;
+	if( $y>-1 ) $this->y = $y;
+
+	// If position been given as a fraction of the image size
+	// calculate the absolute position
+	if( $this->x < 1 ) $this->x *= $aImg->width;
+	if( $this->y < 1 ) $this->y *= $aImg->height;
+
+	$aImg->PushColor($this->color);	
+	$aImg->SetFont($this->font_family,$this->font_style,$this->font_size);
+	$aImg->SetTextAlign($this->halign,$this->valign);
+	if( $this->boxed ) {
+	    if( $this->fcolor=="nofill" ) $this->fcolor=false;		
+	    $aImg->StrokeBoxedText($this->x,$this->y,$this->t,
+	    $this->dir,$this->fcolor,$this->bcolor,$this->shadow,
+	    $this->paragraph_align);
+	}
+	else {
+	    $aImg->StrokeText($this->x,$this->y,$this->t,$this->dir,
+	    $this->paragraph_align);
+	}
+	$aImg->PopColor($this->color);	
+    }
+} // Class
+
+//===================================================
+// CLASS Grid
+// Description: responsible for drawing grid lines in graph
+//===================================================
+class Grid {
+    var $img;
+    var $scale;
+    var $grid_color=array(196,196,196);
+    var $type="solid";
+    var $show=false, $showMinor=false,$weight=1;
+//---------------
+// CONSTRUCTOR
+    function Grid(&$aAxis) {
+	$this->scale = &$aAxis->scale;
+	$this->img = &$aAxis->img;
+    }
+//---------------
+// PUBLIC METHODS
+    function SetColor($aColor) {
+	$this->grid_color=$aColor;
+    }
+	
+    function SetWeight($aWeight) {
+	$this->weight=$aWeight;
+    }
+	
+    // Specify if grid should be dashed, dotted or solid
+    function SetLineStyle($aType) {
+	$this->type = $aType;
+    }
+	
+    // Decide if both major and minor grid should be displayed
+    function Show($aShowMajor=true,$aShowMinor=false) {
+	$this->show=$aShowMajor;
+	$this->showMinor=$aShowMinor;
+    }
+	
+    // Display the grid
+    function Stroke() {
+	if( $this->showMinor ) 
+	    $this->DoStroke($this->scale->ticks->ticks_pos);
+	else
+	    $this->DoStroke($this->scale->ticks->maj_ticks_pos);
+    }
+	
+//--------------
+// Private methods	
+    // Draw the grid
+    function DoStroke(&$aTicksPos) {
+	if( !$this->show )
+	    return;	
+	$this->img->SetColor($this->grid_color);
+	$this->img->SetLineWeight($this->weight);
+	$nbrgrids = count($aTicksPos);					
+	if( $this->scale->type=="y" ) {
+	    $xl=$this->img->left_margin;
+	    $xr=$this->img->width-$this->img->right_margin;
+	    for($i=0; $i<$nbrgrids; ++$i) {
+		$y=$aTicksPos[$i];
+		if( $this->type == "solid" )
+		    $this->img->Line($xl,$y,$xr,$y);
+		elseif( $this->type == "dotted" )
+		    $this->img->DashedLine($xl,$y,$xr,$y,1,6);
+		elseif( $this->type == "dashed" )
+		    $this->img->DashedLine($xl,$y,$xr,$y,2,4);
+		elseif( $this->type == "longdashed" )
+		    $this->img->DashedLine($xl,$y,$xr,$y,8,6);
+	    }
+	}
+				
+	if( $this->scale->type=="x" ) {	
+	    $yu=$this->img->top_margin;
+	    $yl=$this->img->height-$this->img->bottom_margin;
+	    $x=$aTicksPos[0];
+	    $limit=$this->img->width-$this->img->right_margin;
+	    $i=0;
+	    // We must also test for limit since we might have
+	    // an offset and the number of ticks is calculated with
+	    // assumption offset==0 so we might end up drawing one
+	    // to many gridlines
+	    while( $x<=$limit && $i<count($aTicksPos)) {
+		$x=$aTicksPos[$i];
+		if( $this->type == "solid" )				
+		    $this->img->Line($x,$yl,$x,$yu);
+		elseif( $this->type == "dotted" )
+		    $this->img->DashedLine($x,$yl,$x,$yu,1,6);
+		elseif( $this->type == "dashed" )
+		    $this->img->DashedLine($x,$yl,$x,$yu,2,4);
+		elseif( $this->type == "longdashed" )
+		    $this->img->DashedLine($x,$yl,$x,$yu,8,6);									
+		++$i;									
+	    }
+	}		
+	return true;
+    }
+} // Class
+
+//===================================================
+// CLASS Axis
+// Description: Defines X and Y axis. Notes that at the
+// moment the code is not really good since the axis on
+// several occasion must know wheter it's an X or Y axis.
+// This was a design decision to make the code easier to
+// follow. 
+//===================================================
+class Axis {
+    var $pos = false;
+    var $weight=1;
+    var $color=array(0,0,0),$label_color=array(0,0,0);
+    var $img=null,$scale=null; 
+    var $hide=false;
+    var $ticks_label=false;
+    var $show_first_label=true;
+    var $label_step=1; // Used by a text axis to specify what multiple of major steps
+    // should be labeled.
+    var $tick_step=1;
+    var $labelPos=0;   // Which side of the axis should the labels be?
+    var $title=null,$title_adjust,$title_margin,$title_side=SIDE_LEFT;
+    var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12,$label_angle=0;
+    var $tick_label_margin=6;
+//---------------
+// CONSTRUCTOR
+    function Axis(&$img,&$aScale,$color=array(0,0,0)) {
+	$this->img = &$img;
+	$this->scale = &$aScale;
+	$this->color = $color;
+	$this->title=new Text("");
+		
+	if( $aScale->type=="y" ) {
+	    $this->title_margin = 25;
+	    $this->title_adjust="middle";
+	    $this->title->SetOrientation(90);
+	    $this->tick_label_margin=6;
+	}
+	else {
+	    $this->title_margin = 5;
+	    $this->title_adjust="high";
+	    $this->title->SetOrientation(0);			
+	    $this->tick_label_margin=3;
+	}
+    }
+//---------------
+// PUBLIC METHODS	
+
+    // Utility function to set the direction for tick marks
+    function SetTickDirection($aDir) {
+	$this->scale->ticks->SetDirection($aDir);
+    }
+	
+    function SetLabelFormatString($aFormStr) {
+	$this->scale->ticks->SetLabelFormat($aFormStr);
+    }
+	
+    function SetLabelFormatCallback($aFuncName) {
+	$this->scale->ticks->SetFormatCallback($aFuncName);
+    }
+
+    // Don't display the first label
+    function HideFirstTickLabel($aHide=false) {
+	$this->show_first_label=$aHide;
+    }
+	
+    // Hide the axis
+    function Hide($aHide=true) {
+	$this->hide=$aHide;
+    }
+
+    // Weight of axis
+    function SetWeight($aWeight) {
+	$this->weight = $aWeight;
+    }
+
+    // Axis color
+    function SetColor($aColor,$aLabelColor=false) {
+	$this->color = $aColor;
+	if( !$aLabelColor ) $this->label_color = $aColor;
+	else $this->label_color = $aLabelColor;
+    }
+	
+    // Title on axis
+    function SetTitle($aTitle,$aAdjustAlign="high") {
+	$this->title->Set($aTitle);
+	$this->title_adjust=$aAdjustAlign;
+    }
+	
+    // Specify distance from the axis
+    function SetTitleMargin($aMargin) {
+	$this->title_margin=$aMargin;
+    }
+	
+    // Specify text labels for the ticks. One label for each data point
+    function SetTickLabels($aLabelArray) {
+	$this->ticks_label = $aLabelArray;
+    }
+	
+    // How far from the axis should the labels be drawn
+    function SetTickLabelMargin($aMargin) {
+	$this->tick_label_margin=$aMargin;
+    }
+	
+    // Specify that every $step of the ticks should be displayed starting
+    // at $start
+    // DEPRECATED FUNCTION: USE SetTextTickInterval() INSTEAD
+    function SetTextTicks($step,$start=0) {
+	JpGraphError::Raise("<b>JpGraph Error:</b> SetTextTicks() is deprecated. Use SetTextTickInterval() instead.");		
+    }
+
+    // Specify that every $step of the ticks should be displayed starting
+    // at $start	
+    function SetTextTickInterval($aStep,$aStart=0) {
+	$this->scale->ticks->SetTextLabelStart($aStart);
+	$this->tick_step=$aStep;
+    }
+	
+    // Specify that every $step tick mark should have a label 
+    // should be displayed starting
+    function SetTextLabelInterval($aStep) {
+	if( $aStep < 1 )
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Text label interval must be specified >= 1.");
+	$this->label_step=$aStep;
+    }
+	
+	
+    // Which side of the axis should the labels be on?
+    function SetLabelPos($aSidePos) {
+	$this->labelPos=$aSidePos;
+    }
+	
+    // Set the font
+    function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
+	$this->font_family = $aFamily;
+	$this->font_style = $aStyle;
+	$this->font_size = $aSize;
+    }
+	
+    // Which side of the axis should the axis title be?
+    function SetTitleSide($aSideOfAxis) {
+	$this->title_side = $aSideOfAxis;
+    }
+	
+    // Stroke the axis.
+    function Stroke($aOtherAxisScale) {		
+	if( $this->hide ) return;		
+	if( is_numeric($this->pos) ) {
+	    $pos=$aOtherAxisScale->Translate($this->pos);
+	}
+	else {	// Default to minimum of other scale if pos not set
+	    if( $aOtherAxisScale->GetMinVal() >= 0 || $this->pos=="min" ) {
+		$pos = $aOtherAxisScale->scale_abs[0];
+	    }
+	    else { // If negative set x-axis at 0
+		$this->pos=0;
+		$pos=$aOtherAxisScale->Translate(0);
+	    }
+	}	
+	$this->img->SetLineWeight($this->weight);
+	$this->img->SetColor($this->color);		
+	$this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
+	if( $this->scale->type == "x" ) {
+	    $this->img->FilledRectangle($this->img->left_margin,$pos,
+	    $this->img->width-$this->img->right_margin,$pos+$this->weight-1);
+	    $y=$pos+$this->img->GetFontHeight()+$this->title_margin;	
+	    if( $this->title_adjust=="high" )
+		$this->title->Pos($this->img->width-$this->img->right_margin,$y,"right","top");
+	    elseif($this->title_adjust=="middle") 
+		$this->title->Pos(($this->img->width-$this->img->left_margin-$this->img->right_margin)/2+$this->img->left_margin,$y,"center","top");
+	    elseif($this->title_adjust=="low")
+		$this->title->Pos($this->img->left_margin,$y,"left","top");
+	}
+	elseif( $this->scale->type == "y" ) {
+	    // Add line weight to the height of the axis since
+	    // the x-axis could have a width>1 and we want the axis to fit nicely together.
+	    $this->img->FilledRectangle($pos-$this->weight+1,$this->img->top_margin,
+	    $pos,$this->img->height-$this->img->bottom_margin+$this->weight-1);
+	    $x=$pos ;
+	    if( $this->title_side == SIDE_LEFT ) {
+		$x -= $this->title_margin;
+		$halign="right";
+	    }
+	    else {
+		$x += $this->title_margin;
+		$halign="left";
+	    }
+	    if( $this->title_adjust=="high" ) 
+		$this->title->Pos($x,$this->img->top_margin,$halign,"top"); 
+	    elseif($this->title_adjust=="middle" || $this->title_adjust=="center")  
+		$this->title->Pos($x,($this->img->height-$this->img->top_margin-$this->img->bottom_margin)/2+$this->img->top_margin,$halign,"center");
+	    elseif($this->title_adjust=="low")
+		$this->title->Pos($x,$this->img->height-$this->img->bottom_margin,$halign,"bottom");				
+	}
+	$this->scale->ticks->Stroke($this->img,$this->scale,$pos);
+	$this->StrokeLabels($pos);
+	$this->title->Stroke($this->img);
+    }
+
+    // Position for axis line on the "other" scale
+    function SetPos($aPosOnOtherScale) {
+	$this->pos=$aPosOnOtherScale;
+    }
+	
+    // Specify the angle for the tick labels
+    function SetLabelAngle($aAngle) {
+	$this->label_angle = $aAngle;
+    }
+	
+//---------------
+// PRIVATE METHODS	
+    // Draw all the tick labels on major tick marks
+    function StrokeLabels($aPos,$aMinor=false) {
+	$this->img->SetColor($this->label_color);
+	$this->img->SetFont($this->font_family,$this->font_style,$this->font_size);
+	$yoff=$this->img->GetFontHeight()/2;
+
+	// Only draw labels at major tick marks
+	$nbr = count($this->scale->ticks->maj_ticks_label);
+
+	// We have the option to not-display the very first mark
+	// (Usefull when the first label might interfere with another
+	// axis.)
+	if( $this->show_first_label ) $start=0;
+	else $start=1;
+			
+	// Note. the $limit is only used for the x axis since we
+	// might otherwise overshoot if the scale has been centered
+	// This is due to us "loosing" the last tick mark if we center.
+	//if( $this->scale->type=="x" )
+	//	$limit=$this->img->width-$this->img->right_margin;
+	//else
+	//	$limit=$this->img->height;
+				
+	// $i holds the current index for the label
+	$i=$start; 
+		
+	// Now run through all labels making sure we don't overshoot the end
+	// of the scale.	
+	while( $i<$nbr ) {
+	    // $tpos holds the absolute text position for the label
+	    $tpos=$this->scale->ticks->maj_ticklabels_pos[$i];															
+	    // we only draw every $label_step label
+	    if( ($i % $this->label_step)==0 ) {
+				
+				// If the label has been specified use that and in other case
+				// just label the mark with the actual scale value 
+		$m=$this->scale->ticks->GetMajor();
+				
+				// ticks_label has an entry for each data point
+		if( isset($this->ticks_label[$i*$m]) )
+		    $label=$this->ticks_label[$i*$m];
+		else
+		    $label=$this->scale->ticks->maj_ticks_label[$i];
+					
+		if( $this->scale->type == "x" ) {
+		    if( $this->label_angle==0 || $this->label_angle==90 ) 
+			$this->img->SetTextAlign("center","top");
+		    else
+			$this->img->SetTextAlign("topanchor","top");
+		    $this->img->StrokeText($tpos,$aPos+$this->tick_label_margin,$label,$this->label_angle);
+		}
+		else {
+		    // scale->type == "y"
+		    if( $this->label_angle!=0 ) 
+			JpGraphError::Raise("<b>JpGraph Error:</b> Labels at an angle are not supported on Y-axis");
+		    if( $this->labelPos == 0 ) { // To the left of y-axis					
+			$this->img->SetTextAlign("right","center");
+			$this->img->StrokeText($aPos-$this->tick_label_margin,$tpos,$label);					
+		    }
+		    else { // To the right of the y-axis
+			$this->img->SetTextAlign("left","center");
+			$this->img->StrokeText($aPos+$this->tick_label_margin,$tpos,$label);					
+		    }
+		}
+	    }
+	    ++$i;	
+	}								
+    }			
+
+} // Class
+
+//===================================================
+// CLASS Ticks
+// Description: Abstract base class for drawing linear and logarithmic
+// tick marks on axis
+//===================================================
+class Ticks {
+    var $minor_abs_size=3, $major_abs_size=5;
+    var $direction=1; // Should ticks be in(=1) the plot area or outside (=-1)?
+    var $scale;
+    var $is_set=false;
+    var $precision=-1;
+    var $supress_zerolabel=false,$supress_first=false;
+    var $supress_last=false,$supress_tickmarks=false,$supress_minor_tickmarks=false;
+    var $mincolor="",$majcolor="";
+    var $weight=1;
+    var $label_formatstr="";   // C-style format string to use for labels
+    var $label_formfunc="";
+
+
+//---------------
+// CONSTRUCTOR
+    function Ticks(&$aScale) {
+	$this->scale=&$aScale;
+    }
+
+//---------------
+// PUBLIC METHODS	
+    // Set format string for automatic labels
+    function SetLabelFormat($aFormatString) {
+	$this->label_formatstr=$aFormatString;
+    }
+	
+    function SetFormatCallback($aCallbackFuncName) {
+	$this->label_formfunc = $aCallbackFuncName;
+    }
+	
+    // Don't display the first zero label
+    function SupressZeroLabel($aFlag=true) {
+	$this->supress_zerolabel=$aFlag;
+    }
+	
+    // Don't display minor tick marks
+    function SupressMinorTickMarks($aHide=true) {
+	$this->supress_minor_tickmarks=$aHide;
+    }
+	
+    // Don't display major tick marks
+    function SupressTickMarks($aHide=true) {
+	$this->supress_tickmarks=$aHide;
+    }
+	
+    // Hide the first tick mark
+    function SupressFirst($aHide=true) {
+	$this->supress_first=$aHide;
+    }
+	
+    // Hide the last tick mark
+    function SupressLast($aHide=true) {
+	$this->supress_last=$aHide;
+    }
+
+    // Size (in pixels) of minor tick marks
+    function GetMinTickAbsSize() {
+	return $this->minor_abs_size;
+    }
+	
+    // Size (in pixels) of major tick marks
+    function GetMajTickAbsSize() {
+	return $this->major_abs_size;		
+    }
+	
+    // Have the ticks been specified
+    function IsSpecified() {
+	return $this->is_set;
+    }
+	
+    // Set the distance between major and minor tick marks
+    function Set($aMaj,$aMin) {
+	// "Virtual method"
+	// Should be implemented by the concrete subclass
+	// if any action is wanted.
+    }
+	
+    // Specify number of decimals in automtic labels
+    // Deprecated from 1.4. Use SetFormatString() instead
+    function SetPrecision($aPrecision) {
+	$this->precision=$aPrecision;
+    }
+	
+    // Which side of the axis should the ticks be on
+    function SetDirection($aSide=SIDE_RIGHT) {
+	$this->direction=$aSide;
+    }
+	
+    // Set colors for major and minor tick marks
+    function SetMarkColor($aMajorColor,$aMinorColor="") {
+	$this->majcolor=$aMajorColor;
+		
+	// If not specified use same as major
+	if( $aMinorColor=="" ) 
+	    $this->mincolor=$aMajorColor;
+	else
+	    $this->mincolor=$aMinorColor;
+    }
+	
+    function SetWeight($aWeight) {
+	$this->weight=$aWeight;
+    }
+	
+} // Class
+
+//===================================================
+// CLASS LinearTicks
+// Description: Draw linear ticks on axis
+//===================================================
+class LinearTicks extends Ticks {
+    var $minor_step=1, $major_step=2;
+    var $xlabel_offset=0,$xtick_offset=0;
+    var $label_offset=0; // What offset should the displayed label have
+    // i.e should we display 0,1,2 or 1,2,3,4 or 2,3,4 etc
+    var $text_label_start=0;
+//---------------
+// CONSTRUCTOR
+    function LinearTicks() {
+	// Empty
+    }
+
+//---------------
+// PUBLIC METHODS	
+	
+	
+    // Return major step size in world coordinates
+    function GetMajor() {
+	return $this->major_step;
+    }
+	
+    // Return minor step size in world coordinates
+    function GetMinor() {
+	return $this->minor_step;
+    }
+	
+    // Set Minor and Major ticks (in world coordinates)
+    function Set($aMajStep,$aMinStep) {
+	if( $aMajStep <= 0 || $aMinStep <= 0 ) {
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Minor or major step size is 0. Check that you haven't
+				got an accidental SetTextTicks(0) in your code.<p>
+				If this is not the case you might have stumbled upon a bug in JpGraph.
+				Please report this and if possible include the data that caused the
+				problem.");
+	}
+		
+	$this->major_step=$aMajStep;
+	$this->minor_step=$aMinStep;
+	$this->is_set = true;
+    }
+
+    // Draw linear ticks
+    function Stroke(&$img,&$scale,$pos) {
+	$maj_step_abs = $scale->scale_factor*$this->major_step;		
+	$min_step_abs = $scale->scale_factor*$this->minor_step;		
+
+	if( $min_step_abs==0 || $maj_step_abs==0 ) 
+	    JpGraphError::Raise("<b>JpGraph Error:</b> A plot has an illegal scale. This could for example be 
+			that you are trying to use text autoscaling to draw a line plot with only one point 
+			or similair abnormality (a line needs two points!).");
+	$limit = $scale->scale_abs[1];	
+	$nbrmajticks=floor(1.000001*(($scale->GetMaxVal()-$scale->GetMinVal())/$this->major_step))+1;
+	$first=0;
+		
+	// If precision hasn't been specified set it to a sensible value
+	if( $this->precision==-1 ) { 
+	    $t = log10($this->minor_step);
+	    if( $t > 0 )
+		$precision = 0;
+	    else
+		$precision = -floor($t);
+	}
+	else
+	    $precision = $this->precision;
+			
+	$img->SetLineWeight($this->weight);			
+		
+	// Handle ticks on X-axis
+	if( $scale->type == "x" ) {
+	    // Draw the major tick marks
+			
+	    $yu = $pos - $this->direction*$this->GetMajTickAbsSize();
+			
+	    // TODO: Add logic to set label_offset for text labels
+	    $label = (float)$scale->GetMinVal()+$this->text_label_start+$this->label_offset;	
+			
+	    $start_abs=$scale->scale_factor*$this->text_label_start;
+			
+	    $nbrmajticks=ceil(($scale->GetMaxVal()-$scale->GetMinVal()-$this->text_label_start )/$this->major_step)+1;			
+	    for( $i=0; $label<=$scale->GetMaxVal()+$this->label_offset; ++$i ) {
+		$x=$scale->scale_abs[0]+$start_abs+$i*$maj_step_abs+$this->xlabel_offset*$min_step_abs;				
+		$this->maj_ticklabels_pos[$i]=ceil($x);				
+				
+				// Apply format
+		if( $this->label_formfunc != "" ) {
+		    $f=$this->label_formfunc;
+		    $l = $f($label);
+		}	
+		elseif( $this->label_formatstr != "" ) 
+		    $l = sprintf($this->label_formatstr,$label);
+		else
+		    $l = sprintf("%01.".$precision."f",round($label,$precision));
+					
+		if( ($this->supress_zerolabel && ($l + 0)==0) ||
+		    ($this->supress_first && $i==0) ||
+		    ($this->supress_last  && $i==$nbrmajticks-1) )
+		    $l="";					
+		$this->maj_ticks_label[$i]=$l;
+		$label+=$this->major_step;
+				
+				// The x-position of the tick marks can be different from the labels.
+				// Note that we record the tick position (not the label) so that the grid
+				// happen upon tick marks and not labels.
+		$xtick=$scale->scale_abs[0]+$start_abs+$i*$maj_step_abs+$this->xtick_offset*$min_step_abs;
+		$this->maj_ticks_pos[$i]=ceil($xtick);				
+		if(!($this->xtick_offset > 0 && $i==$nbrmajticks-1) && !$this->supress_tickmarks) {
+		    if( $this->majcolor!="" ) $img->PushColor($this->majcolor);
+		    $img->Line($xtick,$pos,$xtick,$yu);
+		    if( $this->majcolor!="" ) $img->PopColor();
+		}
+	    }
+	    // Draw the minor tick marks
+			
+	    $yu = $pos - $this->direction*$this->GetMinTickAbsSize();
+	    $label = $scale->GetMinVal();								
+	    for( $i=0,$x=$scale->scale_abs[0]; $x<$limit; ++$i ) {
+		$x=$scale->scale_abs[0]+$i*$min_step_abs;
+		$this->ticks_pos[]=$x;
+		$this->ticks_label[]=$label;
+		$label+=$this->minor_step;
+		if( !$this->supress_tickmarks && !$this->supress_minor_tickmarks)	{						
+		    if( $this->mincolor!="" ) $img->PushColor($this->mincolor);
+		    $img->Line($x,$pos,$x,$yu); 
+		    if( $this->mincolor!="" ) $img->PopColor();
+		}
+	    }
+	}
+	elseif( $scale->type == "y" ) {
+	    // Draw the major tick marks
+	    $xr = $pos + $this->direction*$this->GetMajTickAbsSize();
+	    $label = $scale->GetMinVal();
+	    for( $i=0; $i<$nbrmajticks; ++$i) {
+		$y=$scale->scale_abs[0]+$i*$maj_step_abs;				
+				
+				// THe following two lines might seem to be unecessary but they are not!
+				// The reason being that for X-axis we separate the position of the labels
+				// and the tick marks which we don't do for the Y-axis.
+				// We therefore need to make sure both arrays are corcectly filled
+				// since Axis::StrokeLabels() uses the label positions and Grid::Stroke() uses
+				// the tick positions.
+		$this->maj_ticklabels_pos[$i]=$y;
+		$this->maj_ticks_pos[$i]=$y;
+		
+		if( $this->label_formfunc != "" ) {
+		    $f=$this->label_formfunc;
+		    $l = $f($label);
+		}	
+		elseif( $this->label_formatstr != "" ) 
+		    $l = sprintf($this->label_formatstr,$label);
+		else
+		    $l = sprintf("%01.".$precision."f",round($label,$precision));
+						
+		if( ($this->supress_zerolabel && ($l + 0)==0) ||
+		    ($this->supress_first && $i==0) ||
+		    ($this->supress_last  && $i==$nbrmajticks-1) )
+		    $l="";
+		$this->maj_ticks_label[$i]=$l; 
+		$label+=$this->major_step;	
+		if( !$this->supress_tickmarks ) {
+		    if( $this->majcolor!="" ) $img->PushColor($this->majcolor);
+		    $img->Line($pos,$y,$xr,$y);	
+		    if( $this->majcolor!="" ) $img->PopColor();
+		}
+	    }
+	    // Draw the minor tick marks
+	    $xr = $pos + $this->direction*$this->GetMinTickAbsSize();
+	    $label = $scale->GetMinVal();	
+	    for( $i=0,$y=$scale->scale_abs[0]; $y>=$limit; ) {
+		$this->ticks_pos[$i]=$y;
+		$this->ticks_label[$i]=$label;				
+		$label+=$this->minor_step;				
+		if( !$this->supress_tickmarks && !$this->supress_minor_tickmarks)	{
+		    if( $this->mincolor!="" ) $img->PushColor($this->mincolor);
+		    $img->Line($pos,$y,$xr,$y);
+		    if( $this->mincolor!="" ) $img->PopColor();
+		}
+		++$i;
+		$y=$scale->scale_abs[0]+$i*$min_step_abs;								
+	    }	
+	}	
+    }
+//---------------
+// PRIVATE METHODS
+    // Spoecify the offset of the displayed tick mark with the tick "space"
+    // Legal values for $o is [0,1] used to adjust where the tick marks and label 
+    // should be positioned within the major tick-size
+    // $lo specifies the label offset and $to specifies the tick offset
+    // this comes in handy for example in bar graphs where we wont no offset for the
+    // tick but have the labels displayed halfway under the bars.
+    function SetXLabelOffset($aLabelOff,$aTickOff=-1) {
+	$this->xlabel_offset=$aLabelOff;
+	if( $aTickOff==-1 )	// Same as label offset
+	    $this->xtick_offset=$aLabelOff;
+	else
+	    $this->xtick_offset=$aTickOff;
+	if( $aLabelOff>0 )
+	    $this->SupressLast();	// The last tick wont fit
+    }
+
+    // Which tick label should we start with?
+    function SetTextLabelStart($aTextLabelOff) {
+	$this->text_label_start=$aTextLabelOff;
+    }
+	
+} // Class
+
+//===================================================
+// CLASS LinearScale
+// Description: Handle linear scaling between screen and world 
+//===================================================
+class LinearScale {
+    var $scale=array(0,0);
+    var $scale_abs=array(0,0);
+    var $scale_factor; // Scale factor between world and screen
+    var $world_size;	// Plot area size in world coordinates
+    var $world_abs_size; // Plot area size in pixels
+    var $off; // Offset between image edge and plot area
+    var $type; // is this x or y scale ?
+    var $ticks=null; // Store ticks
+    var $autoscale_min=false; // Forced minimum value, useful to let user force 0 as start and autoscale max
+    var $gracetop=0,$gracebottom=0;
+    var $intscale=false; // Restrict autoscale to integers
+//---------------
+// CONSTRUCTOR
+    function LinearScale($aMin=0,$aMax=0,$aType="y") {
+	assert($aType=="x" || $aType=="y" );
+	assert($aMin<=$aMax);
+		
+	$this->type=$aType;
+	$this->scale=array($aMin,$aMax);		
+	$this->world_size=$aMax-$aMin;	
+	$this->ticks = new LinearTicks();
+    }
+
+//---------------
+// PUBLIC METHODS	
+    // Second phase constructor
+    function Init(&$aImg) {
+	$this->InitConstants($aImg);	
+	// We want image to notify us when the margins changes so we 
+	// can recalculate the constants.
+	// PHP <= 4.04 BUGWARNING: IT IS IMPOSSIBLE TO DO THIS IN THE CONSTRUCTOR
+	// SINCE (FOR SOME REASON) IT IS IMPOSSIBLE TO PASS A REFERENCE
+	// TO 'this' INSTEAD IT WILL ADD AN ANONYMOUS COPY OF THIS OBJECT WHICH WILL
+	// GET ALL THE NOTIFICATIONS. (This took a while to track down...)
+		
+	// Add us as an observer to class Image
+	$aImg->AddObserver("InitConstants",$this);
+    }
+	
+    // Check if scale is set or if we should autoscale
+    // We should do this is either scale or ticks has not been set
+    function IsSpecified() {
+	if( $this->GetMinVal()==$this->GetMaxVal() ) {		// Scale not set
+	    return false;
+	}
+	return true;
+    }
+	
+    // Set the minimum data value when the autoscaling is used. 
+    // Usefull if you want a fix minimum (like 0) but automtic maximum
+    function SetAutoMin($aMin) {
+	$this->autoscale_min=$aMin;
+    }
+	
+    // Specify scale "grace" value (top and bottom)
+    function SetGrace($aGraceTop,$aGraceBottom=0) {
+	if( $aGraceTop<0 || $aGraceBottom < 0  )
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Grace must be larger then 0");
+	$this->gracetop=$aGraceTop;
+	$this->gracebottom=$aGraceBottom;
+    }
+	
+    // Get the minimum value in the scale
+    function GetMinVal() {
+	return $this->scale[0];
+    }
+	
+    // get maximum value for scale
+    function GetMaxVal() {
+	return $this->scale[1];
+    }
+		
+    // Specify a new min/max value for sclae	
+    function Update(&$aImg,$aMin,$aMax) {
+	$this->scale=array($aMin,$aMax);		
+	$this->world_size=$aMax-$aMin;		
+	$this->InitConstants($aImg);					
+    }
+	
+    // Translate between world and screen
+    function Translate($aCoord) {
+	return $this->off+round(($aCoord*1.0 - $this->GetMinVal()) * $this->scale_factor,0); 
+    }
+	
+    // Relative translate (don't include offset) usefull when we just want
+    // to know the relative position (in pixels) on the axis
+    function RelTranslate($aCoord) {
+	return round(($aCoord*1.0 - $this->GetMinVal()) * $this->scale_factor,0); 
+    }
+	
+    // Restrict autoscaling to only use integers
+    function SetIntScale($aIntScale=true) {
+	$this->intscale=$aIntScale;
+    }
+	
+    // Calculate an integer autoscale
+    function IntAutoScale(&$img,$min,$max,$maxsteps,$majend=true) {
+	// Make sure limits are integers
+	$min=floor($min);
+	$max=ceil($max);
+	if( abs($min-$max)==0 ) {
+	    --$min; ++$max;
+	}
+	$gracetop=ceil(($this->gracetop/100.0))*abs($max-$min);
+	$gracebottom=ceil(($this->gracebottom/100.0))*abs($max-$min);
+	if( is_numeric($this->autoscale_min) ) {
+	    $min = ceil($this->autoscale_min);
+	    if( abs($min-$max ) == 0 ) {
+		++$max;
+		--$min;
+	    }
+	}
+		
+	$min -= $gracebottom;
+	$max += $gracetop;		
+
+	// First get tickmarks as multiples of 1, 10, ...	
+	list($num1steps,$adj1min,$adj1max,$maj1step) = 
+	    $this->IntCalcTicks($maxsteps,$min,$max,1);
+		
+	// Then get tick marks as 2:s 2, 20, ...
+	list($num2steps,$adj2min,$adj2max,$maj2step) = 
+	    $this->IntCalcTicks($maxsteps,$min,$max,5);
+		
+	// Then get tickmarks as 5:s 5, 50, 500, ...
+	list($num5steps,$adj5min,$adj5max,$maj5step) = 
+	    $this->IntCalcTicks($maxsteps,$min,$max,2);		
+
+	// Check to see whichof 1:s, 2:s or 5:s fit better with
+	// the requested number of major ticks		
+	$match1=abs($num1steps-$maxsteps);		
+	$match2=abs($num2steps-$maxsteps);
+	if( $maj5step > 1 )
+	    $match5=abs($num5steps-$maxsteps);
+	else
+	    $match5=1000000; 	// Dummy high value 
+		
+	// Compare these three values and see which is the closest match
+	// We use a 0.6 weight to gravitate towards multiple of 5:s 
+	if( $match1 < $match2 ) {
+	    if( $match1 < $match5 )
+		$r=1;			
+	    else 
+		$r=3;
+	}
+	else {
+	    if( $match2 < $match5 )
+		$r=2;			
+	    else 
+		$r=3;		
+	}	
+		
+	// Minsteps are always the same as maxsteps for integer scale
+	switch( $r ) {
+	    case 1:
+		$this->Update($img,$adj1min,$adj1max);
+	    $this->ticks->Set($maj1step,$maj1step);
+	    break;			
+	    case 2:
+		$this->Update($img,$adj2min,$adj2max);		
+	    $this->ticks->Set($maj2step,$maj2step);
+	    break;									
+	    case 3:
+		$this->Update($img,$adj5min,$adj5max);
+	    $this->ticks->Set($maj5step,$maj2step);		
+	    break;			
+	}		
+    }
+	
+	
+    // Calculate autoscale. Used if user hasn't given a scale and ticks
+    // $maxsteps is the maximum number of major tickmarks allowed.
+    function AutoScale(&$img,$min,$max,$maxsteps,$majend=true) {
+	if( $this->intscale ) {	
+	    $this->IntAutoScale($img,$min,$max,$maxsteps,$majend);
+	    return;
+	}
+	if( abs($min-$max) < 0.00001 ) {
+	    // We need some difference to be able to autoscale
+	    // make it 5% above and 5% below value
+	    if( $min==0 && $max==0 ) {		// Special case
+		$min=-1; $max=1;
+	    }
+	    else {
+		$delta = abs($max-$min)*0.05;
+		$min -= $delta;
+		$max += $delta;
+	    }
+	}
+		
+	$gracetop=($this->gracetop/100.0)*abs($max-$min);
+	$gracebottom=($this->gracebottom/100.0)*abs($max-$min);
+	if( is_numeric($this->autoscale_min) ) {
+	    $min = $this->autoscale_min;
+	    if( abs($min-$max ) < 0.00001 )
+		$max *= 1.05;
+	}
+	$min -= $gracebottom;
+	$max += $gracetop;
+
+	// First get tickmarks as multiples of 0.1, 1, 10, ...	
+	list($num1steps,$adj1min,$adj1max,$min1step,$maj1step) = 
+	    $this->CalcTicks($maxsteps,$min,$max,1,2);
+		
+	// Then get tick marks as 2:s 0.2, 2, 20, ...
+	list($num2steps,$adj2min,$adj2max,$min2step,$maj2step) = 
+	    $this->CalcTicks($maxsteps,$min,$max,5,2);
+		
+	// Then get tickmarks as 5:s 0.05, 0.5, 5, 50, ...
+	list($num5steps,$adj5min,$adj5max,$min5step,$maj5step) = 
+	    $this->CalcTicks($maxsteps,$min,$max,2,5);		
+
+	// Check to see whichof 1:s, 2:s or 5:s fit better with
+	// the requested number of major ticks		
+	$match1=abs($num1steps-$maxsteps);		
+	$match2=abs($num2steps-$maxsteps);
+	$match5=abs($num5steps-$maxsteps);
+	// Compare these three values and see which is the closest match
+	// We use a 0.8 weight to gravitate towards multiple of 5:s 
+	$r=$this->MatchMin3($match1,$match2,$match5,0.8);
+	switch( $r ) {
+	    case 1:
+		$this->Update($img,$adj1min,$adj1max);
+	    $this->ticks->Set($maj1step,$min1step);
+	    break;			
+	    case 2:
+		$this->Update($img,$adj2min,$adj2max);		
+	    $this->ticks->Set($maj2step,$min2step);
+	    break;									
+	    case 3:
+		$this->Update($img,$adj5min,$adj5max);
+	    $this->ticks->Set($maj5step,$min5step);		
+	    break;			
+	}
+    }
+
+//---------------
+// PRIVATE METHODS	
+
+    // This method recalculates all constants that are depending on the
+    // margins in the image. If the margins in the image are changed
+    // this method should be called for every scale that is registred with
+    // that image. Should really be installed as an observer of that image.
+    function InitConstants(&$img) {
+	if( $this->type=="x" ) {
+	    $this->world_abs_size=$img->width - $img->left_margin - $img->right_margin;
+	    $this->off=$img->left_margin;
+	    $this->scale_factor = 0;
+	    if( $this->world_size > 0 )
+		$this->scale_factor=$this->world_abs_size/($this->world_size*1.0);
+	}
+	else { // y scale
+	    $this->world_abs_size=$img->height - $img->top_margin - $img->bottom_margin; 
+	    $this->off=$img->top_margin+$this->world_abs_size;			
+	    $this->scale_factor = 0;			
+	    if( $this->world_size > 0 )			
+		$this->scale_factor=-$this->world_abs_size/($this->world_size*1.0);	
+	}
+	$size = $this->world_size * $this->scale_factor;
+	$this->scale_abs=array($this->off,$this->off + $size);	
+    }
+	
+    // Initialize the conversion constants for this scale
+    // This tries to pre-calculate as much as possible to speed up the
+    // actual conversion (with Translate()) later on
+    // $start	=scale start in absolute pixels (for x-scale this is an y-position
+    //				 and for an y-scale this is an x-position
+    // $len 		=absolute length in pixels of scale 			
+    function SetConstants($aStart,$aLen) {
+	$this->world_abs_size=$aLen;
+	$this->off=$aStart;
+		
+	if( $this->world_size<=0 ) {
+	    JpGraphError::Raise("<b>JpGraph Fatal Error</b>:<br>
+		 You have unfortunately stumbled upon a bug in JpGraph. <br>
+		 It seems like the scale range is ".$this->world_size." [for ".
+		 $this->type." scale] <br>
+	         Please report Bug #01 to jpgraph@aditus.nu and include the script
+		 that gave this error. <br>
+		 This problem could potentially be caused by trying to use \"illegal\"
+		 values in the input data arrays (like trying to send in strings or
+		 only NULL values) which causes the autoscaling to fail.");
+	}
+		
+	// scale_factor = number of pixels per world unit
+	$this->scale_factor=$this->world_abs_size/($this->world_size*1.0);
+		
+	// scale_abs = start and end points of scale in absolute pixels
+	$this->scale_abs=array($this->off,$this->off+$this->world_size*$this->scale_factor);		
+    }
+	
+	
+    // Calculate number of ticks steps with a specific division
+    // $a is the divisor of 10**x to generate the first maj tick intervall
+    // $a=1, $b=2 give major ticks with multiple of 10, ...,0.1,1,10,...
+    // $a=5, $b=2 give major ticks with multiple of 2:s ...,0.2,2,20,...
+    // $a=2, $b=5 give major ticks with multiple of 5:s ...,0.5,5,50,...
+    // We return a vector of
+    // 	[$numsteps,$adjmin,$adjmax,$minstep,$majstep]
+    // If $majend==true then the first and last marks on the axis will be major
+    // labeled tick marks otherwise it will be adjusted to the closest min tick mark
+    function CalcTicks($maxsteps,$min,$max,$a,$b,$majend=true) {
+	$diff=$max-$min; 
+	if( $diff==0 )
+	    $ld=0;
+	else
+	    $ld=floor(log10($diff));
+		
+	// Gravitate min towards zero if we are close		
+	if( $min>0 && $min < pow(10,$ld) ) $min=0;
+		
+	$majstep=pow(10,$ld-1)/$a; 
+	$minstep=$majstep/$b;
+	$adjmax=ceil($max/$minstep)*$minstep;
+	$adjmin=floor($min/$minstep)*$minstep;	
+	$adjdiff = $adjmax-$adjmin;
+	$numsteps=$adjdiff/$majstep; 
+	while( $numsteps>$maxsteps ) {
+	    $majstep=pow(10,$ld)/$a; 
+	    $numsteps=$adjdiff/$majstep;
+	    ++$ld;
+	}
+		
+	$minstep=$majstep/$b;
+	$adjmin=floor($min/$minstep)*$minstep;	
+	$adjdiff = $adjmax-$adjmin;		
+	if( $majend ) {
+	    $adjmin = floor($min/$majstep)*$majstep;	
+	    $adjdiff = $adjmax-$adjmin;		
+	    $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
+	}
+	else
+	    $adjmax=ceil($max/$minstep)*$minstep;
+			
+	return array($numsteps,$adjmin,$adjmax,$minstep,$majstep);
+    }
+	
+    function IntCalcTicks($maxsteps,$min,$max,$a,$majend=true) {
+	$diff=$max-$min; 
+	if( $diff==0 )
+	    $ld=0;
+	else
+	    $ld=floor(log10($diff));
+		
+	// Gravitate min towards zero if we are close		
+	if( $min>0 && $min < pow(10,$ld) ) $min=0;
+		
+	$majstep=pow(10,$ld-1)/$a; 
+	$adjmax=ceil($max/$majstep)*$majstep;
+	$adjmin=floor($min/$majstep)*$majstep;	
+	$adjdiff = $adjmax-$adjmin;
+	$numsteps=$adjdiff/$majstep; 
+	while( $numsteps>$maxsteps ) {
+	    $majstep=pow(10,$ld)/$a; 
+	    $numsteps=$adjdiff/$majstep;
+	    ++$ld;
+	}
+		
+	$adjmin=floor($min/$majstep)*$majstep;	
+	$adjdiff = $adjmax-$adjmin;		
+	if( $majend ) {
+	    $adjmin = floor($min/$majstep)*$majstep;	
+	    $adjdiff = $adjmax-$adjmin;		
+	    $adjmax = ceil($adjdiff/$majstep)*$majstep+$adjmin;
+	}
+	else
+	    $adjmax=ceil($max/$majstep)*$majstep;
+			
+	return array($numsteps,$adjmin,$adjmax,$majstep);		
+    }
+
+
+	
+    // Determine the minimum of three values witha  weight for last value
+    function MatchMin3($a,$b,$c,$weight) {
+	if( $a < $b ) {
+	    if( $a < ($c*$weight) ) 
+		return 1; // $a smallest
+	    else 
+		return 3; // $c smallest
+	}
+	elseif( $b < ($c*$weight) ) 
+	    return 2; // $b smallest
+	return 3; // $c smallest
+    }
+} // Class
+
+//===================================================
+// CLASS RGB
+// Description: Color definitions as RGB triples
+//===================================================
+class RGB {
+    var $rgb_table;
+    var $img;
+    function RGB(&$aImg) {
+	$this->img = $aImg;
+		
+	// Conversion array between color names and RGB
+	$this->rgb_table = array(
+	    "aqua"=> array(0,255,255),		
+	    "lime"=> array(0,255,0),		
+	    "teal"=> array(0,128,128),
+	    "whitesmoke"=>array(245,245,245),
+	    "gainsboro"=>array(220,220,220),
+	    "oldlace"=>array(253,245,230),
+	    "linen"=>array(250,240,230),
+	    "antiquewhite"=>array(250,235,215),
+	    "papayawhip"=>array(255,239,213),
+	    "blanchedalmond"=>array(255,235,205),
+	    "bisque"=>array(255,228,196),
+	    "peachpuff"=>array(255,218,185),
+	    "navajowhite"=>array(255,222,173),
+	    "moccasin"=>array(255,228,181),
+	    "cornsilk"=>array(255,248,220),
+	    "ivory"=>array(255,255,240),
+	    "lemonchiffon"=>array(255,250,205),
+	    "seashell"=>array(255,245,238),
+	    "mintcream"=>array(245,255,250),
+	    "azure"=>array(240,255,255),
+	    "aliceblue"=>array(240,248,255),
+	    "lavender"=>array(230,230,250),
+	    "lavenderblush"=>array(255,240,245),
+	    "mistyrose"=>array(255,228,225),
+	    "white"=>array(255,255,255),
+	    "black"=>array(0,0,0),
+	    "darkslategray"=>array(47,79,79),
+	    "dimgray"=>array(105,105,105),
+	    "slategray"=>array(112,128,144),
+	    "lightslategray"=>array(119,136,153),
+	    "gray"=>array(190,190,190),
+	    "lightgray"=>array(211,211,211),
+	    "midnightblue"=>array(25,25,112),
+	    "navy"=>array(0,0,128),
+	    "cornflowerblue"=>array(100,149,237),
+	    "darkslateblue"=>array(72,61,139),
+	    "slateblue"=>array(106,90,205),
+	    "mediumslateblue"=>array(123,104,238),
+	    "lightslateblue"=>array(132,112,255),
+	    "mediumblue"=>array(0,0,205),
+	    "royalblue"=>array(65,105,225),
+	    "blue"=>array(0,0,255),
+	    "dodgerblue"=>array(30,144,255),
+	    "deepskyblue"=>array(0,191,255),
+	    "skyblue"=>array(135,206,235),
+	    "lightskyblue"=>array(135,206,250),
+	    "steelblue"=>array(70,130,180),
+	    "lightred"=>array(211,167,168),
+	    "lightsteelblue"=>array(176,196,222),
+	    "lightblue"=>array(173,216,230),
+	    "powderblue"=>array(176,224,230),
+	    "paleturquoise"=>array(175,238,238),
+	    "darkturquoise"=>array(0,206,209),
+	    "mediumturquoise"=>array(72,209,204),
+	    "turquoise"=>array(64,224,208),
+	    "cyan"=>array(0,255,255),
+	    "lightcyan"=>array(224,255,255),
+	    "cadetblue"=>array(95,158,160),
+	    "mediumaquamarine"=>array(102,205,170),
+	    "aquamarine"=>array(127,255,212),
+	    "darkgreen"=>array(0,100,0),
+	    "darkolivegreen"=>array(85,107,47),
+	    "darkseagreen"=>array(143,188,143),
+	    "seagreen"=>array(46,139,87),
+	    "mediumseagreen"=>array(60,179,113),
+	    "lightseagreen"=>array(32,178,170),
+	    "palegreen"=>array(152,251,152),
+	    "springgreen"=>array(0,255,127),
+	    "lawngreen"=>array(124,252,0),
+	    "green"=>array(0,255,0),
+	    "chartreuse"=>array(127,255,0),
+	    "mediumspringgreen"=>array(0,250,154),
+	    "greenyellow"=>array(173,255,47),
+	    "limegreen"=>array(50,205,50),
+	    "yellowgreen"=>array(154,205,50),
+	    "forestgreen"=>array(34,139,34),
+	    "olivedrab"=>array(107,142,35),
+	    "darkkhaki"=>array(189,183,107),
+	    "khaki"=>array(240,230,140),
+	    "palegoldenrod"=>array(238,232,170),
+	    "lightgoldenrodyellow"=>array(250,250,210),
+	    "lightyellow"=>array(255,255,200),
+	    "yellow"=>array(255,255,0),
+	    "gold"=>array(255,215,0),
+	    "lightgoldenrod"=>array(238,221,130),
+	    "goldenrod"=>array(218,165,32),
+	    "darkgoldenrod"=>array(184,134,11),
+	    "rosybrown"=>array(188,143,143),
+	    "indianred"=>array(205,92,92),
+	    "saddlebrown"=>array(139,69,19),
+	    "sienna"=>array(160,82,45),
+	    "peru"=>array(205,133,63),
+	    "burlywood"=>array(222,184,135),
+	    "beige"=>array(245,245,220),
+	    "wheat"=>array(245,222,179),
+	    "sandybrown"=>array(244,164,96),
+	    "tan"=>array(210,180,140),
+	    "chocolate"=>array(210,105,30),
+	    "firebrick"=>array(178,34,34),
+	    "brown"=>array(165,42,42),
+	    "darksalmon"=>array(233,150,122),
+	    "salmon"=>array(250,128,114),
+	    "lightsalmon"=>array(255,160,122),
+	    "orange"=>array(255,165,0),
+	    "darkorange"=>array(255,140,0),
+	    "coral"=>array(255,127,80),
+	    "lightcoral"=>array(240,128,128),
+	    "tomato"=>array(255,99,71),
+	    "orangered"=>array(255,69,0),
+	    "red"=>array(255,0,0),
+	    "hotpink"=>array(255,105,180),
+	    "deeppink"=>array(255,20,147),
+	    "pink"=>array(255,192,203),
+	    "lightpink"=>array(255,182,193),
+	    "palevioletred"=>array(219,112,147),
+	    "maroon"=>array(176,48,96),
+	    "mediumvioletred"=>array(199,21,133),
+	    "violetred"=>array(208,32,144),
+	    "magenta"=>array(255,0,255),
+	    "violet"=>array(238,130,238),
+	    "plum"=>array(221,160,221),
+	    "orchid"=>array(218,112,214),
+	    "mediumorchid"=>array(186,85,211),
+	    "darkorchid"=>array(153,50,204),
+	    "darkviolet"=>array(148,0,211),
+	    "blueviolet"=>array(138,43,226),
+	    "purple"=>array(160,32,240),
+	    "mediumpurple"=>array(147,112,219),
+	    "thistle"=>array(216,191,216),
+	    "snow1"=>array(255,250,250),
+	    "snow2"=>array(238,233,233),
+	    "snow3"=>array(205,201,201),
+	    "snow4"=>array(139,137,137),
+	    "seashell1"=>array(255,245,238),
+	    "seashell2"=>array(238,229,222),
+	    "seashell3"=>array(205,197,191),
+	    "seashell4"=>array(139,134,130),
+	    "AntiqueWhite1"=>array(255,239,219),
+	    "AntiqueWhite2"=>array(238,223,204),
+	    "AntiqueWhite3"=>array(205,192,176),
+	    "AntiqueWhite4"=>array(139,131,120),
+	    "bisque1"=>array(255,228,196),
+	    "bisque2"=>array(238,213,183),
+	    "bisque3"=>array(205,183,158),
+	    "bisque4"=>array(139,125,107),
+	    "peachPuff1"=>array(255,218,185),
+	    "peachpuff2"=>array(238,203,173),
+	    "peachpuff3"=>array(205,175,149),
+	    "peachpuff4"=>array(139,119,101),
+	    "navajowhite1"=>array(255,222,173),
+	    "navajowhite2"=>array(238,207,161),
+	    "navajowhite3"=>array(205,179,139),
+	    "navajowhite4"=>array(139,121,94),
+	    "lemonchiffon1"=>array(255,250,205),
+	    "lemonchiffon2"=>array(238,233,191),
+	    "lemonchiffon3"=>array(205,201,165),
+	    "lemonchiffon4"=>array(139,137,112),
+	    "ivory1"=>array(255,255,240),
+	    "ivory2"=>array(238,238,224),
+	    "ivory3"=>array(205,205,193),
+	    "ivory4"=>array(139,139,131),
+	    "honeydew"=>array(193,205,193),
+	    "lavenderblush1"=>array(255,240,245),
+	    "lavenderblush2"=>array(238,224,229),
+	    "lavenderblush3"=>array(205,193,197),
+	    "lavenderblush4"=>array(139,131,134),
+	    "mistyrose1"=>array(255,228,225),
+	    "mistyrose2"=>array(238,213,210),
+	    "mistyrose3"=>array(205,183,181),
+	    "mistyrose4"=>array(139,125,123),
+	    "azure1"=>array(240,255,255),
+	    "azure2"=>array(224,238,238),
+	    "azure3"=>array(193,205,205),
+	    "azure4"=>array(131,139,139),
+	    "slateblue1"=>array(131,111,255),
+	    "slateblue2"=>array(122,103,238),
+	    "slateblue3"=>array(105,89,205),
+	    "slateblue4"=>array(71,60,139),
+	    "royalblue1"=>array(72,118,255),
+	    "royalblue2"=>array(67,110,238),
+	    "royalblue3"=>array(58,95,205),
+	    "royalblue4"=>array(39,64,139),
+	    "dodgerblue1"=>array(30,144,255),
+	    "dodgerblue2"=>array(28,134,238),
+	    "dodgerblue3"=>array(24,116,205),
+	    "dodgerblue4"=>array(16,78,139),
+	    "steelblue1"=>array(99,184,255),
+	    "steelblue2"=>array(92,172,238),
+	    "steelblue3"=>array(79,148,205),
+	    "steelblue4"=>array(54,100,139),
+	    "deepskyblue1"=>array(0,191,255),
+	    "deepskyblue2"=>array(0,178,238),
+	    "deepskyblue3"=>array(0,154,205),
+	    "deepskyblue4"=>array(0,104,139),
+	    "skyblue1"=>array(135,206,255),
+	    "skyblue2"=>array(126,192,238),
+	    "skyblue3"=>array(108,166,205),
+	    "skyblue4"=>array(74,112,139),
+	    "lightskyblue1"=>array(176,226,255),
+	    "lightskyblue2"=>array(164,211,238),
+	    "lightskyblue3"=>array(141,182,205),
+	    "lightskyblue4"=>array(96,123,139),
+	    "slategray1"=>array(198,226,255),
+	    "slategray2"=>array(185,211,238),
+	    "slategray3"=>array(159,182,205),
+	    "slategray4"=>array(108,123,139),
+	    "lightsteelblue1"=>array(202,225,255),
+	    "lightsteelblue2"=>array(188,210,238),
+	    "lightsteelblue3"=>array(162,181,205),
+	    "lightsteelblue4"=>array(110,123,139),
+	    "lightblue1"=>array(191,239,255),
+	    "lightblue2"=>array(178,223,238),
+	    "lightblue3"=>array(154,192,205),
+	    "lightblue4"=>array(104,131,139),
+	    "lightcyan1"=>array(224,255,255),
+	    "lightcyan2"=>array(209,238,238),
+	    "lightcyan3"=>array(180,205,205),
+	    "lightcyan4"=>array(122,139,139),
+	    "paleturquoise1"=>array(187,255,255),
+	    "paleturquoise2"=>array(174,238,238),
+	    "paleturquoise3"=>array(150,205,205),
+	    "paleturquoise4"=>array(102,139,139),
+	    "cadetblue1"=>array(152,245,255),
+	    "cadetblue2"=>array(142,229,238),
+	    "cadetblue3"=>array(122,197,205),
+	    "cadetblue4"=>array(83,134,139),
+	    "turquoise1"=>array(0,245,255),
+	    "turquoise2"=>array(0,229,238),
+	    "turquoise3"=>array(0,197,205),
+	    "turquoise4"=>array(0,134,139),
+	    "cyan1"=>array(0,255,255),
+	    "cyan2"=>array(0,238,238),
+	    "cyan3"=>array(0,205,205),
+	    "cyan4"=>array(0,139,139),
+	    "darkslategray1"=>array(151,255,255),
+	    "darkslategray2"=>array(141,238,238),
+	    "darkslategray3"=>array(121,205,205),
+	    "darkslategray4"=>array(82,139,139),
+	    "aquamarine1"=>array(127,255,212),
+	    "aquamarine2"=>array(118,238,198),
+	    "aquamarine3"=>array(102,205,170),
+	    "aquamarine4"=>array(69,139,116),
+	    "darkseagreen1"=>array(193,255,193),
+	    "darkseagreen2"=>array(180,238,180),
+	    "darkseagreen3"=>array(155,205,155),
+	    "darkseagreen4"=>array(105,139,105),
+	    "seagreen1"=>array(84,255,159),
+	    "seagreen2"=>array(78,238,148),
+	    "seagreen3"=>array(67,205,128),
+	    "seagreen4"=>array(46,139,87),
+	    "palegreen1"=>array(154,255,154),
+	    "palegreen2"=>array(144,238,144),
+	    "palegreen3"=>array(124,205,124),
+	    "palegreen4"=>array(84,139,84),
+	    "springgreen1"=>array(0,255,127),
+	    "springgreen2"=>array(0,238,118),
+	    "springgreen3"=>array(0,205,102),
+	    "springgreen4"=>array(0,139,69),
+	    "chartreuse1"=>array(127,255,0),
+	    "chartreuse2"=>array(118,238,0),
+	    "chartreuse3"=>array(102,205,0),
+	    "chartreuse4"=>array(69,139,0),
+	    "olivedrab1"=>array(192,255,62),
+	    "olivedrab2"=>array(179,238,58),
+	    "olivedrab3"=>array(154,205,50),
+	    "olivedrab4"=>array(105,139,34),
+	    "darkolivegreen1"=>array(202,255,112),
+	    "darkolivegreen2"=>array(188,238,104),
+	    "darkolivegreen3"=>array(162,205,90),
+	    "darkolivegreen4"=>array(110,139,61),
+	    "khaki1"=>array(255,246,143),
+	    "khaki2"=>array(238,230,133),
+	    "khaki3"=>array(205,198,115),
+	    "khaki4"=>array(139,134,78),
+	    "lightgoldenrod1"=>array(255,236,139),
+	    "lightgoldenrod2"=>array(238,220,130),
+	    "lightgoldenrod3"=>array(205,190,112),
+	    "lightgoldenrod4"=>array(139,129,76),
+	    "yellow1"=>array(255,255,0),
+	    "yellow2"=>array(238,238,0),
+	    "yellow3"=>array(205,205,0),
+	    "yellow4"=>array(139,139,0),
+	    "gold1"=>array(255,215,0),
+	    "gold2"=>array(238,201,0),
+	    "gold3"=>array(205,173,0),
+	    "gold4"=>array(139,117,0),
+	    "goldenrod1"=>array(255,193,37),
+	    "goldenrod2"=>array(238,180,34),
+	    "goldenrod3"=>array(205,155,29),
+	    "goldenrod4"=>array(139,105,20),
+	    "darkgoldenrod1"=>array(255,185,15),
+	    "darkgoldenrod2"=>array(238,173,14),
+	    "darkgoldenrod3"=>array(205,149,12),
+	    "darkgoldenrod4"=>array(139,101,8),
+	    "rosybrown1"=>array(255,193,193),
+	    "rosybrown2"=>array(238,180,180),
+	    "rosybrown3"=>array(205,155,155),
+	    "rosybrown4"=>array(139,105,105),
+	    "indianred1"=>array(255,106,106),
+	    "indianred2"=>array(238,99,99),
+	    "indianred3"=>array(205,85,85),
+	    "indianred4"=>array(139,58,58),
+	    "sienna1"=>array(255,130,71),
+	    "sienna2"=>array(238,121,66),
+	    "sienna3"=>array(205,104,57),
+	    "sienna4"=>array(139,71,38),
+	    "burlywood1"=>array(255,211,155),
+	    "burlywood2"=>array(238,197,145),
+	    "burlywood3"=>array(205,170,125),
+	    "burlywood4"=>array(139,115,85),
+	    "wheat1"=>array(255,231,186),
+	    "wheat2"=>array(238,216,174),
+	    "wheat3"=>array(205,186,150),
+	    "wheat4"=>array(139,126,102),
+	    "tan1"=>array(255,165,79),
+	    "tan2"=>array(238,154,73),
+	    "tan3"=>array(205,133,63),
+	    "tan4"=>array(139,90,43),
+	    "chocolate1"=>array(255,127,36),
+	    "chocolate2"=>array(238,118,33),
+	    "chocolate3"=>array(205,102,29),
+	    "chocolate4"=>array(139,69,19),
+	    "firebrick1"=>array(255,48,48),
+	    "firebrick2"=>array(238,44,44),
+	    "firebrick3"=>array(205,38,38),
+	    "firebrick4"=>array(139,26,26),
+	    "brown1"=>array(255,64,64),
+	    "brown2"=>array(238,59,59),
+	    "brown3"=>array(205,51,51),
+	    "brown4"=>array(139,35,35),
+	    "salmon1"=>array(255,140,105),
+	    "salmon2"=>array(238,130,98),
+	    "salmon3"=>array(205,112,84),
+	    "salmon4"=>array(139,76,57),
+	    "lightsalmon1"=>array(255,160,122),
+	    "lightsalmon2"=>array(238,149,114),
+	    "lightsalmon3"=>array(205,129,98),
+	    "lightsalmon4"=>array(139,87,66),
+	    "orange1"=>array(255,165,0),
+	    "orange2"=>array(238,154,0),
+	    "orange3"=>array(205,133,0),
+	    "orange4"=>array(139,90,0),
+	    "darkorange1"=>array(255,127,0),
+	    "darkorange2"=>array(238,118,0),
+	    "darkorange3"=>array(205,102,0),
+	    "darkorange4"=>array(139,69,0),
+	    "coral1"=>array(255,114,86),
+	    "coral2"=>array(238,106,80),
+	    "coral3"=>array(205,91,69),
+	    "coral4"=>array(139,62,47),
+	    "tomato1"=>array(255,99,71),
+	    "tomato2"=>array(238,92,66),
+	    "tomato3"=>array(205,79,57),
+	    "tomato4"=>array(139,54,38),
+	    "orangered1"=>array(255,69,0),
+	    "orangered2"=>array(238,64,0),
+	    "orangered3"=>array(205,55,0),
+	    "orangered4"=>array(139,37,0),
+	    "deeppink1"=>array(255,20,147),
+	    "deeppink2"=>array(238,18,137),
+	    "deeppink3"=>array(205,16,118),
+	    "deeppink4"=>array(139,10,80),
+	    "hotpink1"=>array(255,110,180),
+	    "hotpink2"=>array(238,106,167),
+	    "hotpink3"=>array(205,96,144),
+	    "hotpink4"=>array(139,58,98),
+	    "pink1"=>array(255,181,197),
+	    "pink2"=>array(238,169,184),
+	    "pink3"=>array(205,145,158),
+	    "pink4"=>array(139,99,108),
+	    "lightpink1"=>array(255,174,185),
+	    "lightpink2"=>array(238,162,173),
+	    "lightpink3"=>array(205,140,149),
+	    "lightpink4"=>array(139,95,101),
+	    "palevioletred1"=>array(255,130,171),
+	    "palevioletred2"=>array(238,121,159),
+	    "palevioletred3"=>array(205,104,137),
+	    "palevioletred4"=>array(139,71,93),
+	    "maroon1"=>array(255,52,179),
+	    "maroon2"=>array(238,48,167),
+	    "maroon3"=>array(205,41,144),
+	    "maroon4"=>array(139,28,98),
+	    "violetred1"=>array(255,62,150),
+	    "violetred2"=>array(238,58,140),
+	    "violetred3"=>array(205,50,120),
+	    "violetred4"=>array(139,34,82),
+	    "magenta1"=>array(255,0,255),
+	    "magenta2"=>array(238,0,238),
+	    "magenta3"=>array(205,0,205),
+	    "magenta4"=>array(139,0,139),
+	    "mediumred"=>array(140,34,34),         
+	    "orchid1"=>array(255,131,250),
+	    "orchid2"=>array(238,122,233),
+	    "orchid3"=>array(205,105,201),
+	    "orchid4"=>array(139,71,137),
+	    "plum1"=>array(255,187,255),
+	    "plum2"=>array(238,174,238),
+	    "plum3"=>array(205,150,205),
+	    "plum4"=>array(139,102,139),
+	    "mediumorchid1"=>array(224,102,255),
+	    "mediumorchid2"=>array(209,95,238),
+	    "mediumorchid3"=>array(180,82,205),
+	    "mediumorchid4"=>array(122,55,139),
+	    "darkorchid1"=>array(191,62,255),
+	    "darkorchid2"=>array(178,58,238),
+	    "darkorchid3"=>array(154,50,205),
+	    "darkorchid4"=>array(104,34,139),
+	    "purple1"=>array(155,48,255),
+	    "purple2"=>array(145,44,238),
+	    "purple3"=>array(125,38,205),
+	    "purple4"=>array(85,26,139),
+	    "mediumpurple1"=>array(171,130,255),
+	    "mediumpurple2"=>array(159,121,238),
+	    "mediumpurple3"=>array(137,104,205),
+	    "mediumpurple4"=>array(93,71,139),
+	    "thistle1"=>array(255,225,255),
+	    "thistle2"=>array(238,210,238),
+	    "thistle3"=>array(205,181,205),
+	    "thistle4"=>array(139,123,139),
+	    "gray1"=>array(10,10,10),
+	    "gray2"=>array(40,40,30),
+	    "gray3"=>array(70,70,70),
+	    "gray4"=>array(100,100,100),
+	    "gray5"=>array(130,130,130),
+	    "gray6"=>array(160,160,160),
+	    "gray7"=>array(190,190,190),
+	    "gray8"=>array(210,210,210),
+	    "gray9"=>array(240,240,240),
+	    "darkgray"=>array(100,100,100),
+	    "darkblue"=>array(0,0,139),
+	    "darkcyan"=>array(0,139,139),
+	    "darkmagenta"=>array(139,0,139),
+	    "darkred"=>array(139,0,0),
+	    "silver"=>array(192, 192, 192),
+	    "eggplant"=>array(144,176,168),
+	    "lightgreen"=>array(144,238,144));		
+    }
+//----------------
+// PUBLIC METHODS
+    // Colors can be specified as either
+    // 1. #xxxxxx			HTML style
+    // 2. "colorname" 	as a named color
+    // 3. array(r,g,b)	RGB triple
+    // This function translates this to a native RGB format and returns an 
+    // RGB triple.
+    function Color($aColor) {
+	if (is_string($aColor)) {
+
+	   // Extract potential adjustment figure at end of color
+	   // specification
+	    $aColor = strtok($aColor,":");
+	    $adj = 0+strtok(":");
+	    if( $adj==0 ) $adj=1;
+	    if (substr($aColor, 0, 1) == "#") {
+		return array($adj*hexdec(substr($aColor, 1, 2)), 
+			     $adj*hexdec(substr($aColor, 3, 2)),
+			     $adj*hexdec(substr($aColor, 5, 2)));
+	    } else {
+      		if(!isset($this->rgb_table[$aColor]) )
+		    JpGraphError::Raise("<b>JpGraph Error:</b> Unknown color: <strong>$aColor</strong>");
+		$tmp=$this->rgb_table[$aColor];
+		return array($adj*$tmp[0],$adj*$tmp[1],$adj*$tmp[2]);
+	    }
+	} elseif( is_array($aColor) && (count($aColor)==3) ) {
+	    return $aColor;
+	}
+	else
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Unknown color specification: $aColor , size=".count($aColor));
+    }
+	
+    // Compare two colors
+    // return true if equal
+    function Equal($aCol1,$aCol2) {
+	$c1 = $this->Color($aCol1);
+	$c2 = $this->Color($aCol2);
+	if( $c1[0]==$c2[0] && $c1[1]==$c2[1] && $c1[2]==$c2[2] )
+	    return true;
+	else
+	    return false;
+    }
+	
+    // Allocate a new color in the current image
+    // Return new color index, -1 if no more colors could be allocated
+    function Allocate($aColor) {
+	list ($r, $g, $b) = $this->color($aColor);
+	if($GLOBALS['gd2']==true) {
+	    return imagecolorresolvealpha($this->img, $r, $g, $b, 0);
+	} else {
+	    $index = imagecolorexact($this->img, $r, $g, $b);
+	    if ($index == -1) {
+      		$index = imagecolorallocate($this->img, $r, $g, $b);
+      		if( USE_APPROX_COLORS && $index == -1 )
+		    $index = imagecolorresolve($this->img, $r, $g, $b);
+	    } 
+	    return $index;
+	}
+    }
+} // Class
+
+	
+//===================================================
+// CLASS Image
+// Description: Wrapper class with some goodies to form the
+// Interface to low level image drawing routines.
+//===================================================
+class Image {
+    var $img_format;
+    var $expired=false;
+    var $img;
+    var $left_margin=30,$right_margin=30,$top_margin=20,$bottom_margin=30;
+    var $plotwidth,$plotheight;
+    var $rgb;
+    var $current_color,$current_color_name;
+    var $lastx=0, $lasty=0;
+    var $width, $height;
+    var $line_weight=1;
+    var $line_style=1;	// Default line style is solid
+    var $obs_list=array();
+    var $font_size=12,$font_family=FF_FONT1, $font_style=FS_NORMAL;
+    var $text_halign="left",$text_valign="bottom";
+    var $ttf=null;
+    var $use_anti_aliasing=false;
+    var $quality=null;
+    var $colorstack=array(),$colorstackidx=0;
+    //---------------
+    // CONSTRUCTOR
+    function Image($aWidth,$aHeight,$aFormat=DEFAULT_GFORMAT) {
+	$this->CreateImgCanvas($aWidth,$aHeight);
+	if( !$this->SetImgFormat($aFormat) ) {
+	    JpGraphError::Raise("JpGraph: Selected graphic format is either not supported or unknown [$aFormat]");
+	}
+	$this->ttf = new TTF();
+    }
+	
+    function SetAutoMargin() {	
+	$min_bm=10;
+	if( BRAND_TIMING )
+	    $min_bm=15;		
+	$lm = max(12,$this->width/7);
+	$rm = max(12,$this->width/10);
+	$tm = max(24,$this->height/7);
+	$bm = max($min_bm,$this->height/7);
+	$this->SetMargin($lm,$rm,$tm,$bm);		
+    }
+	
+    function CreateImgCanvas($aWidth=-1,$aHeight=-1) {
+	$this->width=$aWidth;
+	$this->height=$aHeight;		
+
+	$this->SetAutoMargin();
+
+	if( $aWidth==-1 || $aHeight==-1 ) {
+	    // We will set the final size later. 
+	    // Note: The size must be specified before any other
+	    // img routines that stroke anything are called.
+	    $this->img = null;
+	    $this->rgb = null;
+	    return;
+	}
+		
+	if( $GLOBALS['gd2']==true && USE_TRUECOLOR ) {
+	    $this->img = imagecreatetruecolor($aWidth, $aHeight);	
+	    imagefilledrectangle($this->img, 0, 0, $aWidth, $aHeight, 0xffffff);
+	} else {
+	    $this->img = imagecreate($aWidth, $aHeight);	
+	}		
+	assert($this->img != 0);		
+	$this->rgb = new RGB($this->img);				
+		
+	// First index is background so this will be white
+	$this->SetColor("white");
+    }
+	
+				
+    //---------------
+    // PUBLIC METHODS	
+
+    // Add observer. The observer will be notified when
+    // the margin changes
+    function AddObserver($aMethod,&$aObject) {
+	$this->obs_list[]=array($aMethod,&$aObject);
+    }
+	
+    // Call all observers
+    function NotifyObservers() {
+	//	foreach($this->obs_list as $o)
+	//		$o[1]->$o[0]($this);
+	for($i=0; $i < count($this->obs_list); ++$i) {
+	    $obj = & $this->obs_list[$i][1];
+	    $method = $this->obs_list[$i][0];
+	    $obj->$method($this);
+	}
+    }	
+	
+    function SetFont($family,$style=FS_NORMAL,$size=10) {
+	if($family==FONT1_BOLD || $family==FONT2_BOLD || $family==FONT0 || $family==FONT1 || $family==FONT2 )
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Usage of FONT0, FONT1, FONT2 is deprecated. Use FF_xxx instead.");
+		
+	$this->font_family=$family;
+	$this->font_style=$style;
+	$this->font_size=$size;
+	if( ($this->font_family==FF_FONT1 || $this->font_family==FF_FONT2) && $this->font_style==FS_BOLD ){
+	    ++$this->font_family;
+	}
+    }
+	
+    // Get the specific height for a text string
+    function GetTextHeight($txt="",$angle=0) {
+	// Builtin font?
+	$tmp = split("\n",$txt);
+	$n = count($tmp);
+	$m=0;
+	for($i=0; $i<count($tmp); ++$i)
+	    $m = max($m,strlen($tmp[$i]));
+
+	if( $this->font_family <= FF_FONT2+1 ) {
+	    if( $angle==0 )
+		return $n*imagefontheight($this->font_family);
+	    else 
+		return $m*imagefontwidth($this->font_family);
+	}
+	else {
+	    $file = $this->ttf->File($this->font_family,$this->font_style);			
+	    $bbox = ImageTTFBBox($this->font_size,$angle,$file,"XXOOMM"/*$txt*/);
+	    return $n*(abs($bbox[5])+abs($bbox[1])); // upper_right_y - lower_left_y			
+	}
+    }
+	
+    // Estimate font height
+    function GetFontHeight($txt="XMg",$angle=0) {
+	$tmp = split("\n",$txt);
+	return $this->GetTextHeight($tmp[0],$angle);
+    }
+	
+    // Approximate font width with width of letter "O"
+    function GetFontWidth($txt="O",$angle=0) {
+	return $this->GetTextWidth($txt,$angle);
+    }
+	
+    // Get actual width of text in absolute pixels
+    function GetTextWidth($txt,$angle=0) {
+	// Builtin font?
+	$tmp = split("\n",$txt);
+	$n = count($tmp);
+	$m=0;
+	for($i=0; $i<count($tmp); ++$i)
+	    $m = max($m,strlen($tmp[$i]));
+
+	if( $this->font_family <= FF_FONT2+1 ) {
+	    if( $angle==0 ) {
+		$width=$m*imagefontwidth($this->font_family);
+		return $width;
+	    }
+	    else
+		return $n*imagefontheight($this->font_family); // 90 degrees internal so height become width
+	}
+	else {
+	    $file = $this->ttf->File($this->font_family,$this->font_style);			
+	    $bbox = ImageTTFBBox($this->font_size,$angle,$file,$txt);
+	    return $n*(abs($bbox[2]-$bbox[6]));
+	}
+    }
+	
+    // Draw text with a box around it
+    function StrokeBoxedText($x,$y,$txt,$dir=0,$fcolor="white",$bcolor="black",
+    $shadow=false,$paragraph_align="left") {
+
+	if( !is_numeric($dir) ) {
+	    if( $dir=="h" ) $dir=0;
+	    elseif( $dir=="v" ) $dir=90;
+	    else JpGraphError::Raise("<b>JpGraph Error:</b> Unknown direction specified in call to StrokeBoxedText() [$dir]");
+	}
+		
+	$width=$this->GetTextWidth($txt,$dir);
+	$height=$this->GetTextHeight($txt,$dir);
+
+	if( $this->font_family<=FF_FONT2+1 ) {
+	    $xmarg=3;	
+	    $ymarg=3;
+	}
+	else {
+	    $xmarg=6;	
+	    $ymarg=6;
+	}		
+	$height += 2*$ymarg;
+	$width += 2*$xmarg;
+	if( $this->text_halign=="right" ) $x -= $width;
+	elseif( $this->text_halign=="center" ) $x -= $width/2;
+	if( $this->text_valign=="bottom" ) $y -= $height;
+	elseif( $this->text_valign=="center" ) $y -= $height/2;
+	
+	if( $shadow ) {
+	    $oc=$this->current_color;
+	    $this->SetColor($bcolor);
+	    $this->ShadowRectangle($x,$y,$x+$width+2,$y+$height+2,$fcolor,2);
+	    $this->current_color=$oc;
+	}
+	else {
+	    if( $fcolor ) {
+		$oc=$this->current_color;
+		$this->SetColor($fcolor);
+		$this->FilledRectangle($x,$y,$x+$width,$y+$height);
+		$this->current_color=$oc;
+	    }
+	    if( $bcolor ) {
+		$oc=$this->current_color;
+		$this->SetColor($bcolor);			
+		$this->Rectangle($x,$y,$x+$width,$y+$height);
+		$this->current_color=$oc;			
+	    }
+	}
+		
+	$h=$this->text_halign;
+	$v=$this->text_valign;
+	$this->SetTextAlign("left","top");
+	$this->StrokeText($x+$xmarg, $y+$ymarg, $txt, $dir, $paragraph_align);
+	$this->SetTextAlign($h,$v);
+    }
+
+    // Set text alignment	
+    function SetTextAlign($halign,$valign="bottom") {
+	$this->text_halign=$halign;
+	$this->text_valign=$valign;
+    }
+	
+    // Should we use anti-aliasing. Note: This really slows down graphics!
+    function SetAntiAliasing() {
+	$this->use_anti_aliasing=true;
+    }
+	
+    function StrokeText($x,$y,$txt,$dir=0,$paragraph_align) {
+
+	// Do special language encoding
+	if( LANGUAGE_CYRILLIC )
+	    $txt = LanguageConv::ToCyrillic($txt);
+
+	if( !is_numeric($dir) )
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Direction for text most be given as an angle between 0 and 90.");
+			
+	if( $this->font_family >= FF_FONT0 && $this->font_family <= FF_FONT2+1) {	// Internal font
+	    if( is_numeric($dir) && $dir!=90 && $dir!=0) 
+		JpGraphError::Raise("<b>JpGraph Error:</b> Internal font does not support drawing text at arbitrary angle. Use TTF fonts instead.");
+
+	    $h=$this->GetTextHeight($txt);
+	    $fh=$this->GetFontHeight($txt);
+	    $w=$this->GetTextWidth($txt);
+
+	    if( $this->text_halign=="right") 				
+		$x -= $dir==0 ? $w : $h;
+	    elseif( $this->text_halign=="center" ) 
+		$x -= $dir==0 ? $w/2 : $h/2;
+				
+	    if( $this->text_valign=="top" )
+		$y +=	$dir==0 ? $h : $w;
+	    elseif( $this->text_valign=="center" ) 				
+		$y +=	$dir==0 ? $h/2 : $w/2;
+				
+	    if( $dir==90 )
+		imagestringup($this->img,$this->font_family,$x,$y,$txt,$this->current_color);
+	    else	{
+		if (ereg("\n",$txt)) { 
+		    $tmp = split("\n",$txt);
+		    for($i=0; $i<count($tmp); ++$i) {
+			$w1 = $this->GetTextWidth($tmp[$i]);
+			if( $paragraph_align=="left" ) {
+			    imagestring($this->img,$this->font_family,$x,$y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
+			}
+			elseif( $paragraph_align=="right" ) {
+			    imagestring($this->img,$this->font_family,$x+($w-$w1),
+			    $y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
+			}
+			else {
+			    imagestring($this->img,$this->font_family,$x+$w/2-$w1/2,
+			    $y-$h+1+$i*$fh,$tmp[$i],$this->current_color);
+			}
+		    }
+		}else{
+		    //Put the text
+		    imagestring($this->img,$this->font_family,$x,$y-$h+1,$txt,$this->current_color);
+		}
+	    }
+	}
+	elseif($this->font_family >= FF_COURIER && $this->font_family <= FF_VERA)  { // TTF font
+	    $file = $this->ttf->File($this->font_family,$this->font_style);			
+	    $angle=$dir;
+	    $bbox=ImageTTFBBox($this->font_size,$angle,$file,$txt);
+	    if( $this->text_halign=="right" ) $x -= $bbox[2]-$bbox[0];
+	    elseif( $this->text_halign=="center" )	$x -= ($bbox[4]-$bbox[0])/2; 
+	    elseif( $this->text_halign=="topanchor" ) $x -= $bbox[4]-$bbox[0];
+	    elseif( $this->text_halign=="left" ) $x += -($bbox[6]-$bbox[0]);
+			
+	    if( $this->text_valign=="top" ) $y -= $bbox[5];
+	    elseif( $this->text_valign=="center" )	$y -= ($bbox[5]-$bbox[1])/2; 
+	    elseif( $this->text_valign=="bottom" ) $y -= $bbox[1]; 
+				
+	    // Use lower left of bbox as fix-point, not the default baselinepoint.				
+	    $x -= $bbox[0];
+	    if($GLOBALS['gd2']) {
+		$old = ImageAlphaBlending($this->img, true);
+	    }
+	    ImageTTFText ($this->img, $this->font_size, $angle, $x, $y, 
+	    $this->current_color,$file,$txt); 
+	    if($GLOBALS['gd2']) {
+		ImageAlphaBlending($this->img, $old);
+	    }
+	}
+	else
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Unknown font font family specification. ");
+    }
+	
+    function SetMargin($lm,$rm,$tm,$bm) {
+	$this->left_margin=$lm;
+	$this->right_margin=$rm;
+	$this->top_margin=$tm;
+	$this->bottom_margin=$bm;
+	$this->plotwidth=$this->width - $this->left_margin-$this->right_margin ; 
+	$this->plotheight=$this->height - $this->top_margin-$this->bottom_margin ;	
+	$this->NotifyObservers();
+    }
+
+    function SetTransparent($color) {
+	imagecolortransparent ($this->img,$this->rgb->allocate($color));
+    }
+	
+    function SetColor($color) {
+	$this->current_color_name = $color;
+	$this->current_color=$this->rgb->allocate($color);
+	if( $this->current_color == -1 ) {
+	    $tc=imagecolorstotal($this->img);
+	    JpGraphError::Raise("<b><b>JpGraph Error:</b> Can't allocate any more colors.</b><br>
+				Image has already allocated maximum of <b>$tc colors</b>. 
+				This might happen if you have anti-aliasing turned on
+				together with a background image or perhaps gradient fill 
+				since this requires many, many colors. Try to turn off
+				anti-aliasing.<p>
+				If there is still a problem try downgrading the quality of
+				the background image to use a smaller pallete to leave some 
+				entries for your graphs. You should try to limit the number
+				of colors in your background image to 64.<p>
+				If there is still problem set the constant 
+<pre>
+DEFINE(\"USE_APPROX_COLORS\",true);
+</pre>
+				in jpgraph.php This will use approximative colors
+				when the palette is full.
+				<p>
+				Unfortunately there is not much JpGraph can do about this
+				since the palette size is a limitation of current graphic format and
+				what the underlying GD library suppports."); 
+	}
+	return $this->current_color;
+    }
+	
+    function PushColor($color) {
+	if( $color != "" ) {
+	    $this->colorstack[$this->colorstackidx]=$this->current_color_name;
+	    $this->colorstack[$this->colorstackidx+1]=$this->current_color;
+	    $this->colorstackidx+=2;
+	    $this->SetColor($color);
+	}
+    }
+	
+    function PopColor() {
+	if($this->colorstackidx<0)
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Negative Color stack index. Unmatched call to PopColor()");
+	$this->current_color=$this->colorstack[--$this->colorstackidx];
+	$this->current_color_name=$this->colorstack[--$this->colorstackidx];
+    }
+	
+	
+    // Why this duplication? Because this way we can call this method
+    // for any image and not only the current objsct
+    function AdjSat($sat) {	$this->_AdjSat($this->img,$sat);	}	
+	
+    function _AdjSat($img,$sat) {
+	$nbr = imagecolorstotal ($img);
+	for( $i=0; $i<$nbr; ++$i ) {
+	    $colarr = imagecolorsforindex ($img,$i);
+	    $rgb[0]=$colarr["red"];
+	    $rgb[1]=$colarr["green"];
+	    $rgb[2]=$colarr["blue"];
+	    $rgb = $this->AdjRGBSat($rgb,$sat);
+	    imagecolorset ($img, $i, $rgb[0], $rgb[1], $rgb[2]);
+	}
+    }
+	
+    function AdjBrightContrast($bright,$contr=0) {
+	$this->_AdjBrightContrast($this->img,$bright,$contr);
+    }
+    function _AdjBrightContrast($img,$bright,$contr=0) {
+	if( $bright < -1 || $bright > 1 || $contr < -1 || $contr > 1 )
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Parameters for brightness and Contrast out of range [-1,1]");		
+	$nbr = imagecolorstotal ($img);
+	for( $i=0; $i<$nbr; ++$i ) {
+	    $colarr = imagecolorsforindex ($img,$i);
+	    $r = $this->AdjRGBBrightContrast($colarr["red"],$bright,$contr);
+	    $g = $this->AdjRGBBrightContrast($colarr["green"],$bright,$contr);
+	    $b = $this->AdjRGBBrightContrast($colarr["blue"],$bright,$contr);		
+	    imagecolorset ($img, $i, $r, $g, $b);
+	}
+    }
+	
+    // Private helper function for adj sat
+    // Adjust saturation for RGB array $u. $sat is a value between -1 and 1
+    // Note: Due to GD inability to handle true color the RGB values are only between
+    // 8 bit. This makes saturation quite sensitive for small increases in parameter sat.
+    // 
+    // Tip: To get a grayscale picture set sat=-100, values <-100 changes the colors
+    // to it's complement.
+    // 
+    // Implementation note: The saturation is implemented directly in the RGB space
+    // by adjusting the perpendicular distance between the RGB point and the "grey"
+    // line (1,1,1). Setting $sat>0 moves the point away from the line along the perp.
+    // distance and a negative value moves the point closer to the line.
+    // The values are truncated when the color point hits the bounding box along the
+    // RGB axis.
+    // DISCLAIMER: I'm not 100% sure this is he correct way to implement a color 
+    // saturation function in RGB space. However, it looks ok and has the expected effect.
+    function AdjRGBSat($rgb,$sat) {
+	// TODO: Should be moved to the RGB class
+	// Grey vector
+	$v=array(1,1,1);
+
+	// Dot product
+	$dot = $rgb[0]*$v[0]+$rgb[1]*$v[1]+$rgb[2]*$v[2];
+
+	// Normalize dot product
+	$normdot = $dot/3;	// dot/|v|^2
+
+	// Direction vector between $u and its projection onto $v
+	for($i=0; $i<3; ++$i)
+	    $r[$i] = $rgb[$i] - $normdot*$v[$i];
+
+	// Adjustment factor so that sat==1 sets the highest RGB value to 255
+	if( $sat > 0 ) {
+	    $m=0;
+	    for( $i=0; $i<3; ++$i) {
+		if( sign($r[$i]) == 1 && $r[$i]>0)
+		    $m=max($m,(255-$rgb[$i])/$r[$i]);
+	    }
+	    $tadj=$m;
+	}
+	else
+	    $tadj=1;
+		
+	$tadj = $tadj*$sat;	
+	for($i=0; $i<3; ++$i) {
+	    $un[$i] = round($rgb[$i] + $tadj*$r[$i]);		
+	    if( $un[$i]<0 ) $un[$i]=0;		// Truncate color when they reach 0
+	    if( $un[$i]>255 ) $un[$i]=255;// Avoid potential rounding error
+	}		
+	return $un;	
+    }	
+
+    // Private helper function for AdjBrightContrast
+    function AdjRGBBrightContrast($rgb,$bright,$contr) {
+	// TODO: Should be moved to the RGB class
+	// First handle contrast, i.e change the dynamic range around grey
+	if( $contr <= 0 ) {
+	    // Decrease contrast
+	    $adj = abs($rgb-128) * (-$contr);
+	    if( $rgb < 128 ) $rgb += $adj;
+	    else $rgb -= $adj;
+	}
+	else { // $contr > 0
+	    // Increase contrast
+	    if( $rgb < 128 ) $rgb = $rgb - ($rgb * $contr);
+	    else $rgb = $rgb + ((255-$rgb) * $contr);
+	}
+	
+	// Add (or remove) various amount of white
+	$rgb += $bright*255;	
+	$rgb=min($rgb,255);
+	$rgb=max($rgb,0);
+	return $rgb;	
+    }
+	
+    function SetLineWeight($weight) {
+	$this->line_weight = $weight;
+    }
+	
+    function SetStartPoint($x,$y) {
+	$this->lastx=$x;
+	$this->lasty=$y;
+    }
+	
+    function Arc($cx,$cy,$w,$h,$s,$e) {
+	imagearc($this->img,$cx,$cy,$w,$h,$s,$e,$this->current_color);
+    }
+    
+    function FilledArc($cx,$cy,$w,$h,$s,$e) {
+	if( $GLOBALS['gd2'] == false ) {
+	    JpGraphError::Raise("<b>JpGraph Error:</b> This plot type requires GD 2.x or later");
+	    die();
+	}
+	imagefilledarc($this->img,$cx,$cy,$w,$h,$s,$e,$this->current_color,IMG_ARC_PIE);
+    }
+
+    function FilledCakeSlice($cx,$cy,$w,$h,$s,$e) {
+	$this->CakeSlice($cx,$cy,$w,$h,$s,$e,$this->current_color_name);
+    }
+
+    function CakeSlice($cx,$cy,$w,$h,$s,$e,$fillcolor="",$arccolor="",$stepping=2500) {
+	// Calculate a polygon approximation
+	$p[0] = $cx;
+	$p[1] = $cy;
+	// Heuristic on how many polygons we need to make the
+	// arc look good
+	$numsteps = round(8 * abs($e-$s) * ($w*$w+$h*$h)/$stepping);
+	//echo "Numsteps=$numsteps<br>\n";
+	if( $numsteps < 2 ) $numsteps=2;
+	$delta = ($e-$s)/$numsteps;
+	$pa=array();
+	$a = $s;
+	for($i=1; $i<=$numsteps; ++$i ) {
+	    $p[2*$i] = round($cx + $w*cos($a));
+	    $p[2*$i+1] = round($cy - $h*sin($a));
+	    $a = $s + $i*$delta; 
+	    $pa[2*($i-1)] = $p[2*$i];
+	    $pa[2*($i-1)+1] = $p[2*$i+1];
+	}
+	// Get the last point at the exact ending angle to avoid
+	// any rounding errors.
+	$p[2*$i] = round($cx + $w*cos($e));
+	$p[2*$i+1] = round($cy - $h*sin($e));
+	$pa[2*($i-1)] = $p[2*$i];
+	$pa[2*($i-1)+1] = $p[2*$i+1];
+	$i++;
+
+	$p[2*$i] = $cx;
+    	$p[2*$i+1] = $cy;
+	if( $fillcolor != "" ) {
+	    $this->PushColor($fillcolor);
+	    $this->FilledPolygon($p);
+	    $this->PopColor();
+	}
+	$this->Polygon($p);
+	if( $arccolor != "" && ($arccolor != $this->current_color) ) {
+	    $n = count($pa)/2;
+	    $this->PushColor($arccolor);
+	    $this->SetStartPoint($pa[0],$pa[1]);
+	    for($i=1; $i<$n; ++$i)
+		$this->LineTo($pa[2*$i],$pa[2*$i+1]);
+	    $this->PopColor();
+	}
+    }
+
+    function Ellipse($xc,$yc,$w,$h) {
+	$this->Arc($xc,$yc,$w,$h,0,360);
+    }
+	
+    // Breseham circle gives visually better result then using GD
+    // built in arc(). It takes some more time but gives better
+    // accuracy.
+    function BresenhamCircle($xc,$yc,$r) {
+	$d = 3-2*$r;
+	$x = 0;
+	$y = $r;
+	while($x<=$y) {
+	    $this->Point($xc+$x,$yc+$y);			
+	    $this->Point($xc+$x,$yc-$y);
+	    $this->Point($xc-$x,$yc+$y);
+	    $this->Point($xc-$x,$yc-$y);
+			
+	    $this->Point($xc+$y,$yc+$x);
+	    $this->Point($xc+$y,$yc-$x);
+	    $this->Point($xc-$y,$yc+$x);
+	    $this->Point($xc-$y,$yc-$x);
+			
+	    if( $d<0 ) $d += 4*$x+6;
+	    else {
+		$d += 4*($x-$y)+10;		
+		--$y;
+	    }
+	    ++$x;
+	}
+    }
+			
+    function Circle($xc,$yc,$r) {
+	if( USE_BRESENHAM )
+	    $this->BresenhamCircle($xc,$yc,$r);
+	else {
+	    $this->Arc($xc,$yc,$r*2,$r*2,0,360);		
+	    // For some reason imageellipse() isn't in GD 2.0.1, PHP 4.1.1
+	    //imageellipse($this->img,$xc,$yc,$r,$r,$this->current_color);
+	}
+    }
+	
+    function FilledCircle($xc,$yc,$r) {
+	imagefilledellipse($this->img,$xc,$yc,2*$r,2*$r,$this->current_color);
+    }
+	
+    // Linear Color InterPolation
+    function lip($f,$t,$p) {
+	$p = round($p,1);
+	$r = $f[0] + ($t[0]-$f[0])*$p;
+	$g = $f[1] + ($t[1]-$f[1])*$p;
+	$b = $f[2] + ($t[2]-$f[2])*$p;
+	return array($r,$g,$b);
+    }
+
+    // Anti-aliased line. 
+    // Note that this is roughly 8 times slower then a normal line!
+    function WuLine($x1,$y1,$x2,$y2) {
+	// Get foreground line color
+	$lc = imagecolorsforindex($this->img,$this->current_color);
+	$lc = array($lc["red"],$lc["green"],$lc["blue"]);
+
+	$dx = $x2-$x1;
+	$dy = $y2-$y1;
+	
+	if( abs($dx) > abs($dy) ) {
+	    if( $dx<0 ) {
+		$dx = -$dx;$dy = -$dy;
+		$tmp=$x2;$x2=$x1;$x1=$tmp;
+		$tmp=$y2;$y2=$y1;$y1=$tmp;
+	    }
+	    $x=$x1<<16; $y=$y1<<16;
+	    $yinc = ($dy*65535)/$dx;
+	    while( ($x >> 16) < $x2 ) {
+				
+		$bc = imagecolorsforindex($this->img,imagecolorat($this->img,$x>>16,$y>>16));
+		$bc=array($bc["red"],$bc["green"],$bc["blue"]);
+				
+		$this->SetColor($this->lip($lc,$bc,($y & 0xFFFF)/65535));
+		imagesetpixel($this->img,$x>>16,$y>>16,$this->current_color);
+		$this->SetColor($this->lip($lc,$bc,(~$y & 0xFFFF)/65535));
+		imagesetpixel($this->img,$x>>16,($y>>16)+1,$this->current_color);
+		$x += 65536; $y += $yinc;
+	    }
+	}
+	else {
+	    if( $dy<0 ) {
+		$dx = -$dx;$dy = -$dy;
+		$tmp=$x2;$x2=$x1;$x1=$tmp;
+		$tmp=$y2;$y2=$y1;$y1=$tmp;
+	    }
+	    $x=$x1<<16; $y=$y1<<16;
+	    $xinc = ($dx*65535)/$dy;	
+	    while( ($y >> 16) < $y2 ) {
+				
+		$bc = imagecolorsforindex($this->img,imagecolorat($this->img,$x>>16,$y>>16));
+		$bc=array($bc["red"],$bc["green"],$bc["blue"]);				
+				
+		$this->SetColor($this->lip($lc,$bc,($x & 0xFFFF)/65535));
+		imagesetpixel($this->img,$x>>16,$y>>16,$this->current_color);
+		$this->SetColor($this->lip($lc,$bc,(~$x & 0xFFFF)/65535));
+		imagesetpixel($this->img,($x>>16)+1,$y>>16,$this->current_color);
+		$y += 65536; $x += $xinc;
+	    }
+	}
+	$this->SetColor($lc);
+	imagesetpixel($this->img,$x2,$y2,$this->current_color);		
+	imagesetpixel($this->img,$x1,$y1,$this->current_color);			
+    }
+
+    // Set line style dashed, dotted etc
+    function SetLineStyle($s) {
+	if( is_numeric($s) ) {
+	    if( $s<1 || $s>4 ) 
+		JpGraphError::Raise("<b>JpGraph Error:</b> Illegal numeric argument to SetLineStyle(): $s");
+	}
+	elseif( is_string($s) ) {
+	    if( $s == "solid" ) $s=1;
+	    elseif( $s == "dotted" ) $s=2;
+	    elseif( $s == "dashed" ) $s=3;
+	    elseif( $s == "longdashed" ) $s=4;
+	    else JpGraphError::Raise("<b>JpGraph Error:</b> Illegal string argument to SetLineStyle(): $s");
+	}
+	else JpGraphError::Raise("<b>JpGraph Error:</b> Illegal argument to SetLineStyle $s");
+	$this->line_style=$s;
+    }
+	
+    // Same as Line but take the line_style into account
+    function StyleLine($x1,$y1,$x2,$y2) {
+	switch( $this->line_style ) {
+	    case 1:// Solid
+		$this->Line($x1,$y1,$x2,$y2);
+	    break;
+	    case 2: // Dotted
+		$this->DashedLine($x1,$y1,$x2,$y2,1,6);
+	    break;
+	    case 3: // Dashed
+		$this->DashedLine($x1,$y1,$x2,$y2,2,4);
+	    break;
+	    case 4: // Longdashes
+		$this->DashedLine($x1,$y1,$x2,$y2,8,6);
+	    break;
+	    default:
+		JpGraphError::Raise("<b>JpGraph Error:</b> Unknown line style: $this->line_style ");
+		break;
+	}
+    }
+
+    function Line($x1,$y1,$x2,$y2) {
+	if( $this->line_weight==0 ) return;
+	if( $this->use_anti_aliasing ) {
+	    $dx = $x2-$x1;
+	    $dy = $y2-$y1;
+	    // Vertical, Horizontal or 45 lines don't need anti-aliasing
+	    if( $dx!=0 && $dy!=0 && $dx!=$dy ) {
+		$this->WuLine($x1,$y1,$x2,$y2);
+		return;
+	    }
+	}
+	if( $this->line_weight==1 )
+	    imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
+	elseif( $x1==$x2 ) {		// Special case for vertical lines
+	    imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
+	    $w1=floor($this->line_weight/2);
+	    $w2=floor(($this->line_weight-1)/2);
+	    for($i=1; $i<=$w1; ++$i) 
+		imageline($this->img,$x1+$i,$y1,$x2+$i,$y2,$this->current_color);
+	    for($i=1; $i<=$w2; ++$i) 
+		imageline($this->img,$x1-$i,$y1,$x2-$i,$y2,$this->current_color);
+	}
+	elseif( $y1==$y2 ) {		// Special case for horizontal lines
+	    imageline($this->img,$x1,$y1,$x2,$y2,$this->current_color);
+	    $w1=floor($this->line_weight/2);
+	    $w2=floor(($this->line_weight-1)/2);
+	    for($i=1; $i<=$w1; ++$i) 
+		imageline($this->img,$x1,$y1+$i,$x2,$y2+$i,$this->current_color);
+	    for($i=1; $i<=$w2; ++$i) 
+		imageline($this->img,$x1,$y1-$i,$x2,$y2-$i,$this->current_color);		
+	}
+	else {	// General case with a line at an angle
+	    $a = atan2($y1-$y2,$x2-$x1);
+	    // Now establish some offsets from the center. This gets a little
+	    // bit involved since we are dealing with integer functions and we
+	    // want the apperance to be as smooth as possible and never be thicker
+	    // then the specified width.
+			
+	    // We do the trig stuff to make sure that the endpoints of the line
+	    // are perpendicular to the line itself.
+	    $dx=(sin($a)*$this->line_weight/2);
+	    $dy=(cos($a)*$this->line_weight/2);
+
+	    $pnts = array($x2+$dx,$y2+$dy,$x2-$dx,$y2-$dy,$x1-$dx,$y1-$dy,$x1+$dx,$y1+$dy);
+	    imagefilledpolygon($this->img,$pnts,count($pnts)/2,$this->current_color);
+	}		
+	$this->lastx=$x2; $this->lasty=$y2;		
+    }
+	
+    function Polygon($p) {
+	if( $this->line_weight==0 ) return;
+	$n=count($p)/2;
+	for( $i=0; $i<$n; ++$i ) {
+	    $j=($i+1)%$n;
+	    $this->Line($p[$i*2],$p[$i*2+1],$p[$j*2],$p[$j*2+1]);
+	}
+    }
+	
+    function FilledPolygon($pts) {
+	imagefilledpolygon($this->img,$pts,count($pts)/2,$this->current_color);
+    }
+	
+    function Rectangle($xl,$yu,$xr,$yl) {
+	$this->Polygon(array($xl,$yu,$xr,$yu,$xr,$yl,$xl,$yl));
+    }
+	
+    function FilledRectangle($xl,$yu,$xr,$yl) {
+	$this->FilledPolygon(array($xl,$yu,$xr,$yu,$xr,$yl,$xl,$yl));
+    }
+
+    function ShadowRectangle($xl,$yu,$xr,$yl,$fcolor=false,$shadow_width=3,$shadow_color=array(102,102,102)) {
+	$this->PushColor($shadow_color);
+	$this->FilledRectangle($xr-$shadow_width,$yu+$shadow_width,$xr,$yl);
+	$this->FilledRectangle($xl+$shadow_width,$yl-$shadow_width,$xr,$yl);
+	$this->PopColor();
+	if( $fcolor==false )
+	    $this->Rectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
+	else {		
+	    $this->PushColor($fcolor);
+	    $this->FilledRectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);
+	    $this->PopColor();
+	    $this->Rectangle($xl,$yu,$xr-$shadow_width-1,$yl-$shadow_width-1);							
+	}
+    }
+
+    function StyleLineTo($x,$y) {
+	$this->StyleLine($this->lastx,$this->lasty,$x,$y);
+	$this->lastx=$x;
+	$this->lasty=$y;
+    }
+	
+    function LineTo($x,$y) {
+	$this->Line($this->lastx,$this->lasty,$x,$y);
+	$this->lastx=$x;
+	$this->lasty=$y;
+    }
+	
+    function Point($x,$y) {
+	imagesetpixel($this->img,$x,$y,$this->current_color);
+    }
+	
+    function Fill($x,$y) {
+	imagefill($this->img,$x,$y,$this->current_color);
+    }
+	
+    function DashedLine($x1,$y1,$x2,$y2,$dash_length=1,$dash_space=4) {
+	// Code based on, but not identical to, work by Ariel Garza and James Pine
+	$line_length = ceil (sqrt(pow(($x2 - $x1),2) + pow(($y2 - $y1),2)) );
+	$dx = ($x2 - $x1) / $line_length;
+	$dy = ($y2 - $y1) / $line_length;
+	$lastx = $x1; $lasty = $y1;
+	$xmax = max($x1,$x2);
+	$xmin = min($x1,$x2);
+	$ymax = max($y1,$y2);
+	$ymin = min($y1,$y2);
+	for ($i = 0; $i < $line_length; $i += ($dash_length + $dash_space)) {
+	    $x = ($dash_length * $dx) + $lastx;
+	    $y = ($dash_length * $dy) + $lasty;
+			
+	    // The last section might overshoot so we must take a computational hit
+	    // and check this.
+	    if( $x>$xmax ) $x=$xmax;
+	    if( $y>$ymax ) $y=$ymax;
+			
+	    if( $x<$xmin ) $x=$xmin;
+	    if( $y<$ymin ) $y=$ymin;
+
+	    $this->Line($lastx,$lasty,$x,$y);
+	    $lastx = $x + ($dash_space * $dx);
+	    $lasty = $y + ($dash_space * $dy);
+	} 
+    } 
+	
+    // Generate image header
+    function Headers() {
+	if ($this->expired) {
+	    header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
+	    header("Last-Modified: " . gmdate("D, d M Y H:i:s") . "GMT");
+	    header("Cache-Control: no-cache, must-revalidate");
+	    header("Pragma: no-cache");
+	}
+	header("Content-type: image/$this->img_format");
+    }
+
+    // Adjust image quality for formats that allow this
+    function SetQuality($q) {
+	$this->quality = $q;
+    }
+	
+    // Stream image to browser or to file
+    function Stream($aFile="") {
+	$func="image".$this->img_format;
+	if( $this->img_format=="jpeg" && $this->quality != null ) {
+	    $res = @$func($this->img,$aFile,$this->quality);
+	}
+	else {
+	    if( $aFile != "" ) {
+		$res = @$func($this->img,$aFile);
+	    }
+	    else
+		$res = @$func($this->img);
+	}
+	if( !$res )
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Can't create or stream image to file: $aFile<br>
+Check that PHP has enough permission to write a file to the current directory.");
+    }
+		
+    // Clear resource tide up by image
+    function Destroy() {
+	imagedestroy($this->img);
+    }
+	
+    // Specify image format. Note depending on your installation
+    // of PHP not all formats may be supported.
+    function SetImgFormat($aFormat) {		
+	$aFormat = strtolower($aFormat);
+	$tst = true;
+	$supported = imagetypes();
+	if( $aFormat=="auto" ) {
+	    if( $supported & IMG_PNG )
+		$this->img_format="png";
+	    elseif( $supported & IMG_JPG )
+		$this->img_format="jpeg";
+	    elseif( $supported & IMG_GIF )
+		$this->img_format="gif";
+	    else
+		JpGraphError::Raise("<b>JpGraph Error:</b> Your PHP (and GD-lib) installation does not appear to support any known graphic formats.".
+		    "You need to first make sure GD is compiled as a module to PHP. If you also want to use JPEG images".
+		    "you must get the JPEG library. Please see the PHP docs for details.");
+				
+	    return true;
+	}
+	else {
+	    if( $aFormat=="jpeg" || $aFormat=="png" || $aFormat=="gif" ) {
+		if( $aFormat=="jpeg" && !($supported & IMG_JPG) )
+		    $tst=false;
+		elseif( $aFormat=="png" && !($supported & IMG_PNG) ) 
+		    $tst=false;
+		elseif( $aFormat=="gif" && !($supported & IMG_GIF) ) 	
+		    $tst=false;
+		else {
+		    $this->img_format=$aFormat;
+		    return true;
+		}
+	    }
+	    else 
+		$tst=false;
+	    if( !$tst )
+		JpGraphError::Raise("<b>JpGraph Error:</b> Your PHP installation does not support the chosen graphic format: $aFormat");
+	}
+    }	
+} // CLASS
+
+//===================================================
+// CLASS RotImage
+// Description: Exactly as Image but draws the image at
+// a specified angle around a specified rotation point.
+//===================================================
+class RotImage extends Image {
+    var $m=array();
+    var $a=0;
+    var $dx=0,$dy=0,$transx=0,$transy=0; 
+	
+    function RotImage($aWidth,$aHeight,$a=0,$aFormat=DEFAULT_GFORMAT) {
+	$this->Image($aWidth,$aHeight,$aFormat);
+	$this->dx=$this->left_margin+$this->plotwidth/2;
+	$this->dy=$this->top_margin+$this->plotheight/2;
+	$this->SetAngle($a);	
+    }
+	
+    function SetCenter($dx,$dy) {
+	$old_dx = $this->dx;
+	$old_dy = $this->dy;
+	$this->dx=$dx;
+	$this->dy=$dy;
+	return array($old_dx,$old_dy);
+    }
+	
+    function SetTranslation($dx,$dy) {
+	$old = array($this->transx,$this->transy);
+	$this->transx = $dx;
+	$this->transy = $dy;
+	return $old;
+    }
+
+    function SetAngle($a) {
+	$tmp = $this->a;
+	$this->a = $a;
+	$a *= M_PI/180;
+	$sa=sin($a); $ca=cos($a);
+		
+	// Create the rotation matrix
+	$this->m[0][0] = $ca;
+	$this->m[0][1] = -$sa;
+	$this->m[0][2] = $this->dx*(1-$ca) + $sa*$this->dy ;
+	$this->m[1][0] = $sa;
+	$this->m[1][1] = $ca;
+	$this->m[1][2] = $this->dy*(1-$ca) - $sa*$this->dx ;
+	return $tmp;
+    }
+
+    function Circle($xc,$yc,$r) {
+	list($xc,$yc) = $this->Rotate($xc,$yc);
+	parent::Circle($xc,$yc,$r);
+    }
+
+    function FilledCircle($xc,$yc,$r) {
+	list($xc,$yc) = $this->Rotate($xc,$yc);
+	parent::FilledCircle($xc,$yc,$r);
+    }
+
+	
+    function Arc($xc,$yc,$w,$h,$s,$e) {
+	list($xc,$yc) = $this->Rotate($xc,$yc);
+	parent::Arc($xc,$yc,$w,$h,$s,$e);
+    }
+
+    function FilledArc($xc,$yc,$w,$h,$s,$e) {
+	list($xc,$yc) = $this->Rotate($xc,$yc);
+	parent::FilledArc($xc,$yc,$w,$h,$s,$e);
+    }
+
+    function SetMargin($lm,$rm,$tm,$bm) {	
+	parent::SetMargin($lm,$rm,$tm,$bm);
+	$this->SetAngle($this->a);
+    }
+	
+    function Rotate($x,$y) {
+	$x1=round($this->m[0][0]*$x + $this->m[0][1]*$y + $this->m[0][2] + $this->transx);
+	$y1=round($this->m[1][0]*$x + $this->m[1][1]*$y + $this->m[1][2] + $this->transy);
+	return array($x1,$y1);
+    }
+	
+    function ArrRotate($pnts) {
+	for($i=0; $i < count($pnts)-1; $i+=2)
+	    list($pnts[$i],$pnts[$i+1]) = $this->Rotate($pnts[$i],$pnts[$i+1]);
+	return $pnts;
+    }
+	
+    function Line($x1,$y1,$x2,$y2) {
+	list($x1,$y1) = $this->Rotate($x1,$y1);
+	list($x2,$y2) = $this->Rotate($x2,$y2);
+	parent::Line($x1,$y1,$x2,$y2);
+    }
+	
+    function Rectangle($x1,$y1,$x2,$y2) {
+	$this->Polygon(array($x1,$y1,$x2,$y1,$x2,$y2,$x1,$y2));
+    }
+	
+    function FilledRectangle($x1,$y1,$x2,$y2) {
+	if( $y1==$y2 || $x1==$x2 )
+	    $this->Line($x1,$y1,$x2,$y2);
+	else 
+	    $this->FilledPolygon(array($x1,$y1,$x2,$y1,$x2,$y2,$x1,$y2));
+    }
+	
+    function Polygon($pnts) {
+	//Polygon uses Line() so it will be rotated through that call
+	parent::Polygon($pnts);
+    }
+	
+    function FilledPolygon($pnts) {
+	parent::FilledPolygon($this->ArrRotate($pnts));
+    }
+	
+    function Point($x,$y) {
+	list($xp,$yp) = $this->Rotate($x,$y);
+	parent::Point($xp,$yp);
+    }
+	
+    function DashedLine($x1,$y1,$x2,$y2,$length=1,$space=4) {
+	list($x1,$y1) = $this->Rotate($x1,$y1);
+	list($x2,$y2) = $this->Rotate($x2,$y2);
+	parent::DashedLine($x1,$y1,$x2,$y2,$length,$space);
+    }
+	
+    function StrokeText($x,$y,$txt,$dir=0,$paragraph_align="left") {
+	list($xp,$yp) = $this->Rotate($x,$y);
+	parent::StrokeText($xp,$yp,$txt,$dir,$paragraph_align);
+    }
+}
+
+//===================================================
+// CLASS ImgStreamCache
+// Description: Handle caching of graphs to files
+//===================================================
+class ImgStreamCache {
+    var $cache_dir;
+    var $img=null;
+    var $timeout=0; 	// Infinite timeout
+    //---------------
+    // CONSTRUCTOR
+    function ImgStreamCache(&$aImg, $aCacheDir=CACHE_DIR) {
+	$this->img = &$aImg;
+	$this->cache_dir = $aCacheDir;
+    }
+
+//---------------
+// PUBLIC METHODS	
+
+    // Specify a timeout (in minutes) for the file. If the file is older then the
+    // timeout value it will be overwritten with a newer version.
+    // If timeout is set to 0 this is the same as infinite large timeout and if
+    // timeout is set to -1 this is the same as infinite small timeout
+    function SetTimeout($aTimeout) {
+	$this->timeout=$aTimeout;	
+    }
+	
+    // Output image to browser and also write it to the cache
+    function PutAndStream(&$aImage,$aCacheFileName,$aInline,$aStrokeFileName) {
+	// Some debugging code to brand the image with numbe of colors
+	// used
+
+	if( JPG_DEBUG ) {
+	    $c=$aImage->SetColor("black");
+	    $t=imagecolorstotal($this->img->img);
+	    imagestring($this->img->img,2,5,$this->img->height-20,$t,$c);					
+	}
+		
+	if( BRAND_TIMING ) {
+	    global $tim;
+	    $t=$tim->Pop()/1000.0;
+	    $c=$aImage->SetColor("black");
+	    $t=sprintf(BRAND_TIME_FORMAT,round($t,3));
+	    imagestring($this->img->img,2,5,$this->img->height-20,$t,$c);			
+	}
+
+	// Check if we should stroke the image to an arbitrary file
+	if( $aStrokeFileName!="" ) {
+	    if( $aStrokeFileName == "auto" )
+		$aStrokeFileName = GenImgName();
+	    if( file_exists($aStrokeFileName) ) {
+	 			// Delete the old file
+		if( !@unlink($aStrokeFileName) )
+		    JpGraphError::Raise("<b>JpGraph Error:</b> Can't delete cached image $aStrokeFileName. Permission problem?");
+	    }
+	    $aImage->Stream($aStrokeFileName);
+	    return;
+	}
+
+	if( $aCacheFileName != "" && USE_CACHE) {
+
+	    $aCacheFileName = $this->cache_dir . $aCacheFileName;
+	    if( file_exists($aCacheFileName) ) {
+		if( !$aInline ) {
+		    // If we are generating image off-line (just writing to the cache)
+		    // and the file exists and is still valid (no timeout)
+		    // then do nothing, just return.
+		    $diff=time()-filemtime($aCacheFileName);
+		    if( $diff < 0 )
+			JpGraphError::Raise("<b>JpGraph Error:</b> Cached imagefile ($aCacheFileName) has file date in the future!!");
+		    if( $this->timeout>0 && ($diff <= $this->timeout*60) ) 
+			return;		
+		}			
+		if( !@unlink($aCacheFileName) )
+		    JpGraphError::Raise("<b>JpGraph Error:</b> Can't delete cached image $aStrokeFileName. Permission problem?");
+		$aImage->Stream($aCacheFileName);	
+	    }
+	    else {
+		$this->_MakeDirs(dirname($aCacheFileName));
+		$aImage->Stream($aCacheFileName);
+	    }
+			
+	    $res=true;
+	    // Set group to specified
+	    if( CACHE_FILE_GROUP != "" )
+		$res = @chgrp($aCacheFileName,CACHE_FILE_GROUP);
+	    if( CACHE_FILE_MOD != "" )
+		$res = @chmod($aCacheFileName,CACHE_FILE_MOD);
+	    if( !$res )
+		JpGraphError::Raise("<b>JpGraph Error:</b> Can't set permission for cached image $aStrokeFileName. Permission problem?");
+			
+	    $aImage->Destroy();
+	    if( $aInline ) {
+		if ($fh = @fopen($aCacheFileName, "rb") ) {
+		    $this->img->Headers();
+		    fpassthru($fh);
+		    return;
+		}
+		else
+		    JpGraphError::Raise("<b>JpGraph Error:</b> Cant open file from cache [$aFile]"); 
+	    }
+	}
+	elseif( $aInline ) {
+	    $this->img->Headers();	 		
+	    $aImage->Stream();	
+	    return;
+	}
+    }
+	
+    // Check if a given image is in cache and in that case
+    // pass it directly on to web browser. Return false if the
+    // image file doesn't exist or exists but is to old
+    function GetAndStream($aCacheFileName) {
+	$aCacheFileName = $this->cache_dir.$aCacheFileName;		 
+	if ( USE_CACHE && file_exists($aCacheFileName) && $this->timeout>=0 ) {
+	    $diff=time()-filemtime($aCacheFileName);
+	    if( $this->timeout>0 && ($diff > $this->timeout*60) ) {
+		return false;		
+	    }
+	    else {
+		if ($fh = @fopen($aCacheFileName, "rb")) {
+		    $this->img->Headers();
+		    fpassthru($fh);
+		    fclose($fh);
+		    return true;
+		}
+		else
+		    JpGraphError::Raise("<b>JpGraph Error:</b> Can't open cached image \"$aCacheFileName\" for reading.");
+	    }
+	} 
+	return false;
+    }
+	
+    //---------------
+    // PRIVATE METHODS	
+    // Create all necessary directories in a path
+    function _MakeDirs($aFile) {
+	$dirs = array();
+	while (! (file_exists($aFile))) {
+	    $dirs[] = $aFile;
+	    $aFile = dirname($aFile);
+	}
+	for ($i = sizeof($dirs)-1; $i>=0; $i--) {
+	    if(! @mkdir($dirs[$i],0777) )
+		JpGraphError::Raise("<b>JpGraph Error:</b> Can't create directory in $aFile. Permission problems?");
+				
+	    // We also specify mode here after we have changed group. 
+	    // This is necessary if Apache user doesn't belong the
+	    // default group and hence can't specify group permission
+	    // in the previous mkdir() call
+	    if( CACHE_FILE_GROUP != "" ) {
+		$res=true;
+		$res =@chgrp($dirs[$i],CACHE_FILE_GROUP);
+		$res &= @chmod($dirs[$i],0777);
+		if( !$res )
+		    JpGraphError::Raise("<b>JpGraph Error:</b> Can't set permissions for $aFile. Permission problems?");
+	    }
+	}
+	return true;
+    }	
+} // CLASS Cache
+	
+//===================================================
+// CLASS Legend
+// Description: Responsible for drawing the box containing
+// all the legend text for the graph
+//===================================================
+class Legend {
+    var $color=array(0,0,0); // Default fram color
+    var $fill_color=array(235,235,235); // Default fill color
+    var $shadow=true; // Shadow around legend "box"
+    var $txtcol=array();
+    var $mark_abs_size=10,$xmargin=5,$ymargin=5,$shadow_width=2;
+    var $xpos=0.05, $ypos=0.15, $halign="right", $valign="top";
+    var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12;
+    var $hide=false,$layout=LEGEND_VERT;
+    var $weight=1;
+//---------------
+// CONSTRUCTOR
+    function Legend() {
+	// Empty
+    }
+//---------------
+// PUBLIC METHODS	
+    function Hide($aHide=true) {
+	$this->hide=$aHide;
+    }
+	
+    function SetShadow($aShow=true,$aWidth=2) {
+	$this->shadow=$aShow;
+	$this->shadow_width=$aWidth;
+    }
+	
+    function SetLineWeight($aWeight) {
+	$this->weight = $aWeight;
+    }
+	
+    function SetLayout($aDirection=LEGEND_VERT) {
+	$this->layout=$aDirection;
+    }
+	
+    // Set color on frame around box
+    function SetColor($aColor) {
+	$this->color=$aColor;
+    }
+	
+    function SetFont($aFamily,$aStyle=FS_NORMAL,$aSize=10) {
+	$this->font_family = $aFamily;
+	$this->font_style = $aStyle;
+	$this->font_size = $aSize;
+    }
+	
+    function Pos($aX,$aY,$aHAlign="right",$aVAlign="top") {
+	if( !($aX<1 && $aY<1) )
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Position for legend must be given as percentage in range 0-1");
+	$this->xpos=$aX;
+	$this->ypos=$aY;
+	$this->halign=$aHAlign;
+	$this->valign=$aVAlign;
+    }
+
+    function SetBackground($aDummy) {
+	JpGraphError::Raise("<b>JpGraph Error:</b> Deprecated function Legend::SetBaqckground() use Legend::SetFillColor() instead.");
+    }
+
+    function SetFillColor($aColor) {
+	$this->fill_color=$aColor;
+    }
+	
+    function Add($aTxt,$aColor,$aPlotmark="",$aLinestyle=1) {
+	$this->txtcol[]=array($aTxt,$aColor,$aPlotmark,$aLinestyle);
+    }
+	
+    function Stroke(&$aImg) {
+	if( $this->hide ) return;
+
+	$nbrplots=count($this->txtcol);
+	if( $nbrplots==0 ) return;
+		
+	$aImg->SetFont($this->font_family,$this->font_style,$this->font_size);	
+	if( $this->layout==LEGEND_VERT )		
+	    $abs_height=$aImg->GetFontHeight() + $this->mark_abs_size*$nbrplots +
+		$this->ymargin*($nbrplots-1);
+	else
+	    $abs_height=2*$this->mark_abs_size+$this->ymargin;
+						
+	if( $this->shadow ) $abs_height += $this->shadow_width;
+	$mtw=0;
+	foreach($this->txtcol as $p) {
+	    if( $this->layout==LEGEND_VERT )
+		$mtw=max($mtw,$aImg->GetTextWidth($p[0]));
+	    else
+		$mtw+=$aImg->GetTextWidth($p[0])+$this->mark_abs_size+$this->xmargin;
+	}
+	$abs_width=$mtw+2*$this->mark_abs_size+2*$this->xmargin;
+	if( $this->halign=="left" )
+	    $xp=$this->xpos*$aImg->width;
+	elseif( $this->halign=="center" )
+	    $xp=$this->xpos*$aImg->width - $abs_width/2; 
+	else  
+	    $xp = $aImg->width - $this->xpos*$aImg->width - $abs_width;
+	$yp=$this->ypos*$aImg->height;
+	if( $this->valign=="center" )
+	    $yp-=$abs_height/2;
+	elseif( $this->valign=="bottom" )
+	    $yp-=$abs_height;
+			
+	$aImg->SetColor($this->color);				
+	$aImg->SetLineWeight($this->weight);
+	if( $this->shadow )
+	    $aImg->ShadowRectangle($xp,$yp,$xp+$abs_width,$yp+$abs_height,$this->fill_color,$this->shadow_width);
+	else {
+	    $aImg->SetColor($this->fill_color);				
+	    $aImg->FilledRectangle($xp,$yp,$xp+$abs_width,$yp+$abs_height);
+	    $aImg->SetColor($this->color);							
+	    $aImg->Rectangle($xp,$yp,$xp+$abs_width,$yp+$abs_height);
+	}
+	$aImg->SetLineWeight(1);
+	$x1=$xp+$this->mark_abs_size/2;
+	$y1=$yp+$aImg->GetFontHeight()*0.5;
+	foreach($this->txtcol as $p) {
+	    $aImg->SetColor($p[1]);
+	    if ( $p[2] != "" && $p[2]->GetType() > -1 ) {
+		$p[2]->Stroke($aImg,$x1+$this->mark_abs_size/2,$y1+$aImg->GetFontHeight()/2);
+	    } 
+	    elseif ( $p[2] != "" ) {
+		$aImg->SetLineStyle($p[3]);
+		$aImg->StyleLine($x1,$y1+$aImg->GetFontHeight()/2,$x1+$this->mark_abs_size,$y1+$aImg->GetFontHeight()/2);
+		$aImg->StyleLine($x1,$y1+$aImg->GetFontHeight()/2-1,$x1+$this->mark_abs_size,$y1+$aImg->GetFontHeight()/2-1);
+	    } 
+	    else {
+		$aImg->FilledRectangle($x1,$y1,$x1+$this->mark_abs_size,$y1+$this->mark_abs_size);
+		$aImg->SetColor($this->color);
+		$aImg->Rectangle($x1,$y1,$x1+$this->mark_abs_size,$y1+$this->mark_abs_size);
+	    }
+	    $aImg->SetColor($this->color);
+	    $aImg->SetTextAlign("left");			
+	    $aImg->StrokeText($x1+$this->mark_abs_size+$this->xmargin,$y1+$this->mark_abs_size,$p[0]);
+	    if( $this->layout==LEGEND_VERT )
+		$y1 += $this->ymargin+$this->mark_abs_size;
+	    else
+		$x1 += 2*$this->ymargin+$this->mark_abs_size+$aImg->GetTextWidth($p[0]);
+	}											 								 
+    }
+} // Class
+	
+//===================================================
+// CLASS Plot
+// Description: Abstract base class for all concrete plot classes
+//===================================================
+class Plot {
+    var $line_weight=1;
+    var $coords=array();
+    var $legend="";
+    var $csimtargets=array();	// Array of targets for CSIM
+    var $csimareas="";			// Resultant CSIM area tags	
+    var $csimalts=null;			// ALT:s for corresponding target
+    var $color="black";
+    var $numpoints=0;
+    var $weight=1;	
+//---------------
+// CONSTRUCTOR
+    function Plot(&$aDatay,$aDatax=false) {
+	$this->numpoints = count($aDatay);
+	if( $this->numpoints==0 )
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Empty data array specified for plot. Must have at least one data point.");
+	$this->coords[0]=$aDatay;
+	if( is_array($aDatax) )
+	    $this->coords[1]=$aDatax;
+    }
+
+//---------------
+// PUBLIC METHODS	
+
+    // Stroke the plot
+    // "virtual" function which must be implemented by
+    // the subclasses
+    function Stroke(&$aImg,&$aXScale,&$aYScale) {
+	JpGraphError::Raise("JpGraph: Stroke() must be implemented by concrete subclass to class Plot");
+    }
+	
+    // Set href targets for CSIM	
+    function SetCSIMTargets(&$aTargets,$aAlts=null) {
+	$this->csimtargets=$aTargets;
+	$this->csimalts=$aAlts;		
+    }
+ 	
+    // Get all created areas
+    function GetCSIMareas() {
+	return $this->csimareas;
+    }	
+	
+    // "Virtual" function which gets called before any scale
+    // or axis are stroked used to do any plot specific adjustment
+    function PreStrokeAdjust(&$aGraph) {
+	if( substr($aGraph->axtype,0,4) == "text" && (isset($this->coords[1])) )
+	    JpGraphError::Raise("JpGraph: You can't use a text X-scale with specified X-coords. Use a \"int\" or \"lin\" scale instead.");
+	return true;	
+    }
+	
+    function SetWeight($aWeight) {
+	$this->weight=$aWeight;
+    }
+	
+    // Get minimum values in plot
+    function Min() {
+	if( isset($this->coords[1]) )
+	    $x=$this->coords[1];
+	else
+	    $x="";
+	if( $x != "" && count($x) > 0 )
+	    $xm=min($x);
+	else 
+	    $xm=0;
+	$y=$this->coords[0];
+	if( count($y) > 0 ) {
+	    $ym = $y[0];
+	    $cnt = count($y);
+	    $i=0;
+	    while( $i<$cnt && !is_numeric($ym=$y[$i]) )
+		$i++;
+	    while( $i < $cnt) {
+		if( is_numeric($y[$i]) ) 
+		    $ym=min($ym,$y[$i]);
+		++$i;
+	    }			
+	}
+	else 
+	    $ym="";
+	return array($xm,$ym);
+    }
+	
+    // Get maximum value in plot
+    function Max() {
+	if( isset($this->coords[1]) )
+	    $x=$this->coords[1];
+	else
+	    $x="";
+
+	if( $x!="" && count($x) > 0 )
+	    $xm=max($x);
+	else 
+	    $xm=count($this->coords[0])-1;	// We count from 0..(n-1)
+	$y=$this->coords[0];
+	if( count($y) > 0 ) {
+	    if( !isset($y[0]) ) {
+		$y[0] = 0;
+// Change in 1.5.1 Don't treat this as an error any more. Just silently concert to 0
+//		JpGraphError::Raise("<b>JpGraph Error:</b> You have not specified a y[0] value!!");
+	    }
+	    $cnt = count($y);
+	    $i=0;
+	    while( $i<$cnt && !is_numeric($ym=$y[$i]) )
+		$i++;				
+	    while( $i < $cnt ) {
+		if( is_numeric($y[$i]) ) $ym=max($ym,$y[$i]);
+		++$i;
+	    }
+	}
+	else 
+	    $ym="";
+	return array($xm,$ym);
+    }
+	
+    function SetColor($aColor) {
+	$this->color=$aColor;
+    }
+	
+    function SetLegend($aLegend) {
+	$this->legend = $aLegend;
+    }
+	
+    function SetLineWeight($aWeight=1) {
+	$this->line_weight=$aWeight;
+    }
+	
+    // This method gets called by Graph class to plot anything that should go
+    // into the margin after the margin color has been set.
+    function StrokeMargin(&$aImg) {
+	return true;
+    }
+
+    // Framework function the chance for each plot class to set a legend
+    function Legend(&$aGraph) {
+	if( $this->legend!="" )
+	    $aGraph->legend->Add($this->legend,$this->color);		
+    }
+	
+} // Class
+
+
+//===================================================
+// CLASS PlotMark
+// Description: Handles the plot marks in graphs
+// mostly used in line and scatter plots.
+//===================================================
+class PlotMark {
+    var $title, $show=true;
+    var $type=-1, $weight=1;
+    var $color="black", $width=5, $fill_color="blue";
+//	--------------
+// CONSTRUCTOR
+    function PlotMark() {
+	$this->title = new Text();
+	$this->title->Hide();
+    }
+//---------------
+// PUBLIC METHODS	
+    function SetType($t) {
+	$this->type = $t;
+    }
+	
+    function GetType() {
+	return $this->type;
+    }
+	
+    function SetColor($c) {
+	$this->color=$c;
+    }
+	
+    function SetFillColor($c) {
+	$this->fill_color = $c;
+    }
+	
+    function SetWidth($w) {
+	$this->width=$w;
+    }
+	
+    function GetWidth() {
+	return $this->width;
+    }
+	
+    function Hide($aHide=true) {
+	$this->show = !$aHide;
+    }
+	
+    function Show($aShow=true) {
+	$this->show = $aShow;
+    }
+	
+    function Stroke(&$img,$x,$y) {
+	if( !$this->show ) return;
+	$dx=round($this->width/2,0);
+	$dy=round($this->width/2,0);
+	$pts=0;		
+	switch( $this->type ) {
+	    case MARK_SQUARE:
+		$c[]=$x-$dx;$c[]=$y-$dy;
+		$c[]=$x+$dx;$c[]=$y-$dy;
+		$c[]=$x+$dx;$c[]=$y+$dy;
+		$c[]=$x-$dx;$c[]=$y+$dy;
+		$pts=4;
+		break;
+	    case MARK_UTRIANGLE:
+		++$dx;++$dy;
+		$c[]=$x-$dx;$c[]=$y+0.87*$dy;	// tan(60)/2*$dx
+		$c[]=$x;$c[]=$y-0.87*$dy;
+		$c[]=$x+$dx;$c[]=$y+0.87*$dy;
+		$pts=3;
+		break;
+	    case MARK_DTRIANGLE:
+		++$dx;++$dy;			
+		$c[]=$x;$c[]=$y+0.87*$dy;	// tan(60)/2*$dx
+		$c[]=$x-$dx;$c[]=$y-0.87*$dy;
+		$c[]=$x+$dx;$c[]=$y-0.87*$dy;
+		$pts=3;
+		break;				
+	    case MARK_DIAMOND:
+		$c[]=$x;$c[]=$y+$dy;
+		$c[]=$x-$dx;$c[]=$y;
+		$c[]=$x;$c[]=$y-$dy;
+		$c[]=$x+$dx;$c[]=$y;
+		$pts=4;
+		break;				
+	}
+	if( $pts>0 ) {
+	    $img->SetLineWeight($this->weight);
+	    $img->SetColor($this->fill_color);								
+	    $img->FilledPolygon($c);
+	    $img->SetColor($this->color);					
+	    $img->Polygon($c);
+	}
+	elseif( $this->type==MARK_CIRCLE ) {
+	    $img->SetColor($this->color);					
+	    $img->Circle($x,$y,$this->width);
+	}
+	elseif( $this->type==MARK_FILLEDCIRCLE ) {
+	    $img->SetColor($this->fill_color);		
+	    $img->FilledCircle($x,$y,$this->width);
+	    $img->SetColor($this->color);		
+	    $img->Circle($x,$y,$this->width);
+	}
+	elseif( $this->type==MARK_CROSS ) {
+	    // Oversize by a pixel to match the X
+	    $img->SetColor($this->color);
+	    $img->SetLineWeight($this->weight);
+	    $img->Line($x,$y+$dy+1,$x,$y-$dy-1);
+	    $img->Line($x-$dx-1,$y,$x+$dx+1,$y);
+	}
+	elseif( $this->type==MARK_X ) {
+	    $img->SetColor($this->color);
+	    $img->SetLineWeight($this->weight);
+	    $img->Line($x+$dx,$y+$dy,$x-$dx,$y-$dy);
+	    $img->Line($x-$dx,$y+$dy,$x+$dx,$y-$dy);		
+	}			
+	elseif( $this->type==MARK_STAR ) {
+	    $img->SetColor($this->color);
+	    $img->SetLineWeight($this->weight);
+	    $img->Line($x+$dx,$y+$dy,$x-$dx,$y-$dy);
+	    $img->Line($x-$dx,$y+$dy,$x+$dx,$y-$dy);
+	    // Oversize by a pixel to match the X				
+	    $img->Line($x,$y+$dy+1,$x,$y-$dy-1);
+	    $img->Line($x-$dx-1,$y,$x+$dx+1,$y);
+	}
+		
+	// Stroke title
+	$this->title->Align("center","center");
+	$this->title->Stroke($img,$x,$y);			
+    }
+} // Class
+
+//==============================================================================
+// The following section contains classes to implement the "band" functionality
+//==============================================================================
+
+// Utility class to hold coordinates for a rectangle
+class Rectangle {
+    var $x,$y,$w,$h;
+    var $xe, $ye;
+    function Rectangle($aX,$aY,$aWidth,$aHeight) {
+	$this->x=$aX;
+	$this->y=$aY;
+	$this->w=$aWidth;
+	$this->h=$aHeight;
+	$this->xe=$aX+$aWidth-1;
+	$this->ye=$aY+$aHeight-1;
+    }
+}
+
+//=====================================================================
+// Class RectPattern
+// Base class for pattern hierarchi that is used to display patterned
+// bands on the graph. Any subclass that doesn't override Stroke()
+// must at least implement method DoPattern(&$aImg) which is responsible
+// for drawing the pattern onto the graph.
+//=====================================================================
+class RectPattern {
+    var $color;
+    var $weight;
+    var $rect=null;
+    var $doframe=true;
+    var $linespacing;	// Line spacing in pixels
+    var $iBackgroundColor=-1;  // Default is no background fill
+	
+    function RectPattern($aColor,$aWeight=1) {
+	$this->color = $aColor;
+	$this->weight = $aWeight;		
+    }
+
+    function SetBackground($aBackgroundColor) {
+	$this->iBackgroundColor=$aBackgroundColor;
+    }
+
+    function SetPos(&$aRect) {
+	$this->rect = $aRect;
+    }
+	
+    function ShowFrame($aShow=true) {
+	$this->doframe=$aShow;
+    }
+
+    function SetDensity($aDens) {
+	if( $aDens <1 || $aDens > 100 )
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Desity for pattern must be between 1 and 100. (You tried $aDens)");
+	// 1% corresponds to linespacing=50
+	// 100 % corresponds to linespacing 1
+	$this->linespacing = floor(((100-$aDens)/100.0)*50)+1;
+
+    }
+
+    function Stroke(&$aImg) {
+	if( $this->rect == null )
+	    JpGraphError::Raise("<b>JpGraph Error:</b> No positions specified for pattern.");
+
+	if( !(is_numeric($this->iBackgroundColor) && $this->iBackgroundColor==-1) ) {
+	    $aImg->SetColor($this->iBackgroundColor);
+	    $aImg->FilledRectangle($this->rect->x,$this->rect->y,$this->rect->xe,$this->rect->ye); 
+	}
+
+	$aImg->SetColor($this->color);
+	$aImg->SetLineWeight($this->weight);
+
+	// Virtual function implemented by subclass
+	$this->DoPattern($aImg);
+
+	// Frame around the pattern area
+	if( $this->doframe ) 
+	    $aImg->Rectangle($this->rect->x,$this->rect->y,$this->rect->xe,$this->rect->ye);
+    }
+
+}
+
+
+//=====================================================================
+// Class RectPatternSolid
+// Implements a solid band
+//=====================================================================
+class RectPatternSolid extends RectPattern {
+
+    function RectPatternSolid($aColor="black",$aWeight=1) {
+	parent::RectPattern($aColor,$aWeight);
+    }
+
+    function Stroke(&$aImg) {
+	$aImg->SetColor($this->color);
+	$aImg->FilledRectangle($this->rect->x,$this->rect->y,
+	$this->rect->xe,$this->rect->ye);
+    }
+}
+
+//=====================================================================
+// Class RectPatternHor
+// Implements horizontal line pattern
+//=====================================================================
+class RectPatternHor extends RectPattern {
+		
+    function RectPatternHor($aColor="black",$aWeight=1,$aLineSpacing=7) {
+	parent::RectPattern($aColor,$aWeight);
+	$this->linespacing = $aLineSpacing;
+    }
+		
+    function DoPattern(&$aImg) {
+	$x0 = $this->rect->x;		
+	$x1 = $this->rect->xe;
+	$y = $this->rect->y;
+	while( $y < $this->rect->ye ) {
+	    $aImg->Line($x0,$y,$x1,$y);
+	    $y += $this->linespacing;
+	}
+    }
+}
+
+//=====================================================================
+// Class RectPatternVert
+// Implements vertical line pattern
+//=====================================================================
+class RectPatternVert extends RectPattern {
+    var $linespacing=10;	// Line spacing in pixels
+		
+    function RectPatternVert($aColor="black",$aWeight=1,$aLineSpacing=7) {
+	parent::RectPattern($aColor,$aWeight);
+	$this->linespacing = $aLineSpacing;
+    }
+
+    //--------------------
+    // Private methods
+    //
+    function DoPattern(&$aImg) {
+	$x = $this->rect->x;		
+	$y0 = $this->rect->y;
+	$y1 = $this->rect->ye;
+	while( $x < $this->rect->xe ) {
+	    $aImg->Line($x,$y0,$x,$y1);
+	    $x += $this->linespacing;
+	}
+    }
+}
+
+
+//=====================================================================
+// Class RectPatternRDiag
+// Implements right diagonal pattern
+//=====================================================================
+class RectPatternRDiag extends RectPattern {
+    var $linespacing;	// Line spacing in pixels
+		
+    function RectPatternRDiag($aColor="black",$aWeight=1,$aLineSpacing=12) {
+	parent::RectPattern($aColor,$aWeight);
+	$this->linespacing = $aLineSpacing;
+    }
+
+    function DoPattern(&$aImg) {
+	//  --------------------
+	//  | /   /   /   /   /|
+	//  |/   /   /   /   / |
+	//  |   /   /   /   /  |
+	//  --------------------
+	$xe = $this->rect->xe;
+	$ye = $this->rect->ye;
+	$x0 = $this->rect->x + round($this->linespacing/2); 
+	$y0 = $this->rect->y;
+	$x1 = $this->rect->x; 
+	$y1 = $this->rect->y + round($this->linespacing/2);
+
+	while($x0<=$xe && $y1<=$ye) {
+	    $aImg->Line($x0,$y0,$x1,$y1);
+	    $x0 += $this->linespacing;
+	    $y1 += $this->linespacing;
+	}
+
+	$x1 = $this->rect->x + ($y1-$ye);
+	//$x1 = $this->rect->x +$this->linespacing;
+	$y0=$this->rect->y; $y1=$ye; 
+	while( $x0 <= $xe ) {
+	    $aImg->Line($x0,$y0,$x1,$y1);
+	    $x0 += $this->linespacing;
+	    $x1 += $this->linespacing;
+	}
+
+	$y0=$this->rect->y + ($x0-$xe);
+	$x0=$xe;
+	while( $y0 <= $ye ) {
+	    $aImg->Line($x0,$y0,$x1,$y1);
+	    $y0 += $this->linespacing;
+	    $x1 += $this->linespacing;
+	}
+    }
+
+}
+
+//=====================================================================
+// Class RectPatternLDiag
+// Implements left diagonal pattern
+//=====================================================================
+class RectPatternLDiag extends RectPattern {
+    var $linespacing;	// Line spacing in pixels
+		
+    function RectPatternLDiag($aColor="black",$aWeight=1,$aLineSpacing=12) {
+	$this->linespacing = $aLineSpacing;
+	parent::RectPattern($aColor,$aWeight);
+    }
+
+    function DoPattern(&$aImg) {
+	//  --------------------
+	//  |\   \   \   \   \ |
+	//  | \   \   \   \   \|
+	//  |  \   \   \   \   |
+	//  |------------------|
+	$xe = $this->rect->xe;
+	$ye = $this->rect->ye;
+	$x0 = $this->rect->x + round($this->linespacing/2); 
+	$y0 = $this->rect->ye;
+	$x1 = $this->rect->x; 
+	$y1 = $this->rect->ye - round($this->linespacing/2);
+
+	while($x0<=$xe && $y1>=$this->rect->y) {
+	    $aImg->Line($x0,$y0,$x1,$y1);
+	    $x0 += $this->linespacing;
+	    $y1 -= $this->linespacing;
+	}
+
+	$x1 = $this->rect->x + ($this->rect->y-$y1);
+	$y0=$ye; $y1=$this->rect->y; 
+	while( $x0 <= $xe ) {
+	    $aImg->Line($x0,$y0,$x1,$y1);
+	    $x0 += $this->linespacing;
+	    $x1 += $this->linespacing;
+	}
+
+	$y0=$this->rect->ye - ($x0-$xe);
+	$x0=$xe;
+	while( $y0 >= $this->rect->y ) {
+	    $aImg->Line($x0,$y0,$x1,$y1);
+	    $y0 -= $this->linespacing;
+	    $x1 += $this->linespacing;
+	}
+    }
+}
+
+//=====================================================================
+// Class RectPattern3DPlane
+// Implements "3D" plane pattern
+//=====================================================================
+class RectPattern3DPlane extends RectPattern {
+    var $alpha=50;  // Parameter that specifies the distance
+    // to "simulated" horizon in pixel from the
+    // top of the band. Specifies how fast the lines
+    // converge.
+
+    function RectPattern3DPlane($aColor="black",$aWeight=1) {
+	parent::RectPattern($aColor,$aWeight);
+	$this->SetDensity(10);  // Slightly larger default
+    }
+
+    function SetHorizon($aHorizon) {
+	$this->alpha=$aHorizon;
+    }
+	
+    function DoPattern(&$aImg) {
+	// "Fake" a nice 3D grid-effect. 
+	$x0 = $this->rect->x + $this->rect->w/2;
+	$y0 = $this->rect->y;
+	$x1 = $x0;
+	$y1 = $this->rect->ye;
+	$x0_right = $x0;
+	$x1_right = $x1;
+
+	// BTW "apa" means monkey in Swedish but is really a shortform for
+	// "alpha+a" which was the labels I used on paper when I derived the
+	// geometric to get the 3D perspective right. 
+	// $apa is the height of the bounding rectangle plus the distance to the
+	// artifical horizon (alpha)
+	$apa = $this->rect->h + $this->alpha;
+
+	// Three cases and three loops
+	// 1) The endpoint of the line ends on the bottom line
+	// 2) The endpoint ends on the side
+	// 3) Horizontal lines
+
+	// Endpoint falls on bottom line
+	$middle=$this->rect->x + $this->rect->w/2;
+	$dist=$this->linespacing;
+	$factor=$this->alpha /($apa);
+	while($x1>$this->rect->x) {
+	    $aImg->Line($x0,$y0,$x1,$y1);
+	    $aImg->Line($x0_right,$y0,$x1_right,$y1);
+	    $x1 = $middle - $dist;
+	    $x0 = $middle - $dist * $factor;
+	    $x1_right = $middle + $dist;
+	    $x0_right =  $middle + $dist * $factor;
+	    $dist += $this->linespacing;
+	}
+
+	// Endpoint falls on sides
+	$dist -= $this->linespacing;
+	$d=$this->rect->w/2;
+	$c = $apa - $d*$apa/$dist;
+	while( $x0>$this->rect->x ) {
+	    $aImg->Line($x0,$y0,$this->rect->x,$this->rect->ye-$c);
+	    $aImg->Line($x0_right,$y0,$this->rect->xe,$this->rect->ye-$c);
+	    $dist += $this->linespacing;			
+	    $x0 = $middle - $dist * $factor;
+	    $x1 = $middle - $dist;
+	    $x0_right =  $middle + $dist * $factor;			
+	    $c = $apa - $d*$apa/$dist;
+	}		
+		
+	// Horizontal lines
+	// They need some serious consideration since they are a function
+	// of perspective depth (alpha) and density (linespacing)
+	$x0=$this->rect->x;
+	$x1=$this->rect->xe;
+	$y=$this->rect->ye;
+		
+	// The first line is drawn directly. Makes the loop below slightly
+	// more readable.
+	$aImg->Line($x0,$y,$x1,$y);
+	$hls = $this->linespacing;
+		
+	// A correction factor for vertical "brick" line spacing to account for
+	// a) the difference in number of pixels hor vs vert
+	// b) visual apperance to make the first layer of "bricks" look more
+	// square.
+	$vls = $this->linespacing*0.6;
+		
+	$ds = $hls*($apa-$vls)/$apa;
+	// Get the slope for the "perspective line" going from bottom right
+	// corner to top left corner of the "first" brick.
+		
+	// Uncomment the following lines if you want to get a visual understanding
+	// of what this helpline does. BTW this mimics the way you would get the
+	// perspective right when drawing on paper.
+	/*
+	  $x0 = $middle;
+	  $y0 = $this->rect->ye;
+	  $len=floor(($this->rect->ye-$this->rect->y)/$vls);
+	  $x1 = $middle-round($len*$ds);
+	  $y1 = $this->rect->ye-$len*$vls;
+	  $aImg->PushColor("red");
+	  $aImg->Line($x0,$y0,$x1,$y1);
+	  $aImg->PopColor();
+	*/
+		
+	$y -= $vls;		
+	$k=($this->rect->ye-($this->rect->ye-$vls))/($middle-($middle-$ds));
+	$dist = $hls;
+	while( $y>$this->rect->y ) {
+	    $aImg->Line($this->rect->x,$y,$this->rect->xe,$y);
+	    $adj = $k*$dist/(1+$dist*$k/$apa);
+	    if( $adj < 2 ) $adj=2;
+	    $y = $this->rect->ye - round($adj);
+	    $dist += $hls;
+	}
+    }
+}
+
+//=====================================================================
+// Class RectPatternCross
+// Vert/Hor crosses
+//=====================================================================
+class RectPatternCross extends RectPattern {
+    var $vert=null;
+    var $hor=null;
+    function RectPatternCross($aColor="black",$aWeight=1) {
+	parent::RectPattern($aColor,$aWeight);
+	$this->vert = new RectPatternVert($aColor,$aWeight);
+	$this->hor  = new RectPatternHor($aColor,$aWeight);
+    }
+
+    function SetOrder($aDepth) {
+	$this->vert->SetOrder($aDepth);
+	$this->hor->SetOrder($aDepth);
+    }
+
+    function SetPos(&$aRect) {
+	parent::SetPos($aRect);
+	$this->vert->SetPos($aRect);
+	$this->hor->SetPos($aRect);
+    }
+
+    function SetDensity($aDens) {
+	$this->vert->SetDensity($aDens);
+	$this->hor->SetDensity($aDens);
+    }
+
+    function DoPattern(&$aImg) {
+	$this->vert->DoPattern($aImg);
+	$this->hor->DoPattern($aImg);
+    }
+}
+
+//=====================================================================
+// Class RectPatternDiagCross
+// Vert/Hor crosses
+//=====================================================================
+
+class RectPatternDiagCross extends RectPattern {
+    var $left=null;
+    var $right=null;
+    function RectPatternDiagCross($aColor="black",$aWeight=1) {
+	parent::RectPattern($aColor,$aWeight);
+	$this->right = new RectPatternRDiag($aColor,$aWeight);
+	$this->left  = new RectPatternLDiag($aColor,$aWeight);
+    }
+
+    function SetOrder($aDepth) {
+	$this->left->SetOrder($aDepth);
+	$this->right->SetOrder($aDepth);
+    }
+
+    function SetPos(&$aRect) {
+	parent::SetPos($aRect);
+	$this->left->SetPos($aRect);
+	$this->right->SetPos($aRect);
+    }
+
+    function SetDensity($aDens) {
+	$this->left->SetDensity($aDens);
+	$this->right->SetDensity($aDens);
+    }
+
+    function DoPattern(&$aImg) {
+	$this->left->DoPattern($aImg);
+	$this->right->DoPattern($aImg);
+    }
+
+}
+
+//=====================================================================
+// Class RectPatternFactory
+// Factory class for rectangular pattern 
+//=====================================================================
+class RectPatternFactory {
+    function RectPatternFactory() {
+	// Empty
+    }
+    function Create($aPattern,$aColor,$aWeight=1) {
+	switch($aPattern) {
+	    case BAND_RDIAG:
+		$obj =  new RectPatternRDiag($aColor,$aWeight);
+	    break;
+	    case BAND_LDIAG:
+		$obj =  new RectPatternLDiag($aColor,$aWeight);
+	    break;
+	    case BAND_SOLID:
+		$obj =  new RectPatternSolid($aColor,$aWeight);
+	    break;
+	    case BAND_LVERT:
+		$obj =  new RectPatternVert($aColor,$aWeight);
+	    break;
+	    case BAND_LHOR:
+		$obj =  new RectPatternHor($aColor,$aWeight);
+	    break;
+	    case BAND_3DPLANE:
+		$obj =  new RectPattern3DPlane($aColor,$aWeight);
+	    break;
+	    case BAND_HVCROSS:
+		$obj =  new RectPatternCross($aColor,$aWeight);
+	    break;
+	    case BAND_DIAGCROSS:
+		$obj =  new RectPatternDiagCross($aColor,$aWeight);
+	    break;
+	    default:
+		JpGraphError::Raise("<b>JpGraph Error:</b> Unknown pattern specification ($aPattern)");
+	}
+	return $obj;
+    }
+}
+
+
+//=====================================================================
+// Class PlotBand
+// Factory class which is used by the client.
+// It is reposnsible for factoring the corresponding pattern
+// concrete class.
+//=====================================================================
+class PlotBand {
+    var $prect=null;
+    var $depth;
+
+    function PlotBand($aDir,$aPattern,$aMin,$aMax,$aColor="black",$aWeight=1,$aDepth=DEPTH_BACK) {
+	$f =  new RectPatternFactory();
+	$this->prect = $f->Create($aPattern,$aColor,$aWeight);
+	$this->dir = $aDir;
+	$this->min = $aMin;
+	$this->max = $aMax;
+	$this->depth=$aDepth;
+    }
+	
+    // Set position. aRect contains absolute image coordinates
+    function SetPos(&$aRect) {
+	assert( $this->prect != null ) ;
+	$this->prect->SetPos($aRect);
+    }
+	
+    function ShowFrame($aFlag=true) {
+	$this->prect->ShowFrame($aFlag);
+    }
+
+    // Set z-order. In front of pplot or in the back
+    function SetOrder($aDepth) {
+	$this->depth=$aDepth;
+    }
+	
+    function SetDensity($aDens) {
+	$this->prect->SetDensity($aDens);
+    }
+	
+    function GetDir() {
+	return $this->dir;
+    }
+	
+    function GetMin() {
+	return $this->min;
+    }
+	
+    function GetMax() {
+	return $this->max;
+    }
+	
+    // Display band
+    function Stroke(&$aImg,&$aXScale,&$aYScale) {
+	assert( $this->prect != null ) ;
+	if( $this->dir == HORIZONTAL ) {
+	    if( !is_numeric($this->min) && $this->min == "min" ) $this->min = $aYScale->GetMinVal();
+	    if( !is_numeric($this->max) && $this->max == "max" ) $this->max = $aYScale->GetMaxVal();
+	    $x=$aXScale->scale_abs[0];
+	    $y=$aYScale->Translate($this->max);
+	    $width=$aXScale->scale_abs[1]-$aXScale->scale_abs[0]+1;
+	    $height=abs($y-$aYScale->Translate($this->min))+1;
+	    $this->prect->SetPos(new Rectangle($x,$y,$width,$height));
+	}
+	else {	// VERTICAL
+	    if( !is_numeric($this->min) && $this->min == "min" ) $this->min = $aXScale->GetMinVal();
+	    if( !is_numeric($this->max) && $this->max == "max" ) $this->max = $aXScale->GetMaxVal();
+	    $y=$aYScale->scale_abs[1];
+	    $x=$aXScale->Translate($this->min);
+	    $height=abs($aYScale->scale_abs[1]-$aYScale->scale_abs[0]);
+	    $width=abs($x-$aXScale->Translate($this->max));
+	    $this->prect->SetPos(new Rectangle($x,$y,$width,$height));
+	}
+	$this->prect->Stroke($aImg);
+    }
+}
+
+//===================================================
+// CLASS PlotLine
+// Description: 
+// Data container class to hold properties for a static
+// line that is drawn directly in the plot area.
+// Usefull to add static borders inside a plot to show
+// for example set-values
+//===================================================
+class PlotLine {
+    var $weight=1;
+    var $color="black";
+    var $direction=-1; 
+    var $scaleposition;
+
+//---------------
+// CONSTRUCTOR
+    function PlotLine($aDir=HORIZONTAL,$aPos=0,$aColor="black",$aWeight=1) {
+	$this->direction = $aDir;
+	$this->color=$aColor;
+	$this->weight=$aWeight;
+	$this->scaleposition=$aPos;
+    }
+	
+//---------------
+// PUBLIC METHODS	
+    function SetPosition($aScalePosition) {
+	$this->scaleposition=$aScalePosition;
+    }
+	
+    function SetDirection($aDir) {
+	$this->direction = $aDir;
+    }
+	
+    function SetColor($aColor) {
+	$this->color=$aColor;
+    }
+	
+    function SetWeight($aWeight) {
+	$this->weight=$aWeight;
+    }
+	
+    function Stroke(&$aImg,&$aXScale,&$aYScale) {
+	$aImg->SetColor($this->color);
+	$aImg->SetLineWeight($this->weight);		
+	if( $this->direction == VERTICAL ) {
+	    $ymin_abs=$aYScale->Translate($aYScale->GetMinVal());
+	    $ymax_abs=$aYScale->Translate($aYScale->GetMaxVal());
+	    $xpos_abs=$aXScale->Translate($this->scaleposition);
+	    $aImg->Line($xpos_abs, $ymin_abs, $xpos_abs, $ymax_abs);
+	}
+	elseif( $this->direction == HORIZONTAL ) {
+	    $xmin_abs=$aXScale->Translate($aXScale->GetMinVal());
+	    $xmax_abs=$aXScale->Translate($aXScale->GetMaxVal());
+	    $ypos_abs=$aYScale->Translate($this->scaleposition);
+	    $aImg->Line($xmin_abs, $ypos_abs, $xmax_abs, $ypos_abs);
+	}
+	else
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Illegal direction for static line");
+    }
+}
+
+// <EOF>
+?>
Index: /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_log.php
===================================================================
--- /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_log.php	(revision 3594)
+++ /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_log.php	(revision 3594)
@@ -0,0 +1,174 @@
+<?php
+/*=======================================================================
+// File: 	JPGRAPH_LOG.PHP
+// Description:	Log scale plot extension for JpGraph
+// Created: 	2001-01-08
+// Author:	Johan Persson (johanp@aditus.nu)
+// Ver:		$Id: jpgraph_log.php 18250 2005-05-07 14:13:43Z ralfbecker $
+//
+// License:	This code is released under GPL 2.0
+// Copyright (C) 2001 Johan Persson
+//========================================================================
+*/
+
+//===================================================
+// CLASS LogScale
+// Description: Logarithmic scale between world and screen
+//===================================================
+class LogScale extends LinearScale {
+//---------------
+// CONSTRUCTOR
+
+    // Log scale is specified using the log of min and max
+    function LogScale($min,$max,$type="y") {
+	$this->LinearScale($min,$max,$type);
+	$this->ticks = new LogTicks();
+    }
+
+//----------------
+// PUBLIC METHODS	
+
+    // Translate between world and screen
+    function	Translate($a) {
+	if( $a==0 ) $a=1;
+	$a=log10($a);
+	return floor($this->off + ($a*1.0 - $this->scale[0]) * $this->scale_factor); 
+    }
+
+    // Relative translate (don't include offset) usefull when we just want
+    // to know the relative position (in pixels) on the axis	
+    function RelTranslate($a) {
+	if( $a==0 ) $a=1;
+	$a=log10($a);
+	return ($a*1.0 - $this->scale[0]) * $this->scale_factor; 
+    }
+		
+    function GetMinVal() {
+	return pow(10,$this->scale[0]);
+    }
+	
+    function GetMaxVal() {
+	return pow(10,$this->scale[1]);
+    }
+	
+    // Logarithmic autoscaling is much simplier since we just
+    // set the min and max to logs of the min and max values.
+    // Note that for log autoscale the "maxstep" the fourth argument
+    // isn't used. This is just included to give the method the same
+    // signature as the linear counterpart.
+    function AutoScale(&$img,$min,$max,$dummy) {
+	if( $min==0 ) $min=1;
+	assert($max>0);		
+	$smin = floor(log10($min));
+	$smax = ceil(log10($max));
+	$this->Update($img,$smin,$smax);					
+    }
+//---------------
+// PRIVATE METHODS	
+} // Class
+
+//===================================================
+// CLASS LogTicks
+// Description: 
+//===================================================
+class LogTicks extends Ticks{
+//---------------
+// CONSTRUCTOR
+    function LogTicks() {
+    }
+//---------------
+// PUBLIC METHODS	
+    function IsSpecified() {
+	return true;
+    }
+	
+    // For log scale it's meaningless to speak about a major step
+    // We just return -1 to make the framework happy specificall
+    // StrokeLabels()
+    function GetMajor() {
+	return -1;
+    }
+    // Draw ticks on image "img" using scale "scale". The axis absolute
+    // position in the image is specified in pos, i.e. for an x-axis
+    // it specifies the absolute y-coord and for Y-ticks it specified the
+    // absolute x-position.
+    function Stroke(&$img,&$scale,$pos) {
+	$start = $scale->GetMinVal();
+	$limit = $scale->GetMaxVal();
+	$nextMajor = 10*$start;
+	$step = $nextMajor / 10.0;
+		
+		
+	$img->SetLineWeight($this->weight);			
+		
+	if( $scale->type == "y" ) {
+	    // member direction specified if the ticks should be on
+	    // left or right side.
+	    $a=$pos + $this->direction*$this->GetMinTickAbsSize();
+	    $a2=$pos + $this->direction*$this->GetMajTickAbsSize();	
+			
+	    $count=1; 
+	    $this->maj_ticks_pos[0]=$scale->Translate($start);
+	    $this->maj_ticklabels_pos[0]=$scale->Translate($start);
+	    if( $this->supress_first )
+		$this->maj_ticks_label[0]="";
+	    else
+		$this->maj_ticks_label[0]=$start;	
+	    $i=1;
+	    for($y=$start; $y<=$limit; $y+=$step,++$count  ) {
+		$ys=$scale->Translate($y);	
+		$this->ticks_pos[]=$ys;
+		$this->ticklabels_pos[]=$ys;
+		if( $count % 10 == 0 ) {
+		    if( $this->majcolor!="" ) $img->PushColor($this->majcolor);
+		    $img->Line($pos,$ys,$a2,$ys);
+		    if( $this->majcolor!="" ) $img->PopColor();
+		    $this->maj_ticks_pos[$i]=$ys;
+		    $this->maj_ticklabels_pos[$i]=$ys;
+		    $this->maj_ticks_label[$i]=$nextMajor;	
+		    ++$i;						
+		    $nextMajor *= 10;
+		    $step *= 10;	
+		    $count=1; 				
+		}
+		else {
+		    if( $this->mincolor!="" ) $img->PushColor($this->mincolor);
+		    $img->Line($pos,$ys,$a,$ys);		
+		    if( $this->mincolor!="" ) $img->PopCOlor();
+		}
+	    }		
+	}
+	else {
+	    $a=$pos - $this->direction*$this->GetMinTickAbsSize();
+	    $a2=$pos - $this->direction*$this->GetMajTickAbsSize();	
+	    $count=1; 
+	    $this->maj_ticks_pos[0]=$scale->Translate($start);
+	    $this->maj_ticklabels_pos[0]=$scale->Translate($start);
+	    if( $this->supress_first )
+		$this->maj_ticks_label[0]="";
+	    else
+		$this->maj_ticks_label[0]=$start;	
+	    $i=1;			
+	    for($x=$start; $x<=$limit; $x+=$step,++$count  ) {
+		$xs=$scale->Translate($x);	
+		$this->ticks_pos[]=$xs;
+		$this->ticklabels_pos[]=$xs;
+		if( $count % 10 == 0 ) {
+		    $img->Line($xs,$pos,$xs,$a2);
+		    $this->maj_ticks_pos[$i]=$xs;
+		    $this->maj_ticklabels_pos[$i]=$xs;
+		    $this->maj_ticks_label[$i]=$nextMajor;	
+		    ++$i;								
+		    $nextMajor *= 10;
+		    $step *= 10;	
+		    $count=1; 				
+		}
+		else
+		    $img->Line($xs,$pos,$xs,$a);		
+	    }		
+	}
+	return true;
+    }
+} // Class
+/* EOF */
+?>
Index: /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_bar.php
===================================================================
--- /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_bar.php	(revision 3594)
+++ /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_bar.php	(revision 3594)
@@ -0,0 +1,742 @@
+<?php
+/*=======================================================================
+// File:	JPGRAPH_BAR.PHP
+// Description:	Bar plot extension for JpGraph
+// Created: 	2001-01-08
+// Author:	Johan Persson (johanp@aditus.nu)
+// Ver:		$Id: jpgraph_bar.php 18250 2005-05-07 14:13:43Z ralfbecker $
+//
+// License:	This code is released under QPL
+// Copyright (C) 2001 Johan Persson
+//========================================================================
+*/
+
+//===================================================
+// CLASS Gradient
+// Description: Handles gradient fills. This is to be
+// considered a "friend" class of Class Image. The
+// Gradient takes care of production a gradient fill for
+// the bars.
+//===================================================
+class Gradient {
+    var $img=null;
+//---------------
+// CONSTRUCTOR
+    function Gradient(&$img) {
+	$this->img = $img;
+    }
+
+//---------------
+// PUBLIC METHODS	
+    // Produce a gradient filled rectangle with a smooth transition between
+    // two colors.
+    // ($xl,$yt) 	Top left corner
+    // ($xr,$yb)	Bottom right
+    // $from_color	Starting color in gradient
+    // $to_color	End color in the gradient
+    // $style		Which way is the gradient oriented?
+    function FilledRectangle($xl,$yt,$xr,$yb,$from_color,$to_color,$style=1) {
+	switch( $style ) {	
+	    case 1:  // HORIZONTAL
+		$steps = abs($xr-$xl);
+	    $delta = $xr>=$xl ? 1 : -1;
+	    $this->GetColArray($from_color,$to_color,$steps,$colors);
+	    for( $i=0, $x=$xl; $i<$steps; ++$i ) {
+		$this->img->current_color = $colors[$i];
+		$this->img->Line($x,$yt,$x,$yb);
+		$x += $delta;
+	    }
+	    break;
+
+	    case 2: // VERTICAL
+		$steps = abs($yb-$yt);
+	    $delta = $yb>=$yt ? 1 : -1;
+	    $this->GetColArray($from_color,$to_color,$steps,$colors);
+	    for($i=0,$y=$yt; $i<$steps; ++$i) {
+		$this->img->current_color = $colors[$i];
+		$this->img->Line($xl,$y,$xr,$y);
+		$y += $delta;
+	    }
+	    break;
+
+	    case 3: // VERTICAL FROM MIDDLE
+		$steps = abs($yb-$yt)/2;
+	    $delta = $yb>=$yt ? 1 : -1;
+	    $this->GetColArray($from_color,$to_color,$steps,$colors);
+	    for($y=$yt, $i=0; $i<$steps;  ++$i) {
+		$this->img->current_color = $colors[$i];
+		$this->img->Line($xl,$y,$xr,$y);
+		$y += $delta;
+	    }
+	    --$i;
+	    for($j=0; $j<$steps; ++$j, --$i) {
+		$this->img->current_color = $colors[$i];
+		$this->img->Line($xl,$y,$xr,$y);
+		$y += $delta;
+	    }
+	    $this->img->Line($xl,$y,$xr,$y);
+	    break;
+
+	    case 4: // HORIZONTAL FROM MIDDLE
+		$steps = abs($xr-$xl)/2;
+	    $delta = $xr>=$xl ? 1 : -1;
+	    $this->GetColArray($from_color,$to_color,$steps,$colors);
+	    for($x=$xl, $i=0; $i<$steps; ++$i) {
+		$this->img->current_color = $colors[$i];
+		$this->img->Line($x,$yb,$x,$yt);
+		$x += $delta;
+	    }
+	    --$i;
+	    for($j=0; $j<$steps; ++$j, --$i) {
+		$this->img->current_color = $colors[$i];
+		$this->img->Line($x,$yb,$x,$yt);
+		$x += $delta;
+	    }
+	    $this->img->Line($x,$yb,$x,$yt);		
+	    break;
+
+	    case 6: // HORIZONTAL WIDER MIDDLE
+		$steps = abs($xr-$xl)/3;
+	    $delta = $xr>=$xl ? 1 : -1;
+	    $this->GetColArray($from_color,$to_color,$steps,$colors);
+	    for($x=$xl, $i=0; $i<$steps; ++$i) {
+		$this->img->current_color = $colors[$i];
+		$this->img->Line($x,$yb,$x,$yt);
+		$x += $delta;
+	    }
+	    --$i;
+	    $this->img->current_color = $colors[$i];
+	    for($j=0; $j< $steps; ++$j) {
+		$this->img->Line($x,$yb,$x,$yt);
+		$x += $delta;
+	    }
+
+	    for($j=0; $j<$steps; ++$j, --$i) {
+		$this->img->current_color = $colors[$i];				
+		$this->img->Line($x,$yb,$x,$yt);	
+		$x += $delta;
+	    }				
+	    break;
+
+	    case 7: // VERTICAL WIDER MIDDLE
+		$steps = abs($yb-$yt)/3;
+	    $delta = $yb>=$yt? 1 : -1;
+	    $this->GetColArray($from_color,$to_color,$steps,$colors);
+	    for($y=$yt, $i=0; $i<$steps;  ++$i) {
+		$this->img->current_color = $colors[$i];
+		$this->img->Line($xl,$y,$xr,$y);
+		$y += $delta;
+	    }
+	    --$i;
+	    $this->img->current_color = $colors[$i];
+	    for($j=0; $j< $steps; ++$j) {
+		$this->img->Line($xl,$y,$xr,$y);
+		$y += $delta;
+	    }
+	    for($j=0; $j<$steps; ++$j, --$i) {
+		$this->img->current_color = $colors[$i];				
+		$this->img->Line($xl,$y,$xr,$y);
+		$y += $delta;
+	    }				
+	    break;
+
+	    case 5: // Rectangle
+		$steps = floor(min(($yb-$yt)+1,($xr-$xl)+1)/2);	
+	    $this->GetColArray($from_color,$to_color,$steps,$colors);
+	    $dx = ($xr-$xl)/2;
+	    $dy = ($yb-$yt)/2;
+	    $x=$xl;$y=$yt;$x2=$xr;$y2=$yb;
+	    for($x=$xl, $i=0; $x<$xl+$dx && $y<$yt+$dy ; ++$x, ++$y, --$x2, --$y2, ++$i) {
+		assert($i<count($colors));
+		$this->img->current_color = $colors[$i];			
+		$this->img->Rectangle($x,$y,$x2,$y2);
+	    }
+	    $this->img->Line($x,$y,$x2,$y2);
+	    break;
+
+	    default:
+		die("JpGraph Error: Unknown gradient style (=$style).");
+		break;
+	}
+    }
+
+//---------------
+// PRIVATE METHODS	
+    // Add to the image color map the necessary colors to do the transition
+    // between the two colors using $numcolors intermediate colors
+    function GetColArray($from_color,$to_color,$arr_size,&$colors,$numcols=100) {
+	if( $arr_size==0 ) return;
+	// If color is give as text get it's corresponding r,g,b values
+	$from_color = $this->img->rgb->Color($from_color);
+	$to_color = $this->img->rgb->Color($to_color);
+		
+	$rdelta=($to_color[0]-$from_color[0])/$numcols;
+	$gdelta=($to_color[1]-$from_color[1])/$numcols;
+	$bdelta=($to_color[2]-$from_color[2])/$numcols;
+	$stepspercolor	= $numcols/$arr_size;
+	$prevcolnum	= -1;
+	for ($i=0; $i<$arr_size; ++$i) {
+	    $colnum	= floor($stepspercolor*$i);
+	    if ( $colnum == $prevcolnum ) 
+		$colors[$i]	= $colidx;
+	    else {
+		$r = floor($from_color[0] + $colnum*$rdelta);
+		$g = floor($from_color[1] + $colnum*$gdelta);
+		$b = floor($from_color[2] + $colnum*$bdelta);
+		$colidx = $this->img->rgb->Allocate(sprintf("#%02x%02x%02x",$r,$g,$b));
+		$colors[$i]	= $colidx;
+	    }
+	    $prevcolnum = $colnum;
+	}
+    }	
+} // Class
+
+
+//===================================================
+// CLASS BarPlot
+// Description: Main code to produce a bar plot 
+//===================================================
+class BarPlot extends Plot {
+    var $width=0.4; // in percent of major ticks
+    var $abswidth=-1; // Width in absolute pixels
+    var $fill_color="lightblue"; // Default is to fill with light blue
+    var $ybase=0; // Bars start at 0 
+    var $align="left";
+    var $grad=false,$grad_style=1;
+    var $grad_fromcolor=array(50,50,200),$grad_tocolor=array(255,255,255);
+    var $bar_shadow=false;
+    var $bar_shadow_color="black";
+    var $bar_shadow_hsize=3,$bar_shadow_vsize=3;	
+    var $show_value=false,$show_value_format="%d",$show_value_angle=0;
+    var $show_value_ff=FF_FONT1,$show_value_fs=FS_NORMAL,$show_value_fsize=12;
+    var $show_value_color="black",$show_value_margin=3;
+	
+//---------------
+// CONSTRUCTOR
+    function BarPlot(&$datay,$datax=false) {
+	$this->Plot($datay,$datax);		
+	++$this->numpoints;
+    }
+
+//---------------
+// PUBLIC METHODS	
+	
+    // Set a drop shadow for the bar (or rather an "up-right" shadow)
+    function SetShadow($color="black",$hsize=3,$vsize=3) {
+	$this->bar_shadow=true;
+	$this->bar_shadow_color=$color;
+	$this->bar_shadow_vsize=$vsize;
+	$this->bar_shadow_hsize=$hsize;
+		
+	// Adjust the value margin to compensate for shadow
+	$this->show_value_margin += $vsize;
+    }
+	
+    // Display the value of the bar at the top of bar
+    function ShowValue($f=true) {
+	$this->show_value = $f;	
+    }
+	
+    function SetValueFormat($format="%d",$angle=0) {
+	$this->show_value_format = $format;
+	$this->show_value_angle = $angle;
+    }
+	
+    function SetValueFont($ff=FF_FONT1,$fs=FS_NORMAL,$size=10) {
+	$this->show_value_ff=$ff;
+	$this->show_value_fs=$fs;
+	$this->show_value_fsize=$size;
+    }
+	
+    function SetValueColor($color) {
+	$this->show_value_color=$color;
+    }
+	
+    function SetValueMargin($m) {
+	$this->show_value_margin=$m;
+    }
+
+    function SetYStart($y) {	
+	die("JpGraph Error: Deprecated function SetYStart. Use SetYMin() instead.");
+    }
+	
+    // DEPRECATED use SetYBase instead
+    function SetYMin($y) {
+	$this->ybase=$y;
+    }
+
+    //
+    function SetYBase($y) {
+	$this->ybase=$y;
+    }
+	
+    function Legend(&$graph) {
+	if( $this->fill_color && $this->legend!="" ) {
+	    if( is_array($this->fill_color) )
+		$graph->legend->Add($this->legend,$this->fill_color[0]);		
+	    else
+		$graph->legend->Add($this->legend,$this->fill_color);		
+	}
+    }
+
+    // Gets called before any axis are stroked
+    function PreStrokeAdjust(&$graph) {
+	parent::PreStrokeAdjust($graph);
+		
+	// For a "text" X-axis scale we will adjust the
+	// display of the bars a little bit.
+	if( substr($graph->axtype,0,3)=="tex" ) {
+	    // Position the ticks between the bars
+	    $graph->xaxis->scale->ticks->SetXLabelOffset(0.5,0);
+		
+	    // Position the labels under each bar in the middle of the
+	    // major steps.
+	    $graph->SetTextScaleOff(0.5-$this->width/2);						
+	}
+	else {
+	    // We only set an absolute width for linear and int scale
+	    // for text scale the width will be set to a fraction of
+	    // the majstep width.
+	    if( $this->abswidth == -1 ) // Not set
+				// set width to a visuable sensible default
+		$this->abswidth = $graph->img->plotwidth/(2*count($this->coords[0]));
+	}
+    }
+
+    function Min() {
+	$m = parent::Min();
+	if( $m[1] >= $this->ybase )
+	    $m[1] = $this->ybase;
+	return $m;	
+    }
+
+    function Max() {
+	$m = parent::Max();
+	if( $m[1] <= $this->ybase )
+	    $m[1] = $this->ybase;
+	return $m;	
+    }	
+	
+    // Specify width as fractions of the major stepo size
+    function SetWidth($w) {
+	assert($w > 0 && $w <= 1.0);
+	$this->width=$w;
+    }
+	
+    // Specify width in absolute pixels. If specified this
+    // overrides SetWidth()
+    function SetAbsWidth($w) {
+	$this->abswidth=$w;
+    }
+		
+    function SetAlign($a) {
+	$this->align=$a;
+    }
+	
+    function SetNoFill() {
+	$this->grad = false;
+	$this->fill_color=false;
+    }
+		
+    function SetFillColor($c) {
+	$this->fill_color=$c;
+    }
+	
+    function SetFillGradient($from_color,$to_color,$style) {
+	$this->grad=true;
+	$this->grad_fromcolor=$from_color;
+	$this->grad_tocolor=$to_color;
+	$this->grad_style=$style;
+    }
+	
+    function Stroke(&$img,&$xscale,&$yscale) { 
+		
+	$numpoints = count($this->coords[0]);
+	if( isset($this->coords[1]) ) {
+	    if( count($this->coords[1])!=$numpoints )
+		die("JpGraph Error: Number of X and Y points are not equal.<br>
+					Number of X-points:".count($this->coords[1])."<br>
+					Number of Y-points:$numpoints");
+	    else
+		$exist_x = true;
+	}
+	else 
+	    $exist_x = false;
+		
+		
+	$numbars=count($this->coords[0]);
+	if( $yscale->scale[0] >= 0 )
+	    $zp=$yscale->scale_abs[0]; 
+	else
+	    $zp=$yscale->Translate(0.0);
+	
+	if( $this->abswidth > -1 )
+	    $abswidth=$this->abswidth;
+	else
+	    $abswidth=round($this->width*$xscale->scale_factor,0);
+			
+		
+	for($i=0; $i<$numbars; $i++) {
+	    if( $exist_x ) $x=$this->coords[1][$i];
+	    else $x=$i;
+			
+	    $x=$xscale->Translate($x);
+			
+	    if($this->align=="center")
+		$x -= $abswidth/2;
+	    elseif($this->align=="right")
+		$x -= $abswidth;			
+	    $pts=array(
+		$x,$zp,
+		$x,$yscale->Translate($this->coords[0][$i]),
+		$x+$abswidth,$yscale->Translate($this->coords[0][$i]),
+		$x+$abswidth,$zp);
+	    if( $this->grad ) {
+		$grad = new Gradient($img);
+		$grad->FilledRectangle($pts[2],$pts[3],
+		$pts[6],$pts[7],
+		$this->grad_fromcolor,$this->grad_tocolor,$this->grad_style);				
+	    }
+	    elseif( !empty($this->fill_color) ) {
+		if(is_array($this->fill_color)) {
+		    $img->PushColor($this->fill_color[$i % count($this->fill_color)]);
+		} else {
+		    $img->PushColor($this->fill_color);
+		}
+		$img->FilledPolygon($pts);
+		$img->PopColor();
+	    }
+			
+	    // Remember value of this bar
+	    $val=$this->coords[0][$i];
+			
+	    if( $this->bar_shadow && $val != 0 ) {
+		$ssh = $this->bar_shadow_hsize;
+		$ssv = $this->bar_shadow_vsize;
+				// Create points to create a "upper-right" shadow
+		if( $val > 0 ) {
+		    $sp[0]=$pts[6];		$sp[1]=$pts[7];
+		    $sp[2]=$pts[4];		$sp[3]=$pts[5];
+		    $sp[4]=$pts[2];		$sp[5]=$pts[3];
+		    $sp[6]=$pts[2]+$ssh;	$sp[7]=$pts[3]-$ssv;
+		    $sp[8]=$pts[4]+$ssh;	$sp[9]=$pts[5]-$ssv;
+		    $sp[10]=$pts[6]+$ssh;	$sp[11]=$pts[7]-$ssv;
+		} 
+		elseif( $val < 0 ) {
+		    $sp[0]=$pts[4];		$sp[1]=$pts[5];
+		    $sp[2]=$pts[6];		$sp[3]=$pts[7];
+		    $sp[4]=$pts[0];	$sp[5]=$pts[1];
+		    $sp[6]=$pts[0]+$ssh;	$sp[7]=$pts[1]-$ssv;
+		    $sp[8]=$pts[6]+$ssh;	$sp[9]=$pts[7]-$ssv;
+		    $sp[10]=$pts[4]+$ssh;	$sp[11]=$pts[5]-$ssv;
+		}
+				
+		$img->PushColor($this->bar_shadow_color);
+		$img->FilledPolygon($sp);
+		$img->PopColor();
+	    }
+			
+	    // Stroke the outline of the bar
+	    if( is_array($this->color) )
+		$img->SetColor($this->color[$i % count($this->color)]);
+	    else
+		$img->SetColor($this->color);
+	    $img->SetLineWeight($this->weight);
+	    $img->Polygon($pts);
+					
+	    if( $this->show_value) {
+		$sval=sprintf($this->show_value_format,$val);
+		$txt = new Text($sval);
+		$txt->SetFont($this->show_value_ff,$this->show_value_fs,$this->show_value_fsize);
+		$txt->SetColor($this->show_value_color);
+		$x=$pts[2]+($pts[4]-$pts[2])/2;
+		if($this->bar_shadow)
+		    $x += $ssh;
+				
+		if( $val >= 0 ) {
+		    $txt->Pos($x,$pts[3]-$this->show_value_margin);
+		    $txt->Align("center","bottom");
+		}
+		else {
+		    $txt->Pos($x,$pts[3]+$this->show_value_margin);
+		    $txt->Align("center","top");
+		}
+		$txt->SetOrientation($this->show_value_angle);
+		$txt->Stroke($img);			
+	    }
+			
+	    // Create the client side image map
+	    $this->csimareas.= "<area shape=\"rect\" coords=\"";
+	    // Hmmm, this is fishy.  Fixes a bug in Opera whereby if Y2<Y1 or X2<X1 the csim doesn't work
+	    if ($pts[3] < $pts[7]) {
+		if ($pts[2] < $pts[6]) 
+		    $this->csimareas .= "$pts[2], $pts[3], $pts[6], $pts[7]\"";
+		else 
+		    $this->csimareas .= "$pts[6], $pts[3], $pts[2], $pts[7]\"";
+	    } else {
+		if ($pts[2] < $pts[6]) 
+		    $this->csimareas .= "$pts[2], $pts[7], $pts[6], $pts[3]\"";
+		else 
+		    $this->csimareas .= "$pts[6], $pts[7], $pts[2], $pts[3]\"";
+	    }
+	    if( !empty($this->csimtargets[$i]) )
+		$this->csimareas .= " href=\"".$this->csimtargets[$i]."\"";
+	    if( !empty($this->csimalts[$i]) ) {
+		$sval=sprintf($this->csimalts[$i],$this->coords[0][$i]);
+		$this->csimareas .= " alt=\"$sval\" title=\"$sval\" ";
+	    }
+	    $this->csimareas .= ">\r\n";
+	}
+	return true;
+    }
+} // Class
+
+//===================================================
+// CLASS GroupBarPlot
+// Description: Produce grouped bar plots
+//===================================================
+class GroupBarPlot extends BarPlot {
+    var $plots;
+    var $width=0.7;
+    var $nbrplots=0;
+    var $numpoints;
+//---------------
+// CONSTRUCTOR
+    function GroupBarPlot($plots) {
+	$this->plots = $plots;
+	$this->nbrplots = count($plots);
+	$this->numpoints = $plots[0]->numpoints;
+    }
+
+//---------------
+// PUBLIC METHODS	
+    function Legend(&$graph) {
+	$n = count($this->plots);
+	for($i=0; $i<$n; ++$i)
+	    $this->plots[$i]->Legend($graph);
+    }
+	
+    function Min() {
+	list($xmin,$ymin) = $this->plots[0]->Min();
+	$n = count($this->plots);
+	for($i=0; $i<$n; ++$i) {
+	    list($xm,$ym) = $this->plots[$i]->Min();
+	    $xmin = max($xmin,$xm);
+	    $ymin = min($ymin,$ym);
+	}
+	return array($xmin,$ymin);		
+    }
+	
+    function Max() {
+	list($xmax,$ymax) = $this->plots[0]->Max();
+	$n = count($this->plots);
+	for($i=0; $i<$n; ++$i) {
+	    list($xm,$ym) = $this->plots[$i]->Max();
+	    $xmax = max($xmax,$xm);
+	    $ymax = max($ymax,$ym);
+	}
+	return array($xmax,$ymax);
+    }
+	
+    function GetCSIMareas() {
+	$n = count($this->plots);
+	for($i=0; $i<$n; ++$i) {
+	    $csimareas.= $this->plots[$i]->csimareas;
+	}
+	return $csimareas;
+    }
+	
+    // Stroke all the bars next to each other
+    function Stroke(&$img,&$xscale,&$yscale) { 
+	$tmp=$xscale->off;
+	$n = count($this->plots);
+	for( $i=0; $i<$n; ++$i ) {
+	    $this->plots[$i]->ymin=$this->ybase;
+	    $this->plots[$i]->SetWidth($this->width/$this->nbrplots);
+	    $xscale->off = $tmp+$i*round($xscale->ticks->major_step*$xscale->scale_factor*$this->width/$this->nbrplots);
+	    $this->plots[$i]->Stroke($img,$xscale,$yscale);
+	}
+	$xscale->off=$tmp;
+    }
+} // Class
+
+//===================================================
+// CLASS AccBarPlot
+// Description: Produce accumulated bar plots
+//===================================================
+class AccBarPlot extends BarPlot {
+    var $plots=null,$nbrplots=0,$numpoints=0;
+//---------------
+// CONSTRUCTOR
+    function AccBarPlot($plots) {
+	$this->plots = $plots;
+	$this->nbrplots = count($plots);
+	$this->numpoints = $plots[0]->numpoints;		
+    }
+
+//---------------
+// PUBLIC METHODS	
+    function Legend(&$graph) {
+	$n = count($this->plots);
+	for( $i=0; $i<$n; ++$i ) 
+	    $this->plots[$i]->Legend($graph);
+    }
+
+    function Max() {
+	list($xmax) = $this->plots[0]->Max();
+	$nmax=0;
+	for($i=0; $i<count($this->plots); ++$i) {
+	    $n = count($this->plots[$i]->coords[0]);
+	    $nmax = max($nmax,$n);
+	    list($x) = $this->plots[$i]->Max();
+	    $xmax = Max($xmax,$x);
+	}
+	for( $i = 0; $i < $nmax; $i++ ) {
+	    // Get y-value for bar $i by adding the
+	    // individual bars from all the plots added.
+	    // It would be wrong to just add the
+	    // individual plots max y-value since that
+	    // would in most cases give to large y-value.
+	    $y=$this->plots[0]->coords[0][$i];
+	    for( $j = 1; $j < $this->nbrplots; $j++ ) {
+		$y += $this->plots[ $j ]->coords[0][$i];
+	    }
+	    $ymax[$i] = $y;
+	}
+	$ymax = max($ymax);
+
+	// Bar always start at baseline
+	if( $ymax <= $this->ybase ) 
+	    $ymax = $this->ybase;
+	return array($xmax,$ymax);
+    }
+
+    function Min() {
+	$nmax=0;
+	list($xmin,$ysetmin) = $this->plots[0]->Min();
+	for($i=0; $i<count($this->plots); ++$i) {
+	    $n = count($this->plots[$i]->coords[0]);
+	    $nmax = max($nmax,$n);
+	    list($x,$y) = $this->plots[$i]->Min();
+	    $xmin = Min($xmin,$x);
+	    $ysetmin = Min($y,$ysetmin);
+	}
+	for( $i = 0; $i < $nmax; $i++ ) {
+	    // Get y-value for bar $i by adding the
+	    // individual bars from all the plots added.
+	    // It would be wrong to just add the
+	    // individual plots max y-value since that
+	    // would in most cases give to large y-value.
+	    $y=$this->plots[0]->coords[0][$i];
+	    for( $j = 1; $j < $this->nbrplots; $j++ ) {
+		$y += $this->plots[ $j ]->coords[0][$i];
+	    }
+	    $ymin[$i] = $y;
+	}
+	$ymin = Min($ysetmin,Min($ymin));
+	// Bar always start at baseline
+	if( $ymin >= $this->ybase )
+	    $ymin = $this->ybase;
+	return array($xmin,$ymin);
+    }
+
+    // Method description
+    function Stroke(&$img,&$xscale,&$yscale) {
+	$img->SetLineWeight($this->weight);
+	for($i=0; $i<$this->numpoints-1; $i++) {
+	    $accy = 0;
+	    $accy_neg = 0; 
+	    for($j=0; $j<$this->nbrplots; ++$j ) {				
+		$img->SetColor($this->plots[$j]->color);
+		if ($this->plots[$j]->coords[0][$i] > 0) {
+		    $yt=$yscale->Translate($this->plots[$j]->coords[0][$i]+$accy);
+		    $accyt=$yscale->Translate($accy);
+		    $accy+=$this->plots[$j]->coords[0][$i];
+		} else {
+		    $yt=$yscale->Translate($this->plots[$j]->coords[0][$i]+$accy_neg);
+		    $accyt=$yscale->Translate($accy_neg);
+		    $accy_neg+=$this->plots[$j]->coords[0][$i];
+		}				
+				
+		$xt=$xscale->Translate($i);
+		$abswidth=round($this->width*$xscale->scale_factor,0);
+		$pts=array($xt,$accyt,$xt,$yt,$xt+$abswidth,$yt,$xt+$abswidth,$accyt);
+				
+		if( $this->plots[$j]->grad ) {
+		    $grad = new Gradient($img);
+		    $grad->FilledRectangle(
+			$pts[2],$pts[3],
+			$pts[6],$pts[7],
+			$this->plots[$j]->grad_fromcolor,
+			$this->plots[$j]->grad_tocolor,
+			$this->plots[$j]->grad_style);				
+		} elseif ($this->plots[$j]->fill_color ) {
+		    $img->SetColor($this->plots[$j]->fill_color);
+		    $img->FilledPolygon($pts,4);
+		    $img->SetColor($this->plots[$j]->color);
+		}				  
+
+		if( $this->bar_shadow ) {
+		    $ssh = $this->bar_shadow_hsize;
+		    $ssv = $this->bar_shadow_vsize;
+		    // Create points to create a "upper-right" shadow
+		    $sp[0]=$pts[6];		$sp[1]=$pts[7];
+		    $sp[2]=$pts[4];		$sp[3]=$pts[5];
+		    $sp[4]=$pts[2];	$sp[5]=$pts[3];
+		    $sp[6]=$pts[2]+$ssh;	$sp[7]=$pts[3]-$ssv;
+		    $sp[8]=$pts[4]+$ssh;	$sp[9]=$pts[5]-$ssv;
+		    $sp[10]=$pts[6]+$ssh;	$sp[11]=$pts[7]-$ssv;
+		    $img->SetColor($this->bar_shadow_color);
+		    $img->FilledPolygon($sp,4);
+		}
+
+		if( $i < count($this->plots[$j]->csimtargets) ) {
+		    $this->csimareas.= "<area shape=\"rect\" coords=\"";
+		    // Hmmm, this is fishy.  Fixes a bug in Opera whereby if Y2<Y1 or X2<X1 the csim doesn't work
+		    // This means that the area MUST specify top left and bottom right corners
+		    if ($pts[3] < $pts[7]) {
+			if ($pts[2] < $pts[6]) {
+			    $this->csimareas.= "$pts[2], $pts[3], $pts[6], $pts[7]\"";
+			} else {
+			    $this->csimareas.= "$pts[6], $pts[3], $pts[2], $pts[7]\"";
+			}
+		    } else {
+			if ($pts[2] < $pts[6]) {
+			    $this->csimareas.= "$pts[2], $pts[7], $pts[6], $pts[3]\"";
+			} else {
+			    $this->csimareas.= "$pts[6], $pts[7], $pts[2], $pts[3]\"";
+			}
+		    }
+		    $this->csimareas.= " href=\"".$this->plots[$j]->csimtargets[$i]."\"";
+		    if( !empty($this->plots[$j]->csimalts[$i]) ) {
+			$sval=sprintf($this->plots[$j]->csimalts[$i],$this->plots[$j]->coords[0][$i]);										
+			$this->csimareas .= " alt=\"$sval\" title=\"$sval\" ";
+		    }
+		    $this->csimareas .= ">\r\n";				
+		}
+				
+		$img->Polygon($pts,4);
+	    }
+			
+	    $yt=$yscale->Translate($accy);
+			
+	    if( $this->show_value) {
+		$sval=sprintf($this->show_value_format,$accy);
+		$txt = new Text($sval);
+		$txt->SetFont($this->show_value_ff,$this->show_value_fs,$this->show_value_fsize);
+		$txt->SetColor($this->show_value_color);
+		$x=$pts[2]+($pts[4]-$pts[2])/2;
+		if($this->bar_shadow)
+		    $x += $ssh;
+		$txt->Pos($x,$yt-$this->show_value_margin);
+		$txt->Align("center","bottom");
+		$txt->SetOrientation($this->show_value_angle);
+		$txt->Stroke($img);			
+	    }			
+	}
+	return true;
+    }
+} // Class
+
+/* EOF */
+?>
Index: /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_pie3d.php
===================================================================
--- /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_pie3d.php	(revision 3594)
+++ /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_pie3d.php	(revision 3594)
@@ -0,0 +1,306 @@
+<?php
+/*=======================================================================
+// File:	JPGRAPH_PIE3D.PHP
+// Description: 3D Pie plot extension for JpGraph
+// Created: 	2001-03-24
+// Author:	Johan Persson (johanp@aditus.nu)
+// Ver:		$Id: jpgraph_pie3d.php 18250 2005-05-07 14:13:43Z ralfbecker $
+//
+// License:	This code is released under GPL 2.0
+// Copyright (C) 2001 Johan Persson
+//========================================================================
+*/
+
+//===================================================
+// CLASS PiePlot3D
+// Description: Plots a 3D pie with a specified projection 
+// angle between 20 and 70 degrees.
+//===================================================
+class PiePlot3D extends PiePlot {
+    var $labelhintcolor="red",$showlabelhint=true,$labelmargin=0.30;
+    var $angle=30;	
+	
+//---------------
+// CONSTRUCTOR
+    function PiePlot3d(&$data) {
+	$this->data = $data;
+	$this->title = new Text("");
+	$this->title->SetFont(FF_FONT1,FS_BOLD);
+    }
+
+//---------------
+// PUBLIC METHODS	
+	
+    // Specify projection angle for 3D in degrees
+    // Must be between 20 and 70 degrees
+    function SetAngle($a) {
+	if( $a<30 || $a>70 )
+	    JpGraphError::Raise("JpGraph: 3D Pie projection angle must be between 30 and 70 degrees.");
+	else
+	    $this->angle = $a;
+    }
+
+    function AddSliceToCSIM($i,$xc,$yc,$height,$width,$thick,$sa,$ea) {  //Slice number, ellipse centre (x,y), height, width, start angle, end angle
+		
+	//add coordinates of the centre to the map
+	$coords = "$xc, $yc";
+
+	//add coordinates of the first point on the arc to the map
+	$xp = floor($width*cos($sa)/2+$xc);
+	$yp = floor($yc-$height*sin($sa)/2);
+	$coords.= ", $xp, $yp";
+
+	//If on the front half, add the thickness offset
+	if ($sa >= M_PI && $sa <= 2*M_PI*1.01) {
+	    $yp = floor($yp+($thick*$width));
+	    $coords.= ", $xp, $yp";
+	}
+		
+	//add coordinates every 0.2 radians
+	$a=$sa+0.2;
+	while ($a<$ea) {
+	    $xp = floor($width*cos($a)/2+$xc);
+	    if ($a >= M_PI && $a <= 2*M_PI*1.01) {
+		$yp = floor($yc-($height*sin($a)/2)+$thick*$width);
+	    } else {
+		$yp = floor($yc-$height*sin($a)/2);
+	    }
+	    $coords.= ", $xp, $yp";
+	    $a += 0.2;
+	}
+		
+	//Add the last point on the arc
+	$xp = floor($width*cos($ea)/2+$xc);
+	$yp = floor($yc-$height*sin($ea)/2);
+
+	//If on the front half, add the thickness offset
+	if ($ea >= M_PI && $ea <= 2*M_PI*1.01) {
+	    $coords.= ", $xp, ".floor($yp+($thick*$width));
+	}
+	$coords.= ", $xp, $yp";
+	if( !empty($this->csimalts[$i]) ) {										
+	    $tmp=sprintf($this->csimalts[$i],$this->data[$i]);
+	    $alt="alt=\"$tmp\"";
+	}
+	if( !empty($this->csimtargets[$i]) )
+	    $this->csimareas .= "<area shape=\"poly\" coords=\"$coords\" href=\"".$this->csimtargets[$i]."\" $alt>\r\n";
+    }
+
+	
+    function ExplodeSlice($e) {
+	JpGraphError::Raise("JpGraph Error: Exploding slices are not (yet) implemented for 3d pies graphs.");
+	//$this->explode_slice=$e;
+    }
+	
+    // Distance from the pie to the labels
+    function SetLabelMargin($m) {
+	assert($m>0 && $m<1);
+	$this->labelmargin=$m;
+    }
+	
+    // Show a thin line from the pie to the label for a specific slice
+    function ShowLabelHint($f=true) {
+	$this->showlabelhint=$f;
+    }
+	
+    // Set color of hint line to label for each slice
+    function SetLabelHintColor($c) {
+	$this->labelhintcolor=$c;
+    }
+		
+    function Stroke(&$img) {
+		
+	$colors = array_keys($img->rgb->rgb_table);
+   	sort($colors);	
+   	$ta=$this->themearr[$this->theme];	
+   	
+   	if( $this->setslicecolors==null ) 
+	    $numcolors=count($ta);
+   	else
+	    $numcolors=count($this->setslicecolors);
+   	
+	// Draw the slices
+	$sum=0;
+	foreach($this->data as $d)
+	    $sum += $d;
+		
+	// Format the titles for each slice
+	for( $i=0; $i<count($this->data); ++$i) {
+	    if( $this->labeltype==0 )
+		if( $sum != 0 )
+		    $l = round(100*$this->data[$i]/$sum,$this->precision);
+		else
+		    $l=0;
+	    else
+		$l = $this->data[$i];
+	    $l = sprintf($this->labelformat,$l);
+	    if( $this->show_psign ) $l .= "%";
+	    $this->labels[$i]=$l;			
+	}
+		
+	// Set up the pie-circle with some heuristic constants
+	$thick=0.16-($this->angle-20)/60*0.07;
+	
+	$width = floor(2.0*$this->radius*min($img->width,$img->height));
+	$height = ($this->angle/90.0)*$width;
+	$xc = $this->posx*$img->width;
+	$yc = $this->posy*$img->height;
+
+	$img->SetColor($this->color);			
+	$img->Ellipse($xc,$yc,$width,$height);
+	$img->Arc($xc,$yc+$width*$thick,$width,$height,0,180);
+	$img->Line($xc+$width/2,$yc,$xc+$width/2,$yc+$width*$thick);
+	$img->Line($xc-$width/2,$yc,$xc-$width/2,$yc+$width*$thick);
+	$fillPerimeter[0] = array('x' => round((($xc - ($width / 2)) + 1)),
+				  'y' => round(($yc + ($width * $thick) / 2)));
+		
+	// Draw the first slice first line
+	$img->SetColor($this->color);			
+	$img->SetLineWeight($this->weight);
+	$a = $this->startangle;
+
+	$xp = $width*cos($a)/2+$xc;
+	$yp = $yc-$height*sin($a)/2;
+	$img->Line($xc,$yc,$xp,$yp);
+
+	for($i=0; $sum>0 && $i<count($this->data); $i++) {
+	    $img->SetColor($this->color);	
+	    $d = $this->data[$i];
+	    $la = $a + M_PI*$d/$sum;
+	    $old_a = $a;
+	    $a += 2*M_PI*$d/$sum;
+
+	    if ($this->csimtargets[$i]) {
+		$this->AddSliceToCSIM($i,$xc,$yc,$height,$width,$thick,$old_a,$a);
+	    }
+
+	    $xp = $width*cos($a)/2+$xc;
+	    $yp = $yc-$height*sin($a)/2;
+
+	    if( $i<count($this->data)-1)
+		$img->Line($xc,$yc,$xp,$yp);
+
+	    if( $a > M_PI && $a < 0.999*2*M_PI )
+		$img->Line($xp,$yp,$xp,$yp+$width*$thick-1);
+
+	    if($a < M_PI) {
+		$fillPerimeter[$i + 1] = $fillPerimeter[$i];
+	    } else {
+		$fillPerimeter[$i + 1] = array('x' => round(($xp + 1)),
+					       'y' => round(($yp + ($width * $thick) / 2)));
+	    }
+			
+	    if( $this->setslicecolors==null )
+		$slicecolor=$colors[$ta[$i%$numcolors]];
+	    else
+		$slicecolor=$this->setslicecolors[$i%$numcolors];
+				
+	    if( $this->show_labels ) {
+		$margin = 1 + $this->labelmargin;
+		$xp = $width*cos($la)/2*$margin;
+		$yp = $height*sin($la)/2*$margin;
+									
+		if( ($la >= 0 && $la <= M_PI) || $la>2*M_PI*0.98 ) {
+		    $this->StrokeLabels($this->labels[$i],$img,$la,$xc+$xp,$yc-$yp);	
+		    if( $this->showlabelhint ) {
+			$img->SetColor($this->labelhintcolor);
+			$img->Line($xc+$xp/$margin,$yc-$yp/$margin,$xc+$xp,$yc-$yp);
+		    }
+		}
+		else {
+		    $this->StrokeLabels($this->labels[$i],$img,$la,$xc+$xp,$yc-$yp+$width*$thick);	
+		    if( $this->showlabelhint ) {
+			$img->SetColor($this->labelhintcolor);
+			$img->Line($xc+$xp/$margin,$yc-$yp/$margin+$width*$thick,$xc+$xp,$yc-$yp+$width*$thick);
+		    }
+		}					
+
+
+		$img->SetColor($slicecolor);
+		$xp = $width*cos($la)/3+$xc;
+		$yp = $yc-$height*sin($la)/3;
+		$img->Fill(round($xp), round($yp)); 
+
+		// Make the edge color 35% darker
+		$img->SetColor($slicecolor.":0.65");
+				
+		if($fillPerimeter[$i]['x'] <= $xc + ($width / 2)) {
+		    $img->Fill($fillPerimeter[$i]['x'],$fillPerimeter[$i]['y']); 
+		}
+	    }
+	}	
+		
+	// Adjust title position
+	$this->title->Pos($xc,$yc-$img->GetFontHeight()-$this->radius,"center","bottom");
+	$this->title->Stroke($img);
+
+	// Draw the pie ellipse one more time since the filling might have
+	// written partly on the lines due to the filling in the edges.
+	$img->SetColor($this->color);			
+	$img->Ellipse($xc,$yc,$width,$height);
+	$img->Arc($xc,$yc+$width*$thick,$width,$height,0,180);
+
+	// Draw the first slice first line
+	$a = $this->startangle;
+	$xp = $width*cos($a)/2+$xc;
+	$yp = $yc-$height*sin($a)/2;
+	$img->Line($xc,$yc,$xp,$yp);
+
+	// Draw the rest of the slice lines
+	for($i=0, $a=0; $sum>0 && $i<count($this->data); $i++) {
+	    $d = $this->data[$i];
+	    $la = $a + M_PI*$d/$sum;
+	    $old_a = $a;
+	    $a += 2*M_PI*$d/$sum;
+		  
+	    $xp = $width*cos($a)/2+$xc;
+	    $yp = $yc-$height*sin($a)/2;
+
+	    if( $a > M_PI && $a < 0.999*2*M_PI )
+		$img->Line($xp,$yp,$xp,$yp+$width*$thick-1);
+		  
+	    if( $i<count($this->data)-1)
+		$img->Line($xc,$yc,$xp,$yp);
+	}
+
+	$img->Line($xc+$width/2,$yc,$xc+$width/2,$yc+$width*$thick);
+	$img->Line($xc-$width/2,$yc,$xc-$width/2,$yc+$width*$thick);
+		
+    }
+
+//---------------
+// PRIVATE METHODS	
+
+    // Position the labels of each slice
+    function StrokeLabels($label,$img,$a,$xp,$yp) {
+		
+	$img->SetFont($this->font_family,$this->font_style,$this->font_size);
+	$img->SetColor($this->font_color);
+	$img->SetTextAlign("left","top");
+	$marg=6;
+
+	// Position the axis title. 
+	// dx, dy is the offset from the top left corner of the bounding box that sorrounds the text
+	// that intersects with the extension of the corresponding axis. The code looks a little
+	// bit messy but this is really the only way of having a reasonable position of the
+	// axis titles.
+	$h=$img->GetTextHeight($label);
+	$w=$img->GetTextWidth($label);
+	while( $a > 2*M_PI ) $a -= 2*M_PI;
+	if( $a>=7*M_PI/4 || $a <= M_PI/4 ) $dx=0;
+	if( $a>=M_PI/4 && $a <= 3*M_PI/4 ) $dx=($a-M_PI/4)*2/M_PI; 
+	if( $a>=3*M_PI/4 && $a <= 5*M_PI/4 ) $dx=1;
+	if( $a>=5*M_PI/4 && $a <= 7*M_PI/4 ) $dx=(1-($a-M_PI*5/4)*2/M_PI);
+		
+	if( $a>=7*M_PI/4 ) $dy=(($a-M_PI)-3*M_PI/4)*2/M_PI;
+	if( $a<=M_PI/4 ) $dy=(1-$a*2/M_PI);
+	if( $a>=M_PI/4 && $a <= 3*M_PI/4 ) $dy=1;
+	if( $a>=3*M_PI/4 && $a <= 5*M_PI/4 ) $dy=(1-($a-3*M_PI/4)*2/M_PI);
+	if( $a>=5*M_PI/4 && $a <= 7*M_PI/4 ) $dy=0;
+		
+	$img->StrokeText($xp-$dx*$w,$yp-$dy*$h,$label);		
+    }	
+} // Class
+
+/* EOF */
+?>
Index: /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_scatter.php
===================================================================
--- /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_scatter.php	(revision 3594)
+++ /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_scatter.php	(revision 3594)
@@ -0,0 +1,82 @@
+<?php 
+/*=======================================================================
+// File:	JPGRAPH_SCATTER.PHP
+// Description: Scatter (and impuls) plot extension for JpGraph
+// Created: 	2001-02-11
+// Author:	Johan Persson (johanp@aditus.nu)
+// Ver:		$Id: jpgraph_scatter.php 18250 2005-05-07 14:13:43Z ralfbecker $
+//
+// License:	This code is released under GPL 2.0
+// Copyright (C) 2001 Johan Persson
+//========================================================================
+*/
+
+//===================================================
+// CLASS ScatterPlot
+// Description: Render X and Y plots
+//===================================================
+class ScatterPlot extends Plot {
+    var $impuls = false;
+    var $linkpoints = false, $linkpointweight=1, $linkpointcolor="black";
+//---------------
+// CONSTRUCTOR
+    function ScatterPlot(&$datay,$datax=false) {
+	if( (count($datax) != count($datay)) && is_array($datax))
+	    JpGraphError::Raise("JpGraph: Scatterplot must have equal number of X and Y points.");
+	$this->Plot($datay,$datax);
+	$this->mark = new PlotMark();
+	$this->mark->SetType(MARK_CIRCLE);
+	$this->mark->SetColor($this->color);
+    }
+
+//---------------
+// PUBLIC METHODS	
+    function SetImpuls($f=true) {
+	$this->impuls = $f;
+    }
+
+    // Combine the scatter plot points with a line
+    function SetLinkPoints($f=true,$lpc="black",$weight=1) {
+	$this->linkpoints=$f;
+	$this->linkpointcolor=$lpc;
+	$this->linkpointweight=$weight;
+    }
+
+    function Stroke(&$img,&$xscale,&$yscale) {
+	$ymin=$yscale->scale_abs[0];
+	if( $yscale->scale[0] < 0 )
+	    $yzero=$yscale->Translate(0);
+	else
+	    $yzero=$yscale->scale_abs[0];
+	for( $i=0; $i<$this->numpoints; ++$i ) {
+	    if( isset($this->coords[1]) )
+		$xt = $xscale->Translate($this->coords[1][$i]);
+	    else
+		$xt = $xscale->Translate($i);
+	    $yt = $yscale->Translate($this->coords[0][$i]);	
+	    if( $this->linkpoints && isset($yt_old) ) {
+		$img->SetColor($this->linkpointcolor);
+		$img->SetLineWeight($this->linkpointweight);
+		$img->Line($xt_old,$yt_old,$xt,$yt);
+	    }
+	    if( $this->impuls ) {
+		$img->SetColor($this->color);
+		$img->SetLineWeight($this->weight);
+		$img->Line($xt,$yzero,$xt,$yt);
+	    }
+	    $this->mark->Stroke($img,$xt,$yt);	
+	    $xt_old = $xt;
+	    $yt_old = $yt;
+	}
+    }
+	
+    // Framework function
+    function Legend(&$aGraph) {
+	if( $this->legend != "" ) {
+	    $aGraph->legend->Add($this->legend,$this->mark->fill_color);		
+	}
+    }	
+	
+} // Class
+/* EOF */
+?>
Index: /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_spider.php
===================================================================
--- /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_spider.php	(revision 3594)
+++ /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_spider.php	(revision 3594)
@@ -0,0 +1,557 @@
+<?php
+/*=======================================================================
+// File:	JPGRAPH_SPIDER.PHP
+// Description: Spider plot extension for JpGraph
+// Created: 	2001-02-04
+// Author:	Johan Persson (johanp@aditus.nu)
+// Ver:		$Id: jpgraph_spider.php 18250 2005-05-07 14:13:43Z ralfbecker $
+//
+// License:	This code is released under GPL 2.0
+// Copyright (C) 2001 Johan Persson
+//========================================================================
+*/
+
+//===================================================
+// CLASS FontProp
+// Description: Utility class to enable the use
+// of a "title" instance variable for the spider axis.
+// This clas is only used to hold a font and a color
+// property for the axis title.
+//===================================================
+class FontProp {
+    var $font_family=FF_FONT1, $font_style=FS_NORMAL,$font_size=14,$font_color=array(0,0,0);	
+    function SetFont($family,$style=FS_NORMAL,$size=14) {
+	$this->font_family = $family;
+	$this->font_style = $style;
+	$this->font_size = $size;
+    }
+	
+    function SetColor($c) {
+	$this->font_color = $c;
+    }
+}
+
+class SpiderLogTicks extends Ticks {
+//---------------
+// CONSTRUCTOR
+    function SpiderLogTicks() {
+    }
+//---------------
+// PUBLIC METHODS	
+
+    // TODO: Add Argument grid
+    function Stroke(&$aImg,&$grid,$aPos,$aAxisAngle,&$aScale,&$aMajPos,&$aMajLabel) {
+	$start = $aScale->GetMinVal();
+	$limit = $aScale->GetMaxVal();
+	$nextMajor = 10*$start;
+	$step = $nextMajor / 10.0;
+	$count=1; 
+				
+	$ticklen_maj=5;
+	$dx_maj=round(sin($aAxisAngle)*$ticklen_maj);
+	$dy_maj=round(cos($aAxisAngle)*$ticklen_maj);
+	$ticklen_min=3;
+	$dx_min=round(sin($aAxisAngle)*$ticklen_min);
+	$dy_min=round(cos($aAxisAngle)*$ticklen_min);
+			
+	$aMajPos=array();
+	$aMajLabel=array();
+			
+	if( $this->supress_first )
+	    $aMajLabel[]="";
+	else
+	    $aMajLabel[]=$start;	
+	$yr=$aScale->RelTranslate($start);	
+	$xt=round($yr*cos($aAxisAngle))+$aScale->scale_abs[0];
+	$yt=$aPos-round($yr*sin($aAxisAngle));
+	$aMajPos[]=$xt+2*$dx_maj;
+	$aMajPos[]=$yt-$aImg->GetFontheight()/2;
+	$grid[]=$xt;
+	$grid[]=$yt;
+
+	$aImg->SetLineWeight($this->weight);			
+		
+	for($y=$start; $y<=$limit; $y+=$step,++$count  ) {
+	    $yr=$aScale->RelTranslate($y);	
+	    $xt=round($yr*cos($aAxisAngle))+$aScale->scale_abs[0];
+	    $yt=$aPos-round($yr*sin($aAxisAngle));
+	    if( $count % 10 == 0 ) {
+		$grid[]=$xt;
+		$grid[]=$yt;
+		$aMajPos[]=$xt+2*$dx_maj;
+		$aMajPos[]=$yt-$aImg->GetFontheight()/2;							
+		if( !$this->supress_tickmarks )	{		
+		    if( $this->majcolor!="" ) $aImg->PushColor($this->majcolor);
+		    $aImg->Line($xt+$dx_maj,$yt+$dy_maj,$xt-$dx_maj,$yt-$dy_maj);
+		    if( $this->majcolor!="" ) $aImg->PopColor();
+		}
+		$aMajLabel[]=$nextMajor;	
+		$nextMajor *= 10;
+		$step *= 10;	
+		$count=1; 				
+	    }
+	    else
+		if( !$this->supress_minor_tickmarks )	{
+		    if( $this->mincolor!="" ) $aImg->PushColor($this->mincolor);
+		    $aImg->Line($xt+$dx_min,$yt+$dy_min,$xt-$dx_min,$yt-$dy_min);
+		    if( $this->mincolor!="" ) $aImg->PopColor();
+		}
+	}		
+    }		
+}
+	
+class SpiderLinearTicks extends LinearTicks {
+//---------------
+// CONSTRUCTOR
+    function SpiderLinearTicks() {
+	// Empty
+    }
+
+//---------------
+// PUBLIC METHODS	
+
+    // TODO: Add argument grid
+    function Stroke(&$aImg,&$grid,$aPos,$aAxisAngle,&$aScale,&$aMajPos,&$aMajLabel) {
+	// Prepare to draw linear ticks
+	$maj_step_abs = abs($aScale->scale_factor*$this->major_step);	
+	$min_step_abs = abs($aScale->scale_factor*$this->minor_step);	
+	$nbrmaj = floor(($aScale->world_abs_size)/$maj_step_abs);
+	$nbrmin = floor(($aScale->world_abs_size)/$min_step_abs);
+	$skip = round($nbrmin/$nbrmaj); // Don't draw minor ontop of major
+
+	// Draw major ticks
+	$ticklen2=4;
+	$dx=round(sin($aAxisAngle)*$ticklen2);
+	$dy=round(cos($aAxisAngle)*$ticklen2);
+	$label=$aScale->scale[0]+$this->major_step;
+		
+	$aImg->SetLineWeight($this->weight);
+		
+	for($i=1; $i<=$nbrmaj; ++$i) {
+	    $xt=round($i*$maj_step_abs*cos($aAxisAngle))+$aScale->scale_abs[0];
+	    $yt=$aPos-round($i*$maj_step_abs*sin($aAxisAngle));
+	    $aMajLabel[]=$label;
+	    $label += $this->major_step;
+	    $grid[]=$xt;
+	    $grid[]=$yt;
+	    $aMajPos[($i-1)*2]=$xt+2*$dx;
+	    $aMajPos[($i-1)*2+1]=$yt-$aImg->GetFontheight()/2;				
+	    if( !$this->supress_tickmarks ) {
+		if( $this->majcolor!="" ) $aImg->PushColor($this->majcolor);
+		$aImg->Line($xt+$dx,$yt+$dy,$xt-$dx,$yt-$dy);
+		if( $this->majcolor!="" ) $aImg->PopColor();
+	    }
+	}
+
+	// Draw minor ticks
+	$ticklen2=3;
+	$dx=round(sin($aAxisAngle)*$ticklen2);
+	$dy=round(cos($aAxisAngle)*$ticklen2);
+	if( !$this->supress_tickmarks && !$this->supress_minor_tickmarks)	{
+	    if( $this->mincolor!="" ) $aImg->PushColor($this->mincolor);						
+	    for($i=1; $i<=$nbrmin; ++$i) {
+		if( ($i % $skip) == 0 ) continue;
+		$xt=round($i*$min_step_abs*cos($aAxisAngle))+$aScale->scale_abs[0];
+		$yt=$pos-round($i*$min_step_abs*sin($aAxisAngle));
+		$aImg->Line($xt+$dx,$yt+$dy,$xt-$dx,$yt-$dy);
+	    }
+	    if( $this->mincolor!="" ) $aImg->PopColor();
+	}
+    }
+}
+
+	
+
+//===================================================
+// CLASS SpiderAxis
+// Description: Implements axis for the spider graph
+//===================================================
+class SpiderAxis extends Axis {
+    var $title_color="navy";
+    var $title=null;
+//---------------
+// CONSTRUCTOR
+    function SpiderAxis(&$img,&$aScale,$color=array(0,0,0)) {
+	parent::Axis($img,$aScale,$color);
+	$this->len=$img->plotheight;
+	$this->font_size = FF_FONT1;
+	$this->title = new FontProp();
+	$this->color = array(0,0,0);
+    }
+//---------------
+// PUBLIC METHODS	
+    function SetTickLabels($l) {
+	$this->ticks_label = $l;
+    }
+	
+	
+    // Stroke the axis 
+    // $pos 			= Vertical position of axis
+    // $aAxisAngle = Axis angle
+    // $grid			= Returns an array with positions used to draw the grid
+    //	$lf			= Label flag, TRUE if the axis should have labels
+    function Stroke($pos,$aAxisAngle,&$grid,$title,$lf) {
+	$this->img->SetColor($this->color);
+		
+	// Determine end points for the axis
+	$x=round($this->scale->world_abs_size*cos($aAxisAngle)+$this->scale->scale_abs[0]);
+	$y=round($pos-$this->scale->world_abs_size*sin($aAxisAngle));
+		
+	// Draw axis
+	$this->img->SetColor($this->color);
+	$this->img->SetLineWeight($this->weight);
+	if( !$this->hide )
+	    $this->img->Line($this->scale->scale_abs[0],$pos,$x,$y);
+	
+	$this->scale->ticks->Stroke($this->img,$grid,$pos,$aAxisAngle,$this->scale,$majpos,$majlabel);
+		
+	// Draw labels
+	if( $lf && !$this->hide ) {
+	    $this->img->SetFont($this->font_family,$this->font_style,$this->font_size);	
+	    $this->img->SetTextAlign("left","top");
+	    $this->img->SetColor($this->color);
+			
+	    // majpos contsins (x,y) coordinates for labels
+	    for($i=0; $i<count($majpos)/2; ++$i) {
+		if( $this->ticks_label != null )
+		    $this->img->StrokeText($majpos[$i*2],$majpos[$i*2+1],$this->ticks_label[$i]);
+		else
+		    $this->img->StrokeText($majpos[$i*2],$majpos[$i*2+1],$majlabel[$i]);
+	    }
+	}
+	$this->_StrokeAxisTitle($pos,$aAxisAngle,$title);
+    }
+//---------------
+// PRIVATE METHODS	
+	
+    function _StrokeAxisTitle($pos,$aAxisAngle,$title) {
+	// Draw title of this axis
+	$this->img->SetFont($this->title->font_family,$this->title->font_style,$this->title->font_size);
+	$this->img->SetColor($this->title->font_color);
+	$marg=6;
+	$xt=round(($this->scale->world_abs_size+$marg)*cos($aAxisAngle)+$this->scale->scale_abs[0]);
+	$yt=round($pos-($this->scale->world_abs_size+$marg)*sin($aAxisAngle));
+
+	// Position the axis title. 
+	// dx, dy is the offset from the top left corner of the bounding box that sorrounds the text
+	// that intersects with the extension of the corresponding axis. The code looks a little
+	// bit messy but this is really the only way of having a reasonable position of the
+	// axis titles.
+	$h=$this->img->GetFontHeight();
+	$w=$this->img->GetTextWidth($title);
+	while( $aAxisAngle > 2*M_PI ) $aAxisAngle -= 2*M_PI;
+	if( $aAxisAngle>=7*M_PI/4 || $aAxisAngle <= M_PI/4 ) $dx=0;
+	if( $aAxisAngle>=M_PI/4 && $aAxisAngle <= 3*M_PI/4 ) $dx=($aAxisAngle-M_PI/4)*2/M_PI; 
+	if( $aAxisAngle>=3*M_PI/4 && $aAxisAngle <= 5*M_PI/4 ) $dx=1;
+	if( $aAxisAngle>=5*M_PI/4 && $aAxisAngle <= 7*M_PI/4 ) $dx=(1-($aAxisAngle-M_PI*5/4)*2/M_PI);
+		
+	if( $aAxisAngle>=7*M_PI/4 ) $dy=(($aAxisAngle-M_PI)-3*M_PI/4)*2/M_PI;
+	if( $aAxisAngle<=M_PI/4 ) $dy=(1-$aAxisAngle*2/M_PI);
+	if( $aAxisAngle>=M_PI/4 && $aAxisAngle <= 3*M_PI/4 ) $dy=1;
+	if( $aAxisAngle>=3*M_PI/4 && $aAxisAngle <= 5*M_PI/4 ) $dy=(1-($aAxisAngle-3*M_PI/4)*2/M_PI);
+	if( $aAxisAngle>=5*M_PI/4 && $aAxisAngle <= 7*M_PI/4 ) $dy=0;
+		
+	if( !$this->hide )
+	    $this->img->StrokeText($xt-$dx*$w,$yt-$dy*$h,$title);
+
+    }
+		
+	
+} // Class
+
+
+//===================================================
+// CLASS SpiderGrid
+// Description: Draws grid for the spider graph
+//===================================================
+class SpiderGrid extends Grid {
+//------------
+// CONSTRUCTOR
+    function SpiderGrid() {
+    }
+
+//----------------
+// PRIVATE METHODS	
+    function Stroke(&$img,&$grid) {
+	if( !$this->show ) return;
+	$nbrticks = count($grid[0])/2;
+	$nbrpnts = count($grid);
+	$img->SetColor($this->grid_color);
+	$img->SetLineWeight($this->weight);
+	for($i=0; $i<$nbrticks; ++$i) {
+	    for($j=0; $j<$nbrpnts; ++$j) {
+		$pnts[$j*2]=$grid[$j][$i*2];
+		$pnts[$j*2+1]=$grid[$j][$i*2+1];
+	    }
+	    for($k=0; $k<$nbrpnts; ++$k ){
+		$l=($k+1)%$nbrpnts;
+		if( $this->type == "solid" )
+		    $img->Line($pnts[$k*2],$pnts[$k*2+1],$pnts[$l*2],$pnts[$l*2+1]);
+		elseif( $this->type == "dotted" )
+		    $img->DashedLine($pnts[$k*2],$pnts[$k*2+1],$pnts[$l*2],$pnts[$l*2+1],1,6);
+		elseif( $this->type == "dashed" )
+		    $img->DashedLine($pnts[$k*2],$pnts[$k*2+1],$pnts[$l*2],$pnts[$l*2+1],2,4);
+		elseif( $this->type == "longdashed" )
+		    $img->DashedLine($pnts[$k*2],$pnts[$k*2+1],$pnts[$l*2],$pnts[$l*2+1],8,6);
+	    }
+	    $pnts=array();
+	}
+    }
+} // Class
+
+
+//===================================================
+// CLASS SpiderPlot
+// Description: Plot a spiderplot
+//===================================================
+class SpiderPlot {
+    var $data=array();
+    var $fill=false, $fill_color=array(200,170,180);
+    var $color=array(0,0,0);
+    var $legend="";
+    var $weight=1;
+//---------------
+// CONSTRUCTOR
+    function SpiderPlot($data) {
+	$this->data = $data;
+    }
+
+//---------------
+// PUBLIC METHODS	
+    function Min() {
+	return Min($this->data);
+    }
+	
+    function Max() {
+	return Max($this->data);
+    }
+	
+    function SetLegend($legend) {
+	$this->legend=$legend;
+    }
+	
+    function SetFill($f=true) {
+	$this->fill = $f;
+    }
+	
+    function SetLineWeight($w) {
+	$this->weight=$w;
+    }
+		
+    function SetColor($color,$fill_color=array(160,170,180)) {
+	$this->color = $color;
+	$this->fill_color = $fill_color;
+    }
+	
+    function GetCSIMareas() {
+	JpGraphError::Raise("JpGraph Error: Client side image maps not supported for SpiderPlots.");
+    }
+	
+    function Stroke(&$img, $pos, &$scale, $startangle) {
+	$nbrpnts = count($this->data);
+	$astep=2*M_PI/$nbrpnts;		
+	$a=$startangle;
+		
+	// Rotate each point to the correct axis-angle
+	// TODO: Update for LogScale
+	for($i=0; $i<$nbrpnts; ++$i) {
+	    //$c=$this->data[$i];
+	    $cs=$scale->RelTranslate($this->data[$i]);
+	    $x=round($cs*cos($a)+$scale->scale_abs[0]);
+	    $y=round($pos-$cs*sin($a));
+	    /*
+	      $c=log10($c);
+	      $x=round(($c-$scale->scale[0])*$scale->scale_factor*cos($a)+$scale->scale_abs[0]);
+	      $y=round($pos-($c-$scale->scale[0])*$scale->scale_factor*sin($a));		
+	    */
+	    $pnts[$i*2]=$x;
+	    $pnts[$i*2+1]=$y;
+	    $a += $astep;
+	}
+	if( $this->fill ) {
+	    $img->SetColor($this->fill_color);
+	    $img->FilledPolygon($pnts);
+	}
+	$img->SetLineWeight($this->weight);
+	$img->SetColor($this->color);
+	$img->Polygon($pnts);
+    }
+	
+//---------------
+// PRIVATE METHODS
+    function GetCount() {
+	return count($this->data);
+    }
+	
+    function Legend(&$graph) {
+	if( $this->legend=="" ) return;
+	if( $this->fill )
+	    $graph->legend->Add($this->legend,$this->fill_color);
+	else
+	    $graph->legend->Add($this->legend,$this->color);	
+    }
+	
+} // Class
+
+//===================================================
+// CLASS SpiderGraph
+// Description: Main container for a spider graph
+//===================================================
+class SpiderGraph extends Graph {
+    var $posx;
+    var $posy;
+    var $len;		
+    var $plots=null, $axis_title=null;
+    var $grid,$axis=null;
+//---------------
+// CONSTRUCTOR
+    function SpiderGraph($width=300,$height=200,$cachedName="",$timeout=0,$inline=1) {
+	$this->Graph($width,$height,$cachedName,$timeout,$inline);
+	$this->posx=$width/2;
+	$this->posy=$height/2;
+	$this->len=min($width,$height)*0.35;
+	$this->SetColor(array(255,255,255));
+	$this->SetTickDensity(TICKD_NORMAL);
+	$this->SetScale("lin");
+    }
+
+//---------------
+// PUBLIC METHODS
+    function SupressTickMarks($f=true) {
+	$this->axis->scale->ticks->SupressTickMarks($f);
+    }
+	
+    function SetScale($axtype,$ymin=1,$ymax=1) {
+	if( $axtype != "lin" && $axtype != "log" ) {
+	    JpGraphError::Raise("Illegal scale for spiderplot ($axtype). Must be \"lin\" or \"log\"");
+	}
+	if( $axtype=="lin" ) {
+	    $this->yscale = & new LinearScale(1,1);
+	    $this->yscale->ticks = & new SpiderLinearTicks();
+	    $this->yscale->ticks->SupressMinorTickMarks();
+	}
+	elseif( $axtype=="log" ) {
+	    $this->yscale = & new LogScale(1,1);
+	    $this->yscale->ticks = & new SpiderLogTicks();
+	    //JpGraphError::Raise("JpGraph Error: Logarithmic spider plots are not yet supported");
+	}
+		
+	$this->axis = & new SpiderAxis($this->img,$this->yscale);
+	$this->grid = & new SpiderGrid();		
+    }
+
+    function SetPlotSize($s) {
+	if( $s<0.1 || $s>1 )
+	    JpGraphError::Raise("JpGraph Error: Spider Plot size must be between 0.1 and 1. (Your value=$s)");
+	$this->len=min($this->img->width,$this->img->height)*$s/2;
+    }
+
+    function SetTickDensity($densy=TICKD_NORMAL) {
+	$this->ytick_factor=25;		
+	switch( $densy ) {
+	    case TICKD_DENSE:
+		$this->ytick_factor=12;			
+	    break;
+	    case TICKD_NORMAL:
+		$this->ytick_factor=25;			
+	    break;
+	    case TICKD_SPARSE:
+		$this->ytick_factor=40;			
+	    break;
+	    case TICKD_VERYSPARSE:
+		$this->ytick_factor=70;			
+	    break;		
+	    default:
+		JpGraphError::Raise("Unsupported Tick density: $densy");
+	}
+    }
+
+    function SetCenter($px,$py=0.5) {
+	assert($px > 0 && $py > 0 );
+	$this->posx=$this->img->width*$px;
+	$this->posy=$this->img->height*$py;
+    }
+
+    function SetColor($c) {
+	$this->SetMarginColor($c);
+    }
+			
+    function SetTitles($title) {
+	$this->axis_title = $title;
+    }
+
+    function Add(&$splot) {
+	$this->plots[]=$splot;
+    }
+	
+    function GetPlotsYMinMax() {
+	$min=$this->plots[0]->Min();
+	$max=$this->plots[0]->Max();
+	foreach( $this->plots as $p ) {
+	    $max=max($max,$p->Max());
+	    $min=min($min,$p->Min());
+	}
+	if( $min < 0 ) 
+	    JpGraphError::Raise("JpGraph Error: Minimum data $min (Spider plots only makes sence to use when all data points > 0)");
+	return array($min,$max);
+    }	
+
+    // Stroke the Spider graph
+    function Stroke($aStrokeFileName="") {
+	// Set Y-scale
+	if( !$this->yscale->IsSpecified() && 
+	count($this->plots)>0 ) {
+	    list($min,$max) = $this->GetPlotsYMinMax();
+	    $this->yscale->AutoScale($this->img,0,$max,$this->len/$this->ytick_factor);
+	}
+	// Set start position end length of scale (in absolute pixels)
+	$this->yscale->SetConstants($this->posx,$this->len);
+		
+	// We need as many axis as there are data points
+	$nbrpnts=$this->plots[0]->GetCount();
+		
+	// If we have no titles just number the axis 1,2,3,...
+	if( $this->axis_title==null ) {
+	    for($i=0; $i<$nbrpnts; ++$i ) 
+		$this->axis_title[$i] = $i+1;
+	}
+	elseif(count($this->axis_title)<$nbrpnts) 
+	    JpGraphError::Raise("JpGraph: Number of titles does not match number of points in plot.");
+	for($i=0; $i<count($this->plots); ++$i )
+	    if( $nbrpnts != $this->plots[$i]->GetCount() )
+		JpGraphError::Raise("JpGraph: Each spider plot must have the same number of data points.");
+
+	$this->StrokeFrame();
+	$astep=2*M_PI/$nbrpnts;
+
+	// Prepare legends
+	for($i=0; $i<count($this->plots); ++$i)
+	    $this->plots[$i]->Legend($this);
+	$this->legend->Stroke($this->img);			
+		
+	// Plot points
+	$a=M_PI/2;
+	for($i=0; $i<count($this->plots); ++$i )
+	    $this->plots[$i]->Stroke($this->img, $this->posy, $this->yscale, $a);
+		
+	// Draw axis and grid
+	for( $i=0,$a=M_PI/2; $i<$nbrpnts; ++$i, $a+=$astep ) {
+	    $this->axis->Stroke($this->posy,$a,$grid[$i],$this->axis_title[$i],$i==0);
+	}	
+	$this->grid->Stroke($this->img,$grid);
+	$this->title->Center($this->img->left_margin,$this->img->width-$this->img->right_margin,5);
+	$this->title->Stroke($this->img);
+		
+	// Stroke texts
+	if( $this->texts != null )
+	    foreach( $this->texts as $t) 
+		$t->Stroke($this->img);
+	
+			
+	// Finally output the image
+	$this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,$aStrokeFileName);	
+    }
+} // Class
+
+/* EOF */
+?>
Index: /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_line.php
===================================================================
--- /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_line.php	(revision 3594)
+++ /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_line.php	(revision 3594)
@@ -0,0 +1,285 @@
+<?php
+/*=======================================================================
+// File: 	JPGRAPH_LINE.PHP
+// Description:	Line plot extension for JpGraph
+// Created: 	2001-01-08
+// Author:	Johan Persson (johanp@aditus.nu)
+// Ver:		$Id: jpgraph_line.php 18250 2005-05-07 14:13:43Z ralfbecker $
+//
+// License:	This code is released under GPL 2.0
+// Copyright (C) 2001 Johan Persson
+//========================================================================
+*/
+
+//===================================================
+// CLASS Value
+// Description: 
+//===================================================
+class DisplayValue {    
+    var $show=false,$format="%d",$angle=0;
+    var $ff=FF_FONT1,$fs=FS_NORMAL,$fsize=10;
+    var $color="navy",$margin=3;
+
+    function Show($f=true) {
+	$this->show=$f;
+    }
+
+    function SetColor($color) {
+	$this->color = $color;
+    }
+
+    function SetFont($ff,$fs=FS_NORMAL,$fsize=10) {
+	$this->ff=$ff;
+	$this->fs=$fs;
+	$this->fsize=$fsize;
+    }
+
+    function SetMargin($m) {
+	$this->margin = $m;
+    }
+
+    function SetFormat($format,$angle=0) {
+	$this->format= $format;
+	$this->angle = $angle;
+    }
+}
+
+
+//===================================================
+// CLASS LinePlot
+// Description: 
+//===================================================
+class LinePlot extends Plot{
+    var $filled=false;
+    var $fill_color;
+    var $mark=null;
+    var $step_style=false, $center=false;
+    var $line_style=1;	// Default to solid
+    var $value;
+
+//---------------
+// CONSTRUCTOR
+    function LinePlot(&$datay,$datax=false) {
+	$this->Plot($datay,$datax);
+	$this->mark = new PlotMark();
+	$this->mark->SetColor($this->color);
+	$this->value = new DisplayValue();
+    }
+//---------------
+// PUBLIC METHODS	
+
+    // Set style, filled or open
+    function SetFilled($f=true) {
+	$this->filled=$f;
+    }
+	
+    function SetStyle($s) {
+	$this->line_style=$s;
+    }
+	
+    function SetStepStyle($f=true) {
+	$this->step_style = $f;
+    }
+	
+    function SetColor($c) {
+	parent::SetColor($c);
+	$this->mark->SetColor($this->color);
+    }
+	
+    function SetFillColor($c,$f=true) {
+	$this->fill_color=$c;
+	$this->filled=$f;
+    }
+	
+    function Legend(&$graph) {
+	if( $this->legend!="" ) {
+	    if( $this->filled ) {
+		$graph->legend->Add($this->legend,
+		$this->fill_color,$this->mark);
+	    } else {
+		$graph->legend->Add($this->legend,
+		$this->color,$this->mark,$this->line_style);
+	    }
+	}	
+    }
+	
+    function SetCenter($c=true) {
+	$this->center=$c;
+    }	
+	
+    // Gets called before any axis are stroked
+    function PreStrokeAdjust(&$graph) {
+	if( $this->center ) {
+	    ++$this->numpoints;
+	    $a=0.5; $b=0.5;
+	} else {
+	    $a=0; $b=0;
+	}
+	$graph->xaxis->scale->ticks->SetXLabelOffset($a);
+	$graph->SetTextScaleOff($b);						
+	$graph->xaxis->scale->ticks->SupressMinorTickMarks();
+    }
+	
+    function Stroke(&$img,&$xscale,&$yscale) {
+	$numpoints=count($this->coords[0]);
+	if( isset($this->coords[1]) ) {
+	    if( count($this->coords[1])!=$numpoints )
+		JpGraphError::Raise("JpGraph Error: Number of X and Y points are not equal.<br>
+					Number of X-points:".count($this->coords[1])."<br>
+					Number of Y-points:$numpoints");
+	    else
+		$exist_x = true;
+	}
+	else 
+	    $exist_x = false;
+
+	if( $exist_x )
+	    $xs=$this->coords[1][0];
+	else
+	    $xs=0;
+
+	$img->SetStartPoint($xscale->Translate($xs),
+	                    $yscale->Translate($this->coords[0][0]));
+		
+	if( $this->filled ) {
+	    $cord[] = $xscale->Translate($xs);
+	    $cord[] = $yscale->Translate($yscale->GetMinVal());
+	}
+	$cord[] = $xscale->Translate($xs);
+	$cord[] = $yscale->Translate($this->coords[0][0]);
+	$yt_old = $yscale->Translate($this->coords[0][0]);
+	$img->SetColor($this->color);
+	$img->SetLineWeight($this->weight);	
+	$img->SetLineStyle($this->line_style);
+	for( $pnts=1; $pnts<$numpoints; ++$pnts) {
+	    if( $exist_x ) $x=$this->coords[1][$pnts];
+	    else $x=$pnts;
+	    $xt = $xscale->Translate($x);
+	    $yt = $yscale->Translate($this->coords[0][$pnts]);
+	    $cord[] = $xt;
+	    $cord[] = $yt;
+	    if( $this->step_style ) {
+		$img->StyleLineTo($xt,$yt_old);
+		$img->StyleLineTo($xt,$yt);
+	    }
+	    else {
+		$y=$this->coords[0][$pnts];
+		if( is_numeric($y) || (is_string($y) && $y != "-") ) { 		 			
+		    $tmp1=$this->coords[0][$pnts];
+		    $tmp2=$this->coords[0][$pnts-1]; 		 			
+		    if( is_numeric($tmp1)  && (is_numeric($tmp2) || $tmp2=="-" ) ) { 
+			$img->StyleLineTo($xt,$yt);
+		    } 
+		    else {
+			$img->SetStartPoint($xt,$yt);
+		    }
+		}
+	    }
+	    $yt_old = $yt;
+	    if( $this->value->show) 
+	    {
+		$sval=sprintf($this->value->format,$this->coords[0][$pnts]);
+		$txt = new Text($sval,$xt,$yt-$this->value->margin);
+		$txt->SetFont($this->value->ff,$this->value->fs,$this->value->fsize);
+		$txt->Align("center","bottom");
+		$txt->SetOrientation($this->value->angle);
+		$txt->SetColor($this->value->color);
+		$txt->Stroke($img);
+	    }
+	}	
+	if( $this->filled ) {
+	    $cord[] = $xt;
+	    $cord[] = $yscale->Translate($yscale->GetMinVal());					
+	    $img->SetColor($this->fill_color);	
+	    $img->FilledPolygon($cord);
+	    $img->SetColor($this->color);
+	    $img->Polygon($cord);
+	}
+	$adjust=0;
+	if( $this->filled ) $adjust=2;
+	for($i=$adjust; $i<count($cord)-$adjust; $i+=2) {
+	    if( is_numeric($this->coords[0][($i-$adjust)/2]) )
+		$this->mark->Stroke($img,$cord[$i],$cord[$i+1]);
+	}
+    }
+//---------------
+// PRIVATE METHODS	
+} // Class
+
+
+//===================================================
+// CLASS AccLinePlot
+// Description: 
+//===================================================
+class AccLinePlot extends Plot {
+    var $plots=null,$nbrplots=0,$numpoints=0;
+//---------------
+// CONSTRUCTOR
+    function AccLinePlot($plots) {
+	$this->plots = $plots;
+	$this->nbrplots = count($plots);
+	$this->numpoints = $plots[0]->numpoints;		
+    }
+
+//---------------
+// PUBLIC METHODS	
+    function Legend(&$graph) {
+	foreach( $this->plots as $p )
+	    $p->Legend($graph);
+    }
+	
+    function Max() {
+	$accymax=0;
+	list($xmax,$dummy) = $this->plots[0]->Max();
+	foreach($this->plots as $p) {
+	    list($xm,$ym) = $p->Max();
+	    $xmax = max($xmax,$xm);
+	    $accymax += $ym;
+	}
+	return array($xmax,$accymax);
+    }
+
+    function Min() {
+	list($xmin,$ymin)=$this->plots[0]->Min();
+	foreach( $this->plots as $p ) {
+	    list($xm,$ym)=$p->Min();
+	    $xmin=Min($xmin,$xm);
+	    $ymin=Min($ymin,$ym);
+	}
+	return array($xmin,$ymin);	
+    }
+
+    // To avoid duplicate of line drawing code here we just
+    // change the y-values for each plot and then restore it
+    // after we have made the stroke. We must do this copy since
+    // it wouldn't be possible to create an acc line plot
+    // with the same graphs, i.e AccLinePlot(array($pl,$pl,$pl));
+    // since this method would have a side effect.
+    function Stroke(&$img,&$xscale,&$yscale) {
+	$img->SetLineWeight($this->weight);
+	// Allocate array
+	$coords[$this->nbrplots][$this->numpoints]=0;
+	for($i=0; $i<$this->numpoints; $i++) {
+	    $coords[0][$i]=$this->plots[0]->coords[0][$i]; 
+	    $accy=$coords[0][$i];
+	    for($j=1; $j<$this->nbrplots; ++$j ) {
+		$coords[$j][$i] = $this->plots[$j]->coords[0][$i]+$accy; 
+		$accy = $coords[$j][$i];
+	    }
+	}
+	for($j=$this->nbrplots-1; $j>=0; --$j) {
+	    $p=$this->plots[$j];
+	    for( $i=0; $i<$this->numpoints; ++$i) {
+		$tmp[$i]=$p->coords[0][$i];
+		$p->coords[0][$i]=$coords[$j][$i];
+	    }
+	    $p->Stroke($img,$xscale,$yscale);
+	    for( $i=0; $i<$this->numpoints; ++$i) 
+		$p->coords[0][$i]=$tmp[$i];
+	    $p->coords[0][]=$tmp;
+	}
+    }
+} // Class
+
+/* EOF */
+?>
Index: /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_error.php
===================================================================
--- /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_error.php	(revision 3594)
+++ /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_error.php	(revision 3594)
@@ -0,0 +1,98 @@
+<?php
+/*=======================================================================
+// File: 	JPGRAPH_ERROR.PHP
+// Description:	Error plot extension for JpGraph
+// Created: 	2001-01-08
+// Author:	Johan Persson (johanp@aditus.nu)
+// Ver:		$Id: jpgraph_error.php 18250 2005-05-07 14:13:43Z ralfbecker $
+//
+// License:	This code is released under GPL 2.0
+// Copyright (C) 2001 Johan Persson
+//========================================================================
+*/
+
+//===================================================
+// CLASS ErrorPlot
+// Description: Error plot with min/max value for
+// each datapoint
+//===================================================
+class ErrorPlot extends Plot {
+    var $errwidth=2;
+    var $center=false;
+//---------------
+// CONSTRUCTOR
+    function ErrorPlot(&$datay,$datax=false) {
+	$this->Plot($datay,$datax);
+	$this->numpoints /= 2;
+    }
+//---------------
+// PUBLIC METHODS
+    function SetCenter($c=true) {
+	$this->center=$c;
+    }	
+	
+    // Gets called before any axis are stroked
+    function PreStrokeAdjust(&$graph) {
+	if( $this->center ) {
+	    $a=0.5; $b=0.5;
+	    ++$this->numpoints;			
+	} else {
+	    $a=0; $b=0;
+	}
+	$graph->xaxis->scale->ticks->SetXLabelOffset($a);
+	$graph->SetTextScaleOff($b);						
+	$graph->xaxis->scale->ticks->SupressMinorTickMarks();
+    }
+	
+    // Method description
+    function Stroke(&$img,&$xscale,&$yscale) {
+	$numpoints=count($this->coords[0])/2;
+	$img->SetColor($this->color);
+	$img->SetLineWeight($this->weight);	
+		
+	for( $i=0; $i<$numpoints; ++$i) {
+	    $xt = $xscale->Translate($i);
+	    $yt1 = $yscale->Translate($this->coords[0][$i*2]);
+	    $yt2 = $yscale->Translate($this->coords[0][$i*2+1]);
+	    $img->Line($xt,$yt1,$xt,$yt2);
+	    $img->Line($xt-$this->errwidth,$yt1,$xt+$this->errwidth,$yt1);
+	    $img->Line($xt-$this->errwidth,$yt2,$xt+$this->errwidth,$yt2);
+	}			
+	return true;
+    }
+} // Class
+
+
+//===================================================
+// CLASS ErrorLinePlot
+// Description: Combine a line and error plot
+//===================================================
+class ErrorLinePlot extends ErrorPlot {
+    var $line=null;
+//---------------
+// CONSTRUCTOR
+    function ErrorLinePlot(&$datay,$datax=false) {
+	$this->ErrorPlot($datay);
+	// Calculate line coordinates as the average of the error limits
+	for($i=0; $i<count($datay); $i+=2 ) {
+	    $ly[]=($datay[$i]+$datay[$i+1])/2;
+	}		
+	$this->line=new LinePlot($ly);
+    }
+
+//---------------
+// PUBLIC METHODS
+    function Legend(&$graph) {
+	if( $this->legend != "" )
+	    $graph->legend->Add($this->legend,$this->color);
+	$this->line->Legend($graph);
+    }
+			
+    function Stroke(&$img,&$xscale,&$yscale) {
+	parent::Stroke($img,$xscale,$yscale);
+	$this->line->Stroke($img,$xscale,$yscale);
+    }
+} // Class
+
+/* EOF */
+?>
Index: /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_canvas.php
===================================================================
--- /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_canvas.php	(revision 3594)
+++ /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_canvas.php	(revision 3594)
@@ -0,0 +1,44 @@
+<?php
+/*=======================================================================
+// File: 	JPGRAPH_CANVAS.PHP
+// Description:	Canvas drawing extension for JpGraph
+// Created: 	2001-01-08
+// Author:	Johan Persson (johanp@aditus.nu)
+// Ver:		$Id: jpgraph_canvas.php 18250 2005-05-07 14:13:43Z ralfbecker $
+//
+// License:	This code is released under GPL 2.0
+// Copyright (C) 2001 Johan Persson
+//========================================================================
+*/
+
+//===================================================
+// CLASS CanvasGraph
+// Description: Creates a simple canvas graph which
+// might be used together with the basic Image drawing
+// primitives. Useful to auickoly produce some arbitrary
+// graphic which benefits from all the functionality in the
+// graph liek caching for example. 
+//===================================================
+class CanvasGraph extends Graph {
+//---------------
+// CONSTRUCTOR
+    function CanvasGraph($aWidth=300,$aHeight=200,$aCachedName="",$timeout=0,$inline=1) {
+	$this->Graph($aWidth,$aHeight,$aCachedName,$timeout,$inline);
+    }
+
+//---------------
+// PUBLIC METHODS	
+
+    // Method description
+    function Stroke($aStrokeFileName="") {
+	if( $this->texts != null ) {
+	    for($i=0; $i<count($this->texts); ++$i) {
+		$this->texts[$i]->Stroke($this->img);
+	    }
+	}				
+	// Stream the generated picture					
+	$this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,$aStrokeFileName);	
+    }
+} // Class
+/* EOF */
+?>
Index: /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_gantt.php
===================================================================
--- /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_gantt.php	(revision 3594)
+++ /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_gantt.php	(revision 3594)
@@ -0,0 +1,1444 @@
+<?php
+/*=======================================================================
+// File:	JPGRAPH_GANTT.PHP
+// Description:	JpGraph Gantt plot extension
+// Created: 	2001-11-12
+// Author:	Johan Persson (johanp@aditus.nu)
+// Ver:		$Id: jpgraph_gantt.php 18250 2005-05-07 14:13:43Z ralfbecker $
+//
+// License:	This code is released under GPL 2.0
+//
+//========================================================================
+*/
+
+// Scale Header types
+DEFINE("GANTT_HDAY",1);
+DEFINE("GANTT_HWEEK",2);
+DEFINE("GANTT_HMONTH",4);
+DEFINE("GANTT_HYEAR",8);
+
+// Bar patterns
+DEFINE("GANTT_RDIAG",BAND_RDIAG);	// Right diagonal lines
+DEFINE("GANTT_LDIAG",BAND_LDIAG); // Left diagonal lines
+DEFINE("GANTT_SOLID",BAND_SOLID); // Solid one color
+DEFINE("GANTT_VLINE",BAND_VLINE); // Vertical lines
+DEFINE("GANTT_HLINE",BAND_HLINE);  // Horizontal lines
+DEFINE("GANTT_3DPLANE",BAND_3DPLANE);  // "3D" Plane
+DEFINE("GANTT_HVCROSS",BAND_HVCROSS);  // Vertical/Hor crosses
+DEFINE("GANTT_DIAGCROSS",BAND_DIAGCROSS); // Diagonal crosses
+
+// Conversion constant
+DEFINE("SECPERDAY",3600*24);
+
+// Locales
+DEFINE("LOCALE_EN",0);
+DEFINE("LOCALE_SE",1);
+DEFINE("LOCALE_DE",2);
+
+// Layout of bars
+DEFINE("GANTT_EVEN",1);
+DEFINE("GANTT_FROMTOP",2);
+
+// Styles for week header
+DEFINE("WEEKSTYLE_WNBR",0);
+DEFINE("WEEKSTYLE_FIRSTDAY",1);
+DEFINE("WEEKSTYLE_FIRSTDAY2",1);
+
+// Styles for month header
+DEFINE("MONTHSTYLE_SHORTNAME",0);
+DEFINE("MONTHSTYLE_LONGNAME",1);
+DEFINE("MONTHSTYLE_LONGNAMEYEAR2",2);
+DEFINE("MONTHSTYLE_SHORTNAMEYEAR2",3);
+DEFINE("MONTHSTYLE_LONGNAMEYEAR4",4);
+DEFINE("MONTHSTYLE_SHORTNAMEYEAR4",5);
+
+//===================================================
+// CLASS DateLocale
+// Description: Hold localized text used in dates
+// ToDOo: Rewrite this to use the real local locale
+// instead.
+//===================================================
+class DateLocale {
+    var $iLocale=0; 	// Default to english
+    var $iDayAbb = array(
+	array("M","T","W","T","F","S","S"),			// English locale
+	array("M","T","O","T","F","L","S"),			// Swedish locale
+	array("M","D","M","D","F","S","S"));			// German locale
+    var $iShortDay = array(
+	array("Mon","Tue","Wed","Thu","Fri","Sat","Sun"),
+	array("Mån","Tis","Ons","Tor","Fre","Lör","Sön"),
+	array("Mon","Die","Mit","Don","Fre","Sam","Son"));
+    var $iShortMonth = array(
+	array("Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"),
+	array("Jan","Feb","Mar","Apr","Maj","Jun","Jul","Aug","Sep","Okt","Nov","Dec"),
+	array("Jan","Feb","Mär","Apr","Mai","Jun","Jul","Aug","Sep","Okt","Nov","Dez"));
+    var $iMonthName = array(
+	array("January","February","Mars","April","May","June","July","August","September","October","November","December"),
+	array("Januari","Februari","Mars","April","Maj","Juni","Juli","Augusti","September","Oktober","November","December"),
+	array("Januar","Februar","März","April","Mai","Juni","July","August","September","Oktober","November","Dezember"));
+	
+//---------------
+// CONSTRUCTOR	
+    function DateLocale() {
+	// Empty
+    }
+
+//---------------
+// PUBLIC METHODS	
+    function Set($aLocale) {
+	if( $aLocale < LOCALE_EN || $aLocale > LOCALE_DE )
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Unsupported locale ($aLocale)");
+	$this->iLocale = $aLocale;
+    }
+	
+    function GetDayAbb() {
+	return $this->iDayAbb[$this->iLocale];
+    }
+	
+    function GetShortDay() {
+	return $this->iShortDay[$this->iLocale];
+    }
+
+    function GetShortMonth($aMonth=null) {
+	return $this->iShortMonth[$this->iLocale];
+    }
+	
+    function GetShortMonthName($aNbr) {
+	return $this->iShortMonth[$this->iLocale][$aNbr];
+    }
+
+    function GetLongMonthName($aNbr) {
+	return $this->iMonthName[$this->iLocale][$aNbr];
+    }
+
+    function GetMonth() {
+	return $this->iMonthName[$this->iLocale];
+    }
+}
+
+//===================================================
+// CLASS GanttGraph
+// Description: Main class to handle gantt graphs
+//===================================================
+class GanttGraph extends Graph {
+    var $scale;							// Public accessible
+    var $iObj=array();				// Gantt objects
+    var $iLabelHMarginFactor=0.2;	// 10% margin on each side of the labels
+    var $iLabelVMarginFactor=0.4;	// 40% margin on top and bottom of label
+    var $iLayout=GANTT_FROMTOP;	// Could also be GANTT_EVEN
+
+//---------------
+// CONSTRUCTOR	
+    // Create a new gantt graph
+    function GanttGraph($aWidth=-1,$aHeight=-1,$aCachedName="",$aTimeOut=0,$aInline=true) {
+	Graph::Graph($aWidth,$aHeight,$aCachedName,$aTimeOut,$aInline);		
+	$this->scale = new GanttScale($this->img);
+	$this->img->SetMargin($aWidth/17,$aWidth/17,$aHeight/7,$aHeight/10);
+		
+	$this->scale->ShowHeaders(GANTT_HWEEK|GANTT_HDAY);
+	$this->SetBox();
+    }
+	
+//---------------
+// PUBLIC METHODS	
+    // Set what headers should be shown
+    function ShowHeaders($aFlg) {
+	$this->scale->ShowHeaders($aFlg);
+    }
+	
+    // Specify the fraction of the font height that should be added 
+    // as vertical margin
+    function SetLabelVMarginFactor($aVal) {
+	$this->iLabelVMarginFactor = $aVal;
+    }
+	
+    // Add a new Gantt object
+    function Add(&$aObject) {
+	if( is_array($aObject) ) {
+	    for($i=0; $i<count($aObject); ++$i)
+		$this->iObj[] = $aObject[$i];
+	}
+	else
+	    $this->iObj[] = $aObject;
+    }
+
+    // Override inherit method from Graph and give a warning message
+    function SetScale() {
+	JpGraphError::Raise("<b>JpGraph Error:</b> SetScale() is not meaningfull with Gantt charts.");
+	// Empty
+    }
+
+    // Specify the date range for Gantt graphs (if this is not set it will be
+    // automtically determined from the input data)
+    function SetDateRange($aStart,$aEnd) {
+	$this->scale->SetRange($aStart,$aEnd);
+    }
+	
+    // Get the maximum width of the titles for the bars	
+    function GetMaxLabelWidth() {
+	$m=0;
+	if( $this->iObj != null ) {
+	    $m = $this->iObj[0]->title->GetWidth($this->img);
+	    for($i=1; $i<count($this->iObj); ++$i) {
+		if( $this->iObj[$i]->title->HasTabs() ) {
+		    list($tot,$w) = $this->iObj[$i]->title->GetWidth($this->img,true);
+		    $m=max($m,$tot);
+		}
+		else 
+		    $m=max($m,$this->iObj[$i]->title->GetWidth($this->img));
+	    }
+	}
+	return $m;
+    }
+	
+    // Get the maximum height of the titles for the bars
+    function GetMaxLabelHeight() {
+	$m=0;
+	if( $this->iObj != null ) {
+	    $m = $this->iObj[0]->title->GetHeight($this->img);
+	    for($i=1; $i<count($this->iObj); ++$i) {
+		$m=max($m,$this->iObj[$i]->title->GetHeight($this->img));
+	    }
+	}
+	return $m;
+    }
+
+    function GetMaxBarAbsHeight() {
+	$m=0;
+	if( $this->iObj != null ) {
+	    $m = $this->iObj[0]->GetAbsHeight($this->img);
+	    for($i=1; $i<count($this->iObj); ++$i) {
+		$m=max($m,$this->iObj[$i]->GetAbsHeight($this->img));
+	    }
+	}
+	return $m;		
+    }
+	
+    // Get the maximum used line number (vertical position) for bars
+    function GetBarMaxLineNumber() {
+	$m=0;
+	if( $this->iObj != null ) {
+	    $m = $this->iObj[0]->GetLineNbr();
+	    for($i=1; $i<count($this->iObj); ++$i) {
+		$m=max($m,$this->iObj[$i]->GetLineNbr());
+	    }
+	}
+	return $m;
+    }
+	
+    // Get the minumum and maximum used dates for all bars
+    function GetBarMinMax() {
+	$max=$this->scale->NormalizeDate($this->iObj[0]->GetMaxDate());
+	$min=$this->scale->NormalizeDate($this->iObj[0]->GetMinDate());
+	for($i=1; $i<count($this->iObj); ++$i) {
+	    $max=Max($max,$this->scale->NormalizeDate($this->iObj[$i]->GetMaxDate()));
+	    $min=Min($min,$this->scale->NormalizeDate($this->iObj[$i]->GetMinDate()));
+	}
+	$minDate = date("Y-m-d",$min);
+	$min = strtotime($minDate);
+	$maxDate = date("Y-m-d",$max);
+	$max = strtotime($maxDate);	
+	return array($min,$max);
+    }
+
+    // Stroke the gantt chart
+    function Stroke($aStrokeFileName="") {	
+
+	// Should we autoscale dates?
+	if( !$this->scale->IsRangeSet() ) {
+	    list($min,$max) = $this->GetBarMinMax();
+	    $this->scale->SetRange($min,$max);
+	}
+		
+	if( $this->img->img == null ) {
+	    // The predefined left, right, top, bottom margins.
+	    // Note that the top margin might incease depending on
+	    // the title.
+	    $lm=30;$rm=30;$tm=20;$bm=30;			
+	    if( BRAND_TIMING ) $bm += 10;
+			
+	    // First find out the height			
+	    $n=$this->GetBarMaxLineNumber()+1;
+	    $m=max($this->GetMaxLabelHeight(),$this->GetMaxBarAbsHeight());
+	    $height=$n*((1+$this->iLabelVMarginFactor)*$m);			
+			
+	    // Add the height of the scale titles			
+	    $h=$this->scale->GetHeaderHeight();
+	    $height += $h;
+
+	    // Calculate the top margin needed for title and subtitle
+	    if( $this->title->t != "" ) {
+		$tm += $this->title->GetFontHeight($this->img);
+	    }
+	    if( $this->subtitle->t != "" ) {
+		$tm += $this->subtitle->GetFontHeight($this->img);
+	    }
+
+	    // ...and then take the bottom and top plot margins into account
+	    $height += $tm + $bm + $this->scale->iTopPlotMargin + $this->scale->iBottomPlotMargin;
+			
+	    // Now find the minimum width for the chart required
+	    $fw=$this->scale->day->GetFontWidth($this->img)+4;
+	    $nd=$this->scale->GetNumberOfDays();
+	    if( !$this->scale->IsDisplayDay() ) {
+				// If we don't display the individual days we can shrink the
+				// scale a little bit. This is a little bit pragmatic at the 
+				// moment and should be re-written to take into account
+				// a) What scales exactly are shown and 
+				// b) what format do they use so we know how wide we need to
+				// make each scale text space at minimum.
+		$fw /= 2;
+		if( !$this->scale->IsDisplayWeek() ) {
+		    $fw /= 1.8;
+		}
+	    }
+			
+	    // Now determine the width for the activity titles column
+	    // This is complicated by the fact that the titles may have
+	    // tabs. In that case we also need to calculate the individual
+	    // tab positions based on the width of the individual columns
+			
+	    $titlewidth = $this->GetMaxLabelWidth();
+						
+	    // Now get the total width taking 
+	    // titlewidth, left and rigt margin, dayfont size 
+	    // into account
+	    $width = $titlewidth + $nd*$fw + $lm+$rm;
+						
+	    $this->img->CreateImgCanvas($width,$height);			
+	    $this->img->SetMargin($lm,$rm,$tm,$bm);
+	}
+		
+	// Should we start from the top or just spread the bars out even over the
+	// available height
+	$this->scale->SetVertLayout($this->iLayout);			
+	if( $this->iLayout == GANTT_FROMTOP ) {
+	    $maxheight=max($this->GetMaxLabelHeight(),$this->GetMaxBarAbsHeight());
+	    $this->scale->SetVertSpacing($maxheight*(1+$this->iLabelVMarginFactor));
+	}
+	// If it hasn't been set find out the maximum line number
+	if( $this->scale->iVertLines == -1 ) 
+	    $this->scale->iVertLines = $this->GetBarMaxLineNumber()+1; 	
+		
+	$maxwidth=max($this->GetMaxLabelWidth(),$this->scale->tableTitle->GetWidth($this->img));
+	$this->scale->SetLabelWidth($maxwidth*(1+$this->iLabelHMarginFactor));
+	$this->StrokePlotArea();
+	$this->scale->Stroke();
+	$this->StrokePlotBox();
+		
+	for($i=0; $i<count($this->iObj); ++$i) {
+	    $this->iObj[$i]->SetLabelLeftMargin(round($maxwidth*$this->iLabelHMarginFactor/2));
+	    $this->iObj[$i]->Stroke($this->img,$this->scale);
+	}
+		
+	$this->StrokeTitles();
+	$this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,$aStrokeFileName);				
+    }
+}
+
+//===================================================
+// CLASS TextProperty
+// Description: Holds properties for a text
+//===================================================
+class TextProperty {
+    var $iFFamily=FF_FONT1,$iFStyle=FS_NORMAL,$iFSize=10;
+    var $iColor="black";
+    var $iShow=true;
+    var $iText="";
+    var $iHAlign="left",$iVAlign="bottom";
+	
+//---------------
+// CONSTRUCTOR	
+    function TextProperty($aTxt="") {
+	$this->iText = $aTxt;
+    }		
+	
+//---------------
+// PUBLIC METHODS	
+    function Set($aTxt) {
+	$this->iText = $aTxt;
+    }
+	
+    // Set text color
+    function SetColor($aColor) {
+	$this->iColor = $aColor;
+    }
+	
+    function HasTabs() {
+	return substr_count($this->iText,"\t") > 0;
+    }
+	
+    // Get number of tabs in string
+    function GetNbrTabs() {
+	substr_count($this->iText,"\t");
+    }
+	
+    // Set alignment
+    function Align($aHAlign,$aVAlign="bottom") {
+	$this->iHAlign=$aHAlign;
+	$this->iVAlign=$aVAlign;
+    }
+	
+    // Specify font
+    function SetFont($aFFamily,$aFStyle=FS_NORMAL,$aFSize=10) {
+	$this->iFFamily = $aFFamily;
+	$this->iFStyle	 = $aFStyle;
+	$this->iFSize	 = $aFSize;
+    }
+	
+    // Get width of text. If text contains several columns separated by
+    // tabs then return both the total width as well as an array with a 
+    // width for each column.
+    function GetWidth($aImg,$aUseTabs=false,$aTabExtraMargin=1.1) {
+	if( strlen($this->iText)== 0 ) return;
+	$tmp = split("\t",$this->iText);
+	$aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
+	if( count($tmp) <= 1 || !$aUseTabs ) {
+	    return $aImg->GetTextWidth($this->iText);
+	}
+	else {
+	    $tot=0;
+	    for($i=0; $i<count($tmp); ++$i) {
+		$res[$i] = $aImg->GetTextWidth($tmp[$i]);
+		$tot += $res[$i]*$aTabExtraMargin;
+	    }
+	    return array($tot,$res);
+	}
+    }
+	
+    // Get total height of text
+    function GetHeight($aImg) {
+	$aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
+	return $aImg->GetFontHeight();
+    }
+	
+    // Unhide/hide the text	
+    function Show($aShow) {
+	$this->iShow=$aShow;
+    }
+	
+    // Stroke text at (x,y) coordinates. If the text contains tabs then the
+    // x parameter should be an array of positions to be used for each successive
+    // tab mark. If no array is supplied then the tabs will be ignored.
+    function Stroke($aImg,$aX,$aY) {
+	if( $this->iShow ) {
+	    $aImg->SetColor($this->iColor);
+	    $aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
+	    $aImg->SetTextAlign($this->iHAlign,$this->iVAlign);			
+	    if( $this->GetNbrTabs() <= 1 || !is_array($aX) ) {
+				// Get rid of any "\t" characters and stroke string
+		$aImg->StrokeText($aX,$aY,str_replace("\t"," ",$this->iText));
+	    }
+	    else {
+		$tmp = split("\t",$this->iText);
+		$n = min(count($tmp),count($aX));
+		for($i=0; $i<$n; ++$i) {
+		    $aImg->StrokeText($aX[$i],$aY,$tmp[$i]);
+		}	
+	    }
+	}
+    }
+}
+
+//===================================================
+// CLASS HeaderProperty
+// Description: Data encapsulating class to hold property 
+// for each type of the scale headers
+//===================================================
+class HeaderProperty {
+    var $iTitleVertMargin=3,$iFFamily=FF_FONT0,$iFStyle=FS_NORMAL,$iFSize=8;
+    var $iFrameColor="black",$iFrameWeight=1;
+    var $iShowLabels=true,$iShowGrid=true;
+    var $iBackgroundColor="white";
+    var $iWeekendBackgroundColor="lightgray",$iSundayTextColor="red"; // these are only used with day scale
+    var $iTextColor="black";
+    var $iLabelFormStr="%d";
+    var $grid,$iStyle=0;
+
+//---------------
+// CONSTRUCTOR	
+    function HeaderProperty() {
+	$this->grid = new LineProperty();
+    }
+
+//---------------
+// PUBLIC METHODS		
+    function Show($aShow) {
+	$this->iShowLabels = $aShow;
+    }
+	
+    function SetFont($aFFamily,$aFStyle=FS_NORMAL,$aFSize=10) {
+	$this->iFFamily = $aFFamily;
+	$this->iFStyle	 = $aFStyle;
+	$this->iFSize	 = $aFSize;
+    }
+
+    function SetFontColor($aColor) {
+	$this->iTextColor = $aColor;
+    }
+	
+    function GetFontHeight($aImg) {
+	$aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
+	return $aImg->GetFontHeight();
+    }
+
+    function GetFontWidth($aImg) {
+	$aImg->SetFont($this->iFFamily,$this->iFStyle,$this->iFSize);
+	return $aImg->GetFontWidth();
+    }
+	
+    function SetStyle($aStyle) {
+	$this->iStyle = $aStyle;
+    }
+	
+    function SetBackgroundColor($aColor) {
+	$this->iBackgroundColor=$aColor;
+    }
+
+    function SetFrameWeight($aWeight) {
+	$this->iFrameWeight=$aWeight;
+    }
+
+    function SetFrameColor($aColor) {
+	$this->iFrameColor=$aColor;
+    }
+	
+    // Only used by day scale
+    function SetWeekendColor($aColor) {
+	$this->iWeekendBackgroundColor=$aColor;
+    }
+	
+    // Only used by day scale
+    function SetSundayFontColor($aColor) {
+	$this->iSundayTextColor=$aColor;
+    }
+	
+    function SetTitleVertMargin($aMargin) {
+	$this->iTitleVertMargin=$aMargin;
+    }
+	
+    function SetLabelFormatString($aStr) {
+	$this->iLabelFormStr=$aStr;
+    }
+}
+
+//===================================================
+// CLASS GanttScale
+// Description: Responsible for calculating and showing
+// the scale in a gantt chart. This includes providing methods for
+// converting dates to position in the chart as well as stroking the
+// date headers (days, week, etc).
+//===================================================
+class GanttScale {
+    var $day,$week,$month,$year;
+    var $divider,$dividerh,$tableTitle;
+    var $iStartDate=-1,$iEndDate=-1;	
+    // Number of gantt bar position (n.b not necessariliy the same as the number of bars)
+    // we could have on bar in position 1, and one bar in position 5 then there are two
+    // bars but the number of bar positions is 5
+    var $iVertLines=-1;	
+    // The width of the labels (defaults to the widest of all labels)
+    var $iLabelWidth;	
+    // Out image to stroke the scale to
+    var $iImg;	
+    var $iTableHeaderBackgroundColor="white",$iTableHeaderFrameColor="black";
+    var $iTableHeaderFrameWeight=1;
+    var $iAvailableHeight=-1,$iVertSpacing=-1,$iVertHeaderSize=-1;
+    var $iDateLocale;
+    var $iVertLayout=GANTT_EVEN;
+    var $iTopPlotMargin=10,$iBottomPlotMargin=15;
+    var $iUsePlotWeekendBackground=true;
+	
+//---------------
+// CONSTRUCTOR	
+    function GanttScale(&$aImg) {
+	$this->iImg = &$aImg;		
+	$this->iDateLocale = new DateLocale();
+	$this->day = new HeaderProperty();
+	$this->day->grid->SetColor("gray");
+
+	$this->week = new HeaderProperty();
+	$this->week->SetLabelFormatString("w%d");
+	$this->week->SetFont(FF_FONT1);
+
+	$this->month = new HeaderProperty();
+	$this->month->SetFont(FF_FONT1,FS_BOLD);
+
+	$this->year = new HeaderProperty();
+	$this->year->SetFont(FF_FONT1,FS_BOLD);		
+		
+	$this->divider=new LineProperty();
+	$this->dividerh=new LineProperty();		
+	$this->tableTitle=new TextProperty();
+    }
+	
+//---------------
+// PUBLIC METHODS	
+    // Specify what headers should be visible
+    function ShowHeaders($aFlg) {
+	$this->day->Show($aFlg & GANTT_HDAY);
+	$this->week->Show($aFlg & GANTT_HWEEK);
+	$this->month->Show($aFlg & GANTT_HMONTH);
+	$this->year->Show($aFlg & GANTT_HYEAR);
+
+	// Make some default settings of gridlines whihc makes sense
+	if( $aFlg & GANTT_HWEEK ) {
+	    $this->month->grid->Show(false);
+	    $this->year->grid->Show(false);
+	}
+    }
+	
+    // Should the weekend background stretch all the way down in the plotarea
+    function UseWeekendBackground($aShow) {
+	$this->iUsePlotWeekendBackground = $aShow;
+    }
+	
+    // Have a range been specified?
+    function IsRangeSet() {
+	return $this->iStartDate!=-1 && $this->iEndDate!=-1;
+    }
+	
+    // Should the layout be from top or even?
+    function SetVertLayout($aLayout) {
+	$this->iVertLayout = $aLayout;
+    }
+	
+    // Which locale should be used?
+    function SetDateLocale($aLocale) {
+	$this->iDateLocale->Set($aLocale);
+    }
+	
+    // Number of days we are showing
+    function GetNumberOfDays() {
+	return round(($this->iEndDate-$this->iStartDate)/SECPERDAY)+1;
+    }
+	
+    // The widthj of the actual plot area
+    function GetPlotWidth() {
+	$img=$this->iImg;
+	return $img->width - $img->left_margin - $img->right_margin;
+    }
+
+    // Specify the width of the titles(labels) for the activities
+    // (This is by default set to the minimum width enought for the
+    // widest title)
+    function SetLabelWidth($aLabelWidth) {
+	$this->iLabelWidth=$aLabelWidth;
+    }
+	
+    // Do we show day scale?
+    function IsDisplayDay() {
+	return $this->day->iShowLabels;
+    }
+	
+    // Do we show week scale?
+    function IsDisplayWeek() {
+	return $this->week->iShowLabels;
+    }
+	
+    // Do we show month scale?
+    function IsDisplayMonth() {
+	return $this->month->iShowLabels;
+    }
+	
+    // Do we show year scale?
+    function IsDisplayYear() {
+	return $this->year->iShowLabels;
+    }
+
+    // Specify spacing (in percent of bar height) between activity bars
+    function SetVertSpacing($aSpacing) {
+	$this->iVertSpacing = $aSpacing;
+    }
+
+    // Specify scale min and max date either as timestamp or as date strings
+    // Always round to the nearest week boundary
+    function SetRange($aMin,$aMax) {
+	$this->iStartDate = $this->NormalizeDate($aMin);
+	$this->iEndDate = $this->NormalizeDate($aMax);
+		
+	// Get day in week Sun=0
+	$ds=strftime("%w",$this->iStartDate);
+	$de=strftime("%w",$this->iEndDate);
+		
+	if( $ds==0 ) $ds=7;
+	if( $de==0 ) $de=7;
+	
+	// We want to start on Monday
+	$this->iStartDate -= SECPERDAY*($ds-1);
+		
+	// We want to end on a Sunday
+	$this->iEndDate += SECPERDAY*(7-$de);
+    }
+
+    // Specify background for the table title area (upper left corner of the table)	
+    function SetTableTitleBackground($aColor) {
+	$this->iTableHeaderBackgroundColor = $aColor;
+    }
+
+///////////////////////////////////////
+// PRIVATE Methods
+	
+    // Determine the height of all the scale headers combined
+    function GetHeaderHeight() {
+	$img=$this->iImg;
+	$height=1;
+	if( $this->day->iShowLabels ) {
+	    $height += $this->day->GetFontHeight($img);
+	    $height += $this->day->iTitleVertMargin;
+	}
+	if( $this->week->iShowLabels ) {
+	    $height += $this->week->GetFontHeight($img);
+	    $height += $this->week->iTitleVertMargin;
+	}
+	if( $this->month->iShowLabels ) {
+	    $height += $this->month->GetFontHeight($img);
+	    $height += $this->month->iTitleVertMargin;
+	}
+	if( $this->year->iShowLabels ) {
+	    $height += $this->year->GetFontHeight($img);
+	    $height += $this->year->iTitleVertMargin;
+	}
+	return $height;
+    }
+	
+    // Get width (in pisels) for a single day
+    function GetDayWidth() {
+	return ($this->GetPlotWidth()-$this->iLabelWidth+1)/$this->GetNumberOfDays();	
+    }
+
+    // Nuber of days in a year
+    function GetNumDaysInYear($aYear) {
+	if( $this->IsLeap($aYear) )
+	    return 366;
+	else
+	    return 365;
+    }
+	
+    // Get day number in year	
+    function GetDayNbrInYear($aDate) {
+	return 0+strftime("%j",$aDate);
+    }
+	
+    // Get week number 
+    function GetWeekNbr($aDate) {
+	// We can't use the internal strftime() since it gets the weeknumber
+	// wrong since it doesn't follow ISO.
+	// Even worse is that this works differently if we are on a Windows
+	// or UNIX box (it even differs between UNIX boxes how strftime()
+	// is natively implemented)
+	//
+	// Credit to Nicolas Hoizey <nhoizey@phpheaven.net> for this elegant
+	// version of Week Nbr calculation. 
+		
+	$day = $this->NormalizeDate($aDate);
+		
+	/*-------------------------------------------------------------------------
+	  According to ISO-8601 :
+	  "Week 01 of a year is per definition the first week that has the Thursday in this year,
+	  which is equivalent to the week that contains the fourth day of January.
+	  In other words, the first week of a new year is the week that has the majority of its
+	  days in the new year."
+		  
+	  Be carefull, with PHP, -3 % 7 = -3, instead of 4 !!!
+		  
+	  day of year             = date("z", $day) + 1
+	  offset to thursday      = 3 - (date("w", $day) + 6) % 7
+	  first thursday of year  = 1 + (11 - date("w", mktime(0, 0, 0, 1, 1, date("Y", $day)))) % 7
+	  week number             = (thursday's day of year - first thursday's day of year) / 7 + 1
+	  ---------------------------------------------------------------------------*/
+		 
+	$thursday = $day + 60 * 60 * 24 * (3 - (date("w", $day) + 6) % 7);              // take week's thursday
+	$week = 1 + (date("z", $thursday) - (11 - date("w", mktime(0, 0, 0, 1, 1, date("Y", $thursday)))) % 7) / 7;
+		  
+	return $week;
+    }
+	
+    // Is year a leap year?
+    function IsLeap($aYear) {
+	// Is the year a leap year?
+	//$year = 0+date("Y",$aDate);
+	if( $aYear % 4 == 0)
+	    if( !($aYear % 100 == 0) || ($aYear % 400 == 0) )
+		return true;
+	return false;
+    }
+
+    // Get current year
+    function GetYear($aDate) {
+	return 0+Date("Y",$aDate);
+    }
+	
+    // Return number of days in a year
+    function GetNumDaysInMonth($aMonth,$aYear) {
+	$days=array(31,28,31,30,31,30,31,31,30,31,30,31);
+	$daysl=array(31,29,31,30,31,30,31,31,30,31,30,31);
+	if( $this->IsLeap($aYear))
+	    return $daysl[$aMonth];
+	else
+	    return $days[$aMonth];
+    }
+	
+    // Get day in month
+    function GetMonthDayNbr($aDate) {
+	return 0+strftime("%d",$aDate);
+    }
+
+    // Get day in year
+    function GetYearDayNbr($aDate) {
+	return 0+strftime("%j",$aDate);
+    }
+	
+    // Get month number
+    function GetMonthNbr($aDate) {
+	return 0+strftime("%m",$aDate);
+    }
+	
+    // Translate a date to screen coordinates	(horizontal scale)
+    function TranslateDate($aDate) {
+	$aDate = $this->NormalizeDate($aDate);
+	$img=$this->iImg;		
+	//print date('d-m-Y H:i:s',$aDate).'--'.date('d-m-Y H:i:s',$this->iEndDate)."<br>";
+	if( $aDate < $this->iStartDate || $aDate > $this->iEndDate )
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Date is outside specified scale range.");
+	return ($aDate-$this->iStartDate)/SECPERDAY*$this->GetDayWidth()+$img->left_margin+$this->iLabelWidth;;
+    }
+
+    // Get screen coordinatesz for the vertical position for a bar		
+    function TranslateVertPos($aPos) {
+	$img=$this->iImg;
+	$ph=$this->iAvailableHeight;
+	if( $aPos > $this->iVertLines ) 
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Illegal vertical position $aPos");
+	if( $this->iVertLayout == GANTT_EVEN ) {
+	    // Position the top bar at 1 vert spacing from the scale
+	    return round($img->top_margin + $this->iVertHeaderSize +  ($aPos+1)*$this->iVertSpacing);
+	}
+	else {
+	    // position the top bar at 1/2 a vert spacing from the scale
+	    return round($img->top_margin + $this->iVertHeaderSize  + $this->iTopPlotMargin + ($aPos+1)*$this->iVertSpacing);		
+	}
+    }
+	
+    // What is the vertical spacing?
+    function GetVertSpacing() {
+	return $this->iVertSpacing;
+    }
+					
+    // Convert a date to timestamp
+    function NormalizeDate($aDate) {
+	if( is_string($aDate) )
+	    return strtotime($aDate);
+	elseif( is_int($aDate) || is_float($aDate) )
+	    return $aDate;
+	else
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Unknown date format in GanttScale ($aDate).");
+    }
+
+    // Stroke the day scale (including gridlines)			
+    function StrokeDays($aYCoord) {
+	$wdays=$this->iDateLocale->GetDayAbb();	
+	$img=$this->iImg;	
+	$daywidth=$this->GetDayWidth();
+	$xt=$img->left_margin+$this->iLabelWidth;
+	$yt=$aYCoord+$img->top_margin;		
+	if( $this->day->iShowLabels ) {
+	    $img->SetFont($this->day->iFFamily,$this->day->iFStyle,$this->day->iFSize);
+	    $xb=$img->width-$img->right_margin;
+	    $yb=$yt + $img->GetFontHeight() + $this->day->iTitleVertMargin + $this->day->iFrameWeight;
+	    $img->SetColor($this->day->iBackgroundColor);
+	    $img->FilledRectangle($xt,$yt,$xb,$yb);
+
+	    $img->SetColor($this->day->grid->iColor);
+	    $x = $xt;
+	    $img->SetTextAlign("center");
+	    for($i=0; $i<$this->GetNumberOfDays(); ++$i, $x+=$daywidth) {
+		$day=$i%7;
+		if( $day==5 ) {
+		    $img->PushColor($this->day->iWeekendBackgroundColor);
+		    if( $this->iUsePlotWeekendBackground )
+			$img->FilledRectangle($x,$yt+$this->day->iFrameWeight,$x+2*$daywidth,$img->height-$img->bottom_margin);						
+		    else
+			$img->FilledRectangle($x,$yt+$this->day->iFrameWeight,$x+2*$daywidth,$yb-$this->day->iFrameWeight);
+		    $img->PopColor();
+		}
+		if( $day==6 ) 
+		    $img->PushColor($this->day->iSundayTextColor);
+		else
+		    $img->PushColor($this->day->iTextColor);
+		$img->StrokeText(round($x+$daywidth/2+1),
+				 round($yb-$this->day->iTitleVertMargin),
+				 $wdays[$i%7]);
+		$img->PopColor();						
+		$img->Line($x,$yt,$x,$yb);
+		$this->day->grid->Stroke($img,$x,$yb,$x,$img->height-$img->bottom_margin);
+	    }			
+	    $img->SetColor($this->day->iFrameColor);
+	    $img->SetLineWeight($this->day->iFrameWeight);
+	    $img->Rectangle($xt,$yt,$xb,$yb);
+	    return $yb - $img->top_margin;
+	}
+	return $aYCoord;
+    }
+	
+    // Stroke week header and grid
+    function StrokeWeeks($aYCoord) {
+	$wdays=$this->iDateLocale->GetDayAbb();	
+	$img=$this->iImg;	
+	$weekwidth=$this->GetDayWidth()*7;
+	$xt=$img->left_margin+$this->iLabelWidth;
+	$yt=$aYCoord+$img->top_margin;		
+	$img->SetFont($this->week->iFFamily,$this->week->iFStyle,$this->week->iFSize);
+	$xb=$img->width-$img->right_margin;
+	$yb=$yt + $img->GetFontHeight() + $this->week->iTitleVertMargin + $this->week->iFrameWeight;
+		
+	$week = $this->iStartDate;
+	$weeknbr=$this->GetWeekNbr($week);
+	if( $this->week->iShowLabels ) {
+	    $img->SetColor($this->week->iBackgroundColor);
+	    $img->FilledRectangle($xt,$yt,$xb,$yb);
+	    $img->SetColor($this->week->grid->iColor);
+	    $x = $xt;
+	    if( $this->week->iStyle==WEEKSTYLE_WNBR ) {
+		$img->SetTextAlign("center");
+		$txtOffset = $weekwidth/2+1;
+	    }
+	    elseif( $this->week->iStyle==WEEKSTYLE_FIRSTDAY || $this->week->iStyle==WEEKSTYLE_FIRSTDAY2 ) {
+		$img->SetTextAlign("left");
+		$txtOffset = 2;
+	    }
+	    else
+		JpGraphError::Raise("<b>JpGraph Error:</b>Unknown formatting style for week.");
+				
+	    for($i=0; $i<$this->GetNumberOfDays()/7; ++$i, $x+=$weekwidth) {
+		$img->PushColor($this->week->iTextColor);
+				
+		if( $this->week->iStyle==WEEKSTYLE_WNBR )
+		    $txt = sprintf($this->week->iLabelFormStr,$weeknbr);
+		elseif( $this->week->iStyle==WEEKSTYLE_FIRSTDAY )
+		    $txt = date("j/n",$week);
+		elseif( $this->week->iStyle==WEEKSTYLE_FIRSTDAY2 ) {
+		    $monthnbr = date("n",$week)-1;
+		    $shortmonth = $this->iDateLocale->GetShortMonthName($monthnbr);
+		    $txt = Date("j",$week)." ".$shortmonth;
+		}
+				
+		$img->StrokeText(round($x+$txtOffset),round($yb-$this->week->iTitleVertMargin),$txt);
+				
+		$week += 7*SECPERDAY;
+		$weeknbr = $this->GetWeekNbr($week);
+		$img->PopColor();						
+		$img->Line($x,$yt,$x,$yb);
+		$this->week->grid->Stroke($img,$x,$yb,$x,$img->height-$img->bottom_margin);
+	    }			
+	    $img->SetColor($this->week->iFrameColor);
+	    $img->SetLineWeight($this->week->iFrameWeight);
+	    $img->Rectangle($xt,$yt,$xb,$yb);
+	    return $yb-$img->top_margin;
+	}
+	return $aYCoord;
+    }	
+	
+    // Format the mont scale header string
+    function GetMonthLabel($aMonthNbr,$year) {
+	$sn = $this->iDateLocale->GetShortMonthName($aMonthNbr);
+	$ln = $this->iDateLocale->GetLongMonthName($aMonthNbr);
+	switch($this->month->iStyle) {
+	    case MONTHSTYLE_SHORTNAME:
+		$m=$sn;
+	    break;
+	    case MONTHSTYLE_LONGNAME:
+		$m=$ln;
+	    break;
+	    case MONTHSTYLE_SHORTNAMEYEAR2:
+		$m=$sn." '".substr("".$year,2);
+	    break;
+	    case MONTHSTYLE_SHORTNAMEYEAR4:
+		$m=$sn." ".$year;
+	    break;
+	    case MONTHSTYLE_LONGNAMEYEAR2:
+		$m=$ln." '".substr("".$year,2);
+	    break;
+	    case MONTHSTYLE_LONGNAMEYEAR4:
+		$m=$ln." ".$year;
+	    break;
+	}
+	return $m;
+    }
+	
+    // Stroke month scale and gridlines
+    function StrokeMonths($aYCoord) {
+	if( $this->month->iShowLabels ) {
+	    $monthnbr = $this->GetMonthNbr($this->iStartDate)-1; 
+	    $img=$this->iImg;	
+		
+	    $xt=$img->left_margin+$this->iLabelWidth;
+	    $yt=$aYCoord+$img->top_margin;		
+	    $img->SetFont($this->month->iFFamily,$this->month->iFStyle,$this->month->iFSize);
+	    $xb=$img->width-$img->right_margin;
+	    $yb=$yt + $img->GetFontHeight() + $this->month->iTitleVertMargin + $this->month->iFrameWeight;
+			
+	    $img->SetColor($this->month->iBackgroundColor);
+	    $img->FilledRectangle($xt,$yt,$xb,$yb);
+
+	    $img->SetLineWeight($this->month->grid->iWeight);
+	    $img->SetColor($this->month->iTextColor);
+	    $year = 0+strftime("%Y",$this->iStartDate);
+	    $img->SetTextAlign("center");
+	    $monthwidth=$this->GetDayWidth()*($this->GetNumDaysInMonth($monthnbr,$year)-$this->GetMonthDayNbr($this->iStartDate)+1);
+	    // Is it enough space to stroke the first month?
+	    $monthName = $this->GetMonthLabel($monthnbr,$year);
+	    if( $monthwidth >= 1.2*$img->GetTextWidth($monthName) ) {
+		$img->SetColor($this->month->iTextColor);				
+		$img->StrokeText(round($xt+$monthwidth/2+1),
+				 round($yb-$this->month->iTitleVertMargin),
+				 $monthName);
+	    }
+	    $x = $xt + $monthwidth;
+	    while( $x < $xb ) {
+		$img->SetColor($this->month->grid->iColor);				
+		$img->Line($x,$yt,$x,$yb);
+		$this->month->grid->Stroke($img,$x,$yb,$x,$img->height-$img->bottom_margin);
+		$monthnbr++;
+		if( $monthnbr==12 ) {
+		    $monthnbr=0;
+		    $year++;
+		}
+		$monthName = $this->GetMonthLabel($monthnbr,$year);
+		$monthwidth=$this->GetDayWidth()*$this->GetNumDaysInMonth($monthnbr,$year);				
+		if( $x + $monthwidth < $xb )
+		    $w = $monthwidth;
+		else
+		    $w = $xb-$x;
+		if( $w >= 1.2*$img->GetTextWidth($monthName) ) {
+		    $img->SetColor($this->month->iTextColor);				
+		    $img->StrokeText(round($x+$w/2+1),
+				     round($yb-$this->month->iTitleVertMargin),$monthName);
+		}
+		$x += $monthwidth;
+	    }	
+	    $img->SetColor($this->month->iFrameColor);
+	    $img->SetLineWeight($this->month->iFrameWeight);
+	    $img->Rectangle($xt,$yt,$xb,$yb);			
+	    return $yb-$img->top_margin;
+	}
+	return $aYCoord;
+    }
+
+    // Stroke year scale and gridlines
+    function StrokeYears($aYCoord) {
+	if( $this->year->iShowLabels ) {
+	    $year = $this->GetYear($this->iStartDate); 
+	    $img=$this->iImg;	
+		
+	    $xt=$img->left_margin+$this->iLabelWidth;
+	    $yt=$aYCoord+$img->top_margin;		
+	    $img->SetFont($this->year->iFFamily,$this->year->iFStyle,$this->year->iFSize);
+	    $xb=$img->width-$img->right_margin;
+	    $yb=$yt + $img->GetFontHeight() + $this->year->iTitleVertMargin + $this->year->iFrameWeight;
+			
+	    $img->SetColor($this->year->iBackgroundColor);
+	    $img->FilledRectangle($xt,$yt,$xb,$yb);
+	    $img->SetLineWeight($this->year->grid->iWeight);
+	    $img->SetTextAlign("center");
+	    if( $year == $this->GetYear($this->iEndDate) )
+		$yearwidth=$this->GetDayWidth()*($this->GetYearDayNbr($this->iEndDate)-$this->GetYearDayNbr($this->iStartDate)+1);
+	    else
+		$yearwidth=$this->GetDayWidth()*($this->GetNumDaysInYear($year)-$this->GetYearDayNbr($this->iStartDate)+1);
+			
+	    // The space for a year must be at least 20% bigger than the actual text 
+	    // so we allow 10% margin on each side
+	    if( $yearwidth >= 1.20*$img->GetTextWidth("".$year) ) {
+		$img->SetColor($this->year->iTextColor);				
+		$img->StrokeText(round($xt+$yearwidth/2+1),
+				 round($yb-$this->year->iTitleVertMargin),
+				 $year);
+	    }
+	    $x = $xt + $yearwidth;
+	    while( $x < $xb ) {
+		$img->SetColor($this->year->grid->iColor);				
+		$img->Line($x,$yt,$x,$yb);
+		$this->year->grid->Stroke($img,$x,$yb,$x,$img->height-$img->bottom_margin);
+		$year += 1;
+		$yearwidth=$this->GetDayWidth()*$this->GetNumDaysInYear($year);				
+		if( $x + $yearwidth < $xb )
+		    $w = $yearwidth;
+		else
+		    $w = $xb-$x;
+		if( $w >= 1.2*$img->GetTextWidth("".$year) ) {
+		    $img->SetColor($this->year->iTextColor);
+		    $img->StrokeText(round($x+$w/2+1),
+				     round($yb-$this->year->iTitleVertMargin),
+				     $year);
+		}
+		$x += $yearwidth;
+	    }
+	    $img->SetColor($this->year->iFrameColor);
+	    $img->SetLineWeight($this->year->iFrameWeight);
+	    $img->Rectangle($xt,$yt,$xb,$yb);			
+	    return $yb-$img->top_margin;
+	}
+	return $aYCoord;
+    }
+	
+    // Stroke table title (upper left corner)
+    function StrokeTableHeaders($aYBottom) {
+	$img=$this->iImg;
+	$xt=$img->left_margin;
+	$yt=$img->top_margin;
+	$xb=$xt+$this->iLabelWidth;
+	$yb=$aYBottom+$img->top_margin;
+		
+	$img->SetColor($this->iTableHeaderBackgroundColor);
+	$img->FilledRectangle($xt,$yt,$xb,$yb);
+	$this->tableTitle->Align("center","center");
+	$this->tableTitle->Stroke($img,$xt+($xb-$xt)/2+1,$yt+($yb-$yt)/2);		
+	$img->SetColor($this->iTableHeaderFrameColor);
+	$img->SetLineWeight($this->iTableHeaderFrameWeight);
+	$img->Rectangle($xt,$yt,$xb,$yb);
+		
+	// Draw the vertical dividing line
+	$this->divider->Stroke($img,$xb,$yt,$xb,$img->height-$img->bottom_margin);
+		
+	// Draw the horizontal dividing line		
+	$this->dividerh->Stroke($img,$xt,$yb,$img->width-$img->right_margin,$yb);		
+    }
+
+    // Main entry point to stroke scale
+    function Stroke() {
+	if( !$this->IsRangeSet() )
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Gantt scale has not been specified.");
+	$img=$this->iImg;
+		
+	// Stroke all headers. Aa argument we supply the offset from the
+	// top which depends on any previous headers
+	$offy=$this->StrokeYears(0);
+	$offm=$this->StrokeMonths($offy);
+	$offw=$this->StrokeWeeks($offm);
+	$offd=$this->StrokeDays($offw);
+
+	// We stroke again in case days also have gridlines that may have
+	// overwritten the weeks gridline (or month/year). It may seem that we should have logic
+	// in the days routine instead but this is much easier and wont make to much
+	// of an performance impact.
+	$this->StrokeWeeks($offm);		
+	$this->StrokeMonths($offy);		
+	$this->StrokeYears(0);
+	$this->StrokeTableHeaders($offd);
+		
+	// Now we can calculate the correct scaling factor for each vertical position
+	$this->iAvailableHeight = $img->height - $img->top_margin - $img->bottom_margin - $offd;		
+	$this->iVertHeaderSize = $offd;
+	if( $this->iVertSpacing == -1 )
+	    $this->iVertSpacing = $this->iAvailableHeight / $this->iVertLines;
+    }	
+}
+
+//===================================================
+// CLASS GanttPlotObject
+// The common signature for a Gantt object
+//===================================================
+class GanttPlotObject {
+    var $iVPos=0;					// Vertical position
+    var $iLabelLeftMargin=2;	// Title margin
+    var $iStart="";				// Start date
+    var $title,$caption;
+    var $iCaptionMargin=5;
+		
+    function GanttPlotObject() {
+	$this->title = new TextProperty();
+	$this->title->Align("left","center");
+	$this->caption = new TextProperty();
+    }
+	
+    function GetMinDate() {
+	return $this->iStart;
+    }
+
+    function GetMaxDate() {
+	return $this->iStart;
+    }
+	
+    function SetCaptionMargin($aMarg) {
+	$this->iCaptionMargin=$aMarg;
+    }
+
+#    function GetLineNbr() {
+#	return 0;
+#    }
+
+    function GetAbsHeight($aImg) {
+	return 0;
+    }
+	
+    function GetLineNbr() {
+	return $this->iVPos;
+    }
+
+    function SetLabelLeftMargin($aOff) {
+	$this->iLabelLeftMargin=$aOff;
+    }		
+}
+
+//===================================================
+// CLASS Progress
+// Holds parameters for the progress indicator 
+// displyed within a bar
+//===================================================
+class Progress {
+    var $iProgress=-1, $iColor="black", $iPattern=GANTT_SOLID;
+    var $iDensity=98, $iHeight=0.65; 
+	
+    function Set($aProg) {
+	if( $aProg < 0.0 || $aProg > 1.0 )
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Progress value must in range [0, 1]");
+	$this->iProgress = $aProg;
+    }
+
+    function SetPattern($aPattern,$aColor="blue",$aDensity=98) {		
+	$this->iPattern = $aPattern;
+	$this->iColor = $aColor;
+	$this->iDensity = $aDensity;
+    }
+	
+    function SetHeight($aHeight) {
+	$this->iHeight = $aHeight;
+    }
+}
+
+//===================================================
+// CLASS GanttBar
+// Responsible for formatting individual gantt bars
+//===================================================
+class GanttBar extends GanttPlotObject {
+    var $iEnd;
+    var $iHeightFactor=0.5;
+    var $iFillColor="white",$iFrameColor="blue";
+    var $iShadow=false,$iShadowColor="darkgray",$iShadowWidth=1,$iShadowFrame="black";
+    var $iPattern=GANTT_RDIAG,$iPatternColor="blue",$iPatternDensity=95;
+    var $leftMark,$rightMark;
+    var $progress;
+	
+//---------------
+// CONSTRUCTOR	
+    function GanttBar($aPos,$aLabel,$aStart,$aEnd,$aCaption="",$aHeightFactor=0.6) {
+	parent::GanttPlotObject();	
+	$this->iStart = $aStart;	
+	// Is the end date given as a date or as number of days added to start date?
+	if( is_string($aEnd) )
+	    $this->iEnd = strtotime($aEnd)+SECPERDAY;
+	// check for unix timestamp
+	elseif($aEnd > 1000000)
+	    $this->iEnd = $aEnd;
+	elseif(is_int($aEnd) || is_float($aEnd) ) 
+	    $this->iEnd = strtotime($aStart)+round($aEnd*SECPERDAY);
+	$this->iVPos = $aPos;
+	$this->iHeightFactor = $aHeightFactor;
+	$this->title->Set($aLabel);
+	$this->caption = new TextProperty($aCaption);
+	$this->caption->Align("left","center");
+	$this->leftMark =new PlotMark();
+	$this->leftMark->Hide();
+	$this->rightMark=new PlotMark();
+	$this->rightMark->Hide();
+	$this->progress = new Progress();
+    }
+	
+//---------------
+// PUBLIC METHODS	
+    function SetShadow($aShadow=true,$aColor="gray") {
+	$this->iShadow=$aShadow;
+	$this->iShadowColor=$aColor;
+    }
+		
+    function GetMaxDate() {
+	return $this->iEnd;
+    }
+	
+    function SetHeight($aHeight) {
+	$this->iHeightFactor = $aHeight;
+    }
+
+    function SetColor($aColor) {
+	$this->iFrameColor = $aColor;
+    }
+
+    function SetFillColor($aColor) {
+	$this->iFillColor = $aColor;
+    }
+
+    function GetAbsHeight($aImg) {
+	if( is_int($this->iHeightFactor) || $this->leftMark->show || $this->rightMark->show ) {
+	    $m=-1;
+	    if( is_int($this->iHeightFactor) )
+		$m = $this->iHeightFactor;
+	    if( $this->leftMark->show ) 
+		$m = max($m,$this->leftMark->width*2);
+	    if( $this->rightMark->show ) 
+		$m = max($m,$this->rightMark->width*2);
+	    return $m;
+	}
+	else
+	    return -1;
+    }
+	
+    function SetPattern($aPattern,$aColor="blue",$aDensity=95) {		
+	$this->iPattern = $aPattern;
+	$this->iPatternColor = $aColor;
+	$this->iPatternDensity = $aDensity;
+    }
+
+    function Stroke($aImg,$aScale) {
+	$factory = new RectPatternFactory();
+	$prect = $factory->Create($this->iPattern,$this->iPatternColor);
+	$prect->SetDensity($this->iPatternDensity);
+		
+	// If height factor is specified as a float between 0,1 then we take it as meaning
+	// percetage of the scale width between horizontal line.
+	// If it is an integer > 1 we take it to mean the absolute height in pixels
+	if( $this->iHeightFactor > -0.0 && $this->iHeightFactor <= 1.1)
+	    $vs = $aScale->GetVertSpacing()*$this->iHeightFactor;
+	elseif(is_int($this->iHeightFactor) && $this->iHeightFactor>2 && $this->iHeightFactor<200)
+	    $vs = $this->iHeightFactor;
+	else
+	    JpGraphError::Raise("<b>JpGraph Error:</b>Specified height (".$this->iHeightFactor.") for gantt bar is out of range.");
+
+	$xt = $aScale->TranslateDate($aScale->NormalizeDate($this->iStart));
+	$xb = $aScale->TranslateDate($aScale->NormalizeDate($this->iEnd));
+	$yt = $aScale->TranslateVertPos($this->iVPos)-$vs-($aScale->GetVertSpacing()/2-$vs/2);
+	$yb = $aScale->TranslateVertPos($this->iVPos)-($aScale->GetVertSpacing()/2-$vs/2);
+
+	$prect->ShowFrame(false);
+	$prect->SetBackground($this->iFillColor);
+	if( $this->iShadow ) {
+	    $aImg->SetColor($this->iFrameColor);
+	    $aImg->ShadowRectangle($xt,$yt,$xb,$yb,$this->iFillColor,$this->iShadowWidth,$this->iShadowColor);				
+	    $prect->SetPos(new Rectangle($xt+1,$yt+1,$xb-$xt-$this->iShadowWidth-2,$yb-$yt-$this->iShadowWidth-2));				
+	    $prect->Stroke($aImg);
+	}
+	else {	
+	    $prect->SetPos(new Rectangle($xt,$yt,$xb-$xt+1,$yb-$yt+1));				
+	    $prect->Stroke($aImg);
+	    $aImg->SetColor($this->iFrameColor);
+	    $aImg->Rectangle($xt,$yt,$xb,$yb);
+	}
+	if( $this->progress->iProgress > 0 ) {
+	    $prog = $factory->Create($this->progress->iPattern,$this->progress->iColor);
+	    $prog->SetDensity($this->progress->iDensity);
+	    $barheight = ($yb-$yt+1);
+	    if( $this->iShadow ) 
+		$barheight -= $this->iShadowWidth;
+	    $progressheight = floor($barheight*$this->progress->iHeight);
+	    $marg = ceil(($barheight-$progressheight)/2);
+	    $pos = new Rectangle($xt,
+	    $yt + $marg,
+	    ($xb-$xt+1)*$this->progress->iProgress,
+	    $barheight-2*$marg);
+	    $prog->SetPos($pos);
+	    $prog->Stroke($aImg);
+	}
+		
+	$middle = round($yt+($yb-$yt)/2);
+	$this->title->Stroke($aImg,$aImg->left_margin+$this->iLabelLeftMargin,$middle);
+	$this->leftMark->Stroke($aImg,$xt,$middle);
+	$this->rightMark->Stroke($aImg,$xb,$middle);
+	$margin = $this->iCaptionMargin;
+	if( $this->rightMark->show ) 
+	    $margin += $this->rightMark->GetWidth();
+
+	$this->caption->Stroke($aImg,$xb+$margin,$middle);
+    }
+}
+
+//===================================================
+// CLASS MileStone
+// Responsible for formatting individual milestones
+//===================================================
+class MileStone extends GanttPlotObject {
+    var $mark;
+	
+//---------------
+// CONSTRUCTOR	
+    function MileStone($aVPos,$aLabel,$aDate,$aCaption="") {
+	GanttPlotObject::GanttPlotObject();
+	$this->caption->Set($aCaption);
+	$this->caption->Align("left","center");
+	$this->caption->SetFont(FF_FONT1,FS_BOLD);
+	$this->title->Set($aLabel);
+	$this->title->SetColor("darkred");
+	$this->mark = new PlotMark();
+	$this->mark->SetWidth(10);
+	$this->mark->SetType(MARK_DIAMOND);
+	$this->mark->SetColor("darkred");
+	$this->mark->SetFillColor("darkred");
+	$this->iVPos = $aVPos;
+	$this->iStart = $aDate;
+    }
+	
+//---------------
+// PUBLIC METHODS	
+	
+    function GetAbsHeight($aImg) {
+	return max($this->title->GetHeight($aImg),$this->mark->GetWidth());
+    }
+		
+    function Stroke($aImg,$aScale) {
+	// Put the mark in the middle at the middle of the day
+	$x = $aScale->TranslateDate($aScale->NormalizeDate($this->iStart)+SECPERDAY/2);
+	$y = $aScale->TranslateVertPos($this->iVPos)-($aScale->GetVertSpacing()/2);
+		
+	$this->mark->Stroke($aImg,$x,$y);		
+	$this->caption->Stroke($aImg,$x+$this->mark->width/2+$this->iCaptionMargin,$y);
+	$x=$aImg->left_margin+$this->iLabelLeftMargin;
+	$this->title->Stroke($aImg,$x,$y);
+    }
+}
+
+
+//===================================================
+// CLASS GanttVLine
+// Responsible for formatting individual milestones
+//===================================================
+
+class GanttVLine extends GanttPlotObject {
+
+    var $iLine,$title_margin=3;
+    var $iDayOffset=0;	// Defult to left edge of day
+	
+//---------------
+// CONSTRUCTOR	
+    function GanttVLine($aDate,$aTitle="",$aColor="black",$aWeight=3,$aStyle="dashed") {
+	GanttPlotObject::GanttPlotObject();
+	$this->iLine = new LineProperty();
+	$this->iLine->SetColor($aColor);
+	$this->iLine->SetWeight($aWeight);
+	$this->iLine->SetStyle($aStyle);
+	$this->iStart = $aDate;
+	$this->title->Set($aTitle);
+    }
+
+//---------------
+// PUBLIC METHODS	
+
+    function SetDayOffset($aOff=0.5) {
+	if( $aOff < 0.0 || $aOff > 1.0 )
+	    JpGraphError::Raise("<b>JpGraph Error:</b> Offset for vertical line must be in range [0,1]");
+	$this->iDayOffset = $aOff;
+    }
+	
+    function SetTitleMargin($aMarg) {
+	$this->title_margin = $aMarg;
+    }
+	
+    function Stroke($aImg,$aScale) {
+	$x = $aScale->TranslateDate($aScale->NormalizeDate($this->iStart)+$this->iDayOffset*SECPERDAY);	
+	$y1 = $aScale->iVertHeaderSize+$aImg->top_margin;
+	$y2 = $aImg->height - $aImg->bottom_margin;
+	$this->iLine->Stroke($aImg,$x,$y1,$x,$y2);
+	$this->title->Align("center","top");
+	$this->title->Stroke($aImg,$x,$y2+$this->title_margin);
+    }	
+}
+
+// <EOF>
+?>
Index: /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_pie.php
===================================================================
--- /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_pie.php	(revision 3594)
+++ /contrib/ProjectManager/inc/jpgraph-1.5.2/src/jpgraph_pie.php	(revision 3594)
@@ -0,0 +1,439 @@
+<?php
+/*=======================================================================
+// File:	JPGRAPH_PIE.PHP
+// Description:	Pie plot extension for JpGraph
+// Created: 	2001-02-14
+// Author:	Johan Persson (johanp@aditus.nu)
+// Ver:		$Id: jpgraph_pie.php 18250 2005-05-07 14:13:43Z ralfbecker $
+//
+// License:	This code is released under GPL 2.0
+// Copyright (C) 2001 Johan Persson
+//========================================================================
+*/
+
+//===================================================
+// CLASS PiePlot
+// Description: 
+//===================================================
+class PiePlot {
+    var $posx=0.5,$posy=0.5;
+    var $radius=0.3;
+    var $explode_radius=array(),$explode_all=false,$explode_r=20;
+    var $labels, $legends=null;
+    var $csimtargets=null;  // Array of targets for CSIM
+    var $csimareas='';		// Generated CSIM text	
+    var $csimalts=null;		// ALT tags for corresponding target
+    var $data=null;
+    var $title;
+    var $startangle=0;
+    var $weight=1, $color="black";
+    var $font_family=FF_FONT1,$font_style=FS_NORMAL,$font_size=12,$font_color="black";
+    var $legend_margin=6,$show_labels=true;
+    var $precision=1,$show_psign=true;
+    var $themearr=array(
+	"earth" 	=> array(10,34,40,45,46,62,63,134,74,77,120,136,141,168,180,209,218,346,395,89,430),
+	"pastel" => array(27,38,42,58,66,79,105,110,128,147,152,230,236,240,331,337,405,415),
+	"water"  => array(8,370,10,40,335,56,213,237,268,14,326,387,24,388),
+	"sand"   => array(27,168,34,170,19,50,65,72,131,209,46,393));
+    var $theme="earth";
+    var $setslicecolors=array();
+    var $labelformat="%01.0f"; // Default format for labels
+    var $labeltype=0; // Default to percentage
+    var $pie_border=true,$pie_interior_border=true;
+	
+//---------------
+// CONSTRUCTOR
+    function PiePlot(&$data) {
+	$this->data = $data;
+	$this->title = new Text("");
+	$this->title->SetFont(FF_FONT1,FS_BOLD);
+    }
+
+//---------------
+// PUBLIC METHODS	
+    function SetCenter($x,$y=0.5) {
+	$this->posx = $x;
+	$this->posy = $y;
+    }
+
+    function SetCSIMTargets(&$targets,$alts=null) {
+	$this->csimtargets=$targets;
+	$this->csimalts=$alts;
+    }
+	
+    function GetCSIMareas() {
+	return $this->csimareas;
+    }
+
+    function AddSliceToCSIM($i,$xc,$yc,$radius,$sa,$ea) {  //Slice number, ellipse centre (x,y), height, width, start angle, end angle
+		
+	//add coordinates of the centre to the map
+	$coords = "$xc, $yc";
+
+	//add coordinates of the first point on the arc to the map
+	$xp = floor(($radius*cos($sa))+$xc);
+	$yp = floor($yc-$radius*sin($sa));
+	$coords.= ", $xp, $yp";
+		
+	//add coordinates every 0.2 radians
+	$a=$sa+0.2;
+	while ($a<$ea) {
+	    $xp = floor($radius*cos($a)+$xc);
+	    $yp = floor($yc-$radius*sin($a));
+	    $coords.= ", $xp, $yp";
+	    $a += 0.2;
+	}
+		
+	//Add the last point on the arc
+	$xp = floor($radius*cos($ea)+$xc);
+	$yp = floor($yc-$radius*sin($ea));
+	$coords.= ", $xp, $yp";
+	if( !empty($this->csimtargets[$i]) )
+	    $this->csimareas .= "<area shape=\"poly\" coords=\"$coords\" href=\"".$this->csimtargets[$i]."\"";
+	if( !empty($this->csimalts[$i]) ) {										
+	    $tmp=sprintf($this->csimalts[$i],$this->data[$i]);
+	    $this->csimareas .= " alt=\"$tmp\"";
+	}
+	$this->csimareas .= ">\r\n";
+    }
+
+	
+    function SetTheme($t) {
+	if( in_array($t,array_keys($this->themearr)) )
+	    $this->theme = $t;
+	else
+	    JpGraphError::Raise("JpGraph Error: Unknown theme: $t");
+    }
+	
+    function ExplodeSlice($e) {
+	$this->explode_radius[$e]=20;
+    }
+
+    function ExplodeAll($radius=-1) {
+	$this->explode_all=true;
+	if( $radius==-1 )
+	    $this->explode_r = 20;
+	else
+	    $this->explode_r = $radius;
+    }
+
+    function Explode($radarr) {
+	$this->explode_radius = $radarr;
+    }
+	
+    function SetSliceColors($c) {
+	$this->setslicecolors = $c;
+    }
+	
+    function SetStartAngle($a) {
+	assert($a>=0 && $a<2*M_PI);
+	$this->startangle = $a;
+    }
+	
+    function SetFont($family,$style=FS_NORMAL,$size=10) {
+	$this->font_family=$family;
+	$this->font_style=$style;
+	$this->font_size=$size;
+    }
+	
+    // Size in percentage
+    function SetSize($size) {
+	if( ($size>0 && $size<=0.5) || ($size>10 && $size<1000) )
+	    $this->radius = $size;
+	else
+	    JpGraphError::Raise("JpGraph Error: Size (radius) for pie must either be specified as a fraction
+                                [0, 0.5] of the size of the image or as an absolute size in pixels 
+                                in the range [10, 1000]");
+    }
+	
+    function SetFontColor($color) {
+	$this->font_color = $color;
+    }
+	
+    // Set label arrays
+    function SetLegends($l) {
+	$this->legends = $l;
+    }
+	
+    // Should the values be displayed?
+    function HideLabels($f=true) {
+	$this->show_labels = !$f;
+    }
+	
+    // Specify label format as a "C" printf string
+    function SetLabelFormat($f) {
+	$this->labelformat=$f;
+	// If format is specified don't add any %-sign
+	$this->show_psign=0;
+    }
+	
+    // Should we display actual value or percentage?
+    function SetLabelType($t) {
+	if( $t<0 || $t>1 ) 
+	    JpGraphError::Raise("JpGraph Error: Label type for pie plots must be 0 or 1 (not $t).");
+	$this->labeltype=$t;
+	// Don't show percentage value when displaying absolute values
+	if( $t==1 )
+	    $this->show_psign=0;
+    }
+
+    // Should the circle around a pie plot be displayed
+    function ShowBorder($exterior=true,$interior=true) {
+	$this->pie_border = $exterior;
+	$this->pie_interior_border = $interior;
+    }
+	
+    // Setup the legends
+    function Legend(&$graph) {
+	$colors = array_keys($graph->img->rgb->rgb_table);
+   	sort($colors);	
+   	$ta=$this->themearr[$this->theme];	
+   	
+   	if( $this->setslicecolors==null ) 
+	    $numcolors=count($ta);
+   	else
+	    $numcolors=count($this->setslicecolors);
+		
+	$sum=0;
+	for($i=0; $i<count($this->data); ++$i)
+	    $sum += $this->data[$i];
+				
+	$i=0;
+	if( count($this->legends)>0 ) {
+	    foreach( $this->legends as $l ) {
+				
+				// Replace possible format with actual values
+		if( $this->labeltype==0 )
+		    $l = sprintf($l,100*$this->data[$i]/$sum);
+		else
+		    $l = sprintf($l,$this->data[$i]);
+				
+		if( $this->setslicecolors==null ) 
+		    $graph->legend->Add($l,$colors[$ta[$i%$numcolors]]);
+		else
+		    $graph->legend->Add($l,$this->setslicecolors[$i%$numcolors]);
+		++$i;
+				
+				// Breakout if there are more legends then values
+		if( $i==count($this->data) ) return;
+	    }
+	}
+    }
+	
+    // Specify precision for labels. This is almost a deprecated function
+    // nowadays since the introduction of SetLabelFormat()
+    function SetPrecision($p,$psign=true) {
+	if( $p<0 || $p>8 ) 
+	    JpGraphError::Raise("JpGraph Error: Pie label Precision must be between 0 and 8");
+	$this->labelformat="%01.".$p."f";
+	$this->show_psign=$psign;
+    }
+	
+    function Stroke(&$img) {
+		
+	$colors = array_keys($img->rgb->rgb_table);
+   	sort($colors);	
+   	$ta=$this->themearr[$this->theme];	
+   	
+   	if( $this->setslicecolors==null ) 
+	    $numcolors=count($ta);
+   	else
+	    $numcolors=count($this->setslicecolors);
+   	
+	// Draw the slices
+	$sum=0;
+	for($i=0; $i<count($this->data); ++$i)
+	    $sum += $this->data[$i];
+		
+	// Format the titles for each slice
+	for( $i=0; $i<count($this->data); ++$i) {
+	    if( $this->labeltype==0 )
+		if( $sum != 0 )
+		    $l = round(100*$this->data[$i]/$sum,$this->precision);
+		else
+		    $l = 0;
+	    else
+		$l = $this->data[$i];
+	    $l = sprintf($this->labelformat,$l);
+	    if( $this->show_psign ) $l .= "%";
+	    $this->labels[$i]=$l;
+	}
+		
+	// Set up the pic-circle
+	if( $this->radius < 1 )
+	    $radius = floor($this->radius*min($img->width,$img->height));
+	else
+	    $radius = $this->radius;
+	$xc = $this->posx*$img->width;
+	$yc = $this->posy*$img->height;
+		
+	$accsum=0;
+	$angle2 = $this->startangle;
+	$img->SetColor($this->color);
+
+	if( $this->explode_all )
+	    for($i=0;$i<count($this->data);++$i)
+		$this->explode_radius[$i]=$this->explode_r;
+
+	for($i=0; $sum>0 && $i<count($this->data); ++$i) {
+	    $d = $this->data[$i];
+	    $angle1 = $angle2;
+	    $accsum += $d;
+	    $angle2 = $this->startangle+2.0*M_PI*$accsum/$sum;
+	    
+	    if( $this->setslicecolors==null )
+		$slicecolor=$colors[$ta[$i%$numcolors]];
+	    else
+		$slicecolor=$this->setslicecolors[$i%$numcolors];
+
+	    if( $this->pie_interior_border )
+		$img->SetColor($this->color);
+	    else
+		$img->SetColor($slicecolor);
+
+	    $arccolor = $this->pie_border ? $this->color : $slicecolor;
+
+	    $la = abs($angle2-$angle1)/2.0+$angle1;
+	    if( empty($this->explode_radius[$i]) )
+		$this->explode_radius[$i]=0;
+	    $xcm = $xc + $this->explode_radius[$i]*cos($la);
+	    $ycm = $yc - $this->explode_radius[$i]*sin($la);
+	    
+	    $img->CakeSlice($xcm,$ycm,$radius-1,$radius-1,$angle1,$angle2,
+	                    $slicecolor,$arccolor);
+
+	    if( $this->show_labels ) 
+		$this->StrokeLabels($this->labels[$i],$img,$xc,$yc,$la,
+		                    $radius+$this->explode_radius[$i]);  
+
+	    if ($this->csimtargets) {
+		$this->AddSliceToCSIM($i,$xcm,$ycm,$radius,$angle1,$angle2);
+	    }			
+
+	}
+	// Adjust title position
+	$this->title->Pos($xc,$yc-$img->GetFontHeight()-$radius,"center","bottom");
+	$this->title->Stroke($img);
+		
+    }
+
+//---------------
+// PRIVATE METHODS	
+	
+    // Position the labels of each slice
+    function StrokeLabels($label,$img,$xc,$yc,$a,$r) {
+	$img->SetFont($this->font_family,$this->font_style,$this->font_size);
+	$img->SetColor($this->font_color);
+	$img->SetTextAlign("left","top");
+	$marg=6;
+	$r += $img->GetFontHeight()/2;
+	$xt=round($r*cos($a)+$xc);
+	$yt=round($yc-$r*sin($a));
+
+	// Position the axis title. 
+	// dx, dy is the offset from the top left corner of the bounding box that sorrounds the text
+	// that intersects with the extension of the corresponding axis. The code looks a little
+	// bit messy but this is really the only way of having a reasonable position of the
+	// axis titles.
+	$h=$img->GetTextHeight($label);
+	$w=$img->GetTextWidth($label);
+	while( $a > 2*M_PI ) $a -= 2*M_PI;
+	if( $a>=7*M_PI/4 || $a <= M_PI/4 ) $dx=0;
+	if( $a>=M_PI/4 && $a <= 3*M_PI/4 ) $dx=($a-M_PI/4)*2/M_PI; 
+	if( $a>=3*M_PI/4 && $a <= 5*M_PI/4 ) $dx=1;
+	if( $a>=5*M_PI/4 && $a <= 7*M_PI/4 ) $dx=(1-($a-M_PI*5/4)*2/M_PI);
+		
+	if( $a>=7*M_PI/4 ) $dy=(($a-M_PI)-3*M_PI/4)*2/M_PI;
+	if( $a<=M_PI/4 ) $dy=(1-$a*2/M_PI);
+	if( $a>=M_PI/4 && $a <= 3*M_PI/4 ) $dy=1;
+	if( $a>=3*M_PI/4 && $a <= 5*M_PI/4 ) $dy=(1-($a-3*M_PI/4)*2/M_PI);
+	if( $a>=5*M_PI/4 && $a <= 7*M_PI/4 ) $dy=0;
+		
+	$img->StrokeText($xt-$dx*$w,$yt-$dy*$h,$label);		
+    }	
+} // Class
+
+
+
+//===================================================
+// CLASS PieGraph
+// Description: 
+//===================================================
+class PieGraph extends Graph {
+    var $posx, $posy, $radius;		
+    var $legends=array();	
+    var $plots=array();
+//---------------
+// CONSTRUCTOR
+    function PieGraph($width=300,$height=200,$cachedName="",$timeout=0,$inline=1) {
+	$this->Graph($width,$height,$cachedName,$timeout,$inline);
+	$this->posx=$width/2;
+	$this->posy=$height/2;
+	$this->SetColor(array(255,255,255));		
+    }
+
+//---------------
+// PUBLIC METHODS	
+    function Add(&$pie) {
+	$this->plots[] = $pie;
+    }
+	
+    function SetColor($c) {
+	$this->SetMarginColor($c);
+    }
+
+    // Method description
+    function Stroke($aStrokeFileName="") {
+		
+	$this->StrokeFrame();		
+		
+	for($i=0; $i<count($this->plots); ++$i) 
+	    $this->plots[$i]->Stroke($this->img);
+		
+	foreach( $this->plots as $p)
+	    $p->Legend($this);	
+		
+	$this->legend->Stroke($this->img);
+	$this->title->Center($this->img->left_margin,$this->img->width-$this->img->right_margin,5);
+	$this->title->Stroke($this->img);		
+
+	// Stroke texts
+	if( $this->texts != null )
+	    foreach( $this->texts as $t) 
+		$t->Stroke($this->img);
+
+		
+	if ($this->showcsim) {
+	    foreach($this->plots as $p ) {
+		$csim.= $p->GetCSIMareas();
+	    }
+	    //$csim.= $this->legend->GetCSIMareas();
+	    if (preg_match_all("/area shape=\"(\w+)\" coords=\"([0-9\, ]+)\"/", $csim, $coords)) {
+		$this->img->SetColor($this->csimcolor);
+		for ($i=0; $i<count($coords[0]); $i++) {
+		    if ($coords[1][$i]=="poly") {
+			preg_match_all('/\s*([0-9]+)\s*,\s*([0-9]+)\s*,*/',$coords[2][$i],$pts);
+			$this->img->SetStartPoint($pts[1][count($pts[0])-1],$pts[2][count($pts[0])-1]);
+			for ($j=0; $j<count($pts[0]); $j++) {
+			    $this->img->LineTo($pts[1][$j],$pts[2][$j]);
+			}
+		    } else if ($coords[1][$i]=="rect") {
+			$pts = preg_split('/,/', $coords[2][$i]);
+			$this->img->SetStartPoint($pts[0],$pts[1]);
+			$this->img->LineTo($pts[2],$pts[1]);
+			$this->img->LineTo($pts[2],$pts[3]);
+			$this->img->LineTo($pts[0],$pts[3]);
+			$this->img->LineTo($pts[0],$pts[1]);
+						
+		    }
+		}
+	    }
+	}
+		
+	// Finally output the image
+	$this->cache->PutAndStream($this->img,$this->cache_name,$this->inline,$aStrokeFileName);					
+    }
+} // Class
+
+/* EOF */
+?>
Index: /contrib/ProjectManager/inc/ttf-bitstream-vera-1.10/local.conf
===================================================================
--- /contrib/ProjectManager/inc/ttf-bitstream-vera-1.10/local.conf	(revision 3594)
+++ /contrib/ProjectManager/inc/ttf-bitstream-vera-1.10/local.conf	(revision 3594)
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
+<!-- /etc/fonts.conf file to configure system font access -->
+<fontconfig>
+        <!--  Enable sub-pixel rendering
+        <match target="font">
+                <test qual="all" name="rgba">
+                        <const>unknown</const>
+                </test>
+                <edit name="rgba" mode="assign"><const>rgb</const></edit>
+        </match>
+         -->
+
+        <alias>
+                <family>serif</family>
+                <prefer>
+                        <family>Bitstream Vera Serif</family>
+                </prefer>
+        </alias>
+        <alias>
+                <family>sans-serif</family>
+                <prefer>
+                        <family>Bitstream Vera Sans</family>
+                </prefer>
+        </alias>
+        <alias>
+                <family>monospace</family>
+                <prefer>
+                        <family>Bitstream Vera Sans Mono</family>
+                </prefer>
+        </alias>
+</fontconfig>
Index: /contrib/ProjectManager/inc/class.uiinfolog.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.uiinfolog.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.uiinfolog.inc.php	(revision 3594)
@@ -0,0 +1,1578 @@
+<?php
+  /**************************************************************************\
+  * eGroupWare - infolog                                                    *
+  * http://www.eGroupWare.org                                                *
+  * Maintained and further developed by RalfBecker@outdoor-training.de       *
+  * Based on Webinfolog by Craig Knudsen <cknudsen@radix.net>               *
+  *          http://www.radix.net/~cknudsen                                  *
+  * Originaly modified by Mark Peters <skeeter@phpgroupware.org>             *
+  * --------------------------------------------                             *
+  *  This program is free software; you can redistribute it and/or modify it *
+  *  under the terms of the GNU General Public License as published by the   *
+  *  Free Software Foundation; either version 2 of the License, or (at your  *
+  *  option) any later version.                                              *
+  \**************************************************************************/
+
+
+	class uiinfolog 
+	{
+		var $dbb;
+		var $template;
+		var $cat;
+		var $debug = False;
+		var $cat_id;
+		var $theme;
+		var $link_tpl;
+		var $public_functions = array(
+		'index' => True,		
+		'view' => True,
+		'edit' => True,
+		'update'=> True,				
+		'delete' => True,
+		'close'       => True,
+		'import_mail' => True,
+		'search' => True,
+		'header' => True,
+		'footer' => True			
+		);
+
+
+	var $prefs;
+	/**
+	 * instance of the bo-class
+	 * 
+	 * @var boinfolog
+	 */
+	var $bo;
+	/**
+	 * reference to instance of the link-class of bo
+	 * 
+	 * @var bolink
+	 */
+	var $link;
+	/**
+	 * instance of the etemplate class
+	 *
+	 * @var etemplate
+	 */
+	var $tmpl;
+	/**
+	 * reference to the html object of etemplate
+	 *
+	 * @var html
+	 */
+	var $html;
+	/**
+	 * allowed units and hours per day, can be overwritten by the projectmanager configuration, default all units, 8h
+	 * 
+	 * @var string
+	 */
+	var $duration_format = ',';	// comma is necessary!
+	
+	var $icons = array(
+		'type' => array(
+			'task'      => 'task.png',      'task_alt'      => 'Task',
+			'phone'     => 'phone.gpng',     'phone_alt'     => 'Phonecall',
+			'note'      => 'note.png',      'note_alt'      => 'Note',
+			'confirm'   => 'confirm.png',   'confirm_alt'   => 'Confirmation',
+			'reject'    => 'reject.png',    'reject_alt'    => 'Reject',
+			'email'     => 'email.png',     'email_alt'     => 'Email' ),
+		'action' => array(
+			'new'       => 'new.png',       'new_alt'       => 'Add Sub',
+			'view'      => 'view.png',      'view_alt'      => 'View Subs',
+			'parent'    => 'parent.png',    'parent_alt'    => 'View other Subs',
+			'edit'      => 'edit.png',      'edit_alt'      => 'Edit',
+			'addfile'   => 'addfile.png',   'addfile_alt'   => 'Add a file',
+			'delete'    => 'delete.png',    'delete_alt'    => 'Delete',
+			'close'     => 'done.png',      'close_alt'     => 'Close' ),
+		'status' => array(
+			'billed'    => 'billed.png',    'billed_alt'    => 'billed',
+			'done'      => 'done.png',      'done_alt'      => 'done',
+			'will-call' => 'will-call.png', 'will-call_alt' => 'will-call',
+			'call'      => 'call.png',      'call_alt'      => 'call',
+			'ongoing'   => 'ongoing.png',   'ongoing_alt'   => 'ongoing',
+			'offer'     => 'offer.png',     'offer_alt'     => 'offer' )
+	);
+	var $filters = array(
+		'none'                     => 'no Filter',
+		'done'                     => 'done',
+		'responsible'              => 'responsible',
+		'responsible-open-today'   => 'responsible open',
+		'responsible-open-overdue' => 'responsible overdue',
+		'responsible-upcoming'     => 'responsible upcoming',
+		'delegated'                => 'delegated',
+		'delegated-open-today'     => 'delegated open',
+		'delegated-open-overdue'   => 'delegated overdue',
+		'delegated-upcoming'       => 'delegated upcomming',
+		'own'                      => 'own',
+		'own-open-today'           => 'own open',
+		'own-open-overdue'         => 'own overdue',
+		'own-upcoming'             => 'own upcoming',
+		'open-today'               => 'open',
+		'open-overdue'             => 'overdue',
+		'upcoming'                 => 'upcoming',
+	);
+	var $messages = array(
+		'edit'    => 'InfoLog - Edit',
+		'add'     => 'InfoLog - New',
+		'add_sub' => 'InfoLog - New Subproject',
+		'sp'      => '- Subprojects from',
+		're'      => 'Re:'
+	);
+
+
+var $optionldap;
+
+	/**
+	 * Constructor
+	 *
+	 * @return uiinfolog
+	 */
+
+
+
+		function uiinfolog()
+		{
+
+
+
+
+			
+ 
+//print_r($GLOBALS);
+
+			$GLOBALS['phpgw']->nextmatchs = CreateObject('phpgwapi.nextmatchs');
+			$this->bo = CreateObject('infolog.boinfolog',1);
+			$this->cat = &$this->bo->cat;
+			//echo "pwnre".$this->bo->owner."  ".$this->bo->today."  ".$this->bo->evento;
+			$this->dbb = CreateObject('class.DBAccess',1);
+			$this->cat = &$this->bo->cat;
+
+
+			$this->resources = &$this->bo->resources;
+
+			$this->priority = &$this->bo->priority;
+
+			print_debug('BO Owner',$this->bo->owner);
+
+			$this->template = $GLOBALS['phpgw']->template;
+			$this->template_dir = $GLOBALS['phpgw']->common->get_tpl_dir('infolog');
+
+			$this->cat_id   = $this->bo->cat_id;
+
+			$this->link_tpl = CreateObject('phpgwapi.Template',$this->template_dir);
+			$this->link_tpl->set_unknowns('remove');
+			$this->link_tpl->set_file(
+				Array(
+					'link_picture'	=> 'link_pict.tpl'
+				)
+			);
+			$this->link_tpl->set_block('link_picture','link_pict','link_pict');
+			$this->link_tpl->set_block('link_picture','pict','pict');
+			$this->link_tpl->set_block('link_picture','link_open','link_open');
+			$this->link_tpl->set_block('link_picture','link_close','link_close');
+			$this->link_tpl->set_block('link_picture','link_text','link_text');
+
+
+
+$this->ds = $GLOBALS['phpgw']->common->ldapConnect();
+			if(!$this->ds)
+				$this->ds = $GLOBALS['phpgw']->common->ldapConnect();
+			if(!@is_object($GLOBALS['phpgw']->translation))
+			{
+				$GLOBALS['phpgw']->translation = CreateObject('phpgwapi.translation');
+			}
+			$this->user_context  = $GLOBALS['phpgw_info']['server']['ldap_context'];
+			$this->group_context = $GLOBALS['phpgw_info']['server']['ldap_group_context'];
+		
+/*************************************************/
+
+			$this->always_app_header = $this->bo->prefs['common']['template_set'] == 'idots';
+
+			//print_debug('UI',$this->_debug_sqsof());
+
+			if (!is_object($GLOBALS['phpgw']->html))
+			{
+				$GLOBALS['phpgw']->html = CreateObject('phpgwapi.html');
+			}
+			$this->html = &$GLOBALS['phpgw']->html;
+		}
+
+		/* Public functions */
+
+		
+
+		function index($params='')
+
+		{
+
+			if (!$params)
+			{
+				foreach(array('date','year','month','day') as $field)
+				{
+					if (isset($_GET[$field]))
+					{
+						$params[$field] = $_REQUEST[$field];
+					}
+				}
+			}
+//$todos = $this->get_todos($todo_label);
+
+			$GLOBALS['phpgw']->redirect($this->page('search',$params));
+			
+		}
+
+
+		
+function page($_page='',$params='')
+		{
+			if($_page == '')
+			{
+				$page_ = explode('.',$this->bo->prefs['infolog']['defaultinfolog']);
+				$_page = $page_[0];
+
+				if ($_page=='planner_cat' || $_page=='planner_user')
+				{
+					$_page = 'month';
+				}
+				elseif ($_page=='index' || ($_page != 'day' && $_page != 'week' && $_page != 'month' && $_page != 'year' && $_page != 'planner'))
+				{
+					$_page = 'month';
+					$GLOBALS['phpgw']->preferences->add('infolog','defaultinfolog','month');
+					$GLOBALS['phpgw']->preferences->save_repository();
+				}
+			}
+			if($GLOBALS['phpgw_info']['flags']['currentapp'] == 'home' ||
+				strstr($GLOBALS['phpgw_info']['flags']['currentapp'],'mail'))	// email, felamimail, ...
+			{
+				$page_app = 'infolog';
+			}
+			else
+			{
+				$page_app = $GLOBALS['phpgw_info']['flags']['currentapp'];
+			}
+			if (is_array($params))
+			{
+				$params['menuaction'] = $page_app.'.ui'.$page_app.'.'.$_page;
+			}
+			else
+			{
+				$params = 'menuaction='.$page_app.'.ui'.$page_app.'.'.$_page.$params;
+			}
+			return $GLOBALS['phpgw']->link('/index.php',$params);
+		}
+
+
+
+// NDEE: printer-friendly
+function printer_friendly($body,$app_header='')
+		{
+			$_SESSION['infolog']['category'] = $this->cat->formated_list('select','all',$this->bo->cat_id,'True');
+$_SESSION['infolog']['resources'] = $this->resources->formated_list('select','all',$this->bo->resources_id,'True');
+			if((!isset($GLOBALS['phpgw_info']['server']['deny_user_grants_access']) || !$GLOBALS['phpgw_info']['server']['deny_user_grants_access']) && count($this->bo->grants) > 0)
+				$_SESSION['infolog']['cals'] = $this->bo->list_cals();
+
+			
+				unset($GLOBALS['phpgw_info']['flags']['noheader']);
+				unset($GLOBALS['phpgw_info']['flags']['nonavbar']);
+				unset($GLOBALS['phpgw_info']['flags']['noappheader']);
+				unset($GLOBALS['phpgw_info']['flags']['noappfooter']);
+				if ($app_header && $this->always_app_header)
+				{
+					$GLOBALS['phpgw_info']['flags']['app_header'] = $GLOBALS['phpgw_info']['apps']['infolog']['title'].' - '.$app_header;
+				}
+				
+				$GLOBALS['phpgw']->common->phpgw_header();
+				
+			return $new_body;
+		}
+
+
+
+
+
+
+
+		function header()
+		{
+			$cols = 8;
+			if($this->bo->check_perms(PHPGW_ACL_PRIVATE) == True)
+			{
+				$cols++;
+			}
+
+			$tpl = $GLOBALS['phpgw']->template;
+			$tpl->set_unknowns('remove');
+
+			if (!file_exists($file = $this->template_dir.'/header.inc.php'))
+			{
+				$file = PHPGW_SERVER_ROOT . '/infolog/templates/default/header.inc.php';
+			}
+
+			include($file);
+			$refer = explode('.',$GLOBALS['HTTP_GET_VARS']['menuaction']);
+			if($refer[2] != 'view') {
+			$header = $tpl->fp('out','head');
+			unset($tpl);
+			echo $header;
+		}
+			unset($tpl);
+		}
+
+		function footer()
+		{
+			$menuaction = $_GET['menuaction'];
+			list(,,$method) = explode('.',$menuaction);
+
+			if (@$this->bo->printer_friendly)
+			{
+				return;
+			}
+
+			$p = $GLOBALS['phpgw']->template;
+
+			$p->set_file(
+				Array(
+					'footer'	=> 'footer.tpl',
+					'form_button'	=> 'form_button_script.tpl'
+				)
+			);
+			$p->set_block('footer','footer_table','footer_table');
+			$p->set_block('footer','footer_row','footer_row');
+			$p->set_block('footer','blank_row','blank_row');
+			$p->set_block('footer','num_dias','num_dias');
+
+			$m = $this->bo->month;
+			$y = $this->bo->year;
+
+			$hoje = date('Ymd',$GLOBALS['phpgw']->datetime->gmtnow);
+			$mes_hoje = (int)substr($hoje,4,2);
+			$mes_footer = (int)substr($_POST['date'],4,2);
+			if($_GET['sday'])
+			{
+				if($_GET['month'] == $mes_hoje)
+				{
+					$day = $this->bo->day;
+				} 
+				else
+				{
+					$day = $_GET['sday'];
+				}
+			}
+			else
+			{
+				$day = $this->bo->day;
+				$dia_ini = $day;
+			}
+			if($mes_footer == $mes_hoje)
+			{
+				$dia_ini = (int)substr($hoje,6,2);
+				$day = $dia_ini;
+			}
+			
+			/********************************************************************************************/
+
+		}
+
+		
+
+
+
+
+/****************************************************************************************/
+
+
+function edit($cd=0,$readsess=0)
+		{
+
+			$this->today = date('Ymd',$GLOBALS['phpgw']->datetime->users_localtime);
+
+
+
+
+			if(!$this->bo->check_perms(PHPGW_ACL_ADD))
+			{
+				$this->index();
+			}
+			
+			if($readsess)
+			{
+
+				$event = $this->bo->restore_from_appsession;
+				if(!$event['owner'])
+				{
+					$this->bo->add_attribute('owner',$this->bo->owner);
+				}
+				$can_edit = True;
+			}
+			else
+			{
+
+
+				$this->bo->event_init();
+				$this->bo->add_attribute('id',0);
+
+				$can_edit = True;
+				$participants = (string)(get_var('participants',array('GET'),FALSE));
+				$starthour = (int)(get_var('hour',array('GET'),$this->bo->prefs['infolog']['workdaystarts']));
+				$startmin  = (int)(get_var('minute',array('GET'),0));
+				$endmin    = $startmin + (int)$this->bo->prefs['infolog']['defaultlength'];
+				$endhour   = $starthour + $this->bo->normalizeminutes($endmin);
+				
+				$subject	= (string)(get_var('title',array('GET'),0));
+				if ($subject != '0')
+				{
+					$description	= (string)(get_var('description',array('GET'),0));
+					$description1	= (string)(get_var('description1',array('GET'),0));
+					$location	= (string)(get_var('location',array('GET'),0));
+				}
+				else
+					unset($subject);
+				
+				$this->bo->set_start($this->bo->year,$this->bo->month,$this->bo->day,$starthour,$startmin,0);
+				$this->bo->set_end($this->bo->year,$this->bo->month,$this->bo->day,$endhour,$endmin,0);
+
+			//echo "kkkk".$this->bo->year;
+				if(@$this->bo->prefs['infolog']['default_private'])
+				{
+					$this->bo->set_class(False);
+				}
+				else
+				{
+					$this->bo->set_class(True);
+				}
+				
+				$event = $this->bo->restore_from_appsession;
+				
+			}
+			$this->edit_form(
+				Array(
+					'event' => $event,
+					'cd' => $cd,
+					'plain' => $_GET['plain']
+				)
+			);
+			$GLOBALS['phpgw']->common->phpgw_footer();
+		}
+
+
+function edit_form($param)
+		{
+
+
+if(isset($_REQUEST['id'])){
+			$event=$this->select($_REQUEST['id']);
+}else{
+$event=$this->select_init();
+}
+
+unset($GLOBALS['phpgw_info']['flags']['noheader']);
+
+
+			unset($GLOBALS['phpgw_info']['flags']['nonavbar']);
+			
+	
+
+$justthese = array("uidnumber", "gidnumber", "uid", "cn", "givenname", "sn", "mail","telephonenumber","gecos","dn");
+				$sri = @ldap_search($this->ds, "ou=usuarios,ou=cnti,dc=gob,dc=ve", ("(&(sn=*)(mail=*@*)(structuralObjectClass=inetOrgPerson)(deliveryMode=virtual))"), $justthese);
+		
+
+if(!$sri)
+				return null;
+			$allValues = ldap_get_entries($this->ds, $sri);
+//echo $event['propietario']."  ".(( $event['propietario'] == "1003")?'SELECTED':'');
+
+
+$ttt=count($allValues);
+
+for($i=0;$i<$ttt;$i++){
+
+							if(isset($allValues[$i]['mail'][0]))
+				{
+								$option = '    <option  value="' . $allValues[$i]['uidnumber'][0] . '"   '.(( $event['propietario'] ==  $allValues[$i]['uidnumber'][0] )?'SELECTED':'').'>'.$allValues[$i]['gecos'][0].'</option>'."\n";
+								$optionldap .= $option;
+								$option2 = '    <option  value="' . $allValues[$i]['uidnumber'][0] . '"   '.(( $event['responsable'] ==  $allValues[$i]['uidnumber'][0] )?'SELECTED':'').'>'.$allValues[$i]['gecos'][0].'</option>'."\n";
+								$optionldap2 .= $option2;
+}
+
+
+
+
+}
+
+
+
+
+
+			if(!is_array($param))
+			{
+				$this->index();
+			}
+			
+			if(isset($param['event']))
+			{
+				$event = $param['event'];
+			}
+			
+			$hourformat = substr($this->bo->users_timeformat,0,1);
+			
+			$jscal = CreateObject('phpgwapi.jscalendar');	// before phpgw_header() !!!
+			
+			unset($GLOBALS['phpgw_info']['flags']['noheader']);
+			unset($GLOBALS['phpgw_info']['flags']['nonavbar']);
+			$GLOBALS['phpgw_info']['flags']['noappheader'] = True;
+			$GLOBALS['phpgw_info']['flags']['noappfooter'] = True;
+			$GLOBALS['phpgw_info']['flags']['app_header'] = $event['id'] ? lang('Editing event') : lang('Adding event');
+			if ($param['plain'] != "True"){
+				$GLOBALS['phpgw']->common->phpgw_header();
+			}
+			else{
+				$GLOBALS['phpgw_info']['theme']['row_on'] = "F8F8F8";
+				$GLOBALS['phpgw_info']['theme']['row_off'] = "F8F8F8";
+			}
+
+			$p = &$GLOBALS['phpgw']->template;
+			$p->set_file(
+				Array(
+					'edit'		=> 'edit.tpl',
+					'form_button'	=> 'form_button_script.tpl'
+				)
+			);
+			$p->set_block('edit','edit_entry','edit_entry');
+			$p->set_block('edit','list','list');
+			$p->set_block('edit','hr','hr');
+
+			$vars = Array(
+				'font'			=> $this->theme['font'],
+				'bg_color'		=> $this->theme['bg_text'],
+				'action_url'		=> $GLOBALS['phpgw']->link('/index.php',Array('menuaction'=>'infolog.uiinfolog.update')),
+				'accounts_link'		=> $GLOBALS['phpgw']->link('/index.php','menuaction=infolog.uiinfolog.accounts_popup'),
+				'common_hidden'	=> '<input type="hidden" name="info[id]" value="'.$event['id'].'">'."\n".
+'<input type="hidden" name="infolog[parent]" value="'.$_POST['parent'].'">'."\n"
+					. '<input type="hidden" name="info[owner]" value="'.$event['owner'].'">'."\n"
+					. (@isset($GLOBALS['phpgw_info']['server']['deny_user_grants_access']) && $GLOBALS['phpgw_info']['server']['deny_user_grants_access']?
+					'<input type="hidden" name="participants[]" value="'.$this->bo->owner.'">'."\n":''),
+				'errormsg'		=> ($param['cd']?$GLOBALS['phpgw']->common->check_code($param['cd']):'')
+			);
+			
+			$p->set_var($vars);
+
+// info_type
+	//echo $event['type'];		
+$var['title'] = Array(
+'tr_color' => $this->theme['th_bg'],
+			'field' => lang('Type'),
+			'data'  => '<select onchange="javascript:changeViewMode(this.value);" id="exec[info_type]" name="exec[info_type]" '.($event['type'] == 'H'?'DISABLED':'').'><option value="email" '.(($event['public'] && $event['type'] == 'email')?'SELECTED':'').' >'.lang('Email').'</option><option value="phone" '.((!$event['public'] && $event['type'] == 'phone')?'SELECTED':'').' >'. lang('Phone').'</option><option value="n" '.($event['type']=='n'?'SELECTED':'').' >'. lang('n').'</option><option value="note" '.($event['type'] == 'note'?'SELECTED':'').' >'.lang('Note').'</option><option value="task" '.($event['type'] == 'task'?'SELECTED':'').' >'.lang('Task').'</option></select>'
+			); //event['public']
+
+// Display Categories
+			if(strpos($event['category'],','))
+			{
+				$temp_cats = explode(',',$event['category']);
+				@reset($temp_cats);
+				while(list($key,$value) = each($temp_cats))
+				{
+					$check_cats[] = (int)$value;
+				}
+			}
+			elseif($event['category'])
+			{
+				$check_cats[] = (int)$event['category'];
+			}
+			else
+			{
+				$check_cats[] = 0;
+			}
+			$var['category'] = Array(
+				'field'	=> lang('Category'),
+				'data'	=> '<select name="infolog[category]" onchange="javascript:updateTitleField(this)" >'.$this->cat->formated_list('select','all',$check_cats,True).'</select>'
+			);
+		
+
+// Display location
+// Location
+
+
+
+// 
+			if(strpos($event['location'],','))
+			{
+				$temp_cats = explode(',',$event['location']);
+				@reset($temp_cats);
+				while(list($key,$value) = each($temp_cats))
+				{
+					$check_cats[] = (int)$value;
+				}
+			}
+			elseif($event['location'])
+			{
+				$check_cats[] = (int)$event['location'];
+			}
+			else
+			{
+				$check_cats[] = 0;
+			}
+			$var['location'] = Array(
+				'field'	=> lang('Location'),
+				'data'	=> '<select name="infolog[location]" onchange="javascript:updateTitleField(this)" >'.$this->resources->formated_list('select','name,name','',True).'</select>'
+			);
+
+
+
+// Participants
+			
+if(!isset($GLOBALS['phpgw_info']['server']['deny_user_grants_access']) || !$GLOBALS['phpgw_info']['server']['deny_user_grants_access'])
+			{
+				
+					
+						$str = utf8_decode($optionldap);
+						
+						$var['participants'] = array
+						(
+							'field'	=> lang('Contacto'),							
+							'data'	=> "
+									<select id='user_list' name='participants[]' style='width: 220px' ><option value=0>seleccione</option>".$str."</select>										
+											
+									
+									"
+						);
+									
+						
+						
+						
+						
+				}
+
+
+// asunto
+			$var['description1'] = Array(
+				'field'	=> lang('Asunto'),
+				'data'	=> '<input type="text" name="cal[description1]" maxlength="255" value="'.$event['asunto'].'">'
+			);
+
+
+
+
+// Full Description
+			$var['description'] = Array(
+				'field'	=> lang('Description'),
+				'data'	=> '<textarea name="cal[description]" rows="5" cols="40" wrap="virtual" maxlength="2048">'.$event['descripcion'].'</textarea>'
+			);
+
+
+
+
+//echo "quuuue".$GLOBALS['phpgw']->datetime->tz_offset."  ";
+// Date
+//echo $event['fechainicio'];
+			//$start = $this->bo->maketime( $event['fechainicio']) - $GLOBALS['phpgw']->datetime->tz_offset;
+//$starttime = $this->bo->maketime2(5,23,0,22,10,2010) - $GLOBALS['phpgw']->datetime->tz_offset;
+			
+$valorfechainicio=$this->bo->time2array($event['fechainicio'],$alarm = 0);
+$start=$valorfechainicio['mday']."/".$valorfechainicio['month']."/".$valorfechainicio['year'];
+//echo "ddddd".$valorfechainicio['mday']."/".$valorfechainicio['month']."/".$valorfechainicio['year'];
+			$var['startdate'] = Array(
+				'field'	=> lang('Start Date'),
+
+				'data' => $jscal->input('start[str]',$start)
+			);
+
+// Time
+			if ($this->bo->prefs['common']['timeformat'] == '12')
+			{
+				$str .= '<input type="radio" name="start[ampm]" value="am"'.($event['fechainicio']['hour'] >= 12?'':' checked').'>am'."\n"
+					. '<input type="radio" name="start[ampm]" value="pm"'.($event['fechainicio']['hour'] >= 12?' checked':'').'>pm'."\n";
+			}
+			$var['starttime'] = Array(
+				'field'	=> lang('Start Time'),
+				'data'	=> '<input name="start[hour]" size="2" VALUE="'.$GLOBALS['phpgw']->common->show_date($start,$hourformat).'" maxlength="2">:<input name="start[min]" size="2" value="'.$GLOBALS['phpgw']->common->show_date($start,'i').'" maxlength="2">'."\n"
+			);
+
+// End Date
+			//$end = $this->bo->maketime($event['fechafin']) - $GLOBALS['phpgw']->datetime->tz_offset;
+
+
+
+$eee=$this->bo->maketime($GLOBALS['phpgw']->datetime->users_localtime);
+$valorfechaend=$this->bo->time2array($eee,$alarm = 0);
+
+//echo $valorfechaend['month']."rrr   ".$event['fechafin']."  ".$eee;
+$valorfechaend=$this->bo->time2array($event['fechafin'],$alarm = 0);
+$end=$valorfechaend['mday']."/".$valorfechaend['month']."/".$valorfechaend['year'];
+
+
+
+
+
+			$var['enddate'] = Array(
+				'field'	=> lang('End Date'),
+		'data' => $jscal->input('end[str]',$end)
+			);
+
+// End Time
+			if ($this->bo->prefs['common']['timeformat'] == '12')
+			{
+				$str = '<input type="radio" name="end[ampm]" value="am"'.($event['end']['hour'] >= 12?'':' checked').'>am'."\n"
+					. '<input type="radio" name="end[ampm]" value="pm"'.($event['end']['hour'] >= 12?' checked':'').'>pm'."\n";
+			}
+			$var['endtime'] = Array(
+				'field'	=> lang('End Time'),
+				'data'	=> '<input name="end[hour]" size="2" VALUE="'.$GLOBALS['phpgw']->common->show_date($end,$hourformat).'" maxlength="2">:<input name="end[min]" size="2" value="'.$GLOBALS['phpgw']->common->show_date($end,'i').'" maxlength="2">'."\n".$str
+			);
+
+
+
+
+
+//  estado
+
+$var['status'] = Array(
+				'field'	=> lang('Status'),
+				'data'	=> '<select name="status"><option value="ongoing" '.(( $event['status'] ==  'ongoing' )?'SELECTED':'').'>en curso</option>
+<option value="done"  '.(( $event['status'] ==  'done' )?'SELECTED':'').'>hecho</option></select>'
+			);
+
+
+//  prioridad
+
+
+
+
+
+if(strpos($event['priority'],','))
+			{
+				$temp_cats = explode(',',$event['priority']);
+				@reset($temp_cats);
+				while(list($key,$value) = each($temp_cats))
+				{
+					$check_cats[] = (int)$value;
+				}
+			}
+			elseif($event['priority'])
+			{
+				$check_cats[] = (int)$event['priority'];
+			}
+			else
+			{
+				$check_cats[] = 0;
+			}
+
+
+
+			$var['priority'] = Array(
+				'field'	=> lang('Priority'),
+				'data'	=> '<select name="priority" onchange="javascript:updateTitleField(this)" >'.$this->priority->formated_list('select','name,name',$event['priority'],True).'</select>'
+			);
+
+
+
+
+
+//  accesso
+
+$var['accesso'] = Array(
+				'field'	=> lang('access'),
+				'data'	=> '<select name="exec[accesso]"><option value="public" '.(( $event['access'] ==  'public' )?'SELECTED':'').'>Publico</option>
+<option value="private"  '.(( $event['access'] ==  'private' )?'SELECTED':'').'>Privado</option></select>'
+			);
+
+
+//porcentaje
+
+$var['percent'] = Array(
+				'field'	=> lang('Completado'),
+				'data'	=> '<select name="percent"><option value="0" '.(( $event['percent'] ==  0 )?'SELECTED':'').'>0%</option>
+<option value="10"  '.(( $event['percent'] ==  10 )?'SELECTED':'').'>10%</option>
+<option value="20" '.(( $event['percent'] ==  20 )?'SELECTED':'').'>20%</option>
+
+<option value="30" '.(( $event['percent'] ==  30 )?'SELECTED':'').'>30%</option>
+<option value="40" '.(( $event['percent'] ==  40 )?'SELECTED':'').'>40%</option>
+<option value="50" '.(( $event['percent'] ==  50 )?'SELECTED':'').'>50%</option>
+<option value="60" '.(( $event['percent'] ==  60 )?'SELECTED':'').'>60%</option>
+<option value="70" '.(( $event['percent'] ==  70 )?'SELECTED':'').'>70%</option>
+<option value="80" '.(( $event['percent'] ==  80 )?'SELECTED':'').'>80%</option>
+<option value="90" '.(( $event['percent'] ==  90 )?'SELECTED':'').'>90%</option>
+<option value="100" '.(( $event['percent'] ==  100 )?'SELECTED':'').'>100%</option></select>'
+			);
+
+
+/****************************************************/
+
+
+
+
+
+
+
+
+//responsable
+			if(!isset($GLOBALS['phpgw_info']['server']['deny_user_grants_access']) || !$GLOBALS['phpgw_info']['server']['deny_user_grants_access'])
+			{
+				
+					
+						$str2 = utf8_decode($optionldap2);
+						
+						$var['participants1'] = array
+						(
+							'field'	=> lang('Responsable'),							
+							'data'	=> "
+									<select id='user_list' name='participants1[]' style='width: 220px' ><option value=0>seleccione</option>".$str2."</select>										
+											
+									
+									"
+						);
+									
+						
+						
+						
+						
+				}
+				
+			
+
+
+
+
+
+
+
+
+
+/*********************************************************************/
+
+
+
+
+// End Date2
+			//$end2 = $this->bo->maketime($event['fechacompletada']) - $GLOBALS['phpgw']->datetime->tz_offset;
+$valorfechaend2=$this->bo->time2array($event['fechacompletada'],$alarm = 0);
+if(isset($event['fechacompletada'])&&$event['fechacompletada']>0){
+$end2=$valorfechaend2['mday']."/".$valorfechaend2['month']."/".$valorfechaend2['year'];
+}else{
+$end2="";
+}
+
+
+			$var['enddate2'] = Array(
+				'field'	=> lang('Fecha completada'),
+
+				'data' => $jscal->input('end2[str]',$end2)
+			);
+
+
+
+
+
+
+
+			$i = 0; $boxes = '';
+			foreach ($this->bo->rpt_day as $mask => $name)
+			{
+				$boxes .= '<input type="checkbox" name="cal[rpt_day][]" value="'.$mask.'"'.($event['recur_data'] & $mask ? ' checked' : '').'>&nbsp;'.lang($name)."\n";
+				if (++$i == 5) $boxes .= '<br>';
+			}
+			$var['recure_day'] = Array(
+				'field'	=> lang('Repeat Day').'<br>'.lang('(required for weekly recursivity)'),
+				'data'	=> $boxes
+			);
+
+			$var['recure_interval'] = Array(
+				'field'	=> lang('Interval'),
+				'data'	=> '<input name="cal[recur_interval]" size="4" maxlength="4" value="'.$event['recur_interval'].'">'
+			);
+//			$this->output_template_array($p,'row','list',Array('data' => '<script src="simple_show_hide.js" type="text/javascript"></script>'));
+
+			if (!isset($this->fields))
+			{
+				$this->custom_fields = CreateObject('infolog.bocustom_fields');
+				$this->fields = &$this->custom_fields->fields;
+				$this->stock_fields = &$this->custom_fields->stock_fields;
+			}
+		//	$this->output_template_array($p,'row','list',$var['access']);
+			unset($var['access']);
+
+			$preserved = False;
+			foreach($this->fields as $field => $data)
+			{
+
+				if (!$data['disabled'])
+				{
+
+
+					if (isset($var[$field]))
+					{
+
+
+						switch($field)
+						{
+							case 'startdate':
+								$this->output_template_array($p,'row','list',$var['startdate']);
+								$this->output_template_array($p,'row','list',$var['starttime']);
+								break;
+							case 'enddate':
+								$this->output_template_array($p,'row','list',$var['enddate']);
+								$this->output_template_array($p,'row','list',$var['endtime']);
+								break;
+							case 'recure_type':
+								$p->set_var('tr_color',$this->theme['th_bg']);
+								$p->set_var('hr_text','<center id="rpt_label"><b>'.lang('Repeating Event Information').'</b></center>');
+								$p->parse('row','hr',True);
+								$this->output_template_array($p,'row','list',$var['recure_type']);
+								$this->output_template_array($p,'row','list',$var['recure_enddate']);
+								$this->output_template_array($p,'row','list',$var['recure_day']);
+								$this->output_template_array($p,'row','list',$var['recure_interval']);
+								break;
+							default:
+								$this->output_template_array($p,'row','list',$var[$field]);
+
+						}
+					}
+					elseif (!isset($this->stock_fields[$field]))	// Custom field
+					{
+
+
+						$lang = lang($name = substr($field,1));
+						$size = 'SIZE='.($data['shown'] ? $data['shown'] : ($data['length'] ? $data['length'] : 30)).
+							' MAXLENGTH='.($data['length'] ? $data['length'] : 255);
+						$v = array(
+							'field'	=> $lang == $name.'*' ? $name : $lang,
+							'data'	=> '<input name="cal['.htmlspecialchars($field).']" '.$size.' value="'.$event['#'.$name].'">'
+						);
+						if ($data['title'])
+						{
+							$v['tr_color'] = $this->theme['th_bg'];
+						}
+						if (!$data['length'] && $data['title'])
+						{
+							$p->set_var('tr_color',$this->theme['th_bg']);
+							$p->set_var('hr_text','<center><b>'.$v['field'].'</b></center>');
+							$p->parse('row','hr',True);
+						}
+						else
+						{
+							$this->output_template_array($p,'row','list',$v);
+						}
+					}
+				}
+				else	// preserve disabled fields
+				{
+
+
+					switch ($field)
+					{
+						case 'owner':
+							$preserved[$field] = $event['id'] ? $event['participants'][$event['owner']] : 'A';
+							break;
+						case 'recure_type':
+							foreach(array('recur_type','recur_enddate','recur_data','recur_interval') as $field)
+							{
+								$preserved[$field] = $event[$field];
+							}
+							break;
+						case 'startdate':
+						case 'enddate':
+							$field = substr($field,0,-4);
+						default:
+							$preserved[$field] = $event[$field];
+					}
+				}
+			}
+			unset($var);
+			if (is_array($preserved))
+			{
+				//echo "preserving<pre>"; print_r($preserved); echo "</pre>\n";
+				$p->set_var('common_hidden',$p->get_var('common_hidden').'<input type="hidden" name="preserved" value="'.htmlspecialchars(serialize($preserved)).'">'."\n");
+			}
+			$p->set_var('submit_button',lang('Save'));
+
+			$delete_button = $cancel_button = '';
+			if ($event['id'] > 0)
+			{
+				$var = Array(
+					'action_url_button'	=> $this->page('delete','&cal_id='.$event['id']),
+					'action_text_button'	=> lang('Delete'),
+					'action_confirm_button'	=> "onClick=\"return confirm('".lang('Are you sure want to delete this entry?\\nThis will delete this entry for all users.')."')\"",
+					'action_extra_field'	=> '',
+					'button_id'	=>	'delete_button'
+				);
+				$p->set_var($var);
+				$delete_button = $p->fp('out','form_button');
+			}
+			$p->set_var('delete_button',$delete_button);
+			$p->set_var('alert_msg',lang('Required field (category) is empty'));
+
+			if ($this->bo->return_to)
+			{
+				$var = Array(
+					'action_url_button'	=> $GLOBALS['phpgw']->link('/index.php','menuaction='.$this->bo->return_to),
+					'action_text_button'	=> lang('Cancel'),
+					'action_confirm_button'	=> '',
+					'action_extra_field'	=> '',
+					'button_id'	=>	'cancel_button'
+				);
+				$p->set_var($var);
+				$cancel_button = $p->fp('out','form_button');
+			}
+			$p->set_var('cancel_button',$cancel_button);
+			$p->pparse('out','edit_entry');
+		}
+
+	function output_template_array(&$p,$row,$list,$var)
+		{
+			if (!isset($var['hidden_vars']))
+			{
+				$var['hidden_vars'] = '';
+			}
+			if (!isset($var['tr_color']))
+			{
+				$var['tr_color'] = $GLOBALS['phpgw']->nextmatchs->alternate_row_color();
+			}
+			$p->set_var($var);
+			$p->parse($row,$list,True);
+		}
+
+
+function get_organizations($context, $selected='', $recursive = false)
+		{
+			$s = CreateObject('phpgwapi.sector_search_ldap');
+						
+			return ($recursive ? 
+				$s->get_organizations($context, $selected, false ,true) :
+				$s->get_sectors($selected, false, false));
+		}	
+	
+
+
+
+/**********************************************/
+
+
+function _debug_sqsof()
+		{
+			$data = array(
+				'filter'     => $this->bo->filter,
+				'cat_id'     => $this->bo->cat_id,
+				'owner'      => $this->bo->owner,
+				'year'       => $this->bo->year,
+				'month'      => $this->bo->month,
+				'day'        => $this->bo->day,
+				'sortby'     => $this->bo->sortby,
+				'num_months' => $this->bo->num_months
+			);
+			Return _debug_array($data,False);
+		}
+
+
+
+
+/***********************************/
+
+
+
+
+function search()
+		{
+
+print_r($_REQUEST); 
+
+$ordenar=$_REQUEST['ordenar'];
+echo $ordenar."fff";
+$adicional="";
+switch($ordenar)
+{
+case '2':$adicional= " and t1.info_type='".$_REQUEST['valor']."'";break;
+case '3':$adicional= " and t1.info_from='".$_REQUEST['valor']."'";break;
+case '4':$adicional= " and t1.info_type='".$_REQUEST['valor']."'";break;
+case '7':$adicional= " and t1.info_owner='".$_REQUEST['valor']."'";break;
+case '8':$adicional= " and t1.info_responsible='".$_REQUEST['valor']."'";break;
+case '9':$adicional= " and t1.info_access='".$_REQUEST['valor']."'";break;
+default:$adicional= " ";
+
+
+}
+			
+
+			unset($GLOBALS['phpgw_info']['flags']['noheader']);
+			unset($GLOBALS['phpgw_info']['flags']['nonavbar']);
+			$GLOBALS['phpgw_info']['flags']['app_header'] = $GLOBALS['phpgw_info']['apps']['infolog']['title'].' - '.lang('Search Results');
+			$GLOBALS['phpgw']->common->phpgw_header();
+
+			$error = '';
+
+			$matches = 0;
+
+		//$event = $this->bo->read_entry($info_id);	
+$event = $this->get_event_ids($adicional);
+
+
+			//$event_ids = $this->bo->search_keywords($_POST['keywords']);
+
+			
+			$matches = count($event);
+
+//echo "matches" . $matches;
+
+			if ($matches == 1)
+			{
+				$quantity = lang('1 match found').'.';
+			}
+			elseif ($matches > 0)
+			{
+				$quantity = lang('%1 matches found',$matches).'.';
+			}
+			else
+			{
+				echo '<b>'.lang('Error').':</b>'.lang('no matches found');
+				return;
+			}
+
+			$p = $GLOBALS['phpgw']->template;
+			$p->set_file(
+				Array(
+					'search_form'	=> 'search.tpl'
+				)
+			);
+			$p->set_block('search_form','search','search');
+			$p->set_block('search_form','search_list_header','search_list_header');
+			$p->set_block('search_form','search_list','search_list');
+			$p->set_block('search_form','search_list_footer','search_list_footer');
+
+
+$script='';
+
+			$typo='<select name="search_info_type"   onchange="return submitit(this.form,2)" class="noPrint">
+				<option value="" selected="selected">Tipo</option>
+				<option value="email">'.lang('email').'</option>
+				<option value="phone">'.lang('phone').'</option>
+				<option value="n">n</option>
+				<option value="note">'.lang('note').'</option>
+				<option value="task">'.lang('task').'</option>
+				</select>';
+
+
+$estatus='<select name="search[info_status]" onchange="return submitit(this.form,3);" class="noPrint">
+					<option value="" selected="selected">Estado</option>
+					<option value="offer">'.lang('offer').'</option>
+					<option value="not-started">'.lang('not-started').'</option>
+					<option value="ongoing">'.lang('ongoing').'</option>
+					<option value="done">'.lang('done').'</option>
+					<option value="cancelled">'.lang('cancelled').'</option>
+					<option value="billed">'.lang('billed').'</option>
+					</select>';
+
+
+			$var = Array(
+				'th_bg'		=> $this->theme['th_bg'],
+				'search_text'	=> lang('Search Results'),
+				'quantity'	=> $quantity,
+                                'title'	=> 'Registro de notas y tareas',
+				'typo'	=> $typo,
+				'script'=>$script,
+				'estatus'	=> $estatus
+
+			);
+			$p->set_var($var);
+
+			if($matches > 0)
+			{
+				$p->parse('rows','search_list_header',True);
+			}
+
+//if($info){
+			foreach($event  as $id => $data)
+			{
+
+
+				$datetime = $this->bo->maketime($event['start']) - $GLOBALS['phpgw']->datetime->tz_offset;
+
+
+
+
+				$info = array(
+					'tr_color'	=> $GLOBALS['phpgw']->nextmatchs->alternate_row_color(),
+					'date'		=> $GLOBALS['phpgw']->common->show_date($datetime),
+					'tipo'		=> $data['tipo'],
+'id'	=>	$data['id'],
+'estado'		=> $data['estado'],
+'asunto'		=> $data['asunto'],
+'descripcion'		=> $data['descripcion'],
+'fechainicio'		=> $data['fechainicio'],
+'fechafin'		=> $data['fechafin'],
+'fechacompletada'		=> $data['fechacompletada'],
+'propietario'		=> $data['propietario'],
+'responsable'		=> $data['responsable'],
+'padre'		=> $data['padre'],
+'priority'=>$data['priority'],
+'accionagregar'	=>'index.php?menuaction=infolog.uiinfolog.edit&parent='. $data['id'],
+'accionmodificar'	=>'index.php?menuaction=infolog.uiinfolog.edit&id='. $data['id'],
+'accioneditar'	=>'',
+'accionagregartimet'=>'',
+'accionborrar'	=>'index.php?menuaction=infolog.uiinfolog.borrar&id='. $data['id']
+				);
+
+//'accionagregar'	=>'index.php?menuaction=infolog.uiinfolog.edit&parent='. $data['id']
+$p->set_var($info);
+				$p->set_var($data);
+				$p->parse('rows','search_list',True);
+			}
+//	}
+			if($matches > 0)
+			{
+				$p->parse('rows','search_list_footer',True);
+			}
+
+			$p->pparse('out','search');
+		}
+
+
+function get_event_ids($search)
+		{
+echo "tamannn".count($search)."  ".$search[0];
+
+$from ="";
+if (isset($_REQUEST['id'])){
+
+			$from = ' and t1.info_id='.$_REQUEST['id'];
+			}
+
+			$sql = 'select t1.*,t2.list_descripcion from   phpgw_infolog t1, phpgw_listas t2 where t1.info_priority=t2.list_id '
+					. $from
+					.$search;
+					//. 'WHERE (phpgw_cal_user.info_id = phpgw_cal.info_id) '
+					//. $where . $extra;
+
+
+echo "FULL SQL : ".$sql."<br>\n";
+
+
+			if($this->debug)
+			{
+				echo "FULL SQL : ".$sql."<br>\n";
+			}
+
+ //$resultado=pg_query($this->bo->connect(),$sql) or die("Error al mostrar");
+
+
+$GLOBALS['phpgw']->db->query($sql,__LINE__,__FILE__);
+while( $GLOBALS['phpgw']->db->next_record() )
+			{
+			//	
+
+$valorfechaini=$this->bo->time2array($GLOBALS['phpgw']->db->f('info_startdate'),$alarm = 0);
+$ini=$valorfechaini['mday']."/".$valorfechaini['month']."/".$valorfechaini['year'];
+
+$valorfechaend=$this->bo->time2array($GLOBALS['phpgw']->db->f('info_enddate'),$alarm = 0);
+$end=$valorfechaend['mday']."/".$valorfechaend['month']."/".$valorfechaend['year'];
+
+
+if($GLOBALS['phpgw']->db->f('info_datecompleted')==0){
+$comp="";
+}else{
+$valorfechacom=$this->bo->time2array($GLOBALS['phpgw']->db->f('info_datecompleted'),$alarm = 0);
+$comp=$valorfechacom['mday']."/".$valorfechacom['month']."/".$valorfechacom['year'];
+}
+
+$retval[] =  array(
+					'id'    => $GLOBALS['phpgw']->db->f('info_id'),
+					'type'         => '<img src=/infolog/templates/default/images/'.$GLOBALS['phpgw']->db->f('info_type').'.png>',
+					'asunto'         => $GLOBALS['phpgw']->db->f('info_subject'),
+					'descripcion'         => $GLOBALS['phpgw']->db->f('info_des'),
+					'fechainicio' => $ini,
+					'fechafin'  => $end,
+'status'  => lang($GLOBALS['phpgw']->db->f('info_status')),
+'fechacompletada'  => $comp,
+'propietario'  => $this->get_account_name($GLOBALS['phpgw']->db->f('info_owner'),'gecos'),
+'responsable'  => $this->get_account_name($GLOBALS['phpgw']->db->f('info_responsible'),'gecos'),
+'owner'  => $GLOBALS['phpgw']->db->f('info_uid'),
+'percent'  => $GLOBALS['phpgw']->db->f('info_percent'),
+ 'access'  => $GLOBALS['phpgw']->db->f('info_access'),
+'location'  =>$GLOBALS['phpgw']->db->f('info_location'),
+'category'  =>$GLOBALS['phpgw']->db->f('info_cat'),
+'priority'  =>  $GLOBALS['phpgw']->db->f('list_descripcion'),
+'padre'  => $GLOBALS['phpgw']->db->f('info_id_parent')
+				);
+
+
+			}
+		
+
+
+			
+			return $retval;
+		}
+
+
+
+
+
+
+
+
+
+
+
+function update()
+		{
+
+
+
+#  $vars = get_defined_vars();  
+#   print_r($vars);
+/*foreach($_POST as $k=>$v){
+$$k=$v;
+echo $$k."<br>";
+} 
+foreach($_POST as $nombre_campo => $valor){
+   $asignacion = "\$" . $nombre_campo . "='" . $valor . "';";
+   echo $asignacion."<br>";
+} */
+
+
+
+
+unset($GLOBALS['phpgw_info']['flags']['noheader']);
+			unset($GLOBALS['phpgw_info']['flags']['nonavbar']);
+			$GLOBALS['phpgw_info']['flags']['app_header'] = $GLOBALS['phpgw_info']['apps']['infolog']['title'].' - '.lang('Search Results');
+			$GLOBALS['phpgw']->common->phpgw_header();
+
+			$error = '';
+
+			$matches = 0;
+
+
+
+
+
+list($dia,$mes,$ano)=split("/",$_POST['start']['str']);
+
+	$starttime = $this->bo->maketime2($_POST['start']['hour'],$_POST['start']['min'],0,$dia,$mes,$ano) - $GLOBALS['phpgw']->datetime->tz_offset;
+//echo $starttime;
+
+list($dia2,$mes2,$ano2)=split("/",$_POST['end']['str']);
+
+	$endtime = $this->bo->maketime2($_POST['end']['hour'],$_POST['end']['min'],0,$dia2,$mes2,$ano2) - $GLOBALS['phpgw']->datetime->tz_offset;
+//echo "gggg".$_POST['end2']['month'];
+if($_POST['end2']['str']['month']>0){
+list($dia3,$mes3,$ano3)=split("/",$_POST['end2']['str']);
+	$endtime2 = $this->bo->maketime2($_POST['end2']['hour'],$_POST['end2']['min'],0,$dia3,$mes3,$ano3) - $GLOBALS['phpgw']->datetime->tz_offset;
+}else{
+$endtime2=0;
+}
+ 
+			//$starttime = $this->bo->maketime2($starthora,$startmin,0,$startfecha) - $GLOBALS['phpgw']->datetime->tz_offset;
+echo "kkkk3".$_POST['exec']['accesso'];
+if($_REQUEST['infolog']['parent']==""){
+$parent=0;
+
+}else{
+$parent=$_REQUEST['infolog']['parent'];
+
+}
+		//	$endtime = $this->bo->maketime($event['end']) - $GLOBALS['phpgw']->datetime->tz_offset;
+if(isset($_POST['info']['id'])&&$_POST['info']['id']>0){
+			$sql = "update  phpgw_infolog set  info_type='".$_POST['exec']['info_type']."',info_cat='".$_POST['infolog']['category']."',info_from='".$_POST['participants'][0]."',info_subject='".$_POST['cal']['description1']."',info_des='".$_POST['cal']['description']."',info_status='".$_POST['status']."',info_percent=".$_POST['percent'].",info_responsible='".$_POST['participants1'][0]."',info_startdate=".$starttime.",info_enddate=".$endtime.",info_datecompleted=".$endtime2.",info_datemodified=".$GLOBALS['phpgw']->datetime->users_localtime.",info_owner=".$_POST['info']['owner'].",info_priority=".$_POST['priority'].",info_location='".$_POST['infolog']['location']."',info_id_parent=".$parent.",info_access='".$_POST['exec']['accesso']."'  where info_id='".$_POST['info']['id']."'";
+}else{
+			$sql = "insert into  phpgw_infolog (info_type,info_cat,info_from,info_subject,info_des,info_status,info_percent,info_responsible,info_startdate,info_enddate,info_datecompleted,info_datemodified,info_owner,info_priority,info_location,info_id_parent,info_access) values ('".$_POST['exec']['info_type']."',".$_POST['infolog']['category'].",'".$_POST['participants'][0]."','".$_POST['cal']['description1']."','".$_POST['cal']['description']."','".$_POST['status']."',".$_POST['percent'].",'".$_POST['participants1'][0]."',".$starttime.",".$endtime.",".$endtime2.",".$GLOBALS['phpgw']->datetime->users_localtime.",".$_POST['info']['owner'].",".$_POST['priority'].",'".$_POST['infolog']['location']."',".$parent.",'".$_POST['exec']['accesso']."')";
+}
+
+$GLOBALS['phpgw']->db->query($sql,__LINE__,__FILE__);	
+
+
+return "";
+		}
+
+
+
+
+function get_account_name($account_id,$valor)
+		{
+			$acct_type = $this->get_type($account_id);
+
+			/* jakjr: using justthese with ldap_search */
+			//$justthese = array("cn","uid","givenname","sn","gecos");
+
+			/* search the dn for the given uid */
+
+$justthese = array("uidnumber",  "cn",  "mail","gecos","dn");
+			//	$sri = @ldap_search($this->ds, "ou=usuarios,ou=cnti,dc=gob,dc=ve", ("(&(sn=*)(mail=*@*)(structuralObjectClass=inetOrgPerson)(deliveryMode=virtual))"), $justthese);
+
+			if(($acct_type == 'g') && $this->group_context)
+			{
+				$sri = @ldap_search($this->ds, $this->group_context, '(gidnumber=' . (int)$account_id . ')', $justthese);
+			}
+			else
+			{
+				$sri = @ldap_search($this->ds, $this->user_context, '(uidnumber=' . (int)$account_id . ')', $justthese);
+			}
+
+//echo $this->ds.",". $this->user_context.",". '(uidnumber=' . (int)$account_id . ')'.",". $justthese;
+			if(!$sri)
+				return False;
+			$allValues = ldap_get_entries($this->ds, $sri);
+$retorna="";
+			if($acct_type =='g')
+			{
+				if($valor=='gecos') {
+					$retorna=$GLOBALS['phpgw']->translation->convert($allValues[0]['gecos'][0],'utf-8');
+				}
+				if($valor=='uidnumber') {
+					$retorna=$GLOBALS['phpgw']->translation->convert($allValues[0]['uidnumber'][0],'utf-8');
+				}
+				if($valor=='cn') {
+					$retorna=$GLOBALS['phpgw']->translation->convert($allValues[0]['cn'][0],'utf-8');
+				}
+
+				if($valor=='mail') {
+					$retorna=$GLOBALS['phpgw']->translation->convert($allValues[0]['mail'][0],'utf-8');
+				}
+				if($valor=='dn') {
+					$retorna=$GLOBALS['phpgw']->translation->convert($allValues[0]['dn'][0],'utf-8');
+				}
+				
+			}
+			else
+			{
+				if($valor=='gecos') {
+					$retorna=$GLOBALS['phpgw']->translation->convert($allValues[0]['gecos'][0],'utf-8');
+				}
+				if($valor=='uidnumber') {
+					$retorna=$GLOBALS['phpgw']->translation->convert($allValues[0]['uidnumber'][0],'utf-8');
+				}
+				if($valor=='cn') {
+					$retorna=$GLOBALS['phpgw']->translation->convert($allValues[0]['cn'][0],'utf-8');
+				}
+
+				if($valor=='mail') {
+					$retorna=$GLOBALS['phpgw']->translation->convert($allValues[0]['mail'][0],'utf-8');
+				}
+				if($valor=='dn') {
+					$retorna=$GLOBALS['phpgw']->translation->convert($allValues[0]['dn'][0],'utf-8');
+				}
+				
+
+
+
+			}
+			return $retorna;
+		}
+
+
+
+
+
+function get_type($account_id)
+		{
+			$allValues = array();
+			/* jakjr: using justthese with ldap_search */
+			$sri = @ldap_search($this->ds, $this->user_context, '(&(uidnumber=' . (int)$account_id . ')(phpgwaccounttype=u))', array("phpgwaccounttype"));
+			if(!$sri)
+				return False;
+			$allValues = ldap_get_entries($this->ds, $sri);
+
+			if ($allValues[0]['phpgwaccounttype'][0])
+			{
+				return $allValues[0]['phpgwaccounttype'][0];
+			}
+
+			$allValues = array();
+			/* jakjr: using justthese with ldap_search */
+			$sri = @ldap_search($this->ds, $this->group_context, '(&(gidnumber=' . (int)$account_id . ')(phpgwaccounttype=g))', array("phpgwaccounttype"));
+			if(!$sri)
+				return False;
+			$allValues = ldap_get_entries($this->ds, $sri);
+
+			if ($allValues[0]['phpgwaccounttype'][0])
+			{
+				return $allValues[0]['phpgwaccounttype'][0];
+			}
+			return False;
+		}
+
+
+
+
+
+
+
+function select($id)
+		{
+
+
+			$sql = "select * from   phpgw_infolog where info_id='".$id."'";
+
+$GLOBALS['phpgw']->db->query($sql,__LINE__,__FILE__);	
+
+if( $GLOBALS['phpgw']->db->next_record() )
+			{
+
+
+			
+$retval =  array(
+					'id'    => $GLOBALS['phpgw']->db->f('info_id'),
+					'type'         => $GLOBALS['phpgw']->db->f('info_type'),
+					'asunto'         => $GLOBALS['phpgw']->db->f('info_subject'),
+					'descripcion'         => $GLOBALS['phpgw']->db->f('info_des'),
+					'fechainicio' => $GLOBALS['phpgw']->db->f('info_startdate'),
+					'fechafin'  => $GLOBALS['phpgw']->db->f('info_enddate'),
+'status'  => $GLOBALS['phpgw']->db->f('info_status'),
+'fechacompletada'  => $GLOBALS['phpgw']->db->f('info_datecompleted'),
+'propietario'  => $GLOBALS['phpgw']->db->f('info_from'),
+'responsable'  => $GLOBALS['phpgw']->db->f('info_responsible'),
+'owner'  => $GLOBALS['phpgw']->db->f('info_owner'),
+'percent'  => $GLOBALS['phpgw']->db->f('info_percent'),
+ 'access'  => $GLOBALS['phpgw']->db->f('info_access'),
+'location'  =>$GLOBALS['phpgw']->db->f('info_location'),
+'category'  =>$GLOBALS['phpgw']->db->f('info_cat'),
+'priority'  => $GLOBALS['phpgw']->db->f('info_priority'),
+'padre'  => $GLOBALS['phpgw']->db->f('info_id_parent')
+				);
+
+			}
+		
+//echo "iddd".$id."  56666 ".$retval['id'];
+
+
+return $retval;
+		
+		}
+
+
+function select_init()
+		{
+
+
+			
+$retval =  array(
+					'id'    => 0,
+					'type'         => '',
+					'asunto'         => '',
+					'descripcion'         => '',
+					'fechainicio' => $GLOBALS['phpgw']->datetime->users_localtime,
+					'fechafin'  =>$GLOBALS['phpgw']->datetime->users_localtime,
+'status'  => '',
+'fechacompletada'  => 0,
+'propietario'  => '',
+'responsable'  => '',
+'owner'  => $this->bo->owner,
+'percent'  => '',
+ 'access'  => 'public',
+'location'  =>'',
+'category'  =>0,
+'priority'  => 1,
+'padre'  => 0
+				);
+
+			
+
+
+return $retval;
+		
+		}
+
+
+
+
+
+}
+
+?>
Index: /contrib/ProjectManager/inc/class.soinfolog.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.soinfolog.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.soinfolog.inc.php	(revision 3594)
@@ -0,0 +1,432 @@
+<?php
+  /**************************************************************************\
+  * eGroupWare - infolog                                                    *
+  * http://www.eGroupWare.org                                                *
+  * Maintained and further developed by RalfBecker@outdoor-training.de       *
+  * Based on Webinfolog by Craig Knudsen <cknudsen@radix.net>               *
+  *          http://www.radix.net/~cknudsen                                  *
+  * Originaly modified by Mark Peters <skeeter@phpgroupware.org>             *
+  * --------------------------------------------                             *
+  *  This program is free software; you can redistribute it and/or modify it *
+  *  under the terms of the GNU General Public License as published by the   *
+  *  Free Software Foundation; either version 2 of the License, or (at your  *
+  *  option) any later version.                                              *
+  \**************************************************************************/
+
+
+	class soinfolog
+	{
+//		var $debug = True;
+		var $debug = False;
+		var $cal;
+		var $db;
+		var $owner;
+		var $g_owner;
+		var $is_group = False;
+		var $datetime;
+		var $filter;
+		var $cat_id;
+		
+		function soinfolog($param)
+		{
+			$this->db = $GLOBALS['phpgw']->db;
+			if(!is_object($GLOBALS['phpgw']->datetime))
+			{
+				$GLOBALS['phpgw']->datetime = createobject('phpgwapi.date_time');
+			}
+
+			$this->owner = (!isset($param['owner']) || $param['owner'] == 0?$GLOBALS['phpgw_info']['user']['account_id']:$param['owner']);
+			$this->filter = (isset($param['filter']) && $param['filter'] != ''?$param['filter']:$this->filter);
+			$this->cat_id = (isset($param['category']) && $param['category'] != ''?$param['category']:$this->cat_id);
+			if(isset($param['g_owner']) && is_array($param['g_owner']))
+			{
+				$this->is_group = True;
+				$this->g_owner = $param['g_owner'];
+			}
+			if($this->debug)
+			{
+				echo '<!-- SO Filter : '.$this->filter.' -->'."\n";
+				echo '<!-- SO cat_id : '.$this->cat_id.' -->'."\n";
+			}
+			$this->cal = CreateObject('infolog.soinfolog_');
+			//$this->open_box($this->owner);
+		}
+		
+		// It returns uidNumber and cn ( Retorna o uidNumber e o cn )
+		function search_uidNumber($mail)
+		{
+			$connection = $GLOBALS['phpgw']->common->ldapConnect();
+			$justthese = array("uidNumber","cn","mail");
+			$search = ldap_search($connection, $GLOBALS['phpgw_info']['server']['ldap_context'], "mail=" . $mail, $justthese);
+			$result = ldap_get_entries($connection, $search);
+			ldap_close($connection);
+			return $result;
+		}
+
+		
+
+		function maketime($time)
+		{
+			return mktime($time['hour'],$time['min'],$time['sec'],$time['month'],$time['mday'],$time['year']);
+		}
+
+		function read_entry($id)
+		{
+			return $this->cal->get_event_ids($id);
+		}
+
+		function list_events($startYear,$startMonth,$startDay,$endYear=0,$endMonth=0,$endDay=0,$owner_id=0)
+		{
+			$extra = '';
+			$extra .= (strpos($this->filter,'private')?'AND phpgw_cal.is_public=0 ':'');
+			//$extra .= ($this->cat_id?"AND phpgw_cal.category like '%".$this->cat_id."%' ":'');
+			if ($this->cat_id)
+			{
+				if (!is_object($GLOBALS['phpgw']->categories))
+				{
+					$GLOBALS['phpgw']->categories = CreateObject('phpgwapi.categories');
+				}
+				$cats = $GLOBALS['phpgw']->categories->return_all_children($this->cat_id);
+				$extra .= "AND (phpgw_cal.category".(count($cats) > 1 ? " IN ('".implode("','",$cats)."')" : '=\''.(int)$this->cat_id."'");
+				foreach($cats as $cat)
+				{
+					$extra .= " OR phpgw_cal.category LIKE '$cat,%' OR phpgw_cal.category LIKE '%,$cat,%' OR phpgw_cal.category LIKE '%,$cat'";
+				}
+				$extra .= ') ';
+			}
+			if($owner_id)
+			{
+				return $this->cal->list_events($startYear,$startMonth,$startDay,$endYear,$endMonth,$endDay,$extra,$GLOBALS['phpgw']->datetime->tz_offset,$owner_id);
+			}
+			else
+			{
+				return $this->cal->list_events($startYear,$startMonth,$startDay,$endYear,$endMonth,$endDay,$extra,$GLOBALS['phpgw']->datetime->tz_offset);
+			}
+		}
+
+		function list_repeated_events($syear,$smonth,$sday,$eyear,$emonth,$eday,$owner_id=0)
+		{
+			if(!$owner_id)
+			{
+				$owner_id = $this->is_group ? $this->g_owner : $this->owner;
+			}
+			if($GLOBALS['phpgw_info']['server']['infolog_type'] != 'sql' ||
+				!count($owner_id))	// happens with empty groups
+			{
+				return Array();
+			}
+
+			$starttime = mktime(0,0,0,$smonth,$sday,$syear) - $GLOBALS['phpgw']->datetime->tz_offset;
+			$endtime = mktime(23,59,59,$emonth,$eday,$eyear) - $GLOBALS['phpgw']->datetime->tz_offset;
+			$sql = "AND phpgw_cal.cal_type='M' AND phpgw_cal_user.cal_login IN (".
+				(is_array($owner_id) ? implode(',',$owner_id) : $owner_id).')';
+
+//			$member_groups = $GLOBALS['phpgw']->accounts->membership($this->user);
+//			@reset($member_groups);
+//			while(list($key,$group_info) = each($member_groups))
+//			{
+//				$member[] = $group_info['account_id'];
+//			}
+//			@reset($member);
+//			$sql .= ','.implode(',',$member).') ';
+//			$sql .= 'AND (phpgw_cal.datetime <= '.$starttime.') ';
+//			$sql .= 'AND (((phpgw_cal_repeats.recur_enddate >= '.$starttime.') AND (phpgw_cal_repeats.recur_enddate <= '.$endtime.')) OR (phpgw_cal_repeats.recur_enddate=0))) '
+			$sql .= ' AND (phpgw_cal_repeats.recur_enddate >= '.$starttime.' OR phpgw_cal_repeats.recur_enddate=0) '
+				. (strpos($this->filter,'private')?'AND phpgw_cal.is_public=0 ':'')
+				. ($this->cat_id?"AND phpgw_cal.category like '%".$this->cat_id."%' ":'')
+				. 'ORDER BY phpgw_cal.datetime ASC, phpgw_cal.edatetime ASC, phpgw_cal.priority ASC';
+
+			if($this->debug)
+			{
+				echo '<!-- SO list_repeated_events : SQL : '.$sql.' -->'."\n";
+			}
+
+			return $this->get_event_ids(True,$sql);
+		}
+
+		function list_events_keyword($keywords,$members='')
+		{
+			if (!$members)
+			{
+				$members[] = $this->owner;
+			}
+			$sql = 'AND (phpgw_cal_user.cal_login IN ('.implode(',',$members).')) AND '.
+				'(phpgw_cal_user.cal_login=' . (int)$this->owner . ' OR phpgw_cal.is_public=1) AND (';
+
+			$words = split(' ',$keywords);
+			foreach($words as $i => $word)
+			{
+				$sql .= $i > 0 ? ' OR ' : '';
+				$sql .= "(UPPER(phpgw_cal.title) LIKE UPPER('%".addslashes($word)."%') OR "
+						. "UPPER(phpgw_cal.description) LIKE UPPER('%".addslashes($word)."%') OR "
+						. "UPPER(phpgw_cal.location) LIKE UPPER('%".addslashes($word)."%') OR "
+						. "UPPER(phpgw_cal_extra.cal_extra_value) LIKE UPPER('%".addslashes($word)."%'))";
+			}
+			$sql .= ') ';
+
+			$sql .= (strpos($this->filter,'private')?'AND phpgw_cal.is_public=0 ':'');
+			$sql .= ($this->cat_id? "AND (phpgw_cal.category='$this->cat_id' OR phpgw_cal.category like '%,".$this->cat_id.",%') ":'');
+			$sql .= 'ORDER BY phpgw_cal.datetime DESC, phpgw_cal.edatetime DESC, phpgw_cal.priority ASC';
+
+			return $this->get_event_ids(False,$sql,True);
+		}
+
+		function read_from_store($startYear,$startMonth,$startDay,$endYear='',$endMonth='',$endDay='')
+		{
+			$events = $this->list_events($startYear,$startMonth,$startDay,$endYear,$endMonth,$endDay);
+			$events_cached = Array();
+			for($i=0;$i<count($events);$i++)
+			{
+				$events_cached[] = $this->read_entry($events[$i]);
+			}
+			return $events_cached;
+		}
+
+		function get_event_ids($search_repeats=False, $sql='',$search_extra=False)
+		{
+			return $this->cal->get_event_ids($search_repeats,$sql,$search_extra);
+		}
+
+		function find_uid($uid)
+		{
+			$sql = " AND (phpgw_cal.uid = '".$uid."') ";
+
+			$found = $this->cal->get_event_ids(False,$sql);
+			if(!$found)
+			{
+				$found = $this->cal->get_event_ids(True,$sql);
+			}
+			if(is_array($found))
+			{
+				return $found[0];
+			}
+			else
+			{
+				return False;
+			}
+		}
+
+		function add_entry(&$event)
+		{
+			return $this->cal->save_event($event);
+		}
+
+		function save_alarm($cal_id,$alarm,$id=0)
+		{
+			return $this->cal->save_alarm($cal_id,$alarm,$id);
+		}
+
+		function delete_alarm($id)
+		{
+			return $this->cal->delete_alarm($id);
+		}
+
+		function delete_alarms($cal_id)
+		{
+			return $this->cal->delete_alarms($cal_id);
+		}
+
+		function delete_entry($id)
+		{
+			return $this->cal->delete_event($id);
+		}
+
+		function expunge()
+		{
+			$this->cal->expunge();
+		}
+
+		function delete_infolog($owner)
+		{
+			$this->cal->delete_infolog($owner);
+		}
+
+		function change_owner($account_id,$new_owner)
+		{
+			if($GLOBALS['phpgw_info']['server']['infolog_type'] == 'sql')
+			{
+				$db2 = $this->cal->stream;
+				$this->cal->stream->query('SELECT cal_id FROM phpgw_cal_user WHERE cal_login='.$account_id,__LINE__,__FILE__);
+				while($this->cal->stream->next_record())
+				{
+					$id = $this->cal->stream->f('cal_id');
+					$db2->query('SELECT count(*) FROM phpgw_cal_user WHERE cal_id='.$id.' AND cal_login='.$new_owner,__LINE__,__FILE__);
+					$db2->next_record();
+					if($db2->f(0) == 0)
+					{
+						$db2->query('UPDATE phpgw_cal_user SET cal_login='.$new_owner.' WHERE cal_id='.$id.' AND cal_login='.$account_id,__LINE__,__FILE__);
+					}
+					else
+					{
+						$db2->query('DELETE FROM phpgw_cal_user WHERE cal_id='.$id.' AND cal_login='.$account_id,__LINE__,__FILE__);
+					}
+				}
+				$this->cal->stream->query('UPDATE phpgw_cal SET owner='.$new_owner.' WHERE owner='.$account_id,__LINE__,__FILE__);
+			}
+		}
+
+		function set_status($id,$status)
+		{
+			$this->cal->set_status($id,$this->owner,$status);
+		}
+
+		function get_alarm($cal_id)
+		{
+			if (!method_exists($this->cal,'get_alarm'))
+			{
+				return False;
+			}
+			return $this->cal->get_alarm($cal_id);
+		}
+
+		function read_alarm($id)
+		{
+			if (!method_exists($this->cal,'read_alarm'))
+			{
+				return False;
+			}
+			return $this->cal->read_alarm($id);
+		}
+
+		function read_alarms($cal_id)
+		{
+			if (!method_exists($this->cal,'read_alarms'))
+			{
+				return False;
+			}
+			return $this->cal->read_alarms($cal_id);
+		}
+
+		function find_recur_exceptions($event_id)
+		{
+			if($GLOBALS['phpgw_info']['server']['infolog_type'] == 'sql')
+			{
+				$arr = Array();
+				$this->cal->query('SELECT datetime FROM phpgw_cal WHERE reference='.$event_id,__LINE__,__FILE__);
+				if($this->cal->num_rows())
+				{
+					while($this->cal->next_record())
+					{
+						$arr[] = (int)$this->cal->f('datetime');
+					}
+				}
+				if(count($arr) == 0)
+				{
+					return False;
+				}
+				else
+				{
+					return $arr;
+				}
+			}
+			else
+			{
+				return False;
+			}
+		}
+
+		/* Begin mcal equiv functions */
+		function get_cached_event()
+		{
+			return $this->cal->event;
+		}
+		
+		function add_attribute($var,$value,$element='**(**')
+		{
+			$this->cal->add_attribute($var,$value,$element);
+		}
+
+		function event_init()
+		{
+			$this->cal->event_init();
+		}
+
+		function set_date($element,$year,$month,$day=0,$hour=0,$min=0,$sec=0)
+		{
+			$this->cal->set_date($element,$year,$month,$day,$hour,$min,$sec);
+		}
+
+		function set_start($year,$month,$day=0,$hour=0,$min=0,$sec=0)
+		{
+			$this->cal->set_start($year,$month,$day,$hour,$min,$sec);
+		}
+
+		function set_end($year,$month,$day=0,$hour=0,$min=0,$sec=0)
+		{
+			$this->cal->set_end($year,$month,$day,$hour,$min,$sec);
+		}
+
+		function set_title($title='')
+		{
+			$this->cal->set_title($title);
+		}
+function set_access($access='')
+		{
+			$this->cal->set_access($access);
+		}
+
+		function set_description($description='')
+		{
+			$this->cal->set_description($description);
+		}
+function set_description1($description1='')
+		{
+			$this->cal->set_description1($description1);
+		}
+function set_participants1($participants1='')
+		{
+			$this->cal->set_participants1($participants1);
+		}
+		function set_ex_participants($ex_participants='')
+		{
+			$this->cal->set_ex_participants($ex_participants);
+		}
+
+		function set_class($class)
+		{
+			$this->cal->set_class($class);
+		}
+
+		function set_category($category='')
+		{
+			$this->cal->set_category($category);
+		}
+
+		function set_alarm($alarm)
+		{
+			$this->cal->set_alarm($alarm);
+		}
+
+		function set_recur_none()
+		{
+			$this->cal->set_recur_none();
+		}
+
+		function set_recur_daily($year,$month,$day,$interval)
+		{
+			$this->cal->set_recur_daily($year,$month,$day,$interval);
+		}
+
+		function set_recur_weekly($year,$month,$day,$interval,$weekdays)
+		{
+			$this->cal->set_recur_weekly($year,$month,$day,$interval,$weekdays);
+		}
+
+		function set_recur_monthly_mday($year,$month,$day,$interval)
+		{
+			$this->cal->set_recur_monthly_mday($year,$month,$day,$interval);
+		}
+
+		function set_recur_monthly_wday($year,$month,$day,$interval)
+		{
+			$this->cal->set_recur_monthly_wday($year,$month,$day,$interval);
+		}
+
+		function set_recur_yearly($year,$month,$day,$interval)
+		{
+			$this->cal->set_recur_yearly($year,$month,$day,$interval);
+		}
+		
+		/* End mcal equiv functions */
+	}
+?>
Index: /contrib/ProjectManager/inc/class.uiprojectmanager.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.uiprojectmanager.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.uiprojectmanager.inc.php	(revision 3594)
@@ -0,0 +1,646 @@
+<?php
+/**
+ * ProjectManager - Projects user interface
+ *
+ * @link http://www.egroupware.org
+ * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @package projectmanager
+ * @copyright (c) 2005 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @version $Id: class.uiprojectmanager.inc.php 23741 2007-04-30 10:29:44Z lkneschke $ 
+ */
+
+include_once(PHPGW_INCLUDE_ROOT.'/projectmanager/inc/class.boprojectmanager.inc.php');
+//include_once(PHPGW_INCLUDE_ROOT.'/etemplate/inc/class.uietemplate.inc.php');
+
+/**
+ * ProjectManager UI: list and edit projects
+ */
+class uiprojectmanager extends boprojectmanager 
+{
+	/**
+	 * Functions to call via menuaction
+	 * 
+	 * @var array
+	 */
+	var $public_functions = array(
+		'index' => true,
+		'edit'  => true,
+		'view'  => true,
+	);
+	/**
+	 * Labels for pm_status, value - label pairs
+	 * 
+	 * @var array
+	 */
+	var $status_labels;
+	/**
+	 * Labels for pm_access, value - label pairs
+	 * 
+	 * @var array
+	 */
+	var $access_labels;
+	/**
+	 * Labels for mains- & sub-projects filter
+	 * 
+	 * @var array
+	 */
+	var $filter_labels;
+
+	/**
+	 * Constructor, calls the constructor of the extended class
+	 * 
+	 * @return uiprojectmanager
+	 */
+	function uiprojectmanager()
+	{
+		$this->boprojectmanager();
+		
+		$this->status_labels = array(
+			'active'    => lang('Active'),
+			'nonactive' => lang('Nonactive'),
+			'archive'   => lang('Archive'),
+			'template'  => lang('Template'),
+		);
+		$this->access_labels = array(
+			'public'    => lang('Public'),
+			'anonym'    => lang('Anonymous public'),
+			'private'   => lang('Private'),
+		);
+		$this->filter_labels = array(
+			''			=> lang('All'),
+			'mains'		=> lang('Mainprojects'),
+			'subs'		=> lang('Subprojects'),
+		);
+	}
+	
+	
+	/**
+	 * View a project
+	 */
+	function view()
+	{
+		$this->edit(null,true);
+	}
+	
+	/**
+	 * Edit, add or view a project
+	 *
+	 * @var array $content content-array if called by process-exec
+	 * @var boolean $view only view project, default false, only used on first call !is_array($content)
+	 */
+	function edit($content=null,$view=false)
+	{
+		if ((int) $this->debug >= 1 || $this->debug == 'edit') $this->debug_message("uiprojectmanager::edit(,$view) content=".print_r($content,true));
+
+		$tpl =& new etemplate('projectmanager.edit');
+
+		if (is_array($content))
+		{
+			if ($content['cancel'])
+			{
+				$tpl->location(array(
+					'menuaction' => $content['referer'],
+				));
+			}
+			if ($content['pm_id'])
+			{
+				$this->read($content['pm_id']);
+			}
+			else
+			{
+				$this->init();
+			}
+			$view = $content['view'] && !$content['edit'] || !$this->check_acl(PHPGW_ACL_EDIT);
+			
+			if (!$content['view'])	// just changed to edit-mode, still counts as view
+			{
+				//echo "content="; _debug_array($content);
+				$this->data_merge($content);
+				//echo "after data_merge data="; _debug_array($this->data);
+
+				// set the data of the pe_summary, if the project-value is unset
+				$pe_summary = $this->pe_summary();
+				$datasource =& CreateObject('projectmanager.datasource'); 
+				foreach($datasource->name2id as $pe_name => $id)
+				{
+					$pm_name = str_replace('pe_','pm_',$pe_name);
+					// check if update is necessary, because a field has be set or changed
+					if (($content[$pm_name] || $pm_name == 'pm_completion' && $content[$pm_name] !== '') &&
+						($content[$pm_name] != $this->data[$pm_name] || !($this->data['pm_overwrite'] & $id)))
+					{
+						//echo "$pm_name set to '".$this->data[$pm_name]."'<br>\n";
+						$this->data['pm_overwrite'] |= $id;
+					}
+					// or check if a field is no longer set, or the datasource changed => set it from the datasource
+					elseif ((!$content[$pm_name] || $pm_name == 'pm_completion' && $content[$pm_name] === '') &&
+						    ($this->data['pm_overwrite'] & $id) || 
+						    !($this->data['pm_overwrite'] & $id) && $this->data[$pm_name] != $pe_summary[$pe_name])
+					{
+						// if we have a change in the datasource, set pe_synced
+						if ($this->data[$pm_name] != $pe_summary[$name])
+						{
+							$this->data['pm_synced'] = $this->now_su;
+						}
+						$this->data[$pm_name] = $pe_summary[$pe_name];
+						//echo "$pm_name re-set to default '".$this->data[$pm_name]."'<br>\n";
+						$this->data['pm_overwrite'] &= ~$id;
+					}
+				}
+				// process new and changed project-members
+				foreach((array)$content['member'] as $n => $uid)
+				{
+					if (!(int) $content['role'][$n])
+					{
+						unset($this->data['pm_members'][$uid]);
+					}
+					elseif ((int) $uid)
+					{
+						$this->data['pm_members'][(int)$uid] = array(
+							'member_uid' => (int) $uid,
+							'member_availibility' => empty($content['availibility'][$n]) ? 100.0 : $content['availibility'][$n],
+							'role_id'    => (int) $content['role'][$n],
+						);
+						if ($GLOBALS['phpgw_info']['user']['apps']['admin'] && $content['general_avail'][$n])
+						{
+							$this->set_availibility($uid,$content['general_avail'][$n]);
+						}
+					}
+				}
+			}
+			//echo "uiprojectmanager::edit(): data="; _debug_array($this->data);
+
+			if (($content['save'] || $content['apply']) && $this->check_acl(PHPGW_ACL_EDIT))
+			{
+				// generate project-number, taking into account a given parent-project
+				if (empty($this->data['pm_number'])) 
+				{
+					$parent_number = '';
+					if ($content['add_link'])
+					{
+						list($app,$app_id) = explode(':',$content['add_link'],2);
+						if ($app == 'projectmanager' && ($parent = $this->search(array('pm_id'=>$app_id),'pm_number')))
+						{
+							$parent_number = $parent[0]['pm_number'];
+						}
+					}						
+					$this->generate_pm_number(true,$parent_number);
+				}				
+				if ($this->not_unique())
+				{
+					$msg = lang('Error: project-ID already exist, choose an other one or have one generated by leaving it emtpy !!!');
+					unset($content['save']);	// dont exit
+				}
+				elseif ($this->save() != 0)
+				{
+					$msg = lang('Error: saving the project (%1) !!!',$this->db->Error);
+					unset($content['save']);	// dont exit
+				}
+				else
+				{
+					$msg = lang('Project saved');
+
+					// create project already linked to a parent, in that case param of link-call need to be swaped
+					// as we want the new project to be the sub of the given project
+					if ($content['add_link'])
+					{
+						list($app,$app_id) = explode(':',$content['add_link'],2);
+						$this->link->link($app,$app_id,'projectmanager',$this->data['pm_id']);
+					}
+					// writing links for new entry, existing ones are handled by the widget itself
+					if (!$content['pm_id'] && is_array($content['link_to']['to_id']))	
+					{
+						$this->link->link('projectmanager',$this->data['pm_id'],$content['link_to']['to_id']);
+					}
+					if ($content['template'] && $this->copy($content['template'],2))
+					{
+						$msg = lang('Template including elment-tree saved as new project');
+						unset($content['template']);
+					}
+				}
+			}
+			if ($content['save'])
+			{
+				$tpl->location(array(
+					'menuaction' => $content['referer'],
+					'msg'        => $msg,
+				));
+			}
+			if ($content['delete'] && $this->check_acl(PHPGW_ACL_DELETE))
+			{
+				// all delete are done by index
+				return $this->index(array('nm'=>array('rows'=>array(
+					'delete' => array($this->data['pm_id']=>true)
+				))));
+			}
+			$referer = $content['referer'];
+			$template = $content['template'];
+		}
+		else
+		{
+			$referer = preg_match('/menuaction=([^&]+)/',$_SERVER['HTTP_REFERER'],$matches) ? $matches[1] : 'projectmanager.uiprojectmanager.index';
+
+			if ((int) $_GET['pm_id'])
+			{
+				$this->read((int) $_GET['pm_id']);
+			}
+			// for a new sub-project set some data from the parent
+			elseif ($_GET['link_app'] == 'projectmanager' && (int) $_GET['link_id'] && $this->read((int) $_GET['link_id']))
+			{
+				if (!$this->check_acl(PHPGW_ACL_READ))	// no read-rights for the parent, eg. someone edited the url
+				{
+					$tpl->location(array(
+						'menuaction' => $referer,
+						'msg' => lang('Permission denied !!!'),
+					));
+				}
+				else
+				{
+					$this->generate_pm_number(true,$this->data['pm_number']);
+					foreach(array('pm_id','pm_title','pm_description','pm_creator','pm_created','pm_modified','pm_modifier','pm_real_start','pm_real_end','pm_completion','pm_status','pm_used_time','pm_planned_time','pm_used_budget','pm_planned_budget') as $key)
+					{
+						unset($this->data[$key]);
+					}
+					include_once(PHPGW_INCLUDE_ROOT.'/projectmanager/inc/class.datasource.inc.php');
+					$this->data['pm_overwrite'] &= PM_PLANNED_START | PM_PLANNED_END;
+				}
+			}
+			elseif((int)$_GET['template'] && $this->read((int) $_GET['template']))
+			{
+				if (!$this->check_acl(PHPGW_ACL_READ))	// no read-rights for the template, eg. someone edited the url
+				{
+					$tpl->location(array(
+						'menuaction' => $referer,
+						'msg' => lang('Permission denied !!!'),
+					));
+				}
+				// we do only stage 1 of the copy, so if the user hits cancel everythings Ok
+				$this->copy($template = (int) $_GET['template'],1);
+			}
+			if ($this->data['pm_id'])
+			{
+				if (!$this->check_acl(PHPGW_ACL_READ))
+				{
+					$tpl->location(array(
+						'menuaction' => $referer,
+						'msg' => lang('Permission denied !!!'),
+					));
+				}
+				if (!$this->check_acl(PHPGW_ACL_EDIT)) $view = true;
+			}
+			// no pm-number set, generate one
+			if (!$this->data['pm_number']) $this->generate_pm_number(true);
+		}
+		if (!$pe_summary) $pe_summary = $this->pe_summary();
+
+		if (!isset($content['add_link']) && !$this->data->pm_id && isset($_GET['link_app']) && isset($_GET['link_id']) &&
+			preg_match('/^[a-z_0-9-]+:[:a-z_0-9-]+$/i',$_GET['link_app'].':'.$_GET['link_id']))	// gard against XSS
+		{
+			$add_link = $_GET['link_app'].':'.$_GET['link_id'];
+		}
+		else
+		{
+			$add_link = $content['add_link'];
+		}
+		$content = $this->data + array(
+			'msg'  => $msg,
+			'general|description|members|accounting|custom|links' => $content['general|description|members|accounting|custom|links'],
+			'view' => $view,
+			'ds'   => $pe_summary,
+			'link_to' => array(
+				'to_id' => $content['link_to']['to_id'] ? $content['link_to']['to_id'] : $this->data['pm_id'],
+				'to_app' => 'projectmanager',
+			),
+			'duration_format' => ','.$this->config['duration_format'],
+			'no_budget' => !$this->check_acl(PHPGW_ACL_BUDGET,0,true) || in_array($this->data['pm_accounting_type'],array('status','times')),
+		);
+		if ($add_link && !is_array($content['link_to']['to_id']))
+		{
+			list($app,$app_id) = explode(':',$add_link,2);
+			$this->link->link('projectmanager',$content['link_to']['to_id'],$app,$app_id);
+		}
+		$content['links'] = $content['link_to'];
+
+		// empty not explicitly in the project set values
+		if (!is_object($datasource)) $datasource =& CreateObject('projectmanager.datasource'); 
+		foreach($datasource->name2id as $pe_name => $id)
+		{
+			$pm_name = str_replace('pe_','pm_',$pe_name);
+			if (!($this->data['pm_overwrite'] & $id) && $pm_name != 'pm_title')
+			{
+				$content[$pm_name] = $preserv[$pm_name] = '';
+			}
+		}
+		$readonlys = array(
+			'delete' => !$this->data['pm_id'] || !$this->check_acl(PHPGW_ACL_DELETE),
+			'edit' => !$view || !$this->check_acl(PHPGW_ACL_EDIT),
+			'general|description|members|accounting|custom|links' => array(
+				'accounting' => !$this->check_acl(PHPGW_ACL_BUDGET) &&	// disable the tab, if no budget rights and no owner or coordinator
+					($this->config['accounting_types'] && count(explode(',',$this->config['accounting_types'])) == 1 ||
+					!($this->data['pm_creator'] == $this->user || $this->data['pm_members'][$this->user]['role_id'] == 1)),	
+				'custom' => !count($this->customfields),	// only show customfields tab, if there are some
+			),
+			'customfields' => $view,
+			'general_avail[1]' => !$GLOBALS['phpgw_info']['user']['apps']['admin'],
+		);
+		if (!$this->check_acl(PHPGW_ACL_EDIT_BUDGET))
+		{
+			$readonlys['pm_planned_budget'] = $readonlys['pm_used_budget'] = true;
+		}
+		$n = 2;
+		foreach((array)$this->data['pm_members'] as $uid => $data)
+		{
+			$content['role'][$n] = $data['role_id'];
+			$content['member'][$n] = $data['member_uid'];
+			$content['availibility'][$n] = empty($data['member_availibility']) ? 100.0 : $data['member_availibility'];
+			if (!is_array($general_avail)) $general_avail = $this->get_availibility();
+			$content['general_avail'][$n] = empty($general_avail[$uid]) ? 100.0 : $general_avail[$uid];
+			$readonlys["general_avail[$n]"] = $view || !$GLOBALS['phpgw_info']['user']['apps']['admin'];
+			$readonlys["role[$n]"] = $readonlys["availibility[$n]"] = $view;
+			++$n;
+		}
+		//_debug_array($content);
+		$preserv = $this->data + array(
+			'view'     => $view,
+			'add_link' => $add_link,
+			'member'   => $content['member'],
+			'referer'  => $referer,
+			'template' => $template,
+		);
+		$this->instanciate('roles');
+
+		$sel_options = array(
+			'pm_status' => &$this->status_labels,
+			'pm_access' => &$this->access_labels,
+			'role'      => $this->roles->query_list(array(
+				'label' => 'role_title',
+				'title' => 'role_description',
+			),'role_id',array(
+				'pm_id' => array(0,(int)$this->data['pm_id'])
+			)),
+			'pm_accounting_type' => array(
+				'status' => 'No accounting, only status',
+				'times'  => 'No accounting, only times and status',
+				'budget' => 'Budget (no pricelist)',
+				'pricelist' => 'Budget and pricelist',
+			),
+		);
+		if ($this->config['accounting_types'])	// only allow the configured types
+		{
+			$allowed = explode(',',$this->config['accounting_types']);
+			foreach($sel_options['pm_accounting_type'] as $key => $label)
+			{
+				if (!in_array($key,$allowed)) unset($sel_options['pm_accounting_type'][$key]);
+			}
+			if (count($sel_options['pm_accounting_type']) == 1)
+			{
+				$readonlys['pm_accounting_type'] = true;
+			}
+		}
+		if ($view)
+		{
+			foreach($this->db_cols as $name)
+			{
+				$readonlys[$name] = true;
+			}
+			$readonlys['save'] = $readonlys['apply'] = true;
+
+			// add fields not stored in the main-table
+			$readonlys['pm_members'] = $readonlys['edit_roles'] = true;
+			
+			$readonlys['links'] = $readonlys['link_to'] = true;
+		}
+		$GLOBALS['phpgw_info']['flags']['app_header'] = lang('projectmanager') . ' - ' . 
+			($this->data['pm_id'] ? ($view ? lang('View project') : lang('Edit project')) : lang('Add project'));
+		$tpl->exec('projectmanager.uiprojectmanager.edit',$content,$sel_options,$readonlys,$preserv);		
+	}
+
+	/**
+	 * query projects for nextmatch in the projects-list
+	 *
+	 * reimplemented from so_sql to disable action-buttons based on the acl and make some modification on the data
+	 *
+	 * @param array $query
+	 * @param array &$rows returned rows/cups
+	 * @param array &$readonlys eg. to disable buttons based on acl
+	 */
+	function get_rows(&$query_in,&$rows,&$readonlys)
+	{
+		$GLOBALS['phpgw']->session->appsession('project_list','projectmanager',$query=$query_in);
+
+		// handle nextmatch filters like col_filters
+		foreach(array('cat_id' => 'cat_id','filter2' => 'pm_status') as $nm_name => $pm_name)
+		{
+			unset($query['col_filter'][$pm_name]);
+			if ($query[$nm_name]) $query['col_filter'][$pm_name] = $query[$nm_name];
+		}
+		$query['col_filter']['subs_or_mains'] = $query['filter'];
+
+		$total = parent::get_rows($query,$rows,$readonlys,true,true);
+		
+		$this->instanciate('roles');
+		
+		$readonlys = array();
+		foreach($rows as $n => $val)
+		{
+			$row =& $rows[$n];
+			if (!$this->check_acl(PHPGW_ACL_EDIT,$row))
+			{
+				$readonlys["edit[$row[pm_id]]"] = true;
+			}
+			if (!$this->check_acl(PHPGW_ACL_DELETE,$row))
+			{
+				$readonlys["delete[$row[pm_id]]"] = true;
+			}
+			$pm_ids[] = $row['pm_id'];
+			
+			if (!($row['role_acl'] & PHPGW_ACL_BUDGET))
+			{
+				unset($row['pm_used_budget']);
+				unset($row['pm_planned_budget']);
+			}
+		}
+		$roles = $this->roles->query_list();
+		// query the project-members only, if user choose to display them
+		if ($pm_ids && @strstr($GLOBALS['phpgw_info']['user']['preferences']['projectmanager']['nextmatch-projectmanager.list.rows'],',role') !== false)
+		{
+			$all_members = $this->read_members($pm_ids);
+			foreach($rows as $n => $val)
+			{
+				$row =& $rows[$n];
+				$members = $row['pm_members'] = $all_members[$row['pm_id']];
+				if (!$members) continue;
+				
+				foreach($members as $uid => $data)
+				{
+					if (($pos = array_search($data['role_id'],array_keys($roles))) !== false)
+					{
+						$row['role'.$pos][] = $uid;
+					}
+				}
+			}
+		}
+		//_debug_array($rows);
+		if ((int) $this->debug >= 2 || $this->debug == 'get_rows')
+		{
+			$this->debug_message("uiprojectmanager::get_rows(".print_r($query,true).") total=$total, rows =".print_r($rows,true)."\nreadonlys=".print_r($readonlys,true));
+		}
+		$rows['roles'] = array_values($roles);
+		for($i = count($roles); $i < 5; ++$i)
+		{
+			$rows['no_role'.$i] = true;
+		}
+		// disable time & budget columns if pm is configures for status or status and time only
+		if ($this->config['accounting_types'] == 'status')
+		{
+			$rows['no_pm_used_time_pm_planned_time'] = true;
+			$rows['no_pm_used_budget_pm_planned_budget'] = true;
+			$query_in['options-selectcols']['pm_used_time'] = $query_in['options-selectcols']['pm_planned_time'] = false;
+			$query_in['options-selectcols']['pm_used_budget'] = $query_in['options-selectcols']['pm_planned_budget'] = false;
+		}
+		if ($this->config['accounting_types'] == 'status,times')
+		{
+			$rows['no_pm_used_budget_pm_planned_budget'] = true;
+			$query_in['options-selectcols']['pm_used_budget'] = $query_in['options-selectcols']['pm_planned_budget'] = false;
+		}
+		$rows['duration_format'] = ','.$this->config['duration_format'].',,1';
+
+		return $total;		
+	}
+
+	/**
+	 * List existing projects
+	 *
+	 * @param array $content=null
+	 * @param string $msg=''
+	 */
+	function index($content=null,$msg='')
+	{
+		if ((int) $this->debug >= 1 || $this->debug == 'index') $this->debug_message("uiprojectmanager::index(,$msg) content=".print_r($content,true));
+
+
+
+		//$tpl =& new etemplate('projectmanager.list');
+
+		if ($_GET['msg']) $msg = $_GET['msg'];
+
+		if ($content['add'])
+		{
+			$tpl->location(array(
+				'menuaction' => 'projectmanager.uiprojectmanager.edit',
+				'template'   => $content['template'],
+			));
+		}
+		if ($content['delete_checked'] || $content['gantt_checked'])
+		{
+			$checked = $content['nm']['rows']['checked'];
+			if (!is_array($checked) || !count($checked))
+			{
+				$msg = lang('You need to select a project first');
+			}
+			else
+			{
+				if ($content['gantt_checked'])
+				{
+					$tpl->location(array(
+						'menuaction' => 'projectmanager.ganttchart.show',
+						'pm_id'      => implode(',',$checked),
+					));
+				}
+				// delete all checked
+				$deleted = $no_perms = 0;
+				foreach($checked as $pm_id)
+				{
+					if ($this->read($pm_id) && !$this->check_acl(PHPGW_ACL_DELETE))
+					{
+						$no_perms++;
+					}
+					else
+					{
+						$this->delete($pm_id);
+						$deleted++;
+					}
+				}
+				$msg = $no_perms ? lang('%1 times permission denied, %2 projects deleted',$no_perms,$deleted) :
+					lang('%1 projects deleted',$deleted);
+			}
+		}
+		$content = $content['nm']['rows'];
+		
+		if ($content['view'] || $content['edit'] || $content['delete'] || $content['ganttchart'])
+		{
+			foreach(array('view','edit','delete','ganttchart') as $action)
+			{
+				if ($content[$action])
+				{
+					list($pm_id) = each($content[$action]);
+					break;
+				}
+			}
+			//echo "<p>uiprojectmanger::index() action='$action', pm_id='$pm_id'</p>\n";
+			switch($action)
+			{
+				case 'ganttchart':
+					$tpl->location(array(
+						'menuaction' => 'projectmanager.ganttchart.show',
+						'pm_id'      => $pm_id,
+					));
+					break;
+				case 'view':
+				case 'edit':
+					$tpl->location(array(
+						'menuaction' => 'projectmanager.uiprojectmanager.'.$action,
+						'pm_id'      => $pm_id,
+					));
+					break;
+					
+				case 'delete':
+					if ($this->read($pm_id) && !$this->check_acl(PHPGW_ACL_DELETE))
+					{
+						$msg = lang('Permission denied !!!');
+					}
+					else
+					{
+						$msg = $this->delete($pm_id) ? lang('Project deleted') : 
+							lang('Error: deleting project !!!');
+					}
+					break;
+			}						
+		}
+		$content = array(
+			'nm' => $GLOBALS['phpgw']->session->appsession('project_list','projectmanager'),
+			'msg' => $msg,
+		);		
+		if (!is_array($content['nm']))
+		{
+			$content['nm'] = array(
+				'get_rows'       =>	'projectmanager.uiprojectmanager.get_rows',
+				'filter2'        => 'active',// I initial value for the filter
+				'options-filter2'=> $this->status_labels,
+				'filter2_no_lang'=> True,// I  set no_lang for filter (=dont translate the options)
+				'filter'         => 'mains',
+				'filter_label'   => lang('Filter'),// I  label for filter    (optional)
+				'options-filter' => $this->filter_labels,
+				'filter_no_lang' => True,// I  set no_lang for filter (=dont translate the options)
+//				'bottom_too'     => True,// I  show the nextmatch-line (arrows, filters, search, ...) again after the rows
+				'order'          =>	'pm_modified',// IO name of the column to sort after (optional for the sortheaders)
+				'sort'           =>	'DESC',// IO direction of the sort: 'ASC' or 'DESC'
+				'default_cols'   => '!role0,role1,role2,role3,role4',
+			);
+		}
+		$templates = array();
+		foreach((array)$this->search(array(
+			'pm_status' => 'template',
+		),$this->table_name.'.pm_id AS pm_id,pm_number,pm_title','pm_number','','',False,'OR') as $template)
+		{
+			$templates[$template['pm_id']] = array(
+				'label' => $template['pm_number'],
+				'title' => $template['pm_title'],
+			);
+		}
+		$GLOBALS['phpgw_info']['flags']['app_header'] = lang('projectmanager').' - '.lang('Projectlist');
+		$tpl->exec('projectmanager.uiprojectmanager.index',$content,array(
+			'template' => $templates,
+		));
+	}
+}
Index: /contrib/ProjectManager/inc/class.soprojectmanager.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.soprojectmanager.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.soprojectmanager.inc.php	(revision 3594)
@@ -0,0 +1,410 @@
+<?php
+/**
+ * ProjectManager - General (projects) storage object
+ *
+ * @link http://www.egroupware.org
+ * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @package projectmanager
+ * @copyright (c) 2005 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @version $Id: class.soprojectmanager.inc.php 25098 2008-03-17 08:49:22Z ralfbecker $ 
+ */
+
+include_once(PHPGW_INCLUDE_ROOT.'/etemplate/inc/class.so_sql.inc.php');
+
+/**
+ * General storage object of the projectmanager: access the main project data
+ *
+ * Tables: phpgw_pm_projects, phpgw_pm_extra, phpgw_pm_roles, phpgw_pm_members
+ *
+ * A project P is the parent of an other project C, if link_id1=P.pm_id and link_id2=C.pm_id !
+ */
+class soprojectmanager extends so_sql
+{
+	/**
+	 * Table name 'phpgw_links'
+	 * 
+	 * @var string
+	 */
+	var $links_table = 'phpgw_links';
+	/**
+	 * Configuration data
+	 * 
+	 * @var array
+	 */
+	var $config = array(
+		'customfields' => array(),
+	);
+	/**
+	 * Custom fields
+	 *
+	 * @var array
+	 */
+	var $customfields;
+	/**
+	 * Name of customefields table
+	 * 
+	 * @var string
+	 */
+	var $extra_table = 'phpgw_pm_extra';
+	/**
+	 * Name of project-members table
+	 * 
+	 * @var string
+	 */
+	var $members_table = 'phpgw_pm_members';
+	/**
+	 * Name of roles table
+	 * 
+	 * @var string
+	 */
+	var $roles_table = 'phpgw_pm_roles';
+	/**
+	 * Join with the members and the roles table to get the role-ACL of the current user
+	 * 
+	 * @var string
+	 */
+	var $acl_join;
+	/**
+	 * Extracolumns from the members table
+	 * 
+	 * @var array/string
+	 */
+	var $acl_extracols='role_acl';
+	/**
+	 * ACL grants from other users
+	 * 
+	 * @var array
+	 */
+	var $grants;
+	var $read_grants,$private_grants;
+
+	/**
+	 * Constructor, calls the constructor of the extended class
+	 * 
+	 * @param int $pm_id id of the project to load, default null
+	 * @return soprojectmanger
+	 */
+	function soprojectmanager($pm_id=null)
+	{
+		$this->so_sql('projectmanager','phpgw_pm_projects');
+		
+		$config =& CreateObject('phpgwapi.config','projectmanager');
+		$config->read_repository();
+		$this->config =& $config->config_data;
+		unset($config);
+		$this->customfields =& $this->config['customfields'];
+		$this->config['duration_format'] = str_replace(',','',$this->config['duration_units']).','.$this->config['hours_per_workday'];
+
+		$this->grants = $GLOBALS['phpgw']->acl->get_grants('projectmanager');
+		$this->user = (int) $GLOBALS['phpgw_info']['user']['account_id'];
+
+		$this->read_grants = $this->private_grants = array();
+		foreach($this->grants as $owner => $rights)
+		{
+			if ($rights) $this->read_grants[] = $owner;		// ANY ACL implies READ!
+			
+			if ($rights & PHPGW_ACL_PRIVATE) $this->private_grants[] = $owner;
+		}
+		$this->acl_join = "LEFT JOIN $this->members_table ON ($this->table_name.pm_id=$this->members_table.pm_id AND member_uid=$this->user) ".
+			" LEFT JOIN $this->roles_table ON $this->members_table.role_id=$this->roles_table.role_id";
+
+		if ($pm_id) $this->read($pm_id);
+	}
+	
+	/**
+	 * reads a project
+	 *
+	 * reimplemented to handle custom fields
+	 */
+	function read($keys)
+	{
+		//echo "<p>soprojectmanager::read(".print_r($keys,true).")</p>\n";
+
+		if ($keys && is_numeric($keys) && $this->data['pm_id'] == $keys ||
+			$keys['pm_id'] &&  $this->data['pm_id'] == $keys['pm_id'])
+		{
+			return $this->data;
+		}
+		if (!parent::read($keys))
+		{
+			return false;
+		}
+		if ($this->customfields)
+		{
+			$this->db->select($this->extra_table,'*',array('pm_id' => $this->data['pm_id']),__LINE__,__FILE__);
+
+			while (($row = $this->db->row(true)))
+			{
+				$this->data['#'.$row['pm_extra_name']] = $row['pm_extra_value'];
+			}
+		}
+		// query project_members and their roles
+/*
+		$this->db->select($this->members_table,'*',$this->members_table.'.pm_id='.(int)$this->data['pm_id'],__LINE__,__FILE__,
+			False,'',False,0,"LEFT JOIN $this->roles_table ON $this->members_table.role_id=$this->roles_table.role_id");
+	
+		while (($row = $this->db->row(true)))
+		{
+			$this->data['pm_members'][$row['member_uid']] = $row;
+		}
+*/
+		$this->data['pm_members'] = $this->read_members($this->data['pm_id']);
+		$this->data['role_acl'] = $this->data['pm_members'][$this->user]['role_acl'];
+		
+		return $this->data;
+	}
+	
+	/**
+	 * Read the projectmembers of one or more projects
+	 *
+	 * @param int/array $pm_id
+	 * @return array with projectmembers
+	 */
+	function read_members($pm_id)
+	{
+		$this->db->select($this->members_table,'*,'.$this->members_table.'.pm_id AS pm_id',
+			$this->db->expression($this->members_table,$this->members_table.'.',array('pm_id'=>$pm_id)),__LINE__,__FILE__,
+			False,'',False,0,"LEFT JOIN $this->roles_table ON $this->members_table.role_id=$this->roles_table.role_id");
+		
+		while(($row = $this->db->row(true)))
+		{
+			$members[$row['pm_id']][$row['member_uid']] = $row;
+		}
+		return is_array($pm_id) ? $members : $members[$pm_id];
+	}
+	
+	/**
+	 * saves a project
+	 *
+	 * reimplemented to handle custom fields and set modification and creation data
+	 *
+	 * @param array $keys if given $keys are copied to data before saveing => allows a save as
+	 * @return int 0 on success and errno != 0 else
+	 */
+	function save($keys=null)
+	{
+		//echo "soprojectmanager::save(".print_r($keys,true).") this->data="; _debug_array($this->data);
+		
+		if (is_array($keys) && count($keys))
+		{
+			$this->data_merge($keys);
+			$keys = null;
+		}
+		if (parent::save($keys) == 0 && $this->data['pm_id'])
+		{
+			if ($this->customfields)
+			{
+				// custome fields: first delete all, then save the ones with non-empty content
+				$this->db->delete($this->extra_table,array('pm_id' => $this->data['pm_id']),__LINE__,__FILE__);
+				foreach($this->customfields as $name => $data)
+				{
+					if ($name && isset($this->data['#'.$name]) && !empty($this->data['#'.$name]))
+					{
+						$this->db->insert($this->extra_table,array(
+							'pm_id'          => $this->data['pm_id'],
+							'pm_extra_name'  => $name,
+							'pm_extra_value' => $this->data['#'.$name],
+						),false,__LINE__,__FILE__);
+					}
+				}
+			}
+			// project-members: first delete all, then save the (still) assigned ones
+			$this->db->delete($this->members_table,array('pm_id' => $this->data['pm_id']),__LINE__,__FILE__);
+			foreach((array)$this->data['pm_members'] as $uid => $data)
+			{
+				$this->db->insert($this->members_table,array(
+					'pm_id'      => $this->data['pm_id'],
+					'member_uid' => $uid,
+					'role_id'    => $data['role_id'],
+					'member_availibility' => $data['member_availibility'], 
+				),false,__LINE__,__FILE__);
+			}
+		}
+		return $this->db->Errno;
+	}
+	
+	/**
+	 * merges in new values from the given new data-array
+	 *
+	 * reimplemented to also merge the customfields
+	 *
+	 * @param $new array in form col => new_value with values to set
+	 */
+	function data_merge($new)
+	{
+		parent::data_merge($new);
+		
+		if (is_array($this->customfields))
+		{
+			foreach($this->customfields as $name => $data)
+			{
+				if (isset($new['#'.$name]))
+				{
+					$this->data['#'.$name] = $new['#'.$name];
+				}
+			}
+		}
+	}
+
+	/**
+	 * search projects, re-implemented to include sub-cats and allow to filter for subs and mains
+	 *
+	 * @param array/string $criteria array of key and data cols, OR a SQL query (content for WHERE), fully quoted (!)
+	 * @param boolean $only_keys True returns only keys, False returns all cols
+	 * @param string $order_by fieldnames + {ASC|DESC} separated by colons ','
+	 * @param string/array $extra_cols string or array of strings to be added to the SELECT, eg. "count(*) as num"
+	 * @param string $wildcard appended befor and after each criteria
+	 * @param boolean $empty False=empty criteria are ignored in query, True=empty have to be empty in row
+	 * @param string $op defaults to 'AND', can be set to 'OR' too, then criteria's are OR'ed together
+	 * @param int/boolean $start if != false, return only maxmatch rows begining with start
+	 * @param array $filter if set (!=null) col-data pairs, to be and-ed (!) into the query without wildcards
+	 * @param string/boolean $join=true sql to do a join, added as is after the table-name, default true=add join for acl
+	 * @return array of matching rows (the row is an array of the cols) or False
+	 */
+	function search($criteria,$only_keys=True,$order_by='',$extra_cols='',$wildcard='',$empty=False,$op='AND',$start=false,$filter=null,$join=true,$need_full_no_count=false)
+	{
+		// include sub-categories in the search
+		if ($filter['cat_id'])
+		{
+			if (!is_object($GLOBALS['phpgw']->categories))
+			{
+				$GLOBALS['phpgw']->categories =& CreateObject('phpgwapi.categories');
+			}
+			$filter['cat_id'] = $GLOBALS['phpgw']->categories->return_all_children($filter['cat_id']);
+		}
+		if ($join === true)	// add acl-join, to get role_acl of current user
+		{
+			$join = $this->acl_join;
+			
+			if (!is_array($extra_cols)) $extra_cols = $extra_cols ? explode(',',$extra_cols) : array();
+			$extra_cols = array_merge($extra_cols,array(
+				$this->acl_extracols,
+				$this->table_name.'.pm_id AS pm_id',
+			));
+			if ($only_keys === true) $only_keys='';	// otherwise we use ambigues pm_id
+	
+			if (isset($criteria['pm_id']))
+			{
+				$criteria[$this->table_name.'.pm_id'] = $criteria['pm_id'];
+				unset($criteria['pm_id']);
+			}
+			if (isset($filter['pm_id']) && $filter['pm_id'])
+			{
+				$filter[$this->table_name.'.pm_id'] = $filter['pm_id'];
+				unset($filter['pm_id']);
+			}
+			// include an ACL filter for read-access
+			$filter[] = "(pm_access='anonym' OR pm_access='public' AND pm_creator IN (".implode(',',$this->read_grants).
+				") OR pm_access='private' AND pm_creator IN (".implode(',',$this->private_grants).')'.
+				($join == $this->acl_join ? ' OR '.$this->roles_table.'.role_acl!=0' : '').')';
+		}
+		if ($filter['subs_or_mains'])
+		{
+/* old code using a sub-query
+			$ids = "SELECT link_id2 FROM $this->links_table WHERE link_app2='projectmanager' AND link_app1='projectmanager'";
+			
+			if (is_array($filter['subs_or_mains']))		// sub-projects of given parent-projects
+			{
+				$ids .= ' AND '.$this->db->expression($this->links_table,array('link_id1' => $filter['subs_or_mains']));
+				$filter['subs_or_mains'] = 'subs';
+			}
+			if (!$this->db->capabilities['sub_queries'])
+			{
+				$this->db->query($ids,__LINE__,__FILE__);
+				$ids = array();
+				while($this->db->next_record())
+				{
+					$ids[] = $this->db->f(0);
+				}
+				$ids = count($ids) ? implode(',',$ids) : 0;
+			}
+			$filter[] = $this->table_name.'.pm_id '.($filter['subs_or_mains'] == 'mains' ? 'NOT ' : '').'IN ('.$ids.')';
+*/
+			// new code using a JOIN
+			if ($filter['subs_or_mains'] == 'mains')
+			{
+				$filter[] = 'link_id2 IS NULL';
+				$join .= ' LEFT';
+			}
+			// postgres 8.3 requires cast as link_idx is varchar and pm_id an integer, the cast should be no problem for other DB's
+			$join .= " JOIN $this->links_table ON link_app2='projectmanager' AND link_app1='projectmanager' AND link_id2=CAST($this->table_name.pm_id AS CHAR)";
+
+			if (is_array($filter['subs_or_mains']))	// sub-projects of given parent-projects
+			{
+				$join .= ' AND '.$this->db->expression($this->links_table,array('link_id1' => $filter['subs_or_mains']));
+			}
+		}
+		unset($filter['subs_or_mains']);
+		
+		return parent::search($criteria,$only_keys,$order_by,$extra_cols,$wildcard,$empty,$op,$start,$filter,$join,$need_full_no_count);
+	}
+	
+	/**
+	 * reimplemented to set some defaults and cope with ambigues pm_id column
+	 *
+	 * @param string $value_col='pm_title' column-name for the values of the array, can also be an expression aliased with AS
+	 * @param string $key_col='pm_id' column-name for the keys, default '' = same as $value_col: returns a distinct list
+	 * @param array $filter=array() to filter the entries
+	 * @return array with key_col => value_col pairs, ordered by value_col
+	 */
+	function query_list($value_col='pm_title',$key_col='pm_id',$filter=array('pm_status'=>'active'))
+	{
+		if ($key_col == 'pm_id') $key_col = $this->table_name.'.pm_id AS pm_id';
+		
+		return parent::query_list($value_col,$key_col,$filter);
+	}
+	
+	/**
+	 * read the general availebility of one or all users
+	 *
+	 * A not set availibility is by default 100%
+	 *
+	 * @param int $uid user-id or 0 to read all
+	 * @return array uid => availibility
+	 */
+	function get_availibility($uid=0)
+	{
+		$where = array('pm_id' => 0);
+		
+		if ($uid) $where['member_uid'] = $uid;
+		
+		$this->db->select($this->members_table,'member_uid,member_availibility',$where,__LINE__,__FILE__);
+		$avails = array();
+		while (($row = $this->db->row(true)))
+		{
+			$avails[$row['member_uid']] = empty($row['member_availibility']) ? 100.0 : $row['member_availibility'];
+		}
+		return $avails;
+	}
+	
+	/**
+	 * set or delete the general availibility of a user
+	 *
+	 * A not set availibility is by default 100%
+	 *
+	 * @param int $uid user-id
+	 * @param float $availiblity=null percentage to set or nothing to delete the avail. for the user
+	 */
+	function set_availibility($uid,$availibility=null)
+	{
+		if (!is_numeric($uid)) return;
+
+		if (!is_null($availibility) && !empty($availibility) && $availibility != 100.0)
+		{
+			$this->db->insert($this->members_table,array(
+				'member_availibility' => $availibility
+			),array(
+				'member_uid' => $uid,
+				'pm_id'      => 0,
+			),__LINE__,__FILE__);
+		}
+		else
+		{
+			$this->db->delete($this->members_table,array(
+				'pm_id' => 0,
+				'member_uid' => $uid,
+			),__LINE__,__FILE__);
+		}
+	}
+}
Index: /contrib/ProjectManager/inc/class.DBAccess.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.DBAccess.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.DBAccess.inc.php	(revision 3594)
@@ -0,0 +1,70 @@
+<?php
+///////////////////////////////////////////////////////////////////////
+// We dont use ths db class any more, and use the egroupware db      //
+// class instead, rewritten by Shang Wenbin <wbsh@realss.com>        //
+///////////////////////////////////////////////////////////////////////
+
+//include $settings->_ADOdbPath . "adodb.inc.php";
+
+class DBAccess
+{
+	var $db;
+	/**
+	 * just copy the existent class $GLOBALS['phpgw']->db 
+	 */
+	function connect()
+	{
+		copyobj($GLOBALS['phpgw']->db,$this->db);
+	}
+
+	/**
+	 * always return true
+	 */
+	function ensureConnected()
+	{
+		if(!is_object($this->db))
+		{
+			$this->connect();
+		}
+		return true;
+	}
+
+	function getResultArray($queryStr)
+	{
+		$resArr = array();
+
+		$res = $this->db->query($queryStr,__LINE__,__FILE__);
+		$resArr = $res->GetArray();
+		$res->Close();
+		return $resArr;
+	}
+
+	/**
+	 * seems only used for update query
+	 */
+	function getResult($queryStr)
+	{
+		return $this->db->query($queryStr,__LINE__,__FILE__);
+	}
+
+	/**
+	 * get the last insert id
+	 * @param string $table
+	 * @param string $column
+	 * @return int
+	 */
+	function getInsertID($table,$column)
+	{
+		return $this->db->get_last_insert_id($table,$column);
+	}
+
+}
+
+
+$db = new DBAccess();
+$db->connect();
+
+$GLOBALS['infolog']->db = new DBAccess();
+$GLOBALS['infolog']->db->connect();
+
+?>
Index: /contrib/ProjectManager/inc/class.boholiday.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.boholiday.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.boholiday.inc.php	(revision 3594)
@@ -0,0 +1,453 @@
+<?php
+  /**************************************************************************\
+  * eGroupWare - Holiday                                                     *
+  * http://www.egroupware.org                                                *
+  * Written by Mark Peters <skeeter@phpgroupware.org>                        *
+  * --------------------------------------------                             *
+  *  This program is free software; you can redistribute it and/or modify it *
+  *  under the terms of the GNU General Public License as published by the   *
+  *  Free Software Foundation; either version 2 of the License, or (at your  *
+  *  option) any later version.                                              *
+  \**************************************************************************/
+
+
+	class boholiday
+	{
+		var $public_functions = Array(
+			'add'		=> True,
+			'delete_holiday'	=> True,
+			'delete_locale'	=> True,
+			'accept_holiday'	=> True,
+			
+			'read_entries'	=> True,
+			'read_entry'	=> True,
+			'add_entry'	=> True,
+			'update_entry'	=> True
+		);
+
+		var $debug = False;
+		var $base_url = '/index.php';
+
+		var $ui;
+		var $so;
+		var $owner;
+		var $year;
+
+		var $id;
+		var $total;
+		var $start;
+		var $query;
+		var $sort;
+
+		var $locales = Array();
+		var $holidays;
+		var $cached_holidays;
+
+		function boholiday()
+		{
+			$this->so = CreateObject('calendar.soholiday');
+
+			$this->start  = (int)get_var('start',array('POST','GET'));
+			$this->query  = get_var('query',array('POST','GET'));
+			$this->sort   = get_var('sort',array('POST','GET'));
+			$this->order  = get_var('order',array('POST','GET'));
+			$this->id     = get_var('id',array('POST','GET'));
+			$this->year   = get_var('year',array('POST','GET'),date('Y'));
+			$this->locale = get_var('locale',array('POST','GET'));
+			if ($this->locale)
+			{
+				$this->locales[] = $this->locale;
+			}
+
+			if($this->debug)
+			{
+				echo '<-- Locale = '.$this->locales[0].' -->'."\n";
+			}
+
+			$this->total = $this->so->holiday_total($this->locales[0],$this->query,$this->year);
+		}
+
+		/* Begin Calendar functions */
+		function read_entry($id=0)
+		{
+			if($this->debug)
+			{
+				echo "BO : Reading Holiday ID : ".$id."<br>\n";
+			}
+
+			if(!$id)
+			{
+				if(!$this->id)
+				{
+					return Array();
+				}
+				else
+				{
+					$id = $this->id;
+				}
+			}
+
+			return $this->so->read_holiday($id);
+		}
+
+		function delete_holiday($id=0)
+		{
+			if(!$id)
+			{
+				if($this->id)
+				{
+					$id = $this->id;
+				}
+			}
+
+			$this->ui = CreateObject('calendar.uiholiday');
+			if($id)
+			{
+				$this->so->delete_holiday($id);
+				$this->ui->edit_locale();
+			}
+			else
+			{
+				$this->ui->admin();
+			}
+		}
+
+		function delete_locale($locale='')
+		{
+			if(!$locale)
+			{
+				if($this->locales[0])
+				{
+					$locale = $this->locales[0];
+				}
+			}
+
+			if($locale)
+			{
+				$this->so->delete_locale($locale);
+			}
+			$this->ui = CreateObject('calendar.uiholiday');
+			$this->ui->admin();
+		}
+
+		function accept_holiday()
+		{
+			$send_back_to = str_replace('submitlocale','holiday_admin',$_SERVER['HTTP_REFERER']);
+			if(!@$this->locales[0])
+			{
+				Header('Location: '.$send_back_to);
+			}
+
+			$send_back_to = str_replace('&locale='.$this->locales[0],'',$send_back_to);
+			$file = './holidays.'.$this->locales[0];
+			if(!file_exists($file) && count($_POST['name']))
+			{
+				$c_holidays = count($_POST['name']);
+				$fp = fopen($file,'w');
+				for($i=0;$i<$c_holidays;$i++)
+				{
+					fwrite($fp,$this->locales[0]."\t".$_POST['name'][$i]."\t".$_POST['day'][$i]."\t".$_POST['month'][$i]."\t".$_POST['occurence'][$i]."\t".$_POST['dow'][$i]."\t".$_POST['observance'][$i]."\n");
+				}
+				fclose($fp);
+			}
+			Header('Location: '.$send_back_to);
+		}
+
+		function get_holiday_list($locale='', $sort='', $order='', $query='', $total='', $year=0)
+		{
+			$locale = ($locale?$locale:$this->locales[0]);
+			$sort = ($sort?$sort:$this->sort);
+			$order = ($order?$order:$this->order);
+			$query = ($query?$query:$this->query);
+			$year = ($$year?$$year:$this->year);
+			return $this->so->read_holidays($locale,$query,$order,$year);
+		}
+
+		function get_locale_list($sort='', $order='', $query='')
+		{
+			$sort = ($sort?$sort:$this->sort);
+			$order = ($order?$order:$this->order);
+			$query = ($query?$query:$this->query);
+			return $this->so->get_locale_list($sort,$order,$query);
+		}
+
+		function prepare_read_holidays($year=0,$owner=0)
+		{
+			$this->year = (isset($year) && $year > 0?$year:$GLOBALS['phpgw']->common->show_date(time() - $GLOBALS['phpgw']->datetime->tz_offset,'Y'));
+			$this->owner = ($owner?$owner:$GLOBALS['phpgw_info']['user']['account_id']);
+
+			if($this->debug)
+			{
+				echo 'Setting Year to : '.$this->year.'<br>'."\n";
+			}
+
+			if(@$GLOBALS['phpgw_info']['user']['preferences']['common']['country'])
+			{
+				$this->locales[] = $GLOBALS['phpgw_info']['user']['preferences']['common']['country'];
+			}
+			elseif(@$GLOBALS['phpgw_info']['user']['preferences']['calendar']['locale'])
+			{
+				$this->locales[] = $GLOBALS['phpgw_info']['user']['preferences']['calendar']['locale'];
+			}
+			else
+			{
+				$this->locales[] = 'US';
+			}
+
+			if($this->owner != $GLOBALS['phpgw_info']['user']['account_id'])
+			{
+				$owner_pref = CreateObject('phpgwapi.preferences',$owner);
+				$owner_prefs = $owner_pref->read_repository();
+				if(@$owner_prefs['common']['country'])
+				{
+					$this->locales[] = $owner_prefs['common']['country'];
+				}
+				elseif(@$owner_prefs['calendar']['locale'])
+				{
+					$this->locales[] = $owner_prefs['calendar']['locale'];
+				}
+				unset($owner_pref);
+			}
+
+			@reset($this->locales);
+			if($GLOBALS['phpgw_info']['server']['auto_load_holidays'] == True)
+			{
+				while(list($key,$value) = each($this->locales))
+				{
+					$this->auto_load_holidays($value);
+				}
+			}
+		}
+
+		function auto_load_holidays($locale)
+		{
+			if($this->so->holiday_total($locale) == 0)
+			{
+				@set_time_limit(0);
+
+				/* get the file that contains the calendar events for your locale */
+				/* "http://www.egroupware.org/cal/holidays.US.csv";                 */
+				$network = CreateObject('phpgwapi.network');
+				if(isset($GLOBALS['phpgw_info']['server']['holidays_url_path']) && $GLOBALS['phpgw_info']['server']['holidays_url_path'] != 'localhost')
+				{
+					$load_from = $GLOBALS['phpgw_info']['server']['holidays_url_path'];
+				}
+				else
+				{
+					$pos = strpos(' '.$GLOBALS['phpgw_info']['server']['webserver_url'],$_SERVER['HTTP_HOST']);
+					if($pos == 0)
+					{
+						switch($_SERVER['SERVER_PORT'])
+						{
+							case 80:
+								$http_protocol = 'http://';
+								break;
+							case 443:
+								$http_protocol = 'https://';
+								break;
+						}
+						$server_host = $http_protocol.$_SERVER['HTTP_HOST'].$GLOBALS['phpgw_info']['server']['webserver_url'];
+					}
+					else
+					{
+						$server_host = $GLOBALS['phpgw_info']['server']['webserver_url'];
+					}
+					$load_from = $server_host.'/calendar/egroupware.org';
+				}
+//				echo 'Loading from: '.$load_from.'/holidays.'.strtoupper($locale).'.csv'."<br>\n";
+				$lines = $network->gethttpsocketfile($load_from.'/holidays.'.strtoupper($locale).'.csv');
+				if (!$lines)
+				{
+					return false;
+				}
+				$c_lines = count($lines);
+				for($i=0;$i<$c_lines;$i++)
+				{
+//					echo 'Line #'.$i.' : '.$lines[$i]."<br>\n";
+					$holiday = explode("\t",$lines[$i]);
+					if(count($holiday) == 7)
+					{
+						$holiday['locale'] = $holiday[0];
+						$holiday['name'] = $GLOBALS['phpgw']->db->db_addslashes($holiday[1]);
+						$holiday['mday'] = (int)$holiday[2];
+						$holiday['month_num'] = (int)$holiday[3];
+						$holiday['occurence'] = (int)$holiday[4];
+						$holiday['dow'] = (int)$holiday[5];
+						$holiday['observance_rule'] = (int)$holiday[6];
+						$holiday['hol_id'] = 0;
+						$this->so->save_holiday($holiday);
+					}
+				}
+			}
+		}
+
+		function save_holiday($holiday)
+		{
+			$this->so->save_holiday($holiday);
+		}
+
+		function add()
+		{
+			if(@$_POST['submit'])
+			{
+				$holiday = $_POST['holiday'];
+
+				if(empty($holiday['mday']))
+				{
+					$holiday['mday'] = 0;
+				}
+				if(!isset($this->bo->locales[0]) || $this->bo->locales[0]=='')
+				{
+					$this->bo->locales[0] = $holiday['locale'];
+				}
+				elseif(!isset($holiday['locale']) || $holiday['locale']=='')
+				{
+					$holiday['locale'] = $this->bo->locales[0];
+				}
+				if(!isset($holiday['hol_id']))
+				{
+					$holiday['hol_id'] = $this->bo->id;
+				}
+
+				// some input validation
+
+				if (!$holiday['mday'] == !$holiday['occurence'])
+				{
+					$errors[] = lang('You need to set either a day or a occurence !!!');
+				}
+				if($holiday['year'] && $holiday['occurence'])
+				{
+					$errors[] = lang('You can only set a year or a occurence !!!');
+				}
+				else
+				{
+					$holiday['occurence'] = (int)($holiday['occurence'] ? $holiday['occurence'] : $holiday['year']);
+					unset($holiday['year']);
+				}
+
+	// Still need to put some validation in here.....
+
+				$this->ui = CreateObject('calendar.uiholiday');
+
+				if (is_array($errors))
+				{
+					$holiday['month'] = $holiday['month_num'];
+					$holiday['day']   = $holiday['mday'];
+					$this->ui->edit_holiday($errors,$holiday);
+				}
+				else
+				{
+					$this->so->save_holiday($holiday);
+					$this->ui->edit_locale($holiday['locale']);
+				}
+			}
+		}
+
+		function sort_holidays_by_date($holidays)
+		{
+			$c_holidays = count($holidays);
+			for($outer_loop=0;$outer_loop<($c_holidays - 1);$outer_loop++)
+			{
+				for($inner_loop=$outer_loop;$inner_loop<$c_holidays;$inner_loop++)
+				{
+					if($holidays[$outer_loop]['date'] > $holidays[$inner_loop]['date'])
+					{
+						$temp = $holidays[$inner_loop];
+						$holidays[$inner_loop] = $holidays[$outer_loop];
+						$holidays[$outer_loop] = $temp;
+					}
+				}
+			}
+			return $holidays;
+		}
+
+		function set_holidays_to_date($holidays)
+		{
+			$new_holidays = Array();
+			for($i=0;$i<count($holidays);$i++)
+			{
+//	echo "Setting Holidays Date : ".date('Ymd',$holidays[$i]['date'])."<br>\n";
+				$new_holidays[date('Ymd',$holidays[$i]['date'])][] = $holidays[$i];
+			}
+			return $new_holidays;
+		}
+
+		function read_holiday()
+		{
+			if(isset($this->cached_holidays))
+			{
+				return $this->cached_holidays;
+			}
+			$holidays = $this->so->read_holidays($this->locales,'','',$this->year);
+
+			if(count($holidays) == 0)
+			{
+				return $holidays;
+			}
+
+			$temp_locale = $GLOBALS['phpgw_info']['user']['preferences']['common']['country'];
+			for($i=0;$i<count($holidays);$i++)
+			{
+				$c = $i;
+				if($i == 0 || $holidays[$i]['locale'] != $holidays[$i - 1]['locale'])
+				{
+					if(is_object($holidaycalc))
+					{
+						unset($holidaycalc);
+					}
+					$GLOBALS['phpgw_info']['user']['preferences']['common']['country'] = $holidays[$i]['locale'];
+					$holidaycalc = CreateObject('calendar.holidaycalc');
+				}
+				$holidays[$i]['date'] = $holidaycalc->calculate_date($holidays[$i], $holidays, $this->year, $c);
+				if($c != $i)
+				{
+					$i = $c;
+				}
+			}
+			unset($holidaycalc);
+			$this->holidays = $this->sort_holidays_by_date($holidays);
+			$this->cached_holidays = $this->set_holidays_to_date($this->holidays);
+			$GLOBALS['phpgw_info']['user']['preferences']['common']['country'] = $temp_locale;
+			return $this->cached_holidays;
+		}
+		/* End Calendar functions */
+
+		function check_admin()
+		{
+			if(!@$GLOBALS['phpgw_info']['user']['apps']['admin'])
+			{
+				Header('Location: ' . $GLOBALS['phpgw']->link('/index.php'));
+			}
+		}
+
+		function rule_string($holiday)
+		{
+			if (!is_array($holiday))
+			{
+				return false;
+			}
+			$sbox = CreateObject('phpgwapi.sbox');
+			$month = $holiday['month'] ? lang($sbox->monthnames[$holiday['month']]) : '';
+			unset($sbox);
+
+			if (!$holiday['day'])
+			{
+				$occ = $holiday['occurence'] == 99 ? lang('last') : $holiday['occurence'].'.';
+
+				$dow_str = Array(lang('Sun'),lang('Mon'),lang('Tue'),lang('Wed'),lang('Thu'),lang('Fri'),lang('Sat'));
+				$dow = $dow_str[$holiday['dow']];
+				
+				$str = lang('%1 %2 in %3',$occ,$dow,$month);
+			}
+			else
+			{
+				$str = $GLOBALS['phpgw']->common->dateformatorder($holiday['occurence']>1900?$holiday['occurence']:'',$month,$holiday[day]);
+			}
+			if ($holiday['observance_rule'])
+			{
+				$str .= ' ('.lang('Observance Rule').')';
+			}
+			return $str;
+		}
+	}
+?>
Index: /contrib/ProjectManager/inc/class.datasource_projectmanager.inc.php
===================================================================
--- /contrib/ProjectManager/inc/class.datasource_projectmanager.inc.php	(revision 3594)
+++ /contrib/ProjectManager/inc/class.datasource_projectmanager.inc.php	(revision 3594)
@@ -0,0 +1,164 @@
+<?php
+/**
+ * ProjectManager - DataSource for ProjectManger (Subprojects)
+ *
+ * @link http://www.egroupware.org
+ * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @package projectmanager
+ * @copyright (c) 2005 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @version $Id: class.datasource_projectmanager.inc.php 24317 2007-07-18 12:29:59Z ralfbecker $ 
+ */
+
+include_once(PHPGW_INCLUDE_ROOT.'/projectmanager/inc/class.datasource.inc.php');
+
+/**
+ * DataSource for ProjectManager itself
+ */
+class datasource_projectmanager extends datasource
+{
+	/**
+	 * @var int/string $debug 0 = no debug-messages, 1 = main, 2 = more, 3 = all, or string function-name to debug
+	 */
+	var $debug=false;
+	/**
+	 * @var array $get_cache return value or the last call to the get method, used by the re-implemented read method
+	 */
+	var $get_cache=null;
+
+	/**
+	 * Constructor
+	 */
+	function datasource_projectmanager()
+	{
+		$this->datasource('projectmanager');
+		
+		$this->valid = PM_ALL_DATA;
+	}
+	
+	/**
+	 * read an item from a datasource (via the get methode) and try to set (guess) some not supported values
+	 *
+	 * Reimplemented from the datasource parent to set the start-date of the project itself and call it's sync-all
+	 * method if necessary to move it's elements 
+	 * 
+	 * @param mixed $data_id id as used in the link-class for that app, or complete entry as array
+	 * @param array $pe_data data of the project-element or null, eg. to use the constraints
+	 * @return array/boolean array with the data supported by that source or false on error (eg. not found, not availible)
+	 */
+	function read($data_id,$pe_data=null)
+	{
+		$ds = parent::read($data_id,$pe_data);	// calls $this->get($data_id) to fetch the data
+		// we use $GLOBALS['boprojectmanager'] instanciated by get
+
+		if ((int) $this->debug > 1 || $this->debug == 'read')
+		{
+			$GLOBALS['boprojectmanager']->debug_message("datasource_projectmanager::read(pm_id=$data_id,".print_r($pe_data,true).')='.print_r($ds,true));
+		}
+		// check if datasource::read changed our planned start, because it's determined by the constrains or parent
+		if (!is_null($pe_data) && $pe_data['pe_id'] && $ds['pe_planned_start'] != $pe_data['pe_planned_start'])
+		{
+			$pm_id = is_array($data_id) ? $data_id['pm_id'] : $data_id;
+
+			$GLOBALS['boprojectmanager']->debug_message("datasource_projectmanager::read(pm_id=$pm_id: $ds[pe_title]) planned start changed from $pe_data[pe_planned_start]=".date('Y-m-d H:i',$pe_data['pe_planned_start'])." to $ds[pe_planned_start]=".date('Y-m-d H:i',$ds['pe_planned_start'])/*.", pe_data=".print_r($pe_data,true)*/);
+
+			$bope =& CreateObject('projectmanager.boprojectelements',$pm_id);
+			
+			if (!($bope->project->data['pm_overwrite'] & PM_PLANNED_START))
+			{
+				// set the planned start, as it came from the project elements and then call sync_all to move the elements
+				$bope->project->data['pm_planned_start'] = $ds['pe_planned_start'];
+				$bope->project->save(null,false,false);	// not modification and NO notification
+				if (($updated_pes = $bope->sync_all()))
+				{
+					$GLOBALS['boprojectmanager']->debug_message("datasource_projectmanager::read(pm_id=$pm_id: $ds[pe_title]) $updated_pes elements updated to new project-start $ds[pe_planned_start]=".date('Y-m-d H:i',$ds['pe_planned_start']));					
+				}
+			}
+		}
+		return $ds;
+	}
+
+	/**
+	 * get an entry from the underlaying app (if not given) and convert it into a datasource array
+	 * 
+	 * @param mixed $data_id id as used in the link-class for that app, or complete entry as array
+	 * @return array/boolean array with the data supported by that source or false on error (eg. not found, not availible)
+	 */
+	function get($data_id)
+	{
+		// we use $GLOBALS['boprojectmanager'] as an already running instance may be availible there
+		if (!is_object($GLOBALS['boprojectmanager']))
+		{
+			include_once(PHPGW_INCLUDE_ROOT.'/projectmanager/inc/class.boprojectmanager.inc.php');
+			$GLOBALS['boprojectmanager'] =& new boprojectmanager();
+		}
+		if (!is_array($data_id))
+		{
+			if (!$GLOBALS['boprojectmanager']->read((int) $data_id)) return false;
+
+			$data =& $GLOBALS['boprojectmanager']->data;
+		}
+		else
+		{
+			$data =& $data_id;
+		}
+		// if pm_ds_ignore_elements is set, ignore planned start&end for the element-list (not overwritten)
+		$ds = !$GLOBALS['phpgw_info']['flags']['projectmanager']['pm_ds_ignore_elements'] ? array() :	array(
+			'ignore_planned_start' => !($data['pm_overwrite'] & PM_PLANNED_START),
+			'ignore_planned_end'   => !($data['pm_overwrite'] & PM_PLANNED_END),
+			'ignore_real_start'    => !($data['pm_overwrite'] & PM_REAL_START),
+			'ignore_real_end'      => !($data['pm_overwrite'] & PM_REAL_END),
+		);
+		foreach($this->name2id as $name => $id)
+		{
+			$pm_name = str_replace('pe_','pm_',$name);
+			
+			if (isset($data[$pm_name]))
+			{
+				$ds[$name] = $data[$pm_name];
+			}
+		}
+		$ds['pe_title'] = $GLOBALS['boprojectmanager']->link_title($data['pm_id'],$data);
+		// return the projectmembers as resources
+		$ds['pe_resources'] = $data['pm_members'] ? array_keys($data['pm_members']) : array($data['pm_creator']);
+		$ds['pe_details'] = $data['pm_description'];
+		if (is_numeric($ds['pe_completion'])) $ds['pe_completion'] .= '%';
+
+		if ((int) $this->debug > 1 || $this->debug == 'get') $GLOBALS['boprojectmanager']->debug_message("datasource_projectmanager::get($data_id) =".print_r($ds,true));
+
+		return $ds;
+	}
+	
+	/**
+	 * Copy the datasource of a projectelement (sub-project) and re-link it with project $target
+	 *
+	 * @param array $element source project element representing an sub-project, $element['pe_app_id'] = pm_id
+	 * @param int $target target project id
+	 * @param array $target_data=null data of target-project, atm only pm_number is used
+	 * @return array/boolean array(pm_id,link_id) on success, false otherwise
+	 */
+	function copy($element,$target,$target_data=null)
+	{
+		if (!is_object($GLOBALS['boprojectmanager']))
+		{
+			include_once(PHPGW_INCLUDE_ROOT.'/projectmanager/inc/class.boprojectmanager.inc.php');
+			$GLOBALS['boprojectmanager'] =& new boprojectmanager();
+		}
+		if ((int) $this->debug > 1 || $this->debug == 'copy') $GLOBALS['boprojectmanager']->debug_message("datasource_projectmanager::copy(".print_r($element,true).",$target)");
+
+		$data_backup = $GLOBALS['boprojectmanager']->data;
+
+		if (($pm_id = $GLOBALS['boprojectmanager']->copy((int) $element['pe_app_id'],0,$target_data['pm_number'])))
+		{
+			if ($this->debug > 3 || $this->debug == 'copy') $GLOBALS['boprojectmanager']->debug_message("datasource_projectmanager::copy() data=".print_r($GLOBALS['boprojectmanager']->data,true));
+
+			// link the new sub-project with the project
+			$link_id = $GLOBALS['boprojectmanager']->link->link('projectmanager',$target,'projectmanager',$pm_id,$element['pe_remark'],0,0,1);
+		}
+		$GLOBALS['boprojectmanager']->data = $data_backup;
+
+		if ($this->debug > 2 || $this->debug == 'copy') $GLOBALS['boprojectmanager']->debug_message("datasource_projectmanager::copy() returning pm_id=$pm_id, link_id=$link_id, data=".print_r($GLOBALS['boprojectmanager']->data,true));
+
+		return $pm_id ? array($pm_id,$link_id) : false;
+	}
+}
Index: /contrib/ProjectManager/index.php
===================================================================
--- /contrib/ProjectManager/index.php	(revision 3594)
+++ /contrib/ProjectManager/index.php	(revision 3594)
@@ -0,0 +1,38 @@
+<?php
+/**
+ * ProjectManager - Index page
+ *
+ * @link http://www.egroupware.org
+ * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @package projectmanager
+ * @copyright (c) 2005 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
+ * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
+ * @version $Id: index.php 22355 2006-08-26 16:30:45Z ralfbecker $ 
+ */
+
+$GLOBALS['phpgw_info'] = array(
+	'flags' => array(
+		'currentapp'	=> 'projectmanager', 
+		'noheader'		=> True,
+		'nonavbar'		=> True
+));
+include('../header.inc.php');
+
+include_once(PHPGW_INCLUDE_ROOT.'/projectmanager/setup/setup.inc.php');
+if ($setup_info['projectmanager']['version'] != $GLOBALS['phpgw_info']['apps']['projectmanager']['version'])
+{
+	$GLOBALS['phpgw']->common->phpgw_header();
+	parse_navbar();
+	echo '<p style="text-align: center; color:red; font-weight: bold;">'.lang('Your database is NOT up to date (%1 vs. %2), please run %3setup%4 to update your database.',
+		$setup_info['projectmanager']['version'],$GLOBALS['phpgw_info']['apps']['projectmanager']['version'],
+		'<a href="../setup/">','</a>')."</p>\n";
+	$GLOBALS['phpgw']->common->phpgw_exit();
+}
+unset($setup_info);
+	$GLOBALS['phpgw_info']['flags'] = $phpgw_flags;
+	include('../header.inc.php');
+	
+
+	
+	ExecMethod('projectmanager.uiprojectmanager.index',$parms);
+$GLOBALS['phpgw']->common->phpgw_exit();
Index: /contrib/ProjectManager/templates/default/form_button_script.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/form_button_script.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/form_button_script.tpl	(revision 3594)
@@ -0,0 +1,12 @@
+
+<!-- BEGIN form_button -->
+<nobr>
+<form action="{action_url_button}" method="post" name="{action_text_button}form">
+<div style="padding-top:15px; padding-right: 2px">
+	{action_extra_field} 
+	<input id="{button_id}" style="font-size:10px" type="submit" value="{action_text_button}" {action_confirm_button}>
+</div>
+</form>
+</nobr>
+
+<!-- END form_button -->
Index: /contrib/ProjectManager/templates/default/locales.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/locales.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/locales.tpl	(revision 3594)
@@ -0,0 +1,60 @@
+<!-- BEGIN list -->
+<p>
+ <table border="0" width="45%" align="center">
+  <tr>
+   <td align="left">{left_next_matchs}</td>
+   {center}
+   <td align="right">{right_next_matchs}</td>
+  </tr>
+ </table>
+
+ <table border="0" width="70%" align="center">
+  <tr bgcolor="{th_bg}">
+   <td>&nbsp;{sort_name}</td>
+   {header_rule}
+   <td>{header_edit}</td>
+   <td>{header_delete}</td>
+   <td>{header_extra}</td>
+  </tr>
+
+  {rows}
+
+ </table>
+
+ <table border="0" width="70%" cellspacing="5" align="center">
+  <tr>
+   <td align="left">
+    <form method="POST" action="{new_action}">
+     <input type="submit" value="{lang_add}">
+    </form>
+   </td>
+   {back_button}
+   <td width="80%" align="right">
+    <form method="POST" action="{search_action}">
+     {lang_search}&nbsp;<input name="query">
+    </form>
+   </td>
+  </tr>
+ </table>
+<!-- END list -->
+<!-- BEGIN row -->
+ <tr bgcolor="{tr_color}">
+  <td>&nbsp;{group_name}</td>
+  {rule}
+  <td width="5%">{edit_link}</td>
+  <td width="5%">{delete_link}</td>
+  <td align="center" {extra_width}>{extra_link}</td>
+ </tr>
+<!-- END row -->
+<!-- BEGIN row_empty -->
+   <tr>
+    <td colspan="5" align="center">{message}</td>
+   </tr>
+<!-- END row_empty -->
+<!-- BEGIN back_button_form -->
+   <td align="center">
+    <form method="POST" action="{back_action}">
+     <input type="submit" value="{lang_back}">
+    </form>
+   </td>
+<!-- END back_button_form -->
Index: /contrib/ProjectManager/templates/default/milestone.edit.xet
===================================================================
--- /contrib/ProjectManager/templates/default/milestone.edit.xet	(revision 3594)
+++ /contrib/ProjectManager/templates/default/milestone.edit.xet	(revision 3594)
@@ -0,0 +1,47 @@
+<?xml version="1.0"?>
+<!-- $Id: milestone.edit.xet 18388 2005-05-31 21:28:29Z ralfbecker $ -->
+<overlay>
+	<template id="projectmanager.milestone.edit" template="" lang="" group="0" version="0.2.001">
+		<grid width="100%">
+			<columns>
+				<column width="95"/>
+				<column/>
+				<column/>
+			</columns>
+			<rows>
+				<row disabled="!@msg">
+					<description span="all" class="redItalic" align="center" id="msg" no_lang="1"/>
+					<description/>
+					<description/>
+				</row>
+				<row class="th">
+					<description options=",,,pm_id" value="Project"/>
+					<menulist span="all">
+						<menupopup id="pm_id" no_lang="1"/>
+					</menulist>
+				</row>
+				<row class="row">
+					<description options=",,,ms_title" value="Title"/>
+					<textbox size="64" maxlength="255" id="ms_title" span="all" needed="1"/>
+				</row>
+				<row class="row">
+					<description options=",,,ms_date" value="Date"/>
+					<date id="ms_date" needed="1"/>
+					<html id="js"/>
+				</row>
+				<row class="row">
+					<htmlarea span="all" id="ms_description"/>
+				</row>
+				<row>
+					<hbox span="2">
+						<button label="Edit" id="edit" statustext="Edit this milestone"/>
+						<button id="save" label="Save" statustext="Save the changes made"/>
+						<button id="apply" label="Apply" statustext="Apply the changes"/>
+						<button id="cancel" label="Cancel" statustext="Close the window, discard the changes made" onclick="window.close();"/>
+					</hbox>
+					<button label="Delete" align="right" id="delete" statustext="Delete this milestone" onclick="return confirm('Delete this milestone');"/>
+				</row>
+			</rows>
+		</grid>
+	</template>
+</overlay>
Index: /contrib/ProjectManager/templates/default/app_print.css
===================================================================
--- /contrib/ProjectManager/templates/default/app_print.css	(revision 3594)
+++ /contrib/ProjectManager/templates/default/app_print.css	(revision 3594)
@@ -0,0 +1,294 @@
+/* Este documento é um css somente para impressão do calendário no papel!
+O seu efeito não aparece na tela  */
+
+.calendar_print_main {
+	text-align:center;
+	font-family: Verdana, Arial, Helvetica, sans-serif;
+	font-size: 8px;
+	color: #000066;
+	background-color: #F7F7F7;
+	border: 1px solid #CCCCCC;
+	margin: 5px;
+	padding: 5px;
+	height: 700px;
+	width: 600px;
+}
+.button {
+	display: none;
+}
+
+
+.to_continue
+{
+	font-size: 9px;
+}
+
+A.minicalendar 
+{ 
+	color: #000000; 
+	font-size: 9px;
+	
+}
+
+A.bminicalendar 
+{ 
+	color: #336699; 
+	font-style: italic;
+	font-weight: bold;
+	font-size: 9px; 
+}
+
+A.minicalendargrey 
+{ 
+	color: #999999; 
+	font-size: 10px;
+	font-weight: bold;
+}
+
+A.bminicalendargrey 
+{ 
+	color: #336699; 
+	font-style: italic; 
+	font-size:10px; 
+}
+
+A.minicalhol
+{ 
+	padding-left:3px;
+	padding-right:3px;
+	background: #dab0b0;
+	color: #000000; 
+	font-size: 10px; 
+}
+
+A.bminicalhol
+{ 
+	padding-left:3px;
+	padding-right:3px;
+	background: #dab0b0;
+	color: #336699; 
+	font-size: 10px; 
+}
+
+A.minicalgreyhol
+{ 
+	padding-left:3px;
+	padding-right:3px;
+	background: #dab0b0;
+	color: #999999; 
+	font-size: 10px; 
+}
+
+A.bminicalgreyhol
+{ 
+	padding-left:3px;
+	padding-right:3px;
+	background: #dab0b0;
+	color: #999999; 
+	font-size: 10px; 
+}
+
+
+.event-on
+{ 
+	background: #D3DCE3;
+	border: #E8F0F0 1px solid;
+}
+
+.event-off
+{ 
+	background: #E8F0F0;
+	border: #D3DCE3 1px solid;
+}
+
+.event-holiday
+{
+	font-size: 100%;
+	background: #dac0c0;
+	color:#000000;
+	vertical-align: middle;
+}
+
+.time
+{
+	background: #D3DCE3;
+	color:#000000;
+	font-size: 10px;
+	font-weight: bold;
+	vertical-align: middle;
+	width: 5.2%;
+	text-align: right;
+	padding-right: 5px;
+	line-height: 11px;
+	border: #E8F0F0 1px solid;
+}
+
+
+.planner-cell
+{
+	cursor: pointer;
+	cursor: hand;
+	border: thin solid black;
+}
+
+/* common */
+.calendar_link_print
+{
+	text-align: center;
+	font-size: 10px;
+}
+
+//Mini Calendar
+
+.calendar_minical_table
+{
+	width: 180px;
+	font-weight: bold;
+}
+
+.calendar_minical_daytable
+{
+	width: 100%;
+}
+
+.calendar_minical_dayname
+{
+	font-size: 9px;
+	text-align: right;
+}
+
+.calendar_minical_hrule
+{
+	width: 96%;
+}
+
+/* Header */
+.calendar_dropbuttons
+{
+	align: center;
+	text-align: center;
+	vertical-align: bottom;
+	font-weight: bold;
+	font-size: 8px;
+}
+
+.calendar-weekidentifier
+{
+	font-size:16px;
+	font-weight: bold;
+	text-align: center;
+}
+
+.calendar_header
+{
+	font-size: 16px;
+	font-weight: bold;
+	text-align: center;
+}
+
+.calendar_dayview_table_header
+{
+	font-size: 13px;
+	font-weight: bold;
+	text-align: center;
+}
+
+.calendar_dayview_todo_header
+{
+	margin: 0px; 
+	padding: 2px;
+	font-weight: bold;
+}
+
+/* week view */
+.calendar_week_identifier_table
+{
+	border: 0px solid black;
+	width: 100%;
+}
+
+.calendar_week_identifier
+{
+	font-size: 13px;
+	text-align: center;
+	font-weight: bold;
+}
+
+.calendar_weekview_daytitle
+{
+	font-weight: bold;
+	font-size: 12px;
+	background: #D3DCE3;
+}
+
+.calendar_m_w_table
+{
+	table-layout:fixed;
+	overflow:hidden;
+	height: 90%;
+	width: 100%;
+	border: 0px #000000 solid;
+}
+
+.calendar_m_w_tablecell
+{
+	overflow:hidden;
+	vertical-align: top;
+	padding-left: 4px;
+	padding-right: 4px;
+	padding-bottom: 6px;
+	border: 1px solid #000000;
+}
+
+.screen_only
+{
+	display:none;
+}
+
+.calendar_m_w_table_daynumber
+{
+	font-weight: bold;
+	font-family: Arial;
+	font-size: 10px;
+	vertical-align: top;
+	background-color: #778899;
+	border: 1px solid #000000;
+	margin: 1px;
+	padding: 1px;
+}
+
+A.event_entry
+{
+	font-size:10px;
+}
+
+/* month view */
+
+.calendar_month_identifier
+{
+	font-size:18px;
+	font-weight: bold;
+}
+
+.calendar_user_identifier
+{
+	font-size:12px;
+	color: #009999;
+	font-weight: bold;
+}
+
+.calendar_week_minical_table
+{
+	width:100%;
+	border: 0px solid #000000
+}
+
+.calendar_weekinfo
+{
+	color: #000000;
+	padding-left: 2px;
+	padding-right: 2px;
+	font-size: 10px;
+	font-weight: bold;
+	font-style: italic;
+}
Index: /contrib/ProjectManager/templates/default/csv_import.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/csv_import.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/csv_import.tpl	(revision 3594)
@@ -0,0 +1,74 @@
+<!-- BEGIN filename -->
+	  		<TR>
+	    		<TD>{lang_csvfile}</td>
+				<td><INPUT NAME="csvfile" SIZE=30 TYPE="file" VALUE="{csvfile}"></td>
+			</tr>
+			<tr>
+				<td>{lang_fieldsep}</td>
+				<td><input name="fieldsep" size=1 value="{fieldsep}"></td>
+			</tr>
+			<tr>
+				<td>{lang_charset}</td>
+				<td>
+			{select_charset}
+				</td>
+			</tr>
+			<tr><td>&nbsp;</td>
+				<td><INPUT NAME="convert" TYPE="submit" VALUE="{submit}"></TD>
+			</TR>
+<!-- END filename -->
+
+<!-- BEGIN fheader -->
+			<tr>
+				<td><b>{lang_csv_fieldname}</b></td>
+				<td><b>{lang_info_fieldname}</b></td>
+				<td><b>{lang_translation}</b></td>
+			</tr>
+<!-- END fheader -->
+
+<!-- BEGIN fields -->
+			<tr>
+				<td>{csv_field}</td>
+				<td><SELECT name="cal_fields[{csv_idx}]">{cal_fields}</select></td>
+				<td><input name="trans[{csv_idx}]" size=60 value="{trans}"></td>
+			</tr>
+<!-- END fields -->
+
+<!-- BEGIN ffooter -->
+			<tr>
+				<td rowspan="2" valign="middle" nowrap><br>{submit}</TD>
+				<td colspan="2"><br>
+					{lang_start} <INPUT name="start" type="text" size="5" value="{start}"> &nbsp; &nbsp;
+					{lang_max} <INPUT name="max" type="text" size="3" value="{max}"><td>
+			</tr>
+			<tr>
+				<td colspan="2"><INPUT name="debug" type="checkbox" value="1"{debug}> {lang_debug}</td>
+			</TR>
+			<tr><td colspan="3">&nbsp;<p>
+				{help_on_trans}
+			</td></tr>
+<!-- END ffooter -->
+
+<!-- BEGIN imported -->
+			<tr>
+				<td colspan=2 align=center>
+					{log}<p>
+					{anz_imported}
+				</td>
+			</TR>
+<!-- END imported -->
+
+<!-- BEGIN import -->
+<br/>
+<center>
+		<form {enctype} action="{action_url}" method="post">
+		<table>
+
+		{rows}
+
+		</table>
+		{hiddenvars}
+		</form>
+
+</center>
+<!-- END import -->
Index: /contrib/ProjectManager/templates/default/listUsers.php
===================================================================
--- /contrib/ProjectManager/templates/default/listUsers.php	(revision 3594)
+++ /contrib/ProjectManager/templates/default/listUsers.php	(revision 3594)
@@ -0,0 +1,167 @@
+<?php			
+	
+	$GLOBALS['phpgw_info']['flags'] = array(
+		'noheader' => True,
+		'nonavbar' => True,
+		'currentapp' => 'calendar'		
+	);
+	
+	include('../../../header.inc.php');
+	include('../../../phpgwapi/templates/default/head.inc.php');
+
+	$owner = $GLOBALS['_GET']['owner'];
+	
+	if(!@is_object($GLOBALS['phpgw']->js))	{
+		$GLOBALS['phpgw']->js = CreateObject('phpgwapi.javascript');
+	}
+	
+	$GLOBALS['phpgw']->js->validate_file('jscode','scripts','preferences');		
+	$t = &$GLOBALS['phpgw']->template;						
+	// seta o Template
+	$t->set_file(array('addUser_t' => '../../../calendar/templates/default/listUsers.tpl'));
+	
+	$obj_account = CreateObject('phpgwapi.accounts',$this->bo->owner);
+
+	$post_select_organization = $_POST['select_organization'];
+	$post_select_sector = $_POST['select_sector'];
+	$change_organization = $_POST['change_organization'];
+	
+	$obj_org_sector = CreateObject('phpgwapi.sector_search_ldap');
+	if ((!$post_select_organization) && (!$post_select_sector)) //primeira vez
+	{
+		$user_org = $obj_account->get_organization($GLOBALS['phpgw_info']['user']['account_dn']);
+		$user_sector = $obj_account->get_sector($GLOBALS['phpgw_info']['user']['account_dn']);
+		$user_context = $obj_account->get_context($GLOBALS['phpgw_info']['user']['account_dn']);
+		
+		$organizations_info = $obj_org_sector->organization_search($GLOBALS['phpgw_info']['server']['ldap_context']);
+		@asort($organizations_info);
+		@reset($organizations_info);					
+		$sectors_info = $obj_org_sector->sector_search('ou='.$user_org.','.$GLOBALS['phpgw_info']['server']['ldap_context']);
+	}
+	else //mudou uma das combos
+	{
+		$user_org = $post_select_organization;
+		$user_sector = $post_select_sector;
+		if ($change_organization == "True")
+			$user_context = 'ou='.$user_org.','.$GLOBALS['phpgw_info']['server']['ldap_context'];
+		else
+			$user_context = $post_select_sector;
+		$organizations_info = $obj_org_sector->organization_search($GLOBALS['phpgw_info']['server']['ldap_context']);
+		@asort($organizations_info);
+		@reset($organizations_info);					
+		$sectors_info = $obj_org_sector->sector_search('ou='.$user_org.','.$GLOBALS['phpgw_info']['server']['ldap_context']);
+	}
+		
+	foreach($organizations_info as $organization)
+	{
+		$combo_organization .= '<option value="' . $organization . '"'; 	
+		
+		if (!$post_select_organization)
+		{
+			if(strtolower($organization) == strtolower($user_org))
+			{
+				$combo_organization .= ' selected';
+			}
+		}
+		else
+		{
+			if(strtolower($organization) == strtolower($post_select_organization))
+			{
+				$combo_organization .= ' selected';
+			}
+		}	
+		$combo_organization .= '>' .$organization.'</option>'."\n";
+	}
+	
+	$combo_sector .= '<option value="ou='.$user_org.','.$GLOBALS['phpgw_info']['server']['ldap_context'].'"> --------- </option>'."\n";
+	foreach($sectors_info as $sector)
+	{
+		if ($sector->sector_level == 1)
+		{
+			$sector->sector_name = '- '.$sector->sector_name;
+		}
+		else
+		{
+			$sector_space = '';
+			for ($i=1; $i < $sector->sector_level; $i++)
+				$sector_space = '---'.$sector_space;
+			$sector->sector_name = $sector_space.' '.$sector->sector_name;
+		}
+	
+		$combo_sector .= '<option value="' . $sector->sector_context . '"';
+
+		if (!$post_select_sector)
+		{
+			if($sector->sector_name == $user_sector)
+				$combo_sector .= ' selected';
+		}
+		else
+		{
+			if($sector->sector_context == $post_select_sector)
+				$combo_sector .= ' selected';
+		}
+		$combo_sector .= '>' .$sector->sector_name.'</option>'."\n";
+	}			
+	
+	// Monta lista de Grupos e Usuários
+	$users = Array();
+	$groups = Array();
+
+	$ds = $GLOBALS['phpgw']->common->ldapConnect();
+    if ($ds) 
+    {
+		$sr=ldap_list($ds, $user_context, ("(&(cn=*)(phpgwaccounttype=g))"));
+		$info = ldap_get_entries($ds, $sr);
+		for ($i=0; $i<$info["count"]; $i++)
+			$groups[$uids=$info[$i]["gidnumber"][0]] = Array('name'	=>	$uids=$info[$i]["cn"][0], 'type'	=>	g);
+
+		$sr=ldap_list($ds, $user_context, ("(&(cn=*)(phpgwaccounttype=u)(!(cn=lista*)))"));
+		$info = ldap_get_entries($ds, $sr);
+		for ($i=0; $i<$info["count"]; $i++)
+		{
+			if ($info[$i]["phpgwaccountvisible"][0] == '-1')
+				continue;
+			$users[$uids=$info[$i]["uidnumber"][0]] = Array('name'	=>	$uids=$info[$i]["cn"][0], 'type'	=>	u);
+		}
+	}
+	ldap_close($ds);
+    	
+	@asort($users);
+	@reset($users);	
+	@asort($groups);
+	@reset($groups);
+
+	$options ='';
+	if(count($groups))
+		$options .= '<option  value="-1" disabled>--------------------&nbsp;&nbsp;&nbsp;&nbsp;Grupos&nbsp;&nbsp;&nbsp;&nbsp;------------------- </option>'."\n";
+		
+	foreach($groups as $id => $user_array) {
+			$newId = $id.'U';
+			$options .= '<option  value="'.$newId.'">'.utf8_decode($user_array['name']).$array_app.'</option>'."\n";
+	}
+	
+	if(count($users))
+		$options .= '<option  value="-1" disabled>-------------------&nbsp;&nbsp;&nbsp;&nbsp;Usuários&nbsp;&nbsp;&nbsp;&nbsp;------------------ </option>'."\n";
+	
+	foreach($users as $id => $user_array) {
+		if($owner != $id){
+			$newId = $id.'U';
+			$options .= '<option  value="'.$newId.'">'.utf8_decode($user_array['name']).$array_app.'</option>'."\n";
+		}
+	}
+
+	$t->set_var ('lang_Organization',lang('Organization'));
+	$t->set_var ('lang_Sector',lang('Sector'));
+	$t->set_var ('lang_Calendar',lang('Calendar'));
+	$t->set_var ('lang_Add_Participants',lang('Add Participants'));
+	$t->set_var ('lang_to_Search',lang('to Search'));
+	$t->set_var ('lang_Close',lang('Close'));
+	$t->set_var ('lang_Add',lang('Add'));
+	
+	$t->set_var('options',$options);
+	$t->set_var('combo_organization', $combo_organization);
+	$t->set_var('combo_sector', $combo_sector);
+	$t->parse('out','addUser_t',true);
+	$t->p('out');
+	$GLOBALS['phpgw']->common->phpgw_exit();	
+?>
Index: /contrib/ProjectManager/templates/default/month.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/month.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/month.tpl	(revision 3594)
@@ -0,0 +1,6 @@
+
+<table id="calendar_month_table" width="100%" border="0" bordercolor="#FFFFFF" cellspacing="2" cellpadding="2" cols={cols}>
+	{row}
+</table>
+
+
Index: /contrib/ProjectManager/templates/default/roles.xet
===================================================================
--- /contrib/ProjectManager/templates/default/roles.xet	(revision 3594)
+++ /contrib/ProjectManager/templates/default/roles.xet	(revision 3594)
@@ -0,0 +1,75 @@
+<?xml version="1.0"?>
+<!-- $Id: roles.xet 18388 2005-05-31 21:28:29Z ralfbecker $ -->
+<overlay>
+	<template id="projectmanager.roles" template="" lang="" group="0" version="0.2.001">
+		<vbox>
+			<hbox orient=",0,0">
+				<menulist>
+					<menupopup label="Project" onchange="1" no_lang="1" id="pm_id" options="All"/>
+				</menulist>
+				<description no_lang="1" align="right" id="msg" class="redItalic"/>
+			</hbox>
+			<grid>
+				<columns>
+					<column/>
+					<column/>
+					<column disabled="!@pm_id"/>
+					<column/>
+					<column/>
+					<column/>
+					<column/>
+					<column/>
+					<column/>
+					<column disabled="@view"/>
+				</columns>
+				<rows>
+					<row class="th">
+						<description value="Role"/>
+						<description value="Description"/>
+						<description align="center" value="Only this project"/>
+						<description value="Read"/>
+						<description value="Edit"/>
+						<description value="Delete"/>
+						<description align="center" value="Add element"/>
+						<description value="Budget"/>
+						<description align="center" value="Edit budget"/>
+						<description/>
+					</row>
+					<row class="row" disabled="@view">
+						<textbox size="16" maxlength="80" id="${row}[role_title]"/>
+						<textbox size="24" maxlength="255" id="${row}[role_description]"/>
+						<checkbox align="center" id="${row}[pm_id]"/>
+						<checkbox align="center" id="${row}[acl_read]"/>
+						<checkbox align="center" id="${row}[acl_edit]"/>
+						<checkbox align="center" id="${row}[acl_delete]"/>
+						<checkbox align="center" id="${row}[acl_add]"/>
+						<checkbox align="center" id="${row}[acl_budget]"/>
+						<checkbox align="center" id="${row}[acl_edit_budget]"/>
+						<description align="right"/>
+					</row>
+					<row class="row">
+						<description id="${row}[role_title]"/>
+						<description id="${row}[role_description]"/>
+						<checkbox align="center" readonly="true" id="${row}[pm_id]"/>
+						<checkbox align="center" readonly="true" id="${row}[acl_read]"/>
+						<checkbox align="center" readonly="true" id="${row}[acl_edit]"/>
+						<checkbox align="center" readonly="true" id="${row}[acl_delete]"/>
+						<checkbox align="center" readonly="true" id="${row}[acl_add]"/>
+						<checkbox align="center" readonly="true" id="${row}[acl_budget]"/>
+						<checkbox align="center" readonly="true" id="${row}[acl_edit_budget]"/>
+						<hbox orient=",0,0">
+							<button label="Edit" id="edit[$row_cont[role_id]]" image="edit" statustext="Edit this role"/>
+							<button label="Delete" onclick="return confirm('Delete this role');" align="right" id="delete[$row_cont[role_id]]" image="delete" statustext="Delete this role"/>
+						</hbox>
+					</row>
+				</rows>
+			</grid>
+			<hbox>
+				<button label="Save" id="save" statustext="Save the changes made"/>
+				<button label="Apply" id="apply" statustext="Applies the changes made"/>
+				<button label="Cancel" onclick="window.close();" id="cancel" statustext="Close the window, discard the changes made"/>
+				<html id="js"/>
+			</hbox>
+		</vbox>
+	</template>
+</overlay>
Index: /contrib/ProjectManager/templates/default/viewDocument.tpll
===================================================================
--- /contrib/ProjectManager/templates/default/viewDocument.tpll	(revision 3594)
+++ /contrib/ProjectManager/templates/default/viewDocument.tpll	(revision 3594)
@@ -0,0 +1,499 @@
+<!-- BEGIN main -->
+
+<table width="100%" border="0" cellspacing="0" cellpading="0" bgcolor="white" hheight="100px">
+	<tr class="topRow">
+		<th width="33%">{download}</th>
+		<th width="33%">{view_online}</th>
+		<th width="33%">{delete}</th>
+	</tr>
+</table>
+<br>
+<table width="100%" border="0" cellspacing="0" cellpading="0">
+	<tr>
+		<th width="20%" id="tab1" class="activetab" onclick="javascript:tab.display(1);"><a href="#" tabindex="0" accesskey="1" onfocus="tab.display(1);" onclick="tab.display(1); return(false);" style="font-size:10px;">{lang_informations}</a></th>
+		<th width="20%" id="tab2" class="activetab" onclick="javascript:tab.display(2);"><a href="#" tabindex="0" accesskey="2" onfocus="tab.display(2);" onclick="tab.display(2); return(false);" style="font-size:10px;">{lang_all_versions}</a></th>
+		<th width="20%" id="tab3" class="activetab" onclick="javascript:tab.display(3);"><a href="#" tabindex="0" accesskey="3" onfocus="tab.display(3);" onclick="tab.display(3); return(false);" style="font-size:10px;">{lang_linked_documents}</a></th>
+		<th width="20%" id="tab4" class="activetab" onclick="javascript:tab.display(4);"><a href="#" tabindex="0" accesskey="4" onfocus="tab.display(4);" onclick="tab.display(4); return(false);" style="font-size:10px;">{lang_notifications}</a></th>
+		<th width="20%" id="tab5" class="activetab" onclick="javascript:tab.display(5);"><a href="#" tabindex="0" accesskey="5" onfocus="tab.display(5);" onclick="tab.display(5); return(false);" style="font-size:10px;">{lang_acl}</a></th>
+	</tr>
+</table>
+<div style="border-left: dotted 1px grey; border-bottom: dotted 1px grey; border-right: dotted 1px grey; background: white;">
+<div id="tabcontent1" class="inactivetab" bgcolor="white">
+	{informations}
+</div>
+
+<div id="tabcontent2" class="inactivetab">
+
+	<table cellspacing="5" cellpadding="0" border="0" width="100%">
+		<tr>
+			<td></td>
+			<td></td>
+			<td class="filelist" style="border-bottom: 1pt solid #000080;"><i>{lang_version}</i></td>
+			<td rowspan="{rownum}" style="border-left: 1pt solid #000080;">&nbsp;</td>
+			<td class="filelist" style="border-bottom: 1pt solid #000080;"><i>{lang_upload_date}</i></td>
+			<td rowspan="{rownum}" style="border-left: 1pt solid #000080;">&nbsp;</td>
+			<td class="filelist" style="border-bottom: 1pt solid #000080;"><i>{lang_comment}</i></td>
+			<td rowspan="{rownum}" style="border-left: 1pt solid #000080;">&nbsp;</td>
+			<td class="filelist" style="border-bottom: 1pt solid #000080;"><i>{lang_uploaded_by}</i></td>
+			<!-- if (($document->getAccessMode($user) >= M_READWRITE) && (count($versions) > 1))
+ -->
+			<td></td>
+		</tr>
+		{versions}
+	</table>
+</div>
+
+<div id="tabcontent3" class="inactivetab">
+<!--
+<table cellspacing="5" cellpadding="0" border="1">
+	<?
+	if ($rownum > 1)
+	{
+		?>
+		<tr>
+		<td></td>
+		<td class="filelist" style="border-bottom: 1pt solid #000080;"><i><?printMLText("name");?></i></td>
+		<td rowspan="<?print $rownum;?>" style="border-left: 1pt solid #000080;">&nbsp;</td>
+		<td class="filelist" style="border-bottom: 1pt solid #000080;"><i><?printMLText("comment");?></i></td>
+		<td rowspan="<?print $rownum;?>" style="border-left: 1pt solid #000080;">&nbsp;</td>
+		<td class="filelist" style="border-bottom: 1pt solid #000080;"><i><?printMLText("document_link_by");?></i></td>
+		<td rowspan="<?print $rownum;?>" style="border-left: 1pt solid #000080;">&nbsp;</td>
+		<td class="filelist" style="border-bottom: 1pt solid #000080;"><i><?printMLText("document_link_public");?></i></td>
+		<td></td>
+		</tr>
+		<?
+		foreach($links as $link)
+		{
+			$responsibleUser = $link->getUser();
+			$targetDoc = $link->getTarget();
+			
+			print "<tr>";
+			print "<td><img src=\"images/file.gif\" width=18 height=18 border=0></td>";
+			print "<td class=\"linklist\"><a href=\"out.ViewDocument.php?documentid=".$targetDoc->getID()."\" class=\"linklist\">".$targetDoc->getName()."</a></td>";
+			print "<td class=\"linklist\">".$targetDoc->getComment()."</td>";
+			print "<td class=\"linklist\">".$responsibleUser->getFullName()."</td>";
+			print "<td class=\"linklist\">" . (($link->isPublic()) ? getMLText("yes") : getMLText("no")) . "</td>";
+			print "<td>";
+			if (($user->getID() == $responsibleUser->getID()) || ($user->getID() == $settings->_adminID) || ($link->isPublic() && ($document->getAccessMode($user) >= M_READWRITE )))
+				print "<a href=\"../op/op.RemoveDocumentLink.php?documentid=".$documentid."&linkid=".$link->getID()."\"><img src=\"images/del.gif\" border=0></a>";
+			print "</td>";
+			print "</tr>";
+		}
+	}
+	else
+		print "<tr><td class=\"filelist\">".getMLText("no_document_links")."</td></tr>";
+	?>
+</table>
+
+
+if ($user->getID() != $settings->_guestID)
+
+	<form action="../op/op.AddDocumentLink.php" name="form1">
+	<input type="Hidden" name="documentid" value="<?print $documentid;?>">
+	<table>
+		<tr>
+			<td class="inputDescription"><?printMLText("choose_target_document");?>:</td>
+			<td><?printDocumentChooser("form1");?></td>
+		</tr>
+		<?
+			if ($document->getAccessMode($user) >= M_READWRITE)
+			{
+				print "<tr><td class=\"inputDescription\">".getMLText("document_link_public")."</td><td class=\"inputDescription\">";
+				print "<input type=\"Radio\" name=\"public\" value=\"true\" checked>" . getMLText("yes") . "&nbsp;&nbsp;";
+				print "<input type=\"Radio\" name=\"public\" value=\"false\">" . getMLText("no");
+				print "</td></tr>";
+			}
+		?>
+		<tr>
+			<td colspan="2"><br><input type="Submit" value="<?printMLText("add_document_link");?>"></td>
+		</tr>
+	</table>
+	</form>-->
+</div>
+
+<div id="tabcontent4" class="inactivetab">
+	<table cellspacing="0" cellpadding="5" border="0" width="100%">
+		<tr>
+			<td style="border-bottom: 1pt solid #000080;" width="50px">&nbsp;</td>
+			<td style="border-bottom: 1pt solid #000080;" class="notifylist"><i>{lang_name}</i></td>
+			<td style="border-bottom: 1pt solid #000080;" width="50px">&nbsp;</td>
+		</tr>
+		{notifications}
+		<tr>
+			<td colspan="3">&nbsp;</td>
+		</tr>
+	</table>
+
+	<form action="{notify_form_action}" method="post" name="notify_form">
+		<fieldset><legend>{lang_add_notification}</legend>
+		<table cellspacing="0" cellpadding="5" border="0" width="100%">
+			<tr>
+				<td class="inputDescription">{lang_user}:</td>
+				<td>
+					{select_userid}
+				</td>
+			</tr>
+			<tr>
+				<td class="inputDescription">{lang_group}:</td>
+				<td>
+					{select_groupid}
+				</td>
+			</tr>
+		</table>
+		</fieldset>
+	</form>
+</div>
+
+<div id="tabcontent5" class="inactivetab">
+	{change_owner}
+	{display_acl}
+</div>
+
+<div id="tabcontent6" class="inactivetab">
+	<form enctype="multipart/form-data" name="update_file_form" method="post" action="{action_update_file}">
+	<table cellpadding="5" cellspacing="0" border="0" width="100%">
+		<tr class="row_on">
+			<td class="description" valign="top" width="150px">{lang_filename}:</td>
+			<td class="infos" colspan="2"><input type="file" name="userfile"></td>
+		</tr>
+		<tr class="row_on">
+			<td class="description" valign="top">{lang_comment}:</td>
+			<td class="infos" colspan="2"><textarea name="comment" style="width: 100%;"></textarea></td>
+		</tr>
+		<tr class="row_on">
+			<td class="description" valign="top">{lang_expires}:</td>
+			<td class="infos" valign="top" colspan="2">
+				{select_expire_update}
+				<span id="jscalspan_update" class="{expire_class_update}">{expire_date_update}</span>
+			</td>
+		</tr>
+		<tr>
+			<td colspan="3" valign="top">
+				<input type="submit" name="save" value="{lang_update}">
+				<button type="button" name="cancel" value="{lang_cancel}" onClick="javascript:tab.display(1);">
+					{lang_cancel}
+				</button>
+			</td>
+		</tr>
+	</table>
+	</form>
+</div>
+
+
+
+</div>
+<!-- END main -->
+
+<!-- BEGIN lock_row -->
+	<tr class="row_off">
+		<td class="description" valign="top">{lang_lock_status}:</td>
+		<td class="infos" colspan="2">
+			<input id="lockStatus" type="hidden" name="lockStatus" value="unchanged">
+			<input type="checkbox" onchange="javascript:toggleLock(this);" {checked_lock_status}>
+			<div id="currentLockStatus" class="active">
+				{lang_current_status}
+			</div>
+			<div id="lockFile" class="inactive">
+				{lang_file_gets_unlocked}
+			</div>
+			<div id="unlockFile" class="inactive">
+				{lang_file_gets_locked}
+			</div>
+		</td>
+	</tr>
+<!-- END lock_row -->
+
+<!-- BEGIN version_row -->
+	<tr>
+		<td>
+			{url_view_online}
+		</td>
+		<td>
+			<a href="{url_download_file}" class="oldcontent"><img src="{download_image}" width=22 height=22 border=0 title="{lang_download}"></a>
+		</td>
+		<td class="filelist" align="center">{version_version}</td>
+		<td class="filelist">{version_date}</td>
+		<td class="filelist">{version_comment}</td>
+		<td class="filelist">{version_uploadingUser}</td>
+		<td>{url_delete_file}</td>
+	</tr>
+<!-- END version_row -->
+
+<!-- BEGIN information_ro -->
+	<table cellpadding="5" cellspacing="0" border="0" width="100%">
+		<tr>
+			<td class="infos" valign="top" width="150px">{lang_filename}:</td>
+			<td style="border-left: 1pt solid #000080;" rowspan="14" width="1px">&nbsp;</td>
+			<td class="infos">{filename}</td>
+		</tr>
+		<tr>
+			<td class="infos" valign="top">{lang_owner}:</td>
+			<td class="infos">
+				<a class="infos" href="mailto:{owner_email}">{owner_fullname}</a>
+			</td>
+		</tr>
+		<tr>
+			<td class="infos" valign="top">{lang_comment}:</td>
+			<td class="infos">{comment}</td>
+		</tr>
+		<tr>
+			<td class="infos" valign="top">{lang_creation_date}:</td>
+			<td class="infos">{creation_date}</td>
+		</tr>
+		<tr>
+			<td class="infos" valign="top">{lang_keywords}:</td>
+			<td class="infos">{keywords}</td>
+		</tr>
+		{locking}
+		<tr>
+			<td>&nbsp;</td>
+			<td>&nbsp;</td>
+		</tr>
+		<tr>
+			<td class="infos" valign="top">{lang_last_update}</td>
+			<td class="infos">{last_update}</td>
+		</tr>
+		<tr>
+			<td class="infos" valign="top">{lang_current_version}:</td>
+			<td class="infos">{current_version}</td>
+		</tr>
+		<tr>
+			<td class="infos" valign="top">{lang_comment_for_current_version}:</td>
+			<td class="infos" valign="top">{current_comment}</td>
+		</tr>
+		<tr>
+			<td class="infos" valign="top">{lang_uploaded_by}:</td>
+			<td class="infos">
+				<a class="infos" href="mailto:{updater_email}">{updater_fullname}</a>
+			</td>
+		</tr>
+		<tr>
+			<td class="infos" valign="top">{lang_file_size}:</td>
+			<td class="infos">{file_size} bytes</td>
+		</tr>
+		<tr>
+			<td class="infos" valign="top">{lang_mime_type}:</td>
+			<td class="infos">
+				<img align="absmiddle" src="images/icons/<?print getMimeIcon($latestContent->getFileType());?>"> 
+				{mime_type}
+			</td>
+		</tr>
+		<tr>
+			<td class="infos" valign="top">{lang_expires}:</td>
+			<td class="infos" valign="top">
+				{expire_date_ro}
+			</td>
+		</tr>
+	</table>
+<!-- END information_ro -->
+
+<!-- BEGIN information_rw -->
+	<form name="info_form" method="post" action="{action_informations}">
+	<fieldset><legend>{lang_general_information}</legend>
+	<table cellpadding="5" cellspacing="0" border="0" width="100%">
+		<tr class="row_on">
+			<td class="description" valign="top" width="150px">{lang_filename}:</td>
+			<td class="infos"><input style="width: 100%; border: dotted 1px silver;" type="text" name="fname" value="{filename}"></td>
+			<td align="center" width="150px"><a href="#" tabindex="0" onfocus="tab.display(6);" onclick="tab.display(6); return(false);" style="font-size:10px;">{lang_update_document}</a></td>
+		</tr>
+		<tr class="row_off">
+			<td class="description" valign="top" width="150px">{lang_folder}:</td>
+			<td class="infos">
+				<input id="targetname" style="width: 100%; border: dotted 1px silver;" type="text" name="targetname" value="{foldername}" disabled>
+				<input type="hidden" id="targetid" name="targetid" value="unchanged">
+			</td>
+			<td align="center" width="150px"><a href="#" onclick="selectFolder({current_folder_id},'info_form'); return(false);" style="font-size:10px;">{lang_move_document}</a></td>
+		</tr>
+		<tr class="row_on">
+			<td class="description" valign="top">{lang_owner}:</td>
+			<td class="infos" colspan="2">
+				<a class="infos" href="mailto:{owner_email}">{owner_fullname}</a>
+			</td>
+		</tr>
+		<tr class="row_off">
+			<td class="description" valign="top">{lang_creation_date}:</td>
+			<td class="infos" colspan="2">{creation_date}</td>
+		</tr>
+		<tr class="row_on">
+			<td class="description" valign="top">{lang_comment}:</td>
+			<td class="infos" colspan="2"><textarea name="comment" style="width: 100%;">{comment}</textarea></td>
+		</tr>
+		<tr class="row_off">
+			<td class="description" valign="top">{lang_keywords}:</td>
+			<td class="infos" colspan="2"><textarea name="keywords" style="width: 100%;">{keywords}</textarea></td>
+		</tr>
+		<tr class="row_on">
+			<td class="description" valign="top">{lang_expires}:</td>
+			<td class="infos" valign="top" colspan="2">
+				{select_expire}
+				<span id="jscalspan" class="{expire_class}">{expire_date}</span>
+			</td>
+		</tr>
+		{locking}
+		<tr class="row_on">
+			<td colspan="3" valign="top">
+				<input type="submit" name="save" value="{lang_save}">
+				<button type="button" name="cancel" value="{lang_cancel}" onClick="window.close()">
+					{lang_cancel}
+				</button>
+			</td>
+		</tr>
+	</table>
+	</fieldset>
+
+	<fieldset><legend>{lang_information_about_last_update}</legend>
+	<table cellpadding="5" cellspacing="0" border="0" width="100%">
+		<tr class="row_on">
+			<td class="description" valign="top" width="150px">{lang_last_update}</td>
+			<td class="infos">{last_update}</td>
+			<td class="description" valign="top" width="150px">{lang_current_version}:</td>
+			<td class="infos">{current_version}</td>
+		</tr>
+		<tr class="row_off">
+			<td class="description" valign="top">{lang_uploaded_by}:</td>
+			<td class="infos">
+				<a class="infos" href="mailto:{updater_email}">{updater_fullname}</a>
+			</td>
+			<td class="description" valign="top">{lang_file_size}:</td>
+			<td class="infos">{file_size} bytes</td>
+		</tr>
+		<tr class="row_on">
+			<td class="description" valign="top">{lang_comment_for_current_version}:</td>
+			<td class="infos" valign="top" colspan="3">{current_comment}</td>
+		</tr>
+		<tr>
+			<td class="description" valign="top">{lang_mime_type}:</td>
+			<td class="infos" colspan="3">
+				<img align="absmiddle" src="images/icons/<?print getMimeIcon($latestContent->getFileType());?>"> 
+				{mime_type}
+			</td>
+		</tr>
+	</table>
+	</fieldset>
+	</form>
+<!-- END information_rw -->
+
+<!-- BEGIN block_download -->
+<a href="{download_link}"><img src="{download_image}" align="middle" border="0">{lang_download}</a>
+<!-- END block_download -->
+
+<!-- BEGIN block_view_online -->
+<a target="_blank" href="{view_link}"><img src="{view_image}" align="middle" border="0">{lang_view_online}</a>
+<!-- END block_view_online -->
+
+<!-- BEGIN block_delete -->
+<a href="{delete_link}" onClick="return confirm('{lang_confirm_delete}');"><img src="{delete_image}" align="middle" border="0">{lang_delete}</a>
+<!-- END block_delete -->
+
+<!-- BEGIN notification_row -->
+	<tr>
+		<td><img src="{notify_image}" width=16 height=16></td>
+		<td class="notifylist">{notify_username}</td>
+		<td align="right"><a href="{link_notify_delete}"><img title="{lang_delete_this_notification}" src="{delete_image}" width=15 height=15 border=0></a>
+	</tr>
+<!-- END notification_row -->
+
+<!-- BEGIN block_change_owner -->
+	<form name="change_owner" action="{action_change_owner}" method="post">
+		<fieldset><legend>{lang_owner}</legend>
+		<table cellspacing="0" cellpadding="5" border="0" width="100%">
+			<tr>
+				<td>
+					{select_ownerid}
+				</td>
+				<td><input type="Submit"></td>
+			</tr>
+		</table>
+		</fieldset>
+		<table cellspacing="0" cellpadding="5" border="0" width="100%">
+			<tr>
+				<td>&nbsp;</td>
+			</tr>
+		</table>
+	</form>
+<!-- END block_change_owner -->
+
+<!-- BEGIN block_acl_inherite -->
+	<fieldset><legend>{lang_acl}</legend>
+	<div>{lang_acl_get_inherited}</div>
+	<p>
+	<a href="{link_acl_copy}">{lang_copy_acl}</a><br>
+	<a href="{link_acl_empty}">{lang_create_empty_acl}</a><br>
+	</fieldset>
+<!-- END block_acl_inherite -->
+
+<!-- BEGIN block_acl_notinherite -->
+	<fieldset><legend>{lang_acl}</legend>
+	<div>{lang_inherite_acl_again}</div>
+	<p>
+	<a href="{link_acl_inherit_again}">{lang_inherit_acl_again}</a><br>
+
+	<fieldset><legend>{lang_default_access}</legend>
+	<form name="change_default_access" action="{action_change_default_access}" method="post">
+		<table cellspacing="0" cellpadding="5" border="0" width="100%">
+			<tr>
+				<td>
+					{select_default_access}
+				</td>
+			</tr>
+		</table>
+	</form>
+	</fieldset>
+
+	<fieldset><legend>{lang_current_acl}</legend>
+		<table cellspacing="0" cellpadding="5" border="0" width="100%">
+			<tr>
+				<td></td>
+				<td class="accessList" style="border-bottom: 1pt solid #000080;"><i>{lang_name}</i></td>
+				<!-- <td rowspan="<?print $rownum;?>" style="border-left: 1pt solid #000080;">&nbsp;</td> -->
+				<td class="accessList" style="border-bottom: 1pt solid #000080;"><i>{lang_access_mode}</i></td>
+				<td></td>
+				<td></td>
+			</tr>
+			{acls}
+		</table>
+	</fieldset>
+
+	<fieldset><legend>{lang_add_acl}</legend>
+	<form name="add_acl" action="{action_add_acl}" method="post">
+		<table cellspacing="0" cellpadding="5" border="0" width="100%">
+			<tr>
+				<td class="inputDescription">{lang_user}:</td>
+				<td>
+					{select_add_acl_userid}
+				</td>
+				<td rowspan="3">
+					<input type="Submit">
+				</td>
+			</tr>
+			<tr>
+				<td class="inputDescription">{lang_group}:</td>
+				<td>
+					{select_add_acl_groupid}
+				</td>
+			</tr>
+			<tr>
+				<td class="inputDescription">{lang_permission}:</td>
+				<td>
+					{select_add_acl_permission}
+				</td>
+			</tr>
+		</table>
+	</form>
+	</fieldset>
+
+	</fieldset>
+<!-- END block_acl_notinherite -->
+
+<!-- BEGIN acl_row -->
+	<form method="post" action="{action_acl_row}">
+	<tr>
+		<td width="20px"><img src="{acl_image}" width=16 height=16></td>
+		<td class="acllist">{acl_username}</td>
+		<td class="acllist" width="310px">{acl_selectbox}</td>
+		<td width="20px"><input type="Image" src="{save_image}" alt="{lang_save}"></td>
+		<td width="20px" align="right"><a href="{link_acl_delete}" onClick="return confirm('{lang_confirm_acl_delete}');"><img title="{lang_delete_this_acl}" alt="{lang_delete}" src="{delete_image}" width=15 height=15 border=0></a>
+	</tr>
+	</form>
+<!-- END acl_row -->
+
Index: /contrib/ProjectManager/templates/default/edit.xet
===================================================================
--- /contrib/ProjectManager/templates/default/edit.xet	(revision 3594)
+++ /contrib/ProjectManager/templates/default/edit.xet	(revision 3594)
@@ -0,0 +1,273 @@
+<?xml version="1.0"?>
+<!-- $Id: edit.xet 24288 2007-07-11 15:43:09Z ralfbecker $ -->
+<overlay>
+	<template id="projectmanager.edit.general" template="" lang="" group="0" version="0.4.001">
+		<grid width="100%" height="260">
+			<columns>
+				<column width="95"/>
+				<column width="40%"/>
+				<column width="8%"/>
+				<column/>
+			</columns>
+			<rows>
+				<row class="row">
+					<description options=",,,cat_id" value="Category"/>
+					<menulist>
+						<menupopup type="select-cat" options="None" id="cat_id"/>
+					</menulist>
+					<description value="Access" options=",,,pm_access"/>
+					<menulist>
+						<menupopup id="pm_access" no_lang="1"/>
+					</menulist>
+				</row>
+				<row class="row">
+					<description options=",,,pm_priority" value="Priority"/>
+					<menulist>
+						<menupopup type="select-number" options=",1,10" id="pm_priority"/>
+					</menulist>
+					<description value="State" options=",,,pm_status"/>
+					<menulist>
+						<menupopup id="pm_status" no_lang="1"/>
+					</menulist>
+				</row>
+				<row class="th">
+					<description value="Value"/>
+					<description value="Project"/>
+					<description value="Elementlist" span="all"/>
+				</row>
+				<row class="row">
+					<description options=",,,pm_real_start" value="Startdate"/>
+					<date-time id="pm_real_start"/>
+					<date-time id="ds[pe_real_start]" readonly="true" span="all"/>
+				</row>
+				<row class="row">
+					<description value="- planned" options=",,,pm_planned_end"/>
+					<date-time id="pm_planned_start"/>
+					<date-time id="ds[pe_planned_start]" readonly="true" span="all"/>
+				</row>
+				<row class="row">
+					<description options=",,,pm_real_end" value="Enddate"/>
+					<date-time id="pm_real_end"/>
+					<date-time id="ds[pe_real_end]" readonly="true" span="all"/>
+				</row>
+				<row class="row">
+					<description value="- planned" options=",,,pm_planned_end"/>
+					<date-time id="pm_planned_end"/>
+					<date-time id="ds[pe_planned_end]" readonly="true" span="all"/>
+				</row>
+				<row class="row" disabled="@pm_accounting_type=status">
+					<description options=",,,pm_used_time" value="Times"/>
+					<date-duration id="pm_used_time" options="@duration_format"/>
+					<date-duration id="ds[pe_used_time]" readonly="true" span="all" options="@duration_format"/>
+				</row>
+				<row class="row" disabled="@pm_accounting_type=status">
+					<description options=",,,pm_planned_time" value="- planned"/>
+					<date-duration id="pm_planned_time" options="@duration_format"/>
+					<date-duration id="ds[pe_planned_time]" readonly="true" span="all" options="@duration_format"/>
+				</row>
+				<row class="row">
+					<description options=",,,pm_completion" value="Status"/>
+					<menulist>
+						<menupopup type="select-percent" options="None" id="pm_completion"/>
+					</menulist>
+					<description no_lang="1" id="ds[pe_completion]" readonly="true" span="all"/>
+				</row>
+			</rows>
+		</grid>
+	</template>
+	<template id="projectmanager.edit.description" template="" lang="" group="0" version="0.4.001">
+		<grid width="100%" height="260">
+			<columns>
+				<column/>
+			</columns>
+			<rows>
+				<row valign="top">
+					<htmlarea id="pm_description" mode="extended" height="250px" width="100%" toolbar="true"/>
+				</row>
+			</rows>
+		</grid>
+	</template>
+	<template id="projectmanager.edit.members" template="" lang="" group="0" version="0.4.002">
+		<grid width="100%" height="260" overflow="auto">
+			<columns>
+				<column/>
+				<column/>
+				<column/>
+				<column width="70%"/>
+			</columns>
+			<rows>
+				<row class="th" height="12">
+					<description value="Members"/>
+					<description value="General availibility"/>
+					<description value="Project availibility"/>
+					<hbox>
+						<description value="Role"/>
+						<button label="Edit" onclick="window.open(egw::link('/index.php','menuaction=projectmanager.uiroles.roles&amp;pm_id=$cont[pm_id]'),'roles','dependent=yes,width=740,height=450,scrollbars=yes,status=yes'); return false;" statustext="Add or edit roles and their ACL" id="edit_roles"/>
+					</hbox>
+				</row>
+				<row class="row" height="12" disabled="@view">
+					<menulist>
+						<menupopup type="select-account" id="member[1]" options="select one"/>
+					</menulist>
+					<textbox type="float" id="general_avail[1]" statustext="Percentage the member is availible for projects"/>
+					<textbox type="float" id="availibility[1]" statustext="Percentage the member is availible for this project"/>
+					<hbox options="0,0">
+						<menulist>
+							<menupopup id="role[1]"/>
+						</menulist>
+						<button label="Add" id="add"/>
+					</hbox>
+				</row>
+				<row class="row" valign="top" disabled="!@member">
+					<menulist>
+						<menupopup type="select-account" readonly="true" id="member[$row]"/>
+					</menulist>
+					<textbox type="float" id="general_avail[$row]" statustext="Percentage the member is availible for projects"/>
+					<textbox type="float" id="availibility[$row]" statustext="Percentage the member is availible for this project"/>
+					<menulist>
+						<menupopup id="role[$row]" options="None"/>
+					</menulist>
+				</row>
+			</rows>
+		</grid>
+	</template>
+	<template id="projectmanager.edit.accounting" template="" lang="" group="0" version="0.4.001">
+		<grid width="100%" height="260">
+			<columns>
+				<column width="95"/>
+				<column width="40%"/>
+				<column/>
+			</columns>
+			<rows>
+				<row class="row" valign="top" height="12">
+					<description options=",,,pm_accounting_type" value="Accounting type"/>
+					<menulist span="all">
+						<menupopup id="pm_accounting_type" onchange="1"/>
+					</menulist>
+				</row>
+				<row class="th" height="12" disabled="@no_budget">
+					<description value="Value"/>
+					<description value="Project"/>
+					<description value="Elementlist"/>
+				</row>
+				<row class="row" height="12" disabled="@no_budget">
+					<description options=",,,pm_used_budget" value="Budget"/>
+					<textbox type="float" id="pm_used_budget"/>
+					<textbox type="float" min="2" id="ds[pe_used_budget]" readonly="true"/>
+				</row>
+				<row class="row" valign="top" height="12" disabled="@no_budget">
+					<description options=",,,pm_planned_budget" value="- planned"/>
+					<textbox type="float" id="pm_planned_budget"/>
+					<textbox type="float" min="2" id="ds[pe_planned_budget]" readonly="true"/>
+				</row>
+				<row valign="top">
+					<description/>
+					<description/>
+					<description/>
+				</row>
+			</rows>
+		</grid>
+	</template>
+	<template id="projectmanager.edit.custom" template="" lang="" group="0" version="0.4.001">
+		<grid width="100%" height="260" overflow="auto">
+			<columns>
+				<column/>
+			</columns>
+			<rows>
+				<row class="row" valign="top">
+					<customfields/>
+				</row>
+			</rows>
+		</grid>
+	</template>
+	<template id="projectmanager.edit.links" template="" lang="" group="0" version="0.4.001">
+		<grid width="100%" height="260" overflow="auto">
+			<columns>
+				<column width="95"/>
+				<column/>
+			</columns>
+			<rows>
+				<row class="th" disabled="@view">
+					<description span="all" value="Create new links"/>
+				</row>
+				<row class="row" disabled="@view">
+					<link-to span="all" id="link_to"/>
+				</row>
+				<row class="th">
+					<description span="all" value="Existing links"/>
+				</row>
+				<row class="row_off" valign="top" height="164">
+					<link-list span="all" id="link_to"/>
+				</row>
+			</rows>
+		</grid>
+	</template>
+	<template id="projectmanager.edit" template="" lang="" group="0" version="0.2.001">
+		<grid width="100%">
+			<columns>
+				<column width="100"/>
+				<column width="150"/>
+				<column width="10%"/>
+				<column/>
+			</columns>
+			<rows>
+				<row disabled="!@msg">
+					<description span="all" class="redItalic" no_lang="1" align="center" id="msg"/>
+					<description/>
+					<description/>
+					<description/>
+				</row>
+				<row class="th">
+					<description options=",,,pm_number" value="Project ID"/>
+					<textbox size="20" maxlength="64" no_lang="1" id="pm_number" statustext="leave it empty to have one generated"/>
+					<description options=",,,pm_title" value="Title"/>
+					<textbox size="64" maxlength="255" needed="1" id="pm_title" statustext="a describtive title for the project, must not be empty"/>
+				</row>
+				<row>
+					<tabbox span="all">
+						<tabs>
+							<tab label="General" statustext="Start, End, Category, ..."/>
+							<tab label="Description" statustext="free descriptiv text"/>
+							<tab label="Members" statustext="Project members and roles"/>
+							<tab label="Accounting" statustext="Accounting options"/>
+							<tab label="Custom" statustext="Custom fields"/>
+							<tab label="Links" statustext="Linked elements"/>
+						</tabs>
+						<tabpanels>
+							<template id="projectmanager.edit.general"/>
+							<template id="projectmanager.edit.description"/>
+							<template id="projectmanager.edit.members"/>
+							<template id="projectmanager.edit.accounting"/>
+							<template id="projectmanager.edit.custom"/>
+							<template id="projectmanager.edit.links"/>
+						</tabpanels>
+					</tabbox>
+				</row>
+				<row class="row" disabled="!@pm_id">
+					<description value="Modified"/>
+					<hbox span="2" options="2,0">
+						<date-time readonly="true" id="pm_modified"/>
+						<menulist>
+							<menupopup type="select-account" label="by" readonly="true" id="pm_modifier"/>
+						</menulist>
+					</hbox>
+					<hbox span="2" options="2,0" align="right">
+						<date-time readonly="true" label="created" no_lang="1" id="pm_created"/>
+						<menulist>
+							<menupopup type="select-account" label="by" readonly="true" id="pm_creator"/>
+						</menulist>
+					</hbox>
+				</row>
+				<row>
+					<hbox span="3">
+						<button label="Edit" id="edit" statustext="Edit this project"/>
+						<button label="Save" id="save" statustext="Save the changes made"/>
+						<button label="Apply" id="apply" statustext="Apply the changes"/>
+						<button label="Cancel" id="cancel" statustext="Exit without saving"/>
+					</hbox>
+					<button label="Delete" align="right" onclick="return confirm('Delete this project');" id="delete" statustext="Delete this project"/>
+				</row>
+			</rows>
+		</grid>
+	</template>
+</overlay>
Index: /contrib/ProjectManager/templates/default/elements.list.xet
===================================================================
--- /contrib/ProjectManager/templates/default/elements.list.xet	(revision 3594)
+++ /contrib/ProjectManager/templates/default/elements.list.xet	(revision 3594)
@@ -0,0 +1,168 @@
+<?xml version="1.0"?>
+<!-- $Id: elements.list.xet 23488 2007-03-07 12:31:48Z ralfbecker $ -->
+<overlay>
+	<template id="projectmanager.elements.list.rows" template="" lang="" group="0" version="1.3.001">
+		<grid width="100%">
+			<columns>
+				<column/>
+				<column width="25%"/>
+				<column/>
+				<column/>
+				<column/>
+				<column/>
+				<column disabled="@no_pe_used_time_pe_planned_time"/>
+				<column disabled="@no_pe_used_budget_pe_planned_budget"/>
+				<column/>
+				<column/>
+				<column/>
+			</columns>
+			<rows>
+				<row class="th">
+					<nextmatch-sortheader align="center" id="pe_app" label="App." options=",Application"/>
+					<vbox>
+						<nextmatch-sortheader label="Title" id="pe_title"/>
+						<nextmatch-sortheader label="Comment" id="pe_remark"/>
+					</vbox>
+					<nextmatch-header label="Category" id="cat_id"/>
+					<nextmatch-accountfilter id="pe_resources" options="Resources"/>
+					<vbox>
+						<nextmatch-sortheader label="Startdate" id="pe_real_start"/>
+						<date id="pe_real_start" readonly="true"/>
+						<nextmatch-sortheader label="planned" id="pe_planned_start" class="planned"/>
+						<date id="pe_planned_start" readonly="true"/>
+					</vbox>
+					<vbox>
+						<nextmatch-sortheader label="Enddate" id="pe_real_end"/>
+						<date id="pe_real_end" readonly="true"/>
+						<nextmatch-sortheader label="planned" id="pe_planned_end" class="planned"/>
+						<date id="pe_planned_end" readonly="true"/>
+					</vbox>
+					<vbox>
+						<nextmatch-sortheader label="Times" id="pe_used_time"/>
+						<date-duration id="pe_used_time" readonly="true" options="@duration_format"/>
+						<nextmatch-sortheader label="planned" id="pe_planned_time" class="planned"/>
+						<date-duration id="pe_planned_time" readonly="true" options="@duration_format" class="planned"/>
+					</vbox>
+					<vbox>
+						<nextmatch-sortheader label="Budget" id="pe_used_budget"/>
+						<description id="pe_used_budget" no_lang="1"/>
+						<nextmatch-sortheader label="planned" id="pe_planned_budget" class="planned"/>
+						<description id="pe_planned_budget" no_lang="1"/>
+					</vbox>
+					<vbox align="center">
+						<nextmatch-sortheader id="pe_health" label="Health" align="center" disabled="true"/>
+						<nextmatch-sortheader label="Status" align="center" id="pe_completion"/>
+						<description id="pe_completion" no_lang="1"/>
+					</vbox>
+					<vbox>
+						<nextmatch-sortheader label="last modified" id="pe_modified"/>
+						<nextmatch-sortheader label="modified by" id="pe_modifier"/>
+					</vbox>
+					<description align="center" value="Actions" class="noPrint"/>
+				</row>
+				<row class="th">
+					<image label="$row_cont[pe_app]" align="center" src="${row}[pe_icon]"/>
+					<vbox>
+						<description no_lang="1" id="${row}[pe_title]" options=",@${row}[view_link]"/>
+						<html id="${row}[pe_details]" no_lang="1"/>
+						<description no_lang="1" id="${row}[pe_remark]"/>
+					</vbox>
+					<menulist>
+						<menupopup type="select-cat" readonly="true" id="${row}[cat_id]"/>
+					</menulist>
+					<listbox type="select-account" id="${row}[pe_resources]" readonly="true" rows="2"/>
+					<vbox>
+						<date readonly="true" id="${row}[pe_real_start]"/>
+						<date readonly="true" no_lang="1" id="${row}[pe_planned_start]" class="planned"/>
+					</vbox>
+					<vbox readonly="true">
+						<date readonly="true" id="${row}[pe_real_end]"/>
+						<date readonly="true" no_lang="1" id="${row}[pe_planned_end]" class="planned"/>
+					</vbox>
+					<vbox readonly="true">
+						<date-duration readonly="true" id="${row}[pe_used_time]" options="@duration_format"/>
+						<date-duration readonly="true" id="${row}[pe_planned_time]" class="planned" options="@duration_format"/>
+					</vbox>
+					<vbox readonly="true">
+						<textbox type="float" readonly="true" id="${row}[pe_used_budget]" max="2"/>
+						<textbox type="float" readonly="true" no_lang="1" id="${row}[pe_planned_budget]" max="2" class="planned"/>
+					</vbox>
+					<image label="$row_cont[pe_completion]" no_lang="1" align="center" src="${row}[pe_completion]"/>
+					<vbox readonly="true">
+						<date-time readonly="true" id="${row}[pe_modified]"/>
+						<menulist>
+							<menupopup type="select-account" readonly="true" id="${row}[pe_modifier]"/>
+						</menulist>
+					</vbox>
+					<hbox align="center" class="noPrint">
+						<image label="Ganttchart" src="ganttchart" options="projectmanager.ganttchart.show&amp;pm_id=$row_cont[pm_id]" statustext="View Ganttchart of this project"/>
+						<image label="View" src="view" options="projectmanager.uiprojectmanager.view&amp;pm_id=$row_cont[pm_id]" statustext="View this project"/>
+						<image label="Edit" src="edit" options="projectmanager.uiprojectmanager.edit&amp;pm_id=$row_cont[pm_id]" statustext="Edit this project"/>
+					</hbox>
+				</row>
+				<row class="row">
+					<image label="$row_cont[pe_app]" align="center" src="${row}[pe_icon]"/>
+					<vbox>
+						<link id="${row}[pm_link]" no_lang="1"/>
+						<link no_lang="1" id="${row}[link]"/>
+						<html id="${row}[pe_details]" no_lang="1"/>
+						<description no_lang="1" id="${row}[pe_remark]"/>
+					</vbox>
+					<menulist>
+						<menupopup type="select-cat" readonly="true" id="${row}[cat_id]"/>
+					</menulist>
+					<listbox type="select-account" id="${row}[pe_resources]" readonly="true" rows="2"/>
+					<vbox>
+						<date readonly="true" id="${row}[pe_real_start]"/>
+						<date readonly="true" no_lang="1" id="${row}[pe_planned_start]" class="planned"/>
+					</vbox>
+					<vbox readonly="true">
+						<date readonly="true" id="${row}[pe_real_end]"/>
+						<date readonly="true" no_lang="1" id="${row}[pe_planned_end]" class="planned"/>
+					</vbox>
+					<vbox readonly="true">
+						<date-duration readonly="true" id="${row}[pe_used_time]" options="@duration_format"/>
+						<date-duration readonly="true" id="${row}[pe_planned_time]" class="planned" options="@duration_format"/>
+					</vbox>
+					<vbox readonly="true">
+						<textbox type="float" readonly="true" id="${row}[pe_used_budget]" max="2"/>
+						<textbox type="float" readonly="true" no_lang="1" id="${row}[pe_planned_budget]" max="2" class="planned"/>
+					</vbox>
+					<vbox align="center">
+						<image src="${row}[pe_healt_icon]" align="center"/>
+						<image label="$row_cont[pe_completion]" no_lang="1" align="center" src="${row}[pe_completion_icon]"/>
+					</vbox>
+					<vbox readonly="true">
+						<date-time readonly="true" id="${row}[pe_modified]"/>
+						<menulist>
+							<menupopup type="select-account" readonly="true" id="${row}[pe_modifier]"/>
+						</menulist>
+					</vbox>
+					<hbox align="center" class="noPrint">
+						<button label="View" onclick="window.open(egw::link('/index.php','menuaction=projectmanager.uiprojectelements.view&amp;pm_id=$row_cont[pm_id]&amp;pe_id=$row_cont[pe_id]'),'view-elements','dependent=yes,width=600,height=450,scrollbars=yes,status=yes'); return false;" id="view[$row_cont[pe_id]]" image="view" statustext="View this project-element"/>
+						<button label="Edit" onclick="window.open(egw::link('/index.php','menuaction=projectmanager.uiprojectelements.edit&amp;pm_id=$row_cont[pm_id]&amp;pe_id=$row_cont[pe_id]'),'_blank','dependent=yes,width=600,height=450,scrollbars=yes,status=yes'); return false;" id="edit[$row_cont[pe_id]]" image="edit" statustext="Edit this project-element"/>
+						<button label="Delete" onclick="return confirm('Delete this project-element, does NOT remove the linked entry');" id="delete[$row_cont[pe_id]]" image="delete" statustext="Delete this project-element, does NOT remove the linked entry"/>
+					</hbox>
+				</row>
+			</rows>
+		</grid>
+	</template>
+	<template id="projectmanager.elements.list" template="" lang="" group="0" version="0.3.001">
+		<grid width="100%">
+			<columns>
+				<column/>
+			</columns>
+			<rows>
+				<row disabled="!@msg">
+					<description align="center" no_lang="1" id="msg" class="redItalic"/>
+				</row>
+				<row>
+					<nextmatch id="nm" options="projectmanager.elements.list.rows"/>
+				</row>
+				<row class="noPrint">
+					<button label="Synchronise all" id="sync_all" statustext="necessary for project-elements doing that not automatic"/>
+				</row>
+			</rows>
+		</grid>
+	</template>
+</overlay>
Index: /contrib/ProjectManager/templates/default/header.inc.php
===================================================================
--- /contrib/ProjectManager/templates/default/header.inc.php	(revision 3594)
+++ /contrib/ProjectManager/templates/default/header.inc.php	(revision 3594)
@@ -0,0 +1,54 @@
+<?php
+  /**************************************************************************\
+  * eGroupWare                                                               *
+  * http://www.egroupware.org                                                *
+  * --------------------------------------------                             *
+  *  This program is free software; you can redistribute it and/or modify it *
+  *  under the terms of the GNU General Public License as published by the   *
+  *  Free Software Foundation; either version 2 of the License, or (at your  *
+  *  option) any later version.                                              *
+  \**************************************************************************/
+
+
+	function add_col(&$tpl,$str)
+	{
+		$tpl->set_var('str',$str);
+		$tpl->parse('header_column','head_col',True);
+	}
+
+	function add_image_ahref($link,$image,$alt)
+	{
+		return '<a href="'.$link.'"><img src="'.$GLOBALS['phpgw']->common->image('calendar',$image).'" alt="'.$alt.'" title="'.$alt.'" border="0"></a>';
+	}
+
+	$refer = explode('.',$GLOBALS['HTTP_GET_VARS']['menuaction']);
+	$referrer = $refer[2];
+
+	$templates = Array(
+		'head_tpl'	=> 'head.tpl',
+		'form_button_dropdown'	=> 'form_button_dropdown.tpl',
+		'form_button_script'	=> 'form_button_script.tpl'
+	);
+	$tpl->set_file($templates);
+	$tpl->set_block('head_tpl','head','head');
+	$tpl->set_block('head_tpl','head_table','head_table');
+	$tpl->set_block('head_tpl','head_col','head_col');
+	$tpl->set_block('form_button_script','form_button');
+
+	
+		$tpl->set_var('cols',3);
+	
+
+	$today = date('Ymd',$GLOBALS['phpgw']->datetime->users_localtime);
+
+	//$col_width = 12;
+//add_col($tpl,'  <td >&nbsp;</td>');
+
+
+	$tpl->parse('row','head_table',True);
+
+	$tpl->set_var('header_column','');
+	$tpl->set_var('cols',$cols);
+
+	
+?>
Index: /contrib/ProjectManager/templates/default/holiday.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/holiday.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/holiday.tpl	(revision 3594)
@@ -0,0 +1,33 @@
+<!-- BEGIN form -->
+<center>
+{message}<br>
+<table border="0" width="80%" cellspacing="2" cellpadding="2"> 
+ <form name="form" action="{actionurl}" method="POST">
+  {hidden_vars}
+{rows}
+ <tr>
+  <td colspan="2">
+   <table width="100%" border="0" cellspacing="5">
+    <tr>
+     <td>
+      <input type="submit" name="submit" value="{lang_add}"></form>
+     </td>
+     <td>
+      {cancel_button}
+     </td>
+     <td align="right" width="80%">
+      {delete_button}
+     </td>
+    </tr>
+   </table>
+  <td>
+ </tr>
+</table>
+</center>
+<!-- END form -->
+<!-- BEGIN list -->
+ <tr bgcolor="{tr_color}">
+  <td valign="top" width="35%"><b>{field}:</b></td>
+  <td valign="top" width="65%">{data}</td>
+ </tr>
+<!-- END list -->
Index: /contrib/ProjectManager/templates/default/alarm.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/alarm.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/alarm.tpl	(revision 3594)
@@ -0,0 +1,62 @@
+<!-- BEGIN alarm_management -->
+<form id="calendar_alarmform" action="{action_url}" method="post" name="alarmform">
+{hidden_vars}
+	<table id="calendar_alarmform_table" border="0" width="90%" align="center">
+		{rows}
+		<tr>
+			<td colspan="4">
+				<br>&nbsp;{input_days}&nbsp;{input_hours}&nbsp;{input_minutes}&nbsp;{input_owner}&nbsp;{input_add}<br>&nbsp;
+			</td>
+			<td align="right">
+				{input_cancel}
+			</td>
+		</tr>
+	</table>
+</form>
+<!-- END alarm_management -->
+
+<!-- BEGIN alarm_headers -->
+	<tr bgcolor="{tr_color}">
+		<th align="left" width="25%">{lang_time}</th>
+		<th align="left" width="30%">{lang_text}</th>
+		<th align="left" width="25%">{lang_owner}</th>
+		<th width="10%">{lang_enabled}</th>
+		<th width="10%">{lang_select}</th>
+	</tr>
+<!-- END alarm_headers -->
+
+<!-- BEGIN list -->
+	<tr bgcolor="{tr_color}">
+		<td>
+			<b>{field}:</b>
+		</td>
+		<td>
+			{data}
+		</td>
+		<td>
+			{owner}
+		</td>
+		<td align="center">
+			{enabled}
+		</td>
+		<td align="center">
+			{select}
+		</td>
+	</tr>
+<!-- END list -->
+
+<!-- BEGIN hr -->
+	<tr bgcolor="{th_bg}">
+		<td colspan="5" align="center">
+			<b>{hr_text}</b>
+		</td>
+	</tr>
+<!-- END hr -->
+
+<!-- BEGIN buttons -->
+	<tr>
+		<td colspan="6" align="right">
+			{enable_button}&nbsp;{disable_button}&nbsp;{delete_button}
+		</td>
+	</tr>
+<!-- END buttons -->
Index: /contrib/ProjectManager/templates/default/pref.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/pref.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/pref.tpl	(revision 3594)
@@ -0,0 +1,22 @@
+<!-- BEGIN pref -->
+<p><b>{title}:</b><hr><p>
+<form action="{action_url}" method="POST">
+ <table border="0" align="center" width="90%">
+  {row}
+ </table>
+ <center><input type="submit" name="submit" value="{submit_lang}"></center>
+</form>
+<!-- END pref -->
+
+<!-- BEGIN pref_colspan -->
+  <tr bgcolor="{bg_color}">
+   <td colspan="2">{text}</td>
+  </tr>
+<!-- END pref_colspan -->
+
+<!-- BEGIN pref_list -->
+  <tr bgcolor="{bg_color}">
+   <td align="right">{field}</td>
+   <td align="center">{data}</td>
+  </tr>
+<!-- END pref_list -->
Index: /contrib/ProjectManager/templates/default/month_header.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/month_header.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/month_header.tpl	(revision 3594)
@@ -0,0 +1,12 @@
+<!-- BEGIN monthly_header -->
+<tr class="calendar_m_w_table_header" colspan="{cols}" width="{col_width}%">
+{column_header}</tr>
+<!-- END monthly_header -->
+
+<!-- BEGIN column_title -->
+<th bGCOLOR="#D3DCE3" width="14.28%">{col_title}</th>
+<!-- END column_title -->
+
+<!-- BEGIN month_column -->
+<td class="calendar_m_w_tablecell" colspan="1" width="{col_width}%"{extra}>{column_data}</td>
+<!-- END month_column -->
Index: /contrib/ProjectManager/templates/default/confirm.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/confirm.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/confirm.tpl	(revision 3594)
@@ -0,0 +1,31 @@
+<!-- BEGIN list -->
+<html>
+<head>
+<title> .: Expresso :. </title>
+<link href='/phpgwapi/templates/classic/login.css' rel='stylesheet' type='text/css'>
+</head>
+<body class='body'>
+<br>
+<center>
+<table border='0' cellpading='0' cellspacing='0'>
+	<tr>
+		<td class='td'><b><font face='Verdana, Arial, Helvetica, sans-serif'>{notify_message}</font></b></td>
+	</tr>
+	<tr>
+</table>
+</center>
+<br>
+<center>
+<table border='0' cellpading='0' cellspacing='0'>
+<tr>
+	<td>
+		<form>
+		<input type='submit' value='Ok' onclick='javascript:window.close()'>
+		</form>
+	</td>
+</tr>
+</table>
+</center>
+</body>
+</html>
+<!-- END list -->
Index: /contrib/ProjectManager/templates/default/month_day_print.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/month_day_print.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/month_day_print.tpl	(revision 3594)
@@ -0,0 +1,26 @@
+
+<!-- BEGIN m_w_table -->
+
+<table id="calendar_m_w_table" class="calendar_m_w_table" border="0" width="100%">
+	<!-- from month_header.tpl -->
+	{row}
+</table>
+<!-- END m_w_table -->
+
+
+<table>
+<!-- BEGIN month_daily -->
+<tr><td style="font-size: 10px; font-weight: bold; text-decoration: underline;" width="10px">{day_number}</td>
+<td>{daily_events}</td>
+<!-- END month_daily -->
+
+<!-- BEGIN day_event -->
+
+<td>{events}</td>
+<!-- END day_event -->
+
+<!-- BEGIN event -->
+<td>{day_events}</td></tr>
+<!-- END event -->
+<br />
+</table>
Index: /contrib/ProjectManager/templates/default/footer.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/footer.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/footer.tpl	(revision 3594)
@@ -0,0 +1,64 @@
+<!-- BEGIN footer_table -->
+       <hr clear="all">
+       <font size="-1">
+       <table border="0" width="100%" cellpadding="0" cellspacing="0">
+        <tr>
+{table_row}
+	</tr>
+       </table>
+<!-- END footer_table -->
+<!-- BEGIN footer_row -->
+         <td valign="top" width="30%">
+          <font size="-1">
+           <form action="{action_url}" method="post" name="{form_name}">
+            <B>{label}:</B>
+			{hidden_vars}
+            <select name="{form_label}" onchange="{form_onchange}">
+	     {row}
+	    </select>
+	    <input type="hidden" name="user" value="{user}">
+	    <input type="hidden" name="dia_ini" value="{dia_ini}">
+            <noscript><input type="submit" value="{go}"></noscript>
+	   </form>
+	  </font>
+	 </td>
+<!-- END footer_row -->
+<!-- BEGIN blank_row -->
+         <td>
+         {b_row}
+         </td>
+         <td>
+         {b_row2}
+         </td>
+<!-- END blank_row -->
+
+<!-- BEGIN num_dias -->
+<td valign="top" width="30%">
+ <font size="-1">
+    <form action="{acao}" method="post" name="{formname}">
+    <span style="display: {display}" >
+ 
+	<label>
+		<b>{num_dias_label}:</b>
+	</label>
+	<select name="{num_dias_name}" onchange="{formonchange}"/>
+		{row_qtd}
+	</select>
+	<br />
+	<span style="font-size: 9px;">
+		{tip}
+	</span>
+	<br /> <br />
+    </span>
+<span style="display: {display}">
+	<b>{day_ini_label}:</b>
+	<select name="{day_ini_name}" onchange="{formonchange}">
+	{row_ini}
+	</select>
+	</span>
+<input type="hidden" name="user" value="{user}">
+	<noscript><input type="submit" value="{go}"></noscript>
+     </form>
+ </font>
+</td>
+<!-- END num_dias -->
Index: /contrib/ProjectManager/templates/default/custom_fields.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/custom_fields.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/custom_fields.tpl	(revision 3594)
@@ -0,0 +1,45 @@
+<!-- BEGIN custom_fields -->
+{lang_error}<br>
+<form action="{action_url}" method="post">
+ {hidden_vars}
+ <center>
+  <table border="0" width="90%">
+   <tr class="th">
+    <th align="left" width="30%">{lang_name}</th>
+    <th>{lang_length}</th>
+    <th>{lang_shown}</th>
+    <th>{lang_order}</th>
+    <th>{lang_title}</th>
+    <th>{lang_disabled}</th>
+    <th>&nbsp;</th>
+   </tr>
+   {rows}
+   <tr class="th">
+    <td>{name}</td>
+    <td align="center">{length}</td>
+    <td align="center">{shown}</td>
+    <td align="center">{order}</td>
+    <td align="center">{title}</td>
+    <td align="center">{disabled}</td>
+    <td align="center">{button}</td>
+   </tr>
+   <tr>
+    <td>{save_button} &nbsp; {cancel_button}</td>
+   </tr>
+  </table>
+ </center>
+</form>
+<br>
+<!-- END custom_fields -->
+
+<!-- BEGIN row -->
+   <tr bgcolor="{tr_color}">
+    <td>{name}</td>
+    <td align="center">{length}</td>
+    <td align="center">{shown}</td>
+    <td align="center">{order}</td>
+    <td align="center">{title}</td>
+    <td align="center">{disabled}</td>
+    <td align="center">{button}</td>
+   </tr>
+<!-- END row -->
Index: /contrib/ProjectManager/templates/default/publicView.php
===================================================================
--- /contrib/ProjectManager/templates/default/publicView.php	(revision 3594)
+++ /contrib/ProjectManager/templates/default/publicView.php	(revision 3594)
@@ -0,0 +1,89 @@
+<?php 	
+	$user = $_POST['user'] ? $_POST['user'] : 
+		($_GET['user'] ? $_GET['user']: '');
+	
+	if($user) {
+		$organization = $_POST['organization'] ? $_POST['organization'] : 
+		($_GET['organization'] ? $_GET['organization']: '');
+		
+		if($organization)
+			$arg1 = $organization."-".$user;
+		else
+			$arg1 = $user;
+		
+		$params = "&account_name=".$arg1;
+	}
+	else
+		$params = '';
+?>
+<html>
+	<head><title>Visualizar Agenda</title>
+	<head>
+	<style type="text/css">
+		.grid_right{
+		border-style: groove;
+		border-width: 0px;
+		border-right-width: 1px;		
+		border-color: BLACK;		
+		}
+		.grid_top{
+		border-style: groove;
+		border-width: 0px;
+		border-top-width: 1px;		
+		border-color: BLACK;		
+		}
+	</style>
+	<LINK href="../../../phpgwapi/templates/default/css/azul.css" type=text/css rel=StyleSheet>
+	<script language="Javascript">
+		function validate(){
+			if(document.login.user.value =="")	{
+				alert('Você deve digitar um nome no campo Usuário.');
+				document.login.user.focus();
+				return false;
+			}
+			
+			return true;
+		}
+	</script>
+	</head>
+	<body>
+	  <div align="center" valign="center">
+		<table cellspacing="0" cellpadding="5" width="100%" height="100%">			
+			<tr><td width="10%" valign="center"  class="grid_right"><br>
+				<table border="0" cellpadding="0" cellspacing="0" class="login" height="100%" width="100%">
+					<tr>
+						<td valign="top">
+	  					<FORM name="login" method="post" action="publicView.php" onSubmit="javascript:return validate()">
+							<table border="0" cellpadding="0" cellspacing="0" class="tableLogin">
+								<tr>
+									<td width="66" class="loginLabel" align="center"><b>Organização</b><br><input type="text" value="<?=$organization?>" name="organization" size="15"></td>
+								</tr>
+								<tr>									
+									<td>&nbsp;</td>
+								</tr>
+								<tr>
+									<td width="136" align="center" class="loginLabel"><b>Usuário</b><br><input name="user" size="15" value="<?=$user?>"></td>
+								</tr>
+								<tr>									
+									<td height="16">&nbsp;</td>
+								</tr>
+								<tr>
+									<td width="135" colspan="2" align="center">						
+									<input type="submit" value="Visualizar" name="submitit" class=button >
+									</td>
+									</tr>
+							</table>
+							</FORM>
+						</td>
+					</tr>
+				</table>								
+			</td>
+				<td width="90%" valign="top">
+					<iframe  width="100%" height="95%" src="../../../index.php?menuaction=calendar.uipublicview.publicView<?=$params?>"
+						marginwidth="0" marginheight="0"  topmargin="0" leftmargin="0" frameborder="0" scrolling="auto" noresize>
+					</iframe>
+			</td></tr>
+		</table>
+	  </div>
+	</body>
+</html>
Index: /contrib/ProjectManager/templates/default/elements.edit.xet
===================================================================
--- /contrib/ProjectManager/templates/default/elements.edit.xet	(revision 3594)
+++ /contrib/ProjectManager/templates/default/elements.edit.xet	(revision 3594)
@@ -0,0 +1,263 @@
+<?xml version="1.0"?>
+<!-- $Id: elements.edit.xet 23913 2007-05-22 04:04:13Z lkneschke $ -->
+<overlay>
+	<template id="projectmanager.elements.edit.dates" template="" lang="" group="0" version="0.3.001">
+		<grid width="100%" height="170">
+			<columns>
+				<column width="95"/>
+				<column width="51%"/>
+				<column/>
+			</columns>
+			<rows>
+				<row class="row">
+					<description options=",,,pe_real_start" value="Startdate"/>
+					<date-time id="pe_real_start"/>
+					<date-time id="ds[pe_real_start]" readonly="true"/>
+				</row>
+				<row class="row">
+					<description value="- planned" options=",,,pe_planned_end"/>
+					<date-time id="pe_planned_start"/>
+					<date-time id="ds[pe_planned_start]" readonly="true"/>
+				</row>
+				<row class="row">
+					<description options=",,,pe_real_end" value="Enddate"/>
+					<date-time id="pe_real_end"/>
+					<date-time id="ds[pe_real_end]" readonly="true"/>
+				</row>
+				<row class="row" valign="top" height="50%">
+					<description value="- planned" options=",,,pe_planned_end"/>
+					<date-time id="pe_planned_end"/>
+					<date-time id="ds[pe_planned_end]" readonly="true"/>
+				</row>
+				<row class="row" disabled="!@no_times">
+					<description options=",,,pe_share" value="Share"/>
+					<hbox options="1,1" span="all">
+						<textbox id="pe_share" statustext="Share on the whole project" size="10" validator="/^[0-9.]*\.?[0-9%]*$/"/>
+						<textbox type="float" id="default_percentage" label="default" readonly="true"/>
+					</hbox>
+				</row>
+			</rows>
+		</grid>
+	</template>
+	<template id="projectmanager.elements.edit.times" template="" lang="" group="0" version="0.3.001">
+		<grid width="100%" height="170">
+			<columns>
+				<column width="95"/>
+				<column width="51%"/>
+				<column/>
+			</columns>
+			<rows>
+				<row class="row">
+					<description options=",,,pe_used_time" value="Times"/>
+					<date-duration id="pe_used_time" options=",$cont[duration_format]"/>
+					<date-duration id="ds[pe_used_time]" readonly="true" options="@duration_format"/>
+				</row>
+				<row class="row">
+					<description options=",,,pe_planned_time" value="- planned"/>
+					<date-duration id="pe_planned_time" options=",$cont[duration_format]"/>
+					<date-duration id="ds[pe_planned_time]" readonly="true" options="@duration_format"/>
+				</row>
+				<row class="row">
+					<description options=",,,pe_share" value="Share"/>
+					<hbox options="0,0">
+						<date-duration id="pe_share" statustext="Share on the whole project in hours or percent, defaults to planned time" options=",%$cont[duration_format],1"/>
+						<date-duration id="share_total" label="/" readonly="true" options=",$cont[duration_format],1"/>
+						<description id="share_percentage" value="=" no_lang="1"/>
+					</hbox>
+					<description/>
+				</row>
+				<row class="row" valign="top" height="50%">
+					<description value="Default"/>
+					<hbox>
+						<date-duration id="default_share" readonly="true" options=",$cont[duration_format]"/>
+						<date-duration id="default_total" label="/" readonly="true" options=",$cont[duration_format]"/>
+						<description id="default_percentage" value="=" no_lang="1"/>
+					</hbox>
+					<description/>
+				</row>
+			</rows>
+		</grid>
+	</template>
+	<template id="projectmanager.elements.edit.budget" template="" lang="" group="0" version="0.5.002">
+		<grid width="100%" height="170">
+			<columns>
+				<column width="95"/>
+				<column width="51%"/>
+				<column/>
+			</columns>
+			<rows>
+				<row class="th">
+					<description span="all" value="Calculate budget by quantity (eg. time) and price"/>
+				</row>
+				<row class="row">
+					<description options=",,,pe_used_quantity" value="Quantity"/>
+					<textbox type="float" id="pe_used_quantity" statustext="leave it empty, if identitcal to the time" blur="@used_quantity_blur" onchange="calc_budget(this.form);"/>
+					<textbox type="float" id="ds[pe_used_quantity]" readonly="true"/>
+				</row>
+				<row class="row">
+					<description options=",,,pe_planned_quantity" value="- planned"/>
+					<textbox type="float" id="pe_planned_quantity" statustext="leave it empty, if identitcal to the time" blur="@planned_quantity_blur" onchange="calc_budget(this.form);"/>
+					<textbox type="float" id="ds[pe_planned_quantity]" readonly="true"/>
+				</row>
+				<row class="row" disabled="@no_pricelist">
+					<description value="Activity"/>
+					<projectmanager-pricelist id="pl_id" statustext="select an activity if you want to calculate costs by quantity (time) and price" options="None" onchange="this.form['exec[pe_unitprice]'].value=this.options[this.selectedIndex].text.lastIndexOf('(') &lt; 0 ? '' : this.options[this.selectedIndex].text.slice(this.options[this.selectedIndex].text.lastIndexOf('(')+1,-1); calc_budget(this.form);"/>
+					<projectmanager-pricelist span="all" id="ds[pl_id]" readonly="true"/>
+				</row>
+				<row class="row" valign="top">
+					<description options=",,,pe_cost_per_time" value="Unitprice"/>
+					<textbox type="float" id="pe_unitprice" blur="@ds[pe_unitprice]" onchange="calc_budget(this.form);"/>
+					<textbox type="float" min="2" id="ds[pe_unitprice]" readonly="true"/>
+				</row>
+				<row class="th">
+					<description options=",,,pe_real_budget" value="Budget"/>
+					<textbox type="float" id="pe_used_budget" blur="@ds[pe_used_budget]"/>
+					<textbox type="float" min="2" id="ds[pe_used_budget]" readonly="true"/>
+				</row>
+				<row class="row">
+					<description options=",,,pe_planned_budget" value="- planned"/>
+					<textbox type="float" id="pe_planned_budget" blur="@ds[pe_planned_budget]"/>
+					<textbox type="float" min="2" id="ds[pe_planned_budget]" readonly="true"/>
+				</row>
+			</rows>
+		</grid>
+	</template>
+	<template id="projectmanager.elements.edit.constraints" template="" lang="" group="0" version="0.4.001">
+		<grid width="100%" height="170" overflow="auto">
+			<columns>
+				<column width=" 95"/>
+				<column/>
+			</columns>
+			<rows>
+				<row class="row" valign="top">
+					<description options=",,,end_constraints" value="Start after"/>
+					<listbox rows="3" id="pe_constraints[start]" statustext="Elements which have to be finished before this can start" no_lang="1"/>
+				</row>
+				<row class="row" valign="top">
+					<description options=",,,end_constraints" value="End before"/>
+					<listbox rows="3" id="pe_constraints[end]" statustext="Elements that can't start before this one is finished" no_lang="1"/>
+				</row>
+				<row class="row" valign="top">
+					<description options=",,,milestone_constraints" value="Milestones"/>
+					<listbox id="pe_constraints[milestone]" statustext="Milestones before this element has to be finished" rows="3" no_lang="1"/>
+				</row>
+			</rows>
+		</grid>
+	</template>
+	<template id="projectmanager.elements.edit.resources" template="" lang="" group="0" version="0.4.001">
+		<grid width="100%" height="170">
+			<columns>
+				<column width="95"/>
+				<column width="51%"/>
+				<column/>
+			</columns>
+			<rows>
+				<row class="row" valign="top">
+					<description rows="1" cols="1" value="Resources"/>
+					<listbox type="select-account" rows="12" id="pe_resources"/>
+					<listbox type="select-account" rows="8" id="ds[pe_resources]" readonly="true"/>
+				</row>
+			</rows>
+		</grid>
+	</template>
+	<template id="projectmanager.elements.edit.details" template="" lang="" group="0" version="0.4.001">
+		<grid width="100%" height="170" overflow="auto">
+			<columns>
+				<column width="95"/>
+				<column/>
+			</columns>
+			<rows>
+				<row class="row" valign="top">
+					<description value="Details"/>
+					<html id="ds[pe_details]"/>
+				</row>
+			</rows>
+		</grid>
+	</template>
+	<template id="projectmanager.elements.edit" template="" lang="" group="0" version="0.4.001">
+		<grid width="100%" height="170">
+			<columns>
+				<column width="100"/>
+				<column width="50%"/>
+				<column/>
+			</columns>
+			<rows>
+				<row disabled="!@msg">
+					<description span="all" class="redItalic" align="center" id="msg" no_lang="1"/>
+					<description/>
+					<description/>
+				</row>
+				<row class="th">
+					<image src="pe_icon" align="center"/>
+					<description no_lang="1" id="pe_title" span="2"/>
+				</row>
+				<row class="row">
+					<description options=",,,pe_cat_id" value="Category"/>
+					<menulist>
+						<menupopup type="select-cat" options="None" id="cat_id"/>
+					</menulist>
+					<checkbox options="ignore,regular" id="pe_status" statustext="do not use that entry as project-element" label="ignore that entry" span="2"/>
+				</row>
+				<row class="row">
+					<description options=",,,pe_remark" value="Comment"/>
+					<textbox size="50" maxlength="50" id="pe_remark" span="2"/>
+				</row>
+				<row class="th">
+					<description value="Value"/>
+					<description value="Overwrite the datasource"/>
+					<description value="Datasource"/>
+				</row>
+				<row class="row">
+					<description options=",,,pe_completion" value="Status"/>
+					<hbox options="0" orient="0">
+						<menulist>
+							<menupopup type="select-percent" options="None" id="pe_completion"/>
+						</menulist>
+						<description/>
+					</hbox>
+					<description id="ds[pe_completion]" no_lang="1"/>
+				</row>
+				<row>
+					<tabbox span="all">
+						<tabs>
+							<tab label="Dates" statustext="Start- &amp; Enddate"/>
+							<tab label="Times" statustext="Times"/>
+							<tab label="Budget" statustext="Budget"/>
+							<tab label="Constraints" statustext="Constraints"/>
+							<tab label="Resources" statustext="Resources"/>
+							<tab label="Details" statustext="Details"/>
+						</tabs>
+						<tabpanels>
+							<template id="projectmanager.elements.edit.dates"/>
+							<template id="projectmanager.elements.edit.times"/>
+							<template id="projectmanager.elements.edit.budget"/>
+							<template id="projectmanager.elements.edit.constraints"/>
+							<template id="projectmanager.elements.edit.resources"/>
+							<template id="projectmanager.elements.edit.details"/>
+						</tabpanels>
+					</tabbox>
+				</row>
+				<row class="row_on">
+					<description value="Modified"/>
+					<hbox>
+						<date-time id="pe_modified" readonly="true"/>
+						<menulist>
+							<menupopup type="select-account" id="pe_modifier" label="by" readonly="true"/>
+						</menulist>
+					</hbox>
+					<date-time id="pe_synced" readonly="true"/>
+				</row>
+				<row>
+					<hbox span="2">
+						<button label="Edit" id="edit" statustext="Edit this project-element"/>
+						<button id="save" label="Save" statustext="Save this project element"/>
+						<button id="apply" label="Apply" statustext="Apply the changes"/>
+						<button id="cancel" label="Cancel" statustext="Close the window, discard the changes made" onclick="window.close();"/>
+						<html id="js"/>
+					</hbox>
+					<button label="Delete" align="right" id="delete" statustext="Delete this project-element, does NOT remove the linked entry" onclick="return confirm('Delete this project-element, does NOT remove the linked entry');"/>
+				</row>
+			</rows>
+		</grid>
+	</template>
+</overlay>
Index: /contrib/ProjectManager/templates/default/search.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/search.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/search.tpl	(revision 3594)
@@ -0,0 +1,190 @@
+<!-- BEGIN search -->
+{script}
+<script language="Javascript">
+  
+    function submitit(form,campo)
+{
+	//alert(name+' pressed');
+ var texto;
+  //  texto = "El numero de opciones del select: " + document.form.search_info_type.length;
+    var indice = document.form.search_info_type.selectedIndex;
+    //texto += "\nIndice de la opcion escogida: " + indice;
+    var valor = document.form.search_info_type.options[indice].value;
+   // texto += "\nValor de la opcion escogida: " + valor;
+    var textoEscogido = document.form.search_info_type.options[indice].text;
+  //  texto += "\nTexto de la opcion escogida: " + textoEscogido;
+  //  alert(texto) ;
+	//document.form.ordena.value = name;
+form.action='/index.php?menuaction=infolog.uiinfolog.search&ordenar='+campo+'&valor='+valor;
+	form.submit();
+	//form.submit_button.value = '';
+	return false;
+}
+ 
+</script>
+<center>
+
+<table id="calendar_search_table" width="90%">
+<tr><td>
+	<table width="98%">
+	<tr><td></td></tr>
+	<tr><td align=right><table><tr>
+		<td class="headertext">A&ntilde;adir: </td>
+		<td><a href="" onclick="index.php?menuaction=infolog.uiinfolog.edit&amp;type=task&amp;" onmouseover="self.status=\'A&ntilde;adir nueva tarea pendiente\'; return true;" onmouseout="self.status=\'\'; return true;" onfocus="self.status=\'A&ntilde;adir nueva tarea pendiente\'; return true;" onblur="self.status=\'\'; return true;"><img src="infolog/templates/default/images/task.png" title="Tareas pendientes" border="0"></a></td>
+		<td><a href="" onclick="index.php?menuaction=infolog.uiinfolog.edit&amp;type=phone&amp" onmouseover="self.status=\'A&ntilde;adir una nueva llamada\'; return true;" onmouseout="self.status=\'\'; return true;" onfocus="self.status=\'A&ntilde;adir una nueva llamada\'; return true;" onblur="self.status=\'\'; return true;"><img src="infolog/templates/default/images/phone.png" title="Llamada telef&oacute;nica" border="0"></a></td>
+		<td><a href="" onclick="index.php?menuaction=infolog.uiinfolog.edit&amp;type=note"  onmouseover="self.status=\'A&ntilde;adir una nota nueva\'; return true;" onmouseout="self.status=\'\'; return true;" onfocus="self.status=\'A&ntilde;adir una nota nueva\'; return true;" onblur="self.status=\'\'; return true;"><img src="infolog/templates/default/images/note.png" title="Nota" border="0">
+	</tr>
+	</table>
+</td></tr>
+<tr><td align=left>
+<form  METHOD=POST name="form">
+<input type=hidden name=ordena value="">
+	<table><tr class="th">
+	<td>
+		<table>
+			<tr><td class="filterheader"><span class="onlyPrint">Tipo</span>{typo}
+			    </td>
+			</tr>
+			<tr>
+				<td class="filterheader" align="left"><span class="onlyPrint">Estado</span>{estatus}
+				</td>
+			</tr>
+			<tr>
+				<td class="inactiv_sortcolumn" align="left"><a href="" onclick="return submitit(this.form,'percent asc')">% completado</a></td>
+			</tr>
+		</table>
+
+	</td>
+	<td>
+		<table>
+	
+			<tr>
+			<td class="inactiv_sortcolumn" align="left"><a href="" onclick="return submitit(eTemplate,\'exec[nm][rows][info_subject]\');; return false;">Asunto</a></td>
+			
+			</tr>
+			<tr>
+			<td class="inactiv_sortcolumn" align="left"><a href="" onclick="return submitit(eTemplate,\'exec[nm][rows][info_des]\');; return false;">Descripci&oacute;n</a></td>
+			</tr>
+		</table>
+	</td>
+	<td>
+		<table><tr>
+			
+			<td class="noPrint inactiv_sortcolumn" align="right"><a href="" onclick="return submitit(eTemplate,\'exec[nm][rows][info_priority]\');; return false;" >Prioridad</a></td>
+</tr>
+			
+
+
+		</table>
+	</td>
+	<td>
+		<table cellpadding="0" cellspacing="0" width="100%"  class="th">
+			<tbody><tr>
+			<td class="inactiv_sortcolumn"><a href="" onclick="return submitit(eTemplate,\'exec[nm][rows][info_startdate]\');; return false;">Fecha de inicio</a></td>
+			</tr>
+			<tr>
+			<td class="inactiv_sortcolumn"><a href="" onclick="return submitit(eTemplate,\'exec[nm][rows][info_enddate]\');; return false;" >Fecha de finalizaci&oacute;n</a></td>
+
+			</tr>
+			<tr>
+			<td class="inactiv_sortcolumn"><a href="" onclick="return submitit(eTemplate,\'exec[nm][rows][info_datecompleted]\');; return false;" >Fecha completada</a></td>
+			</tr>
+			</tbody>
+		</table>
+	</td>
+	<td>
+		<table cellpadding="0" cellspacing="0">
+			<tbody>
+			<tr>
+			<td class="user_filter filterheader"><span class="onlyPrint">Propietario</span><select name="exec[nm][rows][info_owner]"  onchange="this.form.submit();" >
+			<option value="">Propietario</option>
+			</select>
+			</td>
+			</tr>
+
+			<tr>
+			<td class="user_filter filterheader"><span class="onlyPrint">Responsable</span><select name="exec[nm][rows][info_responsible]" onchange="this.form.submit();">
+				<option value="">Responsable</option>
+				</select>
+
+			</td>
+			</tr>
+			</tbody>
+		</table>
+	</td>
+	<td class="noPrint" align="left" width="3%">Sub </td>
+	<td class="noPrint" align="left" width="3%">Acci&oacute;n </td>
+
+	</td>
+	</tr>
+
+	<tr>
+		<td colspan="2" align="center" bgcolor="{th_bg}">
+			<b></b>
+		</td>
+	</tr>
+	{rows}
+</form>
+</table>
+<!-- END search -->
+
+<!-- BEGIN search_list_header -->
+
+<!-- END search_list_header -->
+
+<!-- BEGIN search_list -->
+	<tr bgcolor="{tr_color}">
+		
+		<td>
+<table><tr><td>	{type}</td>
+</tr>
+			<tr>
+<td>{status}</td>
+</tr>
+			<tr>
+<td>{percent}%</td></tr></table>
+		</td>
+<td>
+<table><tr><td>	{asunto}</td>
+</tr>
+			<tr>
+<td>{descripcion}</td></tr></table>
+			<br>
+
+		</td>
+
+
+<td>
+<table><tr><td>	{priority}</td>
+</tr>
+			</table>
+			<br>
+
+		</td>
+<td>
+			{fechainicio}<br>
+{fechafin}<br>
+{fechacompletada}
+		</td>
+<td>
+			{propietario}<br>
+{responsable}
+		</td>
+
+<td>
+			<a href="{accionagregar}"><img src=/infolog/templates/default/images/new.png alt="agregar sub"></a>&nbsp;&nbsp;&nbsp;
+<a href="{accionagregartimet}"><img src=/infolog/templates/default/images/timesheet.png alt="agregar hoja de presencia"></a>
+
+		</td>
+<td>
+<a href="{accionmodificar}"><img src=/infolog/templates/default/images/edit.png alt="modificar"></a>&nbsp;&nbsp;&nbsp;
+
+<a href="{accionborrar}"><img src=/infolog/templates/default/images/delete.png alt="borrar"></a><br>
+
+</td>
+	</tr>
+<!-- END search_list -->
+
+<!-- BEGIN search_list_footer -->
+
+<!-- END search_list_footer -->
Index: /contrib/ProjectManager/templates/default/copyFolder.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/copyFolder.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/copyFolder.tpl	(revision 3594)
@@ -0,0 +1,51 @@
+<!-- BEGIN main -->
+<script type='text/javascript'>
+	var folderChooserURL ='{folderChooserURL}';
+</script>
+
+<form name="copy_form" method="post" action="{form_action}">
+<fieldset><legend>{lang_target_folder}</legend>
+	<table border="0" cellspacing="0" cellpadding="5" width="100%">
+		<tr class="row_on">
+			<td class="description" valign="top" width="150px">{lang_folder}:</td>
+			<td class="infos">
+				<input id="targetname" style="width: 100%;" type="text" name="targetname" value="{foldername}" disabled>
+				<input type="hidden" id="targetid" name="targetid" value="{folderid}">
+			</td>
+			<td align="center" width="150px"><a href="#" onclick="selectFolder(1,'copy_form'); return(false);" style="font-size:10px;">{lang_select_target_folder}...</a></td>
+		</tr>
+		<tr class="row_on">
+			<td class="description" valign="top" width="150px">{lang_name}:</td>
+			<td class="infos">
+				<input id="newfoldername" style="width: 100%;" type="text" name="newfoldername" value="{newfoldername}">
+			</td>
+			<td align="center" width="150px"><a href="#" onclick="selectFolder(1,'copy_form'); return(false);" style="font-size:10px;">&nbsp;</td>
+		</tr>
+	</table>
+</fieldset>
+<p>
+<fieldset><legend>{lang_copy_options}</legend>
+	<table border="0" cellspacing="0" cellpadding="5" width="100%">
+		<tr class="row_off">
+			<td class="infos">
+				<input type="checkbox" name="copy_subfolder" id="copy_subfolder"> <label for="copy_subfolder">{lang_copy_subfolders}</label>
+			</td>
+		</tr>
+		<tr class="row_off">
+			<td class="infos">
+				<input type="checkbox" name="copy_documents" id="copy_documents" disabled> <label for="copy_documents">{lang_copy_documents}</label>
+			</td>
+		</tr>
+	</table>
+</fieldset>
+<p>
+	<table border="0" cellspacing="0" cellpadding="5" width="100%">
+		<tr class="row_off">
+			<td class="infos">
+				<button type="submit" name="cancel">{lang_cancel}</button>
+				<button type="submit" name="copy">{lang_copy}</button>
+			</td>
+		</tr>
+	</table>
+</form>
+<!-- END main -->
Index: /contrib/ProjectManager/templates/default/config.xet
===================================================================
--- /contrib/ProjectManager/templates/default/config.xet	(revision 3594)
+++ /contrib/ProjectManager/templates/default/config.xet	(revision 3594)
@@ -0,0 +1,86 @@
+<?xml version="1.0"?>
+<!-- $Id: config.xet 23852 2007-05-09 16:29:32Z lkneschke $ -->
+<overlay>
+	<template id="projectmanager.config" template="" lang="" group="0" version="1.3.001">
+		<grid width="80%">
+			<columns>
+				<column width="40%"/>
+				<column/>
+			</columns>
+			<rows>
+				<row>
+					<description span="all" class="redItalic" no_lang="1" align="center" id="msg"/>
+					<description/>
+				</row>
+				<row class="th">
+					<description span="all" value="Times"/>
+				</row>
+				<row class="row" valign="top">
+					<description options=",,,duration_units" value="Allowed units"/>
+					<listbox rows="2" id="duration_units" statustext="default all and a selectbox to choose" needed="1"/>
+				</row>
+				<row class="row">
+					<description options=",,,hours_per_workday" value="Hours per workday"/>
+					<menulist>
+						<menupopup type="select-number" options=",1,24" id="hours_per_workday" statustext="conversation between days and hours, default 8"/>
+					</menulist>
+				</row>
+				<row class="row">
+					<description value="Allow users to change their working times"/>
+					<menulist>
+						<menupopup id="allow_change_workingtimes"/>
+					</menulist>
+				</row>
+				<row>
+					<description span="all"/>
+				</row>
+				<row class="th">
+					<description span="all" value="Accounting"/>
+				</row>
+				<row class="row" valign="top">
+					<description options=",,,accounting_types" value="Allowed accounting types"/>
+					<listbox rows="4" id="accounting_types" statustext="select the accounting types visible for users of projectmanager" no_lang="1" needed="1"/>
+				</row>
+				<row class="th">
+					<description span="all" value="Ganttchart"/>
+				</row>
+				<row disabled="!@jpg_msg">
+					<html id="jpg_msg" span="all" class="redItalic" no_lang="1"/>
+					<description/>
+				</row>
+				<row class="row" disabled="@jpg_msg">
+					<description value="Font"/>
+					<hbox options="0,0">
+						<menulist>
+							<menupopup id="GANTT_FONT" statustext="Font to use in the Ganttchart, only the Vera fonts are included in the ProjectManager." no_lang="1"/>
+						</menulist>
+						<description id="font_msg" class="leftPad5 $cont[font_msg_class]" no_lang="1" value=" "/>
+					</hbox>
+				</row>
+				<row class="row" disabled="@jpg_msg">
+					<description options=",,,LANGUAGE_CHARSET" value="Font charset" statustext="Charset of the above selected font"/>
+					<textbox id="LANGUAGE_CHARSET" statustext="The charset depends on the font you want to use."/>
+				</row>
+				<row class="row" disabled="@jpg_msg">
+					<description options=",,,GANTT_STYLE" value="Font style"/>
+					<menulist>
+						<menupopup id="GANTT_STYLE" statustext="Style used for everything but elements. Some fonts have only normal style." no_lang="1"/>
+					</menulist>
+				</row>
+				<row disabled="@jpg_msg">
+					<description options=",,,GANTT_CHAR_ENCODE" value="Encode non-ascii"/>
+					<menulist>
+						<menupopup id="GANTT_CHAR_ENCODE" statustext="If you see entities (eg. &amp;#123;) in the Ganttchart, switch this off."/>
+					</menulist>
+				</row>
+				<row>
+					<hbox span="all">
+						<button label="Save" id="save" statustext="Save the changes made"/>
+						<button id="apply" label="Apply" statustext="apply the changes"/>
+						<button id="cancel" label="Cancel" statustext="Exit without saving"/>
+					</hbox>
+				</row>
+			</rows>
+		</grid>
+	</template>
+</overlay>
Index: /contrib/ProjectManager/templates/default/images/buttons/atualizar.sh
===================================================================
--- /contrib/ProjectManager/templates/default/images/buttons/atualizar.sh	(revision 3594)
+++ /contrib/ProjectManager/templates/default/images/buttons/atualizar.sh	(revision 3594)
@@ -0,0 +1,14 @@
+cp /public/home/desenvolvimento/arte/imagens_file_manager/up.png uplvl_big.png
+cp /public/home/desenvolvimento/arte/imagens_file_manager/refresh.png fs_refresh.png
+cp /public/home/desenvolvimento/arte/imagens_file_manager/new_folder.png new_folder.png
+
+cp /public/home/desenvolvimento/arte/imagens_file_manager/up_load.png upload.png
+cp /public/home/desenvolvimento/arte/imagens_file_manager/lixeira.png delete_big.png
+cp /public/home/desenvolvimento/arte/imagens_file_manager/cut.png .
+cp /public/home/desenvolvimento/arte/imagens_file_manager/copy.png .
+cp /public/home/desenvolvimento/arte/imagens_file_manager/sharing.png .
+cp /public/home/desenvolvimento/arte/imagens_file_manager/searching.png ab_search.png
+
+cp /public/home/desenvolvimento/arte/imagens_file_manager/compress.png .
+cp /public/home/desenvolvimento/arte/imagens_file_manager/decompress.png .
+cp /public/home/desenvolvimento/arte/imagens_file_manager/home.png .
Index: /contrib/ProjectManager/templates/default/images/index.html
===================================================================
--- /contrib/ProjectManager/templates/default/images/index.html	(revision 3594)
+++ /contrib/ProjectManager/templates/default/images/index.html	(revision 3594)
@@ -0,0 +1,34 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+
+<html>
+<head>
+	<title>eGroupWare Info Log Icons</title>
+</head>
+
+<body bgcolor=Silver>
+	Types:<br>
+	<img src="phone.gif" width="20" height="20" alt="Telefon" border="0"> &nbsp;
+	<img src="task.gif" width="20" height="20" alt="Aufgabe" border="0"> &nbsp;
+	<img src="note.gif" width="20" height="20" alt="Notiz" border="0"> &nbsp;
+	<img src="confirm.gif" width="20" height="20" alt="Bestätigung" border="0"> &nbsp;
+	<img src="reject.gif" width="20" height="20" alt="Absage" border="0">
+	<p>
+	Status:<br>
+	<img src="offer.gif" width="20" height="20" alt="Angebot" border="0"> &nbsp;
+	<img src="ongoing.gif" width="20" height="20" alt="läuft" border="0"> &nbsp;
+	<img src="call.gif" width="20" height="20" alt="anrufen" border="0"> &nbsp;
+	<img src="will-call.gif" width="20" height="20" alt="ruft zurück" border="0"> 
+&nbsp;
+	<img src="done.gif" width="20" height="20" alt="erledigt" border="0"> &nbsp;
+	<img src="billed.gif" width="20" height="20" alt="abgerechnet" border="0">
+	<p>
+	Actions:<br>
+	<img src="new.gif" width="20" height="20" alt="neue Teilaufgabe" border="0"> 
+&nbsp;
+	<img src="view.gif" width="20" height="20" alt="Teilaufgagen anzeigen" 
+border="0"> &nbsp;
+	<img src="edit.gif" width="20" height="20" alt="bearbeiten" border="0"> &nbsp;
+	<img src="delete.gif" width="20" height="20" alt="löschen" border="0">
+</body>
+</html>
+
Index: /contrib/ProjectManager/templates/default/overlap.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/overlap.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/overlap.tpl	(revision 3594)
@@ -0,0 +1,30 @@
+<!-- BEGIN overlap -->
+<center><br />
+<table id="calendar_overlap_table">
+	<tr>
+		<td>
+			{overlap_text}
+		</td>
+	</tr>
+	<tr>
+		<td>
+			<ul>{overlap_list}</ul>
+		</td>
+	</tr>
+	<tr>
+		<td>
+			<table id="calendar_overlap_table2" cellspacing="5">
+				<tr>
+					<td>
+						{resubmit_button}
+					</td>
+					<td>
+						{reedit_button}
+					</td>
+				</tr>
+			</table>
+		</td>
+	</tr>
+</table>
+</center>
+<!-- END overlap -->
Index: /contrib/ProjectManager/templates/default/day_list.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/day_list.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/day_list.tpl	(revision 3594)
@@ -0,0 +1,28 @@
+<!-- BEGIN day -->
+<div class="event-off">
+<table border="0" width="100%" cellpadding="0">
+	<tr>
+		<td class="event-on">
+{row}
+		</td>
+	</tr>
+</table>
+</div>
+<!-- END day -->
+<!-- BEGIN day_row -->
+    <font style="font-size: 8pt;">{event}</font>
+<!-- END day_row -->
+<!-- BEGIN day_event_on -->
+	<font class="event-on">{event}</font>
+<!-- END day_event_on -->
+<!-- BEGIN day_event_off -->
+	<font class="event-on">{event}</font>
+<!-- END day_event_off -->
+<!-- BEGIN day_event_holiday -->
+	{event}
+<!-- END day_event_holiday -->
+<!-- BEGIN day_time -->
+	{time}
+     <!--<td class="time" nowrap>{open_link}{time}{close_link}</td>-->
+<!-- END day_time -->
+
Index: /contrib/ProjectManager/templates/default/grant_group_access.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/grant_group_access.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/grant_group_access.tpl	(revision 3594)
@@ -0,0 +1,65 @@
+<div align="center" style="width:100%">
+<input type="hidden" id="txt_loading" value="{lang_Loading}">
+<input type="hidden" id="txt_searching" value="{lang_Searching}">
+<input type="hidden" id="txt_users" value="{lang_Users}">
+<input type="hidden" id="txt_groups" value="{lang_Groups}">
+<input type="hidden" id="txt_confirm" value="{lang_confirm}">
+<input type="hidden" id="txt_success" value="{lang_success}">
+<input type="hidden" id="txt_exist" value="{lang_exist}">
+<input type="hidden" id="txt_nouser" value="{lang_nouser}">
+<input type="hidden" id="txt_nogroup" value="{lang_nogroup}">
+<input type="hidden" id="txt_nopermissiontype" value="{lang_nopermissiontype}">
+<input type="hidden" id="txt_typemoreletters" value="{lang_typemoreletters}">
+<input type="hidden" id="template_set" value="{template_set}">
+<table width="80%" border="0">
+<tr>
+<td align='center' valign="bottom"><br><b>{lang_User_to_grant_access}</b><br>
+	<font color="red"><span id="cal_span_searching1">&nbsp;</span></font>
+	<br>{lang_Search_for}: <input type="text" id="cal_input_searchUser1" name="cal_input_searchUser1" value="" size=30 autocomplete="off" onkeyup="javascript:search_object(this,'cal_span_searching1','user','u')"/>
+	<br><br>
+	<select id="user" style="width: 300px" size="13"></select>
+</td>
+<td width="20px" valign="middle" align="left"><br><br><br><br>
+<button type="button" onClick="javascript:add_user();"><img src="{template_set}/images/add.png" style="vertical-align: middle;" >{lang_Add}</button>
+<br><br><b>{lang_access_type}</b><br>
+<input type="checkbox" id="right_L" value="L"/>{lang_read}<br>
+<input type="checkbox" id="right_A" value="A"/>{lang_add}<br>
+<input type="checkbox" id="right_E" value="E"/>{lang_edit}<br>
+<input type="checkbox" id="right_R" value="R"/>{lang_delete}<br>
+<input type="checkbox" id="right_P" value="P"/>{lang_private}
+</td>
+<td align='center' valign="bottom"><br><b>{lang_Group_to_share_calendar}</b><br>
+	<font color="red"><span id="cal_span_searching2">&nbsp;</span></font>
+	<br>{lang_Search_for}:	<input type="text" id="cal_input_searchUser2" name="cal_input_searchUser2" value="" size=30 autocomplete="off" onkeyup="javascript:search_object(this,'cal_span_searching2','group','g')"/><br><br>
+	<select id="group" style="width: 300px" size="13"></select>
+</td>
+</tr>
+	<tr height="20px"></tr>
+	<tr bgcolor="{tr_color}" style="font-size:0.8em">
+		<th align="left" width="25%" nowrap>&nbsp;{lang_granted_user}</th>
+		<th align="center" width="1%">{lang_access_type}</th>
+		<th align="left" width="40%" nowrap>&nbsp;{lang_shared_group}</th>
+		<th width="1%">&nbsp;</th>
+	</tr>
+<tbody id='tbody_list' >
+<!-- BEGIN list -->
+	<tr id='{select}' bgcolor="{tr_color_header}">
+		<td>
+			<b>&nbsp;&nbsp;{granted_user}</b>
+		</td>
+		<td align="center">
+			{access_type}
+		</td>		
+		<td>
+			&nbsp;&nbsp;{shared_group}
+		</td>
+		<td align="center">
+			<button  title="{lang_Remove}" type="button" onClick="javascript:remove_user('{select}');"><img alt="{lang_Remove}" src="{template_set}/images/delete.png" style="vertical-align: middle;"/></button>
+		</td>
+	</tr>
+<!-- END list -->
+</tbody>
+</table>
+<br><br>
+</div>
+{scripts}
Index: /contrib/ProjectManager/templates/default/pricelist.list.xet
===================================================================
--- /contrib/ProjectManager/templates/default/pricelist.list.xet	(revision 3594)
+++ /contrib/ProjectManager/templates/default/pricelist.list.xet	(revision 3594)
@@ -0,0 +1,80 @@
+<?xml version="1.0"?>
+<!-- $Id: pricelist.list.xet 20891 2006-03-28 11:48:29Z ralfbecker $ -->
+<overlay>
+	<template id="projectmanager.pricelist.list.rows" template="" lang="" group="0" version="0.5.001">
+		<grid width="100%">
+			<columns>
+				<column/>
+				<column disabled="@standard_only"/>
+				<column disabled="@standard_only"/>
+				<column/>
+				<column/>
+				<column/>
+			</columns>
+			<rows>
+				<row class="th">
+					<nextmatch-sortheader label="Title" id="pl_title"/>
+					<vbox>
+						<nextmatch-filterheader id="pm_id" options="Project" no_lang="1"/>
+						<nextmatch-sortheader id="pl_customertitle" label="Customer title"/>
+					</vbox>
+					<nextmatch-filterheader options="Availible" id="pl_billable"/>
+					<vbox>
+						<nextmatch-sortheader label="Price / Unit" id="pl_price"/>
+						<nextmatch-sortheader id="pl_validsince" label="Valid since"/>
+					</vbox>
+					<vbox>
+						<nextmatch-sortheader label="Last modified" id="pl_modified"/>
+						<nextmatch-sortheader id="pl_modifier" label="Modified by"/>
+					</vbox>
+					<description value="Actions" class="noPrint"/>
+				</row>
+				<row class="row">
+					<textbox id="${row}[pl_title]" no_lang="1" readonly="true"/>
+					<vbox>
+						<menulist>
+							<menupopup id="${row}[pm_id]" no_lang="1" readonly="true"/>
+						</menulist>
+						<description id="${row}[pl_customertitle]" no_lang="1"/>
+					</vbox>
+					<menulist>
+						<menupopup id="${row}[pl_billable]" readonly="true" options="No"/>
+					</menulist>
+					<vbox>
+						<textbox type="float" max="2" id="${row}[pl_price]" readonly="true" label="%s / $row_cont[pl_unit]" no_lang="1"/>
+						<date id="${row}[pl_validsince]" readonly="true"/>
+					</vbox>
+					<vbox>
+						<date-time id="${row}[pl_modified]" readonly="true"/>
+						<menulist>
+							<menupopup type="select-account" id="${row}[pl_modifier]" readonly="true"/>
+						</menulist>
+					</vbox>
+					<hbox class="noPrint">
+						<button image="view" label="View" id="view[$row_cont[pl_id]]" statustext="View this price" onclick="window.open(egw::link('/index.php','menuaction=projectmanager.uipricelist.view&amp;pl_id=$row_cont[pl_id]'),'_blank','dependent=yes,width=600,height=450,scrollbars=yes,status=yes'); return false;"/>
+						<button image="edit" label="Edit" id="edit[$row_cont[pl_id]]" statustext="Edit this price" onclick="window.open(egw::link('/index.php','menuaction=projectmanager.uipricelist.edit&amp;pl_id=$row_cont[pl_id]'),'_blank','dependent=yes,width=600,height=450,scrollbars=yes,status=yes'); return false;"/>
+						<button id="delete[$row_cont[pm_id]:$row_cont[pl_id]]" image="delete" label="Delete" statustext="Delete this price" onclick="return confirm('Delete this price');"/>
+					</hbox>
+				</row>
+			</rows>
+		</grid>
+	</template>
+	<template id="projectmanager.pricelist.list" template="" lang="" group="0" version="0.5.001">
+		<grid width="100%">
+			<columns>
+				<column/>
+			</columns>
+			<rows>
+				<row disabled="!@msg">
+					<description span="all" class="redItalic" align="center" id="msg" no_lang="1"/>
+				</row>
+				<row>
+					<nextmatch options="projectmanager.pricelist.list.rows" id="nm"/>
+				</row>
+				<row class="noPrint">
+					<button label="Add" id="add" onclick="window.open(egw::link('/index.php','menuaction=projectmanager.uipricelist.edit'),'_blank','dependent=yes,width=600,height=450,scrollbars=yes,status=yes'); return false;"/>
+				</row>
+			</rows>
+		</grid>
+	</template>
+</overlay>
Index: /contrib/ProjectManager/templates/default/app.css
===================================================================
--- /contrib/ProjectManager/templates/default/app.css	(revision 3594)
+++ /contrib/ProjectManager/templates/default/app.css	(revision 3594)
@@ -0,0 +1,4 @@
+.redItalic { color: red; font-style: italic; }
+.planned { font-style: italic; font-size: 95%; }
+div.solidBorder { border: 2px solid black; }
+.leftPad5 { padding-left: 5px; }
Index: /contrib/ProjectManager/templates/default/hr.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/hr.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/hr.tpl	(revision 3594)
@@ -0,0 +1,6 @@
+ <tr>
+  <td colspan="2">
+   {hr_text}
+  </td>
+ </tr>
+
Index: /contrib/ProjectManager/templates/default/viewDocument.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/viewDocument.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/viewDocument.tpl	(revision 3594)
@@ -0,0 +1,499 @@
+<!-- BEGIN main -->
+<script type='text/javascript'>
+	var folderChooserURL ='{folderChooserURL}';
+</script>
+
+<table width="100%" border="0" cellspacing="0" cellpading="0" bgcolor="white" hheight="100px">
+	<tr class="topRow">
+		<th width="33%">{delete}</th>
+		<th width="33%">{view_online}</th>
+		<th width="33%">{download}</th>
+	</tr>
+</table>
+<br>
+<table width="100%" border="0" cellspacing="0" cellpading="0">
+	<tr>
+		<th width="20%" id="tab1" class="activetab" onclick="javascript:tab.display(1);"><a href="#" tabindex="0" accesskey="1" onfocus="tab.display(1);" onclick="tab.display(1); return(false);" style="font-size:10px;">{lang_informations}</a></th>
+		<th width="20%" id="tab2" class="activetab" onclick="javascript:tab.display(2);"><a href="#" tabindex="0" accesskey="2" onfocus="tab.display(2);" onclick="tab.display(2); return(false);" style="font-size:10px;">{lang_all_versions}</a></th>
+		<th width="20%" id="tab3" class="activetab" onclick="javascript:tab.display(3);"><a href="#" tabindex="0" accesskey="3" onfocus="tab.display(3);" onclick="tab.display(3); return(false);" style="font-size:10px;">{lang_linked_documents}</a></th>
+		<th width="20%" id="tab4" class="activetab" onclick="javascript:tab.display(4);"><a href="#" tabindex="0" accesskey="4" onfocus="tab.display(4);" onclick="tab.display(4); return(false);" style="font-size:10px;">{lang_notifications}</a></th>
+		<th width="20%" id="tab5" class="activetab" onclick="javascript:tab.display(5);"><a href="#" tabindex="0" accesskey="5" onfocus="tab.display(5);" onclick="tab.display(5); return(false);" style="font-size:10px;">{lang_acl}</a></th>
+	</tr>
+</table>
+<div style="border-left: dotted 1px grey; border-bottom: dotted 1px grey; border-right: dotted 1px grey; background: white;">
+<div id="tabcontent1" class="inactivetab" bgcolor="white">
+	{informations}
+</div>
+
+<div id="tabcontent2" class="inactivetab">
+
+	<table cellspacing="5" cellpadding="0" border="0" width="100%">
+		<tr>
+			<td></td>
+			<td></td>
+			<td class="filelist" style="border-bottom: 1pt solid #000080;"><i>{lang_version}</i></td>
+			<td rowspan="{rownum}" style="border-left: 1pt solid #000080;">&nbsp;</td>
+			<td class="filelist" style="border-bottom: 1pt solid #000080;"><i>{lang_upload_date}</i></td>
+			<td rowspan="{rownum}" style="border-left: 1pt solid #000080;">&nbsp;</td>
+			<td class="filelist" style="border-bottom: 1pt solid #000080;"><i>{lang_comment}</i></td>
+			<td rowspan="{rownum}" style="border-left: 1pt solid #000080;">&nbsp;</td>
+			<td class="filelist" style="border-bottom: 1pt solid #000080;"><i>{lang_uploaded_by}</i></td>
+			<td></td>
+		</tr>
+		{versions}
+	</table>
+</div>
+
+<div id="tabcontent3" class="inactivetab">
+<!--
+<table cellspacing="5" cellpadding="0" border="1">
+	<?php
+	if ($rownum > 1)
+	{
+		?>
+		<tr>
+		<td></td>
+		<td class="filelist" style="border-bottom: 1pt solid #000080;"><i><?php printMLText("name");?></i></td>
+		<td rowspan="<?php print $rownum;?>" style="border-left: 1pt solid #000080;">&nbsp;</td>
+		<td class="filelist" style="border-bottom: 1pt solid #000080;"><i><?php printMLText("comment");?></i></td>
+		<td rowspan="<?php print $rownum;?>" style="border-left: 1pt solid #000080;">&nbsp;</td>
+		<td class="filelist" style="border-bottom: 1pt solid #000080;"><i><?php printMLText("document_link_by");?></i></td>
+		<td rowspan="<?php print $rownum;?>" style="border-left: 1pt solid #000080;">&nbsp;</td>
+		<td class="filelist" style="border-bottom: 1pt solid #000080;"><i><?php printMLText("document_link_public");?></i></td>
+		<td></td>
+		</tr>
+		<?php 
+		foreach($links as $link)
+		{
+			$responsibleUser = $link->getUser();
+			$targetDoc = $link->getTarget();
+			
+			print "<tr>";
+			print "<td><img src=\"images/file.gif\" width=18 height=18 border=0></td>";
+			print "<td class=\"linklist\"><a href=\"out.ViewDocument.php?documentid=".$targetDoc->getID()."\" class=\"linklist\">".$targetDoc->getName()."</a></td>";
+			print "<td class=\"linklist\">".$targetDoc->getComment()."</td>";
+			print "<td class=\"linklist\">".$responsibleUser->getFullName()."</td>";
+			print "<td class=\"linklist\">" . (($link->isPublic()) ? getMLText("yes") : getMLText("no")) . "</td>";
+			print "<td>";
+			if (($user->getID() == $responsibleUser->getID()) || ($user->getID() == $settings->_adminID) || ($link->isPublic() && ($document->getAccessMode($user) >= M_READWRITE )))
+				print "<a href=\"../op/op.RemoveDocumentLink.php?documentid=".$documentid."&linkid=".$link->getID()."\"><img src=\"images/del.gif\" border=0></a>";
+			print "</td>";
+			print "</tr>";
+		}
+	}
+	else
+		print "<tr><td class=\"filelist\">".getMLText("no_document_links")."</td></tr>";
+	?>
+</table>
+
+
+if ($user->getID() != $settings->_guestID)
+
+	<form action="../op/op.AddDocumentLink.php" name="form1">
+	<input type="Hidden" name="documentid" value="<?php print $documentid;?>">
+	<table>
+		<tr>
+			<td class="inputDescription"><?php printMLText("choose_target_document");?>:</td>
+			<td><?php printDocumentChooser("form1");?></td>
+		</tr>
+		<?php 
+			if ($document->getAccessMode($user) >= M_READWRITE)
+			{
+				print "<tr><td class=\"inputDescription\">".getMLText("document_link_public")."</td><td class=\"inputDescription\">";
+				print "<input type=\"Radio\" name=\"public\" value=\"true\" checked>" . getMLText("yes") . "&nbsp;&nbsp;";
+				print "<input type=\"Radio\" name=\"public\" value=\"false\">" . getMLText("no");
+				print "</td></tr>";
+			}
+		?>
+		<tr>
+			<td colspan="2"><br><input type="Submit" value="<?php printMLText("add_document_link");?>"></td>
+		</tr>
+	</table>
+	</form>-->
+</div>
+
+<div id="tabcontent4" class="inactivetab">
+	<table cellspacing="0" cellpadding="5" border="0" width="100%">
+		<tr>
+			<td style="border-bottom: 1pt solid #000080;" width="50px">&nbsp;</td>
+			<td style="border-bottom: 1pt solid #000080;" class="notifylist"><i>{lang_name}</i></td>
+			<td style="border-bottom: 1pt solid #000080;" width="50px">&nbsp;</td>
+		</tr>
+		{notifications}
+		<tr>
+			<td colspan="3">&nbsp;</td>
+		</tr>
+	</table>
+
+	<form action="{notify_form_action}" method="post" name="notify_form">
+		<fieldset><legend>{lang_add_notification}</legend>
+		<table cellspacing="0" cellpadding="5" border="0" width="100%">
+			<tr>
+				<td class="inputDescription">{lang_user}:</td>
+				<td>
+					{select_userid}
+				</td>
+			</tr>
+			<tr>
+				<td class="inputDescription">{lang_group}:</td>
+				<td>
+					{select_groupid}
+				</td>
+			</tr>
+		</table>
+		</fieldset>
+	</form>
+</div>
+
+<div id="tabcontent5" class="inactivetab">
+	{change_owner}
+	{display_acl}
+</div>
+
+<div id="tabcontent6" class="inactivetab">
+	<form enctype="multipart/form-data" name="update_file_form" method="post" action="{action_update_file}">
+	<table cellpadding="5" cellspacing="0" border="0" width="100%">
+		<tr class="row_on">
+			<td class="description" valign="top" width="150px">{lang_filename}:</td>
+			<td class="infos" colspan="2"><input type="file" name="userfile"></td>
+		</tr>
+		<tr class="row_on">
+			<td class="description" valign="top">{lang_comment}:</td>
+			<td class="infos" colspan="2"><textarea name="comment" style="width: 100%;"></textarea></td>
+		</tr>
+		<tr class="row_on">
+			<td class="description" valign="top">{lang_expires}:</td>
+			<td class="infos" valign="top" colspan="2">
+				{select_expire_update}
+				<span id="jscalspan_update" class="{expire_class_update}">{expire_date_update}</span>
+			</td>
+		</tr>
+		<tr>
+			<td colspan="3" valign="top">
+				<input type="submit" name="save" value="{lang_update}">
+				<button type="button" name="cancel" value="{lang_cancel}" onClick="javascript:tab.display(1);">
+					{lang_cancel}
+				</button>
+			</td>
+		</tr>
+	</table>
+	</form>
+</div>
+
+
+
+</div>
+<!-- END main -->
+
+<!-- BEGIN lock_row -->
+	<tr class="row_on">
+		<td class="description" valign="top">{lang_lock_status}:</td>
+		<td class="infos" colspan="3">
+			<input id="lockStatus" type="hidden" name="lockStatus" value="unchanged">
+			<input type="checkbox" onClick="javascript:toggleLock(this);" {checked_lock_status}>
+			<div id="currentLockStatus" class="active">
+				{lang_current_status}
+			</div>
+			<div id="lockFile" class="inactive">
+				{lang_file_gets_unlocked}
+			</div>
+			<div id="unlockFile" class="inactive">
+				{lang_file_gets_locked}
+			</div>
+		</td>
+	</tr>
+<!-- END lock_row -->
+
+<!-- BEGIN version_row -->
+	<tr>
+		<td>
+			{url_view_online}
+		</td>
+		<td>
+			<a href="{url_download_file}" class="oldcontent"><img src="{download_image}" width=22 height=22 border=0 title="{lang_download}"></a>
+		</td>
+		<td class="filelist" align="center">{version_version}</td>
+		<td class="filelist">{version_date}</td>
+		<td class="filelist">{version_comment}</td>
+		<td class="filelist">{version_uploadingUser}</td>
+		<td>{url_delete_file}</td>
+	</tr>
+<!-- END version_row -->
+
+<!-- BEGIN information_ro -->
+	<table cellpadding="5" cellspacing="0" border="0" width="100%">
+		<tr>
+			<td class="infos" valign="top" width="150px">{lang_filename}:</td>
+			<td style="border-left: 1pt solid #000080;" rowspan="14" width="1px">&nbsp;</td>
+			<td class="infos">{filename}</td>
+		</tr>
+		<tr>
+			<td class="infos" valign="top">{lang_owner}:</td>
+			<td class="infos">
+				<a class="infos" href="mailto:{owner_email}">{owner_fullname}</a>
+			</td>
+		</tr>
+		<tr>
+			<td class="infos" valign="top">{lang_comment}:</td>
+			<td class="infos">{comment}</td>
+		</tr>
+		<tr>
+			<td class="infos" valign="top">{lang_creation_date}:</td>
+			<td class="infos">{creation_date}</td>
+		</tr>
+		<tr>
+			<td class="infos" valign="top">{lang_keywords}:</td>
+			<td class="infos">{keywords}</td>
+		</tr>
+		{locking}
+		<tr>
+			<td>&nbsp;</td>
+			<td>&nbsp;</td>
+		</tr>
+		<tr>
+			<td class="infos" valign="top">{lang_last_update}</td>
+			<td class="infos">{last_update}</td>
+		</tr>
+		<tr>
+			<td class="infos" valign="top">{lang_current_version}:</td>
+			<td class="infos">{current_version}</td>
+		</tr>
+		<tr>
+			<td class="infos" valign="top">{lang_comment_for_current_version}:</td>
+			<td class="infos" valign="top">{current_comment}</td>
+		</tr>
+		<tr>
+			<td class="infos" valign="top">{lang_uploaded_by}:</td>
+			<td class="infos">
+				<a class="infos" href="mailto:{updater_email}">{updater_fullname}</a>
+			</td>
+		</tr>
+		<tr>
+			<td class="infos" valign="top">{lang_file_size}:</td>
+			<td class="infos">{file_size} bytes</td>
+		</tr>
+		<tr>
+			<td class="infos" valign="top">{lang_mime_type}:</td>
+			<td class="infos">
+				<img align="absmiddle" src="images/icons/<?php print getMimeIcon($latestContent->getFileType());?>"> 
+				{mime_type}
+			</td>
+		</tr>
+		<tr>
+			<td class="infos" valign="top">{lang_expires}:</td>
+			<td class="infos" valign="top">
+				{expire_date_ro}
+			</td>
+		</tr>
+	</table>
+<!-- END information_ro -->
+
+<!-- BEGIN information_rw -->
+	<form name="info_form" method="post" action="{action_informations}">
+	<fieldset><legend>{lang_general_information}</legend>
+	<table cellpadding="5" cellspacing="0" border="0" width="100%">
+		<tr class="row_on">
+			<td class="description" valign="top" width="150px">{lang_filename}:</td>
+			<td class="infos" colspan="2"><input style="width: 100%;" type="text" name="fname" value="{filename}"></td>
+			<td align="center" width="150px"><a href="#" tabindex="0" onfocus="tab.display(6);" onclick="tab.display(6); return(false);" style="font-size:10px;">{lang_update_document}</a></td>
+		</tr>
+		<tr class="row_on">
+			<td class="description" valign="top" width="150px">{lang_folder}:</td>
+			<td class="infos" colspan="2">
+				<input id="targetname" style="width: 100%;" type="text" name="targetname" value="{foldername}" disabled>
+				<input type="hidden" id="targetid" name="targetid" value="unchanged">
+			</td>
+			<td align="center" width="150px"><a href="#" onclick="selectFolder({current_folder_id},'info_form'); return(false);" style="font-size:10px;">{lang_move_document}</a></td>
+		</tr>
+		<tr class="row_on">
+			<td class="description" valign="top">{lang_comment}:</td>
+			<td class="infos" colspan="3"><textarea name="comment" style="width: 100%; height: 80px;">{comment}</textarea></td>
+		</tr>
+		<tr class="row_on">
+			<td class="description" valign="top">{lang_keywords}:</td>
+			<td class="infos" colspan="3"><textarea name="keywords" style="width: 100%; height: 30px;">{keywords}</textarea></td>
+		</tr>
+		<tr class="row_on">
+			<td class="description" valign="top">{lang_expires}:</td>
+			<td class="infos" valign="top" colspan="3">
+				{select_expire}
+				<span id="jscalspan" class="{expire_class}">{expire_date}</span>
+			</td>
+		</tr>
+		{locking}
+		<tr class="row_on">
+			<td class="description_small" valign="top">{lang_owner}:</td>
+			<td class="infos" colspan="1">
+				<a class="infos" href="mailto:{owner_email}">{owner_fullname}</a>
+			</td>
+			<td class="description_small" valign="top">{lang_creation_date}:</td>
+			<td class="infos" colspan="1">{creation_date}</td>
+		</tr>
+		<tr class="row_on">
+			<td colspan="4" valign="top">
+				<button type="submit" name="save" value="save">
+					{lang_save}
+				</button>
+				<button style="margin-left: 10px;" type="button" name="cancel" value="cancel" onClick="window.close()">
+					{lang_cancel}
+				</button>
+			</td>
+		</tr>
+	</table>
+	</fieldset>
+	<p>
+	<fieldset style="border: 1px solid silver;"><legend>{lang_information_about_last_update}</legend>
+	<table cellpadding="5" cellspacing="0" border="0" width="100%">
+		<tr class="row_off">
+			<td class="description_small" valign="top" width="150px">{lang_last_update}</td>
+			<td class="infos">{last_update}</td>
+			<td class="description_small" valign="top" width="150px">{lang_current_version}:</td>
+			<td class="infos">{current_version}</td>
+		</tr>
+		<tr class="row_off">
+			<td class="description_small" valign="top">{lang_uploaded_by}:</td>
+			<td class="infos">
+				<a class="infos" href="mailto:{updater_email}">{updater_fullname}</a>
+			</td>
+			<td class="description_small" valign="top">{lang_file_size}:</td>
+			<td class="infos">{file_size} bytes</td>
+		</tr>
+		<tr class="row_off">
+			<td class="description_small" valign="top">{lang_comment_for_current_version}:</td>
+			<td class="infos" valign="top" colspan="3">{current_comment}</td>
+		</tr>
+		<tr>
+			<td class="description_small" valign="top">{lang_mime_type}:</td>
+			<td class="infos" colspan="3">
+				<img align="absmiddle" src="images/icons/<?php print getMimeIcon($latestContent->getFileType());?>"> 
+				{mime_type}
+			</td>
+		</tr>
+	</table>
+	</fieldset>
+	</form>
+<!-- END information_rw -->
+
+<!-- BEGIN block_download -->
+<a href="{download_link}"><img src="{download_image}" align="middle" border="0">{lang_download}</a>
+<!-- END block_download -->
+
+<!-- BEGIN block_view_online -->
+<a target="_blank" href="{view_link}"><img src="{view_image}" align="middle" border="0">{lang_view_online}</a>
+<!-- END block_view_online -->
+
+<!-- BEGIN block_delete -->
+<a href="{delete_link}" onClick="return confirm('{lang_confirm_delete}');"><img src="{delete_image}" align="middle" border="0">{lang_delete}</a>
+<!-- END block_delete -->
+
+<!-- BEGIN notification_row -->
+	<tr>
+		<td><img src="{notify_image}" width=16 height=16></td>
+		<td class="notifylist">{notify_username}</td>
+		<td align="right"><a href="{link_notify_delete}"><img title="{lang_delete_this_notification}" src="{delete_image}" width=15 height=15 border=0></a>
+	</tr>
+<!-- END notification_row -->
+
+<!-- BEGIN block_change_owner -->
+	<form name="change_owner" action="{action_change_owner}" method="post">
+		<fieldset><legend>{lang_owner}</legend>
+		<table cellspacing="0" cellpadding="5" border="0" width="100%">
+			<tr>
+				<td>
+					{select_ownerid}
+				</td>
+			</tr>
+		</table>
+		</fieldset>
+		<table cellspacing="0" cellpadding="5" border="0" width="100%">
+			<tr>
+				<td>&nbsp;</td>
+			</tr>
+		</table>
+	</form>
+<!-- END block_change_owner -->
+
+<!-- BEGIN block_acl_inherite -->
+	<fieldset><legend>{lang_acl}</legend>
+	<div>{lang_acl_get_inherited}</div>
+	<p>
+	<a href="{link_acl_copy}">{lang_copy_acl}</a><br>
+	<a href="{link_acl_empty}">{lang_create_empty_acl}</a><br>
+	</fieldset>
+<!-- END block_acl_inherite -->
+
+<!-- BEGIN block_acl_notinherite -->
+	<fieldset><legend>{lang_acl}</legend>
+	<div>{lang_inherite_acl_again}</div>
+	<p>
+	<a href="{link_acl_inherit_again}">{lang_inherit_acl_again}</a><br>
+
+	<fieldset><legend>{lang_default_access}</legend>
+	<form name="change_default_access" action="{action_change_default_access}" method="post">
+		<table cellspacing="0" cellpadding="5" border="0" width="100%">
+			<tr>
+				<td>
+					{select_default_access}
+				</td>
+			</tr>
+		</table>
+	</form>
+	</fieldset>
+
+	<fieldset><legend>{lang_current_acl}</legend>
+		<table cellspacing="0" cellpadding="5" border="0" width="100%">
+			<tr>
+				<td></td>
+				<td class="accessList" style="border-bottom: 1pt solid #000080;"><i>{lang_name}</i></td>
+				<!-- <td rowspan="<?php print $rownum;?>" style="border-left: 1pt solid #000080;">&nbsp;</td> -->
+				<td class="accessList" style="border-bottom: 1pt solid #000080;"><i>{lang_access_mode}</i></td>
+				<td></td>
+				<td></td>
+			</tr>
+			{acls}
+		</table>
+	</fieldset>
+
+	<fieldset><legend>{lang_add_acl}</legend>
+	<form name="add_acl" action="{action_add_acl}" method="post">
+		<table cellspacing="0" cellpadding="5" border="0" width="100%">
+			<tr>
+				<td class="inputDescription">{lang_user}:</td>
+				<td>
+					{select_add_acl_userid}
+				</td>
+				<td rowspan="3">
+					<input type="Submit">
+				</td>
+			</tr>
+			<tr>
+				<td class="inputDescription">{lang_group}:</td>
+				<td>
+					{select_add_acl_groupid}
+				</td>
+			</tr>
+			<tr>
+				<td class="inputDescription">{lang_permission}:</td>
+				<td>
+					{select_add_acl_permission}
+				</td>
+			</tr>
+		</table>
+	</form>
+	</fieldset>
+
+	</fieldset>
+<!-- END block_acl_notinherite -->
+
+<!-- BEGIN acl_row -->
+	<form method="post" action="{action_acl_row}">
+	<tr>
+		<td width="20px"><img src="{acl_image}" width=16 height=16></td>
+		<td class="acllist">{acl_username}</td>
+		<td class="acllist" width="310px">{acl_selectbox}</td>
+		<td width="20px"><input type="Image" src="{save_image}" alt="{lang_save}"></td>
+		<td width="20px" align="right"><a href="{link_acl_delete}" onClick="return confirm('{lang_confirm_acl_delete}');"><img title="{lang_delete_this_acl}" alt="{lang_delete}" src="{delete_image}" width=15 height=15 border=0></a>
+	</tr>
+	</form>
+<!-- END acl_row -->
+
Index: /contrib/ProjectManager/templates/default/delete_common.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/delete_common.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/delete_common.tpl	(revision 3594)
@@ -0,0 +1,16 @@
+<!-- BEGIN form -->
+ <center>
+  <table border="0" with="65%">
+   <tr>
+    <td colspan="2" align="center">
+     {messages}
+    </td>
+   </tr>
+   <tr>
+    <td align="center">{no}</td>
+    <td align="center">{yes}</td>
+   </tr>
+  </table>
+ </center>
+<!-- END form -->
+
Index: /contrib/ProjectManager/templates/default/view.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/view.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/view.tpl	(revision 3594)
@@ -0,0 +1,46 @@
+
+<!-- BEGIN view_event -->
+<center>
+<table id="calendar_view_event" border="0" width="90%">
+	{row}
+	<tr>
+		<td>
+			<table id="calendar_viewevent_button_left" cellspacing="5">
+				<tr>
+					{button_left}
+				</tr>
+			</table>
+		</td>
+	   <td align="center">
+			<table id="calendar_viewevent_button_center" cellspacing="5">
+				<tr>
+					{button_center}
+				</tr>
+			</table>
+		</td>
+		<td align="right">
+			<table id="calendar_viewevent_button_right" cellspacing="5">
+				<tr>
+					{button_right}
+				</tr>
+			</table>
+		</td>
+	</tr>
+</table>
+</center>
+<!-- END view_event -->
+
+<!-- BEGIN list -->
+	<tr bgcolor="{tr_color}">
+		<td valign="top" width="30%">&nbsp;<b>{field}:</b></td>
+		<td colspan="2" valign="top" width="70%">{data}</td>
+	</tr>
+<!-- END list -->
+
+<!-- BEGIN hr -->
+	<tr>
+		<td colspan="3" bgcolor="{th_bg}" align="center">
+			<b>{hr_text}</b>
+		</td>
+	</tr>
+<!-- END hr -->
Index: /contrib/ProjectManager/templates/default/list.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/list.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/list.tpl	(revision 3594)
@@ -0,0 +1,6 @@
+<!-- BEGIN list -->
+ <tr>
+  <td valign="top" width="35%"><b>{field}:</b></td>
+  <td valign="top" width="65%">{data}</td>
+ </tr>
+<!-- END list -->
Index: /contrib/ProjectManager/templates/default/mini_cal.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/mini_cal.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/mini_cal.tpl	(revision 3594)
@@ -0,0 +1,42 @@
+
+<!-- BEGIN mini_cal -->
+
+	<table id="calendar_minical_table" class="calendar_minical_table" border=0 cellspacing="0" cellpadding="0">
+		<tr>
+			<td style="padding-left: 8px; font-weight: bold; text-align: left;">
+				{month}
+			</td>
+			<td align="right">
+				<!-- why empty? -->
+				{prevmonth}&nbsp;&nbsp;{nextmonth}
+			</td>
+		</tr>
+		<tr>
+			<td  style="padding-left: 8px; text-align: left;">
+<!--				<img src="{cal_img_root}" width="90%" height="1"> -->
+					<hr class="calendar_minical_hrule">
+			</td>
+		</tr>
+		<tr valign="top">
+			<td>
+				<table id="calendar_minical_daytable" class="calendar_minical_daytable" cellspacing="5" cellpadding="0">
+					<tr>{daynames}</tr>
+						{display_monthweek}
+				</table>
+
+			</td>
+	 	</tr>
+	</table>
+<!-- END mini_cal -->
+
+<!-- BEGIN mini_week -->
+	<tr>
+		{monthweek_day}
+	</tr>
+<!-- END mini_week -->
+
+<!-- BEGIN mini_day -->
+		<td class="calendar_minical_dayname" {day_image}>
+			{dayname}
+		</td>
+<!-- END mini_day -->
Index: /contrib/ProjectManager/templates/default/link_pict.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/link_pict.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/link_pict.tpl	(revision 3594)
@@ -0,0 +1,25 @@
+
+<!-- BEGIN link_pict -->
+{picture}
+<!-- END link_pict -->
+
+<!-- BEGIN link_open -->
+<div id="calendar_event_entry" style="overflow:hidden;">
+<a class="event_entry" href="{link_link}" onMouseOver="window.status='{lang_view}'; return true;" onMouseOut="window.status=''; return true;" title="{desc} {location}"><br>
+<!-- END link_open -->
+
+<!-- BEGIN pict -->
+ <img src="{pic_image}" width="{width}" height="{height}" title="{title}" border="0" />
+<!-- END pict -->
+
+<!-- BEGIN link_text_old -->
+<nobr>&nbsp;{time}&nbsp;</nobr> {title}&nbsp;{users_status}: <i>{desc}</i><!--({location})-->
+<!-- END link_text_old -->
+
+<!-- BEGIN link_text -->
+&nbsp;<FONT SIZE=1><span style="color: black">{time}</span> {users_status}<br><b>{title}</b><br><i>{desc}</i> {location}
+<!-- END link_text -->
+
+<!-- BEGIN link_close -->
+</a></div>
+<!-- END link_close -->
Index: /contrib/ProjectManager/templates/default/day.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/day.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/day.tpl	(revision 3594)
@@ -0,0 +1,39 @@
+<!-- BEGIN day -->
+{printer_friendly}
+<table id="calendar_dayview_table" class="calendar_dayview_table" border="0" width="100%">
+	<tr>
+		<td valign="top" width="70%">
+			<table id="calendar_dayview1" border="0" width=100%>
+				<tr>
+					<td class="calendar_dayview_table_header">
+      					{date}&nbsp;<span class="calendar_user_identifier">:&nbsp;{username}&nbsp;:</span>
+						<br />
+     				</td>
+    			</tr>
+				{day_events}
+   			</table>
+   			<p align="center">{print}</p>
+  		</td>
+ 		 <td align="center" valign="top">
+  			 <table id="calendar_dayview2" width="100%">
+    			<tr>
+     				<td align="center">
+						{small_calendar}
+     				</td>
+    			</tr>
+
+   			</table>
+  		</td>
+ 	</tr>
+</table>
+<!-- END day -->
+
+<!-- BEGIN day_event -->
+	<tr>
+		<td>
+
+			{daily_events}
+
+		</td>
+	</tr>
+<!-- END day_event -->
Index: /contrib/ProjectManager/templates/default/js/edit_exmail.js
===================================================================
--- /contrib/ProjectManager/templates/default/js/edit_exmail.js	(revision 3594)
+++ /contrib/ProjectManager/templates/default/js/edit_exmail.js	(revision 3594)
@@ -0,0 +1,393 @@
+/******************************************************************************\
+|**************************   CALENDAR MODULE   *******************************|
+|********** INCLUDE PARTICIPANTS - PLUGIN USING AJAX EXPRESSOMAIL COMPONENT  **|
+\******************************************************************************/
+var contacts = '';
+var expresso_offline = '';
+var array_lang = new Array();
+var finderTimeout = '';
+// onUnload edit message
+if(!document.all)
+{
+	var beforeunload = window.onbeforeunload;
+	window.onbeforeunload = function()
+	{
+		if ( typeof beforeunload == 'function' )
+			beforeunload();
+	};
+}
+	
+// Verifica versão do Firefox
+var agt = navigator.userAgent.toLowerCase();
+var is_firefox_0 = agt.indexOf('firefox/1.0') != -1 && agt.indexOf('firefox/0.') ? true : false;
+
+var handler_get_available_users = function(data)
+{	
+	select_available_users = Element('user_list_in');
+	
+	for(var i=0; i<select_available_users.options.length; i++){
+		select_available_users.options[i] = null;
+		i--;
+	}
+	var options = '###';
+	if (data) {
+		if(data.groups && data.groups.length > 0) {
+			data.groups = '<option  value="-1" disabled>------------------- '+Element("txt_groups").value+' ------------------ </option>' + data.groups;
+		}		
+		if(data.users && data.users.length > 0) {
+			data.users = '<option  value="-1" disabled>------------------- '+Element("txt_users").value+' ------------------ </option>' + data.users;
+		}
+		options +=  data.groups && data.groups.length > 0 ? data.groups : '';
+		options +=  data.users  && data.users.length  > 0 ? data.users  : '';
+		
+		if(is_firefox_0)
+			fixBugInnerSelect(select_available_users,options);
+		else
+			select_available_users.innerHTML = options;
+
+		select_available_users.outerHTML = select_available_users.outerHTML;
+		select_available_users.disabled = false;
+		select_available_users_clone = Element('user_list_in').cloneNode(true);		
+	}
+}	
+
+function get_available_users(module,context,type, autoSearch){
+	Element('cal_input_searchUser').value = '';
+	if(autoSearch != 'True'){
+		return true;
+	}
+	var context = document.getElementById('combo_org').value;	
+	cExecute (module+'.ldap_functions.get_available_users&context='+context+'&type='+type, handler_get_available_users);
+}
+
+function optionFinderTimeout(obj, numMin, type, autoSearch){
+	var oWait = Element("cal_span_searching");
+	oWait.innerHTML = 'Buscando...';
+	clearTimeout(finderTimeout);		
+	
+	if(autoSearch == "True"){
+		finderTimeout = setTimeout("optionFinderLocal('"+obj.id+"')",500);
+	}
+	else if (obj.value.length >= numMin){
+		finderTimeout = setTimeout("optionFinderLdap('"+obj.id+"','"+numMin+"','"+type+"')",500);
+	}else {
+		oWait.innerHTML = 'Mínimo de '+numMin+' letras para pesquisa';
+		var select_available_users_tmp = document.getElementById('user_list_in');
+		for(var i = 0;i < select_available_users_tmp.options.length; i++)
+		select_available_users_tmp.options[i--] = null;
+	}
+}
+// Pesquisa Javascript
+function optionFinderLocal(id){
+	var oText = Element(id);
+	var oWait = Element("cal_span_searching");
+	var select_available_users_tmp = Element('user_list_in');
+	for(var i = 0;i < select_available_users_tmp.options.length; i++)
+		select_available_users_tmp.options[i--] = null;
+	var RegExp_name = new RegExp("\\b"+oText.value, "i");
+
+	for(i = 0; i < select_available_users_clone.length; i++){
+		if (RegExp_name.test(select_available_users_clone[i].text) || select_available_users_clone[i].value =="-1")
+		{
+			sel = select_available_users_tmp.options;
+			option = new Option(select_available_users_clone[i].text,select_available_users_clone[i].value);
+			if( select_available_users_clone[i].value == "-1") option.disabled = true;
+			sel[sel.length] = option;
+		}
+	}
+	oWait.innerHTML = '&nbsp;';	
+}
+
+// Pesquisa LDAP
+function optionFinderLdap(id,numMin, type){
+		var oWait = Element("cal_span_searching");
+		var oText = Element(id);
+			
+		if (oText.value.length < numMin) {
+				oWait.innerHTML = '';
+				var select_available_users_tmp = document.getElementById('user_list_in');
+				for(var i = 0;i < select_available_users_tmp.options.length; i++)
+					select_available_users_tmp.options[i--] = null;
+		}
+		
+		if (oText.value.length >= numMin){
+			var context = document.getElementById('combo_org').value;
+			cExecute ('expressoMail1_2.ldap_functions.search_users&context='+(context)+'&type='+(type == '' ? 'list' : 'search')+'&filter='+oText.value, handler_get_available_users);
+			oWait.innerHTML = '&nbsp;';
+		}
+}
+
+function add_user()
+{
+	var select_available_users = document.getElementById('user_list_in');
+	var select_users = document.getElementById('user_list');
+	var count_available_users = select_available_users.length;
+	var count_users = select_users.options.length;
+	var new_options = '';
+	
+	for (i = 0 ; i < count_available_users ; i++) {
+		if (select_available_users.options[i].selected)	{
+			if(document.all) {
+				if ( (select_users.innerHTML.indexOf('value='+select_available_users.options[i].value)) == '-1' ) {
+					new_options +=  '<option value='
+								+ select_available_users.options[i].value
+								+ '>'
+								+ select_available_users.options[i].text
+								+ '</option>';
+				}
+			}
+			else if ( (select_users.innerHTML.indexOf('value="'+select_available_users.options[i].value+'"')) == '-1' ) {
+					new_options +=  '<option value='
+								+ select_available_users.options[i].value
+								+ '>'
+								+ select_available_users.options[i].text
+								+ '</option>';
+			}
+		}
+	}
+
+	if (new_options != '') {
+
+		if(is_firefox_0)
+			fixBugInnerSelect(select_users,'###' + new_options + select_users.innerHTML);
+		else
+			select_users.innerHTML = '###' + new_options + select_users.innerHTML;
+
+		select_users.outerHTML = select_users.outerHTML;
+	}
+}
+
+function remove_user(){
+	select_users = document.getElementById('user_list');
+	
+	for(var i = 0;i < select_users.options.length; i++)
+		if(select_users.options[i].selected)
+			select_users.options[i--] = null;
+}
+
+function submitValues(){
+	var typeField = document.getElementById('cal[type]');
+	if (typeField && typeField.value == 'hourAppointment') {
+		if(document.getElementsByName('categories[]')[0].value == ""){
+			alert(alert_msg);
+			return false;
+		}
+	}
+	var select_in = document.getElementById('user_list');
+	for(i = 0; i < select_in.length; i++)
+	 	select_in.options[i].selected = true;
+}	
+
+function Element(id){
+	return document.getElementById(id);
+}
+
+function loadScript(scriptPath){
+
+	if(!connector)
+		throw new Error("Error : Connector is not loaded.");
+		
+  	if (document.getElementById('uploadscript_'+scriptPath)) {
+  		return;
+   	}
+  	
+	connector.oxmlhttp.open("GET", scriptPath, false);
+    connector.oxmlhttp.setRequestHeader('Content-Type','text/plain');
+	connector.oxmlhttp.send(null);
+	if(connector.oxmlhttp.status != 0 && connector.oxmlhttp.status != 200 || 	connector.oxmlhttp.status == 0 && connector.oxmlhttp.responseText.length == 0)
+		throw new Error("Error " + connector.oxmlhttp.status + "("+connector.oxmlhttp.statusText+") when loading script file '"+scriptPath+"'");
+	
+	var head = document.getElementsByTagName("head")[0];
+	var script = document.createElement("SCRIPT");
+	script.id = 'uploadscript_'+scriptPath;
+	script.type = 'text/javascript';		
+	script.text = connector.oxmlhttp.responseText;
+	head.appendChild(script);
+	return;	
+}
+
+function showExParticipants(el,path){
+	Element('tbl_ext_participants').style.display='';
+	el.style.display='none';
+	loadScript(path+"/js/DropDownContacts.js");
+	loadScript(path+"/js/common_functions.js");
+	loadScript(path+"/js/QuickCatalogSearch.js");
+	loadScript("calendar/templates/default/js/over_fn_exmail.js");
+	if(!contacts)
+		cExecute (path+'.db_functions.get_dropdown_contacts', save_contacts);
+}
+
+function hideExParticipants(el,path){
+	Element('a_ext_participants').style.display = '';
+	Element('tbl_ext_participants').style.display ='none';
+}
+
+function save_contacts(data){
+	contacts = data;	
+	var input_to = Element("ex_participants");
+	input_to.style.width = "100%";
+	input_to.setAttribute("wrap","soft");	
+	input_to.onfocus = function(){clearTimeout(parseInt(setTimeOutLayer)); search_contacts('onfocus', this.id);};
+	input_to.onblur = function(){setTimeOutLayer=setTimeout('search_contacts("lostfocus","'+this.id+'")',100);};
+	if (!is_ie)
+	{
+		input_to.rows = 2;
+		input_to.onkeypress = function (e)
+		{
+			if ((e.keyCode) == 120) //F9
+			{
+				emQuickSearch(input_to);
+			}
+			else 
+			{ 
+				if (((e.keyCode == 13) || ((e.keyCode == 38)||(e.keyCode == 40))) && (document.getElementById('tipDiv').style.visibility!='hidden'))
+				{
+					e.preventDefault();
+					search_contacts(e.keyCode,this.id);
+				}
+			}
+		}
+		input_to.onkeyup = function (e)
+		{
+			if ((e.keyCode != 13) && (e.keyCode != 38) && (e.keyCode != 40))
+			{
+				search_contacts(e.keyCode,this.id);
+			}
+		}
+	}
+	else
+	{
+		input_to.rows = 3;
+		input_to.onkeyup = function (e)
+		{
+			if ((window.event.keyCode) == 120) //F9
+			{
+				emQuickSearch(input_to);
+			}
+			else 
+			{ 
+				search_contacts(window.event.keyCode,this.id);
+			}	
+		}
+	}
+}
+
+function fixBugInnerSelect(objeto,innerHTML){
+/******
+* select_innerHTML - altera o innerHTML de um select independente se é FF ou IE
+* Corrige o problema de não ser possível usar o innerHTML no IE corretamente
+* Veja o problema em: http://support.microsoft.com/default.aspx?scid=kb;en-us;276228
+* Use a vontade mas coloque meu nome nos créditos. Dúvidas, me mande um email.
+* Versão: 1.0 - 06/04/2006
+* Autor: Micox - Náiron José C. Guimarães - micoxjcg@yahoo.com.br
+* Parametros:
+* objeto(tipo object): o select a ser alterado
+* innerHTML(tipo string): o novo valor do innerHTML
+*******/
+    objeto.innerHTML = ""
+    var selTemp = document.createElement("micoxselect")
+    var opt;
+    selTemp.id="micoxselect1"
+    document.body.appendChild(selTemp)
+    selTemp = document.getElementById("micoxselect1")
+    selTemp.style.display="none"
+    if(innerHTML.toLowerCase().indexOf("<option")<0){//se não é option eu converto
+        innerHTML = "<option>" + innerHTML + "</option>"
+    }
+    innerHTML = innerHTML.replace(/<option/g,"<span").replace(/<\/option/g,"</span")
+    selTemp.innerHTML = innerHTML
+    for(var i=0;i<selTemp.childNodes.length;i++){
+        if(selTemp.childNodes[i].tagName){
+            opt = document.createElement("OPTION")
+            for(var j=0;j<selTemp.childNodes[i].attributes.length;j++){
+                opt.setAttributeNode(selTemp.childNodes[i].attributes[j].cloneNode(true))
+            }
+            opt.value = selTemp.childNodes[i].getAttribute("value")
+            opt.text = selTemp.childNodes[i].innerHTML
+            if(document.all){ //IEca
+                objeto.add(opt)
+            }else{
+                objeto.appendChild(opt)
+            }                    
+        }    
+    }
+    document.body.removeChild(selTemp)
+    selTemp = null
+}
+
+function changeViewMode(eltype){
+	var chValue = eltype;
+	switch (chValue){
+		case 'hourAppointment':
+			var names=new Array('title','priority','location','alarmhours','alarmminutes','recur_type','rpt_use_end','recur_interval','rpt_label');
+			for (var i=0; i < names.length; i++)
+			{
+				var Field = document.getElementsByName('cal['+names[i]+']');
+				if (Field[0])
+					Field[0].parentNode.parentNode.style.display = "none";
+			}
+
+			Field = document.getElementsByName('participants[]');
+			Field[0].parentNode.parentNode.style.display = "none";
+			Field[1].parentNode.parentNode.style.display = "none";
+			Field = document.getElementById('txt_loading');
+			Field.parentNode.parentNode.style.display = "none";
+			Field = document.getElementsByName('cal[rpt_day][]');
+			Field[0].parentNode.parentNode.style.display = "none";
+			break;
+		case 'privateHiddenFields':
+			var names=new Array('title','priority','location','alarmhours','alarmminutes','recur_type','rpt_use_end','recur_interval','rpt_label');
+			for (var i=0; i < names.length; i++)
+			{
+				var Field = document.getElementsByName('cal['+names[i]+']');
+				if (Field[0])
+					Field[0].parentNode.parentNode.style.display = "";
+			}
+			Field = document.getElementsByName('participants[]');
+			Field[0].parentNode.parentNode.style.display = "none";
+			Field[1].parentNode.parentNode.style.display = "none";
+			Field = document.getElementById('txt_loading');
+			Field.parentNode.parentNode.style.display = "none";
+			Field = document.getElementsByName('cal[rpt_day][]');
+			Field[0].parentNode.parentNode.style.display = "";
+			break;
+		default:
+			var names=new Array('title','priority','location','alarmhours','alarmminutes','recur_type','rpt_use_end','recur_interval','rpt_label');
+			for (var i=0; i < names.length; i++)
+			{
+				var Field = document.getElementsByName('cal['+names[i]+']');
+				if (Field[0])
+					Field[0].parentNode.parentNode.style.display = "";
+			}
+			Field = document.getElementsByName('participants[]');
+			Field[0].parentNode.parentNode.style.display = "";
+			Field[1].parentNode.parentNode.style.display = "";
+			Field = document.getElementById('txt_loading');
+			Field.parentNode.parentNode.style.display = "";
+			Field = document.getElementsByName('cal[rpt_day][]');
+			Field[0].parentNode.parentNode.style.display = "";
+			break;
+	}
+
+}
+function updateTitleField(select){
+	var typeField = document.getElementsByName('cal[type]');
+	if (typeField[0].value != 'hourAppointment')
+		return;
+	var titleField = document.getElementsByName('cal[title]');
+	var optionsArray = select.childNodes;
+	titleField[0].value = '';
+	for(option in optionsArray)
+		if (optionsArray[option].selected)
+			titleField[0].value += optionsArray[option].text + ' ';
+}
+
+var __onLoad = window.onload;
+window.onload = function(){ 
+	__onLoad();
+	var cal_type = document.getElementById('cal[type]').value;
+	changeViewMode(cal_type);
+	if(cal_type == 'hourAppointment'){
+		clearTimeout(timeout_get_available_users);
+	}
+};
Index: /contrib/ProjectManager/templates/default/js/edit.js
===================================================================
--- /contrib/ProjectManager/templates/default/js/edit.js	(revision 3594)
+++ /contrib/ProjectManager/templates/default/js/edit.js	(revision 3594)
@@ -0,0 +1,101 @@
+/******************************************************************************\
+|**************************   MODULO   AGENDA   *******************************|
+|********** SCRIPT REFERENTE A CAIXA DE INCLUSAO DE PARTICIPANTES  ************|
+\******************************************************************************/
+
+// Variaveis Locais 
+	var select_in  = document.getElementById('user_list');
+	var select_out  = document.getElementById('user_list_in');	
+	var users_out = select_out ? select_out.cloneNode(true) : '';
+	var finderTimeout = '';
+																		
+// Funcoes				
+	function showExParticipants(el,path){
+		document.getElementById('tbl_ext_participants').style.display='';
+		el.style.display='none';
+	}
+	
+	function hideExParticipants(el,path){
+		document.getElementById('a_ext_participants').style.display = '';
+		document.getElementById('tbl_ext_participants').style.display ='none';
+	}
+							
+	function optionFinderTimeout(obj){
+
+		clearTimeout(finderTimeout);	
+		var oWait = document.getElementById("wait");
+		oWait.innerHTML = 'Buscando...';
+		finderTimeout = setTimeout("optionFinder('"+obj.id+"')",500);
+	}
+	function optionFinder(id) {	
+		var oWait = document.getElementById("wait");
+		var oText = document.getElementById(id);
+		for(var i = 0;i < select_out.options.length; i++)				
+			select_out.options[i--] = null;
+																							
+		for(i = 0; i < users_out.length; i++){																							
+			if(users_out[i].text.substring(0 ,oText.value.length).toUpperCase() == oText.value.toUpperCase()) {
+				sel = select_out.options;						
+				option = new Option(users_out[i].text,users_out[i].value);				
+				sel[sel.length] = option;
+			}
+		}
+		oWait.innerHTML = '&nbsp;';
+	}			
+									
+	function rem()
+	{
+		for(var i = 0;i < select_in.options.length; i++)				
+			if(select_in.options[i].selected)
+				select_in.options[i--] = null;																	
+	}
+		
+	function submitValues(alert_msg){
+		var typeField = document.getElementById('cal[type]');
+		if (typeField && typeField.value == 'hourAppointment')
+			if(document.getElementsByName('categories[]')[0].value == ""){
+				alert(alert_msg);
+				return false;
+				}
+		for(i = 0; i < select_in.length; i++)
+		 	select_in.options[i].selected = true;
+	}
+	
+	
+	
+	function openListUsers(newWidth,newHeight, owner){					
+		newScreenX  = screen.width - newWidth;		
+		newScreenY  = 0;		
+		window.open('calendar/templates/default/listUsers.php?owner='+owner,"","width="+newWidth+",height="+newHeight+",screenX="+newScreenX+",left="+newScreenX+",screenY="+newScreenY+",top="+newScreenY+",toolbar=no,scrollbars=yes,resizable=no");
+	}
+
+ 	function adicionaListaCalendar() 
+ 	{
+		var select = window.document.getElementById('user_list_in');
+		var selectOpener = window.opener.document.getElementById('user_list');
+		for (i = 0 ; i < select.length ; i++) {				
+
+			if (select.options[i].selected) {
+				isSelected = false;
+
+				for(var j = 0;j < selectOpener.options.length; j++) {																			
+					if(selectOpener.options[j].value == select.options[i].value){
+						isSelected = true;						
+						break;	
+					}
+				}
+
+				if(!isSelected){
+
+					option = window.opener.document.createElement('option');
+					option.value =select.options[i].value;
+					option.text = select.options[i].text;
+					selectOpener.options[selectOpener.options.length] = option;	
+				}
+				
+			}
+		}
+		selectOpener.options[selectOpener.options.length-1].selected = true;
+ 	}
+	
+// Fim        
Index: /contrib/ProjectManager/templates/default/js/search.js
===================================================================
--- /contrib/ProjectManager/templates/default/js/search.js	(revision 3594)
+++ /contrib/ProjectManager/templates/default/js/search.js	(revision 3594)
@@ -0,0 +1,108 @@
+var searchTimeout;
+
+function lang(key){
+	return document.getElementById("txt_"+key).value;
+}
+
+function remove_user(id){
+	if(!confirm(lang("confirm")))
+		return true;	
+	var handler_rem_user = function(data){
+		if(data) {
+			var tr = document.getElementById(id);
+			tr.parentNode.removeChild(tr);
+		}
+	}	
+	cExecute ('calendar.uigroup_access.rem_user&id='+id, handler_rem_user);
+}
+function add_user(){
+	var select_users = document.getElementById("user");
+	var select_groups = document.getElementById("group");
+	var a_rights = new Array("L","A","E","R","P");
+	var rights = '';
+	for(var z = 0; z < a_rights.length; z++){
+		var check = document.getElementById("right_"+a_rights[z]);
+		if(check.checked){
+			rights += check.value;
+		}
+	}
+	if(!select_users.value){
+		alert(lang("nouser"));
+		return;
+	}
+	else if(!select_groups.value){
+		alert(lang("nogroup"));
+		return;
+	}
+	else if(!rights){
+		alert(lang("nopermissiontype"));
+		return;
+	}
+	
+	var str = select_users.value+";"+select_groups.value;
+	var handler_add_user = function(data){
+		
+		if(data){
+			var t = document.getElementById('tbody_list');
+			var tr = document.createElement("TR");
+			tr.bgColor = "#DCDCDC";
+			var td1 = document.createElement("TD");
+			var td2 = document.createElement("TD");
+			var td3 = document.createElement("TD");
+			var td4 = document.createElement("TD");
+			tr.id = str;
+			td1.innerHTML = "<b>&nbsp;&nbsp;"+select_users[select_users.selectedIndex].text+"</b>";
+			td2.innerHTML = rights;
+			td2.align = "center";
+			td3.innerHTML = "&nbsp;&nbsp;"+select_groups[select_groups.selectedIndex].text;
+			td4.innerHTML = "<button  title='remove' type='button' onClick='javascript:remove_user(\""+str+"\");'><img src='"+document.getElementById("template_set").value+"/images/delete.png' style='vertical-align: middle;'/></button>";
+			tr.appendChild(td1);
+			tr.appendChild(td2);
+			tr.appendChild(td3);
+			tr.appendChild(td4);
+			t.appendChild(tr);
+			alert(lang("success"));
+		}
+		else{
+			alert(lang("exist"));
+		}
+	}
+	cExecute ('calendar.uigroup_access.add_user&id='+str+"&rights="+rights, handler_add_user);
+	return true;
+		
+}
+function search_object(input, id_span, id_select, type)
+{
+	clearTimeout(searchTimeout);	
+	var spam = document.getElementById(id_span);
+	if (input.value.length <= 3){
+		spam.innerHTML = lang("typemoreletters").replace("X",4 - input.value.length);
+	}else{
+		spam.innerHTML = lang("searching")+'...';
+		searchTimeout = setTimeout("search_ldap('"+input.id+"','"+id_span+"','"+id_select+"','"+type+"')",750);
+	}
+}
+
+function search_ldap(id_input, id_span, id_select, type)
+{
+	var search = document.getElementById(id_input).value;
+	
+	var handler_search_user = function(data)
+	{
+		var spam = document.getElementById(id_span);
+		select_available_users = document.getElementById(id_select);
+		if (data.status == 'false')
+		{
+			spam.innerHTML = data.msg;
+			// Necessario, pois o IE6 tem um bug que retira o primeiro options se o innerHTML estiver vazio.
+			select_available_users.innerHTML = '#';
+			select_available_users.outerHTML = select_available_users.outerHTML;
+			return;
+		}	
+		spam.innerHTML = '';
+		// Necessario, pois o IE6 tem um bug que retira o primeiro options se o innerHTML estiver vazio.
+		select_available_users.innerHTML = '#' + data;
+		select_available_users.outerHTML = select_available_users.outerHTML;
+	}
+	cExecute ('calendar.uigroup_access.search_user&search='+search+"&type="+type, handler_search_user);
+}
Index: /contrib/ProjectManager/templates/default/js/over_fn_exmail.js
===================================================================
--- /contrib/ProjectManager/templates/default/js/over_fn_exmail.js	(revision 3594)
+++ /contrib/ProjectManager/templates/default/js/over_fn_exmail.js	(revision 3594)
@@ -0,0 +1,163 @@
+/******************************************************************************\
+|**************************   CALENDAR MODULE   *******************************|
+|********** PLUGIN USING AJAX EXPRESSOMAIL COMPONENT - OVERWRITE FUNCTIONS   **|
+\******************************************************************************/
+
+/******************************************************************************\
+|** Function      : emQuickSearch *********************************************|
+|** Original File : <expressoMail_module>/js/common_functions.js **************|
+\******************************************************************************/
+window.onresize = function(){};
+function emQuickSearch(el){
+
+	var emailList = el.value;
+	var field = el.id;
+	var ex_participants = Element("ex_participants");
+	var quickSearchKeyBegin;
+	var quickSearchKeyEnd;
+	
+	var handler_emQuickSearch = function(data){
+		window_DropDownContacts = Element('tipDiv');
+		if (window_DropDownContacts.style.visibility != 'hidden'){
+			window_DropDownContacts.style.visibility = 'hidden';
+		}
+		
+		if ((!data.status) && (data.error == "many results")){
+			alert(get_lang('More than %1 results. Please, try to refine your search.',200));
+			return false;
+		}
+			
+		if (data.length > 0){
+			QuickCatalogSearch.showList(data, quickSearchKeyBegin, quickSearchKeyEnd);
+		}
+		else
+			alert(get_lang('None result was found.'));
+		return true;
+	}
+	ex_participants.focus(); 				//mecessary for IE.
+	var i = getPosition(ex_participants); //inputBox.selectionStart;
+	var j = --i;
+
+    while ((j >= 0) && (emailList.charAt(j) != ',')){j--};
+    quickSearchKeyBegin = ++j;
+
+    while ((i <= emailList.length) && (emailList.charAt(i) != ',')){i++};
+    quickSearchKeyEnd = i;
+
+    var search_for = trim(emailList.substring(quickSearchKeyBegin, quickSearchKeyEnd));
+
+	if (search_for.length < 4){
+		alert(get_lang('Your search argument must be longer than 4 characters.'));
+		return false;
+	}
+	
+	cExecute ("expressoMail1_2.ldap_functions.quicksearch&search_for="+search_for+"&field="+field, handler_emQuickSearch);
+}
+
+/******************************************************************************\
+|** Function      : emQuickCatalogSearch.prototype.transfer_result ************|
+|** Original File : <expressoMail_module>/js/QuickCatalogSearch.js ************|
+\******************************************************************************/
+emQuickCatalogSearch.prototype.transfer_result = function (field, ID, begin, end){
+	cm = document.getElementById('select_QuickCatalogSearch');
+	option_selected = cm.options[cm.selectedIndex].value + ", ";
+	emailList = Element("ex_participants").value;		
+	new_emailList = emailList.substring(0, begin) + option_selected + emailList.substring((parseInt(end) + 2), emailList.length);
+	document.getElementById("ex_participants").value = new_emailList;
+	if(is_ie)
+		Element("i_blank").style.display = "none";
+	document.getElementById("ex_participants").focus();
+	this.arrayWin['window_QuickCatalogSearch'].close();
+}
+
+/******************************************************************************\
+|** Function      : emQuickCatalogSearch.prototype.close_QuickSearch_window ***|
+|** Original File : <expressoMail_module>/js/QuickCatalogSearch.js ************|
+\******************************************************************************/
+emQuickCatalogSearch.prototype.close_QuickSearch_window = function (field, ID){
+	if(is_ie)
+		Element("i_blank").style.display = "none";
+	document.getElementById("ex_participants").focus();
+	this.arrayWin['window_QuickCatalogSearch'].close();
+}
+
+/******************************************************************************\
+|** Function      : emQuickCatalogSearch.prototype.showWindow *****************|
+|** Original File : <expressoMail_module>/js/QuickCatalogSearch.js ************|
+\******************************************************************************/
+emQuickCatalogSearch.prototype.showWindow = function (div)
+{
+	if(! div) {
+		alert(get_lang('The list has no participant.'));
+		return;
+	}
+						
+	if(! this.arrayWin[div.id]) {
+		div.style.width = "700px";
+		div.style.height = "230px";
+		var title = get_lang('The results were found in the Global Catalog')+':';
+		var wHeight = div.offsetHeight + "px";
+		var wWidth =  div.offsetWidth   + "px";
+		div.style.width = div.offsetWidth - 5;
+
+		win = new dJSWin({			
+			id: 'QuickCatalogSearch_'+div.id,
+			content_id: div.id,
+			width: wWidth,
+			height: wHeight,
+			y:'300px',
+			top: '300px',
+			title_color: '#3978d6',
+			bg_color: '#eee',
+			title: title,						
+			title_text_color: 'white',
+			button_x_img: 'phpgwapi/images/winclose.gif',
+			border: true });
+		
+		this.arrayWin[div.id] = win;
+		win.draw();
+	}
+	else {
+		win = this.arrayWin[div.id];
+	}
+
+	if(is_ie) {	
+		var _IFrame = Element("i_blank");
+		if(_IFrame){
+    		_IFrame.style.display  = '' ;		
+		}
+		else {
+			_IFrame = document.body.appendChild(document.createElement('iframe'));
+			_IFrame.src = 'about:blank' ;
+		    _IFrame.frameBorder		= '0';
+		    _IFrame.scrolling		= 'no' ;
+		    _IFrame.style.left		= findPosX(div)  + document.body.scrollLeft -5;
+			_IFrame.style.top		= findPosY(div)  + document.body.scrollTop  -20;
+		    _IFrame.width			= 610;
+   		    _IFrame.style.border	= 0;
+			_IFrame.height			= 285;
+			_IFrame.border			= 0;
+			_IFrame.id				= "i_blank";
+		    _IFrame.style.position	= 'absolute';
+	   	    _IFrame.style.zIndex	= '5';
+    		_IFrame.style.display  = '' ;
+		}
+	}
+
+	win.open();
+	document.getElementById('select_QuickCatalogSearch').focus();
+}	
+
+
+/******************************************************************************\
+|** Function      :  DRAG *****************************************************|
+|** Original File : phpgwapi/js/wz_dragdrop/wz_dragdrop.js ********************|
+\******************************************************************************/
+var over_fn_DRAG = DRAG;
+DRAG = function (event){
+
+		if(is_ie)
+			return false;
+		else
+			over_fn_DRAG(event);
+}		
Index: /contrib/ProjectManager/templates/default/vcal_import.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/vcal_import.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/vcal_import.tpl	(revision 3594)
@@ -0,0 +1,10 @@
+<!-- vcardin form -->
+{vcal_header}
+    <form ENCTYPE="multipart/form-data" method="POST" action="{action_url}">
+      <table border=0>
+      <tr>
+       <td>{ical_lang}: <input type="file" name="uploadedfile"></td>
+       <td><input type="submit" name="action" value="{load_vcal}"></td>
+      </tr>
+      </table>
+     </form>
Index: /contrib/ProjectManager/templates/default/month_day.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/month_day.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/month_day.tpl	(revision 3594)
@@ -0,0 +1,21 @@
+
+<!-- BEGIN m_w_table -->
+
+<table id="calendar_m_w_table" class="calendar_m_w_table" border="0" width="100%">
+	<! from month_header.tpl -->
+	{row}
+</table>
+<!-- END m_w_table -->
+
+<!-- BEGIN month_daily -->
+<span class="screen_only">[ </span><span class="calendar_m_w_table_daynumber" >{day_number}</span><span class="screen_only"> ]</span>{new_event_link}<br />
+    {daily_events}
+<!-- END month_daily -->
+
+<!-- BEGIN day_event -->
+{events}
+<!-- END day_event -->
+
+<!-- BEGIN event -->
+{day_events}
+<!-- END event -->
Index: /contrib/ProjectManager/templates/default/pricelist.edit.xet
===================================================================
--- /contrib/ProjectManager/templates/default/pricelist.edit.xet	(revision 3594)
+++ /contrib/ProjectManager/templates/default/pricelist.edit.xet	(revision 3594)
@@ -0,0 +1,135 @@
+<?xml version="1.0"?>
+<!-- $Id: pricelist.edit.xet 20226 2006-01-11 05:15:43Z ralfbecker $ -->
+<overlay>
+	<template id="projectmanager.pricelist.edit.price" template="" lang="" group="0" version="0.5.001">
+		<grid width="100%" height="200" overflow="auto">
+			<columns>
+				<column width="95"/>
+				<column/>
+				<column/>
+				<column/>
+				<column disabled="@view_prices"/>
+			</columns>
+			<rows>
+				<row class="th">
+					<description value="Price"/>
+					<description value="Valid since"/>
+					<description value="Last modified"/>
+					<description value="Modified by"/>
+					<description value="Delete"/>
+				</row>
+				<row class="row">
+					<textbox type="float" id="prices[$row][pl_price]"/>
+					<date id="prices[$row][pl_validsince]" statustext="you can leave the date of the first price empty"/>
+					<date-time id="prices[$row][pl_modified]" readonly="true"/>
+					<menulist>
+						<menupopup type="select-account" id="prices[$row][pl_modifier]" readonly="true"/>
+					</menulist>
+					<checkbox id="delete_price[$row]" statustext="Delete this price"/>
+				</row>
+			</rows>
+		</grid>
+	</template>
+	<template id="projectmanager.pricelist.edit.project" template="" lang="" group="0" version="0.5.001">
+		<grid width="100%" height="200" overflow="auto">
+			<columns>
+				<column width="95"/>
+				<column/>
+				<column/>
+				<column/>
+				<column disabled="@view_project_prices"/>
+			</columns>
+			<rows>
+				<row class="row">
+					<description options=",,,pd_customertitle" value="Customer title"/>
+					<textbox size="60" maxlength="255" span="all" id="pl_customertitle" statustext="leave it empty if you dont need a different customer title"/>
+				</row>
+				<row class="row">
+					<description options=",,,pl_billable" value="Availible"/>
+					<menulist span="all">
+						<menupopup id="pl_billable" options="No"/>
+					</menulist>
+				</row>
+				<row class="th">
+					<description value="Price"/>
+					<description value="Valid since"/>
+					<description value="Last modified"/>
+					<description value="Modified by"/>
+					<description value="Delete"/>
+				</row>
+				<row class="row">
+					<textbox type="float" id="project_prices[$row][pl_price]"/>
+					<date id="project_prices[$row][pl_validsince]" statustext="you can leave the date of the first price empty"/>
+					<date-time id="project_prices[$row][pl_modified]" readonly="true"/>
+					<menulist>
+						<menupopup type="select-account" id="project_prices[$row][pl_modifier]" readonly="true"/>
+					</menulist>
+					<checkbox id="delete_project_price[$row]" statustext="Delete this price"/>
+				</row>
+			</rows>
+		</grid>
+	</template>
+	<template id="projectmanager.pricelist.edit.description" template="" lang="" group="0" version="0.5.001">
+		<grid width="100%" height="200">
+			<columns>
+				<column/>
+			</columns>
+			<rows>
+				<row class="row">
+					<textbox multiline="true" data="" rows="12" cols="64" id="pl_description"/>
+				</row>
+			</rows>
+		</grid>
+	</template>
+	<template id="projectmanager.pricelist.edit" template="" lang="" group="0" version="0.5.001">
+		<grid width="100%">
+			<columns>
+				<column width="100"/>
+				<column/>
+			</columns>
+			<rows>
+				<row disabled="!@msg">
+					<description span="all" class="redItalic" no_lang="1" id="msg" align="center"/>
+					<description/>
+				</row>
+				<row class="th" height="28">
+					<description options=",,,pl_title" value="Title"/>
+					<textbox size="60" maxlength="255" id="pl_title" needed="1"/>
+				</row>
+				<row class="row">
+					<description options=",,,pl_unit" value="Unit"/>
+					<textbox size="20" maxlength="20" id="pl_unit" blur="h" span="all"/>
+				</row>
+				<row class="row">
+					<description options=",,,cat_id" value="Category"/>
+					<menulist>
+						<menupopup type="select-cat" options="None" id="cat_id"/>
+					</menulist>
+				</row>
+				<row>
+					<tabbox span="all">
+						<tabs>
+							<tab label="Price" statustext="General pricelist"/>
+							<tab label="Project" statustext="Project specific title, prices, ..."/>
+							<tab label="Description" statustext="Description"/>
+						</tabs>
+						<tabpanels>
+							<template id="projectmanager.pricelist.edit.price"/>
+							<template id="projectmanager.pricelist.edit.project"/>
+							<template id="projectmanager.pricelist.edit.description"/>
+						</tabpanels>
+					</tabbox>
+				</row>
+				<row>
+					<hbox span="all">
+						<button label="Edit" id="button[edit]" statustext="Edit this price"/>
+						<button id="button[save]" label="Save" statustext="Save the changes made"/>
+						<button id="button[apply]" label="Apply" statustext="Apply the changes"/>
+						<button id="button[cancel]" label="Cancel" statustext="Closes the window without saving the changes" onclick="window.close();"/>
+						<html id="js"/>
+					</hbox>
+				</row>
+			</rows>
+		</grid>
+	</template>
+</overlay>
Index: /contrib/ProjectManager/templates/default/preference_acl_row.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/preference_acl_row.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/preference_acl_row.tpl	(revision 3594)
@@ -0,0 +1,11 @@
+<!-- $Id: preference_acl_row.tpl 20466 2006-03-08 10:41:55Z ralfbecker $ -->
+   <tr class="{row_class}">
+    <td>{user}</td>
+    <td align="center"><input type="checkbox" name="{read}" value="Y"{read_selected}></td>
+    <td align="center"><input type="checkbox" name="{edit}" value="Y"{edit_selected}></td>
+    <td align="center"><input type="checkbox" name="{delete}" value="Y"{delete_selected}></td>
+    <td align="center"><input type="checkbox" name="{private}" value="Y"{private_selected}></td>
+    <td align="center"><input type="checkbox" name="{add}" value="Y"{add_selected}></td>
+    <td align="center"><input type="checkbox" name="{custom_1}" value="Y"{custom_1_selected}></td>
+    <td align="center"><input type="checkbox" name="{custom_2}" value="Y"{custom_2_selected}></td>
+  </tr>
Index: /contrib/ProjectManager/templates/default/edit.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/edit.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/edit.tpl	(revision 3594)
@@ -0,0 +1,96 @@
+<!-- BEGIN edit_entry -->
+<style type="text/css">
+div#tipDiv {
+  position:absolute; visibility:hidden; left:0; top:0; z-index:10000;
+  background-color:#EFEFEF; border:1px solid #337;
+  width:220px; padding:3px;
+  color:#000; font-size:11px; line-height:1.2;
+  cursor: default;
+}
+</style>
+
+
+
+<style type="text/css">
+<!--
+.hideFrom input { display: none; }
+.link_select select { width: 250px; }
+-->
+</style>
+
+
+
+
+<div class="sideboxSpace"></div>
+
+</td> <td> 
+<font color="#000000" face="{font}">
+
+<form action="{action_url}" method="post" name="app_form">
+{common_hidden}
+<table id="editFormTable" border="0" width="90%"   class="prefTable">
+ <tr>
+  <td colspan="2">
+   <center><font size="+1"><b id="formStatus">{errormsg}</b></font></center>
+  </td>
+ </tr>
+{row}
+
+ <tr>
+  <td>
+  <table><tr valign="top">
+  <td>
+  <div style="padding-top:15px; padding-right: 2px">
+  	<script language="JavaScript">
+		var alert_field = '{alert_msg}';
+	</script>
+  	<center><input id="submit_button" style="font-size:10px" type="submit" value="{submit_button}" onClick="return submitValues(alert_field);"></center></div></form>
+  </td>
+  <td>{cancel_button}</td>
+  </tr></table>
+  </td>
+  <td align="right">{delete_button}</td>
+ </tr>
+</table>
+</font>
+</center>
+
+<table width="100%">
+	<tbody>
+
+<!-- END edit_entry -->
+<!-- BEGIN list -->
+ <tr bgcolor="{tr_color}">
+  <td valign="top" width="25%">&nbsp;<b>{field}:</b></td>
+  <td valign="top" width="75%">{data}</td>
+ </tr>
+
+<!-- END list -->
+
+<tr>
+<td>
+
+</td>
+</tr>
+<!-- BEGIN hr -->
+ <tr bgcolor="{tr_color}">
+  <td colspan="2">
+  {hr_text}
+  </td>
+ </tr>
+<!-- END hr -->
+</tbody>
+</table>
+
+
+
+
+
+
+
+
+
+</td>
+	</tr>
+</table>
+
Index: /contrib/ProjectManager/templates/default/matrix_query.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/matrix_query.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/matrix_query.tpl	(revision 3594)
@@ -0,0 +1,25 @@
+<!-- BEGIN matrix_query -->
+<center>
+<form action="{action_url}" method="post" name="matrixform">
+<table border="0" width="90%">
+ <tr bgcolor="{th_bg}">
+  <td colspan="2" align="center"><b>{title}</b></td>
+ </tr>
+ {rows}
+ <tr>
+  <td>
+   <table cellspacing="5"><tr>
+    <td><input type="submit" value="{submit_button}"></form></td>
+    <td>{cancel_button}</td>
+   </tr></table>
+  </td>
+ </tr>
+</table>
+</center>
+<!-- END matrix_query -->
+<!-- BEGIN list -->
+ <tr bgcolor="{tr_color}">
+  <td valign="top" width="35%"><b>&nbsp;{field}:</b></td>
+  <td valign="top" width="65%">{data}</td>
+ </tr>
+<!-- END list -->
Index: /contrib/ProjectManager/templates/default/week.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/week.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/week.tpl	(revision 3594)
@@ -0,0 +1,3 @@
+{week_display}
+<!-- from month_day.tpl -->
+<p align="center">{printer_friendly}</p>
Index: /contrib/ProjectManager/templates/default/day_cal.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/day_cal.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/day_cal.tpl	(revision 3594)
@@ -0,0 +1,24 @@
+<!-- BEGIN day -->
+<div class="th">
+ <table class="calendar_dayview_maintable" width="100%" cellpadding="0">
+{row}
+</table>
+ </div>
+<!-- END day -->
+<!-- BEGIN day_row -->
+    <tr>{time}{event}
+    </tr>
+<!-- END day_row -->
+<!-- BEGIN day_event_on -->
+     <td class="event-on"{extras}>&nbsp;{event}</td>
+<!-- END day_event_on -->
+<!-- BEGIN day_event_off -->
+     <td class="event-off"{extras}>&nbsp;{event}</td>
+<!-- END day_event_off -->
+<!-- BEGIN day_event_holiday -->
+     <td class="event-holiday"{extras}>&nbsp;{event}</td>
+<!-- END day_event_holiday -->
+<!-- BEGIN day_time -->
+     <td class="time" nowrap>{open_link}{time}{close_link}</td>
+<!-- END day_time -->
+
Index: /contrib/ProjectManager/templates/default/preference_acl.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/preference_acl.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/preference_acl.tpl	(revision 3594)
@@ -0,0 +1,27 @@
+{errors}
+{title}
+<table border="0" align="center" width="70%">
+ <tr>
+  {nml}
+  <td width="40%">
+   <div align="center">
+    <form method="POST" action="{action_url}">
+{common_hidden_vars}
+     <input type="text" name="query" value="{search_value}">
+     <input type="submit" name="search" value="{search}">
+    </form>
+   </div>
+  </td>
+  {nmr}
+ </tr>
+</table>
+<form method="POST" action="{action_url}">
+ <table border="0" align="center" width="50%" cols="4">
+{row}
+ </table>
+{common_hidden_vars_form}
+ <input type="hidden" name="processed" value="{processed}">
+ <center><input type="submit" name="submit" value="{submit_lang}"></center>
+</form>
+
+
Index: /contrib/ProjectManager/templates/default/preference_colspan.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/preference_colspan.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/preference_colspan.tpl	(revision 3594)
@@ -0,0 +1,10 @@
+  <tr class="th">
+    <td>{string}</td>
+    <td align="center" width="10%">{lang_read}</td>
+    <td align="center" width="10%">{lang_edit}</td>
+    <td align="center" width="10%">{lang_delete}</td>
+    <td align="center" width="10%">{lang_private}</td>
+    <td align="center" width="10%">{lang_add_element}</td>
+    <td align="center" width="10%">{lang_budget}</td>
+    <td align="center" width="10%">{lang_edit_budget}</td>
+ </tr>
Index: /contrib/ProjectManager/templates/default/folderChooser.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/folderChooser.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/folderChooser.tpl	(revision 3594)
@@ -0,0 +1,11 @@
+<!-- BEGIN main -->
+<script language="JavaScript">
+
+var targetName = opener.document.{formName}.targetname;
+var targetID   = opener.document.{formName}.targetid;
+
+</script>
+
+<div id="mydmstreebox" style="top:0px; left:0px; width:200px; height:400px; border:dotted 0px silver; text-align:left;"></div>
+{folderTree}
+<!-- END main -->
Index: /contrib/ProjectManager/templates/default/event_link.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/event_link.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/event_link.tpl	(revision 3594)
@@ -0,0 +1,41 @@
+
+<!-- BEGIN link_event_pict -->
+{picture}
+<!-- END link_event_pict -->
+
+<!-- BEGIN link_event_open -->
+<table>
+
+<!--<div id="calendar_event_entry" style="overflow:hidden;">-->
+<!--<a class="event_entry" href="{link_link}" onMouseOver="window.status='{lang_view}'; return true;" onMouseOut="window.status=''; return true;" title="{desc} {location}"><br />--><br />
+<!-- END link_event_open -->
+
+<!-- BEGIN event_pict -->
+<!--<img src="{pic_image}" width="{width}" height="{height}" title="{title}" border="0" />-->
+<!-- END event_pict -->
+
+<!-- BEGIN link_event_text_old -->
+<!--<nobr>&nbsp;{time}&nbsp;</nobr> {title}&nbsp;{users_status}: <i>{desc}</i>({location}) -->
+<!-- END link_event_text_old -->
+
+<!-- BEGIN link_event_text -->
+	<tr style="font-size: 10px;background: #dddddd;">
+		<td valign="top" style="color: black; font-size: 11px;">
+			<span>
+				{time}
+			</span>
+		</td>
+		<td style="font-size: 11px; border: #E8F0F0 1px solid;" colspan="3">
+			<span><b>{title}</b></span>
+			<br />
+			<span><i>{desc}</i></span>
+			<span>{location}</span>
+			<br />
+		</td>
+	</tr>
+<!-- END link_event_text -->
+
+<!-- BEGIN link_event_close -->
+<!--</a></div><br />-->
+</table>
+<!-- END link_event_close -->
Index: /contrib/ProjectManager/templates/default/ganttchart.xet
===================================================================
--- /contrib/ProjectManager/templates/default/ganttchart.xet	(revision 3594)
+++ /contrib/ProjectManager/templates/default/ganttchart.xet	(revision 3594)
@@ -0,0 +1,73 @@
+<?xml version="1.0"?>
+<!-- $Id: ganttchart.xet 20770 2006-03-21 17:10:00Z ralfbecker $ -->
+<overlay>
+	<template id="projectmanager.ganttchart" template="" lang="" group="0" version="0.5.002">
+		<grid>
+			<columns>
+				<column/>
+			</columns>
+			<rows>
+				<row disabled="!@msg">
+					<html span="all" class="redItalic" id="msg" align="center"/>
+				</row>
+				<row>
+					<groupbox data="" rows="2" cols="5">
+						<caption label="Ganttchart parameter"/>
+						<grid>
+							<columns>
+								<column/>
+								<column/>
+								<column/>
+								<column/>
+								<column/>
+								<column/>
+								<column/>
+								<column/>
+								<column/>
+							</columns>
+							<rows>
+								<row>
+									<description options=",,,start" value="Startdate"/>
+									<date id="start" onchange="1"/>
+									<description/>
+									<description options=",,,depth" value="Depth of display"/>
+									<menulist>
+										<menupopup no_lang="1" id="depth" onchange="1"/>
+									</menulist>
+									<checkbox label="Planned times" id="planned_times" onchange="1" statustext="Show planned times instead of the real times"/>
+									<description/>
+									<description value="Resources"/>
+									<button label="Update" id="update"/>
+								</row>
+								<row>
+									<description options=",,,end" value="Enddate"/>
+									<date id="end" onchange="1"/>
+									<description/>
+									<description options=",,,filter" value="Status filter"/>
+									<menulist>
+										<menupopup no_lang="1" id="filter" onchange="1"/>
+									</menulist>
+									<checkbox label="Constraints" id="constraints" onchange="1" statustext="Show constraints and milestones"/>
+									<description/>
+									<menulist>
+										<menupopup type="select-account" onchange="1" options="All" id="pe_resources"/>
+									</menulist>
+									<button label="Synchronise all" id="sync_all" statustext="necessary for project-elements doing that not automatic"/>
+								</row>
+							</rows>
+						</grid>
+					</groupbox>
+				</row>
+				<row>
+					<html id="map"/>
+				</row>
+				<row>
+					<image src="ganttchart" options=",,ganttchart"/>
+				</row>
+				<row>
+					<button id="milestone" label="Add milestone" statustext="Add a new milestone" onclick="window.open(egw::link('/index.php','menuaction=projectmanager.uimilestones.edit&amp;pm_id=$cont[pm_id]&amp;ms_id=0'),'milestone','dependent=yes,width=680,height=450,scrollbars=yes,status=yes'); return false;"/>
+				</row>
+			</rows>
+		</grid>
+	</template>
+</overlay>
Index: /contrib/ProjectManager/templates/default/year.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/year.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/year.tpl	(revision 3594)
@@ -0,0 +1,35 @@
+<!-- BEGIN year -->
+{print}
+<center>
+<table id="calendar_year_table" border="0" cellspacing="3" cellpadding="4" cols=4>
+	<tr>
+		<td align="center">
+			{left_link}
+		</td>
+		<td align="center">
+			<font face="{font}" size="+1">{year_text}</font>
+		</td>
+		<td align="center">
+			{right_link}
+		</td>
+		{row}
+	</tr>
+</table>
+</center>
+<p>
+<div class="calendar_link_print">
+{printer_friendly}
+</div>
+<!-- END year -->
+
+<!-- BEGIN month -->
+<td valign="top">
+	{mini_month}
+</td>
+<!-- END month -->
+
+<!-- BEGIN month_sep -->
+</tr>
+<tr valign="top">
+<!-- END month_sep -->
+
Index: /contrib/ProjectManager/templates/default/head.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/head.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/head.tpl	(revision 3594)
@@ -0,0 +1,52 @@
+<!-- BEGIN head -->
+<script language="Javascript">
+function openwindow(url)
+{ 
+	
+	var window_features = 	"scrollbars=yes,resizable=yes,location=no,menubar=no," + 
+							"personalbar=no,status=no,titlebar=no,toolbar=no," + 
+							"screenX=screen.width/3,screenY=screen.height/3,top=screen.height/3,left=screen.width/3,width=" + 
+							screen.width/4*3 + ",height=" + screen.height/8*3; 
+	
+	self.open(url,'', window_features); 
+}
+</script>
+{row}<br />
+<!-- END head -->
+<!-- BEGIN head_table -->
+<table id="calendar_head_table" class="calendar_head_table" border="0" width="100%" cols="{cols}" cellpadding="6" cellspacing="0">
+	<tr>
+		<td rowspan=10>
+	
+	<div id="divSubContainer">
+		<table cellpadding="0" cellspacing="0" width="100%">
+		<tbody><tr>
+		<!-- Sidebox Column -->
+<td id="tdSidebox" valign="top"><div id="thesideboxcolumn" style="width: 203px;"><div id="sideresize"></div>
+<div class="divSidebox">
+	<div class="divSideboxHeader"><span>Registro de notas y tareas Men&uacute;</span></div>
+
+	<div>
+		<table cellpadding="0" cellspacing="0" width="100%">
+		<tbody><tr class="divSideboxEntry">
+			<td class="textSidebox" align="center" valign="middle" width="20"><img class="sideboxstar" src="infolog_archivos/orange-ball.png" alt="ball" height="9" width="9"></td><td class="textSidebox"><a class="textSidebox" href="index.php?menuaction=infolog.uiinfolog.search">Lista del registro</a></td>
+		</tr>
+		<tr class="divSideboxEntry">
+			<td class="textSidebox" align="center" valign="middle" width="20"><img class="sideboxstar" src="infolog_archivos/orange-ball.png" alt="ball" height="9" width="9"></td><td class="textSidebox"><a class="textSidebox" href=""></a><a class="textSidebox" href="index.php?menuaction=infolog.uiinfolog.edit"  >A&ntilde;adir</a></td>
+		</tr>
+
+		</tbody></table>	
+	</div>
+</div>
+
+<div class="sideboxSpace"></div>
+
+</td> <td> 
+
+
+
+
+<!-- END head_table -->
+<!-- BEGIN head_col -->
+
+<!-- END head_col -->
Index: /contrib/ProjectManager/templates/default/month_print.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/month_print.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/month_print.tpl	(revision 3594)
@@ -0,0 +1,26 @@
+{printer_friendly}
+<table class="calendar_index_table" id="calendar_index_table"  width="100%" cols="5">
+	<tr>
+		<td align="left" width="33%">
+			<span class="calendar_user_identifier">
+					{username}
+			</span>
+		</td>
+		<td align="center" width="33%">
+			<span class="calendar_month_identifier">
+				{month_identifier}
+			</span>
+		</td>
+		<td align="right" style="width: 33%; font-size: 14px;">
+			<span class="calendar_user_identifier">
+				<p>Agenda de Eventos</p>
+			</span>
+		</td>
+	</tr>
+</table>
+{large_month}
+<p>
+<p>
+<div class="calendar_link_print">
+	{print}
+</div>
Index: /contrib/ProjectManager/templates/default/month_column.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/month_column.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/month_column.tpl	(revision 3594)
@@ -0,0 +1,4 @@
+
+<td valign="top" colspan="1" width="{col_width}%"{extra}>
+	{column_data}
+</td>
Index: /contrib/ProjectManager/templates/default/body_email.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/body_email.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/body_email.tpl	(revision 3594)
@@ -0,0 +1,32 @@
+<html>
+<head>
+<meta http-equiv="Content-Language" content="en" />
+<meta name="GENERATOR" content="PHPEclipse 1.0" />
+<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
+<title>title</title>
+</head>
+<body bgcolor="#FFFFFF" text="#000000" link="#FF9966" vlink="#FF9966" alink="#FFCC99">
+<!-- BEGIN list -->
+<b><p align='left'>{subject}</p></b>
+<br>
+<table border='0' cellpadding='0' cellspacing='0' width='100%'>
+<tr><td width='30%' valign='top'><b>{var1} ::</b></td><td>{vardata1}</td></tr>	
+<tr><td width='30%' valign='top'><b>{var2} ::</b></td><td>{vardata2}</td></tr>
+<tr><td width='30%' valign='top'><b>{var3} ::</b></td><td>{vardata3}</td></tr>
+<tr><td width='30%' valign='top'><b>{var4} ::</b></td><td>{vardata4}</td></tr>
+<tr><td width='25%' valign='top'><b>{var8} ::</b></td><td>{vardata8}</td></tr>
+<tr><td width='25%' valign='top'><b>{var5} ::</b></td><td>{vardata6}</td></tr>
+</table>
+<br>
+<hr size='1' width='100%'>
+<table border='0' cellpadding='4' cellspacing='4' width='100%'>
+<tr>
+<td width='2%' align='left'>{varbuttom}</td>
+<td width='2%' align='left' valign='top'>{varbuttom1}</td>
+<td width='2%' align='left' valign='top'>{varbuttom2}</td>
+<td width='94%' align='left' valign='top'>{varbuttom3}</td>
+</tr>
+</table>
+<!-- END list -->
+</body>
+</html>
Index: /contrib/ProjectManager/templates/default/config.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/config.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/config.tpl	(revision 3594)
@@ -0,0 +1,107 @@
+<!-- BEGIN header -->
+<script type="text/javascript">
+
+function disableMinNum(valor){
+  if( valor == "True" ){
+	document.getElementById("minNum").disabled = true;
+	document.getElementById("minNum").value = 0;
+  }
+  else if (valor == "False"){
+	document.getElementById("minNum").disabled = false;
+  }
+}
+</script>
+<form name="adminForm" method="POST" action="{action_url}" onSubmit="return minValue()">
+<table border="0" align="center">
+   <tr bgcolor="{th_bg}">
+    <td colspan="2"><font color="{th_text}">&nbsp;<b>{title}</b></font></td>
+   </tr>
+<!-- END header -->
+
+<!-- BEGIN body -->
+   <tr bgcolor="{row_on}">
+    <td colspan="2">&nbsp;</td>
+   </tr>
+   <tr bgcolor="{row_off}">
+    <td colspan="2"><b>{lang_Calendar_settings}</b></td>
+   </tr>
+   <tr bgcolor="{row_on}">
+    <td>{lang_Do_you_wish_to_autoload_calendar_holidays_files_dynamically?}</td>
+    <td>
+     <select name="newsettings[auto_load_holidays]">
+      <option value=""{selected_auto_load_holidays_False}>{lang_No}</option>
+      <option value="True"{selected_auto_load_holidays_True}>{lang_Yes}</option>
+     </select>
+    </td>
+   </tr>
+   <tr bgcolor="{row_off}">
+    <td>{lang_Location_to_autoload_from}:</td>
+    <td>
+     <select name="newsettings[holidays_url_path]">
+      <option value="localhost"{selected_holidays_url_path_localhost}>localhost</option>
+      <option value="http://www.phpgroupware.org/cal"{selected_holidays_url_path_http://www.phpgroupware.org/cal}>www.phpgroupware.org</option>
+     </select>
+    </td>
+   <tr bgcolor="{row_on}">
+    <td colspan="2">&nbsp;</td>
+   </tr>    
+   </tr>
+    <tr bgcolor="{row_off}">
+    <td colspan="2"><b>{lang_ExpressoMail_Plugin_Calendar}</b></td>
+   </tr>
+   <tr bgcolor="{row_on}">
+    <td>{lang_Select_module_version_expressoMail_(if_available)}</td>
+    <td>
+     <select name="newsettings[cal_expressoMail]">
+      <option value="False"{selected_cal_expressoMail_False}>{lang_dont_use_any_plugin}</option>
+      <option value="1.0"{selected_cal_expressoMail_1.0}>Expresso 1.0</option>
+      <option value="1.2"{selected_cal_expressoMail_1.2}>Expresso 1.2</option>
+     </select>
+    </td>
+   </tr>
+    <tr bgcolor="{row_off}">
+    <td>{lang_Select_type_tree_view}</td>
+    <td>
+     <select name="newsettings[cal_type_tree_participants]">
+      <option value="3" {selected_cal_type_tree_participants_3}>{lang_all_levels}</option>
+      <option value="2" {selected_cal_type_tree_participants_2}>{lang_first_level_with_recursive_search}</option>
+      <option value="1" {selected_cal_type_tree_participants_1}>{lang_all_levels_with_not_recursive_search}</option>      
+     </select>
+    </td>
+   </tr>
+   
+   <tr bgcolor="{row_on}">
+     <td>{lang_Do_you_wish_enable_autosearch?}</td>
+     <td>
+       <select id="autoSearch" name="newsettings[auto_search]" onchange="disableMinNum(this.value)">
+        <option value="False"{selected_auto_search_False}>{lang_No}</option>
+        <option value="True"{selected_auto_search_True}>{lang_Yes}</option>
+       </select>
+     </td>
+   </tr>
+   
+   <tr bgcolor="{row_off}">
+       <td>{lang_Minimum_number_of_characters_to_start_the_search_for_participants}</td>
+       <td>
+          <input type="text" id="minNum" value="{value_min_num_characters}" name="newsettings[min_num_characters]    " size=2 maxlength=2 />
+       </td>
+   </tr>
+
+   <script> disableMinNum(document.getElementById("autoSearch").value); </script>
+<!-- END body -->
+
+<!-- BEGIN footer -->
+  <tr bgcolor="{th_bg}">
+    <td colspan="2">
+&nbsp;
+    </td>
+  </tr>
+  <tr>
+    <td colspan="2" align="center">
+      <input type="submit" name="submit" value="{lang_submit}">
+      <input type="submit" name="cancel" value="{lang_cancel}">
+     </td>
+  </tr>
+</table>
+</form>
+<!-- END footer -->
Index: /contrib/ProjectManager/templates/default/index.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/index.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/index.tpl	(revision 3594)
@@ -0,0 +1,32 @@
+{printer_friendly}
+<table id="calendar_index_table" border="0" width="100%" cols="5">
+	<tr>
+		<td align="left" valign="top" width="20%">
+			{small_calendar_prev}
+		</td>
+		<td align="center" valign="middle" width="15%">
+			<b>{prev_month_link}</b>
+		</td>
+		<td align="center" width="30%">
+			<span class="calendar_month_identifier">
+				{month_identifier}
+			</span>
+				<br />
+			<span class="calendar_user_identifier">
+				:&nbsp;{username}&nbsp;:
+			</span>
+		</td>
+		<td align="center" valign="middle" width="15%">
+			{next_month_link}
+		</td>
+		<td align="right" valign="top" width="20%">
+			{small_calendar_next}
+		</td>
+	</tr>
+</table>
+{large_month}
+<p>
+<p>
+<div class="calendar_link_print">
+	{print}
+</div>
Index: /contrib/ProjectManager/templates/default/month_header_print.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/month_header_print.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/month_header_print.tpl	(revision 3594)
@@ -0,0 +1,12 @@
+<!-- BEGIN monthly_header -->
+<tr class="calendar_m_w_table_header" colspan="{cols}" width="{col_width}%">
+{column_header}</tr>
+<!-- END monthly_header -->
+
+<!-- BEGIN column_title -->
+<tr bGCOLOR="#D3DCE3" width="14.28%">{col_title}</tr>
+<!-- END column_title -->
+
+<!-- BEGIN month_column -->
+<tr class="calendar_m_w_tablecell" colspan="1" width="{col_width}%"{extra}>{column_data}</tr>
+<!-- END month_column -->
Index: /contrib/ProjectManager/templates/default/form_button_dropdown.tpl
===================================================================
--- /contrib/ProjectManager/templates/default/form_button_dropdown.tpl	(revision 3594)
+++ /contrib/ProjectManager/templates/default/form_button_dropdown.tpl	(revision 3594)
@@ -0,0 +1,13 @@
+<form action="{form_link}" method="post" name="{form_name}form">
+<td width="{form_width}%" align="center" valign="top" style="padding-top:16px">
+	<span style="font-size:10px"><b>{title}:</b>
+    {hidden_vars}    
+    <select name="{form_name}" onchange="document.{form_name}form.submit()">
+		{form_options}
+	</select>
+    <noscript>
+    	<input type="submit" value="{button_value}">
+    </noscript>
+	</span>
+</td>
+</form>
Index: /contrib/ProjectManager/templates/default/list.xet
===================================================================
--- /contrib/ProjectManager/templates/default/list.xet	(revision 3594)
+++ /contrib/ProjectManager/templates/default/list.xet	(revision 3594)
@@ -0,0 +1,137 @@
+<?xml version="1.0"?>
+<!-- $Id: list.xet 23488 2007-03-07 12:31:48Z ralfbecker $ -->
+<overlay>
+	<template id="projectmanager.list.rows" template="" lang="" group="0" version="1.3.001">
+		<grid width="100%">
+			<columns>
+				<column/>
+				<column/>
+				<column/>
+				<column/>
+				<column disabled="@no_role0"/>
+				<column disabled="@no_role1"/>
+				<column disabled="@no_role2"/>
+				<column disabled="@no_role3"/>
+				<column disabled="@no_role4"/>
+				<column/>
+				<column/>
+				<column disabled="@no_pm_used_budget_pm_planned_budget"/>
+				<column disabled="@no_pm_used_time_pm_planned_time"/>
+				<column/>
+				<column/>
+				<column/>
+			</columns>
+			<rows>
+				<row class="th">
+					<nextmatch-sortheader label="Project ID" id="pm_number"/>
+					<nextmatch-sortheader label="Title" id="pm_title"/>
+					<nextmatch-sortheader label="Priority" align="center" id="pm_priority"/>
+					<nextmatch-sortheader label="Owner" id="pm_creator"/>
+					<nextmatch-header label="@roles[0]" id="role0"/>
+					<nextmatch-header label="@roles[1]" id="role1"/>
+					<nextmatch-header label="@roles[2]" id="role2"/>
+					<nextmatch-header label="@roles[3]" id="role3"/>
+					<nextmatch-header label="@roles[4]" id="role4"/>
+					<vbox options="0,0">
+						<nextmatch-sortheader label="Startdate" id="pm_real_start"/>
+						<nextmatch-sortheader label="planned" id="pm_planned_start" class="planned"/>
+					</vbox>
+					<vbox options="0,0">
+						<nextmatch-sortheader label="Enddate" id="pm_real_end"/>
+						<nextmatch-sortheader label="planned" id="pm_planned_end" class="planned"/>
+					</vbox>
+					<vbox options="0,0">
+						<nextmatch-sortheader label="Budget" id="pm_used_budget"/>
+						<nextmatch-sortheader class="planned" label="planned" id="pm_planned_budget"/>
+					</vbox>
+					<vbox options="0,0">
+						<nextmatch-sortheader label="Times" id="pm_used_time"/>
+						<nextmatch-sortheader class="planned" label="planned" id="pm_planned_time"/>
+					</vbox>
+					<nextmatch-sortheader label="Status" id="pm_completion" align="center"/>
+					<nextmatch-sortheader label="last modified" id="pm_modified"/>
+					<hbox align="center" class="noPrint">
+						<description value="Actions" align="center"/>
+						<button image="check" label="Check all" id="check_all" statustext="Check all" onclick="toggle_all(this.form,form::name('checked[]')); return false;" needed="1"/>
+					</hbox>
+				</row>
+				<row class="row">
+					<description id="${row}[pm_number]" no_lang="1"/>
+					<description id="${row}[pm_title]" no_lang="1" options=",projectmanager.uiprojectelements.index&amp;pm_id=$row_cont[pm_id]" statustext="Select this project and show it's elements"/>
+					<description no_lang="1" id="${row}[pm_priority]" align="center"/>
+					<menulist>
+						<menupopup type="select-account" id="${row}[pm_creator]" readonly="true"/>
+					</menulist>
+					<listbox type="select-account" id="${row}[role0]" readonly="true" rows="2"/>
+					<listbox type="select-account" id="${row}[role1]" readonly="true" rows="2"/>
+					<listbox type="select-account" id="${row}[role2]" readonly="true" rows="2"/>
+					<listbox type="select-account" id="${row}[role3]" readonly="true" rows="2"/>
+					<listbox type="select-account" id="${row}[role4]" readonly="true" rows="2"/>
+					<vbox options="0,0">
+						<date id="${row}[pm_real_start]" readonly="true"/>
+						<date id="${row}[pm_planned_start]" readonly="true" class="planned"/>
+					</vbox>
+					<vbox options="0,0">
+						<date id="${row}[pm_real_end]" readonly="true"/>
+						<date id="${row}[pm_planned_end]" readonly="true" class="planned"/>
+					</vbox>
+					<vbox readonly="true">
+						<textbox type="float" readonly="true" id="${row}[pm_used_budget]" max="2"/>
+						<textbox type="float" readonly="true" no_lang="1" id="${row}[pm_planned_budget]" max="2" class="planned"/>
+					</vbox>
+					<vbox readonly="true">
+						<date-duration readonly="true" id="${row}[pm_used_time]" options="@duration_format"/>
+						<date-duration readonly="true" id="${row}[pm_planned_time]" class="planned" options="@duration_format"/>
+					</vbox>
+					<image src="${row}[pm_completion]" label="$row_cont[pm_completion]" no_lang="1" align="center"/>
+					<vbox readonly="true" options="0,0">
+						<date-time id="${row}[pm_modified]" readonly="true"/>
+						<menulist>
+							<menupopup type="select-account" id="${row}[pm_modifier]" readonly="true"/>
+						</menulist>
+					</vbox>
+					<hbox align="center" class="noPrint">
+						<button image="ganttchart" label="Ganttchart" id="ganttchart[$row_cont[pm_id]]" statustext="View ganttchart of this project"/>
+						<button image="view" label="View" id="view[$row_cont[pm_id]]" statustext="View this project"/>
+						<button id="edit[$row_cont[pm_id]]" image="edit" label="Edit" statustext="Edit this project"/>
+						<button id="delete[$row_cont[pm_id]]" image="delete" label="Delete" statustext="Delete this project" onclick="return confirm('Delete this project');"/>
+						<checkbox id="checked[]" options="$row_cont[pm_id]" statustext="Select multiple projects for a further action"/>
+					</hbox>
+				</row>
+			</rows>
+		</grid>
+	</template>
+	<template id="projectmanager.list" template="" lang="" group="0" version="0.4.001">
+		<grid width="100%">
+			<columns>
+				<column/>
+			</columns>
+			<rows>
+				<row disabled="!@msg">
+					<description class="redItalic" id="msg" align="center" no_lang="1"/>
+				</row>
+				<row>
+					<nextmatch options="projectmanager.list.rows" id="nm"/>
+				</row>
+				<row class="noPrint">
+					<hbox>
+						<hbox>
+							<menulist>
+								<menupopup id="template" no_lang="1" statustext="Create a new empty project or use a template" options="Empty"/>
+							</menulist>
+							<button label="Add" id="add" statustext="Add a new project"/>
+						</hbox>
+						<hbox align="right">
+							<button id="gantt_checked" image="ganttchart" label="Ganttchart" statustext="Created a ganttchart from the checked projects"/>
+							<button id="delete_checked" image="delete" label="Delete" statustext="Delete all checked projects" onclick="return confirm('Delete all checked projects');"/>
+							<button image="arrow_ltr" label="Check all" id="check_all" statustext="Check all" onclick="toggle_all(this.form,form::name('nm[rows][checked][]')); return false;" needed="1" class="check_all_arrow"/>
+						</hbox>
+					</hbox>
+				</row>
+			</rows>
+		</grid>
+		<styles>
+			.check_all_arrow input { margin-right: 10px; }
+		</styles>
+	</template>
+</overlay>
