Project   F
last analyzed

Complexity

Total Complexity 430

Size/Duplication

Total Lines 2698
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 1400
dl 0
loc 2698
rs 0.8
c 0
b 0
f 0
wmc 430

36 Methods

Rating   Name   Duplication   Size   Complexity  
F getKanbanView() 0 84 20
A sendEmail() 0 6 1
A getChildren() 0 20 3
A replaceThirdparty() 0 7 1
A remove_element() 0 20 3
B loadTimeSpentMonth() 0 54 8
A update_element() 0 23 4
B load_board() 0 67 6
B loadTimeSpent() 0 50 7
A setCategories() 0 3 1
A loadStateBoard() 0 26 4
C shiftTaskDate() 0 54 11
A generateDocument() 0 19 4
A hasDelay() 0 14 3
A info() 0 25 3
A __construct() 0 37 5
F update() 0 123 51
B getElementCount() 0 33 9
B setValid() 0 51 7
B fetchAndSetSubstitution() 0 22 8
F create() 0 134 41
A getLibStatut() 0 3 2
B deleteTasks() 0 24 8
F getNomUrl() 0 99 32
D restrictedProjectArea() 0 50 34
F delete() 0 150 27
A initAsSpecimen() 0 43 1
B setClose() 0 46 6
F get_element_list() 0 98 28
A LibStatut() 0 21 3
C fetch() 0 91 11
F getProjectsAuthorizedForUser() 0 81 17
D getTooltipContentArray() 0 35 10
F createFromClone() 0 214 40
C createWeeklyReport() 0 165 10
A getLinesArray() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like Project often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Project, and based on these observations, apply Extract Interface, too.

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