1<?php
2/* Copyright (C) 2008-2014	Laurent Destailleur	<eldy@users.sourceforge.net>
3 * Copyright (C) 2010-2012	Regis Houssin		<regis.houssin@inodbox.com>
4 * Copyright (C) 2014       Marcos García       <marcosgdf@gmail.com>
5 * Copyright (C) 2018       Frédéric France     <frederic.france@netlogic.fr>
6 * Copyright (C) 2020       Juanjo Menent		<jmenent@2byte.es>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program. If not, see <https://www.gnu.org/licenses/>.
20 */
21
22/**
23 *      \file       htdocs/projet/class/task.class.php
24 *      \ingroup    project
25 *      \brief      This file is a CRUD class file for Task (Create/Read/Update/Delete)
26 */
27
28require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
29require_once DOL_DOCUMENT_ROOT.'/projet/class/project.class.php';
30
31
32/**
33 * 	Class to manage tasks
34 */
35class Task extends CommonObject
36{
37	/**
38	 * @var string ID to identify managed object
39	 */
40	public $element = 'project_task';
41
42	/**
43	 * @var string Name of table without prefix where object is stored
44	 */
45	public $table_element = 'projet_task';
46
47	/**
48	 * @var string Field with ID of parent key if this field has a parent
49	 */
50	public $fk_element = 'fk_task';
51
52	/**
53	 * @var string String with name of icon for myobject.
54	 */
55	public $picto = 'projecttask';
56
57	/**
58	 * @var array	List of child tables. To test if we can delete object.
59	 */
60	protected $childtables = array('projet_task_time');
61
62	/**
63	 * @var int ID parent task
64	 */
65	public $fk_task_parent = 0;
66
67	/**
68	 * @var string Label of task
69	 */
70	public $label;
71
72	/**
73	 * @var string description
74	 */
75	public $description;
76
77	public $duration_effective; // total of time spent on this task
78	public $planned_workload;
79	public $date_c;
80	public $date_start;
81	public $date_end;
82	public $progress;
83
84	/**
85	 * @var int ID
86	 */
87	public $fk_statut;
88
89	public $priority;
90
91	/**
92	 * @var int ID
93	 */
94	public $fk_user_creat;
95
96	/**
97	 * @var int ID
98	 */
99	public $fk_user_valid;
100
101	public $rang;
102
103	public $timespent_min_date;
104	public $timespent_max_date;
105	public $timespent_total_duration;
106	public $timespent_total_amount;
107	public $timespent_nblinesnull;
108	public $timespent_nblines;
109	// For detail of lines of timespent record, there is the property ->lines in common
110
111	// Var used to call method addTimeSpent(). Bad practice.
112	public $timespent_id;
113	public $timespent_duration;
114	public $timespent_old_duration;
115	public $timespent_date;
116	public $timespent_datehour; // More accurate start date (same than timespent_date but includes hours, minutes and seconds)
117	public $timespent_withhour; // 1 = we entered also start hours for timesheet line
118	public $timespent_fk_user;
119	public $timespent_thm;
120	public $timespent_note;
121
122	public $comments = array();
123
124	public $oldcopy;
125
126
127	/**
128	 *  Constructor
129	 *
130	 *  @param      DoliDB		$db      Database handler
131	 */
132	public function __construct($db)
133	{
134		$this->db = $db;
135	}
136
137
138	/**
139	 *  Create into database
140	 *
141	 *  @param	User	$user        	User that create
142	 *  @param 	int		$notrigger	    0=launch triggers after, 1=disable triggers
143	 *  @return int 		        	<0 if KO, Id of created object if OK
144	 */
145	public function create($user, $notrigger = 0)
146	{
147		global $conf, $langs;
148
149		//For the date
150		$now = dol_now();
151
152		$error = 0;
153
154		// Clean parameters
155		$this->label = trim($this->label);
156		$this->description = trim($this->description);
157
158		// Check parameters
159		// Put here code to add control on parameters values
160
161		// Insert request
162		$sql = "INSERT INTO ".MAIN_DB_PREFIX."projet_task (";
163		$sql .= "entity";
164		$sql .= ", fk_projet";
165		$sql .= ", ref";
166		$sql .= ", fk_task_parent";
167		$sql .= ", label";
168		$sql .= ", description";
169		$sql .= ", datec";
170		$sql .= ", fk_user_creat";
171		$sql .= ", dateo";
172		$sql .= ", datee";
173		$sql .= ", planned_workload";
174		$sql .= ", progress";
175		$sql .= ") VALUES (";
176		$sql .= ((int) $conf->entity);
177		$sql .= ", ".((int) $this->fk_project);
178		$sql .= ", ".(!empty($this->ref) ? "'".$this->db->escape($this->ref)."'" : 'null');
179		$sql .= ", ".((int) $this->fk_task_parent);
180		$sql .= ", '".$this->db->escape($this->label)."'";
181		$sql .= ", '".$this->db->escape($this->description)."'";
182		$sql .= ", '".$this->db->idate($now)."'";
183		$sql .= ", ".((int) $user->id);
184		$sql .= ", ".($this->date_start ? "'".$this->db->idate($this->date_start)."'" : 'null');
185		$sql .= ", ".($this->date_end ? "'".$this->db->idate($this->date_end)."'" : 'null');
186		$sql .= ", ".(($this->planned_workload != '' && $this->planned_workload >= 0) ? $this->planned_workload : 'null');
187		$sql .= ", ".(($this->progress != '' && $this->progress >= 0) ? $this->progress : 'null');
188		$sql .= ")";
189
190		$this->db->begin();
191
192		dol_syslog(get_class($this)."::create", LOG_DEBUG);
193		$resql = $this->db->query($sql);
194		if (!$resql) {
195			$error++; $this->errors[] = "Error ".$this->db->lasterror();
196		}
197
198		if (!$error) {
199			$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."projet_task");
200
201			if (!$notrigger) {
202				// Call trigger
203				$result = $this->call_trigger('TASK_CREATE', $user);
204				if ($result < 0) {
205					$error++;
206				}
207				// End call triggers
208			}
209		}
210
211		// Update extrafield
212		if (!$error) {
213			if (!$error) {
214				$result = $this->insertExtraFields();
215				if ($result < 0) {
216					$error++;
217				}
218			}
219		}
220
221		// Commit or rollback
222		if ($error) {
223			foreach ($this->errors as $errmsg) {
224				dol_syslog(get_class($this)."::create ".$errmsg, LOG_ERR);
225				$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
226			}
227			$this->db->rollback();
228			return -1 * $error;
229		} else {
230			$this->db->commit();
231			return $this->id;
232		}
233	}
234
235
236	/**
237	 *  Load object in memory from database
238	 *
239	 *  @param	int		$id					Id object
240	 *  @param	int		$ref				ref object
241	 *  @param	int		$loadparentdata		Also load parent data
242	 *  @return int 		        		<0 if KO, 0 if not found, >0 if OK
243	 */
244	public function fetch($id, $ref = '', $loadparentdata = 0)
245	{
246		global $langs, $conf;
247
248		$sql = "SELECT";
249		$sql .= " t.rowid,";
250		$sql .= " t.ref,";
251		$sql .= " t.fk_projet as fk_project,";
252		$sql .= " t.fk_task_parent,";
253		$sql .= " t.label,";
254		$sql .= " t.description,";
255		$sql .= " t.duration_effective,";
256		$sql .= " t.planned_workload,";
257		$sql .= " t.datec,";
258		$sql .= " t.dateo,";
259		$sql .= " t.datee,";
260		$sql .= " t.fk_user_creat,";
261		$sql .= " t.fk_user_valid,";
262		$sql .= " t.fk_statut,";
263		$sql .= " t.progress,";
264		$sql .= " t.priority,";
265		$sql .= " t.note_private,";
266		$sql .= " t.note_public,";
267		$sql .= " t.rang";
268		if (!empty($loadparentdata)) {
269			$sql .= ", t2.ref as task_parent_ref";
270			$sql .= ", t2.rang as task_parent_position";
271		}
272		$sql .= " FROM ".MAIN_DB_PREFIX."projet_task as t";
273		if (!empty($loadparentdata)) {
274			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_task as t2 ON t.fk_task_parent = t2.rowid";
275		}
276		$sql .= " WHERE ";
277		if (!empty($ref)) {
278			$sql .= "entity IN (".getEntity('project').")";
279			$sql .= " AND t.ref = '".$this->db->escape($ref)."'";
280		} else {
281			$sql .= "t.rowid = ".((int) $id);
282		}
283
284		dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
285		$resql = $this->db->query($sql);
286		if ($resql) {
287			$num_rows = $this->db->num_rows($resql);
288
289			if ($num_rows) {
290				$obj = $this->db->fetch_object($resql);
291
292				$this->id = $obj->rowid;
293				$this->ref = $obj->ref;
294				$this->fk_project = $obj->fk_project;
295				$this->fk_task_parent = $obj->fk_task_parent;
296				$this->label = $obj->label;
297				$this->description = $obj->description;
298				$this->duration_effective = $obj->duration_effective;
299				$this->planned_workload = $obj->planned_workload;
300				$this->date_c = $this->db->jdate($obj->datec);
301				$this->date_start = $this->db->jdate($obj->dateo);
302				$this->date_end				= $this->db->jdate($obj->datee);
303				$this->fk_user_creat		= $obj->fk_user_creat;
304				$this->fk_user_valid		= $obj->fk_user_valid;
305				$this->fk_statut			= $obj->fk_statut;
306				$this->progress				= $obj->progress;
307				$this->priority				= $obj->priority;
308				$this->note_private = $obj->note_private;
309				$this->note_public = $obj->note_public;
310				$this->rang = $obj->rang;
311
312				if (!empty($loadparentdata)) {
313					$this->task_parent_ref      = $obj->task_parent_ref;
314					$this->task_parent_position = $obj->task_parent_position;
315				}
316
317				// Retrieve all extrafield
318				$this->fetch_optionals();
319			}
320
321			$this->db->free($resql);
322
323			if ($num_rows) {
324				return 1;
325			} else {
326				return 0;
327			}
328		} else {
329			$this->error = "Error ".$this->db->lasterror();
330			return -1;
331		}
332	}
333
334
335	/**
336	 *  Update database
337	 *
338	 *  @param	User	$user        	User that modify
339	 *  @param  int		$notrigger	    0=launch triggers after, 1=disable triggers
340	 *  @return int			         	<=0 if KO, >0 if OK
341	 */
342	public function update($user = null, $notrigger = 0)
343	{
344		global $conf, $langs;
345		$error = 0;
346
347		// Clean parameters
348		if (isset($this->fk_project)) {
349			$this->fk_project = trim($this->fk_project);
350		}
351		if (isset($this->ref)) {
352			$this->ref = trim($this->ref);
353		}
354		if (isset($this->fk_task_parent)) {
355			$this->fk_task_parent = (int) $this->fk_task_parent;
356		}
357		if (isset($this->label)) {
358			$this->label = trim($this->label);
359		}
360		if (isset($this->description)) {
361			$this->description = trim($this->description);
362		}
363		if (isset($this->duration_effective)) {
364			$this->duration_effective = trim($this->duration_effective);
365		}
366		if (isset($this->planned_workload)) {
367			$this->planned_workload = trim($this->planned_workload);
368		}
369
370		// Check parameters
371		// Put here code to add control on parameters values
372
373		// Update request
374		$sql = "UPDATE ".MAIN_DB_PREFIX."projet_task SET";
375		$sql .= " fk_projet=".(isset($this->fk_project) ? $this->fk_project : "null").",";
376		$sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "'".$this->db->escape($this->id)."'").",";
377		$sql .= " fk_task_parent=".(isset($this->fk_task_parent) ? $this->fk_task_parent : "null").",";
378		$sql .= " label=".(isset($this->label) ? "'".$this->db->escape($this->label)."'" : "null").",";
379		$sql .= " description=".(isset($this->description) ? "'".$this->db->escape($this->description)."'" : "null").",";
380		$sql .= " duration_effective=".(isset($this->duration_effective) ? $this->duration_effective : "null").",";
381		$sql .= " planned_workload=".((isset($this->planned_workload) && $this->planned_workload != '') ? $this->planned_workload : "null").",";
382		$sql .= " dateo=".($this->date_start != '' ? "'".$this->db->idate($this->date_start)."'" : 'null').",";
383		$sql .= " datee=".($this->date_end != '' ? "'".$this->db->idate($this->date_end)."'" : 'null').",";
384		$sql .= " progress=".(($this->progress != '' && $this->progress >= 0) ? $this->progress : 'null').",";
385		$sql .= " rang=".((!empty($this->rang)) ? $this->rang : "0");
386		$sql .= " WHERE rowid=".((int) $this->id);
387
388		$this->db->begin();
389
390		dol_syslog(get_class($this)."::update", LOG_DEBUG);
391		$resql = $this->db->query($sql);
392		if (!$resql) {
393			$error++; $this->errors[] = "Error ".$this->db->lasterror();
394		}
395
396		// Update extrafield
397		if (!$error) {
398			$result = $this->insertExtraFields();
399			if ($result < 0) {
400				$error++;
401			}
402		}
403
404		if (!$error && !empty($conf->global->PROJECT_CLASSIFY_CLOSED_WHEN_ALL_TASKS_DONE)) {
405			// Close the parent project if it is open (validated) and its tasks are 100% completed
406			$project = new Project($this->db);
407			if ($project->fetch($this->fk_project) > 0) {
408				if ($project->statut == Project::STATUS_VALIDATED) {
409					$project->getLinesArray(null); // this method does not return <= 0 if fails
410					$projectCompleted = array_reduce(
411						$project->lines,
412						function ($allTasksCompleted, $task) {
413							return $allTasksCompleted && $task->progress >= 100;
414						},
415					1
416					);
417					if ($projectCompleted) {
418						if ($project->setClose($user) <= 0) {
419							$error++;
420						}
421					}
422				}
423			} else {
424				$error++;
425			}
426			if ($error) {
427				$this->errors[] = $project->error;
428			}
429		}
430
431		if (!$error) {
432			if (!$notrigger) {
433				// Call trigger
434				$result = $this->call_trigger('TASK_MODIFY', $user);
435				if ($result < 0) {
436					$error++;
437				}
438				// End call triggers
439			}
440		}
441
442		if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
443			// We remove directory
444			if ($conf->projet->dir_output) {
445				$project = new Project($this->db);
446				$project->fetch($this->fk_project);
447
448				$olddir = $conf->projet->dir_output.'/'.dol_sanitizeFileName($project->ref).'/'.dol_sanitizeFileName($this->oldcopy->ref);
449				$newdir = $conf->projet->dir_output.'/'.dol_sanitizeFileName($project->ref).'/'.dol_sanitizeFileName($this->ref);
450				if (file_exists($olddir)) {
451					include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
452					$res = dol_move($olddir, $newdir);
453					if (!$res) {
454						$langs->load("errors");
455						$this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
456						$error++;
457					}
458				}
459			}
460		}
461
462		// Commit or rollback
463		if ($error) {
464			foreach ($this->errors as $errmsg) {
465				dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
466				$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
467			}
468			$this->db->rollback();
469			return -1 * $error;
470		} else {
471			$this->db->commit();
472			return 1;
473		}
474	}
475
476
477	/**
478	 *	Delete task from database
479	 *
480	 *	@param	User	$user        	User that delete
481	 *  @param  int		$notrigger	    0=launch triggers after, 1=disable triggers
482	 *	@return	int						<0 if KO, >0 if OK
483	 */
484	public function delete($user, $notrigger = 0)
485	{
486
487		global $conf, $langs;
488		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
489
490		$error = 0;
491
492		$this->db->begin();
493
494		if ($this->hasChildren() > 0) {
495			dol_syslog(get_class($this)."::delete Can't delete record as it has some sub tasks", LOG_WARNING);
496			$this->error = 'ErrorRecordHasSubTasks';
497			$this->db->rollback();
498			return 0;
499		}
500
501		$objectisused = $this->isObjectUsed($this->id);
502		if (!empty($objectisused)) {
503			dol_syslog(get_class($this)."::delete Can't delete record as it has some child", LOG_WARNING);
504			$this->error = 'ErrorRecordHasChildren';
505			$this->db->rollback();
506			return 0;
507		}
508
509		if (!$error) {
510			// Delete linked contacts
511			$res = $this->delete_linked_contact();
512			if ($res < 0) {
513				$this->error = 'ErrorFailToDeleteLinkedContact';
514				//$error++;
515				$this->db->rollback();
516				return 0;
517			}
518		}
519
520		if (!$error) {
521			$sql = "DELETE FROM ".MAIN_DB_PREFIX."projet_task_time";
522			$sql .= " WHERE fk_task=".$this->id;
523
524			$resql = $this->db->query($sql);
525			if (!$resql) {
526				$error++; $this->errors[] = "Error ".$this->db->lasterror();
527			}
528		}
529
530		if (!$error) {
531			$sql = "DELETE FROM ".MAIN_DB_PREFIX."projet_task_extrafields";
532			$sql .= " WHERE fk_object=".$this->id;
533
534			$resql = $this->db->query($sql);
535			if (!$resql) {
536				$error++; $this->errors[] = "Error ".$this->db->lasterror();
537			}
538		}
539
540		if (!$error) {
541			$sql = "DELETE FROM ".MAIN_DB_PREFIX."projet_task";
542			$sql .= " WHERE rowid=".((int) $this->id);
543
544			$resql = $this->db->query($sql);
545			if (!$resql) {
546				$error++; $this->errors[] = "Error ".$this->db->lasterror();
547			}
548		}
549
550		if (!$error) {
551			if (!$notrigger) {
552				// Call trigger
553				$result = $this->call_trigger('TASK_DELETE', $user);
554				if ($result < 0) {
555					$error++;
556				}
557				// End call triggers
558			}
559		}
560
561		// Commit or rollback
562		if ($error) {
563			foreach ($this->errors as $errmsg) {
564				dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
565				$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
566			}
567			$this->db->rollback();
568			return -1 * $error;
569		} else {
570			//Delete associated link file
571			if ($conf->projet->dir_output) {
572				$projectstatic = new Project($this->db);
573				$projectstatic->fetch($this->fk_project);
574
575				$dir = $conf->projet->dir_output."/".dol_sanitizeFileName($projectstatic->ref).'/'.dol_sanitizeFileName($this->id);
576				dol_syslog(get_class($this)."::delete dir=".$dir, LOG_DEBUG);
577				if (file_exists($dir)) {
578					require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
579					$res = @dol_delete_dir_recursive($dir);
580					if (!$res) {
581						$this->error = 'ErrorFailToDeleteDir';
582						$this->db->rollback();
583						return 0;
584					}
585				}
586			}
587
588			$this->db->commit();
589
590			return 1;
591		}
592	}
593
594	/**
595	 *	Return nb of children
596	 *
597	 *	@return	int		<0 if KO, 0 if no children, >0 if OK
598	 */
599	public function hasChildren()
600	{
601		$error = 0;
602		$ret = 0;
603
604		$sql = "SELECT COUNT(*) as nb";
605		$sql .= " FROM ".MAIN_DB_PREFIX."projet_task";
606		$sql .= " WHERE fk_task_parent=".$this->id;
607
608		dol_syslog(get_class($this)."::hasChildren", LOG_DEBUG);
609		$resql = $this->db->query($sql);
610		if (!$resql) {
611			$error++; $this->errors[] = "Error ".$this->db->lasterror();
612		} else {
613			$obj = $this->db->fetch_object($resql);
614			if ($obj) {
615				$ret = $obj->nb;
616			}
617			$this->db->free($resql);
618		}
619
620		if (!$error) {
621			return $ret;
622		} else {
623			return -1;
624		}
625	}
626
627	/**
628	 *	Return nb of time spent
629	 *
630	 *	@return	int		<0 if KO, 0 if no children, >0 if OK
631	 */
632	public function hasTimeSpent()
633	{
634		$error = 0;
635		$ret = 0;
636
637		$sql = "SELECT COUNT(*) as nb";
638		$sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time";
639		$sql .= " WHERE fk_task=".$this->id;
640
641		dol_syslog(get_class($this)."::hasTimeSpent", LOG_DEBUG);
642		$resql = $this->db->query($sql);
643		if (!$resql) {
644			$error++; $this->errors[] = "Error ".$this->db->lasterror();
645		} else {
646			$obj = $this->db->fetch_object($resql);
647			if ($obj) {
648				$ret = $obj->nb;
649			}
650			$this->db->free($resql);
651		}
652
653		if (!$error) {
654			return $ret;
655		} else {
656			return -1;
657		}
658	}
659
660
661	/**
662	 *	Return clicable name (with picto eventually)
663	 *
664	 *	@param	int		$withpicto		0=No picto, 1=Include picto into link, 2=Only picto
665	 *	@param	string	$option			'withproject' or ''
666	 *  @param	string	$mode			Mode 'task', 'time', 'contact', 'note', document' define page to link to.
667	 * 	@param	int		$addlabel		0=Default, 1=Add label into string, >1=Add first chars into string
668	 *  @param	string	$sep			Separator between ref and label if option addlabel is set
669	 *  @param	int   	$notooltip		1=Disable tooltip
670	 *  @param  int     $save_lastsearch_value    -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
671	 *	@return	string					Chaine avec URL
672	 */
673	public function getNomUrl($withpicto = 0, $option = '', $mode = 'task', $addlabel = 0, $sep = ' - ', $notooltip = 0, $save_lastsearch_value = -1)
674	{
675		global $conf, $langs, $user;
676
677		if (!empty($conf->dol_no_mouse_hover)) {
678			$notooltip = 1; // Force disable tooltips
679		}
680
681		$result = '';
682		$label = img_picto('', $this->picto).' <u>'.$langs->trans("Task").'</u>';
683		if (!empty($this->ref)) {
684			$label .= '<br><b>'.$langs->trans('Ref').':</b> '.$this->ref;
685		}
686		if (!empty($this->label)) {
687			$label .= '<br><b>'.$langs->trans('LabelTask').':</b> '.$this->label;
688		}
689		if ($this->date_start || $this->date_end) {
690			$label .= "<br>".get_date_range($this->date_start, $this->date_end, '', $langs, 0);
691		}
692
693		$url = DOL_URL_ROOT.'/projet/tasks/'.$mode.'.php?id='.$this->id.($option == 'withproject' ? '&withproject=1' : '');
694		// Add param to save lastsearch_values or not
695		$add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
696		if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
697			$add_save_lastsearch_values = 1;
698		}
699		if ($add_save_lastsearch_values) {
700			$url .= '&save_lastsearch_values=1';
701		}
702
703		$linkclose = '';
704		if (empty($notooltip)) {
705			if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
706				$label = $langs->trans("ShowTask");
707				$linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
708			}
709			$linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
710			$linkclose .= ' class="classfortooltip nowraponall"';
711		} else {
712			$linkclose .= ' class="nowraponall"';
713		}
714
715		$linkstart = '<a href="'.$url.'"';
716		$linkstart .= $linkclose.'>';
717		$linkend = '</a>';
718
719		$picto = 'projecttask';
720
721		$result .= $linkstart;
722		if ($withpicto) {
723			$result .= img_object(($notooltip ? '' : $label), $picto, ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
724		}
725		if ($withpicto != 2) {
726			$result .= $this->ref;
727		}
728		$result .= $linkend;
729		if ($withpicto != 2) {
730			$result .= (($addlabel && $this->label) ? $sep.dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
731		}
732
733		return $result;
734	}
735
736	/**
737	 *  Initialise an instance with random values.
738	 *  Used to build previews or test instances.
739	 *	id must be 0 if object instance is a specimen.
740	 *
741	 *  @return	void
742	 */
743	public function initAsSpecimen()
744	{
745		$this->id = 0;
746
747		$this->fk_project = '';
748		$this->ref = 'TK01';
749		$this->fk_task_parent = null;
750		$this->label = 'Specimen task TK01';
751		$this->duration_effective = '';
752		$this->fk_user_creat = null;
753		$this->progress = '25';
754		$this->fk_statut = null;
755		$this->note = 'This is a specimen task not';
756	}
757
758	/**
759	 * Return list of tasks for all projects or for one particular project
760	 * Sort order is on project, then on position of task, and last on start date of first level task
761	 *
762	 * @param	User	$usert				Object user to limit tasks affected to a particular user
763	 * @param	User	$userp				Object user to limit projects of a particular user and public projects
764	 * @param	int		$projectid			Project id
765	 * @param	int		$socid				Third party id
766	 * @param	int		$mode				0=Return list of tasks and their projects, 1=Return projects and tasks if exists
767	 * @param	string	$filteronproj    	Filter on project ref or label
768	 * @param	string	$filteronprojstatus	Filter on project status ('-1'=no filter, '0,1'=Draft+Validated only)
769	 * @param	string	$morewherefilter	Add more filter into where SQL request (must start with ' AND ...')
770	 * @param	string	$filteronprojuser	Filter on user that is a contact of project
771	 * @param	string	$filterontaskuser	Filter on user assigned to task
772	 * @param	array	$extrafields	    Show additional column from project or task
773	 * @param   int     $includebilltime    Calculate also the time to bill and billed
774	 * @param   array   $search_array_options Array of search
775	 * @return 	array						Array of tasks
776	 */
777	public function getTasksArray($usert = null, $userp = null, $projectid = 0, $socid = 0, $mode = 0, $filteronproj = '', $filteronprojstatus = '-1', $morewherefilter = '', $filteronprojuser = 0, $filterontaskuser = 0, $extrafields = array(), $includebilltime = 0, $search_array_options = array())
778	{
779		global $conf, $hookmanager;
780
781		$tasks = array();
782
783		//print $usert.'-'.$userp.'-'.$projectid.'-'.$socid.'-'.$mode.'<br>';
784
785		// List of tasks (does not care about permissions. Filtering will be done later)
786		$sql = "SELECT ";
787		if ($filteronprojuser > 0 || $filterontaskuser > 0) {
788			$sql .= " DISTINCT"; // We may get several time the same record if user has several roles on same project/task
789		}
790		$sql .= " p.rowid as projectid, p.ref, p.title as plabel, p.public, p.fk_statut as projectstatus, p.usage_bill_time,";
791		$sql .= " t.rowid as taskid, t.ref as taskref, t.label, t.description, t.fk_task_parent, t.duration_effective, t.progress, t.fk_statut as status,";
792		$sql .= " t.dateo as date_start, t.datee as date_end, t.planned_workload, t.rang,";
793		$sql .= " t.description, ";
794		$sql .= " s.rowid as thirdparty_id, s.nom as thirdparty_name, s.email as thirdparty_email,";
795		$sql .= " p.fk_opp_status, p.opp_amount, p.opp_percent, p.budget_amount";
796		if (!empty($extrafields->attributes['projet']['label'])) {
797			foreach ($extrafields->attributes['projet']['label'] as $key => $val) {
798				$sql .= ($extrafields->attributes['projet']['type'][$key] != 'separate' ? ",efp.".$key.' as options_'.$key : '');
799			}
800		}
801		if (!empty($extrafields->attributes['projet_task']['label'])) {
802			foreach ($extrafields->attributes['projet_task']['label'] as $key => $val) {
803				$sql .= ($extrafields->attributes['projet_task']['type'][$key] != 'separate' ? ",efpt.".$key.' as options_'.$key : '');
804			}
805		}
806		if ($includebilltime) {
807			$sql .= ", SUM(tt.task_duration * ".$this->db->ifsql("invoice_id IS NULL", "1", "0").") as tobill, SUM(tt.task_duration * ".$this->db->ifsql("invoice_id IS NULL", "0", "1").") as billed";
808		}
809
810		$sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
811		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
812		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_extrafields as efp ON (p.rowid = efp.fk_object)";
813
814		if ($mode == 0) {
815			if ($filteronprojuser > 0) {
816				$sql .= ", ".MAIN_DB_PREFIX."element_contact as ec";
817				$sql .= ", ".MAIN_DB_PREFIX."c_type_contact as ctc";
818			}
819			$sql .= ", ".MAIN_DB_PREFIX."projet_task as t";
820			if ($includebilltime) {
821				$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_task_time as tt ON tt.fk_task = t.rowid";
822			}
823			if ($filterontaskuser > 0) {
824				$sql .= ", ".MAIN_DB_PREFIX."element_contact as ec2";
825				$sql .= ", ".MAIN_DB_PREFIX."c_type_contact as ctc2";
826			}
827			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_task_extrafields as efpt ON (t.rowid = efpt.fk_object)";
828			$sql .= " WHERE p.entity IN (".getEntity('project').")";
829			$sql .= " AND t.fk_projet = p.rowid";
830		} elseif ($mode == 1) {
831			if ($filteronprojuser > 0) {
832				$sql .= ", ".MAIN_DB_PREFIX."element_contact as ec";
833				$sql .= ", ".MAIN_DB_PREFIX."c_type_contact as ctc";
834			}
835			if ($filterontaskuser > 0) {
836				$sql .= ", ".MAIN_DB_PREFIX."projet_task as t";
837				if ($includebilltime) {
838					$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_task_time as tt ON tt.fk_task = t.rowid";
839				}
840				$sql .= ", ".MAIN_DB_PREFIX."element_contact as ec2";
841				$sql .= ", ".MAIN_DB_PREFIX."c_type_contact as ctc2";
842			} else {
843				$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_task as t on t.fk_projet = p.rowid";
844				if ($includebilltime) {
845					$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_task_time as tt ON tt.fk_task = t.rowid";
846				}
847			}
848			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."projet_task_extrafields as efpt ON (t.rowid = efpt.fk_object)";
849			$sql .= " WHERE p.entity IN (".getEntity('project').")";
850		} else {
851			return 'BadValueForParameterMode';
852		}
853
854		if ($filteronprojuser > 0) {
855			$sql .= " AND p.rowid = ec.element_id";
856			$sql .= " AND ctc.rowid = ec.fk_c_type_contact";
857			$sql .= " AND ctc.element = 'project'";
858			$sql .= " AND ec.fk_socpeople = ".((int) $filteronprojuser);
859			$sql .= " AND ec.statut = 4";
860			$sql .= " AND ctc.source = 'internal'";
861		}
862		if ($filterontaskuser > 0) {
863			$sql .= " AND t.fk_projet = p.rowid";
864			$sql .= " AND p.rowid = ec2.element_id";
865			$sql .= " AND ctc2.rowid = ec2.fk_c_type_contact";
866			$sql .= " AND ctc2.element = 'project_task'";
867			$sql .= " AND ec2.fk_socpeople = ".((int) $filterontaskuser);
868			$sql .= " AND ec2.statut = 4";
869			$sql .= " AND ctc2.source = 'internal'";
870		}
871		if ($socid) {
872			$sql .= " AND p.fk_soc = ".((int) $socid);
873		}
874		if ($projectid) {
875			$sql .= " AND p.rowid IN (".$this->db->sanitize($projectid).")";
876		}
877		if ($filteronproj) {
878			$sql .= natural_search(array("p.ref", "p.title"), $filteronproj);
879		}
880		if ($filteronprojstatus && $filteronprojstatus != '-1') {
881			$sql .= " AND p.fk_statut IN (".$this->db->sanitize($filteronprojstatus).")";
882		}
883		if ($morewherefilter) {
884			$sql .= $morewherefilter;
885		}
886		// Add where from extra fields
887		$extrafieldsobjectkey = 'projet_task';
888		$extrafieldsobjectprefix = 'efpt.';
889		include DOL_DOCUMENT_ROOT.'/core/tpl/extrafields_list_search_sql.tpl.php';
890		// Add where from hooks
891		$parameters = array();
892		$reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters); // Note that $action and $object may have been modified by hook
893		$sql .= $hookmanager->resPrint;
894		if ($includebilltime) {
895			$sql .= " GROUP BY p.rowid, p.ref, p.title, p.public, p.fk_statut, p.usage_bill_time,";
896			$sql .= " t.datec, t.dateo, t.datee, t.tms,";
897			$sql .= " t.rowid, t.ref, t.label, t.description, t.fk_task_parent, t.duration_effective, t.progress, t.fk_statut,";
898			$sql .= " t.dateo, t.datee, t.planned_workload, t.rang,";
899			$sql .= " t.description, ";
900			$sql .= " s.rowid, s.nom, s.email,";
901			$sql .= " p.fk_opp_status, p.opp_amount, p.opp_percent, p.budget_amount";
902			if (!empty($extrafields->attributes['projet']['label'])) {
903				foreach ($extrafields->attributes['projet']['label'] as $key => $val) {
904					$sql .= ($extrafields->attributes['projet']['type'][$key] != 'separate' ? ",efp.".$key : '');
905				}
906			}
907			if (!empty($extrafields->attributes['projet_task']['label'])) {
908				foreach ($extrafields->attributes['projet_task']['label'] as $key => $val) {
909					$sql .= ($extrafields->attributes['projet_task']['type'][$key] != 'separate' ? ",efpt.".$key : '');
910				}
911			}
912		}
913
914
915		$sql .= " ORDER BY p.ref, t.rang, t.dateo";
916
917		//print $sql;exit;
918		dol_syslog(get_class($this)."::getTasksArray", LOG_DEBUG);
919		$resql = $this->db->query($sql);
920		if ($resql) {
921			$num = $this->db->num_rows($resql);
922			$i = 0;
923			// Loop on each record found, so each couple (project id, task id)
924			while ($i < $num) {
925				$error = 0;
926
927				$obj = $this->db->fetch_object($resql);
928
929				if ((!$obj->public) && (is_object($userp))) {	// If not public project and we ask a filter on project owned by a user
930					if (!$this->getUserRolesForProjectsOrTasks($userp, 0, $obj->projectid, 0)) {
931						$error++;
932					}
933				}
934				if (is_object($usert)) {							// If we ask a filter on a user affected to a task
935					if (!$this->getUserRolesForProjectsOrTasks(0, $usert, $obj->projectid, $obj->taskid)) {
936						$error++;
937					}
938				}
939
940				if (!$error) {
941					$tasks[$i] = new Task($this->db);
942					$tasks[$i]->id = $obj->taskid;
943					$tasks[$i]->ref = $obj->taskref;
944					$tasks[$i]->fk_project		= $obj->projectid;
945					$tasks[$i]->projectref		= $obj->ref;
946					$tasks[$i]->projectlabel = $obj->plabel;
947					$tasks[$i]->projectstatus = $obj->projectstatus;
948
949					$tasks[$i]->fk_opp_status = $obj->fk_opp_status;
950					$tasks[$i]->opp_amount = $obj->opp_amount;
951					$tasks[$i]->opp_percent = $obj->opp_percent;
952					$tasks[$i]->budget_amount = $obj->budget_amount;
953					$tasks[$i]->usage_bill_time = $obj->usage_bill_time;
954
955					$tasks[$i]->label = $obj->label;
956					$tasks[$i]->description = $obj->description;
957					$tasks[$i]->fk_parent = $obj->fk_task_parent; // deprecated
958					$tasks[$i]->fk_task_parent = $obj->fk_task_parent;
959					$tasks[$i]->duration		= $obj->duration_effective;
960					$tasks[$i]->planned_workload = $obj->planned_workload;
961
962					if ($includebilltime) {
963						$tasks[$i]->tobill = $obj->tobill;
964						$tasks[$i]->billed = $obj->billed;
965					}
966
967					$tasks[$i]->progress		= $obj->progress;
968					$tasks[$i]->fk_statut = $obj->status;
969					$tasks[$i]->public = $obj->public;
970					$tasks[$i]->date_start = $this->db->jdate($obj->date_start);
971					$tasks[$i]->date_end		= $this->db->jdate($obj->date_end);
972					$tasks[$i]->rang	   		= $obj->rang;
973
974					$tasks[$i]->socid           = $obj->thirdparty_id; // For backward compatibility
975					$tasks[$i]->thirdparty_id = $obj->thirdparty_id;
976					$tasks[$i]->thirdparty_name	= $obj->thirdparty_name;
977					$tasks[$i]->thirdparty_email = $obj->thirdparty_email;
978
979					if (!empty($extrafields->attributes['projet']['label'])) {
980						foreach ($extrafields->attributes['projet']['label'] as $key => $val) {
981							if ($extrafields->attributes['projet']['type'][$key] != 'separate') {
982								$tasks[$i]->{'options_'.$key} = $obj->{'options_'.$key};
983							}
984						}
985					}
986
987					if (!empty($extrafields->attributes['projet_task']['label'])) {
988						foreach ($extrafields->attributes['projet_task']['label'] as $key => $val) {
989							if ($extrafields->attributes['projet_task']['type'][$key] != 'separate') {
990								$tasks[$i]->{'options_'.$key} = $obj->{'options_'.$key};
991							}
992						}
993					}
994				}
995
996				$i++;
997			}
998			$this->db->free($resql);
999		} else {
1000			dol_print_error($this->db);
1001		}
1002
1003		return $tasks;
1004	}
1005
1006	/**
1007	 * Return list of roles for a user for each projects or each tasks (or a particular project or a particular task).
1008	 *
1009	 * @param	User	$userp			      Return roles on project for this internal user. If set, usert and taskid must not be defined.
1010	 * @param	User	$usert			      Return roles on task for this internal user. If set userp must NOT be defined. -1 means no filter.
1011	 * @param 	int		$projectid		      Project id list separated with , to filter on project
1012	 * @param 	int		$taskid			      Task id to filter on a task
1013	 * @param	integer	$filteronprojstatus	  Filter on project status if userp is set. Not used if userp not defined.
1014	 * @return 	array					      Array (projectid => 'list of roles for project' or taskid => 'list of roles for task')
1015	 */
1016	public function getUserRolesForProjectsOrTasks($userp, $usert, $projectid = '', $taskid = 0, $filteronprojstatus = -1)
1017	{
1018		$arrayroles = array();
1019
1020		dol_syslog(get_class($this)."::getUserRolesForProjectsOrTasks userp=".is_object($userp)." usert=".is_object($usert)." projectid=".$projectid." taskid=".$taskid);
1021
1022		// We want role of user for a projet or role of user for a task. Both are not possible.
1023		if (empty($userp) && empty($usert)) {
1024			$this->error = "CallWithWrongParameters";
1025			return -1;
1026		}
1027		if (!empty($userp) && !empty($usert)) {
1028			$this->error = "CallWithWrongParameters";
1029			return -1;
1030		}
1031
1032		/* Liste des taches et role sur les projets ou taches */
1033		$sql = "SELECT pt.rowid as pid, ec.element_id, ctc.code, ctc.source";
1034		if ($userp) {
1035			$sql .= " FROM ".MAIN_DB_PREFIX."projet as pt";
1036		}
1037		if ($usert && $filteronprojstatus > -1) {
1038			$sql .= " FROM ".MAIN_DB_PREFIX."projet as p, ".MAIN_DB_PREFIX."projet_task as pt";
1039		}
1040		if ($usert && $filteronprojstatus <= -1) {
1041			$sql .= " FROM ".MAIN_DB_PREFIX."projet_task as pt";
1042		}
1043		$sql .= ", ".MAIN_DB_PREFIX."element_contact as ec";
1044		$sql .= ", ".MAIN_DB_PREFIX."c_type_contact as ctc";
1045		$sql .= " WHERE pt.rowid = ec.element_id";
1046		if ($userp && $filteronprojstatus > -1) {
1047			$sql .= " AND pt.fk_statut = ".((int) $filteronprojstatus);
1048		}
1049		if ($usert && $filteronprojstatus > -1) {
1050			$sql .= " AND pt.fk_projet = p.rowid AND p.fk_statut = ".((int) $filteronprojstatus);
1051		}
1052		if ($userp) {
1053			$sql .= " AND ctc.element = 'project'";
1054		}
1055		if ($usert) {
1056			$sql .= " AND ctc.element = 'project_task'";
1057		}
1058		$sql .= " AND ctc.rowid = ec.fk_c_type_contact";
1059		if ($userp) {
1060			$sql .= " AND ec.fk_socpeople = ".((int) $userp->id);
1061		}
1062		if ($usert) {
1063			$sql .= " AND ec.fk_socpeople = ".((int) $usert->id);
1064		}
1065		$sql .= " AND ec.statut = 4";
1066		$sql .= " AND ctc.source = 'internal'";
1067		if ($projectid) {
1068			if ($userp) {
1069				$sql .= " AND pt.rowid IN (".$this->db->sanitize($projectid).")";
1070			}
1071			if ($usert) {
1072				$sql .= " AND pt.fk_projet IN (".$this->db->sanitize($projectid).")";
1073			}
1074		}
1075		if ($taskid) {
1076			if ($userp) {
1077				$sql .= " ERROR SHOULD NOT HAPPENS";
1078			}
1079			if ($usert) {
1080				$sql .= " AND pt.rowid = ".((int) $taskid);
1081			}
1082		}
1083		//print $sql;
1084
1085		dol_syslog(get_class($this)."::getUserRolesForProjectsOrTasks execute request", LOG_DEBUG);
1086		$resql = $this->db->query($sql);
1087		if ($resql) {
1088			$num = $this->db->num_rows($resql);
1089			$i = 0;
1090			while ($i < $num) {
1091				$obj = $this->db->fetch_object($resql);
1092				if (empty($arrayroles[$obj->pid])) {
1093					$arrayroles[$obj->pid] = $obj->code;
1094				} else {
1095					$arrayroles[$obj->pid] .= ','.$obj->code;
1096				}
1097				$i++;
1098			}
1099			$this->db->free($resql);
1100		} else {
1101			dol_print_error($this->db);
1102		}
1103
1104		return $arrayroles;
1105	}
1106
1107
1108	/**
1109	 * 	Return list of id of contacts of task
1110	 *
1111	 *	@param	string	$source		Source
1112	 *  @return array				Array of id of contacts
1113	 */
1114	public function getListContactId($source = 'internal')
1115	{
1116		$contactAlreadySelected = array();
1117		$tab = $this->liste_contact(-1, $source);
1118		//var_dump($tab);
1119		$num = count($tab);
1120		$i = 0;
1121		while ($i < $num) {
1122			if ($source == 'thirdparty') {
1123				$contactAlreadySelected[$i] = $tab[$i]['socid'];
1124			} else {
1125				$contactAlreadySelected[$i] = $tab[$i]['id'];
1126			}
1127			$i++;
1128		}
1129		return $contactAlreadySelected;
1130	}
1131
1132
1133	/**
1134	 *  Add time spent
1135	 *
1136	 *  @param	User	$user           User object
1137	 *  @param  int		$notrigger	    0=launch triggers after, 1=disable triggers
1138	 *  @return	int                     <=0 if KO, >0 if OK
1139	 */
1140	public function addTimeSpent($user, $notrigger = 0)
1141	{
1142		global $conf, $langs;
1143
1144		dol_syslog(get_class($this)."::addTimeSpent", LOG_DEBUG);
1145
1146		$ret = 0;
1147
1148		// Check parameters
1149		if (!is_object($user)) {
1150			dol_print_error('', "Method addTimeSpent was called with wrong parameter user");
1151			return -1;
1152		}
1153
1154		// Clean parameters
1155		if (isset($this->timespent_note)) {
1156			$this->timespent_note = trim($this->timespent_note);
1157		}
1158		if (empty($this->timespent_datehour)) {
1159			$this->timespent_datehour = $this->timespent_date;
1160		}
1161
1162		$this->db->begin();
1163
1164		$sql = "INSERT INTO ".MAIN_DB_PREFIX."projet_task_time (";
1165		$sql .= "fk_task";
1166		$sql .= ", task_date";
1167		$sql .= ", task_datehour";
1168		$sql .= ", task_date_withhour";
1169		$sql .= ", task_duration";
1170		$sql .= ", fk_user";
1171		$sql .= ", note";
1172		$sql .= ") VALUES (";
1173		$sql .= $this->id;
1174		$sql .= ", '".$this->db->idate($this->timespent_date)."'";
1175		$sql .= ", '".$this->db->idate($this->timespent_datehour)."'";
1176		$sql .= ", ".(empty($this->timespent_withhour) ? 0 : 1);
1177		$sql .= ", ".$this->timespent_duration;
1178		$sql .= ", ".$this->timespent_fk_user;
1179		$sql .= ", ".(isset($this->timespent_note) ? "'".$this->db->escape($this->timespent_note)."'" : "null");
1180		$sql .= ")";
1181
1182		$resql = $this->db->query($sql);
1183		if ($resql) {
1184			$tasktime_id = $this->db->last_insert_id(MAIN_DB_PREFIX."projet_task_time");
1185			$ret = $tasktime_id;
1186			$this->timespent_id = $ret;
1187
1188			if (!$notrigger) {
1189				// Call trigger
1190				$result = $this->call_trigger('TASK_TIMESPENT_CREATE', $user);
1191				if ($result < 0) {
1192					$ret = -1;
1193				}
1194				// End call triggers
1195			}
1196		} else {
1197			$this->error = $this->db->lasterror();
1198			$ret = -1;
1199		}
1200
1201		if ($ret > 0) {
1202			// Recalculate amount of time spent for task and update denormalized field
1203			$sql = "UPDATE ".MAIN_DB_PREFIX."projet_task";
1204			$sql .= " SET duration_effective = (SELECT SUM(task_duration) FROM ".MAIN_DB_PREFIX."projet_task_time as ptt where ptt.fk_task = ".((int) $this->id).")";
1205			if (isset($this->progress)) {
1206				$sql .= ", progress = ".((float) $this->progress); // Do not overwrite value if not provided
1207			}
1208			$sql .= " WHERE rowid = ".((int) $this->id);
1209
1210			dol_syslog(get_class($this)."::addTimeSpent", LOG_DEBUG);
1211			if (!$this->db->query($sql)) {
1212				$this->error = $this->db->lasterror();
1213				$ret = -2;
1214			}
1215
1216			// Update hourly rate of this time spent entry
1217			$sql = "UPDATE ".MAIN_DB_PREFIX."projet_task_time";
1218			$sql .= " SET thm = (SELECT thm FROM ".MAIN_DB_PREFIX."user WHERE rowid = ".((int) $this->timespent_fk_user).")"; // set average hour rate of user
1219			$sql .= " WHERE rowid = ".((int) $tasktime_id);
1220
1221			dol_syslog(get_class($this)."::addTimeSpent", LOG_DEBUG);
1222			if (!$this->db->query($sql)) {
1223				$this->error = $this->db->lasterror();
1224				$ret = -2;
1225			}
1226		}
1227
1228		if ($ret > 0) {
1229			$this->db->commit();
1230		} else {
1231			$this->db->rollback();
1232		}
1233		return $ret;
1234	}
1235
1236	/**
1237	 *  Calculate total of time spent for task
1238	 *
1239	 *  @param  User|int	$userobj			Filter on user. null or 0=No filter
1240	 *  @param	string		$morewherefilter	Add more filter into where SQL request (must start with ' AND ...')
1241	 *  @return array		 					Array of info for task array('min_date', 'max_date', 'total_duration', 'total_amount', 'nblines', 'nblinesnull')
1242	 */
1243	public function getSummaryOfTimeSpent($userobj = null, $morewherefilter = '')
1244	{
1245		global $langs;
1246
1247		if (is_object($userobj)) {
1248			$userid = $userobj->id;
1249		} else {
1250			$userid = $userobj; // old method
1251		}
1252
1253		$id = $this->id;
1254		if (empty($id) && empty($userid)) {
1255			dol_syslog("getSummaryOfTimeSpent called on a not loaded task without user param defined", LOG_ERR);
1256			return -1;
1257		}
1258
1259		$result = array();
1260
1261		$sql = "SELECT";
1262		$sql .= " MIN(t.task_datehour) as min_date,";
1263		$sql .= " MAX(t.task_datehour) as max_date,";
1264		$sql .= " SUM(t.task_duration) as total_duration,";
1265		$sql .= " SUM(t.task_duration / 3600 * ".$this->db->ifsql("t.thm IS NULL", 0, "t.thm").") as total_amount,";
1266		$sql .= " COUNT(t.rowid) as nblines,";
1267		$sql .= " SUM(".$this->db->ifsql("t.thm IS NULL", 1, 0).") as nblinesnull";
1268		$sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time as t";
1269		$sql .= " WHERE 1 = 1";
1270		if ($morewherefilter) {
1271			$sql .= $morewherefilter;
1272		}
1273		if ($id > 0) {
1274			$sql .= " AND t.fk_task = ".((int) $id);
1275		}
1276		if ($userid > 0) {
1277			$sql .= " AND t.fk_user = ".((int) $userid);
1278		}
1279
1280		dol_syslog(get_class($this)."::getSummaryOfTimeSpent", LOG_DEBUG);
1281		$resql = $this->db->query($sql);
1282		if ($resql) {
1283			$obj = $this->db->fetch_object($resql);
1284
1285			$result['min_date'] = $obj->min_date; // deprecated. use the ->timespent_xxx instead
1286			$result['max_date'] = $obj->max_date; // deprecated. use the ->timespent_xxx instead
1287			$result['total_duration'] = $obj->total_duration; // deprecated. use the ->timespent_xxx instead
1288
1289			$this->timespent_min_date = $this->db->jdate($obj->min_date);
1290			$this->timespent_max_date = $this->db->jdate($obj->max_date);
1291			$this->timespent_total_duration = $obj->total_duration;
1292			$this->timespent_total_amount = $obj->total_amount;
1293			$this->timespent_nblinesnull = ($obj->nblinesnull ? $obj->nblinesnull : 0);
1294			$this->timespent_nblines = ($obj->nblines ? $obj->nblines : 0);
1295
1296			$this->db->free($resql);
1297		} else {
1298			dol_print_error($this->db);
1299		}
1300		return $result;
1301	}
1302
1303	/**
1304	 *  Calculate quantity and value of time consumed using the thm (hourly amount value of work for user entering time)
1305	 *
1306	 *	@param		User		$fuser		Filter on a dedicated user
1307	 *  @param		string		$dates		Start date (ex 00:00:00)
1308	 *  @param		string		$datee		End date (ex 23:59:59)
1309	 *  @return 	array	        		Array of info for task array('amount','nbseconds','nblinesnull')
1310	 */
1311	public function getSumOfAmount($fuser = '', $dates = '', $datee = '')
1312	{
1313		global $langs;
1314
1315		if (empty($id)) {
1316			$id = $this->id;
1317		}
1318
1319		$result = array();
1320
1321		$sql = "SELECT";
1322		$sql .= " SUM(t.task_duration) as nbseconds,";
1323		$sql .= " SUM(t.task_duration / 3600 * ".$this->db->ifsql("t.thm IS NULL", 0, "t.thm").") as amount, SUM(".$this->db->ifsql("t.thm IS NULL", 1, 0).") as nblinesnull";
1324		$sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time as t";
1325		$sql .= " WHERE t.fk_task = ".((int) $id);
1326		if (is_object($fuser) && $fuser->id > 0) {
1327			$sql .= " AND fk_user = ".((int) $fuser->id);
1328		}
1329		if ($dates > 0) {
1330			$datefieldname = "task_datehour";
1331			$sql .= " AND (".$datefieldname." >= '".$this->db->idate($dates)."' OR ".$datefieldname." IS NULL)";
1332		}
1333		if ($datee > 0) {
1334			$datefieldname = "task_datehour";
1335			$sql .= " AND (".$datefieldname." <= '".$this->db->idate($datee)."' OR ".$datefieldname." IS NULL)";
1336		}
1337		//print $sql;
1338
1339		dol_syslog(get_class($this)."::getSumOfAmount", LOG_DEBUG);
1340		$resql = $this->db->query($sql);
1341		if ($resql) {
1342			$obj = $this->db->fetch_object($resql);
1343
1344			$result['amount'] = $obj->amount;
1345			$result['nbseconds'] = $obj->nbseconds;
1346			$result['nblinesnull'] = $obj->nblinesnull;
1347
1348			$this->db->free($resql);
1349			return $result;
1350		} else {
1351			dol_print_error($this->db);
1352			return $result;
1353		}
1354	}
1355
1356	/**
1357	 *  Load properties of timespent of a task from the time spent ID.
1358	 *
1359	 *  @param	int		$id 	Id in time spent table
1360	 *  @return int		        <0 if KO, >0 if OK
1361	 */
1362	public function fetchTimeSpent($id)
1363	{
1364		global $langs;
1365
1366		$sql = "SELECT";
1367		$sql .= " t.rowid,";
1368		$sql .= " t.fk_task,";
1369		$sql .= " t.task_date,";
1370		$sql .= " t.task_datehour,";
1371		$sql .= " t.task_date_withhour,";
1372		$sql .= " t.task_duration,";
1373		$sql .= " t.fk_user,";
1374		$sql .= " t.thm,";
1375		$sql .= " t.note";
1376		$sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time as t";
1377		$sql .= " WHERE t.rowid = ".((int) $id);
1378
1379		dol_syslog(get_class($this)."::fetchTimeSpent", LOG_DEBUG);
1380		$resql = $this->db->query($sql);
1381		if ($resql) {
1382			if ($this->db->num_rows($resql)) {
1383				$obj = $this->db->fetch_object($resql);
1384
1385				$this->timespent_id = $obj->rowid;
1386				$this->id = $obj->fk_task;
1387				$this->timespent_date = $this->db->jdate($obj->task_date);
1388				$this->timespent_datehour   = $this->db->jdate($obj->task_datehour);
1389				$this->timespent_withhour   = $obj->task_date_withhour;
1390				$this->timespent_duration = $obj->task_duration;
1391				$this->timespent_fk_user	= $obj->fk_user;
1392				$this->timespent_thm    	= $obj->thm; // hourly rate
1393				$this->timespent_note = $obj->note;
1394			}
1395
1396			$this->db->free($resql);
1397
1398			return 1;
1399		} else {
1400			$this->error = "Error ".$this->db->lasterror();
1401			return -1;
1402		}
1403	}
1404
1405	/**
1406	 *  Load all records of time spent
1407	 *
1408	 *  @param	User	$userobj			User object
1409	 *  @param	string	$morewherefilter	Add more filter into where SQL request (must start with ' AND ...')
1410	 *  @return int							<0 if KO, array of time spent if OK
1411	 */
1412	public function fetchAllTimeSpent(User $userobj, $morewherefilter = '')
1413	{
1414		global $langs;
1415
1416		$arrayres = array();
1417
1418		$sql = "SELECT";
1419		$sql .= " s.rowid as socid,";
1420		$sql .= " s.nom as thirdparty_name,";
1421		$sql .= " s.email as thirdparty_email,";
1422		$sql .= " ptt.rowid,";
1423		$sql .= " ptt.fk_task,";
1424		$sql .= " ptt.task_date,";
1425		$sql .= " ptt.task_datehour,";
1426		$sql .= " ptt.task_date_withhour,";
1427		$sql .= " ptt.task_duration,";
1428		$sql .= " ptt.fk_user,";
1429		$sql .= " ptt.note,";
1430		$sql .= " ptt.thm,";
1431		$sql .= " pt.rowid as task_id,";
1432		$sql .= " pt.ref as task_ref,";
1433		$sql .= " pt.label as task_label,";
1434		$sql .= " p.rowid as project_id,";
1435		$sql .= " p.ref as project_ref,";
1436		$sql .= " p.title as project_label,";
1437		$sql .= " p.public as public";
1438		$sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time as ptt, ".MAIN_DB_PREFIX."projet_task as pt, ".MAIN_DB_PREFIX."projet as p";
1439		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
1440		$sql .= " WHERE ptt.fk_task = pt.rowid AND pt.fk_projet = p.rowid";
1441		$sql .= " AND ptt.fk_user = ".((int) $userobj->id);
1442		$sql .= " AND pt.entity IN (".getEntity('project').")";
1443		if ($morewherefilter) {
1444			$sql .= $morewherefilter;
1445		}
1446
1447		dol_syslog(get_class($this)."::fetchAllTimeSpent", LOG_DEBUG);
1448		$resql = $this->db->query($sql);
1449		if ($resql) {
1450			$num = $this->db->num_rows($resql);
1451
1452			$i = 0;
1453			while ($i < $num) {
1454				$obj = $this->db->fetch_object($resql);
1455
1456				$newobj = new stdClass();
1457
1458				$newobj->socid              = $obj->socid;
1459				$newobj->thirdparty_name    = $obj->thirdparty_name;
1460				$newobj->thirdparty_email   = $obj->thirdparty_email;
1461
1462				$newobj->fk_project			= $obj->project_id;
1463				$newobj->project_ref		= $obj->project_ref;
1464				$newobj->project_label = $obj->project_label;
1465				$newobj->public				= $obj->project_public;
1466
1467				$newobj->fk_task			= $obj->task_id;
1468				$newobj->task_ref = $obj->task_ref;
1469				$newobj->task_label = $obj->task_label;
1470
1471				$newobj->timespent_id = $obj->rowid;
1472				$newobj->timespent_date = $this->db->jdate($obj->task_date);
1473				$newobj->timespent_datehour	= $this->db->jdate($obj->task_datehour);
1474				$newobj->timespent_withhour = $obj->task_date_withhour;
1475				$newobj->timespent_duration = $obj->task_duration;
1476				$newobj->timespent_fk_user = $obj->fk_user;
1477				$newobj->timespent_thm = $obj->thm;	// hourly rate
1478				$newobj->timespent_note = $obj->note;
1479
1480				$arrayres[] = $newobj;
1481
1482				$i++;
1483			}
1484
1485			$this->db->free($resql);
1486		} else {
1487			dol_print_error($this->db);
1488			$this->error = "Error ".$this->db->lasterror();
1489			return -1;
1490		}
1491
1492		return $arrayres;
1493	}
1494
1495	/**
1496	 *	Update time spent
1497	 *
1498	 *  @param	User	$user           User id
1499	 *  @param  int		$notrigger	    0=launch triggers after, 1=disable triggers
1500	 *  @return	int						<0 if KO, >0 if OK
1501	 */
1502	public function updateTimeSpent($user, $notrigger = 0)
1503	{
1504		global $conf, $langs;
1505
1506		$ret = 0;
1507
1508		// Check parameters
1509		if ($this->timespent_date == '') {
1510			$this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentities("Date"));
1511			return -1;
1512		}
1513		if (!($this->timespent_fk_user > 0)) {
1514			$this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentities("User"));
1515			return -1;
1516		}
1517
1518		// Clean parameters
1519		if (empty($this->timespent_datehour)) {
1520			$this->timespent_datehour = $this->timespent_date;
1521		}
1522		if (isset($this->timespent_note)) {
1523			$this->timespent_note = trim($this->timespent_note);
1524		}
1525
1526		$this->db->begin();
1527
1528		$sql = "UPDATE ".MAIN_DB_PREFIX."projet_task_time SET";
1529		$sql .= " task_date = '".$this->db->idate($this->timespent_date)."',";
1530		$sql .= " task_datehour = '".$this->db->idate($this->timespent_datehour)."',";
1531		$sql .= " task_date_withhour = ".(empty($this->timespent_withhour) ? 0 : 1).",";
1532		$sql .= " task_duration = ".((int) $this->timespent_duration).",";
1533		$sql .= " fk_user = ".((int) $this->timespent_fk_user).",";
1534		$sql .= " note = ".(isset($this->timespent_note) ? "'".$this->db->escape($this->timespent_note)."'" : "null");
1535		$sql .= " WHERE rowid = ".((int) $this->timespent_id);
1536
1537		dol_syslog(get_class($this)."::updateTimeSpent", LOG_DEBUG);
1538		if ($this->db->query($sql)) {
1539			if (!$notrigger) {
1540				// Call trigger
1541				$result = $this->call_trigger('TASK_TIMESPENT_MODIFY', $user);
1542				if ($result < 0) {
1543					$this->db->rollback();
1544					$ret = -1;
1545				} else {
1546					$ret = 1;
1547				}
1548				// End call triggers
1549			} else {
1550				$ret = 1;
1551			}
1552		} else {
1553			$this->error = $this->db->lasterror();
1554			$this->db->rollback();
1555			$ret = -1;
1556		}
1557
1558		if ($ret == 1 && ($this->timespent_old_duration != $this->timespent_duration)) {
1559			// Recalculate amount of time spent for task and update denormalized field
1560			$sql = "UPDATE ".MAIN_DB_PREFIX."projet_task";
1561			$sql .= " SET duration_effective = (SELECT SUM(task_duration) FROM ".MAIN_DB_PREFIX."projet_task_time as ptt where ptt.fk_task = ".((int) $this->id).")";
1562			if (isset($this->progress)) {
1563				$sql .= ", progress = ".((float) $this->progress); // Do not overwrite value if not provided
1564			}
1565			$sql .= " WHERE rowid = ".((int) $this->id);
1566
1567			dol_syslog(get_class($this)."::updateTimeSpent", LOG_DEBUG);
1568			if (!$this->db->query($sql)) {
1569				$this->error = $this->db->lasterror();
1570				$this->db->rollback();
1571				$ret = -2;
1572			}
1573
1574			// Update hourly rate of this time spent entry, but only if it was not set initialy
1575			$sql = "UPDATE ".MAIN_DB_PREFIX."projet_task_time";
1576			$sql .= " SET thm = (SELECT thm FROM ".MAIN_DB_PREFIX."user WHERE rowid = ".((int) $this->timespent_fk_user).")"; // set average hour rate of user
1577			$sql .= " WHERE (thm IS NULL OR thm = 0) AND rowid = ".((int) $this->timespent_id);
1578
1579			dol_syslog(get_class($this)."::addTimeSpent", LOG_DEBUG);
1580			if (!$this->db->query($sql)) {
1581				$this->error = $this->db->lasterror();
1582				$ret = -2;
1583			}
1584		}
1585
1586		if ($ret >= 0) {
1587			$this->db->commit();
1588		}
1589		return $ret;
1590	}
1591
1592	/**
1593	 *  Delete time spent
1594	 *
1595	 *  @param	User	$user        	User that delete
1596	 *  @param  int		$notrigger	    0=launch triggers after, 1=disable triggers
1597	 *  @return	int						<0 if KO, >0 if OK
1598	 */
1599	public function delTimeSpent($user, $notrigger = 0)
1600	{
1601		global $conf, $langs;
1602
1603		$error = 0;
1604
1605		$this->db->begin();
1606
1607		$sql = "DELETE FROM ".MAIN_DB_PREFIX."projet_task_time";
1608		$sql .= " WHERE rowid = ".$this->timespent_id;
1609
1610		dol_syslog(get_class($this)."::delTimeSpent", LOG_DEBUG);
1611		$resql = $this->db->query($sql);
1612		if (!$resql) {
1613			$error++; $this->errors[] = "Error ".$this->db->lasterror();
1614		}
1615
1616		if (!$error) {
1617			if (!$notrigger) {
1618				// Call trigger
1619				$result = $this->call_trigger('TASK_TIMESPENT_DELETE', $user);
1620				if ($result < 0) {
1621					$error++;
1622				}
1623				// End call triggers
1624			}
1625		}
1626
1627		if (!$error) {
1628			$sql = "UPDATE ".MAIN_DB_PREFIX."projet_task";
1629			$sql .= " SET duration_effective = duration_effective - ".$this->db->escape($this->timespent_duration ? $this->timespent_duration : 0);
1630			$sql .= " WHERE rowid = ".$this->id;
1631
1632			dol_syslog(get_class($this)."::delTimeSpent", LOG_DEBUG);
1633			if ($this->db->query($sql)) {
1634				$result = 0;
1635			} else {
1636				$this->error = $this->db->lasterror();
1637				$result = -2;
1638			}
1639		}
1640
1641		// Commit or rollback
1642		if ($error) {
1643			foreach ($this->errors as $errmsg) {
1644				dol_syslog(get_class($this)."::delTimeSpent ".$errmsg, LOG_ERR);
1645				$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1646			}
1647			$this->db->rollback();
1648			return -1 * $error;
1649		} else {
1650			$this->db->commit();
1651			return 1;
1652		}
1653	}
1654
1655	/**	Load an object from its id and create a new one in database
1656	 *
1657	 *  @param	User	$user		            User making the clone
1658	 *  @param	int		$fromid     			Id of object to clone
1659	 *  @param	int		$project_id				Id of project to attach clone task
1660	 *  @param	int		$parent_task_id			Id of task to attach clone task
1661	 *  @param	bool	$clone_change_dt		recalculate date of task regarding new project start date
1662	 *  @param	bool	$clone_affectation		clone affectation of project
1663	 *  @param	bool	$clone_time				clone time of project
1664	 *  @param	bool	$clone_file				clone file of project
1665	 *  @param	bool	$clone_note				clone note of project
1666	 *  @param	bool	$clone_prog				clone progress of project
1667	 *  @return	int								New id of clone
1668	 */
1669	public function createFromClone(User $user, $fromid, $project_id, $parent_task_id, $clone_change_dt = false, $clone_affectation = false, $clone_time = false, $clone_file = false, $clone_note = false, $clone_prog = false)
1670	{
1671		global $langs, $conf;
1672
1673		$error = 0;
1674
1675		//Use 00:00 of today if time is use on task.
1676		$now = dol_mktime(0, 0, 0, dol_print_date(dol_now(), '%m'), dol_print_date(dol_now(), '%d'), dol_print_date(dol_now(), '%Y'));
1677
1678		$datec = $now;
1679
1680		$clone_task = new Task($this->db);
1681		$origin_task = new Task($this->db);
1682
1683		$clone_task->context['createfromclone'] = 'createfromclone';
1684
1685		$this->db->begin();
1686
1687		// Load source object
1688		$clone_task->fetch($fromid);
1689		$clone_task->fetch_optionals();
1690		//var_dump($clone_task->array_options);exit;
1691
1692		$origin_task->fetch($fromid);
1693
1694		$defaultref = '';
1695		$obj = empty($conf->global->PROJECT_TASK_ADDON) ? 'mod_task_simple' : $conf->global->PROJECT_TASK_ADDON;
1696		if (!empty($conf->global->PROJECT_TASK_ADDON) && is_readable(DOL_DOCUMENT_ROOT."/core/modules/project/task/".$conf->global->PROJECT_TASK_ADDON.".php")) {
1697			require_once DOL_DOCUMENT_ROOT."/core/modules/project/task/".$conf->global->PROJECT_TASK_ADDON.'.php';
1698			$modTask = new $obj;
1699			$defaultref = $modTask->getNextValue(0, $clone_task);
1700		}
1701
1702		$ori_project_id					= $clone_task->fk_project;
1703
1704		$clone_task->id					= 0;
1705		$clone_task->ref				= $defaultref;
1706		$clone_task->fk_project = $project_id;
1707		$clone_task->fk_task_parent = $parent_task_id;
1708		$clone_task->date_c = $datec;
1709		$clone_task->planned_workload = $origin_task->planned_workload;
1710		$clone_task->rang = $origin_task->rang;
1711
1712		//Manage Task Date
1713		if ($clone_change_dt) {
1714			$projectstatic = new Project($this->db);
1715			$projectstatic->fetch($ori_project_id);
1716
1717			//Origin project strat date
1718			$orign_project_dt_start = $projectstatic->date_start;
1719
1720			//Calcultate new task start date with difference between origin proj start date and origin task start date
1721			if (!empty($clone_task->date_start)) {
1722				$clone_task->date_start = $now + $clone_task->date_start - $orign_project_dt_start;
1723			}
1724
1725			//Calcultate new task end date with difference between origin proj end date and origin task end date
1726			if (!empty($clone_task->date_end)) {
1727				$clone_task->date_end = $now + $clone_task->date_end - $orign_project_dt_start;
1728			}
1729		}
1730
1731		if (!$clone_prog) {
1732				$clone_task->progress = 0;
1733		}
1734
1735		// Create clone
1736		$result = $clone_task->create($user);
1737
1738		// Other options
1739		if ($result < 0) {
1740			$this->error = $clone_task->error;
1741			$error++;
1742		}
1743
1744		// End
1745		if (!$error) {
1746			$clone_task_id = $clone_task->id;
1747			$clone_task_ref = $clone_task->ref;
1748
1749			//Note Update
1750			if (!$clone_note) {
1751				$clone_task->note_private = '';
1752				$clone_task->note_public = '';
1753			} else {
1754				$this->db->begin();
1755				$res = $clone_task->update_note(dol_html_entity_decode($clone_task->note_public, ENT_QUOTES | ENT_HTML5), '_public');
1756				if ($res < 0) {
1757					$this->error .= $clone_task->error;
1758					$error++;
1759					$this->db->rollback();
1760				} else {
1761					$this->db->commit();
1762				}
1763
1764				$this->db->begin();
1765				$res = $clone_task->update_note(dol_html_entity_decode($clone_task->note_private, ENT_QUOTES | ENT_HTML5), '_private');
1766				if ($res < 0) {
1767					$this->error .= $clone_task->error;
1768					$error++;
1769					$this->db->rollback();
1770				} else {
1771					$this->db->commit();
1772				}
1773			}
1774
1775			//Duplicate file
1776			if ($clone_file) {
1777				require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1778
1779				//retrieve project origin ref to know folder to copy
1780				$projectstatic = new Project($this->db);
1781				$projectstatic->fetch($ori_project_id);
1782				$ori_project_ref = $projectstatic->ref;
1783
1784				if ($ori_project_id != $project_id) {
1785					$projectstatic->fetch($project_id);
1786					$clone_project_ref = $projectstatic->ref;
1787				} else {
1788					$clone_project_ref = $ori_project_ref;
1789				}
1790
1791				$clone_task_dir = $conf->projet->dir_output."/".dol_sanitizeFileName($clone_project_ref)."/".dol_sanitizeFileName($clone_task_ref);
1792				$ori_task_dir = $conf->projet->dir_output."/".dol_sanitizeFileName($ori_project_ref)."/".dol_sanitizeFileName($fromid);
1793
1794				$filearray = dol_dir_list($ori_task_dir, "files", 0, '', '(\.meta|_preview.*\.png)$', '', SORT_ASC, 1);
1795				foreach ($filearray as $key => $file) {
1796					if (!file_exists($clone_task_dir)) {
1797						if (dol_mkdir($clone_task_dir) < 0) {
1798							$this->error .= $langs->trans('ErrorInternalErrorDetected').':dol_mkdir';
1799							$error++;
1800						}
1801					}
1802
1803					$rescopy = dol_copy($ori_task_dir.'/'.$file['name'], $clone_task_dir.'/'.$file['name'], 0, 1);
1804					if (is_numeric($rescopy) && $rescopy < 0) {
1805						$this->error .= $langs->trans("ErrorFailToCopyFile", $ori_task_dir.'/'.$file['name'], $clone_task_dir.'/'.$file['name']);
1806						$error++;
1807					}
1808				}
1809			}
1810
1811			// clone affectation
1812			if ($clone_affectation) {
1813				$origin_task = new Task($this->db);
1814				$origin_task->fetch($fromid);
1815
1816				foreach (array('internal', 'external') as $source) {
1817					$tab = $origin_task->liste_contact(-1, $source);
1818					$num = count($tab);
1819					$i = 0;
1820					while ($i < $num) {
1821						$clone_task->add_contact($tab[$i]['id'], $tab[$i]['code'], $tab[$i]['source']);
1822						if ($clone_task->error == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1823							$langs->load("errors");
1824							$this->error .= $langs->trans("ErrorThisContactIsAlreadyDefinedAsThisType");
1825							$error++;
1826						} else {
1827							if ($clone_task->error != '') {
1828								$this->error .= $clone_task->error;
1829								$error++;
1830							}
1831						}
1832						$i++;
1833					}
1834				}
1835			}
1836
1837			if ($clone_time) {
1838				//TODO clone time of affectation
1839			}
1840		}
1841
1842		unset($clone_task->context['createfromclone']);
1843
1844		if (!$error) {
1845			$this->db->commit();
1846			return $clone_task_id;
1847		} else {
1848			$this->db->rollback();
1849			dol_syslog(get_class($this)."::createFromClone nbError: ".$error." error : ".$this->error, LOG_ERR);
1850			return -1;
1851		}
1852	}
1853
1854
1855	/**
1856	 *	Return status label of object
1857	 *
1858	 *	@param	integer	$mode		0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto
1859	 * 	@return	string	  			Label
1860	 */
1861	public function getLibStatut($mode = 0)
1862	{
1863		return $this->LibStatut($this->fk_statut, $mode);
1864	}
1865
1866	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1867	/**
1868	 *  Return status label for an object
1869	 *
1870	 *  @param	int			$status	  	Id status
1871	 *  @param	integer		$mode		0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto
1872	 *  @return	string	  				Label
1873	 */
1874	public function LibStatut($status, $mode = 0)
1875	{
1876		// phpcs:enable
1877		global $langs;
1878
1879		// list of Statut of the task
1880		$this->statuts[0] = 'Draft';
1881		$this->statuts[1] = 'ToDo';
1882		$this->statuts[2] = 'Running';
1883		$this->statuts[3] = 'Finish';
1884		$this->statuts[4] = 'Transfered';
1885		$this->statuts_short[0] = 'Draft';
1886		$this->statuts_short[1] = 'ToDo';
1887		$this->statuts_short[2] = 'Running';
1888		$this->statuts_short[3] = 'Completed';
1889		$this->statuts_short[4] = 'Transfered';
1890
1891		if ($mode == 0) {
1892			return $langs->trans($this->statuts[$status]);
1893		} elseif ($mode == 1) {
1894			return $langs->trans($this->statuts_short[$status]);
1895		} elseif ($mode == 2) {
1896			if ($status == 0) {
1897				return img_picto($langs->trans($this->statuts_short[$status]), 'statut0').' '.$langs->trans($this->statuts_short[$status]);
1898			} elseif ($status == 1) {
1899				return img_picto($langs->trans($this->statuts_short[$status]), 'statut1').' '.$langs->trans($this->statuts_short[$status]);
1900			} elseif ($status == 2) {
1901				return img_picto($langs->trans($this->statuts_short[$status]), 'statut3').' '.$langs->trans($this->statuts_short[$status]);
1902			} elseif ($status == 3) {
1903				return img_picto($langs->trans($this->statuts_short[$status]), 'statut6').' '.$langs->trans($this->statuts_short[$status]);
1904			} elseif ($status == 4) {
1905				return img_picto($langs->trans($this->statuts_short[$status]), 'statut6').' '.$langs->trans($this->statuts_short[$status]);
1906			} elseif ($status == 5) {
1907				return img_picto($langs->trans($this->statuts_short[$status]), 'statut5').' '.$langs->trans($this->statuts_short[$status]);
1908			}
1909		} elseif ($mode == 3) {
1910			if ($status == 0) {
1911				return img_picto($langs->trans($this->statuts_short[$status]), 'statut0');
1912			} elseif ($status == 1) {
1913				return img_picto($langs->trans($this->statuts_short[$status]), 'statut1');
1914			} elseif ($status == 2) {
1915				return img_picto($langs->trans($this->statuts_short[$status]), 'statut3');
1916			} elseif ($status == 3) {
1917				return img_picto($langs->trans($this->statuts_short[$status]), 'statut6');
1918			} elseif ($status == 4) {
1919				return img_picto($langs->trans($this->statuts_short[$status]), 'statut6');
1920			} elseif ($status == 5) {
1921				return img_picto($langs->trans($this->statuts_short[$status]), 'statut5');
1922			}
1923		} elseif ($mode == 4) {
1924			if ($status == 0) {
1925				return img_picto($langs->trans($this->statuts_short[$status]), 'statut0').' '.$langs->trans($this->statuts[$status]);
1926			} elseif ($status == 1) {
1927				return img_picto($langs->trans($this->statuts_short[$status]), 'statut1').' '.$langs->trans($this->statuts[$status]);
1928			} elseif ($status == 2) {
1929				return img_picto($langs->trans($this->statuts_short[$status]), 'statut3').' '.$langs->trans($this->statuts[$status]);
1930			} elseif ($status == 3) {
1931				return img_picto($langs->trans($this->statuts_short[$status]), 'statut6').' '.$langs->trans($this->statuts[$status]);
1932			} elseif ($status == 4) {
1933				return img_picto($langs->trans($this->statuts_short[$status]), 'statut6').' '.$langs->trans($this->statuts[$status]);
1934			} elseif ($status == 5) {
1935				return img_picto($langs->trans($this->statuts_short[$status]), 'statut5').' '.$langs->trans($this->statuts[$status]);
1936			}
1937		} elseif ($mode == 5) {
1938			/*if ($status==0) return $langs->trans($this->statuts_short[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut0');
1939			elseif ($status==1) return $langs->trans($this->statuts_short[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut1');
1940			elseif ($status==2) return $langs->trans($this->statuts_short[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut3');
1941			elseif ($status==3) return $langs->trans($this->statuts_short[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut6');
1942			elseif ($status==4) return $langs->trans($this->statuts_short[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut6');
1943			elseif ($status==5) return $langs->trans($this->statuts_short[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut5');
1944			*/
1945			//else return $this->progress.' %';
1946			return '&nbsp;';
1947		} elseif ($mode == 6) {
1948			/*if ($status==0) return $langs->trans($this->statuts[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut0');
1949			elseif ($status==1) return $langs->trans($this->statuts[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut1');
1950			elseif ($status==2) return $langs->trans($this->statuts[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut3');
1951			elseif ($status==3) return $langs->trans($this->statuts[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut6');
1952			elseif ($status==4) return $langs->trans($this->statuts[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut6');
1953			elseif ($status==5) return $langs->trans($this->statuts[$status]).' '.img_picto($langs->trans($this->statuts_short[$status]),'statut5');
1954			*/
1955			//else return $this->progress.' %';
1956			return '&nbsp;';
1957		}
1958	}
1959
1960	/**
1961	 *  Create an intervention document on disk using template defined into PROJECT_TASK_ADDON_PDF
1962	 *
1963	 *  @param	string		$modele			force le modele a utiliser ('' par defaut)
1964	 *  @param	Translate	$outputlangs	objet lang a utiliser pour traduction
1965	 *  @param  int			$hidedetails    Hide details of lines
1966	 *  @param  int			$hidedesc       Hide description
1967	 *  @param  int			$hideref        Hide ref
1968	 *  @return int         				0 if KO, 1 if OK
1969	 */
1970	public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
1971	{
1972		global $conf;
1973
1974		$outputlangs->load("projects");
1975
1976		if (!dol_strlen($modele)) {
1977			$modele = 'nodefault';
1978
1979			if (!empty($this->model_pdf)) {
1980				$modele = $this->model_pdf;
1981			} elseif (!empty($this->modelpdf)) {	// deprecated
1982				$modele = $this->modelpdf;
1983			} elseif (!empty($conf->global->PROJECT_TASK_ADDON_PDF)) {
1984				$modele = $conf->global->PROJECT_TASK_ADDON_PDF;
1985			}
1986		}
1987
1988		$modelpath = "core/modules/project/task/doc/";
1989
1990		return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
1991	}
1992
1993
1994	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1995	/**
1996	 * Load indicators for dashboard (this->nbtodo and this->nbtodolate)
1997	 *
1998	 * @param	User	$user   Objet user
1999	 * @return WorkboardResponse|int <0 if KO, WorkboardResponse if OK
2000	 */
2001	public function load_board($user)
2002	{
2003		// phpcs:enable
2004		global $conf, $langs;
2005
2006		// For external user, no check is done on company because readability is managed by public status of project and assignement.
2007		//$socid = $user->socid;
2008		$socid = 0;
2009
2010		$projectstatic = new Project($this->db);
2011		$projectsListId = $projectstatic->getProjectsAuthorizedForUser($user, 0, 1, $socid);
2012
2013		// List of tasks (does not care about permissions. Filtering will be done later)
2014		$sql = "SELECT p.rowid as projectid, p.fk_statut as projectstatus,";
2015		$sql .= " t.rowid as taskid, t.progress as progress, t.fk_statut as status,";
2016		$sql .= " t.dateo as date_start, t.datee as datee";
2017		$sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
2018		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s on p.fk_soc = s.rowid";
2019		//if (! $user->rights->societe->client->voir && ! $socid) $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON sc.fk_soc = s.rowid";
2020		$sql .= ", ".MAIN_DB_PREFIX."projet_task as t";
2021		$sql .= " WHERE p.entity IN (".getEntity('project', 0).')';
2022		$sql .= " AND p.fk_statut = 1";
2023		$sql .= " AND t.fk_projet = p.rowid";
2024		$sql .= " AND (t.progress IS NULL OR t.progress < 100)"; // tasks to do
2025		if (!$user->rights->projet->all->lire) {
2026			$sql .= " AND p.rowid IN (".$this->db->sanitize($projectsListId).")";
2027		}
2028		// No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser
2029		//if ($socid || ! $user->rights->societe->client->voir)	$sql.= "  AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".((int) $socid).")";
2030		// No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser
2031		// if (! $user->rights->societe->client->voir && ! $socid) $sql.= " AND ((s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id).") OR (s.rowid IS NULL))";
2032
2033		//print $sql;
2034		$resql = $this->db->query($sql);
2035		if ($resql) {
2036			$task_static = new Task($this->db);
2037
2038			$response = new WorkboardResponse();
2039			$response->warning_delay = $conf->projet->task->warning_delay / 60 / 60 / 24;
2040			$response->label = $langs->trans("OpenedTasks");
2041			if ($user->rights->projet->all->lire) {
2042				$response->url = DOL_URL_ROOT.'/projet/tasks/list.php?mainmenu=project';
2043			} else {
2044				$response->url = DOL_URL_ROOT.'/projet/tasks/list.php?mode=mine&amp;mainmenu=project';
2045			}
2046			$response->img = img_object('', "task");
2047
2048			// This assignment in condition is not a bug. It allows walking the results.
2049			while ($obj = $this->db->fetch_object($resql)) {
2050				$response->nbtodo++;
2051
2052				$task_static->projectstatus = $obj->projectstatus;
2053				$task_static->progress = $obj->progress;
2054				$task_static->fk_statut = $obj->status;
2055				$task_static->date_end = $this->db->jdate($obj->datee);
2056
2057				if ($task_static->hasDelay()) {
2058					$response->nbtodolate++;
2059				}
2060			}
2061
2062			return $response;
2063		} else {
2064			$this->error = $this->db->error();
2065			return -1;
2066		}
2067	}
2068
2069
2070	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2071	/**
2072	 *      Charge indicateurs this->nb de tableau de bord
2073	 *
2074	 *      @return     int         <0 if ko, >0 if ok
2075	 */
2076	public function load_state_board()
2077	{
2078		// phpcs:enable
2079		global $user;
2080
2081		$mine = 0; $socid = $user->socid;
2082
2083		$projectstatic = new Project($this->db);
2084		$projectsListId = $projectstatic->getProjectsAuthorizedForUser($user, $mine, 1, $socid);
2085
2086		// List of tasks (does not care about permissions. Filtering will be done later)
2087		$sql = "SELECT count(p.rowid) as nb";
2088		$sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
2089		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s on p.fk_soc = s.rowid";
2090		if (!$user->rights->societe->client->voir && !$socid) {
2091			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON sc.fk_soc = s.rowid";
2092		}
2093		$sql .= ", ".MAIN_DB_PREFIX."projet_task as t";
2094		$sql .= " WHERE p.entity IN (".getEntity('project', 0).')';
2095		$sql .= " AND t.fk_projet = p.rowid"; // tasks to do
2096		if ($mine || !$user->rights->projet->all->lire) {
2097			$sql .= " AND p.rowid IN (".$this->db->sanitize($projectsListId).")";
2098		}
2099		// No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser
2100		//if ($socid || ! $user->rights->societe->client->voir)	$sql.= "  AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".((int) $socid).")";
2101		if ($socid) {
2102			$sql .= "  AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".((int) $socid).")";
2103		}
2104		if (!$user->rights->societe->client->voir && !$socid) {
2105			$sql .= " AND ((s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id).") OR (s.rowid IS NULL))";
2106		}
2107
2108		$resql = $this->db->query($sql);
2109		if ($resql) {
2110			// This assignment in condition is not a bug. It allows walking the results.
2111			while ($obj = $this->db->fetch_object($resql)) {
2112				$this->nb["tasks"] = $obj->nb;
2113			}
2114			$this->db->free($resql);
2115			return 1;
2116		} else {
2117			dol_print_error($this->db);
2118			$this->error = $this->db->error();
2119			return -1;
2120		}
2121	}
2122
2123	/**
2124	 * Is the task delayed?
2125	 *
2126	 * @return bool
2127	 */
2128	public function hasDelay()
2129	{
2130		global $conf;
2131
2132		if (!($this->progress >= 0 && $this->progress < 100)) {
2133			return false;
2134		}
2135
2136		$now = dol_now();
2137
2138		$datetouse = ($this->date_end > 0) ? $this->date_end : ((isset($this->datee) && $this->datee > 0) ? $this->datee : 0);
2139
2140		return ($datetouse > 0 && ($datetouse < ($now - $conf->projet->task->warning_delay)));
2141	}
2142}
2143