Passed
Branch develop (2765c7)
by
unknown
99:11
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
	 * 	Return clickable name (with picto eventually)
1249
	 *
1250
	 * 	@param	int		$withpicto		          0=No picto, 1=Include picto into link, 2=Only picto
1251
	 * 	@param	string	$option			          Variant where the link point to ('', 'nolink')
1252
	 * 	@param	int		$addlabel		          0=Default, 1=Add label into string, >1=Add first chars into string
1253
	 *  @param	string	$moreinpopup	          Text to add into popup
1254
	 *  @param	string	$sep			          Separator between ref and label if option addlabel is set
1255
	 *  @param	int   	$notooltip		          1=Disable tooltip
1256
	 *  @param  int     $save_lastsearch_value    -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
1257
	 *  @param	string	$morecss				  More css on a link
1258
	 * 	@return	string					          String with URL
1259
	 */
1260
	public function getNomUrl($withpicto = 0, $option = '', $addlabel = 0, $moreinpopup = '', $sep = ' - ', $notooltip = 0, $save_lastsearch_value = -1, $morecss = '')
1261
	{
1262
		global $conf, $langs, $user, $hookmanager;
1263
1264
		if (!empty($conf->dol_no_mouse_hover)) {
1265
			$notooltip = 1; // Force disable tooltips
1266
		}
1267
1268
		$result = '';
1269
		if (!empty($conf->global->PROJECT_OPEN_ALWAYS_ON_TAB)) {
1270
			$option = $conf->global->PROJECT_OPEN_ALWAYS_ON_TAB;
1271
		}
1272
1273
		$label = '';
1274
		if ($option != 'nolink') {
1275
			$label = img_picto('', $this->picto, 'class="pictofixedwidth"').' <u class="paddingrightonly">'.$langs->trans("Project").'</u>';
1276
		}
1277
		if (isset($this->status)) {
1278
			$label .= ' '.$this->getLibStatut(5);
1279
		}
1280
		$label .= ($label ? '<br>' : '').'<b>'.$langs->trans('Ref').': </b>'.$this->ref; // The space must be after the : to not being explode when showing the title in img_picto
1281
		$label .= ($label ? '<br>' : '').'<b>'.$langs->trans('Label').': </b>'.$this->title; // The space must be after the : to not being explode when showing the title in img_picto
1282
		if (isset($this->public)) {
1283
			$label .= '<br><b>'.$langs->trans("Visibility").":</b> ";
1284
			$label .= ($this->public ? img_picto($langs->trans('SharedProject'), 'world', 'class="pictofixedwidth"').$langs->trans("SharedProject") : img_picto($langs->trans('PrivateProject'), 'private', 'class="pictofixedwidth"').$langs->trans("PrivateProject"));
1285
		}
1286
		if (!empty($this->thirdparty_name)) {
1287
			$label .= ($label ? '<br>' : '').'<b>'.$langs->trans('ThirdParty').': </b>'.$this->thirdparty_name; // The space must be after the : to not being explode when showing the title in img_picto
1288
		}
1289
		if (!empty($this->date_start)) {
1290
			$label .= ($label ? '<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
1291
		}
1292
		if (!empty($this->date_end)) {
1293
			$label .= ($label ? '<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
1294
		}
1295
		if ($moreinpopup) {
1296
			$label .= '<br>'.$moreinpopup;
1297
		}
1298
1299
		$url = '';
1300
		if ($option != 'nolink') {
1301
			if (preg_match('/\.php$/', $option)) {
1302
				$url = dol_buildpath($option, 1).'?id='.$this->id;
1303
			} elseif ($option == 'task') {
1304
				$url = DOL_URL_ROOT.'/projet/tasks.php?id='.$this->id;
1305
			} elseif ($option == 'preview') {
1306
				$url = DOL_URL_ROOT.'/projet/element.php?id='.$this->id;
1307
			} elseif ($option == 'eventorganization') {
1308
				$url = DOL_URL_ROOT.'/eventorganization/conferenceorbooth_list.php?projectid='.$this->id;
1309
			} else {
1310
				$url = DOL_URL_ROOT.'/projet/card.php?id='.$this->id;
1311
			}
1312
			// Add param to save lastsearch_values or not
1313
			$add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1314
			if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1315
				$add_save_lastsearch_values = 1;
1316
			}
1317
			if ($add_save_lastsearch_values) {
1318
				$url .= '&save_lastsearch_values=1';
1319
			}
1320
		}
1321
1322
		$linkclose = '';
1323
		if (empty($notooltip) && $user->hasRight('projet', 'lire')) {
1324
			if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
1325
				$label = $langs->trans("ShowProject");
1326
				$linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1327
			}
1328
			$linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
1329
			$linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"';
1330
		} else {
1331
			$linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
1332
		}
1333
1334
		$picto = 'projectpub';
1335
		if (!$this->public) {
1336
			$picto = 'project';
1337
		}
1338
1339
		$linkstart = '<a href="'.$url.'"';
1340
		$linkstart .= $linkclose.'>';
1341
		$linkend = '</a>';
1342
1343
		$result .= $linkstart;
1344
		if ($withpicto) {
1345
			$result .= img_object(($notooltip ? '' : $label), $picto, ($notooltip ? (($withpicto != 2) ? 'class="pictofixedwidth"' : '') : 'class="'.(($withpicto != 2) ? 'pictofixedwidth ' : '').'classfortooltip pictofixedwidth em088"'), 0, 0, $notooltip ? 0 : 1);
1346
		}
1347
		if ($withpicto != 2) {
1348
			$result .= $this->ref;
1349
		}
1350
		$result .= $linkend;
1351
		if ($withpicto != 2) {
1352
			$result .= (($addlabel && $this->title) ? '<span class="opacitymedium">'.$sep.dol_trunc($this->title, ($addlabel > 1 ? $addlabel : 0)).'</span>' : '');
1353
		}
1354
1355
		global $action;
1356
		$hookmanager->initHooks(array('projectdao'));
1357
		$parameters = array('id'=>$this->id, 'getnomurl' => &$result);
1358
		$reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1359
		if ($reshook > 0) {
1360
			$result = $hookmanager->resPrint;
1361
		} else {
1362
			$result .= $hookmanager->resPrint;
1363
		}
1364
1365
		return $result;
1366
	}
1367
1368
	/**
1369
	 *  Initialise an instance with random values.
1370
	 *  Used to build previews or test instances.
1371
	 * 	id must be 0 if object instance is a specimen.
1372
	 *
1373
	 *  @return	void
1374
	 */
1375
	public function initAsSpecimen()
1376
	{
1377
		global $user, $langs, $conf;
1378
1379
		$now = dol_now();
1380
1381
		// Initialise parameters
1382
		$this->id = 0;
1383
		$this->ref = 'SPECIMEN';
1384
		$this->specimen = 1;
1385
		$this->socid = 1;
1386
		$this->date_c = $now;
1387
		$this->date_m = $now;
1388
		$this->date_start = $now;
1389
		$this->date_end = $now + (3600 * 24 * 365);
1390
		$this->note_public = 'SPECIMEN';
1391
		$this->fk_ele = 20000;
1392
		$this->opp_amount = 20000;
1393
		$this->budget_amount = 10000;
1394
1395
		$this->usage_opportunity = 1;
1396
		$this->usage_task = 1;
1397
		$this->usage_bill_time = 1;
1398
		$this->usage_organize_event = 1;
1399
1400
		/*
1401
		 $nbp = mt_rand(1, 9);
1402
		 $xnbp = 0;
1403
		 while ($xnbp < $nbp)
1404
		 {
1405
		 $line = new Task($this->db);
1406
		 $line->fk_project = 0;
1407
		 $line->label = $langs->trans("Label") . " " . $xnbp;
1408
		 $line->description = $langs->trans("Description") . " " . $xnbp;
1409
1410
		 $this->lines[]=$line;
1411
		 $xnbp++;
1412
		 }
1413
		 */
1414
	}
1415
1416
	/**
1417
	 * 	Check if user has permission on current project
1418
	 *
1419
	 * 	@param	User	$user		Object user to evaluate
1420
	 * 	@param  string	$mode		Type of permission we want to know: 'read', 'write'
1421
	 * 	@return	int					>0 if user has permission, <0 if user has no permission
1422
	 */
1423
	public function restrictedProjectArea(User $user, $mode = 'read')
1424
	{
1425
		// To verify role of users
1426
		$userAccess = 0;
1427
		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))) {
1428
			$userAccess = 1;
1429
		} elseif ($this->public && (($mode == 'read' && !empty($user->rights->projet->lire)) || ($mode == 'write' && !empty($user->rights->projet->creer)) || ($mode == 'delete' && !empty($user->rights->projet->supprimer)))) {
1430
			$userAccess = 1;
1431
		} else {	// No access due to permission to read all projects, so we check if we are a contact of project
1432
			foreach (array('internal', 'external') as $source) {
1433
				$userRole = $this->liste_contact(4, $source);
1434
				$num = count($userRole);
1435
1436
				$nblinks = 0;
1437
				while ($nblinks < $num) {
1438
					if ($source == 'internal' && $user->id == $userRole[$nblinks]['id']) {	// $userRole[$nblinks]['id'] is id of user (llx_user) for internal contacts
1439
						if ($mode == 'read' && $user->rights->projet->lire) {
1440
							$userAccess++;
1441
						}
1442
						if ($mode == 'write' && $user->rights->projet->creer) {
1443
							$userAccess++;
1444
						}
1445
						if ($mode == 'delete' && $user->rights->projet->supprimer) {
1446
							$userAccess++;
1447
						}
1448
					}
1449
					if ($source == 'external' && $user->socid > 0 && $user->socid == $userRole[$nblinks]['socid']) {	// $userRole[$nblinks]['id'] is id of contact (llx_socpeople) or external contacts
1450
						if ($mode == 'read' && $user->rights->projet->lire) {
1451
							$userAccess++;
1452
						}
1453
						if ($mode == 'write' && $user->rights->projet->creer) {
1454
							$userAccess++;
1455
						}
1456
						if ($mode == 'delete' && $user->rights->projet->supprimer) {
1457
							$userAccess++;
1458
						}
1459
					}
1460
					$nblinks++;
1461
				}
1462
			}
1463
			//if (empty($nblinks))	// If nobody has permission, we grant creator
1464
			//{
1465
			//	if ((!empty($this->user_author_id) && $this->user_author_id == $user->id))
1466
			//	{
1467
			//		$userAccess = 1;
1468
			//	}
1469
			//}
1470
		}
1471
1472
		return ($userAccess ? $userAccess : -1);
1473
	}
1474
1475
	/**
1476
	 * Return array of projects a user has permission on, is affected to, or all projects
1477
	 *
1478
	 * @param 	User	$user			User object
1479
	 * @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
1480
	 * @param 	int		$list			0=Return array, 1=Return string list
1481
	 * @param	int		$socid			0=No filter on third party, id of third party
1482
	 * @param	string	$filter			additionnal filter on project (statut, ref, ...)
1483
	 * @return 	array|string			Array of projects id, or string with projects id separated with "," if list is 1
1484
	 */
1485
	public function getProjectsAuthorizedForUser($user, $mode = 0, $list = 0, $socid = 0, $filter = '')
1486
	{
1487
		$projects = array();
1488
		$temp = array();
1489
1490
		$sql = "SELECT ".(($mode == 0 || $mode == 1) ? "DISTINCT " : "")."p.rowid, p.ref";
1491
		$sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
1492
		if ($mode == 0) {
1493
			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."element_contact as ec ON ec.element_id = p.rowid";
1494
		} elseif ($mode == 1) {
1495
			$sql .= ", ".MAIN_DB_PREFIX."element_contact as ec";
1496
		} elseif ($mode == 2) {
1497
			// No filter. Use this if user has permission to see all project
1498
		}
1499
		$sql .= " WHERE p.entity IN (".getEntity('project').")";
1500
		// Internal users must see project he is contact to even if project linked to a third party he can't see.
1501
		//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).")";
1502
		if ($socid > 0) {
1503
			$sql .= " AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = ".((int) $socid).")";
1504
		}
1505
1506
		// Get id of types of contacts for projects (This list never contains a lot of elements)
1507
		$listofprojectcontacttype = array();
1508
		$sql2 = "SELECT ctc.rowid, ctc.code FROM ".MAIN_DB_PREFIX."c_type_contact as ctc";
1509
		$sql2 .= " WHERE ctc.element = '".$this->db->escape($this->element)."'";
1510
		$sql2 .= " AND ctc.source = 'internal'";
1511
		$resql = $this->db->query($sql2);
1512
		if ($resql) {
1513
			while ($obj = $this->db->fetch_object($resql)) {
1514
				$listofprojectcontacttype[$obj->rowid] = $obj->code;
1515
			}
1516
		} else {
1517
			dol_print_error($this->db);
1518
		}
1519
		if (count($listofprojectcontacttype) == 0) {
1520
			$listofprojectcontacttype[0] = '0'; // To avoid syntax error if not found
1521
		}
1522
1523
		if ($mode == 0) {
1524
			$sql .= " AND ( p.public = 1";
1525
			$sql .= " OR ( ec.fk_c_type_contact IN (".$this->db->sanitize(join(',', array_keys($listofprojectcontacttype))).")";
1526
			$sql .= " AND ec.fk_socpeople = ".((int) $user->id).")";
1527
			$sql .= " )";
1528
		} elseif ($mode == 1) {
1529
			$sql .= " AND ec.element_id = p.rowid";
1530
			$sql .= " AND (";
1531
			$sql .= "  ( ec.fk_c_type_contact IN (".$this->db->sanitize(join(',', array_keys($listofprojectcontacttype))).")";
1532
			$sql .= " AND ec.fk_socpeople = ".((int) $user->id).")";
1533
			$sql .= " )";
1534
		} elseif ($mode == 2) {
1535
			// No filter. Use this if user has permission to see all project
1536
		}
1537
1538
		$sql .= $filter;
1539
		//print $sql;
1540
1541
		$resql = $this->db->query($sql);
1542
		if ($resql) {
1543
			$num = $this->db->num_rows($resql);
1544
			$i = 0;
1545
			while ($i < $num) {
1546
				$row = $this->db->fetch_row($resql);
1547
				$projects[$row[0]] = $row[1];
1548
				$temp[] = $row[0];
1549
				$i++;
1550
			}
1551
1552
			$this->db->free($resql);
1553
1554
			if ($list) {
1555
				if (empty($temp)) {
1556
					return '0';
1557
				}
1558
				$result = implode(',', $temp);
1559
				return $result;
1560
			}
1561
		} else {
1562
			dol_print_error($this->db);
1563
		}
1564
1565
		return $projects;
1566
	}
1567
1568
	/**
1569
	 * Load an object from its id and create a new one in database
1570
	 *
1571
	 *  @param	User	$user		          User making the clone
1572
	 *  @param	int		$fromid     	      Id of object to clone
1573
	 *  @param	bool	$clone_contact	      Clone contact of project
1574
	 *  @param	bool	$clone_task		      Clone task of project
1575
	 *  @param	bool	$clone_project_file	  Clone file of project
1576
	 *  @param	bool	$clone_task_file	  Clone file of task (if task are copied)
1577
	 *  @param	bool	$clone_note		      Clone note of project
1578
	 *  @param	bool	$move_date		      Move task date on clone
1579
	 *  @param	integer	$notrigger		      No trigger flag
1580
	 *  @param  int     $newthirdpartyid      New thirdparty id
1581
	 *  @return	int						      New id of clone
1582
	 */
1583
	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)
1584
	{
1585
		global $langs, $conf;
1586
1587
		$error = 0;
1588
1589
		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);
1590
1591
		$now = dol_mktime(0, 0, 0, idate('m', dol_now()), idate('d', dol_now()), idate('Y', dol_now()));
1592
1593
		$clone_project = new Project($this->db);
1594
1595
		$clone_project->context['createfromclone'] = 'createfromclone';
1596
1597
		$this->db->begin();
1598
1599
		// Load source object
1600
		$clone_project->fetch($fromid);
1601
		$clone_project->fetch_optionals();
1602
		if ($newthirdpartyid > 0) {
1603
			$clone_project->socid = $newthirdpartyid;
1604
		}
1605
		$clone_project->fetch_thirdparty();
1606
1607
		$orign_dt_start = $clone_project->date_start;
1608
		$orign_project_ref = $clone_project->ref;
1609
1610
		$clone_project->id = 0;
1611
		if ($move_date) {
1612
			$clone_project->date_start = $now;
1613
			if (!(empty($clone_project->date_end))) {
1614
				$clone_project->date_end = $clone_project->date_end + ($now - $orign_dt_start);
1615
			}
1616
		}
1617
1618
		$clone_project->date_c = $now;
1619
1620
		if (!$clone_note) {
1621
			$clone_project->note_private = '';
1622
			$clone_project->note_public = '';
1623
		}
1624
1625
		//Generate next ref
1626
		$defaultref = '';
1627
		$obj = empty($conf->global->PROJECT_ADDON) ? 'mod_project_simple' : $conf->global->PROJECT_ADDON;
1628
		// Search template files
1629
		$file = ''; $classname = ''; $filefound = 0;
1630
		$dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
1631
		foreach ($dirmodels as $reldir) {
1632
			$file = dol_buildpath($reldir."core/modules/project/".$obj.'.php', 0);
1633
			if (file_exists($file)) {
1634
				$filefound = 1;
1635
				dol_include_once($reldir."core/modules/project/".$obj.'.php');
1636
				$modProject = new $obj;
1637
				$defaultref = $modProject->getNextValue(is_object($clone_project->thirdparty) ? $clone_project->thirdparty : null, $clone_project);
1638
				break;
1639
			}
1640
		}
1641
		if (is_numeric($defaultref) && $defaultref <= 0) {
1642
			$defaultref = '';
1643
		}
1644
1645
		$clone_project->ref = $defaultref;
1646
		$clone_project->title = $langs->trans("CopyOf").' '.$clone_project->title;
1647
1648
		// Create clone
1649
		$result = $clone_project->create($user, $notrigger);
1650
1651
		// Other options
1652
		if ($result < 0) {
1653
			$this->error .= $clone_project->error;
1654
			$error++;
1655
		}
1656
1657
		if (!$error) {
1658
			//Get the new project id
1659
			$clone_project_id = $clone_project->id;
1660
1661
			//Note Update
1662
			if (!$clone_note) {
1663
				$clone_project->note_private = '';
1664
				$clone_project->note_public = '';
1665
			} else {
1666
				$this->db->begin();
1667
				$res = $clone_project->update_note(dol_html_entity_decode($clone_project->note_public, ENT_QUOTES | ENT_HTML5), '_public');
1668
				if ($res < 0) {
1669
					$this->error .= $clone_project->error;
1670
					$error++;
1671
					$this->db->rollback();
1672
				} else {
1673
					$this->db->commit();
1674
				}
1675
1676
				$this->db->begin();
1677
				$res = $clone_project->update_note(dol_html_entity_decode($clone_project->note_private, ENT_QUOTES | ENT_HTML5), '_private');
1678
				if ($res < 0) {
1679
					$this->error .= $clone_project->error;
1680
					$error++;
1681
					$this->db->rollback();
1682
				} else {
1683
					$this->db->commit();
1684
				}
1685
			}
1686
1687
			//Duplicate contact
1688
			if ($clone_contact) {
1689
				$origin_project = new Project($this->db);
1690
				$origin_project->fetch($fromid);
1691
1692
				foreach (array('internal', 'external') as $source) {
1693
					$tab = $origin_project->liste_contact(-1, $source);
1694
					if (is_array($tab) && count($tab)>0) {
1695
						foreach ($tab as $contacttoadd) {
1696
							$clone_project->add_contact($contacttoadd['id'], $contacttoadd['code'], $contacttoadd['source'], $notrigger);
1697
							if ($clone_project->error == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1698
								$langs->load("errors");
1699
								$this->error .= $langs->trans("ErrorThisContactIsAlreadyDefinedAsThisType");
1700
								$error++;
1701
							} else {
1702
								if ($clone_project->error != '') {
1703
									$this->error .= $clone_project->error;
1704
									$error++;
1705
								}
1706
							}
1707
						}
1708
					} elseif ($tab < 0) {
1709
						$this->error .= $origin_project->error;
1710
						$error++;
1711
					}
1712
				}
1713
			}
1714
1715
			//Duplicate file
1716
			if ($clone_project_file) {
1717
				require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1718
1719
				$clone_project_dir = $conf->project->dir_output."/".dol_sanitizeFileName($defaultref);
1720
				$ori_project_dir = $conf->project->dir_output."/".dol_sanitizeFileName($orign_project_ref);
1721
1722
				if (dol_mkdir($clone_project_dir) >= 0) {
1723
					$filearray = dol_dir_list($ori_project_dir, "files", 0, '', '(\.meta|_preview.*\.png)$', '', SORT_ASC, 1);
1724
					foreach ($filearray as $key => $file) {
1725
						$rescopy = dol_copy($ori_project_dir.'/'.$file['name'], $clone_project_dir.'/'.$file['name'], 0, 1);
1726
						if (is_numeric($rescopy) && $rescopy < 0) {
1727
							$this->error .= $langs->trans("ErrorFailToCopyFile", $ori_project_dir.'/'.$file['name'], $clone_project_dir.'/'.$file['name']);
1728
							$error++;
1729
						}
1730
					}
1731
				} else {
1732
					$this->error .= $langs->trans('ErrorInternalErrorDetected').':dol_mkdir';
1733
					$error++;
1734
				}
1735
			}
1736
1737
			//Duplicate task
1738
			if ($clone_task) {
1739
				require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
1740
1741
				$taskstatic = new Task($this->db);
1742
1743
				// Security check
1744
				$socid = 0;
1745
				if ($user->socid > 0) {
1746
					$socid = $user->socid;
1747
				}
1748
1749
				$tasksarray = $taskstatic->getTasksArray(0, 0, $fromid, $socid, 0);
1750
1751
				$tab_conv_child_parent = array();
1752
1753
				// Loop on each task, to clone it
1754
				foreach ($tasksarray as $tasktoclone) {
1755
					$result_clone = $taskstatic->createFromClone($user, $tasktoclone->id, $clone_project_id, $tasktoclone->fk_parent, $move_date, true, false, $clone_task_file, true, false);
1756
					if ($result_clone <= 0) {
1757
						$this->error .= $taskstatic->error;
1758
						$error++;
1759
					} else {
1760
						$new_task_id = $result_clone;
1761
						$taskstatic->fetch($tasktoclone->id);
1762
1763
						//manage new parent clone task id
1764
						// if the current task has child we store the original task id and the equivalent clone task id
1765
						if (($taskstatic->hasChildren()) && !array_key_exists($tasktoclone->id, $tab_conv_child_parent)) {
1766
							$tab_conv_child_parent[$tasktoclone->id] = $new_task_id;
1767
						}
1768
					}
1769
				}
1770
1771
				//Parse all clone node to be sure to update new parent
1772
				$tasksarray = $taskstatic->getTasksArray(0, 0, $clone_project_id, $socid, 0);
1773
				foreach ($tasksarray as $task_cloned) {
1774
					$taskstatic->fetch($task_cloned->id);
1775
					if ($taskstatic->fk_task_parent != 0) {
1776
						$taskstatic->fk_task_parent = $tab_conv_child_parent[$taskstatic->fk_task_parent];
1777
					}
1778
					$res = $taskstatic->update($user, $notrigger);
1779
					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 1754. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1780
						$this->error .= $taskstatic->error;
1781
						$error++;
1782
					}
1783
				}
1784
			}
1785
		}
1786
1787
		unset($clone_project->context['createfromclone']);
1788
1789
		if (!$error) {
1790
			$this->db->commit();
1791
			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...
1792
		} else {
1793
			$this->db->rollback();
1794
			dol_syslog(get_class($this)."::createFromClone nbError: ".$error." error : ".$this->error, LOG_ERR);
1795
			return -1;
1796
		}
1797
	}
1798
1799
1800
	/**
1801
	 *    Shift project task date from current date to delta
1802
	 *
1803
	 *    @param	integer		$old_project_dt_start	Old project start date
1804
	 *    @return	int				                    1 if OK or < 0 if KO
1805
	 */
1806
	public function shiftTaskDate($old_project_dt_start)
1807
	{
1808
		global $user, $langs, $conf;
1809
1810
		$error = 0;
1811
		$result = 0;
1812
1813
		$taskstatic = new Task($this->db);
1814
1815
		// Security check
1816
		$socid = 0;
1817
		if ($user->socid > 0) {
1818
			$socid = $user->socid;
1819
		}
1820
1821
		$tasksarray = $taskstatic->getTasksArray(0, 0, $this->id, $socid, 0);
1822
1823
		foreach ($tasksarray as $tasktoshiftdate) {
1824
			$to_update = false;
1825
			// Fetch only if update of date will be made
1826
			if ((!empty($tasktoshiftdate->date_start)) || (!empty($tasktoshiftdate->date_end))) {
1827
				//dol_syslog(get_class($this)."::shiftTaskDate to_update", LOG_DEBUG);
1828
				$to_update = true;
1829
				$task = new Task($this->db);
1830
				$result = $task->fetch($tasktoshiftdate->id);
1831
				if (!$result) {
1832
					$error++;
1833
					$this->error .= $task->error;
1834
				}
1835
			}
1836
			//print "$this->date_start + $tasktoshiftdate->date_start - $old_project_dt_start";exit;
1837
1838
			//Calcultate new task start date with difference between old proj start date and origin task start date
1839
			if (!empty($tasktoshiftdate->date_start)) {
1840
				$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...
1841
			}
1842
1843
			//Calcultate new task end date with difference between origin proj end date and origin task end date
1844
			if (!empty($tasktoshiftdate->date_end)) {
1845
				$task->date_end = $this->date_start + ($tasktoshiftdate->date_end - $old_project_dt_start);
1846
			}
1847
1848
			if ($to_update) {
1849
				$result = $task->update($user);
1850
				if (!$result) {
1851
					$error++;
1852
					$this->error .= $task->error;
1853
				}
1854
			}
1855
		}
1856
		if ($error != 0) {
1857
			return -1;
1858
		}
1859
		return $result;
1860
	}
1861
1862
1863
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1864
	/**
1865
	 *    Associate element to a project
1866
	 *
1867
	 *    @param	string	$tableName			Table of the element to update
1868
	 *    @param	int		$elementSelectId	Key-rowid of the line of the element to update
1869
	 *    @return	int							1 if OK or < 0 if KO
1870
	 */
1871
	public function update_element($tableName, $elementSelectId)
1872
	{
1873
		// phpcs:enable
1874
		$sql = "UPDATE ".MAIN_DB_PREFIX.$tableName;
1875
1876
		if ($tableName == "actioncomm") {
1877
			$sql .= " SET fk_project=".$this->id;
1878
			$sql .= " WHERE id=".((int) $elementSelectId);
1879
		} elseif ($tableName == "entrepot") {
1880
			$sql .= " SET fk_project=".$this->id;
1881
			$sql .= " WHERE rowid=".((int) $elementSelectId);
1882
		} else {
1883
			$sql .= " SET fk_projet=".$this->id;
1884
			$sql .= " WHERE rowid=".((int) $elementSelectId);
1885
		}
1886
1887
		dol_syslog(get_class($this)."::update_element", LOG_DEBUG);
1888
		$resql = $this->db->query($sql);
1889
		if (!$resql) {
1890
			$this->error = $this->db->lasterror();
1891
			return -1;
1892
		} else {
1893
			return 1;
1894
		}
1895
	}
1896
1897
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1898
	/**
1899
	 *    Associate element to a project
1900
	 *
1901
	 *    @param	string	$tableName			Table of the element to update
1902
	 *    @param	int		$elementSelectId	Key-rowid of the line of the element to update
1903
	 *    @param	string	$projectfield	    The column name that stores the link with the project
1904
	 *
1905
	 *    @return	int							1 if OK or < 0 if KO
1906
	 */
1907
	public function remove_element($tableName, $elementSelectId, $projectfield = 'fk_projet')
1908
	{
1909
		// phpcs:enable
1910
		$sql = "UPDATE ".MAIN_DB_PREFIX.$tableName;
1911
1912
		if ($tableName == "actioncomm") {
1913
			$sql .= " SET fk_project=NULL";
1914
			$sql .= " WHERE id=".((int) $elementSelectId);
1915
		} else {
1916
			$sql .= " SET ".$projectfield."=NULL";
1917
			$sql .= " WHERE rowid=".((int) $elementSelectId);
1918
		}
1919
1920
		dol_syslog(get_class($this)."::remove_element", LOG_DEBUG);
1921
		$resql = $this->db->query($sql);
1922
		if (!$resql) {
1923
			$this->error = $this->db->lasterror();
1924
			return -1;
1925
		} else {
1926
			return 1;
1927
		}
1928
	}
1929
1930
	/**
1931
	 *  Create an intervention document on disk using template defined into PROJECT_ADDON_PDF
1932
	 *
1933
	 *  @param	string		$modele			Force template to use ('' by default)
1934
	 *  @param	Translate	$outputlangs	Objet lang to use for translation
1935
	 *  @param  int			$hidedetails    Hide details of lines
1936
	 *  @param  int			$hidedesc       Hide description
1937
	 *  @param  int			$hideref        Hide ref
1938
	 *  @return int         				0 if KO, 1 if OK
1939
	 */
1940
	public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
1941
	{
1942
		global $conf, $langs;
1943
1944
		$langs->load("projects");
1945
1946
		if (!dol_strlen($modele)) {
1947
			$modele = 'baleine';
1948
1949
			if ($this->model_pdf) {
1950
				$modele = $this->model_pdf;
1951
			} elseif (!empty($conf->global->PROJECT_ADDON_PDF)) {
1952
				$modele = $conf->global->PROJECT_ADDON_PDF;
1953
			}
1954
		}
1955
1956
		$modelpath = "core/modules/project/doc/";
1957
1958
		return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
1959
	}
1960
1961
1962
	/**
1963
	 * Load time spent into this->weekWorkLoad and this->weekWorkLoadPerTask for all day of a week of project.
1964
	 * Note: array weekWorkLoad and weekWorkLoadPerTask are reset and filled at each call.
1965
	 *
1966
	 * @param 	int		$datestart		First day of week (use dol_get_first_day to find this date)
1967
	 * @param 	int		$taskid			Filter on a task id
1968
	 * @param 	int		$userid			Time spent by a particular user
1969
	 * @return 	int						<0 if OK, >0 if KO
1970
	 */
1971
	public function loadTimeSpent($datestart, $taskid = 0, $userid = 0)
1972
	{
1973
		$error = 0;
1974
1975
		$this->weekWorkLoad = array();
1976
		$this->weekWorkLoadPerTask = array();
1977
1978
		if (empty($datestart)) {
1979
			dol_print_error('', 'Error datestart parameter is empty');
1980
		}
1981
1982
		$sql = "SELECT ptt.rowid as taskid, ptt.task_duration, ptt.task_date, ptt.task_datehour, ptt.fk_task";
1983
		$sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time AS ptt, ".MAIN_DB_PREFIX."projet_task as pt";
1984
		$sql .= " WHERE ptt.fk_task = pt.rowid";
1985
		$sql .= " AND pt.fk_projet = ".((int) $this->id);
1986
		$sql .= " AND (ptt.task_date >= '".$this->db->idate($datestart)."' ";
1987
		$sql .= " AND ptt.task_date <= '".$this->db->idate(dol_time_plus_duree($datestart, 1, 'w') - 1)."')";
1988
		if ($taskid) {
1989
			$sql .= " AND ptt.fk_task=".((int) $taskid);
1990
		}
1991
		if (is_numeric($userid)) {
1992
			$sql .= " AND ptt.fk_user=".((int) $userid);
1993
		}
1994
1995
		//print $sql;
1996
		$resql = $this->db->query($sql);
1997
		if ($resql) {
1998
			$daylareadyfound = array();
1999
2000
			$num = $this->db->num_rows($resql);
2001
			$i = 0;
2002
			// Loop on each record found, so each couple (project id, task id)
2003
			while ($i < $num) {
2004
				$obj = $this->db->fetch_object($resql);
2005
				$day = $this->db->jdate($obj->task_date); // task_date is date without hours
2006
				if (empty($daylareadyfound[$day])) {
2007
					$this->weekWorkLoad[$day] = $obj->task_duration;
2008
					$this->weekWorkLoadPerTask[$day][$obj->fk_task] = $obj->task_duration;
2009
				} else {
2010
					$this->weekWorkLoad[$day] += $obj->task_duration;
2011
					$this->weekWorkLoadPerTask[$day][$obj->fk_task] += $obj->task_duration;
2012
				}
2013
				$daylareadyfound[$day] = 1;
2014
				$i++;
2015
			}
2016
			$this->db->free($resql);
2017
			return 1;
2018
		} else {
2019
			$this->error = "Error ".$this->db->lasterror();
2020
			dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
2021
			return -1;
2022
		}
2023
	}
2024
2025
	/**
2026
	 * Load time spent into this->weekWorkLoad and this->weekWorkLoadPerTask for all day of a week of project.
2027
	 * Note: array weekWorkLoad and weekWorkLoadPerTask are reset and filled at each call.
2028
	 *
2029
	 * @param 	int		$datestart		First day of week (use dol_get_first_day to find this date)
2030
	 * @param 	int		$taskid			Filter on a task id
2031
	 * @param 	int		$userid			Time spent by a particular user
2032
	 * @return 	int						<0 if OK, >0 if KO
2033
	 */
2034
	public function loadTimeSpentMonth($datestart, $taskid = 0, $userid = 0)
2035
	{
2036
		$error = 0;
2037
2038
		$this->monthWorkLoad = array();
2039
		$this->monthWorkLoadPerTask = array();
2040
2041
		if (empty($datestart)) {
2042
			dol_print_error('', 'Error datestart parameter is empty');
2043
		}
2044
2045
		$sql = "SELECT ptt.rowid as taskid, ptt.task_duration, ptt.task_date, ptt.task_datehour, ptt.fk_task";
2046
		$sql .= " FROM ".MAIN_DB_PREFIX."projet_task_time AS ptt, ".MAIN_DB_PREFIX."projet_task as pt";
2047
		$sql .= " WHERE ptt.fk_task = pt.rowid";
2048
		$sql .= " AND pt.fk_projet = ".((int) $this->id);
2049
		$sql .= " AND (ptt.task_date >= '".$this->db->idate($datestart)."' ";
2050
		$sql .= " AND ptt.task_date <= '".$this->db->idate(dol_time_plus_duree($datestart, 1, 'm') - 1)."')";
2051
		if ($taskid) {
2052
			$sql .= " AND ptt.fk_task=".((int) $taskid);
2053
		}
2054
		if (is_numeric($userid)) {
2055
			$sql .= " AND ptt.fk_user=".((int) $userid);
2056
		}
2057
2058
		//print $sql;
2059
		$resql = $this->db->query($sql);
2060
		if ($resql) {
2061
			$weekalreadyfound = array();
2062
2063
			$num = $this->db->num_rows($resql);
2064
			$i = 0;
2065
			// Loop on each record found, so each couple (project id, task id)
2066
			while ($i < $num) {
2067
				$obj = $this->db->fetch_object($resql);
2068
				if (!empty($obj->task_date)) {
2069
					$date = explode('-', $obj->task_date);
2070
					$week_number = getWeekNumber($date[2], $date[1], $date[0]);
2071
				}
2072
				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...
2073
					$this->monthWorkLoad[$week_number] = $obj->task_duration;
2074
					$this->monthWorkLoadPerTask[$week_number][$obj->fk_task] = $obj->task_duration;
2075
				} else {
2076
					$this->monthWorkLoad[$week_number] += $obj->task_duration;
2077
					$this->monthWorkLoadPerTask[$week_number][$obj->fk_task] += $obj->task_duration;
2078
				}
2079
				$weekalreadyfound[$week_number] = 1;
2080
				$i++;
2081
			}
2082
			$this->db->free($resql);
2083
			return 1;
2084
		} else {
2085
			$this->error = "Error ".$this->db->lasterror();
2086
			dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
2087
			return -1;
2088
		}
2089
	}
2090
2091
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2092
	/**
2093
	 * Load indicators for dashboard (this->nbtodo and this->nbtodolate)
2094
	 *
2095
	 * @param	User	$user   Objet user
2096
	 * @return WorkboardResponse|int <0 if KO, WorkboardResponse if OK
2097
	 */
2098
	public function load_board($user)
2099
	{
2100
		// phpcs:enable
2101
		global $conf, $langs;
2102
2103
		// For external user, no check is done on company because readability is managed by public status of project and assignement.
2104
		//$socid=$user->socid;
2105
2106
		$response = new WorkboardResponse();
2107
		$response->warning_delay = $conf->project->warning_delay / 60 / 60 / 24;
2108
		$response->label = $langs->trans("OpenedProjects");
2109
		$response->labelShort = $langs->trans("Opened");
2110
		$response->url = DOL_URL_ROOT.'/projet/list.php?search_project_user=-1&search_status=1&mainmenu=project';
2111
		$response->img = img_object('', "projectpub");
2112
		$response->nbtodo = 0;
2113
		$response->nbtodolate = 0;
2114
2115
		$sql = "SELECT p.rowid, p.fk_statut as status, p.fk_opp_status, p.datee as datee";
2116
		$sql .= " FROM (".MAIN_DB_PREFIX."projet as p";
2117
		$sql .= ")";
2118
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s on p.fk_soc = s.rowid";
2119
		// For external user, no check is done on company permission because readability is managed by public status of project and assignement.
2120
		//if (! $user->rights->societe->client->voir && ! $socid) $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON sc.fk_soc = s.rowid";
2121
		$sql .= " WHERE p.fk_statut = 1";
2122
		$sql .= " AND p.entity IN (".getEntity('project').')';
2123
2124
2125
		$projectsListId = null;
2126
		if (!$user->hasRight("projet", "all", "lire")) {
2127
			$response->url = DOL_URL_ROOT.'/projet/list.php?search_status=1&mainmenu=project';
2128
			$projectsListId = $this->getProjectsAuthorizedForUser($user, 0, 1);
2129
			if (empty($projectsListId)) {
2130
				return $response;
2131
			}
2132
2133
			$sql .= " AND p.rowid IN (".$this->db->sanitize($projectsListId).")";
2134
		}
2135
2136
		// No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser
2137
		//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).")";
2138
		// For external user, no check is done on company permission because readability is managed by public status of project and assignement.
2139
		//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))";
2140
2141
		//print $sql;
2142
		$resql = $this->db->query($sql);
2143
		if ($resql) {
2144
			$project_static = new Project($this->db);
2145
2146
2147
			// This assignment in condition is not a bug. It allows walking the results.
2148
			while ($obj = $this->db->fetch_object($resql)) {
2149
				$response->nbtodo++;
2150
2151
				$project_static->statut = $obj->status;
2152
				$project_static->opp_status = $obj->fk_opp_status;
2153
				$project_static->date_end = $this->db->jdate($obj->datee);
2154
2155
				if ($project_static->hasDelay()) {
2156
					$response->nbtodolate++;
2157
				}
2158
			}
2159
2160
			return $response;
2161
		}
2162
2163
		$this->error = $this->db->error();
2164
		return -1;
2165
	}
2166
2167
	/**
2168
	 * Function used to replace a thirdparty id with another one.
2169
	 *
2170
	 * @param DoliDB $dbs 		Database handler
2171
	 * @param int $origin_id 	Old thirdparty id
2172
	 * @param int $dest_id 		New thirdparty id
2173
	 * @return bool
2174
	 */
2175
	public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2176
	{
2177
		$tables = array(
2178
			'projet'
2179
		);
2180
2181
		return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2182
	}
2183
2184
2185
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2186
	/**
2187
	 * Charge indicateurs this->nb pour le tableau de bord
2188
	 *
2189
	 * @return     int         <0 if KO, >0 if OK
2190
	 */
2191
	public function load_state_board()
2192
	{
2193
		// phpcs:enable
2194
		global $user;
2195
2196
		$this->nb = array();
2197
2198
		$sql = "SELECT count(p.rowid) as nb";
2199
		$sql .= " FROM ".MAIN_DB_PREFIX."projet as p";
2200
		$sql .= " WHERE";
2201
		$sql .= " p.entity IN (".getEntity('project').")";
2202
		if (empty($user->rights->projet->all->lire)) {
2203
			$projectsListId = $this->getProjectsAuthorizedForUser($user, 0, 1);
2204
			$sql .= "AND p.rowid IN (".$this->db->sanitize($projectsListId).")";
2205
		}
2206
2207
		$resql = $this->db->query($sql);
2208
		if ($resql) {
2209
			while ($obj = $this->db->fetch_object($resql)) {
2210
				$this->nb["projects"] = $obj->nb;
2211
			}
2212
			$this->db->free($resql);
2213
			return 1;
2214
		} else {
2215
			dol_print_error($this->db);
2216
			$this->error = $this->db->error();
2217
			return -1;
2218
		}
2219
	}
2220
2221
2222
	/**
2223
	 * Is the project delayed?
2224
	 *
2225
	 * @return bool
2226
	 */
2227
	public function hasDelay()
2228
	{
2229
		global $conf;
2230
2231
		if (!($this->statut == self::STATUS_VALIDATED)) {
2232
			return false;
2233
		}
2234
		if (!$this->date_end) {
2235
			return false;
2236
		}
2237
2238
		$now = dol_now();
2239
2240
		return ($this->date_end) < ($now - $conf->project->warning_delay);
2241
	}
2242
2243
2244
	/**
2245
	 *	Charge les informations d'ordre info dans l'objet commande
2246
	 *
2247
	 *	@param  int		$id       Id of order
2248
	 *	@return	void
2249
	 */
2250
	public function info($id)
2251
	{
2252
		$sql = 'SELECT c.rowid, datec as datec, tms as datem,';
2253
		$sql .= ' date_close as datecloture,';
2254
		$sql .= ' fk_user_creat as fk_user_author, fk_user_close as fk_use_cloture';
2255
		$sql .= ' FROM '.MAIN_DB_PREFIX.'projet as c';
2256
		$sql .= ' WHERE c.rowid = '.((int) $id);
2257
		$result = $this->db->query($sql);
2258
		if ($result) {
2259
			if ($this->db->num_rows($result)) {
2260
				$obj = $this->db->fetch_object($result);
2261
				$this->id = $obj->rowid;
2262
				if ($obj->fk_user_author) {
2263
					$cuser = new User($this->db);
2264
					$cuser->fetch($obj->fk_user_author);
2265
					$this->user_creation = $cuser;
2266
				}
2267
2268
				if (!empty($obj->fk_user_cloture)) {
2269
					$cluser = new User($this->db);
2270
					$cluser->fetch($obj->fk_user_cloture);
2271
					$this->user_cloture = $cluser;
2272
				}
2273
2274
				$this->date_creation     = $this->db->jdate($obj->datec);
2275
				$this->date_modification = $this->db->jdate($obj->datem);
2276
				$this->date_cloture      = $this->db->jdate($obj->datecloture);
2277
			}
2278
2279
			$this->db->free($result);
2280
		} else {
2281
			dol_print_error($this->db);
2282
		}
2283
	}
2284
2285
	/**
2286
	 * Sets object to supplied categories.
2287
	 *
2288
	 * Deletes object from existing categories not supplied.
2289
	 * Adds it to non existing supplied categories.
2290
	 * Existing categories are left untouch.
2291
	 *
2292
	 * @param 	int[]|int 	$categories 	Category or categories IDs
2293
	 * @return 	int							<0 if KO, >0 if OK
2294
	 */
2295
	public function setCategories($categories)
2296
	{
2297
		require_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
2298
		return parent::setCategoriesCommon($categories, Categorie::TYPE_PROJECT);
2299
	}
2300
2301
2302
	/**
2303
	 * 	Create an array of tasks of current project
2304
	 *
2305
	 *  @param  User	$user       		Object user we want project allowed to
2306
	 * @param	int		$loadRoleMode		1= will test Roles on task;  0 used in delete project action
2307
	 * 	@return int							>0 if OK, <0 if KO
2308
	 */
2309
	public function getLinesArray($user, $loadRoleMode = 1)
2310
	{
2311
		require_once DOL_DOCUMENT_ROOT.'/projet/class/task.class.php';
2312
		$taskstatic = new Task($this->db);
2313
2314
		$this->lines = $taskstatic->getTasksArray(0, $user, $this->id, 0, 0, '',  '-1', '', 0, 0, array(),  0,  array(),  0,  $loadRoleMode);
2315
	}
2316
2317
	/**
2318
	 *  Function sending an email to the current member with the text supplied in parameter.
2319
	 *
2320
	 *  @param	string	$text				Content of message (not html entities encoded)
2321
	 *  @param	string	$subject			Subject of message
2322
	 *  @param 	array	$filename_list      Array of attached files
2323
	 *  @param 	array	$mimetype_list      Array of mime types of attached files
2324
	 *  @param 	array	$mimefilename_list  Array of public names of attached files
2325
	 *  @param 	string	$addr_cc            Email cc
2326
	 *  @param 	string	$addr_bcc           Email bcc
2327
	 *  @param 	int		$deliveryreceipt	Ask a delivery receipt
2328
	 *  @param	int		$msgishtml			1=String IS already html, 0=String IS NOT html, -1=Unknown need autodetection
2329
	 *  @param	string	$errors_to			erros to
2330
	 *  @param	string	$moreinheader		Add more html headers
2331
	 *  @since V18
2332
	 *  @return	int							<0 if KO, >0 if OK
2333
	 */
2334
	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 = '')
2335
	{
2336
		global $conf, $langs;
2337
		// TODO EMAIL
2338
2339
		return 1;
2340
	}
2341
}
2342