Passed
Push — dev ( f7d146...05f415 )
by Rafael
60:50
created

Project   F

Complexity

Total Complexity 430

Size/Duplication

Total Lines 2699
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 1400
dl 0
loc 2699
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
F createFromClone() 0 215 40
A remove_element() 0 20 3
B loadTimeSpentMonth() 0 54 8
A __construct() 0 37 5
A update_element() 0 23 4
C createWeeklyReport() 0 165 10
B load_board() 0 67 6
F update() 0 123 51
B getElementCount() 0 33 9
B loadTimeSpent() 0 50 7
A setCategories() 0 3 1
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
A loadStateBoard() 0 26 4
C shiftTaskDate() 0 54 11
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
A generateDocument() 0 19 4
A hasDelay() 0 14 3
F getProjectsAuthorizedForUser() 0 81 17
A getLinesArray() 0 6 1
D getTooltipContentArray() 0 35 10
A info() 0 25 3

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