Task::mergeTask()   D
last analyzed

Complexity

Conditions 22

Size

Total Lines 116
Code Lines 62

Duplication

Lines 0
Ratio 0 %

Importance

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

409
                /** @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...
410
                $this->status               = $obj->status;
411
                $this->progress             = $obj->progress;
412
                $this->budget_amount        = $obj->budget_amount;
413
                $this->priority             = $obj->priority;
414
                $this->note_private = $obj->note_private;
415
                $this->note_public = $obj->note_public;
416
                $this->rang = $obj->rang;
417
418
                if (!empty($loadparentdata)) {
419
                    $this->task_parent_ref      = $obj->task_parent_ref;
420
                    $this->task_parent_position = $obj->task_parent_position;
421
                }
422
423
                // Retrieve all extrafield
424
                $this->fetch_optionals();
425
            }
426
427
            $this->db->free($resql);
428
429
            if ($num_rows) {
430
                return 1;
431
            } else {
432
                return 0;
433
            }
434
        } else {
435
            $this->error = "Error " . $this->db->lasterror();
436
            return -1;
437
        }
438
    }
439
440
441
    /**
442
     *  Update database
443
     *
444
     *  @param  User    $user           User that modify
445
     *  @param  int     $notrigger      0=launch triggers after, 1=disable triggers
446
     *  @return int                     Return integer <=0 if KO, >0 if OK
447
     */
448
    public function update($user = null, $notrigger = 0)
449
    {
450
        global $conf, $langs;
451
        $error = 0;
452
453
        // Clean parameters
454
        if (isset($this->fk_project)) {
455
            $this->fk_project = (int) $this->fk_project;
456
        }
457
        if (isset($this->ref)) {
458
            $this->ref = trim($this->ref);
459
        }
460
        if (isset($this->fk_task_parent)) {
461
            $this->fk_task_parent = (int) $this->fk_task_parent;
462
        }
463
        if (isset($this->label)) {
464
            $this->label = trim($this->label);
465
        }
466
        if (isset($this->description)) {
467
            $this->description = trim($this->description);
468
        }
469
        if (isset($this->note_public)) {
470
            $this->note_public = trim($this->note_public);
471
        }
472
        if (isset($this->note_private)) {
473
            $this->note_private = trim($this->note_private);
474
        }
475
        if (isset($this->duration_effective)) {
476
            $this->duration_effective = trim($this->duration_effective);
477
        }
478
        if (isset($this->planned_workload)) {
479
            $this->planned_workload = trim($this->planned_workload);
480
        }
481
        if (isset($this->budget_amount)) {
482
            $this->budget_amount = (float) $this->budget_amount;
483
        }
484
485
        if (!empty($this->date_start) && !empty($this->date_end) && $this->date_start > $this->date_end) {
486
            $this->errors[] = $langs->trans('StartDateCannotBeAfterEndDate');
487
            return -1;
488
        }
489
490
        // Check parameters
491
        // Put here code to add control on parameters values
492
493
        // Update request
494
        $sql = "UPDATE " . MAIN_DB_PREFIX . "projet_task SET";
495
        $sql .= " fk_projet=" . (isset($this->fk_project) ? $this->fk_project : "null") . ",";
496
        $sql .= " ref=" . (isset($this->ref) ? "'" . $this->db->escape($this->ref) . "'" : "'" . $this->db->escape($this->id) . "'") . ",";
497
        $sql .= " fk_task_parent=" . (isset($this->fk_task_parent) ? $this->fk_task_parent : "null") . ",";
498
        $sql .= " label=" . (isset($this->label) ? "'" . $this->db->escape($this->label) . "'" : "null") . ",";
499
        $sql .= " description=" . (isset($this->description) ? "'" . $this->db->escape($this->description) . "'" : "null") . ",";
500
        $sql .= " note_public=" . (isset($this->note_public) ? "'" . $this->db->escape($this->note_public) . "'" : "null") . ",";
501
        $sql .= " note_private=" . (isset($this->note_private) ? "'" . $this->db->escape($this->note_private) . "'" : "null") . ",";
502
        $sql .= " duration_effective=" . (isset($this->duration_effective) ? $this->duration_effective : "null") . ",";
503
        $sql .= " planned_workload=" . ((isset($this->planned_workload) && $this->planned_workload != '') ? $this->planned_workload : "null") . ",";
504
        $sql .= " dateo=" . ($this->date_start != '' ? "'" . $this->db->idate($this->date_start) . "'" : 'null') . ",";
505
        $sql .= " datee=" . ($this->date_end != '' ? "'" . $this->db->idate($this->date_end) . "'" : 'null') . ",";
506
        $sql .= " progress=" . (($this->progress != '' && $this->progress >= 0) ? $this->progress : 'null') . ",";
507
        $sql .= " budget_amount=" . (($this->budget_amount != '' && $this->budget_amount >= 0) ? $this->budget_amount : 'null') . ",";
508
        $sql .= " rang=" . ((!empty($this->rang)) ? ((int) $this->rang) : "0") . ",";
509
        $sql .= " priority=" . ((!empty($this->priority)) ? ((int) $this->priority) : "0");
510
        $sql .= " WHERE rowid=" . ((int) $this->id);
511
512
        $this->db->begin();
513
514
        dol_syslog(get_only_class($this) . "::update", LOG_DEBUG);
515
        $resql = $this->db->query($sql);
516
        if (!$resql) {
517
            $error++;
518
            $this->errors[] = "Error " . $this->db->lasterror();
519
        }
520
521
        // Update extrafield
522
        if (!$error) {
523
            $result = $this->insertExtraFields();
524
            if ($result < 0) {
525
                $error++;
526
            }
527
        }
528
529
        if (!$error && getDolGlobalString('PROJECT_CLASSIFY_CLOSED_WHEN_ALL_TASKS_DONE')) {
530
            // Close the parent project if it is open (validated) and its tasks are 100% completed
531
            $project = new Project($this->db);
532
            if ($project->fetch($this->fk_project) > 0) {
533
                if ($project->statut == Project::STATUS_VALIDATED) {
534
                    $project->getLinesArray(null); // this method does not return <= 0 if fails
535
                    $projectCompleted = array_reduce(
536
                        $project->lines,
537
                        /**
538
                         * @param bool $allTasksCompleted
539
                         * @param Task $task
540
                         * @return bool
541
                         */
542
                        static function ($allTasksCompleted, $task) {
543
                            return $allTasksCompleted && $task->progress >= 100;
544
                        },
545
                        1
546
                    );
547
                    if ($projectCompleted) {
548
                        if ($project->setClose($user) <= 0) {
549
                            $error++;
550
                        }
551
                    }
552
                }
553
            } else {
554
                $error++;
555
            }
556
            if ($error) {
557
                $this->errors[] = $project->error;
558
            }
559
        }
560
561
        if (!$error) {
562
            if (!$notrigger) {
563
                // Call trigger
564
                $result = $this->call_trigger('TASK_MODIFY', $user);
565
                if ($result < 0) {
566
                    $error++;
567
                }
568
                // End call triggers
569
            }
570
        }
571
572
        if (!$error && (is_object($this->oldcopy) && $this->oldcopy->ref !== $this->ref)) {
573
            // We remove directory
574
            if ($conf->project->dir_output) {
575
                $project = new Project($this->db);
576
                $project->fetch($this->fk_project);
577
578
                $olddir = $conf->project->dir_output . '/' . dol_sanitizeFileName($project->ref) . '/' . dol_sanitizeFileName($this->oldcopy->ref);
579
                $newdir = $conf->project->dir_output . '/' . dol_sanitizeFileName($project->ref) . '/' . dol_sanitizeFileName($this->ref);
580
                if (file_exists($olddir)) {
581
                    include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
582
                    $res = dol_move_dir($olddir, $newdir);
583
                    if (!$res) {
584
                        $langs->load("errors");
585
                        $this->error = $langs->trans('ErrorFailToRenameDir', $olddir, $newdir);
586
                        $error++;
587
                    }
588
                }
589
            }
590
        }
591
592
        // Commit or rollback
593
        if ($error) {
594
            foreach ($this->errors as $errmsg) {
595
                dol_syslog(get_only_class($this) . "::update " . $errmsg, LOG_ERR);
596
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
597
            }
598
            $this->db->rollback();
599
            return -1 * $error;
600
        } else {
601
            $this->db->commit();
602
            return 1;
603
        }
604
    }
605
606
607
    /**
608
     *  Delete task from database
609
     *
610
     *  @param  User    $user           User that delete
611
     *  @param  int     $notrigger      0=launch triggers after, 1=disable triggers
612
     *  @return int                     Return integer <0 if KO, >0 if OK
613
     */
614
    public function delete($user, $notrigger = 0)
615
    {
616
        global $conf;
617
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
618
619
        $error = 0;
620
621
        $this->db->begin();
622
623
        if ($this->hasChildren() > 0) {
624
            dol_syslog(get_only_class($this) . "::delete Can't delete record as it has some sub tasks", LOG_WARNING);
625
            $this->error = 'ErrorRecordHasSubTasks';
626
            $this->db->rollback();
627
            return 0;
628
        }
629
630
        $objectisused = $this->isObjectUsed($this->id);
631
        if (!empty($objectisused)) {
632
            dol_syslog(get_only_class($this) . "::delete Can't delete record as it has some child", LOG_WARNING);
633
            $this->error = 'ErrorRecordHasChildren';
634
            $this->db->rollback();
635
            return 0;
636
        }
637
638
        if (!$error) {
639
            // Delete linked contacts
640
            $res = $this->delete_linked_contact();
641
            if ($res < 0) {
642
                $this->error = 'ErrorFailToDeleteLinkedContact';
643
                //$error++;
644
                $this->db->rollback();
645
                return 0;
646
            }
647
        }
648
649
        if (!$error) {
650
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . "element_time";
651
            $sql .= " WHERE fk_element = " . ((int) $this->id) . " AND elementtype = 'task'";
652
653
            $resql = $this->db->query($sql);
654
            if (!$resql) {
655
                $error++;
656
                $this->errors[] = "Error " . $this->db->lasterror();
657
            }
658
        }
659
660
        if (!$error) {
661
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . "projet_task_extrafields";
662
            $sql .= " WHERE fk_object = " . ((int) $this->id);
663
664
            $resql = $this->db->query($sql);
665
            if (!$resql) {
666
                $error++;
667
                $this->errors[] = "Error " . $this->db->lasterror();
668
            }
669
        }
670
671
        if (!$error) {
672
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . "projet_task";
673
            $sql .= " WHERE rowid=" . ((int) $this->id);
674
675
            $resql = $this->db->query($sql);
676
            if (!$resql) {
677
                $error++;
678
                $this->errors[] = "Error " . $this->db->lasterror();
679
            }
680
        }
681
682
        if (!$error) {
683
            if (!$notrigger) {
684
                // Call trigger
685
                $result = $this->call_trigger('TASK_DELETE', $user);
686
                if ($result < 0) {
687
                    $error++;
688
                }
689
                // End call triggers
690
            }
691
        }
692
693
        // Commit or rollback
694
        if ($error) {
695
            foreach ($this->errors as $errmsg) {
696
                dol_syslog(get_only_class($this) . "::delete " . $errmsg, LOG_ERR);
697
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
698
            }
699
            $this->db->rollback();
700
            return -1 * $error;
701
        } else {
702
            //Delete associated link file
703
            if ($conf->project->dir_output) {
704
                $projectstatic = new Project($this->db);
705
                $projectstatic->fetch($this->fk_project);
706
707
                $dir = $conf->project->dir_output . "/" . dol_sanitizeFileName($projectstatic->ref) . '/' . dol_sanitizeFileName($this->id);
708
                dol_syslog(get_only_class($this) . "::delete dir=" . $dir, LOG_DEBUG);
709
                if (file_exists($dir)) {
710
                    require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
711
                    $res = @dol_delete_dir_recursive($dir);
712
                    if (!$res) {
713
                        $this->error = 'ErrorFailToDeleteDir';
714
                        $this->db->rollback();
715
                        return 0;
716
                    }
717
                }
718
            }
719
720
            $this->db->commit();
721
722
            return 1;
723
        }
724
    }
725
726
    /**
727
     *  Return nb of children
728
     *
729
     *  @return int     Return integer <0 if KO, 0 if no children, >0 if OK
730
     */
731
    public function hasChildren()
732
    {
733
        $error = 0;
734
        $ret = 0;
735
736
        $sql = "SELECT COUNT(*) as nb";
737
        $sql .= " FROM " . MAIN_DB_PREFIX . "projet_task";
738
        $sql .= " WHERE fk_task_parent = " . ((int) $this->id);
739
740
        dol_syslog(get_only_class($this) . "::hasChildren", LOG_DEBUG);
741
        $resql = $this->db->query($sql);
742
        if (!$resql) {
743
            $error++;
744
            $this->errors[] = "Error " . $this->db->lasterror();
745
        } else {
746
            $obj = $this->db->fetch_object($resql);
747
            if ($obj) {
748
                $ret = $obj->nb;
749
            }
750
            $this->db->free($resql);
751
        }
752
753
        if (!$error) {
754
            return $ret;
755
        } else {
756
            return -1;
757
        }
758
    }
759
760
    /**
761
     *  Return nb of time spent
762
     *
763
     *  @return int     Return integer <0 if KO, 0 if no children, >0 if OK
764
     */
765
    public function hasTimeSpent()
766
    {
767
        $error = 0;
768
        $ret = 0;
769
770
        $sql = "SELECT COUNT(*) as nb";
771
        $sql .= " FROM " . MAIN_DB_PREFIX . "element_time";
772
        $sql .= " WHERE fk_element = " . ((int) $this->id);
773
        $sql .= " AND elementtype = 'task'";
774
775
        dol_syslog(get_only_class($this) . "::hasTimeSpent", LOG_DEBUG);
776
        $resql = $this->db->query($sql);
777
        if (!$resql) {
778
            $error++;
779
            $this->errors[] = "Error " . $this->db->lasterror();
780
        } else {
781
            $obj = $this->db->fetch_object($resql);
782
            if ($obj) {
783
                $ret = $obj->nb;
784
            }
785
            $this->db->free($resql);
786
        }
787
788
        if (!$error) {
789
            return $ret;
790
        } else {
791
            return -1;
792
        }
793
    }
794
795
796
    /**
797
     * getTooltipContentArray
798
     *
799
     * @param array $params ex option, infologin
800
     * @since v18
801
     * @return array
802
     */
803
    public function getTooltipContentArray($params)
804
    {
805
        global $langs;
806
807
        $langs->load('projects');
808
809
        $datas = [];
810
        $datas['picto'] = img_picto('', $this->picto) . ' <u>' . $langs->trans("Task") . '</u>';
811
        if (!empty($this->ref)) {
812
            $datas['ref'] = '<br><b>' . $langs->trans('Ref') . ':</b> ' . $this->ref;
813
        }
814
        if (!empty($this->label)) {
815
            $datas['label'] = '<br><b>' . $langs->trans('LabelTask') . ':</b> ' . $this->label;
816
        }
817
        if ($this->date_start || $this->date_end) {
818
            $datas['range'] = "<br>" . get_date_range($this->date_start, $this->date_end, '', $langs, 0);
819
        }
820
821
        return $datas;
822
    }
823
824
    /**
825
     *  Return clicable name (with picto eventually)
826
     *
827
     *  @param  int     $withpicto      0=No picto, 1=Include picto into link, 2=Only picto
828
     *  @param  string  $option         'withproject' or ''
829
     *  @param  string  $mode           Mode 'task', 'time', 'contact', 'note', document' define page to link to.
830
     *  @param  int     $addlabel       0=Default, 1=Add label into string, >1=Add first chars into string
831
     *  @param  string  $sep            Separator between ref and label if option addlabel is set
832
     *  @param  int     $notooltip      1=Disable tooltip
833
     *  @param  int     $save_lastsearch_value    -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
834
     *  @return string                  Chaine avec URL
835
     */
836
    public function getNomUrl($withpicto = 0, $option = '', $mode = 'task', $addlabel = 0, $sep = ' - ', $notooltip = 0, $save_lastsearch_value = -1)
837
    {
838
        global $action, $conf, $hookmanager, $langs;
839
840
        if (!empty($conf->dol_no_mouse_hover)) {
841
            $notooltip = 1; // Force disable tooltips
842
        }
843
844
        $result = '';
845
        $params = [
846
            'id' => $this->id,
847
            'objecttype' => $this->element,
848
        ];
849
        $classfortooltip = 'classfortooltip';
850
        $dataparams = '';
851
        if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
852
            $classfortooltip = 'classforajaxtooltip';
853
            $dataparams = ' data-params="' . dol_escape_htmltag(json_encode($params)) . '"';
854
            $label = '';
855
        } else {
856
            $label = implode($this->getTooltipContentArray($params));
857
        }
858
859
        $url = constant('BASE_URL') . '/projet/tasks/' . $mode . '.php?id=' . $this->id . ($option == 'withproject' ? '&withproject=1' : '');
860
        // Add param to save lastsearch_values or not
861
        $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
862
        if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
863
            $add_save_lastsearch_values = 1;
864
        }
865
        if ($add_save_lastsearch_values) {
866
            $url .= '&save_lastsearch_values=1';
867
        }
868
869
        $linkclose = '';
870
        if (empty($notooltip)) {
871
            if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
872
                $label = $langs->trans("ShowTask");
873
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
874
            }
875
            $linkclose .= ($label ? ' title="' . dol_escape_htmltag($label, 1) . '"' : ' title="tocomplete"');
876
            $linkclose .= $dataparams . ' class="' . $classfortooltip . ' nowraponall"';
877
        } else {
878
            $linkclose .= ' class="nowraponall"';
879
        }
880
881
        $linkstart = '<a href="' . $url . '"';
882
        $linkstart .= $linkclose . '>';
883
        $linkend = '</a>';
884
885
        $picto = 'projecttask';
886
887
        $result .= $linkstart;
888
        if ($withpicto) {
889
            $result .= img_object(($notooltip ? '' : $label), $picto, 'class="paddingright"', 0, 0, $notooltip ? 0 : 1);
890
        }
891
        if ($withpicto != 2) {
892
            $result .= $this->ref;
893
        }
894
        $result .= $linkend;
895
        if ($withpicto != 2) {
896
            $result .= (($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
897
        }
898
899
        $parameters = array('id' => $this->id, 'getnomurl' => &$result);
900
        $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
901
        if ($reshook > 0) {
902
            $result = $hookmanager->resPrint;
903
        } else {
904
            $result .= $hookmanager->resPrint;
905
        }
906
907
        return $result;
908
    }
909
910
    /**
911
     *  Initialise an instance with random values.
912
     *  Used to build previews or test instances.
913
     *  id must be 0 if object instance is a specimen.
914
     *
915
     *  @return int
916
     */
917
    public function initAsSpecimen()
918
    {
919
        global $user;
920
921
        $this->id = 0;
922
923
        $this->fk_project = 0;
924
        $this->ref = 'TK01';
925
        $this->fk_task_parent = 0;
926
        $this->label = 'Specimen task TK01';
927
        $this->duration_effective = '';
928
        $this->fk_user_creat = $user->id;
929
        $this->progress = '25';
930
        $this->status = 0;
931
        $this->priority = 0;
932
        $this->note_private = 'This is a specimen private note';
933
        $this->note_public = 'This is a specimen public note';
934
935
        return 1;
936
    }
937
938
    /**
939
     * Return list of tasks for all projects or for one particular project
940
     * Sort order is on project, then on position of task, and last on start date of first level task
941
     *
942
     * @param   User    $usert                  Object user to limit tasks affected to a particular user
943
     * @param   User    $userp                  Object user to limit projects of a particular user and public projects
944
     * @param   int     $projectid              Project id
945
     * @param   int     $socid                  Third party id
946
     * @param   int     $mode                   0=Return list of tasks and their projects, 1=Return projects and tasks if exists
947
     * @param   string  $filteronproj           Filter on project ref or label
948
     * @param   string  $filteronprojstatus     Filter on project status ('-1'=no filter, '0,1'=Draft+Validated only)
949
     * @param   string  $morewherefilter        Add more filter into where SQL request (must start with ' AND ...')
950
     * @param   int     $filteronprojuser       Filter on user that is a contact of project
951
     * @param   int     $filterontaskuser       Filter on user assigned to task
952
     * @param   ?Extrafields    $extrafields    Show additional column from project or task
953
     * @param   int     $includebilltime        Calculate also the time to bill and billed
954
     * @param   array   $search_array_options   Array of search filters. Not Used yet.
955
     * @param   int     $loadextras             Fetch all Extrafields on each project and task
956
     * @param   int     $loadRoleMode           1= will test Roles on task;  0 used in delete project action
957
     * @param   string  $sortfield              Sort field
958
     * @param   string  $sortorder              Sort order
959
     * @return  array|string                    Array of tasks
960
     */
961
    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 = '')
962
    {
963
        global $hookmanager;
964
965
        $tasks = array();
966
967
        //print $usert.'-'.$userp.'-'.$projectid.'-'.$socid.'-'.$mode.'<br>';
968
969
        // List of tasks (does not care about permissions. Filtering will be done later)
970
        $sql = "SELECT ";
971
        if ($filteronprojuser > 0 || $filterontaskuser > 0) {
972
            $sql .= " DISTINCT"; // We may get several time the same record if user has several roles on same project/task
973
        }
974
        $sql .= " p.rowid as projectid, p.ref, p.title as plabel, p.public, p.fk_statut as projectstatus, p.usage_bill_time,";
975
        $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,";
976
        $sql .= " t.dateo as date_start, t.datee as date_end, t.planned_workload, t.rang, t.priority,";
977
        $sql .= " t.budget_amount,";
978
        $sql .= " t.note_public, t.note_private,";
979
        $sql .= " s.rowid as thirdparty_id, s.nom as thirdparty_name, s.email as thirdparty_email,";
980
        $sql .= " p.fk_opp_status, p.opp_amount, p.opp_percent, p.budget_amount as project_budget_amount";
981
        if ($loadextras) {  // TODO Replace this with a fetch_optionnal() on the project after the fetch_object of line.
982
            if (!empty($extrafields->attributes['projet']['label'])) {
983
                foreach ($extrafields->attributes['projet']['label'] as $key => $val) {
984
                    $sql .= ($extrafields->attributes['projet']['type'][$key] != 'separate' ? ",efp." . $key . " as options_" . $key : '');
985
                }
986
            }
987
            if (!empty($extrafields->attributes['projet_task']['label'])) {
988
                foreach ($extrafields->attributes['projet_task']['label'] as $key => $val) {
989
                    $sql .= ($extrafields->attributes['projet_task']['type'][$key] != 'separate' ? ",efpt." . $key . " as options_" . $key : '');
990
                }
991
            }
992
        }
993
        if ($includebilltime) {
994
            $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";
995
        }
996
997
        $sql .= " FROM " . MAIN_DB_PREFIX . "projet as p";
998
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON p.fk_soc = s.rowid";
999
        if ($loadextras) {
1000
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "projet_extrafields as efp ON (p.rowid = efp.fk_object)";
1001
        }
1002
1003
        if ($mode == 0) {
1004
            if ($filteronprojuser > 0) {
1005
                $sql .= ", " . MAIN_DB_PREFIX . "element_contact as ec";
1006
                $sql .= ", " . MAIN_DB_PREFIX . "c_type_contact as ctc";
1007
            }
1008
            $sql .= ", " . MAIN_DB_PREFIX . "projet_task as t";
1009
            if ($loadextras) {
1010
                $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "projet_task_extrafields as efpt ON (t.rowid = efpt.fk_object)";
1011
            }
1012
            if ($includebilltime) {
1013
                $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "element_time as tt ON (tt.fk_element = t.rowid AND tt.elementtype='task')";
1014
            }
1015
            if ($filterontaskuser > 0) {
1016
                $sql .= ", " . MAIN_DB_PREFIX . "element_contact as ec2";
1017
                $sql .= ", " . MAIN_DB_PREFIX . "c_type_contact as ctc2";
1018
            }
1019
            $sql .= " WHERE p.entity IN (" . getEntity('project') . ")";
1020
            $sql .= " AND t.fk_projet = p.rowid";
1021
        } elseif ($mode == 1) {
1022
            if ($filteronprojuser > 0) {
1023
                $sql .= ", " . MAIN_DB_PREFIX . "element_contact as ec";
1024
                $sql .= ", " . MAIN_DB_PREFIX . "c_type_contact as ctc";
1025
            }
1026
            if ($filterontaskuser > 0) {
1027
                $sql .= ", " . MAIN_DB_PREFIX . "projet_task as t";
1028
                if ($includebilltime) {
1029
                    $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "element_time as tt ON (tt.fk_element = t.rowid AND tt.elementtype='task')";
1030
                }
1031
                $sql .= ", " . MAIN_DB_PREFIX . "element_contact as ec2";
1032
                $sql .= ", " . MAIN_DB_PREFIX . "c_type_contact as ctc2";
1033
            } else {
1034
                $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "projet_task as t on t.fk_projet = p.rowid";
1035
                if ($includebilltime) {
1036
                    $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "element_time as tt ON (tt.fk_element = t.rowid AND tt.elementtype = 'task')";
1037
                }
1038
            }
1039
            $sql .= " WHERE p.entity IN (" . getEntity('project') . ")";
1040
        } else {
1041
            return 'BadValueForParameterMode';
1042
        }
1043
1044
        if ($filteronprojuser > 0) {
1045
            $sql .= " AND p.rowid = ec.element_id";
1046
            $sql .= " AND ctc.rowid = ec.fk_c_type_contact";
1047
            $sql .= " AND ctc.element = 'project'";
1048
            $sql .= " AND ec.fk_socpeople = " . ((int) $filteronprojuser);
1049
            $sql .= " AND ec.statut = 4";
1050
            $sql .= " AND ctc.source = 'internal'";
1051
        }
1052
        if ($filterontaskuser > 0) {
1053
            $sql .= " AND t.fk_projet = p.rowid";
1054
            $sql .= " AND p.rowid = ec2.element_id";
1055
            $sql .= " AND ctc2.rowid = ec2.fk_c_type_contact";
1056
            $sql .= " AND ctc2.element = 'project_task'";
1057
            $sql .= " AND ec2.fk_socpeople = " . ((int) $filterontaskuser);
1058
            $sql .= " AND ec2.statut = 4";
1059
            $sql .= " AND ctc2.source = 'internal'";
1060
        }
1061
        if ($socid) {
1062
            $sql .= " AND p.fk_soc = " . ((int) $socid);
1063
        }
1064
        if ($projectid) {
1065
            $sql .= " AND p.rowid IN (" . $this->db->sanitize($projectid) . ")";
1066
        }
1067
        if ($filteronproj) {
1068
            $sql .= natural_search(array("p.ref", "p.title"), $filteronproj);
1069
        }
1070
        if ($filteronprojstatus && (int) $filteronprojstatus != '-1') {
1071
            $sql .= " AND p.fk_statut IN (" . $this->db->sanitize($filteronprojstatus) . ")";
1072
        }
1073
        if ($morewherefilter) {
1074
            $sql .= $morewherefilter;
1075
        }
1076
1077
        // Add where from extra fields
1078
        $extrafieldsobjectkey = 'projet_task';
1079
        $extrafieldsobjectprefix = 'efpt.';
1080
        global $db, $conf; // needed for extrafields_list_search_sql.tpl
1081
        include DOL_DOCUMENT_ROOT . '/core/tpl/extrafields_list_search_sql.tpl.php';
1082
1083
        // Add where from hooks
1084
        $parameters = array();
1085
        $reshook = $hookmanager->executeHooks('printFieldListWhere', $parameters); // Note that $action and $object may have been modified by hook
1086
        $sql .= $hookmanager->resPrint;
1087
        if ($includebilltime) {
1088
            $sql .= " GROUP BY p.rowid, p.ref, p.title, p.public, p.fk_statut, p.usage_bill_time,";
1089
            $sql .= " t.datec, t.dateo, t.datee, t.tms,";
1090
            $sql .= " t.rowid, t.ref, t.label, t.description, t.fk_task_parent, t.duration_effective, t.progress, t.fk_statut,";
1091
            $sql .= " t.dateo, t.datee, t.planned_workload, t.rang, t.priority,";
1092
            $sql .= " t.budget_amount,";
1093
            $sql .= " t.note_public, t.note_private,";
1094
            $sql .= " s.rowid, s.nom, s.email,";
1095
            $sql .= " p.fk_opp_status, p.opp_amount, p.opp_percent, p.budget_amount";
1096
            if ($loadextras) {
1097
                if (!empty($extrafields->attributes['projet']['label'])) {
1098
                    foreach ($extrafields->attributes['projet']['label'] as $key => $val) {
1099
                        $sql .= ($extrafields->attributes['projet']['type'][$key] != 'separate' ? ",efp." . $key : '');
1100
                    }
1101
                }
1102
                if (!empty($extrafields->attributes['projet_task']['label'])) {
1103
                    foreach ($extrafields->attributes['projet_task']['label'] as $key => $val) {
1104
                        $sql .= ($extrafields->attributes['projet_task']['type'][$key] != 'separate' ? ",efpt." . $key : '');
1105
                    }
1106
                }
1107
            }
1108
        }
1109
1110
        if ($sortfield && $sortorder) {
1111
            $sql .= $this->db->order($sortfield, $sortorder);
1112
        } else {
1113
            $sql .= " ORDER BY p.ref, t.rang, t.dateo";
1114
        }
1115
1116
        //print $sql;exit;
1117
        dol_syslog(get_only_class($this) . "::getTasksArray", LOG_DEBUG);
1118
        $resql = $this->db->query($sql);
1119
        if ($resql) {
1120
            $num = $this->db->num_rows($resql);
1121
            $i = 0;
1122
            // Loop on each record found, so each couple (project id, task id)
1123
            while ($i < $num) {
1124
                $error = 0;
1125
1126
                $obj = $this->db->fetch_object($resql);
1127
1128
                if ($loadRoleMode) {
1129
                    if ((!$obj->public) && (is_object($userp))) {    // If not public project and we ask a filter on project owned by a user
1130
                        if (!$this->getUserRolesForProjectsOrTasks($userp, null, $obj->projectid, 0)) {
1131
                            $error++;
1132
                        }
1133
                    }
1134
                    if (is_object($usert)) {                            // If we ask a filter on a user affected to a task
1135
                        if (!$this->getUserRolesForProjectsOrTasks(null, $usert, $obj->projectid, $obj->taskid)) {
1136
                            $error++;
1137
                        }
1138
                    }
1139
                }
1140
1141
                if (!$error) {
1142
                    $tasks[$i] = new Task($this->db);
1143
                    $tasks[$i]->id = $obj->taskid;
1144
                    $tasks[$i]->ref = $obj->taskref;
1145
                    $tasks[$i]->fk_project = $obj->projectid;
1146
1147
                    // Data from project
1148
                    $tasks[$i]->projectref = $obj->ref;
1149
                    $tasks[$i]->projectlabel = $obj->plabel;
1150
                    $tasks[$i]->projectstatus = $obj->projectstatus;
1151
                    $tasks[$i]->fk_opp_status = $obj->fk_opp_status;
1152
                    $tasks[$i]->opp_amount = $obj->opp_amount;
1153
                    $tasks[$i]->opp_percent = $obj->opp_percent;
1154
                    $tasks[$i]->budget_amount = $obj->budget_amount;
1155
                    $tasks[$i]->project_budget_amount = $obj->project_budget_amount;
1156
                    $tasks[$i]->usage_bill_time = $obj->usage_bill_time;
1157
1158
                    $tasks[$i]->label = $obj->label;
1159
                    $tasks[$i]->description = $obj->description;
1160
1161
                    $tasks[$i]->fk_task_parent = $obj->fk_task_parent;
1162
                    $tasks[$i]->note_public = $obj->note_public;
1163
                    $tasks[$i]->note_private = $obj->note_private;
1164
                    $tasks[$i]->duration_effective = $obj->duration_effective;
1165
                    $tasks[$i]->planned_workload = $obj->planned_workload;
1166
1167
                    if ($includebilltime) {
1168
                        // Data summed from element_time linked to task
1169
                        $tasks[$i]->tobill = $obj->tobill;
1170
                        $tasks[$i]->billed = $obj->billed;
1171
                    }
1172
1173
                    $tasks[$i]->progress        = $obj->progress;
1174
                    $tasks[$i]->fk_statut       = $obj->status;
1175
                    $tasks[$i]->status          = $obj->status;
1176
                    $tasks[$i]->public = $obj->public;
1177
                    $tasks[$i]->date_start = $this->db->jdate($obj->date_start);
1178
                    $tasks[$i]->date_end        = $this->db->jdate($obj->date_end);
1179
                    $tasks[$i]->rang            = $obj->rang;
1180
                    $tasks[$i]->priority        = $obj->priority;
1181
1182
                    $tasks[$i]->socid           = $obj->thirdparty_id; // For backward compatibility
1183
                    $tasks[$i]->thirdparty_id = $obj->thirdparty_id;
1184
                    $tasks[$i]->thirdparty_name = $obj->thirdparty_name;
1185
                    $tasks[$i]->thirdparty_email = $obj->thirdparty_email;
1186
1187
                    if ($loadextras) {
1188
                        if (!empty($extrafields->attributes['projet']['label'])) {
1189
                            foreach ($extrafields->attributes['projet']['label'] as $key => $val) {
1190
                                if ($extrafields->attributes['projet']['type'][$key] != 'separate') {
1191
                                    $tmpvar = 'options_' . $key;
1192
                                    $tasks[$i]->array_options_project['options_' . $key] = $obj->$tmpvar;
1193
                                }
1194
                            }
1195
                        }
1196
                    }
1197
1198
                    if ($loadextras) {
1199
                        $tasks[$i]->fetch_optionals();
1200
                    }
1201
                }
1202
1203
                $i++;
1204
            }
1205
            $this->db->free($resql);
1206
        } else {
1207
            dol_print_error($this->db);
1208
        }
1209
1210
        return $tasks;
1211
    }
1212
1213
    /**
1214
     * Return list of roles for a user for each projects or each tasks (or a particular project or a particular task).
1215
     *
1216
     * @param   User|null   $userp                Return roles on project for this internal user. If set, usert and taskid must not be defined.
1217
     * @param   User|null   $usert                Return roles on task for this internal user. If set userp must NOT be defined. -1 means no filter.
1218
     * @param   string      $projectid            Project id list separated with , to filter on project
1219
     * @param   int         $taskid               Task id to filter on a task
1220
     * @param   integer     $filteronprojstatus   Filter on project status if userp is set. Not used if userp not defined.
1221
     * @return  array|int                         Array (projectid => 'list of roles for project' or taskid => 'list of roles for task')
1222
     */
1223
    public function getUserRolesForProjectsOrTasks($userp, $usert, $projectid = '', $taskid = 0, $filteronprojstatus = -1)
1224
    {
1225
        $arrayroles = array();
1226
1227
        dol_syslog(get_only_class($this) . "::getUserRolesForProjectsOrTasks userp=" . json_encode(is_object($userp)) . " usert=" . json_encode(is_object($usert)) . " projectid=" . $projectid . " taskid=" . $taskid);
1228
1229
        // We want role of user for a projet or role of user for a task. Both are not possible.
1230
        if (empty($userp) && empty($usert)) {
1231
            $this->error = "CallWithWrongParameters";
1232
            return -1;
1233
        }
1234
        if (!empty($userp) && !empty($usert)) {
1235
            $this->error = "CallWithWrongParameters";
1236
            return -1;
1237
        }
1238
1239
        /* Liste des taches et role sur les projects ou taches */
1240
        $sql = "SELECT ";
1241
        if ($userp) {
1242
            $sql .= " p.rowid as pid,";
1243
        } else {
1244
            $sql .= " pt.rowid as pid,";
1245
        }
1246
        $sql .= " ec.element_id, ctc.code, ctc.source";
1247
        if ($userp) {
1248
            $sql .= " FROM " . MAIN_DB_PREFIX . "projet as p";
1249
        }
1250
        if ($usert && $filteronprojstatus > -1) {
1251
            $sql .= " FROM " . MAIN_DB_PREFIX . "projet as p, " . MAIN_DB_PREFIX . "projet_task as pt";
1252
        }
1253
        if ($usert && $filteronprojstatus <= -1) {
1254
            $sql .= " FROM " . MAIN_DB_PREFIX . "projet_task as pt";
1255
        }
1256
        $sql .= ", " . MAIN_DB_PREFIX . "element_contact as ec";
1257
        $sql .= ", " . MAIN_DB_PREFIX . "c_type_contact as ctc";
1258
        if ($userp) {
1259
            $sql .= " WHERE p.rowid = ec.element_id";
1260
        } else {
1261
            $sql .= " WHERE pt.rowid = ec.element_id";
1262
        }
1263
        if ($userp && $filteronprojstatus > -1) {
1264
            $sql .= " AND p.fk_statut = " . ((int) $filteronprojstatus);
1265
        }
1266
        if ($usert && $filteronprojstatus > -1) {
1267
            $sql .= " AND pt.fk_projet = p.rowid AND p.fk_statut = " . ((int) $filteronprojstatus);
1268
        }
1269
        if ($userp) {
1270
            $sql .= " AND ctc.element = 'project'";
1271
        }
1272
        if ($usert) {
1273
            $sql .= " AND ctc.element = 'project_task'";
1274
        }
1275
        $sql .= " AND ctc.rowid = ec.fk_c_type_contact";
1276
        if ($userp) {
1277
            $sql .= " AND ec.fk_socpeople = " . ((int) $userp->id);
1278
        }
1279
        if ($usert) {
1280
            $sql .= " AND ec.fk_socpeople = " . ((int) $usert->id);
1281
        }
1282
        $sql .= " AND ec.statut = 4";
1283
        $sql .= " AND ctc.source = 'internal'";
1284
        if ($projectid) {
1285
            if ($userp) {
1286
                $sql .= " AND p.rowid IN (" . $this->db->sanitize($projectid) . ")";
1287
            }
1288
            if ($usert) {
1289
                $sql .= " AND pt.fk_projet IN (" . $this->db->sanitize($projectid) . ")";
1290
            }
1291
        }
1292
        if ($taskid) {
1293
            if ($userp) {
1294
                $sql .= " ERROR SHOULD NOT HAPPENS";
1295
            }
1296
            if ($usert) {
1297
                $sql .= " AND pt.rowid = " . ((int) $taskid);
1298
            }
1299
        }
1300
        //print $sql;
1301
1302
        dol_syslog(get_only_class($this) . "::getUserRolesForProjectsOrTasks execute request", LOG_DEBUG);
1303
        $resql = $this->db->query($sql);
1304
        if ($resql) {
1305
            $num = $this->db->num_rows($resql);
1306
            $i = 0;
1307
            while ($i < $num) {
1308
                $obj = $this->db->fetch_object($resql);
1309
                if (empty($arrayroles[$obj->pid])) {
1310
                    $arrayroles[$obj->pid] = $obj->code;
1311
                } else {
1312
                    $arrayroles[$obj->pid] .= ',' . $obj->code;
1313
                }
1314
                $i++;
1315
            }
1316
            $this->db->free($resql);
1317
        } else {
1318
            dol_print_error($this->db);
1319
        }
1320
1321
        return $arrayroles;
1322
    }
1323
1324
1325
    /**
1326
     *  Return list of id of contacts of task
1327
     *
1328
     *  @param  string  $source     Source
1329
     *  @return array               Array of id of contacts
1330
     */
1331
    public function getListContactId($source = 'internal')
1332
    {
1333
        $contactAlreadySelected = array();
1334
        $tab = $this->liste_contact(-1, $source);
1335
        //var_dump($tab);
1336
        $num = count($tab);
1337
        $i = 0;
1338
        while ($i < $num) {
1339
            if ($source == 'thirdparty') {
1340
                $contactAlreadySelected[$i] = $tab[$i]['socid'];
1341
            } else {
1342
                $contactAlreadySelected[$i] = $tab[$i]['id'];
1343
            }
1344
            $i++;
1345
        }
1346
        return $contactAlreadySelected;
1347
    }
1348
1349
    /**
1350
     * Merge contact of tasks
1351
     *
1352
     * @param   int     $origin_id  Old task id
1353
     * @param   int     $dest_id    New task id
1354
     * @return  bool
1355
     */
1356
    public function mergeContactTask($origin_id, $dest_id)
1357
    {
1358
        $error = 0;
1359
        $origintask = new Task($this->db);
1360
        $result = $origintask->fetch($origin_id);
1361
        if ($result <= 0) {
1362
            return false;
1363
        }
1364
1365
        //Get list of origin contacts
1366
        $arraycontactorigin = array_merge($origintask->liste_contact(-1, 'internal'), $origintask->liste_contact(-1, 'external'));
1367
        if (is_array($arraycontactorigin)) {
1368
            foreach ($arraycontactorigin as $key => $contact) {
1369
                $result = $this->add_contact($contact["id"], $contact["fk_c_type_contact"], $contact["source"]);
1370
                if ($result < 0) {
1371
                    return false;
1372
                }
1373
            }
1374
        }
1375
        return true;
1376
    }
1377
1378
    /**
1379
     * Merge time spent of tasks
1380
     *
1381
     * @param   int     $origin_id  Old task id
1382
     * @param   int     $dest_id    New task id
1383
     * @return  bool
1384
     */
1385
    public function mergeTimeSpentTask($origin_id, $dest_id)
1386
    {
1387
        $ret = true;
1388
1389
        $this->db->begin();
1390
1391
        $sql = "UPDATE " . MAIN_DB_PREFIX . "element_time as et";
1392
        $sql .= " SET et.fk_element = " . ((int) $dest_id);
1393
        $sql .= " WHERE et.elementtype = 'task'";
1394
        $sql .= " AND et.fk_element = " . ((int) $origin_id);
1395
1396
        dol_syslog(get_only_class($this) . "::mergeTimeSpentTask", LOG_DEBUG);
1397
        if (!$this->db->query($sql)) {
1398
            $this->error = $this->db->lasterror();
1399
            $ret = false;
1400
        }
1401
1402
        if ($ret) {
1403
            $sql = "UPDATE " . MAIN_DB_PREFIX . "projet_task";
1404
            $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) . ")";
1405
            $sql .= " WHERE rowid = " . ((int) $dest_id);
1406
1407
            dol_syslog(get_only_class($this) . "::mergeTimeSpentTask update project_task", LOG_DEBUG);
1408
            if (!$this->db->query($sql)) {
1409
                $this->error = $this->db->lasterror();
1410
                $ret = false;
1411
            }
1412
        }
1413
1414
        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...
1415
            $this->db->commit();
1416
        } else {
1417
            $this->db->rollback();
1418
        }
1419
        return $ret;
1420
    }
1421
1422
    /**
1423
     *  Add time spent
1424
     *
1425
     *  @param  User    $user           User object
1426
     *  @param  int     $notrigger      0=launch triggers after, 1=disable triggers
1427
     *  @return int                     Return integer <=0 if KO, >0 if OK
1428
     */
1429
    public function addTimeSpent($user, $notrigger = 0)
1430
    {
1431
        global $langs;
1432
1433
        dol_syslog(get_only_class($this) . "::addTimeSpent", LOG_DEBUG);
1434
1435
        $ret = 0;
1436
        $now = dol_now();
1437
1438
        // Check parameters
1439
        if (!is_object($user)) {
1440
            dol_print_error(null, "Method addTimeSpent was called with wrong parameter user");
1441
            return -1;
1442
        }
1443
1444
        // Clean parameters
1445
        if (isset($this->timespent_note)) {
1446
            $this->timespent_note = trim($this->timespent_note);
1447
        }
1448
        if (empty($this->timespent_datehour) || ($this->timespent_date != $this->timespent_datehour)) {
1449
            $this->timespent_datehour = $this->timespent_date;
1450
        }
1451
1452
        if (getDolGlobalInt('PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS')) {
1453
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/date.lib.php';
1454
            $restrictBefore = dol_time_plus_duree(dol_now(), - getDolGlobalInt('PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS'), 'm');
1455
1456
            if ($this->timespent_date < $restrictBefore) {
1457
                $this->error = $langs->trans('TimeRecordingRestrictedToNMonthsBack', getDolGlobalString('PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS'));
1458
                $this->errors[] = $this->error;
1459
                return -1;
1460
            }
1461
        }
1462
1463
        $this->db->begin();
1464
1465
        $timespent = new TimeSpent($this->db);
1466
        $timespent->fk_element = $this->id;
1467
        $timespent->elementtype = 'task';
1468
        $timespent->element_date = $this->timespent_date;
1469
        $timespent->element_datehour = $this->timespent_datehour;
1470
        $timespent->element_date_withhour = $this->timespent_withhour;
1471
        $timespent->element_duration = $this->timespent_duration;
1472
        $timespent->fk_user = $this->timespent_fk_user;
1473
        $timespent->fk_product = $this->timespent_fk_product;
1474
        $timespent->note = $this->timespent_note;
1475
        $timespent->datec = $this->db->idate($now);
1476
1477
        $result = $timespent->create($user);
1478
1479
        if ($result > 0) {
1480
            $ret = $result;
1481
            $this->timespent_id = $result;
1482
1483
            if (!$notrigger) {
1484
                // Call trigger
1485
                $result = $this->call_trigger('TASK_TIMESPENT_CREATE', $user);
1486
                if ($result < 0) {
1487
                    $ret = -1;
1488
                }
1489
                // End call triggers
1490
            }
1491
        } else {
1492
            $this->error = $this->db->lasterror();
1493
            $ret = -1;
1494
        }
1495
1496
        if ($ret > 0) {
1497
            // Recalculate amount of time spent for task and update denormalized field
1498
            $sql = "UPDATE " . MAIN_DB_PREFIX . "projet_task";
1499
            $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) . ")";
1500
            if (isset($this->progress)) {
1501
                $sql .= ", progress = " . ((float) $this->progress); // Do not overwrite value if not provided
1502
            }
1503
            $sql .= " WHERE rowid = " . ((int) $this->id);
1504
1505
            dol_syslog(get_only_class($this) . "::addTimeSpent", LOG_DEBUG);
1506
            if (!$this->db->query($sql)) {
1507
                $this->error = $this->db->lasterror();
1508
                $ret = -2;
1509
            }
1510
1511
            // Update hourly rate of this time spent entry
1512
            $resql_thm_user = $this->db->query("SELECT thm FROM " . MAIN_DB_PREFIX . "user WHERE rowid = " . ((int) $timespent->fk_user));
1513
            if (!empty($resql_thm_user)) {
1514
                $obj_thm_user = $this->db->fetch_object($resql_thm_user);
1515
                $timespent->thm = $obj_thm_user->thm;
1516
            }
1517
            $res_update = $timespent->update($user);
1518
1519
            dol_syslog(get_only_class($this) . "::addTimeSpent", LOG_DEBUG);
1520
            if ($res_update <= 0) {
1521
                $this->error = $this->db->lasterror();
1522
                $ret = -2;
1523
            }
1524
        }
1525
1526
        if ($ret > 0) {
1527
            $this->db->commit();
1528
        } else {
1529
            $this->db->rollback();
1530
        }
1531
        return $ret;
1532
    }
1533
1534
    /**
1535
     *  Fetch records of time spent of this task
1536
     *
1537
     *  @param  string  $morewherefilter    Add more filter into where SQL request (must start with ' AND ...')
1538
     *  @return int                         Return integer <0 if KO, array of time spent if OK
1539
     */
1540
    public function fetchTimeSpentOnTask($morewherefilter = '')
1541
    {
1542
        $arrayres = array();
1543
1544
        $sql = "SELECT";
1545
        $sql .= " s.rowid as socid,";
1546
        $sql .= " s.nom as thirdparty_name,";
1547
        $sql .= " s.email as thirdparty_email,";
1548
        $sql .= " ptt.rowid,";
1549
        $sql .= " ptt.ref_ext,";
1550
        $sql .= " ptt.fk_element as fk_task,";
1551
        $sql .= " ptt.element_date as task_date,";
1552
        $sql .= " ptt.element_datehour as task_datehour,";
1553
        $sql .= " ptt.element_date_withhour as task_date_withhour,";
1554
        $sql .= " ptt.element_duration as task_duration,";
1555
        $sql .= " ptt.fk_user,";
1556
        $sql .= " ptt.note,";
1557
        $sql .= " ptt.thm,";
1558
        $sql .= " pt.rowid as task_id,";
1559
        $sql .= " pt.ref as task_ref,";
1560
        $sql .= " pt.label as task_label,";
1561
        $sql .= " p.rowid as project_id,";
1562
        $sql .= " p.ref as project_ref,";
1563
        $sql .= " p.title as project_label,";
1564
        $sql .= " p.public as public";
1565
        $sql .= " FROM " . MAIN_DB_PREFIX . "element_time as ptt, " . MAIN_DB_PREFIX . "projet_task as pt, " . MAIN_DB_PREFIX . "projet as p";
1566
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON p.fk_soc = s.rowid";
1567
        $sql .= " WHERE ptt.fk_element = pt.rowid AND pt.fk_projet = p.rowid";
1568
        $sql .= " AND ptt.elementtype = 'task'";
1569
        $sql .= " AND pt.rowid = " . ((int) $this->id);
1570
        $sql .= " AND pt.entity IN (" . getEntity('project') . ")";
1571
        if ($morewherefilter) {
1572
            $sql .= $morewherefilter;
1573
        }
1574
1575
        dol_syslog(get_only_class($this) . "::fetchAllTimeSpent", LOG_DEBUG);
1576
        $resql = $this->db->query($sql);
1577
        if ($resql) {
1578
            $num = $this->db->num_rows($resql);
1579
1580
            $i = 0;
1581
            while ($i < $num) {
1582
                $obj = $this->db->fetch_object($resql);
1583
1584
                $newobj = new stdClass();
1585
1586
                $newobj->socid              = $obj->socid;
1587
                $newobj->thirdparty_name    = $obj->thirdparty_name;
1588
                $newobj->thirdparty_email   = $obj->thirdparty_email;
1589
1590
                $newobj->fk_project         = $obj->project_id;
1591
                $newobj->project_ref        = $obj->project_ref;
1592
                $newobj->project_label = $obj->project_label;
1593
                $newobj->public             = $obj->project_public;
1594
1595
                $newobj->fk_task            = $obj->task_id;
1596
                $newobj->task_ref = $obj->task_ref;
1597
                $newobj->task_label = $obj->task_label;
1598
1599
                $newobj->timespent_line_id = $obj->rowid;
1600
                $newobj->timespent_line_ref_ext = $obj->ref_ext;
1601
                $newobj->timespent_line_date = $this->db->jdate($obj->task_date);
1602
                $newobj->timespent_line_datehour    = $this->db->jdate($obj->task_datehour);
1603
                $newobj->timespent_line_withhour = $obj->task_date_withhour;
1604
                $newobj->timespent_line_duration = $obj->task_duration;
1605
                $newobj->timespent_line_fk_user = $obj->fk_user;
1606
                $newobj->timespent_line_thm = $obj->thm;    // hourly rate
1607
                $newobj->timespent_line_note = $obj->note;
1608
1609
                $arrayres[] = $newobj;
1610
1611
                $i++;
1612
            }
1613
1614
            $this->db->free($resql);
1615
1616
            $this->lines = $arrayres;
1617
            return 1;
1618
        } else {
1619
            dol_print_error($this->db);
1620
            $this->error = "Error " . $this->db->lasterror();
1621
            return -1;
1622
        }
1623
    }
1624
1625
1626
    /**
1627
     *  Calculate total of time spent for task
1628
     *
1629
     *  @param  User|int    $userobj            Filter on user. null or 0=No filter
1630
     *  @param  string      $morewherefilter    Add more filter into where SQL request (must start with ' AND ...')
1631
     *  @return array|int                       Array of info for task array('min_date', 'max_date', 'total_duration', 'total_amount', 'nblines', 'nblinesnull')
1632
     */
1633
    public function getSummaryOfTimeSpent($userobj = null, $morewherefilter = '')
1634
    {
1635
        if (is_object($userobj)) {
1636
            $userid = $userobj->id;
1637
        } else {
1638
            $userid = $userobj; // old method
1639
        }
1640
1641
        $id = $this->id;
1642
        if (empty($id) && empty($userid)) {
1643
            dol_syslog("getSummaryOfTimeSpent called on a not loaded task without user param defined", LOG_ERR);
1644
            return -1;
1645
        }
1646
1647
        $result = array();
1648
1649
        $sql = "SELECT";
1650
        $sql .= " MIN(t.element_datehour) as min_date,";
1651
        $sql .= " MAX(t.element_datehour) as max_date,";
1652
        $sql .= " SUM(t.element_duration) as total_duration,";
1653
        $sql .= " SUM(t.element_duration / 3600 * " . $this->db->ifsql("t.thm IS NULL", 0, "t.thm") . ") as total_amount,";
1654
        $sql .= " COUNT(t.rowid) as nblines,";
1655
        $sql .= " SUM(" . $this->db->ifsql("t.thm IS NULL", 1, 0) . ") as nblinesnull";
1656
        $sql .= " FROM " . MAIN_DB_PREFIX . "element_time as t";
1657
        $sql .= " WHERE t.elementtype='task'";
1658
        if ($morewherefilter) {
1659
            $sql .= $morewherefilter;
1660
        }
1661
        if ($id > 0) {
1662
            $sql .= " AND t.fk_element = " . ((int) $id);
1663
        }
1664
        if ($userid > 0) {
1665
            $sql .= " AND t.fk_user = " . ((int) $userid);
1666
        }
1667
1668
        dol_syslog(get_only_class($this) . "::getSummaryOfTimeSpent", LOG_DEBUG);
1669
        $resql = $this->db->query($sql);
1670
        if ($resql) {
1671
            $obj = $this->db->fetch_object($resql);
1672
1673
            $result['min_date'] = $obj->min_date; // deprecated. use the ->timespent_xxx instead
1674
            $result['max_date'] = $obj->max_date; // deprecated. use the ->timespent_xxx instead
1675
            $result['total_duration'] = $obj->total_duration; // deprecated. use the ->timespent_xxx instead
1676
1677
            $this->timespent_min_date = $this->db->jdate($obj->min_date);
1678
            $this->timespent_max_date = $this->db->jdate($obj->max_date);
1679
            $this->timespent_total_duration = $obj->total_duration;
1680
            $this->timespent_total_amount = $obj->total_amount;
1681
            $this->timespent_nblinesnull = ($obj->nblinesnull ? $obj->nblinesnull : 0);
1682
            $this->timespent_nblines = ($obj->nblines ? $obj->nblines : 0);
1683
1684
            $this->db->free($resql);
1685
        } else {
1686
            dol_print_error($this->db);
1687
        }
1688
        return $result;
1689
    }
1690
1691
    /**
1692
     *  Calculate quantity and value of time consumed using the thm (hourly amount value of work for user entering time)
1693
     *
1694
     *  @param      User|string $fuser      Filter on a dedicated user
1695
     *  @param      string      $dates      Start date (ex 00:00:00)
1696
     *  @param      string      $datee      End date (ex 23:59:59)
1697
     *  @return     array                   Array of info for task array('amount','nbseconds','nblinesnull')
1698
     */
1699
    public function getSumOfAmount($fuser = '', $dates = '', $datee = '')
1700
    {
1701
        $id = $this->id;
1702
1703
        $result = array();
1704
1705
        $sql = "SELECT";
1706
        $sql .= " SUM(t.element_duration) as nbseconds,";
1707
        $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";
1708
        $sql .= " FROM " . MAIN_DB_PREFIX . "element_time as t";
1709
        $sql .= " WHERE t.elementtype='task' AND t.fk_element = " . ((int) $id);
1710
        if (is_object($fuser) && $fuser->id > 0) {
1711
            $sql .= " AND fk_user = " . ((int) $fuser->id);
1712
        }
1713
        if ($dates > 0) {
1714
            $datefieldname = "element_datehour";
1715
            $sql .= " AND (" . $datefieldname . " >= '" . $this->db->idate($dates) . "' OR " . $datefieldname . " IS NULL)";
1716
        }
1717
        if ($datee > 0) {
1718
            $datefieldname = "element_datehour";
1719
            $sql .= " AND (" . $datefieldname . " <= '" . $this->db->idate($datee) . "' OR " . $datefieldname . " IS NULL)";
1720
        }
1721
        //print $sql;
1722
1723
        dol_syslog(get_only_class($this) . "::getSumOfAmount", LOG_DEBUG);
1724
        $resql = $this->db->query($sql);
1725
        if ($resql) {
1726
            $obj = $this->db->fetch_object($resql);
1727
1728
            $result['amount'] = $obj->amount;
1729
            $result['nbseconds'] = $obj->nbseconds;
1730
            $result['nblinesnull'] = $obj->nblinesnull;
1731
1732
            $this->db->free($resql);
1733
            return $result;
1734
        } else {
1735
            dol_print_error($this->db);
1736
            return $result;
1737
        }
1738
    }
1739
1740
    /**
1741
     *  Load properties of timespent of a task from the time spent ID.
1742
     *
1743
     *  @param  int     $id     Id in time spent table
1744
     *  @return int             Return integer <0 if KO, >0 if OK
1745
     */
1746
    public function fetchTimeSpent($id)
1747
    {
1748
        $timespent = new TimeSpent($this->db);
1749
        $timespent->fetch($id);
1750
1751
        dol_syslog(get_only_class($this) . "::fetchTimeSpent", LOG_DEBUG);
1752
1753
        if ($timespent->id > 0) {
1754
            $this->timespent_id = $timespent->id;
1755
            $this->id = $timespent->fk_element;
1756
            $this->timespent_date = $timespent->element_date;
1757
            $this->timespent_datehour   = $timespent->element_datehour;
1758
            $this->timespent_withhour   = $timespent->element_date_withhour;
1759
            $this->timespent_duration = $timespent->element_duration;
1760
            $this->timespent_fk_user    = $timespent->fk_user;
1761
            $this->timespent_fk_product = $timespent->fk_product;
1762
            $this->timespent_thm        = $timespent->thm; // hourly rate
1763
            $this->timespent_note = $timespent->note;
1764
1765
            return 1;
1766
        }
1767
1768
        return 0;
1769
    }
1770
1771
    /**
1772
     *  Load all records of time spent
1773
     *
1774
     *  @param  User        $userobj            User object
1775
     *  @param  string      $morewherefilter    Add more filter into where SQL request (must start with ' AND ...')
1776
     *  @return array|int                       Return integer <0 if KO, array of time spent if OK
1777
     */
1778
    public function fetchAllTimeSpent(User $userobj, $morewherefilter = '')
1779
    {
1780
        $arrayres = array();
1781
1782
        $sql = "SELECT";
1783
        $sql .= " s.rowid as socid,";
1784
        $sql .= " s.nom as thirdparty_name,";
1785
        $sql .= " s.email as thirdparty_email,";
1786
        $sql .= " ptt.rowid,";
1787
        $sql .= " ptt.fk_element as fk_task,";
1788
        $sql .= " ptt.element_date as task_date,";
1789
        $sql .= " ptt.element_datehour as task_datehour,";
1790
        $sql .= " ptt.element_date_withhour as task_date_withhour,";
1791
        $sql .= " ptt.element_duration as task_duration,";
1792
        $sql .= " ptt.fk_user,";
1793
        $sql .= " ptt.note,";
1794
        $sql .= " ptt.thm,";
1795
        $sql .= " pt.rowid as task_id,";
1796
        $sql .= " pt.ref as task_ref,";
1797
        $sql .= " pt.label as task_label,";
1798
        $sql .= " p.rowid as project_id,";
1799
        $sql .= " p.ref as project_ref,";
1800
        $sql .= " p.title as project_label,";
1801
        $sql .= " p.public as public";
1802
        $sql .= " FROM " . MAIN_DB_PREFIX . "element_time as ptt, " . MAIN_DB_PREFIX . "projet_task as pt, " . MAIN_DB_PREFIX . "projet as p";
1803
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON p.fk_soc = s.rowid";
1804
        $sql .= " WHERE ptt.fk_element = pt.rowid AND pt.fk_projet = p.rowid";
1805
        $sql .= " AND ptt.elementtype = 'task'";
1806
        $sql .= " AND ptt.fk_user = " . ((int) $userobj->id);
1807
        $sql .= " AND pt.entity IN (" . getEntity('project') . ")";
1808
        if ($morewherefilter) {
1809
            $sql .= $morewherefilter;
1810
        }
1811
1812
        dol_syslog(get_only_class($this) . "::fetchAllTimeSpent", LOG_DEBUG);
1813
        $resql = $this->db->query($sql);
1814
        if ($resql) {
1815
            $num = $this->db->num_rows($resql);
1816
1817
            $i = 0;
1818
            while ($i < $num) {
1819
                $obj = $this->db->fetch_object($resql);
1820
1821
                $newobj = new stdClass();
1822
1823
                $newobj->socid              = $obj->socid;
1824
                $newobj->thirdparty_name    = $obj->thirdparty_name;
1825
                $newobj->thirdparty_email   = $obj->thirdparty_email;
1826
1827
                $newobj->fk_project         = $obj->project_id;
1828
                $newobj->project_ref        = $obj->project_ref;
1829
                $newobj->project_label = $obj->project_label;
1830
                $newobj->public             = $obj->project_public;
1831
1832
                $newobj->fk_task            = $obj->task_id;
1833
                $newobj->task_ref = $obj->task_ref;
1834
                $newobj->task_label = $obj->task_label;
1835
1836
                $newobj->timespent_id = $obj->rowid;
1837
                $newobj->timespent_date = $this->db->jdate($obj->task_date);
1838
                $newobj->timespent_datehour = $this->db->jdate($obj->task_datehour);
1839
                $newobj->timespent_withhour = $obj->task_date_withhour;
1840
                $newobj->timespent_duration = $obj->task_duration;
1841
                $newobj->timespent_fk_user = $obj->fk_user;
1842
                $newobj->timespent_thm = $obj->thm; // hourly rate
1843
                $newobj->timespent_note = $obj->note;
1844
1845
                $arrayres[] = $newobj;
1846
1847
                $i++;
1848
            }
1849
1850
            $this->db->free($resql);
1851
        } else {
1852
            dol_print_error($this->db);
1853
            $this->error = "Error " . $this->db->lasterror();
1854
            return -1;
1855
        }
1856
1857
        return $arrayres;
1858
    }
1859
1860
    /**
1861
     *  Update time spent
1862
     *
1863
     *  @param  User    $user           User id
1864
     *  @param  int     $notrigger      0=launch triggers after, 1=disable triggers
1865
     *  @return int                     Return integer <0 if KO, >0 if OK
1866
     */
1867
    public function updateTimeSpent($user, $notrigger = 0)
1868
    {
1869
        global $conf, $langs;
1870
1871
        $ret = 0;
1872
1873
        // Check parameters
1874
        if ($this->timespent_date == '') {
1875
            $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentities("Date"));
1876
            return -1;
1877
        }
1878
        if (!($this->timespent_fk_user > 0)) {
1879
            $this->error = $langs->trans("ErrorFieldRequired", $langs->transnoentities("User"));
1880
            return -1;
1881
        }
1882
1883
        // Clean parameters
1884
        if (empty($this->timespent_datehour)) {
1885
            $this->timespent_datehour = $this->timespent_date;
1886
        }
1887
        if (isset($this->timespent_note)) {
1888
            $this->timespent_note = trim($this->timespent_note);
1889
        }
1890
1891
        if (getDolGlobalString('PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS')) {
1892
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/date.lib.php';
1893
            $restrictBefore = dol_time_plus_duree(dol_now(), - $conf->global->PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS, 'm');
1894
1895
            if ($this->timespent_date < $restrictBefore) {
1896
                $this->error = $langs->trans('TimeRecordingRestrictedToNMonthsBack', getDolGlobalString('PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS'));
1897
                $this->errors[] = $this->error;
1898
                return -1;
1899
            }
1900
        }
1901
1902
        $this->db->begin();
1903
1904
        $timespent = new TimeSpent($this->db);
1905
        $timespent->fetch($this->timespent_id);
1906
        $timespent->element_date = $this->timespent_date;
1907
        $timespent->element_datehour = $this->timespent_datehour;
1908
        $timespent->element_date_withhour = $this->timespent_withhour;
1909
        $timespent->element_duration = $this->timespent_duration;
1910
        $timespent->fk_user = $this->timespent_fk_user;
1911
        $timespent->fk_product = $this->timespent_fk_product;
1912
        $timespent->note = $this->timespent_note;
1913
        $timespent->invoice_id = $this->timespent_invoiceid;
1914
        $timespent->invoice_line_id = $this->timespent_invoicelineid;
1915
1916
        dol_syslog(get_only_class($this) . "::updateTimeSpent", LOG_DEBUG);
1917
        if ($timespent->update($user) > 0) {
1918
            if (!$notrigger) {
1919
                // Call trigger
1920
                $result = $this->call_trigger('TASK_TIMESPENT_MODIFY', $user);
1921
                if ($result < 0) {
1922
                    $this->db->rollback();
1923
                    $ret = -1;
1924
                } else {
1925
                    $ret = 1;
1926
                }
1927
                // End call triggers
1928
            } else {
1929
                $ret = 1;
1930
            }
1931
        } else {
1932
            $this->error = $this->db->lasterror();
1933
            $this->db->rollback();
1934
            $ret = -1;
1935
        }
1936
1937
        if ($ret == 1 && (($this->timespent_old_duration != $this->timespent_duration) || getDolGlobalString('TIMESPENT_ALWAYS_UPDATE_THM'))) {
1938
            if ($this->timespent_old_duration != $this->timespent_duration) {
1939
                // Recalculate amount of time spent for task and update denormalized field
1940
                $sql = "UPDATE " . MAIN_DB_PREFIX . "projet_task";
1941
                $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) . ")";
1942
                if (isset($this->progress)) {
1943
                    $sql .= ", progress = " . ((float) $this->progress); // Do not overwrite value if not provided
1944
                }
1945
                $sql .= " WHERE rowid = " . ((int) $this->id);
1946
1947
                dol_syslog(get_only_class($this) . "::updateTimeSpent", LOG_DEBUG);
1948
                if (!$this->db->query($sql)) {
1949
                    $this->error = $this->db->lasterror();
1950
                    $this->db->rollback();
1951
                    $ret = -2;
1952
                }
1953
            }
1954
1955
            // Update hourly rate of this time spent entry, but only if it was not set initially
1956
            $res_update = 1;
1957
            if (empty($timespent->thm) || getDolGlobalString('TIMESPENT_ALWAYS_UPDATE_THM')) {
1958
                $resql_thm_user = $this->db->query("SELECT thm FROM " . MAIN_DB_PREFIX . "user WHERE rowid = " . ((int) $timespent->fk_user));
1959
                if (!empty($resql_thm_user)) {
1960
                    $obj_thm_user = $this->db->fetch_object($resql_thm_user);
1961
                    $timespent->thm = $obj_thm_user->thm;
1962
                }
1963
                $res_update = $timespent->update($user);
1964
            }
1965
1966
            dol_syslog(get_only_class($this) . "::updateTimeSpent", LOG_DEBUG);
1967
            if ($res_update <= 0) {
1968
                $this->error = $this->db->lasterror();
1969
                $ret = -2;
1970
            }
1971
        }
1972
1973
        if ($ret >= 0) {
1974
            $this->db->commit();
1975
        }
1976
        return $ret;
1977
    }
1978
1979
    /**
1980
     *  Delete time spent
1981
     *
1982
     *  @param  User    $user           User that delete
1983
     *  @param  int     $notrigger      0=launch triggers after, 1=disable triggers
1984
     *  @return int                     Return integer <0 if KO, >0 if OK
1985
     */
1986
    public function delTimeSpent($user, $notrigger = 0)
1987
    {
1988
        global $conf, $langs;
1989
1990
        $error = 0;
1991
1992
        if (getDolGlobalString('PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS')) {
1993
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/date.lib.php';
1994
            $restrictBefore = dol_time_plus_duree(dol_now(), - $conf->global->PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS, 'm');
1995
1996
            if ($this->timespent_date < $restrictBefore) {
1997
                $this->error = $langs->trans('TimeRecordingRestrictedToNMonthsBack', getDolGlobalString('PROJECT_TIMESHEET_PREVENT_AFTER_MONTHS'));
1998
                $this->errors[] = $this->error;
1999
                return -1;
2000
            }
2001
        }
2002
2003
        $this->db->begin();
2004
2005
        if (!$notrigger) {
2006
            // Call trigger
2007
            $result = $this->call_trigger('TASK_TIMESPENT_DELETE', $user);
2008
            if ($result < 0) {
2009
                $error++;
2010
            }
2011
            // End call triggers
2012
        }
2013
2014
        if (!$error) {
2015
            $timespent = new TimeSpent($this->db);
2016
            $timespent->fetch($this->timespent_id);
2017
2018
            $res_del = $timespent->delete($user);
2019
2020
            if ($res_del < 0) {
2021
                $error++;
2022
                $this->errors[] = "Error " . $this->db->lasterror();
2023
            }
2024
        }
2025
2026
        if (!$error) {
2027
            $sql = "UPDATE " . MAIN_DB_PREFIX . "projet_task";
2028
            $sql .= " SET duration_effective = duration_effective - " . $this->db->escape($this->timespent_duration ? $this->timespent_duration : 0);
2029
            $sql .= " WHERE rowid = " . ((int) $this->id);
2030
2031
            dol_syslog(get_only_class($this) . "::delTimeSpent", LOG_DEBUG);
2032
            if ($this->db->query($sql)) {
2033
                $result = 0;
2034
            } else {
2035
                $this->error = $this->db->lasterror();
2036
                $result = -2;
2037
            }
2038
        }
2039
2040
        // Commit or rollback
2041
        if ($error) {
2042
            foreach ($this->errors as $errmsg) {
2043
                dol_syslog(get_only_class($this) . "::delTimeSpent " . $errmsg, LOG_ERR);
2044
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
2045
            }
2046
            $this->db->rollback();
2047
            return -1 * $error;
2048
        } else {
2049
            $this->db->commit();
2050
            return 1;
2051
        }
2052
    }
2053
2054
    /** Load an object from its id and create a new one in database
2055
     *
2056
     *  @param  User    $user                   User making the clone
2057
     *  @param  int     $fromid                 Id of object to clone
2058
     *  @param  int     $project_id             Id of project to attach clone task
2059
     *  @param  int     $parent_task_id         Id of task to attach clone task
2060
     *  @param  bool    $clone_change_dt        recalculate date of task regarding new project start date
2061
     *  @param  bool    $clone_affectation      clone affectation of project
2062
     *  @param  bool    $clone_time             clone time of project
2063
     *  @param  bool    $clone_file             clone file of project
2064
     *  @param  bool    $clone_note             clone note of project
2065
     *  @param  bool    $clone_prog             clone progress of project
2066
     *  @return int                             New id of clone
2067
     */
2068
    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)
2069
    {
2070
        global $langs, $conf;
2071
2072
        $error = 0;
2073
2074
        //Use 00:00 of today if time is use on task.
2075
        $now = dol_mktime(0, 0, 0, dol_print_date(dol_now(), '%m'), dol_print_date(dol_now(), '%d'), dol_print_date(dol_now(), '%Y'));
2076
2077
        $datec = $now;
2078
2079
        $clone_task = new Task($this->db);
2080
        $origin_task = new Task($this->db);
2081
2082
        $clone_task->context['createfromclone'] = 'createfromclone';
2083
2084
        $this->db->begin();
2085
2086
        // Load source object
2087
        $clone_task->fetch($fromid);
2088
        $clone_task->fetch_optionals();
2089
        //var_dump($clone_task->array_options);exit;
2090
2091
        $origin_task->fetch($fromid);
2092
2093
        $defaultref = '';
2094
        $obj = !getDolGlobalString('PROJECT_TASK_ADDON') ? 'mod_task_simple' : $conf->global->PROJECT_TASK_ADDON;
2095
        if (getDolGlobalString('PROJECT_TASK_ADDON') && is_readable(DOL_DOCUMENT_ROOT . "/core/modules/project/task/" . getDolGlobalString('PROJECT_TASK_ADDON') . ".php")) {
2096
            require_once DOL_DOCUMENT_ROOT . "/core/modules/project/task/" . getDolGlobalString('PROJECT_TASK_ADDON') . '.php';
2097
            $modTask = new $obj();
2098
            $defaultref = $modTask->getNextValue(0, $clone_task);
2099
        }
2100
2101
        $ori_project_id                 = $clone_task->fk_project;
2102
2103
        $clone_task->id                 = 0;
2104
        $clone_task->ref                = $defaultref;
2105
        $clone_task->fk_project = $project_id;
2106
        $clone_task->fk_task_parent = $parent_task_id;
2107
        $clone_task->date_c = $datec;
2108
        $clone_task->planned_workload = $origin_task->planned_workload;
2109
        $clone_task->rang = $origin_task->rang;
2110
        $clone_task->priority = $origin_task->priority;
2111
2112
        //Manage Task Date
2113
        if ($clone_change_dt) {
2114
            $projectstatic = new Project($this->db);
2115
            $projectstatic->fetch($ori_project_id);
2116
2117
            //Origin project start date
2118
            $orign_project_dt_start = $projectstatic->date_start;
2119
2120
            //Calculate new task start date with difference between origin proj start date and origin task start date
2121
            if (!empty($clone_task->date_start)) {
2122
                $clone_task->date_start = $now + $clone_task->date_start - $orign_project_dt_start;
2123
            }
2124
2125
            //Calculate new task end date with difference between origin proj end date and origin task end date
2126
            if (!empty($clone_task->date_end)) {
2127
                $clone_task->date_end = $now + $clone_task->date_end - $orign_project_dt_start;
2128
            }
2129
        }
2130
2131
        if (!$clone_prog) {
2132
            $clone_task->progress = 0;
2133
        }
2134
2135
        // Create clone
2136
        $result = $clone_task->create($user);
2137
2138
        // Other options
2139
        if ($result < 0) {
2140
            $this->error = $clone_task->error;
2141
            $error++;
2142
        }
2143
        // End
2144
        if ($error) {
2145
            $clone_task_id = 0;  // For static tool check
2146
        } else {
2147
            $clone_task_id = $clone_task->id;
2148
            $clone_task_ref = $clone_task->ref;
2149
2150
            //Note Update
2151
            if (!$clone_note) {
2152
                $clone_task->note_private = '';
2153
                $clone_task->note_public = '';
2154
            } else {
2155
                $this->db->begin();
2156
                $res = $clone_task->update_note(dol_html_entity_decode($clone_task->note_public, ENT_QUOTES | ENT_HTML5), '_public');
2157
                if ($res < 0) {
2158
                    $this->error .= $clone_task->error;
2159
                    $error++;
2160
                    $this->db->rollback();
2161
                } else {
2162
                    $this->db->commit();
2163
                }
2164
2165
                $this->db->begin();
2166
                $res = $clone_task->update_note(dol_html_entity_decode($clone_task->note_private, ENT_QUOTES | ENT_HTML5), '_private');
2167
                if ($res < 0) {
2168
                    $this->error .= $clone_task->error;
2169
                    $error++;
2170
                    $this->db->rollback();
2171
                } else {
2172
                    $this->db->commit();
2173
                }
2174
            }
2175
2176
            //Duplicate file
2177
            if ($clone_file) {
2178
                require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
2179
2180
                //retrieve project origin ref to know folder to copy
2181
                $projectstatic = new Project($this->db);
2182
                $projectstatic->fetch($ori_project_id);
2183
                $ori_project_ref = $projectstatic->ref;
2184
2185
                if ($ori_project_id != $project_id) {
2186
                    $projectstatic->fetch($project_id);
2187
                    $clone_project_ref = $projectstatic->ref;
2188
                } else {
2189
                    $clone_project_ref = $ori_project_ref;
2190
                }
2191
2192
                $clone_task_dir = $conf->project->dir_output . "/" . dol_sanitizeFileName($clone_project_ref) . "/" . dol_sanitizeFileName($clone_task_ref);
2193
                $ori_task_dir = $conf->project->dir_output . "/" . dol_sanitizeFileName($ori_project_ref) . "/" . dol_sanitizeFileName($fromid);
2194
2195
                $filearray = dol_dir_list($ori_task_dir, "files", 0, '', '(\.meta|_preview.*\.png)$', '', SORT_ASC, 1);
2196
                foreach ($filearray as $key => $file) {
2197
                    if (!file_exists($clone_task_dir)) {
2198
                        if (dol_mkdir($clone_task_dir) < 0) {
2199
                            $this->error .= $langs->trans('ErrorInternalErrorDetected') . ':dol_mkdir';
2200
                            $error++;
2201
                        }
2202
                    }
2203
2204
                    $rescopy = dol_copy($ori_task_dir . '/' . $file['name'], $clone_task_dir . '/' . $file['name'], 0, 1);
2205
                    if (is_numeric($rescopy) && $rescopy < 0) {
2206
                        $this->error .= $langs->trans("ErrorFailToCopyFile", $ori_task_dir . '/' . $file['name'], $clone_task_dir . '/' . $file['name']);
2207
                        $error++;
2208
                    }
2209
                }
2210
            }
2211
2212
            // clone affectation
2213
            if ($clone_affectation) {
2214
                $origin_task = new Task($this->db);
2215
                $origin_task->fetch($fromid);
2216
2217
                foreach (array('internal', 'external') as $source) {
2218
                    $tab = $origin_task->liste_contact(-1, $source);
2219
                    $num = count($tab);
2220
                    $i = 0;
2221
                    while ($i < $num) {
2222
                        $clone_task->add_contact($tab[$i]['id'], $tab[$i]['code'], $tab[$i]['source']);
2223
                        if ($clone_task->error == 'DB_ERROR_RECORD_ALREADY_EXISTS') {
2224
                            $langs->load("errors");
2225
                            $this->error .= $langs->trans("ErrorThisContactIsAlreadyDefinedAsThisType");
2226
                            $error++;
2227
                        } else {
2228
                            if ($clone_task->error != '') {
2229
                                $this->error .= $clone_task->error;
2230
                                $error++;
2231
                            }
2232
                        }
2233
                        $i++;
2234
                    }
2235
                }
2236
            }
2237
2238
            if ($clone_time) {
2239
                //TODO clone time of affectation
2240
            }
2241
        }
2242
2243
        unset($clone_task->context['createfromclone']);
2244
2245
        if (!$error) {
2246
            $this->db->commit();
2247
            return $clone_task_id;
2248
        } else {
2249
            $this->db->rollback();
2250
            dol_syslog(get_only_class($this) . "::createFromClone nbError: " . $error . " error : " . $this->error, LOG_ERR);
2251
            return -1;
2252
        }
2253
    }
2254
2255
2256
    /**
2257
     *  Return status label of object
2258
     *
2259
     *  @param  integer $mode       0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto
2260
     *  @return string              Label
2261
     */
2262
    public function getLibStatut($mode = 0)
2263
    {
2264
        return $this->LibStatut($this->status, $mode);
2265
    }
2266
2267
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2268
    /**
2269
     *  Return status label for an object
2270
     *
2271
     *  @param  int         $status     Id status
2272
     *  @param  integer     $mode       0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto
2273
     *  @return string                  Label
2274
     */
2275
    public function LibStatut($status, $mode = 0)
2276
    {
2277
		// phpcs:enable
2278
        global $langs;
2279
2280
        // list of Statut of the task
2281
        $this->labelStatus[0] = 'Draft';
2282
        $this->labelStatus[1] = 'ToDo';
2283
        $this->labelStatus[2] = 'Running';
2284
        $this->labelStatus[3] = 'Finish';
2285
        $this->labelStatus[4] = 'Transfered';
2286
        $this->labelStatusShort[0] = 'Draft';
2287
        $this->labelStatusShort[1] = 'ToDo';
2288
        $this->labelStatusShort[2] = 'Running';
2289
        $this->labelStatusShort[3] = 'Completed';
2290
        $this->labelStatusShort[4] = 'Transfered';
2291
2292
        if ($mode == 0) {
2293
            return $langs->trans($this->labelStatus[$status]);
2294
        } elseif ($mode == 1) {
2295
            return $langs->trans($this->labelStatusShort[$status]);
2296
        } elseif ($mode == 2) {
2297
            if ($status == 0) {
2298
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut0') . ' ' . $langs->trans($this->labelStatusShort[$status]);
2299
            } elseif ($status == 1) {
2300
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut1') . ' ' . $langs->trans($this->labelStatusShort[$status]);
2301
            } elseif ($status == 2) {
2302
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut3') . ' ' . $langs->trans($this->labelStatusShort[$status]);
2303
            } elseif ($status == 3) {
2304
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut6') . ' ' . $langs->trans($this->labelStatusShort[$status]);
2305
            } elseif ($status == 4) {
2306
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut6') . ' ' . $langs->trans($this->labelStatusShort[$status]);
2307
            } elseif ($status == 5) {
2308
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut5') . ' ' . $langs->trans($this->labelStatusShort[$status]);
2309
            }
2310
        } elseif ($mode == 3) {
2311
            if ($status == 0) {
2312
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut0');
2313
            } elseif ($status == 1) {
2314
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut1');
2315
            } elseif ($status == 2) {
2316
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut3');
2317
            } elseif ($status == 3) {
2318
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut6');
2319
            } elseif ($status == 4) {
2320
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut6');
2321
            } elseif ($status == 5) {
2322
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut5');
2323
            }
2324
        } elseif ($mode == 4) {
2325
            if ($status == 0) {
2326
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut0') . ' ' . $langs->trans($this->labelStatus[$status]);
2327
            } elseif ($status == 1) {
2328
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut1') . ' ' . $langs->trans($this->labelStatus[$status]);
2329
            } elseif ($status == 2) {
2330
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut3') . ' ' . $langs->trans($this->labelStatus[$status]);
2331
            } elseif ($status == 3) {
2332
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut6') . ' ' . $langs->trans($this->labelStatus[$status]);
2333
            } elseif ($status == 4) {
2334
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut6') . ' ' . $langs->trans($this->labelStatus[$status]);
2335
            } elseif ($status == 5) {
2336
                return img_picto($langs->trans($this->labelStatusShort[$status]), 'statut5') . ' ' . $langs->trans($this->labelStatus[$status]);
2337
            }
2338
        } elseif ($mode == 5) {
2339
            /*if ($status==0) return $langs->trans($this->labelStatusShort[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut0');
2340
            elseif ($status==1) return $langs->trans($this->labelStatusShort[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut1');
2341
            elseif ($status==2) return $langs->trans($this->labelStatusShort[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut3');
2342
            elseif ($status==3) return $langs->trans($this->labelStatusShort[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut6');
2343
            elseif ($status==4) return $langs->trans($this->labelStatusShort[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut6');
2344
            elseif ($status==5) return $langs->trans($this->labelStatusShort[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut5');
2345
            */
2346
            //else return $this->progress.' %';
2347
            return '&nbsp;';
2348
        } elseif ($mode == 6) {
2349
            /*if ($status==0) return $langs->trans($this->labelStatus[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut0');
2350
            elseif ($status==1) return $langs->trans($this->labelStatus[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut1');
2351
            elseif ($status==2) return $langs->trans($this->labelStatus[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut3');
2352
            elseif ($status==3) return $langs->trans($this->labelStatus[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut6');
2353
            elseif ($status==4) return $langs->trans($this->labelStatus[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut6');
2354
            elseif ($status==5) return $langs->trans($this->labelStatus[$status]).' '.img_picto($langs->trans($this->labelStatusShort[$status]),'statut5');
2355
            */
2356
            //else return $this->progress.' %';
2357
            return '&nbsp;';
2358
        }
2359
        return "";
2360
    }
2361
2362
    /**
2363
     *  Create an intervention document on disk using template defined into PROJECT_TASK_ADDON_PDF
2364
     *
2365
     *  @param  string      $modele         force le modele a utiliser ('' par default)
2366
     *  @param  Translate   $outputlangs    object lang a utiliser pour traduction
2367
     *  @param  int         $hidedetails    Hide details of lines
2368
     *  @param  int         $hidedesc       Hide description
2369
     *  @param  int         $hideref        Hide ref
2370
     *  @return int                         0 if KO, 1 if OK
2371
     */
2372
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0)
2373
    {
2374
        $outputlangs->load("projects");
2375
2376
        if (!dol_strlen($modele)) {
2377
            $modele = 'nodefault';
2378
2379
            if (!empty($this->model_pdf)) {
2380
                $modele = $this->model_pdf;
2381
            } elseif (getDolGlobalString('PROJECT_TASK_ADDON_PDF')) {
2382
                $modele = getDolGlobalString('PROJECT_TASK_ADDON_PDF');
2383
            }
2384
        }
2385
2386
        $modelpath = "core/modules/project/task/doc/";
2387
2388
        return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref);
2389
    }
2390
2391
2392
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2393
    /**
2394
     * Load indicators for dashboard (this->nbtodo and this->nbtodolate)
2395
     *
2396
     * @param   User    $user   Object user
2397
     * @return WorkboardResponse|int Return integer <0 if KO, WorkboardResponse if OK
2398
     */
2399
    public function load_board($user)
2400
    {
2401
		// phpcs:enable
2402
        global $conf, $langs;
2403
2404
        // For external user, no check is done on company because readability is managed by public status of project and assignment.
2405
        //$socid = $user->socid;
2406
        $socid = 0;
2407
2408
        $projectstatic = new Project($this->db);
2409
        $projectsListId = $projectstatic->getProjectsAuthorizedForUser($user, 0, 1, $socid);
2410
2411
        // List of tasks (does not care about permissions. Filtering will be done later)
2412
        $sql = "SELECT p.rowid as projectid, p.fk_statut as projectstatus,";
2413
        $sql .= " t.rowid as taskid, t.progress as progress, t.fk_statut as status,";
2414
        $sql .= " t.dateo as date_start, t.datee as date_end";
2415
        $sql .= " FROM " . MAIN_DB_PREFIX . "projet as p";
2416
        //$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s on p.fk_soc = s.rowid";
2417
        //if (! $user->rights->societe->client->voir && ! $socid) $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON sc.fk_soc = s.rowid";
2418
        $sql .= ", " . MAIN_DB_PREFIX . "projet_task as t";
2419
        $sql .= " WHERE p.entity IN (" . getEntity('project', 0) . ')';
2420
        $sql .= " AND p.fk_statut = 1";
2421
        $sql .= " AND t.fk_projet = p.rowid";
2422
        $sql .= " AND (t.progress IS NULL OR t.progress < 100)"; // tasks to do
2423
        if (!$user->hasRight('projet', 'all', 'lire')) {
2424
            $sql .= " AND p.rowid IN (" . $this->db->sanitize($projectsListId) . ")";
2425
        }
2426
        // No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser
2427
        //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).")";
2428
        // No need to check company, as filtering of projects must be done by getProjectsAuthorizedForUser
2429
        // 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))";
2430
2431
        //print $sql;
2432
        $resql = $this->db->query($sql);
2433
        if ($resql) {
2434
            $task_static = new Task($this->db);
2435
2436
            $response = new WorkboardResponse();
2437
            $response->warning_delay = $conf->project->task->warning_delay / 60 / 60 / 24;
2438
            $response->label = $langs->trans("OpenedTasks");
2439
            if ($user->hasRight("projet", "all", "lire")) {
2440
                $response->url = constant('BASE_URL') . '/projet/tasks/list.php?mainmenu=project';
2441
            } else {
2442
                $response->url = constant('BASE_URL') . '/projet/tasks/list.php?mode=mine&amp;mainmenu=project';
2443
            }
2444
            $response->img = img_object('', "task");
2445
2446
            // This assignment in condition is not a bug. It allows walking the results.
2447
            while ($obj = $this->db->fetch_object($resql)) {
2448
                $response->nbtodo++;
2449
2450
                $task_static->projectstatus = $obj->projectstatus;
2451
                $task_static->progress = $obj->progress;
2452
                $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

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

2537
        $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...
2538
2539
        return ($datetouse > 0 && ($datetouse < ($now - $conf->project->task->warning_delay)));
2540
    }
2541
2542
    /**
2543
     *  Return clicable link of object (with eventually picto)
2544
     *
2545
     *  @param      string      $option                 Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
2546
     *  @param      array       $arraydata              Array of data
2547
     *  @return     string                              HTML Code for Kanban thumb.
2548
     */
2549
    public function getKanbanView($option = '', $arraydata = null)
2550
    {
2551
        $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
2552
2553
        $return = '<div class="box-flex-item box-flex-grow-zero">';
2554
        $return .= '<div class="info-box info-box-sm info-box-kanban">';
2555
        $return .= '<span class="info-box-icon bg-infobox-action">';
2556
        $return .= img_picto('', $this->picto);
2557
        //$return .= '<i class="fa fa-dol-action"></i>'; // Can be image
2558
        $return .= '</span>';
2559
        $return .= '<div class="info-box-content">';
2560
        $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . (method_exists($this, 'getNomUrl') ? $this->getNomUrl(1) : $this->ref) . '</span>';
2561
        if ($selected >= 0) {
2562
            $return .= '<input id="cb' . $this->id . '" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="' . $this->id . '"' . ($selected ? ' checked="checked"' : '') . '>';
2563
        }
2564
        if (!empty($arraydata['projectlink'])) {
2565
            //$tmpproject = $arraydata['project'];
2566
            //$return .= '<br><span class="info-box-status ">'.$tmpproject->getNomProject().'</span>';
2567
            $return .= '<br><span class="info-box-status ">' . $arraydata['projectlink'] . '</span>';
2568
        }
2569
        if (property_exists($this, 'budget_amount')) {
2570
            //$return .= '<br><span class="info-box-label amount">'.$langs->trans("Budget").' : '.price($this->budget_amount, 0, $langs, 1, 0, 0, $conf->currency).'</span>';
2571
        }
2572
        if (property_exists($this, 'duration_effective')) {
2573
            $return .= '<br><div class="info-box-label progressinkanban paddingtop">' . getTaskProgressView($this, false, true) . '</div>';
2574
        }
2575
        $return .= '</div>';
2576
        $return .= '</div>';
2577
        $return .= '</div>';
2578
2579
        return $return;
2580
    }
2581
2582
    /**
2583
     *    Merge a task with another one, deleting the given task.
2584
     *    The task given in parameter will be removed.
2585
     *
2586
     *    @param    int     $task_origin_id     Task to merge the data from
2587
     *    @return   int                         -1 if error
2588
     */
2589
    public function mergeTask($task_origin_id)
2590
    {
2591
        global $langs, $hookmanager, $user, $action;
2592
2593
        $error = 0;
2594
        $task_origin = new Task($this->db);     // The thirdparty that we will delete
2595
2596
        dol_syslog("mergeTask merge task id=" . $task_origin_id . " (will be deleted) into the task id=" . $this->id);
2597
2598
        $langs->load('error');
2599
2600
        if (!$error && $task_origin->fetch($task_origin_id) < 1) {
2601
            $this->error = $langs->trans('ErrorRecordNotFound');
2602
            $error++;
2603
        }
2604
2605
        if (!$error) {
2606
            $this->db->begin();
2607
2608
            // Recopy some data
2609
            $listofproperties = array(
2610
                'label', 'description', 'duration_effective', 'planned_workload', 'datec', 'date_start',
2611
                'date_end', 'fk_user_creat', 'fk_user_valid', 'fk_statut', 'progress', 'budget_amount',
2612
                'priority', 'rang', 'fk_projet', 'fk_task_parent'
2613
            );
2614
            foreach ($listofproperties as $property) {
2615
                if (empty($this->$property)) {
2616
                    $this->$property = $task_origin->$property;
2617
                }
2618
            }
2619
2620
            // Concat some data
2621
            $listofproperties = array(
2622
                'note_public', 'note_private'
2623
            );
2624
            foreach ($listofproperties as $property) {
2625
                $this->$property = dol_concatdesc($this->$property, $task_origin->$property);
2626
            }
2627
2628
            // Merge extrafields
2629
            if (is_array($task_origin->array_options)) {
2630
                foreach ($task_origin->array_options as $key => $val) {
2631
                    if (empty($this->array_options[$key])) {
2632
                        $this->array_options[$key] = $val;
2633
                    }
2634
                }
2635
            }
2636
2637
            // Update
2638
            $result = $this->update($user);
2639
2640
            if ($result < 0) {
2641
                $error++;
2642
            }
2643
2644
            // Merge time spent
2645
            if (!$error) {
2646
                $result = $this->mergeTimeSpentTask($task_origin_id, $this->id);
2647
                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...
2648
                    $error++;
2649
                }
2650
            }
2651
2652
            // Merge contacts
2653
            if (!$error) {
2654
                $result = $this->mergeContactTask($task_origin_id, $this->id);
2655
                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...
2656
                    $error++;
2657
                }
2658
            }
2659
2660
            // External modules should update their ones too
2661
            if (!$error) {
2662
                $parameters = array('task_origin' => $task_origin->id, 'task_dest' => $this->id);
2663
                $reshook = $hookmanager->executeHooks('replaceThirdparty', $parameters, $this, $action);
2664
2665
                if ($reshook < 0) {
2666
                    $this->error = $hookmanager->error;
2667
                    $this->errors = $hookmanager->errors;
2668
                    $error++;
2669
                }
2670
            }
2671
2672
2673
            if (!$error) {
2674
                $this->context = array('merge' => 1, 'mergefromid' => $task_origin->id, 'mergefromref' => $task_origin->ref);
2675
2676
                // Call trigger
2677
                $result = $this->call_trigger('TASK_MODIFY', $user);
2678
                if ($result < 0) {
2679
                    $error++;
2680
                }
2681
                // End call triggers
2682
            }
2683
2684
            if (!$error) {
2685
                // We finally remove the old task
2686
                if ($task_origin->delete($user) < 1) {
2687
                    $this->error = $task_origin->error;
2688
                    $this->errors = $task_origin->errors;
2689
                    $error++;
2690
                }
2691
            }
2692
2693
            if (!$error) {
2694
                $this->db->commit();
2695
                return 0;
2696
            } else {
2697
                $langs->load("errors");
2698
                $this->error = $langs->trans('ErrorsTaskMerge');
2699
                $this->db->rollback();
2700
                return -1;
2701
            }
2702
        }
2703
2704
        return -1;
2705
    }
2706
}
2707