Passed
Push — EXTRACT_CLASSES ( ae6b5c...83d77a )
by Rafael
60:14 queued 23:58
created

Ticket   F

Complexity

Total Complexity 575

Size/Duplication

Total Lines 3274
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 1703
dl 0
loc 3274
rs 0.8
c 0
b 0
f 0
wmc 575

48 Methods

Rating   Name   Duplication   Size   Complexity  
A generateDocument() 0 20 4
F create() 0 151 57
A setProgression() 0 15 4
A getLibStatut() 0 3 1
B assignUser() 0 44 6
A getTicketAllContacts() 0 13 1
C LibStatut() 0 66 17
D sendTicketMessageByEmail() 0 98 20
D update() 0 152 53
B copyFilesForTicket() 0 67 7
A getTooltipContentArray() 0 27 5
A __construct() 0 32 2
D getNomUrl() 0 75 21
B load_board() 0 58 11
A getIdTicketInternalContact() 0 3 1
A replaceThirdparty() 0 5 1
B loadCacheMsgsTicket() 0 38 9
A setContract() 0 15 4
B loadCacheSeveritiesTickets() 0 33 7
A getInfosTicketExternalContact() 0 3 1
B getKanbanView() 0 31 9
A getIdTicketCustomerContact() 0 3 1
A getInfosTicketInternalContact() 0 3 1
A getIdTicketInternalInvolvedContact() 0 3 1
A getIdTicketCustomerInvolvedContact() 0 3 1
A checkExistingRef() 0 12 5
B markAsRead() 0 48 7
A printSelectStatus() 0 3 1
A getDefaultRef() 0 31 6
B loadCacheTypesTickets() 0 32 7
C close() 0 67 15
D createTicketMessage() 0 108 26
D delete() 0 90 19
A getTicketAllCustomerContacts() 0 9 1
D listeContact() 0 103 20
B loadCacheCategoriesTickets() 0 45 9
A initAsSpecimen() 0 26 1
F verify() 0 83 19
C searchSocidByEmail() 0 53 17
B searchContactByEmail() 0 33 6
D fetch() 0 128 17
A setCustomer() 0 15 4
A loadStateBoard() 0 29 4
F fetchAll() 0 183 41
A createFromClone() 0 38 4
A is_photo_available() 0 25 6
F newMessage() 0 344 88
B setCategories() 0 34 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\Core\Base\CommonObject;
30
31
/**
32
 *  \file       ticket/class/ticket.class.php
33
 *  \ingroup    ticket
34
 *  \brief      Class file for object ticket
35
 */
36
37
require_once constant('DOL_DOCUMENT_ROOT') . '/fichinter/class/fichinter.class.php';
38
require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/ticket.lib.php';
39
40
41
/**
42
 *    Class to manage ticket
43
 */
44
class Ticket extends CommonObject
45
{
46
    /**
47
     * @var DoliDB Database handler
0 ignored issues
show
Bug introduced by
The type Dolibarr\Code\Ticket\Classes\DoliDB was not found. Did you mean DoliDB? If so, make sure to prefix the type with \.
Loading history...
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
            require_once constant('DOL_DOCUMENT_ROOT') . '/categories/class/categorie.class.php';
1605
            $form = new Form($this->db);
1606
            $datas['categories'] = '<br>' . $form->showCategories($this->id, Categorie::TYPE_TICKET, 1);
1607
        }
1608
1609
        return $datas;
1610
    }
1611
1612
    /**
1613
     *  Return a link to the object card (with optionally the picto)
1614
     *
1615
     *  @param  int     $withpicto                  Include picto in link (0=No picto, 1=Include picto into link, 2=Only picto)
1616
     *  @param  string  $option                     On what the link point to ('nolink', ...)
1617
     *  @param  int     $notooltip                  1=Disable tooltip
1618
     *  @param  string  $morecss                    Add more css on link
1619
     *  @param  int     $save_lastsearch_value      -1=Auto, 0=No save of lastsearch_values when clicking, 1=Save lastsearch_values whenclicking
1620
     *  @return string                              String with URL
1621
     */
1622
    public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
1623
    {
1624
        global $action, $conf, $hookmanager, $langs;
1625
1626
        if (!empty($conf->dol_no_mouse_hover)) {
1627
            $notooltip = 1; // Force disable tooltips
1628
        }
1629
1630
        $result = '';
1631
1632
        $params = [
1633
            'id' => $this->id,
1634
            'objecttype' => $this->element,
1635
            'option' => $option,
1636
            'nofetch' => 1,
1637
        ];
1638
        $classfortooltip = 'classfortooltip';
1639
        $dataparams = '';
1640
        if (getDolGlobalInt('MAIN_ENABLE_AJAX_TOOLTIP')) {
1641
            $classfortooltip = 'classforajaxtooltip';
1642
            $dataparams = ' data-params="' . dol_escape_htmltag(json_encode($params)) . '"';
1643
            $label = '';
1644
        } else {
1645
            $label = implode($this->getTooltipContentArray($params));
1646
        }
1647
1648
        $url = constant('BASE_URL') . '/ticket/card.php?id=' . $this->id;
1649
1650
        if ($option != 'nolink') {
1651
            // Add param to save lastsearch_values or not
1652
            $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1653
            if ($save_lastsearch_value == -1 && isset($_SERVER["PHP_SELF"]) && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1654
                $add_save_lastsearch_values = 1;
1655
            }
1656
            if ($add_save_lastsearch_values) {
1657
                $url .= '&save_lastsearch_values=1';
1658
            }
1659
        }
1660
1661
        $linkclose = '';
1662
        if (empty($notooltip)) {
1663
            if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1664
                $label = $langs->trans("ShowTicket");
1665
                $linkclose .= ' alt="' . dol_escape_htmltag($label, 1) . '"';
1666
            }
1667
            $linkclose .= ($label ? ' title="' . dol_escape_htmltag($label, 1) . '"' : ' title="tocomplete"');
1668
            $linkclose .= $dataparams . ' class="' . $classfortooltip . ($morecss ? ' ' . $morecss : '') . '"';
1669
        } else {
1670
            $linkclose = ($morecss ? ' class="' . $morecss . '"' : '');
1671
        }
1672
1673
        $linkstart = '<a href="' . $url . '"';
1674
        $linkstart .= $linkclose . '>';
1675
        $linkend = '</a>';
1676
1677
        $result .= $linkstart;
1678
        if ($withpicto) {
1679
            $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), (($withpicto != 2) ? 'class="paddingright"' : ''), 0, 0, $notooltip ? 0 : 1);
1680
        }
1681
        if ($withpicto != 2) {
1682
            $result .= $this->ref;
1683
        }
1684
        $result .= $linkend;
1685
        //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
1686
1687
        $hookmanager->initHooks(array('ticketdao'));
1688
        $parameters = array('id' => $this->id, 'getnomurl' => &$result);
1689
        $reshook = $hookmanager->executeHooks('getNomUrl', $parameters, $this, $action); // Note that $action and $object may have been modified by some hooks
1690
        if ($reshook > 0) {
1691
            $result = $hookmanager->resPrint;
1692
        } else {
1693
            $result .= $hookmanager->resPrint;
1694
        }
1695
1696
        return $result;
1697
    }
1698
1699
1700
    /**
1701
     *    Mark a message as read
1702
     *
1703
     *    @param    User        $user           Object user
1704
     *    @param    int         $notrigger      No trigger
1705
     *    @return   int                         Return integer <0 if KO, 0=nothing done, >0 if OK
1706
     */
1707
    public function markAsRead($user, $notrigger = 0)
1708
    {
1709
        global $langs;
1710
1711
        $error = 0;
1712
1713
        if ($this->status != self::STATUS_CANCELED) { // no closed
1714
            $this->oldcopy = dol_clone($this, 2);
1715
1716
            $this->db->begin();
1717
1718
            $sql = "UPDATE " . MAIN_DB_PREFIX . "ticket";
1719
            $sql .= " SET fk_statut = " . Ticket::STATUS_READ . ", date_read = '" . $this->db->idate(dol_now()) . "'";
1720
            $sql .= " WHERE rowid = " . ((int) $this->id);
1721
1722
            dol_syslog(get_class($this) . "::markAsRead");
1723
            $resql = $this->db->query($sql);
1724
            if ($resql) {
1725
                $this->context['actionmsg'] = $langs->trans('TicketLogMesgReadBy', $this->ref, $user->getFullName($langs));
1726
                $this->context['actionmsg2'] = $langs->trans('TicketLogMesgReadBy', $this->ref, $user->getFullName($langs));
1727
1728
                if (!$error && !$notrigger) {
1729
                    // Call trigger
1730
                    $result = $this->call_trigger('TICKET_MODIFY', $user);
1731
                    if ($result < 0) {
1732
                        $error++;
1733
                    }
1734
                    // End call triggers
1735
                }
1736
1737
                if (!$error) {
1738
                    $this->db->commit();
1739
                    return 1;
1740
                } else {
1741
                    $this->db->rollback();
1742
                    $this->error = implode(',', $this->errors);
1743
                    dol_syslog(get_class($this) . "::markAsRead " . $this->error, LOG_ERR);
1744
                    return -1;
1745
                }
1746
            } else {
1747
                $this->db->rollback();
1748
                $this->error = $this->db->lasterror();
1749
                dol_syslog(get_class($this) . "::markAsRead " . $this->error, LOG_ERR);
1750
                return -1;
1751
            }
1752
        }
1753
1754
        return 0;
1755
    }
1756
1757
    /**
1758
     *    Set an assigned user to a ticket.
1759
     *
1760
     *    @param    User    $user               Object user
1761
     *    @param    int     $id_assign_user     ID of user assigned
1762
     *    @param    int     $notrigger          Disable trigger
1763
     *    @return   int                         Return integer <0 if KO, 0=Nothing done, >0 if OK
1764
     */
1765
    public function assignUser($user, $id_assign_user, $notrigger = 0)
1766
    {
1767
        $error = 0;
1768
1769
        $this->oldcopy = dol_clone($this, 2);
1770
1771
        $this->db->begin();
1772
1773
        $sql = "UPDATE " . MAIN_DB_PREFIX . "ticket";
1774
        if ($id_assign_user > 0) {
1775
            $sql .= " SET fk_user_assign=" . ((int) $id_assign_user) . ", fk_statut = " . Ticket::STATUS_ASSIGNED;
1776
        } else {
1777
            $sql .= " SET fk_user_assign=null, fk_statut = " . Ticket::STATUS_READ;
1778
        }
1779
        $sql .= " WHERE rowid = " . ((int) $this->id);
1780
1781
        dol_syslog(get_class($this) . "::assignUser sql=" . $sql);
1782
        $resql = $this->db->query($sql);
1783
        if ($resql) {
1784
            $this->fk_user_assign = $id_assign_user; // May be used by trigger
1785
1786
            if (!$notrigger) {
1787
                // Call trigger
1788
                $result = $this->call_trigger('TICKET_ASSIGNED', $user);
1789
                if ($result < 0) {
1790
                    $error++;
1791
                }
1792
                // End call triggers
1793
            }
1794
1795
            if (!$error) {
1796
                $this->db->commit();
1797
                return 1;
1798
            } else {
1799
                $this->db->rollback();
1800
                $this->error = implode(',', $this->errors);
1801
                dol_syslog(get_class($this) . "::assignUser " . $this->error, LOG_ERR);
1802
                return -1;
1803
            }
1804
        } else {
1805
            $this->db->rollback();
1806
            $this->error = $this->db->lasterror();
1807
            dol_syslog(get_class($this) . "::assignUser " . $this->error, LOG_ERR);
1808
            return -1;
1809
        }
1810
    }
1811
1812
    /**
1813
     * Add message into database
1814
     *
1815
     * @param   User    $user               User that creates
1816
     * @param   int     $notrigger          0=launch triggers after, 1=disable triggers
1817
     * @param   array   $filename_list      List of files to attach (full path of filename on file system)
1818
     * @param   array   $mimetype_list      List of MIME type of attached files
1819
     * @param   array   $mimefilename_list  List of attached file name in message
1820
     * @param   boolean $send_email         Whether the message is sent by email
1821
     * @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)
1822
     * @return  int                         Return integer <0 if KO, >0 if OK
1823
     */
1824
    public function createTicketMessage($user, $notrigger = 0, $filename_list = array(), $mimetype_list = array(), $mimefilename_list = array(), $send_email = false, $public_area = 0)
1825
    {
1826
        global $conf, $langs;
1827
        $error = 0;
1828
1829
        $now = dol_now();
1830
1831
        // Clean parameters
1832
        if (isset($this->fk_track_id)) {
1833
            $this->fk_track_id = trim($this->fk_track_id);
1834
        }
1835
1836
        if (isset($this->message)) {
1837
            $this->message = trim($this->message);
1838
        }
1839
1840
        $this->db->begin();
1841
1842
        // Insert entry into agenda with code 'TICKET_MSG'
1843
        include_once DOL_DOCUMENT_ROOT . '/comm/action/class/actioncomm.class.php';
1844
        $actioncomm = new ActionComm($this->db);
1845
        $actioncomm->type_code = 'AC_OTH_AUTO'; // This is not an entry that must appears into manual calendar but only into CRM calendar
1846
        $actioncomm->code = 'TICKET_MSG';
1847
        if ($this->private) {
1848
            $actioncomm->code = 'TICKET_MSG_PRIVATE';
1849
        }
1850
        if ($send_email) {
1851
            $actioncomm->code .= '_SENTBYMAIL';
1852
        }
1853
        if ((empty($user->id) || $user->id == 0) && isset($_SESSION['email_customer'])) {
1854
            $actioncomm->email_from = $_SESSION['email_customer'];
1855
        }
1856
        $actioncomm->socid = $this->socid;
1857
        $actioncomm->label = $this->subject;
1858
        $actioncomm->note_private = $this->message;
1859
        $actioncomm->userassigned = array($user->id => array('id' => $user->id,'transparency' => 0));
1860
        $actioncomm->userownerid = $user->id;
1861
        $actioncomm->datep = $now;
1862
        $actioncomm->percentage = -1; // percentage is not relevant for punctual events
1863
        $actioncomm->elementtype = 'ticket';
1864
        $actioncomm->fk_element = $this->id;
1865
        $actioncomm->fk_project = $this->fk_project;
1866
1867
        // add contact id from author email on public interface
1868
        if ($public_area && !empty($this->origin_email) && getDolGlobalString('TICKET_ASSIGN_CONTACT_TO_MESSAGE')) {
1869
            $contacts = $this->searchContactByEmail($this->origin_email);
1870
            if (!empty($contacts)) {
1871
                // Ensure that contact is active and select first active contact
1872
                foreach ($contacts as $contact) {
1873
                    if ((int) $contact->statut == 1) {
1874
                        $actioncomm->contact_id = $contact->id;
1875
                        break;
1876
                    }
1877
                }
1878
            }
1879
        }
1880
1881
        $attachedfiles = array();
1882
        $attachedfiles['paths'] = $filename_list;
1883
        $attachedfiles['names'] = $mimefilename_list;
1884
        $attachedfiles['mimes'] = $mimetype_list;
1885
        if (is_array($attachedfiles) && count($attachedfiles) > 0) {
1886
            $actioncomm->attachedfiles = $attachedfiles;
1887
        }
1888
1889
        //if (!empty($mimefilename_list) && is_array($mimefilename_list)) {
1890
        //  $actioncomm->note_private = dol_concatdesc($actioncomm->note_private, "\n".$langs->transnoentities("AttachedFiles").': '.implode(';', $mimefilename_list));
1891
        //}
1892
        $actionid = $actioncomm->create($user);
1893
        if ($actionid <= 0) {
1894
            $error++;
1895
            $this->error = $actioncomm->error;
1896
            $this->errors = $actioncomm->errors;
1897
        }
1898
1899
        if ($actionid > 0) {
1900
            if (is_array($attachedfiles) && array_key_exists('paths', $attachedfiles) && count($attachedfiles['paths']) > 0) {
1901
                foreach ($attachedfiles['paths'] as $key => $filespath) {
1902
                    $destdir = $conf->agenda->dir_output . '/' . $actionid;
1903
                    $destfile = $destdir . '/' . $attachedfiles['names'][$key];
1904
                    if (dol_mkdir($destdir) >= 0) {
1905
                        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
1906
                        dol_move($filespath, $destfile);
1907
                        if ($actioncomm->code == "TICKET_MSG") {
1908
                            $ecmfile = new EcmFiles($this->db);
1909
                            $destdir = preg_replace('/^' . preg_quote(DOL_DATA_ROOT, '/') . '/', '', $destdir);
1910
                            $destdir = preg_replace('/[\\/]$/', '', $destdir);
1911
                            $destdir = preg_replace('/^[\\/]/', '', $destdir);
1912
                            $ecmfile->fetch(0, '', $destdir . '/' . $attachedfiles['names'][$key]);
1913
                            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/security2.lib.php';
1914
                            $ecmfile->share = getRandomPassword(true);
1915
                            $result = $ecmfile->update($user);
1916
                            if ($result < 0) {
1917
                                setEventMessages($ecmfile->error, $ecmfile->errors, 'warnings');
1918
                            }
1919
                        }
1920
                    }
1921
                }
1922
            }
1923
        }
1924
1925
        // Commit or rollback
1926
        if ($error) {
1927
            $this->db->rollback();
1928
            return -1 * $error;
1929
        } else {
1930
            $this->db->commit();
1931
            return 1;
1932
        }
1933
    }
1934
1935
    /**
1936
     *      Load the list of event on ticket into ->cache_msgs_ticket
1937
     *
1938
     *      @return int             Number of lines loaded, 0 if already loaded, <0 if KO
1939
     */
1940
    public function loadCacheMsgsTicket()
1941
    {
1942
        if (!empty($this->cache_msgs_ticket) && is_array($this->cache_msgs_ticket) && count($this->cache_msgs_ticket)) {
1943
            return 0;
1944
        }
1945
1946
        // Cache already loaded
1947
1948
        $sql = "SELECT id as rowid, fk_user_author, email_from, datec, datep, label, note as message, code";
1949
        $sql .= " FROM " . MAIN_DB_PREFIX . "actioncomm";
1950
        $sql .= " WHERE fk_element = " . (int) $this->id;
1951
        $sql .= " AND elementtype = 'ticket'";
1952
        $sql .= " ORDER BY datep DESC";
1953
1954
        dol_syslog(get_class($this) . "::load_cache_actions_ticket", LOG_DEBUG);
1955
        $resql = $this->db->query($sql);
1956
        if ($resql) {
1957
            $num = $this->db->num_rows($resql);
1958
            $i = 0;
1959
            while ($i < $num) {
1960
                $obj = $this->db->fetch_object($resql);
1961
                $this->cache_msgs_ticket[$i]['id'] = $obj->rowid;
1962
                $this->cache_msgs_ticket[$i]['fk_user_author'] = $obj->fk_user_author;
1963
                if ($obj->code == 'TICKET_MSG' && empty($obj->fk_user_author)) {
1964
                    $this->cache_msgs_ticket[$i]['fk_contact_author'] = $obj->email_from;
1965
                }
1966
                $this->cache_msgs_ticket[$i]['datec'] = $this->db->jdate($obj->datec);
1967
                $this->cache_msgs_ticket[$i]['datep'] = $this->db->jdate($obj->datep);
1968
                $this->cache_msgs_ticket[$i]['subject'] = $obj->label;
1969
                $this->cache_msgs_ticket[$i]['message'] = $obj->message;
1970
                $this->cache_msgs_ticket[$i]['private'] = (preg_match('/^TICKET_MSG_PRIVATE/', $obj->code) ? 1 : 0);
1971
                $i++;
1972
            }
1973
            return $num;
1974
        } else {
1975
            $this->error = "Error " . $this->db->lasterror();
1976
            dol_syslog(get_class($this) . "::load_cache_actions_ticket " . $this->error, LOG_ERR);
1977
            return -1;
1978
        }
1979
    }
1980
1981
    /**
1982
     *    Close a ticket
1983
     *
1984
     *    @param    User    $user       User that close
1985
     *    @param    int     $mode       0=Close solved, 1=Close abandoned
1986
     *    @return   int                 Return integer <0 if KO, 0=nothing done, >0 if OK
1987
     */
1988
    public function close(User $user, $mode = 0)
1989
    {
1990
        global $conf;
1991
1992
        if ($this->status != Ticket::STATUS_CLOSED && $this->status != Ticket::STATUS_CANCELED) { // not closed
1993
            $this->db->begin();
1994
1995
            $sql = "UPDATE " . MAIN_DB_PREFIX . "ticket";
1996
            $sql .= " SET fk_statut=" . ($mode ? Ticket::STATUS_CANCELED : Ticket::STATUS_CLOSED) . ", progress=100, date_close='" . $this->db->idate(dol_now()) . "'";
1997
            $sql .= " WHERE rowid = " . ((int) $this->id);
1998
1999
            dol_syslog(get_class($this) . "::close mode=" . $mode);
2000
            $resql = $this->db->query($sql);
2001
            if ($resql) {
2002
                $error = 0;
2003
2004
                // Valid and close fichinter linked
2005
                if (isModEnabled('intervention') && getDolGlobalString('WORKFLOW_TICKET_CLOSE_INTERVENTION')) {
2006
                    dol_syslog("We have closed the ticket, so we close all linked interventions");
2007
                    $this->fetchObjectLinked($this->id, $this->element, null, 'fichinter');
2008
                    if ($this->linkedObjectsIds) {
2009
                        foreach ($this->linkedObjectsIds['fichinter'] as $fichinter_id) {
2010
                            $fichinter = new Fichinter($this->db);
2011
                            $fichinter->fetch($fichinter_id);
2012
                            if ($fichinter->statut == 0) {
2013
                                $result = $fichinter->setValid($user);
2014
                                if (!$result) {
2015
                                    $this->errors[] = $fichinter->error;
2016
                                    $error++;
2017
                                }
2018
                            }
2019
                            if ($fichinter->statut < 3) {
2020
                                $result = $fichinter->setStatut(3);
2021
                                if (!$result) {
2022
                                    $this->errors[] = $fichinter->error;
2023
                                    $error++;
2024
                                }
2025
                            }
2026
                        }
2027
                    }
2028
                }
2029
2030
                // Call trigger
2031
                $result = $this->call_trigger('TICKET_CLOSE', $user);
2032
                if ($result < 0) {
2033
                    $error++;
2034
                }
2035
                // End call triggers
2036
2037
                if (!$error) {
2038
                    $this->db->commit();
2039
                    return 1;
2040
                } else {
2041
                    $this->db->rollback();
2042
                    $this->error = implode(',', $this->errors);
2043
                    dol_syslog(get_class($this) . "::close " . $this->error, LOG_ERR);
2044
                    return -1;
2045
                }
2046
            } else {
2047
                $this->db->rollback();
2048
                $this->error = $this->db->lasterror();
2049
                dol_syslog(get_class($this) . "::close " . $this->error, LOG_ERR);
2050
                return -1;
2051
            }
2052
        }
2053
2054
        return 0;
2055
    }
2056
2057
    /**
2058
     *     Search and fetch thirparties by email
2059
     *
2060
     *     @param  string       $email          Email
2061
     *     @param  int          $type           Type of thirdparties (0=any, 1=customer, 2=prospect, 3=supplier)
2062
     *     @param  array        $filters        Array of couple field name/value to filter the companies with the same name
2063
     *     @param  string       $clause         Clause for filters
2064
     *     @return array|int                    Array of thirdparties object
2065
     */
2066
    public function searchSocidByEmail($email, $type = 0, $filters = array(), $clause = 'AND')
2067
    {
2068
        $thirdparties = array();
2069
        $exact = 0;
2070
2071
        // Generation requete recherche
2072
        $sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "societe";
2073
        $sql .= " WHERE entity IN (" . getEntity('ticket', 1) . ")";
2074
        if (!empty($type)) {
2075
            if ($type == 1 || $type == 2) {
2076
                $sql .= " AND client = " . ((int) $type);
2077
            } elseif ($type == 3) {
2078
                $sql .= " AND fournisseur = 1";
2079
            }
2080
        }
2081
        if (!empty($email)) {
2082
            if (empty($exact)) {
2083
                $regs = array();
2084
                if (preg_match('/^([\*])?[^*]+([\*])?$/', $email, $regs) && count($regs) > 1) {
2085
                    $email = str_replace('*', '%', $email);
2086
                } else {
2087
                    $email = '%' . $email . '%';
2088
                }
2089
            }
2090
            $sql .= " AND ";
2091
            if (is_array($filters) && !empty($filters)) {
2092
                $sql .= "(";
2093
            }
2094
2095
            $sql .= "email LIKE '" . $this->db->escape($email) . "'";
2096
        }
2097
        if (is_array($filters) && !empty($filters)) {
2098
            foreach ($filters as $field => $value) {
2099
                $sql .= " " . $clause . " " . $field . " LIKE '" . $this->db->escape($value) . "'";
2100
            }
2101
            if (!empty($email)) {
2102
                $sql .= ")";
2103
            }
2104
        }
2105
2106
        $res = $this->db->query($sql);
2107
        if ($res) {
2108
            while ($rec = $this->db->fetch_array($res)) {
2109
                $soc = new Societe($this->db);
2110
                $soc->fetch($rec['rowid']);
2111
                $thirdparties[] = $soc;
2112
            }
2113
2114
            return $thirdparties;
2115
        } else {
2116
            $this->error = $this->db->error() . ' sql=' . $sql;
2117
            dol_syslog(get_class($this) . "::searchSocidByEmail " . $this->error, LOG_ERR);
2118
            return -1;
2119
        }
2120
    }
2121
2122
    /**
2123
     *     Search and fetch contacts by email
2124
     *
2125
     *     @param  string       $email      Email
2126
     *     @param  int          $socid      Limit to a thirdparty
2127
     *     @param  string       $case       Respect case
2128
     *     @return array|int                Array of contacts object
2129
     */
2130
    public function searchContactByEmail($email, $socid = 0, $case = '')
2131
    {
2132
        $contacts = array();
2133
2134
        // Forge the search SQL
2135
        $sql = "SELECT rowid FROM " . MAIN_DB_PREFIX . "socpeople";
2136
        $sql .= " WHERE entity IN (" . getEntity('contact') . ")";
2137
        if (!empty($socid)) {
2138
            $sql .= " AND fk_soc = " . ((int) $socid);
2139
        }
2140
        if (!empty($email)) {
2141
            $sql .= " AND ";
2142
            if (!$case) {
2143
                $sql .= "email = '" . $this->db->escape($email) . "'";
2144
            } else {
2145
                $sql .= "email LIKE BINARY '" . $this->db->escape($this->db->escapeforlike($email)) . "'";
2146
            }
2147
        }
2148
2149
        $res = $this->db->query($sql);
2150
        if ($res) {
2151
            while ($rec = $this->db->fetch_object($res)) {
2152
                include_once DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php';
2153
                $contactstatic = new Contact($this->db);
2154
                $contactstatic->fetch($rec->rowid);
2155
                $contacts[] = $contactstatic;
2156
            }
2157
2158
            return $contacts;
2159
        } else {
2160
            $this->error = $this->db->error() . ' sql=' . $sql;
2161
            dol_syslog(get_class($this) . "::searchContactByEmail " . $this->error, LOG_ERR);
2162
            return -1;
2163
        }
2164
    }
2165
2166
    /**
2167
     *    Define parent commany of current ticket
2168
     *
2169
     *    @param  int $id       Id of thirdparty to set or '' to remove
2170
     *    @return int           Return integer <0 if KO, >0 if OK
2171
     */
2172
    public function setCustomer($id)
2173
    {
2174
        if ($this->id) {
2175
            $sql = "UPDATE " . MAIN_DB_PREFIX . "ticket";
2176
            $sql .= " SET fk_soc = " . ($id > 0 ? $id : "null");
2177
            $sql .= " WHERE rowid = " . ((int) $this->id);
2178
            dol_syslog(get_class($this) . '::setCustomer sql=' . $sql);
2179
            $resql = $this->db->query($sql);
2180
            if ($resql) {
2181
                return 1;
2182
            } else {
2183
                return -1;
2184
            }
2185
        } else {
2186
            return -1;
2187
        }
2188
    }
2189
2190
    /**
2191
     *    Define progression of current ticket
2192
     *
2193
     *    @param  int $percent Progression percent
2194
     *    @return int             Return integer <0 if KO, >0 if OK
2195
     */
2196
    public function setProgression($percent)
2197
    {
2198
        if ($this->id) {
2199
            $sql = "UPDATE " . MAIN_DB_PREFIX . "ticket";
2200
            $sql .= " SET progress = " . ($percent > 0 ? $percent : "null");
2201
            $sql .= " WHERE rowid = " . ((int) $this->id);
2202
            dol_syslog(get_class($this) . '::set_progression sql=' . $sql);
2203
            $resql = $this->db->query($sql);
2204
            if ($resql) {
2205
                return 1;
2206
            } else {
2207
                return -1;
2208
            }
2209
        } else {
2210
            return -1;
2211
        }
2212
    }
2213
2214
    /**
2215
     *     Link element with a contract
2216
     *
2217
     *     @param  int $contractid Contract id to link element to
2218
     *     @return int                        Return integer <0 if KO, >0 if OK
2219
     */
2220
    public function setContract($contractid)
2221
    {
2222
        if ($this->id) {
2223
            $sql = "UPDATE " . MAIN_DB_PREFIX . "ticket";
2224
            $sql .= " SET fk_contract = " . ($contractid > 0 ? $contractid : "null");
2225
            $sql .= " WHERE rowid = " . ((int) $this->id);
2226
            dol_syslog(get_class($this) . '::setContract sql=' . $sql);
2227
            $resql = $this->db->query($sql);
2228
            if ($resql) {
2229
                return 1;
2230
            } else {
2231
                return -1;
2232
            }
2233
        } else {
2234
            return -1;
2235
        }
2236
    }
2237
2238
    /* gestion des contacts d'un ticket */
2239
2240
    /**
2241
     *  Return id des contacts interne de suivi
2242
     *
2243
     *  @return array       Liste des id contacts suivi ticket
2244
     */
2245
    public function getIdTicketInternalContact()
2246
    {
2247
        return $this->getIdContact('internal', 'SUPPORTTEC');
2248
    }
2249
2250
    /**
2251
     *  Retrieve information about internal contacts
2252
     *
2253
     *  @param    int     $status     Status of user or company                                array<array{id:int,email:string,firstname:string,lastname:string,libelle:string}>
2254
     *  @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
2255
     */
2256
    public function getInfosTicketInternalContact($status = -1)
2257
    {
2258
        return $this->listeContact(-1, 'internal', 0, '', $status);
2259
    }
2260
2261
    /**
2262
     *  Return id des contacts clients pour le suivi ticket
2263
     *
2264
     *  @return array       Liste des id contacts suivi ticket
2265
     */
2266
    public function getIdTicketCustomerContact()
2267
    {
2268
        return $this->getIdContact('external', 'SUPPORTCLI');
2269
    }
2270
2271
    /**
2272
     * Retrieve information about external contacts
2273
     *
2274
     *  @param    int     $status     Status of user or company
2275
     *  @return array                 Array with datas : firstname, lastname, socid (-1 for internal users), email, code, libelle, status
2276
     */
2277
    public function getInfosTicketExternalContact($status = -1)
2278
    {
2279
        return $this->listeContact(-1, 'external', 0, '', $status);
2280
    }
2281
2282
    /**
2283
     *  Return id des contacts clients des intervenants
2284
     *
2285
     *  @return array       Liste des id contacts intervenants
2286
     */
2287
    public function getIdTicketInternalInvolvedContact()
2288
    {
2289
        return $this->getIdContact('internal', 'CONTRIBUTOR');
2290
    }
2291
2292
    /**
2293
     *  Return id des contacts clients des intervenants
2294
     *
2295
     *  @return array       Liste des id contacts intervenants
2296
     */
2297
    public function getIdTicketCustomerInvolvedContact()
2298
    {
2299
        return $this->getIdContact('external', 'CONTRIBUTOR');
2300
    }
2301
2302
    /**
2303
     * Return id of all contacts for ticket
2304
     *
2305
     * @return  array       Array of contacts for tickets
2306
     */
2307
    public function getTicketAllContacts()
2308
    {
2309
        $array_contact = array();
2310
2311
        $array_contact = $this->getIdTicketInternalContact();
2312
2313
        $array_contact = array_merge($array_contact, $this->getIdTicketCustomerContact());
2314
2315
        $array_contact = array_merge($array_contact, $this->getIdTicketInternalInvolvedContact());
2316
2317
        $array_contact = array_merge($array_contact, $this->getIdTicketCustomerInvolvedContact());
2318
2319
        return $array_contact;
2320
    }
2321
2322
    /**
2323
     * Return id of all contacts for ticket
2324
     *
2325
     * @return  array       Array of contacts
2326
     */
2327
    public function getTicketAllCustomerContacts()
2328
    {
2329
        $array_contact = array();
2330
2331
        $array_contact = array_merge($array_contact, $this->getIdTicketCustomerContact());
2332
2333
        $array_contact = array_merge($array_contact, $this->getIdTicketCustomerInvolvedContact());
2334
2335
        return $array_contact;
2336
    }
2337
2338
2339
    /**
2340
     *    Get array of all contacts for a ticket
2341
     *    Override method of file commonobject.class.php to add phone number
2342
     *
2343
     *    @param    int     $statusoflink   Status of lines to get (-1=all)
2344
     *    @param    string  $source         Source of contact: external or thirdparty (llx_socpeople) or internal (llx_user)
2345
     *    @param    int     $list           0:Return array contains all properties, 1:Return array contains just id
2346
     *    @param    string  $code           Filter on this code of contact type ('SHIPPING', 'BILLING', ...)
2347
     *    @param    int     $status         Status of user or company
2348
     *    @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'=>...)
2349
     */
2350
    public function listeContact($statusoflink = -1, $source = 'external', $list = 0, $code = '', $status = -1)
2351
    {
2352
        global $langs;
2353
2354
        $tab = array();
2355
2356
        $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
2357
        if ($source == 'internal') {
2358
            $sql .= ", '-1' as socid, t.statut as statuscontact";
2359
        }
2360
2361
        if ($source == 'external' || $source == 'thirdparty') {
2362
            $sql .= ", t.fk_soc as socid, t.statut as statuscontact";
2363
        }
2364
2365
        $sql .= ", t.civility, t.lastname as lastname, t.firstname, t.email";
2366
        if ($source == 'internal') {
2367
            $sql .= ", t.office_phone as phone, t.user_mobile as phone_mobile";
2368
        }
2369
2370
        if ($source == 'external') {
2371
            $sql .= ", t.phone as phone, t.phone_mobile as phone_mobile, t.phone_perso as phone_perso";
2372
        }
2373
2374
        $sql .= ", tc.source, tc.element, tc.code, tc.libelle as type_contact_label";
2375
        $sql .= " FROM " . MAIN_DB_PREFIX . "c_type_contact tc";
2376
        $sql .= ", " . MAIN_DB_PREFIX . "element_contact ec";
2377
        if ($source == 'internal') {
2378
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "user t on ec.fk_socpeople = t.rowid";
2379
        }
2380
2381
        if ($source == 'external' || $source == 'thirdparty') {
2382
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "socpeople t on ec.fk_socpeople = t.rowid";
2383
        }
2384
2385
        $sql .= " WHERE ec.element_id = " . ((int) $this->id);
2386
        $sql .= " AND ec.fk_c_type_contact=tc.rowid";
2387
        $sql .= " AND tc.element='" . $this->db->escape($this->element) . "'";
2388
        if ($source == 'internal') {
2389
            $sql .= " AND tc.source = 'internal'";
2390
            if ($status >= 0) {
2391
                $sql .= " AND t.statut = " . ((int) $status);
2392
            }
2393
        }
2394
2395
        if ($source == 'external' || $source == 'thirdparty') {
2396
            $sql .= " AND tc.source = 'external'";
2397
            if ($status >= 0) {
2398
                $sql .= " AND t.statut = " . ((int) $status);
2399
            }
2400
        }
2401
2402
        if (!empty($code)) {
2403
            $sql .= " AND tc.code = '" . $this->db->escape($code) . "'";
2404
        }
2405
2406
        $sql .= " AND tc.active=1";
2407
        if ($statusoflink >= 0) {
2408
            $sql .= " AND ec.statut = " . ((int) $statusoflink);
2409
        }
2410
2411
        $sql .= " ORDER BY t.lastname ASC";
2412
2413
        $resql = $this->db->query($sql);
2414
        if ($resql) {
2415
            $num = $this->db->num_rows($resql);
2416
            $i = 0;
2417
            while ($i < $num) {
2418
                $obj = $this->db->fetch_object($resql);
2419
2420
                if (!$list) {
2421
                    $transkey = "TypeContact_" . $obj->element . "_" . $obj->source . "_" . $obj->code;
2422
                    $labelType = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_contact_label);
2423
                    $tab[$i] = array(
2424
                        'source' => $obj->source,
2425
                        'socid' => $obj->socid,
2426
                        'id' => $obj->id,
2427
                        'nom' => $obj->lastname, // For backward compatibility
2428
                        'civility' => $obj->civility,
2429
                        'lastname' => $obj->lastname,
2430
                        'firstname' => $obj->firstname,
2431
                        'email' => $obj->email,
2432
                        'rowid' => $obj->rowid,
2433
                        'code' => $obj->code,
2434
                        'libelle' => $labelType,        // deprecated, replaced with labeltype
2435
                        'labeltype' => $labelType,
2436
                        'status' => $obj->statuslink,
2437
                        'statuscontact' => $obj->statuscontact,
2438
                        'fk_c_type_contact' => $obj->fk_c_type_contact,
2439
                        'phone' => $obj->phone,
2440
                        'phone_mobile' => $obj->phone_mobile);
2441
                } else {
2442
                    $tab[$i] = $obj->id;
2443
                }
2444
2445
                $i++;
2446
            }
2447
2448
            return $tab;
2449
        } else {
2450
            $this->error = $this->db->error();
2451
            dol_print_error($this->db);
2452
            return -1;
2453
        }
2454
    }
2455
2456
    /**
2457
     * Get a default reference.
2458
     *
2459
     * @param   Societe     $thirdparty     Thirdparty
2460
     * @return  string                      Reference
2461
     */
2462
    public function getDefaultRef($thirdparty = null)
2463
    {
2464
        global $conf;
2465
2466
        $defaultref = '';
2467
        $modele = getDolGlobalString('TICKET_ADDON', 'mod_ticket_simple');
2468
2469
        // Search template files
2470
        $file = '';
2471
        $classname = '';
2472
        $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
2473
        foreach ($dirmodels as $reldir) {
2474
            $file = dol_buildpath($reldir . "core/modules/ticket/" . $modele . '.php', 0);
2475
            if (file_exists($file)) {
2476
                $classname = $modele;
2477
                break;
2478
            }
2479
        }
2480
2481
        if ($classname !== '') {
2482
            $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 2473. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
2483
            $modTicket = new $classname();
2484
2485
            $defaultref = $modTicket->getNextValue($thirdparty, $this);
2486
        }
2487
2488
        if (is_numeric($defaultref) && $defaultref <= 0) {
2489
            $defaultref = '';
2490
        }
2491
2492
        return $defaultref;
2493
    }
2494
2495
2496
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2497
    /**
2498
     *  Return if at least one photo is available
2499
     *
2500
     *  @param      string      $sdir       Directory to scan
2501
     *  @return     boolean                 True if at least one photo is available, False if not
2502
     */
2503
    public function is_photo_available($sdir)
2504
    {
2505
		// phpcs:enable
2506
        include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
2507
2508
        global $conf;
2509
2510
        $dir = $sdir . '/';
2511
        $nbphoto = 0;
2512
2513
        $dir_osencoded = dol_osencode($dir);
2514
        if (file_exists($dir_osencoded)) {
2515
            $handle = opendir($dir_osencoded);
2516
            if (is_resource($handle)) {
2517
                while (($file = readdir($handle)) !== false) {
2518
                    if (!utf8_check($file)) {
2519
                        $file = mb_convert_encoding($file, 'UTF-8', 'ISO-8859-1');  // To be sure data is stored in UTF8 in memory
2520
                    }
2521
                    if (dol_is_file($dir . $file)) {
2522
                        return true;
2523
                    }
2524
                }
2525
            }
2526
        }
2527
        return false;
2528
    }
2529
2530
2531
    /**
2532
     * Copy files defined into $_SESSION array into the ticket directory of attached files.
2533
     * Used for files linked into messages.
2534
     * Files may be renamed during copy to avoid overwriting existing files.
2535
     *
2536
     * @param   string      $forcetrackid   Force trackid used for $keytoavoidconflict into get_attached_files()
2537
     * @return  array|int                   Array with final path/name/mime of files.
2538
     */
2539
    public function copyFilesForTicket($forcetrackid = null)
2540
    {
2541
        global $conf;
2542
2543
        // Create form object
2544
        include_once DOL_DOCUMENT_ROOT . '/core/class/html.formmail.class.php';
2545
        include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
2546
        include_once DOL_DOCUMENT_ROOT . '/core/lib/images.lib.php';
2547
2548
        $maxwidthsmall = 270;
2549
        $maxheightsmall = 150;
2550
        $maxwidthmini = 128;
2551
        $maxheightmini = 72;
2552
2553
        $formmail = new FormMail($this->db);
2554
        $formmail->trackid = (is_null($forcetrackid) ? 'tic' . $this->id : '');
2555
        $attachedfiles = $formmail->get_attached_files();
2556
2557
        $filepath = $attachedfiles['paths'];    // path is for example user->dir_temp.'/'.$user->id.'/'...
2558
        $filename = $attachedfiles['names'];
2559
        $mimetype = $attachedfiles['mimes'];
2560
2561
        // Copy files into ticket directory
2562
        $destdir = $conf->ticket->dir_output . '/' . $this->ref;
2563
2564
        if (!dol_is_dir($destdir)) {
2565
            dol_mkdir($destdir);
2566
        }
2567
2568
        $listofpaths = array();
2569
        $listofnames = array();
2570
        foreach ($filename as $i => $val) {
2571
            $destfile = $destdir . '/' . $filename[$i];
2572
            // If destination file already exists, we add a suffix to avoid to overwrite
2573
            if (is_file($destfile)) {
2574
                $pathinfo = pathinfo($filename[$i]);
2575
                $now = dol_now();
2576
                $destfile = $destdir . '/' . $pathinfo['filename'] . ' - ' . dol_print_date($now, 'dayhourlog') . '.' . $pathinfo['extension'];
2577
            }
2578
2579
            $moreinfo = array('description' => 'File saved by copyFilesForTicket', 'src_object_type' => $this->element, 'src_object_id' => $this->id);
2580
            $res = dol_move($filepath[$i], $destfile, 0, 1, 0, 1, $moreinfo);
2581
            if (!$res) {
2582
                // Move has failed
2583
                $this->error = "Failed to move file " . dirbasename($filepath[$i]) . " into " . dirbasename($destfile);
2584
                return -1;
2585
            } else {
2586
                // If file is an image, we create thumbs
2587
                if (image_format_supported($destfile) == 1) {
2588
                    // Create small thumbs for image (Ratio is near 16/9)
2589
                    // Used on logon for example
2590
                    $imgThumbSmall = vignette($destfile, $maxwidthsmall, $maxheightsmall, '_small', 50, "thumbs");
2591
                    // Create mini thumbs for image (Ratio is near 16/9)
2592
                    // Used on menu or for setup page for example
2593
                    $imgThumbMini = vignette($destfile, $maxwidthmini, $maxheightmini, '_mini', 50, "thumbs");
2594
                }
2595
            }
2596
2597
            // Clear variables into session
2598
            $formmail->remove_attached_files($i);
2599
2600
            // Fill array with new names
2601
            $listofpaths[$i] = $destfile;
2602
            $listofnames[$i] = basename($destfile);
2603
        }
2604
2605
        return array('listofpaths' => $listofpaths, 'listofnames' => $listofnames, 'listofmimes' => $mimetype);
2606
    }
2607
2608
    /**
2609
     * Sets object to supplied categories.
2610
     *
2611
     * Deletes object from existing categories not supplied.
2612
     * Adds it to non existing supplied categories.
2613
     * Existing categories are left untouch.
2614
     *
2615
     * @param  int[]|int    $categories     Category or categories IDs
2616
     * @return int                          Return integer <0 if KO, >0 if OK
2617
     */
2618
    public function setCategories($categories)
2619
    {
2620
        // Handle single category
2621
        if (!is_array($categories)) {
2622
            $categories = array($categories);
2623
        }
2624
2625
        // Get current categories
2626
        include_once DOL_DOCUMENT_ROOT . '/categories/class/categorie.class.php';
2627
        $c = new Categorie($this->db);
2628
        $existing = $c->containing($this->id, Categorie::TYPE_TICKET, 'id');
2629
2630
        // Diff
2631
        if (is_array($existing)) {
2632
            $to_del = array_diff($existing, $categories);
2633
            $to_add = array_diff($categories, $existing);
2634
        } else {
2635
            $to_del = array(); // Nothing to delete
2636
            $to_add = $categories;
2637
        }
2638
2639
        // Process
2640
        foreach ($to_del as $del) {
2641
            if ($c->fetch($del) > 0) {
2642
                $c->del_type($this, Categorie::TYPE_TICKET);
2643
            }
2644
        }
2645
        foreach ($to_add as $add) {
2646
            if ($c->fetch($add) > 0) {
2647
                $c->add_type($this, Categorie::TYPE_TICKET);
2648
            }
2649
        }
2650
2651
        return 1;
2652
    }
2653
2654
    /**
2655
     * Add new message on a ticket (private/public area).
2656
     * Can also send it by email if GETPOST('send_email', 'int') is set. For such email, header and footer is added.
2657
     *
2658
     * @param   User    $user           User for action
2659
     * @param   string  $action         Action string
2660
     * @param   int     $private        1=Message is private (must not be visible by external users)
2661
     * @param   int     $public_area    0=Default,
2662
     *                                  1=If we are creating the message from a public area, so confirmation email will be sent to the author
2663
     *                                  and we can search contact from email to add it as contact of ticket if TICKET_ASSIGN_CONTACT_TO_MESSAGE is set
2664
     * @return  int                     Return integer <0 if KO, >= 0 if OK
2665
     */
2666
    public function newMessage($user, &$action, $private = 1, $public_area = 0)
2667
    {
2668
        global $mysoc, $conf, $langs;
2669
2670
        $error = 0;
2671
2672
        $object = new Ticket($this->db);
2673
2674
        $ret = $object->fetch('', '', GETPOST('track_id', 'alpha'));
2675
2676
        $object->socid = $object->fk_soc;
2677
        $object->fetch_thirdparty();
2678
        $object->fetch_project();
2679
2680
        if ($ret < 0) {
2681
            $error++;
2682
            array_push($this->errors, $langs->trans("ErrorTicketIsNotValid"));
2683
            $action = '';
2684
        }
2685
2686
        if (!GETPOST("message")) {
2687
            $error++;
2688
            array_push($this->errors, $langs->trans("ErrorFieldRequired", $langs->transnoentities("Message")));
2689
            $action = 'add_message';
2690
        }
2691
2692
        if (!$error) {
2693
            $object->subject = GETPOST('subject', 'alphanohtml');
2694
            $object->message = GETPOST("message", "restricthtml");
2695
            $object->private = GETPOST("private_message", "alpha");
2696
2697
            $send_email = GETPOSTINT('send_email');
2698
2699
            // Copy attached files (saved into $_SESSION) as linked files to ticket. Return array with final name used.
2700
            $resarray = $object->copyFilesForTicket();
2701
            if (is_numeric($resarray) && $resarray == -1) {
2702
                setEventMessages($object->error, $object->errors, 'errors');
2703
                return -1;
2704
            }
2705
2706
            $listofpaths = $resarray['listofpaths'];
2707
            $listofnames = $resarray['listofnames'];
2708
            $listofmimes = $resarray['listofmimes'];
2709
2710
            $id = $object->createTicketMessage($user, 0, $listofpaths, $listofmimes, $listofnames, $send_email, $public_area);
2711
            if ($id <= 0) {
2712
                $error++;
2713
                $this->error = $object->error;
2714
                $this->errors = $object->errors;
2715
                $action = 'add_message';
2716
            }
2717
2718
            if (!$error && $id > 0) {
2719
                setEventMessages($langs->trans('TicketMessageSuccessfullyAdded'), null, 'mesgs');
2720
2721
                if (!empty($public_area)) {
2722
                    /*
2723
                     * Message created from the Public interface
2724
                     *
2725
                     * Send emails to assigned users (public area notification)
2726
                     */
2727
                    if (getDolGlobalString('TICKET_PUBLIC_NOTIFICATION_NEW_MESSAGE_ENABLED')) {
2728
                        // Retrieve internal contact datas
2729
                        $internal_contacts = $object->getInfosTicketInternalContact(1);
2730
2731
                        $assigned_user_dont_have_email = '';
2732
2733
                        $sendto = array();
2734
2735
                        if ($this->fk_user_assign > 0) {
2736
                            $assigned_user = new User($this->db);
2737
                            $assigned_user->fetch($this->fk_user_assign);
2738
                            if (!empty($assigned_user->email)) {
2739
                                $sendto[$assigned_user->email] = $assigned_user->getFullName($langs) . " <" . $assigned_user->email . ">";
2740
                            } else {
2741
                                $assigned_user_dont_have_email = $assigned_user->getFullName($langs);
2742
                            }
2743
                        }
2744
2745
                        // Build array to display recipient list
2746
                        foreach ($internal_contacts as $key => $info_sendto) {
2747
                            // Avoid duplicate notifications
2748
                            if ($info_sendto['id'] == $user->id) {
2749
                                continue;
2750
                            }
2751
2752
                            // We check if the email address is not the assignee's address to prevent notification from being sent twice
2753
                            if (!empty($info_sendto['email']) && $assigned_user->email != $info_sendto['email']) {
2754
                                $sendto[] = dolGetFirstLastname($info_sendto['firstname'], $info_sendto['lastname']) . " <" . $info_sendto['email'] . ">";
2755
                            }
2756
                        }
2757
2758
                        if (empty($sendto)) {
2759
                            if (getDolGlobalString('TICKET_PUBLIC_NOTIFICATION_NEW_MESSAGE_DEFAULT_EMAIL')) {
2760
                                $sendto[getDolGlobalString('TICKET_PUBLIC_NOTIFICATION_NEW_MESSAGE_DEFAULT_EMAIL')] = getDolGlobalString('TICKET_PUBLIC_NOTIFICATION_NEW_MESSAGE_DEFAULT_EMAIL');
2761
                            } elseif (getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO')) {
2762
                                $sendto[getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO')] = getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO');
2763
                            }
2764
                        }
2765
2766
                        // Add global email address recipient
2767
                        if (
2768
                            getDolGlobalString('TICKET_NOTIFICATION_ALSO_MAIN_ADDRESS') &&
2769
                            getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO') && !array_key_exists(getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO'), $sendto)
2770
                        ) {
2771
                            $sendto[getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO')] = getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO');
2772
                        }
2773
2774
                        if (!empty($sendto)) {
2775
                            $appli = getDolGlobalString('MAIN_APPLICATION_TITLE', $mysoc->name);
2776
2777
                            $subject = '[' . $appli . '- ticket #' . $object->track_id . '] ' . $this->subject;
2778
2779
                            // Message send
2780
                            $message = $langs->trans('TicketMessageMailIntroText');
2781
                            $message .= '<br><br>';
2782
                            $messagePost = GETPOST('message', 'restricthtml');
2783
                            if (!dol_textishtml($messagePost)) {
2784
                                $messagePost = dol_nl2br($messagePost);
2785
                            }
2786
                            $message .= $messagePost;
2787
2788
                            // Customer company infos
2789
                            $message .= '<br><br>';
2790
                            $message .= "==============================================";
2791
                            $message .= !empty($object->thirdparty->name) ? '<br>' . $langs->trans('Thirdparty') . " : " . $object->thirdparty->name : '';
2792
                            $message .= !empty($object->thirdparty->town) ? '<br>' . $langs->trans('Town') . " : " . $object->thirdparty->town : '';
2793
                            $message .= !empty($object->thirdparty->phone) ? '<br>' . $langs->trans('Phone') . " : " . $object->thirdparty->phone : '';
2794
2795
                            // Email send to
2796
                            $message .= '<br><br>';
2797
                            if (!empty($assigned_user_dont_have_email)) {
2798
                                $message .= '<br>' . $langs->trans('NoEMail') . ' : ' . $assigned_user_dont_have_email;
2799
                            }
2800
                            foreach ($sendto as $val) {
2801
                                $message .= '<br>' . $langs->trans('TicketNotificationRecipient') . ' : ' . $val;
2802
                            }
2803
2804
                            // URL ticket
2805
                            $url_internal_ticket = dol_buildpath('/ticket/card.php', 2) . '?track_id=' . $object->track_id;
2806
                            $message .= '<br><br>';
2807
                            $message .= $langs->trans('TicketNotificationEmailBodyInfosTrackUrlinternal') . ' : <a href="' . $url_internal_ticket . '">' . $object->track_id . '</a>';
2808
2809
                            $this->sendTicketMessageByEmail($subject, $message, '', $sendto, $listofpaths, $listofmimes, $listofnames);
2810
                        }
2811
                    }
2812
                } else {
2813
                    /*
2814
                     * Message send from the Backoffice / Private area
2815
                     *
2816
                     * 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)
2817
                     */
2818
                    if ($send_email > 0) {
2819
                        // Retrieve internal contact datas
2820
                        $internal_contacts = $object->getInfosTicketInternalContact(1);
2821
2822
                        $sendto = array();
2823
                        if (is_array($internal_contacts) && count($internal_contacts) > 0) {
2824
                            // Set default subject
2825
                            $appli = getDolGlobalString('MAIN_APPLICATION_TITLE', $mysoc->name);
2826
2827
                            $subject = GETPOST('subject', 'alphanohtml') ? GETPOST('subject', 'alphanohtml') : '[' . $appli . ' - ' . $langs->trans("Ticket") . ' #' . $object->track_id . '] ' . $langs->trans('TicketNewMessage');
2828
2829
                            $message_intro = $langs->trans('TicketNotificationEmailBody', "#" . $object->id);
2830
                            $message_signature = GETPOST('mail_signature') ? GETPOST('mail_signature') : getDolGlobalString('TICKET_MESSAGE_MAIL_SIGNATURE');
2831
2832
                            $message = $langs->trans('TicketMessageMailIntroText');
2833
                            $message .= '<br><br>';
2834
                            $messagePost = GETPOST('message', 'restricthtml');
2835
                            if (!dol_textishtml($messagePost)) {
2836
                                $messagePost = dol_nl2br($messagePost);
2837
                            }
2838
                            $message .= $messagePost;
2839
2840
                            // Data about customer
2841
                            $message .= '<br><br>';
2842
                            $message .= "==============================================<br>";
2843
                            $message .= !empty($object->thirdparty->name) ? $langs->trans('Thirdparty') . " : " . $object->thirdparty->name : '';
2844
                            $message .= !empty($object->thirdparty->town) ? '<br>' . $langs->trans('Town') . " : " . $object->thirdparty->town : '';
2845
                            $message .= !empty($object->thirdparty->phone) ? '<br>' . $langs->trans('Phone') . " : " . $object->thirdparty->phone : '';
2846
2847
                            // Build array to display recipient list
2848
                            foreach ($internal_contacts as $key => $info_sendto) {
2849
                                // Avoid duplicate notifications
2850
                                if ($info_sendto['id'] == $user->id) {
2851
                                    continue;
2852
                                }
2853
2854
                                if ($info_sendto['email'] != '') {
2855
                                    if (!empty($info_sendto['email'])) {
2856
                                        $sendto[$info_sendto['email']] = dolGetFirstLastname($info_sendto['firstname'], $info_sendto['lastname']) . " <" . $info_sendto['email'] . ">";
2857
                                    }
2858
2859
                                    // Contact type
2860
                                    $recipient = dolGetFirstLastname($info_sendto['firstname'], $info_sendto['lastname'], '-1') . ' (' . strtolower($info_sendto['libelle']) . ')';
2861
                                    $message .= (!empty($recipient) ? $langs->trans('TicketNotificationRecipient') . ' : ' . $recipient . '<br>' : '');
2862
                                }
2863
                            }
2864
                            $message .= '<br>';
2865
                            // URL ticket
2866
                            $url_internal_ticket = dol_buildpath('/ticket/card.php', 2) . '?track_id=' . $object->track_id;
2867
2868
                            // Add html link on url
2869
                            $message .= '<br>' . $langs->trans('TicketNotificationEmailBodyInfosTrackUrlinternal') . ' : <a href="' . $url_internal_ticket . '">' . $object->track_id . '</a><br>';
2870
2871
                            // Add global email address recipient
2872
                            if (getDolGlobalString('TICKET_NOTIFICATION_ALSO_MAIN_ADDRESS') && !array_key_exists(getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO'), $sendto)) {
2873
                                if (getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO')) {
2874
                                    $sendto[getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO')] = getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO');
2875
                                }
2876
                            }
2877
2878
                            // don't try to send email if no recipient
2879
                            if (!empty($sendto)) {
2880
                                $this->sendTicketMessageByEmail($subject, $message, '', $sendto, $listofpaths, $listofmimes, $listofnames);
2881
                            }
2882
                        }
2883
2884
                        /*
2885
                         * Send emails for externals users if not private (linked contacts)
2886
                         */
2887
                        if (empty($object->private)) {
2888
                            // Retrieve email of all contacts (external)
2889
                            $external_contacts = $object->getInfosTicketExternalContact(1);
2890
2891
                            // If no contact, get email from thirdparty
2892
                            if (is_array($external_contacts) && count($external_contacts) === 0) {
2893
                                if (!empty($object->fk_soc)) {
2894
                                    $object->fetch_thirdparty($object->fk_soc);
2895
                                    $array_company = array(array('firstname' => '', 'lastname' => $object->thirdparty->name, 'email' => $object->thirdparty->email, 'libelle' => $langs->transnoentities('Customer'), 'socid' => $object->thirdparty->id));
2896
                                    $external_contacts = array_merge($external_contacts, $array_company);
2897
                                } elseif (empty($object->fk_soc) && !empty($object->origin_replyto)) {
2898
                                    $array_external = array(array('firstname' => '', 'lastname' => $object->origin_replyto, 'email' => $object->origin_replyto, 'libelle' => $langs->transnoentities('Customer'), 'socid' => 0));
2899
                                    $external_contacts = array_merge($external_contacts, $array_external);
2900
                                } elseif (empty($object->fk_soc) && !empty($object->origin_email)) {
2901
                                    $array_external = array(array('firstname' => '', 'lastname' => $object->origin_email, 'email' => $object->thirdparty->email, 'libelle' => $langs->transnoentities('Customer'), 'socid' => $object->thirdparty->id));
2902
                                    $external_contacts = array_merge($external_contacts, $array_external);
2903
                                }
2904
                            }
2905
2906
                            $sendto = array();
2907
                            if (is_array($external_contacts) && count($external_contacts) > 0) {
2908
                                // Get default subject for email to external contacts
2909
                                $appli = getDolGlobalString('MAIN_APPLICATION_TITLE', $mysoc->name);
2910
2911
                                $subject = GETPOST('subject') ? GETPOST('subject') : '[' . $appli . ' - ' . $langs->trans("Ticket") . ' #' . $object->track_id . '] ' . $langs->trans('TicketNewMessage');
2912
2913
                                $message_intro = GETPOST('mail_intro') ? GETPOST('mail_intro', 'restricthtml') : getDolGlobalString('TICKET_MESSAGE_MAIL_INTRO');
2914
                                $message_signature = GETPOST('mail_signature') ? GETPOST('mail_signature', 'restricthtml') : getDolGlobalString('TICKET_MESSAGE_MAIL_SIGNATURE');
2915
                                if (!dol_textishtml($message_intro)) {
2916
                                    $message_intro = dol_nl2br($message_intro);
2917
                                }
2918
                                if (!dol_textishtml($message_signature)) {
2919
                                    $message_signature = dol_nl2br($message_signature);
2920
                                }
2921
2922
                                // We put intro after
2923
                                $messagePost = GETPOST('message', 'restricthtml');
2924
                                if (!dol_textishtml($messagePost)) {
2925
                                    $messagePost = dol_nl2br($messagePost);
2926
                                }
2927
                                $message = $messagePost;
2928
                                $message .= '<br><br>';
2929
2930
                                foreach ($external_contacts as $key => $info_sendto) {
2931
                                    // avoid duplicate emails to external contacts
2932
                                    if ($info_sendto['id'] == $user->contact_id) {
2933
                                        continue;
2934
                                    }
2935
2936
                                    if ($info_sendto['email'] != '' && $info_sendto['email'] != $object->origin_email) {
2937
                                        if (!empty($info_sendto['email'])) {
2938
                                            $sendto[$info_sendto['email']] = trim($info_sendto['firstname'] . " " . $info_sendto['lastname']) . " <" . $info_sendto['email'] . ">";
2939
                                        }
2940
2941
                                        $recipient = dolGetFirstLastname($info_sendto['firstname'], $info_sendto['lastname'], '-1') . ' (' . strtolower($info_sendto['libelle']) . ')';
2942
                                        $message .= (!empty($recipient) ? $langs->trans('TicketNotificationRecipient') . ' : ' . $recipient . '<br>' : '');
2943
                                    }
2944
                                }
2945
2946
                                // If public interface is not enable, use link to internal page into mail
2947
                                $url_public_ticket = (getDolGlobalInt('TICKET_ENABLE_PUBLIC_INTERFACE') ?
2948
                                        (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;
2949
2950
                                $message .= '<br>' . $langs->trans('TicketNewEmailBodyInfosTrackUrlCustomer') . ' : <a href="' . $url_public_ticket . '">' . $object->track_id . '</a><br>';
2951
2952
                                // Build final message
2953
                                $message = $message_intro . '<br><br>' . $message;
2954
2955
                                // Add signature
2956
                                $message .= '<br>' . $message_signature;
2957
2958
                                if (!empty($object->origin_replyto)) {
2959
                                    $sendto[$object->origin_replyto] = $object->origin_replyto;
2960
                                } elseif (!empty($object->origin_email)) {
2961
                                    $sendto[$object->origin_email] = $object->origin_email;
2962
                                }
2963
2964
                                if ($object->fk_soc > 0 && !array_key_exists($object->origin_replyto, $sendto) && !array_key_exists($object->origin_email, $sendto)) {
2965
                                    $object->socid = $object->fk_soc;
2966
                                    $object->fetch_thirdparty();
2967
                                    if (!empty($object->thirdparty->email)) {
2968
                                        $sendto[$object->thirdparty->email] = $object->thirdparty->email;
2969
                                    }
2970
                                }
2971
2972
                                // Add global email address recipient
2973
                                if (getDolGlobalString('TICKET_NOTIFICATION_ALSO_MAIN_ADDRESS') && !array_key_exists(getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO'), $sendto)) {
2974
                                    if (getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO')) {
2975
                                        $sendto[getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO')] = getDolGlobalString('TICKET_NOTIFICATION_EMAIL_TO');
2976
                                    }
2977
                                }
2978
2979
                                // Don't try to send email when no recipient
2980
                                if (!empty($sendto)) {
2981
                                    $result = $this->sendTicketMessageByEmail($subject, $message, '', $sendto, $listofpaths, $listofmimes, $listofnames);
2982
                                    if ($result) {
2983
                                        // update last_msg_sent date (for last message sent to external users)
2984
                                        $this->date_last_msg_sent = dol_now();
2985
                                        $this->update($user, 1);    // disable trigger when updating date_last_msg_sent. sendTicketMessageByEmail already create an event in actioncomm table.
2986
                                    }
2987
                                }
2988
                            }
2989
                        }
2990
                    }
2991
                }
2992
2993
                // Set status back to "In progress" if not set yet, but only if internal user and not a private message
2994
                // Or set status to "In progress" if the client has answered and if the ticket has started
2995
                // So we are sure to leave the STATUS_DRAFT, STATUS_NEED_INFO.
2996
                if (
2997
                    ($object->status < self::STATUS_IN_PROGRESS && !$user->socid && !$private) ||
2998
                    ($object->status > self::STATUS_IN_PROGRESS && $public_area)
2999
                ) {
3000
                    $object->setStatut($object::STATUS_IN_PROGRESS);
3001
                }
3002
                return 1;
3003
            } else {
3004
                setEventMessages($object->error, $object->errors, 'errors');
3005
                return -1;
3006
            }
3007
        } else {
3008
            setEventMessages($this->error, $this->errors, 'errors');
3009
            return -1;
3010
        }
3011
    }
3012
3013
3014
    /**
3015
     * Send ticket by email to linked contacts
3016
     *
3017
     * @param string $subject             Email subject
3018
     * @param string $message             Email message
3019
     * @param int    $send_internal_cc    Receive a copy on internal email (getDolGlobalString('TICKET_NOTIFICATION_EMAIL_FROM')
3020
     * @param array  $array_receiver      Array of receiver. Example array('name' => 'John Doe', 'email' => '[email protected]', etc...)
3021
     * @param array  $filename_list       List of files to attach (full path of filename on file system)
3022
     * @param array  $mimetype_list       List of MIME type of attached files
3023
     * @param array  $mimefilename_list   List of attached file name in message
3024
     * @return boolean                      True if mail sent to at least one receiver, false otherwise
3025
     */
3026
    public function sendTicketMessageByEmail($subject, $message, $send_internal_cc = 0, $array_receiver = array(), $filename_list = array(), $mimetype_list = array(), $mimefilename_list = array())
3027
    {
3028
        global $conf, $langs, $user;
3029
3030
        if (getDolGlobalString('TICKET_DISABLE_ALL_MAILS')) {
3031
            dol_syslog(get_class($this) . '::sendTicketMessageByEmail: Emails are disable into ticket setup by option TICKET_DISABLE_ALL_MAILS', LOG_WARNING);
3032
            return false;
3033
        }
3034
3035
        $langs->load("mails");
3036
3037
        include_once DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php';
3038
        //$contactstatic = new Contact($this->db);
3039
3040
        // If no receiver defined, load all ticket linked contacts
3041
        if (!is_array($array_receiver) || !count($array_receiver) > 0) {
3042
            $array_receiver = $this->getInfosTicketInternalContact(1);
3043
            $array_receiver = array_merge($array_receiver, $this->getInfosTicketExternalContact(1));
3044
        }
3045
3046
        $sendtocc = '';
3047
        if ($send_internal_cc) {
3048
            $sendtocc = getDolGlobalString('TICKET_NOTIFICATION_EMAIL_FROM');
3049
        }
3050
3051
        $from = getDolGlobalString('TICKET_NOTIFICATION_EMAIL_FROM');
3052
        $is_sent = false;
3053
        if (is_array($array_receiver) && count($array_receiver) > 0) {
3054
            foreach ($array_receiver as $key => $receiver) {
3055
                $deliveryreceipt = 0;
3056
                $filepath = $filename_list;
3057
                $filename = $mimefilename_list;
3058
                $mimetype = $mimetype_list;
3059
3060
                // Send email
3061
3062
                $old_MAIN_MAIL_AUTOCOPY_TO = getDolGlobalString('MAIN_MAIL_AUTOCOPY_TO');
3063
                if (getDolGlobalString('TICKET_DISABLE_MAIL_AUTOCOPY_TO')) {
3064
                    $conf->global->MAIN_MAIL_AUTOCOPY_TO = '';
3065
                }
3066
3067
                $upload_dir_tmp = $conf->user->dir_output . "/" . $user->id . '/temp';
3068
3069
                include_once DOL_DOCUMENT_ROOT . '/core/class/CMailFile.class.php';
3070
                $trackid = "tic" . $this->id;
3071
3072
                $moreinheader = 'X-Dolibarr-Info: sendTicketMessageByEmail' . "\r\n";
3073
                if (!empty($this->email_msgid)) {
3074
                    // We must also add 1 entry In-Reply-To: <$this->email_msgid> with Message-ID we respond from (See RFC5322).
3075
                    $moreinheader .= 'In-Reply-To: <' . $this->email_msgid . '>' . "\r\n";
3076
                    // TODO We should now be able to give the in_reply_to as a dedicated parameter of new CMailFile() instead of into $moreinheader.
3077
                }
3078
3079
                // We should add here also a header 'References:'
3080
                // According to RFC5322, we should add here all the References fields of the initial message concatenated with
3081
                // the Message-ID of the message we respond from (but each ID must be once).
3082
                $references = '';
3083
                if (!empty($this->origin_references)) {     // $this->origin_references should be '<'.$this->origin_references.'>'
3084
                    $references .= (empty($references) ? '' : ' ') . $this->origin_references;
3085
                }
3086
                if (!empty($this->email_msgid) && !preg_match('/' . preg_quote($this->email_msgid, '/') . '/', $references)) {
3087
                    $references .= (empty($references) ? '' : ' ') . '<' . $this->email_msgid . '>';
3088
                }
3089
                if ($references) {
3090
                    $moreinheader .= 'References: ' . $references . "\r\n";
3091
                    // TODO We should now be able to give the references as a dedicated parameter of new CMailFile() instead of into $moreinheader.
3092
                }
3093
3094
                $mailfile = new CMailFile($subject, $receiver, $from, $message, $filepath, $mimetype, $filename, $sendtocc, '', $deliveryreceipt, -1, '', '', $trackid, $moreinheader, 'ticket', '', $upload_dir_tmp);
3095
3096
                if ($mailfile->error) {
3097
                    setEventMessages($mailfile->error, null, 'errors');
3098
                } else {
3099
                    $result = $mailfile->sendfile();
3100
                    if ($result) {
3101
                        setEventMessages($langs->trans('MailSuccessfulySent', $mailfile->getValidAddress($from, 2), $mailfile->getValidAddress($receiver, 2)), null, 'mesgs');
3102
                        $is_sent = true;
3103
                    } else {
3104
                        $langs->load("other");
3105
                        if ($mailfile->error) {
3106
                            setEventMessages($langs->trans('ErrorFailedToSendMail', $from, $receiver), null, 'errors');
3107
                            dol_syslog($langs->trans('ErrorFailedToSendMail', $from, $receiver) . ' : ' . $mailfile->error);
3108
                        } else {
3109
                            setEventMessages('No mail sent. Feature is disabled by option MAIN_DISABLE_ALL_MAILS', null, 'errors');
3110
                        }
3111
                    }
3112
                }
3113
3114
                if (getDolGlobalString('TICKET_DISABLE_MAIL_AUTOCOPY_TO')) {
3115
                    $conf->global->MAIN_MAIL_AUTOCOPY_TO = $old_MAIN_MAIL_AUTOCOPY_TO;
3116
                }
3117
            }
3118
        } else {
3119
            $langs->load("other");
3120
            setEventMessages($langs->trans('ErrorMailRecipientIsEmptyForSendTicketMessage'), null, 'warnings');
3121
        }
3122
3123
        return $is_sent;
3124
    }
3125
3126
	// phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
3127
    /**
3128
     *  Load indicators for dashboard (this->nbtodo and this->nbtodolate)
3129
     *
3130
     *  @param          User    $user   Object user
3131
     *  @param          int     $mode   "opened" for askprice to close, "signed" for proposal to invoice
3132
     *  @return         WorkboardResponse|int             Return integer <0 if KO, WorkboardResponse if OK
3133
     */
3134
    public function load_board($user, $mode)
3135
    {
3136
		// phpcs:enable
3137
        global $user, $langs;
3138
3139
        $now = dol_now();
3140
        $delay_warning = 0;
3141
3142
        $clause = " WHERE";
3143
3144
        $sql = "SELECT p.rowid, p.ref, p.datec as datec";
3145
        $sql .= " FROM " . MAIN_DB_PREFIX . "ticket as p";
3146
        if (isModEnabled('societe') && !$user->hasRight('societe', 'client', 'voir') && !$user->socid) {
3147
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON p.fk_soc = sc.fk_soc";
3148
            $sql .= " WHERE sc.fk_user = " . ((int) $user->id);
3149
            $clause = " AND";
3150
        }
3151
        $sql .= $clause . " p.entity IN (" . getEntity('ticket') . ")";
3152
        if ($mode == 'opened') {
3153
            $sql .= " AND p.fk_statut NOT IN (" . Ticket::STATUS_CLOSED . ", " . Ticket::STATUS_CANCELED . ")";
3154
        }
3155
        if ($user->socid) {
3156
            $sql .= " AND p.fk_soc = " . ((int) $user->socid);
3157
        }
3158
3159
        $resql = $this->db->query($sql);
3160
        if ($resql) {
3161
            $label = $labelShort = '';
3162
            $status = '';
3163
            if ($mode == 'opened') {
3164
                $status = 'openall';
3165
                //$delay_warning = $conf->ticket->warning_delay;
3166
                $delay_warning = 0;
3167
                $label = $langs->trans("MenuListNonClosed");
3168
                $labelShort = $langs->trans("MenuListNonClosed");
3169
            }
3170
3171
            $response = new WorkboardResponse();
3172
            //$response->warning_delay = $delay_warning / 60 / 60 / 24;
3173
            $response->label = $label;
3174
            $response->labelShort = $labelShort;
3175
            $response->url = constant('BASE_URL') . '/ticket/list.php?search_fk_statut[]=' . $status;
3176
            $response->img = img_object('', "ticket");
3177
3178
            // This assignment in condition is not a bug. It allows walking the results.
3179
            while ($obj = $this->db->fetch_object($resql)) {
3180
                $response->nbtodo++;
3181
                if ($mode == 'opened') {
3182
                    $datelimit = $this->db->jdate($obj->datec) + $delay_warning;
3183
                    if ($datelimit < $now) {
3184
                        //$response->nbtodolate++;
3185
                    }
3186
                }
3187
            }
3188
            return $response;
3189
        } else {
3190
            $this->error = $this->db->lasterror();
3191
            return -1;
3192
        }
3193
    }
3194
3195
    /**
3196
     *      Load indicator this->nb of global stats widget
3197
     *
3198
     *      @return     int         Return integer <0 if ko, >0 if ok
3199
     */
3200
    public function loadStateBoard()
3201
    {
3202
        global $user;
3203
3204
        $this->nb = array();
3205
        $clause = "WHERE";
3206
3207
        $sql = "SELECT count(p.rowid) as nb";
3208
        $sql .= " FROM " . MAIN_DB_PREFIX . "ticket as p";
3209
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe as s ON p.fk_soc = s.rowid";
3210
        if (!$user->hasRight('societe', 'client', 'voir')) {
3211
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "societe_commerciaux as sc ON s.rowid = sc.fk_soc";
3212
            $sql .= " WHERE sc.fk_user = " . ((int) $user->id);
3213
            $clause = "AND";
3214
        }
3215
        $sql .= " " . $clause . " p.entity IN (" . getEntity('ticket') . ")";
3216
3217
        $resql = $this->db->query($sql);
3218
        if ($resql) {
3219
            // This assignment in condition is not a bug. It allows walking the results.
3220
            while ($obj = $this->db->fetch_object($resql)) {
3221
                $this->nb["ticket"] = $obj->nb;
3222
            }
3223
            $this->db->free($resql);
3224
            return 1;
3225
        } else {
3226
            dol_print_error($this->db);
3227
            $this->error = $this->db->lasterror();
3228
            return -1;
3229
        }
3230
    }
3231
3232
    /**
3233
     * Function used to replace a thirdparty id with another one.
3234
     *
3235
     * @param DoliDB    $db             Database handler
3236
     * @param int       $origin_id      Old thirdparty id
3237
     * @param int       $dest_id        New thirdparty id
3238
     * @return bool
3239
     */
3240
    public static function replaceThirdparty($db, $origin_id, $dest_id)
3241
    {
3242
        $tables = array('ticket');
3243
3244
        return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
3245
    }
3246
3247
    /**
3248
     *  Return clicable link of object (with eventually picto)
3249
     *
3250
     *  @param      string      $option                 Where point the link (0=> main card, 1,2 => shipment, 'nolink'=>No link)
3251
     *  @param      array       $arraydata              Array of data
3252
     *  @return     string                              HTML Code for Kanban thumb.
3253
     */
3254
    public function getKanbanView($option = '', $arraydata = null)
3255
    {
3256
        global $langs;
3257
3258
        $selected = (empty($arraydata['selected']) ? 0 : $arraydata['selected']);
3259
3260
        $return = '<div class="box-flex-item box-flex-grow-zero">';
3261
        $return .= '<div class="info-box info-box-sm">';
3262
        $return .= '<span class="info-box-icon bg-infobox-action">';
3263
        $return .= img_picto('', $this->picto);
3264
        $return .= '</span>';
3265
        $return .= '<div class="info-box-content">';
3266
        $return .= '<span class="info-box-ref inline-block tdoverflowmax150 valignmiddle">' . (method_exists($this, 'getNomUrl') ? $this->getNomUrl(1) : $this->ref) . '</span>';
3267
        if ($selected >= 0) {
3268
            $return .= '<input id="cb' . $this->id . '" class="flat checkforselect fright" type="checkbox" name="toselect[]" value="' . $this->id . '"' . ($selected ? ' checked="checked"' : '') . '>';
3269
        }
3270
        if (!empty($arraydata['user_assignment'])) {
3271
            $return .= '<br><span class="info-box-label" title="' . dol_escape_htmltag($langs->trans("AssignedTo")) . '">' . $arraydata['user_assignment'] . '</span>';
3272
        }
3273
        if (property_exists($this, 'type_code') && !empty($this->type_code)) {
3274
            $return .= '<br>';
3275
            $return .= '<div class="tdoverflowmax125 inline-block">' . $langs->getLabelFromKey($this->db, 'TicketTypeShort' . $this->type_code, 'c_ticket_type', 'code', 'label', $this->type_code) . '</div>';
3276
        }
3277
        if (method_exists($this, 'getLibStatut')) {
3278
            $return .= '<br><div class="info-box-status">' . $this->getLibStatut(3) . '</div>';
3279
        }
3280
        $return .= '</div>';
3281
        $return .= '</div>';
3282
        $return .= '</div>';
3283
3284
        return $return;
3285
    }
3286
3287
    /**
3288
     *  Create a document onto disk according to template module.
3289
     *
3290
     *  @param      string      $modele         Force template to use ('' to not force)
3291
     *  @param      Translate   $outputlangs    object lang a utiliser pour traduction
3292
     *  @param      int         $hidedetails    Hide details of lines
3293
     *  @param      int         $hidedesc       Hide description
3294
     *  @param      int         $hideref        Hide ref
3295
     *  @param      null|array  $moreparams     Array to provide more information
3296
     *  @return     int                         0 if KO, 1 if OK
3297
     */
3298
    public function generateDocument($modele, $outputlangs, $hidedetails = 0, $hidedesc = 0, $hideref = 0, $moreparams = null)
3299
    {
3300
        global $langs;
3301
3302
        $langs->load("ticket");
3303
        $outputlangs->load("ticket");
3304
3305
        if (!dol_strlen($modele)) {
3306
            $modele = 'generic_ticket_odt';
3307
3308
            if (!empty($this->model_pdf)) {
3309
                $modele = $this->model_pdf;
3310
            } elseif (getDolGlobalString('TICKET_ADDON_PDF')) {
3311
                $modele = getDolGlobalString('TICKET_ADDON_PDF');
3312
            }
3313
        }
3314
3315
        $modelpath = "core/modules/ticket/doc/";
3316
3317
        return $this->commonGenerateDocument($modelpath, $modele, $outputlangs, $hidedetails, $hidedesc, $hideref, $moreparams);
3318
    }
3319
}
3320