Passed
Branch develop (356b3a)
by
unknown
98:06
created

Project::sendEmail()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
c 0
b 0
f 0
nc 1
nop 11
dl 0
loc 6
rs 10

How to fix   Many Parameters   

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
 * Copyright (C) 2022      Charlene Benke       <[email protected]>
10
 *
11
 * This program is free software; you can redistribute it and/or modify
12
 * it under the terms of the GNU General Public License as published by
13
 * the Free Software Foundation; either version 3 of the License, or
14
 * (at your option) any later version.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19
 * GNU General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU General Public License
22
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
23
 */
24
25
/**
26
 * 		\file       htdocs/projet/class/project.class.php
27
 * 		\ingroup    projet
28
 * 		\brief      File of class to manage projects
29
 */
30
require_once DOL_DOCUMENT_ROOT.'/core/class/commonobject.class.php';
31
32
/**
33
 *	Class to manage projects
34
 */
35
class Project extends CommonObject
36
{
37
38
	/**
39
	 * @var string ID to identify managed object
40
	 */
41
	public $element = 'project';
42
43
	/**
44
	 * @var string Name of table without prefix where object is stored
45
	 */
46
	public $table_element = 'projet';
47
48
	/**
49
	 * @var string    Name of subtable line
50
	 */
51
	public $table_element_line = 'projet_task';
52
53
	/**
54
	 * @var string    Name of field date
55
	 */
56
	public $table_element_date;
57
58
	/**
59
	 * @var string Field with ID of parent key if this field has a parent
60
	 */
61
	public $fk_element = 'fk_projet';
62
63
	/**
64
	 * 0=No test on entity, 1=Test with field entity, 2=Test with link by societe
65
	 * @var int
66
	 */
67
	public $ismultientitymanaged = 1;
68
69
	/**
70
	 * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
71
	 */
72
	public $picto = 'project';
73
74
	/**
75
	 * {@inheritdoc}
76
	 */
77
	protected $table_ref_field = 'ref';
78
79
	/**
80
	 * @var string description
81
	 */
82
	public $description;
83
84
	/**
85
	 * @var string
86
	 */
87
	public $title;
88
89
	/**
90
	 * @var int 	Date start
91
	 * @deprecated
92
	 * @see $date_start
93
	 */
94
	public $dateo;
95
96
	/**
97
	 * @var int 	Date start
98
	 */
99
	public $date_start;
100
101
	/**
102
	 * @var int 	Date end
103
	 * @deprecated
104
	 * @see $date_end
105
	 */
106
	public $datee;
107
108
	/**
109
	 * @var int 	Date end
110
	 */
111
	public $date_end;
112
113
	/**
114
	 * @var int 	Date start event
115
	 */
116
	public $date_start_event;
117
118
	/**
119
	 * @var int 	Date end event
120
	 */
121
	public $date_end_event;
122
123
	/**
124
	 * @var string	Location
125
	 */
126
	public $location;
127
128
	/**
129
	 * @var int Date close
130
	 */
131
	public $date_close;
132
133
	public $socid; // To store id of thirdparty
134
	public $thirdparty_name; // To store name of thirdparty (defined only in some cases)
135
136
	public $user_author_id; //!< Id of project creator. Not defined if shared project.
137
138
	/**
139
	 * @var int user close id
140
	 */
141
	public $fk_user_close;
142
143
	/**
144
	 * @var int user close id
145
	 */
146
	public $user_close_id;
147
	public $public; //!< Tell if this is a public or private project
148
149
	/**
150
	 * @var float budget Amount
151
	 */
152
	public $budget_amount;
153
154
	/**
155
	 * @var integer		Can use projects to follow opportunities
156
	 */
157
	public $usage_opportunity;
158
159
	/**
160
	 * @var integer		Can follow tasks on project and enter time spent on it
161
	 */
162
	public $usage_task;
163
164
	/**
165
	 * @var integer	 	Use to bill task spend time
166
	 */
167
	public $usage_bill_time; // Is the time spent on project must be invoiced or not
168
169
	/**
170
	   * @var integer		Event organization: Use Event Organization
171
	   */
172
	public $usage_organize_event;
173
174
	/**
175
	 * @var integer		Event organization: Allow unknown people to suggest new conferences
176
	 */
177
	public $accept_conference_suggestions;
178
179
	/**
180
	 * @var integer		Event organization: Allow unknown people to suggest new booth
181
	 */
182
	public $accept_booth_suggestions;
183
184
	/**
185
	 * @var float Event organization: registration price
186
	 */
187
	public $price_registration;
188
189
	/**
190
	 * @var float Event organization: booth price
191
	 */
192
	public $price_booth;
193
194
	/**
195
	 * @var float Max attendees
196
	 */
197
	public $max_attendees;
198
199
	public $statuts_short;
200
	public $statuts_long;
201
202
	public $statut; // 0=draft, 1=opened, 2=closed
203
204
	public $opp_status; // opportunity status, into table llx_c_lead_status
205
	public $fk_opp_status; // opportunity status, into table llx_c_lead_status
206
	public $opp_percent; // opportunity probability
207
208
	public $email_msgid;
209
210
	public $oldcopy;
211
212
	public $weekWorkLoad; // Used to store workload details of a projet
213
	public $weekWorkLoadPerTask; // Used to store workload details of tasks of a projet
214
215
	/**
216
	 * @var int Creation date
217
	 * @deprecated
218
	 * @see $date_c
219
	 */
220
	public $datec;
221
222
	/**
223
	 * @var int Creation date
224
	 */
225
	public $date_c;
226
227
	/**
228
	 * @var int Modification date
229
	 * @deprecated
230
	 * @see $date_m
231
	 */
232
	public $datem;
233
234
	/**
235
	 * @var int Modification date
236
	 */
237
	public $date_m;
238
239
	/**
240
	 * @var Task[]
241
	 */
242
	public $lines;
243
244
	/**
245
	 *  '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')
246
	 *         Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
247
	 *  'label' the translation key.
248
	 *  'enabled' is a condition when the field must be managed.
249
	 *  'position' is the sort order of field.
250
	 *  'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
251
	 *  '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)
252
	 *  'noteditable' says if field is not editable (1 or 0)
253
	 *  '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.
254
	 *  'index' if we want an index in database.
255
	 *  'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommanded to name the field fk_...).
256
	 *  'searchall' is 1 if we want to search in this field when making a search from the quick search button.
257
	 *  '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).
258
	 *  'css' is the CSS style to use on field. For example: 'maxwidth200'
259
	 *  'help' is a string visible as a tooltip on field
260
	 *  'showoncombobox' if value of the field must be visible into the label of the combobox that list record
261
	 *  '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.
262
	 *  'arrayofkeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
263
	 *  'comment' is not used. You can store here any text of your choice. It is not used by application.
264
	 *
265
	 *  Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
266
	 */
267
268
	// BEGIN MODULEBUILDER PROPERTIES
269
	/**
270
	 * @var array  Array with all fields and their property. Do not use it as a static var. It may be modified by constructor.
271
	 */
272
	public $fields = array(
273
		'rowid' =>array('type'=>'integer', 'label'=>'ID', 'enabled'=>1, 'visible'=>-1, 'notnull'=>1, 'position'=>10),
274
		'ref' =>array('type'=>'varchar(50)', 'label'=>'Ref', 'enabled'=>1, 'visible'=>1, 'showoncombobox'=>1, 'position'=>15, 'searchall'=>1),
275
		'title' =>array('type'=>'varchar(255)', 'label'=>'ProjectLabel', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'position'=>17, 'showoncombobox'=>2, 'searchall'=>1),
276
		'entity' =>array('type'=>'integer', 'label'=>'Entity', 'default'=>1, 'enabled'=>1, 'visible'=>3, 'notnull'=>1, 'position'=>19),
277
		'fk_soc' =>array('type'=>'integer', 'label'=>'Thirdparty', 'enabled'=>1, 'visible'=>0, 'position'=>20),
278
		'dateo' =>array('type'=>'date', 'label'=>'DateStart', 'enabled'=>1, 'visible'=>1, 'position'=>30),
279
		'datee' =>array('type'=>'date', 'label'=>'DateEnd', 'enabled'=>1, 'visible'=>1, 'position'=>35),
280
		'description' =>array('type'=>'text', 'label'=>'Description', 'enabled'=>1, 'visible'=>3, 'position'=>55, 'searchall'=>1),
281
		'public' =>array('type'=>'integer', 'label'=>'Visibility', 'enabled'=>1, 'visible'=>1, 'position'=>65),
282
		'fk_opp_status' =>array('type'=>'integer', 'label'=>'OpportunityStatusShort', 'enabled'=>'getDolGlobalString("PROJECT_USE_OPPORTUNITIES")', 'visible'=>1, 'position'=>75),
283
		'opp_percent' =>array('type'=>'double(5,2)', 'label'=>'OpportunityProbabilityShort', 'enabled'=>'getDolGlobalString("PROJECT_USE_OPPORTUNITIES")', 'visible'=>1, 'position'=>80),
284
		'note_private' =>array('type'=>'html', 'label'=>'NotePrivate', 'enabled'=>1, 'visible'=>0, 'position'=>85, 'searchall'=>1),
285
		'note_public' =>array('type'=>'html', 'label'=>'NotePublic', 'enabled'=>1, 'visible'=>0, 'position'=>90, 'searchall'=>1),
286
		'model_pdf' =>array('type'=>'varchar(255)', 'label'=>'ModelPdf', 'enabled'=>1, 'visible'=>0, 'position'=>95),
287
		'date_close' =>array('type'=>'datetime', 'label'=>'DateClosing', 'enabled'=>1, 'visible'=>0, 'position'=>105),
288
		'fk_user_close' =>array('type'=>'integer', 'label'=>'UserClosing', 'enabled'=>1, 'visible'=>0, 'position'=>110),
289
		'opp_amount' =>array('type'=>'double(24,8)', 'label'=>'OpportunityAmountShort', 'enabled'=>1, 'visible'=>'getDolGlobalString("PROJECT_USE_OPPORTUNITIES")', 'position'=>115),
290
		'budget_amount' =>array('type'=>'double(24,8)', 'label'=>'Budget', 'enabled'=>1, 'visible'=>1, 'position'=>119),
291
		'usage_bill_time' =>array('type'=>'integer', 'label'=>'UsageBillTimeShort', 'enabled'=>1, 'visible'=>-1, 'position'=>130),
292
		'usage_opportunity' =>array('type'=>'integer', 'label'=>'UsageOpportunity', 'enabled'=>1, 'visible'=>-1, 'position'=>135),
293
		'usage_task' =>array('type'=>'integer', 'label'=>'UsageTasks', 'enabled'=>1, 'visible'=>-1, 'position'=>140),
294
		'usage_organize_event' =>array('type'=>'integer', 'label'=>'UsageOrganizeEvent', 'enabled'=>1, 'visible'=>-1, 'position'=>145),
295
		// Properties for event organization
296
		'date_start_event' =>array('type'=>'date', 'label'=>'DateStartEvent', 'enabled'=>1, 'visible'=>1, 'position'=>200),
297
		'date_end_event' =>array('type'=>'date', 'label'=>'DateEndEvent', 'enabled'=>1, 'visible'=>1, 'position'=>201),
298
		'location' =>array('type'=>'text', 'label'=>'Location', 'enabled'=>1, 'visible'=>3, 'position'=>55, 'searchall'=>202),
299
		'accept_conference_suggestions' =>array('type'=>'integer', 'label'=>'AllowUnknownPeopleSuggestConf', 'enabled'=>1, 'visible'=>-1, 'position'=>210),
300
		'accept_booth_suggestions' =>array('type'=>'integer', 'label'=>'AllowUnknownPeopleSuggestBooth', 'enabled'=>1, 'visible'=>-1, 'position'=>211),
301
		'price_registration' =>array('type'=>'double(24,8)', 'label'=>'PriceOfRegistration', 'enabled'=>1, 'visible'=>-1, 'position'=>212),
302
		'price_booth' =>array('type'=>'double(24,8)', 'label'=>'PriceOfBooth', 'enabled'=>1, 'visible'=>-1, 'position'=>215),
303
		'max_attendees' =>array('type'=>'integer', 'label'=>'MaxNbOfAttendees', 'enabled'=>1, 'visible'=>-1, 'position'=>215),
304
		// Generic
305
		'datec' =>array('type'=>'datetime', 'label'=>'DateCreationShort', 'enabled'=>1, 'visible'=>-2, 'position'=>400),
306
		'tms' =>array('type'=>'timestamp', 'label'=>'DateModificationShort', 'enabled'=>1, 'visible'=>-2, 'notnull'=>1, 'position'=>405),
307
		'fk_user_creat' =>array('type'=>'integer', 'label'=>'UserCreation', 'enabled'=>1, 'visible'=>0, 'notnull'=>1, 'position'=>410),
308
		'fk_user_modif' =>array('type'=>'integer', 'label'=>'UserModification', 'enabled'=>1, 'visible'=>0, 'position'=>415),
309
		'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-1, 'position'=>420),
310
		'email_msgid'=>array('type'=>'varchar(255)', 'label'=>'EmailMsgID', 'enabled'=>1, 'visible'=>-1, 'position'=>450, 'help'=>'EmailMsgIDWhenSourceisEmail'),
311
		'fk_statut' =>array('type'=>'smallint(6)', 'label'=>'Status', 'enabled'=>1, 'visible'=>1, 'notnull'=>1, 'position'=>500),
312
	);
313
	// END MODULEBUILDER PROPERTIES
314
315
	/**
316
	 * Draft status
317
	 */
318
	const STATUS_DRAFT = 0;
319
320
	/**
321
	 * Open/Validated status
322
	 */
323
	const STATUS_VALIDATED = 1;
324
325
	/**
326
	 * Closed status
327
	 */
328
	const STATUS_CLOSED = 2;
329
330
	/**
331
	 *  Constructor
332
	 *
333
	 *  @param      DoliDB		$db      Database handler
334
	 */
335
	public function __construct($db)
336
	{
337
		global $conf;
338
339
		$this->db = $db;
340
341
		$this->statuts_short = array(0 => 'Draft', 1 => 'Opened', 2 => 'Closed');
342
		$this->statuts_long = array(0 => 'Draft', 1 => 'Opened', 2 => 'Closed');
343
344
		global $conf;
345
346
		if (empty($conf->global->MAIN_SHOW_TECHNICAL_ID)) {
347
			$this->fields['rowid']['visible'] = 0;
348
		}
349
350
		if (empty($conf->global->PROJECT_USE_OPPORTUNITIES)) {
351
			$this->fields['fk_opp_status']['enabled'] = 0;
352
			$this->fields['opp_percent']['enabled'] = 0;
353
			$this->fields['opp_amount']['enabled'] = 0;
354
			$this->fields['usage_opportunity']['enabled'] = 0;
355
		}
356
357
		if (!empty($conf->global->PROJECT_HIDE_TASKS)) {
358
			$this->fields['usage_bill_time']['visible'] = 0;
359
			$this->fields['usage_task']['visible'] = 0;
360
		}
361
362
		if (empty($conf->eventorganization->enabled)) {
363
			$this->fields['usage_organize_event']['visible'] = 0;
364
			$this->fields['accept_conference_suggestions']['enabled'] = 0;
365
			$this->fields['accept_booth_suggestions']['enabled'] = 0;
366
			$this->fields['price_registration']['enabled'] = 0;
367
			$this->fields['price_booth']['enabled'] = 0;
368
			$this->fields['max_attendees']['enabled'] = 0;
369
		}
370
	}
371
372
	/**
373
	 *    Create a project into database
374
	 *
375
	 *    @param    User	$user       	User making creation
376
	 *    @param	int		$notrigger		Disable triggers
377
	 *    @return   int         			<0 if KO, id of created project if OK
378
	 */
379
	public function create($user, $notrigger = 0)
380
	{
381
		global $conf, $langs;
382
383
		$error = 0;
384
		$ret = 0;
385
386
		$now = dol_now();
387
388
		// Clean parameters
389
		$this->note_private = dol_substr($this->note_private, 0, 65535);
390
		$this->note_public = dol_substr($this->note_public, 0, 65535);
391
392
		// Check parameters
393
		if (!trim($this->ref)) {
394
			$this->error = 'ErrorFieldsRequired';
395
			dol_syslog(get_class($this)."::create error -1 ref null", LOG_ERR);
396
			return -1;
397
		}
398
		if (!empty($conf->global->PROJECT_THIRDPARTY_REQUIRED) && !($this->socid > 0)) {
399
			$this->error = 'ErrorFieldsRequired';
400
			dol_syslog(get_class($this)."::create error -1 thirdparty not defined and option PROJECT_THIRDPARTY_REQUIRED is set", LOG_ERR);
401
			return -1;
402
		}
403
404
		// Create project
405
		$this->db->begin();
406
407
		$sql = "INSERT INTO ".MAIN_DB_PREFIX."projet (";
408
		$sql .= "ref";
409
		$sql .= ", title";
410
		$sql .= ", description";
411
		$sql .= ", fk_soc";
412
		$sql .= ", fk_user_creat";
413
		$sql .= ", fk_statut";
414
		$sql .= ", fk_opp_status";
415
		$sql .= ", opp_percent";
416
		$sql .= ", public";
417
		$sql .= ", datec";
418
		$sql .= ", dateo";
419
		$sql .= ", datee";
420
		$sql .= ", opp_amount";
421
		$sql .= ", budget_amount";
422
		$sql .= ", usage_opportunity";
423
		$sql .= ", usage_task";
424
		$sql .= ", usage_bill_time";
425
		$sql .= ", usage_organize_event";
426
		$sql .= ", accept_conference_suggestions";
427
		$sql .= ", accept_booth_suggestions";
428
		$sql .= ", price_registration";
429
		$sql .= ", price_booth";
430
		$sql .= ", max_attendees";
431
		$sql .= ", date_start_event";
432
		$sql .= ", date_end_event";
433
		$sql .= ", location";
434
		$sql .= ", email_msgid";
435
		$sql .= ", note_private";
436
		$sql .= ", note_public";
437
		$sql .= ", entity";
438
		$sql .= ", ip";
439
		$sql .= ") VALUES (";
440
		$sql .= "'".$this->db->escape($this->ref)."'";
441
		$sql .= ", '".$this->db->escape($this->title)."'";
442
		$sql .= ", '".$this->db->escape($this->description)."'";
443
		$sql .= ", ".($this->socid > 0 ? $this->socid : "null");
444
		$sql .= ", ".((int) $user->id);
445
		$sql .= ", ".(is_numeric($this->statut) ? ((int) $this->statut) : '0');
446
		$sql .= ", ".((is_numeric($this->opp_status) && $this->opp_status > 0) ? ((int) $this->opp_status) : 'NULL');
447
		$sql .= ", ".(is_numeric($this->opp_percent) ? ((int) $this->opp_percent) : 'NULL');
448
		$sql .= ", ".($this->public ? 1 : 0);
449
		$sql .= ", '".$this->db->idate($now)."'";
450
		$sql .= ", ".($this->date_start != '' ? "'".$this->db->idate($this->date_start)."'" : 'null');
451
		$sql .= ", ".($this->date_end != '' ? "'".$this->db->idate($this->date_end)."'" : 'null');
452
		$sql .= ", ".(strcmp($this->opp_amount, '') ? price2num($this->opp_amount) : 'null');
453
		$sql .= ", ".(strcmp($this->budget_amount, '') ? price2num($this->budget_amount) : 'null');
454
		$sql .= ", ".($this->usage_opportunity ? 1 : 0);
455
		$sql .= ", ".($this->usage_task ? 1 : 0);
456
		$sql .= ", ".($this->usage_bill_time ? 1 : 0);
457
		$sql .= ", ".($this->usage_organize_event ? 1 : 0);
458
		$sql .= ", ".($this->accept_conference_suggestions ? 1 : 0);
459
		$sql .= ", ".($this->accept_booth_suggestions ? 1 : 0);
460
		$sql .= ", ".(strcmp($this->price_registration, '') ? price2num($this->price_registration) : 'null');
461
		$sql .= ", ".(strcmp($this->price_booth, '') ? price2num($this->price_booth) : 'null');
462
		$sql .= ", ".(strcmp($this->max_attendees, '') ? ((int) $this->max_attendees) : 'null');
463
		$sql .= ", ".($this->date_start_event != '' ? "'".$this->db->idate($this->date_start_event)."'" : 'null');
464
		$sql .= ", ".($this->date_end_event != '' ? "'".$this->db->idate($this->date_end_event)."'" : 'null');
465
		$sql .= ", ".($this->location ? "'".$this->db->escape($this->location)."'" : 'null');
466
		$sql .= ", ".($this->email_msgid ? "'".$this->db->escape($this->email_msgid)."'" : 'null');
467
		$sql .= ", ".($this->note_private ? "'".$this->db->escape($this->note_private)."'" : 'null');
468
		$sql .= ", ".($this->note_public ? "'".$this->db->escape($this->note_public)."'" : 'null');
469
		$sql .= ", ".((int) $conf->entity);
470
		$sql .= ", ".(!isset($this->ip) ? 'NULL' : "'".$this->db->escape($this->ip)."'");
471
		$sql .= ")";
472
473
		dol_syslog(get_class($this)."::create", LOG_DEBUG);
474
		$resql = $this->db->query($sql);
475
		if ($resql) {
476
			$this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."projet");
477
			$ret = $this->id;
478
479
			if (!$notrigger) {
480
				// Call trigger
481
				$result = $this->call_trigger('PROJECT_CREATE', $user);
482
				if ($result < 0) {
483
					$error++;
484
				}
485
				// End call triggers
486
			}
487
		} else {
488
			$this->error = $this->db->lasterror();
489
			$this->errno = $this->db->lasterrno();
490
			$error++;
491
		}
492
493
		// Update extrafield
494
		if (!$error) {
495
			$result = $this->insertExtraFields();
496
			if ($result < 0) {
497
				$error++;
498
			}
499
		}
500
501
		if (!$error && !empty($conf->global->MAIN_DISABLEDRAFTSTATUS)) {
502
			$res = $this->setValid($user);
503
			if ($res < 0) {
504
				$error++;
505
			}
506
		}
507
508
		if (!$error) {
509
			$this->db->commit();
510
			return $ret;
511
		} else {
512
			$this->db->rollback();
513
			return -1;
514
		}
515
	}
516
517
	/**
518
	 * Update a project
519
	 *
520
	 * @param  User		$user       User object of making update
521
	 * @param  int		$notrigger  1=Disable all triggers
522
	 * @return int                  <=0 if KO, >0 if OK
523
	 */
524
	public function update($user, $notrigger = 0)
525
	{
526
		global $langs, $conf;
527
528
		$error = 0;
529
530
		// Clean parameters
531
		$this->title = trim($this->title);
532
		$this->description = trim($this->description);
533
		if ($this->opp_amount < 0) {
534
			$this->opp_amount = '';
535
		}
536
		if ($this->opp_percent < 0) {
537
			$this->opp_percent = '';
538
		}
539
		if ($this->date_end && $this->date_end < $this->date_start) {
540
			$this->error = $langs->trans("ErrorDateEndLowerThanDateStart");
541
			$this->errors[] = $this->error;
542
			$this->db->rollback();
543
			dol_syslog(get_class($this)."::update error -3 ".$this->error, LOG_ERR);
544
			return -3;
545
		}
546
547
		$this->entity = ((isset($this->entity) && is_numeric($this->entity)) ? $this->entity : $conf->entity);
548
549
		if (dol_strlen(trim($this->ref)) > 0) {
550
			$this->db->begin();
551
552
			$sql = "UPDATE ".MAIN_DB_PREFIX."projet SET";
553
			$sql .= " ref='".$this->db->escape($this->ref)."'";
554
			$sql .= ", title = '".$this->db->escape($this->title)."'";
555
			$sql .= ", description = '".$this->db->escape($this->description)."'";
556
			$sql .= ", fk_soc = ".($this->socid > 0 ? $this->socid : "null");
557
			$sql .= ", fk_statut = ".((int) $this->statut);
558
			$sql .= ", fk_opp_status = ".((is_numeric($this->opp_status) && $this->opp_status > 0) ? $this->opp_status : 'null');
559
			$sql .= ", opp_percent = ".((is_numeric($this->opp_percent) && $this->opp_percent != '') ? $this->opp_percent : 'null');
560
			$sql .= ", public = ".($this->public ? 1 : 0);
561
			$sql .= ", datec = ".($this->date_c != '' ? "'".$this->db->idate($this->date_c)."'" : 'null');
562
			$sql .= ", dateo = ".($this->date_start != '' ? "'".$this->db->idate($this->date_start)."'" : 'null');
563
			$sql .= ", datee = ".($this->date_end != '' ? "'".$this->db->idate($this->date_end)."'" : 'null');
564
			$sql .= ", date_close = ".($this->date_close != '' ? "'".$this->db->idate($this->date_close)."'" : 'null');
565
			$sql .= ", fk_user_close = ".($this->fk_user_close > 0 ? $this->fk_user_close : "null");
566
			$sql .= ", opp_amount = ".(strcmp($this->opp_amount, '') ? price2num($this->opp_amount) : "null");
567
			$sql .= ", budget_amount = ".(strcmp($this->budget_amount, '') ? price2num($this->budget_amount) : "null");
568
			$sql .= ", fk_user_modif = ".$user->id;
569
			$sql .= ", usage_opportunity = ".($this->usage_opportunity ? 1 : 0);
570
			$sql .= ", usage_task = ".($this->usage_task ? 1 : 0);
571
			$sql .= ", usage_bill_time = ".($this->usage_bill_time ? 1 : 0);
572
			$sql .= ", usage_organize_event = ".($this->usage_organize_event ? 1 : 0);
573
			$sql .= ", accept_conference_suggestions = ".($this->accept_conference_suggestions ? 1 : 0);
574
			$sql .= ", accept_booth_suggestions = ".($this->accept_booth_suggestions ? 1 : 0);
575
			$sql .= ", price_registration = ".(strcmp($this->price_registration, '') ? price2num($this->price_registration) : "null");
576
			$sql .= ", price_booth = ".(strcmp($this->price_booth, '') ? price2num($this->price_booth) : "null");
577
			$sql .= ", max_attendees = ".(strcmp($this->max_attendees, '') ? price2num($this->max_attendees) : "null");
578
			$sql .= ", date_start_event = ".($this->date_start_event != '' ? "'".$this->db->idate($this->date_start_event)."'" : 'null');
579
			$sql .= ", date_end_event = ".($this->date_end_event != '' ? "'".$this->db->idate($this->date_end_event)."'" : 'null');
580
			$sql .= ", location = '".$this->db->escape($this->location)."'";
581
			$sql .= ", entity = ".((int) $this->entity);
582
			$sql .= " WHERE rowid = ".((int) $this->id);
583
584
			dol_syslog(get_class($this)."::update", LOG_DEBUG);
585
			$resql = $this->db->query($sql);
586
			if ($resql) {
587
				// Update extrafield
588
				if (!$error) {
589
					$result = $this->insertExtraFields();
590
					if ($result < 0) {
591
						$error++;
592
					}
593
				}
594
595
				if (!$error && !$notrigger) {
596
					// Call trigger
597
					$result = $this->call_trigger('PROJECT_MODIFY', $user);
598
					if ($result < 0) {
599
						$error++;
600
					}
601
					// End call triggers
602
				}
603
604
				if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
605
					// We remove directory
606
					if ($conf->project->dir_output) {
607
						$olddir = $conf->project->dir_output."/".dol_sanitizeFileName($this->oldcopy->ref);
608
						$newdir = $conf->project->dir_output."/".dol_sanitizeFileName($this->ref);
609
						if (file_exists($olddir)) {
610
							include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
611
							$res = @rename($olddir, $newdir);
612
							if (!$res) {
613
								$langs->load("errors");
614
								$this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
615
								$error++;
616
							}
617
						}
618
					}
619
				}
620
				if (!$error) {
621
					$this->db->commit();
622
					$result = 1;
623
				} else {
624
					$this->db->rollback();
625
					$result = -1;
626
				}
627
			} else {
628
				$this->error = $this->db->lasterror();
629
				$this->errors[] = $this->error;
630
				$this->db->rollback();
631
				if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
632
					$result = -4;
633
				} else {
634
					$result = -2;
635
				}
636
				dol_syslog(get_class($this)."::update error ".$result." ".$this->error, LOG_ERR);
637
			}
638
		} else {
639
			dol_syslog(get_class($this)."::update ref null");
640
			$result = -1;
641
		}
642
643
		return $result;
644
	}
645
646
	/**
647
	 * 	Get object from database
648
	 *
649
	 * 	@param      int		$id       		Id of object to load
650
	 * 	@param		string	$ref			Ref of project
651
	 * 	@param		string	$ref_ext		Ref ext of project
652
	 *  @param		string	$email_msgid	Email msgid
653
	 * 	@return     int      		   		>0 if OK, 0 if not found, <0 if KO
654
	 */
655
	public function fetch($id, $ref = '', $ref_ext = '', $email_msgid = '')
656
	{
657
		global $conf;
658
659
		if (empty($id) && empty($ref) && empty($ref_ext) && empty($email_msgid)) {
660
			dol_syslog(get_class($this)."::fetch Bad parameters", LOG_WARNING);
661
			return -1;
662
		}
663
664
		$sql = "SELECT rowid, entity, ref, title, description, public, datec, opp_amount, budget_amount,";
665
		$sql .= " tms, dateo as date_start, datee as date_end, date_close, fk_soc, fk_user_creat, fk_user_modif, fk_user_close, fk_statut as status, fk_opp_status, opp_percent,";
666
		$sql .= " note_private, note_public, model_pdf, usage_opportunity, usage_task, usage_bill_time, usage_organize_event, email_msgid,";
667
		$sql .= " accept_conference_suggestions, accept_booth_suggestions, price_registration, price_booth, max_attendees, date_start_event, date_end_event, location";
668
		$sql .= " FROM ".MAIN_DB_PREFIX."projet";
669
		if (!empty($id)) {
670
			$sql .= " WHERE rowid = ".((int) $id);
671
		} else {
672
			$sql .= " WHERE entity IN (".getEntity('project').")";
673
			if (!empty($ref)) {
674
				$sql .= " AND ref = '".$this->db->escape($ref)."'";
675
			} elseif (!empty($ref_ext)) {
676
				$sql .= " AND ref_ext = '".$this->db->escape($ref_ext)."'";
677
			} else {
678
				$sql .= " AND email_msgid = '".$this->db->escape($email_msgid)."'";
679
			}
680
		}
681
682
		dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
683
		$resql = $this->db->query($sql);
684
		if ($resql) {
685
			$num_rows = $this->db->num_rows($resql);
686
687
			if ($num_rows) {
688
				$obj = $this->db->fetch_object($resql);
689
690
				$this->id = $obj->rowid;
691
				$this->entity = $obj->entity;
692
				$this->ref = $obj->ref;
693
				$this->title = $obj->title;
694
				$this->description = $obj->description;
695
				$this->date_c = $this->db->jdate($obj->datec);
696
				$this->datec = $this->db->jdate($obj->datec); // TODO deprecated
697
				$this->date_m = $this->db->jdate($obj->tms);
698
				$this->datem = $this->db->jdate($obj->tms); // TODO deprecated
699
				$this->date_start = $this->db->jdate($obj->date_start);
700
				$this->date_end = $this->db->jdate($obj->date_end);
701
				$this->date_close = $this->db->jdate($obj->date_close);
702
				$this->note_private = $obj->note_private;
703
				$this->note_public = $obj->note_public;
704
				$this->socid = $obj->fk_soc;
705
				$this->user_author_id = $obj->fk_user_creat;
706
				$this->user_modification_id = $obj->fk_user_modif;
707
				$this->user_close_id = $obj->fk_user_close;
708
				$this->public = $obj->public;
709
				$this->statut = $obj->status; // deprecated
710
				$this->status = $obj->status;
711
				$this->opp_status = $obj->fk_opp_status;
712
				$this->opp_amount	= $obj->opp_amount;
713
				$this->opp_percent = $obj->opp_percent;
714
				$this->budget_amount = $obj->budget_amount;
715
				$this->model_pdf = $obj->model_pdf;
716
				$this->modelpdf = $obj->model_pdf; // deprecated
717
				$this->usage_opportunity = (int) $obj->usage_opportunity;
718
				$this->usage_task = (int) $obj->usage_task;
719
				$this->usage_bill_time = (int) $obj->usage_bill_time;
720
				$this->usage_organize_event = (int) $obj->usage_organize_event;
721
				$this->accept_conference_suggestions = (int) $obj->accept_conference_suggestions;
722
				$this->accept_booth_suggestions = (int) $obj->accept_booth_suggestions;
723
				$this->price_registration = $obj->price_registration;
724
				$this->price_booth = $obj->price_booth;
725
				$this->max_attendees = $obj->max_attendees;
726
				$this->date_start_event = $this->db->jdate($obj->date_start_event);
727
				$this->date_end_event = $this->db->jdate($obj->date_end_event);
728
				$this->location = $obj->location;
729
				$this->email_msgid = $obj->email_msgid;
730
731
				$this->db->free($resql);
732
733
				// Retrieve all extrafield
734
				// fetch optionals attributes and labels
735
				$this->fetch_optionals();
736
737
				return 1;
738
			}
739
740
			$this->db->free($resql);
741
742
			return 0;
743
		} else {
744
			$this->error = $this->db->lasterror();
745
			$this->errors[] = $this->db->lasterror();
746
			return -1;
747
		}
748
	}
749
750
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
751
	/**
752
	 * 	Return list of elements for type, linked to a project
753
	 *
754
	 * 	@param		string		$type			'propal','order','invoice','order_supplier','invoice_supplier',...
755
	 * 	@param		string		$tablename		name of table associated of the type
756
	 * 	@param		string		$datefieldname	name of date field for filter
757
	 *  @param		int			$date_start		Start date
758
	 *  @param		int			$date_end		End date
759
	 *	@param		string		$projectkey		Equivalent key  to fk_projet for actual type
760
	 * 	@return		mixed						Array list of object ids linked to project, < 0 or string if error
761
	 */
762
	public function get_element_list($type, $tablename, $datefieldname = '', $date_start = '', $date_end = '', $projectkey = 'fk_projet')
763
	{
764
		// phpcs:enable
765
766
		global $hookmanager;
767
768
		$elements = array();
769
770
		if ($this->id <= 0) {
771
			return $elements;
772
		}
773
774
		$ids = $this->id;
775
776
		if ($type == 'agenda') {
777
			$sql = "SELECT id as rowid FROM ".MAIN_DB_PREFIX."actioncomm WHERE fk_project IN (".$this->db->sanitize($ids).") AND entity IN (".getEntity('agenda').")";
778
		} elseif ($type == 'expensereport') {
779
			$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 (".$this->db->sanitize($ids).")";
780
		} elseif ($type == 'project_task') {
781
			$sql = "SELECT DISTINCT pt.rowid FROM ".MAIN_DB_PREFIX."projet_task as pt WHERE pt.fk_projet IN (".$this->db->sanitize($ids).")";
782
		} elseif ($type == 'project_task_time') {	// Case we want to duplicate line foreach user
783
			$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 (".$this->db->sanitize($ids).")";
784
		} elseif ($type == 'stock_mouvement') {
785
			$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 (".$this->db->sanitize($ids).") AND ms.type_mouvement = 1";
786
		} elseif ($type == 'loan') {
787
			$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 (".$this->db->sanitize($ids).")";
788
		} else {
789
			$sql = "SELECT rowid FROM ".MAIN_DB_PREFIX.$tablename." WHERE ".$projectkey." IN (".$this->db->sanitize($ids).") AND entity IN (".getEntity($type).")";
790
		}
791
792
		if ($date_start > 0 && $type == 'loan') {
793
			$sql .= " AND (dateend > '".$this->db->idate($date_start)."' OR dateend IS NULL)";
794
		} elseif ($date_start > 0 && ($type != 'project_task')) {	// For table project_taks, we want the filter on date apply on project_time_spent table
795
			if (empty($datefieldname) && !empty($this->table_element_date)) {
796
				$datefieldname = $this->table_element_date;
797
			}
798
			if (empty($datefieldname)) {
799
				return 'Error this object has no date field defined';
800
			}
801
			$sql .= " AND (".$datefieldname." >= '".$this->db->idate($date_start)."' OR ".$datefieldname." IS NULL)";
802
		}
803
804
		if ($date_end > 0 && $type == 'loan') {
805
			$sql .= " AND (datestart < '".$this->db->idate($date_end)."' OR datestart IS NULL)";
806
		} elseif ($date_end > 0 && ($type != 'project_task')) {	// For table project_taks, we want the filter on date apply on project_time_spent table
807
			if (empty($datefieldname) && !empty($this->table_element_date)) {
808
				$datefieldname = $this->table_element_date;
809
			}
810
			if (empty($datefieldname)) {
811
				return 'Error this object has no date field defined';
812
			}
813
			$sql .= " AND (".$datefieldname." <= '".$this->db->idate($date_end)."' OR ".$datefieldname." IS NULL)";
814
		}
815
816
		$parameters = array(
817
			'sql'=>$sql,
818
			'type' => $type,
819
			'tablename' => $tablename,
820
			'datefieldname'  => $datefieldname,
821
			'dates' => $date_start,
822
			'datee' => $date_end,
823
			'fk_projet' => $projectkey,
824
			'ids' => $ids,
825
		);
826
		$reshook = $hookmanager->executeHooks('getElementList', $parameters);
827
		if ($reshook > 0) {
828
			$sql = $hookmanager->resPrint;
829
		} else {
830
			$sql .= $hookmanager->resPrint;
831
		}
832
833
		if (!$sql) {
834
			return -1;
835
		}
836
837
		//print $sql;
838
		dol_syslog(get_class($this)."::get_element_list", LOG_DEBUG);
839
		$result = $this->db->query($sql);
840
		if ($result) {
841
			$nump = $this->db->num_rows($result);
842
			if ($nump) {
843
				$i = 0;
844
				while ($i < $nump) {
845
					$obj = $this->db->fetch_object($result);
846
847
					$elements[$i] = $obj->rowid.(empty($obj->fk_user) ? '' : '_'.$obj->fk_user);
848
849
					$i++;
850
				}
851
				$this->db->free($result);
852
			}
853
854
			/* Return array even if empty*/
855
			return $elements;
856
		} else {
857
			dol_print_error($this->db);
858
		}
859
	}
860
861
	/**
862
	 *    Delete a project from database
863
	 *
864
	 *    @param       User		$user            User
865
	 *    @param       int		$notrigger       Disable triggers
866
	 *    @return      int       			      <0 if KO, 0 if not possible, >0 if OK
867
	 */
868
	public function delete($user, $notrigger = 0)
869
	{
870
		global $langs, $conf;
871
		require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
872
873
		$error = 0;
874
875
		$this->db->begin();
876
877
		if (!$error) {
878
			// Delete linked contacts
879
			$res = $this->delete_linked_contact();
880
			if ($res < 0) {
881
				$this->error = 'ErrorFailToDeleteLinkedContact';
882
				//$error++;
883
				$this->db->rollback();
884
				return 0;
885
			}
886
		}
887
888
		// Set fk_projet into elements to null
889
		$listoftables = array(
890
			'propal'=>'fk_projet', 'commande'=>'fk_projet', 'facture'=>'fk_projet',
891
			'supplier_proposal'=>'fk_projet', 'commande_fournisseur'=>'fk_projet', 'facture_fourn'=>'fk_projet',
892
			'expensereport_det'=>'fk_projet', 'contrat'=>'fk_projet',
893
			'fichinter'=>'fk_projet',
894
			'don'=>array('field'=>'fk_projet', 'module'=>'don'),
895
			'actioncomm'=>'fk_project',
896
			'mrp_mo'=>'fk_project',
897
			'entrepot'=>'fk_project'
898
		);
899
		foreach ($listoftables as $key => $value) {
900
			if (is_array($value)) {
901
				if (!isModEnabled($value['module'])) {
902
					continue;
903
				}
904
				$fieldname = $value['field'];
905
			} else {
906
				$fieldname = $value;
907
			}
908
			$sql = "UPDATE ".MAIN_DB_PREFIX.$key." SET ".$fieldname." = NULL where ".$fieldname." = ".((int) $this->id);
909
910
			$resql = $this->db->query($sql);
911
			if (!$resql) {
912
				$this->errors[] = $this->db->lasterror();
913
				$error++;
914
				break;
915
			}
916
		}
917
918
		// Remove linked categories.
919
		if (!$error) {
920
			$sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_project";
921
			$sql .= " WHERE fk_project = ".((int) $this->id);
922
923
			$result = $this->db->query($sql);
924
			if (!$result) {
925
				$error++;
926
				$this->errors[] = $this->db->lasterror();
927
			}
928
		}
929
930
		// Fetch tasks
931
		$this->getLinesArray($user, 0);
932
933
		// Delete tasks
934
		$ret = $this->deleteTasks($user);
935
		if ($ret < 0) {
936
			$error++;
937
		}
938
939
940
		// Delete all child tables
941
		if (!$error) {
942
			$elements = array('categorie_project'); // elements to delete. TODO Make goodway to delete
943
			foreach ($elements as $table) {
944
				if (!$error) {
945
					$sql = "DELETE FROM ".MAIN_DB_PREFIX.$table;
946
					$sql .= " WHERE fk_project = ".((int) $this->id);
947
948
					$result = $this->db->query($sql);
949
					if (!$result) {
950
						$error++;
951
						$this->errors[] = $this->db->lasterror();
952
					}
953
				}
954
			}
955
		}
956
957
		if (!$error) {
958
			$sql = "DELETE FROM ".MAIN_DB_PREFIX."projet_extrafields";
959
			$sql .= " WHERE fk_object = ".((int) $this->id);
960
961
			$resql = $this->db->query($sql);
962
			if (!$resql) {
963
				$this->errors[] = $this->db->lasterror();
964
				$error++;
965
			}
966
		}
967
968
		// Delete project
969
		if (!$error) {
970
			$sql = "DELETE FROM ".MAIN_DB_PREFIX."projet";
971
			$sql .= " WHERE rowid=".((int) $this->id);
972
973
			$resql = $this->db->query($sql);
974
			if (!$resql) {
975
				$this->errors[] = $langs->trans("CantRemoveProject", $langs->transnoentitiesnoconv("ProjectOverview"));
976
				$error++;
977
			}
978
		}
979
980
981
982
		if (empty($error)) {
983
			// We remove directory
984
			$projectref = dol_sanitizeFileName($this->ref);
985
			if ($conf->project->dir_output) {
986
				$dir = $conf->project->dir_output."/".$projectref;
987
				if (file_exists($dir)) {
988
					$res = @dol_delete_dir_recursive($dir);
989
					if (!$res) {
990
						$this->errors[] = 'ErrorFailToDeleteDir';
991
						$error++;
992
					}
993
				}
994
			}
995
996
			if (!$notrigger) {
997
				// Call trigger
998
				$result = $this->call_trigger('PROJECT_DELETE', $user);
999
1000
				if ($result < 0) {
1001
					$error++;
1002
				}
1003
				// End call triggers
1004
			}
1005
		}
1006
1007
		if (empty($error)) {
1008
			$this->db->commit();
1009
			return 1;
1010
		} else {
1011
			foreach ($this->errors as $errmsg) {
1012
				dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
1013
				$this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1014
			}
1015
			dol_syslog(get_class($this)."::delete ".$this->error, LOG_ERR);
1016
			$this->db->rollback();
1017
			return -1;
1018
		}
1019
	}
1020
1021
	/**
1022
	 * Return the count of a type of linked elements of this project
1023
	 *
1024
	 * @param string	$type			The type of the linked elements (e.g. 'propal', 'order', 'invoice', 'order_supplier', 'invoice_supplier')
1025
	 * @param string	$tablename		The name of table associated of the type
1026
	 * @param string	$projectkey 	(optional) Equivalent key to fk_projet for actual type
1027
	 * @return integer					The count of the linked elements (the count is zero on request error too)
1028
	 */
1029
	public function getElementCount($type, $tablename, $projectkey = 'fk_projet')
1030
	{
1031
		if ($this->id <= 0) {
1032
			return 0;
1033
		}
1034
1035
		if ($type == 'agenda') {
1036
			$sql = "SELECT COUNT(id) as nb FROM ".MAIN_DB_PREFIX."actioncomm WHERE fk_project = ".((int) $this->id)." AND entity IN (".getEntity('agenda').")";
1037
		} elseif ($type == 'expensereport') {
1038
			$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 = ".((int) $this->id);
1039
		} elseif ($type == 'project_task') {
1040
			$sql = "SELECT DISTINCT COUNT(pt.rowid) as nb FROM ".MAIN_DB_PREFIX."projet_task as pt WHERE pt.fk_projet = ".((int) $this->id);
1041
		} elseif ($type == 'project_task_time') {	// Case we want to duplicate line foreach user
1042
			$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 = ".((int) $this->id);
1043
		} elseif ($type == 'stock_mouvement') {
1044
			$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 = ".((int) $this->id)." AND ms.type_mouvement = 1";
1045
		} elseif ($type == 'loan') {
1046
			$sql = "SELECT COUNT(l.rowid) as nb FROM ".MAIN_DB_PREFIX."loan as l WHERE l.entity IN (".getEntity('loan').") AND l.fk_projet = ".((int) $this->id);
1047
		} else {
1048
			$sql = "SELECT COUNT(rowid) as nb FROM ".MAIN_DB_PREFIX.$tablename." WHERE ".$projectkey." = ".((int) $this->id)." AND entity IN (".getEntity($type).")";
1049
		}
1050
1051
		$result = $this->db->query($sql);
1052
1053
		if (!$result) {
1054
			return 0;
1055
		}
1056
1057
		$obj = $this->db->fetch_object($result);
1058
1059
		$this->db->free($result);
1060
1061
		return $obj->nb;
1062
	}
1063
1064
	/**
1065
	 * 		Delete tasks with no children first, then task with children recursively
1066
	 *
1067
	 *  	@param     	User		$user		User
1068
	 *		@return		int				<0 if KO, 1 if OK
1069
	 */
1070
	public function deleteTasks($user)
1071
	{
1072
		$countTasks = count($this->lines);
1073
		$deleted = false;
1074
		if ($countTasks) {
1075
			foreach ($this->lines as $task) {
1076
				if ($task->hasChildren() <= 0) {		// If there is no children (or error to detect them)
1077
					$deleted = true;
1078
					$ret = $task->delete($user);
1079
					if ($ret <= 0) {
1080
						$this->errors[] = $this->db->lasterror();
1081
						return -1;
1082
					}
1083
				}
1084
			}
1085
		}
1086
		$this->getLinesArray($user);
1087
		if ($deleted && count($this->lines) < $countTasks) {
1088
			if (count($this->lines)) {
1089
				$this->deleteTasks($this->lines);
1090
			}
1091
		}
1092
1093
		return 1;
1094
	}
1095
1096
	/**
1097
	 * 		Validate a project
1098
	 *
1099
	 * 		@param		User	$user		   User that validate
1100
	 *      @param      int     $notrigger     1=Disable triggers
1101
	 * 		@return		int					   <0 if KO, >0 if OK
1102
	 */
1103
	public function setValid($user, $notrigger = 0)
1104
	{
1105
		global $langs, $conf;
1106
1107
		$error = 0;
1108
1109
		if ($this->statut != 1) {
1110
			// Check parameters
1111
			if (preg_match('/^'.preg_quote($langs->trans("CopyOf").' ').'/', $this->title)) {
1112
				$this->error = $langs->trans("ErrorFieldFormat", $langs->transnoentities("Label")).'. '.$langs->trans('RemoveString', $langs->transnoentitiesnoconv("CopyOf"));
1113
				return -1;
1114
			}
1115
1116
			$this->db->begin();
1117
1118
			$sql = "UPDATE ".MAIN_DB_PREFIX."projet";
1119
			$sql .= " SET fk_statut = 1";
1120
			$sql .= " WHERE rowid = ".((int) $this->id);
1121
			$sql .= " AND entity = ".((int) $conf->entity);
1122
1123
			dol_syslog(get_class($this)."::setValid", LOG_DEBUG);
1124
			$resql = $this->db->query($sql);
1125
			if ($resql) {
1126
				// Call trigger
1127
				if (empty($notrigger)) {
1128
					$result = $this->call_trigger('PROJECT_VALIDATE', $user);
1129
					if ($result < 0) {
1130
						$error++;
1131
					}
1132
					// End call triggers
1133
				}
1134
1135
				if (!$error) {
1136
					$this->statut = 1;
1137
					$this->db->commit();
1138
					return 1;
1139
				} else {
1140
					$this->db->rollback();
1141
					$this->error = join(',', $this->errors);
1142
					dol_syslog(get_class($this)."::setValid ".$this->error, LOG_ERR);
1143
					return -1;
1144
				}
1145
			} else {
1146
				$this->db->rollback();
1147
				$this->error = $this->db->lasterror();
1148
				return -1;
1149
			}
1150
		}
1151
	}
1152
1153
	/**
1154
	 * 		Close a project
1155
	 *
1156
	 * 		@param		User	$user		User that close project
1157
	 * 		@return		int					<0 if KO, 0 if already closed, >0 if OK
1158
	 */
1159
	public function setClose($user)
1160
	{
1161
		global $langs, $conf;
1162
1163
		$now = dol_now();
1164
1165
		$error = 0;
1166
1167
		if ($this->statut != self::STATUS_CLOSED) {
1168
			$this->db->begin();
1169
1170
			$sql = "UPDATE ".MAIN_DB_PREFIX."projet";
1171
			$sql .= " SET fk_statut = ".self::STATUS_CLOSED.", fk_user_close = ".((int) $user->id).", date_close = '".$this->db->idate($now)."'";
1172
			$sql .= " WHERE rowid = ".((int) $this->id);
1173
			$sql .= " AND fk_statut = ".self::STATUS_VALIDATED;
1174
1175
			if (!empty($conf->global->PROJECT_USE_OPPORTUNITIES)) {
1176
				// TODO What to do if fk_opp_status is not code 'WON' or 'LOST'
1177
			}
1178
1179
			dol_syslog(get_class($this)."::setClose", LOG_DEBUG);
1180
			$resql = $this->db->query($sql);
1181
			if ($resql) {
1182
				// Call trigger
1183
				$result = $this->call_trigger('PROJECT_CLOSE', $user);
1184
				if ($result < 0) {
1185
					$error++;
1186
				}
1187
				// End call triggers
1188
1189
				if (!$error) {
1190
					$this->statut = 2;
1191
					$this->db->commit();
1192
					return 1;
1193
				} else {
1194
					$this->db->rollback();
1195
					$this->error = join(',', $this->errors);
1196
					dol_syslog(get_class($this)."::setClose ".$this->error, LOG_ERR);
1197
					return -1;
1198
				}
1199
			} else {
1200
				$this->db->rollback();
1201
				$this->error = $this->db->lasterror();
1202
				return -1;
1203
			}
1204
		}
1205
1206
		return 0;
1207
	}
1208
1209
	/**
1210
	 *  Return status label of object
1211
	 *
1212
	 *  @param  int			$mode       0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto
1213
	 * 	@return string      			Label
1214
	 */
1215
	public function getLibStatut($mode = 0)
1216
	{
1217
		return $this->LibStatut(isset($this->statut) ? $this->statut : $this->status, $mode);
1218
	}
1219
1220
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1221
	/**
1222
	 *  Renvoi status label for a status
1223
	 *
1224
	 *  @param	int		$status     id status
1225
	 *  @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
1226
	 * 	@return string				Label
1227
	 */
1228
	public function LibStatut($status, $mode = 0)
1229
	{
1230
		// phpcs:enable
1231
		global $langs;
1232
1233
		$statustrans = array(
1234
			0 => 'status0',
1235
			1 => 'status4',
1236
			2 => 'status6',
1237
		);
1238
1239
		$statusClass = 'status0';
1240
		if (!empty($statustrans[$status])) {
1241
			$statusClass = $statustrans[$status];
1242
		}
1243
1244
		return dolGetStatus($langs->transnoentitiesnoconv($this->statuts_long[$status]), $langs->transnoentitiesnoconv($this->statuts_short[$status]), '', $statusClass, $mode);
1245
	}
1246
1247
	/**
1248
	 * getTooltipContentArray
1249
	 *
1250
	 * @param array $params ex option, infologin
1251
	 * @since v18
1252
	 * @return array
1253
	 */
1254
	public function getTooltipContentArray($params)
1255
	{
1256
		global $conf, $langs;
1257
1258
		$langs->load('projects');
1259
		$option = $params['option'] ?? '';
1260
		$moreinpopup = $params['morinpopup'] ?? '';
1261
1262
		$datas = [];
1263
		if ($option != 'nolink') {
1264
			$datas['picto'] = img_picto('', $this->picto, 'class="pictofixedwidth"').' <u class="paddingrightonly">'.$langs->trans("Project").'</u>';
1265
		}
1266
		if (isset($this->status)) {
1267
			$datas['picto'] .= ' '.$this->getLibStatut(5);
1268
		}
1269
		$datas['ref'] = (isset($datas['picto']) ? '<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
1270
		$datas['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
1271
		if (isset($this->public)) {
1272
			$datas['visibility'] = '<br><b>'.$langs->trans("Visibility").":</b> ";
1273
			$datas['visibility'] .= ($this->public ? img_picto($langs->trans('SharedProject'), 'world', 'class="pictofixedwidth"').$langs->trans("SharedProject") : img_picto($langs->trans('PrivateProject'), 'private', 'class="pictofixedwidth"').$langs->trans("PrivateProject"));
1274
		}
1275
		if (!empty($this->thirdparty_name)) {
1276
			$datas['thirdparty'] = '<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
1277
		}
1278
		if (!empty($this->date_start)) {
1279
			$datas['datestart'] = '<br><b>'.$langs->trans('DateStart').': </b>'.dol_print_date($this->date_start, 'day'); // The space must be after the : to not being explode when showing the title in img_picto
1280
		}
1281
		if (!empty($this->date_end)) {
1282
			$datas['dateend'] = '<br><b>'.$langs->trans('DateEnd').': </b>'.dol_print_date($this->date_end, 'day'); // The space must be after the : to not being explode when showing the title in img_picto
1283
		}
1284
		if ($moreinpopup) {
1285
			$datas['moreinpopup'] = '<br>'.$moreinpopup;
1286
		}
1287
1288
		return $datas;
1289
	}
1290
1291
	/**
1292
	 * 	Return clickable name (with picto eventually)
1293
	 *
1294
	 * 	@param	int		$withpicto		          0=No picto, 1=Include picto into link, 2=Only picto
1295
	 * 	@param	string	$option			          Variant where the link point to ('', 'nolink')
1296
	 * 	@param	int		$addlabel		          0=Default, 1=Add label into string, >1=Add first chars into string
1297
	 *  @param	string	$moreinpopup	          Text to add into popup
1298
	 *  @param	string	$sep			          Separator between ref and label if option addlabel is set
1299
	 *  @param	int   	$notooltip		          1=Disable tooltip
1300
	 *  @param  int     $save_lastsearch_value    -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
1301
	 *  @param	string	$morecss				  More css on a link
1302
	 * 	@return	string					          String with URL
1303
	 */
1304
	public function getNomUrl($withpicto = 0, $option = '', $addlabel = 0, $moreinpopup = '', $sep = ' - ', $notooltip = 0, $save_lastsearch_value = -1, $morecss = '')
1305
	{
1306
		global $conf, $langs, $user, $hookmanager;
1307
1308
		if (!empty($conf->dol_no_mouse_hover)) {
1309
			$notooltip = 1; // Force disable tooltips
1310
		}
1311
1312
		$result = '';
1313
		if (!empty($conf->global->PROJECT_OPEN_ALWAYS_ON_TAB)) {
1314
			$option = $conf->global->PROJECT_OPEN_ALWAYS_ON_TAB;
1315
		}
1316
		$params = [
1317
			'id' => $this->id,
1318
			'objecttype' => $this->element,
1319
			'moreinpopup' => $moreinpopup,
1320
			'option' => $option,
1321
		];
1322
		$classfortooltip = 'classfortooltip';
1323
		$dataparams = '';
1324
		if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1325
			$classfortooltip = 'classforajaxtooltip';
1326
			$dataparams = ' data-params='.json_encode($params);
1327
			// $label = $langs->trans('Loading');
1328
		}
1329
		$label = implode($this->getTooltipContentArray($params));
1330
1331
		$url = '';
1332
		if ($option != 'nolink') {
1333
			if (preg_match('/\.php$/', $option)) {
1334
				$url = dol_buildpath($option, 1).'?id='.$this->id;
1335
			} elseif ($option == 'task') {
1336
				$url = DOL_URL_ROOT.'/projet/tasks.php?id='.$this->id;
1337
			} elseif ($option == 'preview') {
1338
				$url = DOL_URL_ROOT.'/projet/element.php?id='.$this->id;
1339
			} elseif ($option == 'eventorganization') {
1340
				$url = DOL_URL_ROOT.'/eventorganization/conferenceorbooth_list.php?projectid='.$this->id;
1341
			} else {
1342
				$url = DOL_URL_ROOT.'/projet/card.php?id='.$this->id;
1343
			}
1344
			// Add param to save lastsearch_values or not
1345
			$add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1346
			if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1347
				$add_save_lastsearch_values = 1;
1348
			}
1349
			if ($add_save_lastsearch_values) {
1350
				$url .= '&save_lastsearch_values=1';
1351
			}
1352
		}
1353
1354
		$linkclose = '';
1355
		if (empty($notooltip) && $user->hasRight('projet', 'lire')) {
1356
			if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
1357
				$label = $langs->trans("ShowProject");
1358
				$linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1359
			}
1360
			$linkclose .= $dataparams.' title="'.dol_escape_htmltag($label, 1).'"';
1361
			$linkclose .= ' class="'.$classfortooltip.($morecss ? ' '.$morecss : '').'"';
1362
		} else {
1363
			$linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
1364
		}
1365
1366
		$picto = 'projectpub';
1367
		if (!$this->public) {
1368
			$picto = 'project';
1369
		}
1370
1371
		$linkstart = '<a href="'.$url.'"';
1372
		$linkstart .= $linkclose.'>';
1373
		$linkend = '</a>';
1374
1375
		$result .= $linkstart;
1376
		if ($withpicto) {
1377
			$result .= img_object(($notooltip ? '' : $label), $picto, ($notooltip ? (($withpicto != 2) ? 'class="pictofixedwidth"' : '') : $dataparams.' class="'.(($withpicto != 2) ? 'pictofixedwidth ' : '').$classfortooltip.' pictofixedwidth em088"'), 0, 0, $notooltip ? 0 : 1);
1378
		}
1379
		if ($withpicto != 2) {
1380
			$result .= $this->ref;
1381
		}
1382
		$result .= $linkend;
1383
		if ($withpicto != 2) {
1384
			$result .= (($addlabel && $this->title) ? '<span class="opacitymedium">'.$sep.dol_trunc($this->title, ($addlabel > 1 ? $addlabel : 0)).'</span>' : '');
1385
		}
1386
1387
		global $action;
1388
		$hookmanager->initHooks(array('projectdao'));
1389
		$parameters = array('id'=>$this->id, 'getnomurl' => &$result);
1390
		$reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1391
		if ($reshook > 0) {
1392
			$result = $hookmanager->resPrint;
1393
		} else {
1394
			$result .= $hookmanager->resPrint;
1395
		}
1396
1397
		return $result;
1398
	}
1399
1400
	/**
1401
	 *  Initialise an instance with random values.
1402
	 *  Used to build previews or test instances.
1403
	 * 	id must be 0 if object instance is a specimen.
1404
	 *
1405
	 *  @return	void
1406
	 */
1407
	public function initAsSpecimen()
1408
	{
1409
		global $user, $langs, $conf;
1410
1411
		$now = dol_now();
1412
1413
		// Initialise parameters
1414
		$this->id = 0;
1415
		$this->ref = 'SPECIMEN';
1416
		$this->specimen = 1;
1417
		$this->socid = 1;
1418
		$this->date_c = $now;
1419
		$this->date_m = $now;
1420
		$this->date_start = $now;
1421
		$this->date_end = $now + (3600 * 24 * 365);
1422
		$this->note_public = 'SPECIMEN';
1423
		$this->fk_ele = 20000;
1424
		$this->opp_amount = 20000;
1425
		$this->budget_amount = 10000;
1426
1427
		$this->usage_opportunity = 1;
1428
		$this->usage_task = 1;
1429
		$this->usage_bill_time = 1;
1430
		$this->usage_organize_event = 1;
1431
1432
		/*
1433
		 $nbp = mt_rand(1, 9);
1434
		 $xnbp = 0;
1435
		 while ($xnbp < $nbp)
1436
		 {
1437
		 $line = new Task($this->db);
1438
		 $line->fk_project = 0;
1439
		 $line->label = $langs->trans("Label") . " " . $xnbp;
1440
		 $line->description = $langs->trans("Description") . " " . $xnbp;
1441
1442
		 $this->lines[]=$line;
1443
		 $xnbp++;
1444
		 }
1445
		 */
1446
	}
1447
1448
	/**
1449
	 * 	Check if user has permission on current project
1450
	 *
1451
	 * 	@param	User	$user		Object user to evaluate
1452
	 * 	@param  string	$mode		Type of permission we want to know: 'read', 'write'
1453
	 * 	@return	int					>0 if user has permission, <0 if user has no permission
1454
	 */
1455
	public function restrictedProjectArea(User $user, $mode = 'read')
1456
	{
1457
		// To verify role of users
1458
		$userAccess = 0;
1459
		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))) {
1460
			$userAccess = 1;
1461
		} elseif ($this->public && (($mode == 'read' && !empty($user->rights->projet->lire)) || ($mode == 'write' && !empty($user->rights->projet->creer)) || ($mode == 'delete' && !empty($user->rights->projet->supprimer)))) {
1462
			$userAccess = 1;
1463
		} else {	// No access due to permission to read all projects, so we check if we are a contact of project
1464
			foreach (array('internal', 'external') as $source) {
1465
				$userRole = $this->liste_contact(4, $source);
1466
				$num = count($userRole);
1467
1468
				$nblinks = 0;
1469
				while ($nblinks < $num) {
1470
					if ($source == 'internal' && $user->id == $userRole[$nblinks]['id']) {	// $userRole[$nblinks]['id'] is id of user (llx_user) for internal contacts
1471
						if ($mode == 'read' && $user->rights->projet->lire) {
1472
							$userAccess++;
1473
						}
1474
						if ($mode == 'write' && $user->rights->projet->creer) {
1475
							$userAccess++;
1476
						}
1477
						if ($mode == 'delete' && $user->rights->projet->supprimer) {
1478
							$userAccess++;
1479
						}
1480
					}
1481
					if ($source == 'external' && $user->socid > 0 && $user->socid == $userRole[$nblinks]['socid']) {	// $userRole[$nblinks]['id'] is id of contact (llx_socpeople) or external contacts
1482
						if ($mode == 'read' && $user->rights->projet->lire) {
1483
							$userAccess++;
1484
						}
1485
						if ($mode == 'write' && $user->rights->projet->creer) {
1486
							$userAccess++;
1487
						}
1488
						if ($mode == 'delete' && $user->rights->projet->supprimer) {
1489
							$userAccess++;
1490
						}
1491
					}
1492
					$nblinks++;
1493
				}
1494
			}
1495
			//if (empty($nblinks))	// If nobody has permission, we grant creator
1496
			//{
1497
			//	if ((!empty($this->user_author_id) && $this->user_author_id == $user->id))
1498
			//	{
1499
			//		$userAccess = 1;
1500
			//	}
1501
			//}
1502
		}
1503
1504
		return ($userAccess ? $userAccess : -1);
1505
	}
1506
1507
	/**
1508
	 * Return array of projects a user has permission on, is affected to, or all projects
1509
	 *
1510
	 * @param 	User	$user			User object
1511
	 * @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
1512
	 * @param 	int		$list			0=Return array, 1=Return string list
1513
	 * @param	int		$socid			0=No filter on third party, id of third party
1514
	 * @param	string	$filter			additionnal filter on project (statut, ref, ...)
1515
	 * @return 	array|string			Array of projects id, or string with projects id separated with "," if list is 1
1516
	 */
1517
	public function getProjectsAuthorizedForUser($user, $mode = 0, $list = 0, $socid = 0, $filter = '')
1518
	{
1519
		$projects = array();
1520
		$temp = array();
1521
1522
		$sql = "SELECT ".(($mode == 0 || $mode == 1) ? "DISTINCT " : "")."p.rowid, p.ref";
1523
		$sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
1524
		if ($mode == 0) {
1525
			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_contact as ec ON ec.element_id = p.rowid";
1526
		} elseif ($mode == 1) {
1527
			$sql .= ", ".MAIN_DB_PREFIX."element_contact as ec";
1528
		} elseif ($mode == 2) {
1529
			// No filter. Use this if user has permission to see all project
1530
		}
1531
		$sql .= " WHERE p.entity IN (".getEntity('project').")";
1532
		// Internal users must see project he is contact to even if project linked to a third party he can't see.
1533
		//if ($socid || ! $user->rights->societe->client->voir)	$sql.= " AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".((int) $socid).")";
1534
		if ($socid > 0) {
1535
			$sql .= " AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".((int) $socid).")";
1536
		}
1537
1538
		// Get id of types of contacts for projects (This list never contains a lot of elements)
1539
		$listofprojectcontacttype = array();
1540
		$sql2 = "SELECT ctc.rowid, ctc.code FROM ".MAIN_DB_PREFIX."c_type_contact as ctc";
1541
		$sql2 .= " WHERE ctc.element = '".$this->db->escape($this->element)."'";
1542
		$sql2 .= " AND ctc.source = 'internal'";
1543
		$resql = $this->db->query($sql2);
1544
		if ($resql) {
1545
			while ($obj = $this->db->fetch_object($resql)) {
1546
				$listofprojectcontacttype[$obj->rowid] = $obj->code;
1547
			}
1548
		} else {
1549
			dol_print_error($this->db);
1550
		}
1551
		if (count($listofprojectcontacttype) == 0) {
1552
			$listofprojectcontacttype[0] = '0'; // To avoid syntax error if not found
1553
		}
1554
1555
		if ($mode == 0) {
1556
			$sql .= " AND ( p.public = 1";
1557
			$sql .= " OR ( ec.fk_c_type_contact IN (".$this->db->sanitize(join(',', array_keys($listofprojectcontacttype))).")";
1558
			$sql .= " AND ec.fk_socpeople = ".((int) $user->id).")";
1559
			$sql .= " )";
1560
		} elseif ($mode == 1) {
1561
			$sql .= " AND ec.element_id = p.rowid";
1562
			$sql .= " AND (";
1563
			$sql .= "  ( ec.fk_c_type_contact IN (".$this->db->sanitize(join(',', array_keys($listofprojectcontacttype))).")";
1564
			$sql .= " AND ec.fk_socpeople = ".((int) $user->id).")";
1565
			$sql .= " )";
1566
		} elseif ($mode == 2) {
1567
			// No filter. Use this if user has permission to see all project
1568
		}
1569
1570
		$sql .= $filter;
1571
		//print $sql;
1572
1573
		$resql = $this->db->query($sql);
1574
		if ($resql) {
1575
			$num = $this->db->num_rows($resql);
1576
			$i = 0;
1577
			while ($i < $num) {
1578
				$row = $this->db->fetch_row($resql);
1579
				$projects[$row[0]] = $row[1];
1580
				$temp[] = $row[0];
1581
				$i++;
1582
			}
1583
1584
			$this->db->free($resql);
1585
1586
			if ($list) {
1587
				if (empty($temp)) {
1588
					return '0';
1589
				}
1590
				$result = implode(',', $temp);
1591
				return $result;
1592
			}
1593
		} else {
1594
			dol_print_error($this->db);
1595
		}
1596
1597
		return $projects;
1598
	}
1599
1600
	/**
1601
	 * Load an object from its id and create a new one in database
1602
	 *
1603
	 *  @param	User	$user		          User making the clone
1604
	 *  @param	int		$fromid     	      Id of object to clone
1605
	 *  @param	bool	$clone_contact	      Clone contact of project
1606
	 *  @param	bool	$clone_task		      Clone task of project
1607
	 *  @param	bool	$clone_project_file	  Clone file of project
1608
	 *  @param	bool	$clone_task_file	  Clone file of task (if task are copied)
1609
	 *  @param	bool	$clone_note		      Clone note of project
1610
	 *  @param	bool	$move_date		      Move task date on clone
1611
	 *  @param	integer	$notrigger		      No trigger flag
1612
	 *  @param  int     $newthirdpartyid      New thirdparty id
1613
	 *  @return	int						      New id of clone
1614
	 */
1615
	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)
1616
	{
1617
		global $langs, $conf;
1618
1619
		$error = 0;
1620
1621
		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);
1622
1623
		$now = dol_mktime(0, 0, 0, idate('m', dol_now()), idate('d', dol_now()), idate('Y', dol_now()));
1624
1625
		$clone_project = new Project($this->db);
1626
1627
		$clone_project->context['createfromclone'] = 'createfromclone';
1628
1629
		$this->db->begin();
1630
1631
		// Load source object
1632
		$clone_project->fetch($fromid);
1633
		$clone_project->fetch_optionals();
1634
		if ($newthirdpartyid > 0) {
1635
			$clone_project->socid = $newthirdpartyid;
1636
		}
1637
		$clone_project->fetch_thirdparty();
1638
1639
		$orign_dt_start = $clone_project->date_start;
1640
		$orign_project_ref = $clone_project->ref;
1641
1642
		$clone_project->id = 0;
1643
		if ($move_date) {
1644
			$clone_project->date_start = $now;
1645
			if (!(empty($clone_project->date_end))) {
1646
				$clone_project->date_end = $clone_project->date_end + ($now - $orign_dt_start);
1647
			}
1648
		}
1649
1650
		$clone_project->date_c = $now;
1651
1652
		if (!$clone_note) {
1653
			$clone_project->note_private = '';
1654
			$clone_project->note_public = '';
1655
		}
1656
1657
		//Generate next ref
1658
		$defaultref = '';
1659
		$obj = empty($conf->global->PROJECT_ADDON) ? 'mod_project_simple' : $conf->global->PROJECT_ADDON;
1660
		// Search template files
1661
		$file = ''; $classname = ''; $filefound = 0;
1662
		$dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
1663
		foreach ($dirmodels as $reldir) {
1664
			$file = dol_buildpath($reldir."core/modules/project/".$obj.'.php', 0);
1665
			if (file_exists($file)) {
1666
				$filefound = 1;
1667
				dol_include_once($reldir."core/modules/project/".$obj.'.php');
1668
				$modProject = new $obj;
1669
				$defaultref = $modProject->getNextValue(is_object($clone_project->thirdparty) ? $clone_project->thirdparty : null, $clone_project);
1670
				break;
1671
			}
1672
		}
1673
		if (is_numeric($defaultref) && $defaultref <= 0) {
1674
			$defaultref = '';
1675
		}
1676
1677
		$clone_project->ref = $defaultref;
1678
		$clone_project->title = $langs->trans("CopyOf").' '.$clone_project->title;
1679
1680
		// Create clone
1681
		$result = $clone_project->create($user, $notrigger);
1682
1683
		// Other options
1684
		if ($result < 0) {
1685
			$this->error .= $clone_project->error;
1686
			$error++;
1687
		}
1688
1689
		if (!$error) {
1690
			//Get the new project id
1691
			$clone_project_id = $clone_project->id;
1692
1693
			//Note Update
1694
			if (!$clone_note) {
1695
				$clone_project->note_private = '';
1696
				$clone_project->note_public = '';
1697
			} else {
1698
				$this->db->begin();
1699
				$res = $clone_project->update_note(dol_html_entity_decode($clone_project->note_public, ENT_QUOTES | ENT_HTML5), '_public');
1700
				if ($res < 0) {
1701
					$this->error .= $clone_project->error;
1702
					$error++;
1703
					$this->db->rollback();
1704
				} else {
1705
					$this->db->commit();
1706
				}
1707
1708
				$this->db->begin();
1709
				$res = $clone_project->update_note(dol_html_entity_decode($clone_project->note_private, ENT_QUOTES | ENT_HTML5), '_private');
1710
				if ($res < 0) {
1711
					$this->error .= $clone_project->error;
1712
					$error++;
1713
					$this->db->rollback();
1714
				} else {
1715
					$this->db->commit();
1716
				}
1717
			}
1718
1719
			//Duplicate contact
1720
			if ($clone_contact) {
1721
				$origin_project = new Project($this->db);
1722
				$origin_project->fetch($fromid);
1723
1724
				foreach (array('internal', 'external') as $source) {
1725
					$tab = $origin_project->liste_contact(-1, $source);
1726
					if (is_array($tab) && count($tab)>0) {
1727
						foreach ($tab as $contacttoadd) {
1728
							$clone_project->add_contact($contacttoadd['id'], $contacttoadd['code'], $contacttoadd['source'], $notrigger);
1729
							if ($clone_project->error == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1730
								$langs->load("errors");
1731
								$this->error .= $langs->trans("ErrorThisContactIsAlreadyDefinedAsThisType");
1732
								$error++;
1733
							} else {
1734
								if ($clone_project->error != '') {
1735
									$this->error .= $clone_project->error;
1736
									$error++;
1737
								}
1738
							}
1739
						}
1740
					} elseif ($tab < 0) {
1741
						$this->error .= $origin_project->error;
1742
						$error++;
1743
					}
1744
				}
1745
			}
1746
1747
			//Duplicate file
1748
			if ($clone_project_file) {
1749
				require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1750
1751
				$clone_project_dir = $conf->project->dir_output."/".dol_sanitizeFileName($defaultref);
1752
				$ori_project_dir = $conf->project->dir_output."/".dol_sanitizeFileName($orign_project_ref);
1753
1754
				if (dol_mkdir($clone_project_dir) >= 0) {
1755
					$filearray = dol_dir_list($ori_project_dir, "files", 0, '', '(\.meta|_preview.*\.png)$', '', SORT_ASC, 1);
1756
					foreach ($filearray as $key => $file) {
1757
						$rescopy = dol_copy($ori_project_dir.'/'.$file['name'], $clone_project_dir.'/'.$file['name'], 0, 1);
1758
						if (is_numeric($rescopy) && $rescopy < 0) {
1759
							$this->error .= $langs->trans("ErrorFailToCopyFile", $ori_project_dir.'/'.$file['name'], $clone_project_dir.'/'.$file['name']);
1760
							$error++;
1761
						}
1762
					}
1763
				} else {
1764
					$this->error .= $langs->trans('ErrorInternalErrorDetected').':dol_mkdir';
1765
					$error++;
1766
				}
1767
			}
1768
1769
			//Duplicate task
1770
			if ($clone_task) {
1771
				require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
1772
1773
				$taskstatic = new Task($this->db);
1774
1775
				// Security check
1776
				$socid = 0;
1777
				if ($user->socid > 0) {
1778
					$socid = $user->socid;
1779
				}
1780
1781
				$tasksarray = $taskstatic->getTasksArray(0, 0, $fromid, $socid, 0);
1782
1783
				$tab_conv_child_parent = array();
1784
1785
				// Loop on each task, to clone it
1786
				foreach ($tasksarray as $tasktoclone) {
1787
					$result_clone = $taskstatic->createFromClone($user, $tasktoclone->id, $clone_project_id, $tasktoclone->fk_parent, $move_date, true, false, $clone_task_file, true, false);
1788
					if ($result_clone <= 0) {
1789
						$this->error .= $taskstatic->error;
1790
						$error++;
1791
					} else {
1792
						$new_task_id = $result_clone;
1793
						$taskstatic->fetch($tasktoclone->id);
1794
1795
						//manage new parent clone task id
1796
						// if the current task has child we store the original task id and the equivalent clone task id
1797
						if (($taskstatic->hasChildren()) && !array_key_exists($tasktoclone->id, $tab_conv_child_parent)) {
1798
							$tab_conv_child_parent[$tasktoclone->id] = $new_task_id;
1799
						}
1800
					}
1801
				}
1802
1803
				//Parse all clone node to be sure to update new parent
1804
				$tasksarray = $taskstatic->getTasksArray(0, 0, $clone_project_id, $socid, 0);
1805
				foreach ($tasksarray as $task_cloned) {
1806
					$taskstatic->fetch($task_cloned->id);
1807
					if ($taskstatic->fk_task_parent != 0) {
1808
						$taskstatic->fk_task_parent = $tab_conv_child_parent[$taskstatic->fk_task_parent];
1809
					}
1810
					$res = $taskstatic->update($user, $notrigger);
1811
					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 1786. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1812
						$this->error .= $taskstatic->error;
1813
						$error++;
1814
					}
1815
				}
1816
			}
1817
		}
1818
1819
		unset($clone_project->context['createfromclone']);
1820
1821
		if (!$error) {
1822
			$this->db->commit();
1823
			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...
1824
		} else {
1825
			$this->db->rollback();
1826
			dol_syslog(get_class($this)."::createFromClone nbError: ".$error." error : ".$this->error, LOG_ERR);
1827
			return -1;
1828
		}
1829
	}
1830
1831
1832
	/**
1833
	 *    Shift project task date from current date to delta
1834
	 *
1835
	 *    @param	integer		$old_project_dt_start	Old project start date
1836
	 *    @return	int				                    1 if OK or < 0 if KO
1837
	 */
1838
	public function shiftTaskDate($old_project_dt_start)
1839
	{
1840
		global $user, $langs, $conf;
1841
1842
		$error = 0;
1843
		$result = 0;
1844
1845
		$taskstatic = new Task($this->db);
1846
1847
		// Security check
1848
		$socid = 0;
1849
		if ($user->socid > 0) {
1850
			$socid = $user->socid;
1851
		}
1852
1853
		$tasksarray = $taskstatic->getTasksArray(0, 0, $this->id, $socid, 0);
1854
1855
		foreach ($tasksarray as $tasktoshiftdate) {
1856
			$to_update = false;
1857
			// Fetch only if update of date will be made
1858
			if ((!empty($tasktoshiftdate->date_start)) || (!empty($tasktoshiftdate->date_end))) {
1859
				//dol_syslog(get_class($this)."::shiftTaskDate to_update", LOG_DEBUG);
1860
				$to_update = true;
1861
				$task = new Task($this->db);
1862
				$result = $task->fetch($tasktoshiftdate->id);
1863
				if (!$result) {
1864
					$error++;
1865
					$this->error .= $task->error;
1866
				}
1867
			}
1868
			//print "$this->date_start + $tasktoshiftdate->date_start - $old_project_dt_start";exit;
1869
1870
			//Calcultate new task start date with difference between old proj start date and origin task start date
1871
			if (!empty($tasktoshiftdate->date_start)) {
1872
				$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...
1873
			}
1874
1875
			//Calcultate new task end date with difference between origin proj end date and origin task end date
1876
			if (!empty($tasktoshiftdate->date_end)) {
1877
				$task->date_end = $this->date_start + ($tasktoshiftdate->date_end - $old_project_dt_start);
1878
			}
1879
1880
			if ($to_update) {
1881
				$result = $task->update($user);
1882
				if (!$result) {
1883
					$error++;
1884
					$this->error .= $task->error;
1885
				}
1886
			}
1887
		}
1888
		if ($error != 0) {
1889
			return -1;
1890
		}
1891
		return $result;
1892
	}
1893
1894
1895
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1896
	/**
1897
	 *    Associate element to a project
1898
	 *
1899
	 *    @param	string	$tableName			Table of the element to update
1900
	 *    @param	int		$elementSelectId	Key-rowid of the line of the element to update
1901
	 *    @return	int							1 if OK or < 0 if KO
1902
	 */
1903
	public function update_element($tableName, $elementSelectId)
1904
	{
1905
		// phpcs:enable
1906
		$sql = "UPDATE ".MAIN_DB_PREFIX.$tableName;
1907
1908
		if ($tableName == "actioncomm") {
1909
			$sql .= " SET fk_project=".$this->id;
1910
			$sql .= " WHERE id=".((int) $elementSelectId);
1911
		} elseif ($tableName == "entrepot") {
1912
			$sql .= " SET fk_project=".$this->id;
1913
			$sql .= " WHERE rowid=".((int) $elementSelectId);
1914
		} else {
1915
			$sql .= " SET fk_projet=".$this->id;
1916
			$sql .= " WHERE rowid=".((int) $elementSelectId);
1917
		}
1918
1919
		dol_syslog(get_class($this)."::update_element", LOG_DEBUG);
1920
		$resql = $this->db->query($sql);
1921
		if (!$resql) {
1922
			$this->error = $this->db->lasterror();
1923
			return -1;
1924
		} else {
1925
			return 1;
1926
		}
1927
	}
1928
1929
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1930
	/**
1931
	 *    Associate element to a project
1932
	 *
1933
	 *    @param	string	$tableName			Table of the element to update
1934
	 *    @param	int		$elementSelectId	Key-rowid of the line of the element to update
1935
	 *    @param	string	$projectfield	    The column name that stores the link with the project
1936
	 *
1937
	 *    @return	int							1 if OK or < 0 if KO
1938
	 */
1939
	public function remove_element($tableName, $elementSelectId, $projectfield = 'fk_projet')
1940
	{
1941
		// phpcs:enable
1942
		$sql = "UPDATE ".MAIN_DB_PREFIX.$tableName;
1943
1944
		if ($tableName == "actioncomm") {
1945
			$sql .= " SET fk_project=NULL";
1946
			$sql .= " WHERE id=".((int) $elementSelectId);
1947
		} else {
1948
			$sql .= " SET ".$projectfield."=NULL";
1949
			$sql .= " WHERE rowid=".((int) $elementSelectId);
1950
		}
1951
1952
		dol_syslog(get_class($this)."::remove_element", LOG_DEBUG);
1953
		$resql = $this->db->query($sql);
1954
		if (!$resql) {
1955
			$this->error = $this->db->lasterror();
1956
			return -1;
1957
		} else {
1958
			return 1;
1959
		}
1960
	}
1961
1962
	/**
1963
	 *  Create an intervention document on disk using template defined into PROJECT_ADDON_PDF
1964
	 *
1965
	 *  @param	string		$modele			Force template to use ('' by default)
1966
	 *  @param	Translate	$outputlangs	Objet lang to use for translation
1967
	 *  @param  int			$hidedetails    Hide details of lines
1968
	 *  @param  int			$hidedesc       Hide description
1969
	 *  @param  int			$hideref        Hide ref
1970
	 *  @return int         				0 if KO, 1 if OK
1971
	 */
1972
	public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
1973
	{
1974
		global $conf, $langs;
1975
1976
		$langs->load("projects");
1977
1978
		if (!dol_strlen($modele)) {
1979
			$modele = 'baleine';
1980
1981
			if ($this->model_pdf) {
1982
				$modele = $this->model_pdf;
1983
			} elseif (!empty($conf->global->PROJECT_ADDON_PDF)) {
1984
				$modele = $conf->global->PROJECT_ADDON_PDF;
1985
			}
1986
		}
1987
1988
		$modelpath = "core/modules/project/doc/";
1989
1990
		return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
1991
	}
1992
1993
1994
	/**
1995
	 * Load time spent into this->weekWorkLoad and this->weekWorkLoadPerTask for all day of a week of project.
1996
	 * Note: array weekWorkLoad and weekWorkLoadPerTask are reset and filled at each call.
1997
	 *
1998
	 * @param 	int		$datestart		First day of week (use dol_get_first_day to find this date)
1999
	 * @param 	int		$taskid			Filter on a task id
2000
	 * @param 	int		$userid			Time spent by a particular user
2001
	 * @return 	int						<0 if OK, >0 if KO
2002
	 */
2003
	public function loadTimeSpent($datestart, $taskid = 0, $userid = 0)
2004
	{
2005
		$error = 0;
2006
2007
		$this->weekWorkLoad = array();
2008
		$this->weekWorkLoadPerTask = array();
2009
2010
		if (empty($datestart)) {
2011
			dol_print_error('', 'Error datestart parameter is empty');
2012
		}
2013
2014
		$sql = "SELECT ptt.rowid as taskid, ptt.task_duration, ptt.task_date, ptt.task_datehour, ptt.fk_task";
2015
		$sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time AS ptt, ".MAIN_DB_PREFIX."projet_task as pt";
2016
		$sql .= " WHERE ptt.fk_task = pt.rowid";
2017
		$sql .= " AND pt.fk_projet = ".((int) $this->id);
2018
		$sql .= " AND (ptt.task_date >= '".$this->db->idate($datestart)."' ";
2019
		$sql .= " AND ptt.task_date <= '".$this->db->idate(dol_time_plus_duree($datestart, 1, 'w') - 1)."')";
2020
		if ($taskid) {
2021
			$sql .= " AND ptt.fk_task=".((int) $taskid);
2022
		}
2023
		if (is_numeric($userid)) {
2024
			$sql .= " AND ptt.fk_user=".((int) $userid);
2025
		}
2026
2027
		//print $sql;
2028
		$resql = $this->db->query($sql);
2029
		if ($resql) {
2030
			$daylareadyfound = array();
2031
2032
			$num = $this->db->num_rows($resql);
2033
			$i = 0;
2034
			// Loop on each record found, so each couple (project id, task id)
2035
			while ($i < $num) {
2036
				$obj = $this->db->fetch_object($resql);
2037
				$day = $this->db->jdate($obj->task_date); // task_date is date without hours
2038
				if (empty($daylareadyfound[$day])) {
2039
					$this->weekWorkLoad[$day] = $obj->task_duration;
2040
					$this->weekWorkLoadPerTask[$day][$obj->fk_task] = $obj->task_duration;
2041
				} else {
2042
					$this->weekWorkLoad[$day] += $obj->task_duration;
2043
					$this->weekWorkLoadPerTask[$day][$obj->fk_task] += $obj->task_duration;
2044
				}
2045
				$daylareadyfound[$day] = 1;
2046
				$i++;
2047
			}
2048
			$this->db->free($resql);
2049
			return 1;
2050
		} else {
2051
			$this->error = "Error ".$this->db->lasterror();
2052
			dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
2053
			return -1;
2054
		}
2055
	}
2056
2057
	/**
2058
	 * Load time spent into this->weekWorkLoad and this->weekWorkLoadPerTask for all day of a week of project.
2059
	 * Note: array weekWorkLoad and weekWorkLoadPerTask are reset and filled at each call.
2060
	 *
2061
	 * @param 	int		$datestart		First day of week (use dol_get_first_day to find this date)
2062
	 * @param 	int		$taskid			Filter on a task id
2063
	 * @param 	int		$userid			Time spent by a particular user
2064
	 * @return 	int						<0 if OK, >0 if KO
2065
	 */
2066
	public function loadTimeSpentMonth($datestart, $taskid = 0, $userid = 0)
2067
	{
2068
		$error = 0;
2069
2070
		$this->monthWorkLoad = array();
2071
		$this->monthWorkLoadPerTask = array();
2072
2073
		if (empty($datestart)) {
2074
			dol_print_error('', 'Error datestart parameter is empty');
2075
		}
2076
2077
		$sql = "SELECT ptt.rowid as taskid, ptt.task_duration, ptt.task_date, ptt.task_datehour, ptt.fk_task";
2078
		$sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time AS ptt, ".MAIN_DB_PREFIX."projet_task as pt";
2079
		$sql .= " WHERE ptt.fk_task = pt.rowid";
2080
		$sql .= " AND pt.fk_projet = ".((int) $this->id);
2081
		$sql .= " AND (ptt.task_date >= '".$this->db->idate($datestart)."' ";
2082
		$sql .= " AND ptt.task_date <= '".$this->db->idate(dol_time_plus_duree($datestart, 1, 'm') - 1)."')";
2083
		if ($taskid) {
2084
			$sql .= " AND ptt.fk_task=".((int) $taskid);
2085
		}
2086
		if (is_numeric($userid)) {
2087
			$sql .= " AND ptt.fk_user=".((int) $userid);
2088
		}
2089
2090
		//print $sql;
2091
		$resql = $this->db->query($sql);
2092
		if ($resql) {
2093
			$weekalreadyfound = array();
2094
2095
			$num = $this->db->num_rows($resql);
2096
			$i = 0;
2097
			// Loop on each record found, so each couple (project id, task id)
2098
			while ($i < $num) {
2099
				$obj = $this->db->fetch_object($resql);
2100
				if (!empty($obj->task_date)) {
2101
					$date = explode('-', $obj->task_date);
2102
					$week_number = getWeekNumber($date[2], $date[1], $date[0]);
2103
				}
2104
				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...
2105
					$this->monthWorkLoad[$week_number] = $obj->task_duration;
2106
					$this->monthWorkLoadPerTask[$week_number][$obj->fk_task] = $obj->task_duration;
2107
				} else {
2108
					$this->monthWorkLoad[$week_number] += $obj->task_duration;
2109
					$this->monthWorkLoadPerTask[$week_number][$obj->fk_task] += $obj->task_duration;
2110
				}
2111
				$weekalreadyfound[$week_number] = 1;
2112
				$i++;
2113
			}
2114
			$this->db->free($resql);
2115
			return 1;
2116
		} else {
2117
			$this->error = "Error ".$this->db->lasterror();
2118
			dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
2119
			return -1;
2120
		}
2121
	}
2122
2123
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2124
	/**
2125
	 * Load indicators for dashboard (this->nbtodo and this->nbtodolate)
2126
	 *
2127
	 * @param	User	$user   Objet user
2128
	 * @return WorkboardResponse|int <0 if KO, WorkboardResponse if OK
2129
	 */
2130
	public function load_board($user)
2131
	{
2132
		// phpcs:enable
2133
		global $conf, $langs;
2134
2135
		// For external user, no check is done on company because readability is managed by public status of project and assignement.
2136
		//$socid=$user->socid;
2137
2138
		$response = new WorkboardResponse();
2139
		$response->warning_delay = $conf->project->warning_delay / 60 / 60 / 24;
2140
		$response->label = $langs->trans("OpenedProjects");
2141
		$response->labelShort = $langs->trans("Opened");
2142
		$response->url = DOL_URL_ROOT.'/projet/list.php?search_project_user=-1&search_status=1&mainmenu=project';
2143
		$response->img = img_object('', "projectpub");
2144
		$response->nbtodo = 0;
2145
		$response->nbtodolate = 0;
2146
2147
		$sql = "SELECT p.rowid, p.fk_statut as status, p.fk_opp_status, p.datee as datee";
2148
		$sql .= " FROM (".MAIN_DB_PREFIX."projet as p";
2149
		$sql .= ")";
2150
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s on p.fk_soc = s.rowid";
2151
		// For external user, no check is done on company permission because readability is managed by public status of project and assignement.
2152
		//if (! $user->rights->societe->client->voir && ! $socid) $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON sc.fk_soc = s.rowid";
2153
		$sql .= " WHERE p.fk_statut = 1";
2154
		$sql .= " AND p.entity IN (".getEntity('project').')';
2155
2156
2157
		$projectsListId = null;
2158
		if (!$user->hasRight("projet", "all", "lire")) {
2159
			$response->url = DOL_URL_ROOT.'/projet/list.php?search_status=1&mainmenu=project';
2160
			$projectsListId = $this->getProjectsAuthorizedForUser($user, 0, 1);
2161
			if (empty($projectsListId)) {
2162
				return $response;
2163
			}
2164
2165
			$sql .= " AND p.rowid IN (".$this->db->sanitize($projectsListId).")";
2166
		}
2167
2168
		// No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser
2169
		//if ($socid || ! $user->rights->societe->client->voir)	$sql.= "  AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".((int) $socid).")";
2170
		// For external user, no check is done on company permission because readability is managed by public status of project and assignement.
2171
		//if (! $user->rights->societe->client->voir && ! $socid) $sql.= " AND ((s.rowid = sc.fk_soc AND sc.fk_user = ".((int) $user->id).") OR (s.rowid IS NULL))";
2172
2173
		//print $sql;
2174
		$resql = $this->db->query($sql);
2175
		if ($resql) {
2176
			$project_static = new Project($this->db);
2177
2178
2179
			// This assignment in condition is not a bug. It allows walking the results.
2180
			while ($obj = $this->db->fetch_object($resql)) {
2181
				$response->nbtodo++;
2182
2183
				$project_static->statut = $obj->status;
2184
				$project_static->opp_status = $obj->fk_opp_status;
2185
				$project_static->date_end = $this->db->jdate($obj->datee);
2186
2187
				if ($project_static->hasDelay()) {
2188
					$response->nbtodolate++;
2189
				}
2190
			}
2191
2192
			return $response;
2193
		}
2194
2195
		$this->error = $this->db->error();
2196
		return -1;
2197
	}
2198
2199
	/**
2200
	 * Function used to replace a thirdparty id with another one.
2201
	 *
2202
	 * @param DoliDB $dbs 		Database handler
2203
	 * @param int $origin_id 	Old thirdparty id
2204
	 * @param int $dest_id 		New thirdparty id
2205
	 * @return bool
2206
	 */
2207
	public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2208
	{
2209
		$tables = array(
2210
			'projet'
2211
		);
2212
2213
		return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2214
	}
2215
2216
2217
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2218
	/**
2219
	 * Charge indicateurs this->nb pour le tableau de bord
2220
	 *
2221
	 * @return     int         <0 if KO, >0 if OK
2222
	 */
2223
	public function load_state_board()
2224
	{
2225
		// phpcs:enable
2226
		global $user;
2227
2228
		$this->nb = array();
2229
2230
		$sql = "SELECT count(p.rowid) as nb";
2231
		$sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
2232
		$sql .= " WHERE";
2233
		$sql .= " p.entity IN (".getEntity('project').")";
2234
		if (empty($user->rights->projet->all->lire)) {
2235
			$projectsListId = $this->getProjectsAuthorizedForUser($user, 0, 1);
2236
			$sql .= "AND p.rowid IN (".$this->db->sanitize($projectsListId).")";
2237
		}
2238
2239
		$resql = $this->db->query($sql);
2240
		if ($resql) {
2241
			while ($obj = $this->db->fetch_object($resql)) {
2242
				$this->nb["projects"] = $obj->nb;
2243
			}
2244
			$this->db->free($resql);
2245
			return 1;
2246
		} else {
2247
			dol_print_error($this->db);
2248
			$this->error = $this->db->error();
2249
			return -1;
2250
		}
2251
	}
2252
2253
2254
	/**
2255
	 * Is the project delayed?
2256
	 *
2257
	 * @return bool
2258
	 */
2259
	public function hasDelay()
2260
	{
2261
		global $conf;
2262
2263
		if (!($this->statut == self::STATUS_VALIDATED)) {
2264
			return false;
2265
		}
2266
		if (!$this->date_end) {
2267
			return false;
2268
		}
2269
2270
		$now = dol_now();
2271
2272
		return ($this->date_end) < ($now - $conf->project->warning_delay);
2273
	}
2274
2275
2276
	/**
2277
	 *	Charge les informations d'ordre info dans l'objet commande
2278
	 *
2279
	 *	@param  int		$id       Id of order
2280
	 *	@return	void
2281
	 */
2282
	public function info($id)
2283
	{
2284
		$sql = 'SELECT c.rowid, datec as datec, tms as datem,';
2285
		$sql .= ' date_close as datecloture,';
2286
		$sql .= ' fk_user_creat as fk_user_author, fk_user_close as fk_use_cloture';
2287
		$sql .= ' FROM '.MAIN_DB_PREFIX.'projet as c';
2288
		$sql .= ' WHERE c.rowid = '.((int) $id);
2289
		$result = $this->db->query($sql);
2290
		if ($result) {
2291
			if ($this->db->num_rows($result)) {
2292
				$obj = $this->db->fetch_object($result);
2293
				$this->id = $obj->rowid;
2294
				if ($obj->fk_user_author) {
2295
					$cuser = new User($this->db);
2296
					$cuser->fetch($obj->fk_user_author);
2297
					$this->user_creation = $cuser;
2298
				}
2299
2300
				if (!empty($obj->fk_user_cloture)) {
2301
					$cluser = new User($this->db);
2302
					$cluser->fetch($obj->fk_user_cloture);
2303
					$this->user_cloture = $cluser;
2304
				}
2305
2306
				$this->date_creation     = $this->db->jdate($obj->datec);
2307
				$this->date_modification = $this->db->jdate($obj->datem);
2308
				$this->date_cloture      = $this->db->jdate($obj->datecloture);
2309
			}
2310
2311
			$this->db->free($result);
2312
		} else {
2313
			dol_print_error($this->db);
2314
		}
2315
	}
2316
2317
	/**
2318
	 * Sets object to supplied categories.
2319
	 *
2320
	 * Deletes object from existing categories not supplied.
2321
	 * Adds it to non existing supplied categories.
2322
	 * Existing categories are left untouch.
2323
	 *
2324
	 * @param 	int[]|int 	$categories 	Category or categories IDs
2325
	 * @return 	int							<0 if KO, >0 if OK
2326
	 */
2327
	public function setCategories($categories)
2328
	{
2329
		require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
2330
		return parent::setCategoriesCommon($categories, Categorie::TYPE_PROJECT);
2331
	}
2332
2333
2334
	/**
2335
	 * 	Create an array of tasks of current project
2336
	 *
2337
	 *  @param  User	$user       		Object user we want project allowed to
2338
	 * @param	int		$loadRoleMode		1= will test Roles on task;  0 used in delete project action
2339
	 * 	@return int							>0 if OK, <0 if KO
2340
	 */
2341
	public function getLinesArray($user, $loadRoleMode = 1)
2342
	{
2343
		require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
2344
		$taskstatic = new Task($this->db);
2345
2346
		$this->lines = $taskstatic->getTasksArray(0, $user, $this->id, 0, 0, '',  '-1', '', 0, 0, array(),  0,  array(),  0,  $loadRoleMode);
2347
	}
2348
2349
	/**
2350
	 *  Function sending an email to the current member with the text supplied in parameter.
2351
	 *
2352
	 *  @param	string	$text				Content of message (not html entities encoded)
2353
	 *  @param	string	$subject			Subject of message
2354
	 *  @param 	array	$filename_list      Array of attached files
2355
	 *  @param 	array	$mimetype_list      Array of mime types of attached files
2356
	 *  @param 	array	$mimefilename_list  Array of public names of attached files
2357
	 *  @param 	string	$addr_cc            Email cc
2358
	 *  @param 	string	$addr_bcc           Email bcc
2359
	 *  @param 	int		$deliveryreceipt	Ask a delivery receipt
2360
	 *  @param	int		$msgishtml			1=String IS already html, 0=String IS NOT html, -1=Unknown need autodetection
2361
	 *  @param	string	$errors_to			erros to
2362
	 *  @param	string	$moreinheader		Add more html headers
2363
	 *  @since V18
2364
	 *  @return	int							<0 if KO, >0 if OK
2365
	 */
2366
	public function sendEmail($text, $subject, $filename_list = array(), $mimetype_list = array(), $mimefilename_list = array(), $addr_cc = "", $addr_bcc = "", $deliveryreceipt = 0, $msgishtml = -1, $errors_to = '', $moreinheader = '')
2367
	{
2368
		global $conf, $langs;
2369
		// TODO EMAIL
2370
2371
		return 1;
2372
	}
2373
}
2374