Passed
Branch develop (f6c1a1)
by
unknown
28:54
created

Project::createFromClone()   F

Complexity

Conditions 36
Paths > 20000

Size

Total Lines 229
Code Lines 126

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 36
eloc 126
nc 52416
nop 10
dl 0
loc 229
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/* Copyright (C) 2002-2005 Rodolphe Quiedeville <[email protected]>
3
 * Copyright (C) 2005-2020 Laurent Destailleur  <[email protected]>
4
 * Copyright (C) 2005-2010 Regis Houssin        <[email protected]>
5
 * Copyright (C) 2013	   Florian Henry        <[email protected]>
6
 * Copyright (C) 2014-2017 Marcos García        <[email protected]>
7
 * Copyright (C) 2017      Ferran Marcet        <[email protected]>
8
 * Copyright (C) 2019      Juanjo Menent        <[email protected]>
9
 *
10
 * This program is free software; you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation; either version 3 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22
 */
23
24
/**
25
 * 		\file       htdocs/projet/class/project.class.php
26
 * 		\ingroup    projet
27
 * 		\brief      File of class to manage projects
28
 */
29
require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
30
31
/**
32
 *	Class to manage projects
33
 */
34
class Project extends CommonObject
35
{
36
37
	/**
38
	 * @var string ID to identify managed object
39
	 */
40
	public $element = 'project';
41
42
	/**
43
	 * @var string Name of table without prefix where object is stored
44
	 */
45
	public $table_element = 'projet';
46
47
	/**
48
	 * @var int    Name of subtable line
49
	 */
50
	public $table_element_line = 'projet_task';
51
52
	/**
53
	 * @var int    Name of field date
54
	 */
55
	public $table_element_date;
56
57
	/**
58
	 * @var int Field with ID of parent key if this field has a parent
59
	 */
60
	public $fk_element = 'fk_projet';
61
62
	/**
63
	 * 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
64
	 * @var int
65
	 */
66
	public $ismultientitymanaged = 1;
67
68
	/**
69
	 * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
70
	 */
71
	public $picto = 'project';
72
73
	/**
74
	 * {@inheritdoc}
75
	 */
76
	protected $table_ref_field = 'ref';
77
78
	/**
79
	 * @var string description
80
	 */
81
	public $description;
82
83
	/**
84
	 * @var string
85
	 */
86
	public $title;
87
88
	public $date_start;
89
	public $date_end;
90
	public $date_close;
91
92
	public $socid; // To store id of thirdparty
93
	public $thirdparty_name; // To store name of thirdparty (defined only in some cases)
94
95
	public $user_author_id; //!< Id of project creator. Not defined if shared project.
96
97
	/**
98
	 * @var int user close id
99
	 */
100
	public $fk_user_close;
101
102
	/**
103
	 * @var int user close id
104
	 */
105
	public $user_close_id;
106
	public $public; //!< Tell if this is a public or private project
107
	public $budget_amount;
108
	public $usage_bill_time; // Is the time spent on project must be invoiced or not
109
110
	public $statuts_short;
111
	public $statuts_long;
112
113
	public $statut; // 0=draft, 1=opened, 2=closed
114
	public $opp_status; // opportunity status, into table llx_c_lead_status
115
	public $opp_percent; // opportunity probability
116
117
	public $oldcopy;
118
119
	public $weekWorkLoad; // Used to store workload details of a projet
120
	public $weekWorkLoadPerTask; // Used to store workload details of tasks of a projet
121
122
	/**
123
	 * @var int Creation date
124
	 * @deprecated
125
	 * @see $date_c
126
	 */
127
	public $datec;
128
129
	/**
130
	 * @var int Creation date
131
	 */
132
	public $date_c;
133
134
	/**
135
	 * @var int Modification date
136
	 * @deprecated
137
	 * @see $date_m
138
	 */
139
	public $datem;
140
141
	/**
142
	 * @var int Modification date
143
	 */
144
	public $date_m;
145
146
	/**
147
	 * @var Task[]
148
	 */
149
	public $lines;
150
151
	/**
152
	 * Draft status
153
	 */
154
	const STATUS_DRAFT = 0;
155
156
	/**
157
	 * Open/Validated status
158
	 */
159
	const STATUS_VALIDATED = 1;
160
161
	/**
162
	 * Closed status
163
	 */
164
	const STATUS_CLOSED = 2;
165
166
167
	public $fields=array(
168
		'rowid' =>array('type'=>'integer', 'label'=>'ID', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>10),
169
		'fk_soc' =>array('type'=>'integer', 'label'=>'Fk soc', 'enabled'=>1, 'visible'=>-1, 'position'=>15),
170
		'datec' =>array('type'=>'datetime', 'label'=>'Datec', 'enabled'=>1, 'visible'=>-1, 'position'=>20),
171
		'tms' =>array('type'=>'timestamp', 'label'=>'Tms', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>25),
172
		'dateo' =>array('type'=>'date', 'label'=>'Dateo', 'enabled'=>1, 'visible'=>-1, 'position'=>30),
173
		'datee' =>array('type'=>'date', 'label'=>'Datee', 'enabled'=>1, 'visible'=>-1, 'position'=>35),
174
		'ref' =>array('type'=>'varchar(50)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>-1, 'showoncombobox'=>1, 'position'=>40),
175
		'entity' =>array('type'=>'integer', 'label'=>'Entity', 'default'=>1, 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>45),
176
		'title' =>array('type'=>'varchar(255)', 'label'=>'Title', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>50, 'showoncombobox'=>1),
177
		'description' =>array('type'=>'text', 'label'=>'Description', 'enabled'=>1, 'visible'=>-1, 'position'=>55),
178
		'fk_user_creat' =>array('type'=>'integer', 'label'=>'Fk user creat', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>60),
179
		'public' =>array('type'=>'integer', 'label'=>'Public', 'enabled'=>1, 'visible'=>-1, 'position'=>65),
180
		'fk_statut' =>array('type'=>'smallint(6)', 'label'=>'Fk statut', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>500),
181
		'fk_opp_status' =>array('type'=>'integer', 'label'=>'Fk opp status', 'enabled'=>1, 'visible'=>-1, 'position'=>75),
182
		'opp_percent' =>array('type'=>'double(5,2)', 'label'=>'Opp percent', 'enabled'=>1, 'visible'=>-1, 'position'=>80),
183
		'note_private' =>array('type'=>'text', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>85),
184
		'note_public' =>array('type'=>'text', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>90),
185
		'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'Model pdf', 'enabled'=>1, 'visible'=>0, 'position'=>95),
186
		'budget_amount' =>array('type'=>'double(24,8)', 'label'=>'Budget amount', 'enabled'=>1, 'visible'=>-1, 'position'=>100),
187
		'date_close' =>array('type'=>'datetime', 'label'=>'Date close', 'enabled'=>1, 'visible'=>-1, 'position'=>105),
188
		'fk_user_close' =>array('type'=>'integer', 'label'=>'Fk user close', 'enabled'=>1, 'visible'=>-1, 'position'=>110),
189
		'opp_amount' =>array('type'=>'double(24,8)', 'label'=>'Opp amount', 'enabled'=>1, 'visible'=>-1, 'position'=>115),
190
		'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-1, 'position'=>120),
191
		'fk_user_modif' =>array('type'=>'integer', 'label'=>'Fk user modif', 'enabled'=>1, 'visible'=>-1, 'position'=>125),
192
		'usage_bill_time' =>array('type'=>'integer', 'label'=>'Usage bill time', 'enabled'=>1, 'visible'=>-1, 'position'=>130),
193
		'usage_opportunity' =>array('type'=>'integer', 'label'=>'Usage opportunity', 'enabled'=>1, 'visible'=>-1, 'position'=>135),
194
		'usage_task' =>array('type'=>'integer', 'label'=>'Usage task', 'enabled'=>1, 'visible'=>-1, 'position'=>140),
195
		'usage_organize_event' =>array('type'=>'integer', 'label'=>'Usage organize event', 'enabled'=>1, 'visible'=>-1, 'position'=>145),
196
	);
197
198
199
	/**
200
	 *  Constructor
201
	 *
202
	 *  @param      DoliDB		$db      Database handler
203
	 */
204
	public function __construct($db)
205
	{
206
		$this->db = $db;
207
208
		$this->statuts_short = array(0 => 'Draft', 1 => 'Opened', 2 => 'Closed');
209
		$this->statuts_long = array(0 => 'Draft', 1 => 'Opened', 2 => 'Closed');
210
	}
211
212
	/**
213
	 *    Create a project into database
214
	 *
215
	 *    @param    User	$user       	User making creation
216
	 *    @param	int		$notrigger		Disable triggers
217
	 *    @return   int         			<0 if KO, id of created project if OK
218
	 */
219
	public function create($user, $notrigger = 0)
220
	{
221
		global $conf, $langs;
222
223
		$error = 0;
224
		$ret = 0;
225
226
		$now = dol_now();
227
228
		// Clean parameters
229
		$this->note_private = dol_substr($this->note_private, 0, 65535);
230
		$this->note_public = dol_substr($this->note_public, 0, 65535);
231
232
		// Check parameters
233
		if (!trim($this->ref))
234
		{
235
			$this->error = 'ErrorFieldsRequired';
236
			dol_syslog(get_class($this)."::create error -1 ref null", LOG_ERR);
237
			return -1;
238
		}
239
		if (!empty($conf->global->PROJECT_THIRDPARTY_REQUIRED) && !($this->socid > 0))
240
		{
241
			$this->error = 'ErrorFieldsRequired';
242
			dol_syslog(get_class($this)."::create error -1 thirdparty not defined and option PROJECT_THIRDPARTY_REQUIRED is set", LOG_ERR);
243
			return -1;
244
		}
245
246
		// Create project
247
		$this->db->begin();
248
249
		$sql = "INSERT INTO ".MAIN_DB_PREFIX."projet (";
250
		$sql .= "ref";
251
		$sql .= ", title";
252
		$sql .= ", description";
253
		$sql .= ", fk_soc";
254
		$sql .= ", fk_user_creat";
255
		$sql .= ", fk_statut";
256
		$sql .= ", fk_opp_status";
257
		$sql .= ", opp_percent";
258
		$sql .= ", public";
259
		$sql .= ", datec";
260
		$sql .= ", dateo";
261
		$sql .= ", datee";
262
		$sql .= ", opp_amount";
263
		$sql .= ", budget_amount";
264
		$sql .= ", usage_opportunity";
265
		$sql .= ", usage_task";
266
		$sql .= ", usage_bill_time";
267
		$sql .= ", usage_organize_event";
268
		$sql .= ", note_private";
269
		$sql .= ", note_public";
270
		$sql .= ", entity";
271
		$sql .= ") VALUES (";
272
		$sql .= "'".$this->db->escape($this->ref)."'";
273
		$sql .= ", '".$this->db->escape($this->title)."'";
274
		$sql .= ", '".$this->db->escape($this->description)."'";
275
		$sql .= ", ".($this->socid > 0 ? $this->socid : "null");
276
		$sql .= ", ".$user->id;
277
		$sql .= ", ".(is_numeric($this->statut) ? $this->statut : '0');
278
		$sql .= ", ".((is_numeric($this->opp_status) && $this->opp_status > 0) ? $this->opp_status : 'NULL');
279
		$sql .= ", ".(is_numeric($this->opp_percent) ? $this->opp_percent : 'NULL');
280
		$sql .= ", ".($this->public ? 1 : 0);
281
		$sql .= ", '".$this->db->idate($now)."'";
282
		$sql .= ", ".($this->date_start != '' ? "'".$this->db->idate($this->date_start)."'" : 'null');
283
		$sql .= ", ".($this->date_end != '' ? "'".$this->db->idate($this->date_end)."'" : 'null');
284
		$sql .= ", ".(strcmp($this->opp_amount, '') ? price2num($this->opp_amount) : 'null');
285
		$sql .= ", ".(strcmp($this->budget_amount, '') ? price2num($this->budget_amount) : 'null');
286
		$sql .= ", ".($this->usage_opportunity ? 1 : 0);
287
		$sql .= ", ".($this->usage_task ? 1 : 0);
288
		$sql .= ", ".($this->usage_bill_time ? 1 : 0);
289
		$sql .= ", ".($this->usage_organize_event ? 1 : 0);
290
		$sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : 'null');
291
		$sql .= ", ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : 'null');
292
		$sql .= ", ".$conf->entity;
293
		$sql .= ")";
294
295
		dol_syslog(get_class($this)."::create", LOG_DEBUG);
296
		$resql = $this->db->query($sql);
297
		if ($resql)
298
		{
299
			$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."projet");
300
			$ret = $this->id;
301
302
			if (!$notrigger)
303
			{
304
				// Call trigger
305
				$result = $this->call_trigger('PROJECT_CREATE', $user);
306
				if ($result < 0) { $error++; }
307
				// End call triggers
308
			}
309
		} else {
310
			$this->error = $this->db->lasterror();
311
			$this->errno = $this->db->lasterrno();
312
			$error++;
313
		}
314
315
		// Update extrafield
316
		if (!$error) {
317
			$result = $this->insertExtraFields();
318
			if ($result < 0)
319
			{
320
				$error++;
321
			}
322
		}
323
324
		if (!$error && !empty($conf->global->MAIN_DISABLEDRAFTSTATUS))
325
		{
326
			$res = $this->setValid($user);
327
			if ($res < 0) $error++;
328
		}
329
330
		if (!$error)
331
		{
332
			$this->db->commit();
333
			return $ret;
334
		} else {
335
			$this->db->rollback();
336
			return -1;
337
		}
338
	}
339
340
	/**
341
	 * Update a project
342
	 *
343
	 * @param  User		$user       User object of making update
344
	 * @param  int		$notrigger  1=Disable all triggers
345
	 * @return int                  <=0 if KO, >0 if OK
346
	 */
347
	public function update($user, $notrigger = 0)
348
	{
349
		global $langs, $conf;
350
351
		$error = 0;
352
353
		// Clean parameters
354
		$this->title = trim($this->title);
355
		$this->description = trim($this->description);
356
		if ($this->opp_amount < 0) $this->opp_amount = '';
357
		if ($this->opp_percent < 0) $this->opp_percent = '';
358
		if ($this->date_end && $this->date_end < $this->date_start)
359
		{
360
			$this->error = $langs->trans("ErrorDateEndLowerThanDateStart");
361
			$this->errors[] = $this->error;
362
			$this->db->rollback();
363
			dol_syslog(get_class($this)."::update error -3 ".$this->error, LOG_ERR);
364
			return -3;
365
		}
366
367
		if (dol_strlen(trim($this->ref)) > 0)
368
		{
369
			$this->db->begin();
370
371
			$sql = "UPDATE ".MAIN_DB_PREFIX."projet SET";
372
			$sql .= " ref='".$this->db->escape($this->ref)."'";
373
			$sql .= ", title = '".$this->db->escape($this->title)."'";
374
			$sql .= ", description = '".$this->db->escape($this->description)."'";
375
			$sql .= ", fk_soc = ".($this->socid > 0 ? $this->socid : "null");
376
			$sql .= ", fk_statut = ".$this->statut;
377
			$sql .= ", fk_opp_status = ".((is_numeric($this->opp_status) && $this->opp_status > 0) ? $this->opp_status : 'null');
378
			$sql .= ", opp_percent = ".((is_numeric($this->opp_percent) && $this->opp_percent != '') ? $this->opp_percent : 'null');
379
			$sql .= ", public = ".($this->public ? 1 : 0);
380
			$sql .= ", datec=".($this->date_c != '' ? "'".$this->db->idate($this->date_c)."'" : 'null');
381
			$sql .= ", dateo=".($this->date_start != '' ? "'".$this->db->idate($this->date_start)."'" : 'null');
382
			$sql .= ", datee=".($this->date_end != '' ? "'".$this->db->idate($this->date_end)."'" : 'null');
383
			$sql .= ", date_close=".($this->date_close != '' ? "'".$this->db->idate($this->date_close)."'" : 'null');
384
			$sql .= ", fk_user_close=".($this->fk_user_close > 0 ? $this->fk_user_close : "null");
385
			$sql .= ", opp_amount = ".(strcmp($this->opp_amount, '') ? price2num($this->opp_amount) : "null");
386
			$sql .= ", budget_amount = ".(strcmp($this->budget_amount, '') ? price2num($this->budget_amount) : "null");
387
			$sql .= ", fk_user_modif = ".$user->id;
388
			$sql .= ", usage_opportunity = ".($this->usage_opportunity ? 1 : 0);
389
			$sql .= ", usage_task = ".($this->usage_task ? 1 : 0);
390
			$sql .= ", usage_bill_time = ".($this->usage_bill_time ? 1 : 0);
391
			$sql .= ", usage_organize_event = ".($this->usage_organize_event ? 1 : 0);
392
			$sql .= " WHERE rowid = ".$this->id;
393
394
			dol_syslog(get_class($this)."::update", LOG_DEBUG);
395
			$resql = $this->db->query($sql);
396
			if ($resql)
397
			{
398
				// Update extrafield
399
				if (!$error)
400
				{
401
					$result = $this->insertExtraFields();
402
					if ($result < 0)
403
					{
404
						$error++;
405
					}
406
				}
407
408
				if (!$error && !$notrigger)
409
				{
410
					// Call trigger
411
					$result = $this->call_trigger('PROJECT_MODIFY', $user);
412
					if ($result < 0) { $error++; }
413
					// End call triggers
414
				}
415
416
				if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref))
417
				{
418
					// We remove directory
419
					if ($conf->projet->dir_output)
420
					{
421
						$olddir = $conf->projet->dir_output."/".dol_sanitizeFileName($this->oldcopy->ref);
422
						$newdir = $conf->projet->dir_output."/".dol_sanitizeFileName($this->ref);
423
						if (file_exists($olddir))
424
						{
425
							include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
426
							$res = @rename($olddir, $newdir);
427
							if (!$res)
428
							{
429
								$langs->load("errors");
430
								$this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
431
								$error++;
432
							}
433
						}
434
					}
435
				}
436
				if (!$error)
437
				{
438
					$this->db->commit();
439
					$result = 1;
440
				} else {
441
					$this->db->rollback();
442
					$result = -1;
443
				}
444
			} else {
445
				$this->error = $this->db->lasterror();
446
				$this->errors[] = $this->error;
447
				$this->db->rollback();
448
				if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS')
449
				{
450
					$result = -4;
451
				} else {
452
					$result = -2;
453
				}
454
				dol_syslog(get_class($this)."::update error ".$result." ".$this->error, LOG_ERR);
455
			}
456
		} else {
457
			dol_syslog(get_class($this)."::update ref null");
458
			$result = -1;
459
		}
460
461
		return $result;
462
	}
463
464
	/**
465
	 * 	Get object from database
466
	 *
467
	 * 	@param      int		$id       	Id of object to load
468
	 * 	@param		string	$ref		Ref of project
469
	 * 	@return     int      		   	>0 if OK, 0 if not found, <0 if KO
470
	 */
471
	public function fetch($id, $ref = '')
472
	{
473
		global $conf;
474
475
		if (empty($id) && empty($ref)) return -1;
476
477
		$sql = "SELECT rowid, ref, title, description, public, datec, opp_amount, budget_amount,";
478
		$sql .= " tms, dateo, datee, date_close, fk_soc, fk_user_creat, fk_user_modif, fk_user_close, fk_statut as status, fk_opp_status, opp_percent,";
479
		$sql .= " note_private, note_public, model_pdf, usage_opportunity, usage_task, usage_bill_time, usage_organize_event, entity";
480
		$sql .= " FROM ".MAIN_DB_PREFIX."projet";
481
		if (!empty($id))
482
		{
483
			$sql .= " WHERE rowid=".$id;
484
		} elseif (!empty($ref))
485
		{
486
			$sql .= " WHERE ref='".$this->db->escape($ref)."'";
487
			$sql .= " AND entity IN (".getEntity('project').")";
488
		}
489
490
		dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
491
		$resql = $this->db->query($sql);
492
		if ($resql)
493
		{
494
			$num_rows = $this->db->num_rows($resql);
495
496
			if ($num_rows)
497
			{
498
				$obj = $this->db->fetch_object($resql);
499
500
				$this->id = $obj->rowid;
501
				$this->ref = $obj->ref;
502
				$this->title = $obj->title;
503
				$this->description = $obj->description;
504
				$this->date_c = $this->db->jdate($obj->datec);
505
				$this->datec = $this->db->jdate($obj->datec); // TODO deprecated
506
				$this->date_m = $this->db->jdate($obj->tms);
507
				$this->datem = $this->db->jdate($obj->tms); // TODO deprecated
508
				$this->date_start = $this->db->jdate($obj->dateo);
509
				$this->date_end = $this->db->jdate($obj->datee);
510
				$this->date_close = $this->db->jdate($obj->date_close);
511
				$this->note_private = $obj->note_private;
512
				$this->note_public = $obj->note_public;
513
				$this->socid = $obj->fk_soc;
514
				$this->user_author_id = $obj->fk_user_creat;
515
				$this->user_modification_id = $obj->fk_user_modif;
516
				$this->user_close_id = $obj->fk_user_close;
517
				$this->public = $obj->public;
518
				$this->statut = $obj->status; // deprecated
519
				$this->status = $obj->status;
520
				$this->opp_status = $obj->fk_opp_status;
521
				$this->opp_amount	= $obj->opp_amount;
522
				$this->opp_percent = $obj->opp_percent;
523
				$this->budget_amount = $obj->budget_amount;
524
				$this->modelpdf = $obj->model_pdf;
525
				$this->usage_opportunity = (int) $obj->usage_opportunity;
526
				$this->usage_task = (int) $obj->usage_task;
527
				$this->usage_bill_time = (int) $obj->usage_bill_time;
528
				$this->usage_organize_event = (int) $obj->usage_organize_event;
529
				$this->entity = $obj->entity;
530
531
				$this->db->free($resql);
532
533
				// Retreive all extrafield
534
				// fetch optionals attributes and labels
535
				$this->fetch_optionals();
536
537
				return 1;
538
			}
539
540
			$this->db->free($resql);
541
542
			return 0;
543
		} else {
544
			$this->error = $this->db->lasterror();
545
			return -1;
546
		}
547
	}
548
549
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
550
	/**
551
	 * 	Return list of elements for type, linked to a project
552
	 *
553
	 * 	@param		string		$type			'propal','order','invoice','order_supplier','invoice_supplier',...
554
	 * 	@param		string		$tablename		name of table associated of the type
555
	 * 	@param		string		$datefieldname	name of date field for filter
556
	 *  @param		int			$dates			Start date
557
	 *  @param		int			$datee			End date
558
	 *	@param		string		$projectkey		Equivalent key  to fk_projet for actual type
559
	 * 	@return		mixed						Array list of object ids linked to project, < 0 or string if error
560
	 */
561
	public function get_element_list($type, $tablename, $datefieldname = '', $dates = '', $datee = '', $projectkey = 'fk_projet')
562
	{
563
		// phpcs:enable
564
		$elements = array();
565
566
		if ($this->id <= 0) return $elements;
567
568
		$ids = $this->id;
569
570
		if ($type == 'agenda')
571
		{
572
			$sql = "SELECT id as rowid FROM ".MAIN_DB_PREFIX."actioncomm WHERE fk_project IN (".$ids.") AND entity IN (".getEntity('agenda').")";
573
		} elseif ($type == 'expensereport')
574
		{
575
			$sql = "SELECT ed.rowid FROM ".MAIN_DB_PREFIX."expensereport as e, ".MAIN_DB_PREFIX."expensereport_det as ed WHERE e.rowid = ed.fk_expensereport AND e.entity IN (".getEntity('expensereport').") AND ed.fk_projet IN (".$ids.")";
576
		} elseif ($type == 'project_task')
577
		{
578
			$sql = "SELECT DISTINCT pt.rowid FROM ".MAIN_DB_PREFIX."projet_task as pt WHERE pt.fk_projet IN (".$ids.")";
579
		} elseif ($type == 'project_task_time')	// Case we want to duplicate line foreach user
580
		{
581
			$sql = "SELECT DISTINCT pt.rowid, ptt.fk_user FROM ".MAIN_DB_PREFIX."projet_task as pt, ".MAIN_DB_PREFIX."projet_task_time as ptt WHERE pt.rowid = ptt.fk_task AND pt.fk_projet IN (".$ids.")";
582
		} elseif ($type == 'stock_mouvement')
583
		{
584
			$sql = 'SELECT ms.rowid, ms.fk_user_author as fk_user FROM '.MAIN_DB_PREFIX."stock_mouvement as ms, ".MAIN_DB_PREFIX."entrepot as e WHERE e.rowid = ms.fk_entrepot AND e.entity IN (".getEntity('stock').") AND ms.origintype = 'project' AND ms.fk_origin IN (".$ids.") AND ms.type_mouvement = 1";
585
		} elseif ($type == 'loan')
586
		{
587
			$sql = 'SELECT l.rowid, l.fk_user_author as fk_user FROM '.MAIN_DB_PREFIX."loan as l WHERE l.entity IN (".getEntity('loan').") AND l.fk_projet IN (".$ids.")";
588
		} else {
589
			$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX.$tablename." WHERE ".$projectkey." IN (".$ids.") AND entity IN (".getEntity($type).")";
590
		}
591
592
		if ($dates > 0 && $type == 'loan'){
593
			$sql .= " AND (dateend > '".$this->db->idate($dates)."' OR dateend IS NULL)";
594
		} elseif ($dates > 0 && ($type != 'project_task'))	// For table project_taks, we want the filter on date apply on project_time_spent table
595
		{
596
			if (empty($datefieldname) && !empty($this->table_element_date)) $datefieldname = $this->table_element_date;
597
			if (empty($datefieldname)) return 'Error this object has no date field defined';
598
			$sql .= " AND (".$datefieldname." >= '".$this->db->idate($dates)."' OR ".$datefieldname." IS NULL)";
599
		}
600
601
		if ($datee > 0 && $type == 'loan'){
602
			$sql .= " AND (datestart < '".$this->db->idate($datee)."' OR datestart IS NULL)";
603
		} elseif ($datee > 0 && ($type != 'project_task'))	// For table project_taks, we want the filter on date apply on project_time_spent table
604
		{
605
			if (empty($datefieldname) && !empty($this->table_element_date)) $datefieldname = $this->table_element_date;
606
			if (empty($datefieldname)) return 'Error this object has no date field defined';
607
			$sql .= " AND (".$datefieldname." <= '".$this->db->idate($datee)."' OR ".$datefieldname." IS NULL)";
608
		}
609
		if (!$sql) return -1;
610
611
		//print $sql;
612
		dol_syslog(get_class($this)."::get_element_list", LOG_DEBUG);
613
		$result = $this->db->query($sql);
614
		if ($result)
615
		{
616
			$nump = $this->db->num_rows($result);
617
			if ($nump)
618
			{
619
				$i = 0;
620
				while ($i < $nump)
621
				{
622
					$obj = $this->db->fetch_object($result);
623
624
					$elements[$i] = $obj->rowid.(empty($obj->fk_user) ? '' : '_'.$obj->fk_user);
625
626
					$i++;
627
				}
628
				$this->db->free($result);
629
			}
630
631
			/* Return array even if empty*/
632
			return $elements;
633
		} else {
634
			dol_print_error($this->db);
635
		}
636
	}
637
638
	/**
639
	 *    Delete a project from database
640
	 *
641
	 *    @param       User		$user            User
642
	 *    @param       int		$notrigger       Disable triggers
643
	 *    @return      int       			      <0 if KO, 0 if not possible, >0 if OK
644
	 */
645
	public function delete($user, $notrigger = 0)
646
	{
647
		global $langs, $conf;
648
		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
649
650
		$error = 0;
651
652
		$this->db->begin();
653
654
		if (!$error)
655
		{
656
			// Delete linked contacts
657
			$res = $this->delete_linked_contact();
658
			if ($res < 0)
659
			{
660
				$this->error = 'ErrorFailToDeleteLinkedContact';
661
				//$error++;
662
				$this->db->rollback();
663
				return 0;
664
			}
665
		}
666
667
		// Set fk_projet into elements to null
668
		$listoftables = array(
669
			'propal'=>'fk_projet', 'commande'=>'fk_projet', 'facture'=>'fk_projet',
670
			'supplier_proposal'=>'fk_projet', 'commande_fournisseur'=>'fk_projet', 'facture_fourn'=>'fk_projet',
671
			'expensereport_det'=>'fk_projet', 'contrat'=>'fk_projet', 'fichinter'=>'fk_projet', 'don'=>'fk_projet',
672
			'actioncomm'=>'fk_project', 'mrp_mo'=>'fk_project'
673
		);
674
		foreach ($listoftables as $key => $value)
675
		{
676
			$sql = "UPDATE ".MAIN_DB_PREFIX.$key." SET ".$value." = NULL where ".$value." = ".$this->id;
677
			$resql = $this->db->query($sql);
678
			if (!$resql)
679
			{
680
				$this->errors[] = $this->db->lasterror();
681
				$error++;
682
				break;
683
			}
684
		}
685
686
		// Remove linked categories.
687
		if (!$error) {
688
			$sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_project";
689
			$sql .= " WHERE fk_project = ".$this->id;
690
691
			$result = $this->db->query($sql);
692
			if (!$result) {
693
				$error++;
694
				$this->errors[] = $this->db->lasterror();
695
			}
696
		}
697
698
		// Fetch tasks
699
		$this->getLinesArray($user);
700
701
		// Delete tasks
702
		$ret = $this->deleteTasks($user);
703
		if ($ret < 0) $error++;
704
705
706
		// Delete all child tables
707
		if (!$error) {
708
			$elements = array('categorie_project'); // elements to delete. TODO Make goodway to delete
709
			foreach ($elements as $table)
710
			{
711
				if (!$error) {
712
					$sql = "DELETE FROM ".MAIN_DB_PREFIX.$table;
713
					$sql .= " WHERE fk_project = ".$this->id;
714
715
					$result = $this->db->query($sql);
716
					if (!$result) {
717
						$error++;
718
						$this->errors[] = $this->db->lasterror();
719
					}
720
				}
721
			}
722
		}
723
724
		if (!$error)
725
		{
726
			$sql = "DELETE FROM ".MAIN_DB_PREFIX."projet_extrafields";
727
			$sql .= " WHERE fk_object=".$this->id;
728
729
			$resql = $this->db->query($sql);
730
			if (!$resql)
731
			{
732
				$this->errors[] = $this->db->lasterror();
733
				$error++;
734
			}
735
		}
736
737
		// Delete project
738
		if (!$error)
739
		{
740
			$sql = "DELETE FROM ".MAIN_DB_PREFIX."projet";
741
			$sql .= " WHERE rowid=".$this->id;
742
743
			$resql = $this->db->query($sql);
744
			if (!$resql)
745
			{
746
				$this->errors[] = $langs->trans("CantRemoveProject", $langs->transnoentitiesnoconv("ProjectOverview"));
747
				$error++;
748
			}
749
		}
750
751
752
753
		if (empty($error)) {
754
			// We remove directory
755
			$projectref = dol_sanitizeFileName($this->ref);
756
			if ($conf->projet->dir_output) {
757
				$dir = $conf->projet->dir_output."/".$projectref;
758
				if (file_exists($dir)) {
759
					$res = @dol_delete_dir_recursive($dir);
760
					if (!$res) {
761
						$this->errors[] = 'ErrorFailToDeleteDir';
762
						$error++;
763
					}
764
				}
765
			}
766
767
			if (!$notrigger)
768
			{
769
				// Call trigger
770
				$result = $this->call_trigger('PROJECT_DELETE', $user);
771
772
				if ($result < 0) {
773
					$error++;
774
				}
775
				// End call triggers
776
			}
777
		}
778
779
		if (empty($error))
780
		{
781
			$this->db->commit();
782
			return 1;
783
		} else {
784
			foreach ($this->errors as $errmsg)
785
			{
786
				dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
787
				$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
788
			}
789
			dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
790
			$this->db->rollback();
791
			return -1;
792
		}
793
	}
794
795
	/**
796
	 * 		Delete tasks with no children first, then task with children recursively
797
	 *
798
	 *  	@param     	User		$user		User
799
	 *		@return		int				<0 if KO, 1 if OK
800
	 */
801
	public function deleteTasks($user)
802
	{
803
		$countTasks = count($this->lines);
804
		$deleted = false;
805
		if ($countTasks)
806
		{
807
			foreach ($this->lines as $task)
808
			{
809
				if ($task->hasChildren() <= 0) {		// If there is no children (or error to detect them)
810
					$deleted = true;
811
					$ret = $task->delete($user);
812
					if ($ret <= 0)
813
					{
814
						$this->errors[] = $this->db->lasterror();
815
						return -1;
816
					}
817
				}
818
			}
819
		}
820
		$this->getLinesArray($user);
821
		if ($deleted && count($this->lines) < $countTasks)
822
		{
823
			if (count($this->lines)) $this->deleteTasks($this->lines);
824
		}
825
826
		return 1;
827
	}
828
829
	/**
830
	 * 		Validate a project
831
	 *
832
	 * 		@param		User	$user		   User that validate
833
	 *      @param      int     $notrigger     1=Disable triggers
834
	 * 		@return		int					   <0 if KO, >0 if OK
835
	 */
836
	public function setValid($user, $notrigger = 0)
837
	{
838
		global $langs, $conf;
839
840
		$error = 0;
841
842
		if ($this->statut != 1)
843
		{
844
			// Check parameters
845
			if (preg_match('/^'.preg_quote($langs->trans("CopyOf").' ').'/', $this->title))
846
			{
847
				$this->error = $langs->trans("ErrorFieldFormat", $langs->transnoentities("Label")).'. '.$langs->trans('RemoveString', $langs->transnoentitiesnoconv("CopyOf"));
848
				return -1;
849
			}
850
851
			$this->db->begin();
852
853
			$sql = "UPDATE ".MAIN_DB_PREFIX."projet";
854
			$sql .= " SET fk_statut = 1";
855
			$sql .= " WHERE rowid = ".$this->id;
856
			$sql .= " AND entity = ".$conf->entity;
857
858
			dol_syslog(get_class($this)."::setValid", LOG_DEBUG);
859
			$resql = $this->db->query($sql);
860
			if ($resql)
861
			{
862
				// Call trigger
863
				if (empty($notrigger))
864
				{
865
					$result = $this->call_trigger('PROJECT_VALIDATE', $user);
866
					if ($result < 0) { $error++; }
867
					// End call triggers
868
				}
869
870
				if (!$error)
871
				{
872
					$this->statut = 1;
873
					$this->db->commit();
874
					return 1;
875
				} else {
876
					$this->db->rollback();
877
					$this->error = join(',', $this->errors);
878
					dol_syslog(get_class($this)."::setValid ".$this->error, LOG_ERR);
879
					return -1;
880
				}
881
			} else {
882
				$this->db->rollback();
883
				$this->error = $this->db->lasterror();
884
				return -1;
885
			}
886
		}
887
	}
888
889
	/**
890
	 * 		Close a project
891
	 *
892
	 * 		@param		User	$user		User that close project
893
	 * 		@return		int					<0 if KO, 0 if already closed, >0 if OK
894
	 */
895
	public function setClose($user)
896
	{
897
		global $langs, $conf;
898
899
		$now = dol_now();
900
901
		$error = 0;
902
903
		if ($this->statut != self::STATUS_CLOSED)
904
		{
905
			$this->db->begin();
906
907
			$sql = "UPDATE ".MAIN_DB_PREFIX."projet";
908
			$sql .= " SET fk_statut = ".self::STATUS_CLOSED.", fk_user_close = ".$user->id.", date_close = '".$this->db->idate($now)."'";
909
			$sql .= " WHERE rowid = ".$this->id;
910
			$sql .= " AND fk_statut = ".self::STATUS_VALIDATED;
911
912
			if (!empty($conf->global->PROJECT_USE_OPPORTUNITIES))
913
			{
914
				// TODO What to do if fk_opp_status is not code 'WON' or 'LOST'
915
			}
916
917
			dol_syslog(get_class($this)."::setClose", LOG_DEBUG);
918
			$resql = $this->db->query($sql);
919
			if ($resql)
920
			{
921
				// Call trigger
922
				$result = $this->call_trigger('PROJECT_CLOSE', $user);
923
				if ($result < 0) { $error++; }
924
				// End call triggers
925
926
				if (!$error)
927
				{
928
					$this->statut = 2;
929
					$this->db->commit();
930
					return 1;
931
				} else {
932
					$this->db->rollback();
933
					$this->error = join(',', $this->errors);
934
					dol_syslog(get_class($this)."::setClose ".$this->error, LOG_ERR);
935
					return -1;
936
				}
937
			} else {
938
				$this->db->rollback();
939
				$this->error = $this->db->lasterror();
940
				return -1;
941
			}
942
		}
943
944
		return 0;
945
	}
946
947
	/**
948
	 *  Return status label of object
949
	 *
950
	 *  @param  int			$mode       0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto
951
	 * 	@return string      			Label
952
	 */
953
	public function getLibStatut($mode = 0)
954
	{
955
		return $this->LibStatut(isset($this->statut) ? $this->statut : $this->status, $mode);
956
	}
957
958
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
959
	/**
960
	 *  Renvoi status label for a status
961
	 *
962
	 *  @param	int		$status     id status
963
	 *  @param  int		$mode       0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
964
	 * 	@return string				Label
965
	 */
966
	public function LibStatut($status, $mode = 0)
967
	{
968
		// phpcs:enable
969
		global $langs;
970
971
		$statustrans = array(
972
			0 => 'status0',
973
			1 => 'status4',
974
			2 => 'status6',
975
		);
976
977
		$statusClass = 'status0';
978
		if (!empty($statustrans[$status])) {
979
			$statusClass = $statustrans[$status];
980
		}
981
982
		return dolGetStatus($langs->trans($this->statuts_long[$status]), $langs->trans($this->statuts_short[$status]), '', $statusClass, $mode);
983
	}
984
985
	/**
986
	 * 	Return clickable name (with picto eventually)
987
	 *
988
	 * 	@param	int		$withpicto		          0=No picto, 1=Include picto into link, 2=Only picto
989
	 * 	@param	string	$option			          Variant where the link point to ('', 'nolink')
990
	 * 	@param	int		$addlabel		          0=Default, 1=Add label into string, >1=Add first chars into string
991
	 *  @param	string	$moreinpopup	          Text to add into popup
992
	 *  @param	string	$sep			          Separator between ref and label if option addlabel is set
993
	 *  @param	int   	$notooltip		          1=Disable tooltip
994
	 *  @param  int     $save_lastsearch_value    -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
995
	 *  @param	string	$morecss				  More css on a link
996
	 * 	@return	string					          String with URL
997
	 */
998
	public function getNomUrl($withpicto = 0, $option = '', $addlabel = 0, $moreinpopup = '', $sep = ' - ', $notooltip = 0, $save_lastsearch_value = -1, $morecss = '')
999
	{
1000
		global $conf, $langs, $user, $hookmanager;
1001
1002
		if (!empty($conf->dol_no_mouse_hover)) $notooltip = 1; // Force disable tooltips
1003
1004
		$result = '';
1005
		if (! empty($conf->global->PROJECT_OPEN_ALWAYS_ON_TAB)) {
1006
			$option = $conf->global->PROJECT_OPEN_ALWAYS_ON_TAB;
1007
		}
1008
1009
		$label = '';
1010
		if ($option != 'nolink') $label = '<u>'.$langs->trans("Project").'</u>';
1011
		$label .= ($label ? '<br>' : '').'<b>'.$langs->trans('Ref').': </b>'.$this->ref; // The space must be after the : to not being explode when showing the title in img_picto
1012
		$label .= ($label ? '<br>' : '').'<b>'.$langs->trans('Label').': </b>'.$this->title; // The space must be after the : to not being explode when showing the title in img_picto
1013
		if (isset($this->public)) {
1014
			$label .= '<br><b>'.$langs->trans("Visibility").":</b> ".($this->public ? $langs->trans("SharedProject") : $langs->trans("PrivateProject"));
1015
		}
1016
		if (!empty($this->thirdparty_name)) {
1017
			$label .= ($label ? '<br>' : '').'<b>'.$langs->trans('ThirdParty').': </b>'.$this->thirdparty_name; // The space must be after the : to not being explode when showing the title in img_picto
1018
		}
1019
		if (!empty($this->dateo)) {
0 ignored issues
show
Bug introduced by
The property dateo does not exist on Project. Did you mean date?
Loading history...
1020
			$label .= ($label ? '<br>' : '').'<b>'.$langs->trans('DateStart').': </b>'.dol_print_date($this->dateo, 'day'); // The space must be after the : to not being explode when showing the title in img_picto
1021
		}
1022
		if (!empty($this->datee)) {
1023
			$label .= ($label ? '<br>' : '').'<b>'.$langs->trans('DateEnd').': </b>'.dol_print_date($this->datee, 'day'); // The space must be after the : to not being explode when showing the title in img_picto
1024
		}
1025
		if ($moreinpopup) $label .= '<br>'.$moreinpopup;
1026
		if (isset($this->status)) {
1027
			$label .= '<br><b>'.$langs->trans("Status").":</b> ".$this->getLibStatut(5);
1028
		}
1029
1030
		$url = '';
1031
		if ($option != 'nolink')
1032
		{
1033
			if (preg_match('/\.php$/', $option)) {
1034
				$url = dol_buildpath($option, 1).'?id='.$this->id;
1035
			} elseif ($option == 'task')
1036
			{
1037
				$url = DOL_URL_ROOT.'/projet/tasks.php?id='.$this->id;
1038
			} elseif ($option == 'preview')
1039
			{
1040
				$url = DOL_URL_ROOT.'/projet/element.php?id='.$this->id;
1041
			} else {
1042
				$url = DOL_URL_ROOT.'/projet/card.php?id='.$this->id;
1043
			}
1044
			// Add param to save lastsearch_values or not
1045
			$add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1046
			if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) $add_save_lastsearch_values = 1;
1047
			if ($add_save_lastsearch_values) $url .= '&save_lastsearch_values=1';
1048
		}
1049
1050
		$linkclose = '';
1051
		if (empty($notooltip) && $user->rights->projet->lire)
1052
		{
1053
			if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER))
1054
			{
1055
				$label = $langs->trans("ShowProject");
1056
				$linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1057
			}
1058
			$linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
1059
			$linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"';
1060
		}
1061
		else $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
1062
1063
		$picto = 'projectpub';
1064
		if (!$this->public) $picto = 'project';
1065
1066
		$linkstart = '<a href="'.$url.'"';
1067
		$linkstart .= $linkclose.'>';
1068
		$linkend = '</a>';
1069
1070
		$result .= $linkstart;
1071
		if ($withpicto) $result .= img_object(($notooltip ? '' : $label), $picto, ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
1072
		if ($withpicto != 2) $result .= $this->ref;
1073
		$result .= $linkend;
1074
		if ($withpicto != 2) $result .= (($addlabel && $this->title) ? $sep.dol_trunc($this->title, ($addlabel > 1 ? $addlabel : 0)) : '');
1075
1076
		global $action;
1077
		$hookmanager->initHooks(array('projectdao'));
1078
		$parameters = array('id'=>$this->id, 'getnomurl'=>$result);
1079
		$reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1080
		if ($reshook > 0) $result = $hookmanager->resPrint;
1081
		else $result .= $hookmanager->resPrint;
1082
1083
		return $result;
1084
	}
1085
1086
	/**
1087
	 *  Initialise an instance with random values.
1088
	 *  Used to build previews or test instances.
1089
	 * 	id must be 0 if object instance is a specimen.
1090
	 *
1091
	 *  @return	void
1092
	 */
1093
	public function initAsSpecimen()
1094
	{
1095
		global $user, $langs, $conf;
1096
1097
		$now = dol_now();
1098
1099
		// Initialise parameters
1100
		$this->id = 0;
1101
		$this->ref = 'SPECIMEN';
1102
		$this->specimen = 1;
1103
		$this->socid = 1;
1104
		$this->date_c = $now;
1105
		$this->date_m = $now;
1106
		$this->date_start = $now;
1107
		$this->date_end = $now + (3600 * 24 * 365);
1108
		$this->note_public = 'SPECIMEN';
1109
		$this->fk_ele = 20000;
1110
		$this->opp_amount = 20000;
1111
		$this->budget_amount = 10000;
1112
1113
		$this->usage_opportunity = 1;
1114
		$this->usage_task = 1;
1115
		$this->usage_bill_time = 1;
1116
		$this->usage_organize_event = 1;
1117
1118
		/*
1119
		 $nbp = mt_rand(1, 9);
1120
		 $xnbp = 0;
1121
		 while ($xnbp < $nbp)
1122
		 {
1123
		 $line = new Task($this->db);
1124
		 $line->fk_project = 0;
1125
		 $line->label = $langs->trans("Label") . " " . $xnbp;
1126
		 $line->description = $langs->trans("Description") . " " . $xnbp;
1127
1128
		 $this->lines[]=$line;
1129
		 $xnbp++;
1130
		 }
1131
		 */
1132
	}
1133
1134
	/**
1135
	 * 	Check if user has permission on current project
1136
	 *
1137
	 * 	@param	User	$user		Object user to evaluate
1138
	 * 	@param  string	$mode		Type of permission we want to know: 'read', 'write'
1139
	 * 	@return	int					>0 if user has permission, <0 if user has no permission
1140
	 */
1141
	public function restrictedProjectArea($user, $mode = 'read')
1142
	{
1143
		// To verify role of users
1144
		$userAccess = 0;
1145
		if (($mode == 'read' && !empty($user->rights->projet->all->lire)) || ($mode == 'write' && !empty($user->rights->projet->all->creer)) || ($mode == 'delete' && !empty($user->rights->projet->all->supprimer)))
1146
		{
1147
			$userAccess = 1;
1148
		} elseif ($this->public && (($mode == 'read' && !empty($user->rights->projet->lire)) || ($mode == 'write' && !empty($user->rights->projet->creer)) || ($mode == 'delete' && !empty($user->rights->projet->supprimer))))
1149
		{
1150
			$userAccess = 1;
1151
		} else {
1152
			foreach (array('internal', 'external') as $source)
1153
			{
1154
				$userRole = $this->liste_contact(4, $source);
1155
				$num = count($userRole);
1156
1157
				$nblinks = 0;
1158
				while ($nblinks < $num)
1159
				{
1160
					if ($source == 'internal' && preg_match('/^PROJECT/', $userRole[$nblinks]['code']) && $user->id == $userRole[$nblinks]['id'])
1161
					{
1162
						if ($mode == 'read' && $user->rights->projet->lire)      $userAccess++;
1163
						if ($mode == 'write' && $user->rights->projet->creer)     $userAccess++;
1164
						if ($mode == 'delete' && $user->rights->projet->supprimer) $userAccess++;
1165
					}
1166
					$nblinks++;
1167
				}
1168
			}
1169
			//if (empty($nblinks))	// If nobody has permission, we grant creator
1170
			//{
1171
			//	if ((!empty($this->user_author_id) && $this->user_author_id == $user->id))
1172
			//	{
1173
			//		$userAccess = 1;
1174
			//	}
1175
			//}
1176
		}
1177
1178
		return ($userAccess ? $userAccess : -1);
1179
	}
1180
1181
	/**
1182
	 * Return array of projects a user has permission on, is affected to, or all projects
1183
	 *
1184
	 * @param 	User	$user			User object
1185
	 * @param 	int		$mode			0=All project I have permission on (assigned to me or public), 1=Projects assigned to me only, 2=Will return list of all projects with no test on contacts
1186
	 * @param 	int		$list			0=Return array, 1=Return string list
1187
	 * @param	int		$socid			0=No filter on third party, id of third party
1188
	 * @param	string	$filter			additionnal filter on project (statut, ref, ...)
1189
	 * @return 	array or string			Array of projects id, or string with projects id separated with "," if list is 1
1190
	 */
1191
	public function getProjectsAuthorizedForUser($user, $mode = 0, $list = 0, $socid = 0, $filter = '')
1192
	{
1193
		$projects = array();
1194
		$temp = array();
1195
1196
		$sql = "SELECT ".(($mode == 0 || $mode == 1) ? "DISTINCT " : "")."p.rowid, p.ref";
1197
		$sql.= " FROM " . MAIN_DB_PREFIX . "projet as p";
1198
		if ($mode == 0)
1199
		{
1200
			$sql.= " LEFT JOIN " . MAIN_DB_PREFIX . "element_contact as ec ON ec.element_id = p.rowid";
1201
		} elseif ($mode == 1)
1202
		{
1203
			$sql.= ", " . MAIN_DB_PREFIX . "element_contact as ec";
1204
		} elseif ($mode == 2)
1205
		{
1206
			// No filter. Use this if user has permission to see all project
1207
		}
1208
		$sql.= " WHERE p.entity IN (".getEntity('project').")";
1209
		// Internal users must see project he is contact to even if project linked to a third party he can't see.
1210
		//if ($socid || ! $user->rights->societe->client->voir)	$sql.= " AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".$socid.")";
1211
		if ($socid > 0) $sql.= " AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = " . $socid . ")";
1212
1213
		// Get id of types of contacts for projects (This list never contains a lot of elements)
1214
		$listofprojectcontacttype=array();
1215
		$sql2 = "SELECT ctc.rowid, ctc.code FROM ".MAIN_DB_PREFIX."c_type_contact as ctc";
1216
		$sql2.= " WHERE ctc.element = '" . $this->db->escape($this->element) . "'";
1217
		$sql2.= " AND ctc.source = 'internal'";
1218
		$resql = $this->db->query($sql2);
1219
		if ($resql)
1220
		{
1221
			while ($obj = $this->db->fetch_object($resql))
1222
			{
1223
				$listofprojectcontacttype[$obj->rowid]=$obj->code;
1224
			}
1225
		} else dol_print_error($this->db);
1226
		if (count($listofprojectcontacttype) == 0) $listofprojectcontacttype[0]='0';    // To avoid syntax error if not found
1227
1228
		if ($mode == 0)
1229
		{
1230
			$sql.= " AND ( p.public = 1";
1231
			$sql.= " OR ( ec.fk_c_type_contact IN (".join(',', array_keys($listofprojectcontacttype)).")";
1232
			$sql.= " AND ec.fk_socpeople = ".$user->id.")";
1233
			$sql.= " )";
1234
		} elseif ($mode == 1)
1235
		{
1236
			$sql.= " AND ec.element_id = p.rowid";
1237
			$sql.= " AND (";
1238
			$sql.= "  ( ec.fk_c_type_contact IN (".join(',', array_keys($listofprojectcontacttype)).")";
1239
			$sql.= " AND ec.fk_socpeople = ".$user->id.")";
1240
			$sql.= " )";
1241
		} elseif ($mode == 2)
1242
		{
1243
			// No filter. Use this if user has permission to see all project
1244
		}
1245
1246
		$sql.= $filter;
1247
		//print $sql;
1248
1249
		$resql = $this->db->query($sql);
1250
		if ($resql)
1251
		{
1252
			$num = $this->db->num_rows($resql);
1253
			$i = 0;
1254
			while ($i < $num)
1255
			{
1256
				$row = $this->db->fetch_row($resql);
1257
				$projects[$row[0]] = $row[1];
1258
				$temp[] = $row[0];
1259
				$i++;
1260
			}
1261
1262
			$this->db->free($resql);
1263
1264
			if ($list)
1265
			{
1266
				if (empty($temp)) return '0';
1267
				$result = implode(',', $temp);
1268
				return $result;
1269
			}
1270
		} else {
1271
			dol_print_error($this->db);
1272
		}
1273
1274
		return $projects;
1275
	}
1276
1277
	/**
1278
	 * Load an object from its id and create a new one in database
1279
	 *
1280
	 *  @param	User	$user		          User making the clone
1281
	 *  @param	int		$fromid     	      Id of object to clone
1282
	 *  @param	bool	$clone_contact	      Clone contact of project
1283
	 *  @param	bool	$clone_task		      Clone task of project
1284
	 *  @param	bool	$clone_project_file	  Clone file of project
1285
	 *  @param	bool	$clone_task_file	  Clone file of task (if task are copied)
1286
	 *  @param	bool	$clone_note		      Clone note of project
1287
	 *  @param	bool	$move_date		      Move task date on clone
1288
	 *  @param	integer	$notrigger		      No trigger flag
1289
	 *  @param  int     $newthirdpartyid      New thirdparty id
1290
	 *  @return	int						      New id of clone
1291
	 */
1292
	public function createFromClone(User $user, $fromid, $clone_contact = false, $clone_task = true, $clone_project_file = false, $clone_task_file = false, $clone_note = true, $move_date = true, $notrigger = 0, $newthirdpartyid = 0)
1293
	{
1294
		global $langs, $conf;
1295
1296
		$error = 0;
1297
1298
		dol_syslog("createFromClone clone_contact=".$clone_contact." clone_task=".$clone_task." clone_project_file=".$clone_project_file." clone_note=".$clone_note." move_date=".$move_date, LOG_DEBUG);
1299
1300
		$now = dol_mktime(0, 0, 0, idate('m', dol_now()), idate('d', dol_now()), idate('Y', dol_now()));
1301
1302
		$clone_project = new Project($this->db);
1303
1304
		$clone_project->context['createfromclone'] = 'createfromclone';
1305
1306
		$this->db->begin();
1307
1308
		// Load source object
1309
		$clone_project->fetch($fromid);
1310
		$clone_project->fetch_optionals();
1311
		if ($newthirdpartyid > 0) $clone_project->socid = $newthirdpartyid;
1312
		$clone_project->fetch_thirdparty();
1313
1314
		$orign_dt_start = $clone_project->date_start;
1315
		$orign_project_ref = $clone_project->ref;
1316
1317
		$clone_project->id = 0;
1318
		if ($move_date) {
1319
			$clone_project->date_start = $now;
1320
			if (!(empty($clone_project->date_end)))
1321
			{
1322
				$clone_project->date_end = $clone_project->date_end + ($now - $orign_dt_start);
1323
			}
1324
		}
1325
1326
		$clone_project->datec = $now;
1 ignored issue
show
Deprecated Code introduced by
The property Project::$datec has been deprecated. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

1326
		/** @scrutinizer ignore-deprecated */ $clone_project->datec = $now;
Loading history...
1327
1328
		if (!$clone_note)
1329
		{
1330
			$clone_project->note_private = '';
1331
			$clone_project->note_public = '';
1332
		}
1333
1334
		//Generate next ref
1335
		$defaultref = '';
1336
		$obj = empty($conf->global->PROJECT_ADDON) ? 'mod_project_simple' : $conf->global->PROJECT_ADDON;
1337
		// Search template files
1338
		$file = ''; $classname = ''; $filefound = 0;
1339
		$dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
1340
		foreach ($dirmodels as $reldir)
1341
		{
1342
			$file = dol_buildpath($reldir."core/modules/project/".$obj.'.php', 0);
1343
			if (file_exists($file))
1344
			{
1345
				$filefound = 1;
1346
				dol_include_once($reldir."core/modules/project/".$obj.'.php');
1347
				$modProject = new $obj;
1348
				$defaultref = $modProject->getNextValue(is_object($clone_project->thirdparty) ? $clone_project->thirdparty : null, $clone_project);
1349
				break;
1350
			}
1351
		}
1352
		if (is_numeric($defaultref) && $defaultref <= 0) $defaultref = '';
1353
1354
		$clone_project->ref = $defaultref;
1355
		$clone_project->title = $langs->trans("CopyOf").' '.$clone_project->title;
1356
1357
		// Create clone
1358
		$result = $clone_project->create($user, $notrigger);
1359
1360
		// Other options
1361
		if ($result < 0)
1362
		{
1363
			$this->error .= $clone_project->error;
1364
			$error++;
1365
		}
1366
1367
		if (!$error)
1368
		{
1369
			//Get the new project id
1370
			$clone_project_id = $clone_project->id;
1371
1372
			//Note Update
1373
			if (!$clone_note)
1374
			{
1375
				$clone_project->note_private = '';
1376
				$clone_project->note_public = '';
1377
			} else {
1378
				$this->db->begin();
1379
				$res = $clone_project->update_note(dol_html_entity_decode($clone_project->note_public, ENT_QUOTES), '_public');
1380
				if ($res < 0)
1381
				{
1382
					$this->error .= $clone_project->error;
1383
					$error++;
1384
					$this->db->rollback();
1385
				} else {
1386
					$this->db->commit();
1387
				}
1388
1389
				$this->db->begin();
1390
				$res = $clone_project->update_note(dol_html_entity_decode($clone_project->note_private, ENT_QUOTES), '_private');
1391
				if ($res < 0)
1392
				{
1393
					$this->error .= $clone_project->error;
1394
					$error++;
1395
					$this->db->rollback();
1396
				} else {
1397
					$this->db->commit();
1398
				}
1399
			}
1400
1401
			//Duplicate contact
1402
			if ($clone_contact)
1403
			{
1404
				$origin_project = new Project($this->db);
1405
				$origin_project->fetch($fromid);
1406
1407
				foreach (array('internal', 'external') as $source)
1408
				{
1409
					$tab = $origin_project->liste_contact(-1, $source);
1410
1411
					foreach ($tab as $contacttoadd)
0 ignored issues
show
Bug introduced by
The expression $tab of type integer is not traversable.
Loading history...
1412
					{
1413
						$clone_project->add_contact($contacttoadd['id'], $contacttoadd['code'], $contacttoadd['source'], $notrigger);
1414
						if ($clone_project->error == 'DB_ERROR_RECORD_ALREADY_EXISTS')
1415
						{
1416
							$langs->load("errors");
1417
							$this->error .= $langs->trans("ErrorThisContactIsAlreadyDefinedAsThisType");
1418
							$error++;
1419
						} else {
1420
							if ($clone_project->error != '')
1421
							{
1422
								$this->error .= $clone_project->error;
1423
								$error++;
1424
							}
1425
						}
1426
					}
1427
				}
1428
			}
1429
1430
			//Duplicate file
1431
			if ($clone_project_file)
1432
			{
1433
				require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1434
1435
				$clone_project_dir = $conf->projet->dir_output."/".dol_sanitizeFileName($defaultref);
1436
				$ori_project_dir = $conf->projet->dir_output."/".dol_sanitizeFileName($orign_project_ref);
1437
1438
				if (dol_mkdir($clone_project_dir) >= 0)
1439
				{
1440
					$filearray = dol_dir_list($ori_project_dir, "files", 0, '', '(\.meta|_preview.*\.png)$', '', SORT_ASC, 1);
1441
					foreach ($filearray as $key => $file)
1442
					{
1443
						$rescopy = dol_copy($ori_project_dir.'/'.$file['name'], $clone_project_dir.'/'.$file['name'], 0, 1);
1444
						if (is_numeric($rescopy) && $rescopy < 0)
1445
						{
1446
							$this->error .= $langs->trans("ErrorFailToCopyFile", $ori_project_dir.'/'.$file['name'], $clone_project_dir.'/'.$file['name']);
1447
							$error++;
1448
						}
1449
					}
1450
				} else {
1451
					$this->error .= $langs->trans('ErrorInternalErrorDetected').':dol_mkdir';
1452
					$error++;
1453
				}
1454
			}
1455
1456
			//Duplicate task
1457
			if ($clone_task)
1458
			{
1459
				require_once DOL_DOCUMENT_ROOT . '/projet/class/task.class.php';
1460
1461
				$taskstatic = new Task($this->db);
1462
1463
				// Security check
1464
				$socid=0;
1465
				if ($user->socid > 0) $socid = $user->socid;
1466
1467
				$tasksarray=$taskstatic->getTasksArray(0, 0, $fromid, $socid, 0);
1468
1469
				$tab_conv_child_parent=array();
1470
1471
				// Loop on each task, to clone it
1472
				foreach ($tasksarray as $tasktoclone)
1473
				{
1474
					$result_clone = $taskstatic->createFromClone($user, $tasktoclone->id, $clone_project_id, $tasktoclone->fk_parent, $move_date, true, false, $clone_task_file, true, false);
1475
					if ($result_clone <= 0)
1476
					{
1477
						$this->error .= $result_clone->error;
0 ignored issues
show
Bug introduced by
The property error does not exist on integer.
Loading history...
1478
						$error++;
1479
					} else {
1480
						$new_task_id = $result_clone;
1481
						$taskstatic->fetch($tasktoclone->id);
1482
1483
						//manage new parent clone task id
1484
						// if the current task has child we store the original task id and the equivalent clone task id
1485
						if (($taskstatic->hasChildren()) && !array_key_exists($tasktoclone->id, $tab_conv_child_parent))
1486
						{
1487
							$tab_conv_child_parent[$tasktoclone->id] = $new_task_id;
1488
						}
1489
					}
1490
				}
1491
1492
				//Parse all clone node to be sure to update new parent
1493
				$tasksarray = $taskstatic->getTasksArray(0, 0, $clone_project_id, $socid, 0);
1494
				foreach ($tasksarray as $task_cloned)
1495
				{
1496
					$taskstatic->fetch($task_cloned->id);
1497
					if ($taskstatic->fk_task_parent != 0)
1498
					{
1499
						$taskstatic->fk_task_parent = $tab_conv_child_parent[$taskstatic->fk_task_parent];
1500
					}
1501
					$res = $taskstatic->update($user, $notrigger);
1502
					if ($result_clone <= 0)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result_clone seems to be defined by a foreach iteration on line 1472. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1503
					{
1504
						$this->error .= $taskstatic->error;
1505
						$error++;
1506
					}
1507
				}
1508
			}
1509
		}
1510
1511
		unset($clone_project->context['createfromclone']);
1512
1513
		if (!$error)
1514
		{
1515
			$this->db->commit();
1516
			return $clone_project_id;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $clone_project_id does not seem to be defined for all execution paths leading up to this point.
Loading history...
1517
		} else {
1518
			$this->db->rollback();
1519
			dol_syslog(get_class($this)."::createFromClone nbError: ".$error." error : ".$this->error, LOG_ERR);
1520
			return -1;
1521
		}
1522
	}
1523
1524
1525
	/**
1526
	 *    Shift project task date from current date to delta
1527
	 *
1528
	 *    @param	integer		$old_project_dt_start	Old project start date
1529
	 *    @return	int				                    1 if OK or < 0 if KO
1530
	 */
1531
	public function shiftTaskDate($old_project_dt_start)
1532
	{
1533
		global $user, $langs, $conf;
1534
1535
		$error=0;
1536
1537
		$taskstatic = new Task($this->db);
1538
1539
		// Security check
1540
		$socid=0;
1541
		if ($user->socid > 0) $socid = $user->socid;
1542
1543
		$tasksarray=$taskstatic->getTasksArray(0, 0, $this->id, $socid, 0);
1544
1545
		foreach ($tasksarray as $tasktoshiftdate)
1546
		{
1547
			$to_update=false;
1548
			// Fetch only if update of date will be made
1549
			if ((!empty($tasktoshiftdate->date_start)) || (!empty($tasktoshiftdate->date_end)))
1550
			{
1551
				//dol_syslog(get_class($this)."::shiftTaskDate to_update", LOG_DEBUG);
1552
				$to_update=true;
1553
				$task = new Task($this->db);
1554
				$result = $task->fetch($tasktoshiftdate->id);
1555
				if (!$result)
1556
				{
1557
					$error++;
1558
					$this->error.=$task->error;
1559
				}
1560
			}
1561
			//print "$this->date_start + $tasktoshiftdate->date_start - $old_project_dt_start";exit;
1562
1563
			//Calcultate new task start date with difference between old proj start date and origin task start date
1564
			if (!empty($tasktoshiftdate->date_start))
1565
			{
1566
				$task->date_start = $this->date_start + ($tasktoshiftdate->date_start - $old_project_dt_start);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $task does not seem to be defined for all execution paths leading up to this point.
Loading history...
1567
			}
1568
1569
			//Calcultate new task end date with difference between origin proj end date and origin task end date
1570
			if (!empty($tasktoshiftdate->date_end))
1571
			{
1572
				$task->date_end = $this->date_start + ($tasktoshiftdate->date_end - $old_project_dt_start);
1573
			}
1574
1575
			if ($to_update)
1576
			{
1577
				$result = $task->update($user);
1578
				if (!$result)
1579
				{
1580
					$error++;
1581
					$this->error .= $task->error;
1582
				}
1583
			}
1584
		}
1585
		if ($error != 0)
1586
		{
1587
			return -1;
1588
		}
1589
		return $result;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $result does not seem to be defined for all execution paths leading up to this point.
Loading history...
1590
	}
1591
1592
1593
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1594
	/**
1595
	 *    Associate element to a project
1596
	 *
1597
	 *    @param	string	$tableName			Table of the element to update
1598
	 *    @param	int		$elementSelectId	Key-rowid of the line of the element to update
1599
	 *    @return	int							1 if OK or < 0 if KO
1600
	 */
1601
	public function update_element($tableName, $elementSelectId)
1602
	{
1603
		// phpcs:enable
1604
		$sql = "UPDATE ".MAIN_DB_PREFIX.$tableName;
1605
1606
		if ($tableName == "actioncomm")
1607
		{
1608
			$sql .= " SET fk_project=".$this->id;
1609
			$sql .= " WHERE id=".$elementSelectId;
1610
		} else {
1611
			$sql .= " SET fk_projet=".$this->id;
1612
			$sql .= " WHERE rowid=".$elementSelectId;
1613
		}
1614
1615
		dol_syslog(get_class($this)."::update_element", LOG_DEBUG);
1616
		$resql = $this->db->query($sql);
1617
		if (!$resql) {
1618
			$this->error = $this->db->lasterror();
1619
			return -1;
1620
		} else {
1621
			return 1;
1622
		}
1623
	}
1624
1625
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1626
	/**
1627
	 *    Associate element to a project
1628
	 *
1629
	 *    @param	string	$tableName			Table of the element to update
1630
	 *    @param	int		$elementSelectId	Key-rowid of the line of the element to update
1631
	 *    @param	string	$projectfield	    The column name that stores the link with the project
1632
	 *
1633
	 *    @return	int							1 if OK or < 0 if KO
1634
	 */
1635
	public function remove_element($tableName, $elementSelectId, $projectfield = 'fk_projet')
1636
	{
1637
		// phpcs:enable
1638
		$sql = "UPDATE ".MAIN_DB_PREFIX.$tableName;
1639
1640
		if ($tableName == "actioncomm")
1641
		{
1642
			$sql .= " SET fk_project=NULL";
1643
			$sql .= " WHERE id=".$elementSelectId;
1644
		} else {
1645
			$sql .= " SET ".$projectfield."=NULL";
1646
			$sql .= " WHERE rowid=".$elementSelectId;
1647
		}
1648
1649
		dol_syslog(get_class($this)."::remove_element", LOG_DEBUG);
1650
		$resql = $this->db->query($sql);
1651
		if (!$resql) {
1652
			$this->error = $this->db->lasterror();
1653
			return -1;
1654
		} else {
1655
			return 1;
1656
		}
1657
	}
1658
1659
	/**
1660
	 *  Create an intervention document on disk using template defined into PROJECT_ADDON_PDF
1661
	 *
1662
	 *  @param	string		$modele			Force template to use ('' by default)
1663
	 *  @param	Translate	$outputlangs	Objet lang to use for translation
1664
	 *  @param  int			$hidedetails    Hide details of lines
1665
	 *  @param  int			$hidedesc       Hide description
1666
	 *  @param  int			$hideref        Hide ref
1667
	 *  @return int         				0 if KO, 1 if OK
1668
	 */
1669
	public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
1670
	{
1671
		global $conf,$langs;
1672
1673
		$langs->load("projects");
1674
1675
		if (! dol_strlen($modele)) {
1676
			$modele = 'baleine';
1677
1678
			if ($this->modelpdf) {
1679
				$modele = $this->modelpdf;
1680
			} elseif (! empty($conf->global->PROJECT_ADDON_PDF)) {
1681
				$modele = $conf->global->PROJECT_ADDON_PDF;
1682
			}
1683
		}
1684
1685
		$modelpath = "core/modules/project/doc/";
1686
1687
		return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
1688
	}
1689
1690
1691
	/**
1692
	 * Load time spent into this->weekWorkLoad and this->weekWorkLoadPerTask for all day of a week of project.
1693
	 * Note: array weekWorkLoad and weekWorkLoadPerTask are reset and filled at each call.
1694
	 *
1695
	 * @param 	int		$datestart		First day of week (use dol_get_first_day to find this date)
1696
	 * @param 	int		$taskid			Filter on a task id
1697
	 * @param 	int		$userid			Time spent by a particular user
1698
	 * @return 	int						<0 if OK, >0 if KO
1699
	 */
1700
	public function loadTimeSpent($datestart, $taskid = 0, $userid = 0)
1701
	{
1702
		$error=0;
1703
1704
		$this->weekWorkLoad=array();
1705
		$this->weekWorkLoadPerTask=array();
1706
1707
		if (empty($datestart)) dol_print_error('', 'Error datestart parameter is empty');
1708
1709
		$sql = "SELECT ptt.rowid as taskid, ptt.task_duration, ptt.task_date, ptt.task_datehour, ptt.fk_task";
1710
		$sql.= " FROM ".MAIN_DB_PREFIX."projet_task_time AS ptt, ".MAIN_DB_PREFIX."projet_task as pt";
1711
		$sql.= " WHERE ptt.fk_task = pt.rowid";
1712
		$sql.= " AND pt.fk_projet = ".$this->id;
1713
		$sql.= " AND (ptt.task_date >= '".$this->db->idate($datestart)."' ";
1714
		$sql.= " AND ptt.task_date <= '".$this->db->idate(dol_time_plus_duree($datestart, 1, 'w') - 1)."')";
1715
		if ($taskid) $sql.= " AND ptt.fk_task=".$taskid;
1716
		if (is_numeric($userid)) $sql.= " AND ptt.fk_user=".$userid;
1717
1718
		//print $sql;
1719
		$resql=$this->db->query($sql);
1720
		if ($resql)
1721
		{
1722
			$daylareadyfound=array();
1723
1724
			$num = $this->db->num_rows($resql);
1725
			$i = 0;
1726
			// Loop on each record found, so each couple (project id, task id)
1727
			while ($i < $num)
1728
			{
1729
				$obj=$this->db->fetch_object($resql);
1730
				$day=$this->db->jdate($obj->task_date);		// task_date is date without hours
1731
				if (empty($daylareadyfound[$day]))
1732
				{
1733
					$this->weekWorkLoad[$day] = $obj->task_duration;
1734
					$this->weekWorkLoadPerTask[$day][$obj->fk_task] = $obj->task_duration;
1735
				} else {
1736
					$this->weekWorkLoad[$day] += $obj->task_duration;
1737
					$this->weekWorkLoadPerTask[$day][$obj->fk_task] += $obj->task_duration;
1738
				}
1739
				$daylareadyfound[$day]=1;
1740
				$i++;
1741
			}
1742
			$this->db->free($resql);
1743
			return 1;
1744
		} else {
1745
			$this->error = "Error ".$this->db->lasterror();
1746
			dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
1747
			return -1;
1748
		}
1749
	}
1750
1751
	/**
1752
	 * Load time spent into this->weekWorkLoad and this->weekWorkLoadPerTask for all day of a week of project.
1753
	 * Note: array weekWorkLoad and weekWorkLoadPerTask are reset and filled at each call.
1754
	 *
1755
	 * @param 	int		$datestart		First day of week (use dol_get_first_day to find this date)
1756
	 * @param 	int		$taskid			Filter on a task id
1757
	 * @param 	int		$userid			Time spent by a particular user
1758
	 * @return 	int						<0 if OK, >0 if KO
1759
	 */
1760
	public function loadTimeSpentMonth($datestart, $taskid = 0, $userid = 0)
1761
	{
1762
		$error = 0;
1763
1764
		$this->monthWorkLoad = array();
1765
		$this->monthWorkLoadPerTask = array();
1766
1767
		if (empty($datestart)) dol_print_error('', 'Error datestart parameter is empty');
1768
1769
		$sql = "SELECT ptt.rowid as taskid, ptt.task_duration, ptt.task_date, ptt.task_datehour, ptt.fk_task";
1770
		$sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time AS ptt, ".MAIN_DB_PREFIX."projet_task as pt";
1771
		$sql .= " WHERE ptt.fk_task = pt.rowid";
1772
		$sql .= " AND pt.fk_projet = ".$this->id;
1773
		$sql .= " AND (ptt.task_date >= '".$this->db->idate($datestart)."' ";
1774
		$sql .= " AND ptt.task_date <= '".$this->db->idate(dol_time_plus_duree($datestart, 1, 'm') - 1)."')";
1775
		if ($task_id) $sql .= " AND ptt.fk_task=".$taskid;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $task_id does not exist. Did you maybe mean $taskid?
Loading history...
1776
		if (is_numeric($userid)) $sql .= " AND ptt.fk_user=".$userid;
1777
1778
		//print $sql;
1779
		$resql = $this->db->query($sql);
1780
		if ($resql)
1781
		{
1782
			$weekalreadyfound = array();
1783
1784
			$num = $this->db->num_rows($resql);
1785
			$i = 0;
1786
			// Loop on each record found, so each couple (project id, task id)
1787
			while ($i < $num)
1788
			{
1789
				$obj = $this->db->fetch_object($resql);
1790
				if (!empty($obj->task_date)) {
1791
					$date = explode('-', $obj->task_date);
1792
					$week_number = getWeekNumber($date[2], $date[1], $date[0]);
1793
				}
1794
				if (empty($weekalreadyfound[$week_number]))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $week_number does not seem to be defined for all execution paths leading up to this point.
Loading history...
1795
				{
1796
					$this->monthWorkLoad[$week_number] = $obj->task_duration;
1797
					$this->monthWorkLoadPerTask[$week_number][$obj->fk_task] = $obj->task_duration;
1798
				} else {
1799
					$this->monthWorkLoad[$week_number] += $obj->task_duration;
1800
					$this->monthWorkLoadPerTask[$week_number][$obj->fk_task] += $obj->task_duration;
1801
				}
1802
				$weekalreadyfound[$week_number] = 1;
1803
				$i++;
1804
			}
1805
			$this->db->free($resql);
1806
			return 1;
1807
		} else {
1808
			$this->error = "Error ".$this->db->lasterror();
1809
			dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
1810
			return -1;
1811
		}
1812
	}
1813
1814
1815
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1816
	/**
1817
	 * Load indicators for dashboard (this->nbtodo and this->nbtodolate)
1818
	 *
1819
	 * @param	User	$user   Objet user
1820
	 * @return WorkboardResponse|int <0 if KO, WorkboardResponse if OK
1821
	 */
1822
	public function load_board($user)
1823
	{
1824
		// phpcs:enable
1825
		global $conf, $langs;
1826
1827
		// For external user, no check is done on company because readability is managed by public status of project and assignement.
1828
		//$socid=$user->socid;
1829
1830
		$projectsListId = null;
1831
		if (!$user->rights->projet->all->lire) $projectsListId = $this->getProjectsAuthorizedForUser($user, 0, 1);
1832
1833
		$sql = "SELECT p.rowid, p.fk_statut as status, p.fk_opp_status, p.datee as datee";
1834
		$sql .= " FROM (".MAIN_DB_PREFIX."projet as p";
1835
		$sql .= ")";
1836
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s on p.fk_soc = s.rowid";
1837
		// For external user, no check is done on company permission because readability is managed by public status of project and assignement.
1838
		//if (! $user->rights->societe->client->voir && ! $socid) $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON sc.fk_soc = s.rowid";
1839
		$sql .= " WHERE p.fk_statut = 1";
1840
		$sql .= " AND p.entity IN (".getEntity('project').')';
1841
		if (!empty($projectsListId)) $sql .= " AND p.rowid IN (".$projectsListId.")";
1842
		// No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser
1843
		//if ($socid || ! $user->rights->societe->client->voir)	$sql.= "  AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".$socid.")";
1844
		// For external user, no check is done on company permission because readability is managed by public status of project and assignement.
1845
		//if (! $user->rights->societe->client->voir && ! $socid) $sql.= " AND ((s.rowid = sc.fk_soc AND sc.fk_user = " .$user->id.") OR (s.rowid IS NULL))";
1846
1847
		//print $sql;
1848
		$resql = $this->db->query($sql);
1849
		if ($resql)
1850
		{
1851
			$project_static = new Project($this->db);
1852
1853
			$response = new WorkboardResponse();
1854
			$response->warning_delay = $conf->projet->warning_delay / 60 / 60 / 24;
1855
			$response->label = $langs->trans("OpenedProjects");
1856
			$response->labelShort = $langs->trans("Opened");
1857
			if ($user->rights->projet->all->lire) $response->url = DOL_URL_ROOT.'/projet/list.php?search_status=1&mainmenu=project';
1858
			else $response->url = DOL_URL_ROOT.'/projet/list.php?search_project_user=-1&search_status=1&mainmenu=project';
1859
			$response->img = img_object('', "projectpub");
1860
1861
			// This assignment in condition is not a bug. It allows walking the results.
1862
			while ($obj = $this->db->fetch_object($resql))
1863
			{
1864
				$response->nbtodo++;
1865
1866
				$project_static->statut = $obj->status;
1867
				$project_static->opp_status = $obj->opp_status;
1868
				$project_static->datee = $this->db->jdate($obj->datee);
1869
1870
				if ($project_static->hasDelay()) {
1871
					$response->nbtodolate++;
1872
				}
1873
			}
1874
1875
			return $response;
1876
		} else {
1877
			$this->error = $this->db->error();
1878
			return -1;
1879
		}
1880
	}
1881
1882
1883
	/**
1884
	 * Function used to replace a thirdparty id with another one.
1885
	 *
1886
	 * @param DoliDB $db Database handler
1887
	 * @param int $origin_id Old thirdparty id
1888
	 * @param int $dest_id New thirdparty id
1889
	 * @return bool
1890
	 */
1891
	public static function replaceThirdparty(DoliDB $db, $origin_id, $dest_id)
1892
	{
1893
		$tables = array(
1894
			'projet'
1895
		);
1896
1897
		return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
1898
	}
1899
1900
1901
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1902
	/**
1903
	 * Charge indicateurs this->nb pour le tableau de bord
1904
	 *
1905
	 * @return     int         <0 if KO, >0 if OK
1906
	 */
1907
	public function load_state_board()
1908
	{
1909
		// phpcs:enable
1910
		global $user;
1911
1912
		$this->nb = array();
1913
1914
		$sql = "SELECT count(p.rowid) as nb";
1915
		$sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
1916
		$sql .= " WHERE";
1917
		$sql .= " p.entity IN (".getEntity('project').")";
1918
		if (!$user->rights->projet->all->lire)
1919
		{
1920
			$projectsListId = $this->getProjectsAuthorizedForUser($user, 0, 1);
1921
			$sql .= "AND p.rowid IN (".$projectsListId.")";
1922
		}
1923
1924
		$resql = $this->db->query($sql);
1925
		if ($resql)
1926
		{
1927
			while ($obj = $this->db->fetch_object($resql))
1928
			{
1929
				$this->nb["projects"] = $obj->nb;
1930
			}
1931
			$this->db->free($resql);
1932
			return 1;
1933
		} else {
1934
			dol_print_error($this->db);
1935
			$this->error = $this->db->error();
1936
			return -1;
1937
		}
1938
	}
1939
1940
1941
	/**
1942
	 * Is the project delayed?
1943
	 *
1944
	 * @return bool
1945
	 */
1946
	public function hasDelay()
1947
	{
1948
		global $conf;
1949
1950
		if (!($this->statut == self::STATUS_VALIDATED)) return false;
1951
		if (!$this->datee && !$this->date_end) return false;
1952
1953
		$now = dol_now();
1954
1955
		return ($this->datee ? $this->datee : $this->date_end) < ($now - $conf->projet->warning_delay);
1956
	}
1957
1958
1959
	/**
1960
	 *	Charge les informations d'ordre info dans l'objet commande
1961
	 *
1962
	 *	@param  int		$id       Id of order
1963
	 *	@return	void
1964
	 */
1965
	public function info($id)
1966
	{
1967
		$sql = 'SELECT c.rowid, datec as datec, tms as datem,';
1968
		$sql .= ' date_close as datecloture,';
1969
		$sql .= ' fk_user_creat as fk_user_author, fk_user_close as fk_use_cloture';
1970
		$sql .= ' FROM '.MAIN_DB_PREFIX.'projet as c';
1971
		$sql .= ' WHERE c.rowid = '.$id;
1972
		$result = $this->db->query($sql);
1973
		if ($result)
1974
		{
1975
			if ($this->db->num_rows($result))
1976
			{
1977
				$obj = $this->db->fetch_object($result);
1978
				$this->id = $obj->rowid;
1979
				if ($obj->fk_user_author)
1980
				{
1981
					$cuser = new User($this->db);
1982
					$cuser->fetch($obj->fk_user_author);
1983
					$this->user_creation = $cuser;
1984
				}
1985
1986
				if ($obj->fk_user_cloture)
1987
				{
1988
					$cluser = new User($this->db);
1989
					$cluser->fetch($obj->fk_user_cloture);
1990
					$this->user_cloture = $cluser;
1991
				}
1992
1993
				$this->date_creation     = $this->db->jdate($obj->datec);
1994
				$this->date_modification = $this->db->jdate($obj->datem);
1995
				$this->date_cloture      = $this->db->jdate($obj->datecloture);
1996
			}
1997
1998
			$this->db->free($result);
1999
		} else {
2000
			dol_print_error($this->db);
2001
		}
2002
	}
2003
2004
	/**
2005
	 * Sets object to supplied categories.
2006
	 *
2007
	 * Deletes object from existing categories not supplied.
2008
	 * Adds it to non existing supplied categories.
2009
	 * Existing categories are left untouch.
2010
	 *
2011
	 * @param int[]|int $categories Category or categories IDs
2012
	 * @return void
2013
	 */
2014
	public function setCategories($categories)
2015
	{
2016
		$type_categ = Categorie::TYPE_PROJECT;
2017
2018
		// Handle single category
2019
		if (!is_array($categories)) {
2020
			$categories = array($categories);
2021
		}
2022
2023
		// Get current categories
2024
		require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
2025
		$c = new Categorie($this->db);
2026
		$existing = $c->containing($this->id, $type_categ, 'id');
2027
2028
		// Diff
2029
		if (is_array($existing)) {
0 ignored issues
show
introduced by
The condition is_array($existing) is always false.
Loading history...
2030
			$to_del = array_diff($existing, $categories);
2031
			$to_add = array_diff($categories, $existing);
2032
		} else {
2033
			$to_del = array(); // Nothing to delete
2034
			$to_add = $categories;
2035
		}
2036
2037
		// Process
2038
		foreach ($to_del as $del) {
2039
			if ($c->fetch($del) > 0) {
2040
				$result = $c->del_type($this, $type_categ);
2041
				if ($result < 0) {
2042
					$this->errors = $c->errors;
2043
					$this->error = $c->error;
2044
					return -1;
2045
				}
2046
			}
2047
		}
2048
		foreach ($to_add as $add) {
2049
			if ($c->fetch($add) > 0) {
2050
				$result = $c->add_type($this, $type_categ);
2051
				if ($result < 0) {
2052
					$this->errors = $c->errors;
2053
					$this->error = $c->error;
2054
					return -1;
2055
				}
2056
			}
2057
		}
2058
2059
		return 1;
2060
	}
2061
2062
2063
	/**
2064
	 * 	Create an array of tasks of current project
2065
	 *
2066
	 *  @param  User   $user       Object user we want project allowed to
2067
	 * 	@return int		           >0 if OK, <0 if KO
2068
	 */
2069
	public function getLinesArray($user)
2070
	{
2071
		require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
2072
		$taskstatic = new Task($this->db);
2073
2074
		$this->lines = $taskstatic->getTasksArray(0, $user, $this->id, 0, 0);
2075
	}
2076
}
2077