Passed
Push — EXTRACT_CLASSES ( ff35ec...a2ff75 )
by Rafael
48:13
created

Project::setCategories()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
1
<?php
2
3
/* Copyright (C) 2002-2005  Rodolphe Quiedeville        <[email protected]>
4
 * Copyright (C) 2005-2020  Laurent Destailleur         <[email protected]>
5
 * Copyright (C) 2005-2010  Regis Houssin               <[email protected]>
6
 * Copyright (C) 2013	    Florian Henry               <[email protected]>
7
 * Copyright (C) 2014-2017  Marcos García               <[email protected]>
8
 * Copyright (C) 2017       Ferran Marcet               <[email protected]>
9
 * Copyright (C) 2019       Juanjo Menent               <[email protected]>
10
 * Copyright (C) 2022       Charlene Benke              <[email protected]>
11
 * Copyright (C) 2023       Gauthier VERDOL             <[email protected]>
12
 * Copyright (C) 2024	 	Frédéric France			    <[email protected]>
13
 * Copyright (C) 2024		MDW							<[email protected]>
14
 * Copyright (C) 2024       Rafael San José             <[email protected]>
15
 *
16
 * This program is free software; you can redistribute it and/or modify
17
 * it under the terms of the GNU General Public License as published by
18
 * the Free Software Foundation; either version 3 of the License, or
19
 * (at your option) any later version.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
 * GNU General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU General Public License
27
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
28
 */
29
30
namespace Dolibarr\Code\Projet\Classes;
31
32
use Dolibarr\Core\Base\CommonObject;
33
34
/**
35
 *      \file       htdocs/projet/class/project.class.php
36
 *      \ingroup    projet
37
 *      \brief      File of class to manage projects
38
 */
39
40
/**
41
 *  Class to manage projects
42
 */
43
class Project extends CommonObject
44
{
45
    /**
46
     * @var string ID to identify managed object
47
     */
48
    public $element = 'project';
49
50
    /**
51
     * @var string Name of table without prefix where object is stored
52
     */
53
    public $table_element = 'projet';
54
55
    /**
56
     * @var string    Name of subtable line
57
     */
58
    public $table_element_line = 'projet_task';
59
60
    /**
61
     * @var string    Name of field date
62
     */
63
    public $table_element_date;
64
65
    /**
66
     * @var string Field with ID of parent key if this field has a parent
67
     */
68
    public $fk_element = 'fk_projet';
69
70
    /**
71
     * @var string String with name of icon for myobject. Must be the part after the 'object_' into object_myobject.png
72
     */
73
    public $picto = 'project';
74
75
    /**
76
     * {@inheritdoc}
77
     */
78
    protected $table_ref_field = 'ref';
79
80
    /**
81
     * @var int parent project
82
     */
83
    public $fk_project;
84
85
    /**
86
     * @var string description
87
     */
88
    public $description;
89
90
    /**
91
     * @var string
92
     */
93
    public $title;
94
95
    /**
96
     * @var int     Date start
97
     * @deprecated
98
     * @see $date_start
99
     */
100
    public $dateo;
101
102
    /**
103
     * @var int     Date start
104
     */
105
    public $date_start;
106
107
    /**
108
     * @var int     Date end
109
     * @deprecated
110
     * @see $date_end
111
     */
112
    public $datee;
113
114
    /**
115
     * @var int     Date end
116
     */
117
    public $date_end;
118
119
    /**
120
     * @var int     Date start event
121
     */
122
    public $date_start_event;
123
124
    /**
125
     * @var int     Date end event
126
     */
127
    public $date_end_event;
128
129
    /**
130
     * @var string  Location
131
     */
132
    public $location;
133
134
    /**
135
     * @var int Date close
136
     */
137
    public $date_close;
138
139
    public $socid; // To store id of thirdparty
140
    public $thirdparty_name; // To store name of thirdparty (defined only in some cases)
141
142
    public $user_author_id; //!< Id of project creator. Not defined if shared project.
143
144
    /**
145
     * @var int user close id
146
     */
147
    public $fk_user_close;
148
149
    public $public; //!< Tell if this is a public or private project
150
151
    /**
152
     * @var float|string budget Amount (May need price2num)
153
     */
154
    public $budget_amount;
155
156
    /**
157
     * @var integer     Can use projects to follow opportunities
158
     */
159
    public $usage_opportunity;
160
161
    /**
162
     * @var integer     Can follow tasks on project and enter time spent on it
163
     */
164
    public $usage_task;
165
166
    /**
167
     * @var integer     Use to bill task spend time
168
     */
169
    public $usage_bill_time; // Is the time spent on project must be invoiced or not
170
171
    /**
172
       * @var integer       Event organization: Use Event Organization
173
       */
174
    public $usage_organize_event;
175
176
    /**
177
     * @var integer     Event organization: Allow unknown people to suggest new conferences
178
     */
179
    public $accept_conference_suggestions;
180
181
    /**
182
     * @var integer     Event organization: Allow unknown people to suggest new booth
183
     */
184
    public $accept_booth_suggestions;
185
186
    /**
187
     * @var float|string Event organization: registration price (may need price2num)
188
     */
189
    public $price_registration;
190
191
    /**
192
     * @var float|string Event organization: booth price (may need price2num)
193
     */
194
    public $price_booth;
195
196
    /**
197
     * @var int|string Max attendees (may be empty/need cast to iint)
198
     */
199
    public $max_attendees;
200
201
    public $statut; // 0=draft, 1=opened, 2=closed
202
203
    public $opp_status; // opportunity status, into table llx_c_lead_status
204
    public $opp_status_code;
205
    public $fk_opp_status; // opportunity status, into table llx_c_lead_status
206
    public $opp_amount; // opportunity amount
207
    public $opp_percent; // opportunity probability
208
    public $opp_weighted_amount; // opportunity weighted amount
209
210
    public $email_msgid;
211
212
    public $oldcopy;
213
214
    public $weekWorkLoad; // Used to store workload details of a projet
215
    public $weekWorkLoadPerTask; // Used to store workload details of tasks of a projet
216
217
    /**
218
     * @var array Used to store workload details of a projet
219
     */
220
    public $monthWorkLoad;
221
222
    /**
223
     * @var array Used to store workload details of tasks of a projet
224
     */
225
    public $monthWorkLoadPerTask;
226
227
    /**
228
     * @var int Creation date
229
     * @deprecated
230
     * @see $date_c
231
     */
232
    public $datec;
233
234
    /**
235
     * @var int Creation date
236
     */
237
    public $date_c;
238
239
    /**
240
     * @var int Modification date
241
     * @deprecated
242
     * @see $date_m
243
     */
244
    public $datem;
245
246
    /**
247
     * @var int Modification date
248
     */
249
    public $date_m;
250
251
    /**
252
     * @var string Ip address
253
     */
254
    public $ip;
255
256
    /**
257
     * @var Task[]
258
     */
259
    public $lines;
260
261
    /**
262
     *  'type' field format:
263
     *      'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]',
264
     *      'select' (list of values are in 'options'. for integer list of values are in 'arrayofkeyval'),
265
     *      'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter[:CategoryIdType[:CategoryIdList[:SortField]]]]]]',
266
     *      'chkbxlst:...',
267
     *      'varchar(x)',
268
     *      'text', 'text:none', 'html',
269
     *      'double(24,8)', 'real', 'price', 'stock',
270
     *      'date', 'datetime', 'timestamp', 'duration',
271
     *      'boolean', 'checkbox', 'radio', 'array',
272
     *      'mail', 'phone', 'url', 'password', 'ip'
273
     *      Note: Filter must be a Dolibarr Universal Filter syntax string. Example: "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.status:!=:0) or (t.nature:is:NULL)"
274
     *  'label' the translation key.
275
     *  'alias' the alias used into some old hard coded SQL requests
276
     *  'picto' is code of a picto to show before value in forms
277
     *  'enabled' is a condition when the field must be managed (Example: 1 or 'getDolGlobalInt("MY_SETUP_PARAM")' or 'isModEnabled("multicurrency")' ...)
278
     *  'position' is the sort order of field.
279
     *  'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
280
     *  '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)
281
     *  'noteditable' says if field is not editable (1 or 0)
282
     *  'alwayseditable' says if field can be modified also when status is not draft ('1' or '0')
283
     *  '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.
284
     *  'index' if we want an index in database.
285
     *  'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommended to name the field fk_...).
286
     *  'searchall' is 1 if we want to search in this field when making a search from the quick search button.
287
     *  'isameasure' must be set to 1 or 2 if field can be used for measure. Field type must be summable like integer or double(24,8). Use 1 in most cases, or 2 if you don't want to see the column total into list (for example for percentage)
288
     *  'css' and 'cssview' and 'csslist' is the CSS style to use on field. 'css' is used in creation and update. 'cssview' is used in view mode. 'csslist' is used for columns in lists. For example: 'css'=>'minwidth300 maxwidth500 widthcentpercentminusx', 'cssview'=>'wordbreak', 'csslist'=>'tdoverflowmax200'
289
     *  'help' and 'helplist' is a 'TranslationString' to use to show a tooltip on field. You can also use 'TranslationString:keyfortooltiponlick' for a tooltip on click.
290
     *  'showoncombobox' if value of the field must be visible into the label of the combobox that list record
291
     *  '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.
292
     *  'arrayofkeyval' to set a list of values if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel"). Note that type can be 'integer' or 'varchar'
293
     *  'autofocusoncreate' to have field having the focus on a create form. Only 1 field should have this property set to 1.
294
     *  'comment' is not used. You can store here any text of your choice. It is not used by application.
295
     *  'validate' is 1 if you need to validate the field with $this->validateField(). Need MAIN_ACTIVATE_VALIDATION_RESULT.
296
     *  'copytoclipboard' is 1 or 2 to allow to add a picto to copy value into clipboard (1=picto after label, 2=picto after value)
297
     *
298
     *  Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
299
     */
300
301
    // BEGIN MODULEBUILDER PROPERTIES
302
    /**
303
     * @var array<string,array{type:string,label:string,enabled:int<0,2>|string,position:int,notnull?:int,visible:int,noteditable?:int,default?:string,index?:int,foreignkey?:string,searchall?:int,isameasure?:int,css?:string,csslist?:string,help?:string,showoncombobox?:int,disabled?:int,arrayofkeyval?:array<int,string>,comment?:string}>  Array with all fields and their property. Do not use it as a static var. It may be modified by constructor.
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string,array{type:...ring>,comment?:string}> at position 16 could not be parsed: Expected '}' at position 16, but found 'int'.
Loading history...
304
     */
305
    public $fields = array(
306
        'rowid' => array('type' => 'integer', 'label' => 'ID', 'enabled' => 1, 'visible' => -1, 'notnull' => 1, 'position' => 10),
307
        'fk_project' => array('type' => 'integer', 'label' => 'Parent', 'enabled' => 1, 'visible' => 1, 'notnull' => 0, 'position' => 12),
308
        'ref' => array('type' => 'varchar(50)', 'label' => 'Ref', 'enabled' => 1, 'visible' => 1, 'showoncombobox' => 1, 'position' => 15, 'searchall' => 1),
309
        'title' => array('type' => 'varchar(255)', 'label' => 'ProjectLabel', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'position' => 17, 'showoncombobox' => 2, 'searchall' => 1),
310
        'entity' => array('type' => 'integer', 'label' => 'Entity', 'default' => '1', 'enabled' => 1, 'visible' => 3, 'notnull' => 1, 'position' => 19),
311
        'fk_soc' => array('type' => 'integer', 'label' => 'Thirdparty', 'enabled' => 1, 'visible' => 0, 'position' => 20),
312
        'dateo' => array('type' => 'date', 'label' => 'DateStart', 'enabled' => 1, 'visible' => -1, 'position' => 30),
313
        'datee' => array('type' => 'date', 'label' => 'DateEnd', 'enabled' => 1, 'visible' => 1, 'position' => 35),
314
        'description' => array('type' => 'text', 'label' => 'Description', 'enabled' => 1, 'visible' => 3, 'position' => 55, 'searchall' => 1),
315
        'public' => array('type' => 'integer', 'label' => 'Visibility', 'enabled' => 1, 'visible' => -1, 'position' => 65),
316
        'fk_opp_status' => array('type' => 'integer:CLeadStatus:core/class/cleadstatus.class.php', 'label' => 'OpportunityStatusShort', 'enabled' => 'getDolGlobalString("PROJECT_USE_OPPORTUNITIES")', 'visible' => 1, 'position' => 75),
317
        'opp_percent' => array('type' => 'double(5,2)', 'label' => 'OpportunityProbabilityShort', 'enabled' => 'getDolGlobalString("PROJECT_USE_OPPORTUNITIES")', 'visible' => 1, 'position' => 80),
318
        'note_private' => array('type' => 'html', 'label' => 'NotePrivate', 'enabled' => 1, 'visible' => 0, 'position' => 85, 'searchall' => 1),
319
        'note_public' => array('type' => 'html', 'label' => 'NotePublic', 'enabled' => 1, 'visible' => 0, 'position' => 90, 'searchall' => 1),
320
        'model_pdf' => array('type' => 'varchar(255)', 'label' => 'ModelPdf', 'enabled' => 1, 'visible' => 0, 'position' => 95),
321
        'date_close' => array('type' => 'datetime', 'label' => 'DateClosing', 'enabled' => 1, 'visible' => 0, 'position' => 105),
322
        'fk_user_close' => array('type' => 'integer', 'label' => 'UserClosing', 'enabled' => 1, 'visible' => 0, 'position' => 110),
323
        'opp_amount' => array('type' => 'double(24,8)', 'label' => 'OpportunityAmountShort', 'enabled' => 1, 'visible' => 'getDolGlobalString("PROJECT_USE_OPPORTUNITIES")', 'position' => 115),
324
        'budget_amount' => array('type' => 'double(24,8)', 'label' => 'Budget', 'enabled' => 1, 'visible' => -1, 'position' => 119),
325
        'usage_opportunity' => array('type' => 'integer', 'label' => 'UsageOpportunity', 'enabled' => 1, 'visible' => -1, 'position' => 130),
326
        'usage_task' => array('type' => 'integer', 'label' => 'UsageTasks', 'enabled' => 1, 'visible' => -1, 'position' => 135),
327
        'usage_bill_time' => array('type' => 'integer', 'label' => 'UsageBillTimeShort', 'enabled' => 1, 'visible' => -1, 'position' => 140),
328
        'usage_organize_event' => array('type' => 'integer', 'label' => 'UsageOrganizeEvent', 'enabled' => 1, 'visible' => -1, 'position' => 145),
329
        // Properties for event organization
330
        'date_start_event' => array('type' => 'date', 'label' => 'DateStartEvent', 'enabled' => "isModEnabled('eventorganization')", 'visible' => 1, 'position' => 200),
331
        'date_end_event' => array('type' => 'date', 'label' => 'DateEndEvent', 'enabled' => "isModEnabled('eventorganization')", 'visible' => 1, 'position' => 201),
332
        'location' => array('type' => 'text', 'label' => 'Location', 'enabled' => 1, 'visible' => 3, 'position' => 55, 'searchall' => 202),
333
        'accept_conference_suggestions' => array('type' => 'integer', 'label' => 'AllowUnknownPeopleSuggestConf', 'enabled' => 1, 'visible' => -1, 'position' => 210),
334
        'accept_booth_suggestions' => array('type' => 'integer', 'label' => 'AllowUnknownPeopleSuggestBooth', 'enabled' => 1, 'visible' => -1, 'position' => 211),
335
        'price_registration' => array('type' => 'double(24,8)', 'label' => 'PriceOfRegistration', 'enabled' => 1, 'visible' => -1, 'position' => 212),
336
        'price_booth' => array('type' => 'double(24,8)', 'label' => 'PriceOfBooth', 'enabled' => 1, 'visible' => -1, 'position' => 215),
337
        'max_attendees' => array('type' => 'integer', 'label' => 'MaxNbOfAttendees', 'enabled' => 1, 'visible' => -1, 'position' => 215),
338
        // Generic
339
        'datec' => array('type' => 'datetime', 'label' => 'DateCreationShort', 'enabled' => 1, 'visible' => -2, 'position' => 400),
340
        'tms' => array('type' => 'timestamp', 'label' => 'DateModificationShort', 'enabled' => 1, 'visible' => -2, 'notnull' => 1, 'position' => 405),
341
        'fk_user_creat' => array('type' => 'integer', 'label' => 'UserCreation', 'enabled' => 1, 'visible' => 0, 'notnull' => 1, 'position' => 410),
342
        'fk_user_modif' => array('type' => 'integer', 'label' => 'UserModification', 'enabled' => 1, 'visible' => 0, 'position' => 415),
343
        'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -1, 'position' => 420),
344
        'email_msgid' => array('type' => 'varchar(255)', 'label' => 'EmailMsgID', 'enabled' => 1, 'visible' => -1, 'position' => 450, 'help' => 'EmailMsgIDWhenSourceisEmail', 'csslist' => 'tdoverflowmax125'),
345
        'fk_statut' => array('type' => 'smallint(6)', 'label' => 'Status', 'alias' => 'status', 'enabled' => 1, 'visible' => 1, 'notnull' => 1, 'position' => 500, 'arrayofkeyval' => array(0 => 'Draft', 1 => 'Validated', 2 => 'Closed')),
346
    );
347
    // END MODULEBUILDER PROPERTIES
348
349
    /**
350
     * Draft status
351
     */
352
    const STATUS_DRAFT = 0;
353
354
    /**
355
     * Open/Validated status
356
     */
357
    const STATUS_VALIDATED = 1;
358
359
    /**
360
     * Closed status
361
     */
362
    const STATUS_CLOSED = 2;
363
364
365
    /**
366
     *  Constructor
367
     *
368
     *  @param      DoliDB      $db      Database handler
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Projet\Classes\DoliDB was not found. Did you mean DoliDB? If so, make sure to prefix the type with \.
Loading history...
369
     */
370
    public function __construct($db)
371
    {
372
        global $conf;
373
374
        $this->db = $db;
375
376
        $this->ismultientitymanaged = 1;
377
        $this->isextrafieldmanaged = 1;
378
379
        $this->labelStatusShort = array(0 => 'Draft', 1 => 'Opened', 2 => 'Closed');
380
        $this->labelStatus = array(0 => 'Draft', 1 => 'Opened', 2 => 'Closed');
381
382
        global $conf;
383
384
        if (!getDolGlobalString('MAIN_SHOW_TECHNICAL_ID')) {
385
            $this->fields['rowid']['visible'] = 0;
386
        }
387
388
        if (!getDolGlobalString('PROJECT_USE_OPPORTUNITIES')) {
389
            $this->fields['fk_opp_status']['enabled'] = 0;
390
            $this->fields['opp_percent']['enabled'] = 0;
391
            $this->fields['opp_amount']['enabled'] = 0;
392
            $this->fields['usage_opportunity']['enabled'] = 0;
393
        }
394
395
        if (getDolGlobalString('PROJECT_HIDE_TASKS')) {
396
            $this->fields['usage_bill_time']['visible'] = 0;
397
            $this->fields['usage_task']['visible'] = 0;
398
        }
399
400
        if (empty($conf->eventorganization->enabled)) {
401
            $this->fields['usage_organize_event']['visible'] = 0;
402
            $this->fields['accept_conference_suggestions']['enabled'] = 0;
403
            $this->fields['accept_booth_suggestions']['enabled'] = 0;
404
            $this->fields['price_registration']['enabled'] = 0;
405
            $this->fields['price_booth']['enabled'] = 0;
406
            $this->fields['max_attendees']['enabled'] = 0;
407
        }
408
    }
409
410
    /**
411
     *    Create a project into database
412
     *
413
     *    @param    User    $user           User making creation
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Projet\Classes\User was not found. Did you mean User? If so, make sure to prefix the type with \.
Loading history...
414
     *    @param    int     $notrigger      Disable triggers
415
     *    @return   int                     Return integer <0 if KO, id of created project if OK
416
     */
417
    public function create($user, $notrigger = 0)
418
    {
419
        $error = 0;
420
        $ret = 0;
421
422
        $now = dol_now();
423
424
        // Clean parameters
425
        $this->note_private = dol_substr($this->note_private, 0, 65535);
426
        $this->note_public = dol_substr($this->note_public, 0, 65535);
427
428
        // Check parameters
429
        if (!trim($this->ref)) {
430
            $this->error = 'ErrorFieldsRequired';
431
            dol_syslog(get_class($this) . "::create error -1 ref null", LOG_ERR);
432
            return -1;
433
        }
434
        if (getDolGlobalString('PROJECT_THIRDPARTY_REQUIRED') && !($this->socid > 0)) {
435
            $this->error = 'ErrorFieldsRequired';
436
            dol_syslog(get_class($this) . "::create error -1 thirdparty not defined and option PROJECT_THIRDPARTY_REQUIRED is set", LOG_ERR);
437
            return -1;
438
        }
439
440
        // Create project
441
        $this->db->begin();
442
443
        $sql = "INSERT INTO " . MAIN_DB_PREFIX . "projet (";
444
        $sql .= "ref";
445
        $sql .= ", fk_project";
446
        $sql .= ", title";
447
        $sql .= ", description";
448
        $sql .= ", fk_soc";
449
        $sql .= ", fk_user_creat";
450
        $sql .= ", fk_statut";
451
        $sql .= ", fk_opp_status";
452
        $sql .= ", opp_percent";
453
        $sql .= ", public";
454
        $sql .= ", datec";
455
        $sql .= ", dateo";
456
        $sql .= ", datee";
457
        $sql .= ", opp_amount";
458
        $sql .= ", budget_amount";
459
        $sql .= ", usage_opportunity";
460
        $sql .= ", usage_task";
461
        $sql .= ", usage_bill_time";
462
        $sql .= ", usage_organize_event";
463
        $sql .= ", accept_conference_suggestions";
464
        $sql .= ", accept_booth_suggestions";
465
        $sql .= ", price_registration";
466
        $sql .= ", price_booth";
467
        $sql .= ", max_attendees";
468
        $sql .= ", date_start_event";
469
        $sql .= ", date_end_event";
470
        $sql .= ", location";
471
        $sql .= ", email_msgid";
472
        $sql .= ", note_private";
473
        $sql .= ", note_public";
474
        $sql .= ", entity";
475
        $sql .= ", ip";
476
        $sql .= ") VALUES (";
477
        $sql .= "'" . $this->db->escape($this->ref) . "'";
478
        $sql .= ", " . ($this->fk_project ? ((int) $this->fk_project) : "null");
479
        $sql .= ", '" . $this->db->escape($this->title) . "'";
480
        $sql .= ", '" . $this->db->escape($this->description) . "'";
481
        $sql .= ", " . ($this->socid > 0 ? $this->socid : "null");
482
        $sql .= ", " . ((int) $user->id);
483
        $sql .= ", " . (is_numeric($this->statut) ? ((int) $this->statut) : '0');
484
        $sql .= ", " . ((is_numeric($this->opp_status) && $this->opp_status > 0) ? ((int) $this->opp_status) : 'NULL');
485
        $sql .= ", " . (is_numeric($this->opp_percent) ? ((int) $this->opp_percent) : 'NULL');
486
        $sql .= ", " . ($this->public ? 1 : 0);
487
        $sql .= ", '" . $this->db->idate($now) . "'";
488
        $sql .= ", " . ($this->date_start != '' ? "'" . $this->db->idate($this->date_start) . "'" : 'null');
489
        $sql .= ", " . ($this->date_end != '' ? "'" . $this->db->idate($this->date_end) . "'" : 'null');
490
        $sql .= ", " . (strcmp($this->opp_amount, '') ? price2num($this->opp_amount) : 'null');
491
        $sql .= ", " . (strcmp((string) $this->budget_amount, '') ? price2num($this->budget_amount) : 'null');
492
        $sql .= ", " . ($this->usage_opportunity ? 1 : 0);
493
        $sql .= ", " . ($this->usage_task ? 1 : 0);
494
        $sql .= ", " . ($this->usage_bill_time ? 1 : 0);
495
        $sql .= ", " . ($this->usage_organize_event ? 1 : 0);
496
        $sql .= ", " . ($this->accept_conference_suggestions ? 1 : 0);
497
        $sql .= ", " . ($this->accept_booth_suggestions ? 1 : 0);
498
        $sql .= ", " . (strcmp((string) $this->price_registration, '') ? price2num($this->price_registration) : 'null');
499
        $sql .= ", " . (strcmp((string) $this->price_booth, '') ? price2num($this->price_booth) : 'null');
500
        $sql .= ", " . (strcmp((string) $this->max_attendees, '') ? ((int) $this->max_attendees) : 'null');
501
        $sql .= ", " . ($this->date_start_event != '' ? "'" . $this->db->idate($this->date_start_event) . "'" : 'null');
502
        $sql .= ", " . ($this->date_end_event != '' ? "'" . $this->db->idate($this->date_end_event) . "'" : 'null');
503
        $sql .= ", " . ($this->location ? "'" . $this->db->escape($this->location) . "'" : 'null');
504
        $sql .= ", " . ($this->email_msgid ? "'" . $this->db->escape($this->email_msgid) . "'" : 'null');
505
        $sql .= ", " . ($this->note_private ? "'" . $this->db->escape($this->note_private) . "'" : 'null');
506
        $sql .= ", " . ($this->note_public ? "'" . $this->db->escape($this->note_public) . "'" : 'null');
507
        $sql .= ", " . setEntity($this);
508
        $sql .= ", " . (!isset($this->ip) ? 'NULL' : "'" . $this->db->escape($this->ip) . "'");
509
        $sql .= ")";
510
511
        dol_syslog(get_class($this) . "::create", LOG_DEBUG);
512
        $resql = $this->db->query($sql);
513
        if ($resql) {
514
            $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX . "projet");
515
            $ret = $this->id;
516
517
            if (!$notrigger) {
518
                // Call trigger
519
                $result = $this->call_trigger('PROJECT_CREATE', $user);
520
                if ($result < 0) {
521
                    $error++;
522
                }
523
                // End call triggers
524
            }
525
        } else {
526
            $this->error = $this->db->lasterror();
527
            $error++;
528
        }
529
530
        // Update extrafield
531
        if (!$error) {
532
            $result = $this->insertExtraFields();
533
            if ($result < 0) {
534
                $error++;
535
            }
536
        }
537
538
        if (!$error && (getDolGlobalString('MAIN_DISABLEDRAFTSTATUS') || getDolGlobalString('MAIN_DISABLEDRAFTSTATUS_PROJECT'))) {
539
            $res = $this->setValid($user);
540
            if ($res < 0) {
541
                $error++;
542
            }
543
        }
544
545
        if (!$error) {
546
            $this->db->commit();
547
            return $ret;
548
        } else {
549
            $this->db->rollback();
550
            return -1;
551
        }
552
    }
553
554
    /**
555
     * Update a project
556
     *
557
     * @param  User     $user       User object of making update
558
     * @param  int      $notrigger  1=Disable all triggers
559
     * @return int                  Return integer <=0 if KO, >0 if OK
560
     */
561
    public function update($user, $notrigger = 0)
562
    {
563
        global $langs, $conf;
564
565
        $error = 0;
566
567
        // Clean parameters
568
        $this->title = trim($this->title);
569
        $this->description = trim($this->description);
570
        if ($this->opp_amount < 0) {
571
            $this->opp_amount = '';
572
        }
573
        if ($this->opp_percent < 0) {
574
            $this->opp_percent = '';
575
        }
576
        if ($this->date_end && $this->date_end < $this->date_start) {
577
            $this->error = $langs->trans("ErrorDateEndLowerThanDateStart");
578
            $this->errors[] = $this->error;
579
            $this->db->rollback();
580
            dol_syslog(get_class($this) . "::update error -3 " . $this->error, LOG_ERR);
581
            return -3;
582
        }
583
584
        $this->entity = ((isset($this->entity) && is_numeric($this->entity)) ? $this->entity : $conf->entity);
585
586
        if (dol_strlen(trim($this->ref)) > 0) {
587
            $this->db->begin();
588
589
            $sql = "UPDATE " . MAIN_DB_PREFIX . "projet SET";
590
            $sql .= " ref='" . $this->db->escape($this->ref) . "'";
591
            $sql .= ", fk_project=" . ($this->fk_project ? ((int) $this->fk_project) : "null");
592
            $sql .= ", title = '" . $this->db->escape($this->title) . "'";
593
            $sql .= ", description = '" . $this->db->escape($this->description) . "'";
594
            $sql .= ", fk_soc = " . ($this->socid > 0 ? $this->socid : "null");
595
            $sql .= ", fk_statut = " . ((int) $this->statut);
596
            $sql .= ", fk_opp_status = " . ((is_numeric($this->opp_status) && $this->opp_status > 0) ? $this->opp_status : 'null');
597
            $sql .= ", opp_percent = " . ((is_numeric($this->opp_percent) && $this->opp_percent != '') ? $this->opp_percent : 'null');
598
            $sql .= ", public = " . ($this->public ? 1 : 0);
599
            $sql .= ", datec = " . ($this->date_c != '' ? "'" . $this->db->idate($this->date_c) . "'" : 'null');
600
            $sql .= ", dateo = " . ($this->date_start != '' ? "'" . $this->db->idate($this->date_start) . "'" : 'null');
601
            $sql .= ", datee = " . ($this->date_end != '' ? "'" . $this->db->idate($this->date_end) . "'" : 'null');
602
            $sql .= ", date_close = " . ($this->date_close != '' ? "'" . $this->db->idate($this->date_close) . "'" : 'null');
603
            $sql .= ", note_public = " . ($this->note_public ? "'" . $this->db->escape($this->note_public) . "'" : "null");
604
            $sql .= ", note_private = " . ($this->note_private ? "'" . $this->db->escape($this->note_private) . "'" : "null");
605
            $sql .= ", fk_user_close = " . ($this->fk_user_close > 0 ? $this->fk_user_close : "null");
606
            $sql .= ", opp_amount = " . (strcmp($this->opp_amount, '') ? price2num($this->opp_amount) : "null");
607
            $sql .= ", budget_amount = " . (strcmp($this->budget_amount, '') ? price2num($this->budget_amount) : "null");
608
            $sql .= ", fk_user_modif = " . $user->id;
609
            $sql .= ", usage_opportunity = " . ($this->usage_opportunity ? 1 : 0);
610
            $sql .= ", usage_task = " . ($this->usage_task ? 1 : 0);
611
            $sql .= ", usage_bill_time = " . ($this->usage_bill_time ? 1 : 0);
612
            $sql .= ", usage_organize_event = " . ($this->usage_organize_event ? 1 : 0);
613
            $sql .= ", accept_conference_suggestions = " . ($this->accept_conference_suggestions ? 1 : 0);
614
            $sql .= ", accept_booth_suggestions = " . ($this->accept_booth_suggestions ? 1 : 0);
615
            $sql .= ", price_registration = " . (isset($this->price_registration) && strcmp($this->price_registration, '') ? price2num($this->price_registration) : "null");
616
            $sql .= ", price_booth = " . (isset($this->price_booth) && strcmp((string) $this->price_booth, '') ? price2num($this->price_booth) : "null");
617
            $sql .= ", max_attendees = " . (strcmp((string) $this->max_attendees, '') ? (int) $this->max_attendees : "null");
618
            $sql .= ", date_start_event = " . ($this->date_start_event != '' ? "'" . $this->db->idate($this->date_start_event) . "'" : 'null');
619
            $sql .= ", date_end_event = " . ($this->date_end_event != '' ? "'" . $this->db->idate($this->date_end_event) . "'" : 'null');
620
            $sql .= ", location = '" . $this->db->escape($this->location) . "'";
621
            $sql .= ", entity = " . ((int) $this->entity);
622
            $sql .= " WHERE rowid = " . ((int) $this->id);
623
624
            dol_syslog(get_class($this) . "::update", LOG_DEBUG);
625
            $resql = $this->db->query($sql);
626
            if ($resql) {
627
                // Update extrafield
628
                if (!$error) {
629
                    $result = $this->insertExtraFields();
630
                    if ($result < 0) {
631
                        $error++;
632
                    }
633
                }
634
635
                if (!$error && !$notrigger) {
636
                    // Call trigger
637
                    $result = $this->call_trigger('PROJECT_MODIFY', $user);
638
                    if ($result < 0) {
639
                        $error++;
640
                    }
641
                    // End call triggers
642
                }
643
644
                if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
645
                    // We remove directory
646
                    if ($conf->project->dir_output) {
647
                        $olddir = $conf->project->dir_output . "/" . dol_sanitizeFileName($this->oldcopy->ref);
648
                        $newdir = $conf->project->dir_output . "/" . dol_sanitizeFileName($this->ref);
649
                        if (file_exists($olddir)) {
650
                            include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
651
                            $res = @rename($olddir, $newdir);
652
                            if (!$res) {
653
                                $langs->load("errors");
654
                                $this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
655
                                $error++;
656
                            }
657
                        }
658
                    }
659
                }
660
                if (!$error) {
661
                    $this->db->commit();
662
                    $result = 1;
663
                } else {
664
                    $this->db->rollback();
665
                    $result = -1;
666
                }
667
            } else {
668
                $this->error = $this->db->lasterror();
669
                $this->errors[] = $this->error;
670
                $this->db->rollback();
671
                if ($this->db->lasterrno() == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
672
                    $result = -4;
673
                } else {
674
                    $result = -2;
675
                }
676
                dol_syslog(get_class($this) . "::update error " . $result . " " . $this->error, LOG_ERR);
677
            }
678
        } else {
679
            dol_syslog(get_class($this) . "::update ref null");
680
            $result = -1;
681
        }
682
683
        return $result;
684
    }
685
686
    /**
687
     *  Get object from database
688
     *
689
     *  @param      int     $id             Id of object to load
690
     *  @param      string  $ref            Ref of project
691
     *  @param      string  $ref_ext        Ref ext of project
692
     *  @param      string  $email_msgid    Email msgid
693
     *  @return     int                     >0 if OK, 0 if not found, <0 if KO
694
     */
695
    public function fetch($id, $ref = '', $ref_ext = '', $email_msgid = '')
696
    {
697
        if (empty($id) && empty($ref) && empty($ref_ext) && empty($email_msgid)) {
698
            dol_syslog(get_class($this) . "::fetch Bad parameters", LOG_WARNING);
699
            return -1;
700
        }
701
702
        $sql = "SELECT rowid, entity, fk_project, ref, title, description, public, datec, opp_amount, budget_amount,";
703
        $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,";
704
        $sql .= " note_private, note_public, model_pdf, usage_opportunity, usage_task, usage_bill_time, usage_organize_event, email_msgid,";
705
        $sql .= " accept_conference_suggestions, accept_booth_suggestions, price_registration, price_booth, max_attendees, date_start_event, date_end_event, location, extraparams";
706
        $sql .= " FROM " . MAIN_DB_PREFIX . "projet";
707
        if (!empty($id)) {
708
            $sql .= " WHERE rowid = " . ((int) $id);
709
        } else {
710
            $sql .= " WHERE entity IN (" . getEntity('project') . ")";
711
            if (!empty($ref)) {
712
                $sql .= " AND ref = '" . $this->db->escape($ref) . "'";
713
            } elseif (!empty($ref_ext)) {
714
                $sql .= " AND ref_ext = '" . $this->db->escape($ref_ext) . "'";
715
            } else {
716
                $sql .= " AND email_msgid = '" . $this->db->escape($email_msgid) . "'";
717
            }
718
        }
719
720
        dol_syslog(get_class($this) . "::fetch", LOG_DEBUG);
721
        $resql = $this->db->query($sql);
722
        if ($resql) {
723
            $num_rows = $this->db->num_rows($resql);
724
725
            if ($num_rows) {
726
                $obj = $this->db->fetch_object($resql);
727
728
                $this->id = $obj->rowid;
729
                $this->entity = $obj->entity;
730
                $this->ref = $obj->ref;
731
                $this->fk_project = $obj->fk_project;
732
                $this->title = $obj->title;
733
                $this->description = $obj->description;
734
                $this->date_c = $this->db->jdate($obj->datec);
735
                $this->datec = $this->db->jdate($obj->datec); // TODO deprecated
736
                $this->date_m = $this->db->jdate($obj->tms);
737
                $this->datem = $this->db->jdate($obj->tms); // TODO deprecated
738
                $this->date_start = $this->db->jdate($obj->date_start);
739
                $this->date_end = $this->db->jdate($obj->date_end);
740
                $this->date_close = $this->db->jdate($obj->date_close);
741
                $this->note_private = $obj->note_private;
742
                $this->note_public = $obj->note_public;
743
                $this->socid = $obj->fk_soc;
744
                $this->user_author_id = $obj->fk_user_creat;
745
                $this->user_modification_id = $obj->fk_user_modif;
746
                $this->user_closing_id = $obj->fk_user_close;
747
                $this->public = $obj->public;
748
                $this->statut = $obj->status; // deprecated
749
                $this->status = $obj->status;
750
                $this->opp_status = $obj->fk_opp_status;
751
                $this->opp_amount   = $obj->opp_amount;
752
                $this->opp_percent = $obj->opp_percent;
753
                $this->budget_amount = $obj->budget_amount;
754
                $this->model_pdf = $obj->model_pdf;
755
                $this->usage_opportunity = (int) $obj->usage_opportunity;
756
                $this->usage_task = (int) $obj->usage_task;
757
                $this->usage_bill_time = (int) $obj->usage_bill_time;
758
                $this->usage_organize_event = (int) $obj->usage_organize_event;
759
                $this->accept_conference_suggestions = (int) $obj->accept_conference_suggestions;
760
                $this->accept_booth_suggestions = (int) $obj->accept_booth_suggestions;
761
                $this->price_registration = $obj->price_registration;
762
                $this->price_booth = $obj->price_booth;
763
                $this->max_attendees = $obj->max_attendees;
764
                $this->date_start_event = $this->db->jdate($obj->date_start_event);
765
                $this->date_end_event = $this->db->jdate($obj->date_end_event);
766
                $this->location = $obj->location;
767
                $this->email_msgid = $obj->email_msgid;
768
                $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
769
770
                $this->db->free($resql);
771
772
                // Retrieve all extrafield
773
                // fetch optionals attributes and labels
774
                $this->fetch_optionals();
775
776
                return 1;
777
            }
778
779
            $this->db->free($resql);
780
781
            return 0;
782
        } else {
783
            $this->error = $this->db->lasterror();
784
            $this->errors[] = $this->db->lasterror();
785
            return -1;
786
        }
787
    }
788
789
    /**
790
     * Fetch object and substitute key
791
     *
792
     * @param   int         $id                 Project id
793
     * @param   string      $key                Key to substitute
794
     * @param   bool        $fetched            [=false] Not already fetched
795
     * @return  string      Substitute key
796
     */
797
    public function fetchAndSetSubstitution($id, $key, $fetched = false)
798
    {
799
        $substitution = '';
800
801
        if ($fetched === false) {
802
            $res = $this->fetch($id);
803
            if ($res > 0) {
804
                $fetched = true;
805
            }
806
        }
807
808
        if ($fetched === true) {
809
            if ($key == '__PROJECT_ID__') {
810
                $substitution = ($this->id > 0 ? $this->id : '');
811
            } elseif ($key == '__PROJECT_REF__') {
812
                $substitution = $this->ref;
813
            } elseif ($key == '__PROJECT_NAME__') {
814
                $substitution = $this->title;
815
            }
816
        }
817
818
        return $substitution;
819
    }
820
821
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
822
    /**
823
     *  Return list of elements for type, linked to a project
824
     *
825
     *  @param      string      $type           'propal','order','invoice','order_supplier','invoice_supplier',...
826
     *  @param      string      $tablename      name of table associated of the type
827
     *  @param      string      $datefieldname  name of date field for filter
828
     *  @param      int         $date_start     Start date
829
     *  @param      int         $date_end       End date
830
     *  @param      string      $projectkey     Equivalent key  to fk_projet for actual type
831
     *  @return     mixed                       Array list of object ids linked to project, < 0 or string if error
832
     */
833
    public function get_element_list($type, $tablename, $datefieldname = '', $date_start = null, $date_end = null, $projectkey = 'fk_projet')
834
    {
835
		// phpcs:enable
836
837
        global $hookmanager;
838
839
        $elements = array();
840
841
        if ($this->id <= 0) {
842
            return $elements;
843
        }
844
845
        $ids = $this->id;
846
847
        if ($type == 'agenda') {
848
            $sql = "SELECT id as rowid FROM " . MAIN_DB_PREFIX . "actioncomm WHERE fk_project IN (" . $this->db->sanitize($ids) . ") AND entity IN (" . getEntity('agenda') . ")";
849
        } elseif ($type == 'expensereport') {
850
            $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) . ")";
851
        } elseif ($type == 'project_task') {
852
            $sql = "SELECT DISTINCT pt.rowid FROM " . MAIN_DB_PREFIX . "projet_task as pt WHERE pt.fk_projet IN (" . $this->db->sanitize($ids) . ")";
853
        } elseif ($type == 'element_time') {    // Case we want to duplicate line foreach user
854
            $sql = "SELECT DISTINCT pt.rowid, ptt.fk_user FROM " . MAIN_DB_PREFIX . "projet_task as pt, " . MAIN_DB_PREFIX . "element_time as ptt WHERE pt.rowid = ptt.fk_element AND ptt.elementtype = 'task' AND pt.fk_projet IN (" . $this->db->sanitize($ids) . ")";
855
        } elseif ($type == 'stocktransfer_stocktransfer') {
856
            $sql = "SELECT ms.rowid, ms.fk_user_author as fk_user FROM " . MAIN_DB_PREFIX . "stocktransfer_stocktransfer 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";
857
        } elseif ($type == 'loan') {
858
            $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) . ")";
859
        } else {
860
            $sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . $tablename . " WHERE " . $projectkey . " IN (" . $this->db->sanitize($ids) . ") AND entity IN (" . getEntity($type) . ")";
861
        }
862
863
        if (isDolTms($date_start) && $type == 'loan') {
864
            $sql .= " AND (dateend > '" . $this->db->idate($date_start) . "' OR dateend IS NULL)";
865
        } elseif (isDolTms($date_start) && ($type != 'project_task')) { // For table project_taks, we want the filter on date apply on project_time_spent table
866
            if (empty($datefieldname) && !empty($this->table_element_date)) {
867
                $datefieldname = $this->table_element_date;
868
            }
869
            if (empty($datefieldname)) {
870
                return 'Error this object has no date field defined';
871
            }
872
            $sql .= " AND (" . $datefieldname . " >= '" . $this->db->idate($date_start) . "' OR " . $datefieldname . " IS NULL)";
873
        }
874
875
        if (isDolTms($date_end) && $type == 'loan') {
876
            $sql .= " AND (datestart < '" . $this->db->idate($date_end) . "' OR datestart IS NULL)";
877
        } elseif (isDolTms($date_end) && ($type != 'project_task')) {   // For table project_taks, we want the filter on date apply on project_time_spent table
878
            if (empty($datefieldname) && !empty($this->table_element_date)) {
879
                $datefieldname = $this->table_element_date;
880
            }
881
            if (empty($datefieldname)) {
882
                return 'Error this object has no date field defined';
883
            }
884
            $sql .= " AND (" . $datefieldname . " <= '" . $this->db->idate($date_end) . "' OR " . $datefieldname . " IS NULL)";
885
        }
886
887
        $parameters = array(
888
            'sql' => $sql,
889
            'type' => $type,
890
            'tablename' => $tablename,
891
            'datefieldname'  => $datefieldname,
892
            'dates' => $date_start,
893
            'datee' => $date_end,
894
            'fk_projet' => $projectkey,
895
            'ids' => $ids,
896
        );
897
        $reshook = $hookmanager->executeHooks('getElementList', $parameters);
898
        if ($reshook > 0) {
899
            $sql = $hookmanager->resPrint;
900
        } else {
901
            $sql .= $hookmanager->resPrint;
902
        }
903
904
        if (!$sql) {
905
            return -1;
906
        }
907
908
        //print $sql;
909
        dol_syslog(get_class($this) . "::get_element_list", LOG_DEBUG);
910
        $result = $this->db->query($sql);
911
        if ($result) {
912
            $nump = $this->db->num_rows($result);
913
            if ($nump) {
914
                $i = 0;
915
                while ($i < $nump) {
916
                    $obj = $this->db->fetch_object($result);
917
918
                    $elements[$i] = $obj->rowid . (empty($obj->fk_user) ? '' : '_' . $obj->fk_user);
919
920
                    $i++;
921
                }
922
                $this->db->free($result);
923
            }
924
925
            /* Return array even if empty*/
926
            return $elements;
927
        } else {
928
            dol_print_error($this->db);
929
        }
930
        return -1;
931
    }
932
933
    /**
934
     *    Delete a project from database
935
     *
936
     *    @param       User     $user            User
937
     *    @param       int      $notrigger       Disable triggers
938
     *    @return      int                        Return integer <0 if KO, 0 if not possible, >0 if OK
939
     */
940
    public function delete($user, $notrigger = 0)
941
    {
942
        global $langs, $conf;
943
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
944
945
        $error = 0;
946
947
        $this->db->begin();
948
949
        if (!$error) {
950
            // Delete linked contacts
951
            $res = $this->delete_linked_contact();
952
            if ($res < 0) {
953
                $this->error = 'ErrorFailToDeleteLinkedContact';
954
                //$error++;
955
                $this->db->rollback();
956
                return 0;
957
            }
958
        }
959
960
        // Set fk_projet into elements to null
961
        $listoftables = array(
962
            'propal' => 'fk_projet', 'commande' => 'fk_projet', 'facture' => 'fk_projet',
963
            'supplier_proposal' => 'fk_projet', 'commande_fournisseur' => 'fk_projet', 'facture_fourn' => 'fk_projet',
964
            'expensereport_det' => 'fk_projet', 'contrat' => 'fk_projet',
965
            'fichinter' => 'fk_projet',
966
            'don' => array('field' => 'fk_projet', 'module' => 'don'),
967
            'actioncomm' => 'fk_project',
968
            'mrp_mo' => 'fk_project',
969
            'entrepot' => 'fk_project'
970
        );
971
        foreach ($listoftables as $key => $value) {
972
            if (is_array($value)) {
973
                if (!isModEnabled($value['module'])) {
974
                    continue;
975
                }
976
                $fieldname = $value['field'];
977
            } else {
978
                $fieldname = $value;
979
            }
980
            $sql = "UPDATE " . MAIN_DB_PREFIX . $key . " SET " . $fieldname . " = NULL where " . $fieldname . " = " . ((int) $this->id);
981
982
            $resql = $this->db->query($sql);
983
            if (!$resql) {
984
                $this->errors[] = $this->db->lasterror();
985
                $error++;
986
                break;
987
            }
988
        }
989
990
        // Remove linked categories.
991
        if (!$error) {
992
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . "categorie_project";
993
            $sql .= " WHERE fk_project = " . ((int) $this->id);
994
995
            $result = $this->db->query($sql);
996
            if (!$result) {
997
                $error++;
998
                $this->errors[] = $this->db->lasterror();
999
            }
1000
        }
1001
1002
        // Fetch tasks
1003
        $this->getLinesArray($user, 0);
1004
1005
        // Delete tasks
1006
        $ret = $this->deleteTasks($user);
1007
        if ($ret < 0) {
1008
            $error++;
1009
        }
1010
1011
1012
        // Delete all child tables
1013
        if (!$error) {
1014
            $elements = array('categorie_project'); // elements to delete. TODO Make goodway to delete
1015
            foreach ($elements as $table) {
1016
                if (!$error) {
1017
                    $sql = "DELETE FROM " . MAIN_DB_PREFIX . $table;
1018
                    $sql .= " WHERE fk_project = " . ((int) $this->id);
1019
1020
                    $result = $this->db->query($sql);
1021
                    if (!$result) {
1022
                        $error++;
1023
                        $this->errors[] = $this->db->lasterror();
1024
                    }
1025
                }
1026
            }
1027
        }
1028
1029
        if (!$error) {
1030
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . "projet_extrafields";
1031
            $sql .= " WHERE fk_object = " . ((int) $this->id);
1032
1033
            $resql = $this->db->query($sql);
1034
            if (!$resql) {
1035
                $this->errors[] = $this->db->lasterror();
1036
                $error++;
1037
            }
1038
        }
1039
1040
        // Delete project
1041
        if (!$error) {
1042
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . "projet";
1043
            $sql .= " WHERE rowid=" . ((int) $this->id);
1044
1045
            $resql = $this->db->query($sql);
1046
            if (!$resql) {
1047
                $this->errors[] = $langs->trans("CantRemoveProject", $langs->transnoentitiesnoconv("ProjectOverview"));
1048
                $error++;
1049
            }
1050
        }
1051
1052
1053
1054
        if (empty($error)) {
1055
            // We remove directory
1056
            $projectref = dol_sanitizeFileName($this->ref);
1057
            if ($conf->project->dir_output) {
1058
                $dir = $conf->project->dir_output . "/" . $projectref;
1059
                if (file_exists($dir)) {
1060
                    $res = @dol_delete_dir_recursive($dir);
1061
                    if (!$res) {
1062
                        $this->errors[] = 'ErrorFailToDeleteDir';
1063
                        $error++;
1064
                    }
1065
                }
1066
            }
1067
1068
            if (!$notrigger) {
1069
                // Call trigger
1070
                $result = $this->call_trigger('PROJECT_DELETE', $user);
1071
1072
                if ($result < 0) {
1073
                    $error++;
1074
                }
1075
                // End call triggers
1076
            }
1077
        }
1078
1079
        if (empty($error)) {
1080
            $this->db->commit();
1081
            return 1;
1082
        } else {
1083
            foreach ($this->errors as $errmsg) {
1084
                dol_syslog(get_class($this) . "::delete " . $errmsg, LOG_ERR);
1085
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
1086
            }
1087
            dol_syslog(get_class($this) . "::delete " . $this->error, LOG_ERR);
1088
            $this->db->rollback();
1089
            return -1;
1090
        }
1091
    }
1092
1093
    /**
1094
     * Return the count of a type of linked elements of this project
1095
     *
1096
     * @param string    $type           The type of the linked elements (e.g. 'propal', 'order', 'invoice', 'order_supplier', 'invoice_supplier')
1097
     * @param string    $tablename      The name of table associated of the type
1098
     * @param string    $projectkey     (optional) Equivalent key to fk_projet for actual type
1099
     * @return integer                  The count of the linked elements (the count is zero on request error too)
1100
     */
1101
    public function getElementCount($type, $tablename, $projectkey = 'fk_projet')
1102
    {
1103
        if ($this->id <= 0) {
1104
            return 0;
1105
        }
1106
1107
        if ($type == 'agenda') {
1108
            $sql = "SELECT COUNT(id) as nb FROM " . MAIN_DB_PREFIX . "actioncomm WHERE fk_project = " . ((int) $this->id) . " AND entity IN (" . getEntity('agenda') . ")";
1109
        } elseif ($type == 'expensereport') {
1110
            $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);
1111
        } elseif ($type == 'project_task') {
1112
            $sql = "SELECT DISTINCT COUNT(pt.rowid) as nb FROM " . MAIN_DB_PREFIX . "projet_task as pt WHERE pt.fk_projet = " . ((int) $this->id);
1113
        } elseif ($type == 'element_time') {    // Case we want to duplicate line foreach user
1114
            $sql = "SELECT DISTINCT COUNT(pt.rowid) as nb FROM " . MAIN_DB_PREFIX . "projet_task as pt, " . MAIN_DB_PREFIX . "element_time as ptt WHERE pt.rowid = ptt.fk_element AND ptt.elementtype = 'task' AND pt.fk_projet = " . ((int) $this->id);
1115
        } elseif ($type == 'stock_mouvement') {
1116
            $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";
1117
        } elseif ($type == 'loan') {
1118
            $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);
1119
        } else {
1120
            $sql = "SELECT COUNT(rowid) as nb FROM " . MAIN_DB_PREFIX . $tablename . " WHERE " . $projectkey . " = " . ((int) $this->id) . " AND entity IN (" . getEntity($type) . ")";
1121
        }
1122
1123
        $result = $this->db->query($sql);
1124
1125
        if (!$result) {
1126
            return 0;
1127
        }
1128
1129
        $obj = $this->db->fetch_object($result);
1130
1131
        $this->db->free($result);
1132
1133
        return $obj->nb;
1134
    }
1135
1136
    /**
1137
     *      Delete tasks with no children first, then task with children recursively
1138
     *
1139
     *      @param      User        $user       User
1140
     *      @return     int             Return integer <0 if KO, 1 if OK
1141
     */
1142
    public function deleteTasks($user)
1143
    {
1144
        $countTasks = count($this->lines);
1145
        $deleted = false;
1146
        if ($countTasks) {
1147
            foreach ($this->lines as $task) {
1148
                if ($task->hasChildren() <= 0) {        // If there is no children (or error to detect them)
1149
                    $deleted = true;
1150
                    $ret = $task->delete($user);
1151
                    if ($ret <= 0) {
1152
                        $this->errors[] = $this->db->lasterror();
1153
                        return -1;
1154
                    }
1155
                }
1156
            }
1157
        }
1158
        $this->getLinesArray($user);
1159
        if ($deleted && count($this->lines) < $countTasks) {
1160
            if (count($this->lines)) {
1161
                $this->deleteTasks($this->lines);
1162
            }
1163
        }
1164
1165
        return 1;
1166
    }
1167
1168
    /**
1169
     *      Validate a project
1170
     *
1171
     *      @param      User    $user          User that validate
1172
     *      @param      int     $notrigger     1=Disable triggers
1173
     *      @return     int                    Return integer <0 if KO, 0=Nothing done, >0 if KO
1174
     */
1175
    public function setValid($user, $notrigger = 0)
1176
    {
1177
        global $langs, $conf;
1178
1179
        $error = 0;
1180
1181
        // Protection
1182
        if ($this->status == self::STATUS_VALIDATED) {
1183
            dol_syslog(get_class($this) . "::validate action abandoned: already validated", LOG_WARNING);
1184
            return 0;
1185
        }
1186
1187
        // Check parameters
1188
        if (preg_match('/^' . preg_quote($langs->trans("CopyOf") . ' ') . '/', $this->title)) {
1189
            $this->error = $langs->trans("ErrorFieldFormat", $langs->transnoentities("Label")) . '. ' . $langs->trans('RemoveString', $langs->transnoentitiesnoconv("CopyOf"));
1190
            return -1;
1191
        }
1192
1193
        $this->db->begin();
1194
1195
        $sql = "UPDATE " . MAIN_DB_PREFIX . "projet";
1196
        $sql .= " SET fk_statut = " . self::STATUS_VALIDATED;
1197
        $sql .= " WHERE rowid = " . ((int) $this->id);
1198
        //$sql .= " AND entity = ".((int) $conf->entity);   // Disabled, when we use the ID for the where, we must not add any other search condition
1199
1200
        dol_syslog(get_class($this) . "::setValid", LOG_DEBUG);
1201
        $resql = $this->db->query($sql);
1202
        if ($resql) {
1203
            // Call trigger
1204
            if (empty($notrigger)) {
1205
                $result = $this->call_trigger('PROJECT_VALIDATE', $user);
1206
                if ($result < 0) {
1207
                    $error++;
1208
                }
1209
                // End call triggers
1210
            }
1211
1212
            if (!$error) {
1213
                $this->statut = 1;
1214
                $this->db->commit();
1215
                return 1;
1216
            } else {
1217
                $this->db->rollback();
1218
                $this->error = implode(',', $this->errors);
1219
                dol_syslog(get_class($this) . "::setValid " . $this->error, LOG_ERR);
1220
                return -1;
1221
            }
1222
        } else {
1223
            $this->db->rollback();
1224
            $this->error = $this->db->lasterror();
1225
            return -1;
1226
        }
1227
    }
1228
1229
    /**
1230
     *      Close a project
1231
     *
1232
     *      @param      User    $user       User that close project
1233
     *      @return     int                 Return integer <0 if KO, 0 if already closed, >0 if OK
1234
     */
1235
    public function setClose($user)
1236
    {
1237
        $now = dol_now();
1238
1239
        $error = 0;
1240
1241
        if ($this->statut != self::STATUS_CLOSED) {
1242
            $this->db->begin();
1243
1244
            $sql = "UPDATE " . MAIN_DB_PREFIX . "projet";
1245
            $sql .= " SET fk_statut = " . self::STATUS_CLOSED . ", fk_user_close = " . ((int) $user->id) . ", date_close = '" . $this->db->idate($now) . "'";
1246
            $sql .= " WHERE rowid = " . ((int) $this->id);
1247
            $sql .= " AND fk_statut = " . self::STATUS_VALIDATED;
1248
1249
            if (getDolGlobalString('PROJECT_USE_OPPORTUNITIES')) {
1250
                // TODO What to do if fk_opp_status is not code 'WON' or 'LOST'
1251
            }
1252
1253
            dol_syslog(get_class($this) . "::setClose", LOG_DEBUG);
1254
            $resql = $this->db->query($sql);
1255
            if ($resql) {
1256
                // Call trigger
1257
                $result = $this->call_trigger('PROJECT_CLOSE', $user);
1258
                if ($result < 0) {
1259
                    $error++;
1260
                }
1261
                // End call triggers
1262
1263
                if (!$error) {
1264
                    $this->statut = 2;
1265
                    $this->db->commit();
1266
                    return 1;
1267
                } else {
1268
                    $this->db->rollback();
1269
                    $this->error = implode(',', $this->errors);
1270
                    dol_syslog(get_class($this) . "::setClose " . $this->error, LOG_ERR);
1271
                    return -1;
1272
                }
1273
            } else {
1274
                $this->db->rollback();
1275
                $this->error = $this->db->lasterror();
1276
                return -1;
1277
            }
1278
        }
1279
1280
        return 0;
1281
    }
1282
1283
    /**
1284
     *  Return status label of object
1285
     *
1286
     *  @param  int         $mode       0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto
1287
     *  @return string                  Label
1288
     */
1289
    public function getLibStatut($mode = 0)
1290
    {
1291
        return $this->LibStatut(isset($this->statut) ? $this->statut : $this->status, $mode);
1292
    }
1293
1294
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1295
    /**
1296
     *  Renvoi status label for a status
1297
     *
1298
     *  @param  int     $status     id status
1299
     *  @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
1300
     *  @return string              Label
1301
     */
1302
    public function LibStatut($status, $mode = 0)
1303
    {
1304
		// phpcs:enable
1305
        global $langs;
1306
1307
        if (is_null($status)) {
1308
            return '';
1309
        }
1310
1311
        $statustrans = array(
1312
            0 => 'status0',
1313
            1 => 'status4',
1314
            2 => 'status6',
1315
        );
1316
1317
        $statusClass = 'status0';
1318
        if (!empty($statustrans[$status])) {
1319
            $statusClass = $statustrans[$status];
1320
        }
1321
1322
        return dolGetStatus($langs->transnoentitiesnoconv($this->labelStatus[$status]), $langs->transnoentitiesnoconv($this->labelStatusShort[$status]), '', $statusClass, $mode);
1323
    }
1324
1325
    /**
1326
     * getTooltipContentArray
1327
     *
1328
     * @param array $params ex option, infologin
1329
     * @since v18
1330
     * @return array
1331
     */
1332
    public function getTooltipContentArray($params)
1333
    {
1334
        global $conf, $langs;
1335
1336
        $langs->load('projects');
1337
        $option = $params['option'] ?? '';
1338
        $moreinpopup = $params['moreinpopup'] ?? '';
1339
1340
        $datas = [];
1341
        if ($option != 'nolink') {
1342
            $datas['picto'] = img_picto('', $this->picto, 'class="pictofixedwidth"') . ' <u class="paddingrightonly">' . $langs->trans("Project") . '</u>';
1343
        }
1344
        if (isset($this->status)) {
1345
            $datas['picto'] .= ' ' . $this->getLibStatut(5);
1346
        }
1347
        $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
1348
        $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
1349
        if (isset($this->public)) {
1350
            $datas['visibility'] = '<br><b>' . $langs->trans("Visibility") . ":</b> ";
1351
            $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"));
1352
        }
1353
        if (!empty($this->thirdparty_name)) {
1354
            $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
1355
        }
1356
        if (!empty($this->date_start)) {
1357
            $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
1358
        }
1359
        if (!empty($this->date_end)) {
1360
            $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
1361
        }
1362
        if ($moreinpopup) {
1363
            $datas['moreinpopup'] = '<br>' . $moreinpopup;
1364
        }
1365
1366
        return $datas;
1367
    }
1368
1369
    /**
1370
     *  Return clickable name (with picto eventually)
1371
     *
1372
     *  @param  int     $withpicto                0=No picto, 1=Include picto into link, 2=Only picto
1373
     *  @param  string  $option                   Variant where the link point to ('', 'nolink')
1374
     *  @param  int     $addlabel                 0=Default, 1=Add label into string, >1=Add first chars into string
1375
     *  @param  string  $moreinpopup              Text to add into popup
1376
     *  @param  string  $sep                      Separator between ref and label if option addlabel is set
1377
     *  @param  int     $notooltip                1=Disable tooltip
1378
     *  @param  int     $save_lastsearch_value    -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
1379
     *  @param  string  $morecss                  More css on a link
1380
     *  @param  string  $save_pageforbacktolist       Back to this page 'context:url'
1381
     *  @return string                            String with URL
1382
     */
1383
    public function getNomUrl($withpicto = 0, $option = '', $addlabel = 0, $moreinpopup = '', $sep = ' - ', $notooltip = 0, $save_lastsearch_value = -1, $morecss = '', $save_pageforbacktolist = '')
1384
    {
1385
        global $conf, $langs, $user, $hookmanager;
1386
1387
        if (!empty($conf->dol_no_mouse_hover)) {
1388
            $notooltip = 1; // Force disable tooltips
1389
        }
1390
1391
        $result = '';
1392
        if (getDolGlobalString('PROJECT_OPEN_ALWAYS_ON_TAB')) {
1393
            $option = getDolGlobalString('PROJECT_OPEN_ALWAYS_ON_TAB');
1394
        }
1395
        $params = [
1396
            'id' => $this->id,
1397
            'objecttype' => $this->element,
1398
            'moreinpopup' => $moreinpopup,
1399
            'option' => $option,
1400
        ];
1401
        $classfortooltip = 'classfortooltip';
1402
        $dataparams = '';
1403
        if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1404
            $classfortooltip = 'classforajaxtooltip';
1405
            $dataparams = ' data-params="' . dol_escape_htmltag(json_encode($params)) . '"';
1406
            $label = '';
1407
        } else {
1408
            $label = implode($this->getTooltipContentArray($params));
1409
        }
1410
1411
        $url = '';
1412
        if ($option != 'nolink') {
1413
            if (preg_match('/\.php$/', $option)) {
1414
                $url = dol_buildpath($option, 1) . '?id=' . $this->id;
1415
            } elseif ($option == 'task') {
1416
                $url = constant('BASE_URL') . '/projet/tasks.php?id=' . $this->id;
1417
            } elseif ($option == 'preview') {
1418
                $url = constant('BASE_URL') . '/projet/element.php?id=' . $this->id;
1419
            } elseif ($option == 'eventorganization') {
1420
                $url = constant('BASE_URL') . '/eventorganization/conferenceorbooth_list.php?projectid=' . $this->id;
1421
            } else {
1422
                $url = constant('BASE_URL') . '/projet/card.php?id=' . $this->id;
1423
            }
1424
            // Add param to save lastsearch_values or not
1425
            $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1426
            if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1427
                $add_save_lastsearch_values = 1;
1428
            }
1429
            if ($add_save_lastsearch_values) {
1430
                $url .= '&save_lastsearch_values=1';
1431
            }
1432
            $add_save_backpagefor = ($save_pageforbacktolist ? 1 : 0);
1433
            if ($add_save_backpagefor) {
1434
                $url .= "&save_pageforbacktolist=" . urlencode($save_pageforbacktolist);
1435
            }
1436
        }
1437
1438
        $linkclose = '';
1439
        if (empty($notooltip) && $user->hasRight('projet', 'lire')) {
1440
            if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1441
                $label = $langs->trans("ShowProject");
1442
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
1443
            }
1444
            $linkclose .= ($label ? ' title="' . dol_escape_htmltag($label, 1) . '"' : ' title="tocomplete"');
1445
            $linkclose .= $dataparams . ' class="' . $classfortooltip . ($morecss ? ' ' . $morecss : '') . '"';
1446
        } else {
1447
            $linkclose = ($morecss ? ' class="' . $morecss . '"' : '');
1448
        }
1449
1450
        $picto = 'projectpub';
1451
        if (!$this->public) {
1452
            $picto = 'project';
1453
        }
1454
1455
        $linkstart = '<a href="' . $url . '"';
1456
        $linkstart .= $linkclose . '>';
1457
        $linkend = '</a>';
1458
1459
        $result .= $linkstart;
1460
        if ($withpicto) {
1461
            $result .= img_object(($notooltip ? '' : $label), $picto, 'class="pictofixedwidth em088"', 0, 0, $notooltip ? 0 : 1);
1462
        }
1463
        if ($withpicto != 2) {
1464
            $result .= $this->ref;
1465
        }
1466
        $result .= $linkend;
1467
        if ($withpicto != 2) {
1468
            $result .= (($addlabel && $this->title) ? '<span class="opacitymedium">' . $sep . dol_trunc($this->title, ($addlabel > 1 ? $addlabel : 0)) . '</span>' : '');
1469
        }
1470
1471
        global $action;
1472
        $hookmanager->initHooks(array('projectdao'));
1473
        $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1474
        $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1475
        if ($reshook > 0) {
1476
            $result = $hookmanager->resPrint;
1477
        } else {
1478
            $result .= $hookmanager->resPrint;
1479
        }
1480
1481
        return $result;
1482
    }
1483
1484
    /**
1485
     *  Initialise an instance with random values.
1486
     *  Used to build previews or test instances.
1487
     *  id must be 0 if object instance is a specimen.
1488
     *
1489
     *  @return int
1490
     */
1491
    public function initAsSpecimen()
1492
    {
1493
        global $user, $langs, $conf;
1494
1495
        $now = dol_now();
1496
1497
        // Initialise parameters
1498
        $this->id = 0;
1499
        $this->ref = 'SPECIMEN';
1500
        $this->entity = $conf->entity;
1501
        $this->specimen = 1;
1502
        $this->socid = 1;
1503
        $this->date_c = $now;
1504
        $this->date_m = $now;
1505
        $this->date_start = $now;
1506
        $this->date_end = $now + (3600 * 24 * 365);
1507
        $this->note_public = 'SPECIMEN';
1508
        $this->note_private = 'Private Note';
1509
        $this->fk_project = 0;
1510
        $this->opp_amount = 20000;
1511
        $this->budget_amount = 10000;
1512
1513
        $this->usage_opportunity = 1;
1514
        $this->usage_task = 1;
1515
        $this->usage_bill_time = 1;
1516
        $this->usage_organize_event = 1;
1517
1518
        /*
1519
         $nbp = mt_rand(1, 9);
1520
         $xnbp = 0;
1521
         while ($xnbp < $nbp)
1522
         {
1523
         $line = new Task($this->db);
1524
         $line->fk_project = 0;
1525
         $line->label = $langs->trans("Label") . " " . $xnbp;
1526
         $line->description = $langs->trans("Description") . " " . $xnbp;
1527
1528
         $this->lines[]=$line;
1529
         $xnbp++;
1530
         }
1531
         */
1532
1533
        return 1;
1534
    }
1535
1536
    /**
1537
     *  Check if user has permission on current project
1538
     *
1539
     *  @param  User    $user       Object user to evaluate
1540
     *  @param  string  $mode       Type of permission we want to know: 'read', 'write'
1541
     *  @return int                 >0 if user has permission, <0 if user has no permission
1542
     */
1543
    public function restrictedProjectArea(User $user, $mode = 'read')
1544
    {
1545
        // To verify role of users
1546
        $userAccess = 0;
1547
        if (($mode == 'read' && $user->hasRight('projet', 'all', 'lire')) || ($mode == 'write' && $user->hasRight('projet', 'all', 'creer')) || ($mode == 'delete' && $user->hasRight('projet', 'all', 'supprimer'))) {
1548
            $userAccess = 1;
1549
        } elseif ($this->public && (($mode == 'read' && $user->hasRight('projet', 'lire')) || ($mode == 'write' && $user->hasRight('projet', 'creer')) || ($mode == 'delete' && $user->hasRight('projet', 'supprimer')))) {
1550
            $userAccess = 1;
1551
        } else {    // No access due to permission to read all projects, so we check if we are a contact of project
1552
            foreach (array('internal', 'external') as $source) {
1553
                $userRole = $this->liste_contact(4, $source);
1554
                $num = count($userRole);
1555
1556
                $nblinks = 0;
1557
                while ($nblinks < $num) {
1558
                    if ($source == 'internal' && $user->id == $userRole[$nblinks]['id']) {  // $userRole[$nblinks]['id'] is id of user (llx_user) for internal contacts
1559
                        if ($mode == 'read' && $user->hasRight('projet', 'lire')) {
1560
                            $userAccess++;
1561
                        }
1562
                        if ($mode == 'write' && $user->hasRight('projet', 'creer')) {
1563
                            $userAccess++;
1564
                        }
1565
                        if ($mode == 'delete' && $user->hasRight('projet', 'supprimer')) {
1566
                            $userAccess++;
1567
                        }
1568
                    }
1569
                    if ($source == 'external' && $user->socid > 0 && $user->socid == $userRole[$nblinks]['socid']) {    // $userRole[$nblinks]['id'] is id of contact (llx_socpeople) or external contacts
1570
                        if ($mode == 'read' && $user->hasRight('projet', 'lire')) {
1571
                            $userAccess++;
1572
                        }
1573
                        if ($mode == 'write' && $user->hasRight('projet', 'creer')) {
1574
                            $userAccess++;
1575
                        }
1576
                        if ($mode == 'delete' && $user->hasRight('projet', 'supprimer')) {
1577
                            $userAccess++;
1578
                        }
1579
                    }
1580
                    $nblinks++;
1581
                }
1582
            }
1583
            //if (empty($nblinks))  // If nobody has permission, we grant creator
1584
            //{
1585
            //  if ((!empty($this->user_author_id) && $this->user_author_id == $user->id))
1586
            //  {
1587
            //      $userAccess = 1;
1588
            //  }
1589
            //}
1590
        }
1591
1592
        return ($userAccess ? $userAccess : -1);
1593
    }
1594
1595
    /**
1596
     * Return array of projects a user has permission on, is affected to, or all projects
1597
     *
1598
     * @param   User    $user           User object
1599
     * @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
1600
     * @param   int     $list           0=Return array, 1=Return string list
1601
     * @param   int     $socid          0=No filter on third party, id of third party
1602
     * @param   string  $filter         additional filter on project (statut, ref, ...)
1603
     * @return  array|string            Array of projects id, or string with projects id separated with "," if list is 1
1604
     */
1605
    public function getProjectsAuthorizedForUser($user, $mode = 0, $list = 0, $socid = 0, $filter = '')
1606
    {
1607
        $projects = array();
1608
        $temp = array();
1609
1610
        $sql = "SELECT " . (($mode == 0 || $mode == 1) ? "DISTINCT " : "") . "p.rowid, p.ref";
1611
        $sql .= " FROM " . MAIN_DB_PREFIX . "projet as p";
1612
        if ($mode == 0) {
1613
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "element_contact as ec ON ec.element_id = p.rowid";
1614
        } elseif ($mode == 1) {
1615
            $sql .= ", " . MAIN_DB_PREFIX . "element_contact as ec";
1616
        } elseif ($mode == 2) {
1617
            // No filter. Use this if user has permission to see all project
1618
        }
1619
        $sql .= " WHERE p.entity IN (" . getEntity('project') . ")";
1620
        // Internal users must see project he is contact to even if project linked to a third party he can't see.
1621
        //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).")";
1622
        if ($socid > 0) {
1623
            $sql .= " AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = " . ((int) $socid) . ")";
1624
        }
1625
1626
        // Get id of types of contacts for projects (This list never contains a lot of elements)
1627
        $listofprojectcontacttype = array();
1628
        $sql2 = "SELECT ctc.rowid, ctc.code FROM " . MAIN_DB_PREFIX . "c_type_contact as ctc";
1629
        $sql2 .= " WHERE ctc.element = '" . $this->db->escape($this->element) . "'";
1630
        $sql2 .= " AND ctc.source = 'internal'";
1631
        $resql = $this->db->query($sql2);
1632
        if ($resql) {
1633
            while ($obj = $this->db->fetch_object($resql)) {
1634
                $listofprojectcontacttype[$obj->rowid] = $obj->code;
1635
            }
1636
        } else {
1637
            dol_print_error($this->db);
1638
        }
1639
        if (count($listofprojectcontacttype) == 0) {
1640
            $listofprojectcontacttype[0] = '0'; // To avoid syntax error if not found
1641
        }
1642
1643
        if ($mode == 0) {
1644
            $sql .= " AND ( p.public = 1";
1645
            $sql .= " OR ( ec.fk_c_type_contact IN (" . $this->db->sanitize(implode(',', array_keys($listofprojectcontacttype))) . ")";
1646
            $sql .= " AND ec.fk_socpeople = " . ((int) $user->id) . ")";
1647
            $sql .= " )";
1648
        } elseif ($mode == 1) {
1649
            $sql .= " AND ec.element_id = p.rowid";
1650
            $sql .= " AND (";
1651
            $sql .= "  ( ec.fk_c_type_contact IN (" . $this->db->sanitize(implode(',', array_keys($listofprojectcontacttype))) . ")";
1652
            $sql .= " AND ec.fk_socpeople = " . ((int) $user->id) . ")";
1653
            $sql .= " )";
1654
        } elseif ($mode == 2) {
1655
            // No filter. Use this if user has permission to see all project
1656
        }
1657
1658
        $sql .= $filter;
1659
        //print $sql;
1660
1661
        $resql = $this->db->query($sql);
1662
        if ($resql) {
1663
            $num = $this->db->num_rows($resql);
1664
            $i = 0;
1665
            while ($i < $num) {
1666
                $row = $this->db->fetch_row($resql);
1667
                $projects[$row[0]] = $row[1];
1668
                $temp[] = $row[0];
1669
                $i++;
1670
            }
1671
1672
            $this->db->free($resql);
1673
1674
            if ($list) {
1675
                if (empty($temp)) {
1676
                    return '0';
1677
                }
1678
                $result = implode(',', $temp);
1679
                return $result;
1680
            }
1681
        } else {
1682
            dol_print_error($this->db);
1683
        }
1684
1685
        return $projects;
1686
    }
1687
1688
    /**
1689
     * Load an object from its id and create a new one in database
1690
     *
1691
     *  @param  User    $user                 User making the clone
1692
     *  @param  int     $fromid               Id of object to clone
1693
     *  @param  bool    $clone_contact        Clone contact of project
1694
     *  @param  bool    $clone_task           Clone task of project
1695
     *  @param  bool    $clone_project_file   Clone file of project
1696
     *  @param  bool    $clone_task_file      Clone file of task (if task are copied)
1697
     *  @param  bool    $clone_note           Clone note of project
1698
     *  @param  bool    $move_date            Move task date on clone
1699
     *  @param  int     $notrigger            No trigger flag
1700
     *  @param  int     $newthirdpartyid      New thirdparty id
1701
     *  @return int                           New id of clone
1702
     */
1703
    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)
1704
    {
1705
        global $langs, $conf;
1706
1707
        $error = 0;
1708
        $clone_project_id = 0;   // For static toolcheck
1709
1710
        dol_syslog("createFromClone clone_contact=" . json_encode($clone_contact) . " clone_task=" . json_encode($clone_task) . " clone_project_file=" . json_encode($clone_project_file) . " clone_note=" . json_encode($clone_note) . " move_date=" . json_encode($move_date), LOG_DEBUG);
1711
1712
        $now = dol_mktime(0, 0, 0, idate('m', dol_now()), idate('d', dol_now()), idate('Y', dol_now()));
1713
1714
        $clone_project = new Project($this->db);
1715
1716
        $clone_project->context['createfromclone'] = 'createfromclone';
1717
1718
        $this->db->begin();
1719
1720
        // Load source object
1721
        $clone_project->fetch($fromid);
1722
        $clone_project->fetch_optionals();
1723
        if ($newthirdpartyid > 0) {
1724
            $clone_project->socid = $newthirdpartyid;
1725
        }
1726
        $clone_project->fetch_thirdparty();
1727
1728
        $orign_dt_start = $clone_project->date_start;
1729
        $orign_project_ref = $clone_project->ref;
1730
1731
        $clone_project->id = 0;
1732
        if ($move_date) {
1733
            $clone_project->date_start = $now;
0 ignored issues
show
Documentation Bug introduced by
It seems like $now can also be of type string. However, the property $date_start is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1734
            if (!(empty($clone_project->date_end))) {
1735
                $clone_project->date_end = $clone_project->date_end + ($now - $orign_dt_start);
1736
            }
1737
        }
1738
1739
        $clone_project->date_c = $now;
0 ignored issues
show
Documentation Bug introduced by
It seems like $now can also be of type string. However, the property $date_c is declared as type integer. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
1740
1741
        if (!$clone_note) {
1742
            $clone_project->note_private = '';
1743
            $clone_project->note_public = '';
1744
        }
1745
1746
        //Generate next ref
1747
        $defaultref = '';
1748
        $obj = !getDolGlobalString('PROJECT_ADDON') ? 'mod_project_simple' : $conf->global->PROJECT_ADDON;
1749
        // Search template files
1750
        $file = '';
1751
        $classname = '';
1752
        $filefound = 0;
1753
        $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
1754
        foreach ($dirmodels as $reldir) {
1755
            $file = dol_buildpath($reldir . "core/modules/project/" . $obj . '.php', 0);
1756
            if (file_exists($file)) {
1757
                $filefound = 1;
1758
                dol_include_once($reldir . "core/modules/project/" . $obj . '.php');
1759
                $modProject = new $obj();
1760
                $defaultref = $modProject->getNextValue(is_object($clone_project->thirdparty) ? $clone_project->thirdparty : null, $clone_project);
1761
                break;
1762
            }
1763
        }
1764
        if (is_numeric($defaultref) && $defaultref <= 0) {
1765
            $defaultref = '';
1766
        }
1767
1768
        $clone_project->ref = $defaultref;
1769
        $clone_project->title = $langs->trans("CopyOf") . ' ' . $clone_project->title;
1770
1771
        // Create clone
1772
        $result = $clone_project->create($user, $notrigger);
1773
1774
        // Other options
1775
        if ($result < 0) {
1776
            $this->error .= $clone_project->error;
1777
            $error++;
1778
        }
1779
1780
        if (!$error) {
1781
            //Get the new project id
1782
            $clone_project_id = $clone_project->id;
1783
1784
            //Note Update
1785
            if (!$clone_note) {
1786
                $clone_project->note_private = '';
1787
                $clone_project->note_public = '';
1788
            } else {
1789
                $this->db->begin();
1790
                $res = $clone_project->update_note(dol_html_entity_decode($clone_project->note_public, ENT_QUOTES | ENT_HTML5), '_public');
1791
                if ($res < 0) {
1792
                    $this->error .= $clone_project->error;
1793
                    $error++;
1794
                    $this->db->rollback();
1795
                } else {
1796
                    $this->db->commit();
1797
                }
1798
1799
                $this->db->begin();
1800
                $res = $clone_project->update_note(dol_html_entity_decode($clone_project->note_private, ENT_QUOTES | ENT_HTML5), '_private');
1801
                if ($res < 0) {
1802
                    $this->error .= $clone_project->error;
1803
                    $error++;
1804
                    $this->db->rollback();
1805
                } else {
1806
                    $this->db->commit();
1807
                }
1808
            }
1809
1810
            //Duplicate contact
1811
            if ($clone_contact) {
1812
                $origin_project = new Project($this->db);
1813
                $origin_project->fetch($fromid);
1814
1815
                foreach (array('internal', 'external') as $source) {
1816
                    $tab = $origin_project->liste_contact(-1, $source);
1817
                    if (is_array($tab) && count($tab) > 0) {
1818
                        foreach ($tab as $contacttoadd) {
1819
                            $clone_project->add_contact($contacttoadd['id'], $contacttoadd['code'], $contacttoadd['source'], $notrigger);
1820
                            if ($clone_project->error == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
1821
                                $langs->load("errors");
1822
                                $this->error .= $langs->trans("ErrorThisContactIsAlreadyDefinedAsThisType");
1823
                                $error++;
1824
                            } else {
1825
                                if ($clone_project->error != '') {
1826
                                    $this->error .= $clone_project->error;
1827
                                    $error++;
1828
                                }
1829
                            }
1830
                        }
1831
                    } elseif ($tab < 0) {
1832
                        $this->error .= $origin_project->error;
1833
                        $error++;
1834
                    }
1835
                }
1836
            }
1837
1838
            //Duplicate file
1839
            if ($clone_project_file) {
1840
                require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
1841
1842
                $clone_project_dir = $conf->project->dir_output . "/" . dol_sanitizeFileName($defaultref);
1843
                $ori_project_dir = $conf->project->dir_output . "/" . dol_sanitizeFileName($orign_project_ref);
1844
1845
                if (dol_mkdir($clone_project_dir) >= 0) {
1846
                    $filearray = dol_dir_list($ori_project_dir, "files", 0, '', '(\.meta|_preview.*\.png)$', '', SORT_ASC, 1);
1847
                    foreach ($filearray as $key => $file) {
1848
                        $rescopy = dol_copy($ori_project_dir . '/' . $file['name'], $clone_project_dir . '/' . $file['name'], 0, 1);
1849
                        if (is_numeric($rescopy) && $rescopy < 0) {
1850
                            $this->error .= $langs->trans("ErrorFailToCopyFile", $ori_project_dir . '/' . $file['name'], $clone_project_dir . '/' . $file['name']);
1851
                            $error++;
1852
                        }
1853
                    }
1854
                } else {
1855
                    $this->error .= $langs->trans('ErrorInternalErrorDetected') . ':dol_mkdir';
1856
                    $error++;
1857
                }
1858
            }
1859
1860
            //Duplicate task
1861
            if ($clone_task) {
1862
                require_once constant('DOL_DOCUMENT_ROOT') . '/projet/class/task.class.php';
1863
1864
                $taskstatic = new Task($this->db);
1865
1866
                // Security check
1867
                $socid = 0;
1868
                if ($user->socid > 0) {
1869
                    $socid = $user->socid;
1870
                }
1871
1872
                $tasksarray = $taskstatic->getTasksArray(0, 0, $fromid, $socid, 0);
1873
1874
                $tab_conv_child_parent = array();
1875
1876
                // Loop on each task, to clone it
1877
                foreach ($tasksarray as $tasktoclone) {
1878
                    $result_clone = $taskstatic->createFromClone($user, $tasktoclone->id, $clone_project_id, $tasktoclone->fk_task_parent, $move_date, true, false, $clone_task_file, true, false);
1879
                    if ($result_clone <= 0) {
1880
                        $this->error .= $taskstatic->error;
1881
                        $error++;
1882
                    } else {
1883
                        $new_task_id = $result_clone;
1884
                        $taskstatic->fetch($tasktoclone->id);
1885
1886
                        //manage new parent clone task id
1887
                        // if the current task has child we store the original task id and the equivalent clone task id
1888
                        if (($taskstatic->hasChildren()) && !array_key_exists($tasktoclone->id, $tab_conv_child_parent)) {
1889
                            $tab_conv_child_parent[$tasktoclone->id] = $new_task_id;
1890
                        }
1891
                    }
1892
                }
1893
1894
                //Parse all clone node to be sure to update new parent
1895
                $tasksarray = $taskstatic->getTasksArray(0, 0, $clone_project_id, $socid, 0);
1896
                foreach ($tasksarray as $task_cloned) {
1897
                    $taskstatic->fetch($task_cloned->id);
1898
                    if ($taskstatic->fk_task_parent != 0) {
1899
                        $taskstatic->fk_task_parent = $tab_conv_child_parent[$taskstatic->fk_task_parent];
1900
                    }
1901
                    $res = $taskstatic->update($user, $notrigger);
1902
                    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 1877. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1903
                        $this->error .= $taskstatic->error;
1904
                        $error++;
1905
                    }
1906
                }
1907
            }
1908
        }
1909
1910
        unset($clone_project->context['createfromclone']);
1911
1912
        if (!$error && $clone_project_id != 0) {
1913
            $this->db->commit();
1914
            return $clone_project_id;
1915
        } else {
1916
            $this->db->rollback();
1917
            dol_syslog(get_class($this) . "::createFromClone nbError: " . $error . " error : " . $this->error, LOG_ERR);
1918
            return -1;
1919
        }
1920
    }
1921
1922
1923
    /**
1924
     *    Shift project task date from current date to delta
1925
     *
1926
     *    @param    integer     $old_project_dt_start   Old project start date
1927
     *    @return   int                                 1 if OK or < 0 if KO
1928
     */
1929
    public function shiftTaskDate($old_project_dt_start)
1930
    {
1931
        global $user, $langs, $conf;
1932
1933
        $error = 0;
1934
        $result = 0;
1935
1936
        $taskstatic = new Task($this->db);
1937
1938
        // Security check
1939
        $socid = 0;
1940
        if ($user->socid > 0) {
1941
            $socid = $user->socid;
1942
        }
1943
1944
        $tasksarray = $taskstatic->getTasksArray(0, 0, $this->id, $socid, 0);
1945
1946
        foreach ($tasksarray as $tasktoshiftdate) {
1947
            $to_update = false;
1948
            // Fetch only if update of date will be made
1949
            if ((!empty($tasktoshiftdate->date_start)) || (!empty($tasktoshiftdate->date_end))) {
1950
                //dol_syslog(get_class($this)."::shiftTaskDate to_update", LOG_DEBUG);
1951
                $to_update = true;
1952
                $task = new Task($this->db);
1953
                $result = $task->fetch($tasktoshiftdate->id);
1954
                if (!$result) {
1955
                    $error++;
1956
                    $this->error .= $task->error;
1957
                }
1958
            }
1959
            //print "$this->date_start + $tasktoshiftdate->date_start - $old_project_dt_start";exit;
1960
1961
            //Calculate new task start date with difference between old proj start date and origin task start date
1962
            if (!empty($tasktoshiftdate->date_start)) {
1963
                $task->date_start = $this->date_start + ($tasktoshiftdate->date_start - $old_project_dt_start);
1964
            }
1965
1966
            //Calculate new task end date with difference between origin proj end date and origin task end date
1967
            if (!empty($tasktoshiftdate->date_end)) {
1968
                $task->date_end = $this->date_start + ($tasktoshiftdate->date_end - $old_project_dt_start);
1969
            }
1970
1971
            if ($to_update) {
1972
                $result = $task->update($user);
1973
                if (!$result) {
1974
                    $error++;
1975
                    $this->error .= $task->error;
1976
                }
1977
            }
1978
        }
1979
        if ($error != 0) {
1980
            return -1;
1981
        }
1982
        return $result;
1983
    }
1984
1985
1986
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1987
    /**
1988
     *    Associate element to a project
1989
     *
1990
     *    @param    string  $tableName          Table of the element to update
1991
     *    @param    int     $elementSelectId    Key-rowid of the line of the element to update
1992
     *    @return   int                         1 if OK or < 0 if KO
1993
     */
1994
    public function update_element($tableName, $elementSelectId)
1995
    {
1996
		// phpcs:enable
1997
        $sql = "UPDATE " . MAIN_DB_PREFIX . $tableName;
1998
1999
        if ($tableName == "actioncomm") {
2000
            $sql .= " SET fk_project=" . $this->id;
2001
            $sql .= " WHERE id=" . ((int) $elementSelectId);
2002
        } elseif (in_array($tableName, ["entrepot","mrp_mo","stocktransfer_stocktransfer"])) {
2003
            $sql .= " SET fk_project=" . $this->id;
2004
            $sql .= " WHERE rowid=" . ((int) $elementSelectId);
2005
        } else {
2006
            $sql .= " SET fk_projet=" . $this->id;
2007
            $sql .= " WHERE rowid=" . ((int) $elementSelectId);
2008
        }
2009
2010
        dol_syslog(get_class($this) . "::update_element", LOG_DEBUG);
2011
        $resql = $this->db->query($sql);
2012
        if (!$resql) {
2013
            $this->error = $this->db->lasterror();
2014
            return -1;
2015
        } else {
2016
            return 1;
2017
        }
2018
    }
2019
2020
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2021
    /**
2022
     *    Associate element to a project
2023
     *
2024
     *    @param    string  $tableName          Table of the element to update
2025
     *    @param    int     $elementSelectId    Key-rowid of the line of the element to update
2026
     *    @param    string  $projectfield       The column name that stores the link with the project
2027
     *
2028
     *    @return   int                         1 if OK or < 0 if KO
2029
     */
2030
    public function remove_element($tableName, $elementSelectId, $projectfield = 'fk_projet')
2031
    {
2032
		// phpcs:enable
2033
        $sql = "UPDATE " . MAIN_DB_PREFIX . $tableName;
2034
2035
        if ($tableName == "actioncomm") {
2036
            $sql .= " SET fk_project=NULL";
2037
            $sql .= " WHERE id=" . ((int) $elementSelectId);
2038
        } else {
2039
            $sql .= " SET " . $projectfield . "=NULL";
2040
            $sql .= " WHERE rowid=" . ((int) $elementSelectId);
2041
        }
2042
2043
        dol_syslog(get_class($this) . "::remove_element", LOG_DEBUG);
2044
        $resql = $this->db->query($sql);
2045
        if (!$resql) {
2046
            $this->error = $this->db->lasterror();
2047
            return -1;
2048
        } else {
2049
            return 1;
2050
        }
2051
    }
2052
2053
    /**
2054
     *  Create an intervention document on disk using template defined into PROJECT_ADDON_PDF
2055
     *
2056
     *  @param  string      $modele         Force template to use ('' by default)
2057
     *  @param  Translate   $outputlangs    Object lang to use for translation
2058
     *  @param  int         $hidedetails    Hide details of lines
2059
     *  @param  int         $hidedesc       Hide description
2060
     *  @param  int         $hideref        Hide ref
2061
     *  @return int                         0 if KO, 1 if OK
2062
     */
2063
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
2064
    {
2065
        global $conf, $langs;
2066
2067
        $langs->load("projects");
2068
2069
        if (!dol_strlen($modele)) {
2070
            $modele = 'baleine';
2071
2072
            if ($this->model_pdf) {
2073
                $modele = $this->model_pdf;
2074
            } elseif (getDolGlobalString('PROJECT_ADDON_PDF')) {
2075
                $modele = getDolGlobalString('PROJECT_ADDON_PDF');
2076
            }
2077
        }
2078
2079
        $modelpath = "core/modules/project/doc/";
2080
2081
        return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
2082
    }
2083
2084
2085
    /**
2086
     * Load time spent into this->weekWorkLoad and this->weekWorkLoadPerTask for all day of a week of project.
2087
     * Note: array weekWorkLoad and weekWorkLoadPerTask are reset and filled at each call.
2088
     *
2089
     * @param   int     $datestart      First day of week (use dol_get_first_day to find this date)
2090
     * @param   int     $taskid         Filter on a task id
2091
     * @param   int     $userid         Time spent by a particular user
2092
     * @return  int                     Return integer <0 if OK, >0 if KO
2093
     */
2094
    public function loadTimeSpent($datestart, $taskid = 0, $userid = 0)
2095
    {
2096
        $this->weekWorkLoad = array();
2097
        $this->weekWorkLoadPerTask = array();
2098
2099
        if (empty($datestart)) {
2100
            dol_print_error(null, 'Error datestart parameter is empty');
2101
        }
2102
2103
        $sql = "SELECT ptt.rowid as taskid, ptt.element_duration, ptt.element_date, ptt.element_datehour, ptt.fk_element";
2104
        $sql .= " FROM " . MAIN_DB_PREFIX . "element_time AS ptt, " . MAIN_DB_PREFIX . "projet_task as pt";
2105
        $sql .= " WHERE ptt.fk_element = pt.rowid";
2106
        $sql .= " AND ptt.elementtype = 'task'";
2107
        $sql .= " AND pt.fk_projet = " . ((int) $this->id);
2108
        $sql .= " AND (ptt.element_date >= '" . $this->db->idate($datestart) . "' ";
2109
        $sql .= " AND ptt.element_date <= '" . $this->db->idate(dol_time_plus_duree($datestart, 1, 'w') - 1) . "')";
2110
        if ($taskid) {
2111
            $sql .= " AND ptt.fk_element=" . ((int) $taskid);
2112
        }
2113
        if (is_numeric($userid)) {
2114
            $sql .= " AND ptt.fk_user=" . ((int) $userid);
2115
        }
2116
2117
        //print $sql;
2118
        $resql = $this->db->query($sql);
2119
        if ($resql) {
2120
            $daylareadyfound = array();
2121
2122
            $num = $this->db->num_rows($resql);
2123
            $i = 0;
2124
            // Loop on each record found, so each couple (project id, task id)
2125
            while ($i < $num) {
2126
                $obj = $this->db->fetch_object($resql);
2127
                $day = $this->db->jdate($obj->element_date); // task_date is date without hours
2128
                if (empty($daylareadyfound[$day])) {
2129
                    $this->weekWorkLoad[$day] = $obj->element_duration;
2130
                    $this->weekWorkLoadPerTask[$day][$obj->fk_element] = $obj->element_duration;
2131
                } else {
2132
                    $this->weekWorkLoad[$day] += $obj->element_duration;
2133
                    $this->weekWorkLoadPerTask[$day][$obj->fk_element] += $obj->element_duration;
2134
                }
2135
                $daylareadyfound[$day] = 1;
2136
                $i++;
2137
            }
2138
            $this->db->free($resql);
2139
            return 1;
2140
        } else {
2141
            $this->error = "Error " . $this->db->lasterror();
2142
            dol_syslog(get_class($this) . "::fetch " . $this->error, LOG_ERR);
2143
            return -1;
2144
        }
2145
    }
2146
2147
    /**
2148
     * Load time spent into this->weekWorkLoad and this->weekWorkLoadPerTask for all day of a week of project.
2149
     * Note: array weekWorkLoad and weekWorkLoadPerTask are reset and filled at each call.
2150
     *
2151
     * @param   int     $datestart      First day of week (use dol_get_first_day to find this date)
2152
     * @param   int     $taskid         Filter on a task id
2153
     * @param   int     $userid         Time spent by a particular user
2154
     * @return  int                     Return integer <0 if OK, >0 if KO
2155
     */
2156
    public function loadTimeSpentMonth($datestart, $taskid = 0, $userid = 0)
2157
    {
2158
        $this->monthWorkLoad = array();
2159
        $this->monthWorkLoadPerTask = array();
2160
2161
        if (empty($datestart)) {
2162
            dol_print_error(null, 'Error datestart parameter is empty');
2163
        }
2164
2165
        $sql = "SELECT ptt.rowid as taskid, ptt.element_duration, ptt.element_date, ptt.element_datehour, ptt.fk_element";
2166
        $sql .= " FROM " . MAIN_DB_PREFIX . "element_time AS ptt, " . MAIN_DB_PREFIX . "projet_task as pt";
2167
        $sql .= " WHERE ptt.fk_element = pt.rowid";
2168
        $sql .= " AND ptt.elementtype = 'task'";
2169
        $sql .= " AND pt.fk_projet = " . ((int) $this->id);
2170
        $sql .= " AND (ptt.element_date >= '" . $this->db->idate($datestart) . "' ";
2171
        $sql .= " AND ptt.element_date <= '" . $this->db->idate(dol_time_plus_duree($datestart, 1, 'm') - 1) . "')";
2172
        if ($taskid) {
2173
            $sql .= " AND ptt.fk_element=" . ((int) $taskid);
2174
        }
2175
        if (is_numeric($userid)) {
2176
            $sql .= " AND ptt.fk_user=" . ((int) $userid);
2177
        }
2178
2179
        //print $sql;
2180
        $resql = $this->db->query($sql);
2181
        if ($resql) {
2182
            $weekalreadyfound = array();
2183
2184
            $num = $this->db->num_rows($resql);
2185
            $i = 0;
2186
            // Loop on each record found, so each couple (project id, task id)
2187
            while ($i < $num) {
2188
                $obj = $this->db->fetch_object($resql);
2189
                if (!empty($obj->element_date)) {
2190
                    $date = explode('-', $obj->element_date);
2191
                    $week_number = getWeekNumber($date[2], $date[1], $date[0]);
2192
                }
2193
                '@phan-var-force int $week_number';  // Needed because phan considers it might be null
2194
                if (empty($weekalreadyfound[$week_number])) {
2195
                    $this->monthWorkLoad[$week_number] = $obj->element_duration;
2196
                    $this->monthWorkLoadPerTask[$week_number][$obj->fk_element] = $obj->element_duration;
2197
                } else {
2198
                    $this->monthWorkLoad[$week_number] += $obj->element_duration;
2199
                    $this->monthWorkLoadPerTask[$week_number][$obj->fk_element] += $obj->element_duration;
2200
                }
2201
                $weekalreadyfound[$week_number] = 1;
2202
                $i++;
2203
            }
2204
            $this->db->free($resql);
2205
            return 1;
2206
        } else {
2207
            $this->error = "Error " . $this->db->lasterror();
2208
            dol_syslog(get_class($this) . "::fetch " . $this->error, LOG_ERR);
2209
            return -1;
2210
        }
2211
    }
2212
2213
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2214
    /**
2215
     * Load indicators for dashboard (this->nbtodo and this->nbtodolate)
2216
     *
2217
     * @param   User    $user   Object user
2218
     * @return WorkboardResponse|int Return integer <0 if KO, WorkboardResponse if OK
2219
     */
2220
    public function load_board($user)
2221
    {
2222
		// phpcs:enable
2223
        global $conf, $langs;
2224
2225
        // For external user, no check is done on company because readability is managed by public status of project and assignment.
2226
        //$socid=$user->socid;
2227
2228
        $response = new WorkboardResponse();
2229
        $response->warning_delay = $conf->project->warning_delay / 60 / 60 / 24;
2230
        $response->label = $langs->trans("OpenedProjects");
2231
        $response->labelShort = $langs->trans("Opened");
2232
        $response->url = constant('BASE_URL') . '/projet/list.php?search_project_user=-1&search_status=1&mainmenu=project';
2233
        $response->img = img_object('', "projectpub");
2234
        $response->nbtodo = 0;
2235
        $response->nbtodolate = 0;
2236
2237
        $sql = "SELECT p.rowid, p.fk_statut as status, p.fk_opp_status, p.datee as datee";
2238
        $sql .= " FROM (" . MAIN_DB_PREFIX . "projet as p";
2239
        $sql .= ")";
2240
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s on p.fk_soc = s.rowid";
2241
        // For external user, no check is done on company permission because readability is managed by public status of project and assignment.
2242
        //if (! $user->rights->societe->client->voir && ! $socid) $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON sc.fk_soc = s.rowid";
2243
        $sql .= " WHERE p.fk_statut = 1";
2244
        $sql .= " AND p.entity IN (" . getEntity('project') . ')';
2245
2246
2247
        $projectsListId = null;
2248
        if (!$user->hasRight("projet", "all", "lire")) {
2249
            $response->url = constant('BASE_URL') . '/projet/list.php?search_status=1&mainmenu=project';
2250
            $projectsListId = $this->getProjectsAuthorizedForUser($user, 0, 1);
2251
            if (empty($projectsListId)) {
2252
                return $response;
2253
            }
2254
2255
            $sql .= " AND p.rowid IN (" . $this->db->sanitize($projectsListId) . ")";
2256
        }
2257
2258
        // No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser
2259
        //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).")";
2260
        // For external user, no check is done on company permission because readability is managed by public status of project and assignment.
2261
        //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))";
2262
2263
        //print $sql;
2264
        $resql = $this->db->query($sql);
2265
        if ($resql) {
2266
            $project_static = new Project($this->db);
2267
2268
2269
            // This assignment in condition is not a bug. It allows walking the results.
2270
            while ($obj = $this->db->fetch_object($resql)) {
2271
                $response->nbtodo++;
2272
2273
                $project_static->statut = $obj->status;
2274
                $project_static->opp_status = $obj->fk_opp_status;
2275
                $project_static->date_end = $this->db->jdate($obj->datee);
2276
2277
                if ($project_static->hasDelay()) {
2278
                    $response->nbtodolate++;
2279
                }
2280
            }
2281
2282
            return $response;
2283
        }
2284
2285
        $this->error = $this->db->error();
2286
        return -1;
2287
    }
2288
2289
    /**
2290
     * Function used to replace a thirdparty id with another one.
2291
     *
2292
     * @param DoliDB $dbs       Database handler
2293
     * @param int $origin_id    Old thirdparty id
2294
     * @param int $dest_id      New thirdparty id
2295
     * @return bool
2296
     */
2297
    public static function replaceThirdparty(DoliDB $dbs, $origin_id, $dest_id)
2298
    {
2299
        $tables = array(
2300
            'projet'
2301
        );
2302
2303
        return CommonObject::commonReplaceThirdparty($dbs, $origin_id, $dest_id, $tables);
2304
    }
2305
2306
2307
    /**
2308
     * Load indicators this->nb for the state board
2309
     *
2310
     * @return     int         Return integer <0 if KO, >0 if OK
2311
     */
2312
    public function loadStateBoard()
2313
    {
2314
        global $user;
2315
2316
        $this->nb = array();
2317
2318
        $sql = "SELECT count(p.rowid) as nb";
2319
        $sql .= " FROM " . MAIN_DB_PREFIX . "projet as p";
2320
        $sql .= " WHERE";
2321
        $sql .= " p.entity IN (" . getEntity('project') . ")";
2322
        if (!$user->hasRight('projet', 'all', 'lire')) {
2323
            $projectsListId = $this->getProjectsAuthorizedForUser($user, 0, 1);
2324
            $sql .= "AND p.rowid IN (" . $this->db->sanitize($projectsListId) . ")";
2325
        }
2326
2327
        $resql = $this->db->query($sql);
2328
        if ($resql) {
2329
            while ($obj = $this->db->fetch_object($resql)) {
2330
                $this->nb["projects"] = $obj->nb;
2331
            }
2332
            $this->db->free($resql);
2333
            return 1;
2334
        } else {
2335
            dol_print_error($this->db);
2336
            $this->error = $this->db->error();
2337
            return -1;
2338
        }
2339
    }
2340
2341
2342
    /**
2343
     * Is the project delayed?
2344
     *
2345
     * @return bool
2346
     */
2347
    public function hasDelay()
2348
    {
2349
        global $conf;
2350
2351
        if (!($this->statut == self::STATUS_VALIDATED)) {
2352
            return false;
2353
        }
2354
        if (!$this->date_end) {
2355
            return false;
2356
        }
2357
2358
        $now = dol_now();
2359
2360
        return ($this->date_end) < ($now - $conf->project->warning_delay);
2361
    }
2362
2363
2364
    /**
2365
     *  Charge les information d'ordre info dans l'objet commande
2366
     *
2367
     *  @param  int     $id       Id of order
2368
     *  @return void
2369
     */
2370
    public function info($id)
2371
    {
2372
        $sql = 'SELECT c.rowid, datec as datec, tms as datem,';
2373
        $sql .= ' date_close as datecloture,';
2374
        $sql .= ' fk_user_creat as fk_user_author, fk_user_close as fk_user_cloture';
2375
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'projet as c';
2376
        $sql .= ' WHERE c.rowid = ' . ((int) $id);
2377
        $result = $this->db->query($sql);
2378
        if ($result) {
2379
            if ($this->db->num_rows($result)) {
2380
                $obj = $this->db->fetch_object($result);
2381
2382
                $this->id = $obj->rowid;
2383
2384
                $this->user_creation_id = $obj->fk_user_author;
2385
                $this->user_closing_id   = $obj->fk_user_cloture;
2386
2387
                $this->date_creation     = $this->db->jdate($obj->datec);
2388
                $this->date_modification = $this->db->jdate($obj->datem);
2389
                $this->date_cloture      = $this->db->jdate($obj->datecloture);
2390
            }
2391
2392
            $this->db->free($result);
2393
        } else {
2394
            dol_print_error($this->db);
2395
        }
2396
    }
2397
2398
    /**
2399
     * Sets object to supplied categories.
2400
     *
2401
     * Deletes object from existing categories not supplied.
2402
     * Adds it to non existing supplied categories.
2403
     * Existing categories are left untouch.
2404
     *
2405
     * @param   int[]|int   $categories     Category or categories IDs
2406
     * @return  int                         Return integer <0 if KO, >0 if OK
2407
     */
2408
    public function setCategories($categories)
2409
    {
2410
        require_once constant('DOL_DOCUMENT_ROOT') . '/categories/class/categorie.class.php';
2411
        return parent::setCategoriesCommon($categories, Categorie::TYPE_PROJECT);
2412
    }
2413
2414
2415
    /**
2416
     * Create an array of tasks of current project
2417
     *
2418
     * @param   User    $user               Object user we want project allowed to
2419
     * @param   int     $loadRoleMode       1= will test Roles on task;  0 used in delete project action
2420
     * @return  int                         >0 if OK, <0 if KO
2421
     */
2422
    public function getLinesArray($user, $loadRoleMode = 1)
2423
    {
2424
        require_once constant('DOL_DOCUMENT_ROOT') . '/projet/class/task.class.php';
2425
        $taskstatic = new Task($this->db);
2426
2427
        $this->lines = $taskstatic->getTasksArray(0, $user, $this->id, 0, 0, '', '-1', '', 0, 0, array(), 0, array(), 0, $loadRoleMode);
2428
        return 1;
2429
    }
2430
2431
    /**
2432
     *  Function sending an email to the current member with the text supplied in parameter.
2433
     *
2434
     *  @param  string  $text               Content of message (not html entities encoded)
2435
     *  @param  string  $subject            Subject of message
2436
     *  @param  array   $filename_list      Array of attached files
2437
     *  @param  array   $mimetype_list      Array of mime types of attached files
2438
     *  @param  array   $mimefilename_list  Array of public names of attached files
2439
     *  @param  string  $addr_cc            Email cc
2440
     *  @param  string  $addr_bcc           Email bcc
2441
     *  @param  int     $deliveryreceipt    Ask a delivery receipt
2442
     *  @param  int     $msgishtml          1=String IS already html, 0=String IS NOT html, -1=Unknown need autodetection
2443
     *  @param  string  $errors_to          errors to
2444
     *  @param  string  $moreinheader       Add more html headers
2445
     *  @since V18
2446
     *  @return int                         Return integer <0 if KO, >0 if OK
2447
     */
2448
    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 = '')
2449
    {
2450
        global $conf, $langs;
2451
        // TODO EMAIL
2452
2453
        return 1;
2454
    }
2455
    /**
2456
     *  Return clicable link of object (with eventually picto)
2457
     *
2458
     *  @param      string      $option                 Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
2459
     *  @param      array       $arraydata              Array of data
2460
     *  @param      string      $size                   Size of thumb (''=auto, 'large'=large, 'small'=small)
2461
     *  @return     string                              HTML Code for Kanban thumb.
2462
     */
2463
    public function getKanbanView($option = '', $arraydata = null, $size = '')
2464
    {
2465
        global $conf, $langs;
2466
2467
        $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2468
2469
        if (empty($size)) {
2470
            if (empty($conf->dol_optimize_smallscreen)) {
2471
                $size = 'large';
2472
            } else {
2473
                $size = 'small';
2474
            }
2475
        }
2476
2477
        $return = '<div class="box-flex-item ' . ($size == 'small' ? 'box-flex-item-small' : '') . ' box-flex-grow-zero">';
2478
        $return .= '<div class="info-box info-box-sm">';
2479
        $return .= '<span class="info-box-icon bg-infobox-action">';
2480
        $return .= img_picto('', $this->public ? 'projectpub' : $this->picto);
2481
        //$return .= '<i class="fa fa-dol-action"></i>'; // Can be image
2482
        $return .= '</span>';
2483
        $return .= '<div class="info-box-content">';
2484
        $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . (method_exists($this, 'getNomUrl') ? $this->getNomUrl() : $this->ref);
2485
        if ($this->hasDelay()) {
2486
            $return .= img_warning($langs->trans('Late'));
2487
        }
2488
        $return .= '</span>';
2489
        if ($selected >= 0) {
2490
            $return .= '<input id="cb' . $this->id . '" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="' . $this->id . '"' . ($selected ? ' checked="checked"' : '') . '>';
2491
        }
2492
        // Date
2493
        /*
2494
        if (property_exists($this, 'date_start') && $this->date_start) {
2495
            $return .= '<br><span class="info-box-label">'.dol_print_date($this->date_start, 'day').'</>';
2496
        }
2497
        if (property_exists($this, 'date_end') && $this->date_end) {
2498
            if ($this->date_start) {
2499
                $return .= ' - ';
2500
            } else {
2501
                $return .= '<br>';
2502
            }
2503
            $return .= '<span class="info-box-label">'.dol_print_date($this->date_end, 'day').'</span>';
2504
        }*/
2505
        if (property_exists($this, 'thirdparty') && !is_null($this->thirdparty) && is_object($this->thirdparty) && $this->thirdparty instanceof Societe) {
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Projet\Classes\Societe was not found. Did you mean Societe? If so, make sure to prefix the type with \.
Loading history...
2506
            $return .= '<br><div class="info-box-ref tdoverflowmax150 inline-block valignmiddle">' . $this->thirdparty->getNomUrl(1);
2507
            $return .= '</div>';
2508
            if (!empty($this->thirdparty->phone)) {
2509
                $return .= '<div class="inline-block valignmiddle">';
2510
                $return .= dol_print_phone($this->thirdparty->phone, $this->thirdparty->country_code, 0, $this->thirdparty->id, 'tel', 'hidenum', 'phone', $this->thirdparty->phone, 0, 'marginleftonly');
2511
                $return .= '</div>';
2512
            }
2513
        }
2514
        if (!empty($arraydata['assignedusers'])) {
2515
            $return .= '<br>';
2516
            if ($this->public) {
2517
                $return .= img_picto($langs->trans('Visibility') . ': ' . $langs->trans('SharedProject'), 'world', 'class="paddingrightonly valignmiddle"');
2518
                //print $langs->trans('SharedProject');
2519
            } else {
2520
                $return .= img_picto($langs->trans('Visibility') . ': ' . $langs->trans('PrivateProject'), 'private', 'class="paddingrightonly valignmiddle"');
2521
                //print $langs->trans('PrivateProject');
2522
            }
2523
2524
            $return .= ' <span class="small valignmiddle">' . $arraydata['assignedusers'] . '</span>';
2525
        }
2526
        /*if (property_exists($this, 'user_author_id')) {
2527
            $return .= '<br><span class="info-box-label opacitymedium">'.$langs->trans("Author").'</span>';
2528
            $return .= '<span> : '.$user->getNomUrl(1).'</span>';
2529
        }*/
2530
        $return .= '<br><div>'; // start div line status
2531
        if ($this->usage_opportunity && $this->opp_status_code) {
2532
            //$return .= '<br><span class="info-bo-label opacitymedium">'.$langs->trans("OpportunityStatusShort").'</span>';
2533
            //$return .= '<div class="small inline-block">'.dol_trunc($langs->trans("OppStatus".$this->opp_status_code), 5).'</div>';
2534
            $return .= '<div class="opacitymedium small marginrightonly inline-block" title="' . dol_escape_htmltag($langs->trans("OppStatus" . $this->opp_status_code)) . '">' . round($this->opp_percent) . '%</div>';
2535
            $return .= ' <div class="amount small marginrightonly inline-block">' . price($this->opp_amount) . '</div>';
2536
        }
2537
        if (method_exists($this, 'getLibStatut')) {
2538
            $return .= '<div class="info-box-status small inline-block valignmiddle">' . $this->getLibStatut(3) . '</div>';
2539
        }
2540
        $return .= '</div>';    // end div line status
2541
2542
        $return .= '</div>';
2543
        $return .= '</div>';
2544
        $return .= '</div>';
2545
2546
        return $return;
2547
    }
2548
2549
    /**
2550
     *  Return array of sub-projects of the current project
2551
     *
2552
     *  @return     array       Children of this project as objects with rowid & title as members
2553
     */
2554
    public function getChildren()
2555
    {
2556
        $children = [];
2557
        $sql = 'SELECT rowid,title';
2558
        $sql .= ' FROM ' . MAIN_DB_PREFIX . 'projet';
2559
        $sql .= ' WHERE fk_project = ' . ((int) $this->id);
2560
        $sql .= ' ORDER BY title';
2561
        $result = $this->db->query($sql);
2562
        if ($result) {
2563
            $n = $this->db->num_rows($result);
2564
            while ($n) {
2565
                $children[] = $this->db->fetch_object($result);
2566
                $n--;
2567
            }
2568
            $this->db->free($result);
2569
        } else {
2570
            dol_print_error($this->db);
2571
        }
2572
2573
        return $children;
2574
    }
2575
2576
    /**
2577
     * Method for calculating weekly hours worked and generating a report
2578
     * @return int   0 if OK, <>0 if KO (this function is used also by cron so only 0 is OK)
2579
     */
2580
    public function createWeeklyReport()
2581
    {
2582
        global $mysoc, $user;
2583
2584
        $now = dol_now();
2585
        $nowDate = dol_getdate($now, true);
2586
2587
        $errormesg = '';
2588
        $errorsMsg = array();
2589
2590
        $firstDayOfWeekTS = dol_get_first_day_week($nowDate['mday'], $nowDate['mon'], $nowDate['year']);
2591
2592
        $firstDayOfWeekDate = dol_mktime(0, 0, 0, $nowDate['mon'], $firstDayOfWeekTS['first_day'], $nowDate['year']);
2593
2594
        $lastWeekStartTS = dol_time_plus_duree($firstDayOfWeekDate, -7, 'd');
2595
2596
        $lastWeekEndTS = dol_time_plus_duree($lastWeekStartTS, 6, 'd');
2597
2598
        $startDate = dol_print_date($lastWeekStartTS, '%Y-%m-%d 00:00:00');
2599
        $endDate = dol_print_date($lastWeekEndTS, '%Y-%m-%d 23:59:59');
2600
2601
        $sql = "SELECT
2602
		u.rowid AS user_id,
2603
		CONCAT(u.firstname, ' ', u.lastname) AS name,
2604
		u.email,u.weeklyhours,
2605
		SUM(et.element_duration) AS total_seconds
2606
		FROM
2607
			" . MAIN_DB_PREFIX . "element_time AS et
2608
		JOIN
2609
			" . MAIN_DB_PREFIX . "user AS u ON et.fk_user = u.rowid
2610
		WHERE
2611
			et.element_date BETWEEN '" . $this->db->escape($startDate) . "' AND '" . $this->db->escape($endDate) . "'
2612
			AND et.elementtype = 'task'
2613
		GROUP BY
2614
			et.fk_user";
2615
2616
        $resql = $this->db->query($sql);
2617
        if (!$resql) {
2618
            dol_print_error($this->db);
2619
            return -1;
2620
        } else {
2621
            $reportContent = "<span>Weekly time report from $startDate to $endDate </span><br><br>";
2622
            $reportContent .= '<table border="1" style="border-collapse: collapse;">';
2623
            $reportContent .= '<tr><th>Nom d\'utilisateur</th><th>Temps saisi (heures)</th><th>Temps travaillé par semaine (heures)</th></tr>';
2624
2625
            $weekendEnabled = 0;
2626
            $to = '';
2627
            $nbMailSend = 0;
2628
            $error = 0;
2629
            $errors_to = '';
2630
            while ($obj = $this->db->fetch_object($resql)) {
2631
                $to = $obj->email;
2632
                $numHolidays = num_public_holiday($lastWeekStartTS, $lastWeekEndTS, $mysoc->country_code, 1);
2633
                if (getDolGlobalString('MAIN_NON_WORKING_DAYS_INCLUDE_SATURDAY') && getDolGlobalString('MAIN_NON_WORKING_DAYS_INCLUDE_SUNDAY')) {
2634
                    $numHolidays = $numHolidays - 2;
2635
                    $weekendEnabled = 2;
2636
                }
2637
2638
                $dailyHours = $obj->weeklyhours / (7 - $weekendEnabled);
2639
2640
                // Adjust total on seconde
2641
                $adjustedSeconds = $obj->total_seconds + ($numHolidays * $dailyHours * 3600);
2642
2643
                $totalHours = round($adjustedSeconds / 3600, 2);
2644
2645
                $reportContent .= "<tr><td>{$obj->name}</td><td>{$totalHours}</td><td>" . round($obj->weeklyhours, 2) . "</td></tr>";
2646
2647
                $reportContent .= '</table>';
2648
2649
                require_once constant('DOL_DOCUMENT_ROOT') . '/core/class/CMailFile.class.php';
2650
2651
                // PREPARE EMAIL
2652
                $errormesg = '';
2653
2654
                $subject = 'Rapport hebdomadaire des temps travaillés';
2655
2656
                $from = getDolGlobalString('MAIN_MAIL_EMAIL_FROM');
2657
                if (empty($from)) {
2658
                    $errormesg = "Failed to get sender into global setup MAIN_MAIL_EMAIL_FROM";
2659
                    $error++;
2660
                }
2661
2662
                $mail = new CMailFile($subject, $to, $from, $reportContent, array(), array(), array(), '', '', 0, -1, '', '', 0, 'text/html');
2663
2664
                if ($mail->sendfile()) {
2665
                    $nbMailSend++;
2666
2667
                    // Add a line into event table
2668
                    require_once constant('DOL_DOCUMENT_ROOT') . '/comm/action/class/actioncomm.class.php';
2669
2670
                    // Insert record of emails sent
2671
                    $actioncomm = new ActionComm($this->db);
2672
2673
                    $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
2674
                    $actioncomm->socid = $this->thirdparty->id; // To link to a company
2675
                    $actioncomm->contact_id = 0;
2676
2677
                    $actioncomm->code = 'AC_EMAIL';
2678
                    $actioncomm->label = 'createWeeklyReportOK()';
2679
                    $actioncomm->fk_project = $this->id;
2680
                    $actioncomm->datep = dol_now();
2681
                    $actioncomm->datef = $actioncomm->datep;
2682
                    $actioncomm->percentage = -1; // Not applicable
2683
                    $actioncomm->authorid = $user->id; // User saving action
2684
                    $actioncomm->userownerid = $user->id; // Owner of action
2685
                    // Fields when action is an email (content should be added into note)
2686
                    $actioncomm->email_msgid = $mail->msgid;
2687
                    $actioncomm->email_subject = $subject;
2688
                    $actioncomm->email_from = $from;
2689
                    $actioncomm->email_sender = '';
2690
                    $actioncomm->email_to = $to;
2691
2692
                    $actioncomm->errors_to = $errors_to;
2693
2694
                    $actioncomm->elementtype = 'project_task';
2695
                    $actioncomm->fk_element = (int) $this->element;
2696
2697
                    $actioncomm->create($user);
2698
                } else {
2699
                    $errormesg = $mail->error . ' : ' . $to;
2700
                    $error++;
2701
2702
                    // Add a line into event table
2703
                    require_once constant('DOL_DOCUMENT_ROOT') . '/comm/action/class/actioncomm.class.php';
2704
2705
                    // Insert record of emails sent
2706
                    $actioncomm = new ActionComm($this->db);
2707
2708
                    $actioncomm->type_code = 'AC_OTH_AUTO'; // Event insert into agenda automatically
2709
                    $actioncomm->socid = $this->thirdparty->id; // To link to a company
2710
                    $actioncomm->contact_id = 0;
2711
2712
                    $actioncomm->code = 'AC_EMAIL';
2713
                    $actioncomm->label = 'createWeeklyReportKO()';
2714
                    $actioncomm->note_private = $errormesg;
2715
                    $actioncomm->fk_project = $this->id;
2716
                    $actioncomm->datep = dol_now();
2717
                    $actioncomm->datef = $actioncomm->datep;
2718
                    $actioncomm->authorid = $user->id; // User saving action
2719
                    $actioncomm->userownerid = $user->id; // Owner of action
2720
                    // Fields when action is an email (content should be added into note)
2721
                    $actioncomm->email_msgid = $mail->msgid;
2722
                    $actioncomm->email_from = $from;
2723
                    $actioncomm->email_sender = '';
2724
                    $actioncomm->email_to = $to;
2725
2726
                    $actioncomm->errors_to = $errors_to;
2727
2728
                    $actioncomm->elementtype = 'project_task';
2729
                    $actioncomm->fk_element = (int) $this->element;
2730
2731
                    $actioncomm->create($user);
2732
                }
2733
                $this->db->commit();
2734
            }
2735
        }
2736
        if (!empty($errormesg)) {
2737
            $errorsMsg[] = $errormesg;
2738
        }
2739
2740
        if (!$error) {
2741
            $this->output .= 'Nb of emails sent : ' . $nbMailSend;
2742
            dol_syslog(__METHOD__ . " end - " . $this->output, LOG_INFO);
2743
            return 0;
2744
        } else {
2745
            $this->error = 'Nb of emails sent : ' . $nbMailSend . ', ' . (empty($errorsMsg) ? $error : implode(', ', $errorsMsg));
2746
            dol_syslog(__METHOD__ . " end - " . $this->error, LOG_INFO);
2747
            return $error;
2748
        }
2749
    }
2750
}
2751