1<?php
2/**
3 * InfoLog - Datasource for ProjektManager
4 *
5 * @link http://www.egroupware.org
6 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
7 * @package infolog
8 * @subpackage projectmanager
9 * @copyright (c) 2005-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
10 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
11 * @version $Id$
12 */
13
14use EGroupware\Api;
15use EGroupware\Api\Link;
16use EGroupware\Api\Acl;
17
18include_once(EGW_INCLUDE_ROOT.'/projectmanager/inc/class.datasource.inc.php');
19
20/**
21 * DataSource for InfoLog
22 *
23 * The InfoLog datasource set's only real start- and endtimes, plus planned and used time and
24 * the responsible user as resources (not always the owner too!).
25 * The read method of the extended datasource class sets the planned start- and endtime:
26 *  - planned start from the end of a start constrain
27 *  - planned end from the planned time and a start-time
28 *  - planned start and end from the "real" values
29 */
30class infolog_datasource extends datasource
31{
32	/**
33	 * Reference to infolog_bo
34	 *
35	 * @var infolog_bo
36	 */
37	var $infolog_bo;
38
39	/**
40	 * Constructor
41	 */
42	function __construct()
43	{
44		parent::__construct('infolog');
45
46		$this->valid = PM_COMPLETION|PM_PLANNED_START|PM_PLANNED_END|PM_REAL_END|PM_PLANNED_TIME|PM_REPLANNED_TIME|PM_USED_TIME|PM_RESOURCES|PM_CAT_ID;
47
48		// we use $GLOBALS['infolog_bo'] as an already running instance might be availible there
49		if (!is_object($GLOBALS['infolog_bo']))
50		{
51			$GLOBALS['infolog_bo'] = new infolog_bo();
52		}
53		$this->infolog_bo =& $GLOBALS['infolog_bo'];
54	}
55
56	/**
57	 * get an entry from the underlaying app (if not given) and convert it into a datasource array
58	 *
59	 * @param mixed $data_id id as used in the link-class for that app, or complete entry as array
60	 * @return array/boolean array with the data supported by that source or false on error (eg. not found, not availible)
61	 */
62	function get($data_id)
63	{
64		if (!is_array($data_id))
65		{
66			$data =& $this->infolog_bo->read((int) $data_id);
67
68			if (!is_array($data)) return false;
69		}
70		else
71		{
72			$data =& $data_id;
73		}
74
75		return array(
76			'pe_title'        => $this->infolog_bo->link_title($data),
77			'pe_completion'   => $data['info_percent'],
78			'pe_planned_start'=> $data['info_startdate'] ? $data['info_startdate'] : null,
79			'pe_planned_end'  => $data['info_enddate'] ? $data['info_enddate'] : null,
80			'pe_real_end'     => $data['info_datecompleted'] ? $data['info_datecompleted'] : null,
81			'pe_planned_time' => $data['info_planned_time'],
82			'pe_replanned_time' => $data['info_replanned_time'],
83			'pe_used_time'    => $data['info_used_time'],
84			'pe_resources'    => count($data['info_responsible']) ? $data['info_responsible'] : array($data['info_owner']),
85			'pe_details'      => $data['info_des'] ? nl2br($data['info_des']) : '',
86			'pl_id'           => $data['pl_id'],
87			'pe_unitprice'    => $data['info_price'],
88			'pe_planned_quantity' => $data['info_planned_time'] / 60,
89			'pe_planned_budget'   => $data['info_planned_time'] / 60 * $data['info_price'],
90			'pe_used_quantity'    => $data['info_used_time'] / 60,
91			'pe_used_budget'      => $data['info_used_time'] / 60 * $data['info_price'],
92			'cat_id'              => $data['info_cat'],
93		);
94	}
95
96	/**
97	 * Copy the datasource of a projectelement (InfoLog entry) and re-link it with project $target
98	 *
99	 * @param array $element source project element representing an InfoLog entry, $element['pe_app_id'] = info_id
100	 * @param int $target target project id
101	 * @param array $extra =null data of target-project, atm not used by the infolog datasource
102	 * @param DateInterval[] $date_offsets = Array() - When copying, a list of date fields
103	 *	and the amount to offset them from the original while copying
104	 * @return array/boolean array(info_id,link_id) on success, false otherwise
105	 */
106	function copy($element,$target,$extra=null,$date_offsets = Array())
107	{
108		unset($extra);	// not used, but required by function signature
109
110		$info =& $this->infolog_bo->read((int) $element['pe_app_id']);
111
112		if (!is_array($info)) return false;
113
114		$info_contact = $info['info_contact'];
115		$info_from = $info['info_from'];
116
117		// unsetting info_link_id and evtl. info_from
118		if ($info['info_link_id'])
119		{
120			$this->infolog_bo->link_id2from($info);		// unsets info_from and sets info_link_target
121			unset($info['info_link_id']);
122			unset($info['info_contact']);
123		}
124
125		// we need to unset a few fields to get a new entry
126		foreach(array('info_id','info_owner','info_modified','info_modifierer') as $key)
127		{
128			unset($info[$key]);
129		}
130
131		// Apply date offsets, if any
132		$map = array(
133			'planned_start' => 'info_startdate',
134			'planned_end' => 'info_enddate',
135			'real_start' => 'info_startdate',
136			'real_end' => 'info_datecompleted'
137		);
138
139		$startdate_original = $info['info_startdate'];
140		foreach($map as $offset_field => $info_field)
141		{
142			if($date_offsets[$offset_field] && $info[$info_field])
143			{
144				// Don't move startdate twice, but prefer later value
145				if($startdate_original && $info_field == 'info_startdate')
146				{
147					$info[$info_field] = $startdate_original;
148				}
149				//error_log($offset_field . ' ' . Api\DateTime::to($info[$info_field]) . ' ' . $date_offsets[$offset_field]->format('%R%a days') . ' ' . date_add(new Api\DateTime($info[$info_field]), $date_offsets[$offset_field]) );
150
151				$info[$info_field] = date_add(new Api\DateTime($info[$info_field]), $date_offsets[$offset_field])->format('ts');
152			}
153		}
154		// Sanity check - not due or ended before it starts
155		if($info['info_startdate'] && $info['info_enddate'] && $info['info_startdate'] > $info['info_enddate'])
156		{
157			unset($info['info_enddate']);
158		}
159		if($info['info_startdate'] && $info['info_datecompleted'] && $info['info_startdate'] > $info['info_datecompleted'])
160		{
161			unset($info['info_datecompleted']);
162		}
163
164		// If info_from missing or matches project title, update it
165		if (!$info['info_from'] || $info['info_from'] == Link::title('projectmanager', $info['pm_id']))
166		{
167			$info['info_from'] = Link::title('projectmanager',$target);
168			unset($info['info_custom_from']);
169		}
170		if ($info['info_status'] == 'template')
171		{
172			$info['info_status'] = $this->infolog_bo->activate($info);
173		}
174
175		$info['pm_id'] = $target;
176		if(!($info['info_id'] = $this->infolog_bo->write($info))) return false;
177		$this->infolog_bo->link_id2from($info);
178
179		// creating again all links, beside the one to the source-project
180		foreach(Link::get_links('infolog',$element['pe_app_id']) as $link)
181		{
182			if ($link['app'] == 'projectmanager' && $link['id'] == $element['pm_id'] ||		// ignoring the source project
183				$link['app'] == Link::VFS_APPNAME)					// ignoring files attachments for now
184			{
185				continue;
186			}
187			Link::link('infolog',$info['info_id'],$link['app'],$link['id'],$link['remark']);
188		}
189		$this->infolog_bo->write($info);
190		$ret = array($info['info_id'],$info['info_link_id']);
191
192		// if we have a parent set, return our callback to modify the parent id,
193		// or we have a contact or custom info_from and need to re-set it after
194		// all entries are copied
195		if ($info['info_id_parent'] || $info_contact)
196		{
197			$ret[] = array($this,'copy_callback');	// callback
198			$ret[] = array($info['info_id'],$info['info_id_parent'], $info_contact, $info_from);	// $param
199		}
200		return $ret;
201	}
202
203	/**
204	 * Callback called after copying of all datasource, used to:
205	 * - fix parent id's
206	 * - reset contact if it was a link to another entry (not the project)
207	 * - fix info_from
208	 *
209	 * @param array $param array($info_id,$info_id_parent)
210	 * @param array $apps_copied array('infolog' => array($old_info_id => $new_info_id))
211	 */
212	public function copy_callback(array $param, array $apps_copied)
213	{
214		//error_log(__METHOD__."(".array2string($param).', '.array2string($apps_copied).')');
215		list($info_id,$parent_id, $contact, $from) = $param;
216		if ($parent_id && isset($apps_copied['infolog'][$parent_id]) && ($info = $this->infolog_bo->read($info_id)))
217		{
218			$info['info_id_parent'] = $apps_copied['infolog'][$parent_id];
219			$this->infolog_bo->write($info,false,true,true,true);	// no default and no notification
220		}
221		if($contact && $contact['app'] != 'projectmanager' && ($info = $this->infolog_bo->read($info_id)))
222		{
223			$info['info_contact'] = $contact;
224			$this->infolog_bo->write($info,false,true,true,true);	// no default and no notification
225		}
226		if($from && ($info = $this->infolog_bo->read($info_id)))
227		{
228			$info['info_from'] = $from;
229			$this->infolog_bo->write($info,false,true,true,true);	// no default and no notification
230		}
231	}
232
233	/**
234	 * Delete the datasource of a project element
235	 *
236	 * @param int $id
237	 * @return boolean true on success, false on error
238	 */
239	function delete($id)
240	{
241		if (!is_object($GLOBALS['infolog_bo']))
242		{
243			include_once(EGW_INCLUDE_ROOT.'/infolog/inc/class.infolog_bo.inc.php');
244			$GLOBALS['infolog_bo'] = new infolog_bo();
245		}
246		// dont delete infolog, which are linked to other elements, but their project
247		if (count(Link::get_links('infolog',$id)) > 1)
248		{
249			return false;
250		}
251		return $this->infolog_bo->delete($id);
252	}
253
254	/**
255	 * Change the status of an infolog entry according to the project status
256	 *
257	 * @param int $id
258	 * @param string $status
259	 * @return boolean true if status changed, false otherwise
260	 */
261	function change_status($id,$status)
262	{
263		//error_log("datasource_infolog::change_status($id,$status)");
264		if (($info = $this->infolog_bo->read($id)) && (
265				$this->infolog_bo->check_access($info,Acl::EDIT) ||
266				$info['info_status'] == 'deleted' && $this->infolog_bo->check_access($info, infolog_bo::ACL_UNDELETE)
267		))
268		{
269			if ($status == 'active' && in_array($info['info_status'],array('template','nonactive','archive','deleted')))
270			{
271				$status = $this->infolog_bo->activate($info);
272			}
273			if($info['info_status'] != $status && isset($this->infolog_bo->status[$info['info_type']][$status]))
274			{
275				//error_log("datasource_infolog::change_status($id,$status) setting status from ".$info['info_status']);
276				$info['info_status'] = $status;
277				return $this->infolog_bo->write($info) !== false;
278			}
279		}
280		return false;
281	}
282}