Passed
Pull Request — dev (#6)
by Rafael
79:24 queued 24:08
created

Task::create()   F

Complexity

Conditions 25
Paths > 20000

Size

Total Lines 97
Code Lines 70

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 25
eloc 70
nc 393217
nop 2
dl 0
loc 97
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/* Copyright (C) 2008-2014  Laurent Destailleur         <[email protected]>
4
 * Copyright (C) 2010-2012	Regis Houssin		        <[email protected]>
5
 * Copyright (C) 2014       Marcos García               <[email protected]>
6
 * Copyright (C) 2018-2024  Frédéric France             <[email protected]>
7
 * Copyright (C) 2020       Juanjo Menent		        <[email protected]>
8
 * Copyright (C) 2022       Charlene Benke		        <[email protected]>
9
 * Copyright (C) 2023      	Gauthier VERDOL             <[email protected]>
10
 * Copyright (C) 2024		MDW							<[email protected]>
11
 * Copyright (C) 2024       Rafael San José             <[email protected]>
12
 *
13
 * This program is free software; you can redistribute it and/or modify
14
 * it under the terms of the GNU General Public License as published by
15
 * the Free Software Foundation; either version 3 of the License, or
16
 * (at your option) any later version.
17
 *
18
 * This program is distributed in the hope that it will be useful,
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21
 * GNU General Public License for more details.
22
 *
23
 * You should have received a copy of the GNU General Public License
24
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
25
 */
26
27
namespace Dolibarr\Code\Projet\Classes;
28
29
use Dolibarr\Code\Core\Classes\WorkboardResponse;
30
use Dolibarr\Code\User\Classes\User;
31
use Dolibarr\Core\Base\CommonObjectLine;
32
33
/**
34
 *      \file       htdocs/projet/class/task.class.php
35
 *      \ingroup    project
36
 *      \brief      This file is a CRUD class file for Task (Create/Read/Update/Delete)
37
 */
38
39
/**
40
 *  Class to manage tasks
41
 */
42
class Task extends CommonObjectLine
43
{
44
    /**
45
     * @var string ID to identify managed object
46
     */
47
    public $element = 'project_task';
48
49
    /**
50
     * @var string  Name of table without prefix where object is stored
51
     */
52
    public $table_element = 'projet_task';
53
54
    /**
55
     * @var string Field with ID of parent key if this field has a parent
56
     */
57
    public $fk_element = 'fk_element';
58
59
    /**
60
     * @var string String with name of icon for myobject.
61
     */
62
    public $picto = 'projecttask';
63
64
    /**
65
     * @var array<string, array<string>>    List of child tables. To test if we can delete object.
66
     */
67
    protected $childtables = array(
68
        'element_time' => array('name' => 'Task', 'parent' => 'projet_task', 'parentkey' => 'fk_element', 'parenttypefield' => 'elementtype', 'parenttypevalue' => 'task')
69
    );
70
71
    /**
72
     * @var int ID parent task
73
     */
74
    public $fk_task_parent = 0;
75
76
    /**
77
     * @var string Label of task
78
     */
79
    public $label;
80
81
    /**
82
     * @var string description
83
     */
84
    public $description;
85
86
    public $duration_effective; // total of time spent on this task
87
    public $planned_workload;
88
    public $date_c;
89
    public $progress;
90
91
    /**
92
     * @deprecated Use date_start instead
93
     */
94
    public $dateo;
95
96
    public $date_start;
97
98
    /**
99
     * @deprecated Use date_end instead
100
     */
101
    public $datee;
102
103
    public $date_end;
104
105
    /**
106
     * @var int ID
107
     * @deprecated use status instead
108
     */
109
    public $fk_statut;
110
111
    /**
112
     * @var int ID
113
     */
114
    public $status;
115
116
    public $priority;
117
118
    /**
119
     * @var int ID
120
     */
121
    public $fk_user_creat;
122
123
    /**
124
     * @var int ID
125
     */
126
    public $fk_user_valid;
127
128
    public $rang;
129
130
    public $timespent_min_date;
131
    public $timespent_max_date;
132
    public $timespent_total_duration;
133
    public $timespent_total_amount;
134
    public $timespent_nblinesnull;
135
    public $timespent_nblines;
136
    // For detail of lines of timespent record, there is the property ->lines in common
137
138
    // Var used to call method addTimeSpent(). Bad practice.
139
    public $timespent_id;
140
    public $timespent_duration;
141
    public $timespent_old_duration;
142
    public $timespent_date;
143
    public $timespent_datehour; // More accurate start date (same than timespent_date but includes hours, minutes and seconds)
144
    public $timespent_withhour; // 1 = we entered also start hours for timesheet line
145
    public $timespent_fk_user;
146
    public $timespent_thm;
147
    public $timespent_note;
148
    public $timespent_fk_product;
149
    public $timespent_invoiceid;
150
    public $timespent_invoicelineid;
151
152
    public $comments = array();
153
154
    // Properties calculated from sum of llx_element_time linked to task
155
    public $tobill;
156
    public $billed;
157
158
    // Properties to store project information
159
    public $projectref;
160
    public $projectstatus;
161
    public $projectlabel;
162
    public $opp_amount;
163
    public $opp_percent;
164
    public $fk_opp_status;
165
    public $usage_bill_time;
166
    public $public;
167
    public $array_options_project;
168
169
    // Properties to store thirdparty of project information
170
    public $socid;
171
    public $thirdparty_id;
172
    public $thirdparty_name;
173
    public $thirdparty_email;
174
175
    // store parent ref and position
176
    public $task_parent_ref;
177
    public $task_parent_position;
178
179
180
181
    /**
182
     * @var float budget_amount
183
     */
184
    public $budget_amount;
185
186
    /**
187
     * @var float project_budget_amount
188
     */
189
    public $project_budget_amount;
190
191
    /**
192
     * Draft status
193
     */
194
    const STATUS_DRAFT = 0;
195
196
    /**
197
     * Validated status (To do). Note: We also have the field progress to know the progression from 0 to 100%.
198
     */
199
    const STATUS_VALIDATED = 1;
200
201
    /**
202
     * Finished status
203
     */
204
    const STATUS_CLOSED = 3;
205
206
    /**
207
     * Transferred status
208
     */
209
    const STATUS_TRANSFERRED = 4;
210
211
    /**
212
     * status canceled
213
     */
214
    const STATUS_CANCELED = 9;
215
216
217
    /**
218
     *  Constructor
219
     *
220
     *  @param      DoliDB      $db      Database handler
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Projet\Classes\DoliDB was not found. Did you mean DoliDB? If so, make sure to prefix the type with \.
Loading history...
221
     */
222
    public function __construct($db)
223
    {
224
        $this->db = $db;
225
    }
226
227
228
    /**
229
     *  Create into database
230
     *
231
     *  @param  User    $user           User that create
232
     *  @param  int     $notrigger      0=launch triggers after, 1=disable triggers
233
     *  @return int                     Return integer <0 if KO, Id of created object if OK
234
     */
235
    public function create($user, $notrigger = 0)
236
    {
237
        global $conf, $langs;
238
239
        //For the date
240
        $now = dol_now();
241
242
        $error = 0;
243
244
        // Clean parameters
245
        $this->label = trim($this->label);
246
        $this->description = trim($this->description);
247
        $this->note_public = trim($this->note_public);
248
        $this->note_private = trim($this->note_private);
249
250
        if (!empty($this->date_start) && !empty($this->date_end) && $this->date_start > $this->date_end) {
251
            $this->errors[] = $langs->trans('StartDateCannotBeAfterEndDate');
252
            return -1;
253
        }
254
255
        // Insert request
256
        $sql = "INSERT INTO " . MAIN_DB_PREFIX . "projet_task (";
257
        $sql .= "entity";
258
        $sql .= ", fk_projet";
259
        $sql .= ", ref";
260
        $sql .= ", fk_task_parent";
261
        $sql .= ", label";
262
        $sql .= ", description";
263
        $sql .= ", note_public";
264
        $sql .= ", note_private";
265
        $sql .= ", datec";
266
        $sql .= ", fk_user_creat";
267
        $sql .= ", dateo";
268
        $sql .= ", datee";
269
        $sql .= ", planned_workload";
270
        $sql .= ", progress";
271
        $sql .= ", budget_amount";
272
        $sql .= ", priority";
273
        $sql .= ") VALUES (";
274
        $sql .= (!empty($this->entity) ? (int) $this->entity : (int) $conf->entity);
275
        $sql .= ", " . ((int) $this->fk_project);
276
        $sql .= ", " . (!empty($this->ref) ? "'" . $this->db->escape($this->ref) . "'" : 'null');
277
        $sql .= ", " . ((int) $this->fk_task_parent);
278
        $sql .= ", '" . $this->db->escape($this->label) . "'";
279
        $sql .= ", '" . $this->db->escape($this->description) . "'";
280
        $sql .= ", '" . $this->db->escape($this->note_public) . "'";
281
        $sql .= ", '" . $this->db->escape($this->note_private) . "'";
282
        $sql .= ", '" . $this->db->idate($now) . "'";
283
        $sql .= ", " . ((int) $user->id);
284
        $sql .= ", " . ($this->date_start ? "'" . $this->db->idate($this->date_start) . "'" : 'null');
285
        $sql .= ", " . ($this->date_end ? "'" . $this->db->idate($this->date_end) . "'" : 'null');
286
        $sql .= ", " . (($this->planned_workload != '' && $this->planned_workload >= 0) ? ((int) $this->planned_workload) : 'null');
287
        $sql .= ", " . (($this->progress != '' && $this->progress >= 0) ? ((int) $this->progress) : 'null');
288
        $sql .= ", " . (($this->budget_amount != '' && $this->budget_amount >= 0) ? ((int) $this->budget_amount) : 'null');
289
        $sql .= ", " . (($this->priority != '' && $this->priority >= 0) ? (int) $this->priority : 'null');
290
        $sql .= ")";
291
292
        $this->db->begin();
293
294
        dol_syslog(get_class($this) . "::create", LOG_DEBUG);
295
        $resql = $this->db->query($sql);
296
        if (!$resql) {
297
            $error++;
298
            $this->errors[] = "Error " . $this->db->lasterror();
299
        }
300
301
        if (!$error) {
302
            $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX . "projet_task");
303
            // Update extrafield
304
            $result = $this->insertExtraFields();
305
            if ($result < 0) {
306
                $error++;
307
            }
308
        }
309
310
        if (!$error) {
311
            if (!$notrigger) {
312
                // Call trigger
313
                $result = $this->call_trigger('TASK_CREATE', $user);
314
                if ($result < 0) {
315
                    $error++;
316
                }
317
                // End call triggers
318
            }
319
        }
320
321
        // Commit or rollback
322
        if ($error) {
323
            foreach ($this->errors as $errmsg) {
324
                dol_syslog(get_class($this) . "::create " . $errmsg, LOG_ERR);
325
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
326
            }
327
            $this->db->rollback();
328
            return -1 * $error;
329
        } else {
330
            $this->db->commit();
331
            return $this->id;
332
        }
333
    }
334
335
336
    /**
337
     *  Load object in memory from database
338
     *
339
     *  @param  int     $id                 Id object
340
     *  @param  string  $ref                ref object
341
     *  @param  int     $loadparentdata     Also load parent data
342
     *  @return int                         Return integer <0 if KO, 0 if not found, >0 if OK
343
     */
344
    public function fetch($id, $ref = '', $loadparentdata = 0)
345
    {
346
        $sql = "SELECT";
347
        $sql .= " t.rowid,";
348
        $sql .= " t.ref,";
349
        $sql .= " t.entity,";
350
        $sql .= " t.fk_projet as fk_project,";
351
        $sql .= " t.fk_task_parent,";
352
        $sql .= " t.label,";
353
        $sql .= " t.description,";
354
        $sql .= " t.duration_effective,";
355
        $sql .= " t.planned_workload,";
356
        $sql .= " t.datec,";
357
        $sql .= " t.dateo as date_start,";
358
        $sql .= " t.datee as date_end,";
359
        $sql .= " t.fk_user_creat,";
360
        $sql .= " t.fk_user_valid,";
361
        $sql .= " t.fk_statut as status,";
362
        $sql .= " t.progress,";
363
        $sql .= " t.budget_amount,";
364
        $sql .= " t.priority,";
365
        $sql .= " t.note_private,";
366
        $sql .= " t.note_public,";
367
        $sql .= " t.rang";
368
        if (!empty($loadparentdata)) {
369
            $sql .= ", t2.ref as task_parent_ref";
370
            $sql .= ", t2.rang as task_parent_position";
371
        }
372
        $sql .= " FROM " . MAIN_DB_PREFIX . "projet_task as t";
373
        if (!empty($loadparentdata)) {
374
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "projet_task as t2 ON t.fk_task_parent = t2.rowid";
375
        }
376
        $sql .= " WHERE ";
377
        if (!empty($ref)) {
378
            $sql .= "entity IN (" . getEntity('project') . ")";
379
            $sql .= " AND t.ref = '" . $this->db->escape($ref) . "'";
380
        } else {
381
            $sql .= "t.rowid = " . ((int) $id);
382
        }
383
384
        dol_syslog(get_class($this) . "::fetch", LOG_DEBUG);
385
        $resql = $this->db->query($sql);
386
        if ($resql) {
387
            $num_rows = $this->db->num_rows($resql);
388
389
            if ($num_rows) {
390
                $obj = $this->db->fetch_object($resql);
391
392
                $this->id = $obj->rowid;
393
                $this->ref = $obj->ref;
394
                $this->entity = $obj->entity;
395
                $this->fk_project = $obj->fk_project;
396
                $this->fk_task_parent = $obj->fk_task_parent;
397
                $this->label = $obj->label;
398
                $this->description = $obj->description;
399
                $this->duration_effective = $obj->duration_effective;
400
                $this->planned_workload = $obj->planned_workload;
401
                $this->date_c = $this->db->jdate($obj->datec);
402
                $this->date_start = $this->db->jdate($obj->date_start);
403
                $this->date_end             = $this->db->jdate($obj->date_end);
404
                $this->fk_user_creat        = $obj->fk_user_creat;
405
                $this->fk_user_valid        = $obj->fk_user_valid;
406
                $this->fk_statut            = $obj->status;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Projet\Classes\Task::$fk_statut has been deprecated: use status instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

406
                /** @scrutinizer ignore-deprecated */ $this->fk_statut            = $obj->status;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
407
                $this->status               = $obj->status;
408
                $this->progress             = $obj->progress;
409
                $this->budget_amount        = $obj->budget_amount;
410
                $this->priority             = $obj->priority;
411
                $this->note_private = $obj->note_private;
412
                $this->note_public = $obj->note_public;
413
                $this->rang = $obj->rang;
414
415
                if (!empty($loadparentdata)) {
416
                    $this->task_parent_ref      = $obj->task_parent_ref;
417
                    $this->task_parent_position = $obj->task_parent_position;
418
                }
419
420
                // Retrieve all extrafield
421
                $this->fetch_optionals();
422
            }
423
424
            $this->db->free($resql);
425
426
            if ($num_rows) {
427
                return 1;
428
            } else {
429
                return 0;
430
            }
431
        } else {
432
            $this->error = "Error " . $this->db->lasterror();
433
            return -1;
434
        }
435
    }
436
437
438
    /**
439
     *  Update database
440
     *
441
     *  @param  User    $user           User that modify
442
     *  @param  int     $notrigger      0=launch triggers after, 1=disable triggers
443
     *  @return int                     Return integer <=0 if KO, >0 if OK
444
     */
445
    public function update($user = null, $notrigger = 0)
446
    {
447
        global $conf, $langs;
448
        $error = 0;
449
450
        // Clean parameters
451
        if (isset($this->fk_project)) {
452
            $this->fk_project = (int) $this->fk_project;
453
        }
454
        if (isset($this->ref)) {
455
            $this->ref = trim($this->ref);
456
        }
457
        if (isset($this->fk_task_parent)) {
458
            $this->fk_task_parent = (int) $this->fk_task_parent;
459
        }
460
        if (isset($this->label)) {
461
            $this->label = trim($this->label);
462
        }
463
        if (isset($this->description)) {
464
            $this->description = trim($this->description);
465
        }
466
        if (isset($this->note_public)) {
467
            $this->note_public = trim($this->note_public);
468
        }
469
        if (isset($this->note_private)) {
470
            $this->note_private = trim($this->note_private);
471
        }
472
        if (isset($this->duration_effective)) {
473
            $this->duration_effective = trim($this->duration_effective);
474
        }
475
        if (isset($this->planned_workload)) {
476
            $this->planned_workload = trim($this->planned_workload);
477
        }
478
        if (isset($this->budget_amount)) {
479
            $this->budget_amount = (float) $this->budget_amount;
480
        }
481
482
        if (!empty($this->date_start) && !empty($this->date_end) && $this->date_start > $this->date_end) {
483
            $this->errors[] = $langs->trans('StartDateCannotBeAfterEndDate');
484
            return -1;
485
        }
486
487
        // Check parameters
488
        // Put here code to add control on parameters values
489
490
        // Update request
491
        $sql = "UPDATE " . MAIN_DB_PREFIX . "projet_task SET";
492
        $sql .= " fk_projet=" . (isset($this->fk_project) ? $this->fk_project : "null") . ",";
493
        $sql .= " ref=" . (isset($this->ref) ? "'" . $this->db->escape($this->ref) . "'" : "'" . $this->db->escape($this->id) . "'") . ",";
494
        $sql .= " fk_task_parent=" . (isset($this->fk_task_parent) ? $this->fk_task_parent : "null") . ",";
495
        $sql .= " label=" . (isset($this->label) ? "'" . $this->db->escape($this->label) . "'" : "null") . ",";
496
        $sql .= " description=" . (isset($this->description) ? "'" . $this->db->escape($this->description) . "'" : "null") . ",";
497
        $sql .= " note_public=" . (isset($this->note_public) ? "'" . $this->db->escape($this->note_public) . "'" : "null") . ",";
498
        $sql .= " note_private=" . (isset($this->note_private) ? "'" . $this->db->escape($this->note_private) . "'" : "null") . ",";
499
        $sql .= " duration_effective=" . (isset($this->duration_effective) ? $this->duration_effective : "null") . ",";
500
        $sql .= " planned_workload=" . ((isset($this->planned_workload) && $this->planned_workload != '') ? $this->planned_workload : "null") . ",";
501
        $sql .= " dateo=" . ($this->date_start != '' ? "'" . $this->db->idate($this->date_start) . "'" : 'null') . ",";
502
        $sql .= " datee=" . ($this->date_end != '' ? "'" . $this->db->idate($this->date_end) . "'" : 'null') . ",";
503
        $sql .= " progress=" . (($this->progress != '' && $this->progress >= 0) ? $this->progress : 'null') . ",";
504
        $sql .= " budget_amount=" . (($this->budget_amount != '' && $this->budget_amount >= 0) ? $this->budget_amount : 'null') . ",";
505
        $sql .= " rang=" . ((!empty($this->rang)) ? ((int) $this->rang) : "0") . ",";
506
        $sql .= " priority=" . ((!empty($this->priority)) ? ((int) $this->priority) : "0");
507
        $sql .= " WHERE rowid=" . ((int) $this->id);
508
509
        $this->db->begin();
510
511
        dol_syslog(get_class($this) . "::update", LOG_DEBUG);
512
        $resql = $this->db->query($sql);
513
        if (!$resql) {
514
            $error++;
515
            $this->errors[] = "Error " . $this->db->lasterror();
516
        }
517
518
        // Update extrafield
519
        if (!$error) {
520
            $result = $this->insertExtraFields();
521
            if ($result < 0) {
522
                $error++;
523
            }
524
        }
525
526
        if (!$error && getDolGlobalString('PROJECT_CLASSIFY_CLOSED_WHEN_ALL_TASKS_DONE')) {
527
            // Close the parent project if it is open (validated) and its tasks are 100% completed
528
            $project = new Project($this->db);
529
            if ($project->fetch($this->fk_project) > 0) {
530
                if ($project->statut == Project::STATUS_VALIDATED) {
531
                    $project->getLinesArray(null); // this method does not return <= 0 if fails
532
                    $projectCompleted = array_reduce(
533
                        $project->lines,
534
                        /**
535
                         * @param bool $allTasksCompleted
536
                         * @param Task $task
537
                         * @return bool
538
                         */
539
                        static function ($allTasksCompleted, $task) {
540
                            return $allTasksCompleted && $task->progress >= 100;
541
                        },
542
                        1
543
                    );
544
                    if ($projectCompleted) {
545
                        if ($project->setClose($user) <= 0) {
546
                            $error++;
547
                        }
548
                    }
549
                }
550
            } else {
551
                $error++;
552
            }
553
            if ($error) {
554
                $this->errors[] = $project->error;
555
            }
556
        }
557
558
        if (!$error) {
559
            if (!$notrigger) {
560
                // Call trigger
561
                $result = $this->call_trigger('TASK_MODIFY', $user);
562
                if ($result < 0) {
563
                    $error++;
564
                }
565
                // End call triggers
566
            }
567
        }
568
569
        if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
570
            // We remove directory
571
            if ($conf->project->dir_output) {
572
                $project = new Project($this->db);
573
                $project->fetch($this->fk_project);
574
575
                $olddir = $conf->project->dir_output . '/' . dol_sanitizeFileName($project->ref) . '/' . dol_sanitizeFileName($this->oldcopy->ref);
576
                $newdir = $conf->project->dir_output . '/' . dol_sanitizeFileName($project->ref) . '/' . dol_sanitizeFileName($this->ref);
577
                if (file_exists($olddir)) {
578
                    include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
579
                    $res = dol_move_dir($olddir, $newdir);
580
                    if (!$res) {
581
                        $langs->load("errors");
582
                        $this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
583
                        $error++;
584
                    }
585
                }
586
            }
587
        }
588
589
        // Commit or rollback
590
        if ($error) {
591
            foreach ($this->errors as $errmsg) {
592
                dol_syslog(get_class($this) . "::update " . $errmsg, LOG_ERR);
593
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
594
            }
595
            $this->db->rollback();
596
            return -1 * $error;
597
        } else {
598
            $this->db->commit();
599
            return 1;
600
        }
601
    }
602
603
604
    /**
605
     *  Delete task from database
606
     *
607
     *  @param  User    $user           User that delete
608
     *  @param  int     $notrigger      0=launch triggers after, 1=disable triggers
609
     *  @return int                     Return integer <0 if KO, >0 if OK
610
     */
611
    public function delete($user, $notrigger = 0)
612
    {
613
        global $conf;
614
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
615
616
        $error = 0;
617
618
        $this->db->begin();
619
620
        if ($this->hasChildren() > 0) {
621
            dol_syslog(get_class($this) . "::delete Can't delete record as it has some sub tasks", LOG_WARNING);
622
            $this->error = 'ErrorRecordHasSubTasks';
623
            $this->db->rollback();
624
            return 0;
625
        }
626
627
        $objectisused = $this->isObjectUsed($this->id);
628
        if (!empty($objectisused)) {
629
            dol_syslog(get_class($this) . "::delete Can't delete record as it has some child", LOG_WARNING);
630
            $this->error = 'ErrorRecordHasChildren';
631
            $this->db->rollback();
632
            return 0;
633
        }
634
635
        if (!$error) {
636
            // Delete linked contacts
637
            $res = $this->delete_linked_contact();
638
            if ($res < 0) {
639
                $this->error = 'ErrorFailToDeleteLinkedContact';
640
                //$error++;
641
                $this->db->rollback();
642
                return 0;
643
            }
644
        }
645
646
        if (!$error) {
647
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . "element_time";
648
            $sql .= " WHERE fk_element = " . ((int) $this->id) . " AND elementtype = 'task'";
649
650
            $resql = $this->db->query($sql);
651
            if (!$resql) {
652
                $error++;
653
                $this->errors[] = "Error " . $this->db->lasterror();
654
            }
655
        }
656
657
        if (!$error) {
658
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . "projet_task_extrafields";
659
            $sql .= " WHERE fk_object = " . ((int) $this->id);
660
661
            $resql = $this->db->query($sql);
662
            if (!$resql) {
663
                $error++;
664
                $this->errors[] = "Error " . $this->db->lasterror();
665
            }
666
        }
667
668
        if (!$error) {
669
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . "projet_task";
670
            $sql .= " WHERE rowid=" . ((int) $this->id);
671
672
            $resql = $this->db->query($sql);
673
            if (!$resql) {
674
                $error++;
675
                $this->errors[] = "Error " . $this->db->lasterror();
676
            }
677
        }
678
679
        if (!$error) {
680
            if (!$notrigger) {
681
                // Call trigger
682
                $result = $this->call_trigger('TASK_DELETE', $user);
683
                if ($result < 0) {
684
                    $error++;
685
                }
686
                // End call triggers
687
            }
688
        }
689
690
        // Commit or rollback
691
        if ($error) {
692
            foreach ($this->errors as $errmsg) {
693
                dol_syslog(get_class($this) . "::delete " . $errmsg, LOG_ERR);
694
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
695
            }
696
            $this->db->rollback();
697
            return -1 * $error;
698
        } else {
699
            //Delete associated link file
700
            if ($conf->project->dir_output) {
701
                $projectstatic = new Project($this->db);
702
                $projectstatic->fetch($this->fk_project);
703
704
                $dir = $conf->project->dir_output . "/" . dol_sanitizeFileName($projectstatic->ref) . '/' . dol_sanitizeFileName($this->id);
705
                dol_syslog(get_class($this) . "::delete dir=" . $dir, LOG_DEBUG);
706
                if (file_exists($dir)) {
707
                    require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
708
                    $res = @dol_delete_dir_recursive($dir);
709
                    if (!$res) {
710
                        $this->error = 'ErrorFailToDeleteDir';
711
                        $this->db->rollback();
712
                        return 0;
713
                    }
714
                }
715
            }
716
717
            $this->db->commit();
718
719
            return 1;
720
        }
721
    }
722
723
    /**
724
     *  Return nb of children
725
     *
726
     *  @return int     Return integer <0 if KO, 0 if no children, >0 if OK
727
     */
728
    public function hasChildren()
729
    {
730
        $error = 0;
731
        $ret = 0;
732
733
        $sql = "SELECT COUNT(*) as nb";
734
        $sql .= " FROM " . MAIN_DB_PREFIX . "projet_task";
735
        $sql .= " WHERE fk_task_parent = " . ((int) $this->id);
736
737
        dol_syslog(get_class($this) . "::hasChildren", LOG_DEBUG);
738
        $resql = $this->db->query($sql);
739
        if (!$resql) {
740
            $error++;
741
            $this->errors[] = "Error " . $this->db->lasterror();
742
        } else {
743
            $obj = $this->db->fetch_object($resql);
744
            if ($obj) {
745
                $ret = $obj->nb;
746
            }
747
            $this->db->free($resql);
748
        }
749
750
        if (!$error) {
751
            return $ret;
752
        } else {
753
            return -1;
754
        }
755
    }
756
757
    /**
758
     *  Return nb of time spent
759
     *
760
     *  @return int     Return integer <0 if KO, 0 if no children, >0 if OK
761
     */
762
    public function hasTimeSpent()
763
    {
764
        $error = 0;
765
        $ret = 0;
766
767
        $sql = "SELECT COUNT(*) as nb";
768
        $sql .= " FROM " . MAIN_DB_PREFIX . "element_time";
769
        $sql .= " WHERE fk_element = " . ((int) $this->id);
770
        $sql .= " AND elementtype = 'task'";
771
772
        dol_syslog(get_class($this) . "::hasTimeSpent", LOG_DEBUG);
773
        $resql = $this->db->query($sql);
774
        if (!$resql) {
775
            $error++;
776
            $this->errors[] = "Error " . $this->db->lasterror();
777
        } else {
778
            $obj = $this->db->fetch_object($resql);
779
            if ($obj) {
780
                $ret = $obj->nb;
781
            }
782
            $this->db->free($resql);
783
        }
784
785
        if (!$error) {
786
            return $ret;
787
        } else {
788
            return -1;
789
        }
790
    }
791
792
793
    /**
794
     * getTooltipContentArray
795
     *
796
     * @param array $params ex option, infologin
797
     * @since v18
798
     * @return array
799
     */
800
    public function getTooltipContentArray($params)
801
    {
802
        global $langs;
803
804
        $langs->load('projects');
805
806
        $datas = [];
807
        $datas['picto'] = img_picto('', $this->picto) . ' <u>' . $langs->trans("Task") . '</u>';
808
        if (!empty($this->ref)) {
809
            $datas['ref'] = '<br><b>' . $langs->trans('Ref') . ':</b> ' . $this->ref;
810
        }
811
        if (!empty($this->label)) {
812
            $datas['label'] = '<br><b>' . $langs->trans('LabelTask') . ':</b> ' . $this->label;
813
        }
814
        if ($this->date_start || $this->date_end) {
815
            $datas['range'] = "<br>" . get_date_range($this->date_start, $this->date_end, '', $langs, 0);
816
        }
817
818
        return $datas;
819
    }
820
821
    /**
822
     *  Return clicable name (with picto eventually)
823
     *
824
     *  @param  int     $withpicto      0=No picto, 1=Include picto into link, 2=Only picto
825
     *  @param  string  $option         'withproject' or ''
826
     *  @param  string  $mode           Mode 'task', 'time', 'contact', 'note', document' define page to link to.
827
     *  @param  int     $addlabel       0=Default, 1=Add label into string, >1=Add first chars into string
828
     *  @param  string  $sep            Separator between ref and label if option addlabel is set
829
     *  @param  int     $notooltip      1=Disable tooltip
830
     *  @param  int     $save_lastsearch_value    -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
831
     *  @return string                  Chaine avec URL
832
     */
833
    public function getNomUrl($withpicto = 0, $option = '', $mode = 'task', $addlabel = 0, $sep = ' - ', $notooltip = 0, $save_lastsearch_value = -1)
834
    {
835
        global $action, $conf, $hookmanager, $langs;
836
837
        if (!empty($conf->dol_no_mouse_hover)) {
838
            $notooltip = 1; // Force disable tooltips
839
        }
840
841
        $result = '';
842
        $params = [
843
            'id' => $this->id,
844
            'objecttype' => $this->element,
845
        ];
846
        $classfortooltip = 'classfortooltip';
847
        $dataparams = '';
848
        if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
849
            $classfortooltip = 'classforajaxtooltip';
850
            $dataparams = ' data-params="' . dol_escape_htmltag(json_encode($params)) . '"';
851
            $label = '';
852
        } else {
853
            $label = implode($this->getTooltipContentArray($params));
854
        }
855
856
        $url = constant('BASE_URL') . '/projet/tasks/' . $mode . '.php?id=' . $this->id . ($option == 'withproject' ? '&withproject=1' : '');
857
        // Add param to save lastsearch_values or not
858
        $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
859
        if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
860
            $add_save_lastsearch_values = 1;
861
        }
862
        if ($add_save_lastsearch_values) {
863
            $url .= '&save_lastsearch_values=1';
864
        }
865
866
        $linkclose = '';
867
        if (empty($notooltip)) {
868
            if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
869
                $label = $langs->trans("ShowTask");
870
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
871
            }
872
            $linkclose .= ($label ? ' title="' . dol_escape_htmltag($label, 1) . '"' : ' title="tocomplete"');
873
            $linkclose .= $dataparams . ' class="' . $classfortooltip . ' nowraponall"';
874
        } else {
875
            $linkclose .= ' class="nowraponall"';
876
        }
877
878
        $linkstart = '<a href="' . $url . '"';
879
        $linkstart .= $linkclose . '>';
880
        $linkend = '</a>';
881
882
        $picto = 'projecttask';
883
884
        $result .= $linkstart;
885
        if ($withpicto) {
886
            $result .= img_object(($notooltip ? '' : $label), $picto, 'class="paddingright"', 0, 0, $notooltip ? 0 : 1);
887
        }
888
        if ($withpicto != 2) {
889
            $result .= $this->ref;
890
        }
891
        $result .= $linkend;
892
        if ($withpicto != 2) {
893
            $result .= (($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
894
        }
895
896
        $parameters = array('id' => $this->id, 'getnomurl' => &$result);
897
        $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
898
        if ($reshook > 0) {
899
            $result = $hookmanager->resPrint;
900
        } else {
901
            $result .= $hookmanager->resPrint;
902
        }
903
904
        return $result;
905
    }
906
907
    /**
908
     *  Initialise an instance with random values.
909
     *  Used to build previews or test instances.
910
     *  id must be 0 if object instance is a specimen.
911
     *
912
     *  @return int
913
     */
914
    public function initAsSpecimen()
915
    {
916
        global $user;
917
918
        $this->id = 0;
919
920
        $this->fk_project = 0;
921
        $this->ref = 'TK01';
922
        $this->fk_task_parent = 0;
923
        $this->label = 'Specimen task TK01';
924
        $this->duration_effective = '';
925
        $this->fk_user_creat = $user->id;
926
        $this->progress = '25';
927
        $this->status = 0;
928
        $this->priority = 0;
929
        $this->note_private = 'This is a specimen private note';
930
        $this->note_public = 'This is a specimen public note';
931
932
        return 1;
933
    }
934
935
    /**
936
     * Return list of tasks for all projects or for one particular project
937
     * Sort order is on project, then on position of task, and last on start date of first level task
938
     *
939
     * @param   User    $usert                  Object user to limit tasks affected to a particular user
940
     * @param   User    $userp                  Object user to limit projects of a particular user and public projects
941
     * @param   int     $projectid              Project id
942
     * @param   int     $socid                  Third party id
943
     * @param   int     $mode                   0=Return list of tasks and their projects, 1=Return projects and tasks if exists
944
     * @param   string  $filteronproj           Filter on project ref or label
945
     * @param   string  $filteronprojstatus     Filter on project status ('-1'=no filter, '0,1'=Draft+Validated only)
946
     * @param   string  $morewherefilter        Add more filter into where SQL request (must start with ' AND ...')
947
     * @param   int     $filteronprojuser       Filter on user that is a contact of project
948
     * @param   int     $filterontaskuser       Filter on user assigned to task
949
     * @param   ?Extrafields    $extrafields    Show additional column from project or task
950
     * @param   int     $includebilltime        Calculate also the time to bill and billed
951
     * @param   array   $search_array_options   Array of search filters. Not Used yet.
952
     * @param   int     $loadextras             Fetch all Extrafields on each project and task
953
     * @param   int     $loadRoleMode           1= will test Roles on task;  0 used in delete project action
954
     * @param   string  $sortfield              Sort field
955
     * @param   string  $sortorder              Sort order
956
     * @return  array|string                    Array of tasks
957
     */
958
    public function getTasksArray($usert = null, $userp = null, $projectid = 0, $socid = 0, $mode = 0, $filteronproj = '', $filteronprojstatus = '-1', $morewherefilter = '', $filteronprojuser = 0, $filterontaskuser = 0, $extrafields = null, $includebilltime = 0, $search_array_options = array(), $loadextras = 0, $loadRoleMode = 1, $sortfield = '', $sortorder = '')
959
    {
960
        global $hookmanager;
961
962
        $tasks = array();
963
964
        //print $usert.'-'.$userp.'-'.$projectid.'-'.$socid.'-'.$mode.'<br>';
965
966
        // List of tasks (does not care about permissions. Filtering will be done later)
967
        $sql = "SELECT ";
968
        if ($filteronprojuser > 0 || $filterontaskuser > 0) {
969
            $sql .= " DISTINCT"; // We may get several time the same record if user has several roles on same project/task
970
        }
971
        $sql .= " p.rowid as projectid, p.ref, p.title as plabel, p.public, p.fk_statut as projectstatus, p.usage_bill_time,";
972
        $sql .= " t.rowid as taskid, t.ref as taskref, t.label, t.description, t.fk_task_parent, t.duration_effective, t.progress, t.fk_statut as status,";
973
        $sql .= " t.dateo as date_start, t.datee as date_end, t.planned_workload, t.rang, t.priority,";
974
        $sql .= " t.budget_amount,";
975
        $sql .= " t.note_public, t.note_private,";
976
        $sql .= " s.rowid as thirdparty_id, s.nom as thirdparty_name, s.email as thirdparty_email,";
977
        $sql .= " p.fk_opp_status, p.opp_amount, p.opp_percent, p.budget_amount as project_budget_amount";
978
        if ($loadextras) {  // TODO Replace this with a fetch_optionnal() on the project after the fetch_object of line.
979
            if (!empty($extrafields->attributes['projet']['label'])) {
980
                foreach ($extrafields->attributes['projet']['label'] as $key => $val) {
981
                    $sql .= ($extrafields->attributes['projet']['type'][$key] != 'separate' ? ",efp." . $key . " as options_" . $key : '');
982
                }
983
            }
984
            if (!empty($extrafields->attributes['projet_task']['label'])) {
985
                foreach ($extrafields->attributes['projet_task']['label'] as $key => $val) {
986
                    $sql .= ($extrafields->attributes['projet_task']['type'][$key] != 'separate' ? ",efpt." . $key . " as options_" . $key : '');
987
                }
988
            }
989
        }
990
        if ($includebilltime) {
991
            $sql .= ", SUM(tt.element_duration * " . $this->db->ifsql("invoice_id IS NULL", "1", "0") . ") as tobill, SUM(tt.element_duration * " . $this->db->ifsql("invoice_id IS NULL", "0", "1") . ") as billed";
992
        }
993
994
        $sql .= " FROM " . MAIN_DB_PREFIX . "projet as p";
995
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON p.fk_soc = s.rowid";
996
        if ($loadextras) {
997
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "projet_extrafields as efp ON (p.rowid = efp.fk_object)";
998
        }
999
1000
        if ($mode == 0) {
1001
            if ($filteronprojuser > 0) {
1002
                $sql .= ", " . MAIN_DB_PREFIX . "element_contact as ec";
1003
                $sql .= ", " . MAIN_DB_PREFIX . "c_type_contact as ctc";
1004
            }
1005
            $sql .= ", " . MAIN_DB_PREFIX . "projet_task as t";
1006
            if ($loadextras) {
1007
                $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "projet_task_extrafields as efpt ON (t.rowid = efpt.fk_object)";
1008
            }
1009
            if ($includebilltime) {
1010
                $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "element_time as tt ON (tt.fk_element = t.rowid AND tt.elementtype='task')";
1011
            }
1012
            if ($filterontaskuser > 0) {
1013
                $sql .= ", " . MAIN_DB_PREFIX . "element_contact as ec2";
1014
                $sql .= ", " . MAIN_DB_PREFIX . "c_type_contact as ctc2";
1015
            }
1016
            $sql .= " WHERE p.entity IN (" . getEntity('project') . ")";
1017
            $sql .= " AND t.fk_projet = p.rowid";
1018
        } elseif ($mode == 1) {
1019
            if ($filteronprojuser > 0) {
1020
                $sql .= ", " . MAIN_DB_PREFIX . "element_contact as ec";
1021
                $sql .= ", " . MAIN_DB_PREFIX . "c_type_contact as ctc";
1022
            }
1023
            if ($filterontaskuser > 0) {
1024
                $sql .= ", " . MAIN_DB_PREFIX . "projet_task as t";
1025
                if ($includebilltime) {
1026
                    $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "element_time as tt ON (tt.fk_element = t.rowid AND tt.elementtype='task')";
1027
                }
1028
                $sql .= ", " . MAIN_DB_PREFIX . "element_contact as ec2";
1029
                $sql .= ", " . MAIN_DB_PREFIX . "c_type_contact as ctc2";
1030
            } else {
1031
                $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "projet_task as t on t.fk_projet = p.rowid";
1032
                if ($includebilltime) {
1033
                    $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "element_time as tt ON (tt.fk_element = t.rowid AND tt.elementtype = 'task')";
1034
                }
1035
            }
1036
            $sql .= " WHERE p.entity IN (" . getEntity('project') . ")";
1037
        } else {
1038
            return 'BadValueForParameterMode';
1039
        }
1040
1041
        if ($filteronprojuser > 0) {
1042
            $sql .= " AND p.rowid = ec.element_id";
1043
            $sql .= " AND ctc.rowid = ec.fk_c_type_contact";
1044
            $sql .= " AND ctc.element = 'project'";
1045
            $sql .= " AND ec.fk_socpeople = " . ((int) $filteronprojuser);
1046
            $sql .= " AND ec.statut = 4";
1047
            $sql .= " AND ctc.source = 'internal'";
1048
        }
1049
        if ($filterontaskuser > 0) {
1050
            $sql .= " AND t.fk_projet = p.rowid";
1051
            $sql .= " AND p.rowid = ec2.element_id";
1052
            $sql .= " AND ctc2.rowid = ec2.fk_c_type_contact";
1053
            $sql .= " AND ctc2.element = 'project_task'";
1054
            $sql .= " AND ec2.fk_socpeople = " . ((int) $filterontaskuser);
1055
            $sql .= " AND ec2.statut = 4";
1056
            $sql .= " AND ctc2.source = 'internal'";
1057
        }
1058
        if ($socid) {
1059
            $sql .= " AND p.fk_soc = " . ((int) $socid);
1060
        }
1061
        if ($projectid) {
1062
            $sql .= " AND p.rowid IN (" . $this->db->sanitize($projectid) . ")";
1063
        }
1064
        if ($filteronproj) {
1065
            $sql .= natural_search(array("p.ref", "p.title"), $filteronproj);
1066
        }
1067
        if ($filteronprojstatus && (int) $filteronprojstatus != '-1') {
1068
            $sql .= " AND p.fk_statut IN (" . $this->db->sanitize($filteronprojstatus) . ")";
1069
        }
1070
        if ($morewherefilter) {
1071
            $sql .= $morewherefilter;
1072
        }
1073
1074
        // Add where from extra fields
1075
        $extrafieldsobjectkey = 'projet_task';
1076
        $extrafieldsobjectprefix = 'efpt.';
1077
        global $db, $conf; // needed for extrafields_list_search_sql.tpl
1078
        include DOL_DOCUMENT_ROOT . '/core/tpl/extrafields_list_search_sql.tpl.php';
1079
1080
        // Add where from hooks
1081
        $parameters = array();
1082
        $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters); // Note that $action and $object may have been modified by hook
1083
        $sql .= $hookmanager->resPrint;
1084
        if ($includebilltime) {
1085
            $sql .= " GROUP BY p.rowid, p.ref, p.title, p.public, p.fk_statut, p.usage_bill_time,";
1086
            $sql .= " t.datec, t.dateo, t.datee, t.tms,";
1087
            $sql .= " t.rowid, t.ref, t.label, t.description, t.fk_task_parent, t.duration_effective, t.progress, t.fk_statut,";
1088
            $sql .= " t.dateo, t.datee, t.planned_workload, t.rang, t.priority,";
1089
            $sql .= " t.budget_amount,";
1090
            $sql .= " t.note_public, t.note_private,";
1091
            $sql .= " s.rowid, s.nom, s.email,";
1092
            $sql .= " p.fk_opp_status, p.opp_amount, p.opp_percent, p.budget_amount";
1093
            if ($loadextras) {
1094
                if (!empty($extrafields->attributes['projet']['label'])) {
1095
                    foreach ($extrafields->attributes['projet']['label'] as $key => $val) {
1096
                        $sql .= ($extrafields->attributes['projet']['type'][$key] != 'separate' ? ",efp." . $key : '');
1097
                    }
1098
                }
1099
                if (!empty($extrafields->attributes['projet_task']['label'])) {
1100
                    foreach ($extrafields->attributes['projet_task']['label'] as $key => $val) {
1101
                        $sql .= ($extrafields->attributes['projet_task']['type'][$key] != 'separate' ? ",efpt." . $key : '');
1102
                    }
1103
                }
1104
            }
1105
        }
1106
1107
        if ($sortfield && $sortorder) {
1108
            $sql .= $this->db->order($sortfield, $sortorder);
1109
        } else {
1110
            $sql .= " ORDER BY p.ref, t.rang, t.dateo";
1111
        }
1112
1113
        //print $sql;exit;
1114
        dol_syslog(get_class($this) . "::getTasksArray", LOG_DEBUG);
1115
        $resql = $this->db->query($sql);
1116
        if ($resql) {
1117
            $num = $this->db->num_rows($resql);
1118
            $i = 0;
1119
            // Loop on each record found, so each couple (project id, task id)
1120
            while ($i < $num) {
1121
                $error = 0;
1122
1123
                $obj = $this->db->fetch_object($resql);
1124
1125
                if ($loadRoleMode) {
1126
                    if ((!$obj->public) && (is_object($userp))) {    // If not public project and we ask a filter on project owned by a user
1127
                        if (!$this->getUserRolesForProjectsOrTasks($userp, null, $obj->projectid, 0)) {
1128
                            $error++;
1129
                        }
1130
                    }
1131
                    if (is_object($usert)) {                            // If we ask a filter on a user affected to a task
1132
                        if (!$this->getUserRolesForProjectsOrTasks(null, $usert, $obj->projectid, $obj->taskid)) {
1133
                            $error++;
1134
                        }
1135
                    }
1136
                }
1137
1138
                if (!$error) {
1139
                    $tasks[$i] = new Task($this->db);
1140
                    $tasks[$i]->id = $obj->taskid;
1141
                    $tasks[$i]->ref = $obj->taskref;
1142
                    $tasks[$i]->fk_project = $obj->projectid;
1143
1144
                    // Data from project
1145
                    $tasks[$i]->projectref = $obj->ref;
1146
                    $tasks[$i]->projectlabel = $obj->plabel;
1147
                    $tasks[$i]->projectstatus = $obj->projectstatus;
1148
                    $tasks[$i]->fk_opp_status = $obj->fk_opp_status;
1149
                    $tasks[$i]->opp_amount = $obj->opp_amount;
1150
                    $tasks[$i]->opp_percent = $obj->opp_percent;
1151
                    $tasks[$i]->budget_amount = $obj->budget_amount;
1152
                    $tasks[$i]->project_budget_amount = $obj->project_budget_amount;
1153
                    $tasks[$i]->usage_bill_time = $obj->usage_bill_time;
1154
1155
                    $tasks[$i]->label = $obj->label;
1156
                    $tasks[$i]->description = $obj->description;
1157
1158
                    $tasks[$i]->fk_task_parent = $obj->fk_task_parent;
1159
                    $tasks[$i]->note_public = $obj->note_public;
1160
                    $tasks[$i]->note_private = $obj->note_private;
1161
                    $tasks[$i]->duration_effective = $obj->duration_effective;
1162
                    $tasks[$i]->planned_workload = $obj->planned_workload;
1163
1164
                    if ($includebilltime) {
1165
                        // Data summed from element_time linked to task
1166
                        $tasks[$i]->tobill = $obj->tobill;
1167
                        $tasks[$i]->billed = $obj->billed;
1168
                    }
1169
1170
                    $tasks[$i]->progress        = $obj->progress;
1171
                    $tasks[$i]->fk_statut       = $obj->status;
1172
                    $tasks[$i]->status          = $obj->status;
1173
                    $tasks[$i]->public = $obj->public;
1174
                    $tasks[$i]->date_start = $this->db->jdate($obj->date_start);
1175
                    $tasks[$i]->date_end        = $this->db->jdate($obj->date_end);
1176
                    $tasks[$i]->rang            = $obj->rang;
1177
                    $tasks[$i]->priority        = $obj->priority;
1178
1179
                    $tasks[$i]->socid           = $obj->thirdparty_id; // For backward compatibility
1180
                    $tasks[$i]->thirdparty_id = $obj->thirdparty_id;
1181
                    $tasks[$i]->thirdparty_name = $obj->thirdparty_name;
1182
                    $tasks[$i]->thirdparty_email = $obj->thirdparty_email;
1183
1184
                    if ($loadextras) {
1185
                        if (!empty($extrafields->attributes['projet']['label'])) {
1186
                            foreach ($extrafields->attributes['projet']['label'] as $key => $val) {
1187
                                if ($extrafields->attributes['projet']['type'][$key] != 'separate') {
1188
                                    $tmpvar = 'options_' . $key;
1189
                                    $tasks[$i]->array_options_project['options_' . $key] = $obj->$tmpvar;
1190
                                }
1191
                            }
1192
                        }
1193
                    }
1194
1195
                    if ($loadextras) {
1196
                        $tasks[$i]->fetch_optionals();
1197
                    }
1198
                }
1199
1200
                $i++;
1201
            }
1202
            $this->db->free($resql);
1203
        } else {
1204
            dol_print_error($this->db);
1205
        }
1206
1207
        return $tasks;
1208
    }
1209
1210
    /**
1211
     * Return list of roles for a user for each projects or each tasks (or a particular project or a particular task).
1212
     *
1213
     * @param   User|null   $userp                Return roles on project for this internal user. If set, usert and taskid must not be defined.
1214
     * @param   User|null   $usert                Return roles on task for this internal user. If set userp must NOT be defined. -1 means no filter.
1215
     * @param   string      $projectid            Project id list separated with , to filter on project
1216
     * @param   int         $taskid               Task id to filter on a task
1217
     * @param   integer     $filteronprojstatus   Filter on project status if userp is set. Not used if userp not defined.
1218
     * @return  array|int                         Array (projectid => 'list of roles for project' or taskid => 'list of roles for task')
1219
     */
1220
    public function getUserRolesForProjectsOrTasks($userp, $usert, $projectid = '', $taskid = 0, $filteronprojstatus = -1)
1221
    {
1222
        $arrayroles = array();
1223
1224
        dol_syslog(get_class($this) . "::getUserRolesForProjectsOrTasks userp=" . json_encode(is_object($userp)) . " usert=" . json_encode(is_object($usert)) . " projectid=" . $projectid . " taskid=" . $taskid);
1225
1226
        // We want role of user for a projet or role of user for a task. Both are not possible.
1227
        if (empty($userp) && empty($usert)) {
1228
            $this->error = "CallWithWrongParameters";
1229
            return -1;
1230
        }
1231
        if (!empty($userp) && !empty($usert)) {
1232
            $this->error = "CallWithWrongParameters";
1233
            return -1;
1234
        }
1235
1236
        /* Liste des taches et role sur les projects ou taches */
1237
        $sql = "SELECT ";
1238
        if ($userp) {
1239
            $sql .= " p.rowid as pid,";
1240
        } else {
1241
            $sql .= " pt.rowid as pid,";
1242
        }
1243
        $sql .= " ec.element_id, ctc.code, ctc.source";
1244
        if ($userp) {
1245
            $sql .= " FROM " . MAIN_DB_PREFIX . "projet as p";
1246
        }
1247
        if ($usert && $filteronprojstatus > -1) {
1248
            $sql .= " FROM " . MAIN_DB_PREFIX . "projet as p, " . MAIN_DB_PREFIX . "projet_task as pt";
1249
        }
1250
        if ($usert && $filteronprojstatus <= -1) {
1251
            $sql .= " FROM " . MAIN_DB_PREFIX . "projet_task as pt";
1252
        }
1253
        $sql .= ", " . MAIN_DB_PREFIX . "element_contact as ec";
1254
        $sql .= ", " . MAIN_DB_PREFIX . "c_type_contact as ctc";
1255
        if ($userp) {
1256
            $sql .= " WHERE p.rowid = ec.element_id";
1257
        } else {
1258
            $sql .= " WHERE pt.rowid = ec.element_id";
1259
        }
1260
        if ($userp && $filteronprojstatus > -1) {
1261
            $sql .= " AND p.fk_statut = " . ((int) $filteronprojstatus);
1262
        }
1263
        if ($usert && $filteronprojstatus > -1) {
1264
            $sql .= " AND pt.fk_projet = p.rowid AND p.fk_statut = " . ((int) $filteronprojstatus);
1265
        }
1266
        if ($userp) {
1267
            $sql .= " AND ctc.element = 'project'";
1268
        }
1269
        if ($usert) {
1270
            $sql .= " AND ctc.element = 'project_task'";
1271
        }
1272
        $sql .= " AND ctc.rowid = ec.fk_c_type_contact";
1273
        if ($userp) {
1274
            $sql .= " AND ec.fk_socpeople = " . ((int) $userp->id);
1275
        }
1276
        if ($usert) {
1277
            $sql .= " AND ec.fk_socpeople = " . ((int) $usert->id);
1278
        }
1279
        $sql .= " AND ec.statut = 4";
1280
        $sql .= " AND ctc.source = 'internal'";
1281
        if ($projectid) {
1282
            if ($userp) {
1283
                $sql .= " AND p.rowid IN (" . $this->db->sanitize($projectid) . ")";
1284
            }
1285
            if ($usert) {
1286
                $sql .= " AND pt.fk_projet IN (" . $this->db->sanitize($projectid) . ")";
1287
            }
1288
        }
1289
        if ($taskid) {
1290
            if ($userp) {
1291
                $sql .= " ERROR SHOULD NOT HAPPENS";
1292
            }
1293
            if ($usert) {
1294
                $sql .= " AND pt.rowid = " . ((int) $taskid);
1295
            }
1296
        }
1297
        //print $sql;
1298
1299
        dol_syslog(get_class($this) . "::getUserRolesForProjectsOrTasks execute request", LOG_DEBUG);
1300
        $resql = $this->db->query($sql);
1301
        if ($resql) {
1302
            $num = $this->db->num_rows($resql);
1303
            $i = 0;
1304
            while ($i < $num) {
1305
                $obj = $this->db->fetch_object($resql);
1306
                if (empty($arrayroles[$obj->pid])) {
1307
                    $arrayroles[$obj->pid] = $obj->code;
1308
                } else {
1309
                    $arrayroles[$obj->pid] .= ',' . $obj->code;
1310
                }
1311
                $i++;
1312
            }
1313
            $this->db->free($resql);
1314
        } else {
1315
            dol_print_error($this->db);
1316
        }
1317
1318
        return $arrayroles;
1319
    }
1320
1321
1322
    /**
1323
     *  Return list of id of contacts of task
1324
     *
1325
     *  @param  string  $source     Source
1326
     *  @return array               Array of id of contacts
1327
     */
1328
    public function getListContactId($source = 'internal')
1329
    {
1330
        $contactAlreadySelected = array();
1331
        $tab = $this->liste_contact(-1, $source);
1332
        //var_dump($tab);
1333
        $num = count($tab);
1334
        $i = 0;
1335
        while ($i < $num) {
1336
            if ($source == 'thirdparty') {
1337
                $contactAlreadySelected[$i] = $tab[$i]['socid'];
1338
            } else {
1339
                $contactAlreadySelected[$i] = $tab[$i]['id'];
1340
            }
1341
            $i++;
1342
        }
1343
        return $contactAlreadySelected;
1344
    }
1345
1346
    /**
1347
     * Merge contact of tasks
1348
     *
1349
     * @param   int     $origin_id  Old task id
1350
     * @param   int     $dest_id    New task id
1351
     * @return  bool
1352
     */
1353
    public function mergeContactTask($origin_id, $dest_id)
1354
    {
1355
        $error = 0;
1356
        $origintask = new Task($this->db);
1357
        $result = $origintask->fetch($origin_id);
1358
        if ($result <= 0) {
1359
            return false;
1360
        }
1361
1362
        //Get list of origin contacts
1363
        $arraycontactorigin = array_merge($origintask->liste_contact(-1, 'internal'), $origintask->liste_contact(-1, 'external'));
1364
        if (is_array($arraycontactorigin)) {
1365
            foreach ($arraycontactorigin as $key => $contact) {
1366
                $result = $this->add_contact($contact["id"], $contact["fk_c_type_contact"], $contact["source"]);
1367
                if ($result < 0) {
1368
                    return false;
1369
                }
1370
            }
1371
        }
1372
        return true;
1373
    }
1374
1375
    /**
1376
     * Merge time spent of tasks
1377
     *
1378
     * @param   int     $origin_id  Old task id
1379
     * @param   int     $dest_id    New task id
1380
     * @return  bool
1381
     */
1382
    public function mergeTimeSpentTask($origin_id, $dest_id)
1383
    {
1384
        $ret = true;
1385
1386
        $this->db->begin();
1387
1388
        $sql = "UPDATE " . MAIN_DB_PREFIX . "element_time as et";
1389
        $sql .= " SET et.fk_element = " . ((int) $dest_id);
1390
        $sql .= " WHERE et.elementtype = 'task'";
1391
        $sql .= " AND et.fk_element = " . ((int) $origin_id);
1392
1393
        dol_syslog(get_class($this) . "::mergeTimeSpentTask", LOG_DEBUG);
1394
        if (!$this->db->query($sql)) {
1395
            $this->error = $this->db->lasterror();
1396
            $ret = false;
1397
        }
1398
1399
        if ($ret) {
1400
            $sql = "UPDATE " . MAIN_DB_PREFIX . "projet_task";
1401
            $sql .= " SET duration_effective = (SELECT SUM(element_duration) FROM " . MAIN_DB_PREFIX . "element_time as ptt where ptt.elementtype = 'task' AND ptt.fk_element = " . ((int) $dest_id) . ")";
1402
            $sql .= " WHERE rowid = " . ((int) $dest_id);
1403
1404
            dol_syslog(get_class($this) . "::mergeTimeSpentTask update project_task", LOG_DEBUG);
1405
            if (!$this->db->query($sql)) {
1406
                $this->error = $this->db->lasterror();
1407
                $ret = false;
1408
            }
1409
        }
1410
1411
        if ($ret == true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1412
            $this->db->commit();
1413
        } else {
1414
            $this->db->rollback();
1415
        }
1416
        return $ret;
1417
    }
1418
1419
    /**
1420
     *  Add time spent
1421
     *
1422
     *  @param  User    $user           User object
1423
     *  @param  int     $notrigger      0=launch triggers after, 1=disable triggers
1424
     *  @return int                     Return integer <=0 if KO, >0 if OK
1425
     */
1426
    public function addTimeSpent($user, $notrigger = 0)
1427
    {
1428
        global $langs;
1429
1430
        dol_syslog(get_class($this) . "::addTimeSpent", LOG_DEBUG);
1431
1432
        $ret = 0;
1433
        $now = dol_now();
1434
1435
        // Check parameters
1436
        if (!is_object($user)) {
1437
            dol_print_error(null, "Method addTimeSpent was called with wrong parameter user");
1438
            return -1;
1439
        }
1440
1441
        // Clean parameters
1442
        if (isset($this->timespent_note)) {
1443
            $this->timespent_note = trim($this->timespent_note);
1444
        }
1445
        if (empty($this->timespent_datehour) || ($this->timespent_date != $this->timespent_datehour)) {
1446
            $this->timespent_datehour = $this->timespent_date;
1447
        }
1448
1449
        if (getDolGlobalInt('PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS')) {
1450
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/date.lib.php';
1451
            $restrictBefore = dol_time_plus_duree(dol_now(), - getDolGlobalInt('PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS'), 'm');
1452
1453
            if ($this->timespent_date < $restrictBefore) {
1454
                $this->error = $langs->trans('TimeRecordingRestrictedToNMonthsBack', getDolGlobalString('PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS'));
1455
                $this->errors[] = $this->error;
1456
                return -1;
1457
            }
1458
        }
1459
1460
        $this->db->begin();
1461
1462
        $timespent = new TimeSpent($this->db);
1463
        $timespent->fk_element = $this->id;
1464
        $timespent->elementtype = 'task';
1465
        $timespent->element_date = $this->timespent_date;
1466
        $timespent->element_datehour = $this->timespent_datehour;
1467
        $timespent->element_date_withhour = $this->timespent_withhour;
1468
        $timespent->element_duration = $this->timespent_duration;
1469
        $timespent->fk_user = $this->timespent_fk_user;
1470
        $timespent->fk_product = $this->timespent_fk_product;
1471
        $timespent->note = $this->timespent_note;
1472
        $timespent->datec = $this->db->idate($now);
1473
1474
        $result = $timespent->create($user);
1475
1476
        if ($result > 0) {
1477
            $ret = $result;
1478
            $this->timespent_id = $result;
1479
1480
            if (!$notrigger) {
1481
                // Call trigger
1482
                $result = $this->call_trigger('TASK_TIMESPENT_CREATE', $user);
1483
                if ($result < 0) {
1484
                    $ret = -1;
1485
                }
1486
                // End call triggers
1487
            }
1488
        } else {
1489
            $this->error = $this->db->lasterror();
1490
            $ret = -1;
1491
        }
1492
1493
        if ($ret > 0) {
1494
            // Recalculate amount of time spent for task and update denormalized field
1495
            $sql = "UPDATE " . MAIN_DB_PREFIX . "projet_task";
1496
            $sql .= " SET duration_effective = (SELECT SUM(element_duration) FROM " . MAIN_DB_PREFIX . "element_time as ptt where ptt.elementtype = 'task' AND ptt.fk_element = " . ((int) $this->id) . ")";
1497
            if (isset($this->progress)) {
1498
                $sql .= ", progress = " . ((float) $this->progress); // Do not overwrite value if not provided
1499
            }
1500
            $sql .= " WHERE rowid = " . ((int) $this->id);
1501
1502
            dol_syslog(get_class($this) . "::addTimeSpent", LOG_DEBUG);
1503
            if (!$this->db->query($sql)) {
1504
                $this->error = $this->db->lasterror();
1505
                $ret = -2;
1506
            }
1507
1508
            // Update hourly rate of this time spent entry
1509
            $resql_thm_user = $this->db->query("SELECT thm FROM " . MAIN_DB_PREFIX . "user WHERE rowid = " . ((int) $timespent->fk_user));
1510
            if (!empty($resql_thm_user)) {
1511
                $obj_thm_user = $this->db->fetch_object($resql_thm_user);
1512
                $timespent->thm = $obj_thm_user->thm;
1513
            }
1514
            $res_update = $timespent->update($user);
1515
1516
            dol_syslog(get_class($this) . "::addTimeSpent", LOG_DEBUG);
1517
            if ($res_update <= 0) {
1518
                $this->error = $this->db->lasterror();
1519
                $ret = -2;
1520
            }
1521
        }
1522
1523
        if ($ret > 0) {
1524
            $this->db->commit();
1525
        } else {
1526
            $this->db->rollback();
1527
        }
1528
        return $ret;
1529
    }
1530
1531
    /**
1532
     *  Fetch records of time spent of this task
1533
     *
1534
     *  @param  string  $morewherefilter    Add more filter into where SQL request (must start with ' AND ...')
1535
     *  @return int                         Return integer <0 if KO, array of time spent if OK
1536
     */
1537
    public function fetchTimeSpentOnTask($morewherefilter = '')
1538
    {
1539
        $arrayres = array();
1540
1541
        $sql = "SELECT";
1542
        $sql .= " s.rowid as socid,";
1543
        $sql .= " s.nom as thirdparty_name,";
1544
        $sql .= " s.email as thirdparty_email,";
1545
        $sql .= " ptt.rowid,";
1546
        $sql .= " ptt.ref_ext,";
1547
        $sql .= " ptt.fk_element as fk_task,";
1548
        $sql .= " ptt.element_date as task_date,";
1549
        $sql .= " ptt.element_datehour as task_datehour,";
1550
        $sql .= " ptt.element_date_withhour as task_date_withhour,";
1551
        $sql .= " ptt.element_duration as task_duration,";
1552
        $sql .= " ptt.fk_user,";
1553
        $sql .= " ptt.note,";
1554
        $sql .= " ptt.thm,";
1555
        $sql .= " pt.rowid as task_id,";
1556
        $sql .= " pt.ref as task_ref,";
1557
        $sql .= " pt.label as task_label,";
1558
        $sql .= " p.rowid as project_id,";
1559
        $sql .= " p.ref as project_ref,";
1560
        $sql .= " p.title as project_label,";
1561
        $sql .= " p.public as public";
1562
        $sql .= " FROM " . MAIN_DB_PREFIX . "element_time as ptt, " . MAIN_DB_PREFIX . "projet_task as pt, " . MAIN_DB_PREFIX . "projet as p";
1563
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON p.fk_soc = s.rowid";
1564
        $sql .= " WHERE ptt.fk_element = pt.rowid AND pt.fk_projet = p.rowid";
1565
        $sql .= " AND ptt.elementtype = 'task'";
1566
        $sql .= " AND pt.rowid = " . ((int) $this->id);
1567
        $sql .= " AND pt.entity IN (" . getEntity('project') . ")";
1568
        if ($morewherefilter) {
1569
            $sql .= $morewherefilter;
1570
        }
1571
1572
        dol_syslog(get_class($this) . "::fetchAllTimeSpent", LOG_DEBUG);
1573
        $resql = $this->db->query($sql);
1574
        if ($resql) {
1575
            $num = $this->db->num_rows($resql);
1576
1577
            $i = 0;
1578
            while ($i < $num) {
1579
                $obj = $this->db->fetch_object($resql);
1580
1581
                $newobj = new stdClass();
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Projet\Classes\stdClass was not found. Did you mean stdClass? If so, make sure to prefix the type with \.
Loading history...
1582
1583
                $newobj->socid              = $obj->socid;
1584
                $newobj->thirdparty_name    = $obj->thirdparty_name;
1585
                $newobj->thirdparty_email   = $obj->thirdparty_email;
1586
1587
                $newobj->fk_project         = $obj->project_id;
1588
                $newobj->project_ref        = $obj->project_ref;
1589
                $newobj->project_label = $obj->project_label;
1590
                $newobj->public             = $obj->project_public;
1591
1592
                $newobj->fk_task            = $obj->task_id;
1593
                $newobj->task_ref = $obj->task_ref;
1594
                $newobj->task_label = $obj->task_label;
1595
1596
                $newobj->timespent_line_id = $obj->rowid;
1597
                $newobj->timespent_line_ref_ext = $obj->ref_ext;
1598
                $newobj->timespent_line_date = $this->db->jdate($obj->task_date);
1599
                $newobj->timespent_line_datehour    = $this->db->jdate($obj->task_datehour);
1600
                $newobj->timespent_line_withhour = $obj->task_date_withhour;
1601
                $newobj->timespent_line_duration = $obj->task_duration;
1602
                $newobj->timespent_line_fk_user = $obj->fk_user;
1603
                $newobj->timespent_line_thm = $obj->thm;    // hourly rate
1604
                $newobj->timespent_line_note = $obj->note;
1605
1606
                $arrayres[] = $newobj;
1607
1608
                $i++;
1609
            }
1610
1611
            $this->db->free($resql);
1612
1613
            $this->lines = $arrayres;
1614
            return 1;
1615
        } else {
1616
            dol_print_error($this->db);
1617
            $this->error = "Error " . $this->db->lasterror();
1618
            return -1;
1619
        }
1620
    }
1621
1622
1623
    /**
1624
     *  Calculate total of time spent for task
1625
     *
1626
     *  @param  User|int    $userobj            Filter on user. null or 0=No filter
1627
     *  @param  string      $morewherefilter    Add more filter into where SQL request (must start with ' AND ...')
1628
     *  @return array|int                       Array of info for task array('min_date', 'max_date', 'total_duration', 'total_amount', 'nblines', 'nblinesnull')
1629
     */
1630
    public function getSummaryOfTimeSpent($userobj = null, $morewherefilter = '')
1631
    {
1632
        if (is_object($userobj)) {
1633
            $userid = $userobj->id;
1634
        } else {
1635
            $userid = $userobj; // old method
1636
        }
1637
1638
        $id = $this->id;
1639
        if (empty($id) && empty($userid)) {
1640
            dol_syslog("getSummaryOfTimeSpent called on a not loaded task without user param defined", LOG_ERR);
1641
            return -1;
1642
        }
1643
1644
        $result = array();
1645
1646
        $sql = "SELECT";
1647
        $sql .= " MIN(t.element_datehour) as min_date,";
1648
        $sql .= " MAX(t.element_datehour) as max_date,";
1649
        $sql .= " SUM(t.element_duration) as total_duration,";
1650
        $sql .= " SUM(t.element_duration / 3600 * " . $this->db->ifsql("t.thm IS NULL", 0, "t.thm") . ") as total_amount,";
1651
        $sql .= " COUNT(t.rowid) as nblines,";
1652
        $sql .= " SUM(" . $this->db->ifsql("t.thm IS NULL", 1, 0) . ") as nblinesnull";
1653
        $sql .= " FROM " . MAIN_DB_PREFIX . "element_time as t";
1654
        $sql .= " WHERE t.elementtype='task'";
1655
        if ($morewherefilter) {
1656
            $sql .= $morewherefilter;
1657
        }
1658
        if ($id > 0) {
1659
            $sql .= " AND t.fk_element = " . ((int) $id);
1660
        }
1661
        if ($userid > 0) {
1662
            $sql .= " AND t.fk_user = " . ((int) $userid);
1663
        }
1664
1665
        dol_syslog(get_class($this) . "::getSummaryOfTimeSpent", LOG_DEBUG);
1666
        $resql = $this->db->query($sql);
1667
        if ($resql) {
1668
            $obj = $this->db->fetch_object($resql);
1669
1670
            $result['min_date'] = $obj->min_date; // deprecated. use the ->timespent_xxx instead
1671
            $result['max_date'] = $obj->max_date; // deprecated. use the ->timespent_xxx instead
1672
            $result['total_duration'] = $obj->total_duration; // deprecated. use the ->timespent_xxx instead
1673
1674
            $this->timespent_min_date = $this->db->jdate($obj->min_date);
1675
            $this->timespent_max_date = $this->db->jdate($obj->max_date);
1676
            $this->timespent_total_duration = $obj->total_duration;
1677
            $this->timespent_total_amount = $obj->total_amount;
1678
            $this->timespent_nblinesnull = ($obj->nblinesnull ? $obj->nblinesnull : 0);
1679
            $this->timespent_nblines = ($obj->nblines ? $obj->nblines : 0);
1680
1681
            $this->db->free($resql);
1682
        } else {
1683
            dol_print_error($this->db);
1684
        }
1685
        return $result;
1686
    }
1687
1688
    /**
1689
     *  Calculate quantity and value of time consumed using the thm (hourly amount value of work for user entering time)
1690
     *
1691
     *  @param      User|string $fuser      Filter on a dedicated user
1692
     *  @param      string      $dates      Start date (ex 00:00:00)
1693
     *  @param      string      $datee      End date (ex 23:59:59)
1694
     *  @return     array                   Array of info for task array('amount','nbseconds','nblinesnull')
1695
     */
1696
    public function getSumOfAmount($fuser = '', $dates = '', $datee = '')
1697
    {
1698
        $id = $this->id;
1699
1700
        $result = array();
1701
1702
        $sql = "SELECT";
1703
        $sql .= " SUM(t.element_duration) as nbseconds,";
1704
        $sql .= " SUM(t.element_duration / 3600 * " . $this->db->ifsql("t.thm IS NULL", 0, "t.thm") . ") as amount, SUM(" . $this->db->ifsql("t.thm IS NULL", 1, 0) . ") as nblinesnull";
1705
        $sql .= " FROM " . MAIN_DB_PREFIX . "element_time as t";
1706
        $sql .= " WHERE t.elementtype='task' AND t.fk_element = " . ((int) $id);
1707
        if (is_object($fuser) && $fuser->id > 0) {
1708
            $sql .= " AND fk_user = " . ((int) $fuser->id);
1709
        }
1710
        if ($dates > 0) {
1711
            $datefieldname = "element_datehour";
1712
            $sql .= " AND (" . $datefieldname . " >= '" . $this->db->idate($dates) . "' OR " . $datefieldname . " IS NULL)";
1713
        }
1714
        if ($datee > 0) {
1715
            $datefieldname = "element_datehour";
1716
            $sql .= " AND (" . $datefieldname . " <= '" . $this->db->idate($datee) . "' OR " . $datefieldname . " IS NULL)";
1717
        }
1718
        //print $sql;
1719
1720
        dol_syslog(get_class($this) . "::getSumOfAmount", LOG_DEBUG);
1721
        $resql = $this->db->query($sql);
1722
        if ($resql) {
1723
            $obj = $this->db->fetch_object($resql);
1724
1725
            $result['amount'] = $obj->amount;
1726
            $result['nbseconds'] = $obj->nbseconds;
1727
            $result['nblinesnull'] = $obj->nblinesnull;
1728
1729
            $this->db->free($resql);
1730
            return $result;
1731
        } else {
1732
            dol_print_error($this->db);
1733
            return $result;
1734
        }
1735
    }
1736
1737
    /**
1738
     *  Load properties of timespent of a task from the time spent ID.
1739
     *
1740
     *  @param  int     $id     Id in time spent table
1741
     *  @return int             Return integer <0 if KO, >0 if OK
1742
     */
1743
    public function fetchTimeSpent($id)
1744
    {
1745
        $timespent = new TimeSpent($this->db);
1746
        $timespent->fetch($id);
1747
1748
        dol_syslog(get_class($this) . "::fetchTimeSpent", LOG_DEBUG);
1749
1750
        if ($timespent->id > 0) {
1751
            $this->timespent_id = $timespent->id;
1752
            $this->id = $timespent->fk_element;
1753
            $this->timespent_date = $timespent->element_date;
1754
            $this->timespent_datehour   = $timespent->element_datehour;
1755
            $this->timespent_withhour   = $timespent->element_date_withhour;
1756
            $this->timespent_duration = $timespent->element_duration;
1757
            $this->timespent_fk_user    = $timespent->fk_user;
1758
            $this->timespent_fk_product = $timespent->fk_product;
1759
            $this->timespent_thm        = $timespent->thm; // hourly rate
1760
            $this->timespent_note = $timespent->note;
1761
1762
            return 1;
1763
        }
1764
1765
        return 0;
1766
    }
1767
1768
    /**
1769
     *  Load all records of time spent
1770
     *
1771
     *  @param  User        $userobj            User object
1772
     *  @param  string      $morewherefilter    Add more filter into where SQL request (must start with ' AND ...')
1773
     *  @return array|int                       Return integer <0 if KO, array of time spent if OK
1774
     */
1775
    public function fetchAllTimeSpent(User $userobj, $morewherefilter = '')
1776
    {
1777
        $arrayres = array();
1778
1779
        $sql = "SELECT";
1780
        $sql .= " s.rowid as socid,";
1781
        $sql .= " s.nom as thirdparty_name,";
1782
        $sql .= " s.email as thirdparty_email,";
1783
        $sql .= " ptt.rowid,";
1784
        $sql .= " ptt.fk_element as fk_task,";
1785
        $sql .= " ptt.element_date as task_date,";
1786
        $sql .= " ptt.element_datehour as task_datehour,";
1787
        $sql .= " ptt.element_date_withhour as task_date_withhour,";
1788
        $sql .= " ptt.element_duration as task_duration,";
1789
        $sql .= " ptt.fk_user,";
1790
        $sql .= " ptt.note,";
1791
        $sql .= " ptt.thm,";
1792
        $sql .= " pt.rowid as task_id,";
1793
        $sql .= " pt.ref as task_ref,";
1794
        $sql .= " pt.label as task_label,";
1795
        $sql .= " p.rowid as project_id,";
1796
        $sql .= " p.ref as project_ref,";
1797
        $sql .= " p.title as project_label,";
1798
        $sql .= " p.public as public";
1799
        $sql .= " FROM " . MAIN_DB_PREFIX . "element_time as ptt, " . MAIN_DB_PREFIX . "projet_task as pt, " . MAIN_DB_PREFIX . "projet as p";
1800
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON p.fk_soc = s.rowid";
1801
        $sql .= " WHERE ptt.fk_element = pt.rowid AND pt.fk_projet = p.rowid";
1802
        $sql .= " AND ptt.elementtype = 'task'";
1803
        $sql .= " AND ptt.fk_user = " . ((int) $userobj->id);
1804
        $sql .= " AND pt.entity IN (" . getEntity('project') . ")";
1805
        if ($morewherefilter) {
1806
            $sql .= $morewherefilter;
1807
        }
1808
1809
        dol_syslog(get_class($this) . "::fetchAllTimeSpent", LOG_DEBUG);
1810
        $resql = $this->db->query($sql);
1811
        if ($resql) {
1812
            $num = $this->db->num_rows($resql);
1813
1814
            $i = 0;
1815
            while ($i < $num) {
1816
                $obj = $this->db->fetch_object($resql);
1817
1818
                $newobj = new stdClass();
1819
1820
                $newobj->socid              = $obj->socid;
1821
                $newobj->thirdparty_name    = $obj->thirdparty_name;
1822
                $newobj->thirdparty_email   = $obj->thirdparty_email;
1823
1824
                $newobj->fk_project         = $obj->project_id;
1825
                $newobj->project_ref        = $obj->project_ref;
1826
                $newobj->project_label = $obj->project_label;
1827
                $newobj->public             = $obj->project_public;
1828
1829
                $newobj->fk_task            = $obj->task_id;
1830
                $newobj->task_ref = $obj->task_ref;
1831
                $newobj->task_label = $obj->task_label;
1832
1833
                $newobj->timespent_id = $obj->rowid;
1834
                $newobj->timespent_date = $this->db->jdate($obj->task_date);
1835
                $newobj->timespent_datehour = $this->db->jdate($obj->task_datehour);
1836
                $newobj->timespent_withhour = $obj->task_date_withhour;
1837
                $newobj->timespent_duration = $obj->task_duration;
1838
                $newobj->timespent_fk_user = $obj->fk_user;
1839
                $newobj->timespent_thm = $obj->thm; // hourly rate
1840
                $newobj->timespent_note = $obj->note;
1841
1842
                $arrayres[] = $newobj;
1843
1844
                $i++;
1845
            }
1846
1847
            $this->db->free($resql);
1848
        } else {
1849
            dol_print_error($this->db);
1850
            $this->error = "Error " . $this->db->lasterror();
1851
            return -1;
1852
        }
1853
1854
        return $arrayres;
1855
    }
1856
1857
    /**
1858
     *  Update time spent
1859
     *
1860
     *  @param  User    $user           User id
1861
     *  @param  int     $notrigger      0=launch triggers after, 1=disable triggers
1862
     *  @return int                     Return integer <0 if KO, >0 if OK
1863
     */
1864
    public function updateTimeSpent($user, $notrigger = 0)
1865
    {
1866
        global $conf, $langs;
1867
1868
        $ret = 0;
1869
1870
        // Check parameters
1871
        if ($this->timespent_date == '') {
1872
            $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentities("Date"));
1873
            return -1;
1874
        }
1875
        if (!($this->timespent_fk_user > 0)) {
1876
            $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentities("User"));
1877
            return -1;
1878
        }
1879
1880
        // Clean parameters
1881
        if (empty($this->timespent_datehour)) {
1882
            $this->timespent_datehour = $this->timespent_date;
1883
        }
1884
        if (isset($this->timespent_note)) {
1885
            $this->timespent_note = trim($this->timespent_note);
1886
        }
1887
1888
        if (getDolGlobalString('PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS')) {
1889
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/date.lib.php';
1890
            $restrictBefore = dol_time_plus_duree(dol_now(), - $conf->global->PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS, 'm');
1891
1892
            if ($this->timespent_date < $restrictBefore) {
1893
                $this->error = $langs->trans('TimeRecordingRestrictedToNMonthsBack', getDolGlobalString('PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS'));
1894
                $this->errors[] = $this->error;
1895
                return -1;
1896
            }
1897
        }
1898
1899
        $this->db->begin();
1900
1901
        $timespent = new TimeSpent($this->db);
1902
        $timespent->fetch($this->timespent_id);
1903
        $timespent->element_date = $this->timespent_date;
1904
        $timespent->element_datehour = $this->timespent_datehour;
1905
        $timespent->element_date_withhour = $this->timespent_withhour;
1906
        $timespent->element_duration = $this->timespent_duration;
1907
        $timespent->fk_user = $this->timespent_fk_user;
1908
        $timespent->fk_product = $this->timespent_fk_product;
1909
        $timespent->note = $this->timespent_note;
1910
        $timespent->invoice_id = $this->timespent_invoiceid;
1911
        $timespent->invoice_line_id = $this->timespent_invoicelineid;
1912
1913
        dol_syslog(get_class($this) . "::updateTimeSpent", LOG_DEBUG);
1914
        if ($timespent->update($user) > 0) {
1915
            if (!$notrigger) {
1916
                // Call trigger
1917
                $result = $this->call_trigger('TASK_TIMESPENT_MODIFY', $user);
1918
                if ($result < 0) {
1919
                    $this->db->rollback();
1920
                    $ret = -1;
1921
                } else {
1922
                    $ret = 1;
1923
                }
1924
                // End call triggers
1925
            } else {
1926
                $ret = 1;
1927
            }
1928
        } else {
1929
            $this->error = $this->db->lasterror();
1930
            $this->db->rollback();
1931
            $ret = -1;
1932
        }
1933
1934
        if ($ret == 1 && (($this->timespent_old_duration != $this->timespent_duration) || getDolGlobalString('TIMESPENT_ALWAYS_UPDATE_THM'))) {
1935
            if ($this->timespent_old_duration != $this->timespent_duration) {
1936
                // Recalculate amount of time spent for task and update denormalized field
1937
                $sql = "UPDATE " . MAIN_DB_PREFIX . "projet_task";
1938
                $sql .= " SET duration_effective = (SELECT SUM(element_duration) FROM " . MAIN_DB_PREFIX . "element_time as ptt where ptt.elementtype = 'task' AND ptt.fk_element = " . ((int) $this->id) . ")";
1939
                if (isset($this->progress)) {
1940
                    $sql .= ", progress = " . ((float) $this->progress); // Do not overwrite value if not provided
1941
                }
1942
                $sql .= " WHERE rowid = " . ((int) $this->id);
1943
1944
                dol_syslog(get_class($this) . "::updateTimeSpent", LOG_DEBUG);
1945
                if (!$this->db->query($sql)) {
1946
                    $this->error = $this->db->lasterror();
1947
                    $this->db->rollback();
1948
                    $ret = -2;
1949
                }
1950
            }
1951
1952
            // Update hourly rate of this time spent entry, but only if it was not set initially
1953
            $res_update = 1;
1954
            if (empty($timespent->thm) || getDolGlobalString('TIMESPENT_ALWAYS_UPDATE_THM')) {
1955
                $resql_thm_user = $this->db->query("SELECT thm FROM " . MAIN_DB_PREFIX . "user WHERE rowid = " . ((int) $timespent->fk_user));
1956
                if (!empty($resql_thm_user)) {
1957
                    $obj_thm_user = $this->db->fetch_object($resql_thm_user);
1958
                    $timespent->thm = $obj_thm_user->thm;
1959
                }
1960
                $res_update = $timespent->update($user);
1961
            }
1962
1963
            dol_syslog(get_class($this) . "::updateTimeSpent", LOG_DEBUG);
1964
            if ($res_update <= 0) {
1965
                $this->error = $this->db->lasterror();
1966
                $ret = -2;
1967
            }
1968
        }
1969
1970
        if ($ret >= 0) {
1971
            $this->db->commit();
1972
        }
1973
        return $ret;
1974
    }
1975
1976
    /**
1977
     *  Delete time spent
1978
     *
1979
     *  @param  User    $user           User that delete
1980
     *  @param  int     $notrigger      0=launch triggers after, 1=disable triggers
1981
     *  @return int                     Return integer <0 if KO, >0 if OK
1982
     */
1983
    public function delTimeSpent($user, $notrigger = 0)
1984
    {
1985
        global $conf, $langs;
1986
1987
        $error = 0;
1988
1989
        if (getDolGlobalString('PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS')) {
1990
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/date.lib.php';
1991
            $restrictBefore = dol_time_plus_duree(dol_now(), - $conf->global->PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS, 'm');
1992
1993
            if ($this->timespent_date < $restrictBefore) {
1994
                $this->error = $langs->trans('TimeRecordingRestrictedToNMonthsBack', getDolGlobalString('PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS'));
1995
                $this->errors[] = $this->error;
1996
                return -1;
1997
            }
1998
        }
1999
2000
        $this->db->begin();
2001
2002
        if (!$notrigger) {
2003
            // Call trigger
2004
            $result = $this->call_trigger('TASK_TIMESPENT_DELETE', $user);
2005
            if ($result < 0) {
2006
                $error++;
2007
            }
2008
            // End call triggers
2009
        }
2010
2011
        if (!$error) {
2012
            $timespent = new TimeSpent($this->db);
2013
            $timespent->fetch($this->timespent_id);
2014
2015
            $res_del = $timespent->delete($user);
2016
2017
            if ($res_del < 0) {
2018
                $error++;
2019
                $this->errors[] = "Error " . $this->db->lasterror();
2020
            }
2021
        }
2022
2023
        if (!$error) {
2024
            $sql = "UPDATE " . MAIN_DB_PREFIX . "projet_task";
2025
            $sql .= " SET duration_effective = duration_effective - " . $this->db->escape($this->timespent_duration ? $this->timespent_duration : 0);
2026
            $sql .= " WHERE rowid = " . ((int) $this->id);
2027
2028
            dol_syslog(get_class($this) . "::delTimeSpent", LOG_DEBUG);
2029
            if ($this->db->query($sql)) {
2030
                $result = 0;
2031
            } else {
2032
                $this->error = $this->db->lasterror();
2033
                $result = -2;
2034
            }
2035
        }
2036
2037
        // Commit or rollback
2038
        if ($error) {
2039
            foreach ($this->errors as $errmsg) {
2040
                dol_syslog(get_class($this) . "::delTimeSpent " . $errmsg, LOG_ERR);
2041
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2042
            }
2043
            $this->db->rollback();
2044
            return -1 * $error;
2045
        } else {
2046
            $this->db->commit();
2047
            return 1;
2048
        }
2049
    }
2050
2051
    /** Load an object from its id and create a new one in database
2052
     *
2053
     *  @param  User    $user                   User making the clone
2054
     *  @param  int     $fromid                 Id of object to clone
2055
     *  @param  int     $project_id             Id of project to attach clone task
2056
     *  @param  int     $parent_task_id         Id of task to attach clone task
2057
     *  @param  bool    $clone_change_dt        recalculate date of task regarding new project start date
2058
     *  @param  bool    $clone_affectation      clone affectation of project
2059
     *  @param  bool    $clone_time             clone time of project
2060
     *  @param  bool    $clone_file             clone file of project
2061
     *  @param  bool    $clone_note             clone note of project
2062
     *  @param  bool    $clone_prog             clone progress of project
2063
     *  @return int                             New id of clone
2064
     */
2065
    public function createFromClone(User $user, $fromid, $project_id, $parent_task_id, $clone_change_dt = false, $clone_affectation = false, $clone_time = false, $clone_file = false, $clone_note = false, $clone_prog = false)
2066
    {
2067
        global $langs, $conf;
2068
2069
        $error = 0;
2070
2071
        //Use 00:00 of today if time is use on task.
2072
        $now = dol_mktime(0, 0, 0, dol_print_date(dol_now(), '%m'), dol_print_date(dol_now(), '%d'), dol_print_date(dol_now(), '%Y'));
2073
2074
        $datec = $now;
2075
2076
        $clone_task = new Task($this->db);
2077
        $origin_task = new Task($this->db);
2078
2079
        $clone_task->context['createfromclone'] = 'createfromclone';
2080
2081
        $this->db->begin();
2082
2083
        // Load source object
2084
        $clone_task->fetch($fromid);
2085
        $clone_task->fetch_optionals();
2086
        //var_dump($clone_task->array_options);exit;
2087
2088
        $origin_task->fetch($fromid);
2089
2090
        $defaultref = '';
2091
        $obj = !getDolGlobalString('PROJECT_TASK_ADDON') ? 'mod_task_simple' : $conf->global->PROJECT_TASK_ADDON;
2092
        if (getDolGlobalString('PROJECT_TASK_ADDON') && is_readable(DOL_DOCUMENT_ROOT . "/core/modules/project/task/" . getDolGlobalString('PROJECT_TASK_ADDON') . ".php")) {
2093
            require_once DOL_DOCUMENT_ROOT . "/core/modules/project/task/" . getDolGlobalString('PROJECT_TASK_ADDON') . '.php';
2094
            $modTask = new $obj();
2095
            $defaultref = $modTask->getNextValue(0, $clone_task);
2096
        }
2097
2098
        $ori_project_id                 = $clone_task->fk_project;
2099
2100
        $clone_task->id                 = 0;
2101
        $clone_task->ref                = $defaultref;
2102
        $clone_task->fk_project = $project_id;
2103
        $clone_task->fk_task_parent = $parent_task_id;
2104
        $clone_task->date_c = $datec;
2105
        $clone_task->planned_workload = $origin_task->planned_workload;
2106
        $clone_task->rang = $origin_task->rang;
2107
        $clone_task->priority = $origin_task->priority;
2108
2109
        //Manage Task Date
2110
        if ($clone_change_dt) {
2111
            $projectstatic = new Project($this->db);
2112
            $projectstatic->fetch($ori_project_id);
2113
2114
            //Origin project start date
2115
            $orign_project_dt_start = $projectstatic->date_start;
2116
2117
            //Calculate new task start date with difference between origin proj start date and origin task start date
2118
            if (!empty($clone_task->date_start)) {
2119
                $clone_task->date_start = $now + $clone_task->date_start - $orign_project_dt_start;
2120
            }
2121
2122
            //Calculate new task end date with difference between origin proj end date and origin task end date
2123
            if (!empty($clone_task->date_end)) {
2124
                $clone_task->date_end = $now + $clone_task->date_end - $orign_project_dt_start;
2125
            }
2126
        }
2127
2128
        if (!$clone_prog) {
2129
            $clone_task->progress = 0;
2130
        }
2131
2132
        // Create clone
2133
        $result = $clone_task->create($user);
2134
2135
        // Other options
2136
        if ($result < 0) {
2137
            $this->error = $clone_task->error;
2138
            $error++;
2139
        }
2140
        // End
2141
        if ($error) {
2142
            $clone_task_id = 0;  // For static tool check
2143
        } else {
2144
            $clone_task_id = $clone_task->id;
2145
            $clone_task_ref = $clone_task->ref;
2146
2147
            //Note Update
2148
            if (!$clone_note) {
2149
                $clone_task->note_private = '';
2150
                $clone_task->note_public = '';
2151
            } else {
2152
                $this->db->begin();
2153
                $res = $clone_task->update_note(dol_html_entity_decode($clone_task->note_public, ENT_QUOTES | ENT_HTML5), '_public');
2154
                if ($res < 0) {
2155
                    $this->error .= $clone_task->error;
2156
                    $error++;
2157
                    $this->db->rollback();
2158
                } else {
2159
                    $this->db->commit();
2160
                }
2161
2162
                $this->db->begin();
2163
                $res = $clone_task->update_note(dol_html_entity_decode($clone_task->note_private, ENT_QUOTES | ENT_HTML5), '_private');
2164
                if ($res < 0) {
2165
                    $this->error .= $clone_task->error;
2166
                    $error++;
2167
                    $this->db->rollback();
2168
                } else {
2169
                    $this->db->commit();
2170
                }
2171
            }
2172
2173
            //Duplicate file
2174
            if ($clone_file) {
2175
                require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
2176
2177
                //retrieve project origin ref to know folder to copy
2178
                $projectstatic = new Project($this->db);
2179
                $projectstatic->fetch($ori_project_id);
2180
                $ori_project_ref = $projectstatic->ref;
2181
2182
                if ($ori_project_id != $project_id) {
2183
                    $projectstatic->fetch($project_id);
2184
                    $clone_project_ref = $projectstatic->ref;
2185
                } else {
2186
                    $clone_project_ref = $ori_project_ref;
2187
                }
2188
2189
                $clone_task_dir = $conf->project->dir_output . "/" . dol_sanitizeFileName($clone_project_ref) . "/" . dol_sanitizeFileName($clone_task_ref);
2190
                $ori_task_dir = $conf->project->dir_output . "/" . dol_sanitizeFileName($ori_project_ref) . "/" . dol_sanitizeFileName($fromid);
2191
2192
                $filearray = dol_dir_list($ori_task_dir, "files", 0, '', '(\.meta|_preview.*\.png)$', '', SORT_ASC, 1);
2193
                foreach ($filearray as $key => $file) {
2194
                    if (!file_exists($clone_task_dir)) {
2195
                        if (dol_mkdir($clone_task_dir) < 0) {
2196
                            $this->error .= $langs->trans('ErrorInternalErrorDetected') . ':dol_mkdir';
2197
                            $error++;
2198
                        }
2199
                    }
2200
2201
                    $rescopy = dol_copy($ori_task_dir . '/' . $file['name'], $clone_task_dir . '/' . $file['name'], 0, 1);
2202
                    if (is_numeric($rescopy) && $rescopy < 0) {
2203
                        $this->error .= $langs->trans("ErrorFailToCopyFile", $ori_task_dir . '/' . $file['name'], $clone_task_dir . '/' . $file['name']);
2204
                        $error++;
2205
                    }
2206
                }
2207
            }
2208
2209
            // clone affectation
2210
            if ($clone_affectation) {
2211
                $origin_task = new Task($this->db);
2212
                $origin_task->fetch($fromid);
2213
2214
                foreach (array('internal', 'external') as $source) {
2215
                    $tab = $origin_task->liste_contact(-1, $source);
2216
                    $num = count($tab);
2217
                    $i = 0;
2218
                    while ($i < $num) {
2219
                        $clone_task->add_contact($tab[$i]['id'], $tab[$i]['code'], $tab[$i]['source']);
2220
                        if ($clone_task->error == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
2221
                            $langs->load("errors");
2222
                            $this->error .= $langs->trans("ErrorThisContactIsAlreadyDefinedAsThisType");
2223
                            $error++;
2224
                        } else {
2225
                            if ($clone_task->error != '') {
2226
                                $this->error .= $clone_task->error;
2227
                                $error++;
2228
                            }
2229
                        }
2230
                        $i++;
2231
                    }
2232
                }
2233
            }
2234
2235
            if ($clone_time) {
2236
                //TODO clone time of affectation
2237
            }
2238
        }
2239
2240
        unset($clone_task->context['createfromclone']);
2241
2242
        if (!$error) {
2243
            $this->db->commit();
2244
            return $clone_task_id;
2245
        } else {
2246
            $this->db->rollback();
2247
            dol_syslog(get_class($this) . "::createFromClone nbError: " . $error . " error : " . $this->error, LOG_ERR);
2248
            return -1;
2249
        }
2250
    }
2251
2252
2253
    /**
2254
     *  Return status label of object
2255
     *
2256
     *  @param  integer $mode       0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto
2257
     *  @return string              Label
2258
     */
2259
    public function getLibStatut($mode = 0)
2260
    {
2261
        return $this->LibStatut($this->status, $mode);
2262
    }
2263
2264
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2265
    /**
2266
     *  Return status label for an object
2267
     *
2268
     *  @param  int         $status     Id status
2269
     *  @param  integer     $mode       0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto
2270
     *  @return string                  Label
2271
     */
2272
    public function LibStatut($status, $mode = 0)
2273
    {
2274
		// phpcs:enable
2275
        global $langs;
2276
2277
        // list of Statut of the task
2278
        $this->labelStatus[0] = 'Draft';
2279
        $this->labelStatus[1] = 'ToDo';
2280
        $this->labelStatus[2] = 'Running';
2281
        $this->labelStatus[3] = 'Finish';
2282
        $this->labelStatus[4] = 'Transfered';
2283
        $this->labelStatusShort[0] = 'Draft';
2284
        $this->labelStatusShort[1] = 'ToDo';
2285
        $this->labelStatusShort[2] = 'Running';
2286
        $this->labelStatusShort[3] = 'Completed';
2287
        $this->labelStatusShort[4] = 'Transfered';
2288
2289
        if ($mode == 0) {
2290
            return $langs->trans($this->labelStatus[$status]);
2291
        } elseif ($mode == 1) {
2292
            return $langs->trans($this->labelStatusShort[$status]);
2293
        } elseif ($mode == 2) {
2294
            if ($status == 0) {
2295
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut0') . ' ' . $langs->trans($this->labelStatusShort[$status]);
2296
            } elseif ($status == 1) {
2297
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut1') . ' ' . $langs->trans($this->labelStatusShort[$status]);
2298
            } elseif ($status == 2) {
2299
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut3') . ' ' . $langs->trans($this->labelStatusShort[$status]);
2300
            } elseif ($status == 3) {
2301
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut6') . ' ' . $langs->trans($this->labelStatusShort[$status]);
2302
            } elseif ($status == 4) {
2303
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut6') . ' ' . $langs->trans($this->labelStatusShort[$status]);
2304
            } elseif ($status == 5) {
2305
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut5') . ' ' . $langs->trans($this->labelStatusShort[$status]);
2306
            }
2307
        } elseif ($mode == 3) {
2308
            if ($status == 0) {
2309
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut0');
2310
            } elseif ($status == 1) {
2311
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut1');
2312
            } elseif ($status == 2) {
2313
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut3');
2314
            } elseif ($status == 3) {
2315
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut6');
2316
            } elseif ($status == 4) {
2317
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut6');
2318
            } elseif ($status == 5) {
2319
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut5');
2320
            }
2321
        } elseif ($mode == 4) {
2322
            if ($status == 0) {
2323
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut0') . ' ' . $langs->trans($this->labelStatus[$status]);
2324
            } elseif ($status == 1) {
2325
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut1') . ' ' . $langs->trans($this->labelStatus[$status]);
2326
            } elseif ($status == 2) {
2327
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut3') . ' ' . $langs->trans($this->labelStatus[$status]);
2328
            } elseif ($status == 3) {
2329
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut6') . ' ' . $langs->trans($this->labelStatus[$status]);
2330
            } elseif ($status == 4) {
2331
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut6') . ' ' . $langs->trans($this->labelStatus[$status]);
2332
            } elseif ($status == 5) {
2333
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut5') . ' ' . $langs->trans($this->labelStatus[$status]);
2334
            }
2335
        } elseif ($mode == 5) {
2336
            /*if ($status==0) return $langs->trans($this->labelStatusShort[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut0');
2337
            elseif ($status==1) return $langs->trans($this->labelStatusShort[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut1');
2338
            elseif ($status==2) return $langs->trans($this->labelStatusShort[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut3');
2339
            elseif ($status==3) return $langs->trans($this->labelStatusShort[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut6');
2340
            elseif ($status==4) return $langs->trans($this->labelStatusShort[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut6');
2341
            elseif ($status==5) return $langs->trans($this->labelStatusShort[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut5');
2342
            */
2343
            //else return $this->progress.' %';
2344
            return '&nbsp;';
2345
        } elseif ($mode == 6) {
2346
            /*if ($status==0) return $langs->trans($this->labelStatus[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut0');
2347
            elseif ($status==1) return $langs->trans($this->labelStatus[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut1');
2348
            elseif ($status==2) return $langs->trans($this->labelStatus[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut3');
2349
            elseif ($status==3) return $langs->trans($this->labelStatus[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut6');
2350
            elseif ($status==4) return $langs->trans($this->labelStatus[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut6');
2351
            elseif ($status==5) return $langs->trans($this->labelStatus[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut5');
2352
            */
2353
            //else return $this->progress.' %';
2354
            return '&nbsp;';
2355
        }
2356
        return "";
2357
    }
2358
2359
    /**
2360
     *  Create an intervention document on disk using template defined into PROJECT_TASK_ADDON_PDF
2361
     *
2362
     *  @param  string      $modele         force le modele a utiliser ('' par default)
2363
     *  @param  Translate   $outputlangs    object lang a utiliser pour traduction
2364
     *  @param  int         $hidedetails    Hide details of lines
2365
     *  @param  int         $hidedesc       Hide description
2366
     *  @param  int         $hideref        Hide ref
2367
     *  @return int                         0 if KO, 1 if OK
2368
     */
2369
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
2370
    {
2371
        $outputlangs->load("projects");
2372
2373
        if (!dol_strlen($modele)) {
2374
            $modele = 'nodefault';
2375
2376
            if (!empty($this->model_pdf)) {
2377
                $modele = $this->model_pdf;
2378
            } elseif (getDolGlobalString('PROJECT_TASK_ADDON_PDF')) {
2379
                $modele = getDolGlobalString('PROJECT_TASK_ADDON_PDF');
2380
            }
2381
        }
2382
2383
        $modelpath = "core/modules/project/task/doc/";
2384
2385
        return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
2386
    }
2387
2388
2389
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2390
    /**
2391
     * Load indicators for dashboard (this->nbtodo and this->nbtodolate)
2392
     *
2393
     * @param   User    $user   Object user
2394
     * @return WorkboardResponse|int Return integer <0 if KO, WorkboardResponse if OK
2395
     */
2396
    public function load_board($user)
2397
    {
2398
		// phpcs:enable
2399
        global $conf, $langs;
2400
2401
        // For external user, no check is done on company because readability is managed by public status of project and assignment.
2402
        //$socid = $user->socid;
2403
        $socid = 0;
2404
2405
        $projectstatic = new Project($this->db);
2406
        $projectsListId = $projectstatic->getProjectsAuthorizedForUser($user, 0, 1, $socid);
2407
2408
        // List of tasks (does not care about permissions. Filtering will be done later)
2409
        $sql = "SELECT p.rowid as projectid, p.fk_statut as projectstatus,";
2410
        $sql .= " t.rowid as taskid, t.progress as progress, t.fk_statut as status,";
2411
        $sql .= " t.dateo as date_start, t.datee as date_end";
2412
        $sql .= " FROM " . MAIN_DB_PREFIX . "projet as p";
2413
        //$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s on p.fk_soc = s.rowid";
2414
        //if (! $user->rights->societe->client->voir && ! $socid) $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON sc.fk_soc = s.rowid";
2415
        $sql .= ", " . MAIN_DB_PREFIX . "projet_task as t";
2416
        $sql .= " WHERE p.entity IN (" . getEntity('project', 0) . ')';
2417
        $sql .= " AND p.fk_statut = 1";
2418
        $sql .= " AND t.fk_projet = p.rowid";
2419
        $sql .= " AND (t.progress IS NULL OR t.progress < 100)"; // tasks to do
2420
        if (!$user->hasRight('projet', 'all', 'lire')) {
2421
            $sql .= " AND p.rowid IN (" . $this->db->sanitize($projectsListId) . ")";
2422
        }
2423
        // No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser
2424
        //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).")";
2425
        // No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser
2426
        // 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))";
2427
2428
        //print $sql;
2429
        $resql = $this->db->query($sql);
2430
        if ($resql) {
2431
            $task_static = new Task($this->db);
2432
2433
            $response = new WorkboardResponse();
2434
            $response->warning_delay = $conf->project->task->warning_delay / 60 / 60 / 24;
2435
            $response->label = $langs->trans("OpenedTasks");
2436
            if ($user->hasRight("projet", "all", "lire")) {
2437
                $response->url = constant('BASE_URL') . '/projet/tasks/list.php?mainmenu=project';
2438
            } else {
2439
                $response->url = constant('BASE_URL') . '/projet/tasks/list.php?mode=mine&amp;mainmenu=project';
2440
            }
2441
            $response->img = img_object('', "task");
2442
2443
            // This assignment in condition is not a bug. It allows walking the results.
2444
            while ($obj = $this->db->fetch_object($resql)) {
2445
                $response->nbtodo++;
2446
2447
                $task_static->projectstatus = $obj->projectstatus;
2448
                $task_static->progress = $obj->progress;
2449
                $task_static->fk_statut = $obj->status;
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Projet\Classes\Task::$fk_statut has been deprecated: use status instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2449
                /** @scrutinizer ignore-deprecated */ $task_static->fk_statut = $obj->status;

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
2450
                $task_static->status = $obj->status;
2451
                $task_static->date_start = $this->db->jdate($obj->date_start);
2452
                $task_static->date_end = $this->db->jdate($obj->date_end);
2453
2454
                if ($task_static->hasDelay()) {
2455
                    $response->nbtodolate++;
2456
                }
2457
            }
2458
2459
            return $response;
2460
        } else {
2461
            $this->error = $this->db->error();
2462
            return -1;
2463
        }
2464
    }
2465
2466
2467
    /**
2468
     *      Load indicators this->nb for state board
2469
     *
2470
     *      @return     int         Return integer <0 if ko, >0 if ok
2471
     */
2472
    public function loadStateBoard()
2473
    {
2474
        global $user;
2475
2476
        $mine = 0;
2477
        $socid = $user->socid;
2478
2479
        $projectstatic = new Project($this->db);
2480
        $projectsListId = $projectstatic->getProjectsAuthorizedForUser($user, $mine, 1, $socid);
2481
2482
        // List of tasks (does not care about permissions. Filtering will be done later)
2483
        $sql = "SELECT count(p.rowid) as nb";
2484
        $sql .= " FROM " . MAIN_DB_PREFIX . "projet as p";
2485
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s on p.fk_soc = s.rowid";
2486
        if (!$user->hasRight('societe', 'client', 'voir')) {
2487
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON sc.fk_soc = s.rowid";
2488
        }
2489
        $sql .= ", " . MAIN_DB_PREFIX . "projet_task as t";
2490
        $sql .= " WHERE p.entity IN (" . getEntity('project', 0) . ')';
2491
        $sql .= " AND t.fk_projet = p.rowid"; // tasks to do
2492
        if ($mine || !$user->hasRight('projet', 'all', 'lire')) {
2493
            $sql .= " AND p.rowid IN (" . $this->db->sanitize($projectsListId) . ")";
2494
        }
2495
        // No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser
2496
        //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).")";
2497
        if ($socid) {
2498
            $sql .= "  AND (p.fk_soc IS NULL OR p.fk_soc = 0 OR p.fk_soc = " . ((int) $socid) . ")";
2499
        }
2500
        if (!$user->hasRight('societe', 'client', 'voir')) {
2501
            $sql .= " AND ((s.rowid = sc.fk_soc AND sc.fk_user = " . ((int) $user->id) . ") OR (s.rowid IS NULL))";
2502
        }
2503
2504
        $resql = $this->db->query($sql);
2505
        if ($resql) {
2506
            // This assignment in condition is not a bug. It allows walking the results.
2507
            while ($obj = $this->db->fetch_object($resql)) {
2508
                $this->nb["tasks"] = $obj->nb;
2509
            }
2510
            $this->db->free($resql);
2511
            return 1;
2512
        } else {
2513
            dol_print_error($this->db);
2514
            $this->error = $this->db->error();
2515
            return -1;
2516
        }
2517
    }
2518
2519
    /**
2520
     * Is the task delayed?
2521
     *
2522
     * @return bool
2523
     */
2524
    public function hasDelay()
2525
    {
2526
        global $conf;
2527
2528
        if (!($this->progress >= 0 && $this->progress < 100)) {
2529
            return false;
2530
        }
2531
2532
        $now = dol_now();
2533
2534
        $datetouse = ($this->date_end > 0) ? $this->date_end : ((isset($this->datee) && $this->datee > 0) ? $this->datee : 0);
0 ignored issues
show
Deprecated Code introduced by
The property Dolibarr\Code\Projet\Classes\Task::$datee has been deprecated: Use date_end instead ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

2534
        $datetouse = ($this->date_end > 0) ? $this->date_end : ((isset(/** @scrutinizer ignore-deprecated */ $this->datee) && $this->datee > 0) ? $this->datee : 0);

This property has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the property will be removed from the class and what other property to use instead.

Loading history...
2535
2536
        return ($datetouse > 0 && ($datetouse < ($now - $conf->project->task->warning_delay)));
2537
    }
2538
2539
    /**
2540
     *  Return clicable link of object (with eventually picto)
2541
     *
2542
     *  @param      string      $option                 Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
2543
     *  @param      array       $arraydata              Array of data
2544
     *  @return     string                              HTML Code for Kanban thumb.
2545
     */
2546
    public function getKanbanView($option = '', $arraydata = null)
2547
    {
2548
        $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2549
2550
        $return = '<div class="box-flex-item box-flex-grow-zero">';
2551
        $return .= '<div class="info-box info-box-sm info-box-kanban">';
2552
        $return .= '<span class="info-box-icon bg-infobox-action">';
2553
        $return .= img_picto('', $this->picto);
2554
        //$return .= '<i class="fa fa-dol-action"></i>'; // Can be image
2555
        $return .= '</span>';
2556
        $return .= '<div class="info-box-content">';
2557
        $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . (method_exists($this, 'getNomUrl') ? $this->getNomUrl(1) : $this->ref) . '</span>';
2558
        if ($selected >= 0) {
2559
            $return .= '<input id="cb' . $this->id . '" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="' . $this->id . '"' . ($selected ? ' checked="checked"' : '') . '>';
2560
        }
2561
        if (!empty($arraydata['projectlink'])) {
2562
            //$tmpproject = $arraydata['project'];
2563
            //$return .= '<br><span class="info-box-status ">'.$tmpproject->getNomProject().'</span>';
2564
            $return .= '<br><span class="info-box-status ">' . $arraydata['projectlink'] . '</span>';
2565
        }
2566
        if (property_exists($this, 'budget_amount')) {
2567
            //$return .= '<br><span class="info-box-label amount">'.$langs->trans("Budget").' : '.price($this->budget_amount, 0, $langs, 1, 0, 0, $conf->currency).'</span>';
2568
        }
2569
        if (property_exists($this, 'duration_effective')) {
2570
            $return .= '<br><div class="info-box-label progressinkanban paddingtop">' . getTaskProgressView($this, false, true) . '</div>';
2571
        }
2572
        $return .= '</div>';
2573
        $return .= '</div>';
2574
        $return .= '</div>';
2575
2576
        return $return;
2577
    }
2578
2579
    /**
2580
     *    Merge a task with another one, deleting the given task.
2581
     *    The task given in parameter will be removed.
2582
     *
2583
     *    @param    int     $task_origin_id     Task to merge the data from
2584
     *    @return   int                         -1 if error
2585
     */
2586
    public function mergeTask($task_origin_id)
2587
    {
2588
        global $langs, $hookmanager, $user, $action;
2589
2590
        $error = 0;
2591
        $task_origin = new Task($this->db);     // The thirdparty that we will delete
2592
2593
        dol_syslog("mergeTask merge task id=" . $task_origin_id . " (will be deleted) into the task id=" . $this->id);
2594
2595
        $langs->load('error');
2596
2597
        if (!$error && $task_origin->fetch($task_origin_id) < 1) {
2598
            $this->error = $langs->trans('ErrorRecordNotFound');
2599
            $error++;
2600
        }
2601
2602
        if (!$error) {
2603
            $this->db->begin();
2604
2605
            // Recopy some data
2606
            $listofproperties = array(
2607
                'label', 'description', 'duration_effective', 'planned_workload', 'datec', 'date_start',
2608
                'date_end', 'fk_user_creat', 'fk_user_valid', 'fk_statut', 'progress', 'budget_amount',
2609
                'priority', 'rang', 'fk_projet', 'fk_task_parent'
2610
            );
2611
            foreach ($listofproperties as $property) {
2612
                if (empty($this->$property)) {
2613
                    $this->$property = $task_origin->$property;
2614
                }
2615
            }
2616
2617
            // Concat some data
2618
            $listofproperties = array(
2619
                'note_public', 'note_private'
2620
            );
2621
            foreach ($listofproperties as $property) {
2622
                $this->$property = dol_concatdesc($this->$property, $task_origin->$property);
2623
            }
2624
2625
            // Merge extrafields
2626
            if (is_array($task_origin->array_options)) {
2627
                foreach ($task_origin->array_options as $key => $val) {
2628
                    if (empty($this->array_options[$key])) {
2629
                        $this->array_options[$key] = $val;
2630
                    }
2631
                }
2632
            }
2633
2634
            // Update
2635
            $result = $this->update($user);
2636
2637
            if ($result < 0) {
2638
                $error++;
2639
            }
2640
2641
            // Merge time spent
2642
            if (!$error) {
2643
                $result = $this->mergeTimeSpentTask($task_origin_id, $this->id);
2644
                if ($result != true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison !== instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
2645
                    $error++;
2646
                }
2647
            }
2648
2649
            // Merge contacts
2650
            if (!$error) {
2651
                $result = $this->mergeContactTask($task_origin_id, $this->id);
2652
                if ($result != true) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison !== instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
2653
                    $error++;
2654
                }
2655
            }
2656
2657
            // External modules should update their ones too
2658
            if (!$error) {
2659
                $parameters = array('task_origin' => $task_origin->id, 'task_dest' => $this->id);
2660
                $reshook = $hookmanager->executeHooks('replaceThirdparty', $parameters, $this, $action);
2661
2662
                if ($reshook < 0) {
2663
                    $this->error = $hookmanager->error;
2664
                    $this->errors = $hookmanager->errors;
2665
                    $error++;
2666
                }
2667
            }
2668
2669
2670
            if (!$error) {
2671
                $this->context = array('merge' => 1, 'mergefromid' => $task_origin->id, 'mergefromref' => $task_origin->ref);
2672
2673
                // Call trigger
2674
                $result = $this->call_trigger('TASK_MODIFY', $user);
2675
                if ($result < 0) {
2676
                    $error++;
2677
                }
2678
                // End call triggers
2679
            }
2680
2681
            if (!$error) {
2682
                // We finally remove the old task
2683
                if ($task_origin->delete($user) < 1) {
2684
                    $this->error = $task_origin->error;
2685
                    $this->errors = $task_origin->errors;
2686
                    $error++;
2687
                }
2688
            }
2689
2690
            if (!$error) {
2691
                $this->db->commit();
2692
                return 0;
2693
            } else {
2694
                $langs->load("errors");
2695
                $this->error = $langs->trans('ErrorsTaskMerge');
2696
                $this->db->rollback();
2697
                return -1;
2698
            }
2699
        }
2700
2701
        return -1;
2702
    }
2703
}
2704