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

Project::getTooltipContentArray()   D

Complexity

Conditions 10
Paths 384

Size

Total Lines 35
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 10
eloc 23
nc 384
nop 1
dl 0
loc 35
rs 4.5333
c 1
b 0
f 1

How to fix   Complexity   

Long Method

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

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

Commonly applied refactorings include:

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