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