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-2020 Frédéric France <frederic.france@netlogic.fr> 5 * 6 * This program is free software: you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation, either version 3 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program. If not, see <https://www.gnu.org/licenses/>. 18 */ 19 20/** 21 * \file core/lib/ticket.lib.php 22 * \ingroup ticket 23 * \brief This file is a library for Ticket module 24 */ 25 26/** 27 * Build tabs for admin page 28 * 29 * @return array 30 */ 31function ticketAdminPrepareHead() 32{ 33 global $langs, $conf; 34 35 $langs->load("ticket"); 36 37 $h = 0; 38 $head = array(); 39 40 $head[$h][0] = DOL_URL_ROOT.'/admin/ticket.php'; 41 $head[$h][1] = $langs->trans("TicketSettings"); 42 $head[$h][2] = 'settings'; 43 $h++; 44 45 $head[$h][0] = DOL_URL_ROOT.'/admin/ticket_extrafields.php'; 46 $head[$h][1] = $langs->trans("ExtraFieldsTicket"); 47 $head[$h][2] = 'attributes'; 48 $h++; 49 50 $head[$h][0] = DOL_URL_ROOT.'/admin/ticket_public.php'; 51 $head[$h][1] = $langs->trans("PublicInterface"); 52 $head[$h][2] = 'public'; 53 $h++; 54 55 // Show more tabs from modules 56 // Entries must be declared in modules descriptor with line 57 //$this->tabs = array( 58 // 'entity:+tabname:Title:@ticket:/ticket/mypage.php?id=__ID__' 59 //); // to add new tab 60 //$this->tabs = array( 61 // 'entity:-tabname:Title:@ticket:/ticket/mypage.php?id=__ID__' 62 //); // to remove a tab 63 complete_head_from_modules($conf, $langs, null, $head, $h, 'ticketadmin'); 64 65 complete_head_from_modules($conf, $langs, null, $head, $h, 'ticketadmin', 'remove'); 66 67 return $head; 68} 69 70/** 71 * Build tabs for a Ticket object 72 * 73 * @param Ticket $object Object Ticket 74 * @return array Array of tabs 75 */ 76function ticket_prepare_head($object) 77{ 78 global $db, $langs, $conf, $user; 79 80 $h = 0; 81 $head = array(); 82 $head[$h][0] = DOL_URL_ROOT.'/ticket/card.php?action=view&track_id='.$object->track_id; 83 $head[$h][1] = $langs->trans("Ticket"); 84 $head[$h][2] = 'tabTicket'; 85 $h++; 86 87 if (empty($conf->global->MAIN_DISABLE_CONTACTS_TAB) && empty($user->socid) && $conf->societe->enabled) { 88 $nbContact = count($object->liste_contact(-1, 'internal')) + count($object->liste_contact(-1, 'external')); 89 $head[$h][0] = DOL_URL_ROOT.'/ticket/contact.php?track_id='.$object->track_id; 90 $head[$h][1] = $langs->trans('ContactsAddresses'); 91 if ($nbContact > 0) { 92 $head[$h][1] .= '<span class="badge marginleftonlyshort">'.$nbContact.'</span>'; 93 } 94 $head[$h][2] = 'contact'; 95 $h++; 96 } 97 98 complete_head_from_modules($conf, $langs, $object, $head, $h, 'ticket'); 99 100 // Attached files 101 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php'; 102 $upload_dir = $conf->ticket->dir_output."/".$object->ref; 103 $nbFiles = count(dol_dir_list($upload_dir, 'files')); 104 $head[$h][0] = dol_buildpath('/ticket/document.php', 1).'?id='.$object->id; 105 $head[$h][1] = $langs->trans("Documents"); 106 if ($nbFiles > 0) { 107 $head[$h][1] .= '<span class="badge marginleftonlyshort">'.$nbFiles.'</span>'; 108 } 109 110 $head[$h][2] = 'tabTicketDocument'; 111 $h++; 112 113 114 // History 115 $ticketViewType = "messaging"; 116 if (empty($_SESSION['ticket-view-type'])) { 117 $_SESSION['ticket-view-type'] = $ticketViewType; 118 } else { 119 $ticketViewType = $_SESSION['ticket-view-type']; 120 } 121 122 if ($ticketViewType == "messaging") { 123 $head[$h][0] = DOL_URL_ROOT.'/ticket/messaging.php?track_id='.$object->track_id; 124 } else { 125 // $ticketViewType == "list" 126 $head[$h][0] = DOL_URL_ROOT.'/ticket/agenda.php?track_id='.$object->track_id; 127 } 128 $head[$h][1] = $langs->trans('Events'); 129 if (!empty($conf->agenda->enabled) && (!empty($user->rights->agenda->myactions->read) || !empty($user->rights->agenda->allactions->read))) { 130 $head[$h][1] .= '/'; 131 $head[$h][1] .= $langs->trans("Agenda"); 132 } 133 $head[$h][2] = 'tabTicketLogs'; 134 $h++; 135 136 complete_head_from_modules($conf, $langs, $object, $head, $h, 'ticket', 'remove'); 137 138 return $head; 139} 140 141/** 142 * Return string with full Url. The file qualified is the one defined by relative path in $object->last_main_doc 143 * 144 * @param Object $object Object 145 * @return string Url string 146 */ 147function showDirectPublicLink($object) 148{ 149 global $conf, $langs; 150 151 require_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php'; 152 $email = CMailFile::getValidAddress($object->origin_email, 2); 153 $url = ''; 154 if ($email) { 155 $url = dol_buildpath('/public/ticket/view.php', 3).'?track_id='.$object->track_id.'&email='.$email; 156 } 157 158 $out = ''; 159 if (empty($conf->global->TICKET_ENABLE_PUBLIC_INTERFACE)) { 160 $langs->load('errors'); 161 $out .= '<span class="opacitymedium">'.$langs->trans("ErrorPublicInterfaceNotEnabled").'</span>'; 162 } else { 163 $out .= img_picto('', 'object_globe.png').' <span class="opacitymedium">'.$langs->trans("TicketPublicAccess").'</span><br>'; 164 if ($url) { 165 $out .= '<div class="urllink">'; 166 $out .= '<input type="text" id="directpubliclink" class="quatrevingtpercentminusx" value="'.$url.'">'; 167 $out .= '<a href="'.$url.'" target="_blank" rel="noopener">'.img_picto('', 'object_globe.png', 'class="paddingleft"').'</a>'; 168 $out .= '</div>'; 169 $out .= ajax_autoselect("directpubliclink", 0); 170 } else { 171 $out .= '<span class="opacitymedium">'.$langs->trans("TicketNotCreatedFromPublicInterface").'</span>'; 172 } 173 } 174 175 return $out; 176} 177 178/** 179 * Generate a random id 180 * 181 * @param int $car Length of string to generate key 182 * @return string 183 */ 184function generate_random_id($car = 16) 185{ 186 $string = ""; 187 $chaine = "abcdefghijklmnopqrstuvwxyz123456789"; 188 srand((double) microtime() * 1000000); 189 for ($i = 0; $i < $car; $i++) { 190 $string .= $chaine[rand() % strlen($chaine)]; 191 } 192 return $string; 193} 194 195/** 196 * Show header for public pages 197 * 198 * @param string $title Title 199 * @param string $head Head array 200 * @param int $disablejs More content into html header 201 * @param int $disablehead More content into html header 202 * @param array $arrayofjs Array of complementary js files 203 * @param array $arrayofcss Array of complementary css files 204 * @return void 205 */ 206function llxHeaderTicket($title, $head = "", $disablejs = 0, $disablehead = 0, $arrayofjs = '', $arrayofcss = '') 207{ 208 global $user, $conf, $langs, $mysoc; 209 210 top_htmlhead($head, $title, $disablejs, $disablehead, $arrayofjs, $arrayofcss, 0, 1); // Show html headers 211 212 print '<body id="mainbody" class="publicnewticketform">'; 213 print '<div class="center">'; 214 215 // Define urllogo 216 if (!empty($conf->global->TICKET_SHOW_COMPANY_LOGO) || !empty($conf->global->TICKET_PUBLIC_INTERFACE_TOPIC)) { 217 // Print logo 218 if (!empty($conf->global->TICKET_SHOW_COMPANY_LOGO)) { 219 $urllogo = DOL_URL_ROOT.'/theme/common/login_logo.png'; 220 221 if (!empty($mysoc->logo_small) && is_readable($conf->mycompany->dir_output.'/logos/thumbs/'.$mysoc->logo_small)) { 222 $urllogo = DOL_URL_ROOT.'/viewimage.php?modulepart=mycompany&entity='.$conf->entity.'&file='.urlencode('logos/thumbs/'.$mysoc->logo_small); 223 } elseif (!empty($mysoc->logo) && is_readable($conf->mycompany->dir_output.'/logos/'.$mysoc->logo)) { 224 $urllogo = DOL_URL_ROOT.'/viewimage.php?modulepart=mycompany&entity='.$conf->entity.'&file='.urlencode('logos/'.$mysoc->logo); 225 } elseif (is_readable(DOL_DOCUMENT_ROOT.'/theme/dolibarr_logo.svg')) { 226 $urllogo = DOL_URL_ROOT.'/theme/dolibarr_logo.svg'; 227 } 228 } 229 } 230 231 // Output html code for logo 232 if ($urllogo || !empty($conf->global->TICKET_PUBLIC_INTERFACE_TOPIC)) { 233 print '<div class="backgreypublicpayment">'; 234 print '<div class="logopublicpayment">'; 235 if ($urllogo) { 236 print '<a href="'.($conf->global->TICKET_URL_PUBLIC_INTERFACE ? $conf->global->TICKET_URL_PUBLIC_INTERFACE : dol_buildpath('/public/ticket/index.php', 1)).'">'; 237 print '<img id="dolpaymentlogo" src="'.$urllogo.'"'; 238 print '>'; 239 print '</a>'; 240 } 241 if (!empty($conf->global->TICKET_PUBLIC_INTERFACE_TOPIC)) { 242 print '<div class="clearboth"></div><strong>'.($conf->global->TICKET_PUBLIC_INTERFACE_TOPIC ? $conf->global->TICKET_PUBLIC_INTERFACE_TOPIC : $langs->trans("TicketSystem")).'</strong>'; 243 } 244 print '</div>'; 245 if (empty($conf->global->MAIN_HIDE_POWERED_BY)) { 246 print '<div class="poweredbypublicpayment opacitymedium right"><a class="poweredbyhref" href="https://www.dolibarr.org?utm_medium=website&utm_source=poweredby" target="dolibarr" rel="noopener">'.$langs->trans("PoweredBy").'<br><img src="'.DOL_URL_ROOT.'/theme/dolibarr_logo.svg" width="80px"></a></div>'; 247 } 248 print '</div>'; 249 } 250 251 if (!empty($conf->global->TICKET_IMAGE_PUBLIC_INTERFACE)) { 252 print '<div class="backimagepublicticket">'; 253 print '<img id="idRECRUITMENT_IMAGE_PUBLIC_INTERFACE" src="'.$conf->global->MEMBER_IMAGE_PUBLIC_REGISTRATION.'">'; 254 print '</div>'; 255 } 256 257 print '</div>'; 258 259 print '<div class="ticketlargemargin">'; 260} 261 262 263 264/** 265 * Show html area with actions for ticket messaging. 266 * Note: Global parameter $param must be defined. 267 * 268 * @param Conf $conf Object conf 269 * @param Translate $langs Object langs 270 * @param DoliDB $db Object db 271 * @param mixed $filterobj Filter on object Adherent|Societe|Project|Product|CommandeFournisseur|Dolresource|Ticket|... to list events linked to an object 272 * @param Contact $objcon Filter on object contact to filter events on a contact 273 * @param int $noprint Return string but does not output it 274 * @param string $actioncode Filter on actioncode 275 * @param string $donetodo Filter on event 'done' or 'todo' or ''=nofilter (all). 276 * @param array $filters Filter on other fields 277 * @param string $sortfield Sort field 278 * @param string $sortorder Sort order 279 * @return string|void Return html part or void if noprint is 1 280 */ 281function show_ticket_messaging($conf, $langs, $db, $filterobj, $objcon = '', $noprint = 0, $actioncode = '', $donetodo = 'done', $filters = array(), $sortfield = 'a.datep,a.id', $sortorder = 'DESC') 282{ 283 global $user, $conf; 284 global $form; 285 286 global $param, $massactionbutton; 287 288 dol_include_once('/comm/action/class/actioncomm.class.php'); 289 290 // Check parameters 291 if (!is_object($filterobj) && !is_object($objcon)) { 292 dol_print_error('', 'BadParameter'); 293 } 294 295 $histo = array(); 296 $numaction = 0; 297 $now = dol_now(); 298 299 $sortfield_list = explode(',', $sortfield); 300 $sortfield_label_list = array('a.id' => 'id', 'a.datep' => 'dp', 'a.percent' => 'percent'); 301 $sortfield_new_list = array(); 302 foreach ($sortfield_list as $sortfield_value) { 303 $sortfield_new_list[] = $sortfield_label_list[trim($sortfield_value)]; 304 } 305 $sortfield_new = implode(',', $sortfield_new_list); 306 307 if (!empty($conf->agenda->enabled)) { 308 // Search histo on actioncomm 309 if (is_object($objcon) && $objcon->id > 0) { 310 $sql = "SELECT DISTINCT a.id, a.label as label,"; 311 } else { 312 $sql = "SELECT a.id, a.label as label,"; 313 } 314 $sql .= " a.datep as dp,"; 315 $sql .= " a.note as message,"; 316 $sql .= " a.datep2 as dp2,"; 317 $sql .= " a.percent as percent, 'action' as type,"; 318 $sql .= " a.fk_element, a.elementtype,"; 319 $sql .= " a.fk_contact,"; 320 $sql .= " c.code as acode, c.libelle as alabel, c.picto as apicto,"; 321 $sql .= " u.rowid as user_id, u.login as user_login, u.photo as user_photo, u.firstname as user_firstname, u.lastname as user_lastname"; 322 if (is_object($filterobj) && get_class($filterobj) == 'Societe') { 323 $sql .= ", sp.lastname, sp.firstname"; 324 } elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') { 325 $sql .= ", m.lastname, m.firstname"; 326 } elseif (is_object($filterobj) && get_class($filterobj) == 'CommandeFournisseur') { 327 $sql .= ", o.ref"; 328 } elseif (is_object($filterobj) && get_class($filterobj) == 'Product') { 329 $sql .= ", o.ref"; 330 } elseif (is_object($filterobj) && get_class($filterobj) == 'Ticket') { 331 $sql .= ", o.ref"; 332 } elseif (is_object($filterobj) && get_class($filterobj) == 'BOM') { 333 $sql .= ", o.ref"; 334 } elseif (is_object($filterobj) && get_class($filterobj) == 'Contrat') { 335 $sql .= ", o.ref"; 336 } 337 $sql .= " FROM ".MAIN_DB_PREFIX."actioncomm as a"; 338 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."user as u on u.rowid = a.fk_user_action"; 339 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_actioncomm as c ON a.fk_action = c.id"; 340 341 $force_filter_contact = false; 342 if (is_object($objcon) && $objcon->id > 0) { 343 $force_filter_contact = true; 344 $sql .= " INNER JOIN ".MAIN_DB_PREFIX."actioncomm_resources as r ON a.id = r.fk_actioncomm"; 345 $sql .= " AND r.element_type = '".$db->escape($objcon->table_element)."' AND r.fk_element = ".((int) $objcon->id); 346 } 347 348 if (is_object($filterobj) && get_class($filterobj) == 'Societe') { 349 $sql .= " LEFT JOIN ".MAIN_DB_PREFIX."socpeople as sp ON a.fk_contact = sp.rowid"; 350 } elseif (is_object($filterobj) && get_class($filterobj) == 'Dolresource') { 351 $sql .= " INNER JOIN ".MAIN_DB_PREFIX."element_resources as er"; 352 $sql .= " ON er.resource_type = 'dolresource'"; 353 $sql .= " AND er.element_id = a.id"; 354 $sql .= " AND er.resource_id = ".((int) $filterobj->id); 355 } elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') { 356 $sql .= ", ".MAIN_DB_PREFIX."adherent as m"; 357 } elseif (is_object($filterobj) && get_class($filterobj) == 'CommandeFournisseur') { 358 $sql .= ", ".MAIN_DB_PREFIX."commande_fournisseur as o"; 359 } elseif (is_object($filterobj) && get_class($filterobj) == 'Product') { 360 $sql .= ", ".MAIN_DB_PREFIX."product as o"; 361 } elseif (is_object($filterobj) && get_class($filterobj) == 'Ticket') { 362 $sql .= ", ".MAIN_DB_PREFIX."ticket as o"; 363 } elseif (is_object($filterobj) && get_class($filterobj) == 'BOM') { 364 $sql .= ", ".MAIN_DB_PREFIX."bom_bom as o"; 365 } elseif (is_object($filterobj) && get_class($filterobj) == 'Contrat') { 366 $sql .= ", ".MAIN_DB_PREFIX."contrat as o"; 367 } 368 369 $sql .= " WHERE a.entity IN (".getEntity('agenda').")"; 370 if ($force_filter_contact === false) { 371 if (is_object($filterobj) && in_array(get_class($filterobj), array('Societe', 'Client', 'Fournisseur')) && $filterobj->id) { 372 $sql .= " AND a.fk_soc = ".((int) $filterobj->id); 373 } elseif (is_object($filterobj) && get_class($filterobj) == 'Project' && $filterobj->id) { 374 $sql .= " AND a.fk_project = ".((int) $filterobj->id); 375 } elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') { 376 $sql .= " AND a.fk_element = m.rowid AND a.elementtype = 'member'"; 377 if ($filterobj->id) { 378 $sql .= " AND a.fk_element = ".((int) $filterobj->id); 379 } 380 } elseif (is_object($filterobj) && get_class($filterobj) == 'CommandeFournisseur') { 381 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'order_supplier'"; 382 if ($filterobj->id) { 383 $sql .= " AND a.fk_element = ".((int) $filterobj->id); 384 } 385 } elseif (is_object($filterobj) && get_class($filterobj) == 'Product') { 386 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'product'"; 387 if ($filterobj->id) { 388 $sql .= " AND a.fk_element = ".((int) $filterobj->id); 389 } 390 } elseif (is_object($filterobj) && get_class($filterobj) == 'Ticket') { 391 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'ticket'"; 392 if ($filterobj->id) { 393 $sql .= " AND a.fk_element = ".((int) $filterobj->id); 394 } 395 } elseif (is_object($filterobj) && get_class($filterobj) == 'BOM') { 396 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'bom'"; 397 if ($filterobj->id) { 398 $sql .= " AND a.fk_element = ".((int) $filterobj->id); 399 } 400 } elseif (is_object($filterobj) && get_class($filterobj) == 'Contrat') { 401 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'contract'"; 402 if ($filterobj->id) { 403 $sql .= " AND a.fk_element = ".((int) $filterobj->id); 404 } 405 } 406 } 407 408 // Condition on actioncode 409 if (!empty($actioncode)) { 410 if (empty($conf->global->AGENDA_USE_EVENT_TYPE)) { 411 if ($actioncode == 'AC_NON_AUTO') { 412 $sql .= " AND c.type != 'systemauto'"; 413 } elseif ($actioncode == 'AC_ALL_AUTO') { 414 $sql .= " AND c.type = 'systemauto'"; 415 } else { 416 if ($actioncode == 'AC_OTH') { 417 $sql .= " AND c.type != 'systemauto'"; 418 } elseif ($actioncode == 'AC_OTH_AUTO') { 419 $sql .= " AND c.type = 'systemauto'"; 420 } 421 } 422 } else { 423 if ($actioncode == 'AC_NON_AUTO') { 424 $sql .= " AND c.type != 'systemauto'"; 425 } elseif ($actioncode == 'AC_ALL_AUTO') { 426 $sql .= " AND c.type = 'systemauto'"; 427 } else { 428 $sql .= " AND c.code = '".$db->escape($actioncode)."'"; 429 } 430 } 431 } 432 if ($donetodo == 'todo') { 433 $sql .= " AND ((a.percent >= 0 AND a.percent < 100) OR (a.percent = -1 AND a.datep > '".$db->idate($now)."'))"; 434 } elseif ($donetodo == 'done') { 435 $sql .= " AND (a.percent = 100 OR (a.percent = -1 AND a.datep <= '".$db->idate($now)."'))"; 436 } 437 if (is_array($filters) && $filters['search_agenda_label']) { 438 $sql .= natural_search('a.label', $filters['search_agenda_label']); 439 } 440 } 441 442 // Add also event from emailings. TODO This should be replaced by an automatic event ? May be it's too much for very large emailing. 443 if (!empty($conf->mailing->enabled) && !empty($objcon->email) 444 && (empty($actioncode) || $actioncode == 'AC_OTH_AUTO' || $actioncode == 'AC_EMAILING')) { 445 $langs->load("mails"); 446 447 $sql2 = "SELECT m.rowid as id, m.titre as label, mc.date_envoi as dp, mc.date_envoi as dp2, '100' as percent, 'mailing' as type"; 448 $sql2 .= ", null as fk_element, '' as elementtype, null as contact_id"; 449 $sql2 .= ", 'AC_EMAILING' as acode, '' as alabel, '' as apicto"; 450 $sql2 .= ", u.rowid as user_id, u.login as user_login, u.photo as user_photo, u.firstname as user_firstname, u.lastname as user_lastname"; // User that valid action 451 if (is_object($filterobj) && get_class($filterobj) == 'Societe') { 452 $sql2 .= ", '' as lastname, '' as firstname"; 453 } elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') { 454 $sql2 .= ", '' as lastname, '' as firstname"; 455 } elseif (is_object($filterobj) && get_class($filterobj) == 'CommandeFournisseur') { 456 $sql2 .= ", '' as ref"; 457 } elseif (is_object($filterobj) && get_class($filterobj) == 'Product') { 458 $sql2 .= ", '' as ref"; 459 } elseif (is_object($filterobj) && get_class($filterobj) == 'Ticket') { 460 $sql2 .= ", '' as ref"; 461 } 462 $sql2 .= " FROM ".MAIN_DB_PREFIX."mailing as m, ".MAIN_DB_PREFIX."mailing_cibles as mc, ".MAIN_DB_PREFIX."user as u"; 463 $sql2 .= " WHERE mc.email = '".$db->escape($objcon->email)."'"; // Search is done on email. 464 $sql2 .= " AND mc.statut = 1"; 465 $sql2 .= " AND u.rowid = m.fk_user_valid"; 466 $sql2 .= " AND mc.fk_mailing=m.rowid"; 467 } 468 469 if (!empty($sql) && !empty($sql2)) { 470 $sql = $sql." UNION ".$sql2; 471 } elseif (empty($sql) && !empty($sql2)) { 472 $sql = $sql2; 473 } 474 475 // TODO Add limit in nb of results 476 if ($sql) { // May not be defined if module Agenda is not enabled and mailing module disabled too 477 $sql .= $db->order($sortfield_new, $sortorder); 478 479 dol_syslog("company.lib::show_actions_done", LOG_DEBUG); 480 $resql = $db->query($sql); 481 if ($resql) { 482 $i = 0; 483 $num = $db->num_rows($resql); 484 485 while ($i < $num) { 486 $obj = $db->fetch_object($resql); 487 488 if ($obj->type == 'action') { 489 $contactaction = new ActionComm($db); 490 $contactaction->id = $obj->id; 491 $result = $contactaction->fetchResources(); 492 if ($result < 0) { 493 dol_print_error($db); 494 setEventMessage("company.lib::show_actions_done Error fetch ressource", 'errors'); 495 } 496 497 //if ($donetodo == 'todo') $sql.= " AND ((a.percent >= 0 AND a.percent < 100) OR (a.percent = -1 AND a.datep > '".$db->idate($now)."'))"; 498 //elseif ($donetodo == 'done') $sql.= " AND (a.percent = 100 OR (a.percent = -1 AND a.datep <= '".$db->idate($now)."'))"; 499 $tododone = ''; 500 if (($obj->percent >= 0 and $obj->percent < 100) || ($obj->percent == -1 && $obj->datep > $now)) { 501 $tododone = 'todo'; 502 } 503 504 $histo[$numaction] = array( 505 'type'=>$obj->type, 506 'tododone'=>$tododone, 507 'id'=>$obj->id, 508 'datestart'=>$db->jdate($obj->dp), 509 'dateend'=>$db->jdate($obj->dp2), 510 'note'=>$obj->label, 511 'message'=>$obj->message, 512 'percent'=>$obj->percent, 513 514 'userid'=>$obj->user_id, 515 'login'=>$obj->user_login, 516 'userfirstname'=>$obj->user_firstname, 517 'userlastname'=>$obj->user_lastname, 518 'userphoto'=>$obj->user_photo, 519 520 'contact_id'=>$obj->fk_contact, 521 'socpeopleassigned' => $contactaction->socpeopleassigned, 522 'lastname'=>$obj->lastname, 523 'firstname'=>$obj->firstname, 524 'fk_element'=>$obj->fk_element, 525 'elementtype'=>$obj->elementtype, 526 // Type of event 527 'acode'=>$obj->acode, 528 'alabel'=>$obj->alabel, 529 'libelle'=>$obj->alabel, // deprecated 530 'apicto'=>$obj->apicto 531 ); 532 } else { 533 $histo[$numaction] = array( 534 'type'=>$obj->type, 535 'tododone'=>'done', 536 'id'=>$obj->id, 537 'datestart'=>$db->jdate($obj->dp), 538 'dateend'=>$db->jdate($obj->dp2), 539 'note'=>$obj->label, 540 'message'=>$obj->message, 541 'percent'=>$obj->percent, 542 'acode'=>$obj->acode, 543 544 'userid'=>$obj->user_id, 545 'login'=>$obj->user_login, 546 'userfirstname'=>$obj->user_firstname, 547 'userlastname'=>$obj->user_lastname, 548 'userphoto'=>$obj->user_photo 549 ); 550 } 551 552 $numaction++; 553 $i++; 554 } 555 } else { 556 dol_print_error($db); 557 } 558 } 559 560 // Set $out to sow events 561 $out = ''; 562 563 if (empty($conf->agenda->enabled)) { 564 $langs->loadLangs(array("admin", "errors")); 565 $out = info_admin($langs->trans("WarningModuleXDisabledSoYouMayMissEventHere", $langs->transnoentitiesnoconv("Module2400Name")), 0, 0, 'warning'); 566 } 567 568 if (!empty($conf->agenda->enabled) || (!empty($conf->mailing->enabled) && !empty($objcon->email))) { 569 $delay_warning = $conf->global->MAIN_DELAY_ACTIONS_TODO * 24 * 60 * 60; 570 571 require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php'; 572 include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php'; 573 require_once DOL_DOCUMENT_ROOT.'/core/class/html.formactions.class.php'; 574 require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php'; 575 576 $formactions = new FormActions($db); 577 578 $actionstatic = new ActionComm($db); 579 $userstatic = new User($db); 580 $contactstatic = new Contact($db); 581 $userGetNomUrlCache = array(); 582 583 $out .= '<div class="filters-container" >'; 584 $out .= '<form name="listactionsfilter" class="listactionsfilter" action="'.$_SERVER["PHP_SELF"].'" method="POST">'; 585 $out .= '<input type="hidden" name="token" value="'.newToken().'">'; 586 587 if ($objcon && get_class($objcon) == 'Contact' && 588 (is_null($filterobj) || get_class($filterobj) == 'Societe')) { 589 $out .= '<input type="hidden" name="id" value="'.$objcon->id.'" />'; 590 } else { 591 $out .= '<input type="hidden" name="id" value="'.$filterobj->id.'" />'; 592 } 593 if ($filterobj && get_class($filterobj) == 'Societe') { 594 $out .= '<input type="hidden" name="socid" value="'.$filterobj->id.'" />'; 595 } 596 597 $out .= "\n"; 598 599 $out .= '<div class="div-table-responsive-no-min">'; 600 $out .= '<table class="noborder borderbottom centpercent">'; 601 602 $out .= '<tr class="liste_titre">'; 603 604 $out .= getTitleFieldOfList('Date', 0, $_SERVER["PHP_SELF"], 'a.datep', '', $param, '', $sortfield, $sortorder, '')."\n"; 605 606 $out .= '<th class="liste_titre"><strong class="hideonsmartphone">'.$langs->trans("Search").' : </strong></th>'; 607 if ($donetodo) { 608 $out .= '<th class="liste_titre"></th>'; 609 } 610 $out .= '<th class="liste_titre">'; 611 $out .= '<span class="fas fa-square inline-block fawidth30" style=" color: #ddd;" title="'.$langs->trans("ActionType").'"></span>'; 612 //$out .= img_picto($langs->trans("Type"), 'type'); 613 $out .= $formactions->select_type_actions($actioncode, "actioncode", '', empty($conf->global->AGENDA_USE_EVENT_TYPE) ? 1 : -1, 0, 0, 1, 'minwidth200imp'); 614 $out .= '</th>'; 615 $out .= '<th class="liste_titre maxwidth100onsmartphone">'; 616 $out .= '<input type="text" class="maxwidth100onsmartphone" name="search_agenda_label" value="'.$filters['search_agenda_label'].'" placeholder="'.$langs->trans("Label").'">'; 617 $out .= '</th>'; 618 619 $out .= '<th class="liste_titre width50 middle">'; 620 $searchpicto = $form->showFilterAndCheckAddButtons($massactionbutton ? 1 : 0, 'checkforselect', 1); 621 $out .= $searchpicto; 622 $out .= '</th>'; 623 $out .= '</tr>'; 624 625 626 $out .= '</table>'; 627 628 $out .= '</form>'; 629 $out .= '</div>'; 630 631 $out .= "\n"; 632 633 $out .= '<ul class="timeline">'; 634 635 if ($donetodo) { 636 $tmp = ''; 637 if (get_class($filterobj) == 'Societe') { 638 $tmp .= '<a href="'.DOL_URL_ROOT.'/comm/action/list.php?action=show_list&socid='.$filterobj->id.'&status=done">'; 639 } 640 $tmp .= ($donetodo != 'done' ? $langs->trans("ActionsToDoShort") : ''); 641 $tmp .= ($donetodo != 'done' && $donetodo != 'todo' ? ' / ' : ''); 642 $tmp .= ($donetodo != 'todo' ? $langs->trans("ActionsDoneShort") : ''); 643 //$out.=$langs->trans("ActionsToDoShort").' / '.$langs->trans("ActionsDoneShort"); 644 if (get_class($filterobj) == 'Societe') { 645 $tmp .= '</a>'; 646 } 647 $out .= getTitleFieldOfList($tmp); 648 } 649 650 651 //require_once DOL_DOCUMENT_ROOT.'/comm/action/class/cactioncomm.class.php'; 652 //$caction=new CActionComm($db); 653 //$arraylist=$caction->liste_array(1, 'code', '', (empty($conf->global->AGENDA_USE_EVENT_TYPE)?1:0), '', 1); 654 655 $actualCycleDate = false; 656 657 foreach ($histo as $key => $value) { 658 $actionstatic->fetch($histo[$key]['id']); // TODO Do we need this, we already have a lot of data of line into $histo 659 660 $actionstatic->type_picto = $histo[$key]['apicto']; 661 $actionstatic->type_code = $histo[$key]['acode']; 662 663 $url = DOL_URL_ROOT.'/comm/action/card.php?id='.$histo[$key]['id']; 664 665 $tmpa = dol_getdate($histo[$key]['datestart'], false); 666 if ($actualCycleDate !== $tmpa['year'].'-'.$tmpa['yday']) { 667 $actualCycleDate = $tmpa['year'].'-'.$tmpa['yday']; 668 $out .= '<!-- timeline time label -->'; 669 $out .= '<li class="time-label">'; 670 $out .= '<span class="timeline-badge-date">'; 671 $out .= dol_print_date($histo[$key]['datestart'], 'daytext', 'tzuserrel', $langs); 672 $out .= '</span>'; 673 $out .= '</li>'; 674 $out .= '<!-- /.timeline-label -->'; 675 } 676 677 678 $out .= '<!-- timeline item -->'."\n"; 679 $out .= '<li class="timeline-code-'.strtolower($actionstatic->code).'">'; 680 681 $out .= getTicketTimelineIcon($actionstatic, $histo, $key); 682 683 $out .= '<div class="timeline-item">'."\n"; 684 685 $out .= '<span class="timeline-header-action">'; 686 687 if (isset($histo[$key]['type']) && $histo[$key]['type'] == 'mailing') { 688 $out .= '<a class="timeline-btn" href="'.DOL_URL_ROOT.'/comm/mailing/card.php?id='.$histo[$key]['id'].'">'.img_object($langs->trans("ShowEMailing"), "email").' '; 689 $out .= $histo[$key]['id']; 690 $out .= '</a> '; 691 } else { 692 $out .= $actionstatic->getNomUrl(1, -1, 'valignmiddle').' '; 693 } 694 695 //if ($user->rights->agenda->allactions->read || $actionstatic->authorid == $user->id) 696 //{ 697 // $out.='<a href="'.$url.'" class="timeline-btn" title="'.$langs->trans('Show').'" ><i class="fa fa-calendar" ></i>'.$langs->trans('Show').'</a>'; 698 //} 699 700 if ($user->rights->agenda->allactions->create || 701 (($actionstatic->authorid == $user->id || $actionstatic->userownerid == $user->id) && $user->rights->agenda->myactions->create)) { 702 $out .= '<a class="timeline-btn" href="'.DOL_MAIN_URL_ROOT.'/comm/action/card.php?action=edit&id='.$actionstatic->id.'"><i class="fa fa-pencil" title="'.$langs->trans("Modify").'" ></i></a>'; 703 } 704 705 $out .= '</span>'; 706 // Date 707 $out .= '<span class="time"><i class="fa fa-clock-o"></i> '; 708 $out .= dol_print_date($histo[$key]['datestart'], 'dayhour', 'tzuserrel'); 709 if ($histo[$key]['dateend'] && $histo[$key]['dateend'] != $histo[$key]['datestart']) { 710 $tmpa = dol_getdate($histo[$key]['datestart'], true); 711 $tmpb = dol_getdate($histo[$key]['dateend'], true); 712 if ($tmpa['mday'] == $tmpb['mday'] && $tmpa['mon'] == $tmpb['mon'] && $tmpa['year'] == $tmpb['year']) { 713 $out .= '-'.dol_print_date($histo[$key]['dateend'], 'hour', 'tzuserrel'); 714 } else { 715 $out .= '-'.dol_print_date($histo[$key]['dateend'], 'dayhour', 'tzuserrel'); 716 } 717 } 718 $late = 0; 719 if ($histo[$key]['percent'] == 0 && $histo[$key]['datestart'] && $histo[$key]['datestart'] < ($now - $delay_warning)) { 720 $late = 1; 721 } 722 if ($histo[$key]['percent'] == 0 && !$histo[$key]['datestart'] && $histo[$key]['dateend'] && $histo[$key]['datestart'] < ($now - $delay_warning)) { 723 $late = 1; 724 } 725 if ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100 && $histo[$key]['dateend'] && $histo[$key]['dateend'] < ($now - $delay_warning)) { 726 $late = 1; 727 } 728 if ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100 && !$histo[$key]['dateend'] && $histo[$key]['datestart'] && $histo[$key]['datestart'] < ($now - $delay_warning)) { 729 $late = 1; 730 } 731 if ($late) { 732 $out .= img_warning($langs->trans("Late")).' '; 733 } 734 $out .= "</span>\n"; 735 736 // Ref 737 $out .= '<h3 class="timeline-header">'; 738 739 // Author of event 740 $out .= '<span class="messaging-author">'; 741 if ($histo[$key]['userid'] > 0) { 742 if (!isset($userGetNomUrlCache[$histo[$key]['userid']])) { // is in cache ? 743 $userstatic->fetch($histo[$key]['userid']); 744 $userGetNomUrlCache[$histo[$key]['userid']] = $userstatic->getNomUrl(-1, '', 0, 0, 16, 0, 'firstelselast', ''); 745 } 746 $out .= $userGetNomUrlCache[$histo[$key]['userid']]; 747 } 748 $out .= '</span>'; 749 750 // Title 751 $out .= ' <span class="messaging-title">'; 752 753 if ($actionstatic->code == 'TICKET_MSG') { 754 $out .= $langs->trans('TicketNewMessage'); 755 } elseif ($actionstatic->code == 'TICKET_MSG_PRIVATE') { 756 $out .= $langs->trans('TicketNewMessage').' <em>('.$langs->trans('Private').')</em>'; 757 } else { 758 if (isset($histo[$key]['type']) && $histo[$key]['type'] == 'action') { 759 $transcode = $langs->trans("Action".$histo[$key]['acode']); 760 $libelle = ($transcode != "Action".$histo[$key]['acode'] ? $transcode : $histo[$key]['alabel']); 761 $libelle = $histo[$key]['note']; 762 $actionstatic->id = $histo[$key]['id']; 763 $out .= dol_trunc($libelle, 120); 764 } 765 if (isset($histo[$key]['type']) && $histo[$key]['type'] == 'mailing') { 766 $out .= '<a href="'.DOL_URL_ROOT.'/comm/mailing/card.php?id='.$histo[$key]['id'].'">'.img_object($langs->trans("ShowEMailing"), "email").' '; 767 $transcode = $langs->trans("Action".$histo[$key]['acode']); 768 $libelle = ($transcode != "Action".$histo[$key]['acode'] ? $transcode : 'Send mass mailing'); 769 $out .= dol_trunc($libelle, 120); 770 } 771 } 772 773 $out .= '</span>'; 774 775 $out .= '</h3>'; 776 777 if (!empty($histo[$key]['message']) 778 && $actionstatic->code != 'AC_TICKET_CREATE' 779 && $actionstatic->code != 'AC_TICKET_MODIFY' 780 ) { 781 $out .= '<div class="timeline-body">'; 782 $out .= $histo[$key]['message']; 783 $out .= '</div>'; 784 } 785 786 // Timeline footer 787 $footer = ''; 788 789 // Contact for this action 790 if (isset($histo[$key]['socpeopleassigned']) && is_array($histo[$key]['socpeopleassigned']) && count($histo[$key]['socpeopleassigned']) > 0) { 791 $contactList = ''; 792 foreach ($histo[$key]['socpeopleassigned'] as $cid => $Tab) { 793 $contact = new Contact($db); 794 $result = $contact->fetch($cid); 795 796 if ($result < 0) { 797 dol_print_error($db, $contact->error); 798 } 799 800 if ($result > 0) { 801 $contactList .= !empty($contactList) ? ', ' : ''; 802 $contactList .= $contact->getNomUrl(1); 803 if (isset($histo[$key]['acode']) && $histo[$key]['acode'] == 'AC_TEL') { 804 if (!empty($contact->phone_pro)) { 805 $contactList .= '('.dol_print_phone($contact->phone_pro).')'; 806 } 807 } 808 } 809 } 810 811 $footer .= $langs->trans('ActionOnContact').' : '.$contactList; 812 } elseif (empty($objcon->id) && isset($histo[$key]['contact_id']) && $histo[$key]['contact_id'] > 0) { 813 $contact = new Contact($db); 814 $result = $contact->fetch($histo[$key]['contact_id']); 815 816 if ($result < 0) { 817 dol_print_error($db, $contact->error); 818 } 819 820 if ($result > 0) { 821 $footer .= $contact->getNomUrl(1); 822 if (isset($histo[$key]['acode']) && $histo[$key]['acode'] == 'AC_TEL') { 823 if (!empty($contact->phone_pro)) { 824 $footer .= '('.dol_print_phone($contact->phone_pro).')'; 825 } 826 } 827 } 828 } 829 830 $documents = getTicketActionCommEcmList($actionstatic); 831 if (!empty($documents)) { 832 $footer .= '<div class="timeline-documents-container">'; 833 foreach ($documents as $doc) { 834 $footer .= '<span id="document_'.$doc->id.'" class="timeline-documents" '; 835 $footer .= ' data-id="'.$doc->id.'" '; 836 $footer .= ' data-path="'.$doc->filepath.'"'; 837 $footer .= ' data-filename="'.dol_escape_htmltag($doc->filename).'" '; 838 $footer .= '>'; 839 840 $filePath = DOL_DATA_ROOT.'/'.$doc->filepath.'/'.$doc->filename; 841 $mime = dol_mimetype($filePath); 842 $file = $actionstatic->id.'/'.$doc->filename; 843 $thumb = $actionstatic->id.'/thumbs/'.substr($doc->filename, 0, strrpos($doc->filename, '.')).'_mini'.substr($doc->filename, strrpos($doc->filename, '.')); 844 $doclink = dol_buildpath('document.php', 1).'?modulepart=actions&attachment=0&file='.urlencode($file).'&entity='.$conf->entity; 845 $viewlink = dol_buildpath('viewimage.php', 1).'?modulepart=actions&file='.urlencode($thumb).'&entity='.$conf->entity; 846 847 $mimeAttr = ' mime="'.$mime.'" '; 848 $class = ''; 849 if (in_array($mime, array('image/png', 'image/jpeg', 'application/pdf'))) { 850 $class .= ' documentpreview'; 851 } 852 853 $footer .= '<a href="'.$doclink.'" class="btn-link '.$class.'" target="_blank" '.$mimeAttr.' >'; 854 $footer .= img_mime($filePath).' '.$doc->filename; 855 $footer .= '</a>'; 856 857 $footer .= '</span>'; 858 } 859 $footer .= '</div>'; 860 } 861 862 if (!empty($footer)) { 863 $out .= '<div class="timeline-footer">'.$footer.'</div>'; 864 } 865 866 $out .= '</div>'."\n"; // end timeline-item 867 868 $out .= '</li>'; 869 $out .= '<!-- END timeline item -->'; 870 871 $i++; 872 } 873 $out .= "</ul>\n"; 874 } 875 876 if ($noprint) { 877 return $out; 878 } else { 879 print $out; 880 } 881} 882 883/** 884 * Get timeline icon 885 * @param ActionComm $actionstatic actioncomm 886 * @param array $histo histo 887 * @param int $key key 888 * @return string 889 */ 890function getTicketTimelineIcon($actionstatic, &$histo, $key) 891{ 892 global $conf, $langs; 893 $out = '<!-- timeline icon -->'."\n"; 894 $iconClass = 'fa fa-comments'; 895 $img_picto = ''; 896 $colorClass = ''; 897 $pictoTitle = ''; 898 899 if ($histo[$key]['percent'] == -1) { 900 $colorClass = 'timeline-icon-not-applicble'; 901 $pictoTitle = $langs->trans('StatusNotApplicable'); 902 } elseif ($histo[$key]['percent'] == 0) { 903 $colorClass = 'timeline-icon-todo'; 904 $pictoTitle = $langs->trans('StatusActionToDo').' (0%)'; 905 } elseif ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100) { 906 $colorClass = 'timeline-icon-in-progress'; 907 $pictoTitle = $langs->trans('StatusActionInProcess').' ('.$histo[$key]['percent'].'%)'; 908 } elseif ($histo[$key]['percent'] >= 100) { 909 $colorClass = 'timeline-icon-done'; 910 $pictoTitle = $langs->trans('StatusActionDone').' (100%)'; 911 } 912 913 if ($actionstatic->code == 'AC_TICKET_CREATE') { 914 $iconClass = 'fa fa-ticket'; 915 } elseif ($actionstatic->code == 'AC_TICKET_MODIFY') { 916 $iconClass = 'fa fa-pencilxxx'; 917 } elseif ($actionstatic->code == 'TICKET_MSG') { 918 $iconClass = 'fa fa-comments'; 919 } elseif ($actionstatic->code == 'TICKET_MSG_PRIVATE') { 920 $iconClass = 'fa fa-mask'; 921 } elseif (!empty($conf->global->AGENDA_USE_EVENT_TYPE)) { 922 if ($actionstatic->type_picto) { 923 $img_picto = img_picto('', $actionstatic->type_picto); 924 } else { 925 if ($actionstatic->type_code == 'AC_RDV') { 926 $iconClass = 'fa fa-handshake'; 927 } elseif ($actionstatic->type_code == 'AC_TEL') { 928 $iconClass = 'fa fa-phone'; 929 } elseif ($actionstatic->type_code == 'AC_FAX') { 930 $iconClass = 'fa fa-fax'; 931 } elseif ($actionstatic->type_code == 'AC_EMAIL') { 932 $iconClass = 'fa fa-envelope'; 933 } elseif ($actionstatic->type_code == 'AC_INT') { 934 $iconClass = 'fa fa-shipping-fast'; 935 } elseif ($actionstatic->type_code == 'AC_OTH_AUTO') { 936 $iconClass = 'fa fa-robot'; 937 } elseif (!preg_match('/_AUTO/', $actionstatic->type_code)) { 938 $iconClass = 'fa fa-robot'; 939 } 940 } 941 } 942 943 $out .= '<i class="'.$iconClass.' '.$colorClass.'" title="'.$pictoTitle.'">'.$img_picto.'</i>'."\n"; 944 return $out; 945} 946 947/** 948 * getTicketActionCommEcmList 949 * 950 * @param ActionComm $object Object ActionComm 951 * @return array Array of documents in index table 952 */ 953function getTicketActionCommEcmList($object) 954{ 955 global $conf, $db; 956 957 $documents = array(); 958 959 $sql = 'SELECT ecm.rowid as id, ecm.src_object_type, ecm.src_object_id, ecm.filepath, ecm.filename'; 960 $sql .= ' FROM '.MAIN_DB_PREFIX.'ecm_files ecm'; 961 $sql .= ' WHERE ecm.filepath = \'agenda/'.$object->id.'\''; 962 //$sql.= ' ecm.src_object_type = \''.$object->element.'\' AND ecm.src_object_id = '.$object->id; // Actually upload file doesn't add type 963 $sql .= ' ORDER BY ecm.position ASC'; 964 965 $resql = $db->query($sql); 966 if ($resql) { 967 if ($db->num_rows($resql)) { 968 while ($obj = $db->fetch_object($resql)) { 969 $documents[$obj->id] = $obj; 970 } 971 } 972 } 973 974 return $documents; 975} 976