Passed
Pull Request — dev (#8)
by Rafael
58:47
created

Ticket   F

Complexity

Total Complexity 575

Size/Duplication

Total Lines 3267
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 1696
dl 0
loc 3267
rs 0.8
c 0
b 0
f 0
wmc 575

48 Methods

Rating   Name   Duplication   Size   Complexity  
F create() 0 151 57
A getLibStatut() 0 3 1
C LibStatut() 0 66 17
D update() 0 152 53
A __construct() 0 32 2
B loadCacheSeveritiesTickets() 0 33 7
A checkExistingRef() 0 12 5
A printSelectStatus() 0 3 1
B loadCacheTypesTickets() 0 32 7
D delete() 0 90 19
B loadCacheCategoriesTickets() 0 45 9
A initAsSpecimen() 0 26 1
F verify() 0 83 19
D fetch() 0 128 17
F fetchAll() 0 183 41
A createFromClone() 0 38 4
B assignUser() 0 44 6
A getTooltipContentArray() 0 26 5
D getNomUrl() 0 75 21
B loadCacheMsgsTicket() 0 38 9
B markAsRead() 0 48 7
C close() 0 67 15
D createTicketMessage() 0 107 26
C searchSocidByEmail() 0 53 17
A setProgression() 0 15 4
A getTicketAllContacts() 0 13 1
A getIdTicketInternalContact() 0 3 1
A setContract() 0 15 4
A getInfosTicketExternalContact() 0 3 1
A getIdTicketCustomerContact() 0 3 1
A getInfosTicketInternalContact() 0 3 1
A getIdTicketInternalInvolvedContact() 0 3 1
A getIdTicketCustomerInvolvedContact() 0 3 1
A getDefaultRef() 0 31 6
A getTicketAllCustomerContacts() 0 9 1
D listeContact() 0 103 20
B searchContactByEmail() 0 32 6
A setCustomer() 0 15 4
A is_photo_available() 0 25 6
A generateDocument() 0 20 4
D sendTicketMessageByEmail() 0 96 20
B copyFilesForTicket() 0 66 7
B load_board() 0 58 11
A replaceThirdparty() 0 5 1
B getKanbanView() 0 31 9
A loadStateBoard() 0 29 4
F newMessage() 0 344 88
B setCategories() 0 33 7

How to fix   Complexity   

Complex Class

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

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

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

1
<?php
2
3
/* Copyright (C) 2013-2018  Jean-François Ferry         <[email protected]>
4
 * Copyright (C) 2016       Christophe Battarel         <[email protected]>
5
 * Copyright (C) 2019-2024  Frédéric France             <[email protected]>
6
 * Copyright (C) 2020       Laurent Destailleur         <[email protected]>
7
 * Copyright (C) 2023       Charlene Benke 	            <[email protected]>
8
 * Copyright (C) 2023	    Benjamin Falière	        <[email protected]>
9
 * Copyright (C) 2024		William Mead		        <[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\Ticket\Classes;
28
29
use Dolibarr\Code\Core\Classes\WorkboardResponse;
30
use Dolibarr\Core\Base\CommonObject;
31
use DoliDB;
32
33
/**
34
 *  \file       ticket/class/ticket.class.php
35
 *  \ingroup    ticket
36
 *  \brief      Class file for object ticket
37
 */
38
39
require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/ticket.lib.php';
40
41
/**
42
 *    Class to manage ticket
43
 */
44
class Ticket extends CommonObject
45
{
46
    /**
47
     * @var DoliDB Database handler
48
     */
49
    public $db;
50
51
    /**
52
     * @var string ID to identify managed object
53
     */
54
    public $element = 'ticket';
55
56
    /**
57
     * @var string Name of table without prefix where object is stored
58
     */
59
    public $table_element = 'ticket';
60
61
    /**
62
     * @var string Name of field for link to tickets
63
     */
64
    public $fk_element = 'fk_ticket';
65
66
    /**
67
     * @var string String with name of icon for ticketcore. Must be the part after the 'object_' into object_ticketcore.png
68
     */
69
    public $picto = 'ticket';
70
71
    /**
72
     * @var ?string Hash to identify ticket publicly
73
     */
74
    public $track_id;
75
76
    /**
77
     * @var int Thirdparty ID
78
     */
79
    public $fk_soc;
80
    public $socid;
81
82
    /**
83
     * @var int Project ID
84
     */
85
    public $fk_project;
86
87
    /**
88
     * @var int Contract ID
89
     */
90
    public $fk_contract;
91
92
    /**
93
     * @var ?string Email of person who created the ticket
94
     */
95
    public $origin_email;
96
97
    /**
98
     * @var int User id who created the ticket
99
     */
100
    public $fk_user_create;
101
102
    /**
103
     * @var int User id who the ticket is assigned to
104
     */
105
    public $fk_user_assign;
106
107
    /**
108
     * var string Ticket subject
109
     */
110
    public $subject;
111
112
    /**
113
     * @var string Ticket message
114
     */
115
    public $message;
116
117
    /**
118
     * @var string Private message
119
     */
120
    public $private;
121
122
    /**
123
     * @var int  Ticket statut
124
     * @deprecated use status
125
     * @see $status
126
     */
127
    public $fk_statut;
128
129
    /**
130
     * @var int  Ticket status
131
     */
132
    public $status;
133
134
    /**
135
     * @var string State resolution
136
     */
137
    public $resolution;
138
139
    /**
140
     * @var int Progress in percent
141
     */
142
    public $progress;
143
144
    /**
145
     * @var string Duration for ticket
146
     */
147
    public $timing;
148
149
    /**
150
     * @var string Type code
151
     */
152
    public $type_code;
153
154
    /**
155
     * @var string Category code
156
     */
157
    public $category_code;
158
159
    /**
160
     * @var string Severity code
161
     */
162
    public $severity_code;
163
164
    /**
165
     * Type label
166
     */
167
    public $type_label;
168
169
    /**
170
     * Category label
171
     */
172
    public $category_label;
173
174
    /**
175
     * Severity label
176
     */
177
    public $severity_label;
178
179
    /**
180
     * Email from user
181
     */
182
    public $email_from;
183
184
    /**
185
     * Email to reply to
186
     */
187
    public $origin_replyto;
188
189
    /**
190
     * References from origin email
191
     */
192
    public $origin_references;
193
194
    /**
195
     * @var int Creation date
196
     */
197
    public $datec;
198
199
    /**
200
     * @var int Read date
201
     */
202
    public $date_read;
203
204
    /**
205
     * @var int Last message date
206
     */
207
    public $date_last_msg_sent;
208
209
    /**
210
     * @var int Close ticket date
211
     */
212
    public $date_close;
213
214
    /**
215
     * @var array cache_types_tickets
216
     */
217
    public $cache_types_tickets;
218
219
    /**
220
     * @var array tickets categories
221
     */
222
    public $cache_category_tickets;
223
224
    /**
225
     * @var array cache msgs ticket
226
     */
227
    public $cache_msgs_ticket;
228
229
    /**
230
     * @var int     Notify thirdparty at create
231
     */
232
    public $notify_tiers_at_create;
233
234
    /**
235
     * @var string  Email MSGID
236
     */
237
    public $email_msgid;
238
239
    /**
240
     * @var string  Email Date
241
     */
242
    public $email_date;
243
244
    /**
245
     * @var string  IP address
246
     */
247
    public $ip;
248
249
    /**
250
     * @var static $oldcopy  State of this ticket as it was stored before an update operation (for triggers)
251
     */
252
    public $oldcopy;
253
254
    /**
255
     * @var Ticket[] array of Tickets
256
     */
257
    public $lines;
258
259
260
    /**
261
     * @var string Regex pour les images
262
     */
263
    public $regeximgext = '\.gif|\.jpg|\.jpeg|\.png|\.bmp|\.webp|\.xpm|\.xbm'; // See also into images.lib.php
264
265
    /**
266
     * Status
267
     */
268
    const STATUS_NOT_READ = 0;          // Draft. Not take into account yet.
269
    const STATUS_READ = 1;              // Ticket was read.
270
    const STATUS_ASSIGNED = 2;          // Ticket was just assigned to someone. Not in progress yet.
271
    const STATUS_IN_PROGRESS = 3;       // In progress
272
    const STATUS_NEED_MORE_INFO = 5;    // Waiting requester feedback
273
    const STATUS_WAITING = 7;           // On hold
274
    const STATUS_CLOSED = 8;            // Closed - Solved
275
    const STATUS_CANCELED = 9;          // Closed - Not solved
276
277
278
    /**
279
     *  'type' field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]', 'sellist:TableName:LabelFieldName[:KeyFieldName[:KeyFieldParent[:Filter]]]', 'varchar(x)', 'double(24,8)', 'real', 'price', 'text', 'text:none', 'html', 'date', 'datetime', 'timestamp', 'duration', 'mail', 'phone', 'url', 'password')
280
     *         Note: Filter can be a string like "(t.ref:like:'SO-%') or (t.date_creation:<:'20160101') or (t.nature:is:NULL)"
281
     *  'label' the translation key.
282
     *  'picto' is code of a picto to show before value in forms
283
     *  'enabled' is a condition when the field must be managed (Example: 1 or 'getDolGlobalString('MY_SETUP_PARAM'))
284
     *  'position' is the sort order of field.
285
     *  'notnull' is set to 1 if not null in database. Set to -1 if we must set data to null if empty ('' or 0).
286
     *  'visible' says if field is visible in list (Examples: 0=Not visible, 1=Visible on list and create/update/view forms, 2=Visible on list only, 3=Visible on create/update/view form only (not list), 4=Visible on list and update/view form only (not create). 5=Visible on list and view only (not create/not update). Using a negative value means field is not shown by default on list but can be selected for viewing)
287
     *  'noteditable' says if field is not editable (1 or 0)
288
     *  'default' is a default value for creation (can still be overwrote by the Setup of Default Values if field is editable in creation form). Note: If default is set to '(PROV)' and field is 'ref', the default value will be set to '(PROVid)' where id is rowid when a new record is created.
289
     *  'index' if we want an index in database.
290
     *  'foreignkey'=>'tablename.field' if the field is a foreign key (it is recommended to name the field fk_...).
291
     *  'searchall' is 1 if we want to search in this field when making a search from the quick search button.
292
     *  'isameasure' must be set to 1 if you want to have a total on list for this field. Field type must be summable like integer or double(24,8).
293
     *  'css' and 'cssview' and 'csslist' is the CSS style to use on field. 'css' is used in creation and update. 'cssview' is used in view mode. 'csslist' is used for columns in lists. For example: 'maxwidth200', 'wordbreak', 'tdoverflowmax200'
294
     *  'help' is a 'TranslationString' to use to show a tooltip on field. You can also use 'TranslationString:keyfortooltiponlick' for a tooltip on click.
295
     *  'showoncombobox' if value of the field must be visible into the label of the combobox that list record
296
     *  'disabled' is 1 if we want to have the field locked by a 'disabled' attribute. In most cases, this is never set into the definition of $fields into class, but is set dynamically by some part of code.
297
     *  'arrayofkeyval' to set list of value if type is a list of predefined values. For example: array("0"=>"Draft","1"=>"Active","-1"=>"Cancel")
298
     *  'autofocusoncreate' to have field having the focus on a create form. Only 1 field should have this property set to 1.
299
     *  'comment' is not used. You can store here any text of your choice. It is not used by application.
300
     *
301
     *  Note: To have value dynamic, you can set value to 0 in definition and edit the value on the fly into the constructor.
302
     */
303
304
    // BEGIN MODULEBUILDER PROPERTIES
305
    public $fields = array(
306
        'rowid' => array('type' => 'integer', 'label' => 'TechnicalID', 'visible' => -2, 'enabled' => 1, 'position' => 1, 'notnull' => 1, 'index' => 1, 'comment' => "Id"),
307
        'entity' => array('type' => 'integer', 'label' => 'Entity', 'visible' => 0, 'enabled' => 1, 'position' => 5, 'notnull' => 1, 'index' => 1),
308
        'ref' => array('type' => 'varchar(128)', 'label' => 'Ref', 'visible' => 1, 'enabled' => 1, 'position' => 10, 'notnull' => 1, 'index' => 1, 'searchall' => 1, 'comment' => "Reference of object", 'css' => '', 'showoncombobox' => 1),
309
        'track_id' => array('type' => 'varchar(255)', 'label' => 'TicketTrackId', 'visible' => -2, 'enabled' => 1, 'position' => 11, 'notnull' => -1, 'searchall' => 1, 'help' => "Help text"),
310
        'fk_user_create' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'Author', 'visible' => 1, 'enabled' => 1, 'position' => 15, 'notnull' => 1, 'csslist' => 'tdoverflowmax100 maxwidth150onsmartphone'),
311
        'origin_email' => array('type' => 'mail', 'label' => 'OriginEmail', 'visible' => -2, 'enabled' => 1, 'position' => 16, 'notnull' => 1, 'index' => 1, 'searchall' => 1, 'comment' => "Reference of object", 'csslist' => 'tdoverflowmax150'),
312
        'origin_replyto' => array('type' => 'mail', 'label' => 'EmailReplyto', 'visible' => -2, 'enabled' => 1, 'position' => 17, 'notnull' => 1, 'index' => 1, 'searchall' => 1, 'comment' => "Email to reply to", 'csslist' => 'tdoverflowmax150'),
313
        'origin_references' => array('type' => 'text', 'label' => 'EmailReferences', 'visible' => -2, 'enabled' => 1, 'position' => 18, 'notnull' => 1, 'index' => 1, 'searchall' => 1, 'comment' => "References from origin email", 'csslist' => 'tdoverflowmax150'),
314
        'subject' => array('type' => 'varchar(255)', 'label' => 'Subject', 'visible' => 1, 'enabled' => 1, 'position' => 19, 'notnull' => -1, 'searchall' => 1, 'help' => "", 'css' => 'maxwidth200 tdoverflowmax200', 'csslist' => 'tdoverflowmax250', 'autofocusoncreate' => 1),
315
        'type_code' => array('type' => 'varchar(32)', 'label' => 'Type', 'visible' => 1, 'enabled' => 1, 'position' => 20, 'notnull' => -1, 'help' => "", 'csslist' => 'tdoverflowmax100'),
316
        'category_code' => array('type' => 'varchar(32)', 'label' => 'TicketCategory', 'visible' => -1, 'enabled' => 1, 'position' => 21, 'notnull' => -1, 'help' => "", 'css' => 'maxwidth100 tdoverflowmax200'),
317
        'severity_code' => array('type' => 'varchar(32)', 'label' => 'Severity', 'visible' => 1, 'enabled' => 1, 'position' => 22, 'notnull' => -1, 'help' => "", 'css' => 'maxwidth100'),
318
        'fk_soc' => array('type' => 'integer:Societe:societe/class/societe.class.php', 'label' => 'ThirdParty', 'visible' => 1, 'enabled' => 'isModEnabled("societe")', 'position' => 50, 'notnull' => -1, 'index' => 1, 'searchall' => 1, 'help' => "OrganizationEventLinkToThirdParty", 'css' => 'tdoverflowmax150 maxwidth150onsmartphone'),
319
        'notify_tiers_at_create' => array('type' => 'integer', 'label' => 'NotifyThirdparty', 'visible' => -1, 'enabled' => 0, 'position' => 51, 'notnull' => 1, 'index' => 1),
320
        'fk_project' => array('type' => 'integer:Project:projet/class/project.class.php', 'label' => 'Project', 'visible' => -1, 'enabled' => '$conf->project->enabled', 'position' => 52, 'notnull' => -1, 'index' => 1, 'help' => "LinkToProject"),
321
        'fk_contract' => array('type' => 'integer:Contrat:contrat/class/contrat.class.php', 'label' => 'Contract', 'visible' => -1, 'enabled' => '$conf->contract->enabled', 'position' => 53, 'notnull' => -1, 'index' => 1, 'help' => "LinkToContract"),
322
        //'timing' => array('type'=>'varchar(20)', 'label'=>'Timing', 'visible'=>-1, 'enabled'=>1, 'position'=>42, 'notnull'=>-1, 'help'=>""),  // what is this ?
323
        'datec' => array('type' => 'datetime', 'label' => 'DateCreation', 'visible' => 1, 'enabled' => 1, 'position' => 500, 'notnull' => 1, 'csslist' => 'nowraponall'),
324
        'tms' => array('type' => 'timestamp', 'label' => 'DateModification', 'visible' => -1, 'enabled' => 1, 'position' => 501, 'notnull' => 1),
325
        'date_read' => array('type' => 'datetime', 'label' => 'DateReading', 'visible' => -1, 'enabled' => 1, 'position' => 505, 'notnull' => 1, 'csslist' => 'nowraponall'),
326
        'date_last_msg_sent' => array('type' => 'datetime', 'label' => 'TicketLastMessageDate', 'visible' => 0, 'enabled' => 1, 'position' => 506, 'notnull' => -1),
327
        'fk_user_assign' => array('type' => 'integer:User:user/class/user.class.php', 'label' => 'AssignedTo', 'visible' => 1, 'enabled' => 1, 'position' => 507, 'notnull' => 1, 'csslist' => 'tdoverflowmax100 maxwidth150onsmartphone'),
328
        'date_close' => array('type' => 'datetime', 'label' => 'TicketCloseOn', 'visible' => -1, 'enabled' => 1, 'position' => 510, 'notnull' => 1),
329
        'message' => array('type' => 'html', 'label' => 'Message', 'visible' => -2, 'enabled' => 1, 'position' => 540, 'notnull' => -1,),
330
        'email_msgid' => array('type' => 'varchar(255)', 'label' => 'EmailMsgID', 'visible' => -2, 'enabled' => 1, 'position' => 540, 'notnull' => -1, 'help' => 'EmailMsgIDDesc', 'csslist' => 'tdoverflowmax100'),
331
        'email_date' => array('type' => 'datetime', 'label' => 'EmailDate', 'visible' => -2, 'enabled' => 1, 'position' => 541),
332
        'progress' => array('type' => 'integer', 'label' => 'Progression', 'visible' => -1, 'enabled' => 1, 'position' => 540, 'notnull' => -1, 'css' => 'right', 'help' => "", 'isameasure' => 2, 'csslist' => 'width50'),
333
        'resolution' => array('type' => 'integer', 'label' => 'Resolution', 'visible' => -1, 'enabled' => 'getDolGlobalString("TICKET_ENABLE_RESOLUTION")', 'position' => 550, 'notnull' => 1),
334
        'model_pdf' => array('type' => 'varchar(255)', 'label' => 'PDFTemplate', 'enabled' => 1, 'visible' => 0, 'position' => 560),
335
        'extraparams' => array('type' => 'varchar(255)', 'label' => 'Extraparams', 'enabled' => 1, 'visible' => -1, 'position' => 570),
336
        'fk_statut' => array('type' => 'integer', 'label' => 'Status', 'visible' => 1, 'enabled' => 1, 'position' => 600, 'notnull' => 1, 'index' => 1, 'arrayofkeyval' => array(0 => 'Unread', 1 => 'Read', 2 => 'Assigned', 3 => 'InProgress', 5 => 'NeedMoreInformation', 7 => 'OnHold', 8 => 'SolvedClosed', 9 => 'Deleted')),
337
        'import_key' => array('type' => 'varchar(14)', 'label' => 'ImportId', 'enabled' => 1, 'visible' => -2, 'position' => 900),
338
    );
339
    // END MODULEBUILDER PROPERTIES
340
341
342
    /**
343
     *  Constructor
344
     *
345
     *  @param DoliDB $db Database handler
346
     */
347
    public function __construct($db)
348
    {
349
        $this->db = $db;
350
351
        $this->ismultientitymanaged = 1;
352
        $this->isextrafieldmanaged = 1;
353
354
        $this->labelStatusShort = array(
355
            self::STATUS_NOT_READ => 'Unread',
356
            self::STATUS_READ => 'Read',
357
            self::STATUS_ASSIGNED => 'Assigned',
358
            self::STATUS_IN_PROGRESS => 'InProgress',
359
            self::STATUS_NEED_MORE_INFO => 'NeedMoreInformationShort',
360
            self::STATUS_WAITING => 'OnHold',
361
            self::STATUS_CLOSED => 'SolvedClosed',
362
            self::STATUS_CANCELED => 'Canceled'
363
        );
364
        $this->labelStatus = array(
365
            self::STATUS_NOT_READ => 'Unread',
366
            self::STATUS_READ => 'Read',
367
            self::STATUS_ASSIGNED => 'Assigned',
368
            self::STATUS_IN_PROGRESS => 'InProgress',
369
            self::STATUS_NEED_MORE_INFO => 'NeedMoreInformation',
370
            self::STATUS_WAITING => 'OnHold',
371
            self::STATUS_CLOSED => 'SolvedClosed',
372
            self::STATUS_CANCELED => 'Canceled'
373
        );
374
375
        if (!getDolGlobalString('TICKET_INCLUDE_SUSPENDED_STATUS')) {
376
            unset($this->fields['fk_statut']['arrayofkeyval'][self::STATUS_WAITING]);
377
            unset($this->labelStatusShort[self::STATUS_WAITING]);
378
            unset($this->labelStatus[self::STATUS_WAITING]);
379
        }
380
    }
381
382
    /**
383
     *    Check properties of ticket are ok (like ref, track_id, ...).
384
     *    All properties must be already loaded on object (this->ref, this->track_id, ...).
385
     *
386
     *    @return int        0 if OK, <0 if KO
387
     */
388
    private function verify()
389
    {
390
        $this->errors = array();
391
392
        $result = 0;
393
394
        // Clean parameters
395
        if (isset($this->ref)) {
396
            $this->ref = trim($this->ref);
397
        }
398
399
        if (isset($this->track_id)) {
400
            $this->track_id = trim($this->track_id);
401
        }
402
403
        if (isset($this->fk_soc)) {
404
            $this->fk_soc = (int) $this->fk_soc;
405
        }
406
407
        if (isset($this->fk_project)) {
408
            $this->fk_project = (int) $this->fk_project;
409
        }
410
411
        if (isset($this->origin_email)) {
412
            $this->origin_email = trim($this->origin_email);
413
        }
414
415
        if (isset($this->fk_user_create)) {
416
            $this->fk_user_create = (int) $this->fk_user_create;
417
        }
418
419
        if (isset($this->fk_user_assign)) {
420
            $this->fk_user_assign = (int) $this->fk_user_assign;
421
        }
422
423
        if (isset($this->subject)) {
424
            $this->subject = trim($this->subject);
425
        }
426
427
        if (isset($this->message)) {
428
            $this->message = trim($this->message);
429
            if (dol_strlen($this->message) > 65000) {
430
                $this->errors[] = 'ErrorFieldTooLong';
431
                dol_syslog(get_class($this) . '::create error -1 message too long', LOG_ERR);
432
                $result = -1;
433
            }
434
        }
435
436
        if (isset($this->fk_statut)) {
437
            $this->fk_statut = (int) $this->fk_statut;
438
        }
439
440
        if (isset($this->resolution)) {
441
            $this->resolution = trim($this->resolution);
442
        }
443
444
        if (isset($this->progress)) {
445
            $this->progress = (int) $this->progress;
446
        }
447
448
        if (isset($this->timing)) {
449
            $this->timing = trim($this->timing);
450
        }
451
452
        if (isset($this->type_code)) {
453
            $this->type_code = trim($this->type_code);
454
        }
455
456
        if (isset($this->category_code)) {
457
            $this->category_code = trim($this->category_code);
458
        }
459
460
        if (isset($this->severity_code)) {
461
            $this->severity_code = trim($this->severity_code);
462
        }
463
464
        if (empty($this->ref)) {
465
            $this->errors[] = 'ErrorTicketRefRequired';
466
            dol_syslog(get_class($this) . "::create error -1 ref null", LOG_ERR);
467
            $result = -1;
468
        }
469
470
        return $result;
471
    }
472
473
    /**
474
     *
475
     * Check if ref exists or not
476
     *
477
     * @param string $action    Action
478
     * @param string $getRef    Reference of object
479
     * @return bool
480
     */
481
    public function checkExistingRef(string $action, string $getRef): bool
482
    {
483
        $test = new self($this->db);
484
485
        if ($test->fetch('', $getRef) > 0) {
486
            if (($action == 'add') || ($action == 'update' && $this->ref != $getRef)) {
487
                return true;
488
            }
489
        }
490
491
        $this->ref = $getRef;
492
        return false;
493
    }
494
495
    /**
496
     *  Create object into database
497
     *
498
     *  @param  User $user      User that creates
499
     *  @param  int  $notrigger 0=launch triggers after, 1=disable triggers
500
     *  @return int                      Return integer <0 if KO, Id of created object if OK
501
     */
502
    public function create($user, $notrigger = 0)
503
    {
504
        global $conf;
505
506
        $error = 0;
507
508
        // Clean parameters
509
        $this->datec = dol_now();
510
        if (empty($this->track_id)) {
511
            $this->track_id = generate_random_id(16);
512
        }
513
514
        // Check more parameters
515
        // If error, this->errors[] is filled
516
        $result = $this->verify();
517
518
        if ($result >= 0) {
519
            $this->entity = ((isset($this->entity) && is_numeric($this->entity)) ? $this->entity : $conf->entity);
520
521
            // Insert request
522
            $sql = "INSERT INTO " . MAIN_DB_PREFIX . "ticket(";
523
            $sql .= "ref,";
524
            $sql .= "track_id,";
525
            $sql .= "fk_soc,";
526
            $sql .= "fk_project,";
527
            $sql .= "fk_contract,";
528
            $sql .= "origin_email,";
529
            $sql .= "origin_replyto,";
530
            $sql .= "origin_references,";
531
            $sql .= "fk_user_create,";
532
            $sql .= "fk_user_assign,";
533
            $sql .= "email_msgid,";
534
            $sql .= "email_date,";
535
            $sql .= "subject,";
536
            $sql .= "message,";
537
            $sql .= "fk_statut,";
538
            $sql .= "resolution,";
539
            $sql .= "progress,";
540
            $sql .= "timing,";
541
            $sql .= "type_code,";
542
            $sql .= "category_code,";
543
            $sql .= "severity_code,";
544
            $sql .= "datec,";
545
            $sql .= "date_read,";
546
            $sql .= "date_close,";
547
            $sql .= "entity,";
548
            $sql .= "notify_tiers_at_create,";
549
            $sql .= "model_pdf,";
550
            $sql .= "ip";
551
            $sql .= ") VALUES (";
552
            $sql .= " " . (!isset($this->ref) ? '' : "'" . $this->db->escape($this->ref) . "'") . ",";
553
            $sql .= " " . (!isset($this->track_id) ? 'NULL' : "'" . $this->db->escape($this->track_id) . "'") . ",";
554
            $sql .= " " . ($this->fk_soc > 0 ? ((int) $this->fk_soc) : "null") . ",";
555
            $sql .= " " . ($this->fk_project > 0 ? ((int) $this->fk_project) : "null") . ",";
556
            $sql .= " " . ($this->fk_contract > 0 ? ((int) $this->fk_contract) : "null") . ",";
557
            $sql .= " " . (!isset($this->origin_email) ? 'NULL' : "'" . $this->db->escape($this->origin_email) . "'") . ",";
558
            $sql .= " " . (!isset($this->origin_replyto) ? 'NULL' : "'" . $this->db->escape($this->origin_replyto) . "'") . ",";
559
            $sql .= " " . (!isset($this->origin_references) ? 'NULL' : "'" . $this->db->escape($this->origin_references) . "'") . ",";
560
            $sql .= " " . (!isset($this->fk_user_create) ? ($user->id > 0 ? ((int) $user->id) : 'NULL') : ($this->fk_user_create > 0 ? ((int) $this->fk_user_create) : 'NULL')) . ",";
561
            $sql .= " " . ($this->fk_user_assign > 0 ? ((int) $this->fk_user_assign) : 'NULL') . ",";
562
            $sql .= " " . (empty($this->email_msgid) ? 'NULL' : "'" . $this->db->escape($this->email_msgid) . "'") . ",";
563
            $sql .= " " . (empty($this->email_date) ? 'NULL' : "'" . $this->db->idate($this->email_date) . "'") . ",";
564
            $sql .= " " . (!isset($this->subject) ? 'NULL' : "'" . $this->db->escape($this->subject) . "'") . ",";
565
            $sql .= " " . (!isset($this->message) ? 'NULL' : "'" . $this->db->escape($this->message) . "'") . ",";
566
            $sql .= " " . (!isset($this->status) ? '0' : ((int) $this->status)) . ",";
567
            $sql .= " " . (!isset($this->resolution) ? 'NULL' : ((int) $this->resolution)) . ",";
568
            $sql .= " " . (!isset($this->progress) ? '0' : ((int) $this->progress)) . ",";
569
            $sql .= " " . (!isset($this->timing) ? 'NULL' : "'" . $this->db->escape($this->timing) . "'") . ",";
570
            $sql .= " " . (!isset($this->type_code) ? 'NULL' : "'" . $this->db->escape($this->type_code) . "'") . ",";
571
            $sql .= " " . (empty($this->category_code) || $this->category_code == '-1' ? 'NULL' : "'" . $this->db->escape($this->category_code) . "'") . ",";
572
            $sql .= " " . (!isset($this->severity_code) ? 'NULL' : "'" . $this->db->escape($this->severity_code) . "'") . ",";
573
            $sql .= " " . (!isset($this->datec) || dol_strlen($this->datec) == 0 ? 'NULL' : "'" . $this->db->idate($this->datec) . "'") . ",";
574
            $sql .= " " . (!isset($this->date_read) || dol_strlen($this->date_read) == 0 ? 'NULL' : "'" . $this->db->idate($this->date_read) . "'") . ",";
575
            $sql .= " " . (!isset($this->date_close) || dol_strlen($this->date_close) == 0 ? 'NULL' : "'" . $this->db->idate($this->date_close) . "'");
576
            $sql .= ", " . ((int) $this->entity);
577
            $sql .= ", " . (!isset($this->notify_tiers_at_create) ? 1 : ((int) $this->notify_tiers_at_create));
578
            $sql .= ", '" . $this->db->escape($this->model_pdf) . "'";
579
            $sql .= ", " . (!isset($this->ip) ? 'NULL' : "'" . $this->db->escape($this->ip) . "'");
580
            $sql .= ")";
581
582
            $this->db->begin();
583
584
            dol_syslog(get_class($this) . "::create", LOG_DEBUG);
585
            $resql = $this->db->query($sql);
586
            if (!$resql) {
587
                $error++;
588
                $this->errors[] = "Error " . $this->db->lasterror();
589
            }
590
591
            if (!$error) {
592
                $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX . "ticket");
593
            }
594
595
            if (!$error && getDolGlobalString('TICKET_ADD_AUTHOR_AS_CONTACT') && empty($this->context["createdfrompublicinterface"])) {
596
                // add creator as contributor
597
598
                // We first check the type of contact (internal or external)
599
                if (!empty($user->socid) && !empty($user->contact_id) && getDolGlobalInt('TICKET_ADD_AUTHOR_AS_CONTACT') == 2) {
600
                    $contact_type = 'external';
601
                    $contributor_id = $user->contact_id;
602
                } else {
603
                    $contact_type = 'internal';
604
                    $contributor_id = $user->id;
605
                }
606
607
                // We add the creator as contributor
608
                if ($this->add_contact($contributor_id, 'CONTRIBUTOR', $contact_type) < 0) {
609
                    $error++;
610
                }
611
            }
612
613
            if (!$error && $this->fk_user_assign > 0) {
614
                if ($this->add_contact($this->fk_user_assign, 'SUPPORTTEC', 'internal') < 0) {
615
                    $error++;
616
                }
617
            }
618
619
620
            //Update extrafield
621
            if (!$error) {
622
                $result = $this->insertExtraFields();
623
                if ($result < 0) {
624
                    $error++;
625
                }
626
            }
627
628
            if (!$error && !$notrigger) {
629
                // Call trigger
630
                $result = $this->call_trigger('TICKET_CREATE', $user);
631
                if ($result < 0) {
632
                    $error++;
633
                }
634
                // End call triggers
635
            }
636
637
            // Commit or rollback
638
            if ($error) {
639
                foreach ($this->errors as $errmsg) {
640
                    dol_syslog(get_class($this) . "::create " . $errmsg, LOG_ERR);
641
                    $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
642
                }
643
                $this->db->rollback();
644
                return -1 * $error;
645
            } else {
646
                $this->db->commit();
647
                return $this->id;
648
            }
649
        } else {
650
            $this->db->rollback();
651
            dol_syslog(get_class($this) . "::Create fails verify " . implode(',', $this->errors), LOG_WARNING);
652
            return -3;
653
        }
654
    }
655
656
    /**
657
     *  Load object in memory from the database
658
     *
659
     *  @param  int         $id             Id object
660
     *  @param  string      $ref            Ref
661
     *  @param  string      $track_id       Track id, a hash like ref
662
     *  @param  string      $email_msgid    Email msgid
663
     *  @return int                         Return integer <0 if KO, >0 if OK
664
     */
665
    public function fetch($id = 0, $ref = '', $track_id = '', $email_msgid = '')
666
    {
667
        global $langs;
668
669
        // Check parameters
670
        if (empty($id) && empty($ref) && empty($track_id) && empty($email_msgid)) {
671
            $this->error = 'ErrorWrongParameters';
672
            dol_print_error(null, get_class($this) . "::fetch " . $this->error);
673
            return -1;
674
        }
675
676
        $sql = "SELECT";
677
        $sql .= " t.rowid,";
678
        $sql .= " t.entity,";
679
        $sql .= " t.ref,";
680
        $sql .= " t.track_id,";
681
        $sql .= " t.fk_soc,";
682
        $sql .= " t.fk_project,";
683
        $sql .= " t.fk_contract,";
684
        $sql .= " t.origin_email,";
685
        $sql .= " t.origin_replyto,";
686
        $sql .= " t.origin_references,";
687
        $sql .= " t.fk_user_create,";
688
        $sql .= " t.fk_user_assign,";
689
        $sql .= " t.email_msgid,";
690
        $sql .= " t.email_date,";
691
        $sql .= " t.subject,";
692
        $sql .= " t.message,";
693
        $sql .= " t.fk_statut as status,";
694
        $sql .= " t.resolution,";
695
        $sql .= " t.progress,";
696
        $sql .= " t.timing,";
697
        $sql .= " t.type_code,";
698
        $sql .= " t.category_code,";
699
        $sql .= " t.severity_code,";
700
        $sql .= " t.datec,";
701
        $sql .= " t.date_read,";
702
        $sql .= " t.date_last_msg_sent,";
703
        $sql .= " t.date_close,";
704
        $sql .= " t.tms,";
705
        $sql .= " t.model_pdf,";
706
        $sql .= " t.extraparams,";
707
        $sql .= " t.ip,";
708
        $sql .= " type.label as type_label, category.label as category_label, severity.label as severity_label";
709
        $sql .= " FROM " . MAIN_DB_PREFIX . "ticket as t";
710
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "c_ticket_type as type ON type.code=t.type_code";
711
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "c_ticket_category as category ON category.code=t.category_code";
712
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "c_ticket_severity as severity ON severity.code=t.severity_code";
713
714
        if ($id) {
715
            $sql .= " WHERE t.rowid = " . ((int) $id);
716
        } else {
717
            $sql .= " WHERE t.entity IN (" . getEntity($this->element, 1) . ")";
718
            if (!empty($ref)) {
719
                $sql .= " AND t.ref = '" . $this->db->escape($ref) . "'";
720
            } elseif ($track_id) {
721
                $sql .= " AND t.track_id = '" . $this->db->escape($track_id) . "'";
722
            } else {
723
                $sql .= " AND t.email_msgid = '" . $this->db->escape($email_msgid) . "'";
724
            }
725
        }
726
727
        dol_syslog(get_class($this) . "::fetch", LOG_DEBUG);
728
        $resql = $this->db->query($sql);
729
        if ($resql) {
730
            if ($this->db->num_rows($resql)) {
731
                $obj = $this->db->fetch_object($resql);
732
733
                $this->id = $obj->rowid;
734
                $this->entity = $obj->entity;
735
                $this->ref = $obj->ref;
736
                $this->track_id = $obj->track_id;
737
                $this->fk_soc = $obj->fk_soc;
738
                $this->socid = $obj->fk_soc; // for fetch_thirdparty() method
739
                $this->fk_project = $obj->fk_project;
740
                $this->fk_contract = $obj->fk_contract;
741
                $this->origin_email = $obj->origin_email;
742
                $this->origin_replyto = $obj->origin_replyto;
743
                $this->origin_references = $obj->origin_references;
744
                $this->fk_user_create = $obj->fk_user_create;
745
                $this->fk_user_assign = $obj->fk_user_assign;
746
                $this->email_msgid = $obj->email_msgid;
747
                $this->email_date = $this->db->jdate($obj->email_date);
748
                $this->subject = $obj->subject;
749
                $this->message = $obj->message;
750
                $this->model_pdf = $obj->model_pdf;
751
                $this->extraparams = !empty($obj->extraparams) ? (array) json_decode($obj->extraparams, true) : array();
752
                $this->ip = $obj->ip;
753
754
                $this->status = $obj->status;
755
                $this->fk_statut = $this->status; // For backward compatibility
756
757
                $this->resolution = $obj->resolution;
758
                $this->progress = $obj->progress;
759
                $this->timing = $obj->timing;
760
761
                $this->type_code = $obj->type_code;
762
                $label_type = ($langs->trans("TicketTypeShort" . $obj->type_code) != "TicketTypeShort" . $obj->type_code ? $langs->trans("TicketTypeShort" . $obj->type_code) : ($obj->type_label != '-' ? $obj->type_label : ''));
763
                $this->type_label = $label_type;
764
765
                $this->category_code = $obj->category_code;
766
                $label_category = ($langs->trans("TicketCategoryShort" . $obj->category_code) != "TicketCategoryShort" . $obj->category_code ? $langs->trans("TicketCategoryShort" . $obj->category_code) : ($obj->category_label != '-' ? $obj->category_label : ''));
767
                $this->category_label = $label_category;
768
769
                $this->severity_code = $obj->severity_code;
770
                $label_severity = ($langs->trans("TicketSeverityShort" . $obj->severity_code) != "TicketSeverityShort" . $obj->severity_code ? $langs->trans("TicketSeverityShort" . $obj->severity_code) : ($obj->severity_label != '-' ? $obj->severity_label : ''));
771
                $this->severity_label = $label_severity;
772
773
                $this->datec = $this->db->jdate($obj->datec);
774
                $this->date_creation = $this->db->jdate($obj->datec);
775
                $this->date_read = $this->db->jdate($obj->date_read);
776
                $this->date_validation = $this->db->jdate($obj->date_read);
777
                $this->date_last_msg_sent = $this->db->jdate($obj->date_last_msg_sent);
778
                $this->date_close = $this->db->jdate($obj->date_close);
779
                $this->tms = $this->db->jdate($obj->tms);
780
                $this->date_modification = $this->db->jdate($obj->tms);
781
782
                $this->fetch_optionals();
783
784
                $this->db->free($resql);
785
                return 1;
786
            } else {
787
                return 0;
788
            }
789
        } else {
790
            $this->error = "Error " . $this->db->lasterror();
791
            dol_syslog(get_class($this) . "::fetch " . $this->error, LOG_ERR);
792
            return -1;
793
        }
794
    }
795
796
    /**
797
     * Load all objects in memory from database
798
     *
799
     * @param  User         $user       User for action
800
     * @param  string       $sortorder  Sort order
801
     * @param  string       $sortfield  Sort field
802
     * @param  int          $limit      Limit
803
     * @param  int          $offset     Offset page
804
     * @param  int          $arch       Archive or not (not used)
805
     * @param  string|array $filter     Filter for query
806
     * @return int                      Return integer <0 if KO, >0 if OK
807
     */
808
    public function fetchAll($user, $sortorder = 'ASC', $sortfield = 't.datec', $limit = 0, $offset = 0, $arch = 0, $filter = '')
809
    {
810
        global $langs, $extrafields;
811
812
        // fetch optionals attributes and labels
813
        $extrafields->fetch_name_optionals_label($this->table_element);
814
815
        $sql = "SELECT";
816
        $sql .= " t.rowid,";
817
        $sql .= " t.ref,";
818
        $sql .= " t.track_id,";
819
        $sql .= " t.fk_soc,";
820
        $sql .= " t.fk_project,";
821
        $sql .= " t.fk_contract,";
822
        $sql .= " t.origin_email,";
823
        $sql .= " t.origin_replyto,";
824
        $sql .= " t.origin_references,";
825
        $sql .= " t.fk_user_create, uc.lastname as user_create_lastname, uc.firstname as user_create_firstname,";
826
        $sql .= " t.fk_user_assign, ua.lastname as user_assign_lastname, ua.firstname as user_assign_firstname,";
827
        $sql .= " t.subject,";
828
        $sql .= " t.message,";
829
        $sql .= " t.fk_statut as status,";
830
        $sql .= " t.resolution,";
831
        $sql .= " t.progress,";
832
        $sql .= " t.timing,";
833
        $sql .= " t.type_code,";
834
        $sql .= " t.category_code,";
835
        $sql .= " t.severity_code,";
836
        $sql .= " t.datec,";
837
        $sql .= " t.date_read,";
838
        $sql .= " t.date_last_msg_sent,";
839
        $sql .= " t.date_close,";
840
        $sql .= " t.tms,";
841
        $sql .= " type.label as type_label, category.label as category_label, severity.label as severity_label";
842
        // Add fields for extrafields
843
        if ($extrafields->attributes[$this->table_element]['count'] > 0) {
844
            foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
845
                $sql .= ($extrafields->attributes[$this->table_element]['type'][$key] != 'separate' ? ",ef." . $key . " as options_" . $key : '');
846
            }
847
        }
848
        $sql .= " FROM " . MAIN_DB_PREFIX . "ticket as t";
849
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "c_ticket_type as type ON type.code = t.type_code";
850
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "c_ticket_category as category ON category.code = t.category_code";
851
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "c_ticket_severity as severity ON severity.code = t.severity_code";
852
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON s.rowid = t.fk_soc";
853
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "user as uc ON uc.rowid = t.fk_user_create";
854
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "user as ua ON ua.rowid = t.fk_user_assign";
855
        if ($extrafields->attributes[$this->table_element]['count'] > 0) {
856
            if (is_array($extrafields->attributes[$this->table_element]['label']) && count($extrafields->attributes[$this->table_element]['label'])) {
857
                $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "ticket_extrafields as ef on (t.rowid = ef.fk_object)";
858
            }
859
        }
860
        $sql .= " WHERE t.entity IN (" . getEntity('ticket') . ")";
861
862
        // Manage filter
863
        if (is_array($filter)) {
864
            foreach ($filter as $key => $value) {
865
                if (strpos($key, 'date')) { // To allow $filter['YEAR(s.dated)']=>$year
866
                    $sql .= " AND " . $this->db->sanitize($key) . " = '" . $this->db->escape($value) . "'";
867
                } elseif (($key == 't.fk_user_assign') || ($key == 't.type_code') || ($key == 't.category_code') || ($key == 't.severity_code') || ($key == 't.fk_soc')) {
868
                    $sql .= " AND " . $this->db->sanitize($key) . " = '" . $this->db->escape($value) . "'";
869
                } elseif ($key == 't.fk_statut') {
870
                    if (is_array($value) && count($value) > 0) {
871
                        $sql .= " AND " . $this->db->sanitize($key) . " IN (" . $this->db->sanitize(implode(',', $value)) . ")";
872
                    } else {
873
                        $sql .= " AND " . $this->db->sanitize($key) . ' = ' . ((int) $value);
874
                    }
875
                } elseif ($key == 't.fk_contract') {
876
                    $sql .= " AND " . $this->db->sanitize($key) . ' = ' . ((int) $value);
877
                } else {
878
                    $sql .= " AND " . $this->db->sanitize($key) . " LIKE '%" . $this->db->escape($this->db->escapeforlike($value)) . "%'";
879
                }
880
            }
881
882
            $filter = '';
883
        }
884
885
        // Manage filter
886
        $errormessage = '';
887
        $sql .= forgeSQLFromUniversalSearchCriteria($filter, $errormessage);
888
        if ($errormessage) {
889
            $this->errors[] = $errormessage;
890
            dol_syslog(__METHOD__ . ' ' . implode(',', $this->errors), LOG_ERR);
891
            return -1;
892
        }
893
894
        // Case of external user
895
        $socid = $user->socid ? $user->socid : 0;
896
        // If the internal user must only see his customers, force searching by him
897
        $search_sale = 0;
898
        if (!$user->hasRight('societe', 'client', 'voir')) {
899
            $search_sale = $user->id;
900
        }
901
        // Search on sale representative
902
        if ($search_sale && $search_sale != '-1') {
903
            if ($search_sale == -2) {
904
                $sql .= " AND NOT EXISTS (SELECT sc.fk_soc FROM " . MAIN_DB_PREFIX . "societe_commerciaux as sc WHERE sc.fk_soc = t.fk_soc)";
905
            } elseif ($search_sale > 0) {
906
                $sql .= " AND EXISTS (SELECT sc.fk_soc FROM " . MAIN_DB_PREFIX . "societe_commerciaux as sc WHERE sc.fk_soc = t.fk_soc AND sc.fk_user = " . ((int) $search_sale) . ")";
907
            }
908
        }
909
        // Search on socid
910
        if ($socid) {
911
            $sql .= " AND t.fk_soc = " . ((int) $socid);
912
        }
913
914
        $sql .= $this->db->order($sortfield, $sortorder);
915
        if (!empty($limit)) {
916
            $sql .= $this->db->plimit($limit + 1, $offset);
917
        }
918
919
        dol_syslog(get_class($this) . "::fetchAll", LOG_DEBUG);
920
        $resql = $this->db->query($sql);
921
922
        if ($resql) {
923
            $this->lines = array();
924
925
            $num = $this->db->num_rows($resql);
926
            $i = 0;
927
928
            if ($num) {
929
                while ($i < $num) {
930
                    $obj = $this->db->fetch_object($resql);
931
932
                    $line = new self($this->db);
933
934
                    $line->id = $obj->rowid;
935
                    //$line->rowid = $obj->rowid;
936
                    $line->ref = $obj->ref;
937
                    $line->track_id = $obj->track_id;
938
                    $line->fk_soc = $obj->fk_soc;
939
                    $line->fk_project = $obj->fk_project;
940
                    $line->fk_contract = $obj->fk_contract;
941
                    $line->origin_email = $obj->origin_email;
942
                    $line->origin_replyto = $obj->origin_replyto;
943
                    $line->origin_references = $obj->origin_references;
944
945
                    $line->fk_user_create = $obj->fk_user_create;
946
                    $line->fk_user_assign = $obj->fk_user_assign;
947
948
                    $line->subject = $obj->subject;
949
                    $line->message = $obj->message;
950
                    $line->fk_statut = $obj->status;
951
                    $line->status = $obj->status;
952
                    $line->resolution = $obj->resolution;
953
                    $line->progress = $obj->progress;
954
                    $line->timing = $obj->timing;
955
956
                    $label_type = ($langs->trans("TicketTypeShort" . $obj->type_code) != "TicketTypeShort" . $obj->type_code ? $langs->trans("TicketTypeShort" . $obj->type_code) : ($obj->type_label != '-' ? $obj->type_label : ''));
957
                    $line->type_label = $label_type;
958
959
                    $this->category_code = $obj->category_code;
960
                    $label_category = ($langs->trans("TicketCategoryShort" . $obj->category_code) != "TicketCategoryShort" . $obj->category_code ? $langs->trans("TicketCategoryShort" . $obj->category_code) : ($obj->category_label != '-' ? $obj->category_label : ''));
961
                    $line->category_label = $label_category;
962
963
                    $this->severity_code = $obj->severity_code;
964
                    $label_severity = ($langs->trans("TicketSeverityShort" . $obj->severity_code) != "TicketSeverityShort" . $obj->severity_code ? $langs->trans("TicketSeverityShort" . $obj->severity_code) : ($obj->severity_label != '-' ? $obj->severity_label : ''));
965
                    $line->severity_label = $label_severity;
966
967
                    $line->datec = $this->db->jdate($obj->datec);
968
                    $line->date_read = $this->db->jdate($obj->date_read);
969
                    $line->date_last_msg_sent = $this->db->jdate($obj->date_last_msg_sent);
970
                    $line->date_close = $this->db->jdate($obj->date_close);
971
972
                    // Extra fields
973
                    if ($extrafields->attributes[$this->table_element]['count'] > 0) {
974
                        if (is_array($extrafields->attributes[$this->table_element]['label']) && count($extrafields->attributes[$this->table_element]['label'])) {
975
                            foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
976
                                $tmpkey = 'options_' . $key;
977
                                $line->{$tmpkey} = $obj->$tmpkey;
978
                            }
979
                        }
980
                    }
981
                    $this->lines[$i] = $line;
982
                    $i++;
983
                }
984
            }
985
            $this->db->free($resql);
986
            return $num;
987
        } else {
988
            $this->error = "Error " . $this->db->lasterror();
989
            dol_syslog(get_class($this) . "::fetchAll " . $this->error, LOG_ERR);
990
            return -1;
991
        }
992
    }
993
994
    /**
995
     *  Update object into database
996
     *
997
     *  @param  User $user      User that modifies
998
     *  @param  int  $notrigger 0=launch triggers after, 1=disable triggers
999
     *  @return int                     Return integer <0 if KO, >0 if OK
1000
     */
1001
    public function update($user, $notrigger = 0)
1002
    {
1003
        $error = 0;
1004
1005
        // $this->oldcopy should have been set by the caller of update
1006
        //if (empty($this->oldcopy)) {
1007
        //  dol_syslog("this->oldcopy should have been set by the caller of update (here properties were already modified)", LOG_WARNING);
1008
        //  $this->oldcopy = dol_clone($this, 2);
1009
        //}
1010
1011
        // Clean parameters
1012
        if (isset($this->ref)) {
1013
            $this->ref = trim($this->ref);
1014
        }
1015
1016
        if (isset($this->track_id)) {
1017
            $this->track_id = trim($this->track_id);
1018
        }
1019
1020
        if (isset($this->fk_soc)) {
1021
            $this->fk_soc = (int) $this->fk_soc;
1022
        }
1023
1024
        if (isset($this->fk_project)) {
1025
            $this->fk_project = (int) $this->fk_project;
1026
        }
1027
1028
        if (isset($this->fk_contract)) {
1029
            $this->fk_contract = (int) $this->fk_contract;
1030
        }
1031
1032
        if (isset($this->origin_email)) {
1033
            $this->origin_email = trim($this->origin_email);
1034
        }
1035
1036
        if (isset($this->fk_user_create)) {
1037
            $this->fk_user_create = (int) $this->fk_user_create;
1038
        }
1039
1040
        if (isset($this->fk_user_assign)) {
1041
            $this->fk_user_assign = (int) $this->fk_user_assign;
1042
        }
1043
1044
        if (isset($this->subject)) {
1045
            $this->subject = trim($this->subject);
1046
        }
1047
1048
        if (isset($this->message)) {
1049
            $this->message = trim($this->message);
1050
            if (dol_strlen($this->message) > 65000) {
1051
                $this->errors[] = 'ErrorFieldTooLong';
1052
                dol_syslog(get_class($this) . '::update error -1 message too long', LOG_ERR);
1053
                return -1;
1054
            }
1055
        }
1056
1057
        if (isset($this->fk_statut)) {
1058
            $this->fk_statut = (int) $this->fk_statut;
1059
        }
1060
1061
        if (isset($this->resolution)) {
1062
            $this->resolution = trim($this->resolution);
1063
        }
1064
1065
        if (isset($this->progress)) {
1066
            $this->progress = (int) $this->progress;
1067
        }
1068
1069
        if (isset($this->timing)) {
1070
            $this->timing = trim($this->timing);
1071
        }
1072
1073
        if (isset($this->type_code)) {
1074
            $this->timing = trim($this->type_code);
1075
        }
1076
1077
        if (isset($this->category_code)) {
1078
            $this->timing = trim($this->category_code);
1079
        }
1080
1081
        if (isset($this->severity_code)) {
1082
            $this->timing = trim($this->severity_code);
1083
        }
1084
        if (isset($this->model_pdf)) {
1085
            $this->model_pdf = trim($this->model_pdf);
1086
        }
1087
        // Check parameters
1088
        // Put here code to add a control on parameters values
1089
        // Update request
1090
        $sql = "UPDATE " . MAIN_DB_PREFIX . "ticket SET";
1091
        $sql .= " ref=" . (isset($this->ref) ? "'" . $this->db->escape($this->ref) . "'" : "") . ",";
1092
        $sql .= " track_id=" . (isset($this->track_id) ? "'" . $this->db->escape($this->track_id) . "'" : "null") . ",";
1093
        $sql .= " fk_soc=" . (isset($this->fk_soc) ? (int) $this->fk_soc : "null") . ",";
1094
        $sql .= " fk_project=" . (isset($this->fk_project) ? (int) $this->fk_project : "null") . ",";
1095
        $sql .= " fk_contract=" . (isset($this->fk_contract) ? (int) $this->fk_contract : "null") . ",";
1096
        $sql .= " origin_email=" . (isset($this->origin_email) ? "'" . $this->db->escape($this->origin_email) . "'" : "null") . ",";
1097
        $sql .= " origin_replyto=" . (isset($this->origin_replyto) ? "'" . $this->db->escape($this->origin_replyto) . "'" : "null") . ",";
1098
        $sql .= " origin_references=" . (isset($this->origin_references) ? "'" . $this->db->escape($this->origin_references) . "'" : "null") . ",";
1099
        $sql .= " fk_user_create=" . (isset($this->fk_user_create) ? (int) $this->fk_user_create : "null") . ",";
1100
        $sql .= " fk_user_assign=" . (isset($this->fk_user_assign) ? (int) $this->fk_user_assign : "null") . ",";
1101
        $sql .= " subject=" . (isset($this->subject) ? "'" . $this->db->escape($this->subject) . "'" : "null") . ",";
1102
        $sql .= " message=" . (isset($this->message) ? "'" . $this->db->escape($this->message) . "'" : "null") . ",";
1103
        $sql .= " fk_statut=" . (isset($this->fk_statut) ? (int) $this->fk_statut : "0") . ",";
1104
        $sql .= " resolution=" . (isset($this->resolution) ? (int) $this->resolution : "null") . ",";
1105
        $sql .= " progress=" . (isset($this->progress) ? "'" . $this->db->escape($this->progress) . "'" : "null") . ",";
1106
        $sql .= " timing=" . (isset($this->timing) ? "'" . $this->db->escape($this->timing) . "'" : "null") . ",";
1107
        $sql .= " type_code=" . (isset($this->type_code) ? "'" . $this->db->escape($this->type_code) . "'" : "null") . ",";
1108
        $sql .= " category_code=" . (isset($this->category_code) ? "'" . $this->db->escape($this->category_code) . "'" : "null") . ",";
1109
        $sql .= " severity_code=" . (isset($this->severity_code) ? "'" . $this->db->escape($this->severity_code) . "'" : "null") . ",";
1110
        $sql .= " datec=" . (dol_strlen($this->datec) != 0 ? "'" . $this->db->idate($this->datec) . "'" : 'null') . ",";
1111
        $sql .= " date_read=" . (dol_strlen($this->date_read) != 0 ? "'" . $this->db->idate($this->date_read) . "'" : 'null') . ",";
1112
        $sql .= " date_last_msg_sent=" . (dol_strlen($this->date_last_msg_sent) != 0 ? "'" . $this->db->idate($this->date_last_msg_sent) . "'" : 'null') . ",";
1113
        $sql .= " model_pdf=" . (isset($this->model_pdf) ? "'" . $this->db->escape($this->model_pdf) . "'" : "null") . ",";
1114
        $sql .= " date_close=" . (dol_strlen($this->date_close) != 0 ? "'" . $this->db->idate($this->date_close) . "'" : 'null');
1115
        $sql .= " WHERE rowid=" . ((int) $this->id);
1116
1117
        $this->db->begin();
1118
1119
        $resql = $this->db->query($sql);
1120
        if (!$resql) {
1121
            $error++;
1122
            $this->errors[] = "Error " . $this->db->lasterror();
1123
        }
1124
1125
        if (!$error) {
1126
            // Update extrafields
1127
            $result = $this->insertExtraFields();
1128
            if ($result < 0) {
1129
                $error++;
1130
            }
1131
        }
1132
1133
        if (!$error && !$notrigger) {
1134
            // Call trigger
1135
            $result = $this->call_trigger('TICKET_MODIFY', $user);
1136
            if ($result < 0) {
1137
                $error++;
1138
            }
1139
            // End call triggers
1140
        }
1141
1142
        // Commit or rollback
1143
        if ($error) {
1144
            foreach ($this->errors as $errmsg) {
1145
                dol_syslog(get_class($this) . "::update " . $errmsg, LOG_ERR);
1146
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
1147
            }
1148
            $this->db->rollback();
1149
            return -1 * $error;
1150
        } else {
1151
            $this->db->commit();
1152
            return 1;
1153
        }
1154
    }
1155
1156
    /**
1157
     *  Delete object in database
1158
     *
1159
     *     @param  User $user      User that deletes
1160
     *  @param  int  $notrigger 0=launch triggers after, 1=disable triggers
1161
     *  @return int                     Return integer <0 if KO, >0 if OK
1162
     */
1163
    public function delete($user, $notrigger = 0)
1164
    {
1165
        $error = 0;
1166
1167
        $this->db->begin();
1168
1169
        if (!$error) {
1170
            if (!$notrigger) {
1171
                // Call trigger
1172
                $result = $this->call_trigger('TICKET_DELETE', $user);
1173
                if ($result < 0) {
1174
                    $error++;
1175
                }
1176
                // End call triggers
1177
            }
1178
        }
1179
1180
        if (!$error) {
1181
            // Delete linked contacts
1182
            $res = $this->delete_linked_contact();
1183
            if ($res < 0) {
1184
                dol_syslog(get_class($this) . "::delete error", LOG_ERR);
1185
                $error++;
1186
            }
1187
        }
1188
1189
        if (!$error) {
1190
            // Delete linked object
1191
            $res = $this->deleteObjectLinked();
1192
            if ($res < 0) {
1193
                $error++;
1194
            }
1195
        }
1196
1197
        // Removed extrafields
1198
        if (!$error) {
1199
            $result = $this->deleteExtraFields();
1200
            if ($result < 0) {
1201
                $error++;
1202
                dol_syslog(get_class($this) . "::delete error -3 " . $this->error, LOG_ERR);
1203
            }
1204
        }
1205
1206
        // Delete all child tables
1207
1208
        if (!$error) {
1209
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . "categorie_ticket";
1210
            $sql .= " WHERE fk_ticket = " . (int) $this->id;
1211
1212
            $result = $this->db->query($sql);
1213
            if (!$result) {
1214
                $error++;
1215
                $this->errors[] = $this->db->lasterror();
1216
            }
1217
        }
1218
1219
        if (!$error) {
1220
            $sql = "DELETE FROM " . MAIN_DB_PREFIX . "ticket";
1221
            $sql .= " WHERE rowid=" . ((int) $this->id);
1222
1223
            dol_syslog(get_class($this) . "::delete sql=" . $sql);
1224
            $resql = $this->db->query($sql);
1225
            if (!$resql) {
1226
                $error++;
1227
                $this->errors[] = "Error " . $this->db->lasterror();
1228
            } else {
1229
                // we delete file with dol_delete_dir_recursive
1230
                $this->deleteEcmFiles(1);
1231
1232
                $dir = DOL_DATA_ROOT . '/' . $this->element . '/' . $this->ref;
1233
                // For remove dir
1234
                if (dol_is_dir($dir)) {
1235
                    if (!dol_delete_dir_recursive($dir)) {
1236
                        $this->errors[] = $this->error;
1237
                    }
1238
                }
1239
            }
1240
        }
1241
1242
        // Commit or rollback
1243
        if ($error) {
1244
            foreach ($this->errors as $errmsg) {
1245
                dol_syslog(get_class($this) . "::delete " . $errmsg, LOG_ERR);
1246
                $this->error .= ($this->error ? ', ' . $errmsg : $errmsg);
1247
            }
1248
            $this->db->rollback();
1249
            return -1 * $error;
1250
        } else {
1251
            $this->db->commit();
1252
            return 1;
1253
        }
1254
    }
1255
1256
    /**
1257
     *     Load an object from its id and create a new one in database
1258
     *
1259
     *     @param   User    $user       User that clone
1260
     *     @param   int     $fromid     Id of object to clone
1261
     *     @return  int                 New id of clone
1262
     */
1263
    public function createFromClone(User $user, $fromid)
1264
    {
1265
        $error = 0;
1266
1267
        $object = new Ticket($this->db);
1268
1269
        $this->db->begin();
1270
1271
        // Load source object
1272
        $object->fetch($fromid);
1273
1274
        // Clear fields
1275
        $object->id = 0;
1276
        $object->statut = 0;
1277
        $object->status = 0;
1278
1279
        // Create clone
1280
        $object->context['createfromclone'] = 'createfromclone';
1281
        $result = $object->create($user);
1282
1283
        // Other options
1284
        if ($result < 0) {
1285
            $this->error = $object->error;
1286
            $error++;
1287
        }
1288
1289
        if (!$error) {
1290
        }
1291
1292
        unset($object->context['createfromclone']);
1293
1294
        // End
1295
        if (!$error) {
1296
            $this->db->commit();
1297
            return $object->id;
1298
        } else {
1299
            $this->db->rollback();
1300
            return -1;
1301
        }
1302
    }
1303
1304
    /**
1305
     *     Initialise object with example values
1306
     *     Id must be 0 if object instance is a specimen
1307
     *
1308
     *     @return int
1309
     */
1310
    public function initAsSpecimen()
1311
    {
1312
        $this->id = 0;
1313
        $this->entity = 1;
1314
        $this->ref = 'TI0501-001';
1315
        $this->track_id = 'XXXXaaaa';
1316
        $this->origin_email = '[email protected]';
1317
        $this->fk_project = 1;
1318
        $this->fk_user_create = 1;
1319
        $this->fk_user_assign = 1;
1320
        $this->subject = 'Subject of ticket';
1321
        $this->message = 'Message of ticket';
1322
        $this->status = 0;
1323
        $this->resolution = '1';
1324
        $this->progress = 10;
1325
        //$this->timing = '30';
1326
        $this->type_code = 'TYPECODE';
1327
        $this->category_code = 'CATEGORYCODE';
1328
        $this->severity_code = 'SEVERITYCODE';
1329
        $this->datec = dol_now();
1330
        $this->date_read = dol_now();
1331
        $this->date_last_msg_sent = dol_now();
1332
        $this->date_close = dol_now();
1333
        $this->tms = dol_now();
1334
1335
        return 1;
1336
    }
1337
1338
    /**
1339
     * Print selected status
1340
     *
1341
     * @param   string    $selected     Selected status
1342
     * @return  void
1343
     */
1344
    public function printSelectStatus($selected = "")
1345
    {
1346
        print Form::selectarray('search_fk_statut', $this->labelStatusShort, $selected, $show_empty = 1, $key_in_label = 0, $value_as_key = 0, $option = '', $translate = 1, $maxlen = 0, $disabled = 0, $sort = '', $morecss = '');
1347
    }
1348
1349
1350
    /**
1351
     * Load into a cache the types of tickets (setup done into dictionaries)
1352
     *
1353
     * @return  int       Number of lines loaded, 0 if already loaded, <0 if KO
1354
     */
1355
    public function loadCacheTypesTickets()
1356
    {
1357
        global $langs;
1358
1359
        if (!empty($this->cache_types_tickets) && count($this->cache_types_tickets)) {
1360
            return 0;
1361
        }
1362
        // Cache deja charge
1363
1364
        $sql = "SELECT rowid, code, label, use_default, pos, description";
1365
        $sql .= " FROM " . MAIN_DB_PREFIX . "c_ticket_type";
1366
        $sql .= " WHERE entity IN (" . getEntity('c_ticket_type') . ")";
1367
        $sql .= " AND active > 0";
1368
        $sql .= " ORDER BY pos";
1369
        dol_syslog(get_class($this) . "::load_cache_type_tickets", LOG_DEBUG);
1370
        $resql = $this->db->query($sql);
1371
        if ($resql) {
1372
            $num = $this->db->num_rows($resql);
1373
            $i = 0;
1374
            while ($i < $num) {
1375
                $obj = $this->db->fetch_object($resql);
1376
                $label = ($langs->trans("TicketTypeShort" . $obj->code) != "TicketTypeShort" . $obj->code ? $langs->trans("TicketTypeShort" . $obj->code) : ($obj->label != '-' ? $obj->label : ''));
1377
                $this->cache_types_tickets[$obj->rowid]['code'] = $obj->code;
1378
                $this->cache_types_tickets[$obj->rowid]['label'] = $label;
1379
                $this->cache_types_tickets[$obj->rowid]['use_default'] = $obj->use_default;
1380
                $this->cache_types_tickets[$obj->rowid]['pos'] = $obj->pos;
1381
                $i++;
1382
            }
1383
            return $num;
1384
        } else {
1385
            dol_print_error($this->db);
1386
            return -1;
1387
        }
1388
    }
1389
1390
    /**
1391
     *      Load into a cache array, the list of ticket categories (setup done into dictionary)
1392
     *
1393
     *      @param  int     $publicgroup    0=No public group, 1=Public group only, -1=All
1394
     *      @return int                     Number of lines loaded, 0 if already loaded, <0 if KO
1395
     */
1396
    public function loadCacheCategoriesTickets($publicgroup = -1)
1397
    {
1398
        global $conf, $langs;
1399
1400
        if ($publicgroup == -1 && !empty($this->cache_category_ticket) && count($this->cache_category_tickets)) {
1401
            // Cache already loaded
1402
            return 0;
1403
        }
1404
1405
        $sql = "SELECT rowid, code, label, use_default, pos, description, public, active, force_severity, fk_parent";
1406
        $sql .= " FROM " . MAIN_DB_PREFIX . "c_ticket_category";
1407
        $sql .= " WHERE entity IN (" . getEntity('c_ticket_category') . ")";
1408
        $sql .= " AND active > 0";
1409
        if ($publicgroup > -1) {
1410
            $sql .= " AND public = " . ((int) $publicgroup);
1411
        }
1412
        $sql .= " ORDER BY pos";
1413
1414
        dol_syslog(get_class($this) . "::load_cache_categories_tickets", LOG_DEBUG);
1415
1416
        $resql = $this->db->query($sql);
1417
        if ($resql) {
1418
            $num = $this->db->num_rows($resql);
1419
            $i = 0;
1420
            while ($i < $num) {
1421
                $obj = $this->db->fetch_object($resql);
1422
                $this->cache_category_tickets[$obj->rowid]['code'] = $obj->code;
1423
                $this->cache_category_tickets[$obj->rowid]['use_default'] = $obj->use_default;
1424
                $this->cache_category_tickets[$obj->rowid]['pos'] = $obj->pos;
1425
                $this->cache_category_tickets[$obj->rowid]['public'] = $obj->public;
1426
                $this->cache_category_tickets[$obj->rowid]['active'] = $obj->active;
1427
                $this->cache_category_tickets[$obj->rowid]['force_severity'] = $obj->force_severity;
1428
                $this->cache_category_tickets[$obj->rowid]['fk_parent'] = $obj->fk_parent;
1429
1430
                // If  translation exists, we use it to store already translated string.
1431
                // Warning: You should not use this and recompute the translated string into caller code to get the value into expected language
1432
                $label = ($langs->trans("TicketCategoryShort" . $obj->code) != "TicketCategoryShort" . $obj->code ? $langs->trans("TicketCategoryShort" . $obj->code) : ($obj->label != '-' ? $obj->label : ''));
1433
                $this->cache_category_tickets[$obj->rowid]['label'] = $label;
1434
1435
                $i++;
1436
            }
1437
            return $num;
1438
        } else {
1439
            dol_print_error($this->db);
1440
            return -1;
1441
        }
1442
    }
1443
1444
    /**
1445
     *      Charge dans cache la liste des sévérité de tickets (paramétrable dans dictionnaire)
1446
     *
1447
     *      @return int             Number of lines loaded, 0 if already loaded, <0 if KO
1448
     */
1449
    public function loadCacheSeveritiesTickets()
1450
    {
1451
        global $conf, $langs;
1452
1453
        if (!empty($conf->cache['severity_tickets']) && count($conf->cache['severity_tickets'])) {
1454
            // Cache already loaded
1455
            return 0;
1456
        }
1457
1458
        $sql = "SELECT rowid, code, label, use_default, pos, description";
1459
        $sql .= " FROM " . MAIN_DB_PREFIX . "c_ticket_severity";
1460
        $sql .= " WHERE entity IN (" . getEntity('c_ticket_severity') . ")";
1461
        $sql .= " AND active > 0";
1462
        $sql .= " ORDER BY pos";
1463
        dol_syslog(get_class($this) . "::loadCacheSeveritiesTickets", LOG_DEBUG);
1464
        $resql = $this->db->query($sql);
1465
        if ($resql) {
1466
            $num = $this->db->num_rows($resql);
1467
            $i = 0;
1468
            while ($i < $num) {
1469
                $obj = $this->db->fetch_object($resql);
1470
1471
                $conf->cache['severity_tickets'][$obj->rowid]['code'] = $obj->code;
1472
                $label = ($langs->trans("TicketSeverityShort" . $obj->code) != "TicketSeverityShort" . $obj->code ? $langs->trans("TicketSeverityShort" . $obj->code) : ($obj->label != '-' ? $obj->label : ''));
1473
                $conf->cache['severity_tickets'][$obj->rowid]['label'] = $label;
1474
                $conf->cache['severity_tickets'][$obj->rowid]['use_default'] = $obj->use_default;
1475
                $conf->cache['severity_tickets'][$obj->rowid]['pos'] = $obj->pos;
1476
                $i++;
1477
            }
1478
            return $num;
1479
        } else {
1480
            dol_print_error($this->db);
1481
            return -1;
1482
        }
1483
    }
1484
1485
1486
    /**
1487
     * Return status label of object
1488
     *
1489
     * @param       int     $mode       0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
1490
     * @return      string              Label
1491
     */
1492
    public function getLibStatut($mode = 0)
1493
    {
1494
        return $this->LibStatut($this->status, $mode, 0, $this->progress);
1495
    }
1496
1497
1498
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1499
    /**
1500
     * Return status label of object
1501
     *
1502
     * @param   string      $status         Id status
1503
     * @param   int         $mode           0=long label, 1=short label, 2=Picto + short label, 3=Picto, 4=Picto + long label, 5=Short label + Picto, 6=Long label + Picto
1504
     * @param   int         $notooltip      1=No tooltip
1505
     * @param   int         $progress       Progression (0 to 100)
1506
     * @return  string                      Label
1507
     */
1508
    public function LibStatut($status, $mode = 0, $notooltip = 0, $progress = 0)
1509
    {
1510
		// phpcs:enable
1511
        global $langs, $hookmanager;
1512
1513
        $labelStatus = (isset($status) && !empty($this->labelStatus[$status])) ? $this->labelStatus[$status] : '';
1514
        $labelStatusShort = (isset($status) && !empty($this->labelStatusShort[$status])) ? $this->labelStatusShort[$status] : '';
1515
1516
        switch ($status) {
1517
            case self::STATUS_NOT_READ:                     // Not read
1518
                $statusType = 'status0';
1519
                break;
1520
            case self::STATUS_READ:                         // Read
1521
                $statusType = 'status1';
1522
                break;
1523
            case self::STATUS_ASSIGNED:                     // Assigned
1524
                $statusType = 'status2';
1525
                break;
1526
            case self::STATUS_IN_PROGRESS:                  // In progress
1527
                $statusType = 'status4';
1528
                break;
1529
            case self::STATUS_WAITING:                      // Waiting/pending/suspended
1530
                $statusType = 'status7';
1531
                break;
1532
            case self::STATUS_NEED_MORE_INFO:               // Waiting more information from the requester
1533
                $statusType = 'status3';
1534
                break;
1535
            case self::STATUS_CANCELED:                     // Canceled
1536
                $statusType = 'status9';
1537
                break;
1538
            case self::STATUS_CLOSED:                       // Closed
1539
                $statusType = 'status6';
1540
                break;
1541
            default:
1542
                $labelStatus = 'Unknown';
1543
                $labelStatusShort = 'Unknown';
1544
                $statusType = 'status0';
1545
                $mode = 0;
1546
        }
1547
1548
        $parameters = array(
1549
            'status'          => $status,
1550
            'mode'            => $mode,
1551
        );
1552
1553
        // Note that $action and $object may have been modified by hook
1554
        $reshook = $hookmanager->executeHooks('LibStatut', $parameters, $this);
1555
1556
        if ($reshook > 0) {
1557
            return $hookmanager->resPrint;
1558
        }
1559
1560
        $params = array();
1561
        if ($notooltip) {
1562
            $params = array('tooltip' => 'no');
1563
        }
1564
1565
        $labelStatus = $langs->transnoentitiesnoconv($labelStatus);
1566
        $labelStatusShort = $langs->transnoentitiesnoconv($labelStatusShort);
1567
1568
        if ($status == self::STATUS_IN_PROGRESS && $progress > 0) {
1569
            $labelStatus .= ' (' . round($progress) . '%)';
1570
            $labelStatusShort .= ' (' . round($progress) . '%)';
1571
        }
1572
1573
        return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode, '', $params);
1574
    }
1575
1576
    /**
1577
     * getTooltipContentArray
1578
     *
1579
     * @param array $params ex option, infologin
1580
     * @since v18
1581
     * @return array
1582
     */
1583
    public function getTooltipContentArray($params)
1584
    {
1585
        global $langs;
1586
1587
        $langs->load('ticket');
1588
        $nofetch = !empty($params['nofetch']);
1589
1590
        $datas = array();
1591
        $datas['picto'] = img_picto('', $this->picto) . ' <u class="paddingrightonly">' . $langs->trans("Ticket") . '</u>';
1592
        $datas['picto'] .= ' ' . $this->getLibStatut(4);
1593
        $datas['ref'] = '<br><b>' . $langs->trans('Ref') . ':</b> ' . $this->ref;
1594
        $datas['track_id'] = '<br><b>' . $langs->trans('TicketTrackId') . ':</b> ' . $this->track_id;
1595
        $datas['subject'] = '<br><b>' . $langs->trans('Subject') . ':</b> ' . $this->subject;
1596
        if ($this->date_creation) {
1597
            $datas['date_creation'] = '<br><b>' . $langs->trans('DateCreation') . ':</b> ' . dol_print_date($this->date_creation, 'dayhour');
1598
        }
1599
        if ($this->date_modification) {
1600
            $datas['date_modification'] = '<br><b>' . $langs->trans('DateModification') . ':</b> ' . dol_print_date($this->date_modification, 'dayhour');
1601
        }
1602
        // show categories for this record only in ajax to not overload lists
1603
        if (isModEnabled('category') && !$nofetch) {
1604
            $form = new Form($this->db);
1605
            $datas['categories'] = '<br>' . $form->showCategories($this->id, Categorie::TYPE_TICKET, 1);
1606
        }
1607
1608
        return $datas;
1609
    }
1610
1611
    /**
1612
     *  Return a link to the object card (with optionally the picto)
1613
     *
1614
     *  @param  int     $withpicto                  Include picto in link (0=No picto, 1=Include picto into link, 2=Only picto)
1615
     *  @param  string  $option                     On what the link point to ('nolink', ...)
1616
     *  @param  int     $notooltip                  1=Disable tooltip
1617
     *  @param  string  $morecss                    Add more css on link
1618
     *  @param  int     $save_lastsearch_value      -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
1619
     *  @return string                              String with URL
1620
     */
1621
    public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
1622
    {
1623
        global $action, $conf, $hookmanager, $langs;
1624
1625
        if (!empty($conf->dol_no_mouse_hover)) {
1626
            $notooltip = 1; // Force disable tooltips
1627
        }
1628
1629
        $result = '';
1630
1631
        $params = [
1632
            'id' => $this->id,
1633
            'objecttype' => $this->element,
1634
            'option' => $option,
1635
            'nofetch' => 1,
1636
        ];
1637
        $classfortooltip = 'classfortooltip';
1638
        $dataparams = '';
1639
        if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1640
            $classfortooltip = 'classforajaxtooltip';
1641
            $dataparams = ' data-params="' . dol_escape_htmltag(json_encode($params)) . '"';
1642
            $label = '';
1643
        } else {
1644
            $label = implode($this->getTooltipContentArray($params));
1645
        }
1646
1647
        $url = constant('BASE_URL') . '/ticket/card.php?id=' . $this->id;
1648
1649
        if ($option != 'nolink') {
1650
            // Add param to save lastsearch_values or not
1651
            $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1652
            if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1653
                $add_save_lastsearch_values = 1;
1654
            }
1655
            if ($add_save_lastsearch_values) {
1656
                $url .= '&save_lastsearch_values=1';
1657
            }
1658
        }
1659
1660
        $linkclose = '';
1661
        if (empty($notooltip)) {
1662
            if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1663
                $label = $langs->trans("ShowTicket");
1664
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
1665
            }
1666
            $linkclose .= ($label ? ' title="' . dol_escape_htmltag($label, 1) . '"' : ' title="tocomplete"');
1667
            $linkclose .= $dataparams . ' class="' . $classfortooltip . ($morecss ? ' ' . $morecss : '') . '"';
1668
        } else {
1669
            $linkclose = ($morecss ? ' class="' . $morecss . '"' : '');
1670
        }
1671
1672
        $linkstart = '<a href="' . $url . '"';
1673
        $linkstart .= $linkclose . '>';
1674
        $linkend = '</a>';
1675
1676
        $result .= $linkstart;
1677
        if ($withpicto) {
1678
            $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
1679
        }
1680
        if ($withpicto != 2) {
1681
            $result .= $this->ref;
1682
        }
1683
        $result .= $linkend;
1684
        //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
1685
1686
        $hookmanager->initHooks(array('ticketdao'));
1687
        $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1688
        $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1689
        if ($reshook > 0) {
1690
            $result = $hookmanager->resPrint;
1691
        } else {
1692
            $result .= $hookmanager->resPrint;
1693
        }
1694
1695
        return $result;
1696
    }
1697
1698
1699
    /**
1700
     *    Mark a message as read
1701
     *
1702
     *    @param    User        $user           Object user
1703
     *    @param    int         $notrigger      No trigger
1704
     *    @return   int                         Return integer <0 if KO, 0=nothing done, >0 if OK
1705
     */
1706
    public function markAsRead($user, $notrigger = 0)
1707
    {
1708
        global $langs;
1709
1710
        $error = 0;
1711
1712
        if ($this->status != self::STATUS_CANCELED) { // no closed
1713
            $this->oldcopy = dol_clone($this, 2);
1714
1715
            $this->db->begin();
1716
1717
            $sql = "UPDATE " . MAIN_DB_PREFIX . "ticket";
1718
            $sql .= " SET fk_statut = " . Ticket::STATUS_READ . ", date_read = '" . $this->db->idate(dol_now()) . "'";
1719
            $sql .= " WHERE rowid = " . ((int) $this->id);
1720
1721
            dol_syslog(get_class($this) . "::markAsRead");
1722
            $resql = $this->db->query($sql);
1723
            if ($resql) {
1724
                $this->context['actionmsg'] = $langs->trans('TicketLogMesgReadBy', $this->ref, $user->getFullName($langs));
1725
                $this->context['actionmsg2'] = $langs->trans('TicketLogMesgReadBy', $this->ref, $user->getFullName($langs));
1726
1727
                if (!$error && !$notrigger) {
1728
                    // Call trigger
1729
                    $result = $this->call_trigger('TICKET_MODIFY', $user);
1730
                    if ($result < 0) {
1731
                        $error++;
1732
                    }
1733
                    // End call triggers
1734
                }
1735
1736
                if (!$error) {
1737
                    $this->db->commit();
1738
                    return 1;
1739
                } else {
1740
                    $this->db->rollback();
1741
                    $this->error = implode(',', $this->errors);
1742
                    dol_syslog(get_class($this) . "::markAsRead " . $this->error, LOG_ERR);
1743
                    return -1;
1744
                }
1745
            } else {
1746
                $this->db->rollback();
1747
                $this->error = $this->db->lasterror();
1748
                dol_syslog(get_class($this) . "::markAsRead " . $this->error, LOG_ERR);
1749
                return -1;
1750
            }
1751
        }
1752
1753
        return 0;
1754
    }
1755
1756
    /**
1757
     *    Set an assigned user to a ticket.
1758
     *
1759
     *    @param    User    $user               Object user
1760
     *    @param    int     $id_assign_user     ID of user assigned
1761
     *    @param    int     $notrigger          Disable trigger
1762
     *    @return   int                         Return integer <0 if KO, 0=Nothing done, >0 if OK
1763
     */
1764
    public function assignUser($user, $id_assign_user, $notrigger = 0)
1765
    {
1766
        $error = 0;
1767
1768
        $this->oldcopy = dol_clone($this, 2);
1769
1770
        $this->db->begin();
1771
1772
        $sql = "UPDATE " . MAIN_DB_PREFIX . "ticket";
1773
        if ($id_assign_user > 0) {
1774
            $sql .= " SET fk_user_assign=" . ((int) $id_assign_user) . ", fk_statut = " . Ticket::STATUS_ASSIGNED;
1775
        } else {
1776
            $sql .= " SET fk_user_assign=null, fk_statut = " . Ticket::STATUS_READ;
1777
        }
1778
        $sql .= " WHERE rowid = " . ((int) $this->id);
1779
1780
        dol_syslog(get_class($this) . "::assignUser sql=" . $sql);
1781
        $resql = $this->db->query($sql);
1782
        if ($resql) {
1783
            $this->fk_user_assign = $id_assign_user; // May be used by trigger
1784
1785
            if (!$notrigger) {
1786
                // Call trigger
1787
                $result = $this->call_trigger('TICKET_ASSIGNED', $user);
1788
                if ($result < 0) {
1789
                    $error++;
1790
                }
1791
                // End call triggers
1792
            }
1793
1794
            if (!$error) {
1795
                $this->db->commit();
1796
                return 1;
1797
            } else {
1798
                $this->db->rollback();
1799
                $this->error = implode(',', $this->errors);
1800
                dol_syslog(get_class($this) . "::assignUser " . $this->error, LOG_ERR);
1801
                return -1;
1802
            }
1803
        } else {
1804
            $this->db->rollback();
1805
            $this->error = $this->db->lasterror();
1806
            dol_syslog(get_class($this) . "::assignUser " . $this->error, LOG_ERR);
1807
            return -1;
1808
        }
1809
    }
1810
1811
    /**
1812
     * Add message into database
1813
     *
1814
     * @param   User    $user               User that creates
1815
     * @param   int     $notrigger          0=launch triggers after, 1=disable triggers
1816
     * @param   array   $filename_list      List of files to attach (full path of filename on file system)
1817
     * @param   array   $mimetype_list      List of MIME type of attached files
1818
     * @param   array   $mimefilename_list  List of attached file name in message
1819
     * @param   boolean $send_email         Whether the message is sent by email
1820
     * @param   int     $public_area        0=Default, 1 if we are creating the message from a public area (so we can search contact from email to add it as contact of ticket if TICKET_ASSIGN_CONTACT_TO_MESSAGE is set)
1821
     * @return  int                         Return integer <0 if KO, >0 if OK
1822
     */
1823
    public function createTicketMessage($user, $notrigger = 0, $filename_list = array(), $mimetype_list = array(), $mimefilename_list = array(), $send_email = false, $public_area = 0)
1824
    {
1825
        global $conf, $langs;
1826
        $error = 0;
1827
1828
        $now = dol_now();
1829
1830
        // Clean parameters
1831
        if (isset($this->fk_track_id)) {
1832
            $this->fk_track_id = trim($this->fk_track_id);
1833
        }
1834
1835
        if (isset($this->message)) {
1836
            $this->message = trim($this->message);
1837
        }
1838
1839
        $this->db->begin();
1840
1841
        // Insert entry into agenda with code 'TICKET_MSG'
1842
        $actioncomm = new ActionComm($this->db);
1843
        $actioncomm->type_code = 'AC_OTH_AUTO'; // This is not an entry that must appears into manual calendar but only into CRM calendar
1844
        $actioncomm->code = 'TICKET_MSG';
1845
        if ($this->private) {
1846
            $actioncomm->code = 'TICKET_MSG_PRIVATE';
1847
        }
1848
        if ($send_email) {
1849
            $actioncomm->code .= '_SENTBYMAIL';
1850
        }
1851
        if ((empty($user->id) || $user->id == 0) && isset($_SESSION['email_customer'])) {
1852
            $actioncomm->email_from = $_SESSION['email_customer'];
1853
        }
1854
        $actioncomm->socid = $this->socid;
1855
        $actioncomm->label = $this->subject;
1856
        $actioncomm->note_private = $this->message;
1857
        $actioncomm->userassigned = array($user->id => array('id' => $user->id,'transparency' => 0));
1858
        $actioncomm->userownerid = $user->id;
1859
        $actioncomm->datep = $now;
1860
        $actioncomm->percentage = -1; // percentage is not relevant for punctual events
1861
        $actioncomm->elementtype = 'ticket';
1862
        $actioncomm->fk_element = $this->id;
1863
        $actioncomm->fk_project = $this->fk_project;
1864
1865
        // add contact id from author email on public interface
1866
        if ($public_area && !empty($this->origin_email) && getDolGlobalString('TICKET_ASSIGN_CONTACT_TO_MESSAGE')) {
1867
            $contacts = $this->searchContactByEmail($this->origin_email);
1868
            if (!empty($contacts)) {
1869
                // Ensure that contact is active and select first active contact
1870
                foreach ($contacts as $contact) {
1871
                    if ((int) $contact->statut == 1) {
1872
                        $actioncomm->contact_id = $contact->id;
1873
                        break;
1874
                    }
1875
                }
1876
            }
1877
        }
1878
1879
        $attachedfiles = array();
1880
        $attachedfiles['paths'] = $filename_list;
1881
        $attachedfiles['names'] = $mimefilename_list;
1882
        $attachedfiles['mimes'] = $mimetype_list;
1883
        if (is_array($attachedfiles) && count($attachedfiles) > 0) {
1884
            $actioncomm->attachedfiles = $attachedfiles;
1885
        }
1886
1887
        //if (!empty($mimefilename_list) && is_array($mimefilename_list)) {
1888
        //  $actioncomm->note_private = dol_concatdesc($actioncomm->note_private, "\n".$langs->transnoentities("AttachedFiles").': '.implode(';', $mimefilename_list));
1889
        //}
1890
        $actionid = $actioncomm->create($user);
1891
        if ($actionid <= 0) {
1892
            $error++;
1893
            $this->error = $actioncomm->error;
1894
            $this->errors = $actioncomm->errors;
1895
        }
1896
1897
        if ($actionid > 0) {
1898
            if (is_array($attachedfiles) && array_key_exists('paths', $attachedfiles) && count($attachedfiles['paths']) > 0) {
1899
                foreach ($attachedfiles['paths'] as $key => $filespath) {
1900
                    $destdir = $conf->agenda->dir_output . '/' . $actionid;
1901
                    $destfile = $destdir . '/' . $attachedfiles['names'][$key];
1902
                    if (dol_mkdir($destdir) >= 0) {
1903
                        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
1904
                        dol_move($filespath, $destfile);
1905
                        if ($actioncomm->code == "TICKET_MSG") {
1906
                            $ecmfile = new EcmFiles($this->db);
1907
                            $destdir = preg_replace('/^' . preg_quote(DOL_DATA_ROOT, '/') . '/', '', $destdir);
1908
                            $destdir = preg_replace('/[\\/]$/', '', $destdir);
1909
                            $destdir = preg_replace('/^[\\/]/', '', $destdir);
1910
                            $ecmfile->fetch(0, '', $destdir . '/' . $attachedfiles['names'][$key]);
1911
                            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/security2.lib.php';
1912
                            $ecmfile->share = getRandomPassword(true);
1913
                            $result = $ecmfile->update($user);
1914
                            if ($result < 0) {
1915
                                setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
1916
                            }
1917
                        }
1918
                    }
1919
                }
1920
            }
1921
        }
1922
1923
        // Commit or rollback
1924
        if ($error) {
1925
            $this->db->rollback();
1926
            return -1 * $error;
1927
        } else {
1928
            $this->db->commit();
1929
            return 1;
1930
        }
1931
    }
1932
1933
    /**
1934
     *      Load the list of event on ticket into ->cache_msgs_ticket
1935
     *
1936
     *      @return int             Number of lines loaded, 0 if already loaded, <0 if KO
1937
     */
1938
    public function loadCacheMsgsTicket()
1939
    {
1940
        if (!empty($this->cache_msgs_ticket) && is_array($this->cache_msgs_ticket) && count($this->cache_msgs_ticket)) {
1941
            return 0;
1942
        }
1943
1944
        // Cache already loaded
1945
1946
        $sql = "SELECT id as rowid, fk_user_author, email_from, datec, datep, label, note as message, code";
1947
        $sql .= " FROM " . MAIN_DB_PREFIX . "actioncomm";
1948
        $sql .= " WHERE fk_element = " . (int) $this->id;
1949
        $sql .= " AND elementtype = 'ticket'";
1950
        $sql .= " ORDER BY datep DESC";
1951
1952
        dol_syslog(get_class($this) . "::load_cache_actions_ticket", LOG_DEBUG);
1953
        $resql = $this->db->query($sql);
1954
        if ($resql) {
1955
            $num = $this->db->num_rows($resql);
1956
            $i = 0;
1957
            while ($i < $num) {
1958
                $obj = $this->db->fetch_object($resql);
1959
                $this->cache_msgs_ticket[$i]['id'] = $obj->rowid;
1960
                $this->cache_msgs_ticket[$i]['fk_user_author'] = $obj->fk_user_author;
1961
                if ($obj->code == 'TICKET_MSG' && empty($obj->fk_user_author)) {
1962
                    $this->cache_msgs_ticket[$i]['fk_contact_author'] = $obj->email_from;
1963
                }
1964
                $this->cache_msgs_ticket[$i]['datec'] = $this->db->jdate($obj->datec);
1965
                $this->cache_msgs_ticket[$i]['datep'] = $this->db->jdate($obj->datep);
1966
                $this->cache_msgs_ticket[$i]['subject'] = $obj->label;
1967
                $this->cache_msgs_ticket[$i]['message'] = $obj->message;
1968
                $this->cache_msgs_ticket[$i]['private'] = (preg_match('/^TICKET_MSG_PRIVATE/', $obj->code) ? 1 : 0);
1969
                $i++;
1970
            }
1971
            return $num;
1972
        } else {
1973
            $this->error = "Error " . $this->db->lasterror();
1974
            dol_syslog(get_class($this) . "::load_cache_actions_ticket " . $this->error, LOG_ERR);
1975
            return -1;
1976
        }
1977
    }
1978
1979
    /**
1980
     *    Close a ticket
1981
     *
1982
     *    @param    User    $user       User that close
1983
     *    @param    int     $mode       0=Close solved, 1=Close abandoned
1984
     *    @return   int                 Return integer <0 if KO, 0=nothing done, >0 if OK
1985
     */
1986
    public function close(User $user, $mode = 0)
1987
    {
1988
        global $conf;
1989
1990
        if ($this->status != Ticket::STATUS_CLOSED && $this->status != Ticket::STATUS_CANCELED) { // not closed
1991
            $this->db->begin();
1992
1993
            $sql = "UPDATE " . MAIN_DB_PREFIX . "ticket";
1994
            $sql .= " SET fk_statut=" . ($mode ? Ticket::STATUS_CANCELED : Ticket::STATUS_CLOSED) . ", progress=100, date_close='" . $this->db->idate(dol_now()) . "'";
1995
            $sql .= " WHERE rowid = " . ((int) $this->id);
1996
1997
            dol_syslog(get_class($this) . "::close mode=" . $mode);
1998
            $resql = $this->db->query($sql);
1999
            if ($resql) {
2000
                $error = 0;
2001
2002
                // Valid and close fichinter linked
2003
                if (isModEnabled('intervention') && getDolGlobalString('WORKFLOW_TICKET_CLOSE_INTERVENTION')) {
2004
                    dol_syslog("We have closed the ticket, so we close all linked interventions");
2005
                    $this->fetchObjectLinked($this->id, $this->element, null, 'fichinter');
2006
                    if ($this->linkedObjectsIds) {
2007
                        foreach ($this->linkedObjectsIds['fichinter'] as $fichinter_id) {
2008
                            $fichinter = new Fichinter($this->db);
2009
                            $fichinter->fetch($fichinter_id);
2010
                            if ($fichinter->statut == 0) {
2011
                                $result = $fichinter->setValid($user);
2012
                                if (!$result) {
2013
                                    $this->errors[] = $fichinter->error;
2014
                                    $error++;
2015
                                }
2016
                            }
2017
                            if ($fichinter->statut < 3) {
2018
                                $result = $fichinter->setStatut(3);
2019
                                if (!$result) {
2020
                                    $this->errors[] = $fichinter->error;
2021
                                    $error++;
2022
                                }
2023
                            }
2024
                        }
2025
                    }
2026
                }
2027
2028
                // Call trigger
2029
                $result = $this->call_trigger('TICKET_CLOSE', $user);
2030
                if ($result < 0) {
2031
                    $error++;
2032
                }
2033
                // End call triggers
2034
2035
                if (!$error) {
2036
                    $this->db->commit();
2037
                    return 1;
2038
                } else {
2039
                    $this->db->rollback();
2040
                    $this->error = implode(',', $this->errors);
2041
                    dol_syslog(get_class($this) . "::close " . $this->error, LOG_ERR);
2042
                    return -1;
2043
                }
2044
            } else {
2045
                $this->db->rollback();
2046
                $this->error = $this->db->lasterror();
2047
                dol_syslog(get_class($this) . "::close " . $this->error, LOG_ERR);
2048
                return -1;
2049
            }
2050
        }
2051
2052
        return 0;
2053
    }
2054
2055
    /**
2056
     *     Search and fetch thirparties by email
2057
     *
2058
     *     @param  string       $email          Email
2059
     *     @param  int          $type           Type of thirdparties (0=any, 1=customer, 2=prospect, 3=supplier)
2060
     *     @param  array        $filters        Array of couple field name/value to filter the companies with the same name
2061
     *     @param  string       $clause         Clause for filters
2062
     *     @return array|int                    Array of thirdparties object
2063
     */
2064
    public function searchSocidByEmail($email, $type = 0, $filters = array(), $clause = 'AND')
2065
    {
2066
        $thirdparties = array();
2067
        $exact = 0;
2068
2069
        // Generation requete recherche
2070
        $sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "societe";
2071
        $sql .= " WHERE entity IN (" . getEntity('ticket', 1) . ")";
2072
        if (!empty($type)) {
2073
            if ($type == 1 || $type == 2) {
2074
                $sql .= " AND client = " . ((int) $type);
2075
            } elseif ($type == 3) {
2076
                $sql .= " AND fournisseur = 1";
2077
            }
2078
        }
2079
        if (!empty($email)) {
2080
            if (empty($exact)) {
2081
                $regs = array();
2082
                if (preg_match('/^([\*])?[^*]+([\*])?$/', $email, $regs) && count($regs) > 1) {
2083
                    $email = str_replace('*', '%', $email);
2084
                } else {
2085
                    $email = '%' . $email . '%';
2086
                }
2087
            }
2088
            $sql .= " AND ";
2089
            if (is_array($filters) && !empty($filters)) {
2090
                $sql .= "(";
2091
            }
2092
2093
            $sql .= "email LIKE '" . $this->db->escape($email) . "'";
2094
        }
2095
        if (is_array($filters) && !empty($filters)) {
2096
            foreach ($filters as $field => $value) {
2097
                $sql .= " " . $clause . " " . $field . " LIKE '" . $this->db->escape($value) . "'";
2098
            }
2099
            if (!empty($email)) {
2100
                $sql .= ")";
2101
            }
2102
        }
2103
2104
        $res = $this->db->query($sql);
2105
        if ($res) {
2106
            while ($rec = $this->db->fetch_array($res)) {
2107
                $soc = new Societe($this->db);
2108
                $soc->fetch($rec['rowid']);
2109
                $thirdparties[] = $soc;
2110
            }
2111
2112
            return $thirdparties;
2113
        } else {
2114
            $this->error = $this->db->error() . ' sql=' . $sql;
2115
            dol_syslog(get_class($this) . "::searchSocidByEmail " . $this->error, LOG_ERR);
2116
            return -1;
2117
        }
2118
    }
2119
2120
    /**
2121
     *     Search and fetch contacts by email
2122
     *
2123
     *     @param  string       $email      Email
2124
     *     @param  int          $socid      Limit to a thirdparty
2125
     *     @param  string       $case       Respect case
2126
     *     @return array|int                Array of contacts object
2127
     */
2128
    public function searchContactByEmail($email, $socid = 0, $case = '')
2129
    {
2130
        $contacts = array();
2131
2132
        // Forge the search SQL
2133
        $sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "socpeople";
2134
        $sql .= " WHERE entity IN (" . getEntity('contact') . ")";
2135
        if (!empty($socid)) {
2136
            $sql .= " AND fk_soc = " . ((int) $socid);
2137
        }
2138
        if (!empty($email)) {
2139
            $sql .= " AND ";
2140
            if (!$case) {
2141
                $sql .= "email = '" . $this->db->escape($email) . "'";
2142
            } else {
2143
                $sql .= "email LIKE BINARY '" . $this->db->escape($this->db->escapeforlike($email)) . "'";
2144
            }
2145
        }
2146
2147
        $res = $this->db->query($sql);
2148
        if ($res) {
2149
            while ($rec = $this->db->fetch_object($res)) {
2150
                    $contactstatic = new Contact($this->db);
2151
                $contactstatic->fetch($rec->rowid);
2152
                $contacts[] = $contactstatic;
2153
            }
2154
2155
            return $contacts;
2156
        } else {
2157
            $this->error = $this->db->error() . ' sql=' . $sql;
2158
            dol_syslog(get_class($this) . "::searchContactByEmail " . $this->error, LOG_ERR);
2159
            return -1;
2160
        }
2161
    }
2162
2163
    /**
2164
     *    Define parent commany of current ticket
2165
     *
2166
     *    @param  int $id       Id of thirdparty to set or '' to remove
2167
     *    @return int           Return integer <0 if KO, >0 if OK
2168
     */
2169
    public function setCustomer($id)
2170
    {
2171
        if ($this->id) {
2172
            $sql = "UPDATE " . MAIN_DB_PREFIX . "ticket";
2173
            $sql .= " SET fk_soc = " . ($id > 0 ? $id : "null");
2174
            $sql .= " WHERE rowid = " . ((int) $this->id);
2175
            dol_syslog(get_class($this) . '::setCustomer sql=' . $sql);
2176
            $resql = $this->db->query($sql);
2177
            if ($resql) {
2178
                return 1;
2179
            } else {
2180
                return -1;
2181
            }
2182
        } else {
2183
            return -1;
2184
        }
2185
    }
2186
2187
    /**
2188
     *    Define progression of current ticket
2189
     *
2190
     *    @param  int $percent Progression percent
2191
     *    @return int             Return integer <0 if KO, >0 if OK
2192
     */
2193
    public function setProgression($percent)
2194
    {
2195
        if ($this->id) {
2196
            $sql = "UPDATE " . MAIN_DB_PREFIX . "ticket";
2197
            $sql .= " SET progress = " . ($percent > 0 ? $percent : "null");
2198
            $sql .= " WHERE rowid = " . ((int) $this->id);
2199
            dol_syslog(get_class($this) . '::set_progression sql=' . $sql);
2200
            $resql = $this->db->query($sql);
2201
            if ($resql) {
2202
                return 1;
2203
            } else {
2204
                return -1;
2205
            }
2206
        } else {
2207
            return -1;
2208
        }
2209
    }
2210
2211
    /**
2212
     *     Link element with a contract
2213
     *
2214
     *     @param  int $contractid Contract id to link element to
2215
     *     @return int                        Return integer <0 if KO, >0 if OK
2216
     */
2217
    public function setContract($contractid)
2218
    {
2219
        if ($this->id) {
2220
            $sql = "UPDATE " . MAIN_DB_PREFIX . "ticket";
2221
            $sql .= " SET fk_contract = " . ($contractid > 0 ? $contractid : "null");
2222
            $sql .= " WHERE rowid = " . ((int) $this->id);
2223
            dol_syslog(get_class($this) . '::setContract sql=' . $sql);
2224
            $resql = $this->db->query($sql);
2225
            if ($resql) {
2226
                return 1;
2227
            } else {
2228
                return -1;
2229
            }
2230
        } else {
2231
            return -1;
2232
        }
2233
    }
2234
2235
    /* gestion des contacts d'un ticket */
2236
2237
    /**
2238
     *  Return id des contacts interne de suivi
2239
     *
2240
     *  @return array       Liste des id contacts suivi ticket
2241
     */
2242
    public function getIdTicketInternalContact()
2243
    {
2244
        return $this->getIdContact('internal', 'SUPPORTTEC');
2245
    }
2246
2247
    /**
2248
     *  Retrieve information about internal contacts
2249
     *
2250
     *  @param    int     $status     Status of user or company                                array<array{id:int,email:string,firstname:string,lastname:string,libelle:string}>
2251
     *  @return array<array{id:int,email:string,firstname:string,lastname:string,libelle:string,socid:int,code:string,status:int}>             Array with datas : firstname, lastname, socid (-1 for internal users), email, code, libelle, status
2252
     */
2253
    public function getInfosTicketInternalContact($status = -1)
2254
    {
2255
        return $this->listeContact(-1, 'internal', 0, '', $status);
2256
    }
2257
2258
    /**
2259
     *  Return id des contacts clients pour le suivi ticket
2260
     *
2261
     *  @return array       Liste des id contacts suivi ticket
2262
     */
2263
    public function getIdTicketCustomerContact()
2264
    {
2265
        return $this->getIdContact('external', 'SUPPORTCLI');
2266
    }
2267
2268
    /**
2269
     * Retrieve information about external contacts
2270
     *
2271
     *  @param    int     $status     Status of user or company
2272
     *  @return array                 Array with datas : firstname, lastname, socid (-1 for internal users), email, code, libelle, status
2273
     */
2274
    public function getInfosTicketExternalContact($status = -1)
2275
    {
2276
        return $this->listeContact(-1, 'external', 0, '', $status);
2277
    }
2278
2279
    /**
2280
     *  Return id des contacts clients des intervenants
2281
     *
2282
     *  @return array       Liste des id contacts intervenants
2283
     */
2284
    public function getIdTicketInternalInvolvedContact()
2285
    {
2286
        return $this->getIdContact('internal', 'CONTRIBUTOR');
2287
    }
2288
2289
    /**
2290
     *  Return id des contacts clients des intervenants
2291
     *
2292
     *  @return array       Liste des id contacts intervenants
2293
     */
2294
    public function getIdTicketCustomerInvolvedContact()
2295
    {
2296
        return $this->getIdContact('external', 'CONTRIBUTOR');
2297
    }
2298
2299
    /**
2300
     * Return id of all contacts for ticket
2301
     *
2302
     * @return  array       Array of contacts for tickets
2303
     */
2304
    public function getTicketAllContacts()
2305
    {
2306
        $array_contact = array();
2307
2308
        $array_contact = $this->getIdTicketInternalContact();
2309
2310
        $array_contact = array_merge($array_contact, $this->getIdTicketCustomerContact());
2311
2312
        $array_contact = array_merge($array_contact, $this->getIdTicketInternalInvolvedContact());
2313
2314
        $array_contact = array_merge($array_contact, $this->getIdTicketCustomerInvolvedContact());
2315
2316
        return $array_contact;
2317
    }
2318
2319
    /**
2320
     * Return id of all contacts for ticket
2321
     *
2322
     * @return  array       Array of contacts
2323
     */
2324
    public function getTicketAllCustomerContacts()
2325
    {
2326
        $array_contact = array();
2327
2328
        $array_contact = array_merge($array_contact, $this->getIdTicketCustomerContact());
2329
2330
        $array_contact = array_merge($array_contact, $this->getIdTicketCustomerInvolvedContact());
2331
2332
        return $array_contact;
2333
    }
2334
2335
2336
    /**
2337
     *    Get array of all contacts for a ticket
2338
     *    Override method of file commonobject.class.php to add phone number
2339
     *
2340
     *    @param    int     $statusoflink   Status of lines to get (-1=all)
2341
     *    @param    string  $source         Source of contact: external or thirdparty (llx_socpeople) or internal (llx_user)
2342
     *    @param    int     $list           0:Return array contains all properties, 1:Return array contains just id
2343
     *    @param    string  $code           Filter on this code of contact type ('SHIPPING', 'BILLING', ...)
2344
     *    @param    int     $status         Status of user or company
2345
     *    @return   array<int|array{source:string,id:int,rowid:int,email:string,civility:string,firstname:string,lastname:string,labeltype:string,libelle:string,socid:int,code:string,status:int,statuscontact:string,fk_c_typecontact:string,phone:string,phone_mobile:string,nom:string}>|int<-1,-1>      Array of array('email'=>..., 'lastname'=>...)
2346
     */
2347
    public function listeContact($statusoflink = -1, $source = 'external', $list = 0, $code = '', $status = -1)
2348
    {
2349
        global $langs;
2350
2351
        $tab = array();
2352
2353
        $sql = "SELECT ec.rowid, ec.statut  as statuslink, ec.fk_socpeople as id, ec.fk_c_type_contact"; // This field contains id of llx_socpeople or id of llx_user
2354
        if ($source == 'internal') {
2355
            $sql .= ", '-1' as socid, t.statut as statuscontact";
2356
        }
2357
2358
        if ($source == 'external' || $source == 'thirdparty') {
2359
            $sql .= ", t.fk_soc as socid, t.statut as statuscontact";
2360
        }
2361
2362
        $sql .= ", t.civility, t.lastname as lastname, t.firstname, t.email";
2363
        if ($source == 'internal') {
2364
            $sql .= ", t.office_phone as phone, t.user_mobile as phone_mobile";
2365
        }
2366
2367
        if ($source == 'external') {
2368
            $sql .= ", t.phone as phone, t.phone_mobile as phone_mobile, t.phone_perso as phone_perso";
2369
        }
2370
2371
        $sql .= ", tc.source, tc.element, tc.code, tc.libelle as type_contact_label";
2372
        $sql .= " FROM " . MAIN_DB_PREFIX . "c_type_contact tc";
2373
        $sql .= ", " . MAIN_DB_PREFIX . "element_contact ec";
2374
        if ($source == 'internal') {
2375
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "user t on ec.fk_socpeople = t.rowid";
2376
        }
2377
2378
        if ($source == 'external' || $source == 'thirdparty') {
2379
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "socpeople t on ec.fk_socpeople = t.rowid";
2380
        }
2381
2382
        $sql .= " WHERE ec.element_id = " . ((int) $this->id);
2383
        $sql .= " AND ec.fk_c_type_contact=tc.rowid";
2384
        $sql .= " AND tc.element='" . $this->db->escape($this->element) . "'";
2385
        if ($source == 'internal') {
2386
            $sql .= " AND tc.source = 'internal'";
2387
            if ($status >= 0) {
2388
                $sql .= " AND t.statut = " . ((int) $status);
2389
            }
2390
        }
2391
2392
        if ($source == 'external' || $source == 'thirdparty') {
2393
            $sql .= " AND tc.source = 'external'";
2394
            if ($status >= 0) {
2395
                $sql .= " AND t.statut = " . ((int) $status);
2396
            }
2397
        }
2398
2399
        if (!empty($code)) {
2400
            $sql .= " AND tc.code = '" . $this->db->escape($code) . "'";
2401
        }
2402
2403
        $sql .= " AND tc.active=1";
2404
        if ($statusoflink >= 0) {
2405
            $sql .= " AND ec.statut = " . ((int) $statusoflink);
2406
        }
2407
2408
        $sql .= " ORDER BY t.lastname ASC";
2409
2410
        $resql = $this->db->query($sql);
2411
        if ($resql) {
2412
            $num = $this->db->num_rows($resql);
2413
            $i = 0;
2414
            while ($i < $num) {
2415
                $obj = $this->db->fetch_object($resql);
2416
2417
                if (!$list) {
2418
                    $transkey = "TypeContact_" . $obj->element . "_" . $obj->source . "_" . $obj->code;
2419
                    $labelType = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_contact_label);
2420
                    $tab[$i] = array(
2421
                        'source' => $obj->source,
2422
                        'socid' => $obj->socid,
2423
                        'id' => $obj->id,
2424
                        'nom' => $obj->lastname, // For backward compatibility
2425
                        'civility' => $obj->civility,
2426
                        'lastname' => $obj->lastname,
2427
                        'firstname' => $obj->firstname,
2428
                        'email' => $obj->email,
2429
                        'rowid' => $obj->rowid,
2430
                        'code' => $obj->code,
2431
                        'libelle' => $labelType,        // deprecated, replaced with labeltype
2432
                        'labeltype' => $labelType,
2433
                        'status' => $obj->statuslink,
2434
                        'statuscontact' => $obj->statuscontact,
2435
                        'fk_c_type_contact' => $obj->fk_c_type_contact,
2436
                        'phone' => $obj->phone,
2437
                        'phone_mobile' => $obj->phone_mobile);
2438
                } else {
2439
                    $tab[$i] = $obj->id;
2440
                }
2441
2442
                $i++;
2443
            }
2444
2445
            return $tab;
2446
        } else {
2447
            $this->error = $this->db->error();
2448
            dol_print_error($this->db);
2449
            return -1;
2450
        }
2451
    }
2452
2453
    /**
2454
     * Get a default reference.
2455
     *
2456
     * @param   Societe     $thirdparty     Thirdparty
2457
     * @return  string                      Reference
2458
     */
2459
    public function getDefaultRef($thirdparty = null)
2460
    {
2461
        global $conf;
2462
2463
        $defaultref = '';
2464
        $modele = getDolGlobalString('TICKET_ADDON', 'mod_ticket_simple');
2465
2466
        // Search template files
2467
        $file = '';
2468
        $classname = '';
2469
        $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
2470
        foreach ($dirmodels as $reldir) {
2471
            $file = dol_buildpath($reldir . "core/modules/ticket/" . $modele . '.php', 0);
2472
            if (file_exists($file)) {
2473
                $classname = $modele;
2474
                break;
2475
            }
2476
        }
2477
2478
        if ($classname !== '') {
2479
            $result = dol_include_once($reldir . "core/modules/ticket/" . $modele . '.php');
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $reldir seems to be defined by a foreach iteration on line 2470. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
2480
            $modTicket = new $classname();
2481
2482
            $defaultref = $modTicket->getNextValue($thirdparty, $this);
2483
        }
2484
2485
        if (is_numeric($defaultref) && $defaultref <= 0) {
2486
            $defaultref = '';
2487
        }
2488
2489
        return $defaultref;
2490
    }
2491
2492
2493
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2494
    /**
2495
     *  Return if at least one photo is available
2496
     *
2497
     *  @param      string      $sdir       Directory to scan
2498
     *  @return     boolean                 True if at least one photo is available, False if not
2499
     */
2500
    public function is_photo_available($sdir)
2501
    {
2502
		// phpcs:enable
2503
        include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
2504
2505
        global $conf;
2506
2507
        $dir = $sdir . '/';
2508
        $nbphoto = 0;
2509
2510
        $dir_osencoded = dol_osencode($dir);
2511
        if (file_exists($dir_osencoded)) {
2512
            $handle = opendir($dir_osencoded);
2513
            if (is_resource($handle)) {
2514
                while (($file = readdir($handle)) !== false) {
2515
                    if (!utf8_check($file)) {
2516
                        $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1');  // To be sure data is stored in UTF8 in memory
2517
                    }
2518
                    if (dol_is_file($dir . $file)) {
2519
                        return true;
2520
                    }
2521
                }
2522
            }
2523
        }
2524
        return false;
2525
    }
2526
2527
2528
    /**
2529
     * Copy files defined into $_SESSION array into the ticket directory of attached files.
2530
     * Used for files linked into messages.
2531
     * Files may be renamed during copy to avoid overwriting existing files.
2532
     *
2533
     * @param   string      $forcetrackid   Force trackid used for $keytoavoidconflict into get_attached_files()
2534
     * @return  array|int                   Array with final path/name/mime of files.
2535
     */
2536
    public function copyFilesForTicket($forcetrackid = null)
2537
    {
2538
        global $conf;
2539
2540
        // Create form object
2541
        include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
2542
        include_once DOL_DOCUMENT_ROOT . '/core/lib/images.lib.php';
2543
2544
        $maxwidthsmall = 270;
2545
        $maxheightsmall = 150;
2546
        $maxwidthmini = 128;
2547
        $maxheightmini = 72;
2548
2549
        $formmail = new FormMail($this->db);
2550
        $formmail->trackid = (is_null($forcetrackid) ? 'tic' . $this->id : '');
2551
        $attachedfiles = $formmail->get_attached_files();
2552
2553
        $filepath = $attachedfiles['paths'];    // path is for example user->dir_temp.'/'.$user->id.'/'...
2554
        $filename = $attachedfiles['names'];
2555
        $mimetype = $attachedfiles['mimes'];
2556
2557
        // Copy files into ticket directory
2558
        $destdir = $conf->ticket->dir_output . '/' . $this->ref;
2559
2560
        if (!dol_is_dir($destdir)) {
2561
            dol_mkdir($destdir);
2562
        }
2563
2564
        $listofpaths = array();
2565
        $listofnames = array();
2566
        foreach ($filename as $i => $val) {
2567
            $destfile = $destdir . '/' . $filename[$i];
2568
            // If destination file already exists, we add a suffix to avoid to overwrite
2569
            if (is_file($destfile)) {
2570
                $pathinfo = pathinfo($filename[$i]);
2571
                $now = dol_now();
2572
                $destfile = $destdir . '/' . $pathinfo['filename'] . ' - ' . dol_print_date($now, 'dayhourlog') . '.' . $pathinfo['extension'];
2573
            }
2574
2575
            $moreinfo = array('description' => 'File saved by copyFilesForTicket', 'src_object_type' => $this->element, 'src_object_id' => $this->id);
2576
            $res = dol_move($filepath[$i], $destfile, 0, 1, 0, 1, $moreinfo);
2577
            if (!$res) {
2578
                // Move has failed
2579
                $this->error = "Failed to move file " . dirbasename($filepath[$i]) . " into " . dirbasename($destfile);
2580
                return -1;
2581
            } else {
2582
                // If file is an image, we create thumbs
2583
                if (image_format_supported($destfile) == 1) {
2584
                    // Create small thumbs for image (Ratio is near 16/9)
2585
                    // Used on logon for example
2586
                    $imgThumbSmall = vignette($destfile, $maxwidthsmall, $maxheightsmall, '_small', 50, "thumbs");
2587
                    // Create mini thumbs for image (Ratio is near 16/9)
2588
                    // Used on menu or for setup page for example
2589
                    $imgThumbMini = vignette($destfile, $maxwidthmini, $maxheightmini, '_mini', 50, "thumbs");
2590
                }
2591
            }
2592
2593
            // Clear variables into session
2594
            $formmail->remove_attached_files($i);
2595
2596
            // Fill array with new names
2597
            $listofpaths[$i] = $destfile;
2598
            $listofnames[$i] = basename($destfile);
2599
        }
2600
2601
        return array('listofpaths' => $listofpaths, 'listofnames' => $listofnames, 'listofmimes' => $mimetype);
2602
    }
2603
2604
    /**
2605
     * Sets object to supplied categories.
2606
     *
2607
     * Deletes object from existing categories not supplied.
2608
     * Adds it to non existing supplied categories.
2609
     * Existing categories are left untouch.
2610
     *
2611
     * @param  int[]|int    $categories     Category or categories IDs
2612
     * @return int                          Return integer <0 if KO, >0 if OK
2613
     */
2614
    public function setCategories($categories)
2615
    {
2616
        // Handle single category
2617
        if (!is_array($categories)) {
2618
            $categories = array($categories);
2619
        }
2620
2621
        // Get current categories
2622
        $c = new Categorie($this->db);
2623
        $existing = $c->containing($this->id, Categorie::TYPE_TICKET, 'id');
2624
2625
        // Diff
2626
        if (is_array($existing)) {
2627
            $to_del = array_diff($existing, $categories);
2628
            $to_add = array_diff($categories, $existing);
2629
        } else {
2630
            $to_del = array(); // Nothing to delete
2631
            $to_add = $categories;
2632
        }
2633
2634
        // Process
2635
        foreach ($to_del as $del) {
2636
            if ($c->fetch($del) > 0) {
2637
                $c->del_type($this, Categorie::TYPE_TICKET);
2638
            }
2639
        }
2640
        foreach ($to_add as $add) {
2641
            if ($c->fetch($add) > 0) {
2642
                $c->add_type($this, Categorie::TYPE_TICKET);
2643
            }
2644
        }
2645
2646
        return 1;
2647
    }
2648
2649
    /**
2650
     * Add new message on a ticket (private/public area).
2651
     * Can also send it by email if GETPOST('send_email', 'int') is set. For such email, header and footer is added.
2652
     *
2653
     * @param   User    $user           User for action
2654
     * @param   string  $action         Action string
2655
     * @param   int     $private        1=Message is private (must not be visible by external users)
2656
     * @param   int     $public_area    0=Default,
2657
     *                                  1=If we are creating the message from a public area, so confirmation email will be sent to the author
2658
     *                                  and we can search contact from email to add it as contact of ticket if TICKET_ASSIGN_CONTACT_TO_MESSAGE is set
2659
     * @return  int                     Return integer <0 if KO, >= 0 if OK
2660
     */
2661
    public function newMessage($user, &$action, $private = 1, $public_area = 0)
2662
    {
2663
        global $mysoc, $conf, $langs;
2664
2665
        $error = 0;
2666
2667
        $object = new Ticket($this->db);
2668
2669
        $ret = $object->fetch('', '', GETPOST('track_id', 'alpha'));
2670
2671
        $object->socid = $object->fk_soc;
2672
        $object->fetch_thirdparty();
2673
        $object->fetch_project();
2674
2675
        if ($ret < 0) {
2676
            $error++;
2677
            array_push($this->errors, $langs->trans("ErrorTicketIsNotValid"));
2678
            $action = '';
2679
        }
2680
2681
        if (!GETPOST("message")) {
2682
            $error++;
2683
            array_push($this->errors, $langs->trans("ErrorFieldRequired", $langs->transnoentities("Message")));
2684
            $action = 'add_message';
2685
        }
2686
2687
        if (!$error) {
2688
            $object->subject = GETPOST('subject', 'alphanohtml');
2689
            $object->message = GETPOST("message", "restricthtml");
2690
            $object->private = GETPOST("private_message", "alpha");
2691
2692
            $send_email = GETPOSTINT('send_email');
2693
2694
            // Copy attached files (saved into $_SESSION) as linked files to ticket. Return array with final name used.
2695
            $resarray = $object->copyFilesForTicket();
2696
            if (is_numeric($resarray) && $resarray == -1) {
2697
                setEventMessages($object->error, $object->errors, 'errors');
2698
                return -1;
2699
            }
2700
2701
            $listofpaths = $resarray['listofpaths'];
2702
            $listofnames = $resarray['listofnames'];
2703
            $listofmimes = $resarray['listofmimes'];
2704
2705
            $id = $object->createTicketMessage($user, 0, $listofpaths, $listofmimes, $listofnames, $send_email, $public_area);
2706
            if ($id <= 0) {
2707
                $error++;
2708
                $this->error = $object->error;
2709
                $this->errors = $object->errors;
2710
                $action = 'add_message';
2711
            }
2712
2713
            if (!$error && $id > 0) {
2714
                setEventMessages($langs->trans('TicketMessageSuccessfullyAdded'), null, 'mesgs');
2715
2716
                if (!empty($public_area)) {
2717
                    /*
2718
                     * Message created from the Public interface
2719
                     *
2720
                     * Send emails to assigned users (public area notification)
2721
                     */
2722
                    if (getDolGlobalString('TICKET_PUBLIC_NOTIFICATION_NEW_MESSAGE_ENABLED')) {
2723
                        // Retrieve internal contact datas
2724
                        $internal_contacts = $object->getInfosTicketInternalContact(1);
2725
2726
                        $assigned_user_dont_have_email = '';
2727
2728
                        $sendto = array();
2729
2730
                        if ($this->fk_user_assign > 0) {
2731
                            $assigned_user = new User($this->db);
2732
                            $assigned_user->fetch($this->fk_user_assign);
2733
                            if (!empty($assigned_user->email)) {
2734
                                $sendto[$assigned_user->email] = $assigned_user->getFullName($langs) . " <" . $assigned_user->email . ">";
2735
                            } else {
2736
                                $assigned_user_dont_have_email = $assigned_user->getFullName($langs);
2737
                            }
2738
                        }
2739
2740
                        // Build array to display recipient list
2741
                        foreach ($internal_contacts as $key => $info_sendto) {
2742
                            // Avoid duplicate notifications
2743
                            if ($info_sendto['id'] == $user->id) {
2744
                                continue;
2745
                            }
2746
2747
                            // We check if the email address is not the assignee's address to prevent notification from being sent twice
2748
                            if (!empty($info_sendto['email']) && $assigned_user->email != $info_sendto['email']) {
2749
                                $sendto[] = dolGetFirstLastname($info_sendto['firstname'], $info_sendto['lastname']) . " <" . $info_sendto['email'] . ">";
2750
                            }
2751
                        }
2752
2753
                        if (empty($sendto)) {
2754
                            if (getDolGlobalString('TICKET_PUBLIC_NOTIFICATION_NEW_MESSAGE_DEFAULT_EMAIL')) {
2755
                                $sendto[getDolGlobalString('TICKET_PUBLIC_NOTIFICATION_NEW_MESSAGE_DEFAULT_EMAIL')] = getDolGlobalString('TICKET_PUBLIC_NOTIFICATION_NEW_MESSAGE_DEFAULT_EMAIL');
2756
                            } elseif (getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO')) {
2757
                                $sendto[getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO')] = getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO');
2758
                            }
2759
                        }
2760
2761
                        // Add global email address recipient
2762
                        if (
2763
                            getDolGlobalString('TICKET_NOTIFICATION_ALSO_MAIN_ADDRESS') &&
2764
                            getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO') && !array_key_exists(getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO'), $sendto)
2765
                        ) {
2766
                            $sendto[getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO')] = getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO');
2767
                        }
2768
2769
                        if (!empty($sendto)) {
2770
                            $appli = getDolGlobalString('MAIN_APPLICATION_TITLE', $mysoc->name);
2771
2772
                            $subject = '[' . $appli . '- ticket #' . $object->track_id . '] ' . $this->subject;
2773
2774
                            // Message send
2775
                            $message = $langs->trans('TicketMessageMailIntroText');
2776
                            $message .= '<br><br>';
2777
                            $messagePost = GETPOST('message', 'restricthtml');
2778
                            if (!dol_textishtml($messagePost)) {
2779
                                $messagePost = dol_nl2br($messagePost);
2780
                            }
2781
                            $message .= $messagePost;
2782
2783
                            // Customer company infos
2784
                            $message .= '<br><br>';
2785
                            $message .= "==============================================";
2786
                            $message .= !empty($object->thirdparty->name) ? '<br>' . $langs->trans('Thirdparty') . " : " . $object->thirdparty->name : '';
2787
                            $message .= !empty($object->thirdparty->town) ? '<br>' . $langs->trans('Town') . " : " . $object->thirdparty->town : '';
2788
                            $message .= !empty($object->thirdparty->phone) ? '<br>' . $langs->trans('Phone') . " : " . $object->thirdparty->phone : '';
2789
2790
                            // Email send to
2791
                            $message .= '<br><br>';
2792
                            if (!empty($assigned_user_dont_have_email)) {
2793
                                $message .= '<br>' . $langs->trans('NoEMail') . ' : ' . $assigned_user_dont_have_email;
2794
                            }
2795
                            foreach ($sendto as $val) {
2796
                                $message .= '<br>' . $langs->trans('TicketNotificationRecipient') . ' : ' . $val;
2797
                            }
2798
2799
                            // URL ticket
2800
                            $url_internal_ticket = dol_buildpath('/ticket/card.php', 2) . '?track_id=' . $object->track_id;
2801
                            $message .= '<br><br>';
2802
                            $message .= $langs->trans('TicketNotificationEmailBodyInfosTrackUrlinternal') . ' : <a href="' . $url_internal_ticket . '">' . $object->track_id . '</a>';
2803
2804
                            $this->sendTicketMessageByEmail($subject, $message, '', $sendto, $listofpaths, $listofmimes, $listofnames);
2805
                        }
2806
                    }
2807
                } else {
2808
                    /*
2809
                     * Message send from the Backoffice / Private area
2810
                     *
2811
                     * Send emails to internal users (linked contacts) then, if private is not set, to external users (linked contacts or thirdparty email if no contact set)
2812
                     */
2813
                    if ($send_email > 0) {
2814
                        // Retrieve internal contact datas
2815
                        $internal_contacts = $object->getInfosTicketInternalContact(1);
2816
2817
                        $sendto = array();
2818
                        if (is_array($internal_contacts) && count($internal_contacts) > 0) {
2819
                            // Set default subject
2820
                            $appli = getDolGlobalString('MAIN_APPLICATION_TITLE', $mysoc->name);
2821
2822
                            $subject = GETPOST('subject', 'alphanohtml') ? GETPOST('subject', 'alphanohtml') : '[' . $appli . ' - ' . $langs->trans("Ticket") . ' #' . $object->track_id . '] ' . $langs->trans('TicketNewMessage');
2823
2824
                            $message_intro = $langs->trans('TicketNotificationEmailBody', "#" . $object->id);
2825
                            $message_signature = GETPOST('mail_signature') ? GETPOST('mail_signature') : getDolGlobalString('TICKET_MESSAGE_MAIL_SIGNATURE');
2826
2827
                            $message = $langs->trans('TicketMessageMailIntroText');
2828
                            $message .= '<br><br>';
2829
                            $messagePost = GETPOST('message', 'restricthtml');
2830
                            if (!dol_textishtml($messagePost)) {
2831
                                $messagePost = dol_nl2br($messagePost);
2832
                            }
2833
                            $message .= $messagePost;
2834
2835
                            // Data about customer
2836
                            $message .= '<br><br>';
2837
                            $message .= "==============================================<br>";
2838
                            $message .= !empty($object->thirdparty->name) ? $langs->trans('Thirdparty') . " : " . $object->thirdparty->name : '';
2839
                            $message .= !empty($object->thirdparty->town) ? '<br>' . $langs->trans('Town') . " : " . $object->thirdparty->town : '';
2840
                            $message .= !empty($object->thirdparty->phone) ? '<br>' . $langs->trans('Phone') . " : " . $object->thirdparty->phone : '';
2841
2842
                            // Build array to display recipient list
2843
                            foreach ($internal_contacts as $key => $info_sendto) {
2844
                                // Avoid duplicate notifications
2845
                                if ($info_sendto['id'] == $user->id) {
2846
                                    continue;
2847
                                }
2848
2849
                                if ($info_sendto['email'] != '') {
2850
                                    if (!empty($info_sendto['email'])) {
2851
                                        $sendto[$info_sendto['email']] = dolGetFirstLastname($info_sendto['firstname'], $info_sendto['lastname']) . " <" . $info_sendto['email'] . ">";
2852
                                    }
2853
2854
                                    // Contact type
2855
                                    $recipient = dolGetFirstLastname($info_sendto['firstname'], $info_sendto['lastname'], '-1') . ' (' . strtolower($info_sendto['libelle']) . ')';
2856
                                    $message .= (!empty($recipient) ? $langs->trans('TicketNotificationRecipient') . ' : ' . $recipient . '<br>' : '');
2857
                                }
2858
                            }
2859
                            $message .= '<br>';
2860
                            // URL ticket
2861
                            $url_internal_ticket = dol_buildpath('/ticket/card.php', 2) . '?track_id=' . $object->track_id;
2862
2863
                            // Add html link on url
2864
                            $message .= '<br>' . $langs->trans('TicketNotificationEmailBodyInfosTrackUrlinternal') . ' : <a href="' . $url_internal_ticket . '">' . $object->track_id . '</a><br>';
2865
2866
                            // Add global email address recipient
2867
                            if (getDolGlobalString('TICKET_NOTIFICATION_ALSO_MAIN_ADDRESS') && !array_key_exists(getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO'), $sendto)) {
2868
                                if (getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO')) {
2869
                                    $sendto[getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO')] = getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO');
2870
                                }
2871
                            }
2872
2873
                            // don't try to send email if no recipient
2874
                            if (!empty($sendto)) {
2875
                                $this->sendTicketMessageByEmail($subject, $message, '', $sendto, $listofpaths, $listofmimes, $listofnames);
2876
                            }
2877
                        }
2878
2879
                        /*
2880
                         * Send emails for externals users if not private (linked contacts)
2881
                         */
2882
                        if (empty($object->private)) {
2883
                            // Retrieve email of all contacts (external)
2884
                            $external_contacts = $object->getInfosTicketExternalContact(1);
2885
2886
                            // If no contact, get email from thirdparty
2887
                            if (is_array($external_contacts) && count($external_contacts) === 0) {
2888
                                if (!empty($object->fk_soc)) {
2889
                                    $object->fetch_thirdparty($object->fk_soc);
2890
                                    $array_company = array(array('firstname' => '', 'lastname' => $object->thirdparty->name, 'email' => $object->thirdparty->email, 'libelle' => $langs->transnoentities('Customer'), 'socid' => $object->thirdparty->id));
2891
                                    $external_contacts = array_merge($external_contacts, $array_company);
2892
                                } elseif (empty($object->fk_soc) && !empty($object->origin_replyto)) {
2893
                                    $array_external = array(array('firstname' => '', 'lastname' => $object->origin_replyto, 'email' => $object->origin_replyto, 'libelle' => $langs->transnoentities('Customer'), 'socid' => 0));
2894
                                    $external_contacts = array_merge($external_contacts, $array_external);
2895
                                } elseif (empty($object->fk_soc) && !empty($object->origin_email)) {
2896
                                    $array_external = array(array('firstname' => '', 'lastname' => $object->origin_email, 'email' => $object->thirdparty->email, 'libelle' => $langs->transnoentities('Customer'), 'socid' => $object->thirdparty->id));
2897
                                    $external_contacts = array_merge($external_contacts, $array_external);
2898
                                }
2899
                            }
2900
2901
                            $sendto = array();
2902
                            if (is_array($external_contacts) && count($external_contacts) > 0) {
2903
                                // Get default subject for email to external contacts
2904
                                $appli = getDolGlobalString('MAIN_APPLICATION_TITLE', $mysoc->name);
2905
2906
                                $subject = GETPOST('subject') ? GETPOST('subject') : '[' . $appli . ' - ' . $langs->trans("Ticket") . ' #' . $object->track_id . '] ' . $langs->trans('TicketNewMessage');
2907
2908
                                $message_intro = GETPOST('mail_intro') ? GETPOST('mail_intro', 'restricthtml') : getDolGlobalString('TICKET_MESSAGE_MAIL_INTRO');
2909
                                $message_signature = GETPOST('mail_signature') ? GETPOST('mail_signature', 'restricthtml') : getDolGlobalString('TICKET_MESSAGE_MAIL_SIGNATURE');
2910
                                if (!dol_textishtml($message_intro)) {
2911
                                    $message_intro = dol_nl2br($message_intro);
2912
                                }
2913
                                if (!dol_textishtml($message_signature)) {
2914
                                    $message_signature = dol_nl2br($message_signature);
2915
                                }
2916
2917
                                // We put intro after
2918
                                $messagePost = GETPOST('message', 'restricthtml');
2919
                                if (!dol_textishtml($messagePost)) {
2920
                                    $messagePost = dol_nl2br($messagePost);
2921
                                }
2922
                                $message = $messagePost;
2923
                                $message .= '<br><br>';
2924
2925
                                foreach ($external_contacts as $key => $info_sendto) {
2926
                                    // avoid duplicate emails to external contacts
2927
                                    if ($info_sendto['id'] == $user->contact_id) {
2928
                                        continue;
2929
                                    }
2930
2931
                                    if ($info_sendto['email'] != '' && $info_sendto['email'] != $object->origin_email) {
2932
                                        if (!empty($info_sendto['email'])) {
2933
                                            $sendto[$info_sendto['email']] = trim($info_sendto['firstname'] . " " . $info_sendto['lastname']) . " <" . $info_sendto['email'] . ">";
2934
                                        }
2935
2936
                                        $recipient = dolGetFirstLastname($info_sendto['firstname'], $info_sendto['lastname'], '-1') . ' (' . strtolower($info_sendto['libelle']) . ')';
2937
                                        $message .= (!empty($recipient) ? $langs->trans('TicketNotificationRecipient') . ' : ' . $recipient . '<br>' : '');
2938
                                    }
2939
                                }
2940
2941
                                // If public interface is not enable, use link to internal page into mail
2942
                                $url_public_ticket = (getDolGlobalInt('TICKET_ENABLE_PUBLIC_INTERFACE') ?
2943
                                        (getDolGlobalString('TICKET_URL_PUBLIC_INTERFACE') !== '' ? getDolGlobalString('TICKET_URL_PUBLIC_INTERFACE') . '/view.php' : dol_buildpath('/public/ticket/view.php', 2)) : dol_buildpath('/ticket/card.php', 2)) . '?track_id=' . $object->track_id;
2944
2945
                                $message .= '<br>' . $langs->trans('TicketNewEmailBodyInfosTrackUrlCustomer') . ' : <a href="' . $url_public_ticket . '">' . $object->track_id . '</a><br>';
2946
2947
                                // Build final message
2948
                                $message = $message_intro . '<br><br>' . $message;
2949
2950
                                // Add signature
2951
                                $message .= '<br>' . $message_signature;
2952
2953
                                if (!empty($object->origin_replyto)) {
2954
                                    $sendto[$object->origin_replyto] = $object->origin_replyto;
2955
                                } elseif (!empty($object->origin_email)) {
2956
                                    $sendto[$object->origin_email] = $object->origin_email;
2957
                                }
2958
2959
                                if ($object->fk_soc > 0 && !array_key_exists($object->origin_replyto, $sendto) && !array_key_exists($object->origin_email, $sendto)) {
2960
                                    $object->socid = $object->fk_soc;
2961
                                    $object->fetch_thirdparty();
2962
                                    if (!empty($object->thirdparty->email)) {
2963
                                        $sendto[$object->thirdparty->email] = $object->thirdparty->email;
2964
                                    }
2965
                                }
2966
2967
                                // Add global email address recipient
2968
                                if (getDolGlobalString('TICKET_NOTIFICATION_ALSO_MAIN_ADDRESS') && !array_key_exists(getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO'), $sendto)) {
2969
                                    if (getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO')) {
2970
                                        $sendto[getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO')] = getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO');
2971
                                    }
2972
                                }
2973
2974
                                // Don't try to send email when no recipient
2975
                                if (!empty($sendto)) {
2976
                                    $result = $this->sendTicketMessageByEmail($subject, $message, '', $sendto, $listofpaths, $listofmimes, $listofnames);
2977
                                    if ($result) {
2978
                                        // update last_msg_sent date (for last message sent to external users)
2979
                                        $this->date_last_msg_sent = dol_now();
2980
                                        $this->update($user, 1);    // disable trigger when updating date_last_msg_sent. sendTicketMessageByEmail already create an event in actioncomm table.
2981
                                    }
2982
                                }
2983
                            }
2984
                        }
2985
                    }
2986
                }
2987
2988
                // Set status back to "In progress" if not set yet, but only if internal user and not a private message
2989
                // Or set status to "In progress" if the client has answered and if the ticket has started
2990
                // So we are sure to leave the STATUS_DRAFT, STATUS_NEED_INFO.
2991
                if (
2992
                    ($object->status < self::STATUS_IN_PROGRESS && !$user->socid && !$private) ||
2993
                    ($object->status > self::STATUS_IN_PROGRESS && $public_area)
2994
                ) {
2995
                    $object->setStatut($object::STATUS_IN_PROGRESS);
2996
                }
2997
                return 1;
2998
            } else {
2999
                setEventMessages($object->error, $object->errors, 'errors');
3000
                return -1;
3001
            }
3002
        } else {
3003
            setEventMessages($this->error, $this->errors, 'errors');
3004
            return -1;
3005
        }
3006
    }
3007
3008
3009
    /**
3010
     * Send ticket by email to linked contacts
3011
     *
3012
     * @param string $subject             Email subject
3013
     * @param string $message             Email message
3014
     * @param int    $send_internal_cc    Receive a copy on internal email (getDolGlobalString('TICKET_NOTIFICATION_EMAIL_FROM')
3015
     * @param array  $array_receiver      Array of receiver. Example array('name' => 'John Doe', 'email' => '[email protected]', etc...)
3016
     * @param array  $filename_list       List of files to attach (full path of filename on file system)
3017
     * @param array  $mimetype_list       List of MIME type of attached files
3018
     * @param array  $mimefilename_list   List of attached file name in message
3019
     * @return boolean                      True if mail sent to at least one receiver, false otherwise
3020
     */
3021
    public function sendTicketMessageByEmail($subject, $message, $send_internal_cc = 0, $array_receiver = array(), $filename_list = array(), $mimetype_list = array(), $mimefilename_list = array())
3022
    {
3023
        global $conf, $langs, $user;
3024
3025
        if (getDolGlobalString('TICKET_DISABLE_ALL_MAILS')) {
3026
            dol_syslog(get_class($this) . '::sendTicketMessageByEmail: Emails are disable into ticket setup by option TICKET_DISABLE_ALL_MAILS', LOG_WARNING);
3027
            return false;
3028
        }
3029
3030
        $langs->load("mails");
3031
3032
                //$contactstatic = new Contact($this->db);
3033
3034
        // If no receiver defined, load all ticket linked contacts
3035
        if (!is_array($array_receiver) || !count($array_receiver) > 0) {
3036
            $array_receiver = $this->getInfosTicketInternalContact(1);
3037
            $array_receiver = array_merge($array_receiver, $this->getInfosTicketExternalContact(1));
3038
        }
3039
3040
        $sendtocc = '';
3041
        if ($send_internal_cc) {
3042
            $sendtocc = getDolGlobalString('TICKET_NOTIFICATION_EMAIL_FROM');
3043
        }
3044
3045
        $from = getDolGlobalString('TICKET_NOTIFICATION_EMAIL_FROM');
3046
        $is_sent = false;
3047
        if (is_array($array_receiver) && count($array_receiver) > 0) {
3048
            foreach ($array_receiver as $key => $receiver) {
3049
                $deliveryreceipt = 0;
3050
                $filepath = $filename_list;
3051
                $filename = $mimefilename_list;
3052
                $mimetype = $mimetype_list;
3053
3054
                // Send email
3055
3056
                $old_MAIN_MAIL_AUTOCOPY_TO = getDolGlobalString('MAIN_MAIL_AUTOCOPY_TO');
3057
                if (getDolGlobalString('TICKET_DISABLE_MAIL_AUTOCOPY_TO')) {
3058
                    $conf->global->MAIN_MAIL_AUTOCOPY_TO = '';
3059
                }
3060
3061
                $upload_dir_tmp = $conf->user->dir_output . "/" . $user->id . '/temp';
3062
3063
                $trackid = "tic" . $this->id;
3064
3065
                $moreinheader = 'X-Dolibarr-Info: sendTicketMessageByEmail' . "\r\n";
3066
                if (!empty($this->email_msgid)) {
3067
                    // We must also add 1 entry In-Reply-To: <$this->email_msgid> with Message-ID we respond from (See RFC5322).
3068
                    $moreinheader .= 'In-Reply-To: <' . $this->email_msgid . '>' . "\r\n";
3069
                    // TODO We should now be able to give the in_reply_to as a dedicated parameter of new CMailFile() instead of into $moreinheader.
3070
                }
3071
3072
                // We should add here also a header 'References:'
3073
                // According to RFC5322, we should add here all the References fields of the initial message concatenated with
3074
                // the Message-ID of the message we respond from (but each ID must be once).
3075
                $references = '';
3076
                if (!empty($this->origin_references)) {     // $this->origin_references should be '<'.$this->origin_references.'>'
3077
                    $references .= (empty($references) ? '' : ' ') . $this->origin_references;
3078
                }
3079
                if (!empty($this->email_msgid) && !preg_match('/' . preg_quote($this->email_msgid, '/') . '/', $references)) {
3080
                    $references .= (empty($references) ? '' : ' ') . '<' . $this->email_msgid . '>';
3081
                }
3082
                if ($references) {
3083
                    $moreinheader .= 'References: ' . $references . "\r\n";
3084
                    // TODO We should now be able to give the references as a dedicated parameter of new CMailFile() instead of into $moreinheader.
3085
                }
3086
3087
                $mailfile = new CMailFile($subject, $receiver, $from, $message, $filepath, $mimetype, $filename, $sendtocc, '', $deliveryreceipt, -1, '', '', $trackid, $moreinheader, 'ticket', '', $upload_dir_tmp);
3088
3089
                if ($mailfile->error) {
3090
                    setEventMessages($mailfile->error, null, 'errors');
3091
                } else {
3092
                    $result = $mailfile->sendfile();
3093
                    if ($result) {
3094
                        setEventMessages($langs->trans('MailSuccessfulySent', $mailfile->getValidAddress($from, 2), $mailfile->getValidAddress($receiver, 2)), null, 'mesgs');
3095
                        $is_sent = true;
3096
                    } else {
3097
                        $langs->load("other");
3098
                        if ($mailfile->error) {
3099
                            setEventMessages($langs->trans('ErrorFailedToSendMail', $from, $receiver), null, 'errors');
3100
                            dol_syslog($langs->trans('ErrorFailedToSendMail', $from, $receiver) . ' : ' . $mailfile->error);
3101
                        } else {
3102
                            setEventMessages('No mail sent. Feature is disabled by option MAIN_DISABLE_ALL_MAILS', null, 'errors');
3103
                        }
3104
                    }
3105
                }
3106
3107
                if (getDolGlobalString('TICKET_DISABLE_MAIL_AUTOCOPY_TO')) {
3108
                    $conf->global->MAIN_MAIL_AUTOCOPY_TO = $old_MAIN_MAIL_AUTOCOPY_TO;
3109
                }
3110
            }
3111
        } else {
3112
            $langs->load("other");
3113
            setEventMessages($langs->trans('ErrorMailRecipientIsEmptyForSendTicketMessage'), null, 'warnings');
3114
        }
3115
3116
        return $is_sent;
3117
    }
3118
3119
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3120
    /**
3121
     *  Load indicators for dashboard (this->nbtodo and this->nbtodolate)
3122
     *
3123
     *  @param          User    $user   Object user
3124
     *  @param          int     $mode   "opened" for askprice to close, "signed" for proposal to invoice
3125
     *  @return         WorkboardResponse|int             Return integer <0 if KO, WorkboardResponse if OK
3126
     */
3127
    public function load_board($user, $mode)
3128
    {
3129
		// phpcs:enable
3130
        global $user, $langs;
3131
3132
        $now = dol_now();
3133
        $delay_warning = 0;
3134
3135
        $clause = " WHERE";
3136
3137
        $sql = "SELECT p.rowid, p.ref, p.datec as datec";
3138
        $sql .= " FROM " . MAIN_DB_PREFIX . "ticket as p";
3139
        if (isModEnabled('societe') && !$user->hasRight('societe', 'client', 'voir') && !$user->socid) {
3140
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON p.fk_soc = sc.fk_soc";
3141
            $sql .= " WHERE sc.fk_user = " . ((int) $user->id);
3142
            $clause = " AND";
3143
        }
3144
        $sql .= $clause . " p.entity IN (" . getEntity('ticket') . ")";
3145
        if ($mode == 'opened') {
3146
            $sql .= " AND p.fk_statut NOT IN (" . Ticket::STATUS_CLOSED . ", " . Ticket::STATUS_CANCELED . ")";
3147
        }
3148
        if ($user->socid) {
3149
            $sql .= " AND p.fk_soc = " . ((int) $user->socid);
3150
        }
3151
3152
        $resql = $this->db->query($sql);
3153
        if ($resql) {
3154
            $label = $labelShort = '';
3155
            $status = '';
3156
            if ($mode == 'opened') {
3157
                $status = 'openall';
3158
                //$delay_warning = $conf->ticket->warning_delay;
3159
                $delay_warning = 0;
3160
                $label = $langs->trans("MenuListNonClosed");
3161
                $labelShort = $langs->trans("MenuListNonClosed");
3162
            }
3163
3164
            $response = new WorkboardResponse();
3165
            //$response->warning_delay = $delay_warning / 60 / 60 / 24;
3166
            $response->label = $label;
3167
            $response->labelShort = $labelShort;
3168
            $response->url = constant('BASE_URL') . '/ticket/list.php?search_fk_statut[]=' . $status;
3169
            $response->img = img_object('', "ticket");
3170
3171
            // This assignment in condition is not a bug. It allows walking the results.
3172
            while ($obj = $this->db->fetch_object($resql)) {
3173
                $response->nbtodo++;
3174
                if ($mode == 'opened') {
3175
                    $datelimit = $this->db->jdate($obj->datec) + $delay_warning;
3176
                    if ($datelimit < $now) {
3177
                        //$response->nbtodolate++;
3178
                    }
3179
                }
3180
            }
3181
            return $response;
3182
        } else {
3183
            $this->error = $this->db->lasterror();
3184
            return -1;
3185
        }
3186
    }
3187
3188
    /**
3189
     *      Load indicator this->nb of global stats widget
3190
     *
3191
     *      @return     int         Return integer <0 if ko, >0 if ok
3192
     */
3193
    public function loadStateBoard()
3194
    {
3195
        global $user;
3196
3197
        $this->nb = array();
3198
        $clause = "WHERE";
3199
3200
        $sql = "SELECT count(p.rowid) as nb";
3201
        $sql .= " FROM " . MAIN_DB_PREFIX . "ticket as p";
3202
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON p.fk_soc = s.rowid";
3203
        if (!$user->hasRight('societe', 'client', 'voir')) {
3204
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3205
            $sql .= " WHERE sc.fk_user = " . ((int) $user->id);
3206
            $clause = "AND";
3207
        }
3208
        $sql .= " " . $clause . " p.entity IN (" . getEntity('ticket') . ")";
3209
3210
        $resql = $this->db->query($sql);
3211
        if ($resql) {
3212
            // This assignment in condition is not a bug. It allows walking the results.
3213
            while ($obj = $this->db->fetch_object($resql)) {
3214
                $this->nb["ticket"] = $obj->nb;
3215
            }
3216
            $this->db->free($resql);
3217
            return 1;
3218
        } else {
3219
            dol_print_error($this->db);
3220
            $this->error = $this->db->lasterror();
3221
            return -1;
3222
        }
3223
    }
3224
3225
    /**
3226
     * Function used to replace a thirdparty id with another one.
3227
     *
3228
     * @param DoliDB    $db             Database handler
3229
     * @param int       $origin_id      Old thirdparty id
3230
     * @param int       $dest_id        New thirdparty id
3231
     * @return bool
3232
     */
3233
    public static function replaceThirdparty($db, $origin_id, $dest_id)
3234
    {
3235
        $tables = array('ticket');
3236
3237
        return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
3238
    }
3239
3240
    /**
3241
     *  Return clicable link of object (with eventually picto)
3242
     *
3243
     *  @param      string      $option                 Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
3244
     *  @param      array       $arraydata              Array of data
3245
     *  @return     string                              HTML Code for Kanban thumb.
3246
     */
3247
    public function getKanbanView($option = '', $arraydata = null)
3248
    {
3249
        global $langs;
3250
3251
        $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
3252
3253
        $return = '<div class="box-flex-item box-flex-grow-zero">';
3254
        $return .= '<div class="info-box info-box-sm">';
3255
        $return .= '<span class="info-box-icon bg-infobox-action">';
3256
        $return .= img_picto('', $this->picto);
3257
        $return .= '</span>';
3258
        $return .= '<div class="info-box-content">';
3259
        $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . (method_exists($this, 'getNomUrl') ? $this->getNomUrl(1) : $this->ref) . '</span>';
3260
        if ($selected >= 0) {
3261
            $return .= '<input id="cb' . $this->id . '" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="' . $this->id . '"' . ($selected ? ' checked="checked"' : '') . '>';
3262
        }
3263
        if (!empty($arraydata['user_assignment'])) {
3264
            $return .= '<br><span class="info-box-label" title="' . dol_escape_htmltag($langs->trans("AssignedTo")) . '">' . $arraydata['user_assignment'] . '</span>';
3265
        }
3266
        if (property_exists($this, 'type_code') && !empty($this->type_code)) {
3267
            $return .= '<br>';
3268
            $return .= '<div class="tdoverflowmax125 inline-block">' . $langs->getLabelFromKey($this->db, 'TicketTypeShort' . $this->type_code, 'c_ticket_type', 'code', 'label', $this->type_code) . '</div>';
3269
        }
3270
        if (method_exists($this, 'getLibStatut')) {
3271
            $return .= '<br><div class="info-box-status">' . $this->getLibStatut(3) . '</div>';
3272
        }
3273
        $return .= '</div>';
3274
        $return .= '</div>';
3275
        $return .= '</div>';
3276
3277
        return $return;
3278
    }
3279
3280
    /**
3281
     *  Create a document onto disk according to template module.
3282
     *
3283
     *  @param      string      $modele         Force template to use ('' to not force)
3284
     *  @param      Translate   $outputlangs    object lang a utiliser pour traduction
3285
     *  @param      int         $hidedetails    Hide details of lines
3286
     *  @param      int         $hidedesc       Hide description
3287
     *  @param      int         $hideref        Hide ref
3288
     *  @param      null|array  $moreparams     Array to provide more information
3289
     *  @return     int                         0 if KO, 1 if OK
3290
     */
3291
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3292
    {
3293
        global $langs;
3294
3295
        $langs->load("ticket");
3296
        $outputlangs->load("ticket");
3297
3298
        if (!dol_strlen($modele)) {
3299
            $modele = 'generic_ticket_odt';
3300
3301
            if (!empty($this->model_pdf)) {
3302
                $modele = $this->model_pdf;
3303
            } elseif (getDolGlobalString('TICKET_ADDON_PDF')) {
3304
                $modele = getDolGlobalString('TICKET_ADDON_PDF');
3305
            }
3306
        }
3307
3308
        $modelpath = "core/modules/ticket/doc/";
3309
3310
        return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3311
    }
3312
}
3313