dolibarr 25.0.0-alpha
html.lib.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2026 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2026 Frédéric France <frederic.france@free.fr>
4 * Copyright (C) 2026 MDW <mdeweerd@users.noreply.github.com>
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 * or see https://www.gnu.org/
19 */
20
46function dolPrintLabel($s, $escapeonlyhtmltags = 0)
47{
48 return dol_escape_htmltag(dol_string_nohtmltag($s, 1, 'UTF-8', 0, 0), 0, 0, '', $escapeonlyhtmltags, 1);
49}
50
60function dolPrintText($s)
61{
62 return dol_escape_htmltag(dol_string_nohtmltag($s, 2, 'UTF-8', 0, 0), 0, 1, '', 0, 1);
63}
64
77function dolPrintHTML($s, $allowiframe = 0, $moreallowedtags = array())
78{
79 // If text is already HTML, we want to escape only dangerous chars else we want to escape all content.
80 //$isAlreadyHTML = dol_textishtml($s);
81
82 // dol_htmlentitiesbr encode all chars except "'" if string is not already HTML, but
83 // encode only special char like accented chars but not &, <, >, ", ' if already HTML.
84 $stringWithEntitesForSpecialChar = dol_htmlentitiesbr((string) $s);
85
86 $allowedtags = 'common';
87 if (!empty($moreallowedtags)) {
88 $allowedtags .= ','.implode(',', $moreallowedtags);
89 }
90 return dol_escape_htmltag(dol_htmlwithnojs(dol_string_onlythesehtmltags($stringWithEntitesForSpecialChar, 1, 1, 1, $allowiframe, $allowedtags)), 1, 1, $allowedtags, 0, 1);
91}
92
104function dolPrintHTMLForAttribute($s, $escapeonlyhtmltags = 0, $allowothertags = array())
105{
106 $allowedtags = array('br', 'b', 'font', 'hr', 'span');
107 if (!empty($allowothertags) && is_array($allowothertags)) {
108 $allowedtags = array_merge($allowedtags, $allowothertags);
109 }
110 // The dol_htmlentitiesbr will convert simple text into html, including switching accent into HTML entities
111 // The dol_escape_htmltag will escape html tags.
112 if ($escapeonlyhtmltags) {
113 return dol_escape_htmltag(dol_string_onlythesehtmltags($s, 1, 0, 0, 0, $allowedtags), 1, -1, '', 1, 1);
114 } else {
115 return dol_escape_htmltag(dol_string_onlythesehtmltags(dol_htmlentitiesbr($s), 1, 0, 0, 0, $allowedtags), 1, -1, '', 0, 1);
116 }
117}
118
128function dolPrintHTMLForAttributeUrl($s)
129{
130 // The dol_htmlentitiesbr has been removed compared to dolPrintHTMLForAttribute because we know content is a HTML URL string (even if we have no way to detect it automatically)
131 // The dol_escape_htmltag will escape html chars.
132 $escapeonlyhtmltags = 1;
133 return dol_escape_htmltag(dol_string_onlythesehtmltags($s, 1, 1, 1, 0, array()), 0, 0, '', $escapeonlyhtmltags, 1);
134}
135
146function dolPrintHTMLForTextArea($s, $allowiframe = 0)
147{
148 return dol_escape_htmltag(dol_htmlwithnojs(dol_string_onlythesehtmltags(dol_htmlentitiesbr($s), 1, 1, 1, $allowiframe)), 1, 1, '', 0, 1);
149}
150
158function dolPrintPassword($s)
159{
160 return htmlspecialchars($s, ENT_HTML5, 'UTF-8');
161}
162
163
181function dol_escape_htmltag($stringtoescape, $keepb = 0, $keepn = 0, $noescapetags = '', $escapeonlyhtmltags = 0, $cleanalsojavascript = 0)
182{
183 if ($stringtoescape === null) {
184 return '';
185 }
186 $reg = array();
187 if (preg_match('/^common([a-z,]*)/', $noescapetags, $reg)) {
188 $noescapetags = 'html,body,a,b,em,hr,i,u,ul,ol,li,br,div,img,font,p,span,strong,table,tr,td,th,tbody,h1,h2,h3,h4,h5,h6,h7,h8,h9';
189 // Add also html5 tags
190 $noescapetags .= ',header,footer,nav,section,menu,menuitem';
191 if (!empty($reg[1])) {
192 $noescapetags .= $reg[1];
193 }
194 }
195 if ($cleanalsojavascript) {
196 $stringtoescape = dol_string_onlythesehtmltags($stringtoescape, 0, 0, $cleanalsojavascript, 0, array(), 0);
197 }
198
199 // escape quotes and backslashes, newlines, etc.
200 if ($escapeonlyhtmltags) {
201 $tmp = htmlspecialchars_decode((string) $stringtoescape, ENT_COMPAT);
202 } else {
203 // We make a manipulation by calling the html_entity_decode() to convert content into NON HTML UTF8 string.
204 // Because content can be or not already HTML.
205 // For example, this decode &egrave; into its UTF-8 char so string is UTF8 (but numbers entities like &#39; is not decoded).
206 // In a future, we should not need this
207
208 $tmp = (string) $stringtoescape;
209
210 // We protect the 6 special entities that we don't want to decode.
211 $tmp = str_ireplace('&lt', '__DONOTDECODELT', $tmp);
212 $tmp = str_ireplace('&gt', '__DONOTDECODEGT', $tmp);
213 $tmp = str_ireplace('&amp', '__DONOTDECODEAMP', $tmp);
214 $tmp = str_ireplace('&quot', '__DONOTDECODEQUOT', $tmp);
215 $tmp = str_ireplace('&apos', '__DONOTDECODEAPOS', $tmp);
216 $tmp = str_ireplace('&#39', '__DONOTDECODE39', $tmp);
217
218 $tmp = html_entity_decode((string) $tmp, ENT_COMPAT, 'UTF-8'); // Convert entities into UTF8
219
220 // We restore the 6 special entities that we don't want to have been decoded by previous command
221 $tmp = str_ireplace('__DONOTDECODELT', '&lt', $tmp);
222 $tmp = str_ireplace('__DONOTDECODEGT', '&gt', $tmp);
223 $tmp = str_ireplace('__DONOTDECODEAMP', '&amp', $tmp);
224 $tmp = str_ireplace('__DONOTDECODEQUOT', '&quot', $tmp);
225 $tmp = str_ireplace('__DONOTDECODEAPOS', '&apos', $tmp);
226 $tmp = str_ireplace('__DONOTDECODE39', '&#39', $tmp);
227
228 $tmp = str_ireplace('&#39;', '__SIMPLEQUOTE__', $tmp); // HTML 4
229 }
230 if (!$keepb) {
231 $tmp = strtr($tmp, array("<b>" => '', '</b>' => '', '<strong>' => '', '</strong>' => ''));
232 }
233 if (!$keepn) {
234 $tmp = strtr($tmp, array("\r" => '\\r', "\n" => '\\n'));
235 } elseif ($keepn == -1) {
236 $tmp = strtr($tmp, array("\r" => '', "\n" => ''));
237 }
238
239 if ($escapeonlyhtmltags) {
240 $tmp = htmlspecialchars($tmp, ENT_COMPAT, 'UTF-8');
241 return $tmp;
242 } else {
243 // Now we protect all the tags we want to keep
244 $tmparrayoftags = array();
245 $tmparrayoftagsfound = array();
246 if ($noescapetags) {
247 $tmparrayoftags = explode(',', $noescapetags);
248 }
249
250 if (count($tmparrayoftags)) {
251 // Now we will protect tags (defined into $tmparrayoftags) that we want to keep untouched
252
253 $reg = array();
254 // Remove reserved keywords. They are forbidden in a source string
255 $tmp = str_ireplace(array('__DOUBLEQUOTE', '__BEGINTAGTOREPLACE', '__ENDTAGTOREPLACE', '__BEGINENDTAGTOREPLACE'), '', $tmp);
256
257 foreach ($tmparrayoftags as $tagtoreplace) {
258 // The 4 patterns below all start with '<tag' or '</tag': a tag that is not in the string can not be protected, so it
259 // does not need the 4 regex passes, nor to be restored later. On a list page, most strings hold no tag at all.
260 if (stripos($tmp, '<' . $tagtoreplace) === false && stripos($tmp, '</' . $tagtoreplace) === false) {
261 continue;
262 }
263 $tmparrayoftagsfound[] = $tagtoreplace;
264
265 // For case of tag without attributes '<abc>', '</abc>', '<abc />', we protect them to avoid transformation by htmlentities() later
266 $tmp = preg_replace('/<' . preg_quote($tagtoreplace, '/') . '>/', '__BEGINTAGTOREPLACE' . $tagtoreplace . '__', $tmp);
267 $tmp = str_ireplace('</' . $tagtoreplace . '>', '__ENDTAGTOREPLACE' . $tagtoreplace . '__', $tmp);
268 $tmp = preg_replace('/<' . preg_quote($tagtoreplace, '/') . ' \/>/', '__BEGINENDTAGTOREPLACE' . $tagtoreplace . '__', $tmp);
269
270 // For case of tag with attributes.
271 // All the occurrences are protected in a single pass: the replacement string contains no '<', so it
272 // can never build a new tag to protect (a loop replacing one distinct attribute string per round was
273 // rescanning the whole content for each of them, so the cost was quadratic on large contents).
274 $tmp = preg_replace_callback(
275 '/<'.preg_quote($tagtoreplace, '/').'(\s+)([^>]+)>/',
280 static function ($reg) use ($tagtoreplace) {
281 // We want to protect the attribute part ... in '<xxx ...>' to avoid transformation by htmlentities() later
282 $tmpattributes = str_ireplace(array('[', ']'), '_', $reg[2]); // We must never have [ ] inside the attribute string
283 $tmpattributes = str_ireplace('"', '__DOUBLEQUOTE__', $tmpattributes);
284 $tmpattributes = preg_replace('/[^a-z0-9_%,\/\?\;\s=&\.\-@:\.#\+]/i', '', $tmpattributes);
285 //$tmpattributes = preg_replace("/float:\s*(left|right)/", "", $tmpattributes); // Disabled: we must not remove content
286 return '__BEGINTAGTOREPLACE'.$tagtoreplace.'['.$tmpattributes.']__';
287 },
288 $tmp
289 ) ?? $tmp;
290 }
291
292 $tmp = str_ireplace('&amp', '__ANDNOSEMICOLON__', $tmp);
293 $tmp = str_ireplace('&quot', '__DOUBLEQUOTENOSEMICOLON__', $tmp);
294 $tmp = str_ireplace('&lt', '__LESSTHAN__', $tmp);
295 $tmp = str_ireplace('&gt', '__GREATERTHAN__', $tmp);
296 }
297
298 // Warning: htmlentities encode all special chars that remains (except "'" with ENT_COMPAT).
299 $result = htmlentities($tmp, ENT_COMPAT, 'UTF-8');
300
301 //print $result;
302
303 if (count($tmparrayoftags)) {
304 // Restore protected tags
305 foreach ($tmparrayoftagsfound as $tagtoreplace) {
306 $result = str_ireplace('__BEGINTAGTOREPLACE' . $tagtoreplace . '__', '<' . $tagtoreplace . '>', $result);
307 $result = preg_replace('/__BEGINTAGTOREPLACE' . $tagtoreplace . '\[([^\]]*)\]__/', '<' . $tagtoreplace . ' \1>', $result);
308 $result = str_ireplace('__ENDTAGTOREPLACE' . $tagtoreplace . '__', '</' . $tagtoreplace . '>', $result);
309 $result = str_ireplace('__BEGINENDTAGTOREPLACE' . $tagtoreplace . '__', '<' . $tagtoreplace . ' />', $result);
310 $result = preg_replace('/__BEGINENDTAGTOREPLACE' . $tagtoreplace . '\[([^\]]*)\]__/', '<' . $tagtoreplace . ' \1 />', $result);
311 }
312
313 $result = str_ireplace('__DOUBLEQUOTE__', '"', $result);
314
315 $result = str_ireplace('__ANDNOSEMICOLON__', '&amp', $result);
316 $result = str_ireplace('__DOUBLEQUOTENOSEMICOLON__', '&quot', $result);
317 $result = str_ireplace('__LESSTHAN__', '&lt', $result);
318 $result = str_ireplace('__GREATERTHAN__', '&gt', $result);
319 }
320
321 $result = str_ireplace('__SIMPLEQUOTE__', '&#39;', $result);
322
323 //$result="\n\n\n".var_export($tmp, true)."\n\n\n".var_export($result, true);
324
325 return $result;
326 }
327}
328
329
341function dolButtonToOpenExportDialog($name, $label, $buttonstring, $exportSiteName, $overwriteGitUrl, $website)
342{
343 global $langs, $db;
344
345 $form = new Form($db);
346
347 $templatenameforexport = $website->name_template; // Example 'website_template-corporate'
348 if (empty($templatenameforexport)) {
349 $templatenameforexport = 'website_' . $website->ref;
350 }
351
352 $out = '';
353 $out .= '<input type="button" class="cursorpointer button bordertransp" id="open-dialog-' . $name . '" value="' . dol_escape_htmltag($buttonstring) . '"/>';
354
355 // for generate popup
356 $out .= '<script nonce="' . getNonce() . '" type="text/javascript">';
357 $out .= 'jQuery(document).ready(function () {';
358 $out .= ' jQuery("#open-dialog-' . $name . '").click(function () {';
359 $out .= ' var dialogHtml = \'';
360
361 $dialogcontent = ' <div id="custom-dialog-' . $name . '">';
362 $dialogcontent .= ' <div style="margin-top: 20px;">';
363 $dialogcontent .= ' <label for="export-site-' . $name . '"><strong>' . $langs->trans("ExportSiteLabel") . '...</label><br>';
364 $dialogcontent .= ' <button class="button smallpaddingimp" id="export-site-' . $name . '">' . dol_escape_htmltag($langs->trans("DownloadZip")) . '</button>';
365 $dialogcontent .= ' </div>';
366 $dialogcontent .= ' <br>';
367 $dialogcontent .= ' <div style="margin-top: 20px;">';
368 $dialogcontent .= ' <strong>' . $langs->trans("ExportSiteGitLabel") . ' ' . $form->textwithpicto('', $langs->trans("SourceFiles"), 1, 'help', '', 0, 3, '') . '</strong><br>';
369 $dialogcontent .= ' <form action="' . dol_escape_htmltag($overwriteGitUrl) . '" method="POST">';
370 $dialogcontent .= ' <input type="hidden" name="action" value="overwritesite">';
371 $dialogcontent .= ' <input type="hidden" name="token" value="' . newToken() . '">';
372 $dialogcontent .= ' <input type="text" autofocus name="export_path" id="export-path-' . $name . '" placeholder="' . $langs->trans('ExportPath') . '" style="width:400px " value="' . dol_escape_htmltag($templatenameforexport) . '"/><br>';
373 $dialogcontent .= ' <button type="submit" class="button smallpaddingimp" id="overwrite-git-' . $name . '">' . dol_escape_htmltag($langs->trans("ExportIntoGIT")) . '</button>';
374 $dialogcontent .= ' </form>';
375 $dialogcontent .= ' </div>';
376 $dialogcontent .= ' </div>';
377
378 $out .= dol_escape_js($dialogcontent);
379
380 $out .= '\';';
381
382
383 // Add the content of the dialog to the body of the page
384 $out .= ' var $dialog = jQuery("#custom-dialog-' . $name . '");';
385 $out .= ' if ($dialog.length > 0) {
386 $dialog.remove();
387 }
388 jQuery("body").append(dialogHtml);';
389
390 // Configuration of popup
391 $out .= ' jQuery("#custom-dialog-' . $name . '").dialog({';
392 $out .= ' autoOpen: false,';
393 $out .= ' modal: true,';
394 $out .= ' height: 290,';
395 $out .= ' width: "40%",';
396 $out .= ' title: \'' . dol_escape_js($label) . '\',';
397 $out .= ' });';
398
399 // Simulate a click on the original "submit" input to export the site.
400 $out .= ' jQuery("#export-site-' . $name . '").click(function () {';
401 $out .= ' console.log("Click on exportsite.");';
402 $out .= ' var target = jQuery("input[name=\'' . dol_escape_js($exportSiteName) . '\']");';
403 $out .= ' console.log("element founded:", target.length > 0);';
404 $out .= ' if (target.length > 0) { target.click(); }';
405 $out .= ' jQuery("#custom-dialog-' . $name . '").dialog("close");';
406 $out .= ' });';
407
408 // open popup
409 $out .= ' jQuery("#custom-dialog-' . $name . '").dialog("open");';
410 $out .= ' return false;';
411 $out .= ' });';
412 $out .= '});';
413 $out .= '</script>';
414
415 return $out;
416}
417
418
435function dolButtonToOpenUrlInDialogPopup($name, $label, $buttonstring, $url, $disabled = '', $morecss = 'classlink button bordertransp', $jsonopen = '', $jsonclose = '', $accesskey = '')
436{
437 global $conf;
438
439 if (strpos($url, '?') > 0) {
440 $url .= '&dol_hide_topmenu=1&dol_hide_leftmenu=1&dol_openinpopup=' . urlencode($name);
441 } else {
442 $url .= '?dol_hide_topmenu=1&dol_hide_leftmenu=1&dol_openinpopup=' . urlencode($name);
443 }
444
445 if (preg_match('/^https/i', $url)) {
446 $urltoopen = $url;
447 } else {
448 $urltoopen = DOL_URL_ROOT . $url;
449 }
450
451 $out = '';
452
453 //print '<input type="submit" class="button bordertransp"'.$disabled.' value="'.dol_escape_htmltag($langs->trans("MediaFiles")).'" name="file_manager">';
454 $out .= '<!-- a link for button to open url into a dialog popup -->';
455 $out .= '<a ' . ($accesskey ? ' accesskey="' . $accesskey . '"' : '') . ' class="cursorpointer reposition button_' . $name . ($morecss ? ' ' . $morecss : '') . '"' . $disabled . ' title="' . dol_escape_htmltag($label) . '"';
456 if (empty($conf->use_javascript_ajax)) {
457 $out .= ' href="' . $urltoopen . '" target="_blank"';
458 } elseif ($jsonopen) {
459 $out .= ' href="#" onclick="' . $jsonopen . '"';
460 } else {
461 $out .= ' href="#"';
462 }
463 $out .= '>' . $buttonstring . '</a>';
464
465 if (!empty($conf->use_javascript_ajax)) {
466 // Add code to open url using the popup.
467 $out .= '<!-- code to open popup and variables to retrieve returned variables -->';
468 $out .= '<div id="idfordialog' . $name . '" class="hidden">' . (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2 ? 'div for dialog' : '') . '</div>';
469
470 $out .= '<!-- Add js code to open dialog popup on dialog -->';
471 $out .= '<script nonce="' . getNonce() . '" type="text/javascript">
472 jQuery(document).ready(function () {
473 jQuery(".button_' . $name . '").click(function () {
474 console.log(\'Open popup with jQuery(...).dialog() on URL ' . dol_escape_js($urltoopen) . '\');
475 var $tmpdialog = $(\'#idfordialog' . $name . '\');
476 $tmpdialog.html(\'<iframe class="iframedialog" id="iframedialog' . $name . '" style="border: 0px;" src="' . $urltoopen . '" width="100%" height="98%"></iframe>\');
477 $tmpdialog.dialog({
478 autoOpen: false,
479 modal: true,
480 height: (window.innerHeight - 150),
481 width: \'80%\',
482 title: \'' . dol_escape_js($label) . '\',
483 open: function (event, ui) {
484 console.log("open popup name=' . $name . '");
485 },
486 close: function (event, ui) {
487 console.log("Popup is closed, run jsonclose = ' . $jsonclose . '");
488 ' . (empty($jsonclose) || preg_match('/^TODO/', $jsonclose) ? '' : $jsonclose . ';') . '
489 }
490 });
491
492 $tmpdialog.dialog(\'open\');
493 return false;
494 });
495 });
496 </script>';
497 }
498 return $out;
499}
500
518function dol_fiche_head($links = array(), $active = '0', $title = '', $notab = 0, $picto = '', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limittoshow = 0, $moretabssuffix = '')
519{
520 print dol_get_fiche_head($links, $active, $title, $notab, $picto, $pictoisfullpath, $morehtmlright, $morecss, $limittoshow, $moretabssuffix);
521}
522
540function dol_get_fiche_head($links = array(), $active = '', $title = '', $notab = 0, $picto = '', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limittoshow = 0, $moretabssuffix = '', $dragdropfile = 0, $morecssdiv = '')
541{
542 global $conf, $langs, $hookmanager;
543
544 // Show title
545 $showtitle = 1;
546 if (!empty($conf->dol_optimize_smallscreen)) {
547 $showtitle = 0;
548 }
549
550 $out = "\n" . '<!-- dol_fiche_head - dol_get_fiche_head -->';
551
552 if ((!empty($title) && $showtitle) || $morehtmlright || !empty($links)) {
553 $out .= '<div class="tabs' . ($picto ? '' : ' nopaddingleft') . '" data-role="controlgroup" data-type="horizontal">' . "\n";
554 }
555
556 // Show right part
557 if ($morehtmlright) {
558 $out .= '<div class="inline-block floatright tabsElem">' . $morehtmlright . '</div>'; // Output right area first so when space is missing, text is in front of tabs and not under.
559 }
560
561 // Show tabs
562
563 // Define max of key (max may be higher than sizeof because of hole due to module disabling some tabs).
564 $maxkey = -1;
565 if (is_array($links) && !empty($links)) {
566 $keys = array_keys($links);
567 if (count($keys)) {
568 $maxkey = max($keys);
569 }
570 }
571
572 // if =0 we don't use the feature
573 if (empty($limittoshow)) {
574 $limittoshow = getDolGlobalInt('MAIN_MAXTABS_IN_CARD', 99);
575 }
576 if (!empty($conf->dol_optimize_smallscreen)) { // If on smartphone, we limit to 1 tab to show
577 $limittoshow = 1;
578 }
579
580 $displaytab = 0;
581 $nbintab = 0;
582 $popuptab = 0;
583 $outmore = '';
584 for ($i = 0; $i <= $maxkey; $i++) {
585 if ((is_numeric($active) && $i == $active) || (!empty($links[$i][2]) && !is_numeric($active) && $active == $links[$i][2])) {
586 // If active tab is already present
587 if ($i >= $limittoshow) {
588 $limittoshow--;
589 }
590 }
591 }
592
593 for ($i = 0; $i <= $maxkey; $i++) {
594 if ((is_numeric($active) && $i == $active) || (!empty($links[$i][2]) && !is_numeric($active) && $active == $links[$i][2])) {
595 $isactive = true;
596 } else {
597 $isactive = false;
598 }
599
600 if ($i < $limittoshow || $isactive) {
601 // Output entry with a visible tab
602 $out .= '<div class="inline-block tabsElem' . ($isactive ? ' tabsElemActive' : '') . ((!$isactive && getDolGlobalString('MAIN_HIDE_INACTIVETAB_ON_PRINT')) ? ' hideonprint' : '') . '"><!-- id tab = ' . (empty($links[$i][2]) ? '' : dol_escape_htmltag($links[$i][2])) . ' -->';
603
604 if (isset($links[$i][2]) && $links[$i][2] == 'image') {
605 if (!empty($links[$i][0])) {
606 $out .= '<a class="tabimage' . ($morecss ? ' ' . $morecss : '') . '" href="' . $links[$i][0] . '">' . $links[$i][1] . '</a>' . "\n";
607 } else {
608 $out .= '<span class="tabspan">' . $links[$i][1] . '</span>' . "\n";
609 }
610 } elseif (!empty($links[$i][1])) {
611 //print "x $i $active ".$links[$i][2]." z";
612 $out .= '<div class="tab tab' . ($isactive ? 'active' : 'unactive') . '" style="margin: 0 !important">';
613
614 if (!empty($links[$i][0])) {
615 $titletoshow = preg_replace('/<.*$/', '', $links[$i][1]);
616 $out .= '<a' . (!empty($links[$i][2]) ? ' id="' . $links[$i][2] . '"' : '') . ' class="tab inline-block valignmiddle' . ($morecss ? ' ' . $morecss : '') . (!empty($links[$i][5]) ? ' ' . $links[$i][5] : '') . '" href="' . $links[$i][0] . '" title="' . dol_escape_htmltag($titletoshow) . '">';
617 }
618
619 if ($displaytab == 0 && $picto) {
620 $out .= img_picto($title, $picto, '', $pictoisfullpath, 0, 0, '', 'imgTabTitle paddingright marginrightonlyshort');
621 }
622
623 $out .= $links[$i][1];
624 if (!empty($links[$i][0])) {
625 $out .= '</a>' . "\n";
626 }
627 $out .= empty($links[$i][4]) ? '' : $links[$i][4];
628 $out .= '</div>';
629 }
630
631 $out .= '</div>';
632 } else {
633 // Add entry into the combo popup with the other tabs
634 if (!$popuptab) {
635 $popuptab = 1;
636 $outmore .= '<div class="popuptabset wordwrap">'; // The css used to hide/show popup
637 }
638 $outmore_content = '';
639
640 if (isset($links[$i][2]) && $links[$i][2] == 'image') {
641 if (!empty($links[$i][0])) {
642 $outmore_content .= '<a class="tabimage' . ($morecss ? ' ' . $morecss : '') . '" href="' . $links[$i][0] . '">' . $links[$i][1] . '</a>' . "\n";
643 } else {
644 $outmore_content .= '<span class="tabspan">' . $links[$i][1] . '</span>' . "\n";
645 }
646 } elseif (!empty($links[$i][1])) {
647 $outmore_content .= '<a' . (!empty($links[$i][2]) ? ' id="' . $links[$i][2] . '"' : '') . ' class="wordwrap inline-block' . ($morecss ? ' ' . $morecss : '') . '" href="' . $links[$i][0] . '">';
648 $outmore_content .= preg_replace('/([a-z])\|([a-z])/i', '\\1 | \\2', $links[$i][1]); // Replace x|y with x | y to allow wrap on long composed texts.
649 $outmore_content .= '</a>' . "\n";
650 }
651 if ($outmore_content !== '') {
652 $outmore .= '<div class="popuptab wordwrap" style="display:inherit;">' . $outmore_content . '</div>';
653 }
654
655 $nbintab++;
656 }
657
658 $displaytab = $i + 1;
659 }
660 if ($popuptab) {
661 $outmore .= '</div>';
662 }
663
664 if ($popuptab) { // If there is some tabs not shown
665 $left = ($langs->trans("DIRECTION") == 'rtl' ? 'right' : 'left');
666 $right = ($langs->trans("DIRECTION") == 'rtl' ? 'left' : 'right');
667 $widthofpopup = 240;
668
669 $tabsname = $moretabssuffix;
670 if (empty($tabsname)) {
671 $tabsname = str_replace("@", "", $picto);
672 }
673 $out .= '<div id="moretabs' . $tabsname . '" class="inline-block tabsElem valignmiddle">';
674 if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
675 $out .= '<div class="tab valignmiddle"><a href="#" class="tab moretab inline-block tabunactive valignmiddle"><span class="fa fa-angle-down"></span> <span class="opacitymedium">+' . $nbintab . '</span></a></div>'; // Do not use "reposition" class in the "More".
676 }
677 $out .= '<div id="moretabsList' . $tabsname . '" style="width: ' . $widthofpopup . 'px; position: absolute; ' . $left . ': -999em; text-align: ' . $left . '; margin:0px; padding:2px; z-index:10;">';
678 $out .= $outmore;
679 $out .= '</div>';
680 $out .= '<div></div>';
681 $out .= "</div>\n";
682
683 $out .= '<script nonce="' . getNonce() . '">';
684 $out .= "$('#moretabs" . $tabsname . "').mouseenter( function() {
685 var x = this.offsetLeft, y = this.offsetTop;
686 console.log('mouseenter " . $left . " x='+x+' y='+y+' window.innerWidth='+window.innerWidth);
687 if ((window.innerWidth - x) < " . ($widthofpopup + 10) . ") {
688 $('#moretabsList" . $tabsname . "').css('" . $right . "','8px');
689 }
690 $('#moretabsList" . $tabsname . "').css('" . $left . "','auto');
691 });
692 ";
693 $out .= "$('#moretabs" . $tabsname . "').mouseleave( function() { console.log('mouseleave " . $left . "'); $('#moretabsList" . $tabsname . "').css('" . $left . "','-999em');});";
694 $out .= "</script>";
695 }
696
697 if ((!empty($title) && $showtitle) || $morehtmlright || !empty($links)) {
698 $out .= "</div>\n";
699 }
700
701 if (!$notab || $notab == -1 || $notab == -2 || $notab == -3 || $notab == -4) {
702 $out .= "\n" . '<div id="dragDropAreaTabBar" class="tabBar' . ($notab == -1 ? '' : ($notab == -2 ? ' tabBarNoTop' : ((($notab == -3 || $notab == -4) ? ' noborderbottom' : '') . ($notab == -4 ? '' : ' tabBarWithBottom'))));
703 $out .= ($morecssdiv ? ' ' . $morecssdiv : '');
704 $out .= '">' . "\n";
705 }
706 if (!empty($dragdropfile)) {
707 include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
708 $out .= dragAndDropFileUpload("dragDropAreaTabBar");
709 }
710 $parameters = array('tabname' => $active, 'out' => $out);
711 $reshook = $hookmanager->executeHooks('printTabsHead', $parameters); // This hook usage is called just before output the head of tabs. Take also a look at "completeTabsHead"
712 if ($reshook > 0) {
713 $out = $hookmanager->resPrint;
714 }
715
716 return $out;
717}
718
727function dol_fiche_end($notab = 0)
728{
729 print dol_get_fiche_end($notab);
730}
731
738function dol_get_fiche_end($notab = 0)
739{
740 if (!$notab || $notab == -1) {
741 return "\n</div>\n";
742 } else {
743 return '';
744 }
745}
746
767function dol_banner_tab($object, $paramid, $morehtml = '', $shownav = 1, $fieldid = 'rowid', $fieldref = 'ref', $morehtmlref = '', $moreparam = '', $nodbprefix = 0, $morehtmlleft = '', $morehtmlstatus = '', $onlybanner = 0, $morehtmlright = '')
768{
769 global $conf, $form, $user, $langs, $hookmanager, $action;
770
771 $error = 0;
772
773 $maxvisiblephotos = 1;
774 $showimage = 1;
775 $entity = (empty($object->entity) ? $conf->entity : $object->entity);
776 // @phan-suppress-next-line PhanUndeclaredMethod
777 $showbarcode = !isModEnabled('barcode') ? 0 : (empty($object->barcode) ? 0 : 1);
778 if (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && !$user->hasRight('barcode', 'lire_advance')) {
779 $showbarcode = 0;
780 }
781 $modulepart = 'unknown';
782
783 if (in_array($object->element, ['societe', 'contact', 'product', 'ticket', 'bom'])) {
784 $modulepart = $object->element;
785 } elseif ($object->element == 'member') {
786 $modulepart = 'memberphoto';
787 } elseif ($object->element == 'user') {
788 $modulepart = 'userphoto';
789 }
790
791 if (class_exists("Imagick")) {
792 if ($object->element == 'expensereport' || $object->element == 'propal' || $object->element == 'commande' || $object->element == 'facture' || $object->element == 'supplier_proposal') {
793 $modulepart = $object->element;
794 } elseif ($object->element == 'fichinter' || $object->element == 'intervention') {
795 $modulepart = 'ficheinter';
796 } elseif ($object->element == 'contrat' || $object->element == 'contract') {
797 $modulepart = 'contract';
798 } elseif ($object->element == 'order_supplier') {
799 $modulepart = 'supplier_order';
800 } elseif ($object->element == 'invoice_supplier') {
801 $modulepart = 'supplier_invoice';
802 }
803 }
804
805 if ($object->element == 'product') {
807 '@phan-var-force Product $object';
808 $width = 80;
809 $cssclass = 'photowithmargin photoref';
810 $showimage = $object->is_photo_available($conf->product->multidir_output[$entity]);
811 $maxvisiblephotos = getDolGlobalInt('PRODUCT_MAX_VISIBLE_PHOTO', 5);
812 if ($conf->browser->layout == 'phone') {
813 $maxvisiblephotos = 1;
814 }
815 $useLinkPathPhoto = getDolGlobalInt('PRODUCT_USE_LINK_PATH_FOR_PHOTO');
816 if ($showimage || $useLinkPathPhoto) {
817 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">' . $object->show_photos('product', $conf->product->multidir_output[$entity], 1, $maxvisiblephotos, 0, 0, 0, 0, $width, 0, '') . '</div>';
818 } else {
819 if (getDolGlobalString('PRODUCT_NODISPLAYIFNOPHOTO')) {
820 $nophoto = '';
821 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
822 } else { // Show no photo link
823 $nophoto = '/public/theme/common/nophoto.png';
824 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><img class="photo' . $modulepart . ' ' . $cssclass . '" title="' . dol_escape_htmltag($langs->trans("UploadAnImageToSeeAPhotoHere", $langs->transnoentitiesnoconv("Documents"))) . '" alt="No photo" style="width: ' . $width . 'px" src="' . DOL_URL_ROOT . $nophoto . '"></div>';
825 }
826 }
827 } elseif ($object->element == 'category') {
829 '@phan-var-force Categorie $object';
830 $width = 80;
831 $cssclass = 'photowithmargin photoref';
832 $showimage = $object->isAnyPhotoAvailable($conf->categorie->multidir_output[$entity]);
833 $maxvisiblephotos = getDolGlobalInt('CATEGORY_MAX_VISIBLE_PHOTO', 5);
834 if ($conf->browser->layout == 'phone') {
835 $maxvisiblephotos = 1;
836 }
837 if ($showimage) {
838 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">' . $object->show_photos('category', $conf->categorie->multidir_output[$entity], 'small', $maxvisiblephotos, 0, 0, 0, 0, $width, 0, '') . '</div>';
839 } else {
840 if (getDolGlobalString('CATEGORY_NODISPLAYIFNOPHOTO')) {
841 $nophoto = '';
842 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
843 } else { // Show no photo link
844 $nophoto = '/public/theme/common/nophoto.png';
845 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><img class="photo' . $modulepart . ' ' . $cssclass . '" title="' . dol_escape_htmltag($langs->trans("UploadAnImageToSeeAPhotoHere", $langs->transnoentitiesnoconv("Documents"))) . '" alt="No photo" style="width: ' . $width . 'px" src="' . DOL_URL_ROOT . $nophoto . '"></div>';
846 }
847 }
848 } elseif ($object->element == 'bom') {
850 '@phan-var-force Bom $object';
851 $width = 80;
852 $cssclass = 'photowithmargin photoref';
853 $showimage = $object->is_photo_available($conf->bom->multidir_output[$entity]);
854 $maxvisiblephotos = getDolGlobalInt('BOM_MAX_VISIBLE_PHOTO', 5);
855 if ($conf->browser->layout == 'phone') {
856 $maxvisiblephotos = 1;
857 }
858 if ($showimage) {
859 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">' . $object->show_photos('bom', $conf->bom->multidir_output[$entity], 'small', $maxvisiblephotos, 0, 0, 0, 0, $width, 0, '') . '</div>';
860 } else {
861 if (getDolGlobalString('BOM_NODISPLAYIFNOPHOTO')) {
862 $nophoto = '';
863 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
864 } else { // Show no photo link
865 $nophoto = '/public/theme/common/nophoto.png';
866 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><img class="photo' . $modulepart . ' ' . $cssclass . '" title="' . dol_escape_htmltag($langs->trans("UploadAnImageToSeeAPhotoHere", $langs->transnoentitiesnoconv("Documents"))) . '" alt="No photo" style="width: ' . $width . 'px" src="' . DOL_URL_ROOT . $nophoto . '"></div>';
867 }
868 }
869 } elseif ($object->element == 'ticket') {
870 $width = 80;
871 $cssclass = 'photoref';
873 '@phan-var-force Ticket $object';
874 $showimage = $object->is_photo_available($conf->ticket->multidir_output[$entity] . '/' . $object->ref);
875 $maxvisiblephotos = getDolGlobalInt('TICKET_MAX_VISIBLE_PHOTO', 2);
876 if ($conf->browser->layout == 'phone') {
877 $maxvisiblephotos = 1;
878 }
879
880 if ($showimage) {
881 $showphoto = $object->show_photos('ticket', $conf->ticket->multidir_output[$entity], 'small', $maxvisiblephotos, 0, 0, 0, $width, 0);
882 if ($object->nbphoto > 0) {
883 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">' . $showphoto . '</div>';
884 } else {
885 $showimage = 0;
886 }
887 }
888 if (!$showimage) {
889 if (getDolGlobalString('TICKET_NODISPLAYIFNOPHOTO')) {
890 $nophoto = '';
891 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
892 } else { // Show no photo link
893 $nophoto = img_picto('No photo', 'object_ticket');
894 $morehtmlleft .= '<!-- No photo to show -->';
895 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
896 $morehtmlleft .= $nophoto;
897 $morehtmlleft .= '</div></div>';
898 }
899 }
900 } else {
901 // $modulepart may have been set previously if Imagick class exists (see before).
902 if ($modulepart != 'unknown' || method_exists($object, 'getDataToShowPhoto')) {
903 $phototoshow = '';
904 // Check if a preview file is available
905 if (in_array($modulepart, array('propal', 'commande', 'facture', 'ficheinter', 'contract', 'supplier_order', 'supplier_proposal', 'supplier_invoice', 'expensereport')) && class_exists("Imagick")) {
906 $objectref = dol_sanitizeFileName($object->ref);
907 $dir_output = (empty($conf->$modulepart->multidir_output[$entity]) ? $conf->$modulepart->dir_output : $conf->$modulepart->multidir_output[$entity]) . "/";
908 if (in_array($modulepart, array('invoice_supplier', 'supplier_invoice'))) {
909 $subdir = get_exdir($object->id, 2, 0, 1, $object, $modulepart);
910 $subdir .= ((!empty($subdir) && !preg_match('/\/$/', $subdir)) ? '/' : '') . $objectref; // the objectref dir is not included into get_exdir when used with level=2, so we add it at end
911 } else {
912 $subdir = get_exdir($object->id, 0, 0, 1, $object, $modulepart);
913 }
914 if (empty($subdir)) {
915 $subdir = 'errorgettingsubdirofobject'; // Protection to avoid to return empty path
916 }
917
918 $filepath = $dir_output . $subdir . "/";
919
920 $filepdf = $filepath . $objectref . ".pdf";
921 $relativepath = $subdir . '/' . $objectref . '.pdf';
922
923 // Define path to preview pdf file (preview precompiled "file.ext" are "file.ext_preview.png")
924 $fileimage = $filepdf . '_preview.png';
925 $relativepathimage = $relativepath . '_preview.png';
926
927 $pdfexists = file_exists($filepdf);
928
929 // If PDF file exists
930 if ($pdfexists) {
931 // Conversion du PDF en image png si fichier png non existent
932 if (!file_exists($fileimage) || (filemtime($fileimage) < filemtime($filepdf))) {
933 if (!getDolGlobalString('MAIN_DISABLE_PDF_THUMBS')) { // If you experience trouble with pdf thumb generation and imagick, you can disable here.
934 include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
935 $ret = dol_convert_file($filepdf, 'png', $fileimage, '0'); // Convert first page of PDF into a file _preview.png
936 if ($ret < 0) {
937 $error++;
938 }
939 }
940 }
941 }
942
943 if ($pdfexists && !$error) {
944 $heightforphotref = 80;
945 if (!empty($conf->dol_optimize_smallscreen)) {
946 $heightforphotref = 60;
947 }
948 // If the preview file is found
949 if (file_exists($fileimage)) {
950 $phototoshow = '<div class="photoref">';
951 $phototoshow .= '<img height="' . $heightforphotref . '" class="photo photowithborder" src="' . DOL_URL_ROOT . '/viewimage.php?modulepart=apercu' . $modulepart . '&amp;file=' . urlencode($relativepathimage) . '">';
952 $phototoshow .= '</div>';
953 }
954 }
955 } elseif (!$phototoshow) { // example if modulepart = 'societe' or 'photo' or 'memberphoto'
956 $phototoshow .= $form->showphoto($modulepart, $object, 0, 0, 0, 'photowithmargin photoref', 'small', 1, 0);
957 }
958
959 if ($phototoshow) {
960 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">';
961 $morehtmlleft .= $phototoshow;
962 $morehtmlleft .= '</div>';
963 }
964 }
965
966 if (empty($phototoshow)) { // Show No photo link (picto of object)
967 if ($object->element == 'action') {
968 $width = 80;
969 $cssclass = 'photorefcenter';
970 $nophoto = img_picto('No photo', 'title_agenda');
971 } else {
972 $width = 14;
973 $cssclass = 'photorefcenter';
974 $picto = $object->picto; // @phan-suppress-current-line PhanUndeclaredProperty
975 $prefix = 'object_';
976 if ($object->element == 'project' && !$object->public) { // @phan-suppress-current-line PhanUndeclaredProperty
977 $picto = 'project'; // instead of projectpub
978 }
979 if (strpos($picto, 'fontawesome_') !== false) {
980 $prefix = '';
981 }
982 $nophoto = img_picto('No photo', $prefix . $picto);
983 }
984 $morehtmlleft .= '<!-- No photo to show -->';
985 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
986 $morehtmlleft .= $nophoto;
987 $morehtmlleft .= '</div></div>';
988 }
989 }
990
991 if (getDolGlobalString('MAIN_SHOW_TECHNICAL_ID') && (getDolGlobalString('MAIN_SHOW_TECHNICAL_ID') == '1' || preg_match('/' . preg_quote($object->element, '/') . '/i', getDolGlobalString('MAIN_SHOW_TECHNICAL_ID'))) && !empty($object->id)) {
992 $morehtmlref .= '<div style="clear: both;"></div>';
993 $morehtmlref .= '<div class="smallimp refidno opacitymedium banner-object-technical-id">';
994 $morehtmlref .= $langs->trans("TechnicalID") . ': ' . ((int) $object->id);
995 $morehtmlref .= '</div>';
996 }
997
998
999 // Show barcode
1000 if ($showbarcode) {
1001 $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">' . $form->showbarcode($object, 100, 'photoref valignmiddle') . '</div>';
1002 }
1003
1004 if ($object->element == 'societe') {
1006 if (!empty($conf->use_javascript_ajax) && $user->hasRight('societe', 'creer') && getDolGlobalString('MAIN_DIRECT_STATUS_UPDATE')) {
1007 $morehtmlstatus .= ajax_object_onoff($object, 'status', 'status', 'InActivity', 'ActivityCeased');
1008 } else {
1009 $morehtmlstatus .= $object->getLibStatut(6);
1010 }
1011 } elseif ($object->element == 'product') {
1013 //$morehtmlstatus.=$langs->trans("Status").' ('.$langs->trans("Sell").') ';
1014 if (!empty($conf->use_javascript_ajax) && $user->hasRight('produit', 'creer') && getDolGlobalString('MAIN_DIRECT_STATUS_UPDATE')) {
1015 $morehtmlstatus .= ajax_object_onoff($object, 'status', 'status', 'ProductStatusOnSell', 'ProductStatusNotOnSell');
1016 } else {
1017 $morehtmlstatus .= '<span class="statusrefsell">' . $object->getLibStatut(6, 0) . '</span>';
1018 }
1019 $morehtmlstatus .= ' &nbsp; ';
1020 //$morehtmlstatus.=$langs->trans("Status").' ('.$langs->trans("Buy").') ';
1021 if (!empty($conf->use_javascript_ajax) && $user->hasRight('produit', 'creer') && getDolGlobalString('MAIN_DIRECT_STATUS_UPDATE')) {
1022 $morehtmlstatus .= ajax_object_onoff($object, 'status_buy', 'status_buy', 'ProductStatusOnBuy', 'ProductStatusNotOnBuy');
1023 } else {
1024 $morehtmlstatus .= '<span class="statusrefbuy">' . $object->getLibStatut(6, 1) . '</span>';
1025 }
1026 } elseif (in_array($object->element, array('salary'))) {
1028 '@phan-var-force Salary $object';
1029 $tmptxt = $object->getLibStatut(6, $object->totalpaid);
1030 if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
1031 $tmptxt = $object->getLibStatut(5, $object->totalpaid);
1032 }
1033 $morehtmlstatus .= $tmptxt;
1034 } elseif (in_array($object->element, array('facture', 'invoice', 'invoice_supplier'))) {
1036 '@phan-var-force Facture|FactureFournisseur|CommonInvoice $object';
1037 if (!isset($object->alreadypaid)) {
1038 $object->totalpaid = $object->getSommePaiement(0);
1039 $object->totalcreditnotes = $object->getSumCreditNotesUsed(0);
1040 $object->totaldeposits = $object->getSumDepositsUsed(0);
1041 $object->alreadypaid = $object->totalpaid + $object->totalcreditnotes + $object->totaldeposits;
1042 }
1043 $tmptxt = $object->getLibStatut(6, (float) $object->alreadypaid);
1044 if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
1045 $tmptxt = $object->getLibStatut(5, (float) $object->alreadypaid);
1046 }
1047 $morehtmlstatus .= $tmptxt;
1048 } elseif (in_array($object->element, array('chargesociales', 'loan', 'tva'))) { // TODO Move this to use ->alreadypaid like for invoices
1050 '@phan-var-force ChargeSociales|Loan|Tva $object';
1051 $tmptxt = $object->getLibStatut(6, $object->totalpaid);
1052 if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
1053 $tmptxt = $object->getLibStatut(5, $object->totalpaid);
1054 }
1055 $morehtmlstatus .= $tmptxt;
1056 } elseif ($object->element == 'contrat' || $object->element == 'contract') {
1058 if ($object->status == 0) {
1059 $morehtmlstatus .= $object->getLibStatut(5);
1060 } else {
1061 $morehtmlstatus .= $object->getLibStatut(4);
1062 }
1063 } elseif ($object->element == 'facturerec') {
1065 '@phan-var-force FactureRec $object';
1066 if ($object->frequency == 0) {
1067 $morehtmlstatus .= $object->getLibStatut(2);
1068 } else {
1069 $morehtmlstatus .= $object->getLibStatut(5);
1070 }
1071 } elseif ($object->element == 'project_task') {
1073 $tmptxt = $object->getLibStatut(4);
1074 $morehtmlstatus .= $tmptxt;
1075 } elseif (method_exists($object, 'getLibStatut')) { // Generic case for status
1076 $tmptxt = $object->getLibStatut(6);
1077 if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
1078 $tmptxt = $object->getLibStatut(5);
1079 }
1080 $morehtmlstatus .= $tmptxt;
1081 }
1082
1083 // Say if object was dispatched/transferred "into accountancy"
1084 if (isModEnabled('accounting') && in_array($object->element, array('bank', 'paiementcharge', 'facture', 'invoice', 'invoice_supplier', 'expensereport', 'payment_various'))) {
1085 // Note: For 'chargesociales', 'salaries'... this is the payments that are dispatched (so element = 'bank')
1086 if (method_exists($object, 'getVentilExportCompta')) {
1087 $accounted = $object->getVentilExportCompta(1);
1088 $langs->load("accountancy");
1089 $morehtmlstatus .= '</div><div class="statusref statusrefbis"><span class="opacitymedium">' . ($accounted > 0 ? '<a href="' . DOL_URL_ROOT . '/accountancy/bookkeeping/list.php?search_mvt_num=' . ((int) $accounted) . '">' . $langs->trans("Accounted") . '</a>' : $langs->trans("NotYetAccounted")) . '</span>';
1090 }
1091 }
1092
1093 // Add alias for thirdparty
1094 if (!empty($object->name_alias)) {
1096 '@phan-var-force Societe $object';
1097 $morehtmlref .= '<div class="refidno opacitymedium banner-object-name-alias">' . dol_escape_htmltag($object->name_alias) . '</div>';
1098 }
1099
1100 // Add label
1101 if (in_array($object->element, array('product', 'bank_account', 'project_task'))) {
1103 if (!empty($object->label)) {
1104 $morehtmlref .= '<div class="refidno banner-object-label">' . $object->label . '</div>';
1105 }
1106 }
1107 // Show address and email
1108 if (method_exists($object, 'getBannerAddress') && !in_array($object->element, array('product', 'bookmark', 'ecm_directories', 'ecm_files'))) {
1109 $moreaddress = $object->getBannerAddress('refaddress', $object); // address, email, url, social networks
1110 if ($moreaddress) {
1111 $morehtmlref .= '<div class="refidno refaddress">';
1112 $morehtmlref .= $moreaddress;
1113 $morehtmlref .= '</div>';
1114 }
1115 }
1116
1117 $parameters = array('morehtmlref' => &$morehtmlref, 'moreparam' => &$moreparam, 'morehtmlleft' => &$morehtmlleft, 'morehtmlstatus' => &$morehtmlstatus, 'morehtmlright' => &$morehtmlright);
1118 $reshook = $hookmanager->executeHooks('formDolBanner', $parameters, $object, $action);
1119 if ($reshook < 0) {
1120 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
1121 } elseif (empty($reshook)) {
1122 $morehtmlref .= $hookmanager->resPrint;
1123 } elseif ($reshook > 0) {
1124 $morehtmlref = $hookmanager->resPrint;
1125 }
1126
1127 // $morehtml is the right part (link "Back to list")
1128 // $morehtmlref is the part after the ref
1129 // $morehtmlleft is the picto or photo of banner
1130 // $morehtmlstatus is part under the status
1131 // $morehtmlright is part of htmlright
1132
1133 print '<div class="' . ($onlybanner ? 'arearefnobottom ' : 'arearef ') . 'heightref valignmiddle centpercent object-banner-tab-container" data-module-part="'.dolPrintHTMLForAttribute($modulepart).'">';
1134 print $form->showrefnav($object, $paramid, $morehtml, $shownav, $fieldid, $fieldref, $morehtmlref, $moreparam, $nodbprefix, $morehtmlleft, $morehtmlstatus, $morehtmlright);
1135 print '</div>';
1136 print '<div class="underrefbanner clearboth"></div>';
1137}
1138
1148function fieldLabel($langkey, $fieldkey, $fieldrequired = 0)
1149{
1150 global $langs;
1151 $ret = '';
1152 if ($fieldrequired) {
1153 $ret .= '<span class="fieldrequired">';
1154 }
1155 $ret .= '<label for="' . $fieldkey . '">';
1156 $ret .= $langs->trans($langkey);
1157 $ret .= '</label>';
1158 if ($fieldrequired) {
1159 $ret .= '</span>';
1160 }
1161 return $ret;
1162}
1163
1164
1180function dolOutputDates($datep, $datef = null, $fullday = 0, $addseconds = 0, $pictotoadd = '', $tzoutput = 'tzuserrel', $reduceformat = 0)
1181{
1182 $tmpa = dol_getdate($datep);
1183 if (empty($datef)) {
1184 $tmpb = $tmpa;
1185 } else {
1186 $tmpb = dol_getdate($datef);
1187 }
1188
1189 $s = '';
1190
1191 if ($tmpa['mday'] == $tmpb['mday'] && $tmpa['mon'] == $tmpb['mon'] && $tmpa['year'] == $tmpb['year']) {
1192 // The same day
1193 $s .= '<div class="center inline-block">';
1194 if ($tmpa['hours'] != $tmpb['hours'] || $tmpa['minutes'] != $tmpb['minutes']) {
1195 // Not the same hour
1196 $s .= dol_print_date($datep, 'day'.($reduceformat ? 'reduceformat' : ''), $tzoutput);
1197 $s .= $pictotoadd;
1198 if (empty($fullday)) {
1199 $s .= '<br><span class="small opacitymedium">';
1200 $s .= dol_print_date($datep, 'hour'.($addseconds ? 'sec' : '').'reduceformat', $tzoutput);
1201 $s .= '-'.dol_print_date($datef, 'hour'.($addseconds ? 'sec' : '').'reduceformat', $tzoutput);
1202 $s .= '</span>';
1203 }
1204 } else {
1205 // The same hour
1206 $s .= dol_print_date($datep, 'day'.($reduceformat ? 'reduceformat' : ''), 'tzuserrel');
1207 $s .= $pictotoadd;
1208 if (empty($fullday)) {
1209 $s .= '<br><span class="small opacitymedium">';
1210 $s .= dol_print_date($datep, 'hour'.($addseconds ? 'sec' : '').'reduceformat', $tzoutput);
1211 $s .= '</span>';
1212 }
1213 }
1214 $s .= '</div>';
1215 } else {
1216 // Not the same day
1217 $s .= '<div class="center inline-block dateborderright">';
1218 $s .= dol_print_date($datep, 'day'.($reduceformat ? 'reduceformat' : ''), $tzoutput);
1219 if (empty($fullday)) {
1220 $s .= '<br><span class="small opacitymedium">';
1221 $s .= dol_print_date($datep, 'hour'.($addseconds ? 'sec' : '').'reduceformat', $tzoutput);
1222 $s .= '</span>';
1223 }
1224 $s .= '</div>';
1225 $s .= '<div class="center inline-block dateborderleft">';
1226 $s .= dol_print_date($datef, 'day'.($reduceformat ? 'reduceformat' : ''), 'tzuserrel');
1227 $s .= $pictotoadd;
1228 if (empty($fullday)) {
1229 $s .= '<br><span class="small opacitymedium">';
1230 $s .= dol_print_date($datef, 'hour'.($addseconds ? 'sec' : '').'reduceformat', $tzoutput);
1231 $s .= '</span>';
1232 }
1233 $s .= '</div>';
1234 }
1235
1236 return $s;
1237}
1238
1239
1247function getPictoForType($key, $morecss = '')
1248{
1249 // Set array with type -> picto
1250 $type2picto = array(
1251 'varchar' => 'font',
1252 'text' => 'font',
1253 'html' => 'code',
1254 'int' => 'sort-numeric-down',
1255 'double' => 'sort-numeric-down',
1256 'price' => 'currency',
1257 'pricecy' => 'multicurrency',
1258 'password' => 'key',
1259 'boolean' => 'check-square',
1260 'date' => 'calendar',
1261 'datetime' => 'calendar',
1262 'duration' => 'hourglass',
1263 'phone' => 'phone',
1264 'mail' => 'email',
1265 'url' => 'url',
1266 'ip' => 'country',
1267 'select' => 'list',
1268 'sellist' => 'list',
1269 'stars' => 'fontawesome_star_fas',
1270 'radio' => 'check-circle',
1271 'checkbox' => 'list',
1272 'chkbxlst' => 'list',
1273 'link' => 'link',
1274 'icon' => "question",
1275 'point' => "country",
1276 'multipts' => 'country',
1277 'linestrg' => "country",
1278 'polygon' => "country",
1279 'separate' => 'minus'
1280 );
1281
1282 if (!empty($type2picto[$key])) {
1283 return img_picto('', $type2picto[$key], 'class="pictofixedwidth' . ($morecss ? ' ' . $morecss : '') . '"');
1284 }
1285
1286 return img_picto('', 'generic', 'class="pictofixedwidth' . ($morecss ? ' ' . $morecss : '') . '"');
1287}
1288
1289
1314function img_picto($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $srconly = 0, $notitle = 0, $alt = '', $morecss = '', $marginleftonlyshort = 2, $allowothertags = array())
1315{
1316 global $conf;
1317
1318 // We forge fullpathpicto for image to $path/img/$picto. By default, we take DOL_URL_ROOT/theme/$conf->theme/img/$picto
1319 $url = DOL_URL_ROOT;
1320 $theme = isset($conf->theme) ? $conf->theme : null;
1321 $path = 'theme/' . $theme;
1322 if (empty($picto)) {
1323 $picto = 'generic';
1324 }
1325
1326 // Define fullpathpicto to use into src
1327 if ($pictoisfullpath) {
1328 // Clean parameters
1329 if (!preg_match('/(\.png|\.gif|\.svg)$/i', $picto)) {
1330 $picto .= '.png';
1331 }
1332 $fullpathpicto = $picto;
1333 $reg = array();
1334 if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
1335 $morecss .= ($morecss ? ' ' : '') . $reg[1];
1336 $moreatt = str_replace('class="' . $reg[1] . '"', '', $moreatt);
1337 }
1338 } else {
1339 // $picto can not be null since replaced with 'generic' in that case
1340 // $pictowithouttext = preg_replace('/(\.png|\.gif|\.svg)$/', '', (is_null($picto) ? '' : $picto));
1341 $pictowithouttext = preg_replace('/(\.png|\.gif|\.svg)$/', '', $picto);
1342 $pictowithouttext = str_replace('object_', '', $pictowithouttext);
1343 $pictowithouttext = str_replace('_nocolor', '', $pictowithouttext);
1344
1345 // Fix some values of $pictowithouttext
1346 $pictoconvertkey = array(
1347 'facture' => 'bill',
1348 'shipping' => 'shipment',
1349 'fichinter' => 'intervention',
1350 'agenda' => 'calendar',
1351 'invoice_supplier' => 'supplier_invoice',
1352 'order_supplier' => 'supplier_order');
1353 if (in_array($pictowithouttext, array_keys($pictoconvertkey))) {
1354 $pictowithouttext = $pictoconvertkey[$pictowithouttext];
1355 }
1356
1357 if (strpos($pictowithouttext, 'fontawesome_') === 0 || strpos($pictowithouttext, 'fa-') === 0) {
1358 // This is a font awesome image 'fontawesome_xxx' or 'fa-xxx'
1359 $pictowithouttext = str_replace('fontawesome_', '', $pictowithouttext);
1360 $pictowithouttext = str_replace('fa-', '', $pictowithouttext);
1361
1362 // Compatibility with old fontawesome versions
1363 if ($pictowithouttext == 'file-o') {
1364 $pictowithouttext = 'file';
1365 }
1366
1367 $pictowithouttextarray = explode('_', $pictowithouttext);
1368 $marginleftonlyshort = 0;
1369
1370 if (!empty($pictowithouttextarray[1])) {
1371 // Syntax is 'fontawesome_fakey_faprefix_facolor_fasize' or 'fa-fakey_faprefix_facolor_fasize'
1372 $fakey = 'fa-' . $pictowithouttextarray[0];
1373 $faprefix = empty($pictowithouttextarray[1]) ? 'fas' : $pictowithouttextarray[1];
1374 $facolor = empty($pictowithouttextarray[2]) ? '' : $pictowithouttextarray[2];
1375 $fasize = empty($pictowithouttextarray[3]) ? '' : $pictowithouttextarray[3];
1376 } else {
1377 $fakey = 'fa-' . $pictowithouttext;
1378 $faprefix = 'fas';
1379 $facolor = '';
1380 $fasize = '';
1381 }
1382
1383 // This snippet only needed since function img_edit accepts only one additional parameter: no separate one for css only.
1384 // class/style need to be extracted to avoid duplicate class/style validation errors when $moreatt is added to the end of the attributes.
1385 $morestyle = '';
1386 $reg = array();
1387 if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
1388 $morecss .= ($morecss ? ' ' : '') . $reg[1];
1389 $moreatt = str_replace('class="' . $reg[1] . '"', '', $moreatt);
1390 }
1391 if (preg_match('/style="([^"]+)"/', $moreatt, $reg)) {
1392 $morestyle = $reg[1];
1393 $moreatt = str_replace('style="' . $reg[1] . '"', '', $moreatt);
1394 }
1395 $moreatt = trim($moreatt);
1396
1397 $enabledisablehtml = '<span class="' . $faprefix . ' ' . $fakey;
1398 $enabledisablehtml .= ($morecss ? ' ' . $morecss : '') . '" style="' . ($fasize ? ('font-size: ' . $fasize . ';') : '') . ($facolor ? (' color: ' . $facolor . ';') : '') . ($morestyle ? ' ' . $morestyle : '') . '"' . (($notitle || empty($titlealt)) ? '' : ' title="' . dol_escape_htmltag($titlealt) . '"') . ($moreatt ? ' ' . $moreatt : '') . '>';
1399 $enabledisablehtml .= '</span>';
1400
1401 return $enabledisablehtml;
1402 }
1403
1404 if (empty($srconly) && !preg_match('/[\.\/@]/', $picto)) { // If original picto code does not contains a / and no . inside, it is not a path to an image file on disk
1405 $fakey = $pictowithouttext;
1406 $facolor = '';
1407 $fasize = '';
1408 $fa = getDolGlobalString('MAIN_FONTAWESOME_ICON_STYLE', 'fas');
1409 if (in_array($pictowithouttext, array('card', 'bell', 'clock', 'establishment', 'file', 'file-o', 'generic', 'minus-square', 'object_generic', 'pdf', 'plus-square', 'timespent', 'note', 'off', 'on', 'object_bookmark', 'bookmark', 'vcard'))) {
1410 $fa = 'far';
1411 }
1412 if (in_array($pictowithouttext, array('black-tie', 'discord', 'facebook', 'flickr', 'github', 'google', 'google-plus-g', 'instagram', 'linkedin', 'meetup', 'microsoft', 'pinterest', 'skype', 'slack', 'twitter', 'reddit', 'snapchat', 'stripe', 'stripe-s', 'tumblr', 'viadeo', 'whatsapp', 'youtube'))) {
1413 $fa = 'fab';
1414 }
1415
1416 $arrayconvpictotofa = getImgPictoConv('fa');
1417
1418 if ($pictowithouttext == 'off') {
1419 $fakey = 'fa-square';
1420 $fasize = '1.3em';
1421 } elseif ($pictowithouttext == 'on') {
1422 $fakey = 'fa-check-square';
1423 $fasize = '1.3em';
1424 } elseif ($pictowithouttext == 'listlight') {
1425 $fakey = 'fa-download';
1426 $marginleftonlyshort = 1;
1427 } elseif ($pictowithouttext == 'printer') {
1428 $fakey = 'fa-print';
1429 $fasize = '1.2em';
1430 } elseif ($pictowithouttext == 'note') {
1431 $fakey = 'fa-sticky-note';
1432 $marginleftonlyshort = 1;
1433 } elseif (in_array($pictowithouttext, array('1uparrow', '1downarrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected'))) {
1434 $convertarray = array('1uparrow' => 'caret-up', '1downarrow' => 'caret-down', '1leftarrow' => 'caret-left', '1rightarrow' => 'caret-right', '1uparrow_selected' => 'caret-up', '1downarrow_selected' => 'caret-down', '1leftarrow_selected' => 'caret-left', '1rightarrow_selected' => 'caret-right');
1435 $fakey = 'fa-' . $convertarray[$pictowithouttext];
1436 if (preg_match('/selected/', $pictowithouttext)) {
1437 $facolor = '#888';
1438 }
1439 $marginleftonlyshort = 1;
1440 } elseif (!empty($arrayconvpictotofa[$pictowithouttext])) {
1441 $fakey = 'fa-' . $arrayconvpictotofa[$pictowithouttext];
1442 } else {
1443 $fakey = 'fa-' . $pictowithouttext;
1444 }
1445
1446 if (in_array($pictowithouttext, array('dollyrevert', 'member', 'members', 'contract', 'group', 'resource', 'shipment', 'reception'))) {
1447 $morecss .= ' em092';
1448 }
1449 if (in_array($pictowithouttext, array('conferenceorbooth', 'eventorganization', 'holiday', 'info', 'info_black', 'project', 'workstation'))) {
1450 $morecss .= ' em088';
1451 }
1452 if (in_array($pictowithouttext, array('asset', 'intervention', 'payment', 'loan', 'partnership', 'stock', 'technic'))) {
1453 $morecss .= ' em080';
1454 }
1455
1456 // Define $marginleftonlyshort
1457 $arrayconvpictotomarginleftonly = array(
1458 'bank',
1459 'check',
1460 'delete',
1461 'generic',
1462 'grip',
1463 'grip_title',
1464 'jabber',
1465 'grip_title',
1466 'grip',
1467 'listlight',
1468 'note',
1469 'on',
1470 'off',
1471 'playdisabled',
1472 'printer',
1473 'resize',
1474 'sign-out',
1475 'stats',
1476 'switch_on',
1477 'switch_on_grey',
1478 'switch_on_red',
1479 'switch_off',
1480 'switch_off_grey',
1481 'switch_off_red',
1482 'uparrow',
1483 '1uparrow',
1484 '1downarrow',
1485 '1leftarrow',
1486 '1rightarrow',
1487 '1uparrow_selected',
1488 '1downarrow_selected',
1489 '1leftarrow_selected',
1490 '1rightarrow_selected'
1491 );
1492 if (!array_key_exists($pictowithouttext, $arrayconvpictotomarginleftonly)) {
1493 $marginleftonlyshort = 0;
1494 }
1495
1496 // Add CSS
1497 $arrayconvpictotomorcess = array(
1498 'action' => 'infobox-action',
1499 'account' => 'infobox-bank_account',
1500 'accounting_account' => 'infobox-bank_account',
1501 'accountline' => 'infobox-bank_account',
1502 'accountancy' => 'infobox-bank_account',
1503 'admin' => 'opacitymedium',
1504 'asset' => 'infobox-bank_account',
1505 'bank_account' => 'infobox-bank_account',
1506 'bill' => 'infobox-commande',
1507 'billa' => 'infobox-commande',
1508 'billr' => 'infobox-commande',
1509 'billd' => 'infobox-commande',
1510 'bookcal' => 'infobox-portal',
1511 'margin' => 'infobox-bank_account',
1512 'conferenceorbooth' => 'infobox-project',
1513 'cash-register' => 'infobox-portal',
1514 'contract' => 'infobox-contrat',
1515 'check' => 'font-status4',
1516 'conversation' => 'infobox-contrat',
1517 'donation' => 'infobox-commande',
1518 'dolly' => 'infobox-commande',
1519 'dollyrevert' => 'flip infobox-order_supplier',
1520 'ecm' => 'infobox-action',
1521 'eventorganization' => 'infobox-project',
1522 'hrm' => 'infobox-adherent',
1523 'group' => 'infobox-adherent',
1524 'intervention' => 'infobox-contrat',
1525 'incoterm' => 'infobox-supplier_proposal',
1526 'intracommreport' => 'infobox-bank_account',
1527 'currency' => 'infobox-bank_account',
1528 'multicurrency' => 'infobox-bank_account',
1529 'members' => 'infobox-adherent',
1530 'member' => 'infobox-adherent',
1531 'money-bill-alt' => 'infobox-bank_account',
1532 'order' => 'infobox-commande',
1533 'user' => 'infobox-adherent',
1534 'users' => 'infobox-adherent',
1535 'error' => 'pictoerror',
1536 'warning' => 'pictowarning',
1537 'switch_on' => 'font-status4',
1538 'switch_on_warning' => 'font-status4 warning',
1539 'switch_on_red' => 'font-status8',
1540 'switch_off_warning' => 'font-status4 warning',
1541 'switch_off_red' => 'font-status8',
1542 'holiday' => 'infobox-holiday',
1543 'info' => 'opacityhigh',
1544 'info_black' => 'purple',
1545 'invoice' => 'infobox-commande',
1546 'knowledgemanagement' => 'infobox-contrat rotate90',
1547 'loan' => 'infobox-commande',
1548 'payment' => 'infobox-bank_account',
1549 'payment_vat' => 'infobox-bank_account',
1550 'poll' => 'infobox-portal',
1551 'pos' => 'infobox-bank_account',
1552 'project' => 'infobox-project',
1553 'projecttask' => 'infobox-project',
1554 'propal' => 'infobox-propal',
1555 'proposal' => 'infobox-propal',
1556 'private' => 'infobox-project',
1557 'reception' => 'flip infobox-order_supplier',
1558 'recruitmentjobposition' => 'infobox-adherent',
1559 'recruitmentcandidature' => 'infobox-adherent',
1560 'resource' => 'infobox-action',
1561 'salary' => 'infobox-commande',
1562 'shapes' => 'infobox-adherent',
1563 'shipment' => 'infobox-commande',
1564 'store' => 'infobox-portal',
1565 'stripe' => 'infobox-bank_account',
1566 'supplier_invoice' => 'infobox-order_supplier',
1567 'supplier_invoicea' => 'infobox-order_supplier',
1568 'supplier_invoiced' => 'infobox-order_supplier',
1569 'supplier_invoicer' => 'infobox-order_supplier',
1570 'supplier' => 'infobox-order_supplier',
1571 'supplier_order' => 'infobox-order_supplier',
1572 'supplier_proposal' => 'infobox-supplier_proposal',
1573 'ticket' => 'infobox-contrat',
1574 'title_accountancy' => 'infobox-bank_account',
1575 'title_hrm' => 'infobox-holiday',
1576 'expensereport' => 'infobox-expensereport',
1577 'trip' => 'infobox-expensereport',
1578 'title_agenda' => 'infobox-action',
1579 'vat' => 'infobox-bank_account',
1580 'webportal' => 'infobox-portal',
1581 'website' => 'infobox-portal',
1582 //'title_setup'=>'infobox-action', 'tools'=>'infobox-action',
1583 'list-alt' => 'imgforviewmode',
1584 'calendar' => 'imgforviewmode',
1585 'calendarweek' => 'imgforviewmode',
1586 'calendarmonth' => 'imgforviewmode',
1587 'calendarday' => 'imgforviewmode',
1588 'calendarperuser' => 'imgforviewmode',
1589 'calendarpertype' => 'imgforviewmode'
1590 );
1591 if (!empty($arrayconvpictotomorcess[$pictowithouttext]) && strpos($picto, '_nocolor') === false) {
1592 $morecss .= ($morecss ? ' ' : '') . $arrayconvpictotomorcess[$pictowithouttext];
1593 }
1594
1595 // Define $color
1596 $arrayconvpictotocolor = array(
1597 'address' => '#6c6aa8',
1598 'building' => '#6c6aa8',
1599 'bom' => '#a69944',
1600 'clone' => '#999',
1601 'cog' => '#999',
1602 'companies' => '#6c6aa8',
1603 'company' => '#6c6aa8',
1604 'contact' => '#6c6aa8',
1605 'cron' => '#555',
1606 'dynamicprice' => '#a69944',
1607 'edit' => '#444',
1608 'note' => '#999',
1609 'error' => '',
1610 'help' => '#bbb',
1611 'listlight' => '#999',
1612 'language' => '#555',
1613 //'dolly'=>'#a69944', 'dollyrevert'=>'#a69944',
1614 'lock' => '#ddd',
1615 'lot' => '#a69944',
1616 'map-marker-alt' => '#aaa',
1617 'mrp' => '#a69944',
1618 'product' => '#a69944',
1619 'service' => '#a69944',
1620 'inventory' => '#a69944',
1621 'stock' => '#a69944',
1622 'movement' => '#a69944',
1623 'other' => '#ddd',
1624 'world' => '#986c6a',
1625 'partnership' => '#6c6aa8',
1626 'playdisabled' => '#ccc',
1627 'printer' => '#444',
1628 'projectpub' => '#986c6a',
1629 'resize' => '#444',
1630 'rss' => '#cba',
1631 //'shipment'=>'#a69944',
1632 'search-plus' => '#808080',
1633 'security' => '#999',
1634 'square' => '#888',
1635 'stop-circle' => '#888',
1636 'stats' => '#444',
1637 'superadmin' => '#600',
1638 'switch_off' => '#999',
1639 'technic' => '#999',
1640 'tick' => '#282',
1641 'timespent' => '#555',
1642 'uncheck' => '#800',
1643 'uparrow' => '#555',
1644 'user-cog' => '#999',
1645 'country' => '#aaa',
1646 'globe-americas' => '#aaa',
1647 'region' => '#aaa',
1648 'state' => '#aaa',
1649 //'website' => '#304',
1650 'workstation' => '#a69944'
1651 );
1652 if (isset($arrayconvpictotocolor[$pictowithouttext]) && strpos($picto, '_nocolor') === false) {
1653 $facolor = $arrayconvpictotocolor[$pictowithouttext];
1654 }
1655
1656 // This snippet only needed since function img_edit accepts only one additional parameter: no separate one for css only.
1657 // class/style need to be extracted to avoid duplicate class/style validation errors when $moreatt is added to the end of the attributes.
1658 $morestyle = '';
1659 $reg = array();
1660 if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
1661 $morecss .= ($morecss ? ' ' : '') . $reg[1];
1662 $moreatt = str_replace('class="' . $reg[1] . '"', '', $moreatt);
1663 }
1664 if (preg_match('/style="([^"]+)"/', $moreatt, $reg)) {
1665 $morestyle = $reg[1];
1666 $moreatt = str_replace('style="' . $reg[1] . '"', '', $moreatt);
1667 }
1668 $moreatt = trim($moreatt);
1669
1670 $enabledisablehtml = '<span class="' . $fa . ' ' . $fakey . ($marginleftonlyshort ? ($marginleftonlyshort == 1 ? ' marginleftonlyshort' : ' marginleftonly') : '');
1671 $enabledisablehtml .= ($morecss ? ' ' . $morecss : '') . '" style="' . ($fasize ? ('font-size: ' . $fasize . ';') : '') . ($facolor ? (' color: ' . $facolor . ';') : '') . ($morestyle ? ' ' . $morestyle : '') . '"' . (($notitle || empty($titlealt)) ? '' : ' title="' . dol_escape_htmltag($titlealt) . '"') . ($moreatt ? ' ' . $moreatt : '') . '>';
1672 $enabledisablehtml .= '</span>';
1673
1674 return $enabledisablehtml;
1675 }
1676
1677 if (getDolGlobalString('MAIN_OVERWRITE_THEME_PATH')) {
1678 $path = getDolGlobalString('MAIN_OVERWRITE_THEME_PATH') . '/theme/' . $theme; // If the theme does not have the same name as the module
1679 } elseif (getDolGlobalString('MAIN_OVERWRITE_THEME_RES')) {
1680 $path = getDolGlobalString('MAIN_OVERWRITE_THEME_RES') . '/theme/' . getDolGlobalString('MAIN_OVERWRITE_THEME_RES'); // To allow an external module to overwrite image resources whatever is activated theme
1681 } elseif (!empty($conf->modules_parts['theme']) && array_key_exists($theme, $conf->modules_parts['theme'])) {
1682 $path = $theme . '/theme/' . $theme; // If the theme have the same name as the module
1683 }
1684
1685 // If we ask an image into $url/$mymodule/img (instead of default path)
1686 $regs = array();
1687 if (preg_match('/^([^@]+)@([^@]+)$/i', $picto, $regs)) {
1688 $picto = $regs[1];
1689 $path = $regs[2]; // $path is $mymodule
1690 }
1691
1692 // Clean parameters
1693 if (!preg_match('/(\.png|\.gif|\.svg)$/i', $picto)) {
1694 $picto .= '.png';
1695 }
1696 // If alt path are defined, define url where img file is, according to physical path
1697 // ex: array(["main"]=>"/home/maindir/htdocs", ["alt0"]=>"/home/moddir0/htdocs", ...)
1698 foreach ($conf->file->dol_document_root as $type => $dirroot) {
1699 if ($type == 'main') {
1700 continue;
1701 }
1702 // This consumes a lot of time, that's why enabling alternative dir like "custom" dir should be avoid
1703 if (file_exists($dirroot . '/' . $path . '/img/' . $picto) && !empty($conf->file->dol_url_root)) {
1704 $url = DOL_URL_ROOT . $conf->file->dol_url_root[$type];
1705 break;
1706 }
1707 }
1708
1709 // $url is '' or '/custom', $path is current theme or
1710 $fullpathpicto = $url . '/' . $path . '/img/' . $picto;
1711 }
1712
1713 if ($srconly) {
1714 return $fullpathpicto;
1715 }
1716
1717 // tag title is used for tooltip on <a>, tag alt can be used with very simple text on image for blind people
1718 return '<img src="' . $fullpathpicto . '"' . ($notitle ? '' : ' alt="' . dolPrintHTMLForAttribute($alt, 0, $allowothertags) . '"') . (($notitle || empty($titlealt)) ? '' : ' title="' . dolPrintHTMLForAttribute($titlealt, 0, $allowothertags) . '"') . ($moreatt ? ' ' . $moreatt . ($morecss ? ' class="' . $morecss . '"' : '') : ' class="inline-block' . ($morecss ? ' ' . $morecss : '') . '"') . '>'; // Alt is used for accessibility, title for popup
1719}
1720
1728function getImgPictoConv($mode = 'fa')
1729{
1730 global $conf;
1731
1732 if (empty($mode) || $mode == 'fa') {
1733 // Array when the fa picto key is different than the Dolibarr picto key.
1734 $arrayconvpictotofa = array(
1735 'account' => 'university',
1736 'accounting_account' => 'clipboard-list',
1737 'accountline' => 'receipt',
1738 'accountancy' => 'search-dollar',
1739 'action' => 'calendar-alt',
1740 'add' => 'plus-circle',
1741 'address' => 'address-book',
1742 'ai' => 'magic',
1743 'admin' => 'star',
1744 'asset' => 'money-check-alt',
1745 'autofill' => 'fill',
1746 'back' => 'arrow-left',
1747 'bank_account' => 'university',
1748 'bill' => 'file-invoice-dollar',
1749 'billa' => 'file-excel',
1750 'billr' => 'file-invoice-dollar',
1751 'billd' => 'file-medical',
1752 'blockedlog' => 'file-archive',
1753 'bookcal' => 'calendar-check',
1754 'supplier_invoice' => 'file-invoice-dollar',
1755 'supplier_invoicea' => 'file-excel',
1756 'supplier_invoicer' => 'file-invoice-dollar',
1757 'supplier_invoiced' => 'file-medical',
1758 'bom' => 'shapes',
1759 'card' => 'address-card',
1760 'chart' => 'chart-line',
1761 'company' => 'building',
1762 'contact' => 'address-book',
1763 'contract' => 'suitcase',
1764 'collab' => 'people-arrows',
1765 'conversation' => 'comments',
1766 'country' => 'globe-americas',
1767 'cron' => 'business-time',
1768 'cross' => 'times',
1769 'chevron-double-left' => 'angle-double-left',
1770 'chevron-double-right' => 'angle-double-right',
1771 'chevron-double-down' => 'angle-double-down',
1772 'chevron-double-top' => 'angle-double-up',
1773 'donation' => 'gift',
1774 'dynamicprice' => 'hand-holding-usd',
1775 'setup' => 'cog',
1776 'companies' => 'building',
1777 'products' => 'cube',
1778 'commercial' => 'suitcase',
1779 'invoicing' => 'coins',
1780 'accounting' => 'search-dollar',
1781 'category' => 'tag',
1782 'dollyrevert' => 'dolly',
1783 'file-o' => 'file',
1784 'generate' => 'plus-square',
1785 'hrm' => 'user-tie',
1786 'incoterm' => 'truck-loading',
1787 'margin' => 'calculator',
1788 'members' => 'user-friends',
1789 'ticket' => 'ticket-alt',
1790 'globe' => 'external-link-alt',
1791 'lot' => 'barcode',
1792 'email' => 'at',
1793 'establishment' => 'building',
1794 'edit' => 'pencil-alt',
1795 'entity' => 'globe',
1796 'graph' => 'chart-line',
1797 'grip_title' => 'arrows-alt',
1798 'grip' => 'arrows-alt',
1799 'help' => 'question-circle',
1800 'generic' => 'file',
1801 'holiday' => 'umbrella-beach',
1802 'info' => 'info-circle',
1803 'info_black' => 'info-circle',
1804 'inventory' => 'boxes',
1805 'intracommreport' => 'globe-europe',
1806 'jobprofile' => 'cogs',
1807 'knowledgemanagement' => 'ticket-alt',
1808 'label' => 'layer-group',
1809 'layout' => 'columns',
1810 'line' => 'bars',
1811 'loan' => 'money-bill-alt',
1812 'member' => 'user-alt',
1813 'meeting' => 'chalkboard-teacher',
1814 'mrp' => 'cubes',
1815 'next' => 'arrow-alt-circle-right',
1816 'trip' => 'wallet',
1817 'expensereport' => 'wallet',
1818 'group' => 'users',
1819 'movement' => 'people-carry',
1820 'sign-out' => 'sign-out-alt',
1821 'superadmin' => 'star',
1822 'switch_off' => 'toggle-off',
1823 'switch_off_grey' => 'toggle-off',
1824 'switch_off_warning' => 'toggle-off',
1825 'switch_off_red' => 'toggle-off',
1826 'switch_on' => 'toggle-on',
1827 'switch_on_grey' => 'toggle-on',
1828 'switch_on_warning' => 'toggle-on',
1829 'switch_on_red' => 'toggle-on',
1830 'check' => 'check',
1831 'bookmark' => 'star',
1832 'bank' => 'university',
1833 'close_title' => 'times',
1834 'delete' => 'trash',
1835 'filter' => 'filter',
1836 'list-alt' => 'list-alt',
1837 'calendarlist' => 'bars',
1838 'calendar' => 'calendar-alt',
1839 'calendarmonth' => 'calendar-alt',
1840 'calendarweek' => 'calendar-week',
1841 'calendarday' => 'calendar-day',
1842 'calendarperuser' => 'table',
1843 'calendarpertype' => 'table',
1844 'intervention' => 'ambulance',
1845 'invoice' => 'file-invoice-dollar',
1846 'order' => 'file-invoice',
1847 'error' => 'exclamation-triangle',
1848 'warning' => 'exclamation-triangle',
1849 'other' => 'square',
1850 'playdisabled' => 'play',
1851 'pdf' => 'file-pdf',
1852 'poll' => 'check-double',
1853 'pos' => 'cash-register',
1854 'preview' => 'binoculars',
1855 'project' => 'project-diagram',
1856 'projectpub' => 'project-diagram',
1857 'projecttask' => 'tasks',
1858 'propal' => 'file-signature',
1859 'proposal' => 'file-signature',
1860 'partnership' => 'handshake',
1861 'payment' => 'money-check-alt',
1862 'payment_vat' => 'money-check-alt',
1863 'pictoconfirm' => 'check-square',
1864 'phoning' => 'phone',
1865 'phoning_mobile' => 'mobile-alt',
1866 'phoning_fax' => 'fax',
1867 'previous' => 'arrow-alt-circle-left',
1868 'printer' => 'print',
1869 'product' => 'cube',
1870 'puce' => 'angle-right',
1871 'recent' => 'check-square',
1872 'reception' => 'dolly',
1873 'recruitmentjobposition' => 'id-card-alt',
1874 'recruitmentcandidature' => 'id-badge',
1875 'resize' => 'crop',
1876 'supplier_order' => 'dol-order_supplier',
1877 'supplier_proposal' => 'file-signature',
1878 'refresh' => 'redo',
1879 'region' => 'map-marked',
1880 'replacement' => 'exchange-alt',
1881 'resource' => 'laptop-house',
1882 'recurring' => 'history',
1883 'service' => 'concierge-bell',
1884 'skill' => 'shapes',
1885 'state' => 'map-marked-alt',
1886 'security' => 'key',
1887 'salary' => 'wallet',
1888 'shipment' => 'dolly',
1889 'stock' => 'box-open',
1890 'stats' => 'chart-bar',
1891 'split' => 'code-branch',
1892 'status' => 'stop-circle',
1893 'stripe' => 'stripe-s',
1894 'supplier' => 'building',
1895 'technic' => 'cogs',
1896 'tick' => 'check',
1897 'timespent' => 'clock',
1898 'title_setup' => 'tools',
1899 'title_accountancy' => 'money-check-alt',
1900 'title_bank' => 'university',
1901 'title_hrm' => 'umbrella-beach',
1902 'title_agenda' => 'calendar-alt',
1903 'uncheck' => 'times',
1904 'uparrow' => 'share',
1905 'url' => 'external-link-alt',
1906 'vat' => 'money-check-alt',
1907 'vcard' => 'arrow-alt-circle-down',
1908 'jabber' => 'comment',
1909 'website' => 'globe-americas',
1910 'workstation' => 'pallet',
1911 'webhook' => 'bullseye',
1912 'world' => 'globe',
1913 'private' => 'user-lock',
1914 'conferenceorbooth' => 'chalkboard-teacher',
1915 'eventorganization' => 'project-diagram',
1916 'webportal' => 'door-open'
1917 );
1918
1919 if ($conf->currency == 'EUR') {
1920 $arrayconvpictotofa['currency'] = 'euro-sign';
1921 $arrayconvpictotofa['multicurrency'] = 'dollar-sign';
1922 } else {
1923 $arrayconvpictotofa['currency'] = 'dollar-sign';
1924 $arrayconvpictotofa['multicurrency'] = 'euro-sign';
1925 }
1926 } else {
1927 $arrayconvpictotofa = array();
1928 }
1929
1930 return $arrayconvpictotofa;
1931}
1932
1933
1949function img_object($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $srconly = 0, $notitle = 0, $allowothertags = array())
1950{
1951 if (strpos($picto, '^') === 0) {
1952 return img_picto($titlealt, str_replace('^', '', $picto), $moreatt, $pictoisfullpath, $srconly, $notitle, '', '', 2, $allowothertags);
1953 } else {
1954 return img_picto($titlealt, 'object_' . $picto, $moreatt, $pictoisfullpath, $srconly, $notitle, '', '', 2, $allowothertags);
1955 }
1956}
1957
1969function img_weather($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $morecss = '')
1970{
1971 global $conf;
1972
1973 if (is_numeric($picto)) {
1974 //$leveltopicto = array(0=>'weather-clear.png', 1=>'weather-few-clouds.png', 2=>'weather-clouds.png', 3=>'weather-many-clouds.png', 4=>'weather-storm.png');
1975 //$picto = $leveltopicto[$picto];
1976 return '<i class="fa fa-weather-level' . $picto . '"></i>';
1977 } elseif (!preg_match('/(\.png|\.gif)$/i', $picto)) {
1978 $picto .= '.png';
1979 }
1980
1981 $path = DOL_URL_ROOT . '/theme/' . $conf->theme . '/img/weather/' . $picto;
1982
1983 return img_picto($titlealt, $path, $moreatt, 1, 0, 0, '', $morecss);
1984}
1985
1997function img_picto_common($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $notitle = 0)
1998{
1999 global $conf;
2000
2001 if (!preg_match('/(\.png|\.gif)$/i', $picto)) {
2002 $picto .= '.png';
2003 }
2004
2005 if ($pictoisfullpath) {
2006 $path = $picto;
2007 } else {
2008 $path = DOL_URL_ROOT . '/theme/common/' . $picto;
2009
2010 if (getDolGlobalInt('MAIN_MODULE_CAN_OVERWRITE_COMMONICONS')) {
2011 $themeimg = '/theme/' . $conf->theme . '/img/' . $picto;
2012
2013 if (file_exists(DOL_DOCUMENT_ROOT . $themeimg)) {
2014 $path = DOL_URL_ROOT . $themeimg;
2015 } elseif (!empty($conf->modules_parts['theme'])) { // Using this feature slow down application
2016 foreach ($conf->modules_parts['theme'] as $reldir) {
2017 if (file_exists(dol_buildpath($reldir . $themeimg, 0))) {
2018 $path = dol_buildpath($reldir . $themeimg, 1);
2019 break;
2020 }
2021 }
2022 }
2023 }
2024 }
2025
2026 return img_picto($titlealt, $path, $moreatt, 1, 0, $notitle);
2027}
2028
2042function img_action($titlealt, $numaction, $picto = '', $moreatt = '')
2043{
2044 global $langs;
2045
2046 if (empty($titlealt) || $titlealt == 'default') {
2047 if ($numaction == '-1' || $numaction == 'ST_NO') {
2048 $numaction = -1;
2049 $titlealt = $langs->transnoentitiesnoconv('ChangeDoNotContact');
2050 } elseif ($numaction == '0' || $numaction == 'ST_NEVER') {
2051 $numaction = 0;
2052 $titlealt = $langs->transnoentitiesnoconv('ChangeNeverContacted');
2053 } elseif ($numaction == '1' || $numaction == 'ST_TODO') {
2054 $numaction = 1;
2055 $titlealt = $langs->transnoentitiesnoconv('ChangeToContact');
2056 } elseif ($numaction == '2' || $numaction == 'ST_PEND') {
2057 $numaction = 2;
2058 $titlealt = $langs->transnoentitiesnoconv('ChangeContactInProcess');
2059 } elseif ($numaction == '3' || $numaction == 'ST_DONE') {
2060 $numaction = 3;
2061 $titlealt = $langs->transnoentitiesnoconv('ChangeContactDone');
2062 } else {
2063 $titlealt = $langs->transnoentitiesnoconv('ChangeStatus ' . $numaction);
2064 $numaction = 0;
2065 }
2066 }
2067 if (!is_numeric($numaction)) {
2068 $numaction = 0;
2069 }
2070
2071 return img_picto($titlealt, (empty($picto) ? 'stcomm' . $numaction . '.png' : $picto), $moreatt);
2072}
2073
2082function img_edit_add($titlealt = 'default', $other = '')
2083{
2084 global $langs;
2085
2086 if ($titlealt == 'default') {
2087 $titlealt = $langs->trans('Add');
2088 }
2089
2090 return img_picto($titlealt, 'edit_add.png', $other);
2091}
2100function img_edit_remove($titlealt = 'default', $other = '')
2101{
2102 global $langs;
2103
2104 if ($titlealt == 'default') {
2105 $titlealt = $langs->trans('Remove');
2106 }
2107
2108 return img_picto($titlealt, 'edit_remove.png', $other);
2109}
2110
2120function img_edit($titlealt = 'default', $float = 0, $other = '')
2121{
2122 global $langs;
2123
2124 if ($titlealt == 'default') {
2125 $titlealt = $langs->trans('Modify');
2126 }
2127
2128 return img_picto($titlealt, 'edit', ($float ? 'style="float: ' . ($langs->tab_translate["DIRECTION"] == 'rtl' ? 'left' : 'right') . '"' : "") . ($other ? ' ' . $other : ''));
2129}
2130
2140function img_view($titlealt = 'default', $float = 0, $other = 'class="valignmiddle"')
2141{
2142 global $langs;
2143
2144 if ($titlealt == 'default') {
2145 $titlealt = $langs->trans('View');
2146 }
2147
2148 $moreatt = ($float ? 'style="float: right" ' : '') . $other;
2149
2150 return img_picto($titlealt, 'eye', $moreatt);
2151}
2152
2162function img_delete($titlealt = 'default', $other = 'class="pictodelete"', $morecss = '')
2163{
2164 global $langs;
2165
2166 if ($titlealt == 'default') {
2167 $titlealt = $langs->trans('Delete');
2168 }
2169
2170 return img_picto($titlealt, 'delete', $other, 0, 0, 0, '', $morecss);
2171}
2172
2180function img_printer($titlealt = "default", $other = '')
2181{
2182 global $langs;
2183 if ($titlealt == "default") {
2184 $titlealt = $langs->trans("Print");
2185 }
2186 return img_picto($titlealt, 'printer', $other);
2187}
2188
2196function img_split($titlealt = 'default', $other = 'class="pictosplit"')
2197{
2198 global $langs;
2199
2200 if ($titlealt == 'default') {
2201 $titlealt = $langs->trans('Split');
2202 }
2203
2204 return img_picto($titlealt, 'split', $other);
2205}
2206
2215function img_help($usehelpcursor = 1, $usealttitle = 1)
2216{
2217 global $langs;
2218
2219 if ($usealttitle) {
2220 if (is_string($usealttitle)) {
2221 $usealttitle = dol_escape_htmltag($usealttitle);
2222 } else {
2223 $usealttitle = $langs->trans('Info');
2224 }
2225 }
2226
2227 return img_picto($usealttitle, 'info', 'style="vertical-align: middle;' . ($usehelpcursor == 1 ? ' cursor: help' : ($usehelpcursor == 2 ? ' cursor: pointer' : '')) . '"');
2228}
2229
2237function img_info($titlealt = 'default')
2238{
2239 global $langs;
2240
2241 if ($titlealt == 'default') {
2242 $titlealt = $langs->trans('Informations');
2243 }
2244
2245 return img_picto($titlealt, 'info', 'style="vertical-align: middle;"');
2246}
2247
2257function img_warning($titlealt = 'default', $moreatt = '', $morecss = 'pictowarning')
2258{
2259 global $langs;
2260
2261 if ($titlealt == 'default') {
2262 $titlealt = $langs->trans('Warning');
2263 }
2264
2265 //return '<div class="imglatecoin">'.img_picto($titlealt, 'warning_white.png', 'class="pictowarning valignmiddle"'.($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' '.$moreatt): '')).'</div>';
2266 return img_picto($titlealt, 'warning', 'class="' . $morecss . '"' . ($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' ' . $moreatt) : ''));
2267}
2268
2276function img_error($titlealt = 'default')
2277{
2278 global $langs;
2279
2280 if ($titlealt == 'default') {
2281 $titlealt = $langs->trans('Error');
2282 }
2283
2284 return img_picto($titlealt, 'error');
2285}
2286
2294function img_next($titlealt = 'default', $moreatt = '')
2295{
2296 global $langs;
2297
2298 if ($titlealt == 'default') {
2299 $titlealt = $langs->trans('Next');
2300 }
2301
2302 //return img_picto($titlealt, 'next.png', $moreatt);
2303 return '<span class="fa fa-chevron-right paddingright paddingleft" title="' . dol_escape_htmltag($titlealt) . '"></span>';
2304}
2305
2313function img_previous($titlealt = 'default', $moreatt = '')
2314{
2315 global $langs;
2316
2317 if ($titlealt == 'default') {
2318 $titlealt = $langs->trans('Previous');
2319 }
2320
2321 //return img_picto($titlealt, 'previous.png', $moreatt);
2322 return '<span class="fa fa-chevron-left paddingright paddingleft" title="' . dol_escape_htmltag($titlealt) . '"></span>';
2323}
2324
2333function img_down($titlealt = 'default', $selected = 0, $moreclass = '')
2334{
2335 global $langs;
2336
2337 if ($titlealt == 'default') {
2338 $titlealt = $langs->trans('Down');
2339 }
2340
2341 return img_picto($titlealt, ($selected ? '1downarrow_selected' : '1downarrow'), 'class="imgdown' . ($moreclass ? " " . $moreclass : "") . '"');
2342}
2343
2352function img_up($titlealt = 'default', $selected = 0, $moreclass = '')
2353{
2354 global $langs;
2355
2356 if ($titlealt == 'default') {
2357 $titlealt = $langs->trans('Up');
2358 }
2359
2360 return img_picto($titlealt, ($selected ? '1uparrow_selected' : '1uparrow'), 'class="imgup' . ($moreclass ? " " . $moreclass : "") . '"');
2361}
2362
2371function img_left($titlealt = 'default', $selected = 0, $moreatt = '')
2372{
2373 global $langs;
2374
2375 if ($titlealt == 'default') {
2376 $titlealt = $langs->trans('Left');
2377 }
2378
2379 return img_picto($titlealt, ($selected ? '1leftarrow_selected' : '1leftarrow'), $moreatt);
2380}
2381
2390function img_right($titlealt = 'default', $selected = 0, $moreatt = '')
2391{
2392 global $langs;
2393
2394 if ($titlealt == 'default') {
2395 $titlealt = $langs->trans('Right');
2396 }
2397
2398 return img_picto($titlealt, ($selected ? '1rightarrow_selected' : '1rightarrow'), $moreatt);
2399}
2400
2408function img_allow($allow, $titlealt = 'default')
2409{
2410 global $langs;
2411
2412 if ($titlealt == 'default') {
2413 $titlealt = $langs->trans('Active');
2414 }
2415
2416 if ($allow == 1) {
2417 return img_picto($titlealt, 'tick');
2418 }
2419
2420 return '-';
2421}
2422
2430function img_credit_card($brand, $morecss = 'fa-2x inline-block valignmiddle')
2431{
2432 if (is_null($morecss)) {
2433 $morecss = 'fa-2x';
2434 }
2435
2436 if ($brand == 'visa' || $brand == 'Visa') {
2437 $brand = 'cc-visa';
2438 } elseif ($brand == 'mastercard' || $brand == 'MasterCard') {
2439 $brand = 'cc-mastercard';
2440 } elseif ($brand == 'amex' || $brand == 'American Express') {
2441 $brand = 'cc-amex';
2442 } elseif ($brand == 'discover' || $brand == 'Discover') {
2443 $brand = 'cc-discover';
2444 } elseif ($brand == 'jcb' || $brand == 'JCB') {
2445 $brand = 'cc-jcb';
2446 } elseif ($brand == 'diners' || $brand == 'Diners club') {
2447 $brand = 'cc-diners-club';
2448 } elseif (!in_array($brand, array('cc-visa', 'cc-mastercard', 'cc-amex', 'cc-discover', 'cc-jcb', 'cc-diners-club'))) {
2449 $brand = 'credit-card';
2450 }
2451
2452 return '<span class="fa fa-' . $brand . ' fa-fw' . ($morecss ? ' ' . $morecss : '') . '"></span>';
2453}
2454
2463function img_mime($file, $titlealt = '', $morecss = '')
2464{
2465 require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
2466
2467 $mimetype = dol_mimetype($file, '', 1);
2468 //$mimeimg = dol_mimetype($file, '', 2);
2469 $mimefa = dol_mimetype($file, '', 4);
2470
2471 if (empty($titlealt)) {
2472 $titlealt = 'Mime type: ' . $mimetype;
2473 }
2474
2475 //return img_picto_common($titlealt, 'mime/'.$mimeimg, 'class="'.$morecss.'"');
2476 return '<i class="fa fa-' . $mimefa . ' ' . (preg_match('/pictofixedwidth/', $morecss) ? '' : 'paddingright ') . ($morecss ? ' ' . $morecss : '') . '"' . ($titlealt ? ' title="' . dolPrintHTMLForAttribute($titlealt) . '"' : '') . '></i>';
2477}
2478
2479
2487function img_search($titlealt = 'default', $other = '')
2488{
2489 global $langs;
2490
2491 if ($titlealt == 'default') {
2492 $titlealt = $langs->trans('Search');
2493 }
2494
2495 $img = img_picto($titlealt, 'search', $other, 0, 1);
2496
2497 $input = '<input type="image" class="liste_titre" name="button_search" src="' . $img . '" ';
2498 $input .= 'value="' . dol_escape_htmltag($titlealt) . '" title="' . dol_escape_htmltag($titlealt) . '" >';
2499
2500 return $input;
2501}
2502
2510function img_searchclear($titlealt = 'default', $other = '')
2511{
2512 global $langs;
2513
2514 if ($titlealt == 'default') {
2515 $titlealt = $langs->trans('Search');
2516 }
2517
2518 $img = img_picto($titlealt, 'searchclear.png', $other, 0, 1);
2519
2520 $input = '<input type="image" class="liste_titre" name="button_removefilter" src="' . $img . '" ';
2521 $input .= 'value="' . dol_escape_htmltag($titlealt) . '" title="' . dol_escape_htmltag($titlealt) . '" >';
2522
2523 return $input;
2524}
2525
2540function info_admin($text, $infoonimgalt = 0, $nodiv = 0, $admin = '1', $morecss = 'hideonsmartphone', $textfordropdown = '', $picto = '', $textonpictotooltip = '', $cssfordropdown = 'info_admin')
2541{
2542 global $conf, $langs;
2543
2544 if ($infoonimgalt) {
2545 $result = img_picto($text, 'info', 'class="' . ($morecss ? ' ' . $morecss : '') . '"');
2546 } else {
2547 if (empty($conf->use_javascript_ajax)) {
2548 $textfordropdown = '';
2549 }
2550
2551 $class = (empty($admin) ? 'undefined' : ((string) $admin == '1' ? 'info' : $admin));
2552 $fa = 'info-circle';
2553 if ($picto == 'warning') {
2554 $fa = 'exclamation-triangle';
2555 }
2556 $result = ($nodiv ? '' : '<div class="wordbreak ' . $class . ($cssfordropdown ? ' ' . $cssfordropdown : '') . ($morecss ? ' ' . $morecss : '') . ($textfordropdown ? ' hidden' : '') . '">');
2557 $result .= img_picto(((string) $admin ? $langs->trans('InfoAdmin') : $langs->trans('Note')).($textonpictotooltip ? ' : '.$textonpictotooltip : ''), $fa);
2558 $result .= ' ';
2559 $result .= dol_escape_htmltag($text, 1, 0, 'div,span,b,br,a');
2560 $result .= ($nodiv ? '' : '</div>');
2561
2562 if ($textfordropdown) {
2563 $tmpresult = '<span class="' . $class . ' '. $cssfordropdown.'text opacitymedium cursorpointer">' . $langs->trans($textfordropdown) . ' ' . img_picto($langs->trans($textfordropdown), '1downarrow') . '</span>';
2564 $tmpresult .= '<script nonce="' . getNonce() . '" type="text/javascript">
2565 jQuery(document).ready(function() {
2566 jQuery(".' . $cssfordropdown . 'text").click(function() {
2567 console.log("toggle text of .'.$cssfordropdown.'");
2568 jQuery(".' . $cssfordropdown . '").toggle().removeClass("hidden");
2569 });
2570 });
2571 </script>';
2572
2573 $result = $tmpresult . $result;
2574 }
2575 }
2576
2577 return $result;
2578}
2579
2580
2593function dol_print_error($db = null, $error = '', $errors = null)
2594{
2595 global $conf, $langs, $user, $argv;
2596 global $dolibarr_main_prod;
2597
2598 $out = '';
2599 $syslog = '';
2600
2601 // If error occurs before the $lang object was loaded
2602 if (!$langs) {
2603 require_once DOL_DOCUMENT_ROOT . '/core/class/translate.class.php';
2604 $langs = new Translate('', $conf);
2605 $langs->load("main");
2606 }
2607
2608 // Load translation files required by the error messages
2609 $langs->loadLangs(array('main', 'errors'));
2610
2611 if ($_SERVER['DOCUMENT_ROOT']) { // Mode web
2612 $out .= $langs->trans("DolibarrHasDetectedError") . ".<br>\n";
2613 if (getDolGlobalInt('MAIN_FEATURES_LEVEL') > 0) {
2614 $out .= "You use an experimental or develop level of features, so please do NOT report any bugs or vulnerability, except if problem is confirmed after moving option MAIN_FEATURES_LEVEL back to 0.<br>\n";
2615 }
2616 $out .= $langs->trans("InformationToHelpDiagnose") . ":<br>\n";
2617
2618 $out .= "<b>" . $langs->trans("Date") . ":</b> " . dol_print_date(dol_now(), 'dayhourlog') . "<br>\n";
2619 $out .= "<b>" . $langs->trans("Dolibarr") . ":</b> " . DOL_VERSION . " - https://www.dolibarr.org<br>\n";
2620 if (isset($conf->global->MAIN_FEATURES_LEVEL)) {
2621 $out .= "<b>" . $langs->trans("LevelOfFeature") . ":</b> " . getDolGlobalInt('MAIN_FEATURES_LEVEL') . "<br>\n";
2622 }
2623 if ($user instanceof User) {
2624 $out .= "<b>" . $langs->trans("Login") . ":</b> " . $user->login . "<br>\n";
2625 }
2626 if (function_exists("phpversion")) {
2627 $out .= "<b>" . $langs->trans("PHP") . ":</b> " . phpversion() . "<br>\n";
2628 }
2629 $out .= "<b>" . $langs->trans("Server") . ":</b> " . (isset($_SERVER["SERVER_SOFTWARE"]) ? dol_htmlentities($_SERVER["SERVER_SOFTWARE"], ENT_COMPAT) : '') . "<br>\n";
2630 if (function_exists("php_uname")) {
2631 $out .= "<b>" . $langs->trans("OS") . ":</b> " . php_uname() . "<br>\n";
2632 }
2633 $out .= "<b>" . $langs->trans("UserAgent") . ":</b> " . (isset($_SERVER["HTTP_USER_AGENT"]) ? dol_htmlentities($_SERVER["HTTP_USER_AGENT"], ENT_COMPAT) : '') . "<br>\n";
2634 $out .= "<br>\n";
2635 $out .= "<b>" . $langs->trans("RequestedUrl") . ":</b> " . (isset($_SERVER["REQUEST_URI"]) ? dol_htmlentities($_SERVER["REQUEST_URI"], ENT_COMPAT) : '') . "<br>\n";
2636 $out .= "<b>" . $langs->trans("Referer") . ":</b> " . (isset($_SERVER["HTTP_REFERER"]) ? dol_htmlentities($_SERVER["HTTP_REFERER"], ENT_COMPAT) : '') . "<br>\n";
2637 $out .= "<b>" . $langs->trans("MenuManager") . ":</b> " . (isset($conf->standard_menu) ? dol_htmlentities($conf->standard_menu, ENT_COMPAT) : '') . "<br>\n";
2638 $out .= "<br>\n";
2639 $syslog .= "url=" . (isset($_SERVER["REQUEST_URI"]) ? dol_escape_htmltag($_SERVER["REQUEST_URI"]) : '');
2640 $syslog .= ", query_string=" . (isset($_SERVER["QUERY_STRING"]) ? dol_escape_htmltag($_SERVER["QUERY_STRING"]) : '');
2641 } else { // Mode CLI
2642 $out .= '> ' . $langs->transnoentities("ErrorInternalErrorDetected") . ":\n" . $argv[0] . "\n";
2643 $syslog .= "pid=" . dol_getmypid();
2644 }
2645
2646 if (!empty($conf->modules)) {
2647 $out .= "<b>" . $langs->trans("Modules") . ":</b> " . implode(', ', $conf->modules) . "<br>\n";
2648 }
2649
2650 if (is_object($db)) {
2651 if ($_SERVER['DOCUMENT_ROOT']) { // Mode web
2652 $out .= "<b>" . $langs->trans("DatabaseTypeManager") . ":</b> " . $db->type . "<br>\n";
2653 $lastqueryerror = $db->lastqueryerror();
2654 if (!utf8_check($lastqueryerror)) {
2655 $lastqueryerror = "SQL error string is not a valid UTF8 string. We can't show it.";
2656 }
2657 $out .= "<b>" . $langs->trans("RequestLastAccessInError") . ":</b> " . ($lastqueryerror ? dol_escape_htmltag($lastqueryerror) : $langs->trans("ErrorNoRequestInError")) . "<br>\n";
2658 $out .= "<b>" . $langs->trans("ReturnCodeLastAccessInError") . ":</b> " . ($db->lasterrno() ? dol_escape_htmltag($db->lasterrno()) : $langs->trans("ErrorNoRequestInError")) . "<br>\n";
2659 $out .= "<b>" . $langs->trans("InformationLastAccessInError") . ":</b> " . ($db->lasterror() ? dol_escape_htmltag($db->lasterror()) : $langs->trans("ErrorNoRequestInError")) . "<br>\n";
2660 $out .= "<br>\n";
2661 } else { // Mode CLI
2662 // No dol_escape_htmltag for output, we are in CLI mode
2663 $out .= '> ' . $langs->transnoentities("DatabaseTypeManager") . ":\n" . $db->type . "\n";
2664 $out .= '> ' . $langs->transnoentities("RequestLastAccessInError") . ":\n" . ($db->lastqueryerror() ? $db->lastqueryerror() : $langs->transnoentities("ErrorNoRequestInError")) . "\n";
2665 $out .= '> ' . $langs->transnoentities("ReturnCodeLastAccessInError") . ":\n" . ($db->lasterrno() ? $db->lasterrno() : $langs->transnoentities("ErrorNoRequestInError")) . "\n";
2666 $out .= '> ' . $langs->transnoentities("InformationLastAccessInError") . ":\n" . ($db->lasterror() ? $db->lasterror() : $langs->transnoentities("ErrorNoRequestInError")) . "\n";
2667 }
2668 $syslog .= ", sql=" . $db->lastquery();
2669 $syslog .= ", db_error=" . $db->lasterror();
2670 }
2671
2672 if ($error || $errors) {
2673 // Merge all into $errors array
2674 if (is_array($error) && is_array($errors)) {
2675 $errors = array_merge($error, $errors);
2676 } elseif (is_array($error)) { // deprecated, use second parameters
2677 $errors = $error;
2678 } elseif (is_array($errors) && !empty($error)) {
2679 $errors = array_merge(array($error), $errors);
2680 } elseif (!empty($error)) {
2681 $errors = array_merge(array($error), array($errors));
2682 }
2683
2684 $langs->load("errors");
2685
2686 foreach ($errors as $msg) {
2687 if (empty($msg)) {
2688 continue;
2689 }
2690 if ($_SERVER['DOCUMENT_ROOT']) { // Mode web
2691 $out .= "<b>" . $langs->trans("Message") . ":</b> " . dol_escape_htmltag($msg) . "<br>\n";
2692 } else { // Mode CLI
2693 $out .= '> ' . $langs->transnoentities("Message") . ":\n" . $msg . "\n";
2694 }
2695 $syslog .= ", msg=" . $msg;
2696 }
2697 }
2698 if (empty($dolibarr_main_prod) && $_SERVER['DOCUMENT_ROOT'] && function_exists('xdebug_print_function_stack') && function_exists('xdebug_call_file')) {
2699 xdebug_print_function_stack();
2700 $out .= '<b>XDebug information:</b>' . "<br>\n";
2701 $out .= 'File: ' . xdebug_call_file() . "<br>\n";
2702 $out .= 'Line: ' . xdebug_call_line() . "<br>\n";
2703 $out .= 'Function: ' . xdebug_call_function() . "<br>\n";
2704 $out .= "<br>\n";
2705 }
2706
2707 // Return a http header with error code if possible
2708 if (!headers_sent()) {
2709 if (function_exists('top_httphead')) { // In CLI context, the method does not exists
2710 top_httphead();
2711 }
2712 //http_response_code(500); // If we use 500, message is not output with some command line tools
2713 http_response_code(202); // If we use 202, this is not really an error message, but this allow to output message on command line tools
2714 }
2715
2716 if (empty($dolibarr_main_prod)) {
2717 print $out;
2718 } else {
2719 if (empty($langs->defaultlang)) {
2720 $langs->setDefaultLang();
2721 }
2722 $langs->loadLangs(array("main", "errors")); // Reload main because language may have been set only on previous line so we have to reload files we need.
2723 // This should not happen, except if there is a bug somewhere. Enabled and check log in such case.
2724 print 'This website or feature is currently temporarily not available or failed after a technical error.<br><br>This may be due to a maintenance operation. Current status of operation (' . dol_print_date(dol_now(), 'dayhourrfc') . ') are on next line...<br><br>' . "\n";
2725 print $langs->trans("DolibarrHasDetectedError") . '. ';
2726 print $langs->trans("YouCanSetOptionDolibarrMainProdToZero");
2727 if (!defined("MAIN_CORE_ERROR")) {
2728 define("MAIN_CORE_ERROR", 1);
2729 }
2730 }
2731
2732 dol_syslog("Error " . $syslog, LOG_ERR);
2733}
2734
2745function dol_print_error_email($prefixcode, $errormessage = '', $errormessages = array(), $morecss = 'error', $email = '')
2746{
2747 global $langs;
2748
2749 if (empty($email)) {
2750 $email = getDolGlobalString('MAIN_INFO_SOCIETE_MAIL');
2751 }
2752
2753 $langs->load("errors");
2754 $now = dol_now();
2755
2756 print '<br><div class="center login_main_message"><div class="' . $morecss . '">';
2757 print $langs->trans("ErrorContactEMail", $email, $prefixcode . '-' . dol_print_date($now, '%Y%m%d%H%M%S'));
2758 if ($errormessage) {
2759 print '<br><br>' . $errormessage;
2760 }
2761 if (is_array($errormessages) && count($errormessages)) {
2762 foreach ($errormessages as $mesgtoshow) {
2763 print '<br><br>' . $mesgtoshow;
2764 }
2765 }
2766 print '</div></div>';
2767}
2768
2785function print_liste_field_titre($name, $file = "", $field = "", $begin = "", $param = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $tooltip = "", $forcenowrapcolumntitle = 0)
2786{
2787 print getTitleFieldOfList($name, 0, $file, $field, $begin, $param, $moreattrib, $sortfield, $sortorder, $prefix, 0, $tooltip, $forcenowrapcolumntitle);
2788}
2789
2808function getTitleFieldOfList($name, $thead = 0, $file = "", $field = "", $begin = "", $moreparam = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $disablesortlink = 0, $tooltip = '', $forcenowrapcolumntitle = 0)
2809{
2810 global $langs, $form;
2811 //print "$name, $file, $field, $begin, $options, $moreattrib, $sortfield, $sortorder<br>\n";
2812
2813 if ($moreattrib == 'class="right"') {
2814 $prefix .= 'right '; // For backward compatibility
2815 }
2816
2817 $tooltip = (string) $tooltip; // In case $tooltip is null
2818
2819 $sortorder = strtoupper((string) $sortorder);
2820 $out = '';
2821 $sortimg = '';
2822
2823 $tag = 'th';
2824 if ($thead == 2) {
2825 $tag = 'div';
2826 }
2827
2828 $tmpsortfield = explode(',', (string) $sortfield);
2829 $sortfield1 = trim($tmpsortfield[0]); // If $sortfield is 'd.datep,d.id', it becomes 'd.datep'
2830 $tmpfield = explode(',', $field);
2831 $field1 = trim($tmpfield[0]); // If $field is 'd.datep,d.id', it becomes 'd.datep'
2832
2833 if (strpos((string) $tooltip, ':') !== false) {
2834 $tmptooltip = explode(':', (string) $tooltip);
2835 } else {
2836 $tmptooltip = array($tooltip);
2837 }
2838
2839 $wrapcolumntitle = (empty($forcenowrapcolumntitle) || (!empty($tmptooltip[2]) && $tmptooltip[2] == '-1'));
2840
2841 if (!getDolGlobalString('MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE') && $wrapcolumntitle) {
2842 $prefix = 'wrapcolumntitle ' . $prefix;
2843 }
2844
2845 //var_dump('field='.$field.' field1='.$field1.' sortfield='.$sortfield.' sortfield1='.$sortfield1);
2846 // If field is used as sort criteria we use a specific css class liste_titre_sel
2847 // Example if (sortfield,field)=("nom","xxx.nom") or (sortfield,field)=("nom","nom")
2848 $liste_titre = 'liste_titre';
2849 if ($field1 && ($sortfield1 == $field1 || $sortfield1 == preg_replace("/^[^\.]+\./", "", $field1))) {
2850 $liste_titre = 'liste_titre_sel';
2851 }
2852
2853 $tagstart = '<' . $tag . ' class="' . $prefix . $liste_titre . '" ' . $moreattrib;
2854 //$out .= (($field && empty($conf->global->MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE) && preg_match('/^[a-zA-Z_0-9\s\.\-:&;]*$/', $name)) ? ' title="'.dol_escape_htmltag($langs->trans($name)).'"' : '');
2855 $tagstart .= ($name && !getDolGlobalString('MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE') && $wrapcolumntitle && !dol_textishtml($name)) ? ' title="' . dolPrintHTMLForAttribute($langs->trans($name)) . '"' : '';
2856 $tagstart .= '>';
2857
2858 if (empty($thead) && $field && empty($disablesortlink)) { // If this is a sort field
2859 $options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', (is_scalar($moreparam) ? $moreparam : ''));
2860 $options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
2861 $options = preg_replace('/&+/i', '&', $options);
2862 if (!preg_match('/^&/', $options)) {
2863 $options = '&' . $options;
2864 }
2865
2866 $sortordertouseinlink = '';
2867 if ($field1 != $sortfield1) { // We are on another field than current sorted field
2868 if (preg_match('/^DESC/i', $sortorder)) {
2869 $sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
2870 } else { // We reverse the var $sortordertouseinlink
2871 $sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
2872 }
2873 } else { // We are on field that is the first current sorting criteria
2874 if (preg_match('/^ASC/i', $sortorder)) { // We reverse the var $sortordertouseinlink
2875 $sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
2876 } else {
2877 $sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
2878 }
2879 }
2880 $sortordertouseinlink = preg_replace('/,$/', '', $sortordertouseinlink);
2881 $out .= '<a class="reposition" href="' . dolBuildUrl($file, ['sortfield' => $field, 'sortorder' => $sortordertouseinlink, 'begin' => $begin]) . $options . '"';
2882 //$out .= (getDolGlobalString('MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE') ? '' : ' title="'.dol_escape_htmltag($langs->trans($name)).'"');
2883 $out .= '>';
2884 }
2885 if ($tooltip && $tmptooltip[0]) {
2886 // You can also use 'TranslationString:[keyfortooltiponclick]:[tooltipdirection]' for a tooltip on click or to change tooltip position.
2887 $out .= $form->textwithpicto($langs->trans((string) $name), $langs->trans((string) $tmptooltip[0]), (empty($tmptooltip[2]) ? '1' : $tmptooltip[2]), 'help', ((!empty($tmptooltip[2]) && $tmptooltip[2] == '-1') ? 'paddingrightonly' : ''), 0, 3, (empty($tmptooltip[1]) ? '' : 'extra_' . str_replace('.', '_', $field) . '_' . $tmptooltip[1]));
2888 } else {
2889 $out .= $langs->trans((string) $name);
2890 }
2891
2892 if (empty($thead) && $field && empty($disablesortlink)) { // If this is a sort field
2893 $out .= '</a>';
2894 }
2895
2896 if (empty($thead) && $field) { // If this is a sort field
2897 $options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', (is_scalar($moreparam) ? $moreparam : ''));
2898 $options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
2899 $options = preg_replace('/&+/i', '&', $options);
2900 if (!preg_match('/^&/', $options)) {
2901 $options = '&' . $options;
2902 }
2903
2904 if (!$sortorder || ($field1 != $sortfield1)) {
2905 // Nothing
2906 } else {
2907 if (preg_match('/^DESC/', $sortorder)) {
2908 $sortimg .= '<span class="nowrap">' . img_up("Z-A", 0, 'paddingright') . '</span>';
2909 }
2910 if (preg_match('/^ASC/', $sortorder)) {
2911 $sortimg .= '<span class="nowrap">' . img_down("A-Z", 0, 'paddingright') . '</span>';
2912 }
2913 }
2914 }
2915
2916 $tagend = '</' . $tag . '>';
2917
2918 $out = $tagstart . $sortimg . $out . $tagend;
2919
2920 return $out;
2921}
2922
2932function print_titre($title)
2933{
2934 dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);
2935
2936 print '<div class="titre">' . $title . '</div>';
2937}
2938
2951function print_fiche_titre($title, $mesg = '', $picto = 'generic', $pictoisfullpath = 0, $id = '')
2952{
2953 print load_fiche_titre($title, $mesg, $picto, $pictoisfullpath, $id);
2954}
2955
2970function load_fiche_titre($title, $morehtmlright = '', $picto = 'generic', $pictoisfullpath = 0, $id = '', $morecssontable = '', $morehtmlcenter = '', $morecssonpicto = 'widthpictotitle')
2971{
2972 $return = '';
2973
2974 if ($picto == 'setup') {
2975 $picto = 'generic';
2976 }
2977
2978 $return .= "\n";
2979 $return .= '<table ' . ($id ? 'id="' . $id . '" ' : '') . 'class="centpercent notopnoleftnoright table-fiche-title' . ($morecssontable ? ' ' . $morecssontable : '') . '">'; // margin bottom must be same than into print_barre_list
2980 $return .= '<tr class="toptitle">';
2981 if ($picto) {
2982 $return .= '<td class="nobordernopadding widthpictotitle valignmiddle col-picto">' . img_picto('', $picto, 'class="valignmiddle pictotitle'.($morecssonpicto ? ' '.$morecssonpicto : '').'"', $pictoisfullpath) . '</td>';
2983 }
2984 $return .= '<td class="nobordernopadding valignmiddle col-title">';
2985 $return .= '<div class="titre inline-block">';
2986 $return .= '<span class="inline-block valignmiddle print-barre-liste">' . $title . '</span>'; // $title is already HTML sanitized content
2987 $return .= '</div>';
2988 $return .= '</td>';
2989 if (dol_strlen($morehtmlcenter)) {
2990 $return .= '<td class="nobordernopadding center valignmiddle col-center">' . $morehtmlcenter . '</td>';
2991 }
2992 if (dol_strlen($morehtmlright)) {
2993 $return .= '<td class="nobordernopadding titre_right wordbreakimp right valignmiddle col-right">' . $morehtmlright . '</td>';
2994 }
2995 $return .= '</tr></table>' . "\n";
2996
2997 return $return;
2998}
2999
3024function print_barre_liste($title, $page, $file, $options = '', $sortfield = '', $sortorder = '', $morehtmlcenter = '', $num = -1, $totalnboflines = '', $picto = 'generic', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limit = -1, $selectlimitsuffix = 0, $hidenavigation = 0, $pagenavastextinput = 0, $morehtmlrightbeforearrow = '')
3025{
3026 global $conf, $langs;
3027
3028 $savlimit = $limit;
3029 $savtotalnboflines = $totalnboflines;
3030 if (is_numeric($totalnboflines)) {
3031 $totalnboflines = abs($totalnboflines);
3032 }
3033
3034 // Detect if there is a subtitle
3035 $subtitle = '';
3036 $tmparray = preg_split('/<br>/i', $title, 2);
3037 if (!empty($tmparray[1])) {
3038 $title = $tmparray[0];
3039 $subtitle = $tmparray[1];
3040 }
3041
3042 $page = (int) $page;
3043
3044 if ($picto == 'setup') {
3045 $picto = 'title_setup';
3046 }
3047 if (($conf->browser->name == 'ie') && $picto == 'generic') {
3048 $picto = 'title.gif';
3049 }
3050 if ($limit < 0) {
3051 $limit = $conf->liste_limit;
3052 }
3053
3054 if ($savlimit != 0 && (($num > $limit) || ($num == -1) || ($limit == 0))) {
3055 $nextpage = 1;
3056 } else {
3057 $nextpage = 0;
3058 }
3059 //print 'totalnboflines='.$totalnboflines.'-savlimit='.$savlimit.'-limit='.$limit.'-num='.$num.'-nextpage='.$nextpage.'-selectlimitsuffix='.$selectlimitsuffix.'-hidenavigation='.$hidenavigation;
3060
3061 print "\n";
3062 print "<!-- Begin print_barre_liste -->\n";
3063 print '<table class="centpercent notopnoleftnoright table-fiche-title' . ($morecss ? ' ' . $morecss : '') . '">';
3064 print '<tr class="toptitle">'; // margin bottom must be same than into load_fiche_tire
3065
3066 // Left
3067
3068 if ($picto && $title) {
3069 print '<td class="nobordernopadding widthpictotitle valignmiddle col-picto">';
3070 print img_picto('', $picto, 'class="valignmiddle pictotitle widthpictotitle"', $pictoisfullpath);
3071 print '</td>';
3072 }
3073
3074 print '<td class="nobordernopadding valignmiddle col-title">';
3075 print '<div class="titre inline-block nowrap">';
3076 print '<span class="inline-block valignmiddle print-barre-liste">' . $title . '</span>'; // $title may contains HTML like a combo list from page consumption.php, so we do not use dolPrintLabel here()
3077 if (!empty($title) && $savtotalnboflines >= 0 && (string) $savtotalnboflines != '') {
3078 if (is_numeric($totalnboflines) && (int) $totalnboflines > 0) {
3079 print '<span class="opacitymedium colorblack marginleftonly totalnboflines valignmiddle" title="' . $langs->trans("NbRecordQualified") . '">(' . $totalnboflines . ')</span>';
3080 } else {
3081 print '<span class="opacitymedium colorblack marginleftonly totalnboflines valignmiddle">(' . $totalnboflines . ')</span>';
3082 }
3083 }
3084 print '</div>';
3085 if (!empty($subtitle)) {
3086 print '<br><div class="subtitle inline-block hideonsmartphone">' . $subtitle . '</div>';
3087 }
3088 print '</td>';
3089
3090 // Center
3091 if ($morehtmlcenter && empty($conf->dol_optimize_smallscreen)) {
3092 print '<td class="nobordernopadding center valignmiddle col-center">' . $morehtmlcenter . '</td>';
3093 }
3094
3095 // Right
3096 print '<td class="nobordernopadding valignmiddle right col-right">';
3097 print '<input type="hidden" name="pageplusoneold" value="' . ((int) $page + 1) . '">';
3098 $query = [];
3099 parse_str($options, $query);
3100 if ($sortfield) {
3101 $query += ['sortfield' => $sortfield];
3102 }
3103 if ($sortorder) {
3104 $query += ['sortorder' => $sortorder];
3105 }
3106
3107 $options = '&' . http_build_query($query);
3108 if ($page) {
3109 $query = array_merge($query, ['page' => $page]);
3110 }
3111 // Show navigation bar
3112 $pagelist = '';
3113 if ($savlimit != 0 && ($page > 0 || $num > $limit)) {
3114 if ($totalnboflines) { // If we know total nb of lines
3115 // Define nb of extra page links before and after selected page + ... + first or last
3116 $maxnbofpage = (empty($conf->dol_optimize_smallscreen) ? 4 : 0);
3117
3118 if ($limit > 0) {
3119 $nbpages = ceil($totalnboflines / $limit);
3120 } else {
3121 $nbpages = 1;
3122 }
3123 $cpt = ($page - $maxnbofpage);
3124 if ($cpt < 0) {
3125 $cpt = 0;
3126 }
3127
3128 if ($cpt >= 1) {
3129 if (empty($pagenavastextinput)) {
3130 $query['page'] = 0;
3131 $pagelist .= '<li class="pagination"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">1</a></li>';
3132 if ($cpt > 2) {
3133 $pagelist .= '<li class="pagination"><span class="inactive">...</span></li>';
3134 } elseif ($cpt == 2) {
3135 $query['page'] = 0;
3136 $pagelist .= '<li class="pagination"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">2</a></li>';
3137 }
3138 }
3139 }
3140
3141 do {
3142 if ($pagenavastextinput) {
3143 if ($cpt == $page) {
3144 $pagelist .= '<li class="pagination pageplusone valignmiddle"><input type="text" class="' . ($totalnboflines > 100 ? 'width40' : 'width25') . ' center pageplusone heightofcombo" name="pageplusone" value="' . ($page + 1) . '"></li>';
3145 $pagelist .= '/';
3146 }
3147 } else {
3148 if ($cpt == $page) {
3149 $pagelist .= '<li class="pagination"><span class="active">' . ($page + 1) . '</span></li>';
3150 } else {
3151 $query['page'] = $cpt;
3152 $pagelist .= '<li class="pagination"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">' . ($cpt + 1) . '</a></li>';
3153 }
3154 }
3155 $cpt++;
3156 } while ($cpt < $nbpages && $cpt <= ($page + $maxnbofpage));
3157
3158 if (empty($pagenavastextinput)) {
3159 if ($cpt < $nbpages) {
3160 if ($cpt < $nbpages - 2) {
3161 $pagelist .= '<li class="pagination"><span class="inactive">...</span></li>';
3162 } elseif ($cpt == $nbpages - 2) {
3163 $query['page'] = ($nbpages - 2);
3164 $pagelist .= '<li class="pagination"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">' . ($nbpages - 1) . '</a></li>';
3165 }
3166 $query['page'] = ($nbpages - 1);
3167 $pagelist .= '<li class="pagination"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">' . $nbpages . '</a></li>';
3168 }
3169 } else {
3170 $query['page'] = ($nbpages - 1);
3171 $pagelist .= '<li class="pagination paginationlastpage"><a class="reposition" href="' . dolBuildUrl($file, $query) . '">' . $nbpages . '</a></li>';
3172 }
3173 } else {
3174 $pagelist .= '<li class="pagination"><span class="active">' . ($page + 1) . "</li>";
3175 }
3176 }
3177
3178 if ($savlimit || $morehtmlright || $morehtmlrightbeforearrow) {
3179 // Show the combolist to select number of record per page and the navigation arrows.
3180 print_fleche_navigation($page, $file, $options, $nextpage, $pagelist, $morehtmlright, $savlimit, $totalnboflines, $selectlimitsuffix, $morehtmlrightbeforearrow, $hidenavigation); // output the div and ul for previous/last completed with page numbers into $pagelist
3181 }
3182
3183 // js to autoselect page field on focus
3184 if ($pagenavastextinput) {
3185 print ajax_autoselect('.pageplusone');
3186 }
3187
3188 print '</td>';
3189 print '</tr>';
3190
3191 print "</table>\n";
3192
3193 // Center
3194 if ($morehtmlcenter && !empty($conf->dol_optimize_smallscreen)) {
3195 print '<div class="nobordernopadding marginbottomonly center valignmiddle col-center centpercent">' . $morehtmlcenter . '</div>';
3196 }
3197
3198 print "<!-- End title -->\n\n";
3199}
3200
3217function print_fleche_navigation($page, $file, $options = '', $nextpage = 0, $betweenarrows = '', $afterarrows = '', $limit = -1, $totalnboflines = 0, $selectlimitsuffix = '', $beforearrows = '', $hidenavigation = 0)
3218{
3219 global $conf, $langs;
3220
3221 print '<div class="pagination"><ul>';
3222 if ($beforearrows) {
3223 print '<li class="paginationbeforearrows">';
3224 print $beforearrows;
3225 print '</li>';
3226 }
3227
3228 if (empty($hidenavigation)) {
3229 if ((int) $limit > 0 && (empty($selectlimitsuffix) || !is_numeric($selectlimitsuffix))) {
3230 $pagesizechoices = '10:10,15:15,20:20,25:25,50:50,100:100,250:250,500:500,1000:1000';
3231 $pagesizechoices .= ',5000:5000';
3232 //$pagesizechoices .= ',10000:10000'; // Memory trouble on most browsers
3233 //$pagesizechoices .= ',20000:20000'; // Memory trouble on most browsers
3234 //$pagesizechoices .= ',0:'.$langs->trans("All"); // Not yet supported
3235 //$pagesizechoices .= ',2:2';
3236 if (getDolGlobalString('MAIN_PAGESIZE_CHOICES')) {
3237 $pagesizechoices = getDolGlobalString('MAIN_PAGESIZE_CHOICES');
3238 }
3239
3240 if (getDolGlobalString('MAIN_USE_HTML5_LIMIT_SELECTOR')) {
3241 print '<li class="pagination">';
3242 print '<input onfocus="this.value=null;" onchange="this.blur();" class="flat selectlimit nopadding maxwidth75 right pageplusone" id="limit" name="limit" list="limitlist" title="' . dol_escape_htmltag($langs->trans("MaxNbOfRecordPerPage")) . '" value="' . $limit . '">';
3243 print '<datalist id="limitlist">';
3244 } else {
3245 print '<li class="paginationcombolimit valignmiddle">';
3246 print '<select id="limit' . (is_numeric($selectlimitsuffix) ? '' : $selectlimitsuffix) . '" name="'.(is_numeric($selectlimitsuffix) ? 'limit' : $selectlimitsuffix).'" class="flat selectlimit nopadding maxwidth75 center' . (is_numeric($selectlimitsuffix) ? '' : ' ' . $selectlimitsuffix) . '" title="' . dol_escape_htmltag($langs->trans("MaxNbOfRecordPerPage")) . '">';
3247 }
3248 $tmpchoice = explode(',', $pagesizechoices);
3249 $tmpkey = $limit . ':' . $limit;
3250 if (!in_array($tmpkey, $tmpchoice)) {
3251 $tmpchoice[$tmpkey] = $tmpkey;
3252 }
3253 $tmpkey = $conf->liste_limit . ':' . $conf->liste_limit;
3254 if (!in_array($tmpkey, $tmpchoice)) {
3255 $tmpchoice[$tmpkey] = $tmpkey;
3256 }
3257 asort($tmpchoice, SORT_NUMERIC);
3258 foreach ($tmpchoice as $val) {
3259 $selected = '';
3260 $tmp = explode(':', $val);
3261 $key = $tmp[0];
3262 $val = $tmp[1];
3263 if ($key != '' && $val != '') {
3264 if ((int) $key == (int) $limit) {
3265 $selected = ' selected="selected"';
3266 }
3267 print '<option name="' . $key . '"' . $selected . '>' . dol_escape_htmltag($val) . '</option>' . "\n";
3268 }
3269 }
3270 if (getDolGlobalString('MAIN_USE_HTML5_LIMIT_SELECTOR')) {
3271 print '</datalist>';
3272 } else {
3273 print '</select>';
3274 print ajax_combobox("limit" . (is_numeric($selectlimitsuffix) ? '' : $selectlimitsuffix), array(), 0, 0, 'resolve', '-1', 'limit');
3275 //print ajax_combobox("limit");
3276 }
3277
3278 if ($conf->use_javascript_ajax) {
3279 print '<!-- JS CODE TO ENABLE select limit to launch submit of page -->
3280 <script>
3281 jQuery(document).ready(function () {
3282 jQuery(".selectlimit").change(function() {
3283 console.log("We change limit so we submit the form");
3284 $(this).parents(\'form:first\').submit();
3285 });
3286 });
3287 </script>
3288 ';
3289 }
3290 print '</li>';
3291 }
3292 if ($page > 0) {
3293 print '<li class="pagination paginationpage paginationpageleft"><a class="paginationprevious reposition" href="' . $file . '?page=' . ($page - 1) . $options . '"><i class="fa fa-chevron-left" title="' . dol_escape_htmltag($langs->trans("Previous")) . '"></i></a></li>';
3294 }
3295 if ($betweenarrows) {
3296 print '<!--<div class="betweenarrows nowraponall inline-block">-->';
3297 print $betweenarrows;
3298 print '<!--</div>-->';
3299 }
3300 if ($nextpage > 0) {
3301 print '<li class="pagination paginationpage paginationpageright"><a class="paginationnext reposition" href="' . $file . '?page=' . ($page + 1) . $options . '"><i class="fa fa-chevron-right" title="' . dol_escape_htmltag($langs->trans("Next")) . '"></i></a></li>';
3302 }
3303 if ($afterarrows) {
3304 print '<li class="paginationafterarrows">';
3305 print $afterarrows;
3306 print '</li>';
3307 }
3308 }
3309 print '</ul></div>' . "\n";
3310}
3311
3312
3319function showTotalAmount($amount)
3320{
3321 return '<span class="amount">'.$amount.'</span>';
3322}
3323
3336function showDimensionInBestUnit($dimension, $unit, $type, $outputlangs, $round = -1, $forceunitoutput = 'no', $use_short_label = 0)
3337{
3338 require_once DOL_DOCUMENT_ROOT . '/core/lib/product.lib.php';
3339
3340 if (($forceunitoutput == 'no' && $dimension < 1 / 10000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -6)) {
3341 $dimension *= 1000000;
3342 $unit -= 6;
3343 } elseif (($forceunitoutput == 'no' && $dimension < 1 / 10 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -3)) {
3344 $dimension *= 1000;
3345 $unit -= 3;
3346 } elseif (($forceunitoutput == 'no' && $dimension > 100000000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 6)) {
3347 $dimension /= 1000000;
3348 $unit += 6;
3349 } elseif (($forceunitoutput == 'no' && $dimension > 100000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 3)) {
3350 $dimension /= 1000;
3351 $unit += 3;
3352 }
3353 // Special case when we want output unit into pound or ounce
3354 /* TODO
3355 if ($unit < 90 && $type == 'weight' && is_numeric($forceunitoutput) && (($forceunitoutput == 98) || ($forceunitoutput == 99))
3356 {
3357 $dimension = // convert dimension from standard unit into ounce or pound
3358 $unit = $forceunitoutput;
3359 }
3360 if ($unit > 90 && $type == 'weight' && is_numeric($forceunitoutput) && $forceunitoutput < 90)
3361 {
3362 $dimension = // convert dimension from standard unit into ounce or pound
3363 $unit = $forceunitoutput;
3364 }*/
3365
3366 $ret = price($dimension, 0, $outputlangs, 0, 0, $round);
3367 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
3368 $ret .= ' ' . measuringUnitString(0, $type, $unit, $use_short_label, $outputlangs);
3369
3370 return $ret;
3371}
3372
3373
3374
3384function yn($yesno, $format = 1, $color = 0)
3385{
3386 global $langs;
3387
3388 $result = 'unknown';
3389 $classname = '';
3390 if ($yesno === true || (int) $yesno == 1 || (isset($yesno) && (strtolower($yesno) == 'yes' || strtolower($yesno) == 'true'))) { // To set to 'no' before the test because of the '== 0'
3391 $result = $langs->trans('yes');
3392 if ($format == 1 || $format == 3) {
3393 $result = $langs->trans("Yes");
3394 }
3395 if ($format == 2) {
3396 $result = '<input type="checkbox" value="1" checked disabled>';
3397 }
3398 if ($format == 3) {
3399 $result = '<input type="checkbox" value="1" checked disabled> ' . $result;
3400 }
3401 if ($format == 4 || !is_numeric($format)) {
3402 $result = img_picto(is_numeric($format) ? '' : $format, 'check');
3403 }
3404
3405 $classname = 'ok';
3406 } else {
3407 $result = $langs->trans("no");
3408 if ($format == 1 || $format == 3) {
3409 $result = $langs->trans("No");
3410 }
3411 if ($format == 2) {
3412 $result = '<input type="checkbox" value="0" disabled>';
3413 }
3414 if ($format == 3) {
3415 $result = '<input type="checkbox" value="0" disabled> ' . $result;
3416 }
3417 if ($format == 4 || !is_numeric($format)) {
3418 $result = img_picto(is_numeric($format) ? '' : $format, 'uncheck');
3419 }
3420
3421 if ($color == 2) {
3422 $classname = 'ok';
3423 } else {
3424 $classname = 'error';
3425 }
3426 }
3427 if ($color) {
3428 return '<span class="' . $classname . '">' . $result . '</span>';
3429 }
3430 return $result;
3431}
3432
3433
3447function setEventMessage($mesgs, $style = 'mesgs', $noduplicate = 0, $attop = 0)
3448{
3449 //dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING); This is not deprecated, it is used by setEventMessages function
3450 if (!is_array($mesgs)) {
3451 $mesgs = trim((string) $mesgs);
3452 // If mesgs is a not an empty string
3453 if ($mesgs) {
3454 if (!empty($noduplicate) && isset($_SESSION['dol_events'][$style]) && in_array($mesgs, $_SESSION['dol_events'][$style])) {
3455 return;
3456 }
3457 if ($attop) {
3458 array_unshift($_SESSION['dol_events'][$style], $mesgs);
3459 } else {
3460 $_SESSION['dol_events'][$style][] = $mesgs;
3461 }
3462 }
3463 } else {
3464 // If mesgs is an array
3465 foreach ($mesgs as $mesg) {
3466 $mesg = trim((string) $mesg);
3467 if ($mesg) {
3468 if (!empty($noduplicate) && isset($_SESSION['dol_events'][$style]) && in_array($mesg, $_SESSION['dol_events'][$style])) {
3469 return;
3470 }
3471 if ($attop) {
3472 array_unshift($_SESSION['dol_events'][$style], $mesgs);
3473 } else {
3474 $_SESSION['dol_events'][$style][] = $mesg;
3475 }
3476 }
3477 }
3478 }
3479}
3480
3495function setEventMessages($mesg, $mesgs, $style = 'mesgs', $messagekey = '', $noduplicate = 0, $attop = 0)
3496{
3497 if (empty($mesg) && empty($mesgs)) {
3498 dol_syslog("Try to add a message in stack, but value to add is empty message" . getCallerInfoString(), LOG_WARNING);
3499 } else {
3500 if ($messagekey) {
3501 // Complete message with a js link to set a cookie "DOLHIDEMESSAGE".$messagekey;
3502 // TODO
3503 $mesg .= '';
3504 }
3505 if (empty($messagekey) || empty($_COOKIE["DOLUSER_HIDEMESSAGE" . $messagekey])) {
3506 if (!in_array((string) $style, array('mesgs', 'warnings', 'errors'))) {
3507 dol_print_error(null, 'Bad parameter style=' . $style . ' for setEventMessages');
3508 }
3509 if (empty($mesgs)) {
3510 setEventMessage((string) $mesg, $style, $noduplicate, $attop);
3511 } else {
3512 if (!empty($mesg) && !in_array($mesg, $mesgs)) {
3513 setEventMessage($mesg, $style, $noduplicate, $attop); // Add message string if not already into array
3514 }
3515 setEventMessage($mesgs, $style, $noduplicate, $attop);
3516 }
3517 }
3518 }
3519}
3520
3530function dol_htmloutput_events($disabledoutputofmessages = 0)
3531{
3532 // Show mesgs
3533 if (isset($_SESSION['dol_events']['mesgs'])) {
3534 if (empty($disabledoutputofmessages)) {
3535 dol_htmloutput_mesg('', $_SESSION['dol_events']['mesgs']);
3536 }
3537 unset($_SESSION['dol_events']['mesgs']);
3538 }
3539 // Show errors
3540 if (isset($_SESSION['dol_events']['errors'])) {
3541 if (empty($disabledoutputofmessages)) {
3542 dol_htmloutput_mesg('', $_SESSION['dol_events']['errors'], 'error');
3543 }
3544 unset($_SESSION['dol_events']['errors']);
3545 }
3546
3547 // Show warnings
3548 if (isset($_SESSION['dol_events']['warnings'])) {
3549 if (empty($disabledoutputofmessages)) {
3550 dol_htmloutput_mesg('', $_SESSION['dol_events']['warnings'], 'warning');
3551 }
3552 unset($_SESSION['dol_events']['warnings']);
3553 }
3554}
3555
3570function get_htmloutput_mesg($mesgstring = '', $mesgarray = [], $style = 'ok', $keepembedded = 0)
3571{
3572 global $conf, $langs;
3573
3574 $ret = 0;
3575 $return = '';
3576 $out = '';
3577 $divstart = $divend = '';
3578
3579 // If inline message with no format, we add it.
3580 if ((empty($conf->use_javascript_ajax) || getDolGlobalString('MAIN_DISABLE_JQUERY_JNOTIFY') || defined('DISABLE_JQUERY_JNOTIFY') || $keepembedded) && !preg_match('/<div class=".*">/i', $out)) {
3581 $divstart = '<div class="' . $style . ' clearboth">';
3582 $divend = '</div>';
3583 }
3584
3585 if ((is_array($mesgarray) && count($mesgarray)) || $mesgstring) {
3586 $langs->load("errors");
3587 $out .= $divstart;
3588 if (is_array($mesgarray) && count($mesgarray)) {
3589 foreach ($mesgarray as $message) {
3590 $ret++;
3591 $out .= $langs->trans($message);
3592 if ($ret < count($mesgarray)) {
3593 $out .= "<br>\n";
3594 }
3595 }
3596 }
3597 if ($mesgstring) {
3598 $ret++;
3599 $out .= $langs->trans($mesgstring);
3600 }
3601 $out .= $divend;
3602 }
3603
3604 if ($out) {
3605 if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_DISABLE_JQUERY_JNOTIFY') && !defined('DISABLE_JQUERY_JNOTIFY') && empty($keepembedded)) {
3606 if ($style == "ok") {
3607 // For success messages (green), allow manual click to close immediately without fade
3608 $return = '<script nonce="' . getNonce() . '">
3609 /* jnotify(message, params) */
3610 $(document).ready(function() {
3611 $.jnotify(\'' . dol_escape_js($out) . '\', {
3612 delay: 3000,
3613 type: \'' . dol_escape_js($style) . '\',
3614 sticky: false,
3615 create: function($note) {
3616 $note.css("cursor", "pointer").click(function(e) {
3617 e.stopPropagation();
3618 $note.remove();
3619 });
3620 }
3621 });
3622 });
3623 </script>';
3624 } else {
3625 // For error and warning messages, close immediately on click without fade
3626 $return = '<script nonce="' . getNonce() . '">
3627 $(document).ready(function() {
3628 $.jnotify(\'' . dol_escape_js($out) . '\', {
3629 delay: 3000,
3630 type: \'' . dol_escape_js($style) . '\',
3631 sticky: true,
3632 create: function($note) {
3633 $note.find("a.jnotify-close").click(function(e) {
3634 e.stopPropagation();
3635 $note.remove();
3636 });
3637 }
3638 });
3639 });
3640 </script>';
3641 }
3642 } else {
3643 $return = $out;
3644 }
3645 }
3646
3647 return $return;
3648}
3649
3661function get_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0)
3662{
3663 return get_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
3664}
3665
3679function dol_htmloutput_mesg($mesgstring = '', $mesgarray = array(), $style = 'ok', $keepembedded = 0)
3680{
3681 if (empty($mesgstring) && (!is_array($mesgarray) || count($mesgarray) == 0)) {
3682 return;
3683 }
3684
3685 $iserror = 0;
3686 $iswarning = 0;
3687 if (is_array($mesgarray)) {
3688 foreach ($mesgarray as $val) {
3689 if ($val && preg_match('/class="error"/i', $val)) {
3690 $iserror++;
3691 break;
3692 }
3693 if ($val && preg_match('/class="warning"/i', $val)) {
3694 $iswarning++;
3695 break;
3696 }
3697 }
3698 } elseif ($mesgstring && preg_match('/class="error"/i', $mesgstring)) {
3699 $iserror++;
3700 } elseif ($mesgstring && preg_match('/class="warning"/i', $mesgstring)) {
3701 $iswarning++;
3702 }
3703 if ($style == 'error' || $style == 'errors') {
3704 $iserror++;
3705 }
3706 if ($style == 'warning' || $style == 'warnings') {
3707 $iswarning++;
3708 }
3709
3710 if ($iserror || $iswarning) {
3711 // Remove div from texts
3712 $mesgstring = preg_replace('/<\/div><div class="(error|warning)">/', '<br>', $mesgstring);
3713 $mesgstring = preg_replace('/<div class="(error|warning)">/', '', $mesgstring);
3714 $mesgstring = preg_replace('/<\/div>/', '', $mesgstring);
3715 // Remove div from texts array
3716 if (is_array($mesgarray)) {
3717 $newmesgarray = array();
3718 foreach ($mesgarray as $val) {
3719 if (is_string($val)) {
3720 $tmpmesgstring = preg_replace('/<\/div><div class="(error|warning)">/', '<br>', $val);
3721 $tmpmesgstring = preg_replace('/<div class="(error|warning)">/', '', $tmpmesgstring);
3722 $tmpmesgstring = preg_replace('/<\/div>/', '', $tmpmesgstring);
3723 $newmesgarray[] = $tmpmesgstring;
3724 } else {
3725 dol_syslog("Error call of dol_htmloutput_mesg with an array with a value that is not a string", LOG_WARNING);
3726 }
3727 }
3728 $mesgarray = $newmesgarray;
3729 }
3730 print get_htmloutput_mesg($mesgstring, $mesgarray, ($iserror ? 'error' : 'warning'), $keepembedded);
3731 } else {
3732 print get_htmloutput_mesg($mesgstring, $mesgarray, 'ok', $keepembedded);
3733 }
3734}
3735
3747function dol_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0)
3748{
3749 dol_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
3750}
3751
3752
3753
3762function picto_from_langcode($codelang, $moreatt = '', $notitlealt = 0)
3763{
3764 if (empty($codelang)) {
3765 return '';
3766 }
3767
3768 if ($codelang == 'auto') {
3769 return '<span class="fa fa-language"></span>';
3770 }
3771
3772 $langtocountryflag = array(
3773 'ar_AR' => '',
3774 'ca_ES' => 'catalonia',
3775 'da_DA' => 'dk',
3776 'fr_CA' => 'mq',
3777 'sv_SV' => 'se',
3778 'sw_SW' => 'unknown',
3779 'AQ' => 'unknown',
3780 'CW' => 'unknown',
3781 'IM' => 'unknown',
3782 'JE' => 'unknown',
3783 'MF' => 'unknown',
3784 'BL' => 'unknown',
3785 'SX' => 'unknown'
3786 );
3787
3788 if (isset($langtocountryflag[$codelang])) {
3789 $flagImage = $langtocountryflag[$codelang];
3790 } else {
3791 $tmparray = explode('_', $codelang);
3792 $flagImage = empty($tmparray[1]) ? $tmparray[0] : $tmparray[1];
3793 }
3794
3795 $morecss = '';
3796 $reg = array();
3797 if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
3798 $morecss = $reg[1];
3799 $moreatt = "";
3800 }
3801
3802 // return img_picto_common($codelang, 'flags/'.strtolower($flagImage).'.png', $moreatt, 0, $notitlealt);
3803 return '<span class="flag-sprite ' . strtolower($flagImage) . ($morecss ? ' ' . $morecss : '') . '"' . ($moreatt ? ' ' . $moreatt : '') . (!$notitlealt ? ' title="' . $codelang . '"' : '') . '></span>';
3804}
3805
3806
3818function printCommonFooter($zone = 'private')
3819{
3820 global $conf, $hookmanager, $user, $langs;
3821 global $action;
3822 global $micro_start_time;
3823
3824 if ($zone == 'private') {
3825 print "\n" . '<!-- Common footer for private page -->' . "\n";
3826 } else {
3827 print "\n" . '<!-- Common footer for public page -->' . "\n";
3828 }
3829
3830 // A div to store page_y POST parameter so we can read it using javascript
3831 print "\n<!-- A div to store page_y POST parameter -->\n";
3832 print '<div id="page_y" style="display: none;">' . (GETPOST('page_y') ? GETPOST('page_y') : '') . '</div>' . "\n";
3833
3834 $parameters = array('zone' => $zone);
3835 $tmpobject = null;
3836 // @phan-suppress-next-line PhanPluginConstantVariableNull
3837 $reshook = $hookmanager->executeHooks('printCommonFooter', $parameters, $tmpobject, $action); // Note that $action and $object may have been modified by some hooks
3838 if (empty($reshook)) {
3839 if (getDolGlobalString('MAIN_HTML_FOOTER')) {
3840 print getDolGlobalString('MAIN_HTML_FOOTER') . "\n";
3841 }
3842
3843 print "\n";
3844 if (!empty($conf->use_javascript_ajax)) {
3845 print "\n<!-- A script section to add menuhider handler on backoffice, manage focus and mandatory fields, tuning info, ... -->\n";
3846 print '<script>' . "\n";
3847 print 'jQuery(document).ready(function() {' . "\n";
3848
3849 if ($zone == 'private' && empty($conf->dol_use_jmobile)) {
3850 print "\n";
3851 print '/* JS CODE TO ENABLE to manage handler to switch left menu page (menuhider) */' . "\n";
3852 print 'jQuery("li.menuhider").click(function(event) {';
3853 print ' if (!$( "body" ).hasClass( "sidebar-collapse" )){ event.preventDefault(); }' . "\n";
3854 print ' console.log("We click on .menuhider");' . "\n";
3855 print ' $("body").toggleClass("sidebar-collapse")' . "\n";
3856 print '});' . "\n";
3857 }
3858
3859 // Management of focus and mandatory for fields
3860 if ($action == 'create' || $action == 'add' || $action == 'edit' || (empty($action) && (preg_match('/new\.php/', $_SERVER["PHP_SELF"]))) || ((empty($action) || $action == 'addline') && (preg_match('/card\.php/', $_SERVER["PHP_SELF"])))) {
3861 print '/* JS CODE TO ENABLE to manage focus and mandatory form fields */' . "\n";
3862 $relativepathstring = $_SERVER["PHP_SELF"];
3863 // Clean $relativepathstring
3864 if (constant('DOL_URL_ROOT')) {
3865 $relativepathstring = preg_replace('/^' . preg_quote(constant('DOL_URL_ROOT'), '/') . '/', '', $relativepathstring);
3866 }
3867 $relativepathstring = preg_replace('/^\//', '', $relativepathstring);
3868 $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
3869 //$tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
3870
3871 if (!empty($user->default_values[$relativepathstring]['focus'])) {
3872 foreach ($user->default_values[$relativepathstring]['focus'] as $defkey => $defval) {
3873 $qualified = 0;
3874 if ($defkey != '_noquery_') {
3875 $tmpqueryarraytohave = explode('&', $defkey);
3876 $foundintru = 0;
3877 foreach ($tmpqueryarraytohave as $tmpquerytohave) {
3878 $tmpquerytohaveparam = explode('=', $tmpquerytohave);
3879 //print "console.log('".$tmpquerytohaveparam[0]." ".$tmpquerytohaveparam[1]." ".GETPOST($tmpquerytohaveparam[0])."');";
3880 if (!GETPOSTISSET($tmpquerytohaveparam[0]) || ($tmpquerytohaveparam[1] != GETPOST($tmpquerytohaveparam[0]))) {
3881 $foundintru = 1;
3882 }
3883 }
3884 if (!$foundintru) {
3885 $qualified = 1;
3886 }
3887 //var_dump($defkey.'-'.$qualified);
3888 } else {
3889 $qualified = 1;
3890 }
3891
3892 if ($qualified) {
3893 print 'console.log("set the focus by executing jQuery(...).focus();")' . "\n";
3894 foreach ($defval as $paramkey => $paramval) {
3895 // Set focus on field
3896 print 'jQuery("input[name=\'' . $paramkey . '\']").focus();' . "\n";
3897 print 'jQuery("textarea[name=\'' . $paramkey . '\']").focus();' . "\n"; // TODO KO with ckeditor
3898 print 'jQuery("select[name=\'' . $paramkey . '\']").focus();' . "\n"; // Not really useful, but we keep it in case of.
3899 }
3900 }
3901 }
3902 }
3903 if (!empty($user->default_values[$relativepathstring]['mandatory'])) {
3904 foreach ($user->default_values[$relativepathstring]['mandatory'] as $defkey => $defval) {
3905 $qualified = 0;
3906 if ($defkey != '_noquery_') {
3907 $tmpqueryarraytohave = explode('&', $defkey);
3908 $foundintru = 0;
3909 foreach ($tmpqueryarraytohave as $tmpquerytohave) {
3910 $tmpquerytohaveparam = explode('=', $tmpquerytohave);
3911 //print "console.log('".$tmpquerytohaveparam[0]." ".$tmpquerytohaveparam[1]." ".GETPOST($tmpquerytohaveparam[0])."');";
3912 if (!GETPOSTISSET($tmpquerytohaveparam[0]) || ($tmpquerytohaveparam[1] != GETPOST($tmpquerytohaveparam[0]))) {
3913 $foundintru = 1;
3914 }
3915 }
3916 if (!$foundintru) {
3917 $qualified = 1;
3918 }
3919 //var_dump($defkey.'-'.$qualified);
3920 } else {
3921 $qualified = 1;
3922 }
3923
3924 if ($qualified) {
3925 print 'console.log("set the js code to manage fields that are set as mandatory");' . "\n";
3926
3927 foreach ($defval as $paramkey => $paramval) {
3928 // Solution 1: Add handler on submit to check if mandatory fields are empty
3929 print 'var form = $(\'[name="'.dol_escape_js($paramkey).'"]\').closest("form");'."\n";
3930 print "form.on('submit', function(event) {
3931 var submitter = \$(this).find(':submit:focus').get(0);
3932 var buttonName = submitter ? \$(submitter).attr('name') : 'save';
3933
3934 if (buttonName == 'cancel') {
3935 console.log('We click on cancel button so we accept submit with no need to check mandatory fields');
3936 return true;
3937 }
3938
3939 console.log('We did not click on cancel button but on something else, we check that field [name=".dol_escape_js($paramkey)."] is not empty');
3940
3941 var tmpvalue = jQuery('[name=\"".dol_escape_js($paramkey)."\"]').val();
3942 let tmptypefield = jQuery('[name=\"".dol_escape_js($paramkey)."\"]').prop('nodeName').toLowerCase(); // Get the tag name (div, section, footer...)
3943
3944 if (tmptypefield == 'textarea') {
3945 // We must instead check the content of ckeditor
3946 var tmpeditor = (typeof CKEDITOR !== 'undefined') ? CKEDITOR.instances['".dol_escape_js($paramkey)."'] : null;
3947 if (tmpeditor) {
3948 tmpvalue = tmpeditor.getData();
3949 console.log('For textarea tmpvalue is '+tmpvalue);
3950 }
3951 }
3952
3953 let tmpvalueisempty = false;
3954 if (tmpvalue === null || tmpvalue === undefined || tmpvalue === '' || tmpvalue === -1 || tmpvalue === '-1') {
3955 tmpvalueisempty = true;
3956 }
3957 if (tmpvalue === '0' && (tmptypefield == 'select' || tmptypefield == 'input')) {
3958 tmpvalueisempty = true;
3959 }
3960 if (tmpvalueisempty && buttonName !== 'cancel') {
3961 console.log('field has type '+tmptypefield+' and is empty, we cancel the submit');
3962 event.preventDefault(); // Stop submission of form to allow custom code to decide.
3963 event.stopPropagation(); // Stop other handlers.
3964
3965 alert('".dol_escape_js($langs->transnoentitiesnoconv("ErrorFieldRequired", $paramkey).' ('.$langs->transnoentitiesnoconv("CustomMandatoryFieldRule").')')."');
3966
3967 return false;
3968 }
3969 console.log('field has type '+tmptypefield+' and is defined to '+tmpvalue);
3970 return true;
3971 });
3972 \n";
3973
3974 // Solution 2: Add property 'required' on input
3975 // so browser will check value and try to focus on it when submitting the form.
3976 //print 'setTimeout(function() {'; // If we want to wait that ckeditor beuatifier has finished its job.
3977 //print 'jQuery("input[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";
3978 //print 'jQuery("textarea[id=\''.$paramkey.'\']").prop(\'required\',true);'."\n";
3979 //print 'jQuery("select[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";*/
3980 //print '// required on a select works only if key is "", so we add the required attributes but also we reset the key -1 or 0 to an empty string'."\n";
3981 //print 'jQuery("select[name=\''.$paramkey.'\'] option[value=\'-1\']").prop(\'value\', \'\');'."\n";
3982 //print 'jQuery("select[name=\''.$paramkey.'\'] option[value=\'0\']").prop(\'value\', \'\');'."\n";
3983 // Add 'field required' class on closest td for all input elements : input, textarea and select
3984 //print '}, 500);'; // 500 milliseconds delay
3985
3986 // Now set the class "fieldrequired"
3987 print 'jQuery(\':input[name="' . dol_escape_js($paramkey) . '"]\').closest("tr").find("td:first").addClass("fieldrequired");' . "\n";
3988 }
3989
3990 // If we submit using the cancel button, we remove the required attributes
3991 print 'jQuery("input[name=\'cancel\']").click(function() {
3992 console.log("We click on cancel button so removed all required attribute");
3993 jQuery("input, textarea, select").each(function(){this.removeAttribute(\'required\');});
3994 });' . "\n";
3995 }
3996 }
3997 }
3998 }
3999
4000 print '});' . "\n";
4001
4002 // End of tuning
4003 if (!empty($_SERVER['MAIN_SHOW_TUNING_INFO']) || getDolGlobalString('MAIN_SHOW_TUNING_INFO')) {
4004 print "\n";
4005 print "/* JS CODE TO ENABLE to add memory info */\n";
4006 print 'window.console && console.log("';
4007 if (getDolGlobalString('MEMCACHED_SERVER')) {
4008 print 'MEMCACHED_SERVER=' . getDolGlobalString('MEMCACHED_SERVER') . ' - ';
4009 }
4010 print 'MAIN_OPTIMIZE_SPEED=' . getDolGlobalString('MAIN_OPTIMIZE_SPEED', 'off');
4011 if (!empty($micro_start_time)) { // Works only if MAIN_SHOW_TUNING_INFO is defined at $_SERVER level. Not in global variable.
4012 $micro_end_time = microtime(true);
4013 print ' - Build time: ' . ceil(1000 * ($micro_end_time - $micro_start_time)) . ' ms';
4014 }
4015
4016 if (function_exists("memory_get_usage")) {
4017 print ' - Mem: ' . memory_get_usage(); // Do not use true here, it seems it takes the peak amount
4018 }
4019 if (function_exists("memory_get_peak_usage")) {
4020 print ' - Real mem peak: ' . memory_get_peak_usage(true);
4021 }
4022 if (function_exists("zend_loader_file_encoded")) {
4023 print ' - Zend encoded file: ' . (zend_loader_file_encoded() ? 'yes' : 'no');
4024 }
4025 print '");' . "\n";
4026 }
4027
4028 print "\n" . '</script>' . "\n";
4029
4030 // Google Analytics
4031 // TODO Remove this, can be replaced with the hook printCommonFooter
4032 if (isModEnabled('google') && getDolGlobalString('MAIN_GOOGLE_AN_ID')) {
4033 $tmptagarray = explode(',', getDolGlobalString('MAIN_GOOGLE_AN_ID'));
4034 foreach ($tmptagarray as $tmptag) {
4035 print "\n";
4036 print "<!-- JS CODE TO ENABLE for google analtics tag -->\n";
4037 print '
4038 <!-- Global site tag (gtag.js) - Google Analytics -->
4039 <script nonce="' . getNonce() . '" async src="https://www.googletagmanager.com/gtag/js?id=' . trim($tmptag) . '"></script>
4040 <script>
4041 window.dataLayer = window.dataLayer || [];
4042 function gtag(){dataLayer.push(arguments);}
4043 gtag(\'js\', new Date());
4044
4045 gtag(\'config\', \'' . trim($tmptag) . '\');
4046 </script>';
4047 print "\n";
4048 }
4049 }
4050 }
4051
4052 // Add Xdebug coverage of code
4053 if (defined('XDEBUGCOVERAGE')) {
4054 print_r(xdebug_get_code_coverage());
4055 }
4056
4057 // Output string from hooks
4058 if (!empty($hookmanager->resPrint)) {
4059 print $hookmanager->resPrint;
4060 }
4061
4062 // Add DebugBar data
4063 if ($user->hasRight('debugbar', 'read')) {
4064 global $debugbar;
4065 if ($debugbar instanceof DebugBar\DebugBar) {
4066 if (isset($debugbar['time'])) {
4067 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
4068 $debugbar['time']->stopMeasure('pageaftermaster');
4069 }
4070 print '<!-- Output debugbar data -->' . "\n";
4071 $renderer = $debugbar->getJavascriptRenderer();
4072 print $renderer->render();
4073 }
4074 } elseif (count($conf->logbuffer)) { // If there is some logs in buffer to show
4075 print "\n";
4076 print "<!-- Start of log output\n";
4077 //print '<div class="hidden">'."\n";
4078 foreach ($conf->logbuffer as $logline) {
4079 print $logline . "<br>\n";
4080 }
4081 //print '</div>'."\n";
4082 print "End of log output -->\n";
4083 }
4084 }
4085}
4086
4087
4094function dol_set_focus($selector)
4095{
4096 print "\n" . '<!-- Set focus onto a specific field -->' . "\n";
4097 print '<script nonce="' . getNonce() . '">jQuery(document).ready(function() { console.log("Force focus by dol_set_focus"); jQuery(\'' . dol_escape_js($selector) . '\').focus(); });</script>' . "\n";
4098}
4099
4100
4101
4109function showSimpleHTMLTable($outputlangs, $object)
4110{
4111 global $conf;
4112
4113 $discountIsAvailable = false;
4114 $orderPositionHasNoPrice = false;
4115
4116 if (!property_exists($object->lines[0], "remise_percent") ||
4117 !property_exists($object->lines[0], "fk_unit") ||
4118 !property_exists($object->lines[0], "multicurrency_total_ttc") ||
4119 !property_exists($object->lines[0], "description") ||
4120 !property_exists($object->lines[0], "qty")) {
4121 return"";
4122 }
4123
4124 foreach ($object->lines as $order_position) {
4125 if (!property_exists($order_position, "price")) {
4126 $orderPositionHasNoPrice = true;
4127 break;
4128 }
4129
4130 if (!empty($order_position->remise_percent)) {
4131 $discountIsAvailable = true;
4132 break;
4133 }
4134 };
4135
4136 if ($orderPositionHasNoPrice) {
4137 return "";
4138 }
4139
4140 $discountHeader = $discountIsAvailable ? '<th style="width:120px">'.$outputlangs->trans("Discount").'</th>' : '';
4141
4142 $table = '<table border="0" cellpadding="1" cellspacing="1">';
4143 $table .= '
4144 <thead>
4145 <tr>
4146 <th style="width:50px; text-align:left">#</th>
4147 <th style="text-align:left">'.$outputlangs->trans("Description").'</th>
4148 <th style="width:120px; text-align:right;">'.$outputlangs->trans("Price").'</th>
4149 <th style="width:100px; text-align:right;">'.$outputlangs->trans("Quantity").'</th>
4150 <th style="width:120px; text-align:right;">'.$outputlangs->trans("Unit").'</th>'.
4151 $discountHeader.'
4152 <th style="width:120px; text-align:right;">'.$outputlangs->trans("Sum").'</th>
4153 </tr>
4154 </thead>
4155 <tbody>';
4156
4157 foreach ($object->lines as $index => $order_position) {
4158 $position = $index + 1;
4159 $price = price($order_position->price, 0, $outputlangs, 0, -1, -1, $conf->currency);
4160 $unit = measuringUnitString($order_position->fk_unit, '', null, 1);
4161 $total = price($order_position->multicurrency_total_ttc, 0, $outputlangs, 0, -1, -1, $conf->currency);
4162 $discount = $discountIsAvailable ? '<td style="text-align:center">'.$order_position->remise_percent.'%</td>' : "";
4163
4164 $table .= '
4165 <tr>
4166 <td>'.$position.'</td>
4167 <td>'.$order_position->description.'</td>
4168 <td style="text-align:right">'.$price.'</td>
4169 <td style="text-align:right">'.$order_position->qty.'</td>
4170 <td style="text-align:right">'.$unit.'</td>'.
4171 $discount.'
4172 <td style="text-align:right">'.$total.'</td>
4173 </tr>';
4174 }
4175 $table .= '</tbody></table>';
4176
4177 return $table;
4178}
4179
4186function showDirectDownloadLink($object)
4187{
4188 global $langs;
4189
4190 $out = '';
4191 $url = $object->getLastMainDocLink($object->element);
4192
4193 $out .= img_picto($langs->trans("PublicDownloadLinkDesc"), 'globe') . ' <span class="opacitymedium">' . $langs->trans("DirectDownloadLink") . '</span><br>';
4194 if ($url) {
4195 $out .= '<div class="urllink"><input type="text" id="directdownloadlink" class="quatrevingtpercent" value="' . $url . '"></div>';
4196 $out .= ajax_autoselect("directdownloadlink", '');
4197 } else {
4198 $out .= '<div class="urllink">' . $langs->trans("FileNotShared") . '</div>';
4199 }
4200
4201 return $out;
4202}
4203
4204
4205
4215function getAdvancedPreviewUrl($modulepart, $relativepath, $alldata = 0, $param = '')
4216{
4217 global $conf, $langs;
4218
4219 if (empty($conf->use_javascript_ajax)) {
4220 return '';
4221 }
4222
4223 $isAllowedForPreview = dolIsAllowedForPreview($relativepath);
4224
4225 if ($alldata == 1) {
4226 if ($isAllowedForPreview) {
4227 return array('target' => '_blank', 'css' => 'documentpreview', 'url' => DOL_URL_ROOT . '/document.php?modulepart=' . urlencode($modulepart) . '&attachment=0&file=' . urlencode($relativepath) . ($param ? '&' . $param : ''), 'mime' => dol_mimetype($relativepath));
4228 } else {
4229 return array();
4230 }
4231 }
4232
4233 // old behavior, return a string
4234 if ($isAllowedForPreview) {
4235 $tmpurl = DOL_URL_ROOT . '/document.php?modulepart=' . urlencode($modulepart) . '&attachment=0&file=' . urlencode($relativepath) . ($param ? '&' . $param : '');
4236 $title = $langs->transnoentities("Preview");
4237 //$title = '%27-alert(document.domain)-%27'; // An example of js injection into a corrupted title string, that should be blocked by the dol_escape_uri().
4238 //$tmpurl = 'file='.urlencode("'-alert(document.domain)-'_small.jpg"); // An example of tmpurl that should be blocked by the dol_escape_uri()
4239
4240 // We need to do a dol_escape_uri() on the full string after the javascript: because such parts are the URI and when we click on such links, a RFC3986 decode is done,
4241 // by the browser, converting the %27 (like when having param file=abc%27def), or when having a corrupted title), into a ', BEFORE interpreting the content that can be a js code.
4242 // Using the dol_escape_uri guarantee that we encode for URI so decode retrieve original expected value.
4243 return 'javascript:' . dol_escape_uri('document_preview(\'' . dol_escape_js($tmpurl) . '\', \'' . dol_escape_js(dol_mimetype($relativepath)) . '\', \'' . dol_escape_js($title) . '\')');
4244 } else {
4245 return '';
4246 }
4247}
4248
4249
4259function ajax_autoselect($htmlname, $addlink = '', $textonlink = 'Link')
4260{
4261 global $langs;
4262 $out = '<script nonce="' . getNonce() . '">
4263 jQuery(document).ready(function () {
4264 jQuery("' . ((strpos($htmlname, '.') === 0 ? '' : '#') . $htmlname) . '").click(function() { jQuery(this).select(); } );
4265 });
4266 </script>';
4267 if ($addlink) {
4268 if ($textonlink === 'image') {
4269 $out .= ' <a href="' . $addlink . '" target="_blank" rel="noopener noreferrer">' . img_picto('', 'globe') . '</a>';
4270 } else {
4271 $out .= ' <a href="' . $addlink . '" target="_blank" rel="noopener noreferrer">' . $langs->trans("Link") . '</a>';
4272 }
4273 }
4274 return $out;
4275}
4276
4277
4278
4291function dolGetBadge($label, $html = '', $type = 'primary', $mode = '', $url = '', $params = array())
4292{
4293 $csstouse = 'badge';
4294 $csstouse .= (!empty($mode) ? ' badge-' . $mode : '');
4295 $csstouse .= (!empty($type) ? ' badge-' . $type : '');
4296 $csstouse .= (empty($params['css']) ? '' : ' ' . $params['css']);
4297
4298 $attr = array(
4299 'class' => $csstouse
4300 );
4301
4302 if (empty($html)) {
4303 $html = $label;
4304 }
4305
4306 if (!empty($url)) {
4307 $attr['href'] = $url;
4308 }
4309
4310 if ($mode === 'dot') {
4311 $attr['class'] .= ' classfortooltip';
4312 $attr['title'] = $html;
4313 $attr['aria-label'] = $label;
4314 $html = '';
4315 }
4316
4317 // Override attr
4318 if (!empty($params['attr']) && is_array($params['attr'])) {
4319 foreach ($params['attr'] as $key => $value) {
4320 if ($key == 'class') {
4321 $attr['class'] .= ' ' . $value;
4322 } elseif ($key == 'classOverride') {
4323 $attr['class'] = $value;
4324 } else {
4325 $attr[$key] = $value;
4326 }
4327 }
4328 }
4329
4330 // TODO: add hook
4331
4332 // escape all attribute
4333 $attr = array_map('dolPrintHTMLForAttribute', $attr);
4334
4335 $TCompiledAttr = array();
4336 foreach ($attr as $key => $value) {
4337 $TCompiledAttr[] = $key . '="' . $value . '"';
4338 }
4339
4340 $compiledAttributes = !empty($TCompiledAttr) ? implode(' ', $TCompiledAttr) : '';
4341
4342 $tag = !empty($url) ? 'a' : 'span';
4343
4344 return '<' . $tag . ' ' . $compiledAttributes . '>' . $html . '</' . $tag . '>';
4345}
4346
4347
4361function dolGetStatus($statusLabel = '', $statusLabelShort = '', $html = '', $statusType = 'status0', $displayMode = 0, $url = '', $params = array())
4362{
4363 global $conf;
4364
4365 $return = '';
4366 $dolGetBadgeParams = array();
4367
4368 if (!empty($params['badgeParams'])) {
4369 $dolGetBadgeParams = $params['badgeParams'];
4370 }
4371
4372 // TODO : add a hook
4373 if ($displayMode == 0) {
4374 $return = !empty($html) ? $html : (empty($conf->dol_optimize_smallscreen) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort));
4375 } elseif ($displayMode == 1) {
4376 $return = !empty($html) ? $html : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort);
4377 } elseif (getDolGlobalString('MAIN_STATUS_USES_IMAGES')) {
4378 // Use status with images (for backward compatibility)
4379 $return = '';
4380 $htmlLabel = (in_array($displayMode, array(1, 2, 5)) ? '<span class="hideonsmartphone">' : '') . (!empty($html) ? $html : $statusLabel) . (in_array($displayMode, array(1, 2, 5)) ? '</span>' : '');
4381 $htmlLabelShort = (in_array($displayMode, array(1, 2, 5)) ? '<span class="hideonsmartphone">' : '') . (!empty($html) ? $html : (!empty($statusLabelShort) ? $statusLabelShort : $statusLabel)) . (in_array($displayMode, array(1, 2, 5)) ? '</span>' : '');
4382
4383 // For small screen, we always use the short label instead of long label.
4384 if (!empty($conf->dol_optimize_smallscreen)) {
4385 if ($displayMode == 0) {
4386 $displayMode = 1;
4387 } elseif ($displayMode == 4) {
4388 $displayMode = 2;
4389 } elseif ($displayMode == 6) {
4390 $displayMode = 5;
4391 }
4392 }
4393
4394 // For backward compatibility. Image's filename are still in French, so we use this array to convert
4395 $statusImg = array(
4396 'status0' => 'statut0',
4397 'status1' => 'statut1',
4398 'status2' => 'statut2',
4399 'status3' => 'statut3',
4400 'status4' => 'statut4',
4401 'status5' => 'statut5',
4402 'status6' => 'statut6',
4403 'status7' => 'statut7',
4404 'status8' => 'statut8',
4405 'status9' => 'statut9'
4406 );
4407
4408 if (!empty($statusImg[$statusType])) {
4409 $htmlImg = img_picto($statusLabel, $statusImg[$statusType]);
4410 } else {
4411 $htmlImg = img_picto($statusLabel, $statusType);
4412 }
4413
4414 if ($displayMode === 2) {
4415 $return = $htmlImg . ' ' . $htmlLabelShort;
4416 } elseif ($displayMode === 3) {
4417 $return = $htmlImg;
4418 } elseif ($displayMode === 4) {
4419 $return = $htmlImg . ' ' . $htmlLabel;
4420 } elseif ($displayMode === 5) {
4421 $return = $htmlLabelShort . ' ' . $htmlImg;
4422 } else { // $displayMode >= 6
4423 $return = $htmlLabel . ' ' . $htmlImg;
4424 }
4425 } elseif (!empty($displayMode)) {
4426 // Use new badge (MAIN_STATUS_USES_IMAGES already handled by the previous branch)
4427 $statusLabelShort = (empty($statusLabelShort) ? $statusLabel : $statusLabelShort);
4428
4429 $dolGetBadgeParams['attr']['class'] = 'badge-status';
4430 if (empty($dolGetBadgeParams['attr']['title'])) {
4431 $dolGetBadgeParams['attr']['title'] = empty($params['tooltip']) ? $statusLabel : ($params['tooltip'] != 'no' ? $params['tooltip'] : '');
4432 } else { // If a title was forced from $params['badgeParams']['attr']['title'], we set the class to get it as a tooltip.
4433 $dolGetBadgeParams['attr']['class'] .= ' classfortooltip';
4434 // And if we use tooltip, we can output title in HTML @phan-suppress-next-line PhanTypeInvalidDimOffset
4435 $dolGetBadgeParams['attr']['title'] = dol_htmlentitiesbr((string) $dolGetBadgeParams['attr']['title'], 1);
4436 }
4437
4438 if ($displayMode == 3) {
4439 $return = dolGetBadge((empty($conf->dol_optimize_smallscreen) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort)), '', $statusType, 'dot', $url, $dolGetBadgeParams);
4440 } elseif ($displayMode === 5) {
4441 $return = dolGetBadge($statusLabelShort, $html, $statusType, '', $url, $dolGetBadgeParams);
4442 } else {
4443 $return = dolGetBadge(((empty($conf->dol_optimize_smallscreen) && $displayMode != 2) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort)), $html, $statusType, '', $url, $dolGetBadgeParams);
4444 }
4445 }
4446
4447 return $return;
4448}
4449
4450
4487function dolGetButtonAction($label, $text = '', $actionType = 'default', $url = '', $id = '', $userRight = 1, $params = array())
4488{
4489 global $hookmanager, $action, $object, $langs;
4490
4491 // If $url is an array, we must build a dropdown button or recursively iterate over each value
4492 if (is_array($url)) {
4493 // Loop on $url array to remove entries of disabled modules
4494 foreach ($url as $key => $subbutton) {
4495 if (isset($subbutton['enabled']) && empty($subbutton['enabled'])) {
4496 unset($url[$key]);
4497 }
4498 }
4499
4500 $out = '';
4501
4502 if (array_key_exists('areDropdownButtons', $params) && $params["areDropdownButtons"] === false) { // @phan-suppress-current-line PhanTypeInvalidDimOffset
4503 foreach ($url as $button) {
4504 if (!empty($button['lang'])) {
4505 $langs->load($button['lang']);
4506 }
4507 $label = $langs->trans($button['label']);
4508 $text = $button['text'] ?? '';
4509 $actionType = $button['actionType'] ?? '';
4510 $tmpUrl = DOL_URL_ROOT . $button['url'] . (empty($params['backtopage']) ? '' : '&amp;backtopage=' . urlencode($params['backtopage']));
4511 $id = $button['id'] ?? '';
4512 $userRight = $button['perm'] ?? 1;
4513 $button['params'] = $button['params'] ?? []; // @phan-suppress-current-line PhanPluginDuplicateExpressionAssignmentOperation
4514
4515 $out .= dolGetButtonAction($label, $text, $actionType, $tmpUrl, $id, $userRight, $button['params']);
4516 }
4517 return $out;
4518 }
4519
4520 if (count($url) > 1 || !empty($params["forceDropdownButtons"])) {
4521 $out .= '<div class="dropdown inline-block dropdown-holder">';
4522 $out .= '<a style="margin-right: auto;" class="dropdown-toggle classfortooltip butAction' . ($userRight ? '' : 'Refused') . '" title="' . dol_escape_htmltag($label) . '" data-toggle="dropdown">' . ($text ? $text : $label) . '</a>';
4523 $out .= '<div class="dropdown-content">';
4524 foreach ($url as $subbutton) {
4525 if (!empty($subbutton['lang'])) {
4526 $langs->load($subbutton['lang']);
4527 }
4528
4529 if (!empty($subbutton['urlraw'])) {
4530 $tmpurl = $subbutton['urlraw']; // Use raw url, no url completion, use only what developer send
4531 } else {
4532 $tmpurl = !empty($subbutton['urlroot']) ? $subbutton['urlroot'] : $subbutton['url'];
4533 $tmpurl = dolCompletUrlForDropdownButton($tmpurl, $params, empty($subbutton['urlroot']));
4534 }
4535
4536 $subbuttonparam = array();
4537 if (!empty($subbutton['attr'])) {
4538 $subbuttonparam['attr'] = $subbutton['attr'];
4539 }
4540 $subbuttonparam['isDropDown'] = (empty($params['isDropDown']) ? ($subbutton['isDropDown'] ?? false) : $params['isDropDown']);
4541
4542 $out .= dolGetButtonAction($subbutton['text'] ?? '', $langs->trans($subbutton['label']), 'default', $tmpurl, $subbutton['id'] ?? '', $subbutton['perm'], $subbuttonparam);
4543 }
4544 $out .= "</div>";
4545 $out .= "</div>";
4546 } else {
4547 foreach ($url as $subbutton) { // Should loop on 1 record only
4548 if (!empty($subbutton['lang'])) {
4549 $langs->load($subbutton['lang']);
4550 }
4551
4552 if (!empty($subbutton['urlraw'])) {
4553 $tmpurl = $subbutton['urlraw']; // Use raw url, no url completion, use only what developer send
4554 } else {
4555 $tmpurl = !empty($subbutton['urlroot']) ? $subbutton['urlroot'] : $subbutton['url'];
4556 $tmpurl = dolCompletUrlForDropdownButton($tmpurl, $params, empty($subbutton['urlroot']));
4557 }
4558
4559 $label = $langs->trans($subbutton['label']);
4560 $text = $subbutton['text'] ?? '';
4561 if (empty($text)) {
4562 $text = $label;
4563 $label = '';
4564 }
4565
4566 $out .= dolGetButtonAction($label, $text, 'default', $tmpurl, '', $subbutton['perm'], $params);
4567 }
4568 }
4569
4570 return $out;
4571 }
4572
4573 // Here, $url is a simple link
4574 if (!empty($params['isDropdown']) || !empty($params['isDropDown'])) { // Use the dropdown-item style (not for action button)
4575 $class = "dropdown-item";
4576 } else {
4577 $class = 'butAction';
4578 if ($actionType == 'edit') {
4579 $class = 'butAction butActionEdit';
4580 } elseif ($actionType == 'email') {
4581 $class = 'butAction butActionEmail';
4582 } elseif ($actionType == 'clone') {
4583 $class = 'butAction butActionClone';
4584 } elseif ($actionType == 'cancel') {
4585 $class = 'butAction butActionDelete';
4586 } elseif ($actionType == 'danger' || $actionType == 'delete') {
4587 $class = 'butAction butActionDelete';
4588 if (!empty($url) && strpos($url, 'token=') === false) {
4589 $url .= '&token=' . newToken();
4590 }
4591 }
4592 }
4593 $attr = array(
4594 'class' => $class,
4595 'href' => empty($url) ? '' : $url,
4596 'title' => $label
4597 );
4598
4599 if (empty($text)) {
4600 $text = $label;
4601 $attr['title'] = ''; // if html not set, using label on title is redundant
4602 } else {
4603 $attr['title'] = $label;
4604 $attr['aria-label'] = $label;
4605 }
4606
4607 if (empty($userRight) || $userRight < 0) {
4608 $attr['class'] = 'butActionRefused';
4609 $attr['href'] = '';
4610 $attr['title'] = (($label && $text && $label != $text) ? $label : '');
4611 $attr['title'] = ($attr['title'] ? $attr['title'] . (empty($userRight) ? '<br>' : '') : '');
4612 $attr['title'] .= ((empty($userRight) && empty($label)) ? $langs->trans('NotEnoughPermissions') : '');
4613 }
4614
4615 if (!empty($id)) {
4616 $attr['id'] = $id;
4617 }
4618
4619 // Override attr
4620 if (!empty($params['attr']) && is_array($params['attr'])) {
4621 foreach ($params['attr'] as $key => $value) {
4622 if ($key == 'class') {
4623 $attr['class'] .= ' ' . $value;
4624 } elseif ($key == 'classOverride') {
4625 $attr['class'] = $value;
4626 } else {
4627 $attr[$key] = $value;
4628 }
4629 }
4630 }
4631
4632 // automatic add tooltip when title is detected
4633 if (!empty($attr['title']) && !empty($attr['class']) && strpos($attr['class'], 'classfortooltip') === false) {
4634 $attr['class'] .= ' classfortooltip';
4635 }
4636
4637 // Js Confirm button
4638 if ($userRight && !empty($params['confirm'])) {
4639 if (!is_array($params['confirm'])) {
4640 $params['confirm'] = array();
4641 }
4642
4643 if (empty($params['confirm']['url'])) {
4644 $params['confirm']['url'] = $url . (strpos($url, '?') > 0 ? '&' : '?') . 'confirm=yes';
4645 }
4646
4647 // for js disabled compatibility set $url as call to confirm action and $params['confirm']['url'] to confirmed action
4648 $attr['data-confirm-url'] = $params['confirm']['url'];
4649 $attr['data-confirm-title'] = !empty($params['confirm']['title']) ? $params['confirm']['title'] : $langs->trans('ConfirmBtnCommonTitle', $label);
4650 $attr['data-confirm-content'] = !empty($params['confirm']['content']) ? $params['confirm']['content'] : $langs->trans('ConfirmBtnCommonContent', $label);
4651 $attr['data-confirm-content'] = preg_replace("/\r|\n/", "", $attr['data-confirm-content']);
4652 $attr['data-confirm-action-btn-label'] = !empty($params['confirm']['action-btn-label']) ? $params['confirm']['action-btn-label'] : $langs->trans('Confirm');
4653 $attr['data-confirm-cancel-btn-label'] = !empty($params['confirm']['cancel-btn-label']) ? $params['confirm']['cancel-btn-label'] : $langs->trans('CloseDialog');
4654 $attr['data-confirm-modal'] = !empty($params['confirm']['modal']) ? $params['confirm']['modal'] : true;
4655
4656 $attr['class'] .= ' butActionConfirm';
4657 }
4658
4659 if (isset($attr['href']) && empty($attr['href'])) {
4660 unset($attr['href']);
4661 }
4662
4663 // TODO replace this $TCompiledAttr generation block by commonHtmlAttributeBuilder like line below
4664 // $TCompiledAttr = commonHtmlAttributeBuilder($attr, $params['use_unsecured_unescapedattr'] ?? []);
4665 $TCompiledAttr = array();
4666 foreach ($attr as $key => $value) {
4667 if (!empty($params['use_unsecured_unescapedattr']) && is_array($params['use_unsecured_unescapedattr']) && in_array($key, $params['use_unsecured_unescapedattr'])) {
4668 // Deprecated, forbidden.
4669 $value = dol_htmlentities($value, ENT_QUOTES | ENT_SUBSTITUTE);
4670 } elseif (in_array($key, ['href', 'data-confirm-url'])) {
4671 $value = dolPrintHTMLForAttributeUrl($value);
4672 } else {
4673 $value = dolPrintHTMLForAttribute($value);
4674 }
4675
4676 $TCompiledAttr[] = $key . '="' . $value . '"'; // $value has been escaped by the dolPrintHTMLForAttribute... just before
4677 }
4678 $compiledAttributes = empty($TCompiledAttr) ? '' : implode(' ', $TCompiledAttr);
4679
4680 $tag = !empty($attr['href']) ? 'a' : 'span';
4681
4682 $parameters = array(
4683 'TCompiledAttr' => $TCompiledAttr, // array
4684 'compiledAttributes' => $compiledAttributes, // string
4685 'attr' => $attr,
4686 'tag' => $tag,
4687 'label' => $label,
4688 'html' => $text,
4689 'actionType' => $actionType,
4690 'url' => $url,
4691 'id' => $id,
4692 'userRight' => $userRight,
4693 'params' => $params
4694 );
4695
4696 $reshook = $hookmanager->executeHooks('dolGetButtonAction', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
4697 if ($reshook < 0) {
4698 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
4699 }
4700
4701 if (empty($reshook)) {
4702 if (dol_textishtml($text)) { // If content already HTML encoded
4703 return '<' . $tag . ' ' . $compiledAttributes . '><span class="textbutton">' . $text . '</span></' . $tag . '>';
4704 } else {
4705 return '<' . $tag . ' ' . $compiledAttributes . '><span class="textbutton">' . dol_escape_htmltag($text) . '</span></' . $tag . '>';
4706 }
4707 } else {
4708 return $hookmanager->resPrint;
4709 }
4710}
4711
4742function commonHtmlAttributeBuilder($attr, array $unescapedAttr = [])
4743{
4744 $TCompiledAttr = array();
4745 if (empty($attr)) {
4746 return [];
4747 }
4748
4749 foreach ($attr as $key => $value) {
4750 // special boolean attributes case
4751 if (in_array($key, getListOfHtmlBooleanAttributes())) {
4752 if ($value) {
4753 $TCompiledAttr[$key] = $key;
4754 }
4755 continue;
4756 }
4757
4758 if (!empty($unescapedAttr) && in_array($key, $unescapedAttr)) {
4759 // Not recommended
4760 $value = dol_htmlentities((string) $value, ENT_QUOTES | ENT_SUBSTITUTE);
4761 } elseif (in_array($key, ['href', 'data-confirm-url'])) {
4762 $value = dolPrintHTMLForAttributeUrl((string) $value);
4763 } else {
4764 $value = dolPrintHTMLForAttribute((string) $value);
4765 }
4766
4767 $TCompiledAttr[$key] = $key . '="' . $value . '"'; // $value has been escaped by the dolPrintHTMLForAttribute... just before
4768 }
4769
4770 return $TCompiledAttr;
4771}
4772
4788{
4789 return [
4790 // Input / Form
4791 'checked',
4792 'disabled',
4793 'readonly',
4794 'required',
4795 'autofocus',
4796 'multiple',
4797
4798 // Option
4799 'selected',
4800
4801 // Form / General
4802 'novalidate',
4803 'formnovalidate',
4804
4805 // Media
4806 'autoplay',
4807 'controls',
4808 'loop',
4809 'muted',
4810 'playsinline',
4811
4812 // Other elements
4813 'hidden',
4814 'open',
4815 'ismap',
4816 'reversed',
4817 'allowfullscreen',
4818 'itemscope',
4819 'nomodule',
4820 'defer',
4821 'async',
4822 'default',
4823 'inert',
4824 ];
4825}
4826
4827
4836function dolCompletUrlForDropdownButton(string $url, array $params, bool $addDolUrlRoot = true)
4837{
4838 if (empty($url)) {
4839 return '';
4840 }
4841
4842 $parsedUrl = parse_url($url);
4843 if ((isset($parsedUrl['scheme']) && in_array($parsedUrl['scheme'], ['javascript', 'mailto', 'tel'])) || strpos($url, '#') === 0) {
4844 return $url;
4845 }
4846
4847 if (!empty($parsedUrl['query'])) {
4848 // Use parse_str() function to parse the string passed via URL
4849 $urlQuery = '';
4850 parse_str($parsedUrl['query'], $urlQuery);
4851 if (!isset($urlQuery['backtopage']) && isset($params['backtopage'])) {
4852 $url .= '&amp;backtopage=' . urlencode($params['backtopage']);
4853 }
4854 }
4855
4856 if (!isset($parsedUrl['scheme']) && $addDolUrlRoot) {
4857 $url = DOL_URL_ROOT . $url;
4858 }
4859
4860 return $url;
4861}
4862
4863
4870function dolGetButtonTitleSeparator($moreClass = "")
4871{
4872 return '<span class="button-title-separator ' . $moreClass . '" ></span>';
4873}
4874
4881function getFieldErrorIcon($fieldValidationErrorMsg)
4882{
4883 $out = '';
4884 if (!empty($fieldValidationErrorMsg)) {
4885 $out .= '<span class="field-error-icon classfortooltip" title="' . dol_escape_htmltag($fieldValidationErrorMsg, 1) . '" role="alert" >'; // role alert is used for accessibility
4886 $out .= '<span class="fa fa-exclamation-circle" aria-hidden="true" ></span>'; // For accessibility icon is separated and aria-hidden
4887 $out .= '</span>';
4888 }
4889
4890 return $out;
4891}
4892
4905function dolGetButtonTitle($label, $helpText = '', $iconClass = 'fa fa-file', $url = '', $id = '', $status = 1, $params = array())
4906{
4907 global $langs, $user;
4908
4909 // Actually this conf is used in css too for external module compatibility and smooth transition to this function
4910 if (getDolGlobalString('MAIN_BUTTON_HIDE_UNAUTHORIZED') && (!$user->admin) && $status <= 0) {
4911 return '';
4912 }
4913 // Fix old picto fa-th-list to use fa-grid-vertical instead
4914 if ($iconClass == 'fa fa-th-list imgforviewmode') {
4915 $iconClass = ' fa fa-grip-horizontal imgforviewmode';
4916 }
4917
4918 $class = 'btnTitle';
4919 if (in_array($iconClass, array('fa fa-plus-circle', 'fa fa-plus-circle size15x', 'fa fa-comment-dots', 'fa fa-paper-plane'))) {
4920 $class .= ' btnTitlePlus';
4921 }
4922 $useclassfortooltip = 1;
4923
4924 if (!empty($params['morecss'])) {
4925 $class .= ' ' . $params['morecss'];
4926 }
4927
4928 $attr = array(
4929 'class' => $class,
4930 'href' => empty($url) ? '' : $url
4931 );
4932
4933 if (!empty($helpText)) {
4934 $attr['title'] = $helpText;
4935 } elseif ($label) { // empty($attr['title']) &&
4936 $attr['title'] = $label;
4937 $useclassfortooltip = 0;
4938 }
4939
4940 if ($status == 2) {
4941 $attr['class'] .= ' btnTitleSelected';
4942 } elseif ($status <= 0) {
4943 $attr['class'] .= ' refused';
4944
4945 $attr['href'] = '';
4946
4947 if ($status == -1) { // disable
4948 $attr['title'] = $langs->transnoentitiesnoconv("FeatureDisabled");
4949 } elseif ($status == 0) { // Not enough permissions
4950 $attr['title'] = $langs->transnoentitiesnoconv("NotEnoughPermissions");
4951 }
4952 }
4953
4954 if (!empty($attr['title']) && $useclassfortooltip) {
4955 $attr['class'] .= ' classfortooltip';
4956 }
4957
4958 if (!empty($id)) {
4959 $attr['id'] = $id;
4960 }
4961
4962 // Override attr
4963 if (!empty($params['attr']) && is_array($params['attr'])) {
4964 foreach ($params['attr'] as $key => $value) {
4965 if ($key == 'class') {
4966 $attr['class'] .= ' ' . $value;
4967 } elseif ($key == 'classOverride') {
4968 $attr['class'] = $value;
4969 } else {
4970 $attr[$key] = $value;
4971 }
4972 }
4973 }
4974
4975 if (isset($attr['href']) && empty($attr['href'])) {
4976 unset($attr['href']);
4977 }
4978
4979 // TODO : add a hook
4980
4981 // Generate attributes with escapement
4982 $TCompiledAttr = array();
4983 foreach ($attr as $key => $value) {
4984 $TCompiledAttr[] = $key . '="' . dol_escape_htmltag($value) . '"'; // Do not use dolPrintHTMLForAttribute() here, we must accept "javascript:string"
4985 }
4986
4987 $compiledAttributes = (empty($TCompiledAttr) ? '' : implode(' ', $TCompiledAttr));
4988
4989 $tag = (empty($attr['href']) ? 'span' : 'a');
4990
4991 $button = '<' . $tag . ' ' . $compiledAttributes . '>';
4992 $button .= '<span class="' . $iconClass . ' valignmiddle btnTitle-icon"></span>';
4993 if (!empty($params['forcenohideoftext'])) {
4994 $button .= '<span class="valignmiddle text-plus-circle btnTitle-label' . (empty($params['forcenohideoftext']) ? ' hideonsmartphone' : '') . '">' . $label . '</span>';
4995 }
4996 $button .= '</' . $tag . '>';
4997
4998 return $button;
4999}
5000
5001
5002
5016function startSimpleTable($header, $link = "", $arguments = "", $emptyColumns = 0, $number = -1, $pictofulllist = '')
5017{
5018 global $langs;
5019
5020 print '<div class="div-table-responsive-no-min">';
5021 print '<table class="noborder centpercent">';
5022 print '<tr class="liste_titre">';
5023
5024 print ($emptyColumns < 1) ? '<th>' : '<th colspan="' . ($emptyColumns + 1) . '">';
5025
5026 print '<span class="valignmiddle">' . $langs->trans($header) . '</span>';
5027
5028 if (!empty($link)) {
5029 if (!empty($arguments)) {
5030 print '<a href="' . DOL_URL_ROOT . '/' . $link . '?' . $arguments . '">';
5031 } else {
5032 print '<a href="' . DOL_URL_ROOT . '/' . $link . '">';
5033 }
5034 }
5035
5036 if ($number > -1) {
5037 print '<span class="badge marginleftonlyshort">' . $number . '</span>';
5038 } elseif (!empty($link)) {
5039 print '<span class="badge marginleftonlyshort">...</span>';
5040 }
5041
5042 if (!empty($link)) {
5043 print '</a>';
5044 }
5045
5046 print '</th>';
5047
5048 if ($number < 0 && !empty($link)) {
5049 print '<th class="right">';
5050 print '</th>';
5051 }
5052
5053 print '</tr>';
5054}
5055
5064function finishSimpleTable($addLineBreak = false)
5065{
5066 print '</table>';
5067 print '</div>';
5068
5069 if ($addLineBreak) {
5070 print '<br>';
5071 }
5072}
5073
5085function addSummaryTableLine($tableColumnCount, $num, $nbofloop = 0, $total = 0, $noneWord = "None", $extraRightColumn = false)
5086{
5087 global $langs;
5088
5089 if ($num === 0) {
5090 print '<tr class="oddeven">';
5091 print '<td colspan="' . $tableColumnCount . '"><span class="opacitymedium">' . $langs->trans($noneWord) . '</span></td>';
5092 print '</tr>';
5093 return;
5094 }
5095
5096 if ($nbofloop === 0) {
5097 // don't show a summary line
5098 return;
5099 }
5100
5101 /* Case already handled above, commented to satisfy phpstan.
5102 if ($num === 0) {
5103 $colspan = $tableColumnCount;
5104 } else
5105 */
5106 if ($num > $nbofloop) {
5107 $colspan = $tableColumnCount;
5108 } else {
5109 $colspan = $tableColumnCount - 1;
5110 }
5111
5112 if ($extraRightColumn) {
5113 $colspan--;
5114 }
5115
5116 print '<tr class="liste_total">';
5117
5118 if ($nbofloop > 0 && $num > $nbofloop) {
5119 print '<td colspan="' . $colspan . '" class="right">' . $langs->trans("XMoreLines", ($num - $nbofloop)) . '</td>';
5120 } else {
5121 print '<td colspan="' . $colspan . '" class="right"> ' . $langs->trans("Total") . '</td>';
5122 print '<td class="right centpercent">' . price($total) . '</td>';
5123 }
5124
5125 if ($extraRightColumn) {
5126 print '<td></td>';
5127 }
5128
5129 print '</tr>';
5130}
5131
5132
5133
5143function showValueWithClipboardCPButton($valuetocopy, $showonlyonhover = 1, $texttoshow = '')
5144{
5145 global $langs;
5146
5147 $tag = 'span'; // Using div (like any style of type 'block') does not work when using the js copy code.
5148
5149 $result = '<span class="clipboardCP' . ($showonlyonhover ? ' clipboardCPShowOnHover valignmiddle' : '') . '">';
5150 if ($texttoshow === 'none') {
5151 $result .= '<' . $tag . ' class="clipboardCPValue hidewithsize">' . dol_escape_htmltag($valuetocopy, 1, 1) . '</' . $tag . '>';
5152 $result .= '<span class="clipboardCPValueToPrint"></span>';
5153 } elseif ($texttoshow) {
5154 $result .= '<' . $tag . ' class="clipboardCPValue hidewithsize">' . dol_escape_htmltag($valuetocopy, 1, 1) . '</' . $tag . '>';
5155 $result .= '<span class="clipboardCPValueToPrint">' . dol_escape_htmltag($texttoshow, 1, 1) . '</span>';
5156 } else {
5157 $result .= '<' . $tag . ' class="clipboardCPValue">' . dol_escape_htmltag($valuetocopy, 1, 1) . '</' . $tag . '>';
5158 }
5159 $result .= '<span class="clipboardCPButton far fa-clipboard opacitymedium paddingleft pictomodule" title="' . dolPrintHTML($langs->trans("ClickToCopyToClipboard")) . '"></span>';
5160 $result .= img_picto('', 'tick', 'class="clipboardCPTick hidden paddingleft pictomodule"');
5161 $result .= '<span class="clipboardCPText"></span>';
5162 $result .= '</span>';
5163
5164 return $result;
5165}
5166
5167
5168
5186function show_actions_messaging($conf, $langs, $db, $filterobj, $objcon = null, $noprint = 0, $actioncode = '', $donetodo = 'done', $filters = array(), $sortfield = 'a.datep,a.id', $sortorder = 'DESC')
5187{
5188 dol_syslog('show_actions_messaging::begin', LOG_DEBUG);
5189 global $user, $conf;
5190 global $form;
5191
5192 global $param, $massactionbutton;
5193
5194 require_once DOL_DOCUMENT_ROOT . '/comm/action/class/actioncomm.class.php';
5195
5196 // Check parameters
5197 if (!is_object($filterobj) && !is_object($objcon)) {
5198 dol_print_error(null, 'BadParameter');
5199 }
5200
5201 $histo = array();
5202 '@phan-var-force array<int,array{type:string,tododone:string,id:string,datestart:int|string,dateend:int|string,fulldayevent:int,note:string,message:string,percent:string,userid:string,login:string,userfirstname:string,userlastname:string,userphoto:string,msg_from?:string,contact_id?:string,socpeopleassigned?:int[],lastname?:string,firstname?:string,fk_element?:int,elementtype?:string,acode:string,alabel?:string,libelle?:string,apicto?:string}> $histo';
5203
5204 $numaction = 0;
5205 $now = dol_now();
5206
5207 $sortfield_list = explode(',', $sortfield);
5208 $sortfield_label_list = array('a.id' => 'id', 'a.datep' => 'dp', 'a.percent' => 'percent');
5209 $sanitized_sortfield_new_list = array();
5210 foreach ($sortfield_list as $sortfield_value) {
5211 $sanitized_sortfield_new_list[] = $sortfield_label_list[trim($sortfield_value)]; //@phan-suppress-current-line SqlInjection
5212 }
5213 $sanitized_sortfield_new = implode(',', $sanitized_sortfield_new_list);
5214
5215 $sql = null;
5216 $sql2 = null;
5217
5218 if (isModEnabled('agenda')) {
5219 // Search histo on actioncomm
5220 if (is_object($objcon) && $objcon->id > 0) {
5221 $sql = "SELECT DISTINCT a.id, a.label as label,";
5222 } else {
5223 $sql = "SELECT a.id, a.label as label,";
5224 }
5225 $sql .= " a.datep as dp,";
5226 $sql .= " a.note as message,";
5227 $sql .= " a.datep2 as dp2,";
5228 $sql .= " a.percent as percent, 'action' as type,";
5229 $sql .= " a.fk_element, a.elementtype,";
5230 $sql .= " a.fk_contact, a.fulldayevent,";
5231 $sql .= " a.email_from as msg_from,";
5232 $sql .= " c.code as acode, c.libelle as alabel, c.picto as apicto,";
5233 $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";
5234 if (is_object($filterobj) && get_class($filterobj) == 'Societe') {
5235 $sql .= ", sp.lastname, sp.firstname";
5236 } elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') {
5237 $sql .= ", m.lastname, m.firstname";
5238 } elseif (is_object($filterobj) && in_array(get_class($filterobj), array('Commande', 'CommandeFournisseur', 'Product', 'Ticket', 'BOM', 'Contrat', 'Facture', 'FactureFournisseur', 'Propal', 'Expedition'))) {
5239 $sql .= ", o.ref";
5240 } else {
5241 if (is_object($filterobj) && !empty($filterobj->table_element) && !empty($filterobj->element) && !empty($filterobj->id) && array_key_exists('ref', $filterobj->fields)) {
5242 $sql .= ", o.ref";
5243 }
5244 }
5245 $sql .= " FROM " . MAIN_DB_PREFIX . "actioncomm as a";
5246 $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "user as u on u.rowid = a.fk_user_action";
5247 $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "c_actioncomm as c ON a.fk_action = c.id";
5248
5249 $force_filter_contact = $filterobj instanceof User;
5250
5251 if (is_object($objcon) && $objcon->id > 0) {
5252 $force_filter_contact = true;
5253 $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "actioncomm_resources as r ON a.id = r.fk_actioncomm";
5254 $sql .= " AND r.element_type = '" . $db->escape($objcon->table_element) . "' AND r.fk_element = " . ((int) $objcon->id);
5255 }
5256
5257 if ((is_object($filterobj) && get_class($filterobj) == 'Societe') || (is_object($filterobj) && get_class($filterobj) == 'Contact')) {
5258 $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "socpeople as sp ON a.fk_contact = sp.rowid";
5259 } elseif (is_object($filterobj) && get_class($filterobj) == 'Dolresource') {
5260 $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "element_resources as er";
5261 $sql .= " ON er.resource_type = 'dolresource'";
5262 $sql .= " AND er.element_id = a.id";
5263 $sql .= " AND er.resource_id = " . ((int) $filterobj->id);
5264 } elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') {
5265 $sql .= ", " . MAIN_DB_PREFIX . "adherent as m";
5266 } elseif (is_object($filterobj) && get_class($filterobj) == 'CommandeFournisseur') {
5267 $sql .= ", " . MAIN_DB_PREFIX . "commande_fournisseur as o";
5268 } elseif (is_object($filterobj) && get_class($filterobj) == 'Product') {
5269 $sql .= ", " . MAIN_DB_PREFIX . "product as o";
5270 } elseif (is_object($filterobj) && get_class($filterobj) == 'Ticket') {
5271 $sql .= ", " . MAIN_DB_PREFIX . "ticket as o";
5272 } elseif (is_object($filterobj) && get_class($filterobj) == 'BOM') {
5273 $sql .= ", " . MAIN_DB_PREFIX . "bom_bom as o";
5274 } elseif (is_object($filterobj) && get_class($filterobj) == 'Contrat') {
5275 $sql .= ", " . MAIN_DB_PREFIX . "contrat as o";
5276 } elseif (is_object($filterobj) && get_class($filterobj) == 'Facture') {
5277 $sql .= ", " . MAIN_DB_PREFIX . "facture as o";
5278 } elseif (is_object($filterobj) && get_class($filterobj) == 'FactureFournisseur') {
5279 $sql .= ", " . MAIN_DB_PREFIX . "facture_fourn as o";
5280 } elseif (is_object($filterobj) && get_class($filterobj) == 'Commande') {
5281 $sql .= ", " . MAIN_DB_PREFIX . "commande as o";
5282 } elseif (is_object($filterobj) && get_class($filterobj) == 'Expedition') {
5283 $sql .= ", " . MAIN_DB_PREFIX . "expedition as o";
5284 } elseif (is_object($filterobj) && get_class($filterobj) == 'Propal') {
5285 $sql .= ", " . MAIN_DB_PREFIX . "propal as o";
5286 } else {
5287 if (is_object($filterobj) && !empty($filterobj->table_element) && !empty($filterobj->element) && !empty($filterobj->id) && array_key_exists('ref', $filterobj->fields)) {
5288 $sql .= ", " . MAIN_DB_PREFIX . $filterobj->table_element . " as o";
5289 }
5290 }
5291 $sql .= " WHERE a.entity IN (" . getEntity('agenda') . ")";
5292 if (!$force_filter_contact) {
5293 if (is_object($filterobj) && in_array(get_class($filterobj), array('Societe', 'Client', 'Fournisseur')) && $filterobj->id) {
5294 $sql .= " AND a.fk_soc = " . ((int) $filterobj->id);
5295 } elseif (is_object($filterobj) && get_class($filterobj) == 'Project' && $filterobj->id) {
5296 $sql .= " AND a.fk_project = o.rowid AND a.fk_project = " . ((int) $filterobj->id);
5297 } elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') {
5298 $sql .= " AND a.fk_element = m.rowid AND a.elementtype = 'member'";
5299 if ($filterobj->id) {
5300 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
5301 }
5302 } elseif (is_object($filterobj) && get_class($filterobj) == 'Commande') {
5303 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'order'";
5304 if ($filterobj->id) {
5305 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
5306 }
5307 } elseif (is_object($filterobj) && get_class($filterobj) == 'Expedition') {
5308 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'shipping'";
5309 if ($filterobj->id) {
5310 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
5311 }
5312 } elseif (is_object($filterobj) && get_class($filterobj) == 'Propal') {
5313 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'propal'";
5314 if ($filterobj->id) {
5315 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
5316 }
5317 } elseif (is_object($filterobj) && get_class($filterobj) == 'CommandeFournisseur') {
5318 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'order_supplier'";
5319 if ($filterobj->id) {
5320 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
5321 }
5322 } elseif (is_object($filterobj) && get_class($filterobj) == 'Product') {
5323 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'product'";
5324 if ($filterobj->id) {
5325 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
5326 }
5327 } elseif (is_object($filterobj) && get_class($filterobj) == 'Ticket') {
5328 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'ticket'";
5329 if ($filterobj->id) {
5330 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
5331 }
5332 } elseif (is_object($filterobj) && get_class($filterobj) == 'BOM') {
5333 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'bom'";
5334 if ($filterobj->id) {
5335 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
5336 }
5337 } elseif (is_object($filterobj) && get_class($filterobj) == 'Contrat') {
5338 $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'contract'";
5339 if ($filterobj->id) {
5340 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
5341 }
5342 } elseif (is_object($filterobj) && get_class($filterobj) == 'Contact' && $filterobj->id) {
5343 $sql .= " AND a.fk_contact = sp.rowid";
5344 $sql .= " AND a.fk_contact = " . ((int) $filterobj->id);
5345 } elseif (is_object($filterobj) && get_class($filterobj) == 'Facture') {
5346 $sql .= " AND a.fk_element = o.rowid";
5347 if ($filterobj->id) {
5348 $sql .= " AND a.fk_element = " . ((int) $filterobj->id) . " AND a.elementtype = 'invoice'";
5349 }
5350 } elseif (is_object($filterobj) && get_class($filterobj) == 'FactureFournisseur') {
5351 $sql .= " AND a.fk_element = o.rowid";
5352 if ($filterobj->id) {
5353 $sql .= " AND a.fk_element = " . ((int) $filterobj->id) . " AND a.elementtype = 'invoice_supplier'";
5354 }
5355 } else {
5356 if (is_object($filterobj) && !empty($filterobj->element) && !empty($filterobj->id) && array_key_exists('ref', $filterobj->fields)) {
5357 $sql .= " AND a.fk_element = o.rowid";
5358 $sql .= " AND a.elementtype = '" . $db->escape($filterobj->element) . "'";
5359 $sql .= " AND a.fk_element = " . ((int) $filterobj->id);
5360 }
5361 }
5362 } else {
5363 $sql .= " AND u.rowid = " . ((int) $filterobj->id);
5364 }
5365
5366 // Condition on actioncode
5367 if (!empty($actioncode) && $actioncode != '-1') {
5368 if (!getDolGlobalString('AGENDA_USE_EVENT_TYPE')) {
5369 if ($actioncode == 'AC_NON_AUTO') {
5370 $sql .= " AND c.type != 'systemauto'";
5371 } elseif ($actioncode == 'AC_ALL_AUTO') {
5372 $sql .= " AND c.type = 'systemauto'";
5373 } else {
5374 if ($actioncode == 'AC_OTH') {
5375 $sql .= " AND c.type != 'systemauto'";
5376 } elseif ($actioncode == 'AC_OTH_AUTO') {
5377 $sql .= " AND c.type = 'systemauto'";
5378 }
5379 }
5380 } else {
5381 if ($actioncode == 'AC_NON_AUTO') {
5382 $sql .= " AND c.type != 'systemauto'";
5383 } elseif ($actioncode == 'AC_ALL_AUTO') {
5384 $sql .= " AND c.type = 'systemauto'";
5385 } else {
5386 $sql .= " AND c.code = '" . $db->escape($actioncode) . "'";
5387 }
5388 }
5389 }
5390 if ($donetodo == 'todo') {
5391 $sql .= " AND ((a.percent >= 0 AND a.percent < 100) OR (a.percent = -1 AND a.datep > '" . $db->idate($now) . "'))";
5392 } elseif ($donetodo == 'done') {
5393 $sql .= " AND (a.percent = 100 OR (a.percent = -1 AND a.datep <= '" . $db->idate($now) . "'))";
5394 }
5395 if (is_array($filters) && $filters['search_agenda_label']) {
5396 $sql .= natural_search('a.label', $filters['search_agenda_label']);
5397 }
5398 }
5399
5400 // Add also event from emailings. TODO This should be replaced by an automatic event ? May be it's too much for very large emailing.
5401 if (
5402 isModEnabled('mailing') && !empty($objcon->email)
5403 && (empty($actioncode) || $actioncode == 'AC_OTH_AUTO' || $actioncode == 'AC_EMAILING')
5404 ) {
5405 $langs->load("mails");
5406
5407 $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";
5408 $sql2 .= ", null as fk_element, '' as elementtype, null as contact_id";
5409 $sql2 .= ", 'AC_EMAILING' as acode, '' as alabel, '' as apicto";
5410 $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
5411 if (is_object($filterobj) && get_class($filterobj) == 'Societe') {
5412 $sql2 .= ", '' as lastname, '' as firstname";
5413 } elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') {
5414 $sql2 .= ", '' as lastname, '' as firstname";
5415 } elseif (is_object($filterobj) && get_class($filterobj) == 'CommandeFournisseur') {
5416 $sql2 .= ", '' as ref";
5417 } elseif (is_object($filterobj) && get_class($filterobj) == 'Product') {
5418 $sql2 .= ", '' as ref";
5419 } elseif (is_object($filterobj) && get_class($filterobj) == 'Ticket') {
5420 $sql2 .= ", '' as ref";
5421 }
5422 $sql2 .= " FROM " . MAIN_DB_PREFIX . "mailing as m, " . MAIN_DB_PREFIX . "mailing_cibles as mc, " . MAIN_DB_PREFIX . "user as u";
5423 $sql2 .= " WHERE mc.email = '" . $db->escape($objcon->email) . "'"; // Search is done on email.
5424 $sql2 .= " AND mc.statut = 1";
5425 $sql2 .= " AND u.rowid = m.fk_user_valid";
5426 $sql2 .= " AND mc.fk_mailing=m.rowid";
5427 }
5428
5429 $num = 0;
5430 $MAXWITHOUTPAGINATION = getDolGlobalInt('AGENDA_MAX_EVENTS_ON_PAGE_WITHOUT_PAGINATION', 100);
5431
5432 if ($sql || $sql2) { // May not be defined if module Agenda is not enabled and mailing module disabled too
5433 if (!empty($sql) && !empty($sql2)) {
5434 $sql = $sql . " UNION " . $sql2;
5435 } elseif (empty($sql) && !empty($sql2)) {
5436 $sql = $sql2;
5437 }
5438
5439 //TODO Add navigation with this limits...
5440 $offset = 0;
5441 $limit = $MAXWITHOUTPAGINATION;
5442
5443 // Complete request and execute it with limit
5444 $sql .= $db->order($sanitized_sortfield_new, $sortorder);
5445 if ($limit) {
5446 $sql .= $db->plimit($limit + 1, $offset);
5447 }
5448
5449 dol_syslog("function.lib::show_actions_messaging", LOG_DEBUG);
5450
5451 $resql = $db->query($sql);
5452 if ($resql) {
5453 $i = 0;
5454 $num = $db->num_rows($resql);
5455
5456 $imaxinloop = ($limit ? min($num, $limit) : $num);
5457 while ($i < $imaxinloop) {
5458 $obj = $db->fetch_object($resql);
5459
5460 if ($obj->type == 'action') {
5461 $contactaction = new ActionComm($db);
5462 $contactaction->id = $obj->id;
5463 $result = $contactaction->fetchResources();
5464 if ($result < 0) {
5465 dol_print_error($db);
5466 setEventMessage("actions.lib::show_actions_messaging Error fetch resource", 'errors');
5467 }
5468
5469 //if ($donetodo == 'todo') $sql.= " AND ((a.percent >= 0 AND a.percent < 100) OR (a.percent = -1 AND a.datep > '".$db->idate($now)."'))";
5470 //elseif ($donetodo == 'done') $sql.= " AND (a.percent = 100 OR (a.percent = -1 AND a.datep <= '".$db->idate($now)."'))";
5471 $tododone = '';
5472 if (($obj->percent >= 0 and $obj->percent < 100) || ($obj->percent == -1 && $obj->dp > $now)) {
5473 $tododone = 'todo';
5474 }
5475
5476 $histo[$numaction] = array(
5477 'type' => $obj->type,
5478 'tododone' => $tododone,
5479 'id' => $obj->id,
5480 'datestart' => $db->jdate($obj->dp),
5481 'dateend' => $db->jdate($obj->dp2),
5482 'fulldayevent' => (int) $obj->fulldayevent,
5483 'note' => $obj->label,
5484 'message' => $obj->message,
5485 'percent' => $obj->percent,
5486
5487 'userid' => $obj->user_id,
5488 'login' => $obj->user_login,
5489 'userfirstname' => $obj->user_firstname,
5490 'userlastname' => $obj->user_lastname,
5491 'userphoto' => $obj->user_photo,
5492 'msg_from' => $obj->msg_from,
5493
5494 'contact_id' => $obj->fk_contact,
5495 'socpeopleassigned' => $contactaction->socpeopleassigned,
5496 'lastname' => (empty($obj->lastname) ? '' : $obj->lastname),
5497 'firstname' => (empty($obj->firstname) ? '' : $obj->firstname),
5498 'fk_element' => $obj->fk_element,
5499 'elementtype' => $obj->elementtype,
5500 // Type of event
5501 'acode' => $obj->acode,
5502 'alabel' => $obj->alabel,
5503 'libelle' => $obj->alabel, // deprecated
5504 'apicto' => $obj->apicto
5505 );
5506 } else {
5507 $histo[$numaction] = array(
5508 'type' => $obj->type,
5509 'tododone' => 'done',
5510 'id' => $obj->id,
5511 'datestart' => $db->jdate($obj->dp),
5512 'dateend' => $db->jdate($obj->dp2),
5513 'fulldayevent' => (int) $obj->fulldayevent,
5514 'note' => $obj->label,
5515 'message' => $obj->message,
5516 'percent' => $obj->percent,
5517 'acode' => $obj->acode,
5518
5519 'userid' => $obj->user_id,
5520 'login' => $obj->user_login,
5521 'userfirstname' => $obj->user_firstname,
5522 'userlastname' => $obj->user_lastname,
5523 'userphoto' => $obj->user_photo
5524 );
5525 }
5526
5527 $numaction++;
5528 $i++;
5529 }
5530 } else {
5531 dol_print_error($db);
5532 }
5533 }
5534
5535 // Set $out to show events
5536 $out = '';
5537
5538 if (!isModEnabled('agenda')) {
5539 $langs->loadLangs(array("admin", "errors"));
5540 $out = info_admin($langs->trans("WarningModuleXDisabledSoYouMayMissEventHere", $langs->transnoentitiesnoconv("Module2400Name")), 0, 0, 'warning');
5541 }
5542
5543 if (isModEnabled('agenda') || (isModEnabled('mailing') && !empty($objcon->email))) {
5544 $delay_warning = getDolGlobalInt('MAIN_DELAY_ACTIONS_TODO') * 24 * 60 * 60;
5545
5546 require_once DOL_DOCUMENT_ROOT . '/comm/action/class/actioncomm.class.php';
5547 include_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php';
5548 require_once DOL_DOCUMENT_ROOT . '/core/class/html.formactions.class.php';
5549 require_once DOL_DOCUMENT_ROOT . '/contact/class/contact.class.php';
5550
5551 $formactions = new FormActions($db);
5552
5553 $actionstatic = new ActionComm($db);
5554 $userstatic = new User($db);
5555 $contactstatic = new Contact($db);
5556 $userGetNomUrlCache = array();
5557 $contactGetNomUrlCache = array();
5558
5559 $out .= '<div class="filters-container" >';
5560 $out .= '<form name="listactionsfilter" class="listactionsfilter" action="' . $_SERVER["PHP_SELF"] . '" method="POST">';
5561 $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
5562
5563 if (
5564 $objcon && get_class($objcon) == 'Contact' &&
5565 (is_null($filterobj) || get_class($filterobj) == 'Societe')
5566 ) {
5567 $out .= '<input type="hidden" name="id" value="' . $objcon->id . '" />';
5568 } else {
5569 $out .= '<input type="hidden" name="id" value="' . $filterobj->id . '" />';
5570 }
5571 if (($filterobj && get_class($filterobj) == 'Societe')) {
5572 $out .= '<input type="hidden" name="socid" value="' . $filterobj->id . '" />';
5573 } else {
5574 $out .= '<input type="hidden" name="userid" value="' . $filterobj->id . '" />';
5575 }
5576
5577 $out .= "\n";
5578
5579 $out .= '<div class="div-table-responsive-no-min">';
5580 $out .= '<table class="noborder borderbottom centpercent">';
5581
5582 $out .= '<tr class="liste_titre">';
5583
5584 // Action column
5585 if ($conf->main_checkbox_left_column) {
5586 $out .= '<th class="liste_titre width50 middle">';
5587 $searchpicto = $form->showFilterAndCheckAddButtons($massactionbutton ? 1 : 0, 'checkforselect', 1);
5588 $out .= $searchpicto;
5589 $out .= '</th>';
5590 }
5591
5592 // Date
5593 $out .= getTitleFieldOfList('Date', 0, $_SERVER["PHP_SELF"], 'a.datep', '', $param, '', $sortfield, $sortorder, 'nowraponall nopaddingleftimp ') . "\n";
5594
5595 $out .= '<th class="liste_titre hideonsmartphone"><strong class="hideonsmartphone">' . $langs->trans("Search") . ' : </strong></th>';
5596 if ($donetodo) {
5597 $out .= '<th class="liste_titre"></th>';
5598 }
5599 // Type of event
5600 $out .= '<th class="liste_titre">';
5601 $out .= '<span class="fas fa-square inline-block fawidth30 hideonsmartphone" style="color: #ddd;" title="' . $langs->trans("ActionType") . '"></span>';
5602 $out .= $formactions->select_type_actions($actioncode, "actioncode", '', getDolGlobalString('AGENDA_USE_EVENT_TYPE') ? -1 : 1, 0, 0, 1, 'selecttype minwidth100', $langs->trans("Type"));
5603 $out .= '</th>';
5604 // Label
5605 $out .= '<th class="liste_titre maxwidth100onsmartphone">';
5606 $out .= '<input type="text" class="maxwidth100onsmartphone" name="search_agenda_label" value="' . $filters['search_agenda_label'] . '" placeholder="' . $langs->trans("Label") . '">';
5607 $out .= '</th>';
5608
5609 // Action column
5610 if (!$conf->main_checkbox_left_column) {
5611 $out .= '<th class="liste_titre width50 middle">';
5612 $searchpicto = $form->showFilterAndCheckAddButtons($massactionbutton ? 1 : 0, 'checkforselect', 1);
5613 $out .= $searchpicto;
5614 $out .= '</th>';
5615 }
5616
5617 $out .= '</tr>';
5618
5619 $out .= '</table>';
5620
5621 $out .= '</form>';
5622 $out .= '</div>';
5623
5624 $out .= "\n";
5625
5626 $out .= '<ul class="timeline">';
5627
5628 if ($donetodo) {
5629 $tmp = '';
5630 if ($filterobj instanceof Societe) {
5631 $tmp .= '<a href="' . DOL_URL_ROOT . '/comm/action/list.php?mode=show_list&socid=' . $filterobj->id . '&status=done">';
5632 }
5633 if ($filterobj instanceof User) {
5634 $tmp .= '<a href="' . DOL_URL_ROOT . '/comm/action/list.php?mode=show_list&socid=' . $filterobj->id . '&status=done">';
5635 }
5636 $tmp .= ($donetodo != 'done' ? $langs->trans("ActionsToDoShort") : '');
5637 $tmp .= ($donetodo != 'done' && $donetodo != 'todo' ? ' / ' : '');
5638 $tmp .= ($donetodo != 'todo' ? $langs->trans("ActionsDoneShort") : '');
5639 //$out.=$langs->trans("ActionsToDoShort").' / '.$langs->trans("ActionsDoneShort");
5640 if ($filterobj instanceof Societe) {
5641 $tmp .= '</a>';
5642 }
5643 if ($filterobj instanceof User) {
5644 $tmp .= '</a>';
5645 }
5646 $out .= getTitleFieldOfList($tmp);
5647 }
5648
5649 require_once DOL_DOCUMENT_ROOT . '/comm/action/class/cactioncomm.class.php';
5650 $caction = new CActionComm($db);
5651 $arraylist = $caction->liste_array(1, 'code', '', (!getDolGlobalString('AGENDA_USE_EVENT_TYPE') ? 1 : 0), '', 1);
5652
5653 $actualCycleDate = false;
5654
5655 // Loop on each event to show it
5656 foreach ($histo as $key => $value) {
5657 $actionstatic->fetch($histo[$key]['id']); // TODO Do we need this, we already have a lot of data of line into $histo
5658
5659 $actionstatic->type_picto = $histo[$key]['apicto'];
5660 $actionstatic->type_code = $histo[$key]['acode'];
5661
5662 $labeltype = $actionstatic->type_code;
5663 if (!getDolGlobalString('AGENDA_USE_EVENT_TYPE') && empty($arraylist[$labeltype])) {
5664 $labeltype = 'AC_OTH';
5665 }
5666 if (!empty($actionstatic->code) && preg_match('/^TICKET_MSG/', $actionstatic->code)) {
5667 $labeltype = $langs->trans("Message");
5668 } else {
5669 if (!empty($arraylist[$labeltype])) {
5670 $labeltype = $arraylist[$labeltype];
5671 }
5672 if ($actionstatic->type_code == 'AC_OTH_AUTO' && ($actionstatic->type_code != $actionstatic->code) && $labeltype && !empty($arraylist[$actionstatic->code])) {
5673 $labeltype .= ' - ' . $arraylist[$actionstatic->code]; // Use code in priority on type_code
5674 }
5675 }
5676
5677 $url = DOL_URL_ROOT . '/comm/action/card.php?id=' . $histo[$key]['id'];
5678
5679 $tmpa = dol_getdate($histo[$key]['datestart'], false);
5680
5681 if (isset($tmpa['year']) && isset($tmpa['yday']) && $actualCycleDate !== $tmpa['year'] . '-' . $tmpa['yday']) {
5682 $actualCycleDate = $tmpa['year'] . '-' . $tmpa['yday'];
5683 $out .= '<!-- timeline time label -->';
5684 $out .= '<li class="time-label">';
5685 $out .= '<span class="timeline-badge-date">';
5686 $out .= dol_print_date($histo[$key]['datestart'], 'daytext', 'tzuserrel', $langs);
5687 $out .= '</span>';
5688 $out .= '</li>';
5689 $out .= '<!-- /.timeline-label -->';
5690 }
5691
5692
5693 $out .= '<!-- timeline item -->' . "\n";
5694 $out .= '<li class="timeline-code-' . (!empty($actionstatic->code) ? strtolower($actionstatic->code) : "none") . '">';
5695
5696 //$timelineicon = getTimelineIcon($actionstatic, $histo, $key);
5697 $typeicon = $actionstatic->getTypePicto('pictofixedwidth timeline-icon-not-applicble', $labeltype);
5698 //$out .= $timelineicon;
5699 //var_dump($timelineicon);
5700 $out .= $typeicon;
5701
5702 $out .= '<div class="timeline-item">' . "\n";
5703
5704 $out .= '<span class="time timeline-header-action2">';
5705
5706 if (isset($histo[$key]['type']) && $histo[$key]['type'] == 'mailing') {
5707 $out .= '<a class="paddingleft paddingright timeline-btn2 editfielda" href="' . DOL_URL_ROOT . '/comm/mailing/card.php?id=' . $histo[$key]['id'] . '">' . img_object($langs->trans("ShowEMailing"), "email") . ' ';
5708 $out .= $histo[$key]['id'];
5709 $out .= '</a> ';
5710 } else {
5711 $out .= $actionstatic->getNomUrl(1, -1, 'valignmiddle') . ' ';
5712 }
5713
5714 if (
5715 $user->hasRight('agenda', 'allactions', 'create') ||
5716 (($actionstatic->authorid == $user->id || $actionstatic->userownerid == $user->id) && $user->hasRight('agenda', 'myactions', 'create'))
5717 ) {
5718 $out .= '<a class="paddingleft paddingright timeline-btn2 editfielda" href="' . DOL_MAIN_URL_ROOT . '/comm/action/card.php?action=edit&token=' . newToken() . '&id=' . $actionstatic->id . '&backtopage=' . urlencode($_SERVER["PHP_SELF"] . '?' . $param) . '">';
5719 //$out .= '<i class="fa fa-pencil" title="'.$langs->trans("Modify").'" ></i>';
5720 $out .= img_picto($langs->trans("Modify"), 'edit', 'class="edita"');
5721 $out .= '</a>';
5722 }
5723
5724 $out .= '</span>';
5725
5726 // Date
5727 $out .= '<span class="time"><i class="fa fa-clock valignmiddle"></i> ';
5728 $out .= '<span class="valignmiddle marginrightonly">';
5729 $out .= dol_print_date($histo[$key]['datestart'], 'day', 'tzuserrel');
5730 //$out .= '</span>';
5731 //$out .= '<span class="valignmiddle">'.
5732 $out .= ' '.dol_print_date($histo[$key]['datestart'], 'hour', 'tzuserrel', null, false, 'opacitymedium');
5733 //$out .= '</span>';
5734 if ($histo[$key]['dateend'] && $histo[$key]['dateend'] != $histo[$key]['datestart']) {
5735 $tmpa = dol_getdate($histo[$key]['datestart'], true);
5736 $tmpb = dol_getdate($histo[$key]['dateend'], true);
5737 if ($tmpa['mday'] == $tmpb['mday'] && $tmpa['mon'] == $tmpb['mon'] && $tmpa['year'] == $tmpb['year']) {
5738 $out .= ' - ' . dol_print_date($histo[$key]['dateend'], 'hour', 'tzuserrel', null, false, 1);
5739 } else {
5740 $out .= ' - ' . dol_print_date($histo[$key]['dateend'], 'day', 'tzuserrel');
5741 //$out .= '<span class="valignmiddle marginrightonly">';
5742 $out .= ' '.dol_print_date($histo[$key]['dateend'], 'hour', 'tzuserrel', null, false, 'opacitymedium');
5743 //$out .= '</span>';
5744 }
5745 }
5746 $late = 0;
5747 if ($histo[$key]['percent'] == 0 && $histo[$key]['datestart'] && $histo[$key]['datestart'] < ($now - $delay_warning)) {
5748 $late = 1;
5749 }
5750 if ($histo[$key]['percent'] == 0 && !$histo[$key]['datestart'] && $histo[$key]['dateend'] && $histo[$key]['datestart'] < ($now - $delay_warning)) {
5751 $late = 1;
5752 }
5753 if ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100 && $histo[$key]['dateend'] && $histo[$key]['dateend'] < ($now - $delay_warning)) {
5754 $late = 1;
5755 }
5756 if ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100 && !$histo[$key]['dateend'] && $histo[$key]['datestart'] && $histo[$key]['datestart'] < ($now - $delay_warning)) {
5757 $late = 1;
5758 }
5759 if ($late) {
5760 $out .= img_warning($langs->trans("Late")) . ' ';
5761 }
5762 $out .= "</span></span>\n";
5763
5764 $out .= '<span class="time">';
5765 $out .= $actionstatic->getLibStatut(2);
5766 $out .= '</span>';
5767
5768 // Ref
5769 $out .= '<h3 class="timeline-header">';
5770
5771 // Author of event
5772 $out .= '<div class="messaging-author inline-block tdoverflowmax150 valignmiddle marginrightonly">';
5773 if ($histo[$key]['userid'] > 0) {
5774 if (!isset($userGetNomUrlCache[$histo[$key]['userid']])) { // is in cache ?
5775 $userstatic->fetch($histo[$key]['userid']);
5776 $userGetNomUrlCache[$histo[$key]['userid']] = $userstatic->getNomUrl(-1, '', 0, 0, 16, 0, 'firstelselast', '');
5777 }
5778 $out .= $userGetNomUrlCache[$histo[$key]['userid']];
5779 } elseif (!empty($histo[$key]['msg_from']) && $actionstatic->code == 'TICKET_MSG') {
5780 if (!isset($contactGetNomUrlCache[$histo[$key]['msg_from']])) {
5781 if ($contactstatic->fetch(0, null, '', $histo[$key]['msg_from']) > 0) {
5782 $contactGetNomUrlCache[$histo[$key]['msg_from']] = $contactstatic->getNomUrl(-1, '', 16);
5783 } else {
5784 $contactGetNomUrlCache[$histo[$key]['msg_from']] = $histo[$key]['msg_from'];
5785 }
5786 }
5787 $out .= $contactGetNomUrlCache[$histo[$key]['msg_from']];
5788 } else {
5789 $out .= '<img class="photomemberphoto userphoto" alt="" src="/public/theme/common/user_anonymous.png">'.$langs->trans("Anonymous");
5790 }
5791 $out .= '</div>';
5792
5793 // Title
5794 $out .= ' <div class="messaging-title inline-block">';
5795 //$out .= $actionstatic->getTypePicto(); // The type of event is already into the timeline on left.
5796 if (empty($conf->dol_optimize_smallscreen) && $actionstatic->type_code != 'AC_OTH_AUTO') {
5797 $out .= $labeltype . ' - ';
5798 }
5799
5800 $tmplabel = '';
5801
5802 if (!empty($actionstatic->code) && preg_match('/^TICKET_MSG_PRIVATE/', $actionstatic->code)) {
5803 $out .= $langs->trans('TicketNewMessage').' - <em>'.img_picto($langs->trans('Private'), 'lock', 'class="valignmiddle"').' '.$langs->trans('Private').'</em>';
5804 $summary = preg_replace('/\[[^\]]*\]\s*/', '', $actionstatic->label);
5805 //if ($summary != $object->title) {
5806 $out .= ' - '.dolPrintHTML($summary);
5807 //}
5808 } elseif (!empty($actionstatic->code) && preg_match('/^TICKET_MSG/', $actionstatic->code)) {
5809 $out .= $langs->trans('TicketNewMessage');
5810 } elseif (isset($histo[$key]['type'])) {
5811 if ($histo[$key]['type'] == 'action') {
5812 $transcode = $langs->transnoentitiesnoconv("Action" . $histo[$key]['acode']);
5813 //$tmplabel = ($transcode != "Action" . $histo[$key]['acode'] ? $transcode : $histo[$key]['alabel']);
5814 $tmplabel = $histo[$key]['note'];
5815 $actionstatic->id = $histo[$key]['id'];
5816 if ($tmplabel != $labeltype) {
5817 $out .= dol_escape_htmltag(dol_trunc($tmplabel, 120));
5818 }
5819 } elseif ($histo[$key]['type'] == 'mailing') {
5820 $out .= '<a href="' . DOL_URL_ROOT . '/comm/mailing/card.php?id=' . $histo[$key]['id'] . '">' . img_object($langs->trans("ShowEMailing"), "email") . ' ';
5821 $transcode = $langs->transnoentitiesnoconv("Action" . $histo[$key]['acode']);
5822 $tmplabel = ($transcode != "Action" . $histo[$key]['acode'] ? $transcode : 'Send mass mailing');
5823 $out .= dol_escape_htmltag(dol_trunc($tmplabel, 120));
5824 } else {
5825 $tmplabel .= $histo[$key]['note'];
5826 $out .= dol_escape_htmltag(dol_trunc($tmplabel, 120));
5827 }
5828 }
5829 $out = preg_replace('/ - $/', '', $out); // Remove ending ' - '
5830
5831 if (isset($histo[$key]['elementtype']) && !empty($histo[$key]['fk_element'])) {
5832 if (isset($conf->cache['elementlinkcache'][$histo[$key]['elementtype']]) && isset($conf->cache['elementlinkcache'][$histo[$key]['elementtype']][$histo[$key]['fk_element']])) {
5833 $link = $conf->cache['elementlinkcache'][$histo[$key]['elementtype']][$histo[$key]['fk_element']];
5834 } else {
5835 if (!isset($conf->cache['elementlinkcache'][$histo[$key]['elementtype']])) {
5836 $conf->cache['elementlinkcache'][$histo[$key]['elementtype']] = array();
5837 }
5838 $link = dolGetElementUrl($histo[$key]['fk_element'], $histo[$key]['elementtype'], 1);
5839 $conf->cache['elementlinkcache'][$histo[$key]['elementtype']][$histo[$key]['fk_element']] = $link;
5840 }
5841
5842 // We do not show if link if on object we are filtering on (no need to show the link to ticket X when we are on page of events for the ticket X)
5843 $showlink = 1;
5844 if (is_object($filterobj) && get_class($filterobj) == 'Ticket') {
5845 if ($histo[$key]['elementtype'] == 'ticket') {
5846 $showlink = 0;
5847 }
5848 }
5849
5850 if ($link && $showlink) {
5851 $out .= ' - ' . $link;
5852 }
5853 }
5854
5855 $out .= '</div>';
5856
5857 $out .= '</h3>';
5858
5859 // Message
5860 if ($actionstatic->code == 'AC_TICKET_CREATE') {
5861 $newmess = $filterobj->message;
5862 } else {
5863 $newmess = $histo[$key]['message'];
5864 }
5865 if (
5866 !empty($newmess && $newmess != $tmplabel)
5867 && $actionstatic->code != 'AC_TICKET_MODIFY'
5868 ) {
5869 $out .= '<div class="timeline-body wordbreak small">';
5870 $truncateLines = getDolGlobalInt('MAIN_TRUNCATE_TIMELINE_MESSAGE', 3);
5871 $truncatedText = dolGetFirstLineOfText($newmess, $truncateLines);
5872 // dolGetFirstLineOfText() cuts on <br> without caring about tag balance, so a message wrapped in
5873 // a block tag leaves the excerpt with an unclosed tag. The browser then nests the read more link
5874 // and the full text inside the excerpt, and hiding the excerpt hides the whole message (#39035).
5875 $truncatedText = dolCloseUnclosedHtmlTags($truncatedText);
5876 if ($truncateLines > 0 && strlen($newmess) > strlen($truncatedText)) {
5877 $out .= '<div class="readmore-block --closed" >';
5878 $out .= ' <div class="readmore-block__excerpt">';
5879 $out .= dolPrintHTML($truncatedText, 0, array('pre', 'code'));
5880 $out .= ' <br><a class="read-more-link" data-read-more-action="open" href="' . DOL_MAIN_URL_ROOT . '/comm/action/card.php?id=' . $actionstatic->id . '&backtopage=' . urlencode($_SERVER["PHP_SELF"] . '?' . $param) . '" >' . $langs->trans("ReadMore") . ' <span class="fa fa-chevron-right" aria-hidden="true"></span></a>';
5881 $out .= ' </div>';
5882 $out .= ' <div class="readmore-block__full-text" >';
5883
5884 $out .= dolPrintHTML($newmess, 0, array('pre', 'code'));
5885
5886 $out .= ' <a class="read-less-link" data-read-more-action="close" href="#" ><span class="fa fa-chevron-up" aria-hidden="true"></span> ' . $langs->trans("ReadLess") . '</a>';
5887 $out .= ' </div>';
5888 $out .= '</div>';
5889 } else {
5890 $out .= dolPrintHTML($newmess, 0, array('pre', 'code'));
5891 }
5892 $out .= '</div>';
5893 }
5894
5895 // Timeline footer
5896 $footer = '';
5897
5898 // Contact for this action
5899 if (isset($histo[$key]['socpeopleassigned']) && is_array($histo[$key]['socpeopleassigned']) && count($histo[$key]['socpeopleassigned']) > 0) {
5900 $contactList = '';
5901 foreach ($histo[$key]['socpeopleassigned'] as $cid => $Tab) {
5902 if (empty($conf->cache['contact'][$cid])) {
5903 $contact = new Contact($db);
5904 $result = $contact->fetch($cid);
5905 $conf->cache['contact'][$cid] = $contact;
5906 } else {
5907 $contact = $conf->cache['contact'][$cid];
5908 $result = ($contact instanceof Contact) ? $contact->id : 0;
5909 }
5910
5911 if ($result > 0) {
5912 $contactList .= !empty($contactList) ? ', ' : '';
5913 $contactList .= $contact->getNomUrl(1);
5914 if (isset($histo[$key]['acode']) && $histo[$key]['acode'] == 'AC_TEL') {
5915 if (!empty($contact->phone_pro)) {
5916 $contactList .= '(' . dol_print_phone($contact->phone_pro) . ')';
5917 }
5918 }
5919 }
5920 }
5921
5922 $footer .= $langs->trans('ActionOnContact') . ' : ' . $contactList;
5923 } elseif (empty($objcon->id) && isset($histo[$key]['contact_id']) && $histo[$key]['contact_id'] > 0) {
5924 if (empty($conf->cache['contact'][$histo[$key]['contact_id']])) {
5925 $contact = new Contact($db);
5926 $result = $contact->fetch($histo[$key]['contact_id']);
5927 $conf->cache['contact'][$histo[$key]['contact_id']] = $contact;
5928 } else {
5929 $contact = $conf->cache['contact'][$histo[$key]['contact_id']];
5930 $result = ($contact instanceof Contact) ? $contact->id : 0;
5931 }
5932
5933 if ($result > 0) {
5934 $footer .= $contact->getNomUrl(1);
5935 if (isset($histo[$key]['acode']) && $histo[$key]['acode'] == 'AC_TEL') {
5936 if (!empty($contact->phone_pro)) {
5937 $footer .= '(' . dol_print_phone($contact->phone_pro) . ')';
5938 }
5939 }
5940 }
5941 }
5942
5943 $documents = getActionCommEcmList($actionstatic);
5944 if (!empty($documents)) {
5945 $footer .= '<div class="timeline-documents-container">';
5946 foreach ($documents as $doc) {
5947 $footer .= '<span id="document_' . $doc->id . '" class="timeline-documents" ';
5948 $footer .= ' data-id="' . $doc->id . '" ';
5949 $footer .= ' data-path="' . $doc->filepath . '"';
5950 $footer .= ' data-filename="' . dol_escape_htmltag($doc->filename) . '" ';
5951 $footer .= '>';
5952
5953 $filePath = DOL_DATA_ROOT . '/' . $doc->filepath . '/' . $doc->filename;
5954 $mime = dol_mimetype($filePath);
5955 if (empty($doc->agenda_id)) {
5956 $dir_ref = $actionstatic->id;
5957 $modulepart = 'actions';
5958 } else {
5959 $split_dir = explode('/', $doc->filepath);
5960 $modulepart = array_shift($split_dir);
5961 $dir_ref = implode('/', $split_dir);
5962 }
5963
5964 $file = $dir_ref . '/' . $doc->filename;
5965 $thumb = $dir_ref . '/thumbs/' . substr($doc->filename, 0, strrpos($doc->filename, '.')) . '_mini' . substr($doc->filename, strrpos($doc->filename, '.'));
5966 $doclink = dol_buildpath('document.php', 1) . '?modulepart=' . $modulepart . '&attachment=0&file=' . urlencode($file) . '&entity=' . $conf->entity;
5967 $viewlink = dol_buildpath('viewimage.php', 1) . '?modulepart=' . $modulepart . '&file=' . urlencode($thumb) . '&entity=' . $conf->entity;
5968
5969
5970
5971 $mimeAttr = ' mime="' . $mime . '" ';
5972 $class = '';
5973 if (in_array($mime, array('image/png', 'image/jpeg', 'application/pdf'))) {
5974 $class .= ' documentpreview';
5975 }
5976
5977 $footer .= '<a href="' . $doclink . '" class="btn-link ' . $class . '" target="_blank" rel="noopener noreferrer" ' . $mimeAttr . ' >';
5978 $footer .= img_mime($filePath) . ' ' . $doc->filename;
5979 $footer .= '</a>';
5980
5981 $footer .= '</span>';
5982 }
5983 $footer .= '</div>';
5984 }
5985
5986 if (!empty($footer)) {
5987 $out .= '<div class="timeline-footer">' . $footer . '</div>';
5988 }
5989
5990 $out .= '</div>' . "\n"; // end timeline-item
5991
5992 $out .= '</li>';
5993 $out .= '<!-- END timeline item -->';
5994 }
5995
5996 $out .= "</ul>\n";
5997
5998 // Code to manage the click on button data-read-more-action to show full description of an event
5999 $out .= '<script>
6000 jQuery(document).ready(function () {
6001 $(document).on("click", "[data-read-more-action]", function(e){
6002 console.log("We click on data-read-more-action");
6003 let readMoreBloc = $(this).closest(".readmore-block");
6004 if(readMoreBloc.length > 0){
6005 e.preventDefault();
6006 if($(this).attr("data-read-more-action") == "close"){
6007 readMoreBloc.addClass("--closed").removeClass("--open");
6008 $("html, body").animate({
6009 scrollTop: readMoreBloc.offset().top - 200
6010 }, 100);
6011 }else{
6012 readMoreBloc.addClass("--open").removeClass("--closed");
6013 }
6014 }
6015 });
6016 });
6017 </script>';
6018
6019
6020 if (empty($histo)) {
6021 $out .= '<span class="opacitymedium">' . $langs->trans("NoRecordFound") . '</span>';
6022 }
6023
6024 if ($num > $MAXWITHOUTPAGINATION) {
6025 $langs->load("errors");
6026 $out .= '<center><span class="opacitymedium">...' . $langs->trans("WarningTooManyDataPleaseUseMoreFilters", $MAXWITHOUTPAGINATION) . '...</span></center>';
6027 }
6028 }
6029
6030 if ($noprint) {
6031 return $out;
6032 } else {
6033 print $out;
6034 return null;
6035 }
6036}
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:47
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
$propal type
'integer', 'integer:ObjectClass:PathToClass[:AddCreateButtonOrNot[:Filter[:Sortfield]]]',...
Definition propal.php:280
ajax_combobox($htmlname, $events=array(), $minLengthToAutocomplete=0, $forcefocus=0, $widthTypeOfAutocomplete='resolve', $idforemptyvalue='-1', $morecss='', $placeholder='')
Convert a html select field into an ajax combobox.
Definition ajax.lib.php:477
ajax_object_onoff($object, $code, $field, $text_on, $text_off, $input=array(), $morecss='', $htmlname='', $forcenojs=0, $moreparam='', $readonly=0)
On/off button to change a property status of an object This uses the ajax service objectonoff....
Definition ajax.lib.php:812
Class to manage agenda events (actions)
Class to manage different types of events.
Class to manage contact/addresses.
Class to manage building of HTML components.
Class to manage generation of HTML components Only common components must be here.
Class to manage third parties objects (customers, suppliers, prospects...)
Class to manage translations.
Class to manage Dolibarr users.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
dol_convert_file($fileinput, $ext='png', $fileoutput='', $page='')
Convert a PDF file into another image format.
dragAndDropFileUpload($htmlname)
Function to manage the drag and drop of a file.
dolGetElementUrl($objectid, $objecttype, $withpicto=0, $option='')
Return link url to an object.
dol_now($mode='gmt')
Return date for now.
dol_print_phone($phone, $countrycode='', $contactid=0, $socid=0, $addlink='', $separ="&nbsp;", $withpicto='', $titlealt='', $adddivfloat=0, $morecss='paddingright')
Format phone numbers according to country.
dol_mimetype($file, $default='application/octet-stream', $mode=0)
Return MIME type of a file from its name with extension.
dolGetFirstLineOfText($text, $nboflines=1, $charset='UTF-8')
Return first line of text.
dol_string_nohtmltag($stringtoclean, $removelinefeed=1, $pagecodeto='UTF-8', $strip_tags=0, $removedoublespaces=1)
Clean a string from all HTML tags and entities.
getCallerInfoString()
Get caller info as a string that can be appended to a log message.
dolBuildUrl($url, $params=[], $addtoken=false, $anchor='')
Return path of url.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0, $allowdash=0)
Clean a string to use it as a file name.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
natural_search($fields, $value, $mode=0, $nofirstand=0, $sqltoadd='')
Generate natural SQL search string for a criteria (this criteria can be tested on one or several fiel...
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_escape_js($stringtoescape, $mode=0, $noescapebackslashn=0)
Returns text escaped for inclusion into JavaScript code.
newToken()
Return the value of token currently saved into session with name 'newtoken'.
dol_htmlentities($string, $flags=ENT_QUOTES|ENT_SUBSTITUTE, $encoding='UTF-8', $double_encode=false)
Replace htmlentities functions.
dol_textishtml($msg, $option=0)
Return if a text is a html content.
dol_escape_uri($stringtoescape)
Returns text escaped by RFC 3986 for inclusion into a clickable link.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
dolCloseUnclosedHtmlTags($text)
Close the HTML tags left open in a truncated HTML string.
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '...' if string larger than length.
getNonce()
Return a random string to be used as a nonce value for js.
dol_htmlentitiesbr($stringtoencode, $nl2brmode=0, $pagecodefrom='UTF-8', $removelasteolbr=1)
This function is called to encode a string into a HTML string but differs from htmlentities because a...
dol_htmlwithnojs($stringtoencode, $nouseofiframesandbox=0, $check='restricthtml')
Sanitize a HTML to remove js, dangerous content and external links.
getActionCommEcmList($object)
getActionCommEcmList
dol_string_onlythesehtmltags($stringtoclean, $cleanalsosomestyles=1, $removeclassattribute=1, $cleanalsojavascript=0, $allowiframe=0, $allowed_tags=array(), $allowlink=0, $allowscript=0, $allowstyle=0, $allowphp=0)
Clean a string to keep only desirable HTML tags.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
utf8_check($str)
Check if a string is in UTF8.
get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart='')
Return a path to have a the directory according to object where files are stored.
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_getdate($timestamp, $fast=false, $forcetimezone='')
Return an array with locale date info.
multi select button
0 = Do not include form tag and submit button -1 = Do not include form tag but include submit button
treeview li table
No Email.
img_weather($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $morecss='')
Show weather picto.
finishSimpleTable($addLineBreak=false)
Add the correct HTML close tags for "startSimpleTable(...)" (use after the last table line)
show_actions_messaging($conf, $langs, $db, $filterobj, $objcon=null, $noprint=0, $actioncode='', $donetodo='done', $filters=array(), $sortfield='a.datep, a.id', $sortorder='DESC')
Show html area with actions in messaging format.
img_credit_card($brand, $morecss='fa-2x inline-block valignmiddle')
Return image of a credit card according to its brand name.
print_liste_field_titre($name, $file="", $field="", $begin="", $param="", $moreattrib="", $sortfield="", $sortorder="", $prefix="", $tooltip="", $forcenowrapcolumntitle=0)
Show title line of an array.
img_right($titlealt='default', $selected=0, $moreatt='')
Show right arrow logo.
commonHtmlAttributeBuilder($attr, array $unescapedAttr=[])
Builds an array of safe and properly escaped HTML attributes from a key-value pair list.
showValueWithClipboardCPButton($valuetocopy, $showonlyonhover=1, $texttoshow='')
Create a button to copy $valuetocopy in the clipboard (for copy and paste feature).
img_left($titlealt='default', $selected=0, $moreatt='')
Show left arrow logo.
getListOfHtmlBooleanAttributes()
Returns a list of HTML boolean attributes.
dol_get_fiche_head($links=array(), $active='', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limittoshow=0, $moretabssuffix='', $dragdropfile=0, $morecssdiv='')
Show tabs of a record.
Definition html.lib.php:540
dolGetButtonTitle($label, $helpText='', $iconClass='fa fa-file', $url='', $id='', $status=1, $params=array())
Function dolGetButtonTitle : this kind of buttons are used in title in list.
dolOutputDates($datep, $datef=null, $fullday=0, $addseconds=0, $pictotoadd='', $tzoutput='tzuserrel', $reduceformat=0)
Print decorated date-hour.
dol_get_fiche_end($notab=0)
Return tab footer of a card.
Definition html.lib.php:738
img_action($titlealt, $numaction, $picto='', $moreatt='')
Show logo action.
img_down($titlealt='default', $selected=0, $moreclass='')
Show down arrow logo.
getPictoForType($key, $morecss='')
Return the picto for a data type.
img_allow($allow, $titlealt='default')
Show tick logo if allowed.
dolButtonToOpenExportDialog($name, $label, $buttonstring, $exportSiteName, $overwriteGitUrl, $website)
Create a dialog with two buttons for export and overwrite of a website.
Definition html.lib.php:341
img_mime($file, $titlealt='', $morecss='')
Show MIME img of a file.
ajax_autoselect($htmlname, $addlink='', $textonlink='Link')
Make content of an input box selected when we click into input field.
dolCompletUrlForDropdownButton(string $url, array $params, bool $addDolUrlRoot=true)
An function to complete dropdown url in dolGetButtonAction.
showDimensionInBestUnit($dimension, $unit, $type, $outputlangs, $round=-1, $forceunitoutput='no', $use_short_label=0)
Output a dimension with best unit.
img_picto_common($titlealt, $picto, $moreatt='', $pictoisfullpath=0, $notitle=0)
Show picto (generic function)
img_search($titlealt='default', $other='')
Show search logo.
showTotalAmount($amount)
Style total amount of an object.
img_previous($titlealt='default', $moreatt='')
Show previous logo.
fieldLabel($langkey, $fieldkey, $fieldrequired=0)
Show a string with the label tag dedicated to the HTML edit field.
img_printer($titlealt="default", $other='')
Show printer logo.
dol_htmloutput_events($disabledoutputofmessages=0)
Print formatted messages to output (Used to show messages on html output).
getTitleFieldOfList($name, $thead=0, $file="", $field="", $begin="", $moreparam="", $moreattrib="", $sortfield="", $sortorder="", $prefix="", $disablesortlink=0, $tooltip='', $forcenowrapcolumntitle=0)
Get title line of an array.
getImgPictoConv($mode='fa')
Get array to convert the Dolibarr picto keys into Font awesome keys.
dolGetButtonTitleSeparator($moreClass="")
Add space between dolGetButtonTitle.
img_split($titlealt='default', $other='class="pictosplit"')
Show split logo.
dol_print_error_email($prefixcode, $errormessage='', $errormessages=array(), $morecss='error', $email='')
Show a public email and error code to contact if technical error.
dol_htmloutput_mesg($mesgstring='', $mesgarray=array(), $style='ok', $keepembedded=0)
Print formatted messages to output (Used to show messages on html output).
img_next($titlealt='default', $moreatt='')
Show next logo.
load_fiche_titre($title, $morehtmlright='', $picto='generic', $pictoisfullpath=0, $id='', $morecssontable='', $morehtmlcenter='', $morecssonpicto='widthpictotitle')
Load a title with picto.
get_htmloutput_mesg($mesgstring='', $mesgarray=[], $style='ok', $keepembedded=0)
Get formatted messages to output (Used to show messages on html output).
print_fleche_navigation($page, $file, $options='', $nextpage=0, $betweenarrows='', $afterarrows='', $limit=-1, $totalnboflines=0, $selectlimitsuffix='', $beforearrows='', $hidenavigation=0)
Function to show navigation arrows into lists.
addSummaryTableLine($tableColumnCount, $num, $nbofloop=0, $total=0, $noneWord="None", $extraRightColumn=false)
Add a summary line to the current open table ("None", "XMoreLines" or "Total xxx")
img_searchclear($titlealt='default', $other='')
Show search logo.
img_up($titlealt='default', $selected=0, $moreclass='')
Show top arrow logo.
info_admin($text, $infoonimgalt=0, $nodiv=0, $admin='1', $morecss='hideonsmartphone', $textfordropdown='', $picto='', $textonpictotooltip='', $cssfordropdown='info_admin')
Show information in HTML for admin users or standard users.
startSimpleTable($header, $link="", $arguments="", $emptyColumns=0, $number=-1, $pictofulllist='')
Start a table with headers and a optional clickable number (don't forget to use "finishSimpleTable()"...
getFieldErrorIcon($fieldValidationErrorMsg)
get field error icon
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Definition html.lib.php:181
if(!defined( 'NOREQUIREMENU')) if(!empty(GETPOST('seteventmessages', 'alpha'))) if(!function_exists("llxHeader")) top_httphead($contenttype='text/html', $forcenocache=0)
Show HTTP header.
measuringUnitString($unitid, $measuring_style='', $unitscale=null, $use_short_label=0, $outputlangs=null)
Return translation label of a unit key.
$conf db name
Only used if Module[ID]Name translation string is not found.
Definition repair.php:140