Passed
Branch develop (07a336)
by
unknown
39:19
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 string    Name of subtable line
49
	 */
50
	public $table_element_line = 'projet_task';
51
52
	/**
53
	 * @var string    Name of field date
54
	 */
55
	public $table_element_date;
56
57
	/**
58
	 * @var string 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 $email_msgid;
118
119
	public $oldcopy;
120
121
	public $weekWorkLoad; // Used to store workload details of a projet
122
	public $weekWorkLoadPerTask; // Used to store workload details of tasks of a projet
123
124
	/**
125
	 * @var int Creation date
126
	 * @deprecated
127
	 * @see $date_c
128
	 */
129
	public $datec;
130
131
	/**
132
	 * @var int Creation date
133
	 */
134
	public $date_c;
135
136
	/**
137
	 * @var int Modification date
138
	 * @deprecated
139
	 * @see $date_m
140
	 */
141
	public $datem;
142
143
	/**
144
	 * @var int Modification date
145
	 */
146
	public $date_m;
147
148
	/**
149
	 * @var Task[]
150
	 */
151
	public $lines;
152
153
	/**
154
	 *  'type' if the field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password')
155
	 *         Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
156
	 *  'label' the translation key.
157
	 *  'enabled' is a condition when the field must be managed.
158
	 *  'position' is the sort order of field.
159
	 *  'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
160
	 *  'visible' says if field is visible in list (Examples: 0=Not visible, 1=Visible on list and create/update/view forms, 2=Visible on list only, 3=Visible on create/update/view form only (not list), 4=Visible on list and update/view form only (not create). 5=Visible on list and view only (not create/not update). Using a negative value means field is not shown by default on list but can be selected for viewing)
161
	 *  'noteditable' says if field is not editable (1 or 0)
162
	 *  'default' is a default value for creation (can still be overwrote by the Setup of Default Values if field is editable in creation form). Note: If default is set to '(PROV)' and field is 'ref', the default value will be set to '(PROVid)' where id is rowid when a new record is created.
163
	 *  'index' if we want an index in database.
164
	 *  'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommanded to name the field fk_...).
165
	 *  'searchall' is 1 if we want to search in this field when making a search from the quick search button.
166
	 *  'isameasure' must be set to 1 if you want to have a total on list for this field. Field type must be summable like integer or double(24,8).
167
	 *  'css' is the CSS style to use on field. For example: 'maxwidth200'
168
	 *  'help' is a string visible as a tooltip on field
169
	 *  'showoncombobox' if value of the field must be visible into the label of the combobox that list record
170
	 *  'disabled' is 1 if we want to have the field locked by a 'disabled' attribute. In most cases, this is never set into the definition of $fields into class, but is set dynamically by some part of code.
171
	 *  'arraykeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
172
	 *  'comment' is not used. You can store here any text of your choice. It is not used by application.
173
	 *
174
	 *  Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
175
	 */
176
177
	// BEGIN MODULEBUILDER PROPERTIES
178
	/**
179
	 * @var array  Array with all fields and their property. Do not use it as a static var. It may be modified by constructor.
180
	 */
181
	public $fields = array(
182
		'rowid' =>array('type'=>'integer', 'label'=>'ID', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>10),
183
		'ref' =>array('type'=>'varchar(50)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>1, 'showoncombobox'=>1, 'position'=>15, 'searchall'=>1),
184
		'title' =>array('type'=>'varchar(255)', 'label'=>'ProjectLabel', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'position'=>17, 'showoncombobox'=>1, 'searchall'=>1),
185
		'entity' =>array('type'=>'integer', 'label'=>'Entity', 'default'=>1, 'enabled'=>1, 'visible'=>3, 'notnull'=>1, 'position'=>19),
186
		'fk_soc' =>array('type'=>'integer', 'label'=>'Thirdparty', 'enabled'=>1, 'visible'=>0, 'position'=>20),
187
		'dateo' =>array('type'=>'date', 'label'=>'DateStart', 'enabled'=>1, 'visible'=>1, 'position'=>30),
188
		'datee' =>array('type'=>'date', 'label'=>'DateEnd', 'enabled'=>1, 'visible'=>1, 'position'=>35),
189
		'description' =>array('type'=>'text', 'label'=>'Description', 'enabled'=>1, 'visible'=>3, 'position'=>55, 'searchall'=>1),
190
		'public' =>array('type'=>'integer', 'label'=>'Visibility', 'enabled'=>1, 'visible'=>1, 'position'=>65),
191
		'fk_opp_status' =>array('type'=>'integer', 'label'=>'OpportunityStatusShort', 'enabled'=>1, 'visible'=>1, 'position'=>75),
192
		'opp_percent' =>array('type'=>'double(5,2)', 'label'=>'OpportunityProbabilityShort', 'enabled'=>1, 'visible'=>1, 'position'=>80),
193
		'note_private' =>array('type'=>'text', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>85, 'searchall'=>1),
194
		'note_public' =>array('type'=>'text', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>90, 'searchall'=>1),
195
		'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'ModelPdf', 'enabled'=>1, 'visible'=>0, 'position'=>95),
196
		'date_close' =>array('type'=>'datetime', 'label'=>'DateClosing', 'enabled'=>1, 'visible'=>0, 'position'=>105),
197
		'fk_user_close' =>array('type'=>'integer', 'label'=>'UserClosing', 'enabled'=>1, 'visible'=>0, 'position'=>110),
198
		'opp_amount' =>array('type'=>'double(24,8)', 'label'=>'OpportunityAmountShort', 'enabled'=>1, 'visible'=>1, 'position'=>115),
199
		'budget_amount' =>array('type'=>'double(24,8)', 'label'=>'Budget', 'enabled'=>1, 'visible'=>1, 'position'=>119),
200
		'usage_bill_time' =>array('type'=>'integer', 'label'=>'UsageBillTimeShort', 'enabled'=>1, 'visible'=>-1, 'position'=>130),
201
		'usage_opportunity' =>array('type'=>'integer', 'label'=>'UsageOpportunity', 'enabled'=>1, 'visible'=>-1, 'position'=>135),
202
		'usage_task' =>array('type'=>'integer', 'label'=>'UsageTasks', 'enabled'=>1, 'visible'=>-1, 'position'=>140),
203
		'usage_organize_event' =>array('type'=>'integer', 'label'=>'Usage organize event', 'enabled'=>1, 'visible'=>-1, 'position'=>145),
204
		'datec' =>array('type'=>'datetime', 'label'=>'DateCreationShort', 'enabled'=>1, 'visible'=>-2, 'position'=>200),
205
		'tms' =>array('type'=>'timestamp', 'label'=>'DateModificationShort', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>205),
206
		'fk_user_creat' =>array('type'=>'integer', 'label'=>'UserCreation', 'enabled'=>1, 'visible'=>0, 'notnull'=>1, 'position'=>210),
207
		'fk_user_modif' =>array('type'=>'integer', 'label'=>'UserModification', 'enabled'=>1, 'visible'=>0, 'position'=>215),
208
		'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>0, 'position'=>220),
209
		'fk_statut' =>array('type'=>'smallint(6)', 'label'=>'Status', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'position'=>500)
210
	);
211
	// END MODULEBUILDER PROPERTIES
212
213
	/**
214
	 * Draft status
215
	 */
216
	const STATUS_DRAFT = 0;
217
218
	/**
219
	 * Open/Validated status
220
	 */
221
	const STATUS_VALIDATED = 1;
222
223
	/**
224
	 * Closed status
225
	 */
226
	const STATUS_CLOSED = 2;
227
228
	/**
229
	 *  Constructor
230
	 *
231
	 *  @param      DoliDB		$db      Database handler
232
	 */
233
	public function __construct($db)
234
	{
235
		$this->db = $db;
236
237
		$this->statuts_short = array(0 => 'Draft', 1 => 'Opened', 2 => 'Closed');
238
		$this->statuts_long = array(0 => 'Draft', 1 => 'Opened', 2 => 'Closed');
239
240
		global $conf;
241
242
		if (empty($conf->global->MAIN_SHOW_TECHNICAL_ID)) $this->fields['rowid']['visible'] = 0;
243
244
		if (!empty($conf->global->PROJECT_USE_OPPORTUNITIES)) {
245
			$this->fields['fk_opp_status']['enabled'] = 0;
246
			$this->fields['opp_percent']['enabled'] = 0;
247
			$this->fields['opp_amount']['enabled'] = 0;
248
			$this->fields['usage_opportunity']['enabled'] = 0;
249
		}
250
251
		if (empty($conf->global->PROJECT_HIDE_TASKS)) {
252
			$this->fields['usage_bill_time']['visible'] = 0;
253
			$this->fields['usage_task']['visible'] = 0;
254
		}
255
256
		if (empty($conf->global->PROJECT_ORGANIZE_EVENTS)) {
257
			$this->fields['usage_organize_event']['visible'] = 0;
258
		}
259
	}
260
261
	/**
262
	 *    Create a project into database
263
	 *
264
	 *    @param    User	$user       	User making creation
265
	 *    @param	int		$notrigger		Disable triggers
266
	 *    @return   int         			<0 if KO, id of created project if OK
267
	 */
268
	public function create($user, $notrigger = 0)
269
	{
270
		global $conf, $langs;
271
272
		$error = 0;
273
		$ret = 0;
274
275
		$now = dol_now();
276
277
		// Clean parameters
278
		$this->note_private = dol_substr($this->note_private, 0, 65535);
279
		$this->note_public = dol_substr($this->note_public, 0, 65535);
280
281
		// Check parameters
282
		if (!trim($this->ref))
283
		{
284
			$this->error = 'ErrorFieldsRequired';
285
			dol_syslog(get_class($this)."::create error -1 ref null", LOG_ERR);
286
			return -1;
287
		}
288
		if (!empty($conf->global->PROJECT_THIRDPARTY_REQUIRED) && !($this->socid > 0))
289
		{
290
			$this->error = 'ErrorFieldsRequired';
291
			dol_syslog(get_class($this)."::create error -1 thirdparty not defined and option PROJECT_THIRDPARTY_REQUIRED is set", LOG_ERR);
292
			return -1;
293
		}
294
295
		// Create project
296
		$this->db->begin();
297
298
		$sql = "INSERT INTO ".MAIN_DB_PREFIX."projet (";
299
		$sql .= "ref";
300
		$sql .= ", title";
301
		$sql .= ", description";
302
		$sql .= ", fk_soc";
303
		$sql .= ", fk_user_creat";
304
		$sql .= ", fk_statut";
305
		$sql .= ", fk_opp_status";
306
		$sql .= ", opp_percent";
307
		$sql .= ", public";
308
		$sql .= ", datec";
309
		$sql .= ", dateo";
310
		$sql .= ", datee";
311
		$sql .= ", opp_amount";
312
		$sql .= ", budget_amount";
313
		$sql .= ", usage_opportunity";
314
		$sql .= ", usage_task";
315
		$sql .= ", usage_bill_time";
316
		$sql .= ", usage_organize_event";
317
		$sql .= ", email_msgid";
318
		$sql .= ", note_private";
319
		$sql .= ", note_public";
320
		$sql .= ", entity";
321
		$sql .= ") VALUES (";
322
		$sql .= "'".$this->db->escape($this->ref)."'";
323
		$sql .= ", '".$this->db->escape($this->title)."'";
324
		$sql .= ", '".$this->db->escape($this->description)."'";
325
		$sql .= ", ".($this->socid > 0 ? $this->socid : "null");
326
		$sql .= ", ".$user->id;
327
		$sql .= ", ".(is_numeric($this->statut) ? $this->statut : '0');
328
		$sql .= ", ".((is_numeric($this->opp_status) && $this->opp_status > 0) ? $this->opp_status : 'NULL');
329
		$sql .= ", ".(is_numeric($this->opp_percent) ? $this->opp_percent : 'NULL');
330
		$sql .= ", ".($this->public ? 1 : 0);
331
		$sql .= ", '".$this->db->idate($now)."'";
332
		$sql .= ", ".($this->date_start != '' ? "'".$this->db->idate($this->date_start)."'" : 'null');
333
		$sql .= ", ".($this->date_end != '' ? "'".$this->db->idate($this->date_end)."'" : 'null');
334
		$sql .= ", ".(strcmp($this->opp_amount, '') ? price2num($this->opp_amount) : 'null');
335
		$sql .= ", ".(strcmp($this->budget_amount, '') ? price2num($this->budget_amount) : 'null');
336
		$sql .= ", ".($this->usage_opportunity ? 1 : 0);
337
		$sql .= ", ".($this->usage_task ? 1 : 0);
338
		$sql .= ", ".($this->usage_bill_time ? 1 : 0);
339
		$sql .= ", ".($this->usage_organize_event ? 1 : 0);
340
		$sql .= ", ".($this->email_msgid ? "'".$this->db->escape($this->email_msgid)."'" : 'null');
341
		$sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : 'null');
342
		$sql .= ", ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : 'null');
343
		$sql .= ", ".$conf->entity;
344
		$sql .= ")";
345
346
		dol_syslog(get_class($this)."::create", LOG_DEBUG);
347
		$resql = $this->db->query($sql);
348
		if ($resql)
349
		{
350
			$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."projet");
351
			$ret = $this->id;
352
353
			if (!$notrigger)
354
			{
355
				// Call trigger
356
				$result = $this->call_trigger('PROJECT_CREATE', $user);
357
				if ($result < 0) { $error++; }
358
				// End call triggers
359
			}
360
		} else {
361
			$this->error = $this->db->lasterror();
362
			$this->errno = $this->db->lasterrno();
363
			$error++;
364
		}
365
366
		// Update extrafield
367
		if (!$error) {
368
			$result = $this->insertExtraFields();
369
			if ($result < 0)
370
			{
371
				$error++;
372
			}
373
		}
374
375
		if (!$error && !empty($conf->global->MAIN_DISABLEDRAFTSTATUS))
376
		{
377
			$res = $this->setValid($user);
378
			if ($res < 0) $error++;
379
		}
380
381
		if (!$error)
382
		{
383
			$this->db->commit();
384
			return $ret;
385
		} else {
386
			$this->db->rollback();
387
			return -1;
388
		}
389
	}
390
391
	/**
392
	 * Update a project
393
	 *
394
	 * @param  User		$user       User object of making update
395
	 * @param  int		$notrigger  1=Disable all triggers
396
	 * @return int                  <=0 if KO, >0 if OK
397
	 */
398
	public function update($user, $notrigger = 0)
399
	{
400
		global $langs, $conf;
401
402
		$error = 0;
403
404
		// Clean parameters
405
		$this->title = trim($this->title);
406
		$this->description = trim($this->description);
407
		if ($this->opp_amount < 0) $this->opp_amount = '';
408
		if ($this->opp_percent < 0) $this->opp_percent = '';
409
		if ($this->date_end && $this->date_end < $this->date_start)
410
		{
411
			$this->error = $langs->trans("ErrorDateEndLowerThanDateStart");
412
			$this->errors[] = $this->error;
413
			$this->db->rollback();
414
			dol_syslog(get_class($this)."::update error -3 ".$this->error, LOG_ERR);
415
			return -3;
416
		}
417
418
		if (dol_strlen(trim($this->ref)) > 0)
419
		{
420
			$this->db->begin();
421
422
			$sql = "UPDATE ".MAIN_DB_PREFIX."projet SET";
423
			$sql .= " ref='".$this->db->escape($this->ref)."'";
424
			$sql .= ", title = '".$this->db->escape($this->title)."'";
425
			$sql .= ", description = '".$this->db->escape($this->description)."'";
426
			$sql .= ", fk_soc = ".($this->socid > 0 ? $this->socid : "null");
427
			$sql .= ", fk_statut = ".$this->statut;
428
			$sql .= ", fk_opp_status = ".((is_numeric($this->opp_status) && $this->opp_status > 0) ? $this->opp_status : 'null');
429
			$sql .= ", opp_percent = ".((is_numeric($this->opp_percent) && $this->opp_percent != '') ? $this->opp_percent : 'null');
430
			$sql .= ", public = ".($this->public ? 1 : 0);
431
			$sql .= ", datec=".($this->date_c != '' ? "'".$this->db->idate($this->date_c)."'" : 'null');
432
			$sql .= ", dateo=".($this->date_start != '' ? "'".$this->db->idate($this->date_start)."'" : 'null');
433
			$sql .= ", datee=".($this->date_end != '' ? "'".$this->db->idate($this->date_end)."'" : 'null');
434
			$sql .= ", date_close=".($this->date_close != '' ? "'".$this->db->idate($this->date_close)."'" : 'null');
435
			$sql .= ", fk_user_close=".($this->fk_user_close > 0 ? $this->fk_user_close : "null");
436
			$sql .= ", opp_amount = ".(strcmp($this->opp_amount, '') ? price2num($this->opp_amount) : "null");
437
			$sql .= ", budget_amount = ".(strcmp($this->budget_amount, '') ? price2num($this->budget_amount) : "null");
438
			$sql .= ", fk_user_modif = ".$user->id;
439
			$sql .= ", usage_opportunity = ".($this->usage_opportunity ? 1 : 0);
440
			$sql .= ", usage_task = ".($this->usage_task ? 1 : 0);
441
			$sql .= ", usage_bill_time = ".($this->usage_bill_time ? 1 : 0);
442
			$sql .= ", usage_organize_event = ".($this->usage_organize_event ? 1 : 0);
443
			$sql .= " WHERE rowid = ".$this->id;
444
445
			dol_syslog(get_class($this)."::update", LOG_DEBUG);
446
			$resql = $this->db->query($sql);
447
			if ($resql)
448
			{
449
				// Update extrafield
450
				if (!$error)
451
				{
452
					$result = $this->insertExtraFields();
453
					if ($result < 0)
454
					{
455
						$error++;
456
					}
457
				}
458
459
				if (!$error && !$notrigger)
460
				{
461
					// Call trigger
462
					$result = $this->call_trigger('PROJECT_MODIFY', $user);
463
					if ($result < 0) { $error++; }
464
					// End call triggers
465
				}
466
467
				if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref))
468
				{
469
					// We remove directory
470
					if ($conf->projet->dir_output)
471
					{
472
						$olddir = $conf->projet->dir_output."/".dol_sanitizeFileName($this->oldcopy->ref);
473
						$newdir = $conf->projet->dir_output."/".dol_sanitizeFileName($this->ref);
474
						if (file_exists($olddir))
475
						{
476
							include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
477
							$res = @rename($olddir, $newdir);
478
							if (!$res)
479
							{
480
								$langs->load("errors");
481
								$this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
482
								$error++;
483
							}
484
						}
485
					}
486
				}
487
				if (!$error)
488
				{
489
					$this->db->commit();
490
					$result = 1;
491
				} else {
492
					$this->db->rollback();
493
					$result = -1;
494
				}
495
			} else {
496
				$this->error = $this->db->lasterror();
497
				$this->errors[] = $this->error;
498
				$this->db->rollback();
499
				if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS')
500
				{
501
					$result = -4;
502
				} else {
503
					$result = -2;
504
				}
505
				dol_syslog(get_class($this)."::update error ".$result." ".$this->error, LOG_ERR);
506
			}
507
		} else {
508
			dol_syslog(get_class($this)."::update ref null");
509
			$result = -1;
510
		}
511
512
		return $result;
513
	}
514
515
	/**
516
	 * 	Get object from database
517
	 *
518
	 * 	@param      int		$id       		Id of object to load
519
	 * 	@param		string	$ref			Ref of project
520
	 * 	@param		string	$ref_ext		Ref ext of project
521
	 *  @param		string	$email_msgid	Email msgid
522
	 * 	@return     int      		   		>0 if OK, 0 if not found, <0 if KO
523
	 */
524
	public function fetch($id, $ref = '', $ref_ext = '', $email_msgid = '')
525
	{
526
		global $conf;
527
528
		if (empty($id) && empty($ref)) return -1;
529
530
		$sql = "SELECT rowid, entity, ref, title, description, public, datec, opp_amount, budget_amount,";
531
		$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,";
532
		$sql .= " note_private, note_public, model_pdf, usage_opportunity, usage_task, usage_bill_time, usage_organize_event, email_msgid";
533
		$sql .= " FROM ".MAIN_DB_PREFIX."projet";
534
		if (!empty($id))
535
		{
536
			$sql .= " WHERE rowid = ".$id;
537
		} else {
538
			$sql .= " WHERE entity IN (".getEntity('project').")";
539
			if (!empty($ref)) {
540
				$sql .= " AND ref = '".$this->db->escape($ref)."'";
541
			} elseif (!empty($ref_ext)) {
542
				$sql .= " AND ref_ext = '".$this->db->escape($ref_ext)."'";
543
			} else {
544
				$sql .= " AND email_msgid = '".$this->db->escape($email_msgid)."'";
545
			}
546
		}
547
548
		dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
549
		$resql = $this->db->query($sql);
550
		if ($resql)
551
		{
552
			$num_rows = $this->db->num_rows($resql);
553
554
			if ($num_rows)
555
			{
556
				$obj = $this->db->fetch_object($resql);
557
558
				$this->id = $obj->rowid;
559
				$this->entity = $obj->entity;
560
				$this->ref = $obj->ref;
561
				$this->title = $obj->title;
562
				$this->description = $obj->description;
563
				$this->date_c = $this->db->jdate($obj->datec);
564
				$this->datec = $this->db->jdate($obj->datec); // TODO deprecated
565
				$this->date_m = $this->db->jdate($obj->tms);
566
				$this->datem = $this->db->jdate($obj->tms); // TODO deprecated
567
				$this->date_start = $this->db->jdate($obj->dateo);
568
				$this->date_end = $this->db->jdate($obj->datee);
569
				$this->date_close = $this->db->jdate($obj->date_close);
570
				$this->note_private = $obj->note_private;
571
				$this->note_public = $obj->note_public;
572
				$this->socid = $obj->fk_soc;
573
				$this->user_author_id = $obj->fk_user_creat;
574
				$this->user_modification_id = $obj->fk_user_modif;
575
				$this->user_close_id = $obj->fk_user_close;
576
				$this->public = $obj->public;
577
				$this->statut = $obj->status; // deprecated
578
				$this->status = $obj->status;
579
				$this->opp_status = $obj->fk_opp_status;
580
				$this->opp_amount	= $obj->opp_amount;
581
				$this->opp_percent = $obj->opp_percent;
582
				$this->budget_amount = $obj->budget_amount;
583
				$this->model_pdf = $obj->model_pdf;
584
				$this->modelpdf = $obj->model_pdf; // deprecated
585
				$this->usage_opportunity = (int) $obj->usage_opportunity;
586
				$this->usage_task = (int) $obj->usage_task;
587
				$this->usage_bill_time = (int) $obj->usage_bill_time;
588
				$this->usage_organize_event = (int) $obj->usage_organize_event;
589
				$this->email_msgid = $obj->email_msgid;
590
591
				$this->db->free($resql);
592
593
				// Retrieve all extrafield
594
				// fetch optionals attributes and labels
595
				$this->fetch_optionals();
596
597
				return 1;
598
			}
599
600
			$this->db->free($resql);
601
602
			return 0;
603
		} else {
604
			$this->error = $this->db->lasterror();
605
			return -1;
606
		}
607
	}
608
609
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
610
	/**
611
	 * 	Return list of elements for type, linked to a project
612
	 *
613
	 * 	@param		string		$type			'propal','order','invoice','order_supplier','invoice_supplier',...
614
	 * 	@param		string		$tablename		name of table associated of the type
615
	 * 	@param		string		$datefieldname	name of date field for filter
616
	 *  @param		int			$dates			Start date
617
	 *  @param		int			$datee			End date
618
	 *	@param		string		$projectkey		Equivalent key  to fk_projet for actual type
619
	 * 	@return		mixed						Array list of object ids linked to project, < 0 or string if error
620
	 */
621
	public function get_element_list($type, $tablename, $datefieldname = '', $dates = '', $datee = '', $projectkey = 'fk_projet')
622
	{
623
		// phpcs:enable
624
625
		global $hookmanager;
626
627
		$elements = array();
628
629
		if ($this->id <= 0) return $elements;
630
631
		$ids = $this->id;
632
633
		if ($type == 'agenda')
634
		{
635
			$sql = "SELECT id as rowid FROM ".MAIN_DB_PREFIX."actioncomm WHERE fk_project IN (".$ids.") AND entity IN (".getEntity('agenda').")";
636
		} elseif ($type == 'expensereport')
637
		{
638
			$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.")";
639
		} elseif ($type == 'project_task')
640
		{
641
			$sql = "SELECT DISTINCT pt.rowid FROM ".MAIN_DB_PREFIX."projet_task as pt WHERE pt.fk_projet IN (".$ids.")";
642
		} elseif ($type == 'project_task_time')	// Case we want to duplicate line foreach user
643
		{
644
			$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.")";
645
		} elseif ($type == 'stock_mouvement')
646
		{
647
			$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";
648
		} elseif ($type == 'loan')
649
		{
650
			$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.")";
651
		} else {
652
			$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX.$tablename." WHERE ".$projectkey." IN (".$ids.") AND entity IN (".getEntity($type).")";
653
		}
654
655
		if ($dates > 0 && $type == 'loan') {
656
			$sql .= " AND (dateend > '".$this->db->idate($dates)."' OR dateend IS NULL)";
657
		} elseif ($dates > 0 && ($type != 'project_task'))	// For table project_taks, we want the filter on date apply on project_time_spent table
658
		{
659
			if (empty($datefieldname) && !empty($this->table_element_date)) $datefieldname = $this->table_element_date;
660
			if (empty($datefieldname)) return 'Error this object has no date field defined';
661
			$sql .= " AND (".$datefieldname." >= '".$this->db->idate($dates)."' OR ".$datefieldname." IS NULL)";
662
		}
663
664
		if ($datee > 0 && $type == 'loan') {
665
			$sql .= " AND (datestart < '".$this->db->idate($datee)."' OR datestart IS NULL)";
666
		} elseif ($datee > 0 && ($type != 'project_task'))	// For table project_taks, we want the filter on date apply on project_time_spent table
667
		{
668
			if (empty($datefieldname) && !empty($this->table_element_date)) $datefieldname = $this->table_element_date;
669
			if (empty($datefieldname)) return 'Error this object has no date field defined';
670
			$sql .= " AND (".$datefieldname." <= '".$this->db->idate($datee)."' OR ".$datefieldname." IS NULL)";
671
		}
672
673
		$parameters = array(
674
			'sql'=>$sql,
675
			'type' => $type,
676
			'tablename' => $tablename,
677
			'datefieldname'  => $datefieldname,
678
			'dates' => $dates,
679
			'datee' => $datee,
680
			'fk_projet' => $projectkey
681
		);
682
		$reshook = $hookmanager->executeHooks('getElementList', $parameters, $object, $action);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $action seems to be never defined.
Loading history...
Comprehensibility Best Practice introduced by
The variable $object seems to be never defined.
Loading history...
683
		if ($reshook > 0) $sql = $hookmanager->resPrint;
684
		else $sql .= $hookmanager->resPrint;
685
686
		if (!$sql) return -1;
687
688
		//print $sql;
689
		dol_syslog(get_class($this)."::get_element_list", LOG_DEBUG);
690
		$result = $this->db->query($sql);
691
		if ($result)
692
		{
693
			$nump = $this->db->num_rows($result);
694
			if ($nump)
695
			{
696
				$i = 0;
697
				while ($i < $nump)
698
				{
699
					$obj = $this->db->fetch_object($result);
700
701
					$elements[$i] = $obj->rowid.(empty($obj->fk_user) ? '' : '_'.$obj->fk_user);
702
703
					$i++;
704
				}
705
				$this->db->free($result);
706
			}
707
708
			/* Return array even if empty*/
709
			return $elements;
710
		} else {
711
			dol_print_error($this->db);
712
		}
713
	}
714
715
	/**
716
	 *    Delete a project from database
717
	 *
718
	 *    @param       User		$user            User
719
	 *    @param       int		$notrigger       Disable triggers
720
	 *    @return      int       			      <0 if KO, 0 if not possible, >0 if OK
721
	 */
722
	public function delete($user, $notrigger = 0)
723
	{
724
		global $langs, $conf;
725
		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
726
727
		$error = 0;
728
729
		$this->db->begin();
730
731
		if (!$error)
732
		{
733
			// Delete linked contacts
734
			$res = $this->delete_linked_contact();
735
			if ($res < 0)
736
			{
737
				$this->error = 'ErrorFailToDeleteLinkedContact';
738
				//$error++;
739
				$this->db->rollback();
740
				return 0;
741
			}
742
		}
743
744
		// Set fk_projet into elements to null
745
		$listoftables = array(
746
			'propal'=>'fk_projet', 'commande'=>'fk_projet', 'facture'=>'fk_projet',
747
			'supplier_proposal'=>'fk_projet', 'commande_fournisseur'=>'fk_projet', 'facture_fourn'=>'fk_projet',
748
			'expensereport_det'=>'fk_projet', 'contrat'=>'fk_projet', 'fichinter'=>'fk_projet', 'don'=>'fk_projet',
749
			'actioncomm'=>'fk_project', 'mrp_mo'=>'fk_project'
750
		);
751
		foreach ($listoftables as $key => $value)
752
		{
753
			$sql = "UPDATE ".MAIN_DB_PREFIX.$key." SET ".$value." = NULL where ".$value." = ".$this->id;
754
			$resql = $this->db->query($sql);
755
			if (!$resql)
756
			{
757
				$this->errors[] = $this->db->lasterror();
758
				$error++;
759
				break;
760
			}
761
		}
762
763
		// Remove linked categories.
764
		if (!$error) {
765
			$sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_project";
766
			$sql .= " WHERE fk_project = ".$this->id;
767
768
			$result = $this->db->query($sql);
769
			if (!$result) {
770
				$error++;
771
				$this->errors[] = $this->db->lasterror();
772
			}
773
		}
774
775
		// Fetch tasks
776
		$this->getLinesArray($user);
777
778
		// Delete tasks
779
		$ret = $this->deleteTasks($user);
780
		if ($ret < 0) $error++;
781
782
783
		// Delete all child tables
784
		if (!$error) {
785
			$elements = array('categorie_project'); // elements to delete. TODO Make goodway to delete
786
			foreach ($elements as $table)
787
			{
788
				if (!$error) {
789
					$sql = "DELETE FROM ".MAIN_DB_PREFIX.$table;
790
					$sql .= " WHERE fk_project = ".$this->id;
791
792
					$result = $this->db->query($sql);
793
					if (!$result) {
794
						$error++;
795
						$this->errors[] = $this->db->lasterror();
796
					}
797
				}
798
			}
799
		}
800
801
		if (!$error)
802
		{
803
			$sql = "DELETE FROM ".MAIN_DB_PREFIX."projet_extrafields";
804
			$sql .= " WHERE fk_object=".$this->id;
805
806
			$resql = $this->db->query($sql);
807
			if (!$resql)
808
			{
809
				$this->errors[] = $this->db->lasterror();
810
				$error++;
811
			}
812
		}
813
814
		// Delete project
815
		if (!$error)
816
		{
817
			$sql = "DELETE FROM ".MAIN_DB_PREFIX."projet";
818
			$sql .= " WHERE rowid=".$this->id;
819
820
			$resql = $this->db->query($sql);
821
			if (!$resql)
822
			{
823
				$this->errors[] = $langs->trans("CantRemoveProject", $langs->transnoentitiesnoconv("ProjectOverview"));
824
				$error++;
825
			}
826
		}
827
828
829
830
		if (empty($error)) {
831
			// We remove directory
832
			$projectref = dol_sanitizeFileName($this->ref);
833
			if ($conf->projet->dir_output) {
834
				$dir = $conf->projet->dir_output."/".$projectref;
835
				if (file_exists($dir)) {
836
					$res = @dol_delete_dir_recursive($dir);
837
					if (!$res) {
838
						$this->errors[] = 'ErrorFailToDeleteDir';
839
						$error++;
840
					}
841
				}
842
			}
843
844
			if (!$notrigger)
845
			{
846
				// Call trigger
847
				$result = $this->call_trigger('PROJECT_DELETE', $user);
848
849
				if ($result < 0) {
850
					$error++;
851
				}
852
				// End call triggers
853
			}
854
		}
855
856
		if (empty($error))
857
		{
858
			$this->db->commit();
859
			return 1;
860
		} else {
861
			foreach ($this->errors as $errmsg)
862
			{
863
				dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
864
				$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
865
			}
866
			dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
867
			$this->db->rollback();
868
			return -1;
869
		}
870
	}
871
872
	/**
873
	 * Return the count of a type of linked elements of this project
874
	 *
875
	 * @param string	$type			The type of the linked elements (e.g. 'propal', 'order', 'invoice', 'order_supplier', 'invoice_supplier')
876
	 * @param string	$tablename		The name of table associated of the type
877
	 * @param string	$projectkey 	(optional) Equivalent key to fk_projet for actual type
878
	 * @return integer					The count of the linked elements (the count is zero on request error too)
879
	 */
880
	public function getElementCount($type, $tablename, $projectkey = 'fk_projet')
881
	{
882
		if ($this->id <= 0) return 0;
883
884
		if ($type == 'agenda') {
885
			$sql = "SELECT COUNT(id) as nb FROM ".MAIN_DB_PREFIX."actioncomm WHERE fk_project = ".$this->id." AND entity IN (".getEntity('agenda').")";
886
		} elseif ($type == 'expensereport') {
887
			$sql = "SELECT COUNT(ed.rowid) as nb 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 = ".$this->id;
888
		} elseif ($type == 'project_task') {
889
			$sql = "SELECT DISTINCT COUNT(pt.rowid) as nb FROM ".MAIN_DB_PREFIX."projet_task as pt WHERE pt.fk_projet = ".$this->id;
890
		} elseif ($type == 'project_task_time') {	// Case we want to duplicate line foreach user
891
			$sql = "SELECT DISTINCT COUNT(pt.rowid) as nb 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 = ".$this->id;
892
		} elseif ($type == 'stock_mouvement') {
893
			$sql = 'SELECT COUNT(ms.rowid) as nb 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 = ".$this->id." AND ms.type_mouvement = 1";
894
		} elseif ($type == 'loan') {
895
			$sql = 'SELECT COUNT(l.rowid) as nb FROM '.MAIN_DB_PREFIX."loan as l WHERE l.entity IN (".getEntity('loan').") AND l.fk_projet = ".$this->id;
896
		} else {
897
			$sql = "SELECT COUNT(rowid) as nb FROM ".MAIN_DB_PREFIX.$tablename." WHERE ".$projectkey." = ".$this->id." AND entity IN (".getEntity($type).")";
898
		}
899
900
		$result = $this->db->query($sql);
901
902
		if (!$result) return 0;
903
904
		$obj = $this->db->fetch_object($result);
905
906
		$this->db->free($result);
907
908
		return $obj->nb;
909
	}
910
911
	/**
912
	 * 		Delete tasks with no children first, then task with children recursively
913
	 *
914
	 *  	@param     	User		$user		User
915
	 *		@return		int				<0 if KO, 1 if OK
916
	 */
917
	public function deleteTasks($user)
918
	{
919
		$countTasks = count($this->lines);
920
		$deleted = false;
921
		if ($countTasks)
922
		{
923
			foreach ($this->lines as $task)
924
			{
925
				if ($task->hasChildren() <= 0) {		// If there is no children (or error to detect them)
926
					$deleted = true;
927
					$ret = $task->delete($user);
928
					if ($ret <= 0)
929
					{
930
						$this->errors[] = $this->db->lasterror();
931
						return -1;
932
					}
933
				}
934
			}
935
		}
936
		$this->getLinesArray($user);
937
		if ($deleted && count($this->lines) < $countTasks)
938
		{
939
			if (count($this->lines)) $this->deleteTasks($this->lines);
940
		}
941
942
		return 1;
943
	}
944
945
	/**
946
	 * 		Validate a project
947
	 *
948
	 * 		@param		User	$user		   User that validate
949
	 *      @param      int     $notrigger     1=Disable triggers
950
	 * 		@return		int					   <0 if KO, >0 if OK
951
	 */
952
	public function setValid($user, $notrigger = 0)
953
	{
954
		global $langs, $conf;
955
956
		$error = 0;
957
958
		if ($this->statut != 1)
959
		{
960
			// Check parameters
961
			if (preg_match('/^'.preg_quote($langs->trans("CopyOf").' ').'/', $this->title))
962
			{
963
				$this->error = $langs->trans("ErrorFieldFormat", $langs->transnoentities("Label")).'. '.$langs->trans('RemoveString', $langs->transnoentitiesnoconv("CopyOf"));
964
				return -1;
965
			}
966
967
			$this->db->begin();
968
969
			$sql = "UPDATE ".MAIN_DB_PREFIX."projet";
970
			$sql .= " SET fk_statut = 1";
971
			$sql .= " WHERE rowid = ".$this->id;
972
			$sql .= " AND entity = ".$conf->entity;
973
974
			dol_syslog(get_class($this)."::setValid", LOG_DEBUG);
975
			$resql = $this->db->query($sql);
976
			if ($resql)
977
			{
978
				// Call trigger
979
				if (empty($notrigger))
980
				{
981
					$result = $this->call_trigger('PROJECT_VALIDATE', $user);
982
					if ($result < 0) { $error++; }
983
					// End call triggers
984
				}
985
986
				if (!$error)
987
				{
988
					$this->statut = 1;
989
					$this->db->commit();
990
					return 1;
991
				} else {
992
					$this->db->rollback();
993
					$this->error = join(',', $this->errors);
994
					dol_syslog(get_class($this)."::setValid ".$this->error, LOG_ERR);
995
					return -1;
996
				}
997
			} else {
998
				$this->db->rollback();
999
				$this->error = $this->db->lasterror();
1000
				return -1;
1001
			}
1002
		}
1003
	}
1004
1005
	/**
1006
	 * 		Close a project
1007
	 *
1008
	 * 		@param		User	$user		User that close project
1009
	 * 		@return		int					<0 if KO, 0 if already closed, >0 if OK
1010
	 */
1011
	public function setClose($user)
1012
	{
1013
		global $langs, $conf;
1014
1015
		$now = dol_now();
1016
1017
		$error = 0;
1018
1019
		if ($this->statut != self::STATUS_CLOSED)
1020
		{
1021
			$this->db->begin();
1022
1023
			$sql = "UPDATE ".MAIN_DB_PREFIX."projet";
1024
			$sql .= " SET fk_statut = ".self::STATUS_CLOSED.", fk_user_close = ".$user->id.", date_close = '".$this->db->idate($now)."'";
1025
			$sql .= " WHERE rowid = ".$this->id;
1026
			$sql .= " AND fk_statut = ".self::STATUS_VALIDATED;
1027
1028
			if (!empty($conf->global->PROJECT_USE_OPPORTUNITIES))
1029
			{
1030
				// TODO What to do if fk_opp_status is not code 'WON' or 'LOST'
1031
			}
1032
1033
			dol_syslog(get_class($this)."::setClose", LOG_DEBUG);
1034
			$resql = $this->db->query($sql);
1035
			if ($resql)
1036
			{
1037
				// Call trigger
1038
				$result = $this->call_trigger('PROJECT_CLOSE', $user);
1039
				if ($result < 0) { $error++; }
1040
				// End call triggers
1041
1042
				if (!$error)
1043
				{
1044
					$this->statut = 2;
1045
					$this->db->commit();
1046
					return 1;
1047
				} else {
1048
					$this->db->rollback();
1049
					$this->error = join(',', $this->errors);
1050
					dol_syslog(get_class($this)."::setClose ".$this->error, LOG_ERR);
1051
					return -1;
1052
				}
1053
			} else {
1054
				$this->db->rollback();
1055
				$this->error = $this->db->lasterror();
1056
				return -1;
1057
			}
1058
		}
1059
1060
		return 0;
1061
	}
1062
1063
	/**
1064
	 *  Return status label of object
1065
	 *
1066
	 *  @param  int			$mode       0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto
1067
	 * 	@return string      			Label
1068
	 */
1069
	public function getLibStatut($mode = 0)
1070
	{
1071
		return $this->LibStatut(isset($this->statut) ? $this->statut : $this->status, $mode);
1072
	}
1073
1074
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1075
	/**
1076
	 *  Renvoi status label for a status
1077
	 *
1078
	 *  @param	int		$status     id status
1079
	 *  @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
1080
	 * 	@return string				Label
1081
	 */
1082
	public function LibStatut($status, $mode = 0)
1083
	{
1084
		// phpcs:enable
1085
		global $langs;
1086
1087
		$statustrans = array(
1088
			0 => 'status0',
1089
			1 => 'status4',
1090
			2 => 'status6',
1091
		);
1092
1093
		$statusClass = 'status0';
1094
		if (!empty($statustrans[$status])) {
1095
			$statusClass = $statustrans[$status];
1096
		}
1097
1098
		return dolGetStatus($langs->trans($this->statuts_long[$status]), $langs->trans($this->statuts_short[$status]), '', $statusClass, $mode);
1099
	}
1100
1101
	/**
1102
	 * 	Return clickable name (with picto eventually)
1103
	 *
1104
	 * 	@param	int		$withpicto		          0=No picto, 1=Include picto into link, 2=Only picto
1105
	 * 	@param	string	$option			          Variant where the link point to ('', 'nolink')
1106
	 * 	@param	int		$addlabel		          0=Default, 1=Add label into string, >1=Add first chars into string
1107
	 *  @param	string	$moreinpopup	          Text to add into popup
1108
	 *  @param	string	$sep			          Separator between ref and label if option addlabel is set
1109
	 *  @param	int   	$notooltip		          1=Disable tooltip
1110
	 *  @param  int     $save_lastsearch_value    -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
1111
	 *  @param	string	$morecss				  More css on a link
1112
	 * 	@return	string					          String with URL
1113
	 */
1114
	public function getNomUrl($withpicto = 0, $option = '', $addlabel = 0, $moreinpopup = '', $sep = ' - ', $notooltip = 0, $save_lastsearch_value = -1, $morecss = '')
1115
	{
1116
		global $conf, $langs, $user, $hookmanager;
1117
1118
		if (!empty($conf->dol_no_mouse_hover)) $notooltip = 1; // Force disable tooltips
1119
1120
		$result = '';
1121
		if (!empty($conf->global->PROJECT_OPEN_ALWAYS_ON_TAB)) {
1122
			$option = $conf->global->PROJECT_OPEN_ALWAYS_ON_TAB;
1123
		}
1124
1125
		$label = '';
1126
		if ($option != 'nolink') $label = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Project").'</u>';
1127
		if (isset($this->status)) {
1128
			$label .= ' '.$this->getLibStatut(5);
1129
		}
1130
		$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
1131
		$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
1132
		if (isset($this->public)) {
1133
			$label .= '<br><b>'.$langs->trans("Visibility").":</b> ".($this->public ? $langs->trans("SharedProject") : $langs->trans("PrivateProject"));
1134
		}
1135
		if (!empty($this->thirdparty_name)) {
1136
			$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
1137
		}
1138
		if (!empty($this->dateo)) {
0 ignored issues
show
Bug introduced by
The property dateo does not exist on Project. Did you mean datem?
Loading history...
1139
			$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
1140
		}
1141
		if (!empty($this->datee)) {
1142
			$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
1143
		}
1144
		if ($moreinpopup) $label .= '<br>'.$moreinpopup;
1145
1146
		$url = '';
1147
		if ($option != 'nolink')
1148
		{
1149
			if (preg_match('/\.php$/', $option)) {
1150
				$url = dol_buildpath($option, 1).'?id='.$this->id;
1151
			} elseif ($option == 'task')
1152
			{
1153
				$url = DOL_URL_ROOT.'/projet/tasks.php?id='.$this->id;
1154
			} elseif ($option == 'preview')
1155
			{
1156
				$url = DOL_URL_ROOT.'/projet/element.php?id='.$this->id;
1157
			} else {
1158
				$url = DOL_URL_ROOT.'/projet/card.php?id='.$this->id;
1159
			}
1160
			// Add param to save lastsearch_values or not
1161
			$add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1162
			if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) $add_save_lastsearch_values = 1;
1163
			if ($add_save_lastsearch_values) $url .= '&save_lastsearch_values=1';
1164
		}
1165
1166
		$linkclose = '';
1167
		if (empty($notooltip) && $user->rights->projet->lire)
1168
		{
1169
			if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER))
1170
			{
1171
				$label = $langs->trans("ShowProject");
1172
				$linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1173
			}
1174
			$linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
1175
			$linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"';
1176
		}
1177
		else $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
1178
1179
		$picto = 'projectpub';
1180
		if (!$this->public) $picto = 'project';
1181
1182
		$linkstart = '<a href="'.$url.'"';
1183
		$linkstart .= $linkclose.'>';
1184
		$linkend = '</a>';
1185
1186
		$result .= $linkstart;
1187
		if ($withpicto) $result .= img_object(($notooltip ? '' : $label), $picto, ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
1188
		if ($withpicto != 2) $result .= $this->ref;
1189
		$result .= $linkend;
1190
		if ($withpicto != 2) $result .= (($addlabel && $this->title) ? $sep.dol_trunc($this->title, ($addlabel > 1 ? $addlabel : 0)) : '');
1191
1192
		global $action;
1193
		$hookmanager->initHooks(array('projectdao'));
1194
		$parameters = array('id'=>$this->id, 'getnomurl'=>$result);
1195
		$reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1196
		if ($reshook > 0) $result = $hookmanager->resPrint;
1197
		else $result .= $hookmanager->resPrint;
1198
1199
		return $result;
1200
	}
1201
1202
	/**
1203
	 *  Initialise an instance with random values.
1204
	 *  Used to build previews or test instances.
1205
	 * 	id must be 0 if object instance is a specimen.
1206
	 *
1207
	 *  @return	void
1208
	 */
1209
	public function initAsSpecimen()
1210
	{
1211
		global $user, $langs, $conf;
1212
1213
		$now = dol_now();
1214
1215
		// Initialise parameters
1216
		$this->id = 0;
1217
		$this->ref = 'SPECIMEN';
1218
		$this->specimen = 1;
1219
		$this->socid = 1;
1220
		$this->date_c = $now;
1221
		$this->date_m = $now;
1222
		$this->date_start = $now;
1223
		$this->date_end = $now + (3600 * 24 * 365);
1224
		$this->note_public = 'SPECIMEN';
1225
		$this->fk_ele = 20000;
1226
		$this->opp_amount = 20000;
1227
		$this->budget_amount = 10000;
1228
1229
		$this->usage_opportunity = 1;
1230
		$this->usage_task = 1;
1231
		$this->usage_bill_time = 1;
1232
		$this->usage_organize_event = 1;
1233
1234
		/*
1235
		 $nbp = mt_rand(1, 9);
1236
		 $xnbp = 0;
1237
		 while ($xnbp < $nbp)
1238
		 {
1239
		 $line = new Task($this->db);
1240
		 $line->fk_project = 0;
1241
		 $line->label = $langs->trans("Label") . " " . $xnbp;
1242
		 $line->description = $langs->trans("Description") . " " . $xnbp;
1243
1244
		 $this->lines[]=$line;
1245
		 $xnbp++;
1246
		 }
1247
		 */
1248
	}
1249
1250
	/**
1251
	 * 	Check if user has permission on current project
1252
	 *
1253
	 * 	@param	User	$user		Object user to evaluate
1254
	 * 	@param  string	$mode		Type of permission we want to know: 'read', 'write'
1255
	 * 	@return	int					>0 if user has permission, <0 if user has no permission
1256
	 */
1257
	public function restrictedProjectArea(User $user, $mode = 'read')
1258
	{
1259
		// To verify role of users
1260
		$userAccess = 0;
1261
		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)))
1262
		{
1263
			$userAccess = 1;
1264
		} elseif ($this->public && (($mode == 'read' && !empty($user->rights->projet->lire)) || ($mode == 'write' && !empty($user->rights->projet->creer)) || ($mode == 'delete' && !empty($user->rights->projet->supprimer))))
1265
		{
1266
			$userAccess = 1;
1267
		} else {	// No access due to permission to read all projects, so we check if we are a contact of project
1268
			foreach (array('internal', 'external') as $source)
1269
			{
1270
				$userRole = $this->liste_contact(4, $source);
1271
				$num = count($userRole);
1272
1273
				$nblinks = 0;
1274
				while ($nblinks < $num)
1275
				{
1276
					if ($source == 'internal' && $user->id == $userRole[$nblinks]['id'])	// $userRole[$nblinks]['id'] is id of user (llx_user) for internal contacts
1277
					{
1278
						if ($mode == 'read' && $user->rights->projet->lire)      $userAccess++;
1279
						if ($mode == 'write' && $user->rights->projet->creer)     $userAccess++;
1280
						if ($mode == 'delete' && $user->rights->projet->supprimer) $userAccess++;
1281
					}
1282
					if ($source == 'external' && $user->socid > 0 && $user->socid == $userRole[$nblinks]['socid'])	// $userRole[$nblinks]['id'] is id of contact (llx_socpeople) or external contacts
1283
					{
1284
						if ($mode == 'read' && $user->rights->projet->lire)      $userAccess++;
1285
						if ($mode == 'write' && $user->rights->projet->creer)     $userAccess++;
1286
						if ($mode == 'delete' && $user->rights->projet->supprimer) $userAccess++;
1287
					}
1288
					$nblinks++;
1289
				}
1290
			}
1291
			//if (empty($nblinks))	// If nobody has permission, we grant creator
1292
			//{
1293
			//	if ((!empty($this->user_author_id) && $this->user_author_id == $user->id))
1294
			//	{
1295
			//		$userAccess = 1;
1296
			//	}
1297
			//}
1298
		}
1299
1300
		return ($userAccess ? $userAccess : -1);
1301
	}
1302
1303
	/**
1304
	 * Return array of projects a user has permission on, is affected to, or all projects
1305
	 *
1306
	 * @param 	User	$user			User object
1307
	 * @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
1308
	 * @param 	int		$list			0=Return array, 1=Return string list
1309
	 * @param	int		$socid			0=No filter on third party, id of third party
1310
	 * @param	string	$filter			additionnal filter on project (statut, ref, ...)
1311
	 * @return 	array or string			Array of projects id, or string with projects id separated with "," if list is 1
1312
	 */
1313
	public function getProjectsAuthorizedForUser($user, $mode = 0, $list = 0, $socid = 0, $filter = '')
1314
	{
1315
		$projects = array();
1316
		$temp = array();
1317
1318
		$sql = "SELECT ".(($mode == 0 || $mode == 1) ? "DISTINCT " : "")."p.rowid, p.ref";
1319
		$sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
1320
		if ($mode == 0)
1321
		{
1322
			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_contact as ec ON ec.element_id = p.rowid";
1323
		} elseif ($mode == 1)
1324
		{
1325
			$sql .= ", ".MAIN_DB_PREFIX."element_contact as ec";
1326
		} elseif ($mode == 2)
1327
		{
1328
			// No filter. Use this if user has permission to see all project
1329
		}
1330
		$sql .= " WHERE p.entity IN (".getEntity('project').")";
1331
		// Internal users must see project he is contact to even if project linked to a third party he can't see.
1332
		//if ($socid || ! $user->rights->societe->client->voir)	$sql.= " AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".$socid.")";
1333
		if ($socid > 0) $sql .= " AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".$socid.")";
1334
1335
		// Get id of types of contacts for projects (This list never contains a lot of elements)
1336
		$listofprojectcontacttype = array();
1337
		$sql2 = "SELECT ctc.rowid, ctc.code FROM ".MAIN_DB_PREFIX."c_type_contact as ctc";
1338
		$sql2 .= " WHERE ctc.element = '".$this->db->escape($this->element)."'";
1339
		$sql2 .= " AND ctc.source = 'internal'";
1340
		$resql = $this->db->query($sql2);
1341
		if ($resql)
1342
		{
1343
			while ($obj = $this->db->fetch_object($resql))
1344
			{
1345
				$listofprojectcontacttype[$obj->rowid] = $obj->code;
1346
			}
1347
		} else dol_print_error($this->db);
1348
		if (count($listofprojectcontacttype) == 0) $listofprojectcontacttype[0] = '0'; // To avoid syntax error if not found
1349
1350
		if ($mode == 0)
1351
		{
1352
			$sql .= " AND ( p.public = 1";
1353
			$sql .= " OR ( ec.fk_c_type_contact IN (".join(',', array_keys($listofprojectcontacttype)).")";
1354
			$sql .= " AND ec.fk_socpeople = ".$user->id.")";
1355
			$sql .= " )";
1356
		} elseif ($mode == 1)
1357
		{
1358
			$sql .= " AND ec.element_id = p.rowid";
1359
			$sql .= " AND (";
1360
			$sql .= "  ( ec.fk_c_type_contact IN (".join(',', array_keys($listofprojectcontacttype)).")";
1361
			$sql .= " AND ec.fk_socpeople = ".$user->id.")";
1362
			$sql .= " )";
1363
		} elseif ($mode == 2)
1364
		{
1365
			// No filter. Use this if user has permission to see all project
1366
		}
1367
1368
		$sql .= $filter;
1369
		//print $sql;
1370
1371
		$resql = $this->db->query($sql);
1372
		if ($resql)
1373
		{
1374
			$num = $this->db->num_rows($resql);
1375
			$i = 0;
1376
			while ($i < $num)
1377
			{
1378
				$row = $this->db->fetch_row($resql);
1379
				$projects[$row[0]] = $row[1];
1380
				$temp[] = $row[0];
1381
				$i++;
1382
			}
1383
1384
			$this->db->free($resql);
1385
1386
			if ($list)
1387
			{
1388
				if (empty($temp)) return '0';
1389
				$result = implode(',', $temp);
1390
				return $result;
1391
			}
1392
		} else {
1393
			dol_print_error($this->db);
1394
		}
1395
1396
		return $projects;
1397
	}
1398
1399
	/**
1400
	 * Load an object from its id and create a new one in database
1401
	 *
1402
	 *  @param	User	$user		          User making the clone
1403
	 *  @param	int		$fromid     	      Id of object to clone
1404
	 *  @param	bool	$clone_contact	      Clone contact of project
1405
	 *  @param	bool	$clone_task		      Clone task of project
1406
	 *  @param	bool	$clone_project_file	  Clone file of project
1407
	 *  @param	bool	$clone_task_file	  Clone file of task (if task are copied)
1408
	 *  @param	bool	$clone_note		      Clone note of project
1409
	 *  @param	bool	$move_date		      Move task date on clone
1410
	 *  @param	integer	$notrigger		      No trigger flag
1411
	 *  @param  int     $newthirdpartyid      New thirdparty id
1412
	 *  @return	int						      New id of clone
1413
	 */
1414
	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)
1415
	{
1416
		global $langs, $conf;
1417
1418
		$error = 0;
1419
1420
		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);
1421
1422
		$now = dol_mktime(0, 0, 0, idate('m', dol_now()), idate('d', dol_now()), idate('Y', dol_now()));
1423
1424
		$clone_project = new Project($this->db);
1425
1426
		$clone_project->context['createfromclone'] = 'createfromclone';
1427
1428
		$this->db->begin();
1429
1430
		// Load source object
1431
		$clone_project->fetch($fromid);
1432
		$clone_project->fetch_optionals();
1433
		if ($newthirdpartyid > 0) $clone_project->socid = $newthirdpartyid;
1434
		$clone_project->fetch_thirdparty();
1435
1436
		$orign_dt_start = $clone_project->date_start;
1437
		$orign_project_ref = $clone_project->ref;
1438
1439
		$clone_project->id = 0;
1440
		if ($move_date) {
1441
			$clone_project->date_start = $now;
1442
			if (!(empty($clone_project->date_end)))
1443
			{
1444
				$clone_project->date_end = $clone_project->date_end + ($now - $orign_dt_start);
1445
			}
1446
		}
1447
1448
		$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

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