dolibarr  x.y.z
ticket.class.php
Go to the documentation of this file.
1 <?php
2 /* Copyright (C) 2013-2018 Jean-François Ferry <hello@librethic.io>
3  * Copyright (C) 2016 Christophe Battarel <christophe@altairis.fr>
4  * Copyright (C) 2019-2022 Frédéric France <frederic.france@netlogic.fr>
5  * Copyright (C) 2020 Laurent Destailleur <eldy@users.sourceforge.net>
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program. If not, see <https://www.gnu.org/licenses/>.
19  */
20 
27 // Put here all includes required by your class file
28 require_once DOL_DOCUMENT_ROOT."/core/class/commonobject.class.php";
29 require_once DOL_DOCUMENT_ROOT.'/fichinter/class/fichinter.class.php';
30 require_once DOL_DOCUMENT_ROOT.'/core/lib/ticket.lib.php';
31 
32 
36 class Ticket extends CommonObject
37 {
41  public $element = 'ticket';
42 
46  public $table_element = 'ticket';
47 
51  public $fk_element = 'fk_ticket';
52 
56  public $ismultientitymanaged = 1;
57 
61  public $isextrafieldmanaged = 1;
62 
66  public $picto = 'ticket';
67 
68 
72  public $track_id;
73 
77  public $fk_soc;
78 
82  public $fk_project;
83 
87  public $origin_email;
88 
92  public $fk_user_create;
93 
97  public $fk_user_assign;
98 
102  public $subject;
103 
107  public $message;
108 
114  public $fk_statut;
115 
119  public $status;
120 
124  public $resolution;
125 
129  public $progress;
130 
134  public $timing;
135 
139  public $type_code;
140 
144  public $category_code;
145 
149  public $severity_code;
150 
154  public $type_label;
155 
160 
165 
169  public $email_from;
170 
174  public $datec = '';
175 
179  public $date_read = '';
180 
184  public $date_last_msg_sent = '';
185 
189  public $date_close = '';
190 
194  public $cache_types_tickets;
195 
199  public $cache_category_tickets;
200 
204  public $notify_tiers_at_create;
205 
209  public $email_msgid;
210 
214  public $email_date;
215 
219  public $oldcopy;
220 
224  public $lines;
225 
226 
230  public $regeximgext = '\.gif|\.jpg|\.jpeg|\.png|\.bmp|\.webp|\.xpm|\.xbm'; // See also into images.lib.php
231 
235  const STATUS_NOT_READ = 0;
236  const STATUS_READ = 1;
237  const STATUS_ASSIGNED = 2;
238  const STATUS_IN_PROGRESS = 3;
239  const STATUS_NEED_MORE_INFO = 5; // waiting requester feedback
240  const STATUS_WAITING = 7; // on hold
241  const STATUS_CLOSED = 8; // Closed - Solved
242  const STATUS_CANCELED = 9; // Closed - Not solved
243 
244 
271  // BEGIN MODULEBUILDER PROPERTIES
272  public $fields = array(
273  'rowid' => array('type'=>'integer', 'label'=>'TechnicalID', 'visible'=>-2, 'enabled'=>1, 'position'=>1, 'notnull'=>1, 'index'=>1, 'comment'=>"Id"),
274  'entity' => array('type'=>'integer', 'label'=>'Entity', 'visible'=>0, 'enabled'=>1, 'position'=>5, 'notnull'=>1, 'index'=>1),
275  '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),
276  'track_id' => array('type'=>'varchar(255)', 'label'=>'TicketTrackId', 'visible'=>-2, 'enabled'=>1, 'position'=>11, 'notnull'=>-1, 'searchall'=>1, 'help'=>"Help text"),
277  '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'),
278  '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'),
279  'subject' => array('type'=>'varchar(255)', 'label'=>'Subject', 'visible'=>1, 'enabled'=>1, 'position'=>18, 'notnull'=>-1, 'searchall'=>1, 'help'=>"", 'css'=>'maxwidth200 tdoverflowmax200', 'autofocusoncreate'=>1),
280  'type_code' => array('type'=>'varchar(32)', 'label'=>'Type', 'visible'=>1, 'enabled'=>1, 'position'=>20, 'notnull'=>-1, 'help'=>"", 'csslist'=>'maxwidth125 tdoverflowmax50'),
281  'category_code' => array('type'=>'varchar(32)', 'label'=>'TicketCategory', 'visible'=>-1, 'enabled'=>1, 'position'=>21, 'notnull'=>-1, 'help'=>"", 'css'=>'maxwidth100 tdoverflowmax200'),
282  'severity_code' => array('type'=>'varchar(32)', 'label'=>'Severity', 'visible'=>1, 'enabled'=>1, 'position'=>22, 'notnull'=>-1, 'help'=>"", 'css'=>'maxwidth100'),
283  'fk_soc' => array('type'=>'integer:Societe:societe/class/societe.class.php', 'label'=>'ThirdParty', 'visible'=>1, 'enabled'=>'$conf->societe->enabled', 'position'=>50, 'notnull'=>-1, 'index'=>1, 'searchall'=>1, 'help'=>"OrganizationEventLinkToThirdParty", 'css'=>'tdoverflowmax150 maxwidth150onsmartphone'),
284  'notify_tiers_at_create' => array('type'=>'integer', 'label'=>'NotifyThirdparty', 'visible'=>-1, 'enabled'=>0, 'position'=>51, 'notnull'=>1, 'index'=>1),
285  '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"),
286  //'timing' => array('type'=>'varchar(20)', 'label'=>'Timing', 'visible'=>-1, 'enabled'=>1, 'position'=>42, 'notnull'=>-1, 'help'=>""), // what is this ?
287  'datec' => array('type'=>'datetime', 'label'=>'DateCreation', 'visible'=>1, 'enabled'=>1, 'position'=>500, 'notnull'=>1, 'csslist'=>'nowraponall'),
288  'date_read' => array('type'=>'datetime', 'label'=>'TicketReadOn', 'visible'=>-1, 'enabled'=>1, 'position'=>501, 'notnull'=>1),
289  'date_last_msg_sent' => array('type'=>'datetime', 'label'=>'TicketLastMessageDate', 'visible'=>0, 'enabled'=>1, 'position'=>502, 'notnull'=>-1),
290  'fk_user_assign' => array('type'=>'integer:User:user/class/user.class.php', 'label'=>'AssignedTo', 'visible'=>1, 'enabled'=>1, 'position'=>505, 'notnull'=>1, 'csslist'=>'tdoverflowmax100 maxwidth150onsmartphone'),
291  'date_close' => array('type'=>'datetime', 'label'=>'TicketCloseOn', 'visible'=>-1, 'enabled'=>1, 'position'=>510, 'notnull'=>1),
292  'tms' => array('type'=>'timestamp', 'label'=>'DateModification', 'visible'=>-1, 'enabled'=>1, 'position'=>520, 'notnull'=>1),
293  'message' => array('type'=>'text', 'label'=>'Message', 'visible'=>-2, 'enabled'=>1, 'position'=>540, 'notnull'=>-1,),
294  'email_msgid' => array('type'=>'varchar(255)', 'label'=>'EmailMsgID', 'visible'=>-2, 'enabled'=>1, 'position'=>540, 'notnull'=>-1, 'help'=>'EmailMsgIDDesc', 'csslist'=>'tdoverflowmax100'),
295  'email_date' => array('type'=>'datetime', 'label'=>'EmailDate', 'visible'=>-2, 'enabled'=>1, 'position'=>541),
296  'progress' => array('type'=>'integer', 'label'=>'Progression', 'visible'=>-1, 'enabled'=>1, 'position'=>540, 'notnull'=>-1, 'css'=>'right', 'help'=>"", 'isameasure'=>2, 'csslist'=>'width50'),
297  'resolution' => array('type'=>'integer', 'label'=>'Resolution', 'visible'=>-1, 'enabled'=>'$conf->global->TICKET_ENABLE_RESOLUTION', 'position'=>550, 'notnull'=>1),
298  'fk_statut' => array('type'=>'integer', 'label'=>'Status', 'visible'=>1, 'enabled'=>1, 'position'=>600, 'notnull'=>1, 'index'=>1, 'arrayofkeyval'=>array(0 => 'Unread', 1 => 'Read', 3 => 'Answered', 4 => 'Assigned', 5 => 'InProgress', 6 => 'Waiting', 8 => 'SolvedClosed', 9 => 'Deleted')),
299  'import_key' =>array('type'=>'varchar(14)', 'label'=>'ImportId', 'enabled'=>1, 'visible'=>-2, 'position'=>900),
300  );
301  // END MODULEBUILDER PROPERTIES
302 
303 
309  public function __construct($db)
310  {
311  global $conf;
312 
313  $this->db = $db;
314 
315  $this->statuts_short = array(
316  self::STATUS_NOT_READ => 'Unread',
317  self::STATUS_READ => 'Read',
318  self::STATUS_ASSIGNED => 'Assigned',
319  self::STATUS_IN_PROGRESS => 'InProgress',
320  self::STATUS_WAITING => 'OnHold',
321  self::STATUS_NEED_MORE_INFO => 'NeedMoreInformationShort',
322  self::STATUS_CLOSED => 'SolvedClosed',
323  self::STATUS_CANCELED => 'Canceled'
324  );
325  $this->statuts = array(
326  self::STATUS_NOT_READ => 'Unread',
327  self::STATUS_READ => 'Read',
328  self::STATUS_ASSIGNED => 'Assigned',
329  self::STATUS_IN_PROGRESS => 'InProgress',
330  self::STATUS_WAITING => 'OnHold',
331  self::STATUS_NEED_MORE_INFO => 'NeedMoreInformation',
332  self::STATUS_CLOSED => 'SolvedClosed',
333  self::STATUS_CANCELED => 'Canceled'
334  );
335  }
336 
343  private function verify()
344  {
345  $this->errors = array();
346 
347  $result = 0;
348 
349  // Clean parameters
350  if (isset($this->ref)) {
351  $this->ref = trim($this->ref);
352  }
353 
354  if (isset($this->track_id)) {
355  $this->track_id = trim($this->track_id);
356  }
357 
358  if (isset($this->fk_soc)) {
359  $this->fk_soc = (int) $this->fk_soc;
360  }
361 
362  if (isset($this->fk_project)) {
363  $this->fk_project = (int) $this->fk_project;
364  }
365 
366  if (isset($this->origin_email)) {
367  $this->origin_email = trim($this->origin_email);
368  }
369 
370  if (isset($this->fk_user_create)) {
371  $this->fk_user_create = (int) $this->fk_user_create;
372  }
373 
374  if (isset($this->fk_user_assign)) {
375  $this->fk_user_assign = (int) $this->fk_user_assign;
376  }
377 
378  if (isset($this->subject)) {
379  $this->subject = trim($this->subject);
380  }
381 
382  if (isset($this->message)) {
383  $this->message = trim($this->message);
384  if (dol_strlen($this->message) > 65000) {
385  $this->errors[] = 'ErrorFieldTooLong';
386  dol_syslog(get_class($this).'::create error -1 message too long', LOG_ERR);
387  $result = -1;
388  }
389  }
390 
391  if (isset($this->fk_statut)) {
392  $this->fk_statut = (int) $this->fk_statut;
393  }
394 
395  if (isset($this->resolution)) {
396  $this->resolution = trim($this->resolution);
397  }
398 
399  if (isset($this->progress)) {
400  $this->progress = trim($this->progress);
401  }
402 
403  if (isset($this->timing)) {
404  $this->timing = trim($this->timing);
405  }
406 
407  if (isset($this->type_code)) {
408  $this->type_code = trim($this->type_code);
409  }
410 
411  if (isset($this->category_code)) {
412  $this->category_code = trim($this->category_code);
413  }
414 
415  if (isset($this->severity_code)) {
416  $this->severity_code = trim($this->severity_code);
417  }
418 
419  if (empty($this->ref)) {
420  $this->errors[] = 'ErrorTicketRefRequired';
421  dol_syslog(get_class($this)."::create error -1 ref null", LOG_ERR);
422  $result = -1;
423  }
424 
425  return $result;
426  }
427 
435  public function create($user, $notrigger = 0)
436  {
437  global $conf, $langs;
438  $error = 0;
439 
440  // Clean parameters
441  $this->datec = dol_now();
442  if (empty($this->track_id)) {
443  $this->track_id = generate_random_id(16);
444  }
445 
446  // Check more parameters
447  // If error, this->errors[] is filled
448  $result = $this->verify();
449 
450  if ($result >= 0) {
451  // Insert request
452  $sql = "INSERT INTO ".MAIN_DB_PREFIX."ticket(";
453  $sql .= "ref,";
454  $sql .= "track_id,";
455  $sql .= "fk_soc,";
456  $sql .= "fk_project,";
457  $sql .= "origin_email,";
458  $sql .= "fk_user_create,";
459  $sql .= "fk_user_assign,";
460  $sql .= "email_msgid,";
461  $sql .= "email_date,";
462  $sql .= "subject,";
463  $sql .= "message,";
464  $sql .= "fk_statut,";
465  $sql .= "resolution,";
466  $sql .= "progress,";
467  $sql .= "timing,";
468  $sql .= "type_code,";
469  $sql .= "category_code,";
470  $sql .= "severity_code,";
471  $sql .= "datec,";
472  $sql .= "date_read,";
473  $sql .= "date_close,";
474  $sql .= "entity,";
475  $sql .= "notify_tiers_at_create,";
476  $sql .= "ip";
477  $sql .= ") VALUES (";
478  $sql .= " ".(!isset($this->ref) ? '' : "'".$this->db->escape($this->ref)."'").",";
479  $sql .= " ".(!isset($this->track_id) ? 'NULL' : "'".$this->db->escape($this->track_id)."'").",";
480  $sql .= " ".($this->fk_soc > 0 ? $this->db->escape($this->fk_soc) : "null").",";
481  $sql .= " ".($this->fk_project > 0 ? $this->db->escape($this->fk_project) : "null").",";
482  $sql .= " ".(!isset($this->origin_email) ? 'NULL' : "'".$this->db->escape($this->origin_email)."'").",";
483  $sql .= " ".($this->fk_user_create > 0 ? $this->fk_user_create : ($user->id > 0 ? $user->id : 'NULL')).",";
484  $sql .= " ".($this->fk_user_assign > 0 ? $this->fk_user_assign : 'NULL').",";
485  $sql .= " ".(empty($this->email_msgid) ? 'NULL' : "'".$this->db->escape($this->email_msgid)."'").",";
486  $sql .= " ".(empty($this->email_date) ? 'NULL' : "'".$this->db->idate($this->email_date)."'").",";
487  $sql .= " ".(!isset($this->subject) ? 'NULL' : "'".$this->db->escape($this->subject)."'").",";
488  $sql .= " ".(!isset($this->message) ? 'NULL' : "'".$this->db->escape($this->message)."'").",";
489  $sql .= " ".(!isset($this->fk_statut) ? '0' : "'".$this->db->escape($this->fk_statut)."'").",";
490  $sql .= " ".(!isset($this->resolution) ? 'NULL' : "'".$this->db->escape($this->resolution)."'").",";
491  $sql .= " ".(!isset($this->progress) ? '0' : "'".$this->db->escape($this->progress)."'").",";
492  $sql .= " ".(!isset($this->timing) ? 'NULL' : "'".$this->db->escape($this->timing)."'").",";
493  $sql .= " ".(!isset($this->type_code) ? 'NULL' : "'".$this->db->escape($this->type_code)."'").",";
494  $sql .= " ".(empty($this->category_code) || $this->category_code == '-1' ? 'NULL' : "'".$this->db->escape($this->category_code)."'").",";
495  $sql .= " ".(!isset($this->severity_code) ? 'NULL' : "'".$this->db->escape($this->severity_code)."'").",";
496  $sql .= " ".(!isset($this->datec) || dol_strlen($this->datec) == 0 ? 'NULL' : "'".$this->db->idate($this->datec)."'").",";
497  $sql .= " ".(!isset($this->date_read) || dol_strlen($this->date_read) == 0 ? 'NULL' : "'".$this->db->idate($this->date_read)."'").",";
498  $sql .= " ".(!isset($this->date_close) || dol_strlen($this->date_close) == 0 ? 'NULL' : "'".$this->db->idate($this->date_close)."'");
499  $sql .= ", ".((int) $conf->entity);
500  $sql .= ", ".(!isset($this->notify_tiers_at_create) ? '1' : "'".$this->db->escape($this->notify_tiers_at_create)."'");
501  $sql .= ", ".(!isset($this->ip) ? 'NULL' : "'".$this->db->escape($this->ip)."'");
502  $sql .= ")";
503 
504  $this->db->begin();
505 
506  dol_syslog(get_class($this)."::create", LOG_DEBUG);
507  $resql = $this->db->query($sql);
508  if (!$resql) {
509  $error++;
510  $this->errors[] = "Error ".$this->db->lasterror();
511  }
512 
513  if (!$error) {
514  $this->id = $this->db->last_insert_id(MAIN_DB_PREFIX."ticket");
515  }
516 
517  if (!$error && !empty($conf->global->TICKET_ADD_AUTHOR_AS_CONTACT)) {
518  // add creator as contributor
519  if ($this->add_contact($user->id, 'CONTRIBUTOR', 'internal') < 0) {
520  $error++;
521  }
522  }
523 
524  if (!$error && $this->fk_user_assign > 0) {
525  if ($this->add_contact($this->fk_user_assign, 'SUPPORTTEC', 'internal') < 0) {
526  $error++;
527  }
528  }
529 
530 
531  //Update extrafield
532  if (!$error) {
533  $result = $this->insertExtraFields();
534  if ($result < 0) {
535  $error++;
536  }
537  }
538 
539  if (!$error && !$notrigger) {
540  // Call trigger
541  $result = $this->call_trigger('TICKET_CREATE', $user);
542  if ($result < 0) {
543  $error++;
544  }
545  // End call triggers
546  }
547 
548  // Commit or rollback
549  if ($error) {
550  foreach ($this->errors as $errmsg) {
551  dol_syslog(get_class($this)."::create ".$errmsg, LOG_ERR);
552  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
553  }
554  $this->db->rollback();
555  return -1 * $error;
556  } else {
557  $this->db->commit();
558  return $this->id;
559  }
560  } else {
561  $this->db->rollback();
562  dol_syslog(get_class($this)."::Create fails verify ".join(',', $this->errors), LOG_WARNING);
563  return -3;
564  }
565  }
566 
576  public function fetch($id = '', $ref = '', $track_id = '', $email_msgid = '')
577  {
578  global $langs;
579 
580  // Check parameters
581  if (empty($id) && empty($ref) && empty($track_id) && empty($email_msgid)) {
582  $this->error = 'ErrorWrongParameters';
583  dol_print_error(get_class($this)."::fetch ".$this->error);
584  return -1;
585  }
586 
587  $sql = "SELECT";
588  $sql .= " t.rowid,";
589  $sql .= " t.entity,";
590  $sql .= " t.ref,";
591  $sql .= " t.track_id,";
592  $sql .= " t.fk_soc,";
593  $sql .= " t.fk_project,";
594  $sql .= " t.origin_email,";
595  $sql .= " t.fk_user_create,";
596  $sql .= " t.fk_user_assign,";
597  $sql .= " t.email_msgid,";
598  $sql .= " t.email_date,";
599  $sql .= " t.subject,";
600  $sql .= " t.message,";
601  $sql .= " t.fk_statut as status,";
602  $sql .= " t.resolution,";
603  $sql .= " t.progress,";
604  $sql .= " t.timing,";
605  $sql .= " t.type_code,";
606  $sql .= " t.category_code,";
607  $sql .= " t.severity_code,";
608  $sql .= " t.datec,";
609  $sql .= " t.date_read,";
610  $sql .= " t.date_last_msg_sent,";
611  $sql .= " t.date_close,";
612  $sql .= " t.tms,";
613  $sql .= " type.label as type_label, category.label as category_label, severity.label as severity_label";
614  $sql .= " FROM ".MAIN_DB_PREFIX."ticket as t";
615  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_ticket_type as type ON type.code=t.type_code";
616  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_ticket_category as category ON category.code=t.category_code";
617  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_ticket_severity as severity ON severity.code=t.severity_code";
618 
619  if ($id) {
620  $sql .= " WHERE t.rowid = ".((int) $id);
621  } else {
622  $sql .= " WHERE t.entity IN (".getEntity($this->element, 1).")";
623  if (!empty($ref)) {
624  $sql .= " AND t.ref = '".$this->db->escape($ref)."'";
625  } elseif ($track_id) {
626  $sql .= " AND t.track_id = '".$this->db->escape($track_id)."'";
627  } else {
628  $sql .= " AND t.email_msgid = '".$this->db->escape($email_msgid)."'";
629  }
630  }
631 
632  dol_syslog(get_class($this)."::fetch", LOG_DEBUG);
633  $resql = $this->db->query($sql);
634  if ($resql) {
635  if ($this->db->num_rows($resql)) {
636  $obj = $this->db->fetch_object($resql);
637 
638  $this->id = $obj->rowid;
639  $this->entity = $obj->entity;
640  $this->ref = $obj->ref;
641  $this->track_id = $obj->track_id;
642  $this->fk_soc = $obj->fk_soc;
643  $this->socid = $obj->fk_soc; // for fetch_thirdparty() method
644  $this->fk_project = $obj->fk_project;
645  $this->origin_email = $obj->origin_email;
646  $this->fk_user_create = $obj->fk_user_create;
647  $this->fk_user_assign = $obj->fk_user_assign;
648  $this->email_msgid = $obj->email_msgid;
649  $this->email_date = $this->db->jdate($obj->email_date);
650  $this->subject = $obj->subject;
651  $this->message = $obj->message;
652 
653  $this->status = $obj->status;
654  $this->fk_statut = $this->status; // For backward compatibility
655 
656  $this->resolution = $obj->resolution;
657  $this->progress = $obj->progress;
658  $this->timing = $obj->timing;
659 
660  $this->type_code = $obj->type_code;
661  // Si traduction existe, on l'utilise, sinon on prend le libelle par defaut
662  $label_type = ($langs->trans("TicketTypeShort".$obj->type_code) != ("TicketTypeShort".$obj->type_code) ? $langs->trans("TicketTypeShort".$obj->type_code) : ($obj->type_label != '-' ? $obj->type_label : ''));
663  $this->type_label = $label_type;
664 
665  $this->category_code = $obj->category_code;
666  // Si traduction existe, on l'utilise, sinon on prend le libelle par defaut
667  $label_category = ($langs->trans("TicketCategoryShort".$obj->category_code) != ("TicketCategoryShort".$obj->category_code) ? $langs->trans("TicketCategoryShort".$obj->category_code) : ($obj->category_label != '-' ? $obj->category_label : ''));
668  $this->category_label = $label_category;
669 
670  $this->severity_code = $obj->severity_code;
671  // Si traduction existe, on l'utilise, sinon on prend le libelle par defaut
672  $label_severity = ($langs->trans("TicketSeverityShort".$obj->severity_code) != ("TicketSeverityShort".$obj->severity_code) ? $langs->trans("TicketSeverityShort".$obj->severity_code) : ($obj->severity_label != '-' ? $obj->severity_label : ''));
673  $this->severity_label = $label_severity;
674 
675  $this->datec = $this->db->jdate($obj->datec);
676  $this->date_creation = $this->db->jdate($obj->datec);
677  $this->date_read = $this->db->jdate($obj->date_read);
678  $this->date_validation = $this->db->jdate($obj->date_read);
679  $this->date_last_msg_sent = $this->db->jdate($obj->date_last_msg_sent);
680  $this->date_close = $this->db->jdate($obj->date_close);
681  $this->tms = $this->db->jdate($obj->tms);
682  $this->date_modification = $this->db->jdate($obj->tms);
683 
684  $this->fetch_optionals();
685 
686  $this->db->free($resql);
687  return 1;
688  } else {
689  return 0;
690  }
691  } else {
692  $this->error = "Error ".$this->db->lasterror();
693  dol_syslog(get_class($this)."::fetch ".$this->error, LOG_ERR);
694  return -1;
695  }
696  }
697 
711  public function fetchAll($user, $sortorder = 'ASC', $sortfield = 't.datec', $limit = '', $offset = 0, $arch = '', $filter = '')
712  {
713  global $langs;
714 
715  $extrafields = new ExtraFields($this->db);
716 
717  // fetch optionals attributes and labels
718  $extrafields->fetch_name_optionals_label($this->table_element);
719 
720  $sql = "SELECT";
721  $sql .= " t.rowid,";
722  $sql .= " t.ref,";
723  $sql .= " t.track_id,";
724  $sql .= " t.fk_soc,";
725  $sql .= " t.fk_project,";
726  $sql .= " t.origin_email,";
727  $sql .= " t.fk_user_create, uc.lastname as user_create_lastname, uc.firstname as user_create_firstname,";
728  $sql .= " t.fk_user_assign, ua.lastname as user_assign_lastname, ua.firstname as user_assign_firstname,";
729  $sql .= " t.subject,";
730  $sql .= " t.message,";
731  $sql .= " t.fk_statut,";
732  $sql .= " t.resolution,";
733  $sql .= " t.progress,";
734  $sql .= " t.timing,";
735  $sql .= " t.type_code,";
736  $sql .= " t.category_code,";
737  $sql .= " t.severity_code,";
738  $sql .= " t.datec,";
739  $sql .= " t.date_read,";
740  $sql .= " t.date_last_msg_sent,";
741  $sql .= " t.date_close,";
742  $sql .= " t.tms";
743  $sql .= ", type.label as type_label, category.label as category_label, severity.label as severity_label";
744  // Add fields for extrafields
745  foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
746  $sql .= ($extrafields->attributes[$this->table_element]['type'][$key] != 'separate' ? ",ef.".$key." as options_".$key : '');
747  }
748  $sql .= " FROM ".MAIN_DB_PREFIX."ticket as t";
749  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_ticket_type as type ON type.code=t.type_code";
750  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_ticket_category as category ON category.code=t.category_code";
751  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_ticket_severity as severity ON severity.code=t.severity_code";
752  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON s.rowid=t.fk_soc";
753  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."user as uc ON uc.rowid=t.fk_user_create";
754  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."user as ua ON ua.rowid=t.fk_user_assign";
755  if (is_array($extrafields->attributes[$this->table_element]['label']) && count($extrafields->attributes[$this->table_element]['label'])) {
756  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."ticket_extrafields as ef on (t.rowid = ef.fk_object)";
757  }
758  if (empty($user->rights->societe->client->voir) && !$user->socid) {
759  $sql .= ", ".MAIN_DB_PREFIX."societe_commerciaux as sc";
760  }
761 
762  $sql .= " WHERE t.entity IN (".getEntity('ticket').")";
763 
764  // Manage filter
765  if (!empty($filter)) {
766  foreach ($filter as $key => $value) {
767  if (strpos($key, 'date')) { // To allow $filter['YEAR(s.dated)']=>$year
768  $sql .= " AND ".$key." = '".$this->db->escape($value)."'";
769  } elseif (($key == 't.fk_user_assign') || ($key == 't.type_code') || ($key == 't.category_code') || ($key == 't.severity_code') || ($key == 't.fk_soc')) {
770  $sql .= " AND ".$key." = '".$this->db->escape($value)."'";
771  } elseif ($key == 't.fk_statut') {
772  if (is_array($value) && count($value) > 0) {
773  $sql .= " AND ".$key." IN (".$this->db->sanitize(implode(',', $value)).")";
774  } else {
775  $sql .= " AND ".$key.' = '.((int) $value);
776  }
777  } else {
778  $sql .= " AND ".$key." LIKE '%".$this->db->escape($value)."%'";
779  }
780  }
781  }
782  if (empty($user->rights->societe->client->voir) && !$user->socid) {
783  $sql .= " AND t.fk_soc = sc.fk_soc AND sc.fk_user = ".((int) $user->id);
784  } elseif ($user->socid) {
785  $sql .= " AND t.fk_soc = ".((int) $user->socid);
786  }
787 
788  $sql .= $this->db->order($sortfield, $sortorder);
789  if (!empty($limit)) {
790  $sql .= $this->db->plimit($limit + 1, $offset);
791  }
792 
793  dol_syslog(get_class($this)."::fetchAll", LOG_DEBUG);
794  $resql = $this->db->query($sql);
795 
796  if ($resql) {
797  $this->lines = array();
798 
799  $num = $this->db->num_rows($resql);
800  $i = 0;
801 
802  if ($num) {
803  while ($i < $num) {
804  $obj = $this->db->fetch_object($resql);
805 
806  $line = new TicketsLine();
807 
808  $line->id = $obj->rowid;
809  $line->rowid = $obj->rowid;
810  $line->ref = $obj->ref;
811  $line->track_id = $obj->track_id;
812  $line->fk_soc = $obj->fk_soc;
813  $line->fk_project = $obj->fk_project;
814  $line->origin_email = $obj->origin_email;
815 
816  $line->fk_user_create = $obj->fk_user_create;
817  $line->user_create_lastname = $obj->user_create_lastname;
818  $line->user_create_firstname = $obj->user_create_firstname;
819 
820  $line->fk_user_assign = $obj->fk_user_assign;
821  $line->user_assign_lastname = $obj->user_assign_lastname;
822  $line->user_assign_firstname = $obj->user_assign_firstname;
823 
824  $line->subject = $obj->subject;
825  $line->message = $obj->message;
826  $line->fk_statut = $obj->fk_statut;
827  $line->resolution = $obj->resolution;
828  $line->progress = $obj->progress;
829  $line->timing = $obj->timing;
830 
831  // Si traduction existe, on l'utilise, sinon on prend le libelle par defaut
832  $label_type = ($langs->trans("TicketTypeShort".$obj->type_code) != ("TicketTypeShort".$obj->type_code) ? $langs->trans("TicketTypeShort".$obj->type_code) : ($obj->type_label != '-' ? $obj->type_label : ''));
833  $line->type_label = $label_type;
834 
835  $this->category_code = $obj->category_code;
836  // Si traduction existe, on l'utilise, sinon on prend le libelle par defaut
837  $label_category = ($langs->trans("TicketCategoryShort".$obj->category_code) != ("TicketCategoryShort".$obj->category_code) ? $langs->trans("TicketCategoryShort".$obj->category_code) : ($obj->category_label != '-' ? $obj->category_label : ''));
838  $line->category_label = $label_category;
839 
840  $this->severity_code = $obj->severity_code;
841  // Si traduction existe, on l'utilise, sinon on prend le libelle par defaut
842  $label_severity = ($langs->trans("TicketSeverityShort".$obj->severity_code) != ("TicketSeverityShort".$obj->severity_code) ? $langs->trans("TicketSeverityShort".$obj->severity_code) : ($obj->severity_label != '-' ? $obj->severity_label : ''));
843  $line->severity_label = $label_severity;
844 
845  $line->datec = $this->db->jdate($obj->datec);
846  $line->date_read = $this->db->jdate($obj->date_read);
847  $line->date_last_msg_sent = $this->db->jdate($obj->date_last_msg_sent);
848  $line->date_close = $this->db->jdate($obj->date_close);
849 
850  // Extra fields
851  if (is_array($extrafields->attributes[$this->table_element]['label']) && count($extrafields->attributes[$this->table_element]['label'])) {
852  foreach ($extrafields->attributes[$this->table_element]['label'] as $key => $val) {
853  $tmpkey = 'options_'.$key;
854  $line->{$tmpkey} = $obj->$tmpkey;
855  }
856  }
857 
858  $this->lines[$i] = $line;
859  $i++;
860  }
861  }
862  $this->db->free($resql);
863  return $num;
864  } else {
865  $this->error = "Error ".$this->db->lasterror();
866  dol_syslog(get_class($this)."::fetchAll ".$this->error, LOG_ERR);
867  return -1;
868  }
869  }
870 
878  public function update($user = 0, $notrigger = 0)
879  {
880  global $conf, $langs, $hookmanager;
881  $error = 0;
882 
883  // $this->oldcopy should have been set by the caller of update (here properties were already modified)
884  //if (empty($this->oldcopy)) {
885  // $this->oldcopy = dol_clone($this);
886  //}
887 
888  // Clean parameters
889  if (isset($this->ref)) {
890  $this->ref = trim($this->ref);
891  }
892 
893  if (isset($this->track_id)) {
894  $this->track_id = trim($this->track_id);
895  }
896 
897  if (isset($this->fk_soc)) {
898  $this->fk_soc = (int) $this->fk_soc;
899  }
900 
901  if (isset($this->fk_project)) {
902  $this->fk_project = (int) $this->fk_project;
903  }
904 
905  if (isset($this->origin_email)) {
906  $this->origin_email = trim($this->origin_email);
907  }
908 
909  if (isset($this->fk_user_create)) {
910  $this->fk_user_create = (int) $this->fk_user_create;
911  }
912 
913  if (isset($this->fk_user_assign)) {
914  $this->fk_user_assign = (int) $this->fk_user_assign;
915  }
916 
917  if (isset($this->subject)) {
918  $this->subject = trim($this->subject);
919  }
920 
921  if (isset($this->message)) {
922  $this->message = trim($this->message);
923  if (dol_strlen($this->message) > 65000) {
924  $this->errors[] = 'ErrorFieldTooLong';
925  dol_syslog(get_class($this).'::update error -1 message too long', LOG_ERR);
926  return -1;
927  }
928  }
929 
930  if (isset($this->fk_statut)) {
931  $this->fk_statut = (int) $this->fk_statut;
932  }
933 
934  if (isset($this->resolution)) {
935  $this->resolution = trim($this->resolution);
936  }
937 
938  if (isset($this->progress)) {
939  $this->progress = trim($this->progress);
940  }
941 
942  if (isset($this->timing)) {
943  $this->timing = trim($this->timing);
944  }
945 
946  if (isset($this->type_code)) {
947  $this->timing = trim($this->type_code);
948  }
949 
950  if (isset($this->category_code)) {
951  $this->timing = trim($this->category_code);
952  }
953 
954  if (isset($this->severity_code)) {
955  $this->timing = trim($this->severity_code);
956  }
957 
958  // Check parameters
959  // Put here code to add a control on parameters values
960  // Update request
961  $sql = "UPDATE ".MAIN_DB_PREFIX."ticket SET";
962  $sql .= " ref=".(isset($this->ref) ? "'".$this->db->escape($this->ref)."'" : "").",";
963  $sql .= " track_id=".(isset($this->track_id) ? "'".$this->db->escape($this->track_id)."'" : "null").",";
964  $sql .= " fk_soc=".(isset($this->fk_soc) ? "'".$this->db->escape($this->fk_soc)."'" : "null").",";
965  $sql .= " fk_project=".(isset($this->fk_project) ? "'".$this->db->escape($this->fk_project)."'" : "null").",";
966  $sql .= " origin_email=".(isset($this->origin_email) ? "'".$this->db->escape($this->origin_email)."'" : "null").",";
967  $sql .= " fk_user_create=".(isset($this->fk_user_create) ? $this->fk_user_create : "null").",";
968  $sql .= " fk_user_assign=".(isset($this->fk_user_assign) ? $this->fk_user_assign : "null").",";
969  $sql .= " subject=".(isset($this->subject) ? "'".$this->db->escape($this->subject)."'" : "null").",";
970  $sql .= " message=".(isset($this->message) ? "'".$this->db->escape($this->message)."'" : "null").",";
971  $sql .= " fk_statut=".(isset($this->fk_statut) ? $this->fk_statut : "null").",";
972  $sql .= " resolution=".(isset($this->resolution) ? $this->resolution : "null").",";
973  $sql .= " progress=".(isset($this->progress) ? "'".$this->db->escape($this->progress)."'" : "null").",";
974  $sql .= " timing=".(isset($this->timing) ? "'".$this->db->escape($this->timing)."'" : "null").",";
975  $sql .= " type_code=".(isset($this->type_code) ? "'".$this->db->escape($this->type_code)."'" : "null").",";
976  $sql .= " category_code=".(isset($this->category_code) ? "'".$this->db->escape($this->category_code)."'" : "null").",";
977  $sql .= " severity_code=".(isset($this->severity_code) ? "'".$this->db->escape($this->severity_code)."'" : "null").",";
978  $sql .= " datec=".(dol_strlen($this->datec) != 0 ? "'".$this->db->idate($this->datec)."'" : 'null').",";
979  $sql .= " date_read=".(dol_strlen($this->date_read) != 0 ? "'".$this->db->idate($this->date_read)."'" : 'null').",";
980  $sql .= " date_last_msg_sent=".(dol_strlen($this->date_last_msg_sent) != 0 ? "'".$this->db->idate($this->date_last_msg_sent)."'" : 'null').",";
981  $sql .= " date_close=".(dol_strlen($this->date_close) != 0 ? "'".$this->db->idate($this->date_close)."'" : 'null');
982  $sql .= " WHERE rowid=".((int) $this->id);
983 
984  $this->db->begin();
985 
986  $resql = $this->db->query($sql);
987  if (!$resql) {
988  $error++;
989  $this->errors[] = "Error ".$this->db->lasterror();
990  }
991 
992  if (!$error) {
993  // Update extrafields
994  $result = $this->insertExtraFields();
995  if ($result < 0) {
996  $error++;
997  }
998  }
999 
1000  if (!$error && !$notrigger) {
1001  // Call trigger
1002  $result = $this->call_trigger('TICKET_MODIFY', $user);
1003  if ($result < 0) {
1004  $error++;
1005  }
1006  // End call triggers
1007  }
1008 
1009  // Commit or rollback
1010  if ($error) {
1011  foreach ($this->errors as $errmsg) {
1012  dol_syslog(get_class($this)."::update ".$errmsg, LOG_ERR);
1013  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1014  }
1015  $this->db->rollback();
1016  return -1 * $error;
1017  } else {
1018  $this->db->commit();
1019  return 1;
1020  }
1021  }
1022 
1030  public function delete($user, $notrigger = 0)
1031  {
1032  global $conf, $langs;
1033  $error = 0;
1034 
1035  $this->db->begin();
1036 
1037  if (!$error) {
1038  if (!$notrigger) {
1039  // Call trigger
1040  $result = $this->call_trigger('TICKET_DELETE', $user);
1041  if ($result < 0) {
1042  $error++;
1043  }
1044  // End call triggers
1045  }
1046  }
1047 
1048  if (!$error) {
1049  // Delete linked contacts
1050  $res = $this->delete_linked_contact();
1051  if ($res < 0) {
1052  dol_syslog(get_class($this)."::delete error", LOG_ERR);
1053  $error++;
1054  }
1055  }
1056 
1057  if (!$error) {
1058  // Delete linked object
1059  $res = $this->deleteObjectLinked();
1060  if ($res < 0) {
1061  $error++;
1062  }
1063  }
1064 
1065  // Removed extrafields
1066  if (!$error) {
1067  $result = $this->deleteExtraFields();
1068  if ($result < 0) {
1069  $error++;
1070  dol_syslog(get_class($this)."::delete error -3 ".$this->error, LOG_ERR);
1071  }
1072  }
1073 
1074  // Delete all child tables
1075 
1076  if (!$error) {
1077  $sql = "DELETE FROM ".MAIN_DB_PREFIX."categorie_ticket";
1078  $sql .= " WHERE fk_ticket = ".(int) $this->id;
1079 
1080  $result = $this->db->query($sql);
1081  if (!$result) {
1082  $error++;
1083  $this->errors[] = $this->db->lasterror();
1084  }
1085  }
1086 
1087  if (!$error) {
1088  $sql = "DELETE FROM ".MAIN_DB_PREFIX."ticket";
1089  $sql .= " WHERE rowid=".((int) $this->id);
1090 
1091  dol_syslog(get_class($this)."::delete sql=".$sql);
1092  $resql = $this->db->query($sql);
1093  if (!$resql) {
1094  $error++;
1095  $this->errors[] = "Error ".$this->db->lasterror();
1096  }
1097  }
1098 
1099  // Commit or rollback
1100  if ($error) {
1101  foreach ($this->errors as $errmsg) {
1102  dol_syslog(get_class($this)."::delete ".$errmsg, LOG_ERR);
1103  $this->error .= ($this->error ? ', '.$errmsg : $errmsg);
1104  }
1105  $this->db->rollback();
1106  return -1 * $error;
1107  } else {
1108  $this->db->commit();
1109  return 1;
1110  }
1111  }
1112 
1120  public function createFromClone(User $user, $fromid)
1121  {
1122  $error = 0;
1123 
1124  $object = new Ticket($this->db);
1125 
1126  $this->db->begin();
1127 
1128  // Load source object
1129  $object->fetch($fromid);
1130  $object->id = 0;
1131  $object->statut = 0;
1132 
1133  // Clear fields
1134  // ...
1135  // Create clone
1136  $object->context['createfromclone'] = 'createfromclone';
1137  $result = $object->create($user);
1138 
1139  // Other options
1140  if ($result < 0) {
1141  $this->error = $object->error;
1142  $error++;
1143  }
1144 
1145  if (!$error) {
1146  }
1147 
1148  unset($object->context['createfromclone']);
1149 
1150  // End
1151  if (!$error) {
1152  $this->db->commit();
1153  return $object->id;
1154  } else {
1155  $this->db->rollback();
1156  return -1;
1157  }
1158  }
1159 
1166  public function initAsSpecimen()
1167  {
1168  $this->id = 0;
1169  $this->entity = 1;
1170  $this->ref = 'TI0501-001';
1171  $this->track_id = 'XXXXaaaa';
1172  $this->origin_email = 'email@email.com';
1173  $this->fk_project = 1;
1174  $this->fk_user_create = 1;
1175  $this->fk_user_assign = 1;
1176  $this->subject = 'Subject of ticket';
1177  $this->message = 'Message of ticket';
1178  $this->status = 0;
1179  $this->resolution = '1';
1180  $this->progress = '10';
1181  //$this->timing = '30';
1182  $this->type_code = 'TYPECODE';
1183  $this->category_code = 'CATEGORYCODE';
1184  $this->severity_code = 'SEVERITYCODE';
1185  $this->datec = '';
1186  $this->date_read = '';
1187  $this->date_last_msg_sent = '';
1188  $this->date_close = '';
1189  $this->tms = '';
1190  return 1;
1191  }
1192 
1199  public function printSelectStatus($selected = "")
1200  {
1201  print Form::selectarray('search_fk_statut', $this->statuts_short, $selected, $show_empty = 1, $key_in_label = 0, $value_as_key = 0, $option = '', $translate = 1, $maxlen = 0, $disabled = 0, $sort = '', $morecss = '');
1202  }
1203 
1204 
1210  public function loadCacheTypesTickets()
1211  {
1212  global $langs;
1213 
1214  if (!empty($this->cache_types_tickets) && count($this->cache_types_tickets)) {
1215  return 0;
1216  }
1217  // Cache deja charge
1218 
1219  $sql = "SELECT rowid, code, label, use_default, pos, description";
1220  $sql .= " FROM ".MAIN_DB_PREFIX."c_ticket_type";
1221  $sql .= " WHERE active > 0";
1222  $sql .= " ORDER BY pos";
1223  dol_syslog(get_class($this)."::load_cache_type_tickets", LOG_DEBUG);
1224  $resql = $this->db->query($sql);
1225  if ($resql) {
1226  $num = $this->db->num_rows($resql);
1227  $i = 0;
1228  while ($i < $num) {
1229  $obj = $this->db->fetch_object($resql);
1230  // Si traduction existe, on l'utilise, sinon on prend le libelle par defaut
1231  $label = ($langs->trans("TicketTypeShort".$obj->code) != ("TicketTypeShort".$obj->code) ? $langs->trans("TicketTypeShort".$obj->code) : ($obj->label != '-' ? $obj->label : ''));
1232  $this->cache_types_tickets[$obj->rowid]['code'] = $obj->code;
1233  $this->cache_types_tickets[$obj->rowid]['label'] = $label;
1234  $this->cache_types_tickets[$obj->rowid]['use_default'] = $obj->use_default;
1235  $this->cache_types_tickets[$obj->rowid]['pos'] = $obj->pos;
1236  $i++;
1237  }
1238  return $num;
1239  } else {
1240  dol_print_error($this->db);
1241  return -1;
1242  }
1243  }
1244 
1250  public function loadCacheCategoriesTickets()
1251  {
1252  global $conf, $langs;
1253 
1254  if (!empty($this->cache_category_ticket) && count($this->cache_category_tickets)) {
1255  // Cache already loaded
1256  return 0;
1257  }
1258 
1259  $sql = "SELECT rowid, code, label, use_default, pos, description, public, active, force_severity, fk_parent";
1260  $sql .= " FROM ".MAIN_DB_PREFIX."c_ticket_category";
1261  $sql .= " WHERE active > 0 AND entity = ".((int) $conf->entity);
1262  $sql .= " ORDER BY pos";
1263  dol_syslog(get_class($this)."::load_cache_categories_tickets", LOG_DEBUG);
1264  $resql = $this->db->query($sql);
1265  if ($resql) {
1266  $num = $this->db->num_rows($resql);
1267  $i = 0;
1268  while ($i < $num) {
1269  $obj = $this->db->fetch_object($resql);
1270  $this->cache_category_tickets[$obj->rowid]['code'] = $obj->code;
1271  $this->cache_category_tickets[$obj->rowid]['use_default'] = $obj->use_default;
1272  $this->cache_category_tickets[$obj->rowid]['pos'] = $obj->pos;
1273  $this->cache_category_tickets[$obj->rowid]['public'] = $obj->public;
1274  $this->cache_category_tickets[$obj->rowid]['active'] = $obj->active;
1275  $this->cache_category_tickets[$obj->rowid]['force_severity'] = $obj->force_severity;
1276  $this->cache_category_tickets[$obj->rowid]['fk_parent'] = $obj->fk_parent;
1277 
1278  // If translation exists, we use it to store already translated string.
1279  // Warning: You should not use this and recompute the translated string into caller code to get the value into expected language
1280  $label = ($langs->trans("TicketCategoryShort".$obj->code) != ("TicketCategoryShort".$obj->code) ? $langs->trans("TicketCategoryShort".$obj->code) : ($obj->label != '-' ? $obj->label : ''));
1281  $this->cache_category_tickets[$obj->rowid]['label'] = $label;
1282 
1283  $i++;
1284  }
1285  return $num;
1286  } else {
1287  dol_print_error($this->db);
1288  return -1;
1289  }
1290  }
1291 
1297  public function loadCacheSeveritiesTickets()
1298  {
1299  global $langs;
1300 
1301  if (!empty($this->cache_severity_tickets) && count($this->cache_severity_tickets)) {
1302  return 0;
1303  }
1304  // Cache deja charge
1305 
1306  $sql = "SELECT rowid, code, label, use_default, pos, description";
1307  $sql .= " FROM ".MAIN_DB_PREFIX."c_ticket_severity";
1308  $sql .= " WHERE active > 0";
1309  $sql .= " ORDER BY pos";
1310  dol_syslog(get_class($this)."::loadCacheSeveritiesTickets", LOG_DEBUG);
1311  $resql = $this->db->query($sql);
1312  if ($resql) {
1313  $num = $this->db->num_rows($resql);
1314  $i = 0;
1315  while ($i < $num) {
1316  $obj = $this->db->fetch_object($resql);
1317 
1318  $this->cache_severity_tickets[$obj->rowid]['code'] = $obj->code;
1319  // Si traduction existe, on l'utilise, sinon on prend le libelle par defaut
1320  $label = ($langs->trans("TicketSeverityShort".$obj->code) != ("TicketSeverityShort".$obj->code) ? $langs->trans("TicketSeverityShort".$obj->code) : ($obj->label != '-' ? $obj->label : ''));
1321  $this->cache_severity_tickets[$obj->rowid]['label'] = $label;
1322  $this->cache_severity_tickets[$obj->rowid]['use_default'] = $obj->use_default;
1323  $this->cache_severity_tickets[$obj->rowid]['pos'] = $obj->pos;
1324  $i++;
1325  }
1326  return $num;
1327  } else {
1328  dol_print_error($this->db);
1329  return -1;
1330  }
1331  }
1332 
1333 
1340  public function getLibStatut($mode = 0)
1341  {
1342  return $this->libStatut($this->fk_statut, $mode, 0, $this->progress);
1343  }
1344 
1345 
1346  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
1356  public function LibStatut($status, $mode = 0, $notooltip = 0, $progress = 0)
1357  {
1358  // phpcs:enable
1359  global $langs;
1360 
1361  $labelStatus = $this->statuts[$status];
1362  $labelStatusShort = $this->statuts_short[$status];
1363 
1364  if ($status == self::STATUS_NOT_READ) {
1365  $statusType = 'status0';
1366  } elseif ($status == self::STATUS_READ) {
1367  $statusType = 'status1';
1368  } elseif ($status == self::STATUS_ASSIGNED) {
1369  $statusType = 'status2';
1370  } elseif ($status == self::STATUS_IN_PROGRESS) {
1371  $statusType = 'status4';
1372  } elseif ($status == self::STATUS_WAITING) {
1373  $statusType = 'status7';
1374  } elseif ($status == self::STATUS_NEED_MORE_INFO) {
1375  $statusType = 'status3';
1376  } elseif ($status == self::STATUS_CANCELED) {
1377  $statusType = 'status9';
1378  } elseif ($status == self::STATUS_CLOSED) {
1379  $statusType = 'status6';
1380  } else {
1381  $labelStatus = 'Unknown';
1382  $labelStatusShort = 'Unknown';
1383  $statusType = 'status0';
1384  $mode = 0;
1385  }
1386 
1387  $params = array();
1388  if ($notooltip) {
1389  $params = array('tooltip' => 'no');
1390  }
1391 
1392  $labelStatus = $langs->transnoentitiesnoconv($labelStatus);
1393  $labelStatusShort = $langs->transnoentitiesnoconv($labelStatusShort);
1394 
1395  if ($status == self::STATUS_IN_PROGRESS && $progress > 0) {
1396  $labelStatus .= ' ('.round($progress).'%)';
1397  $labelStatusShort .= ' ('.round($progress).'%)';
1398  }
1399 
1400  return dolGetStatus($labelStatus, $labelStatusShort, '', $statusType, $mode, '', $params);
1401  }
1402 
1403 
1414  public function getNomUrl($withpicto = 0, $option = '', $notooltip = 0, $morecss = '', $save_lastsearch_value = -1)
1415  {
1416  global $db, $conf, $langs;
1417  global $dolibarr_main_authentication, $dolibarr_main_demo;
1418  global $menumanager;
1419 
1420  if (!empty($conf->dol_no_mouse_hover)) {
1421  $notooltip = 1; // Force disable tooltips
1422  }
1423 
1424  $result = '';
1425 
1426  $label = img_picto('', $this->picto).' <u class="paddingrightonly">'.$langs->trans("Ticket").'</u>';
1427  $label .= ' '.$this->getLibStatut(4);
1428  $label .= '<br>';
1429  $label .= '<b>'.$langs->trans('Ref').':</b> '.$this->ref.'<br>';
1430  $label .= '<b>'.$langs->trans('TicketTrackId').':</b> '.$this->track_id.'<br>';
1431  $label .= '<b>'.$langs->trans('Subject').':</b> '.$this->subject;
1432  if ($this->date_creation) {
1433  $label .= '<br><b>'.$langs->trans('DateCreation').':</b> '.$this->date_creation;
1434  }
1435  if ($this->date_modification) {
1436  $label .= '<br><b>'.$langs->trans('DateModification').':</b> '.$this->date_modification;
1437  }
1438  $url = DOL_URL_ROOT.'/ticket/card.php?id='.$this->id;
1439 
1440  if ($option != 'nolink') {
1441  // Add param to save lastsearch_values or not
1442  $add_save_lastsearch_values = ($save_lastsearch_value == 1 ? 1 : 0);
1443  if ($save_lastsearch_value == -1 && preg_match('/list\.php/', $_SERVER["PHP_SELF"])) {
1444  $add_save_lastsearch_values = 1;
1445  }
1446  if ($add_save_lastsearch_values) {
1447  $url .= '&save_lastsearch_values=1';
1448  }
1449  }
1450 
1451  $linkclose = '';
1452  if (empty($notooltip)) {
1453  if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
1454  $label = $langs->trans("ShowTicket");
1455  $linkclose .= ' alt="'.dol_escape_htmltag($label, 1).'"';
1456  }
1457  $linkclose .= ' title="'.dol_escape_htmltag($label, 1).'"';
1458  $linkclose .= ' class="classfortooltip'.($morecss ? ' '.$morecss : '').'"';
1459  } else {
1460  $linkclose = ($morecss ? ' class="'.$morecss.'"' : '');
1461  }
1462 
1463  $linkstart = '<a href="'.$url.'"';
1464  $linkstart .= $linkclose.'>';
1465  $linkend = '</a>';
1466 
1467  $result .= $linkstart;
1468  if ($withpicto) {
1469  $result .= img_object(($notooltip ? '' : $label), ($this->picto ? $this->picto : 'generic'), ($notooltip ? (($withpicto != 2) ? 'class="paddingright"' : '') : 'class="'.(($withpicto != 2) ? 'paddingright ' : '').'classfortooltip"'), 0, 0, $notooltip ? 0 : 1);
1470  }
1471  if ($withpicto != 2) {
1472  $result .= $this->ref;
1473  }
1474  $result .= $linkend;
1475  //if ($withpicto != 2) $result.=(($addlabel && $this->label) ? $sep . dol_trunc($this->label, ($addlabel > 1 ? $addlabel : 0)) : '');
1476 
1477  return $result;
1478  }
1479 
1480 
1488  public function markAsRead($user, $notrigger = 0)
1489  {
1490  global $conf, $langs;
1491 
1492  $error = 0;
1493 
1494  if ($this->statut != self::STATUS_CANCELED) { // no closed
1495  $this->oldcopy = dol_clone($this);
1496 
1497  $this->db->begin();
1498 
1499  $sql = "UPDATE ".MAIN_DB_PREFIX."ticket";
1500  $sql .= " SET fk_statut = ".Ticket::STATUS_READ.", date_read = '".$this->db->idate(dol_now())."'";
1501  $sql .= " WHERE rowid = ".((int) $this->id);
1502 
1503  dol_syslog(get_class($this)."::markAsRead");
1504  $resql = $this->db->query($sql);
1505  if ($resql) {
1506  $this->actionmsg = $langs->trans('TicketLogMesgReadBy', $this->ref, $user->getFullName($langs));
1507  $this->actionmsg2 = $langs->trans('TicketLogMesgReadBy', $this->ref, $user->getFullName($langs));
1508 
1509  if (!$error && !$notrigger) {
1510  // Call trigger
1511  $result = $this->call_trigger('TICKET_MODIFY', $user);
1512  if ($result < 0) {
1513  $error++;
1514  }
1515  // End call triggers
1516  }
1517 
1518  if (!$error) {
1519  $this->db->commit();
1520  return 1;
1521  } else {
1522  $this->db->rollback();
1523  $this->error = join(',', $this->errors);
1524  dol_syslog(get_class($this)."::markAsRead ".$this->error, LOG_ERR);
1525  return -1;
1526  }
1527  } else {
1528  $this->db->rollback();
1529  $this->error = $this->db->lasterror();
1530  dol_syslog(get_class($this)."::markAsRead ".$this->error, LOG_ERR);
1531  return -1;
1532  }
1533  }
1534  }
1535 
1544  public function assignUser($user, $id_assign_user, $notrigger = 0)
1545  {
1546  global $conf, $langs;
1547 
1548  $error = 0;
1549 
1550  $this->oldcopy = dol_clone($this);
1551 
1552  $this->db->begin();
1553 
1554  $sql = "UPDATE ".MAIN_DB_PREFIX."ticket";
1555  if ($id_assign_user > 0) {
1556  $sql .= " SET fk_user_assign=".((int) $id_assign_user).", fk_statut = ".Ticket::STATUS_ASSIGNED;
1557  } else {
1558  $sql .= " SET fk_user_assign=null, fk_statut = ".Ticket::STATUS_READ;
1559  }
1560  $sql .= " WHERE rowid = ".((int) $this->id);
1561 
1562  dol_syslog(get_class($this)."::assignUser sql=".$sql);
1563  $resql = $this->db->query($sql);
1564  if ($resql) {
1565  $this->fk_user_assign = $id_assign_user; // May be used by trigger
1566 
1567  if (!$notrigger) {
1568  // Call trigger
1569  $result = $this->call_trigger('TICKET_ASSIGNED', $user);
1570  if ($result < 0) {
1571  $error++;
1572  }
1573  // End call triggers
1574  }
1575 
1576  if (!$error) {
1577  $this->db->commit();
1578  return 1;
1579  } else {
1580  $this->db->rollback();
1581  $this->error = join(',', $this->errors);
1582  dol_syslog(get_class($this)."::assignUser ".$this->error, LOG_ERR);
1583  return -1;
1584  }
1585  } else {
1586  $this->db->rollback();
1587  $this->error = $this->db->lasterror();
1588  dol_syslog(get_class($this)."::assignUser ".$this->error, LOG_ERR);
1589  return -1;
1590  }
1591  }
1592 
1604  public function createTicketMessage($user, $notrigger = 0, $filename_list = array(), $mimetype_list = array(), $mimefilename_list = array(), $send_email = false)
1605  {
1606  global $conf, $langs;
1607  $error = 0;
1608 
1609  $now = dol_now();
1610 
1611  // Clean parameters
1612  if (isset($this->fk_track_id)) {
1613  $this->fk_track_id = trim($this->fk_track_id);
1614  }
1615 
1616  if (isset($this->message)) {
1617  $this->message = trim($this->message);
1618  }
1619 
1620  $this->db->begin();
1621 
1622  // Insert entry into agenda with code 'TICKET_MSG'
1623  include_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
1624  $actioncomm = new ActionComm($this->db);
1625  $actioncomm->type_code = 'AC_OTH_AUTO'; // This is not an entry that must appears into manual calendar but only into CRM calendar
1626  $actioncomm->code = 'TICKET_MSG';
1627  if ($this->private) {
1628  $actioncomm->code = 'TICKET_MSG_PRIVATE';
1629  }
1630  if ($send_email) {
1631  $actioncomm->code .= '_SENTBYMAIL';
1632  }
1633  $actioncomm->socid = $this->socid;
1634  $actioncomm->label = $this->subject;
1635  $actioncomm->note_private = $this->message;
1636  $actioncomm->userassigned = array($user->id);
1637  $actioncomm->userownerid = $user->id;
1638  $actioncomm->datep = $now;
1639  $actioncomm->percentage = -1; // percentage is not relevant for punctual events
1640  $actioncomm->elementtype = 'ticket';
1641  $actioncomm->fk_element = $this->id;
1642  $actioncomm->fk_project = $this->fk_project;
1643 
1644  $attachedfiles = array();
1645  $attachedfiles['paths'] = $filename_list;
1646  $attachedfiles['names'] = $mimefilename_list;
1647  $attachedfiles['mimes'] = $mimetype_list;
1648  if (is_array($attachedfiles) && count($attachedfiles) > 0) {
1649  $actioncomm->attachedfiles = $attachedfiles;
1650  }
1651 
1652  if (!empty($mimefilename_list) && is_array($mimefilename_list)) {
1653  $actioncomm->note_private = dol_concatdesc($actioncomm->note_private, "\n".$langs->transnoentities("AttachedFiles").': '.join(';', $mimefilename_list));
1654  }
1655 
1656  $actionid = $actioncomm->create($user);
1657  if ($actionid <= 0) {
1658  $error++;
1659  $this->error = $actioncomm->error;
1660  $this->errors = $actioncomm->errors;
1661  }
1662 
1663  // Commit or rollback
1664  if ($error) {
1665  $this->db->rollback();
1666  return -1 * $error;
1667  } else {
1668  $this->db->commit();
1669  return 1;
1670  }
1671  }
1672 
1678  public function loadCacheMsgsTicket()
1679  {
1680  if (!empty($this->cache_msgs_ticket) && is_array($this->cache_msgs_ticket) && count($this->cache_msgs_ticket)) {
1681  return 0;
1682  }
1683 
1684  // Cache already loaded
1685 
1686  $sql = "SELECT id as rowid, fk_user_author, datec, label, note as message, code";
1687  $sql .= " FROM ".MAIN_DB_PREFIX."actioncomm";
1688  $sql .= " WHERE fk_element = ".(int) $this->id;
1689  $sql .= " AND elementtype = 'ticket'";
1690  $sql .= " ORDER BY datec DESC";
1691 
1692  dol_syslog(get_class($this)."::load_cache_actions_ticket", LOG_DEBUG);
1693  $resql = $this->db->query($sql);
1694  if ($resql) {
1695  $num = $this->db->num_rows($resql);
1696  $i = 0;
1697  while ($i < $num) {
1698  $obj = $this->db->fetch_object($resql);
1699  $this->cache_msgs_ticket[$i]['id'] = $obj->rowid;
1700  $this->cache_msgs_ticket[$i]['fk_user_author'] = $obj->fk_user_author;
1701  $this->cache_msgs_ticket[$i]['datec'] = $this->db->jdate($obj->datec);
1702  $this->cache_msgs_ticket[$i]['subject'] = $obj->label;
1703  $this->cache_msgs_ticket[$i]['message'] = $obj->message;
1704  $this->cache_msgs_ticket[$i]['private'] = (preg_match('/^TICKET_MSG_PRIVATE/', $obj->code) ? 1 : 0);
1705  $i++;
1706  }
1707  return $num;
1708  } else {
1709  $this->error = "Error ".$this->db->lasterror();
1710  dol_syslog(get_class($this)."::load_cache_actions_ticket ".$this->error, LOG_ERR);
1711  return -1;
1712  }
1713  }
1714 
1722  public function close(User $user, $mode = 0)
1723  {
1724  global $conf, $langs;
1725 
1726  if ($this->fk_statut != Ticket::STATUS_CLOSED && $this->fk_statut != Ticket::STATUS_CANCELED) { // not closed
1727  $this->db->begin();
1728 
1729  $sql = "UPDATE ".MAIN_DB_PREFIX."ticket";
1730  $sql .= " SET fk_statut=".($mode ? Ticket::STATUS_CANCELED : Ticket::STATUS_CLOSED).", progress=100, date_close='".$this->db->idate(dol_now())."'";
1731  $sql .= " WHERE rowid = ".((int) $this->id);
1732 
1733  dol_syslog(get_class($this)."::close mode=".$mode);
1734  $resql = $this->db->query($sql);
1735  if ($resql) {
1736  $error = 0;
1737 
1738  // Valid and close fichinter linked
1739  if (isModEnabled('ficheinter') && !empty($conf->global->WORKFLOW_TICKET_CLOSE_INTERVENTION)) {
1740  dol_syslog("We have closed the ticket, so we close all linked interventions");
1741  $this->fetchObjectLinked($this->id, $this->element, null, 'fichinter');
1742  if ($this->linkedObjectsIds) {
1743  foreach ($this->linkedObjectsIds['fichinter'] as $fichinter_id) {
1744  $fichinter = new Fichinter($this->db);
1745  $fichinter->fetch($fichinter_id);
1746  if ($fichinter->statut == 0) {
1747  $result = $fichinter->setValid($user);
1748  if (!$result) {
1749  $this->errors[] = $fichinter->error;
1750  $error++;
1751  }
1752  }
1753  if ($fichinter->statut < 3) {
1754  $result = $fichinter->setStatut(3);
1755  if (!$result) {
1756  $this->errors[] = $fichinter->error;
1757  $error++;
1758  }
1759  }
1760  }
1761  }
1762  }
1763 
1764  // Call trigger
1765  $result = $this->call_trigger('TICKET_CLOSE', $user);
1766  if ($result < 0) {
1767  $error++;
1768  }
1769  // End call triggers
1770 
1771  if (!$error) {
1772  $this->db->commit();
1773  return 1;
1774  } else {
1775  $this->db->rollback();
1776  $this->error = join(',', $this->errors);
1777  dol_syslog(get_class($this)."::close ".$this->error, LOG_ERR);
1778  return -1;
1779  }
1780  } else {
1781  $this->db->rollback();
1782  $this->error = $this->db->lasterror();
1783  dol_syslog(get_class($this)."::close ".$this->error, LOG_ERR);
1784  return -1;
1785  }
1786  }
1787  }
1788 
1798  public function searchSocidByEmail($email, $type = '0', $filters = array(), $clause = 'AND')
1799  {
1800  $thirdparties = array();
1801  $exact = 0;
1802 
1803  // Generation requete recherche
1804  $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."societe";
1805  $sql .= " WHERE entity IN (".getEntity('ticket', 1).")";
1806  if (!empty($type)) {
1807  if ($type == 1 || $type == 2) {
1808  $sql .= " AND client = ".((int) $type);
1809  } elseif ($type == 3) {
1810  $sql .= " AND fournisseur = 1";
1811  }
1812  }
1813  if (!empty($email)) {
1814  if (empty($exact)) {
1815  $regs = array();
1816  if (preg_match('/^([\*])?[^*]+([\*])?$/', $email, $regs) && count($regs) > 1) {
1817  $email = str_replace('*', '%', $email);
1818  } else {
1819  $email = '%'.$email.'%';
1820  }
1821  }
1822  $sql .= " AND ";
1823  if (is_array($filters) && !empty($filters)) {
1824  $sql .= "(";
1825  }
1826 
1827  $sql .= "email LIKE '".$this->db->escape($email)."'";
1828  }
1829  if (is_array($filters) && !empty($filters)) {
1830  foreach ($filters as $field => $value) {
1831  $sql .= " ".$clause." ".$field." LIKE '".$this->db->escape($value)."'";
1832  }
1833  if (!empty($email)) {
1834  $sql .= ")";
1835  }
1836  }
1837 
1838  $res = $this->db->query($sql);
1839  if ($res) {
1840  while ($rec = $this->db->fetch_array($res)) {
1841  $soc = new Societe($this->db);
1842  $soc->fetch($rec['rowid']);
1843  $thirdparties[] = $soc;
1844  }
1845 
1846  return $thirdparties;
1847  } else {
1848  $this->error = $this->db->error().' sql='.$sql;
1849  dol_syslog(get_class($this)."::searchSocidByEmail ".$this->error, LOG_ERR);
1850  return -1;
1851  }
1852  }
1853 
1862  public function searchContactByEmail($email, $socid = '', $case = '')
1863  {
1864  $contacts = array();
1865 
1866  // Generation requete recherche
1867  $sql = "SELECT rowid FROM ".MAIN_DB_PREFIX."socpeople";
1868  $sql .= " WHERE entity IN (".getEntity('contact').")";
1869  if (!empty($socid)) {
1870  $sql .= " AND fk_soc='".$this->db->escape($socid)."'";
1871  }
1872 
1873  if (!empty($email)) {
1874  $sql .= " AND ";
1875 
1876  if (!$case) {
1877  $sql .= "email LIKE '".$this->db->escape($email)."'";
1878  } else {
1879  $sql .= "email LIKE BINARY '".$this->db->escape($email)."'";
1880  }
1881  }
1882 
1883  $res = $this->db->query($sql);
1884  if ($res) {
1885  while ($rec = $this->db->fetch_array($res)) {
1886  include_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
1887  $contactstatic = new Contact($this->db);
1888  $contactstatic->fetch($rec['rowid']);
1889  $contacts[] = $contactstatic;
1890  }
1891 
1892  return $contacts;
1893  } else {
1894  $this->error = $this->db->error().' sql='.$sql;
1895  dol_syslog(get_class($this)."::searchContactByEmail ".$this->error, LOG_ERR);
1896  return -1;
1897  }
1898  }
1899 
1906  public function setCustomer($id)
1907  {
1908  if ($this->id) {
1909  $sql = "UPDATE ".MAIN_DB_PREFIX."ticket";
1910  $sql .= " SET fk_soc = ".($id > 0 ? $id : "null");
1911  $sql .= " WHERE rowid = ".((int) $this->id);
1912  dol_syslog(get_class($this).'::setCustomer sql='.$sql);
1913  $resql = $this->db->query($sql);
1914  if ($resql) {
1915  return 1;
1916  } else {
1917  return -1;
1918  }
1919  } else {
1920  return -1;
1921  }
1922  }
1923 
1930  public function setProgression($percent)
1931  {
1932  if ($this->id) {
1933  $sql = "UPDATE ".MAIN_DB_PREFIX."ticket";
1934  $sql .= " SET progress = ".($percent > 0 ? $percent : "null");
1935  $sql .= " WHERE rowid = ".((int) $this->id);
1936  dol_syslog(get_class($this).'::set_progression sql='.$sql);
1937  $resql = $this->db->query($sql);
1938  if ($resql) {
1939  return 1;
1940  } else {
1941  return -1;
1942  }
1943  } else {
1944  return -1;
1945  }
1946  }
1947 
1954  public function setContract($contractid)
1955  {
1956  if (!$this->table_element) {
1957  dol_syslog(get_class($this)."::setContract was called on objet with property table_element not defined", LOG_ERR);
1958  return -1;
1959  }
1960 
1961  $result = $this->add_object_linked('contrat', $contractid);
1962  if ($result) {
1963  $this->fk_contract = $contractid;
1964  return 1;
1965  } else {
1966  dol_print_error($this->db);
1967  return -1;
1968  }
1969  }
1970 
1971  /* gestion des contacts d'un ticket */
1972 
1978  public function getIdTicketInternalContact()
1979  {
1980  return $this->getIdContact('internal', 'SUPPORTTEC');
1981  }
1982 
1989  {
1990  return $this->listeContact(-1, 'internal');
1991  }
1992 
1998  public function getIdTicketCustomerContact()
1999  {
2000  return $this->getIdContact('external', 'SUPPORTCLI');
2001  }
2002 
2009  {
2010  return $this->listeContact(-1, 'external');
2011  }
2012 
2019  {
2020  return $this->getIdContact('internal', 'CONTRIBUTOR');
2021  }
2022 
2029  {
2030  return $this->getIdContact('external', 'CONTRIBUTOR');
2031  }
2032 
2038  public function getTicketAllContacts()
2039  {
2040  $array_contact = array();
2041 
2042  $array_contact = $this->getIdTicketInternalContact();
2043 
2044  $array_contact = array_merge($array_contact, $this->getIdTicketCustomerContact());
2045 
2046  $array_contact = array_merge($array_contact, $this->getIdTicketInternalInvolvedContact());
2047 
2048  $array_contact = array_merge($array_contact, $this->getIdTicketCustomerInvolvedContact());
2049 
2050  return $array_contact;
2051  }
2052 
2059  {
2060  $array_contact = array();
2061 
2062  $array_contact = array_merge($array_contact, $this->getIdTicketCustomerContact());
2063 
2064  $array_contact = array_merge($array_contact, $this->getIdTicketCustomerInvolvedContact());
2065 
2066  return $array_contact;
2067  }
2068 
2069 
2080  public function listeContact($status = -1, $source = 'external', $list = 0, $code = '')
2081  {
2082  global $langs;
2083 
2084  $tab = array();
2085 
2086  $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
2087  if ($source == 'internal') {
2088  $sql .= ", '-1' as socid, t.statut as statuscontact";
2089  }
2090 
2091  if ($source == 'external' || $source == 'thirdparty') {
2092  $sql .= ", t.fk_soc as socid, t.statut as statuscontact";
2093  }
2094 
2095  $sql .= ", t.civility, t.lastname as lastname, t.firstname, t.email";
2096  if ($source == 'internal') {
2097  $sql .= ", t.office_phone as phone, t.user_mobile as phone_mobile";
2098  }
2099 
2100  if ($source == 'external') {
2101  $sql .= ", t.phone as phone, t.phone_mobile as phone_mobile, t.phone_perso as phone_perso";
2102  }
2103 
2104  $sql .= ", tc.source, tc.element, tc.code, tc.libelle as type_contact_label";
2105  $sql .= " FROM ".MAIN_DB_PREFIX."c_type_contact tc";
2106  $sql .= ", ".MAIN_DB_PREFIX."element_contact ec";
2107  if ($source == 'internal') {
2108  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."user t on ec.fk_socpeople = t.rowid";
2109  }
2110 
2111  if ($source == 'external' || $source == 'thirdparty') {
2112  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."socpeople t on ec.fk_socpeople = t.rowid";
2113  }
2114 
2115  $sql .= " WHERE ec.element_id = ".((int) $this->id);
2116  $sql .= " AND ec.fk_c_type_contact=tc.rowid";
2117  $sql .= " AND tc.element='".$this->db->escape($this->element)."'";
2118  if ($source == 'internal') {
2119  $sql .= " AND tc.source = 'internal'";
2120  }
2121 
2122  if ($source == 'external' || $source == 'thirdparty') {
2123  $sql .= " AND tc.source = 'external'";
2124  }
2125 
2126  if (!empty($code)) {
2127  $sql .= " AND tc.code = '".$this->db->escape($code)."'";
2128  }
2129 
2130  $sql .= " AND tc.active=1";
2131  if ($status >= 0) {
2132  $sql .= " AND ec.statut = ".((int) $status);
2133  }
2134 
2135  $sql .= " ORDER BY t.lastname ASC";
2136 
2137  $resql = $this->db->query($sql);
2138  if ($resql) {
2139  $num = $this->db->num_rows($resql);
2140  $i = 0;
2141  while ($i < $num) {
2142  $obj = $this->db->fetch_object($resql);
2143 
2144  if (!$list) {
2145  $transkey = "TypeContact_".$obj->element."_".$obj->source."_".$obj->code;
2146  $libelle_type = ($langs->trans($transkey) != $transkey ? $langs->trans($transkey) : $obj->type_contact_label);
2147  $tab[$i] = array(
2148  'source' => $obj->source,
2149  'socid' => $obj->socid,
2150  'id' => $obj->id,
2151  'nom' => $obj->lastname, // For backward compatibility
2152  'civility' => $obj->civility,
2153  'lastname' => $obj->lastname,
2154  'firstname' => $obj->firstname,
2155  'email' => $obj->email,
2156  'rowid' => $obj->rowid,
2157  'code' => $obj->code,
2158  'libelle' => $libelle_type,
2159  'status' => $obj->statuslink,
2160  'statuscontact'=>$obj->statuscontact,
2161  'fk_c_type_contact' => $obj->fk_c_type_contact,
2162  'phone' => $obj->phone,
2163  'phone_mobile' => $obj->phone_mobile);
2164  } else {
2165  $tab[$i] = $obj->id;
2166  }
2167 
2168  $i++;
2169  }
2170 
2171  return $tab;
2172  } else {
2173  $this->error = $this->db->error();
2174  dol_print_error($this->db);
2175  return -1;
2176  }
2177  }
2178 
2185  public function getDefaultRef($thirdparty = '')
2186  {
2187  global $conf;
2188 
2189  $defaultref = '';
2190  $modele = empty($conf->global->TICKET_ADDON) ? 'mod_ticket_simple' : $conf->global->TICKET_ADDON;
2191 
2192  // Search template files
2193  $file = '';
2194  $classname = '';
2195  $filefound = 0;
2196  $dirmodels = array_merge(array('/'), (array) $conf->modules_parts['models']);
2197  foreach ($dirmodels as $reldir) {
2198  $file = dol_buildpath($reldir."core/modules/ticket/".$modele.'.php', 0);
2199  if (file_exists($file)) {
2200  $filefound = 1;
2201  $classname = $modele;
2202  break;
2203  }
2204  }
2205 
2206  if ($filefound) {
2207  $result = dol_include_once($reldir."core/modules/ticket/".$modele.'.php');
2208  $modTicket = new $classname;
2209 
2210  $defaultref = $modTicket->getNextValue($thirdparty, $this);
2211  }
2212 
2213  if (is_numeric($defaultref) && $defaultref <= 0) {
2214  $defaultref = '';
2215  }
2216 
2217  return $defaultref;
2218  }
2219 
2220 
2221  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2228  public function is_photo_available($sdir)
2229  {
2230  // phpcs:enable
2231  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2232 
2233  global $conf;
2234 
2235  $dir = $sdir.'/';
2236  $nbphoto = 0;
2237 
2238  $dir_osencoded = dol_osencode($dir);
2239  if (file_exists($dir_osencoded)) {
2240  $handle = opendir($dir_osencoded);
2241  if (is_resource($handle)) {
2242  while (($file = readdir($handle)) !== false) {
2243  if (!utf8_check($file)) {
2244  $file = utf8_encode($file); // To be sure data is stored in UTF8 in memory
2245  }
2246  if (dol_is_file($dir.$file)) {
2247  return true;
2248  }
2249  }
2250  }
2251  }
2252  return false;
2253  }
2254 
2255 
2263  public function copyFilesForTicket()
2264  {
2265  global $conf;
2266 
2267  // Create form object
2268  include_once DOL_DOCUMENT_ROOT.'/core/class/html.formmail.class.php';
2269  include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2270  include_once DOL_DOCUMENT_ROOT.'/core/lib/images.lib.php';
2271 
2272  $maxwidthsmall = 270;
2273  $maxheightsmall = 150;
2274  $maxwidthmini = 128;
2275  $maxheightmini = 72;
2276 
2277  $formmail = new FormMail($this->db);
2278 
2279  $attachedfiles = $formmail->get_attached_files();
2280 
2281  $filepath = $attachedfiles['paths'];
2282  $filename = $attachedfiles['names'];
2283  $mimetype = $attachedfiles['mimes'];
2284 
2285  // Copy files into ticket directory
2286  $destdir = $conf->ticket->dir_output.'/'.$this->ref;
2287 
2288  if (!dol_is_dir($destdir)) {
2289  dol_mkdir($destdir);
2290  }
2291 
2292  $listofpaths = array();
2293  $listofnames = array();
2294  foreach ($filename as $i => $val) {
2295  $destfile = $destdir.'/'.$filename[$i];
2296  // If destination file already exists, we add a suffix to avoid to overwrite
2297  if (is_file($destfile)) {
2298  $pathinfo = pathinfo($filename[$i]);
2299  $now = dol_now();
2300  $destfile = $destdir.'/'.$pathinfo['filename'].' - '.dol_print_date($now, 'dayhourlog').'.'.$pathinfo['extension'];
2301  }
2302 
2303  $res = dol_move($filepath[$i], $destfile, 0, 1);
2304 
2305  if (image_format_supported($destfile) == 1) {
2306  // Create small thumbs for image (Ratio is near 16/9)
2307  // Used on logon for example
2308  $imgThumbSmall = vignette($destfile, $maxwidthsmall, $maxheightsmall, '_small', 50, "thumbs");
2309  // Create mini thumbs for image (Ratio is near 16/9)
2310  // Used on menu or for setup page for example
2311  $imgThumbMini = vignette($destfile, $maxwidthmini, $maxheightmini, '_mini', 50, "thumbs");
2312  }
2313 
2314  $formmail->remove_attached_files($i);
2315 
2316  // Fill array with new names
2317  $listofpaths[$i] = $destfile;
2318  $listofnames[$i] = basename($destfile);
2319  }
2320 
2321  return array('listofpaths'=>$listofpaths, 'listofnames'=>$listofnames, 'listofmimes'=>$mimetype);
2322  }
2323 
2334  public function setCategories($categories)
2335  {
2336  // Handle single category
2337  if (!is_array($categories)) {
2338  $categories = array($categories);
2339  }
2340 
2341  // Get current categories
2342  include_once DOL_DOCUMENT_ROOT.'/categories/class/categorie.class.php';
2343  $c = new Categorie($this->db);
2344  $existing = $c->containing($this->id, Categorie::TYPE_TICKET, 'id');
2345 
2346  // Diff
2347  if (is_array($existing)) {
2348  $to_del = array_diff($existing, $categories);
2349  $to_add = array_diff($categories, $existing);
2350  } else {
2351  $to_del = array(); // Nothing to delete
2352  $to_add = $categories;
2353  }
2354 
2355  // Process
2356  foreach ($to_del as $del) {
2357  if ($c->fetch($del) > 0) {
2358  $c->del_type($this, Categorie::TYPE_TICKET);
2359  }
2360  }
2361  foreach ($to_add as $add) {
2362  if ($c->fetch($add) > 0) {
2363  $c->add_type($this, Categorie::TYPE_TICKET);
2364  }
2365  }
2366 
2367  return 1;
2368  }
2369 
2380  public function newMessage($user, &$action, $private = 1, $public_area = 0)
2381  {
2382  global $mysoc, $conf, $langs;
2383 
2384  $error = 0;
2385 
2386  $object = new Ticket($this->db);
2387 
2388  $ret = $object->fetch('', '', GETPOST('track_id', 'alpha'));
2389 
2390  $object->socid = $object->fk_soc;
2391  $object->fetch_thirdparty();
2392  $object->fetch_project();
2393 
2394  if ($ret < 0) {
2395  $error++;
2396  array_push($this->errors, $langs->trans("ErrorTicketIsNotValid"));
2397  $action = '';
2398  }
2399 
2400  if (!GETPOST("message")) {
2401  $error++;
2402  array_push($this->errors, $langs->trans("ErrorFieldRequired", $langs->transnoentities("message")));
2403  $action = 'add_message';
2404  }
2405 
2406  if (!$error) {
2407  $object->subject = GETPOST('subject', 'alphanohtml');
2408  $object->message = GETPOST("message", "restricthtml");
2409  $object->private = GETPOST("private_message", "alpha");
2410 
2411  $send_email = GETPOST('send_email', 'int');
2412 
2413  // Copy attached files (saved into $_SESSION) as linked files to ticket. Return array with final name used.
2414  $resarray = $object->copyFilesForTicket();
2415 
2416  $listofpaths = $resarray['listofpaths'];
2417  $listofnames = $resarray['listofnames'];
2418  $listofmimes = $resarray['listofmimes'];
2419 
2420  $id = $object->createTicketMessage($user, 0, $listofpaths, $listofmimes, $listofnames, $send_email);
2421  if ($id <= 0) {
2422  $error++;
2423  $this->error = $object->error;
2424  $this->errors = $object->errors;
2425  $action = 'add_message';
2426  }
2427 
2428  if (!$error && $id > 0) {
2429  setEventMessages($langs->trans('TicketMessageSuccessfullyAdded'), null, 'mesgs');
2430 
2431  //var_dump($_SESSION);
2432  //var_dump($listofpaths);exit;
2433 
2434  if (!empty($public_area)) {
2435  /*
2436  * Message created from the Public interface
2437  *
2438  * Send emails to assigned users (public area notification)
2439  */
2440  if (!empty($conf->global->TICKET_PUBLIC_NOTIFICATION_NEW_MESSAGE_ENABLED)) {
2441  // Retrieve internal contact datas
2442  $internal_contacts = $object->getInfosTicketInternalContact();
2443 
2444  $assigned_user_dont_have_email = '';
2445 
2446  $sendto = array();
2447 
2448  if ($this->fk_user_assign > 0) {
2449  $assigned_user = new User($this->db);
2450  $assigned_user->fetch($this->fk_user_assign);
2451  if (!empty($assigned_user->email)) {
2452  $sendto[] = $assigned_user->getFullName($langs)." <".$assigned_user->email.">";
2453  } else {
2454  $assigned_user_dont_have_email = $assigned_user->getFullName($langs);
2455  }
2456  }
2457 
2458  // Build array to display recipient list
2459  foreach ($internal_contacts as $key => $info_sendto) {
2460  // Avoid duplicate notifications
2461  if ($info_sendto['id'] == $user->id) {
2462  continue;
2463  }
2464 
2465  if ($info_sendto['email'] != '') {
2466  if (!empty($info_sendto['email'])) {
2467  $sendto[] = dolGetFirstLastname($info_sendto['firstname'], $info_sendto['lastname'])." <".$info_sendto['email'].">";
2468  }
2469  }
2470  }
2471 
2472  if (empty($sendto)) {
2473  if (!empty($conf->global->TICKET_PUBLIC_NOTIFICATION_NEW_MESSAGE_DEFAULT_EMAIL)) {
2474  $sendto[] = $conf->global->TICKET_PUBLIC_NOTIFICATION_NEW_MESSAGE_DEFAULT_EMAIL;
2475  } elseif (!empty($conf->global->TICKET_NOTIFICATION_EMAIL_TO)) {
2476  $sendto[] = $conf->global->TICKET_NOTIFICATION_EMAIL_TO;
2477  }
2478  }
2479 
2480  // Add global email address recipient
2481  if (!empty($conf->global->TICKET_NOTIFICATION_ALSO_MAIN_ADDRESS) &&
2482  !empty($conf->global->TICKET_NOTIFICATION_EMAIL_TO) && !in_array($conf->global->TICKET_NOTIFICATION_EMAIL_TO, $sendto)
2483  ) {
2484  $sendto[] = $conf->global->TICKET_NOTIFICATION_EMAIL_TO;
2485  }
2486 
2487  if (!empty($sendto)) {
2488  $label_title = empty($conf->global->MAIN_APPLICATION_TITLE) ? $mysoc->name : $conf->global->MAIN_APPLICATION_TITLE;
2489  $subject = '['.$label_title.'- ticket #'.$object->track_id.'] '.$langs->trans('TicketNewMessage');
2490 
2491  // Message send
2492  $message = $langs->trans('TicketMessageMailIntroText');
2493  $message .= '<br><br>';
2494  $messagePost = GETPOST('message', 'restricthtml');
2495  if (!dol_textishtml($messagePost)) {
2496  $messagePost = dol_nl2br($messagePost);
2497  }
2498  $message .= $messagePost;
2499 
2500  // Customer company infos
2501  $message .= '<br><br>';
2502  $message .= "==============================================";
2503  $message .= !empty($object->thirdparty->name) ? '<br>'.$langs->trans('Thirdparty')." : ".$object->thirdparty->name : '';
2504  $message .= !empty($object->thirdparty->town) ? '<br>'.$langs->trans('Town')." : ".$object->thirdparty->town : '';
2505  $message .= !empty($object->thirdparty->phone) ? '<br>'.$langs->trans('Phone')." : ".$object->thirdparty->phone : '';
2506 
2507  // Email send to
2508  $message .= '<br><br>';
2509  if (!empty($assigned_user_dont_have_email)) {
2510  $message .= '<br>'.$langs->trans('NoEMail').' : '.$assigned_user_dont_have_email;
2511  }
2512  foreach ($sendto as $val) {
2513  $message .= '<br>'.$langs->trans('TicketNotificationRecipient').' : '.$val;
2514  }
2515 
2516  // URL ticket
2517  $url_internal_ticket = dol_buildpath('/ticket/card.php', 2).'?track_id='.$object->track_id;
2518  $message .= '<br><br>';
2519  $message .= $langs->trans('TicketNotificationEmailBodyInfosTrackUrlinternal').' : <a href="'.$url_internal_ticket.'">'.$object->track_id.'</a>';
2520 
2521  $this->sendTicketMessageByEmail($subject, $message, '', $sendto, $listofpaths, $listofmimes, $listofnames);
2522  }
2523  }
2524  } else {
2525  /*
2526  * Message send from the Backoffice / Private area
2527  *
2528  * 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)
2529  */
2530  if ($send_email > 0) {
2531  // Retrieve internal contact datas
2532  $internal_contacts = $object->getInfosTicketInternalContact();
2533 
2534  $sendto = array();
2535  if (is_array($internal_contacts) && count($internal_contacts) > 0) {
2536  // Set default subject
2537  $label_title = empty($conf->global->MAIN_APPLICATION_TITLE) ? $mysoc->name : $conf->global->MAIN_APPLICATION_TITLE;
2538  $appli = $label_title;
2539  $subject = GETPOST('subject', 'alphanohtml') ? GETPOST('subject', 'alphanohtml') : '['.$appli.' - '.$langs->trans("Ticket").' #'.$object->track_id.'] '.$langs->trans('TicketNewMessage');
2540 
2541  $message_intro = $langs->trans('TicketNotificationEmailBody', "#".$object->id);
2542  $message_signature = GETPOST('mail_signature') ? GETPOST('mail_signature') : getDolGlobalString('TICKET_MESSAGE_MAIL_SIGNATURE');
2543 
2544  $message = $langs->trans('TicketMessageMailIntroText');
2545  $message .= '<br><br>';
2546  $messagePost = GETPOST('message', 'restricthtml');
2547  if (!dol_textishtml($messagePost)) {
2548  $messagePost = dol_nl2br($messagePost);
2549  }
2550  $message .= $messagePost;
2551 
2552  // Data about customer
2553  $message .= '<br><br>';
2554  $message .= "==============================================<br>";
2555  $message .= !empty($object->thirdparty->name) ? $langs->trans('Thirdparty')." : ".$object->thirdparty->name : '';
2556  $message .= !empty($object->thirdparty->town) ? '<br>'.$langs->trans('Town')." : ".$object->thirdparty->town : '';
2557  $message .= !empty($object->thirdparty->phone) ? '<br>'.$langs->trans('Phone')." : ".$object->thirdparty->phone : '';
2558 
2559  // Build array to display recipient list
2560  foreach ($internal_contacts as $key => $info_sendto) {
2561  // Avoid duplicate notifications
2562  if ($info_sendto['id'] == $user->id) {
2563  continue;
2564  }
2565 
2566  if ($info_sendto['email'] != '') {
2567  if (!empty($info_sendto['email'])) {
2568  $sendto[] = dolGetFirstLastname($info_sendto['firstname'], $info_sendto['lastname'])." <".$info_sendto['email'].">";
2569  }
2570 
2571  // Contact type
2572  $recipient = dolGetFirstLastname($info_sendto['firstname'], $info_sendto['lastname'], '-1').' ('.strtolower($info_sendto['libelle']).')';
2573  $message .= (!empty($recipient) ? $langs->trans('TicketNotificationRecipient').' : '.$recipient.'<br>' : '');
2574  }
2575  }
2576  $message .= '<br>';
2577  // URL ticket
2578  $url_internal_ticket = dol_buildpath('/ticket/card.php', 2).'?track_id='.$object->track_id;
2579 
2580  // Add html link on url
2581  $message .= '<br>'.$langs->trans('TicketNotificationEmailBodyInfosTrackUrlinternal').' : <a href="'.$url_internal_ticket.'">'.$object->track_id.'</a><br>';
2582 
2583  // Add global email address recipient
2584  if ($conf->global->TICKET_NOTIFICATION_ALSO_MAIN_ADDRESS && !in_array($conf->global->TICKET_NOTIFICATION_EMAIL_TO, $sendto)) {
2585  if (!empty($conf->global->TICKET_NOTIFICATION_EMAIL_TO)) {
2586  $sendto[] = $conf->global->TICKET_NOTIFICATION_EMAIL_TO;
2587  }
2588  }
2589 
2590  // dont try to send email if no recipient
2591  if (!empty($sendto)) {
2592  $this->sendTicketMessageByEmail($subject, $message, '', $sendto, $listofpaths, $listofmimes, $listofnames);
2593  }
2594  }
2595 
2596  /*
2597  * Send emails for externals users if not private (linked contacts)
2598  */
2599  if (empty($object->private)) {
2600  // Retrieve email of all contacts (external)
2601  $external_contacts = $object->getInfosTicketExternalContact();
2602 
2603  // If no contact, get email from thirdparty
2604  if (is_array($external_contacts) && count($external_contacts) === 0) {
2605  if (!empty($object->fk_soc)) {
2606  $object->fetch_thirdparty($object->fk_soc);
2607  $array_company = array(array('firstname' => '', 'lastname' => $object->thirdparty->name, 'email' => $object->thirdparty->email, 'libelle' => $langs->transnoentities('Customer'), 'socid' => $object->thirdparty->id));
2608  $external_contacts = array_merge($external_contacts, $array_company);
2609  } elseif (empty($object->fk_soc) && !empty($object->origin_email)) {
2610  $array_external = array(array('firstname' => '', 'lastname' => $object->origin_email, 'email' => $object->thirdparty->email, 'libelle' => $langs->transnoentities('Customer'), 'socid' => $object->thirdparty->id));
2611  $external_contacts = array_merge($external_contacts, $array_external);
2612  }
2613  }
2614 
2615  $sendto = array();
2616  if (is_array($external_contacts) && count($external_contacts) > 0) {
2617  // Get default subject for email to external contacts
2618  $label_title = empty($conf->global->MAIN_APPLICATION_TITLE) ? $mysoc->name : $conf->global->MAIN_APPLICATION_TITLE;
2619  $appli = $mysoc->name;
2620  $subject = GETPOST('subject') ? GETPOST('subject') : '['.$appli.' - '.$langs->trans("Ticket").' #'.$object->track_id.'] '.$langs->trans('TicketNewMessage');
2621 
2622  $message_intro = GETPOST('mail_intro') ? GETPOST('mail_intro', 'restricthtml') : getDolGlobalString('TICKET_MESSAGE_MAIL_INTRO');
2623  $message_signature = GETPOST('mail_signature') ? GETPOST('mail_signature', 'restricthtml') : getDolGlobalString('TICKET_MESSAGE_MAIL_SIGNATURE');
2624  if (!dol_textishtml($message_intro)) {
2625  $message_intro = dol_nl2br($message_intro);
2626  }
2627  if (!dol_textishtml($message_signature)) {
2628  $message_signature = dol_nl2br($message_signature);
2629  }
2630 
2631  // We put intro after
2632  $messagePost = GETPOST('message', 'restricthtml');
2633  if (!dol_textishtml($messagePost)) {
2634  $messagePost = dol_nl2br($messagePost);
2635  }
2636  $message = $messagePost;
2637  $message .= '<br><br>';
2638 
2639  foreach ($external_contacts as $key => $info_sendto) {
2640  // altairis: avoid duplicate emails to external contacts
2641  if ($info_sendto['id'] == $user->contact_id) {
2642  continue;
2643  }
2644 
2645  if ($info_sendto['email'] != '' && $info_sendto['email'] != $object->origin_email) {
2646  if (!empty($info_sendto['email'])) {
2647  $sendto[] = trim($info_sendto['firstname']." ".$info_sendto['lastname'])." <".$info_sendto['email'].">";
2648  }
2649 
2650  $recipient = dolGetFirstLastname($info_sendto['firstname'], $info_sendto['lastname'], '-1').' ('.strtolower($info_sendto['libelle']).')';
2651  $message .= (!empty($recipient) ? $langs->trans('TicketNotificationRecipient').' : '.$recipient.'<br>' : '');
2652  }
2653  }
2654 
2655  // If public interface is not enable, use link to internal page into mail
2656  $url_public_ticket = (!empty($conf->global->TICKET_ENABLE_PUBLIC_INTERFACE) ?
2657  (!empty($conf->global->TICKET_URL_PUBLIC_INTERFACE) ? $conf->global->TICKET_URL_PUBLIC_INTERFACE.'/view.php' : dol_buildpath('/public/ticket/view.php', 2)) : dol_buildpath('/ticket/card.php', 2)).'?track_id='.$object->track_id;
2658  $message .= '<br>'.$langs->trans('TicketNewEmailBodyInfosTrackUrlCustomer').' : <a href="'.$url_public_ticket.'">'.$object->track_id.'</a><br>';
2659 
2660  // Build final message
2661  $message = $message_intro.'<br><br>'.$message;
2662 
2663  // Add signature
2664  $message .= '<br>'.$message_signature;
2665 
2666  if (!empty($object->origin_email)) {
2667  $sendto[] = $object->origin_email;
2668  }
2669 
2670  if ($object->fk_soc > 0 && !in_array($object->origin_email, $sendto)) {
2671  $object->socid = $object->fk_soc;
2672  $object->fetch_thirdparty();
2673  if (!empty($object->thirdparty->email)) {
2674  $sendto[] = $object->thirdparty->email;
2675  }
2676  }
2677 
2678  // Add global email address recipient
2679  if ($conf->global->TICKET_NOTIFICATION_ALSO_MAIN_ADDRESS && !in_array($conf->global->TICKET_NOTIFICATION_EMAIL_TO, $sendto)) {
2680  if (!empty($conf->global->TICKET_NOTIFICATION_EMAIL_TO)) {
2681  $sendto[] = $conf->global->TICKET_NOTIFICATION_EMAIL_TO;
2682  }
2683  }
2684 
2685  // Dont try to send email when no recipient
2686  if (!empty($sendto)) {
2687  $result = $this->sendTicketMessageByEmail($subject, $message, '', $sendto, $listofpaths, $listofmimes, $listofnames);
2688  if ($result) {
2689  // update last_msg_sent date (for last message sent to external users)
2690  $this->date_last_msg_sent = dol_now();
2691  $this->update($user, 1); // disable trigger when updating date_last_msg_sent. sendTicketMessageByEmail already create an event in actioncomm table.
2692  }
2693  }
2694  }
2695  }
2696  }
2697  }
2698 
2699  // Set status to "answered" if not set yet, but only if internal user and not private message
2700  // Or set status to "answered" if the client has answered and if the ticket has started
2701  if (($object->status < self::STATUS_IN_PROGRESS && !$user->socid && !$private) ||
2702  ($object->status > self::STATUS_IN_PROGRESS && $public_area)
2703  ) {
2704  $object->setStatut(3);
2705  }
2706  return 1;
2707  } else {
2708  setEventMessages($object->error, $object->errors, 'errors');
2709  return -1;
2710  }
2711  } else {
2712  setEventMessages($this->error, $this->errors, 'errors');
2713  return -1;
2714  }
2715  }
2716 
2717 
2730  public function sendTicketMessageByEmail($subject, $message, $send_internal_cc = 0, $array_receiver = array(), $filename_list = array(), $mimetype_list = array(), $mimefilename_list = array())
2731  {
2732  global $conf, $langs;
2733 
2734  if ($conf->global->TICKET_DISABLE_ALL_MAILS) {
2735  dol_syslog(get_class($this).'::sendTicketMessageByEmail: Emails are disable into ticket setup by option TICKET_DISABLE_ALL_MAILS', LOG_WARNING);
2736  return false;
2737  }
2738 
2739  $langs->load("mails");
2740 
2741  include_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
2742  //$contactstatic = new Contact($this->db);
2743 
2744  // If no receiver defined, load all ticket linked contacts
2745  if (!is_array($array_receiver) || !count($array_receiver) > 0) {
2746  $array_receiver = $this->getInfosTicketInternalContact();
2747  $array_receiver = array_merge($array_receiver, $this->getInfosTicketExternalContact());
2748  }
2749 
2750  if ($send_internal_cc) {
2751  $sendtocc = $conf->global->TICKET_NOTIFICATION_EMAIL_FROM;
2752  }
2753 
2754  $from = $conf->global->TICKET_NOTIFICATION_EMAIL_FROM;
2755  $is_sent = false;
2756  if (is_array($array_receiver) && count($array_receiver) > 0) {
2757  foreach ($array_receiver as $key => $receiver) {
2758  $deliveryreceipt = 0;
2759  $filepath = $filename_list;
2760  $filename = $mimefilename_list;
2761  $mimetype = $mimetype_list;
2762 
2763  // Send email
2764 
2765  $old_MAIN_MAIL_AUTOCOPY_TO = getDolGlobalString('MAIN_MAIL_AUTOCOPY_TO');
2766 
2767  if (!empty($conf->global->TICKET_DISABLE_MAIL_AUTOCOPY_TO)) {
2768  $conf->global->MAIN_MAIL_AUTOCOPY_TO = '';
2769  }
2770 
2771  $upload_dir_tmp = $conf->user->dir_output."/".$user->id.'/temp';
2772 
2773  include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
2774  $trackid = "tic".$this->id;
2775 
2776  $moreinheader = 'X-Dolibarr-Info: sendTicketMessageByEmail'."\r\n";
2777  if (!empty($this->email_msgid)) {
2778  $moreinheader .= 'References <'.$this->email_msgid.'>'."\r\n";
2779  }
2780 
2781  $mailfile = new CMailFile($subject, $receiver, $from, $message, $filepath, $mimetype, $filename, $sendtocc, '', $deliveryreceipt, -1, '', '', $trackid, $moreinheader, 'ticket', '', $upload_dir_tmp);
2782  if ($mailfile->error) {
2783  setEventMessages($mailfile->error, null, 'errors');
2784  } else {
2785  $result = $mailfile->sendfile();
2786  if ($result) {
2787  setEventMessages($langs->trans('MailSuccessfulySent', $mailfile->getValidAddress($from, 2), $mailfile->getValidAddress($receiver, 2)), null, 'mesgs');
2788  $is_sent = true;
2789  } else {
2790  $langs->load("other");
2791  if ($mailfile->error) {
2792  setEventMessages($langs->trans('ErrorFailedToSendMail', $from, $receiver), null, 'errors');
2793  dol_syslog($langs->trans('ErrorFailedToSendMail', $from, $receiver).' : '.$mailfile->error);
2794  } else {
2795  setEventMessages('No mail sent. Feature is disabled by option MAIN_DISABLE_ALL_MAILS', null, 'errors');
2796  }
2797  }
2798  }
2799 
2800  if (!empty($conf->global->TICKET_DISABLE_MAIL_AUTOCOPY_TO)) {
2801  $conf->global->MAIN_MAIL_AUTOCOPY_TO = $old_MAIN_MAIL_AUTOCOPY_TO;
2802  }
2803  }
2804  } else {
2805  $langs->load("other");
2806  setEventMessages($langs->trans('ErrorMailRecipientIsEmptyForSendTicketMessage'), null, 'warnings');
2807  }
2808 
2809  return $is_sent;
2810  }
2811 
2812  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2820  public function load_board($user, $mode)
2821  {
2822  // phpcs:enable
2823  global $conf, $user, $langs;
2824 
2825  $now = dol_now();
2826  $delay_warning = 0;
2827 
2828  $this->nbtodo = $this->nbtodolate = 0;
2829  $clause = " WHERE";
2830 
2831  $sql = "SELECT p.rowid, p.ref, p.datec as datec";
2832  $sql .= " FROM ".MAIN_DB_PREFIX."ticket as p";
2833  if (isModEnabled('societe') && empty($user->rights->societe->client->voir) && !$user->socid) {
2834  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON p.fk_soc = sc.fk_soc";
2835  $sql .= " WHERE sc.fk_user = ".((int) $user->id);
2836  $clause = " AND";
2837  }
2838  $sql .= $clause." p.entity IN (".getEntity('ticket').")";
2839  if ($mode == 'opened') {
2840  $sql .= " AND p.fk_statut NOT IN (".Ticket::STATUS_CLOSED.", ".Ticket::STATUS_CANCELED.")";
2841  }
2842  if ($user->socid) {
2843  $sql .= " AND p.fk_soc = ".((int) $user->socid);
2844  }
2845 
2846  $resql = $this->db->query($sql);
2847  if ($resql) {
2848  $label = $labelShort = '';
2849  $status = '';
2850  if ($mode == 'opened') {
2851  $status = 'openall';
2852  //$delay_warning = $conf->ticket->warning_delay;
2853  $delay_warning = 0;
2854  $label = $langs->trans("MenuListNonClosed");
2855  $labelShort = $langs->trans("MenuListNonClosed");
2856  }
2857 
2858  $response = new WorkboardResponse();
2859  //$response->warning_delay = $delay_warning / 60 / 60 / 24;
2860  $response->label = $label;
2861  $response->labelShort = $labelShort;
2862  $response->url = DOL_URL_ROOT.'/ticket/list.php?search_fk_statut[]='.$status;
2863  $response->img = img_object('', "ticket");
2864 
2865  // This assignment in condition is not a bug. It allows walking the results.
2866  while ($obj = $this->db->fetch_object($resql)) {
2867  $response->nbtodo++;
2868  if ($mode == 'opened') {
2869  $datelimit = $this->db->jdate($obj->datec) + $delay_warning;
2870  if ($datelimit < $now) {
2871  //$response->nbtodolate++;
2872  }
2873  }
2874  }
2875  return $response;
2876  } else {
2877  $this->error = $this->db->lasterror();
2878  return -1;
2879  }
2880  }
2881 
2882  // phpcs:disable PEAR.NamingConventions.ValidFunctionName.ScopeNotCamelCaps
2888  public function load_state_board()
2889  {
2890  // phpcs:enable
2891  global $conf, $user;
2892 
2893  $this->nb = array();
2894  $clause = "WHERE";
2895 
2896  $sql = "SELECT count(p.rowid) as nb";
2897  $sql .= " FROM ".MAIN_DB_PREFIX."ticket as p";
2898  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe as s ON p.fk_soc = s.rowid";
2899  if (empty($user->rights->societe->client->voir) && !$user->socid) {
2900  $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."societe_commerciaux as sc ON s.rowid = sc.fk_soc";
2901  $sql .= " WHERE sc.fk_user = ".((int) $user->id);
2902  $clause = "AND";
2903  }
2904  $sql .= " ".$clause." p.entity IN (".getEntity('ticket').")";
2905 
2906  $resql = $this->db->query($sql);
2907  if ($resql) {
2908  // This assignment in condition is not a bug. It allows walking the results.
2909  while ($obj = $this->db->fetch_object($resql)) {
2910  $this->nb["ticket"] = $obj->nb;
2911  }
2912  $this->db->free($resql);
2913  return 1;
2914  } else {
2915  dol_print_error($this->db);
2916  $this->error = $this->db->lasterror();
2917  return -1;
2918  }
2919  }
2920 
2929  public static function replaceThirdparty($db, $origin_id, $dest_id)
2930  {
2931  $tables = array('ticket');
2932 
2933  return CommonObject::commonReplaceThirdparty($db, $origin_id, $dest_id, $tables);
2934  }
2935 }
2936 
2937 
2942 {
2947  public $rowid;
2948 
2952  public $id;
2953 
2957  public $ref;
2958 
2962  public $track_id;
2963 
2967  public $fk_soc;
2968 
2972  public $fk_project;
2973 
2978 
2983 
2988 
2992  public $subject;
2993 
2997  public $message;
2998 
3002  public $fk_statut;
3003 
3007  public $resolution;
3008 
3012  public $progress;
3013 
3017  public $timing;
3018 
3022  public $type_code;
3023 
3028 
3033 
3037  public $type_label;
3038 
3043 
3048 
3052  public $datec = '';
3053 
3057  public $date_read = '';
3058 
3062  public $date_last_msg_sent = '';
3063 
3067  public $date_close = '';
3068 }
$object ref
Definition: info.php:78
Class to manage agenda events (actions)
Class to send emails (with attachments or not) Usage: $mailfile = new CMailFile($subject,...
Class to manage categories.
Parent class of all other business classes (invoices, contracts, proposals, orders,...
fetch_optionals($rowid=null, $optionsArray=null)
Function to get extra fields of an object into $this->array_options This method is in most cases call...
static commonReplaceThirdparty(DoliDB $dbs, $origin_id, $dest_id, array $tables, $ignoreerrors=0)
Function used to replace a thirdparty id with another one.
insertExtraFields($trigger='', $userused=null)
Add/Update all extra fields values for the current object.
call_trigger($triggerName, $user)
Call trigger based on this instance.
add_contact($fk_socpeople, $type_contact, $source='external', $notrigger=0)
Add a link between element $this->element and a contact.
Class to manage contact/addresses.
Class to manage standard extra fields.
Class to manage interventions.
static selectarray($htmlname, $array, $id='', $show_empty=0, $key_in_label=0, $value_as_key=0, $moreparam='', $translate=0, $maxlen=0, $disabled=0, $sort='', $morecss='minwidth75', $addjscombo=1, $moreparamonempty='', $disablebademail=0, $nohtmlescape=0)
Return a HTML select string, built from an array of key+value.
Classe permettant la generation du formulaire html d'envoi de mail unitaire Usage: $formail = new For...
Class to manage third parties objects (customers, suppliers, prospects...)
update($user=0, $notrigger=0)
Update object into database.
$email_from
Email from user.
static replaceThirdparty($db, $origin_id, $dest_id)
Function used to replace a thirdparty id with another one.
fetch($id='', $ref='', $track_id='', $email_msgid='')
Load object in memory from the database.
loadCacheMsgsTicket()
Load the list of event on ticket into ->cache_msgs_ticket.
setProgression($percent)
Define progression of current ticket.
listeContact($status=-1, $source='external', $list=0, $code='')
Get array of all contacts for a ticket Override method of file commonobject.class....
loadCacheSeveritiesTickets()
Charge dans cache la liste des sévérité de tickets (paramétrable dans dictionnaire)
$fields
'type' field format ('integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter]]',...
searchContactByEmail($email, $socid='', $case='')
Search and fetch contacts by email.
getIdTicketCustomerInvolvedContact()
Return id des contacts clients des intervenants.
getInfosTicketInternalContact()
Retrieve informations about internal contacts.
copyFilesForTicket()
Copy files defined into $_SESSION array into the ticket directory of attached files.
LibStatut($status, $mode=0, $notooltip=0, $progress=0)
Return status label of object.
$type_label
Type label.
setContract($contractid)
Link element with a contract.
getTicketAllContacts()
Return id of all contacts for ticket.
createFromClone(User $user, $fromid)
Load an object from its id and create a new one in database.
assignUser($user, $id_assign_user, $notrigger=0)
Mark a message as read.
load_state_board()
Load indicator this->nb of global stats widget.
printSelectStatus($selected="")
Print selected status.
is_photo_available($sdir)
Return if at least one photo is available.
getIdTicketInternalContact()
Return id des contacts interne de suivi.
fetchAll($user, $sortorder='ASC', $sortfield='t.datec', $limit='', $offset=0, $arch='', $filter='')
Load all objects in memory from database.
setCustomer($id)
Define parent commany of current ticket.
markAsRead($user, $notrigger=0)
Mark a message as read.
getDefaultRef($thirdparty='')
Get a default reference.
sendTicketMessageByEmail($subject, $message, $send_internal_cc=0, $array_receiver=array(), $filename_list=array(), $mimetype_list=array(), $mimefilename_list=array())
Send ticket by email to linked contacts.
const STATUS_NOT_READ
Status.
__construct($db)
Constructor.
getTicketAllCustomerContacts()
Return id of all contacts for ticket.
$category_label
Category label.
create($user, $notrigger=0)
Create object into database.
searchSocidByEmail($email, $type='0', $filters=array(), $clause='AND')
Search and fetch thirparties by email.
$severity_label
Severity label.
verify()
Check properties of ticket are ok (like ref, track_id, ...).
setCategories($categories)
Sets object to supplied categories.
initAsSpecimen()
Initialise object with example values Id must be 0 if object instance is a specimen.
newMessage($user, &$action, $private=1, $public_area=0)
Add new message on a ticket (private/public area).
$subject
var string Ticket subject
getIdTicketCustomerContact()
Return id des contacts clients pour le suivi ticket.
loadCacheTypesTickets()
Load into a cache the types of tickets (setup done into dictionaries)
getLibStatut($mode=0)
Return status label of object.
close(User $user, $mode=0)
Close a ticket.
getIdTicketInternalInvolvedContact()
Return id des contacts clients des intervenants.
getInfosTicketExternalContact()
Retrieve informations about external contacts.
getNomUrl($withpicto=0, $option='', $notooltip=0, $morecss='', $save_lastsearch_value=-1)
Return a link to the object card (with optionaly the picto)
load_board($user, $mode)
Load indicators for dashboard (this->nbtodo and this->nbtodolate)
createTicketMessage($user, $notrigger=0, $filename_list=array(), $mimetype_list=array(), $mimefilename_list=array(), $send_email=false)
Add message into database.
loadCacheCategoriesTickets()
Load into a cache array, the list of ticket categories (setup done into dictionary)
Ticket line Class.
$message
Ticket message.
$category_code
Category code.
$fk_user_assign
User id who have ticket assigned.
$date_read
Read date.
$type_label
Type label.
$fk_project
Project ID.
$timing
Duration for ticket.
$category_label
Category label.
$subject
Ticket subject.
$severity_code
Severity code.
$track_id
Hash to identify ticket.
$fk_statut
Ticket statut.
$datec
Creation date.
$progress
Progress in percent.
$date_close
Close ticket date.
$type_code
Type code.
$severity_label
Severity label.
$origin_email
Person email who have create ticket.
$resolution
State resolution.
$fk_user_create
User id who have create ticket.
Class to manage Dolibarr users.
Definition: user.class.php:45
if(isModEnabled('facture') &&!empty($user->rights->facture->lire)) if((isModEnabled('fournisseur') &&empty($conf->global->MAIN_USE_NEW_SUPPLIERMOD) && $user->hasRight("fournisseur", "facture", "lire"))||(isModEnabled('supplier_invoice') && $user->hasRight("supplier_invoice", "lire"))) if(isModEnabled('don') &&!empty($user->rights->don->lire)) if(isModEnabled('tax') &&!empty($user->rights->tax->charges->lire)) if(isModEnabled('facture') &&isModEnabled('commande') && $user->hasRight("commande", "lire") &&empty($conf->global->WORKFLOW_DISABLE_CREATE_INVOICE_FROM_ORDER)) $resql
Social contributions to pay.
Definition: index.php:745
dol_is_file($pathoffile)
Return if path is a file.
Definition: files.lib.php:480
dol_is_dir($folder)
Test if filename is a directory.
Definition: files.lib.php:450
dol_move($srcfile, $destfile, $newmask=0, $overwriteifexists=1, $testvirus=0, $indexdatabase=1)
Move a file into another name.
Definition: files.lib.php:874
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
dol_print_error($db='', $error='', $errors=null)
Displays error message system with all the information to facilitate the diagnosis and the escalation...
img_object($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0)
Show a picto called object_picto (generic function)
dol_nl2br($stringtoencode, $nl2brmode=0, $forxml=false)
Replace CRLF in string with a HTML BR tag.
setEventMessages($mesg, $mesgs, $style='mesgs', $messagekey='')
Set event messages in dol_events session object.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs='', $encodetooutput=false)
Output date in a string format according to outputlangs (or langs if not defined).
if(!function_exists('dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
dol_now($mode='auto')
Return date for now.
img_picto($titlealt, $picto, $moreatt='', $pictoisfullpath=false, $srconly=0, $notitle=0, $alt='', $morecss='', $marginleftonlyshort=2)
Show picto whatever it's its name (generic function)
dol_clone($object, $native=0)
Create a clone of instance of object (new instance with same value for each properties) With native =...
dolGetFirstLastname($firstname, $lastname, $nameorder=-1)
Return firstname and lastname in correct order.
dol_concatdesc($text1, $text2, $forxml=false, $invert=false)
Concat 2 descriptions with a new line between them (second operand after first one with appropriate n...
dolGetStatus($statusLabel='', $statusLabelShort='', $html='', $statusType='status0', $displayMode=0, $url='', $params=array())
Output the badge of a status.
dol_textishtml($msg, $option=0)
Return if a text is a html content.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0)
Return value of a param into GET or POST supervariable.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
if(!function_exists('utf8_encode')) if(!function_exists('utf8_decode')) getDolGlobalString($key, $default='')
Return dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
utf8_check($str)
Check if a string is in UTF8.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
dol_mkdir($dir, $dataroot='', $newmask='')
Creation of a directory (this can create recursive subdir)
vignette($file, $maxWidth=160, $maxHeight=120, $extName='_small', $quality=50, $outdir='thumbs', $targetformat=0)
Create a thumbnail from an image file (Supported extensions are gif, jpg, png and bmp).
Definition: images.lib.php:485
image_format_supported($file, $acceptsvg=0)
Return if a filename is file name of a supported image format.
Definition: images.lib.php:58
Class to generate the form for creating a new ticket.
$conf db
API class for accounts.
Definition: inc.php:41
generate_random_id($car=16)
Generate a random id.
Definition: ticket.lib.php:194