dolGetStatus()   F
last analyzed

Complexity

Conditions 38
Paths 12886

Size

Total Lines 87
Code Lines 60

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 38
eloc 60
c 1
b 0
f 0
nc 12886
nop 7
dl 0
loc 87
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/* Copyright (C) 2000-2007  Rodolphe Quiedeville        <[email protected]>
4
 * Copyright (C) 2003		Jean-Louis Bergamo			<[email protected]>
5
 * Copyright (C) 2004-2022	Laurent Destailleur			<[email protected]>
6
 * Copyright (C) 2004		Sebastien Di Cintio			<[email protected]>
7
 * Copyright (C) 2004		Benoit Mortier				<[email protected]>
8
 * Copyright (C) 2004		Christophe Combelles		<[email protected]>
9
 * Copyright (C) 2005-2019	Regis Houssin				<[email protected]>
10
 * Copyright (C) 2008		Raphael Bertrand (Resultic)	<[email protected]>
11
 * Copyright (C) 2010-2018	Juanjo Menent				<[email protected]>
12
 * Copyright (C) 2013		Cédric Salvador				<[email protected]>
13
 * Copyright (C) 2013-2021	Alexandre Spangaro			<[email protected]>
14
 * Copyright (C) 2014		Cédric GROSS				<[email protected]>
15
 * Copyright (C) 2014-2015	Marcos García				<[email protected]>
16
 * Copyright (C) 2015		Jean-François Ferry			<[email protected]>
17
 * Copyright (C) 2018-2024  Frédéric France             <[email protected]>
18
 * Copyright (C) 2019-2023  Thibault Foucart            <[email protected]>
19
 * Copyright (C) 2020       Open-Dsi         			<[email protected]>
20
 * Copyright (C) 2021       Gauthier VERDOL         	<[email protected]>
21
 * Copyright (C) 2022       Anthony Berton	         	<[email protected]>
22
 * Copyright (C) 2022       Ferran Marcet           	<[email protected]>
23
 * Copyright (C) 2022       Charlene Benke           	<[email protected]>
24
 * Copyright (C) 2023       Joachim Kueter              <[email protected]>
25
 * Copyright (C) 2024		MDW							<[email protected]>
26
 * Copyright (C) 2024		Lenin Rivas					<[email protected]>
27
 * Copyright (C) 2024       Rafael San José             <[email protected]>
28
 *
29
 * This program is free software; you can redistribute it and/or modify
30
 * it under the terms of the GNU General Public License as published by
31
 * the Free Software Foundation; either version 3 of the License, or
32
 * (at your option) any later version.
33
 *
34
 * This program is distributed in the hope that it will be useful,
35
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
36
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
37
 * GNU General Public License for more details.
38
 *
39
 * You should have received a copy of the GNU General Public License
40
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
41
 * or see https://www.gnu.org/
42
 */
43
44
/**
45
 * TODO: Convert to abstract class with static methods
46
 */
47
48
use Dolibarr\Code\Bom\Classes\BOM;
49
use Dolibarr\Code\Categories\Classes\Categorie;
50
use Dolibarr\Code\Comm\Classes\ActionComm;
51
use Dolibarr\Code\Comm\Classes\CActionComm;
52
use Dolibarr\Code\Contact\Classes\Contact;
53
use Dolibarr\Code\Core\Classes\Conf;
54
use Dolibarr\Code\Core\Classes\Form;
55
use Dolibarr\Code\Core\Classes\FormActions;
56
use Dolibarr\Code\Core\Classes\HookManager;
57
use Dolibarr\Code\Core\Classes\Translate;
58
use Dolibarr\Code\Product\Classes\Product;
59
use Dolibarr\Code\Societe\Classes\Societe;
60
use Dolibarr\Code\Ticket\Classes\Ticket;
61
use Dolibarr\Code\User\Classes\User;
62
use Dolibarr\Code\Website\Classes\Website;
63
use Dolibarr\Core\Base\CommonObject;
64
use Dolibarr\Lib\Filters;
65
66
/**
67
 * Create a dialog with two buttons for export and overwrite of a website
68
 *
69
 * @param string $name Unique identifier for the dialog
70
 * @param string $label Title of the dialog
71
 * @param string $buttonstring Text for the button that opens the dialog
72
 * @param string $exportSiteName Name of the "submit" input for site export
73
 * @param string $overwriteGitUrl URL for the link that triggers the overwrite action in GIT
74
 * @param Website $website Website object
75
 * @return  string                  HTML and JavaScript code for the button and the dialog
76
 */
77
function dolButtonToOpenExportDialog($name, $label, $buttonstring, $exportSiteName, $overwriteGitUrl, $website)
78
{
79
    global $langs, $db;
80
81
    $form = new Form($db);
82
83
    $templatenameforexport = $website->name_template;   // Example 'website_template-corporate'
84
    if (empty($templatenameforexport)) {
85
        $templatenameforexport = 'website_' . $website->ref;
86
    }
87
88
    $out = '';
89
    $out .= '<input type="button" class="cursorpointer button bordertransp" id="open-dialog-' . $name . '"  value="' . dol_escape_htmltag($buttonstring) . '"/>';
90
91
    // for generate popup
92
    $out .= '<script nonce="' . getNonce() . '" type="text/javascript">';
93
    $out .= 'jQuery(document).ready(function () {';
94
    $out .= '  jQuery("#open-dialog-' . $name . '").click(function () {';
95
    $out .= '    var dialogHtml = \'';
96
97
    $dialogcontent = '      <div id="custom-dialog-' . $name . '">';
98
    $dialogcontent .= '        <div style="margin-top: 20px;">';
99
    $dialogcontent .= '          <label for="export-site-' . $name . '"><strong>' . $langs->trans("ExportSiteLabel") . '...</label><br>';
100
    $dialogcontent .= '          <button class="button smallpaddingimp" id="export-site-' . $name . '">' . dol_escape_htmltag($langs->trans("DownloadZip")) . '</button>';
101
    $dialogcontent .= '        </div>';
102
    $dialogcontent .= '        <br>';
103
    $dialogcontent .= '        <div style="margin-top: 20px;">';
104
    $dialogcontent .= '          <strong>' . $langs->trans("ExportSiteGitLabel") . ' ' . $form->textwithpicto('', $langs->trans("SourceFiles"), 1, 'help', '', 0, 3, '') . '</strong><br>';
105
    $dialogcontent .= '     		<form action="' . dol_escape_htmltag($overwriteGitUrl) . '" method="POST">';
106
    $dialogcontent .= '        		<input type="hidden" name="action" value="overwritesite">';
107
    $dialogcontent .= '        		<input type="hidden" name="token" value="' . newToken() . '">';
108
    $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>';
109
    $dialogcontent .= '          		<button type="submit" class="button smallpaddingimp" id="overwrite-git-' . $name . '">' . dol_escape_htmltag($langs->trans("ExportIntoGIT")) . '</button>';
110
    $dialogcontent .= '      		</form>';
111
    $dialogcontent .= '        </div>';
112
    $dialogcontent .= '      </div>';
113
114
    $out .= dol_escape_js($dialogcontent);
115
116
    $out .= '\';';
117
118
119
    // Add the content of the dialog to the body of the page
120
    $out .= '    var $dialog = jQuery("#custom-dialog-' . $name . '");';
121
    $out .= ' if ($dialog.length > 0) {
122
        $dialog.remove();
123
    }
124
    jQuery("body").append(dialogHtml);';
125
126
    // Configuration of popup
127
    $out .= '    jQuery("#custom-dialog-' . $name . '").dialog({';
128
    $out .= '      autoOpen: false,';
129
    $out .= '      modal: true,';
130
    $out .= '      height: 290,';
131
    $out .= '      width: "40%",';
132
    $out .= '      title: "' . dol_escape_js($label) . '",';
133
    $out .= '    });';
134
135
    // Simulate a click on the original "submit" input to export the site.
136
    $out .= '    jQuery("#export-site-' . $name . '").click(function () {';
137
    $out .= '      console.log("Clic on exportsite.");';
138
    $out .= '      var target = jQuery("input[name=\'' . dol_escape_js($exportSiteName) . '\']");';
139
    $out .= '      console.log("element founded:", target.length > 0);';
140
    $out .= '      if (target.length > 0) { target.click(); }';
141
    $out .= '      jQuery("#custom-dialog-' . $name . '").dialog("close");';
142
    $out .= '    });';
143
144
    // open popup
145
    $out .= '    jQuery("#custom-dialog-' . $name . '").dialog("open");';
146
    $out .= '    return false;';
147
    $out .= '  });';
148
    $out .= '});';
149
    $out .= '</script>';
150
151
    return $out;
152
}
153
154
/**
155
 *  Return HTML code to output a button to open a dialog popup box.
156
 *  Such buttons must be included inside a HTML form.
157
 *
158
 * @param string $name A name for the html component
159
 * @param string $label Label shown in Popup title top bar
160
 * @param string $buttonstring button string (HTML text we can click on)
161
 * @param string $url Relative Url to open. For example '/project/card.php'
162
 * @param string $disabled Disabled text
163
 * @param string $morecss More CSS
164
 * @param string $jsonopen Some JS code to execute on click/open of popup
165
 * @param string $backtopagejsfields The back to page must be managed using javascript instead of a redirect.
166
 *                                      Value is 'keyforpopupid:Name_of_html_component_to_set_with id,Name_of_html_component_to_set_with_label'
167
 * @param string $accesskey A key to use shortcut
168
 * @return string                      HTML component with button
169
 */
170
function dolButtonToOpenUrlInDialogPopup($name, $label, $buttonstring, $url, $disabled = '', $morecss = 'classlink button bordertransp', $jsonopen = '', $backtopagejsfields = '', $accesskey = '')
171
{
172
    global $conf;
173
174
    if (strpos($url, '?') > 0) {
175
        $url .= '&dol_hide_topmenu=1&dol_hide_leftmenu=1&dol_openinpopup=' . urlencode($name);
176
    } else {
177
        $url .= '?dol_hide_topmenu=1&dol_hide_leftmenu=1&dol_openinpopup=' . urlencode($name);
178
    }
179
180
    $out = '';
181
182
    $backtopagejsfieldsid = '';
183
    $backtopagejsfieldslabel = '';
184
    if ($backtopagejsfields) {
185
        $tmpbacktopagejsfields = explode(':', $backtopagejsfields);
186
        if (empty($tmpbacktopagejsfields[1])) { // If the part 'keyforpopupid:' is missing, we add $name for it.
187
            $backtopagejsfields = $name . ":" . $backtopagejsfields;
188
            $tmp2backtopagejsfields = explode(',', $tmpbacktopagejsfields[0]);
189
        } else {
190
            $tmp2backtopagejsfields = explode(',', $tmpbacktopagejsfields[1]);
191
        }
192
        $backtopagejsfieldsid = empty($tmp2backtopagejsfields[0]) ? '' : $tmp2backtopagejsfields[0];
193
        $backtopagejsfieldslabel = empty($tmp2backtopagejsfields[1]) ? '' : $tmp2backtopagejsfields[1];
194
        $url .= '&backtopagejsfields=' . urlencode($backtopagejsfields);
195
    }
196
197
    //print '<input type="submit" class="button bordertransp"'.$disabled.' value="'.dol_escape_htmltag($langs->trans("MediaFiles")).'" name="file_manager">';
198
    $out .= '<!-- a link for button to open url into a dialog popup with backtopagejsfields = ' . $backtopagejsfields . ' -->';
199
    $out .= '<a ' . ($accesskey ? ' accesskey="' . $accesskey . '"' : '') . ' class="cursorpointer reposition button_' . $name . ($morecss ? ' ' . $morecss : '') . '"' . $disabled . ' title="' . dol_escape_htmltag($label) . '"';
200
    if (empty($conf->use_javascript_ajax)) {
201
        $out .= ' href="' . constant('DOL_URL_ROOT') . $url . '" target="_blank"';
202
    } elseif ($jsonopen) {
203
        $out .= ' href="#" onclick="' . $jsonopen . '"';
204
    } else {
205
        $out .= ' href="#"';
206
    }
207
    $out .= '>' . $buttonstring . '</a>';
208
209
    if (!empty($conf->use_javascript_ajax)) {
210
        // Add code to open url using the popup. Add also hidden field to retrieve the returned variables
211
        $out .= '<!-- code to open popup and variables to retrieve returned variables -->';
212
        $out .= '<div id="idfordialog' . $name . '" class="hidden">' . (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2 ? 'div for dialog' : '') . '</div>';
213
        $out .= '<div id="varforreturndialogid' . $name . '" class="hidden">' . (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2 ? 'div for returned id' : '') . '</div>';
214
        $out .= '<div id="varforreturndialoglabel' . $name . '" class="hidden">' . (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2 ? 'div for returned label' : '') . '</div>';
215
216
        $out .= '<!-- Add js code to open dialog popup on dialog -->';
217
        $out .= '<script nonce="' . getNonce() . '" type="text/javascript">
218
					jQuery(document).ready(function () {
219
						jQuery(".button_' . $name . '").click(function () {
220
							console.log(\'Open popup with jQuery(...).dialog() on URL ' . dol_escape_js(DOL_URL_ROOT . $url) . '\');
221
							var $tmpdialog = $(\'#idfordialog' . $name . '\');
222
							$tmpdialog.html(\'<iframe class="iframedialog" id="iframedialog' . $name . '" style="border: 0px;" src="' . constant('DOL_URL_ROOT') . $url . '" width="100%" height="98%"></iframe>\');
223
							$tmpdialog.dialog({
224
								autoOpen: false,
225
							 	modal: true,
226
							 	height: (window.innerHeight - 150),
227
							 	width: \'80%\',
228
							 	title: \'' . dol_escape_js($label) . '\',
229
								open: function (event, ui) {
230
									console.log("open popup name=' . $name . ', backtopagejsfields=' . $backtopagejsfields . '");
231
	       						},
232
								close: function (event, ui) {
233
									var returnedid = jQuery("#varforreturndialogid' . $name . '").text();
234
									var returnedlabel = jQuery("#varforreturndialoglabel' . $name . '").text();
235
									console.log("popup has been closed. returnedid (js var defined into parent page)="+returnedid+" returnedlabel="+returnedlabel);
236
									if (returnedid != "" && returnedid != "div for returned id") {
237
										jQuery("#' . (empty($backtopagejsfieldsid) ? "none" : $backtopagejsfieldsid) . '").val(returnedid);
238
									}
239
									if (returnedlabel != "" && returnedlabel != "div for returned label") {
240
										jQuery("#' . (empty($backtopagejsfieldslabel) ? "none" : $backtopagejsfieldslabel) . '").val(returnedlabel);
241
									}
242
								}
243
							});
244
245
							$tmpdialog.dialog(\'open\');
246
							return false;
247
						});
248
					});
249
				</script>';
250
    }
251
    return $out;
252
}
253
254
/**
255
 *  Show tabs of a record
256
 *
257
 * @param array<int,array<int<0,5>,string>> $links Array of tabs (0=>url, 1=>label, 2=>code, 3=>not used, 4=>text after link, 5=>morecssonlink). Currently initialized by calling a function xxx_admin_prepare_head. Note that label into $links[$i][1] must be already HTML escaped.
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<int,array<int<0,5>,string>> at position 6 could not be parsed: Expected '>' at position 6, but found 'int'.
Loading history...
258
 * @param string $active Active tab name
259
 * @param string $title Title
260
 * @param int $notab -1 or 0=Add tab header, 1=no tab header (if you set this to 1, using print dol_get_fiche_end() to close tab is not required), -2=Add tab header with no separation under tab (to start a tab just after), -3=-2+'noborderbottom'
261
 * @param string $picto Add a picto on tab title
262
 * @param int $pictoisfullpath If 1, image path is a full path. If you set this to 1, you can use url returned by dol_buildpath('/mymodyle/img/myimg.png',1) for $picto.
263
 * @param string $morehtmlright Add more html content on right of tabs title
264
 * @param string $morecss More CSS on the link <a>
265
 * @param int $limittoshow Limit number of tabs to show. Use 0 to use automatic default value.
266
 * @param string $moretabssuffix A suffix to use when you have several dol_get_fiche_head() in same page
267
 * @param int $dragdropfile 0 (default) or 1. 1 enable a drop zone for file to be upload, 0 disable it
268
 * @return string
269
 */
270
function dol_get_fiche_head($links = array(), $active = '', $title = '', $notab = 0, $picto = '', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limittoshow = 0, $moretabssuffix = '', $dragdropfile = 0)
271
{
272
    global $conf, $langs, $hookmanager;
273
274
    // Show title
275
    $showtitle = 1;
276
    if (!empty($conf->dol_optimize_smallscreen)) {
277
        $showtitle = 0;
278
    }
279
280
    $out = "\n" . '<!-- dol_fiche_head - dol_get_fiche_head -->';
281
282
    if ((!empty($title) && $showtitle) || $morehtmlright || !empty($links)) {
283
        $out .= '<div class="tabs' . ($picto ? '' : ' nopaddingleft') . '" data-role="controlgroup" data-type="horizontal">' . "\n";
284
    }
285
286
    // Show right part
287
    if ($morehtmlright) {
288
        $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.
289
    }
290
291
    // Show title
292
    if (!empty($title) && $showtitle && !getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
293
        $limittitle = 30;
294
        $out .= '<a class="tabTitle">';
295
        if ($picto) {
296
            $noprefix = $pictoisfullpath;
297
            if (strpos($picto, 'fontawesome_') !== false) {
298
                $noprefix = 1;
299
            }
300
            $out .= img_picto($title, ($noprefix ? '' : 'object_') . $picto, '', $pictoisfullpath, 0, 0, '', 'imgTabTitle') . ' ';
301
        }
302
        $out .= '<span class="tabTitleText">' . dol_escape_htmltag(dol_trunc($title, $limittitle)) . '</span>';
303
        $out .= '</a>';
304
    }
305
306
    // Show tabs
307
308
    // Define max of key (max may be higher than sizeof because of hole due to module disabling some tabs).
309
    $maxkey = -1;
310
    if (is_array($links) && !empty($links)) {
311
        $keys = array_keys($links);
312
        if (count($keys)) {
313
            $maxkey = max($keys);
314
        }
315
    }
316
317
    // Show tabs
318
    // if =0 we don't use the feature
319
    if (empty($limittoshow)) {
320
        $limittoshow = (!getDolGlobalString('MAIN_MAXTABS_IN_CARD') ? 99 : $conf->global->MAIN_MAXTABS_IN_CARD);
321
    }
322
    if (!empty($conf->dol_optimize_smallscreen)) {
323
        $limittoshow = 2;
324
    }
325
326
    $displaytab = 0;
327
    $nbintab = 0;
328
    $popuptab = 0;
329
    $outmore = '';
330
    for ($i = 0; $i <= $maxkey; $i++) {
331
        if ((is_numeric($active) && $i == $active) || (!empty($links[$i][2]) && !is_numeric($active) && $active == $links[$i][2])) {
332
            // If active tab is already present
333
            if ($i >= $limittoshow) {
334
                $limittoshow--;
335
            }
336
        }
337
    }
338
339
    for ($i = 0; $i <= $maxkey; $i++) {
340
        if ((is_numeric($active) && $i == $active) || (!empty($links[$i][2]) && !is_numeric($active) && $active == $links[$i][2])) {
341
            $isactive = true;
342
        } else {
343
            $isactive = false;
344
        }
345
346
        if ($i < $limittoshow || $isactive) {
347
            // Output entry with a visible tab
348
            $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])) . ' -->';
349
350
            if (isset($links[$i][2]) && $links[$i][2] == 'image') {
351
                if (!empty($links[$i][0])) {
352
                    $out .= '<a class="tabimage' . ($morecss ? ' ' . $morecss : '') . '" href="' . $links[$i][0] . '">' . $links[$i][1] . '</a>' . "\n";
353
                } else {
354
                    $out .= '<span class="tabspan">' . $links[$i][1] . '</span>' . "\n";
355
                }
356
            } elseif (!empty($links[$i][1])) {
357
                //print "x $i $active ".$links[$i][2]." z";
358
                $out .= '<div class="tab tab' . ($isactive ? 'active' : 'unactive') . '" style="margin: 0 !important">';
359
                if (!empty($links[$i][0])) {
360
                    $titletoshow = preg_replace('/<.*$/', '', $links[$i][1]);
361
                    $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) . '">';
362
                }
363
                $out .= $links[$i][1];
364
                if (!empty($links[$i][0])) {
365
                    $out .= '</a>' . "\n";
366
                }
367
                $out .= empty($links[$i][4]) ? '' : $links[$i][4];
368
                $out .= '</div>';
369
            }
370
371
            $out .= '</div>';
372
        } else {
373
            // Add entry into the combo popup with the other tabs
374
            if (!$popuptab) {
375
                $popuptab = 1;
376
                $outmore .= '<div class="popuptabset wordwrap">'; // The css used to hide/show popup
377
            }
378
            $outmore .= '<div class="popuptab wordwrap" style="display:inherit;">';
379
            if (isset($links[$i][2]) && $links[$i][2] == 'image') {
380
                if (!empty($links[$i][0])) {
381
                    $outmore .= '<a class="tabimage' . ($morecss ? ' ' . $morecss : '') . '" href="' . $links[$i][0] . '">' . $links[$i][1] . '</a>' . "\n";
382
                } else {
383
                    $outmore .= '<span class="tabspan">' . $links[$i][1] . '</span>' . "\n";
384
                }
385
            } elseif (!empty($links[$i][1])) {
386
                $outmore .= '<a' . (!empty($links[$i][2]) ? ' id="' . $links[$i][2] . '"' : '') . ' class="wordwrap inline-block' . ($morecss ? ' ' . $morecss : '') . '" href="' . $links[$i][0] . '">';
387
                $outmore .= 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.
388
                $outmore .= '</a>' . "\n";
389
            }
390
            $outmore .= '</div>';
391
392
            $nbintab++;
393
        }
394
        $displaytab = $i;
395
    }
396
    if ($popuptab) {
397
        $outmore .= '</div>';
398
    }
399
400
    if ($popuptab) {    // If there is some tabs not shown
401
        $left = ($langs->trans("DIRECTION") == 'rtl' ? 'right' : 'left');
402
        $right = ($langs->trans("DIRECTION") == 'rtl' ? 'left' : 'right');
403
        $widthofpopup = 200;
404
405
        $tabsname = $moretabssuffix;
406
        if (empty($tabsname)) {
407
            $tabsname = str_replace("@", "", $picto);
408
        }
409
        $out .= '<div id="moretabs' . $tabsname . '" class="inline-block tabsElem valignmiddle">';
410
        if (getDolGlobalInt('MAIN_OPTIMIZEFORTEXTBROWSER') < 2) {
411
            $out .= '<div class="tab valignmiddle"><a href="#" class="tab moretab inline-block tabunactive valignmiddle"><span class="hideonsmartphone">' . $langs->trans("More") . '</span>... (' . $nbintab . ')</a></div>'; // Do not use "reposition" class in the "More".
412
        }
413
        $out .= '<div id="moretabsList' . $tabsname . '" style="width: ' . $widthofpopup . 'px; position: absolute; ' . $left . ': -999em; text-align: ' . $left . '; margin:0px; padding:2px; z-index:10;">';
414
        $out .= $outmore;
415
        $out .= '</div>';
416
        $out .= '<div></div>';
417
        $out .= "</div>\n";
418
419
        $out .= '<script nonce="' . getNonce() . '">';
420
        $out .= "$('#moretabs" . $tabsname . "').mouseenter( function() {
421
			var x = this.offsetLeft, y = this.offsetTop;
422
			console.log('mouseenter " . $left . " x='+x+' y='+y+' window.innerWidth='+window.innerWidth);
423
			if ((window.innerWidth - x) < " . ($widthofpopup + 10) . ") {
424
				$('#moretabsList" . $tabsname . "').css('" . $right . "','8px');
425
			}
426
			$('#moretabsList" . $tabsname . "').css('" . $left . "','auto');
427
			});
428
		";
429
        $out .= "$('#moretabs" . $tabsname . "').mouseleave( function() { console.log('mouseleave " . $left . "'); $('#moretabsList" . $tabsname . "').css('" . $left . "','-999em');});";
430
        $out .= "</script>";
431
    }
432
433
    if ((!empty($title) && $showtitle) || $morehtmlright || !empty($links)) {
434
        $out .= "</div>\n";
435
    }
436
437
    if (!$notab || $notab == -1 || $notab == -2 || $notab == -3) {
438
        $out .= "\n" . '<div id="dragDropAreaTabBar" class="tabBar' . ($notab == -1 ? '' : ($notab == -2 ? ' tabBarNoTop' : (($notab == -3 ? ' noborderbottom' : '') . ' tabBarWithBottom')));
439
        $out .= '">' . "\n";
440
    }
441
    if (!empty($dragdropfile)) {
442
        include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
443
        $out .= dragAndDropFileUpload("dragDropAreaTabBar");
444
    }
445
    $parameters = array('tabname' => $active, 'out' => $out);
446
    $reshook = $hookmanager->executeHooks('printTabsHead', $parameters); // This hook usage is called just before output the head of tabs. Take also a look at "completeTabsHead"
447
    if ($reshook > 0) {
448
        $out = $hookmanager->resPrint;
449
    }
450
451
    return $out;
452
}
453
454
/**
455
 *  Show tab footer of a card.
456
 *  Note: $object->next_prev_filter can be set to restrict select to find next or previous record by $form->showrefnav.
457
 *
458
 * @param CommonObject $object Object to show
459
 * @param string $paramid Name of parameter to use to name the id into the URL next/previous link
460
 * @param string $morehtml More html content to output just before the nav bar
461
 * @param int $shownav Show Condition (navigation is shown if value is 1)
462
 * @param string $fieldid Name of the field in DB to use to select next et previous (we make the select max and min on this field). Use 'none' for no prev/next search.
463
 * @param string $fieldref Name of the field (object->ref) to use to select next et previous
464
 * @param string $morehtmlref More html to show after the ref (see $morehtmlleft for before)
465
 * @param string $moreparam More param to add in nav link url.
466
 * @param int $nodbprefix Do not include DB prefix to forge table name
467
 * @param string $morehtmlleft More html code to show before the ref (see $morehtmlref for after)
468
 * @param string $morehtmlstatus More html code to show under navigation arrows
469
 * @param int $onlybanner Put this to 1, if the card will contains only a banner (this add css 'arearefnobottom' on div)
470
 * @param string $morehtmlright More html code to show before navigation arrows
471
 * @return void
472
 */
473
function dol_banner_tab($object, $paramid, $morehtml = '', $shownav = 1, $fieldid = 'rowid', $fieldref = 'ref', $morehtmlref = '', $moreparam = '', $nodbprefix = 0, $morehtmlleft = '', $morehtmlstatus = '', $onlybanner = 0, $morehtmlright = '')
474
{
475
    global $conf, $form, $user, $langs, $hookmanager, $action;
476
477
    $error = 0;
478
479
    $maxvisiblephotos = 1;
480
    $showimage = 1;
481
    $entity = (empty($object->entity) ? $conf->entity : $object->entity);
482
    // @phan-suppress-next-line PhanUndeclaredMethod
483
    $showbarcode = !isModEnabled('barcode') ? 0 : (empty($object->barcode) ? 0 : 1);
0 ignored issues
show
Bug Best Practice introduced by
The property barcode does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
484
    if (getDolGlobalString('MAIN_USE_ADVANCED_PERMS') && !$user->hasRight('barcode', 'lire_advance')) {
485
        $showbarcode = 0;
486
    }
487
    $modulepart = 'unknown';
488
489
    if (in_array($object->element, ['societe', 'contact', 'product', 'ticket', 'bom'])) {
490
        $modulepart = $object->element;
491
    } elseif ($object->element == 'member') {
492
        $modulepart = 'memberphoto';
493
    } elseif ($object->element == 'user') {
494
        $modulepart = 'userphoto';
495
    }
496
497
    if (class_exists("Imagick")) {
498
        if ($object->element == 'expensereport' || $object->element == 'propal' || $object->element == 'commande' || $object->element == 'facture' || $object->element == 'supplier_proposal') {
499
            $modulepart = $object->element;
500
        } elseif ($object->element == 'fichinter' || $object->element == 'intervention') {
501
            $modulepart = 'ficheinter';
502
        } elseif ($object->element == 'contrat' || $object->element == 'contract') {
503
            $modulepart = 'contract';
504
        } elseif ($object->element == 'order_supplier') {
505
            $modulepart = 'supplier_order';
506
        } elseif ($object->element == 'invoice_supplier') {
507
            $modulepart = 'supplier_invoice';
508
        }
509
    }
510
511
    if ($object->element == 'product') {
512
        /** @var Product $object */
513
        '@phan-var-force Product $object';
514
        $width = 80;
515
        $cssclass = 'photowithmargin photoref';
516
        $showimage = $object->is_photo_available($conf->product->multidir_output[$entity]);
517
        $maxvisiblephotos = getDolGlobalInt('PRODUCT_MAX_VISIBLE_PHOTO', 5);
518
        if ($conf->browser->layout == 'phone') {
519
            $maxvisiblephotos = 1;
520
        }
521
        if ($showimage) {
522
            $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>';
523
        } else {
524
            if (getDolGlobalString('PRODUCT_NODISPLAYIFNOPHOTO')) {
525
                $nophoto = '';
526
                $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
527
            } else {    // Show no photo link
528
                $nophoto = '/public/theme/common/nophoto.png';
529
                $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><img class="photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . '" title="' . dol_escape_htmltag($langs->trans("UploadAnImageToSeeAPhotoHere", $langs->transnoentitiesnoconv("Documents"))) . '" alt="No photo"' . ($width ? ' style="width: ' . $width . 'px"' : '') . ' src="' . constant('DOL_URL_ROOT') . $nophoto . '"></div>';
530
            }
531
        }
532
    } elseif ($object->element == 'category') {
533
        /** @var Categorie $object */
534
        '@phan-var-force Categorie $object';
535
        $width = 80;
536
        $cssclass = 'photowithmargin photoref';
537
        $showimage = $object->isAnyPhotoAvailable($conf->categorie->multidir_output[$entity]);
538
        $maxvisiblephotos = getDolGlobalInt('CATEGORY_MAX_VISIBLE_PHOTO', 5);
539
        if ($conf->browser->layout == 'phone') {
540
            $maxvisiblephotos = 1;
541
        }
542
        if ($showimage) {
543
            $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>';
544
        } else {
545
            if (getDolGlobalString('CATEGORY_NODISPLAYIFNOPHOTO')) {
546
                $nophoto = '';
547
                $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
548
            } else {    // Show no photo link
549
                $nophoto = '/public/theme/common/nophoto.png';
550
                $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><img class="photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . '" title="' . dol_escape_htmltag($langs->trans("UploadAnImageToSeeAPhotoHere", $langs->transnoentitiesnoconv("Documents"))) . '" alt="No photo"' . ($width ? ' style="width: ' . $width . 'px"' : '') . ' src="' . constant('DOL_URL_ROOT') . $nophoto . '"></div>';
551
            }
552
        }
553
    } elseif ($object->element == 'bom') {
554
        /** @var BOM $object */
555
        '@phan-var-force Bom $object';
556
        $width = 80;
557
        $cssclass = 'photowithmargin photoref';
558
        $showimage = $object->is_photo_available($conf->bom->multidir_output[$entity]);
559
        $maxvisiblephotos = getDolGlobalInt('BOM_MAX_VISIBLE_PHOTO', 5);
560
        if ($conf->browser->layout == 'phone') {
561
            $maxvisiblephotos = 1;
562
        }
563
        if ($showimage) {
564
            $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>';
565
        } else {
566
            if (getDolGlobalString('BOM_NODISPLAYIFNOPHOTO')) {
567
                $nophoto = '';
568
                $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
569
            } else {    // Show no photo link
570
                $nophoto = '/public/theme/common/nophoto.png';
571
                $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><img class="photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . '" title="' . dol_escape_htmltag($langs->trans("UploadAnImageToSeeAPhotoHere", $langs->transnoentitiesnoconv("Documents"))) . '" alt="No photo"' . ($width ? ' style="width: ' . $width . 'px"' : '') . ' src="' . constant('DOL_URL_ROOT') . $nophoto . '"></div>';
572
            }
573
        }
574
    } elseif ($object->element == 'ticket') {
575
        $width = 80;
576
        $cssclass = 'photoref';
577
        /** @var Ticket $object */
578
        '@phan-var-force Ticket $object';
579
        $showimage = $object->is_photo_available($conf->ticket->multidir_output[$entity] . '/' . $object->ref);
580
        $maxvisiblephotos = getDolGlobalInt('TICKET_MAX_VISIBLE_PHOTO', 2);
581
        if ($conf->browser->layout == 'phone') {
582
            $maxvisiblephotos = 1;
583
        }
584
585
        if ($showimage) {
586
            $showphoto = $object->show_photos('ticket', $conf->ticket->multidir_output[$entity], 'small', $maxvisiblephotos, 0, 0, 0, $width, 0);
587
            if ($object->nbphoto > 0) {
588
                $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">' . $showphoto . '</div>';
589
            } else {
590
                $showimage = 0;
591
            }
592
        }
593
        if (!$showimage) {
594
            if (getDolGlobalString('TICKET_NODISPLAYIFNOPHOTO')) {
595
                $nophoto = '';
596
                $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
597
            } else {    // Show no photo link
598
                $nophoto = img_picto('No photo', 'object_ticket');
599
                $morehtmlleft .= '<!-- No photo to show -->';
600
                $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
601
                $morehtmlleft .= $nophoto;
602
                $morehtmlleft .= '</div></div>';
603
            }
604
        }
605
    } else {
606
        if ($showimage) {
607
            if ($modulepart != 'unknown' || method_exists($object, 'getDataToShowPhoto')) {
608
                $phototoshow = '';
609
                // Check if a preview file is available
610
                if (in_array($modulepart, array('propal', 'commande', 'facture', 'ficheinter', 'contract', 'supplier_order', 'supplier_proposal', 'supplier_invoice', 'expensereport')) && class_exists("Imagick")) {
611
                    $objectref = dol_sanitizeFileName($object->ref);
612
                    $dir_output = (empty($conf->$modulepart->multidir_output[$entity]) ? $conf->$modulepart->dir_output : $conf->$modulepart->multidir_output[$entity]) . "/";
613
                    if (in_array($modulepart, array('invoice_supplier', 'supplier_invoice'))) {
614
                        $subdir = get_exdir($object->id, 2, 0, 1, $object, $modulepart);
615
                        $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
616
                    } else {
617
                        $subdir = get_exdir($object->id, 0, 0, 1, $object, $modulepart);
618
                    }
619
                    if (empty($subdir)) {
620
                        $subdir = 'errorgettingsubdirofobject'; // Protection to avoid to return empty path
621
                    }
622
623
                    $filepath = $dir_output . $subdir . "/";
624
625
                    $filepdf = $filepath . $objectref . ".pdf";
626
                    $relativepath = $subdir . '/' . $objectref . '.pdf';
627
628
                    // Define path to preview pdf file (preview precompiled "file.ext" are "file.ext_preview.png")
629
                    $fileimage = $filepdf . '_preview.png';
630
                    $relativepathimage = $relativepath . '_preview.png';
631
632
                    $pdfexists = file_exists($filepdf);
633
634
                    // If PDF file exists
635
                    if ($pdfexists) {
636
                        // Conversion du PDF en image png si fichier png non existent
637
                        if (!file_exists($fileimage) || (filemtime($fileimage) < filemtime($filepdf))) {
638
                            if (!getDolGlobalString('MAIN_DISABLE_PDF_THUMBS')) {       // If you experience trouble with pdf thumb generation and imagick, you can disable here.
639
                                include_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
640
                                $ret = dol_convert_file($filepdf, 'png', $fileimage, '0'); // Convert first page of PDF into a file _preview.png
641
                                if ($ret < 0) {
642
                                    $error++;
643
                                }
644
                            }
645
                        }
646
                    }
647
648
                    if ($pdfexists && !$error) {
649
                        $heightforphotref = 80;
650
                        if (!empty($conf->dol_optimize_smallscreen)) {
651
                            $heightforphotref = 60;
652
                        }
653
                        // If the preview file is found
654
                        if (file_exists($fileimage)) {
655
                            $phototoshow = '<div class="photoref">';
656
                            $phototoshow .= '<img height="' . $heightforphotref . '" class="photo photowithborder" src="' . constant('BASE_URL') . '/viewimage.php?modulepart=apercu' . $modulepart . '&amp;file=' . urlencode($relativepathimage) . '">';
657
                            $phototoshow .= '</div>';
658
                        }
659
                    }
660
                } elseif (!$phototoshow) { // example if modulepart = 'societe' or 'photo' or 'memberphoto'
661
                    $phototoshow .= $form->showphoto($modulepart, $object, 0, 0, 0, 'photowithmargin photoref', 'small', 1, 0, $maxvisiblephotos);
662
                }
663
664
                if ($phototoshow) {
665
                    $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">';
666
                    $morehtmlleft .= $phototoshow;
667
                    $morehtmlleft .= '</div>';
668
                }
669
            }
670
671
            if (empty($phototoshow)) {      // Show No photo link (picto of object)
672
                if ($object->element == 'action') {
673
                    $width = 80;
674
                    $cssclass = 'photorefcenter';
675
                    $nophoto = img_picto('No photo', 'title_agenda');
676
                } else {
677
                    $width = 14;
678
                    $cssclass = 'photorefcenter';
679
                    $picto = $object->picto;  // @phan-suppress-current-line PhanUndeclaredProperty
0 ignored issues
show
Bug Best Practice introduced by
The property picto does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
680
                    $prefix = 'object_';
681
                    if ($object->element == 'project' && !$object->public) {  // @phan-suppress-current-line PhanUndeclaredProperty
0 ignored issues
show
Bug Best Practice introduced by
The property public does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
682
                        $picto = 'project'; // instead of projectpub
683
                    }
684
                    if (strpos($picto, 'fontawesome_') !== false) {
685
                        $prefix = '';
686
                    }
687
                    $nophoto = img_picto('No photo', $prefix . $picto);
688
                }
689
                $morehtmlleft .= '<!-- No photo to show -->';
690
                $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
691
                $morehtmlleft .= $nophoto;
692
                $morehtmlleft .= '</div></div>';
693
            }
694
        }
695
    }
696
697
    // Show barcode
698
    if ($showbarcode) {
699
        $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">' . $form->showbarcode($object, 100, 'photoref valignmiddle') . '</div>';
700
    }
701
702
    if ($object->element == 'societe') {
703
        if (!empty($conf->use_javascript_ajax) && $user->hasRight('societe', 'creer') && getDolGlobalString('MAIN_DIRECT_STATUS_UPDATE')) {
704
            $morehtmlstatus .= ajax_object_onoff($object, 'status', 'status', 'InActivity', 'ActivityCeased');
705
        } else {
706
            $morehtmlstatus .= $object->getLibStatut(6);
0 ignored issues
show
Bug introduced by
The method getLibStatut() does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

706
            $morehtmlstatus .= $object->/** @scrutinizer ignore-call */ getLibStatut(6);
Loading history...
707
        }
708
    } elseif ($object->element == 'product') {
709
        //$morehtmlstatus.=$langs->trans("Status").' ('.$langs->trans("Sell").') ';
710
        if (!empty($conf->use_javascript_ajax) && $user->hasRight('produit', 'creer') && getDolGlobalString('MAIN_DIRECT_STATUS_UPDATE')) {
711
            $morehtmlstatus .= ajax_object_onoff($object, 'status', 'tosell', 'ProductStatusOnSell', 'ProductStatusNotOnSell');
712
        } else {
713
            $morehtmlstatus .= '<span class="statusrefsell">' . $object->getLibStatut(6, 0) . '</span>';
714
        }
715
        $morehtmlstatus .= ' &nbsp; ';
716
        //$morehtmlstatus.=$langs->trans("Status").' ('.$langs->trans("Buy").') ';
717
        if (!empty($conf->use_javascript_ajax) && $user->hasRight('produit', 'creer') && getDolGlobalString('MAIN_DIRECT_STATUS_UPDATE')) {
718
            $morehtmlstatus .= ajax_object_onoff($object, 'status_buy', 'tobuy', 'ProductStatusOnBuy', 'ProductStatusNotOnBuy');
719
        } else {
720
            $morehtmlstatus .= '<span class="statusrefbuy">' . $object->getLibStatut(6, 1) . '</span>';
721
        }
722
    } elseif (in_array($object->element, array('salary'))) {
723
        $tmptxt = $object->getLibStatut(6, $object->alreadypaid);
0 ignored issues
show
Bug Best Practice introduced by
The property $alreadypaid is declared private in Dolibarr\Core\Base\CommonObject. Since you implement __get, consider adding a @property or @property-read.
Loading history...
724
        if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
725
            $tmptxt = $object->getLibStatut(5, $object->alreadypaid);
726
        }
727
        $morehtmlstatus .= $tmptxt;
728
    } elseif (in_array($object->element, array('facture', 'invoice', 'invoice_supplier', 'chargesociales', 'loan', 'tva'))) {   // TODO Move this to use ->alreadypaid
729
        $tmptxt = $object->getLibStatut(6, $object->totalpaid);
730
        if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
731
            $tmptxt = $object->getLibStatut(5, $object->totalpaid);
732
        }
733
        $morehtmlstatus .= $tmptxt;
734
    } elseif ($object->element == 'contrat' || $object->element == 'contract') {
735
        if ($object->statut == 0) {
736
            $morehtmlstatus .= $object->getLibStatut(5);
737
        } else {
738
            $morehtmlstatus .= $object->getLibStatut(4);
739
        }
740
    } elseif ($object->element == 'facturerec') {
741
        '@phan-var-force FactureRec $object';
742
        if ($object->frequency == 0) {
0 ignored issues
show
Bug Best Practice introduced by
The property frequency does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property frequency does not exist on Dolibarr\Code\Product\Classes\Product. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property frequency does not exist on Dolibarr\Code\Categories\Classes\Categorie. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property frequency does not exist on Dolibarr\Code\Bom\Classes\BOM. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing $object->frequency of type mixed|null to 0; this is ambiguous as not only 0 == 0 is true, but null == 0 is true, too. Consider using a strict comparison ===.
Loading history...
743
            $morehtmlstatus .= $object->getLibStatut(2);
744
        } else {
745
            $morehtmlstatus .= $object->getLibStatut(5);
746
        }
747
    } elseif ($object->element == 'project_task') {
748
        $object->fk_statut = 1;
0 ignored issues
show
Bug Best Practice introduced by
The property fk_statut does not exist on Dolibarr\Code\Product\Classes\Product. Since you implemented __set, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property fk_statut does not exist on Dolibarr\Code\Bom\Classes\BOM. Since you implemented __set, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property fk_statut does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __set, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property fk_statut does not exist on Dolibarr\Code\Categories\Classes\Categorie. Since you implemented __set, consider adding a @property annotation.
Loading history...
749
        if ($object->progress > 0) {
0 ignored issues
show
Bug Best Practice introduced by
The property progress does not exist on Dolibarr\Code\Categories\Classes\Categorie. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property progress does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property progress does not exist on Dolibarr\Code\Bom\Classes\BOM. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property progress does not exist on Dolibarr\Code\Product\Classes\Product. Since you implemented __get, consider adding a @property annotation.
Loading history...
750
            $object->fk_statut = 2;
751
        }
752
        if ($object->progress >= 100) {
753
            $object->fk_statut = 3;
754
        }
755
        $tmptxt = $object->getLibStatut(5);
756
        $morehtmlstatus .= $tmptxt; // No status on task
757
    } elseif (method_exists($object, 'getLibStatut')) { // Generic case for status
758
        $tmptxt = $object->getLibStatut(6);
759
        if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
760
            $tmptxt = $object->getLibStatut(5);
761
        }
762
        $morehtmlstatus .= $tmptxt;
763
    }
764
765
    // Add if object was dispatched "into accountancy"
766
    if (isModEnabled('accounting') && in_array($object->element, array('bank', 'paiementcharge', 'facture', 'invoice', 'invoice_supplier', 'expensereport', 'payment_various'))) {
767
        // Note: For 'chargesociales', 'salaries'... this is the payments that are dispatched (so element = 'bank')
768
        if (method_exists($object, 'getVentilExportCompta')) {
769
            $accounted = $object->getVentilExportCompta();
0 ignored issues
show
Bug introduced by
The method getVentilExportCompta() does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

769
            /** @scrutinizer ignore-call */ 
770
            $accounted = $object->getVentilExportCompta();
Loading history...
Bug introduced by
The method getVentilExportCompta() does not exist on Dolibarr\Code\Product\Classes\Product. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

769
            /** @scrutinizer ignore-call */ 
770
            $accounted = $object->getVentilExportCompta();
Loading history...
Bug introduced by
The method getVentilExportCompta() does not exist on Dolibarr\Code\Categories\Classes\Categorie. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

769
            /** @scrutinizer ignore-call */ 
770
            $accounted = $object->getVentilExportCompta();
Loading history...
Bug introduced by
The method getVentilExportCompta() does not exist on Dolibarr\Code\Bom\Classes\BOM. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

769
            /** @scrutinizer ignore-call */ 
770
            $accounted = $object->getVentilExportCompta();
Loading history...
770
            $langs->load("accountancy");
771
            $morehtmlstatus .= '</div><div class="statusref statusrefbis"><span class="opacitymedium">' . ($accounted > 0 ? $langs->trans("Accounted") : $langs->trans("NotYetAccounted")) . '</span>';
772
        }
773
    }
774
775
    // Add alias for thirdparty
776
    if (!empty($object->name_alias)) {
0 ignored issues
show
Bug Best Practice introduced by
The property name_alias does not exist on Dolibarr\Code\Product\Classes\Product. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property name_alias does not exist on Dolibarr\Code\Categories\Classes\Categorie. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property name_alias does not exist on Dolibarr\Code\Bom\Classes\BOM. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property name_alias does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
777
        '@phan-var-force Societe $object';
778
        $morehtmlref .= '<div class="refidno opacitymedium">' . dol_escape_htmltag($object->name_alias) . '</div>';
779
    }
780
781
    // Add label
782
    if (in_array($object->element, array('product', 'bank_account', 'project_task'))) {
783
        if (!empty($object->label)) {
784
            $morehtmlref .= '<div class="refidno opacitymedium">' . $object->label . '</div>';
785
        }
786
    }
787
    // Show address and email
788
    if (method_exists($object, 'getBannerAddress') && !in_array($object->element, array('product', 'bookmark', 'ecm_directories', 'ecm_files'))) {
789
        $moreaddress = $object->getBannerAddress('refaddress', $object);    // address, email, url, social networks
0 ignored issues
show
Bug introduced by
The method getBannerAddress() does not exist on Dolibarr\Code\Product\Classes\Product. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

789
        /** @scrutinizer ignore-call */ 
790
        $moreaddress = $object->getBannerAddress('refaddress', $object);    // address, email, url, social networks
Loading history...
Bug introduced by
The method getBannerAddress() does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

789
        /** @scrutinizer ignore-call */ 
790
        $moreaddress = $object->getBannerAddress('refaddress', $object);    // address, email, url, social networks
Loading history...
Bug introduced by
The method getBannerAddress() does not exist on Dolibarr\Code\Categories\Classes\Categorie. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

789
        /** @scrutinizer ignore-call */ 
790
        $moreaddress = $object->getBannerAddress('refaddress', $object);    // address, email, url, social networks
Loading history...
Bug introduced by
The method getBannerAddress() does not exist on Dolibarr\Code\Bom\Classes\BOM. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

789
        /** @scrutinizer ignore-call */ 
790
        $moreaddress = $object->getBannerAddress('refaddress', $object);    // address, email, url, social networks
Loading history...
790
        if ($moreaddress) {
791
            $morehtmlref .= '<div class="refidno refaddress">';
792
            $morehtmlref .= $moreaddress;
793
            $morehtmlref .= '</div>';
794
        }
795
    }
796
    if (getDolGlobalString('MAIN_SHOW_TECHNICAL_ID') && (getDolGlobalString('MAIN_SHOW_TECHNICAL_ID') == '1' || preg_match('/' . preg_quote($object->element, '/') . '/i', $conf->global->MAIN_SHOW_TECHNICAL_ID)) && !empty($object->id)) {
797
        $morehtmlref .= '<div style="clear: both;"></div>';
798
        $morehtmlref .= '<div class="refidno opacitymedium">';
799
        $morehtmlref .= $langs->trans("TechnicalID") . ': ' . ((int)$object->id);
800
        $morehtmlref .= '</div>';
801
    }
802
803
    $parameters = array('morehtmlref' => &$morehtmlref, 'moreparam' => &$moreparam, 'morehtmlleft' => &$morehtmlleft, 'morehtmlstatus' => &$morehtmlstatus, 'morehtmlright' => &$morehtmlright);
804
    $reshook = $hookmanager->executeHooks('formDolBanner', $parameters, $object, $action);
805
    if ($reshook < 0) {
806
        setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
807
    } elseif (empty($reshook)) {
808
        $morehtmlref .= $hookmanager->resPrint;
809
    } elseif ($reshook > 0) {
810
        $morehtmlref = $hookmanager->resPrint;
811
    }
812
813
814
    print '<div class="' . ($onlybanner ? 'arearefnobottom ' : 'arearef ') . 'heightref valignmiddle centpercent">';
815
    print $form->showrefnav($object, $paramid, $morehtml, $shownav, $fieldid, $fieldref, $morehtmlref, $moreparam, $nodbprefix, $morehtmlleft, $morehtmlstatus, $morehtmlright);
816
    print '</div>';
817
    print '<div class="underrefbanner clearboth"></div>';
818
}
819
820
/**
821
 * Show a string with the label tag dedicated to the HTML edit field.
822
 *
823
 * @param string $langkey Translation key
824
 * @param string $fieldkey Key of the html select field the text refers to
825
 * @param int $fieldrequired 1=Field is mandatory
826
 * @return string
827
 * @deprecated Form::editfieldkey
828
 */
829
function fieldLabel($langkey, $fieldkey, $fieldrequired = 0)
830
{
831
    global $langs;
832
    $ret = '';
833
    if ($fieldrequired) {
834
        $ret .= '<span class="fieldrequired">';
835
    }
836
    $ret .= '<label for="' . $fieldkey . '">';
837
    $ret .= $langs->trans($langkey);
838
    $ret .= '</label>';
839
    if ($fieldrequired) {
840
        $ret .= '</span>';
841
    }
842
    return $ret;
843
}
844
845
/**
846
 * Return string to add class property on html element with pair/impair.
847
 *
848
 * @param boolean $var false or true
849
 * @param string $moreclass More class to add
850
 * @return  string                  String to add class onto HTML element
851
 */
852
function dol_bc($var, $moreclass = '')
853
{
854
    global $bc;
855
    $ret = ' ' . $bc[$var];
856
    if ($moreclass) {
857
        $ret = preg_replace('/class=\"/', 'class="' . $moreclass . ' ', $ret);
858
    }
859
    return $ret;
860
}
861
862
/**
863
 * Show Url link
864
 *
865
 * @param string $url Url to show
866
 * @param string $target Target for link
867
 * @param int $max Max number of characters to show
868
 * @param int $withpicto With picto
869
 * @param string $morecss More CSS
870
 * @return  string                  HTML Link
871
 */
872
function dol_print_url($url, $target = '_blank', $max = 32, $withpicto = 0, $morecss = '')
873
{
874
    global $langs;
875
876
    if (empty($url)) {
877
        return '';
878
    }
879
880
    $linkstart = '<a href="';
881
    if (!preg_match('/^http/i', $url)) {
882
        $linkstart .= 'http://';
883
    }
884
    $linkstart .= $url;
885
    $linkstart .= '"';
886
    if ($target) {
887
        $linkstart .= ' target="' . $target . '"';
888
    }
889
    $linkstart .= ' title="' . $langs->trans("URL") . ': ' . $url . '"';
890
    $linkstart .= '>';
891
892
    $link = '';
893
    if (!preg_match('/^http/i', $url)) {
894
        $link .= 'http://';
895
    }
896
    $link .= dol_trunc($url, $max);
897
898
    $linkend = '</a>';
899
900
    if ($morecss == 'float') {  // deprecated
901
        return '<div class="nospan' . ($morecss ? ' ' . $morecss : '') . '" style="margin-right: 10px">' . ($withpicto ? img_picto($langs->trans("Url"), 'globe', 'class="paddingrightonly"') : '') . $link . '</div>';
902
    } else {
903
        return $linkstart . '<span class="nospan' . ($morecss ? ' ' . $morecss : '') . '" style="margin-right: 10px">' . ($withpicto ? img_picto('', 'globe', 'class="paddingrightonly"') : '') . $link . '</span>' . $linkend;
904
    }
905
}
906
907
/**
908
 * Show EMail link formatted for HTML output.
909
 *
910
 * @param string $email EMail to show (only email, without 'Name of recipient' before)
911
 * @param int $cid Id of contact if known
912
 * @param int $socid Id of third party if known
913
 * @param int $addlink 0=no link, 1=email has a html email link (+ link to create action if constant AGENDA_ADDACTIONFOREMAIL is on)
914
 * @param int $max Max number of characters to show
915
 * @param int $showinvalid 1=Show warning if syntax email is wrong
916
 * @param int|string $withpicto Show picto
917
 * @return  string                      HTML Link
918
 */
919
function dol_print_email($email, $cid = 0, $socid = 0, $addlink = 0, $max = 64, $showinvalid = 1, $withpicto = 0)
920
{
921
    global $user, $langs, $hookmanager;
922
923
    //global $conf; $conf->global->AGENDA_ADDACTIONFOREMAIL = 1;
924
    //$showinvalid = 1; $email = 'rrrrr';
925
926
    $newemail = dol_escape_htmltag($email);
927
928
    if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER') && $withpicto) {
929
        $withpicto = 0;
930
    }
931
932
    if (empty($email)) {
933
        return '&nbsp;';
934
    }
935
936
    if (!empty($addlink)) {
937
        $newemail = '<a class="paddingrightonly" style="text-overflow: ellipsis;" href="';
938
        if (!preg_match('/^mailto:/i', $email)) {
939
            $newemail .= 'mailto:';
940
        }
941
        $newemail .= $email;
942
        $newemail .= '">';
943
944
        $newemail .= ($withpicto ? img_picto($langs->trans("EMail") . ' : ' . $email, (is_numeric($withpicto) ? 'email' : $withpicto), 'class="paddingrightonly"') : '');
945
946
        $newemail .= dol_trunc($email, $max);
947
        $newemail .= '</a>';
948
        if ($showinvalid && !isValidEmail($email)) {
949
            $langs->load("errors");
950
            $newemail .= img_warning($langs->trans("ErrorBadEMail", $email), '', 'paddingrightonly');
951
        }
952
953
        if (($cid || $socid) && isModEnabled('agenda') && $user->hasRight("agenda", "myactions", "create")) {
954
            $type = 'AC_EMAIL';
955
            $linktoaddaction = '';
956
            if (getDolGlobalString('AGENDA_ADDACTIONFOREMAIL')) {
957
                $linktoaddaction = '<a href="' . constant('BASE_URL') . '/comm/action/card.php?action=create&amp;backtopage=1&amp;actioncode=' . urlencode($type) . '&amp;contactid=' . ((int)$cid) . '&amp;socid=' . ((int)$socid) . '">' . img_object($langs->trans("AddAction"), "calendar") . '</a>';
958
            }
959
            if ($linktoaddaction) {
960
                $newemail = '<div>' . $newemail . ' ' . $linktoaddaction . '</div>';
961
            }
962
        }
963
    } else {
964
        $newemail = ($withpicto ? img_picto($langs->trans("EMail") . ' : ' . $email, (is_numeric($withpicto) ? 'email' : $withpicto), 'class="paddingrightonly"') : '') . $newemail;
965
966
        if ($showinvalid && !isValidEmail($email)) {
967
            $langs->load("errors");
968
            $newemail .= img_warning($langs->trans("ErrorBadEMail", $email));
969
        }
970
    }
971
972
    //$rep = '<div class="nospan" style="margin-right: 10px">';
973
    //$rep = ($withpicto ? img_picto($langs->trans("EMail").' : '.$email, (is_numeric($withpicto) ? 'email' : $withpicto), 'class="paddingrightonly"') : '').$newemail;
974
    //$rep .= '</div>';
975
    $rep = $newemail;
976
977
    if ($hookmanager) {
978
        $parameters = array('cid' => $cid, 'socid' => $socid, 'addlink' => $addlink, 'picto' => $withpicto);
979
980
        $reshook = $hookmanager->executeHooks('printEmail', $parameters, $email);
981
        if ($reshook > 0) {
982
            $rep = '';
983
        }
984
        $rep .= $hookmanager->resPrint;
985
    }
986
987
    return $rep;
988
}
989
990
/**
991
 * Show social network link
992
 *
993
 * @param string $value Social network ID to show (only skype, without 'Name of recipient' before)
994
 * @param int $cid Id of contact if known
995
 * @param int $socid Id of third party if known
996
 * @param string $type 'skype','facebook',...
997
 * @param array<string,array{rowid:int,label:string,url:string,icon:string,active:int}> $dictsocialnetworks List of socialnetworks available
998
 * @return  string                          HTML Link
999
 */
1000
function dol_print_socialnetworks($value, $cid, $socid, $type, $dictsocialnetworks = array())
1001
{
1002
    global $user, $langs;
1003
1004
    $htmllink = $value;
1005
1006
    if (empty($value)) {
1007
        return '&nbsp;';
1008
    }
1009
1010
    if (!empty($type)) {
1011
        $htmllink = '<div class="divsocialnetwork inline-block valignmiddle">';
1012
        // Use dictionary definition for picto $dictsocialnetworks[$type]['icon']
1013
        $htmllink .= '<span class="fab pictofixedwidth ' . ($dictsocialnetworks[$type]['icon'] ? $dictsocialnetworks[$type]['icon'] : 'fa-link') . '"></span>';
1014
        if ($type == 'skype') {
1015
            $htmllink .= dol_escape_htmltag($value);
1016
            $htmllink .= '&nbsp; <a href="skype:';
1017
            $htmllink .= dol_string_nospecial($value, '_', '', array('@'));
1018
            $htmllink .= '?call" alt="' . $langs->trans("Call") . '&nbsp;' . $value . '" title="' . dol_escape_htmltag($langs->trans("Call") . ' ' . $value) . '">';
1019
            $htmllink .= '<img src="' . constant('DOL_URL_ROOT') . '/theme/common/skype_callbutton.png" border="0">';
1020
            $htmllink .= '</a><a href="skype:';
1021
            $htmllink .= dol_string_nospecial($value, '_', '', array('@'));
1022
            $htmllink .= '?chat" alt="' . $langs->trans("Chat") . '&nbsp;' . $value . '" title="' . dol_escape_htmltag($langs->trans("Chat") . ' ' . $value) . '">';
1023
            $htmllink .= '<img class="paddingleft" src="' . constant('DOL_URL_ROOT') . '/theme/common/skype_chatbutton.png" border="0">';
1024
            $htmllink .= '</a>';
1025
            if (($cid || $socid) && isModEnabled('agenda') && $user->hasRight('agenda', 'myactions', 'create')) {
1026
                $addlink = 'AC_SKYPE';
1027
                $link = '';
1028
                if (getDolGlobalString('AGENDA_ADDACTIONFORSKYPE')) {
1029
                    $link = '<a href="' . constant('BASE_URL') . '/comm/action/card.php?action=create&amp;backtopage=1&amp;actioncode=' . $addlink . '&amp;contactid=' . $cid . '&amp;socid=' . $socid . '">' . img_object($langs->trans("AddAction"), "calendar") . '</a>';
1030
                }
1031
                $htmllink .= ($link ? ' ' . $link : '');
1032
            }
1033
        } else {
1034
            $networkconstname = 'MAIN_INFO_SOCIETE_' . strtoupper($type) . '_URL';
1035
            if (getDolGlobalString($networkconstname)) {
1036
                $link = str_replace('{socialid}', $value, getDolGlobalString($networkconstname));
1037
                if (preg_match('/^https?:\/\//i', $link)) {
1038
                    $htmllink .= '<a href="' . dol_sanitizeUrl($link, 0) . '" target="_blank" rel="noopener noreferrer">' . dol_escape_htmltag($value) . '</a>';
1039
                } else {
1040
                    $htmllink .= '<a href="' . dol_sanitizeUrl($link, 1) . '" target="_blank" rel="noopener noreferrer">' . dol_escape_htmltag($value) . '</a>';
1041
                }
1042
            } elseif (!empty($dictsocialnetworks[$type]['url'])) {
1043
                $tmpvirginurl = preg_replace('/\/?{socialid}/', '', $dictsocialnetworks[$type]['url']);
1044
                if ($tmpvirginurl) {
1045
                    $value = preg_replace('/^www\.' . preg_quote($tmpvirginurl, '/') . '\/?/', '', $value);
1046
                    $value = preg_replace('/^' . preg_quote($tmpvirginurl, '/') . '\/?/', '', $value);
1047
1048
                    $tmpvirginurl3 = preg_replace('/^https:\/\//i', 'https://www.', $tmpvirginurl);
1049
                    if ($tmpvirginurl3) {
1050
                        $value = preg_replace('/^www\.' . preg_quote($tmpvirginurl3, '/') . '\/?/', '', $value);
1051
                        $value = preg_replace('/^' . preg_quote($tmpvirginurl3, '/') . '\/?/', '', $value);
1052
                    }
1053
1054
                    $tmpvirginurl2 = preg_replace('/^https?:\/\//i', '', $tmpvirginurl);
1055
                    if ($tmpvirginurl2) {
1056
                        $value = preg_replace('/^www\.' . preg_quote($tmpvirginurl2, '/') . '\/?/', '', $value);
1057
                        $value = preg_replace('/^' . preg_quote($tmpvirginurl2, '/') . '\/?/', '', $value);
1058
                    }
1059
                }
1060
                $link = str_replace('{socialid}', $value, $dictsocialnetworks[$type]['url']);
1061
                if (preg_match('/^https?:\/\//i', $link)) {
1062
                    $htmllink .= '<a href="' . dol_sanitizeUrl($link, 0) . '" target="_blank" rel="noopener noreferrer">' . dol_escape_htmltag($value) . '</a>';
1063
                } else {
1064
                    $htmllink .= '<a href="' . dol_sanitizeUrl($link, 1) . '" target="_blank" rel="noopener noreferrer">' . dol_escape_htmltag($value) . '</a>';
1065
                }
1066
            } else {
1067
                $htmllink .= dol_escape_htmltag($value);
1068
            }
1069
        }
1070
        $htmllink .= '</div>';
1071
    } else {
1072
        $langs->load("errors");
1073
        $htmllink .= img_warning($langs->trans("ErrorBadSocialNetworkValue", $value));
1074
    }
1075
    return $htmllink;
1076
}
1077
1078
/**
1079
 *  Format phone numbers according to country
1080
 *
1081
 * @param string $phone Phone number to format
1082
 * @param string $countrycode Country code to use for formatting
1083
 * @param int $cid Id of contact if known
1084
 * @param int $socid Id of third party if known
1085
 * @param string $addlink ''=no link to create action, 'AC_TEL'=add link to clicktodial (if module enabled) and add link to create event (if conf->global->AGENDA_ADDACTIONFORPHONE set), 'tel'=Force "tel:..." link
1086
 * @param string $separ Separation between numbers for a better visibility example : xx.xx.xx.xx.xx. You can also use 'hidenum' to hide the number, keep only the picto.
1087
 * @param string $withpicto Show picto ('fax', 'phone', 'mobile')
1088
 * @param string $titlealt Text to show on alt
1089
 * @param int $adddivfloat Add div float around phone.
1090
 * @param string $morecss Add more css
1091
 * @return string                  Formatted phone number
1092
 */
1093
function dol_print_phone($phone, $countrycode = '', $cid = 0, $socid = 0, $addlink = '', $separ = "&nbsp;", $withpicto = '', $titlealt = '', $adddivfloat = 0, $morecss = '')
1094
{
1095
    global $conf, $user, $langs, $mysoc, $hookmanager;
1096
1097
    // Clean phone parameter
1098
    $phone = is_null($phone) ? '' : preg_replace("/[\s.-]/", "", trim($phone));
1099
    if (empty($phone)) {
1100
        return '';
1101
    }
1102
    if (getDolGlobalString('MAIN_PHONE_SEPAR')) {
1103
        $separ = getDolGlobalString('MAIN_PHONE_SEPAR');
1104
    }
1105
    if (empty($countrycode) && is_object($mysoc)) {
1106
        $countrycode = $mysoc->country_code;
1107
    }
1108
1109
    // Short format for small screens
1110
    if (!empty($conf->dol_optimize_smallscreen) && $separ != 'hidenum') {
1111
        $separ = '';
1112
    }
1113
1114
    $newphone = $phone;
1115
    $newphonewa = $phone;
1116
    if (strtoupper($countrycode) == "FR") {
1117
        // France
1118
        if (dol_strlen($phone) == 10) {
1119
            $newphone = substr($newphone, 0, 2) . $separ . substr($newphone, 2, 2) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2);
1120
        } elseif (dol_strlen($phone) == 7) {
1121
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 2);
1122
        } elseif (dol_strlen($phone) == 9) {
1123
            $newphone = substr($newphone, 0, 2) . $separ . substr($newphone, 2, 3) . $separ . substr($newphone, 5, 2) . $separ . substr($newphone, 7, 2);
1124
        } elseif (dol_strlen($phone) == 11) {
1125
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 2) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2);
1126
        } elseif (dol_strlen($phone) == 12) {
1127
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 1) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
1128
        } elseif (dol_strlen($phone) == 13) {
1129
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 3) . $separ . substr($newphone, 11, 2);
1130
        }
1131
    } elseif (strtoupper($countrycode) == "CA") {
1132
        if (dol_strlen($phone) == 10) {
1133
            $newphone = ($separ != '' ? '(' : '') . substr($newphone, 0, 3) . ($separ != '' ? ')' : '') . $separ . substr($newphone, 3, 3) . ($separ != '' ? '-' : '') . substr($newphone, 6, 4);
1134
        }
1135
    } elseif (strtoupper($countrycode) == "PT") {//Portugal
1136
        if (dol_strlen($phone) == 13) {//ex: +351_ABC_DEF_GHI
1137
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
1138
        }
1139
    } elseif (strtoupper($countrycode) == "SR") {//Suriname
1140
        if (dol_strlen($phone) == 10) {//ex: +597_ABC_DEF
1141
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3);
1142
        } elseif (dol_strlen($phone) == 11) {//ex: +597_ABC_DEFG
1143
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 4);
1144
        }
1145
    } elseif (strtoupper($countrycode) == "DE") {//Allemagne
1146
        if (dol_strlen($phone) == 14) {//ex:  +49_ABCD_EFGH_IJK
1147
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4) . $separ . substr($newphone, 7, 4) . $separ . substr($newphone, 11, 3);
1148
        } elseif (dol_strlen($phone) == 13) {//ex: +49_ABC_DEFG_HIJ
1149
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 4) . $separ . substr($newphone, 10, 3);
1150
        }
1151
    } elseif (strtoupper($countrycode) == "ES") {//Espagne
1152
        if (dol_strlen($phone) == 12) {//ex:  +34_ABC_DEF_GHI
1153
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
1154
        }
1155
    } elseif (strtoupper($countrycode) == "BF") {// Burkina Faso
1156
        if (dol_strlen($phone) == 12) {//ex :  +22 A BC_DE_FG_HI
1157
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 1) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
1158
        }
1159
    } elseif (strtoupper($countrycode) == "RO") {// Roumanie
1160
        if (dol_strlen($phone) == 12) {//ex :  +40 AB_CDE_FG_HI
1161
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
1162
        }
1163
    } elseif (strtoupper($countrycode) == "TR") {//Turquie
1164
        if (dol_strlen($phone) == 13) {//ex :  +90 ABC_DEF_GHIJ
1165
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 4);
1166
        }
1167
    } elseif (strtoupper($countrycode) == "US") {//Etat-Unis
1168
        if (dol_strlen($phone) == 12) {//ex: +1 ABC_DEF_GHIJ
1169
            $newphone = substr($newphone, 0, 2) . $separ . substr($newphone, 2, 3) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 4);
1170
        }
1171
    } elseif (strtoupper($countrycode) == "MX") {//Mexique
1172
        if (dol_strlen($phone) == 12) {//ex: +52 ABCD_EFG_HI
1173
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 2);
1174
        } elseif (dol_strlen($phone) == 11) {//ex: +52 AB_CD_EF_GH
1175
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 2) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2);
1176
        } elseif (dol_strlen($phone) == 13) {//ex: +52 ABC_DEF_GHIJ
1177
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 4);
1178
        }
1179
    } elseif (strtoupper($countrycode) == "ML") {//Mali
1180
        if (dol_strlen($phone) == 12) {//ex: +223 AB_CD_EF_GH
1181
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
1182
        }
1183
    } elseif (strtoupper($countrycode) == "TH") {//Thaïlande
1184
        if (dol_strlen($phone) == 11) {//ex: +66_ABC_DE_FGH
1185
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 3);
1186
        } elseif (dol_strlen($phone) == 12) {//ex: +66_A_BCD_EF_GHI
1187
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 1) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 3);
1188
        }
1189
    } elseif (strtoupper($countrycode) == "MU") {
1190
        //Maurice
1191
        if (dol_strlen($phone) == 11) {//ex: +230_ABC_DE_FG
1192
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2);
1193
        } elseif (dol_strlen($phone) == 12) {//ex: +230_ABCD_EF_GH
1194
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 4) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
1195
        }
1196
    } elseif (strtoupper($countrycode) == "ZA") {//Afrique du sud
1197
        if (dol_strlen($phone) == 12) {//ex: +27_AB_CDE_FG_HI
1198
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
1199
        }
1200
    } elseif (strtoupper($countrycode) == "SY") {//Syrie
1201
        if (dol_strlen($phone) == 12) {//ex: +963_AB_CD_EF_GH
1202
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
1203
        } elseif (dol_strlen($phone) == 13) {//ex: +963_AB_CD_EF_GHI
1204
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 3);
1205
        }
1206
    } elseif (strtoupper($countrycode) == "AE") {//Emirats Arabes Unis
1207
        if (dol_strlen($phone) == 12) {//ex: +971_ABC_DEF_GH
1208
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 2);
1209
        } elseif (dol_strlen($phone) == 13) {//ex: +971_ABC_DEF_GHI
1210
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
1211
        } elseif (dol_strlen($phone) == 14) {//ex: +971_ABC_DEF_GHIK
1212
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 4);
1213
        }
1214
    } elseif (strtoupper($countrycode) == "DZ") {//Algérie
1215
        if (dol_strlen($phone) == 13) {//ex: +213_ABC_DEF_GHI
1216
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
1217
        }
1218
    } elseif (strtoupper($countrycode) == "BE") {//Belgique
1219
        if (dol_strlen($phone) == 11) {//ex: +32_ABC_DE_FGH
1220
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 3);
1221
        } elseif (dol_strlen($phone) == 12) {//ex: +32_ABC_DEF_GHI
1222
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
1223
        }
1224
    } elseif (strtoupper($countrycode) == "PF") {//Polynésie française
1225
        if (dol_strlen($phone) == 12) {//ex: +689_AB_CD_EF_GH
1226
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
1227
        }
1228
    } elseif (strtoupper($countrycode) == "CO") {//Colombie
1229
        if (dol_strlen($phone) == 13) {//ex: +57_ABC_DEF_GH_IJ
1230
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 2) . $separ . substr($newphone, 11, 2);
1231
        }
1232
    } elseif (strtoupper($countrycode) == "JO") {//Jordanie
1233
        if (dol_strlen($phone) == 12) {//ex: +962_A_BCD_EF_GH
1234
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 1) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2);
1235
        }
1236
    } elseif (strtoupper($countrycode) == "JM") {//Jamaïque
1237
        if (dol_strlen($newphone) == 12) {//ex: +1867_ABC_DEFG
1238
            $newphone = substr($newphone, 0, 5) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 4);
1239
        }
1240
    } elseif (strtoupper($countrycode) == "MG") {//Madagascar
1241
        if (dol_strlen($phone) == 13) {//ex: +261_AB_CD_EFG_HI
1242
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 3) . $separ . substr($newphone, 11, 2);
1243
        }
1244
    } elseif (strtoupper($countrycode) == "GB") {//Royaume uni
1245
        if (dol_strlen($phone) == 13) {//ex: +44_ABCD_EFG_HIJ
1246
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
1247
        }
1248
    } elseif (strtoupper($countrycode) == "CH") {//Suisse
1249
        if (dol_strlen($phone) == 12) {//ex: +41_AB_CDE_FG_HI
1250
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
1251
        } elseif (dol_strlen($phone) == 15) {// +41_AB_CDE_FGH_IJKL
1252
            $newphone = $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 3) . $separ . substr($newphone, 11, 4);
1253
        }
1254
    } elseif (strtoupper($countrycode) == "TN") {//Tunisie
1255
        if (dol_strlen($phone) == 12) {//ex: +216_AB_CDE_FGH
1256
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
1257
        }
1258
    } elseif (strtoupper($countrycode) == "GF") {//Guyane francaise
1259
        if (dol_strlen($phone) == 13) {//ex: +594_ABC_DE_FG_HI  (ABC=594 de nouveau)
1260
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2) . $separ . substr($newphone, 11, 2);
1261
        }
1262
    } elseif (strtoupper($countrycode) == "GP") {//Guadeloupe
1263
        if (dol_strlen($phone) == 13) {//ex: +590_ABC_DE_FG_HI  (ABC=590 de nouveau)
1264
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2) . $separ . substr($newphone, 11, 2);
1265
        }
1266
    } elseif (strtoupper($countrycode) == "MQ") {//Martinique
1267
        if (dol_strlen($phone) == 13) {//ex: +596_ABC_DE_FG_HI  (ABC=596 de nouveau)
1268
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2) . $separ . substr($newphone, 11, 2);
1269
        }
1270
    } elseif (strtoupper($countrycode) == "IT") {//Italie
1271
        if (dol_strlen($phone) == 12) {//ex: +39_ABC_DEF_GHI
1272
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
1273
        } elseif (dol_strlen($phone) == 13) {//ex: +39_ABC_DEF_GH_IJ
1274
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 2) . $separ . substr($newphone, 11, 2);
1275
        }
1276
    } elseif (strtoupper($countrycode) == "AU") {
1277
        //Australie
1278
        if (dol_strlen($phone) == 12) {
1279
            //ex: +61_A_BCDE_FGHI
1280
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 1) . $separ . substr($newphone, 4, 4) . $separ . substr($newphone, 8, 4);
1281
        }
1282
    } elseif (strtoupper($countrycode) == "LU") {
1283
        // Luxembourg
1284
        if (dol_strlen($phone) == 10) {// fix 6 digits +352_AA_BB_CC
1285
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2);
1286
        } elseif (dol_strlen($phone) == 11) {// fix 7 digits +352_AA_BB_CC_D
1287
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 1);
1288
        } elseif (dol_strlen($phone) == 12) {// fix 8 digits +352_AA_BB_CC_DD
1289
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
1290
        } elseif (dol_strlen($phone) == 13) {// mobile +352_AAA_BB_CC_DD
1291
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2) . $separ . substr($newphone, 11, 2);
1292
        }
1293
    } elseif (strtoupper($countrycode) == "PE") {
1294
        // Peru
1295
        if (dol_strlen($phone) == 7) {// fix 7 chiffres without code AAA_BBBB
1296
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4);
1297
        } elseif (dol_strlen($phone) == 9) {// mobile add code and fix 9 chiffres +51_AAA_BBB_CCC
1298
            $newphonewa = '+51' . $newphone;
1299
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 10, 3);
1300
        } elseif (dol_strlen($phone) == 11) {// fix 11 chiffres +511_AAA_BBBB
1301
            $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 8, 4);
1302
        } elseif (dol_strlen($phone) == 12) {// mobile +51_AAA_BBB_CCC
1303
            $newphonewa = $newphone;
1304
            $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 10, 3) . $separ . substr($newphone, 14, 3);
1305
        }
1306
    }
1307
1308
    $newphoneastart = $newphoneaend = '';
1309
    if (!empty($addlink)) { // Link on phone number (+ link to add action if conf->global->AGENDA_ADDACTIONFORPHONE set)
1310
        if ($addlink == 'tel' || $conf->browser->layout == 'phone' || (isModEnabled('clicktodial') && getDolGlobalString('CLICKTODIAL_USE_TEL_LINK_ON_PHONE_NUMBERS'))) {   // If phone or option for, we use link of phone
1311
            $newphoneastart = '<a href="tel:' . urlencode($phone) . '">';
1312
            $newphoneaend .= '</a>';
1313
        } elseif (isModEnabled('clicktodial') && $addlink == 'AC_TEL') {        // If click to dial, we use click to dial url
1314
            if (empty($user->clicktodial_loaded)) {
1315
                $user->fetch_clicktodial();
1316
            }
1317
1318
            // Define urlmask
1319
            $urlmask = getDolGlobalString('CLICKTODIAL_URL', 'ErrorClickToDialModuleNotConfigured');
1320
            if (!empty($user->clicktodial_url)) {
1321
                $urlmask = $user->clicktodial_url;
1322
            }
1323
1324
            $clicktodial_poste = (!empty($user->clicktodial_poste) ? urlencode($user->clicktodial_poste) : '');
1325
            $clicktodial_login = (!empty($user->clicktodial_login) ? urlencode($user->clicktodial_login) : '');
1326
            $clicktodial_password = (!empty($user->clicktodial_password) ? urlencode($user->clicktodial_password) : '');
1327
            // This line is for backward compatibility  @phan-suppress-next-line PhanPluginPrintfVariableFormatString
1328
            $url = sprintf($urlmask, urlencode($phone), $clicktodial_poste, $clicktodial_login, $clicktodial_password);
1329
            // Those lines are for substitution
1330
            $substitarray = array('__PHONEFROM__' => $clicktodial_poste,
1331
                '__PHONETO__' => urlencode($phone),
1332
                '__LOGIN__' => $clicktodial_login,
1333
                '__PASS__' => $clicktodial_password);
1334
            $url = make_substitutions($url, $substitarray);
1335
            if (!getDolGlobalString('CLICKTODIAL_DO_NOT_USE_AJAX_CALL')) {
1336
                // Default and recommended: New method using ajax without submitting a page making a javascript history.go(-1) back
1337
                $newphoneastart = '<a href="' . $url . '" class="cssforclicktodial">';  // Call of ajax is handled by the lib_foot.js.php on class 'cssforclicktodial'
1338
                $newphoneaend = '</a>';
1339
            } else {
1340
                // Old method
1341
                $newphoneastart = '<a href="' . $url . '"';
1342
                if (getDolGlobalString('CLICKTODIAL_FORCENEWTARGET')) {
1343
                    $newphoneastart .= ' target="_blank" rel="noopener noreferrer"';
1344
                }
1345
                $newphoneastart .= '>';
1346
                $newphoneaend .= '</a>';
1347
            }
1348
        }
1349
1350
        //if (($cid || $socid) && isModEnabled('agenda') && $user->hasRight('agenda', 'myactions', 'create'))
1351
        if (isModEnabled('agenda') && $user->hasRight("agenda", "myactions", "create")) {
1352
            $type = 'AC_TEL';
1353
            $addlinktoagenda = '';
1354
            if ($addlink == 'AC_FAX') {
1355
                $type = 'AC_FAX';
1356
            }
1357
            if (getDolGlobalString('AGENDA_ADDACTIONFORPHONE')) {
1358
                $addlinktoagenda = '<a href="' . constant('BASE_URL') . '/comm/action/card.php?action=create&amp;backtopage=' . urlencode($_SERVER['REQUEST_URI']) . '&amp;actioncode=' . $type . ($cid ? '&amp;contactid=' . $cid : '') . ($socid ? '&amp;socid=' . $socid : '') . '">' . img_object($langs->trans("AddAction"), "calendar") . '</a>';
1359
            }
1360
            if ($addlinktoagenda) {
1361
                $newphone = '<span>' . $newphone . ' ' . $addlinktoagenda . '</span>';
1362
            }
1363
        }
1364
    }
1365
1366
    if (getDolGlobalString('CONTACT_PHONEMOBILE_SHOW_LINK_TO_WHATSAPP') && $withpicto == 'mobile') {
1367
        // Link to Whatsapp
1368
        $newphone .= ' <a href="https://wa.me/' . $newphonewa . '" target="_blank"';// Use api to whatasapp contacts
1369
        $newphone .= '><span class="paddingright fab fa-whatsapp" style="color:#25D366;" title="WhatsApp"></span></a>';
1370
    }
1371
1372
    if (empty($titlealt)) {
1373
        $titlealt = ($withpicto == 'fax' ? $langs->trans("Fax") : $langs->trans("Phone"));
1374
    }
1375
    $rep = '';
1376
1377
    if ($hookmanager) {
1378
        $parameters = array('countrycode' => $countrycode, 'cid' => $cid, 'socid' => $socid, 'titlealt' => $titlealt, 'picto' => $withpicto);
1379
        $reshook = $hookmanager->executeHooks('printPhone', $parameters, $phone);
1380
        $rep .= $hookmanager->resPrint;
1381
    }
1382
    if (empty($reshook)) {
1383
        $picto = '';
1384
        if ($withpicto) {
1385
            if ($withpicto == 'fax') {
1386
                $picto = 'phoning_fax';
1387
            } elseif ($withpicto == 'phone') {
1388
                $picto = 'phoning';
1389
            } elseif ($withpicto == 'mobile') {
1390
                $picto = 'phoning_mobile';
1391
            } else {
1392
                $picto = '';
1393
            }
1394
        }
1395
        if ($adddivfloat == 1) {
1396
            $rep .= '<div class="nospan float' . ($morecss ? ' ' . $morecss : '') . '" style="margin-right: 10px">';
1397
        } elseif (empty($adddivfloat)) {
1398
            $rep .= '<span' . ($morecss ? ' class="' . $morecss . '"' : '') . ' style="margin-right: 10px;">';
1399
        }
1400
1401
        $rep .= $newphoneastart;
1402
        $rep .= ($withpicto ? img_picto($titlealt, 'object_' . $picto . '.png') : '');
1403
        if ($separ != 'hidenum') {
1404
            $rep .= ($withpicto ? ' ' : '') . $newphone;
1405
        }
1406
        $rep .= $newphoneaend;
1407
1408
        if ($adddivfloat == 1) {
1409
            $rep .= '</div>';
1410
        } elseif (empty($adddivfloat)) {
1411
            $rep .= '</span>';
1412
        }
1413
    }
1414
1415
    return $rep;
1416
}
1417
1418
/**
1419
 *  Show warning logo
1420
 *
1421
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
1422
 * @param string $moreatt Add more attribute on img tag (For example 'style="float: right"'). If 1, add float: right. Can't be "class" attribute.
1423
 * @param string $morecss Add more CSS
1424
 * @return string              Return img tag
1425
 */
1426
function img_warning($titlealt = 'default', $moreatt = '', $morecss = 'pictowarning')
1427
{
1428
    global $langs;
1429
1430
    if ($titlealt == 'default') {
1431
        $titlealt = $langs->trans('Warning');
1432
    }
1433
1434
    //return '<div class="imglatecoin">'.img_picto($titlealt, 'warning_white.png', 'class="pictowarning valignmiddle"'.($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' '.$moreatt): '')).'</div>';
1435
    return img_picto($titlealt, 'warning.png', 'class="' . $morecss . '"' . ($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' ' . $moreatt) : ''));
1436
}
1437
1438
/**
1439
 *  Show error logo
1440
 *
1441
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
1442
 * @return string              Return img tag
1443
 */
1444
function img_error($titlealt = 'default')
1445
{
1446
    global $langs;
1447
1448
    if ($titlealt == 'default') {
1449
        $titlealt = $langs->trans('Error');
1450
    }
1451
1452
    return img_picto($titlealt, 'error.png');
1453
}
1454
1455
/**
1456
 *  Show next logo
1457
 *
1458
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
1459
 * @param string $moreatt Add more attribute on img tag (For example 'style="float: right"')
1460
 * @return string              Return img tag
1461
 */
1462
function img_next($titlealt = 'default', $moreatt = '')
1463
{
1464
    global $langs;
1465
1466
    if ($titlealt == 'default') {
1467
        $titlealt = $langs->trans('Next');
1468
    }
1469
1470
    //return img_picto($titlealt, 'next.png', $moreatt);
1471
    return '<span class="fa fa-chevron-right paddingright paddingleft" title="' . dol_escape_htmltag($titlealt) . '"></span>';
1472
}
1473
1474
/**
1475
 *  Show previous logo
1476
 *
1477
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
1478
 * @param string $moreatt Add more attribute on img tag (For example 'style="float: right"')
1479
 * @return string              Return img tag
1480
 */
1481
function img_previous($titlealt = 'default', $moreatt = '')
1482
{
1483
    global $langs;
1484
1485
    if ($titlealt == 'default') {
1486
        $titlealt = $langs->trans('Previous');
1487
    }
1488
1489
    //return img_picto($titlealt, 'previous.png', $moreatt);
1490
    return '<span class="fa fa-chevron-left paddingright paddingleft" title="' . dol_escape_htmltag($titlealt) . '"></span>';
1491
}
1492
1493
/**
1494
 *  Show down arrow logo
1495
 *
1496
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
1497
 * @param int $selected Selected
1498
 * @param string $moreclass Add more CSS classes
1499
 * @return string              Return img tag
1500
 */
1501
function img_down($titlealt = 'default', $selected = 0, $moreclass = '')
1502
{
1503
    global $langs;
1504
1505
    if ($titlealt == 'default') {
1506
        $titlealt = $langs->trans('Down');
1507
    }
1508
1509
    return img_picto($titlealt, ($selected ? '1downarrow_selected.png' : '1downarrow.png'), 'class="imgdown' . ($moreclass ? " " . $moreclass : "") . '"');
1510
}
1511
1512
/**
1513
 *  Show top arrow logo
1514
 *
1515
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
1516
 * @param int $selected Selected
1517
 * @param string $moreclass Add more CSS classes
1518
 * @return string              Return img tag
1519
 */
1520
function img_up($titlealt = 'default', $selected = 0, $moreclass = '')
1521
{
1522
    global $langs;
1523
1524
    if ($titlealt == 'default') {
1525
        $titlealt = $langs->trans('Up');
1526
    }
1527
1528
    return img_picto($titlealt, ($selected ? '1uparrow_selected.png' : '1uparrow.png'), 'class="imgup' . ($moreclass ? " " . $moreclass : "") . '"');
1529
}
1530
1531
/**
1532
 *  Show left arrow logo
1533
 *
1534
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
1535
 * @param int $selected Selected
1536
 * @param string $moreatt Add more attribute on img tag (For example 'style="float: right"')
1537
 * @return string              Return img tag
1538
 */
1539
function img_left($titlealt = 'default', $selected = 0, $moreatt = '')
1540
{
1541
    global $langs;
1542
1543
    if ($titlealt == 'default') {
1544
        $titlealt = $langs->trans('Left');
1545
    }
1546
1547
    return img_picto($titlealt, ($selected ? '1leftarrow_selected.png' : '1leftarrow.png'), $moreatt);
1548
}
1549
1550
/**
1551
 *  Show right arrow logo
1552
 *
1553
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
1554
 * @param int $selected Selected
1555
 * @param string $moreatt Add more attribute on img tag (For example 'style="float: right"')
1556
 * @return string              Return img tag
1557
 */
1558
function img_right($titlealt = 'default', $selected = 0, $moreatt = '')
1559
{
1560
    global $langs;
1561
1562
    if ($titlealt == 'default') {
1563
        $titlealt = $langs->trans('Right');
1564
    }
1565
1566
    return img_picto($titlealt, ($selected ? '1rightarrow_selected.png' : '1rightarrow.png'), $moreatt);
1567
}
1568
1569
/**
1570
 *  Show tick logo if allowed
1571
 *
1572
 * @param string $allow Allow
1573
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
1574
 * @return string              Return img tag
1575
 */
1576
function img_allow($allow, $titlealt = 'default')
1577
{
1578
    global $langs;
1579
1580
    if ($titlealt == 'default') {
1581
        $titlealt = $langs->trans('Active');
1582
    }
1583
1584
    if ($allow == 1) {
1585
        return img_picto($titlealt, 'tick.png');
1586
    }
1587
1588
    return '-';
1589
}
1590
1591
/**
1592
 *  Return image of a credit card according to its brand name
1593
 *
1594
 * @param string $brand Brand name of credit card
1595
 * @param string $morecss More CSS
1596
 * @return string              Return img tag
1597
 */
1598
function img_credit_card($brand, $morecss = null)
1599
{
1600
    if (is_null($morecss)) {
1601
        $morecss = 'fa-2x';
1602
    }
1603
1604
    if ($brand == 'visa' || $brand == 'Visa') {
1605
        $brand = 'cc-visa';
1606
    } elseif ($brand == 'mastercard' || $brand == 'MasterCard') {
1607
        $brand = 'cc-mastercard';
1608
    } elseif ($brand == 'amex' || $brand == 'American Express') {
1609
        $brand = 'cc-amex';
1610
    } elseif ($brand == 'discover' || $brand == 'Discover') {
1611
        $brand = 'cc-discover';
1612
    } elseif ($brand == 'jcb' || $brand == 'JCB') {
1613
        $brand = 'cc-jcb';
1614
    } elseif ($brand == 'diners' || $brand == 'Diners club') {
1615
        $brand = 'cc-diners-club';
1616
    } elseif (!in_array($brand, array('cc-visa', 'cc-mastercard', 'cc-amex', 'cc-discover', 'cc-jcb', 'cc-diners-club'))) {
1617
        $brand = 'credit-card';
1618
    }
1619
1620
    return '<span class="fa fa-' . $brand . ' fa-fw' . ($morecss ? ' ' . $morecss : '') . '"></span>';
1621
}
1622
1623
/**
1624
 *  Show MIME img of a file
1625
 *
1626
 * @param string $file Filename
1627
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
1628
 * @param string $morecss More css
1629
 * @return string              Return img tag
1630
 */
1631
function img_mime($file, $titlealt = '', $morecss = '')
1632
{
1633
    require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
1634
1635
    $mimetype = dol_mimetype($file, '', 1);
1636
    $mimeimg = dol_mimetype($file, '', 2);
1637
    $mimefa = dol_mimetype($file, '', 4);
1638
1639
    if (empty($titlealt)) {
1640
        $titlealt = 'Mime type: ' . $mimetype;
1641
    }
1642
1643
    //return img_picto_common($titlealt, 'mime/'.$mimeimg, 'class="'.$morecss.'"');
1644
    return '<i class="fa fa-' . $mimefa . ' paddingright' . ($morecss ? ' ' . $morecss : '') . '"' . ($titlealt ? ' title="' . $titlealt . '"' : '') . '></i>';
1645
}
1646
1647
1648
/**
1649
 *  Show search logo
1650
 *
1651
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
1652
 * @param string $other Add more attributes on img
1653
 * @return string              Retourne tag img
1654
 */
1655
function img_search($titlealt = 'default', $other = '')
1656
{
1657
    global $langs;
1658
1659
    if ($titlealt == 'default') {
1660
        $titlealt = $langs->trans('Search');
1661
    }
1662
1663
    $img = img_picto($titlealt, 'search.png', $other, 0, 1);
1664
1665
    $input = '<input type="image" class="liste_titre" name="button_search" src="' . $img . '" ';
1666
    $input .= 'value="' . dol_escape_htmltag($titlealt) . '" title="' . dol_escape_htmltag($titlealt) . '" >';
1667
1668
    return $input;
1669
}
1670
1671
/**
1672
 *  Show search logo
1673
 *
1674
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
1675
 * @param string $other Add more attributes on img
1676
 * @return string              Retourne tag img
1677
 */
1678
function img_searchclear($titlealt = 'default', $other = '')
1679
{
1680
    global $langs;
1681
1682
    if ($titlealt == 'default') {
1683
        $titlealt = $langs->trans('Search');
1684
    }
1685
1686
    $img = img_picto($titlealt, 'searchclear.png', $other, 0, 1);
1687
1688
    $input = '<input type="image" class="liste_titre" name="button_removefilter" src="' . $img . '" ';
1689
    $input .= 'value="' . dol_escape_htmltag($titlealt) . '" title="' . dol_escape_htmltag($titlealt) . '" >';
1690
1691
    return $input;
1692
}
1693
1694
/**
1695
 *  Show information in HTML for admin users or standard users
1696
 *
1697
 * @param string $text Text info
1698
 * @param integer $infoonimgalt Info is shown only on alt of star picto, otherwise it is show on output after the star picto
1699
 * @param int $nodiv No div
1700
 * @param string $admin '1'=Info for admin users. '0'=Info for standard users (change only the look), 'error', 'warning', 'xxx'=Other
1701
 * @param string $morecss More CSS ('', 'warning', 'error')
1702
 * @param string $textfordropdown Show a text to click to dropdown the info box.
1703
 * @return string                      String with info text
1704
 */
1705
function info_admin($text, $infoonimgalt = 0, $nodiv = 0, $admin = '1', $morecss = 'hideonsmartphone', $textfordropdown = '')
1706
{
1707
    global $conf, $langs;
1708
1709
    if ($infoonimgalt) {
1710
        $result = img_picto($text, 'info', 'class="' . ($morecss ? ' ' . $morecss : '') . '"');
1711
    } else {
1712
        if (empty($conf->use_javascript_ajax)) {
1713
            $textfordropdown = '';
1714
        }
1715
1716
        $class = (empty($admin) ? 'undefined' : ($admin == '1' ? 'info' : $admin));
1717
        $result = ($nodiv ? '' : '<div class="wordbreak ' . $class . ($morecss ? ' ' . $morecss : '') . ($textfordropdown ? ' hidden' : '') . '">') . '<span class="fa fa-info-circle" title="' . dol_escape_htmltag($admin ? $langs->trans('InfoAdmin') : $langs->trans('Note')) . '"></span> ';
1718
        $result .= dol_escape_htmltag($text, 1, 0, 'div,span,b,br,a');
1719
        $result .= ($nodiv ? '' : '</div>');
1720
1721
        if ($textfordropdown) {
1722
            $tmpresult = '<span class="' . $class . 'text opacitymedium cursorpointer">' . $langs->trans($textfordropdown) . ' ' . img_picto($langs->trans($textfordropdown), '1downarrow') . '</span>';
1723
            $tmpresult .= '<script nonce="' . getNonce() . '" type="text/javascript">
1724
				jQuery(document).ready(function() {
1725
					jQuery(".' . $class . 'text").click(function() {
1726
						console.log("toggle text");
1727
						jQuery(".' . $class . '").toggle();
1728
					});
1729
				});
1730
				</script>';
1731
1732
            $result = $tmpresult . $result;
1733
        }
1734
    }
1735
1736
    return $result;
1737
}
1738
1739
1740
/**
1741
 *  Displays error message system with all the information to facilitate the diagnosis and the escalation of the bugs.
1742
 *  This function must be called when a blocking technical error is encountered.
1743
 *  However, one must try to call it only within php pages, classes must return their error through their property "error".
1744
 *
1745
 * @param DoliDB|null $db Database handler
1746
 * @param string|string[] $error String or array of errors strings to show
1747
 * @param string[]|null $errors Array of errors
1748
 * @return     void
1749
 * @see        dol_htmloutput_errors()
1750
 */
1751
function dol_print_error($db = null, $error = '', $errors = null)
1752
{
1753
    global $conf, $langs, $argv;
1754
    global $dolibarr_main_prod;
1755
1756
    $out = '';
1757
    $syslog = '';
1758
1759
    // If error occurs before the $lang object was loaded
1760
    if (!$langs) {
1761
        $langs = new Translate('', $conf);
1762
        $langs->load("main");
1763
    }
1764
1765
    // Load translation files required by the error messages
1766
    $langs->loadLangs(array('main', 'errors'));
1767
1768
    if ($_SERVER['DOCUMENT_ROOT']) {    // Mode web
1769
        $out .= $langs->trans("DolibarrHasDetectedError") . ".<br>\n";
1770
        if (getDolGlobalInt('MAIN_FEATURES_LEVEL') > 0) {
1771
            $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";
1772
        }
1773
        $out .= $langs->trans("InformationToHelpDiagnose") . ":<br>\n";
1774
1775
        $out .= "<b>" . $langs->trans("Date") . ":</b> " . dol_print_date(time(), 'dayhourlog') . "<br>\n";
1776
        $out .= "<b>" . $langs->trans("Dolibarr") . ":</b> " . DOL_VERSION . " - https://www.dolibarr.org<br>\n";
1777
        if (isset($conf->global->MAIN_FEATURES_LEVEL)) {
1778
            $out .= "<b>" . $langs->trans("LevelOfFeature") . ":</b> " . getDolGlobalInt('MAIN_FEATURES_LEVEL') . "<br>\n";
1779
        }
1780
        if (function_exists("phpversion")) {
1781
            $out .= "<b>" . $langs->trans("PHP") . ":</b> " . phpversion() . "<br>\n";
1782
        }
1783
        $out .= "<b>" . $langs->trans("Server") . ":</b> " . (isset($_SERVER["SERVER_SOFTWARE"]) ? dol_htmlentities($_SERVER["SERVER_SOFTWARE"], ENT_COMPAT) : '') . "<br>\n";
1784
        if (function_exists("php_uname")) {
1785
            $out .= "<b>" . $langs->trans("OS") . ":</b> " . php_uname() . "<br>\n";
1786
        }
1787
        $out .= "<b>" . $langs->trans("UserAgent") . ":</b> " . (isset($_SERVER["HTTP_USER_AGENT"]) ? dol_htmlentities($_SERVER["HTTP_USER_AGENT"], ENT_COMPAT) : '') . "<br>\n";
1788
        $out .= "<br>\n";
1789
        $out .= "<b>" . $langs->trans("RequestedUrl") . ":</b> " . dol_htmlentities($_SERVER["REQUEST_URI"], ENT_COMPAT) . "<br>\n";
1790
        $out .= "<b>" . $langs->trans("Referer") . ":</b> " . (isset($_SERVER["HTTP_REFERER"]) ? dol_htmlentities($_SERVER["HTTP_REFERER"], ENT_COMPAT) : '') . "<br>\n";
1791
        $out .= "<b>" . $langs->trans("MenuManager") . ":</b> " . (isset($conf->standard_menu) ? dol_htmlentities($conf->standard_menu, ENT_COMPAT) : '') . "<br>\n";
1792
        $out .= "<br>\n";
1793
        $syslog .= "url=" . dol_escape_htmltag($_SERVER["REQUEST_URI"]);
1794
        $syslog .= ", query_string=" . dol_escape_htmltag($_SERVER["QUERY_STRING"]);
1795
    } else { // Mode CLI
1796
        $out .= '> ' . $langs->transnoentities("ErrorInternalErrorDetected") . ":\n" . $argv[0] . "\n";
1797
        $syslog .= "pid=" . dol_getmypid();
1798
    }
1799
1800
    if (!empty($conf->modules)) {
1801
        $out .= "<b>" . $langs->trans("Modules") . ":</b> " . implode(', ', $conf->modules) . "<br>\n";
1802
    }
1803
1804
    if (is_object($db)) {
1805
        if ($_SERVER['DOCUMENT_ROOT']) {  // Mode web
1806
            $out .= "<b>" . $langs->trans("DatabaseTypeManager") . ":</b> " . $db->type . "<br>\n";
1807
            $lastqueryerror = $db->lastqueryerror();
1808
            if (!utf8_check($lastqueryerror)) {
1809
                $lastqueryerror = "SQL error string is not a valid UTF8 string. We can't show it.";
1810
            }
1811
            $out .= "<b>" . $langs->trans("RequestLastAccessInError") . ":</b> " . ($lastqueryerror ? dol_escape_htmltag($lastqueryerror) : $langs->trans("ErrorNoRequestInError")) . "<br>\n";
1812
            $out .= "<b>" . $langs->trans("ReturnCodeLastAccessInError") . ":</b> " . ($db->lasterrno() ? dol_escape_htmltag($db->lasterrno()) : $langs->trans("ErrorNoRequestInError")) . "<br>\n";
1813
            $out .= "<b>" . $langs->trans("InformationLastAccessInError") . ":</b> " . ($db->lasterror() ? dol_escape_htmltag($db->lasterror()) : $langs->trans("ErrorNoRequestInError")) . "<br>\n";
1814
            $out .= "<br>\n";
1815
        } else { // Mode CLI
1816
            // No dol_escape_htmltag for output, we are in CLI mode
1817
            $out .= '> ' . $langs->transnoentities("DatabaseTypeManager") . ":\n" . $db->type . "\n";
1818
            $out .= '> ' . $langs->transnoentities("RequestLastAccessInError") . ":\n" . ($db->lastqueryerror() ? $db->lastqueryerror() : $langs->transnoentities("ErrorNoRequestInError")) . "\n";
1819
            $out .= '> ' . $langs->transnoentities("ReturnCodeLastAccessInError") . ":\n" . ($db->lasterrno() ? $db->lasterrno() : $langs->transnoentities("ErrorNoRequestInError")) . "\n";
1820
            $out .= '> ' . $langs->transnoentities("InformationLastAccessInError") . ":\n" . ($db->lasterror() ? $db->lasterror() : $langs->transnoentities("ErrorNoRequestInError")) . "\n";
1821
        }
1822
        $syslog .= ", sql=" . $db->lastquery();
1823
        $syslog .= ", db_error=" . $db->lasterror();
1824
    }
1825
1826
    if ($error || $errors) {
1827
        // Merge all into $errors array
1828
        if (is_array($error) && is_array($errors)) {
1829
            $errors = array_merge($error, $errors);
1830
        } elseif (is_array($error)) {   // deprecated, use second parameters
1831
            $errors = $error;
1832
        } elseif (is_array($errors) && !empty($error)) {
1833
            $errors = array_merge(array($error), $errors);
1834
        } elseif (!empty($error)) {
1835
            $errors = array_merge(array($error), array($errors));
1836
        }
1837
1838
        $langs->load("errors");
1839
1840
        foreach ($errors as $msg) {
1841
            if (empty($msg)) {
1842
                continue;
1843
            }
1844
            if ($_SERVER['DOCUMENT_ROOT']) {  // Mode web
1845
                $out .= "<b>" . $langs->trans("Message") . ":</b> " . dol_escape_htmltag($msg) . "<br>\n";
1846
            } else { // Mode CLI
1847
                $out .= '> ' . $langs->transnoentities("Message") . ":\n" . $msg . "\n";
1848
            }
1849
            $syslog .= ", msg=" . $msg;
1850
        }
1851
    }
1852
    if (empty($dolibarr_main_prod) && $_SERVER['DOCUMENT_ROOT'] && function_exists('xdebug_print_function_stack') && function_exists('xdebug_call_file')) {
1853
        xdebug_print_function_stack();
1854
        $out .= '<b>XDebug information:</b>' . "<br>\n";
1855
        $out .= 'File: ' . xdebug_call_file() . "<br>\n";
1856
        $out .= 'Line: ' . xdebug_call_line() . "<br>\n";
1857
        $out .= 'Function: ' . xdebug_call_function() . "<br>\n";
1858
        $out .= "<br>\n";
1859
    }
1860
1861
    // Return a http header with error code if possible
1862
    if (!headers_sent()) {
1863
        if (function_exists('top_httphead')) {  // In CLI context, the method does not exists
1864
            ViewMain::topHttpHead();
1865
        }
1866
        //http_response_code(500);      // If we use 500, message is not output with some command line tools
1867
        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
1868
    }
1869
1870
    if (empty($dolibarr_main_prod)) {
1871
        print $out;
1872
    } else {
1873
        if (empty($langs->defaultlang)) {
1874
            $langs->setDefaultLang();
1875
        }
1876
        $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.
1877
        // This should not happen, except if there is a bug somewhere. Enabled and check log in such case.
1878
        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";
1879
        print $langs->trans("DolibarrHasDetectedError") . '. ';
1880
        print $langs->trans("YouCanSetOptionDolibarrMainProdToZero");
1881
        if (!defined("MAIN_CORE_ERROR")) {
1882
            define("MAIN_CORE_ERROR", 1);
1883
        }
1884
    }
1885
1886
    dol_syslog("Error " . $syslog, LOG_ERR);
1887
}
1888
1889
/**
1890
 * Show a public email and error code to contact if technical error
1891
 *
1892
 * @param string $prefixcode Prefix of public error code
1893
 * @param string $errormessage Complete error message
1894
 * @param string[] $errormessages Array of error messages
1895
 * @param string $morecss More css
1896
 * @param string $email Email
1897
 * @return  void
1898
 */
1899
function dol_print_error_email($prefixcode, $errormessage = '', $errormessages = array(), $morecss = 'error', $email = '')
1900
{
1901
    global $langs;
1902
1903
    if (empty($email)) {
1904
        $email = getDolGlobalString('MAIN_INFO_SOCIETE_MAIL');
1905
    }
1906
1907
    $langs->load("errors");
1908
    $now = dol_now();
1909
1910
    print '<br><div class="center login_main_message"><div class="' . $morecss . '">';
1911
    print $langs->trans("ErrorContactEMail", $email, $prefixcode . '-' . dol_print_date($now, '%Y%m%d%H%M%S'));
1912
    if ($errormessage) {
1913
        print '<br><br>' . $errormessage;
1914
    }
1915
    if (is_array($errormessages) && count($errormessages)) {
1916
        foreach ($errormessages as $mesgtoshow) {
1917
            print '<br><br>' . $mesgtoshow;
1918
        }
1919
    }
1920
    print '</div></div>';
1921
}
1922
1923
/**
1924
 *  Show title line of an array
1925
 *
1926
 * @param string $name Label of field
1927
 * @param string $file Url used when we click on sort picto
1928
 * @param string $field Field to use for new sorting
1929
 * @param string $begin ("" by default)
1930
 * @param string $moreparam Add more parameters on sort url links ("" by default)
1931
 * @param string $moreattrib Options of attribute td ("" by default)
1932
 * @param string $sortfield Current field used to sort
1933
 * @param string $sortorder Current sort order
1934
 * @param string $prefix Prefix for css. Use space after prefix to add your own CSS tag, for example 'mycss '.
1935
 * @param string $tooltip Tooltip
1936
 * @param int $forcenowrapcolumntitle No need for use 'wrapcolumntitle' css style
1937
 * @return void
1938
 */
1939
function print_liste_field_titre($name, $file = "", $field = "", $begin = "", $moreparam = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $tooltip = "", $forcenowrapcolumntitle = 0)
1940
{
1941
    print getTitleFieldOfList($name, 0, $file, $field, $begin, $moreparam, $moreattrib, $sortfield, $sortorder, $prefix, 0, $tooltip, $forcenowrapcolumntitle);
1942
}
1943
1944
/**
1945
 *  Get title line of an array
1946
 *
1947
 * @param string $name Translation key of field to show or complete HTML string to show
1948
 * @param int $thead 0=To use with standard table format, 1=To use inside <thead><tr>, 2=To use with <div>
1949
 * @param string $file Url used when we click on sort picto
1950
 * @param string $field Field to use for new sorting. Empty if this field is not sortable. Example "t.abc" or "t.abc,t.def"
1951
 * @param string $begin ("" by default)
1952
 * @param string $moreparam Add more parameters on sort url links ("" by default)
1953
 * @param string $moreattrib Add more attributes on th ("" by default). To add more css class, use param $prefix.
1954
 * @param string $sortfield Current field used to sort (Ex: 'd.datep,d.id')
1955
 * @param string $sortorder Current sort order (Ex: 'asc,desc')
1956
 * @param string $prefix Prefix for css. Use space after prefix to add your own CSS tag, for example 'mycss '.
1957
 * @param int $disablesortlink 1=Disable sort link
1958
 * @param string $tooltip Tooltip
1959
 * @param int $forcenowrapcolumntitle No need for use 'wrapcolumntitle' css style
1960
 * @return string
1961
 */
1962
function getTitleFieldOfList($name, $thead = 0, $file = "", $field = "", $begin = "", $moreparam = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $disablesortlink = 0, $tooltip = '', $forcenowrapcolumntitle = 0)
1963
{
1964
    global $langs, $form;
1965
    //print "$name, $file, $field, $begin, $options, $moreattrib, $sortfield, $sortorder<br>\n";
1966
1967
    if ($moreattrib == 'class="right"') {
1968
        $prefix .= 'right '; // For backward compatibility
1969
    }
1970
1971
    $sortorder = strtoupper($sortorder);
1972
    $out = '';
1973
    $sortimg = '';
1974
1975
    $tag = 'th';
1976
    if ($thead == 2) {
1977
        $tag = 'div';
1978
    }
1979
1980
    $tmpsortfield = explode(',', $sortfield);
1981
    $sortfield1 = trim($tmpsortfield[0]); // If $sortfield is 'd.datep,d.id', it becomes 'd.datep'
1982
    $tmpfield = explode(',', $field);
1983
    $field1 = trim($tmpfield[0]); // If $field is 'd.datep,d.id', it becomes 'd.datep'
1984
1985
    if (!getDolGlobalString('MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE') && empty($forcenowrapcolumntitle)) {
1986
        $prefix = 'wrapcolumntitle ' . $prefix;
1987
    }
1988
1989
    //var_dump('field='.$field.' field1='.$field1.' sortfield='.$sortfield.' sortfield1='.$sortfield1);
1990
    // If field is used as sort criteria we use a specific css class liste_titre_sel
1991
    // Example if (sortfield,field)=("nom","xxx.nom") or (sortfield,field)=("nom","nom")
1992
    $liste_titre = 'liste_titre';
1993
    if ($field1 && ($sortfield1 == $field1 || $sortfield1 == preg_replace("/^[^\.]+\./", "", $field1))) {
1994
        $liste_titre = 'liste_titre_sel';
1995
    }
1996
1997
    $tagstart = '<' . $tag . ' class="' . $prefix . $liste_titre . '" ' . $moreattrib;
1998
    //$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)).'"' : '');
1999
    $tagstart .= ($name && !getDolGlobalString('MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE') && empty($forcenowrapcolumntitle) && !dol_textishtml($name)) ? ' title="' . dol_escape_htmltag($langs->trans($name)) . '"' : '';
2000
    $tagstart .= '>';
2001
2002
    if (empty($thead) && $field && empty($disablesortlink)) {    // If this is a sort field
2003
        $options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', (is_scalar($moreparam) ? $moreparam : ''));
2004
        $options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
2005
        $options = preg_replace('/&+/i', '&', $options);
2006
        if (!preg_match('/^&/', $options)) {
2007
            $options = '&' . $options;
2008
        }
2009
2010
        $sortordertouseinlink = '';
2011
        if ($field1 != $sortfield1) { // We are on another field than current sorted field
2012
            if (preg_match('/^DESC/i', $sortorder)) {
2013
                $sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
2014
            } else { // We reverse the var $sortordertouseinlink
2015
                $sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
2016
            }
2017
        } else { // We are on field that is the first current sorting criteria
2018
            if (preg_match('/^ASC/i', $sortorder)) {    // We reverse the var $sortordertouseinlink
2019
                $sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
2020
            } else {
2021
                $sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
2022
            }
2023
        }
2024
        $sortordertouseinlink = preg_replace('/,$/', '', $sortordertouseinlink);
2025
        $out .= '<a class="reposition" href="' . $file . '?sortfield=' . $field . '&sortorder=' . $sortordertouseinlink . '&begin=' . $begin . $options . '"';
2026
        //$out .= (empty($conf->global->MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE) ? ' title="'.dol_escape_htmltag($langs->trans($name)).'"' : '');
2027
        $out .= '>';
2028
    }
2029
    if ($tooltip) {
2030
        // You can also use 'TranslationString:keyfortooltiponclick' for a tooltip on click.
2031
        if (preg_match('/:\w+$/', $tooltip)) {
2032
            $tmptooltip = explode(':', $tooltip);
2033
        } else {
2034
            $tmptooltip = array($tooltip);
2035
        }
2036
        $out .= $form->textwithpicto($langs->trans($name), $langs->trans($tmptooltip[0]), 1, 'help', '', 0, 3, (empty($tmptooltip[1]) ? '' : 'extra_' . str_replace('.', '_', $field) . '_' . $tmptooltip[1]));
2037
    } else {
2038
        $out .= $langs->trans($name);
2039
    }
2040
2041
    if (empty($thead) && $field && empty($disablesortlink)) {    // If this is a sort field
2042
        $out .= '</a>';
2043
    }
2044
2045
    if (empty($thead) && $field) {    // If this is a sort field
2046
        $options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', (is_scalar($moreparam) ? $moreparam : ''));
2047
        $options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
2048
        $options = preg_replace('/&+/i', '&', $options);
2049
        if (!preg_match('/^&/', $options)) {
2050
            $options = '&' . $options;
2051
        }
2052
2053
        if (!$sortorder || ($field1 != $sortfield1)) {
2054
            //$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",0).'</a>';
2055
            //$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",0).'</a>';
2056
        } else {
2057
            if (preg_match('/^DESC/', $sortorder)) {
2058
                //$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",0).'</a>';
2059
                //$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",1).'</a>';
2060
                $sortimg .= '<span class="nowrap">' . img_up("Z-A", 0, 'paddingright') . '</span>';
2061
            }
2062
            if (preg_match('/^ASC/', $sortorder)) {
2063
                //$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",1).'</a>';
2064
                //$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",0).'</a>';
2065
                $sortimg .= '<span class="nowrap">' . img_down("A-Z", 0, 'paddingright') . '</span>';
2066
            }
2067
        }
2068
    }
2069
2070
    $tagend = '</' . $tag . '>';
2071
2072
    $out = $tagstart . $sortimg . $out . $tagend;
2073
2074
    return $out;
2075
}
2076
2077
/**
2078
 *  Show a title.
2079
 *
2080
 * @param string $title Title to show
2081
 * @return void
2082
 * @deprecated                     Use load_fiche_titre instead
2083
 * @see load_fiche_titre()
2084
 */
2085
function print_titre($title)
2086
{
2087
    dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);
2088
2089
    print '<div class="titre">' . $title . '</div>';
2090
}
2091
2092
/**
2093
 *  Show a title with picto
2094
 *
2095
 * @param string $title Title to show
2096
 * @param string $mesg Added message to show on right
2097
 * @param string $picto Icon to use before title (should be a 32x32 transparent png file)
2098
 * @param int $pictoisfullpath 1=Icon name is a full absolute url of image
2099
 * @param string $id To force an id on html objects by example id="name" where name is id
2100
 * @return void
2101
 * @deprecated Use print load_fiche_titre instead
2102
 */
2103
function print_fiche_titre($title, $mesg = '', $picto = 'generic', $pictoisfullpath = 0, $id = '')
2104
{
2105
    print load_fiche_titre($title, $mesg, $picto, $pictoisfullpath, $id);
2106
}
2107
2108
/**
2109
 *  Load a title with picto
2110
 *
2111
 * @param string $title Title to show (HTML sanitized content)
2112
 * @param string $morehtmlright Added message to show on right
2113
 * @param string $picto Icon to use before title (should be a 32x32 transparent png file)
2114
 * @param int $pictoisfullpath 1=Icon name is a full absolute url of image
2115
 * @param string $id To force an id on html objects
2116
 * @param string $morecssontable More css on table
2117
 * @param string $morehtmlcenter Added message to show on center
2118
 * @return string
2119
 * @see print_barre_liste()
2120
 */
2121
function load_fiche_titre($title, $morehtmlright = '', $picto = 'generic', $pictoisfullpath = 0, $id = '', $morecssontable = '', $morehtmlcenter = '')
2122
{
2123
    $return = '';
2124
2125
    if ($picto == 'setup') {
2126
        $picto = 'generic';
2127
    }
2128
2129
    $return .= "\n";
2130
    $return .= '<table ' . ($id ? 'id="' . $id . '" ' : '') . 'class="centpercent notopnoleftnoright table-fiche-title' . ($morecssontable ? ' ' . $morecssontable : '') . '">'; // maring bottom must be same than into print_barre_list
2131
    $return .= '<tr class="titre">';
2132
    if ($picto) {
2133
        $return .= '<td class="nobordernopadding widthpictotitle valignmiddle col-picto">' . img_picto('', $picto, 'class="valignmiddle widthpictotitle pictotitle"', $pictoisfullpath) . '</td>';
2134
    }
2135
    $return .= '<td class="nobordernopadding valignmiddle col-title">';
2136
    $return .= '<div class="titre inline-block">';
2137
    $return .= $title;  // $title is already HTML sanitized content
2138
    $return .= '</div>';
2139
    $return .= '</td>';
2140
    if (dol_strlen($morehtmlcenter)) {
2141
        $return .= '<td class="nobordernopadding center valignmiddle col-center">' . $morehtmlcenter . '</td>';
2142
    }
2143
    if (dol_strlen($morehtmlright)) {
2144
        $return .= '<td class="nobordernopadding titre_right wordbreakimp right valignmiddle col-right">' . $morehtmlright . '</td>';
2145
    }
2146
    $return .= '</tr></table>' . "\n";
2147
2148
    return $return;
2149
}
2150
2151
/**
2152
 *  Print a title with navigation controls for pagination
2153
 *
2154
 * @param string $title Title to show (required)
2155
 * @param int|null $page Numero of page to show in navigation links (required)
2156
 * @param string $file Url of page (required)
2157
 * @param string $options More parameters for links ('' by default, does not include sortfield neither sortorder). Value must be 'urlencoded' before calling function.
2158
 * @param string $sortfield Field to sort on ('' by default)
2159
 * @param string $sortorder Order to sort ('' by default)
2160
 * @param string $morehtmlcenter String in the middle ('' by default). We often find here string $massaction coming from $form->selectMassAction()
2161
 * @param int $num Number of records found by select with limit+1
2162
 * @param int|string $totalnboflines Total number of records/lines for all pages (if known). Use a negative value of number to not show number. Use '' if unknown.
2163
 * @param string $picto Icon to use before title (should be a 32x32 transparent png file)
2164
 * @param int $pictoisfullpath 1=Icon name is a full absolute url of image
2165
 * @param string $morehtmlright More html to show (after arrows)
2166
 * @param string $morecss More css to the table
2167
 * @param int $limit Max number of lines (-1 = use default, 0 = no limit, > 0 = limit).
2168
 * @param int $hideselectlimit Force to hide select limit
2169
 * @param int $hidenavigation Force to hide the arrows and page for navigation
2170
 * @param int $pagenavastextinput 1=Do not suggest list of pages to navigate but suggest the page number into an input field.
2171
 * @param string $morehtmlrightbeforearrow More html to show (before arrows)
2172
 * @return void
2173
 */
2174
function print_barre_liste($title, $page, $file, $options = '', $sortfield = '', $sortorder = '', $morehtmlcenter = '', $num = -1, $totalnboflines = '', $picto = 'generic', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limit = -1, $hideselectlimit = 0, $hidenavigation = 0, $pagenavastextinput = 0, $morehtmlrightbeforearrow = '')
2175
{
2176
    global $conf;
2177
2178
    $savlimit = $limit;
2179
    $savtotalnboflines = $totalnboflines;
2180
    if (is_numeric($totalnboflines)) {
2181
        $totalnboflines = abs($totalnboflines);
2182
    }
2183
2184
    $page = (int)$page;
2185
2186
    if ($picto == 'setup') {
2187
        $picto = 'title_setup.png';
2188
    }
2189
    if (($conf->browser->name == 'ie') && $picto == 'generic') {
2190
        $picto = 'title.gif';
2191
    }
2192
    if ($limit < 0) {
2193
        $limit = $conf->liste_limit;
2194
    }
2195
2196
    if ($savlimit != 0 && (($num > $limit) || ($num == -1) || ($limit == 0))) {
2197
        $nextpage = 1;
2198
    } else {
2199
        $nextpage = 0;
2200
    }
2201
    //print 'totalnboflines='.$totalnboflines.'-savlimit='.$savlimit.'-limit='.$limit.'-num='.$num.'-nextpage='.$nextpage.'-hideselectlimit='.$hideselectlimit.'-hidenavigation='.$hidenavigation;
2202
2203
    print "\n";
2204
    print "<!-- Begin title -->\n";
2205
    print '<table class="centpercent notopnoleftnoright table-fiche-title' . ($morecss ? ' ' . $morecss : '') . '"><tr>'; // maring bottom must be same than into load_fiche_tire
2206
2207
    // Left
2208
2209
    if ($picto && $title) {
2210
        print '<td class="nobordernopadding widthpictotitle valignmiddle col-picto">' . img_picto('', $picto, 'class="valignmiddle pictotitle widthpictotitle"', $pictoisfullpath) . '</td>';
2211
    }
2212
2213
    print '<td class="nobordernopadding valignmiddle col-title">';
2214
    print '<div class="titre inline-block">';
2215
    print $title;   // $title may contains HTML
2216
    if (!empty($title) && $savtotalnboflines >= 0 && (string)$savtotalnboflines != '') {
2217
        print '<span class="opacitymedium colorblack paddingleft">(' . $totalnboflines . ')</span>';
2218
    }
2219
    print '</div></td>';
2220
2221
    // Center
2222
    if ($morehtmlcenter && empty($conf->dol_optimize_smallscreen)) {
2223
        print '<td class="nobordernopadding center valignmiddle col-center">' . $morehtmlcenter . '</td>';
2224
    }
2225
2226
    // Right
2227
    print '<td class="nobordernopadding valignmiddle right col-right">';
2228
    print '<input type="hidden" name="pageplusoneold" value="' . ((int)$page + 1) . '">';
2229
    if ($sortfield) {
2230
        $options .= "&sortfield=" . urlencode($sortfield);
2231
    }
2232
    if ($sortorder) {
2233
        $options .= "&sortorder=" . urlencode($sortorder);
2234
    }
2235
    // Show navigation bar
2236
    $pagelist = '';
2237
    if ($savlimit != 0 && ($page > 0 || $num > $limit)) {
2238
        if ($totalnboflines) {  // If we know total nb of lines
2239
            // Define nb of extra page links before and after selected page + ... + first or last
2240
            $maxnbofpage = (empty($conf->dol_optimize_smallscreen) ? 4 : 0);
2241
2242
            if ($limit > 0) {
2243
                $nbpages = ceil($totalnboflines / $limit);
2244
            } else {
2245
                $nbpages = 1;
2246
            }
2247
            $cpt = ($page - $maxnbofpage);
2248
            if ($cpt < 0) {
2249
                $cpt = 0;
2250
            }
2251
2252
            if ($cpt >= 1) {
2253
                if (empty($pagenavastextinput)) {
2254
                    $pagelist .= '<li class="pagination"><a class="reposition" href="' . $file . '?page=0' . $options . '">1</a></li>';
2255
                    if ($cpt > 2) {
2256
                        $pagelist .= '<li class="pagination"><span class="inactive">...</span></li>';
2257
                    } elseif ($cpt == 2) {
2258
                        $pagelist .= '<li class="pagination"><a class="reposition" href="' . $file . '?page=1' . $options . '">2</a></li>';
2259
                    }
2260
                }
2261
            }
2262
2263
            do {
2264
                if ($pagenavastextinput) {
2265
                    if ($cpt == $page) {
2266
                        $pagelist .= '<li class="pagination"><input type="text" class="' . ($totalnboflines > 100 ? 'width40' : 'width25') . ' center pageplusone" name="pageplusone" value="' . ($page + 1) . '"></li>';
2267
                        $pagelist .= '/';
2268
                    }
2269
                } else {
2270
                    if ($cpt == $page) {
2271
                        $pagelist .= '<li class="pagination"><span class="active">' . ($page + 1) . '</span></li>';
2272
                    } else {
2273
                        $pagelist .= '<li class="pagination"><a class="reposition" href="' . $file . '?page=' . $cpt . $options . '">' . ($cpt + 1) . '</a></li>';
2274
                    }
2275
                }
2276
                $cpt++;
2277
            } while ($cpt < $nbpages && $cpt <= ($page + $maxnbofpage));
2278
2279
            if (empty($pagenavastextinput)) {
2280
                if ($cpt < $nbpages) {
2281
                    if ($cpt < $nbpages - 2) {
2282
                        $pagelist .= '<li class="pagination"><span class="inactive">...</span></li>';
2283
                    } elseif ($cpt == $nbpages - 2) {
2284
                        $pagelist .= '<li class="pagination"><a class="reposition" href="' . $file . '?page=' . ($nbpages - 2) . $options . '">' . ($nbpages - 1) . '</a></li>';
2285
                    }
2286
                    $pagelist .= '<li class="pagination"><a class="reposition" href="' . $file . '?page=' . ($nbpages - 1) . $options . '">' . $nbpages . '</a></li>';
2287
                }
2288
            } else {
2289
                //var_dump($page.' '.$cpt.' '.$nbpages);
2290
                $pagelist .= '<li class="pagination paginationlastpage"><a class="reposition" href="' . $file . '?page=' . ($nbpages - 1) . $options . '">' . $nbpages . '</a></li>';
2291
            }
2292
        } else {
2293
            $pagelist .= '<li class="pagination"><span class="active">' . ($page + 1) . "</li>";
2294
        }
2295
    }
2296
2297
    if ($savlimit || $morehtmlright || $morehtmlrightbeforearrow) {
2298
        print_fleche_navigation($page, $file, $options, $nextpage, $pagelist, $morehtmlright, $savlimit, $totalnboflines, $hideselectlimit, $morehtmlrightbeforearrow, $hidenavigation); // output the div and ul for previous/last completed with page numbers into $pagelist
2299
    }
2300
2301
    // js to autoselect page field on focus
2302
    if ($pagenavastextinput) {
2303
        print ajax_autoselect('.pageplusone');
2304
    }
2305
2306
    print '</td>';
2307
    print '</tr>';
2308
2309
    print '</table>' . "\n";
2310
2311
    // Center
2312
    if ($morehtmlcenter && !empty($conf->dol_optimize_smallscreen)) {
2313
        print '<div class="nobordernopadding marginbottomonly center valignmiddle col-center centpercent">' . $morehtmlcenter . '</div>';
2314
    }
2315
2316
    print "<!-- End title -->\n\n";
2317
}
2318
2319
/**
2320
 *  Function to show navigation arrows into lists
2321
 *
2322
 * @param int $page Number of page
2323
 * @param string $file Page URL (in most cases provided with $_SERVER["PHP_SELF"])
2324
 * @param string $options Other url parameters to propagate ("" by default, may include sortfield and sortorder)
2325
 * @param integer $nextpage Do we show a next page button
2326
 * @param string $betweenarrows HTML content to show between arrows. MUST contains '<li> </li>' tags or '<li><span> </span></li>'.
2327
 * @param string $afterarrows HTML content to show after arrows. Must NOT contains '<li> </li>' tags.
2328
 * @param int $limit Max nb of record to show  (-1 = no combo with limit, 0 = no limit, > 0 = limit)
2329
 * @param int $totalnboflines Total number of records/lines for all pages (if known)
2330
 * @param int $hideselectlimit Force to hide select limit
2331
 * @param string $beforearrows HTML content to show before arrows. Must NOT contains '<li> </li>' tags.
2332
 * @param int $hidenavigation Force to hide the switch mode view and the navigation tool (hide limit section, html in $betweenarrows and $afterarrows but not $beforearrows)
2333
 * @return void
2334
 */
2335
function print_fleche_navigation($page, $file, $options = '', $nextpage = 0, $betweenarrows = '', $afterarrows = '', $limit = -1, $totalnboflines = 0, $hideselectlimit = 0, $beforearrows = '', $hidenavigation = 0)
2336
{
2337
    global $conf, $langs;
2338
2339
    print '<div class="pagination"><ul>';
2340
    if ($beforearrows) {
2341
        print '<li class="paginationbeforearrows">';
2342
        print $beforearrows;
2343
        print '</li>';
2344
    }
2345
2346
    if (empty($hidenavigation)) {
2347
        if ((int)$limit > 0 && empty($hideselectlimit)) {
2348
            $pagesizechoices = '10:10,15:15,20:20,25:25,50:50,100:100,250:250,500:500,1000:1000';
2349
            $pagesizechoices .= ',5000:5000,10000:10000,20000:20000';
2350
            //$pagesizechoices.=',0:'.$langs->trans("All");     // Not yet supported
2351
            //$pagesizechoices.=',2:2';
2352
            if (getDolGlobalString('MAIN_PAGESIZE_CHOICES')) {
2353
                $pagesizechoices = getDolGlobalString('MAIN_PAGESIZE_CHOICES');
2354
            }
2355
2356
            if (getDolGlobalString('MAIN_USE_HTML5_LIMIT_SELECTOR')) {
2357
                print '<li class="pagination">';
2358
                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 . '">';
2359
                print '<datalist id="limitlist">';
2360
            } else {
2361
                print '<li class="paginationxxx valignmiddle">';
2362
                print '<select id="limit" class="flat selectlimit nopadding maxwidth75 center" name="limit" title="' . dol_escape_htmltag($langs->trans("MaxNbOfRecordPerPage")) . '">';
2363
            }
2364
            $tmpchoice = explode(',', $pagesizechoices);
2365
            $tmpkey = $limit . ':' . $limit;
2366
            if (!in_array($tmpkey, $tmpchoice)) {
2367
                $tmpchoice[] = $tmpkey;
2368
            }
2369
            $tmpkey = $conf->liste_limit . ':' . $conf->liste_limit;
2370
            if (!in_array($tmpkey, $tmpchoice)) {
2371
                $tmpchoice[] = $tmpkey;
2372
            }
2373
            asort($tmpchoice, SORT_NUMERIC);
2374
            foreach ($tmpchoice as $val) {
2375
                $selected = '';
2376
                $tmp = explode(':', $val);
2377
                $key = $tmp[0];
2378
                $val = $tmp[1];
2379
                if ($key != '' && $val != '') {
2380
                    if ((int)$key == (int)$limit) {
2381
                        $selected = ' selected="selected"';
2382
                    }
2383
                    print '<option name="' . $key . '"' . $selected . '>' . dol_escape_htmltag($val) . '</option>' . "\n";
2384
                }
2385
            }
2386
            if (getDolGlobalString('MAIN_USE_HTML5_LIMIT_SELECTOR')) {
2387
                print '</datalist>';
2388
            } else {
2389
                print '</select>';
2390
                print ajax_combobox("limit", array(), 0, 0, 'resolve', -1, 'limit');
2391
                //print ajax_combobox("limit");
2392
            }
2393
2394
            if ($conf->use_javascript_ajax) {
2395
                print '<!-- JS CODE TO ENABLE select limit to launch submit of page -->
2396
	            		<script>
2397
	                	jQuery(document).ready(function () {
2398
	            	  		jQuery(".selectlimit").change(function() {
2399
	                            console.log("Change limit. Send submit");
2400
	                            $(this).parents(\'form:first\').submit();
2401
	            	  		});
2402
	                	});
2403
	            		</script>
2404
	                ';
2405
            }
2406
            print '</li>';
2407
        }
2408
        if ($page > 0) {
2409
            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>';
2410
        }
2411
        if ($betweenarrows) {
2412
            print '<!--<div class="betweenarrows nowraponall inline-block">-->';
2413
            print $betweenarrows;
2414
            print '<!--</div>-->';
2415
        }
2416
        if ($nextpage > 0) {
2417
            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>';
2418
        }
2419
        if ($afterarrows) {
2420
            print '<li class="paginationafterarrows">';
2421
            print $afterarrows;
2422
            print '</li>';
2423
        }
2424
    }
2425
    print '</ul></div>' . "\n";
2426
}
2427
2428
/**
2429
 * Displays an error page when a record is not found. It allows customization of the message,
2430
 * whether to include the header and footer, and if only the message should be shown without additional details.
2431
 * The function also supports executing additional hooks for customized handling of error pages.
2432
 *
2433
 * @param string $message Custom error message to display. If empty, a default "Record Not Found" message is shown.
2434
 * @param int<0,1> $printheader Determines if the page header should be printed (1 = yes, 0 = no).
2435
 * @param int<0,1> $printfooter Determines if the page footer should be printed (1 = yes, 0 = no).
2436
 * @param int<0,1> $showonlymessage If set to 1, only the error message is displayed without any additional information or hooks.
2437
 * @param mixed $params Optional parameters to pass to hooks for further processing or customization.
2438
 * @return void This function terminates script execution after outputting the error page.
2439
 * @global Conf $conf Dolibarr configuration object (global)
2440
 * @global DoliDB $db Database connection object (global)
2441
 * @global User $user Current user object (global)
2442
 * @global Translate $langs Language translation object, initialized within the function if not already.
2443
 * @global HookManager $hookmanager Hook manager object, initialized within the function if not already for executing hooks.
2444
 * @global string $action Current action, can be modified by hooks.
2445
 * @global object $object Current object, can be modified by hooks.
2446
 */
2447
function recordNotFound($message = '', $printheader = 1, $printfooter = 1, $showonlymessage = 0, $params = null)
2448
{
2449
    global $conf, $db, $langs, $hookmanager;
2450
    global $action, $object;
2451
2452
    if (!is_object($langs)) {
2453
        $langs = new Translate('', $conf);
2454
        $langs->setDefaultLang();
2455
    }
2456
2457
    $langs->load("errors");
2458
2459
    if ($printheader) {
2460
        if (function_exists("llxHeader")) {
2461
            ViewMain::llxHeader('');
2462
        } elseif (function_exists("llxHeaderVierge")) {
2463
            llxHeaderVierge('');
2464
        }
2465
    }
2466
2467
    print '<div class="error">';
2468
    if (empty($message)) {
2469
        print $langs->trans("ErrorRecordNotFound");
2470
    } else {
2471
        print $langs->trans($message);
2472
    }
2473
    print '</div>';
2474
    print '<br>';
2475
2476
    if (empty($showonlymessage)) {
2477
        if (empty($hookmanager)) {
2478
            $hookmanager = new HookManager($db);
2479
            // Initialize technical object to manage hooks of page. Note that conf->hooks_modules contains array of hook context
2480
            $hookmanager->initHooks(array('main'));
2481
        }
2482
2483
        $parameters = array('message' => $message, 'params' => $params);
2484
        $reshook = $hookmanager->executeHooks('getErrorRecordNotFound', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
2485
        print $hookmanager->resPrint;
2486
    }
2487
2488
    if ($printfooter && function_exists("llxFooter")) {
2489
        ViewMain::llxFooter();
2490
    }
2491
    exit(0);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
2492
}
2493
2494
/**
2495
 *  Show html area with actions in messaging format.
2496
 *  Note: Global parameter $param must be defined.
2497
 *
2498
 * @param Conf $conf Object conf
2499
 * @param Translate $langs Object langs
2500
 * @param DoliDB $db Object db
2501
 * @param  ?CommonObject $filterobj Filter on object Adherent|Societe|Project|Product|CommandeFournisseur|Dolresource|Ticket|... to list events linked to an object
2502
 * @param  ?Contact $objcon Filter on object contact to filter events on a contact
2503
 * @param int $noprint Return string but does not output it
2504
 * @param string $actioncode Filter on actioncode
2505
 * @param string $donetodo Filter on event 'done' or 'todo' or ''=nofilter (all).
2506
 * @param array<string,string> $filters Filter on other fields
2507
 * @param string $sortfield Sort field
2508
 * @param string $sortorder Sort order
2509
 * @return string|void                     Return html part or void if noprint is 1
2510
 */
2511
function show_actions_messaging($conf, $langs, $db, $filterobj, $objcon = null, $noprint = 0, $actioncode = '', $donetodo = 'done', $filters = array(), $sortfield = 'a.datep,a.id', $sortorder = 'DESC')
2512
{
2513
    global $user, $conf;
2514
    global $form;
2515
2516
    global $param, $massactionbutton;
2517
2518
2519
    // Check parameters
2520
    if (!is_object($filterobj) && !is_object($objcon)) {
2521
        dol_print_error(null, 'BadParameter');
2522
    }
2523
2524
    $histo = array();
2525
    '@phan-var-force array<int,array{type:string,tododone:string,id:string,datestart:int|string,dateend:int|string,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';
2526
2527
    $numaction = 0;
2528
    $now = dol_now();
2529
2530
    $sortfield_list = explode(',', $sortfield);
2531
    $sortfield_label_list = array('a.id' => 'id', 'a.datep' => 'dp', 'a.percent' => 'percent');
2532
    $sortfield_new_list = array();
2533
    foreach ($sortfield_list as $sortfield_value) {
2534
        $sortfield_new_list[] = $sortfield_label_list[trim($sortfield_value)];
2535
    }
2536
    $sortfield_new = implode(',', $sortfield_new_list);
2537
2538
    $sql = null;
2539
    $sql2 = null;
2540
2541
    if (isModEnabled('agenda')) {
2542
        // Search histo on actioncomm
2543
        if (is_object($objcon) && $objcon->id > 0) {
2544
            $sql = "SELECT DISTINCT a.id, a.label as label,";
2545
        } else {
2546
            $sql = "SELECT a.id, a.label as label,";
2547
        }
2548
        $sql .= " a.datep as dp,";
2549
        $sql .= " a.note as message,";
2550
        $sql .= " a.datep2 as dp2,";
2551
        $sql .= " a.percent as percent, 'action' as type,";
2552
        $sql .= " a.fk_element, a.elementtype,";
2553
        $sql .= " a.fk_contact,";
2554
        $sql .= " a.email_from as msg_from,";
2555
        $sql .= " c.code as acode, c.libelle as alabel, c.picto as apicto,";
2556
        $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";
2557
        if (is_object($filterobj) && get_only_class($filterobj) == 'Societe') {
2558
            $sql .= ", sp.lastname, sp.firstname";
2559
        } elseif (is_object($filterobj) && get_only_class($filterobj) == 'Adherent') {
2560
            $sql .= ", m.lastname, m.firstname";
2561
        } elseif (is_object($filterobj) && get_only_class($filterobj) == 'CommandeFournisseur') {
2562
            $sql .= ", o.ref";
2563
        } elseif (is_object($filterobj) && get_only_class($filterobj) == 'Product') {
2564
            $sql .= ", o.ref";
2565
        } elseif (is_object($filterobj) && get_only_class($filterobj) == 'Ticket') {
2566
            $sql .= ", o.ref";
2567
        } elseif (is_object($filterobj) && get_only_class($filterobj) == 'BOM') {
2568
            $sql .= ", o.ref";
2569
        } elseif (is_object($filterobj) && get_only_class($filterobj) == 'Contrat') {
2570
            $sql .= ", o.ref";
2571
        }
2572
        $sql .= " FROM " . MAIN_DB_PREFIX . "actioncomm as a";
2573
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "user as u on u.rowid = a.fk_user_action";
2574
        $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "c_actioncomm as c ON a.fk_action = c.id";
2575
2576
        $force_filter_contact = $filterobj instanceof User;
2577
2578
        if (is_object($objcon) && $objcon->id > 0) {
2579
            $force_filter_contact = true;
2580
            $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "actioncomm_resources as r ON a.id = r.fk_actioncomm";
2581
            $sql .= " AND r.element_type = '" . $db->escape($objcon->table_element) . "' AND r.fk_element = " . ((int)$objcon->id);
2582
        }
2583
2584
        if (is_object($filterobj) && get_only_class($filterobj) == 'Societe') {
2585
            $sql .= " LEFT JOIN " . MAIN_DB_PREFIX . "socpeople as sp ON a.fk_contact = sp.rowid";
2586
        } elseif (is_object($filterobj) && get_only_class($filterobj) == 'Dolresource') {
2587
            $sql .= " INNER JOIN " . MAIN_DB_PREFIX . "element_resources as er";
2588
            $sql .= " ON er.resource_type = 'dolresource'";
2589
            $sql .= " AND er.element_id = a.id";
2590
            $sql .= " AND er.resource_id = " . ((int)$filterobj->id);
2591
        } elseif (is_object($filterobj) && get_only_class($filterobj) == 'Adherent') {
2592
            $sql .= ", " . MAIN_DB_PREFIX . "adherent as m";
2593
        } elseif (is_object($filterobj) && get_only_class($filterobj) == 'CommandeFournisseur') {
2594
            $sql .= ", " . MAIN_DB_PREFIX . "commande_fournisseur as o";
2595
        } elseif (is_object($filterobj) && get_only_class($filterobj) == 'Product') {
2596
            $sql .= ", " . MAIN_DB_PREFIX . "product as o";
2597
        } elseif (is_object($filterobj) && get_only_class($filterobj) == 'Ticket') {
2598
            $sql .= ", " . MAIN_DB_PREFIX . "ticket as o";
2599
        } elseif (is_object($filterobj) && get_only_class($filterobj) == 'BOM') {
2600
            $sql .= ", " . MAIN_DB_PREFIX . "bom_bom as o";
2601
        } elseif (is_object($filterobj) && get_only_class($filterobj) == 'Contrat') {
2602
            $sql .= ", " . MAIN_DB_PREFIX . "contrat as o";
2603
        }
2604
2605
        $sql .= " WHERE a.entity IN (" . getEntity('agenda') . ")";
2606
        if (!$force_filter_contact) {
2607
            if (is_object($filterobj) && in_array(get_only_class($filterobj), array('Societe', 'Client', 'Fournisseur')) && $filterobj->id) {
2608
                $sql .= " AND a.fk_soc = " . ((int)$filterobj->id);
2609
            } elseif (is_object($filterobj) && get_only_class($filterobj) == 'Project' && $filterobj->id) {
2610
                $sql .= " AND a.fk_project = " . ((int)$filterobj->id);
2611
            } elseif (is_object($filterobj) && get_only_class($filterobj) == 'Adherent') {
2612
                $sql .= " AND a.fk_element = m.rowid AND a.elementtype = 'member'";
2613
                if ($filterobj->id) {
2614
                    $sql .= " AND a.fk_element = " . ((int)$filterobj->id);
2615
                }
2616
            } elseif (is_object($filterobj) && get_only_class($filterobj) == 'CommandeFournisseur') {
2617
                $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'order_supplier'";
2618
                if ($filterobj->id) {
2619
                    $sql .= " AND a.fk_element = " . ((int)$filterobj->id);
2620
                }
2621
            } elseif (is_object($filterobj) && get_only_class($filterobj) == 'Product') {
2622
                $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'product'";
2623
                if ($filterobj->id) {
2624
                    $sql .= " AND a.fk_element = " . ((int)$filterobj->id);
2625
                }
2626
            } elseif (is_object($filterobj) && get_only_class($filterobj) == 'Ticket') {
2627
                $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'ticket'";
2628
                if ($filterobj->id) {
2629
                    $sql .= " AND a.fk_element = " . ((int)$filterobj->id);
2630
                }
2631
            } elseif (is_object($filterobj) && get_only_class($filterobj) == 'BOM') {
2632
                $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'bom'";
2633
                if ($filterobj->id) {
2634
                    $sql .= " AND a.fk_element = " . ((int)$filterobj->id);
2635
                }
2636
            } elseif (is_object($filterobj) && get_only_class($filterobj) == 'Contrat') {
2637
                $sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'contract'";
2638
                if ($filterobj->id) {
2639
                    $sql .= " AND a.fk_element = " . ((int)$filterobj->id);
2640
                }
2641
            }
2642
        } else {
2643
            $sql .= " AND u.rowid = " . ((int)$filterobj->id);
2644
        }
2645
2646
        // Condition on actioncode
2647
        if (!empty($actioncode)) {
2648
            if (!getDolGlobalString('AGENDA_USE_EVENT_TYPE')) {
2649
                if ($actioncode == 'AC_NON_AUTO') {
2650
                    $sql .= " AND c.type != 'systemauto'";
2651
                } elseif ($actioncode == 'AC_ALL_AUTO') {
2652
                    $sql .= " AND c.type = 'systemauto'";
2653
                } else {
2654
                    if ($actioncode == 'AC_OTH') {
2655
                        $sql .= " AND c.type != 'systemauto'";
2656
                    } elseif ($actioncode == 'AC_OTH_AUTO') {
2657
                        $sql .= " AND c.type = 'systemauto'";
2658
                    }
2659
                }
2660
            } else {
2661
                if ($actioncode == 'AC_NON_AUTO') {
2662
                    $sql .= " AND c.type != 'systemauto'";
2663
                } elseif ($actioncode == 'AC_ALL_AUTO') {
2664
                    $sql .= " AND c.type = 'systemauto'";
2665
                } else {
2666
                    $sql .= " AND c.code = '" . $db->escape($actioncode) . "'";
2667
                }
2668
            }
2669
        }
2670
        if ($donetodo == 'todo') {
2671
            $sql .= " AND ((a.percent >= 0 AND a.percent < 100) OR (a.percent = -1 AND a.datep > '" . $db->idate($now) . "'))";
2672
        } elseif ($donetodo == 'done') {
2673
            $sql .= " AND (a.percent = 100 OR (a.percent = -1 AND a.datep <= '" . $db->idate($now) . "'))";
2674
        }
2675
        if (is_array($filters) && $filters['search_agenda_label']) {
2676
            $sql .= natural_search('a.label', $filters['search_agenda_label']);
2677
        }
2678
    }
2679
2680
    // Add also event from emailings. TODO This should be replaced by an automatic event ? May be it's too much for very large emailing.
2681
    if (
2682
        isModEnabled('mailing') && !empty($objcon->email)
2683
        && (empty($actioncode) || $actioncode == 'AC_OTH_AUTO' || $actioncode == 'AC_EMAILING')
2684
    ) {
2685
        $langs->load("mails");
2686
2687
        $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";
2688
        $sql2 .= ", null as fk_element, '' as elementtype, null as contact_id";
2689
        $sql2 .= ", 'AC_EMAILING' as acode, '' as alabel, '' as apicto";
2690
        $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
2691
        if (is_object($filterobj) && get_only_class($filterobj) == 'Societe') {
2692
            $sql2 .= ", '' as lastname, '' as firstname";
2693
        } elseif (is_object($filterobj) && get_only_class($filterobj) == 'Adherent') {
2694
            $sql2 .= ", '' as lastname, '' as firstname";
2695
        } elseif (is_object($filterobj) && get_only_class($filterobj) == 'CommandeFournisseur') {
2696
            $sql2 .= ", '' as ref";
2697
        } elseif (is_object($filterobj) && get_only_class($filterobj) == 'Product') {
2698
            $sql2 .= ", '' as ref";
2699
        } elseif (is_object($filterobj) && get_only_class($filterobj) == 'Ticket') {
2700
            $sql2 .= ", '' as ref";
2701
        }
2702
        $sql2 .= " FROM " . MAIN_DB_PREFIX . "mailing as m, " . MAIN_DB_PREFIX . "mailing_cibles as mc, " . MAIN_DB_PREFIX . "user as u";
2703
        $sql2 .= " WHERE mc.email = '" . $db->escape($objcon->email) . "'"; // Search is done on email.
2704
        $sql2 .= " AND mc.statut = 1";
2705
        $sql2 .= " AND u.rowid = m.fk_user_valid";
2706
        $sql2 .= " AND mc.fk_mailing=m.rowid";
2707
    }
2708
2709
    if ($sql || $sql2) {    // May not be defined if module Agenda is not enabled and mailing module disabled too
2710
        if (!empty($sql) && !empty($sql2)) {
2711
            $sql = $sql . " UNION " . $sql2;
2712
        } elseif (empty($sql) && !empty($sql2)) {
2713
            $sql = $sql2;
2714
        }
2715
2716
        //TODO Add navigation with this limits...
2717
        $offset = 0;
2718
        $limit = 1000;
2719
2720
        // Complete request and execute it with limit
2721
        $sql .= $db->order($sortfield_new, $sortorder);
2722
        if ($limit) {
2723
            $sql .= $db->plimit($limit + 1, $offset);
2724
        }
2725
2726
        dol_syslog("function.lib::show_actions_messaging", LOG_DEBUG);
2727
        $resql = $db->query($sql);
2728
        if ($resql) {
2729
            $i = 0;
2730
            $num = $db->num_rows($resql);
2731
2732
            $imaxinloop = ($limit ? min($num, $limit) : $num);
2733
            while ($i < $imaxinloop) {
2734
                $obj = $db->fetch_object($resql);
2735
2736
                if ($obj->type == 'action') {
2737
                    $contactaction = new ActionComm($db);
2738
                    $contactaction->id = $obj->id;
2739
                    $result = $contactaction->fetchResources();
2740
                    if ($result < 0) {
2741
                        dol_print_error($db);
2742
                        setEventMessage("actions.lib::show_actions_messaging Error fetch resource", 'errors');
2743
                    }
2744
2745
                    //if ($donetodo == 'todo') $sql.= " AND ((a.percent >= 0 AND a.percent < 100) OR (a.percent = -1 AND a.datep > '".$db->idate($now)."'))";
2746
                    //elseif ($donetodo == 'done') $sql.= " AND (a.percent = 100 OR (a.percent = -1 AND a.datep <= '".$db->idate($now)."'))";
2747
                    $tododone = '';
2748
                    if (($obj->percent >= 0 and $obj->percent < 100) || ($obj->percent == -1 && $obj->dp > $now)) {
2749
                        $tododone = 'todo';
2750
                    }
2751
2752
                    $histo[$numaction] = array(
2753
                        'type' => $obj->type,
2754
                        'tododone' => $tododone,
2755
                        'id' => $obj->id,
2756
                        'datestart' => $db->jdate($obj->dp),
2757
                        'dateend' => $db->jdate($obj->dp2),
2758
                        'note' => $obj->label,
2759
                        'message' => dol_htmlentitiesbr($obj->message),
2760
                        'percent' => $obj->percent,
2761
2762
                        'userid' => $obj->user_id,
2763
                        'login' => $obj->user_login,
2764
                        'userfirstname' => $obj->user_firstname,
2765
                        'userlastname' => $obj->user_lastname,
2766
                        'userphoto' => $obj->user_photo,
2767
                        'msg_from' => $obj->msg_from,
2768
2769
                        'contact_id' => $obj->fk_contact,
2770
                        'socpeopleassigned' => $contactaction->socpeopleassigned,
2771
                        'lastname' => (empty($obj->lastname) ? '' : $obj->lastname),
2772
                        'firstname' => (empty($obj->firstname) ? '' : $obj->firstname),
2773
                        'fk_element' => $obj->fk_element,
2774
                        'elementtype' => $obj->elementtype,
2775
                        // Type of event
2776
                        'acode' => $obj->acode,
2777
                        'alabel' => $obj->alabel,
2778
                        'libelle' => $obj->alabel, // deprecated
2779
                        'apicto' => $obj->apicto
2780
                    );
2781
                } else {
2782
                    $histo[$numaction] = array(
2783
                        'type' => $obj->type,
2784
                        'tododone' => 'done',
2785
                        'id' => $obj->id,
2786
                        'datestart' => $db->jdate($obj->dp),
2787
                        'dateend' => $db->jdate($obj->dp2),
2788
                        'note' => $obj->label,
2789
                        'message' => dol_htmlentitiesbr($obj->message),
2790
                        'percent' => $obj->percent,
2791
                        'acode' => $obj->acode,
2792
2793
                        'userid' => $obj->user_id,
2794
                        'login' => $obj->user_login,
2795
                        'userfirstname' => $obj->user_firstname,
2796
                        'userlastname' => $obj->user_lastname,
2797
                        'userphoto' => $obj->user_photo
2798
                    );
2799
                }
2800
2801
                $numaction++;
2802
                $i++;
2803
            }
2804
        } else {
2805
            dol_print_error($db);
2806
        }
2807
    }
2808
2809
    // Set $out to show events
2810
    $out = '';
2811
2812
    if (!isModEnabled('agenda')) {
2813
        $langs->loadLangs(array("admin", "errors"));
2814
        $out = info_admin($langs->trans("WarningModuleXDisabledSoYouMayMissEventHere", $langs->transnoentitiesnoconv("Module2400Name")), 0, 0, 'warning');
2815
    }
2816
2817
    if (isModEnabled('agenda') || (isModEnabled('mailing') && !empty($objcon->email))) {
2818
        $delay_warning = $conf->global->MAIN_DELAY_ACTIONS_TODO * 24 * 60 * 60;
2819
2820
        include_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php';
2821
2822
        $formactions = new FormActions($db);
2823
        $actionstatic = new ActionComm($db);
2824
        $userstatic = new User($db);
2825
        $contactstatic = new Contact($db);
2826
        $userGetNomUrlCache = array();
2827
        $contactGetNomUrlCache = array();
2828
2829
        $out .= '<div class="filters-container" >';
2830
        $out .= '<form name="listactionsfilter" class="listactionsfilter" action="' . $_SERVER["PHP_SELF"] . '" method="POST">';
2831
        $out .= '<input type="hidden" name="token" value="' . newToken() . '">';
2832
2833
        if (
2834
            $objcon && get_only_class($objcon) == 'Contact' &&
2835
            (is_null($filterobj) || get_only_class($filterobj) == 'Societe')
2836
        ) {
2837
            $out .= '<input type="hidden" name="id" value="' . $objcon->id . '" />';
2838
        } else {
2839
            $out .= '<input type="hidden" name="id" value="' . $filterobj->id . '" />';
2840
        }
2841
        if (($filterobj && get_only_class($filterobj) == 'Societe')) {
2842
            $out .= '<input type="hidden" name="socid" value="' . $filterobj->id . '" />';
2843
        } else {
2844
            $out .= '<input type="hidden" name="userid" value="' . $filterobj->id . '" />';
2845
        }
2846
2847
        $out .= "\n";
2848
2849
        $out .= '<div class="div-table-responsive-no-min">';
2850
        $out .= '<table class="noborder borderbottom centpercent">';
2851
2852
        $out .= '<tr class="liste_titre">';
2853
2854
        // Action column
2855
        if (getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
2856
            $out .= '<th class="liste_titre width50 middle">';
2857
            $searchpicto = $form->showFilterAndCheckAddButtons($massactionbutton ? 1 : 0, 'checkforselect', 1);
2858
            $out .= $searchpicto;
2859
            $out .= '</th>';
2860
        }
2861
2862
        $out .= getTitleFieldOfList('Date', 0, $_SERVER["PHP_SELF"], 'a.datep', '', $param, '', $sortfield, $sortorder, '') . "\n";
2863
2864
        $out .= '<th class="liste_titre"><strong class="hideonsmartphone">' . $langs->trans("Search") . ' : </strong></th>';
2865
        if ($donetodo) {
2866
            $out .= '<th class="liste_titre"></th>';
2867
        }
2868
        $out .= '<th class="liste_titre">';
2869
        $out .= '<span class="fas fa-square inline-block fawidth30" style=" color: #ddd;" title="' . $langs->trans("ActionType") . '"></span>';
2870
        //$out .= img_picto($langs->trans("Type"), 'type');
2871
        $out .= $formactions->select_type_actions($actioncode, "actioncode", '', !getDolGlobalString('AGENDA_USE_EVENT_TYPE') ? 1 : -1, 0, 0, 1, 'minwidth200imp');
2872
        $out .= '</th>';
2873
        $out .= '<th class="liste_titre maxwidth100onsmartphone">';
2874
        $out .= '<input type="text" class="maxwidth100onsmartphone" name="search_agenda_label" value="' . $filters['search_agenda_label'] . '" placeholder="' . $langs->trans("Label") . '">';
2875
        $out .= '</th>';
2876
2877
        // Action column
2878
        if (!getDolGlobalString('MAIN_CHECKBOX_LEFT_COLUMN')) {
2879
            $out .= '<th class="liste_titre width50 middle">';
2880
            $searchpicto = $form->showFilterAndCheckAddButtons($massactionbutton ? 1 : 0, 'checkforselect', 1);
2881
            $out .= $searchpicto;
2882
            $out .= '</th>';
2883
        }
2884
2885
        $out .= '</tr>';
2886
2887
2888
        $out .= '</table>';
2889
2890
        $out .= '</form>';
2891
        $out .= '</div>';
2892
2893
        $out .= "\n";
2894
2895
        $out .= '<ul class="timeline">';
2896
2897
        if ($donetodo) {
2898
            $tmp = '';
2899
            if ($filterobj instanceof Societe) {
2900
                $tmp .= '<a href="' . constant('BASE_URL') . '/comm/action/list.php?mode=show_list&socid=' . $filterobj->id . '&status=done">';
2901
            }
2902
            if ($filterobj instanceof User) {
2903
                $tmp .= '<a href="' . constant('BASE_URL') . '/comm/action/list.php?mode=show_list&socid=' . $filterobj->id . '&status=done">';
2904
            }
2905
            $tmp .= ($donetodo != 'done' ? $langs->trans("ActionsToDoShort") : '');
2906
            $tmp .= ($donetodo != 'done' && $donetodo != 'todo' ? ' / ' : '');
2907
            $tmp .= ($donetodo != 'todo' ? $langs->trans("ActionsDoneShort") : '');
2908
            //$out.=$langs->trans("ActionsToDoShort").' / '.$langs->trans("ActionsDoneShort");
2909
            if ($filterobj instanceof Societe) {
2910
                $tmp .= '</a>';
2911
            }
2912
            if ($filterobj instanceof User) {
2913
                $tmp .= '</a>';
2914
            }
2915
            $out .= getTitleFieldOfList($tmp);
2916
        }
2917
2918
        $caction = new CActionComm($db);
2919
        $arraylist = $caction->liste_array(1, 'code', '', (!getDolGlobalString('AGENDA_USE_EVENT_TYPE') ? 1 : 0), '', 1);
2920
2921
        $actualCycleDate = false;
2922
2923
        // Loop on each event to show it
2924
        foreach ($histo as $key => $value) {
2925
            $actionstatic->fetch($histo[$key]['id']); // TODO Do we need this, we already have a lot of data of line into $histo
2926
2927
            $actionstatic->type_picto = $histo[$key]['apicto'];
2928
            $actionstatic->type_code = $histo[$key]['acode'];
2929
2930
            $labeltype = $actionstatic->type_code;
2931
            if (!getDolGlobalString('AGENDA_USE_EVENT_TYPE') && empty($arraylist[$labeltype])) {
2932
                $labeltype = 'AC_OTH';
2933
            }
2934
            if (!empty($actionstatic->code) && preg_match('/^TICKET_MSG/', $actionstatic->code)) {
2935
                $labeltype = $langs->trans("Message");
2936
            } else {
2937
                if (!empty($arraylist[$labeltype])) {
2938
                    $labeltype = $arraylist[$labeltype];
2939
                }
2940
                if ($actionstatic->type_code == 'AC_OTH_AUTO' && ($actionstatic->type_code != $actionstatic->code) && $labeltype && !empty($arraylist[$actionstatic->code])) {
2941
                    $labeltype .= ' - ' . $arraylist[$actionstatic->code]; // Use code in priority on type_code
2942
                }
2943
            }
2944
2945
            $url = constant('BASE_URL') . '/comm/action/card.php?id=' . $histo[$key]['id'];
2946
2947
            $tmpa = dol_getdate($histo[$key]['datestart'], false);
2948
2949
            if (isset($tmpa['year']) && isset($tmpa['yday']) && $actualCycleDate !== $tmpa['year'] . '-' . $tmpa['yday']) {
2950
                $actualCycleDate = $tmpa['year'] . '-' . $tmpa['yday'];
2951
                $out .= '<!-- timeline time label -->';
2952
                $out .= '<li class="time-label">';
2953
                $out .= '<span class="timeline-badge-date">';
2954
                $out .= dol_print_date($histo[$key]['datestart'], 'daytext', 'tzuserrel', $langs);
2955
                $out .= '</span>';
2956
                $out .= '</li>';
2957
                $out .= '<!-- /.timeline-label -->';
2958
            }
2959
2960
2961
            $out .= '<!-- timeline item -->' . "\n";
2962
            $out .= '<li class="timeline-code-' . strtolower($actionstatic->code) . '">';
2963
2964
            //$timelineicon = getTimelineIcon($actionstatic, $histo, $key);
2965
            $typeicon = $actionstatic->getTypePicto('pictofixedwidth timeline-icon-not-applicble', $labeltype);
2966
            //$out .= $timelineicon;
2967
            //var_dump($timelineicon);
2968
            $out .= $typeicon;
2969
2970
            $out .= '<div class="timeline-item">' . "\n";
2971
2972
            $out .= '<span class="time timeline-header-action2">';
2973
2974
            if (isset($histo[$key]['type']) && $histo[$key]['type'] == 'mailing') {
2975
                $out .= '<a class="paddingleft paddingright timeline-btn2 editfielda" href="' . constant('BASE_URL') . '/comm/mailing/card.php?id=' . $histo[$key]['id'] . '">' . img_object($langs->trans("ShowEMailing"), "email") . ' ';
2976
                $out .= $histo[$key]['id'];
2977
                $out .= '</a> ';
2978
            } else {
2979
                $out .= $actionstatic->getNomUrl(1, -1, 'valignmiddle') . ' ';
2980
            }
2981
2982
            if (
2983
                $user->hasRight('agenda', 'allactions', 'create') ||
2984
                (($actionstatic->authorid == $user->id || $actionstatic->userownerid == $user->id) && $user->hasRight('agenda', 'myactions', 'create'))
2985
            ) {
2986
                $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=' . $_SERVER["PHP_SELF"] . ('?' . $param) . '">';
2987
                //$out .= '<i class="fa fa-pencil" title="'.$langs->trans("Modify").'" ></i>';
2988
                $out .= img_picto($langs->trans("Modify"), 'edit', 'class="edita"');
2989
                $out .= '</a>';
2990
            }
2991
2992
            $out .= '</span>';
2993
2994
            // Date
2995
            $out .= '<span class="time"><i class="fa fa-clock-o valignmiddle"></i> <span class="valignmiddle">';
2996
            $out .= dol_print_date($histo[$key]['datestart'], 'dayhour', 'tzuserrel');
2997
            if ($histo[$key]['dateend'] && $histo[$key]['dateend'] != $histo[$key]['datestart']) {
2998
                $tmpa = dol_getdate($histo[$key]['datestart'], true);
2999
                $tmpb = dol_getdate($histo[$key]['dateend'], true);
3000
                if ($tmpa['mday'] == $tmpb['mday'] && $tmpa['mon'] == $tmpb['mon'] && $tmpa['year'] == $tmpb['year']) {
3001
                    $out .= '-' . dol_print_date($histo[$key]['dateend'], 'hour', 'tzuserrel');
3002
                } else {
3003
                    $out .= '-' . dol_print_date($histo[$key]['dateend'], 'dayhour', 'tzuserrel');
3004
                }
3005
            }
3006
            $late = 0;
3007
            if ($histo[$key]['percent'] == 0 && $histo[$key]['datestart'] && $histo[$key]['datestart'] < ($now - $delay_warning)) {
3008
                $late = 1;
3009
            }
3010
            if ($histo[$key]['percent'] == 0 && !$histo[$key]['datestart'] && $histo[$key]['dateend'] && $histo[$key]['datestart'] < ($now - $delay_warning)) {
3011
                $late = 1;
3012
            }
3013
            if ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100 && $histo[$key]['dateend'] && $histo[$key]['dateend'] < ($now - $delay_warning)) {
3014
                $late = 1;
3015
            }
3016
            if ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100 && !$histo[$key]['dateend'] && $histo[$key]['datestart'] && $histo[$key]['datestart'] < ($now - $delay_warning)) {
3017
                $late = 1;
3018
            }
3019
            if ($late) {
3020
                $out .= img_warning($langs->trans("Late")) . ' ';
3021
            }
3022
            $out .= "</span></span>\n";
3023
3024
            // Ref
3025
            $out .= '<h3 class="timeline-header">';
3026
3027
            // Author of event
3028
            $out .= '<div class="messaging-author inline-block tdoverflowmax150 valignmiddle marginrightonly">';
3029
            if ($histo[$key]['userid'] > 0) {
3030
                if (!isset($userGetNomUrlCache[$histo[$key]['userid']])) { // is in cache ?
3031
                    $userstatic->fetch($histo[$key]['userid']);
3032
                    $userGetNomUrlCache[$histo[$key]['userid']] = $userstatic->getNomUrl(-1, '', 0, 0, 16, 0, 'firstelselast', '');
3033
                }
3034
                $out .= $userGetNomUrlCache[$histo[$key]['userid']];
3035
            } elseif (!empty($histo[$key]['msg_from']) && $actionstatic->code == 'TICKET_MSG') {
3036
                if (!isset($contactGetNomUrlCache[$histo[$key]['msg_from']])) {
3037
                    if ($contactstatic->fetch(0, null, '', $histo[$key]['msg_from']) > 0) {
3038
                        $contactGetNomUrlCache[$histo[$key]['msg_from']] = $contactstatic->getNomUrl(-1, '', 16);
3039
                    } else {
3040
                        $contactGetNomUrlCache[$histo[$key]['msg_from']] = $histo[$key]['msg_from'];
3041
                    }
3042
                }
3043
                $out .= $contactGetNomUrlCache[$histo[$key]['msg_from']];
3044
            }
3045
            $out .= '</div>';
3046
3047
            // Title
3048
            $out .= ' <div class="messaging-title inline-block">';
3049
            //$out .= $actionstatic->getTypePicto();
3050
            if (empty($conf->dol_optimize_smallscreen) && $actionstatic->type_code != 'AC_OTH_AUTO') {
3051
                $out .= $labeltype . ' - ';
3052
            }
3053
3054
            $libelle = '';
3055
            if (preg_match('/^TICKET_MSG/', $actionstatic->code)) {
3056
                $out .= $langs->trans('TicketNewMessage');
3057
            } elseif (preg_match('/^TICKET_MSG_PRIVATE/', $actionstatic->code)) {
3058
                $out .= $langs->trans('TicketNewMessage') . ' <em>(' . $langs->trans('Private') . ')</em>';
3059
            } elseif (isset($histo[$key]['type'])) {
3060
                if ($histo[$key]['type'] == 'action') {
3061
                    $transcode = $langs->transnoentitiesnoconv("Action" . $histo[$key]['acode']);
3062
                    $libelle = ($transcode != "Action" . $histo[$key]['acode'] ? $transcode : $histo[$key]['alabel']);
3063
                    $libelle = $histo[$key]['note'];
3064
                    $actionstatic->id = $histo[$key]['id'];
3065
                    $out .= dol_escape_htmltag(dol_trunc($libelle, 120));
3066
                } elseif ($histo[$key]['type'] == 'mailing') {
3067
                    $out .= '<a href="' . constant('BASE_URL') . '/comm/mailing/card.php?id=' . $histo[$key]['id'] . '">' . img_object($langs->trans("ShowEMailing"), "email") . ' ';
3068
                    $transcode = $langs->transnoentitiesnoconv("Action" . $histo[$key]['acode']);
3069
                    $libelle = ($transcode != "Action" . $histo[$key]['acode'] ? $transcode : 'Send mass mailing');
3070
                    $out .= dol_escape_htmltag(dol_trunc($libelle, 120));
3071
                } else {
3072
                    $libelle .= $histo[$key]['note'];
3073
                    $out .= dol_escape_htmltag(dol_trunc($libelle, 120));
3074
                }
3075
            }
3076
3077
            if (isset($histo[$key]['elementtype']) && !empty($histo[$key]['fk_element'])) {
3078
                if (isset($conf->cache['elementlinkcache'][$histo[$key]['elementtype']]) && isset($conf->cache['elementlinkcache'][$histo[$key]['elementtype']][$histo[$key]['fk_element']])) {
3079
                    $link = $conf->cache['elementlinkcache'][$histo[$key]['elementtype']][$histo[$key]['fk_element']];
3080
                } else {
3081
                    if (!isset($conf->cache['elementlinkcache'][$histo[$key]['elementtype']])) {
3082
                        $conf->cache['elementlinkcache'][$histo[$key]['elementtype']] = array();
3083
                    }
3084
                    $link = dolGetElementUrl($histo[$key]['fk_element'], $histo[$key]['elementtype'], 1);
3085
                    $conf->cache['elementlinkcache'][$histo[$key]['elementtype']][$histo[$key]['fk_element']] = $link;
3086
                }
3087
                if ($link) {
3088
                    $out .= ' - ' . $link;
3089
                }
3090
            }
3091
3092
            $out .= '</div>';
3093
3094
            $out .= '</h3>';
3095
3096
            // Message
3097
            if (
3098
                !empty($histo[$key]['message'] && $histo[$key]['message'] != $libelle)
3099
                && $actionstatic->code != 'AC_TICKET_CREATE'
3100
                && $actionstatic->code != 'AC_TICKET_MODIFY'
3101
            ) {
3102
                $out .= '<div class="timeline-body wordbreak">';
3103
                $truncateLines = getDolGlobalInt('MAIN_TRUNCATE_TIMELINE_MESSAGE', 3);
3104
                $truncatedText = dolGetFirstLineOfText($histo[$key]['message'], $truncateLines);
3105
                if ($truncateLines > 0 && strlen($histo[$key]['message']) > strlen($truncatedText)) {
3106
                    $out .= '<div class="readmore-block --closed" >';
3107
                    $out .= '	<div class="readmore-block__excerpt" >';
3108
                    $out .= dolPrintHTML($truncatedText);
3109
                    $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=' . $_SERVER["PHP_SELF"] . ('?' . $param) . '" >' . $langs->trans("ReadMore") . ' <span class="fa fa-chevron-right" aria-hidden="true"></span></a>';
3110
                    $out .= '	</div>';
3111
                    $out .= '	<div class="readmore-block__full-text" >';
3112
                    $out .= dolPrintHTML($histo[$key]['message']);
3113
                    $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>';
3114
                    $out .= '	</div>';
3115
                    $out .= '</div>';
3116
                } else {
3117
                    $out .= dolPrintHTML($histo[$key]['message']);
3118
                }
3119
3120
                $out .= '</div>';
3121
            }
3122
3123
            // Timeline footer
3124
            $footer = '';
3125
3126
            // Contact for this action
3127
            if (isset($histo[$key]['socpeopleassigned']) && is_array($histo[$key]['socpeopleassigned']) && count($histo[$key]['socpeopleassigned']) > 0) {
3128
                $contactList = '';
3129
                foreach ($histo[$key]['socpeopleassigned'] as $cid => $Tab) {
3130
                    if (empty($conf->cache['contact'][$histo[$key]['contact_id']])) {
3131
                        $contact = new Contact($db);
3132
                        $contact->fetch($cid);
3133
                        $conf->cache['contact'][$histo[$key]['contact_id']] = $contact;
3134
                    } else {
3135
                        $contact = $conf->cache['contact'][$histo[$key]['contact_id']];
3136
                    }
3137
3138
                    if ($contact) {
3139
                        $contactList .= !empty($contactList) ? ', ' : '';
3140
                        $contactList .= $contact->getNomUrl(1);
3141
                        if (isset($histo[$key]['acode']) && $histo[$key]['acode'] == 'AC_TEL') {
3142
                            if (!empty($contact->phone_pro)) {
3143
                                $contactList .= '(' . dol_print_phone($contact->phone_pro) . ')';
3144
                            }
3145
                        }
3146
                    }
3147
                }
3148
3149
                $footer .= $langs->trans('ActionOnContact') . ' : ' . $contactList;
3150
            } elseif (empty($objcon->id) && isset($histo[$key]['contact_id']) && $histo[$key]['contact_id'] > 0) {
3151
                if (empty($conf->cache['contact'][$histo[$key]['contact_id']])) {
3152
                    $contact = new Contact($db);
3153
                    $result = $contact->fetch($histo[$key]['contact_id']);
3154
                    $conf->cache['contact'][$histo[$key]['contact_id']] = $contact;
3155
                } else {
3156
                    $contact = $conf->cache['contact'][$histo[$key]['contact_id']];
3157
                    $result = ($contact instanceof Contact) ? $contact->id : 0;
3158
                }
3159
3160
                if ($result > 0) {
3161
                    $footer .= $contact->getNomUrl(1);
3162
                    if (isset($histo[$key]['acode']) && $histo[$key]['acode'] == 'AC_TEL') {
3163
                        if (!empty($contact->phone_pro)) {
3164
                            $footer .= '(' . dol_print_phone($contact->phone_pro) . ')';
3165
                        }
3166
                    }
3167
                }
3168
            }
3169
3170
            $documents = getActionCommEcmList($actionstatic);
3171
            if (!empty($documents)) {
3172
                $footer .= '<div class="timeline-documents-container">';
3173
                foreach ($documents as $doc) {
3174
                    $footer .= '<span id="document_' . $doc->id . '" class="timeline-documents" ';
3175
                    $footer .= ' data-id="' . $doc->id . '" ';
3176
                    $footer .= ' data-path="' . $doc->filepath . '"';
3177
                    $footer .= ' data-filename="' . dol_escape_htmltag($doc->filename) . '" ';
3178
                    $footer .= '>';
3179
3180
                    $filePath = DOL_DATA_ROOT . '/' . $doc->filepath . '/' . $doc->filename;
3181
                    $mime = dol_mimetype($filePath);
3182
                    $file = $actionstatic->id . '/' . $doc->filename;
3183
                    $thumb = $actionstatic->id . '/thumbs/' . substr($doc->filename, 0, strrpos($doc->filename, '.')) . '_mini' . substr($doc->filename, strrpos($doc->filename, '.'));
3184
                    $doclink = dol_buildpath('document.php', 1) . '?modulepart=actions&attachment=0&file=' . urlencode($file) . '&entity=' . $conf->entity;
3185
                    $viewlink = dol_buildpath('viewimage.php', 1) . '?modulepart=actions&file=' . urlencode($thumb) . '&entity=' . $conf->entity;
3186
3187
                    $mimeAttr = ' mime="' . $mime . '" ';
3188
                    $class = '';
3189
                    if (in_array($mime, array('image/png', 'image/jpeg', 'application/pdf'))) {
3190
                        $class .= ' documentpreview';
3191
                    }
3192
3193
                    $footer .= '<a href="' . $doclink . '" class="btn-link ' . $class . '" target="_blank" rel="noopener noreferrer" ' . $mimeAttr . ' >';
3194
                    $footer .= img_mime($filePath) . ' ' . $doc->filename;
3195
                    $footer .= '</a>';
3196
3197
                    $footer .= '</span>';
3198
                }
3199
                $footer .= '</div>';
3200
            }
3201
3202
            if (!empty($footer)) {
3203
                $out .= '<div class="timeline-footer">' . $footer . '</div>';
3204
            }
3205
3206
            $out .= '</div>' . "\n"; // end timeline-item
3207
3208
            $out .= '</li>';
3209
            $out .= '<!-- END timeline item -->';
3210
        }
3211
3212
        $out .= "</ul>\n";
3213
3214
        $out .= '<script>
3215
				jQuery(document).ready(function () {
3216
				   $(document).on("click", "[data-read-more-action]", function(e){
3217
					   let readMoreBloc = $(this).closest(".readmore-block");
3218
					   if(readMoreBloc.length > 0){
3219
							e.preventDefault();
3220
							if($(this).attr("data-read-more-action") == "close"){
3221
								readMoreBloc.addClass("--closed").removeClass("--open");
3222
								 $("html, body").animate({
3223
									scrollTop: readMoreBloc.offset().top - 200
3224
								}, 100);
3225
							}else{
3226
								readMoreBloc.addClass("--open").removeClass("--closed");
3227
							}
3228
					   }
3229
					});
3230
				});
3231
			</script>';
3232
3233
3234
        if (empty($histo)) {
3235
            $out .= '<span class="opacitymedium">' . $langs->trans("NoRecordFound") . '</span>';
3236
        }
3237
    }
3238
3239
    if ($noprint) {
3240
        return $out;
3241
    } else {
3242
        print $out;
3243
    }
3244
}
3245
3246
/**
3247
 * Get timeline icon
3248
 *
3249
 * @param ActionComm $actionstatic actioncomm
3250
 * @param array<int,array{percent:int}> $histo histo
3251
 * @param int $key key
3252
 * @return  string                      String with timeline icon
3253
 * @deprecated Use actioncomm->getPictoType() instead
3254
 */
3255
function getTimelineIcon($actionstatic, &$histo, $key)
3256
{
3257
    global $langs;
3258
3259
    $out = '<!-- timeline icon -->' . "\n";
3260
    $iconClass = 'fa fa-comments';
3261
    $img_picto = '';
3262
    $colorClass = '';
3263
    $pictoTitle = '';
3264
3265
    if ($histo[$key]['percent'] == -1) {
3266
        $colorClass = 'timeline-icon-not-applicble';
3267
        $pictoTitle = $langs->trans('StatusNotApplicable');
3268
    } elseif ($histo[$key]['percent'] == 0) {
3269
        $colorClass = 'timeline-icon-todo';
3270
        $pictoTitle = $langs->trans('StatusActionToDo') . ' (0%)';
3271
    } elseif ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100) {
3272
        $colorClass = 'timeline-icon-in-progress';
3273
        $pictoTitle = $langs->trans('StatusActionInProcess') . ' (' . $histo[$key]['percent'] . '%)';
3274
    } elseif ($histo[$key]['percent'] >= 100) {
3275
        $colorClass = 'timeline-icon-done';
3276
        $pictoTitle = $langs->trans('StatusActionDone') . ' (100%)';
3277
    }
3278
3279
    if ($actionstatic->code == 'AC_TICKET_CREATE') {
3280
        $iconClass = 'fa fa-ticket';
3281
    } elseif ($actionstatic->code == 'AC_TICKET_MODIFY') {
3282
        $iconClass = 'fa fa-pencilxxx';
3283
    } elseif (preg_match('/^TICKET_MSG/', $actionstatic->code)) {
3284
        $iconClass = 'fa fa-comments';
3285
    } elseif (preg_match('/^TICKET_MSG_PRIVATE/', $actionstatic->code)) {
3286
        $iconClass = 'fa fa-mask';
3287
    } elseif (getDolGlobalString('AGENDA_USE_EVENT_TYPE')) {
3288
        if ($actionstatic->type_picto) {
3289
            $img_picto = img_picto('', $actionstatic->type_picto);
3290
        } else {
3291
            if ($actionstatic->type_code == 'AC_RDV') {
3292
                $iconClass = 'fa fa-handshake';
3293
            } elseif ($actionstatic->type_code == 'AC_TEL') {
3294
                $iconClass = 'fa fa-phone';
3295
            } elseif ($actionstatic->type_code == 'AC_FAX') {
3296
                $iconClass = 'fa fa-fax';
3297
            } elseif ($actionstatic->type_code == 'AC_EMAIL') {
3298
                $iconClass = 'fa fa-envelope';
3299
            } elseif ($actionstatic->type_code == 'AC_INT') {
3300
                $iconClass = 'fa fa-shipping-fast';
3301
            } elseif ($actionstatic->type_code == 'AC_OTH_AUTO') {
3302
                $iconClass = 'fa fa-robot';
3303
            } elseif (!preg_match('/_AUTO/', $actionstatic->type_code)) {
3304
                $iconClass = 'fa fa-robot';
3305
            }
3306
        }
3307
    }
3308
3309
    $out .= '<i class="' . $iconClass . ' ' . $colorClass . '" title="' . $pictoTitle . '">' . $img_picto . '</i>' . "\n";
3310
    return $out;
3311
}
3312
3313
/**
3314
 * Create a button to copy $valuetocopy in the clipboard (for copy and paste feature).
3315
 * Code that handle the click is inside core/js/lib_foot.js.php.
3316
 *
3317
 * @param string $valuetocopy The value to print
3318
 * @param int<0,1> $showonlyonhover Show the copy-paste button only on hover
3319
 * @param string $texttoshow Replace the value to show with this text. Use 'none' to show no text (only the copy-paste picto)
3320
 * @return  string                          The string to print for the button
3321
 */
3322
function showValueWithClipboardCPButton($valuetocopy, $showonlyonhover = 1, $texttoshow = '')
3323
{
3324
    /*
3325
    global $conf;
3326
3327
    if (!empty($conf->dol_no_mouse_hover)) {
3328
        $showonlyonhover = 0;
3329
    }*/
3330
3331
    $tag = 'span';  // Using div (like any style of type 'block') does not work when using the js copy code.
3332
    if ($texttoshow === 'none') {
3333
        $result = '<span class="clipboardCP' . ($showonlyonhover ? ' clipboardCPShowOnHover' : '') . '"><' . $tag . ' class="clipboardCPValue hidewithsize">' . dol_escape_htmltag($valuetocopy, 1, 1) . '</' . $tag . '><span class="clipboardCPValueToPrint"></span><span class="clipboardCPButton far fa-clipboard opacitymedium paddingleft paddingright"></span><span class="clipboardCPText"></span></span>';
3334
    } elseif ($texttoshow) {
3335
        $result = '<span class="clipboardCP' . ($showonlyonhover ? ' clipboardCPShowOnHover' : '') . '"><' . $tag . ' class="clipboardCPValue hidewithsize">' . dol_escape_htmltag($valuetocopy, 1, 1) . '</' . $tag . '><span class="clipboardCPValueToPrint">' . dol_escape_htmltag($texttoshow, 1, 1) . '</span><span class="clipboardCPButton far fa-clipboard opacitymedium paddingleft paddingright"></span><span class="clipboardCPText"></span></span>';
3336
    } else {
3337
        $result = '<span class="clipboardCP' . ($showonlyonhover ? ' clipboardCPShowOnHover' : '') . '"><' . $tag . ' class="clipboardCPValue">' . dol_escape_htmltag($valuetocopy, 1, 1) . '</' . $tag . '><span class="clipboardCPButton far fa-clipboard opacitymedium paddingleft paddingright"></span><span class="clipboardCPText"></span></span>';
3338
    }
3339
3340
    return $result;
3341
}
3342
3343
/**
3344
 * Start a table with headers and a optional clickable number (don't forget to use "finishSimpleTable()" after the last table row)
3345
 *
3346
 * @param string $header The first left header of the table (automatic translated)
3347
 * @param string $link (optional) The link to a internal dolibarr page, where to go on clicking on the number or the ... (without the first "/")
3348
 * @param string $arguments (optional) Additional arguments for the link (e.g. "search_status=0")
3349
 * @param integer $emptyColumns (optional) Number of empty columns to add after the first column
3350
 * @param integer $number (optional) The number that is shown right after the first header, when -1 the link is shown as '...'
3351
 * @param string $pictofulllist (optional) The picto to use for the full list link
3352
 * @return void
3353
 *
3354
 * @see finishSimpleTable()
3355
 */
3356
function startSimpleTable($header, $link = "", $arguments = "", $emptyColumns = 0, $number = -1, $pictofulllist = '')
3357
{
3358
    global $langs;
3359
3360
    print '<div class="div-table-responsive-no-min">';
3361
    print '<table class="noborder centpercent">';
3362
    print '<tr class="liste_titre">';
3363
3364
    print ($emptyColumns < 1) ? '<th>' : '<th colspan="' . ($emptyColumns + 1) . '">';
3365
3366
    print '<span class="valignmiddle">' . $langs->trans($header) . '</span>';
3367
3368
    if (!empty($link)) {
3369
        if (!empty($arguments)) {
3370
            print '<a href="' . constant('BASE_URL') . '/' . $link . '?' . $arguments . '">';
3371
        } else {
3372
            print '<a href="' . constant('BASE_URL') . '/' . $link . '">';
3373
        }
3374
    }
3375
3376
    if ($number > -1) {
3377
        print '<span class="badge marginleftonlyshort">' . $number . '</span>';
3378
    } elseif (!empty($link)) {
3379
        print '<span class="badge marginleftonlyshort">...</span>';
3380
    }
3381
3382
    if (!empty($link)) {
3383
        print '</a>';
3384
    }
3385
3386
    print '</th>';
3387
3388
    if ($number < 0 && !empty($link)) {
3389
        print '<th class="right">';
3390
        print '</th>';
3391
    }
3392
3393
    print '</tr>';
3394
}
3395
3396
/**
3397
 * Add the correct HTML close tags for "startSimpleTable(...)" (use after the last table line)
3398
 *
3399
 * @param bool $addLineBreak (optional) Add a extra line break after the complete table (\<br\>)
3400
 * @return  void
3401
 *
3402
 * @see startSimpleTable()
3403
 */
3404
function finishSimpleTable($addLineBreak = false)
3405
{
3406
    print '</table>';
3407
    print '</div>';
3408
3409
    if ($addLineBreak) {
3410
        print '<br>';
3411
    }
3412
}
3413
3414
/**
3415
 * Add a summary line to the current open table ("None", "XMoreLines" or "Total xxx")
3416
 *
3417
 * @param integer $tableColumnCount The complete count columns of the table
3418
 * @param integer $num The count of the rows of the table, when it is zero (0) the "$noneWord" is shown instead
3419
 * @param integer $nbofloop (optional)  The maximum count of rows thaht the table show (when it is zero (0) no summary line will show, expect "$noneWord" when $num === 0)
3420
 * @param integer $total (optional)  The total value thaht is shown after when the table has minimum of one entire
3421
 * @param string $noneWord (optional)  The word that is shown when the table has no entries ($num === 0)
3422
 * @param boolean $extraRightColumn (optional)  Add a additional column after the summary word and total number
3423
 * @return void
3424
 */
3425
function addSummaryTableLine($tableColumnCount, $num, $nbofloop = 0, $total = 0, $noneWord = "None", $extraRightColumn = false)
3426
{
3427
    global $langs;
3428
3429
    if ($num === 0) {
3430
        print '<tr class="oddeven">';
3431
        print '<td colspan="' . $tableColumnCount . '"><span class="opacitymedium">' . $langs->trans($noneWord) . '</span></td>';
3432
        print '</tr>';
3433
        return;
3434
    }
3435
3436
    if ($nbofloop === 0) {
3437
        // don't show a summary line
3438
        return;
3439
    }
3440
3441
    if ($num === 0) {
3442
        $colspan = $tableColumnCount;
3443
    } elseif ($num > $nbofloop) {
3444
        $colspan = $tableColumnCount;
3445
    } else {
3446
        $colspan = $tableColumnCount - 1;
3447
    }
3448
3449
    if ($extraRightColumn) {
3450
        $colspan--;
3451
    }
3452
3453
    print '<tr class="liste_total">';
3454
3455
    if ($nbofloop > 0 && $num > $nbofloop) {
3456
        print '<td colspan="' . $colspan . '" class="right">' . $langs->trans("XMoreLines", ($num - $nbofloop)) . '</td>';
3457
    } else {
3458
        print '<td colspan="' . $colspan . '" class="right"> ' . $langs->trans("Total") . '</td>';
3459
        print '<td class="right centpercent">' . price($total) . '</td>';
3460
    }
3461
3462
    if ($extraRightColumn) {
3463
        print '<td></td>';
3464
    }
3465
3466
    print '</tr>';
3467
}
3468
3469
/**
3470
 * Add space between dolGetButtonTitle
3471
 *
3472
 * @param string $moreClass more css class label
3473
 * @return string               html of title separator
3474
 */
3475
function dolGetButtonTitleSeparator($moreClass = "")
3476
{
3477
    return '<span class="button-title-separator ' . $moreClass . '" ></span>';
3478
}
3479
3480
/**
3481
 * get field error icon
3482
 *
3483
 * @param string $fieldValidationErrorMsg message to add in tooltip
3484
 * @return string html output
3485
 */
3486
function getFieldErrorIcon($fieldValidationErrorMsg)
3487
{
3488
    $out = '';
3489
    if (!empty($fieldValidationErrorMsg)) {
3490
        $out .= '<span class="field-error-icon classfortooltip" title="' . dol_escape_htmltag($fieldValidationErrorMsg, 1) . '"  role="alert" >'; // role alert is used for accessibility
3491
        $out .= '<span class="fa fa-exclamation-circle" aria-hidden="true" ></span>'; // For accessibility icon is separated and aria-hidden
3492
        $out .= '</span>';
3493
    }
3494
3495
    return $out;
3496
}
3497
3498
/**
3499
 * Function dolGetButtonTitle : this kind of buttons are used in title in list
3500
 *
3501
 * @param string $label label of button
3502
 * @param string $helpText optional : content for help tooltip
3503
 * @param string $iconClass class for icon element (Example: 'fa fa-file')
3504
 * @param string $url the url for link
3505
 * @param string $id attribute id of button
3506
 * @param int<-2,2> $status 0 no user rights, 1 active, 2 current action or selected, -1 Feature Disabled, -2 disable Other reason use param $helpText as tooltip help
3507
 * @param array<string,mixed> $params various params for future : recommended rather than adding more function arguments
3508
 * @return string               html button
3509
 */
3510
function dolGetButtonTitle($label, $helpText = '', $iconClass = 'fa fa-file', $url = '', $id = '', $status = 1, $params = array())
3511
{
3512
    global $langs, $conf, $user;
3513
3514
    // Actually this conf is used in css too for external module compatibility and smooth transition to this function
3515
    if (getDolGlobalString('MAIN_BUTTON_HIDE_UNAUTHORIZED') && (!$user->admin) && $status <= 0) {
3516
        return '';
3517
    }
3518
3519
    $class = 'btnTitle';
3520
    if (in_array($iconClass, array('fa fa-plus-circle', 'fa fa-plus-circle size15x', 'fa fa-comment-dots', 'fa fa-paper-plane'))) {
3521
        $class .= ' btnTitlePlus';
3522
    }
3523
    $useclassfortooltip = 1;
3524
3525
    if (!empty($params['morecss'])) {
3526
        $class .= ' ' . $params['morecss'];
3527
    }
3528
3529
    $attr = array(
3530
        'class' => $class,
3531
        'href' => empty($url) ? '' : $url
3532
    );
3533
3534
    if (!empty($helpText)) {
3535
        $attr['title'] = dol_escape_htmltag($helpText);
3536
    } elseif (empty($attr['title']) && $label) {
3537
        $attr['title'] = $label;
3538
        $useclassfortooltip = 0;
3539
    }
3540
3541
    if ($status == 2) {
3542
        $attr['class'] .= ' btnTitleSelected';
3543
    } elseif ($status <= 0) {
3544
        $attr['class'] .= ' refused';
3545
3546
        $attr['href'] = '';
3547
3548
        if ($status == -1) { // disable
3549
            $attr['title'] = dol_escape_htmltag($langs->transnoentitiesnoconv("FeatureDisabled"));
3550
        } elseif ($status == 0) { // Not enough permissions
3551
            $attr['title'] = dol_escape_htmltag($langs->transnoentitiesnoconv("NotEnoughPermissions"));
3552
        }
3553
    }
3554
3555
    if (!empty($attr['title']) && $useclassfortooltip) {
3556
        $attr['class'] .= ' classfortooltip';
3557
    }
3558
3559
    if (!empty($id)) {
3560
        $attr['id'] = $id;
3561
    }
3562
3563
    // Override attr
3564
    if (!empty($params['attr']) && is_array($params['attr'])) {
3565
        foreach ($params['attr'] as $key => $value) {
3566
            if ($key == 'class') {
3567
                $attr['class'] .= ' ' . $value;
3568
            } elseif ($key == 'classOverride') {
3569
                $attr['class'] = $value;
3570
            } else {
3571
                $attr[$key] = $value;
3572
            }
3573
        }
3574
    }
3575
3576
    if (isset($attr['href']) && empty($attr['href'])) {
3577
        unset($attr['href']);
3578
    }
3579
3580
    // TODO : add a hook
3581
3582
    // escape all attribute
3583
    $attr = array_map('dol_escape_htmltag', $attr);
3584
3585
    $TCompiledAttr = array();
3586
    foreach ($attr as $key => $value) {
3587
        $TCompiledAttr[] = $key . '="' . $value . '"';
3588
    }
3589
3590
    $compiledAttributes = (empty($TCompiledAttr) ? '' : implode(' ', $TCompiledAttr));
3591
3592
    $tag = (empty($attr['href']) ? 'span' : 'a');
3593
3594
    $button = '<' . $tag . ' ' . $compiledAttributes . '>';
3595
    $button .= '<span class="' . $iconClass . ' valignmiddle btnTitle-icon"></span>';
3596
    if (!empty($params['forcenohideoftext'])) {
3597
        $button .= '<span class="valignmiddle text-plus-circle btnTitle-label' . (empty($params['forcenohideoftext']) ? ' hideonsmartphone' : '') . '">' . $label . '</span>';
3598
    }
3599
    $button .= '</' . $tag . '>';
3600
3601
    return $button;
3602
}
3603
3604
/**
3605
 * Get an array with properties of an element.
3606
 *
3607
 * @param string $elementType Element type (Value of $object->element or value of $object->element@$object->module). Example:
3608
 *                                    'action', 'facture', 'project', 'project_task' or
3609
 *                                    'myobject@mymodule' (or old syntax 'mymodule_myobject' like 'project_task')
3610
 * @return  array{module:string,element:string,table_element:string,subelement:string,classpath:string,classfile:string,classname:string,dir_output:string}     array('module'=>, 'classpath'=>, 'element'=>, 'subelement'=>, 'classfile'=>, 'classname'=>, 'dir_output'=>)
3611
 * @see fetchObjectByElement(), getMultidirOutput()
3612
 */
3613
function getElementProperties($elementType)
3614
{
3615
    global $conf, $db, $hookmanager;
3616
3617
    $regs = array();
3618
3619
    //$element_type='facture';
3620
3621
    $classfile = $classname = $classpath = $subdir = $dir_output = '';
3622
3623
    // Parse element/subelement
3624
    $module = $elementType;
3625
    $element = $elementType;
3626
    $subelement = $elementType;
3627
    $table_element = $elementType;
3628
3629
    // If we ask a resource form external module (instead of default path)
3630
    if (preg_match('/^([^@]+)@([^@]+)$/i', $elementType, $regs)) {  // 'myobject@mymodule'
3631
        $element = $subelement = $regs[1];
3632
        $module = $regs[2];
3633
    }
3634
3635
    // If we ask a resource for a string with an element and a subelement
3636
    // Example 'project_task'
3637
    if (preg_match('/^([^_]+)_([^_]+)/i', $element, $regs)) {   // 'myobject_mysubobject' with myobject=mymodule
3638
        $module = $element = $regs[1];
3639
        $subelement = $regs[2];
3640
    }
3641
3642
    // Object lines will use parent classpath and module ref
3643
    if (substr($elementType, -3) == 'det') {
3644
        $module = preg_replace('/det$/', '', $element);
3645
        $subelement = preg_replace('/det$/', '', $subelement);
3646
        $classpath = $module . '/class';
3647
        $classfile = $module;
3648
        $classname = preg_replace('/det$/', 'Line', $element);
3649
        if (in_array($module, array('expedition', 'propale', 'facture', 'contrat', 'fichinter', 'commandefournisseur'))) {
3650
            $classname = preg_replace('/det$/', 'Ligne', $element);
3651
        }
3652
    }
3653
    // For compatibility and to work with non standard path
3654
    if ($elementType == "action" || $elementType == "actioncomm") {
3655
        $classpath = 'comm/action/class';
3656
        $subelement = 'Actioncomm';
3657
        $module = 'agenda';
3658
        $table_element = 'actioncomm';
3659
    } elseif ($elementType == 'cronjob') {
3660
        $classpath = 'cron/class';
3661
        $module = 'cron';
3662
        $table_element = 'cron';
3663
    } elseif ($elementType == 'adherent_type') {
3664
        $classpath = 'adherents/class';
3665
        $classfile = 'adherent_type';
3666
        $module = 'adherent';
3667
        $subelement = 'adherent_type';
3668
        $classname = 'AdherentType';
3669
        $table_element = 'adherent_type';
3670
    } elseif ($elementType == 'bank_account') {
3671
        $classpath = 'compta/bank/class';
3672
        $module = 'bank';   // We need $conf->bank->dir_output and not $conf->banque->dir_output
3673
        $classfile = 'account';
3674
        $classname = 'Account';
3675
    } elseif ($elementType == 'category') {
3676
        $classpath = 'categories/class';
3677
        $module = 'categorie';
3678
        $subelement = 'categorie';
3679
        $table_element = 'categorie';
3680
    } elseif ($elementType == 'contact') {
3681
        $classpath = 'contact/class';
3682
        $classfile = 'contact';
3683
        $module = 'societe';
3684
        $subelement = 'contact';
3685
        $table_element = 'socpeople';
3686
    } elseif ($elementType == 'inventory') {
3687
        $module = 'product';
3688
        $classpath = 'product/inventory/class';
3689
    } elseif ($elementType == 'stock' || $elementType == 'entrepot') {
3690
        $module = 'stock';
3691
        $classpath = 'product/stock/class';
3692
        $classfile = 'entrepot';
3693
        $classname = 'Entrepot';
3694
        $table_element = 'entrepot';
3695
    } elseif ($elementType == 'project') {
3696
        $classpath = 'projet/class';
3697
        $module = 'projet';
3698
        $table_element = 'projet';
3699
    } elseif ($elementType == 'project_task') {
3700
        $classpath = 'projet/class';
3701
        $module = 'projet';
3702
        $subelement = 'task';
3703
        $table_element = 'projet_task';
3704
    } elseif ($elementType == 'facture' || $elementType == 'invoice') {
3705
        $classpath = 'compta/facture/class';
3706
        $module = 'facture';
3707
        $subelement = 'facture';
3708
        $table_element = 'facture';
3709
    } elseif ($elementType == 'facturerec') {
3710
        $classpath = 'compta/facture/class';
3711
        $module = 'facture';
3712
        $classname = 'FactureRec';
3713
    } elseif ($elementType == 'commande' || $elementType == 'order') {
3714
        $classpath = 'commande/class';
3715
        $module = 'commande';
3716
        $subelement = 'commande';
3717
        $table_element = 'commande';
3718
    } elseif ($elementType == 'propal') {
3719
        $classpath = 'comm/propal/class';
3720
        $table_element = 'propal';
3721
    } elseif ($elementType == 'shipping') {
3722
        $classpath = 'expedition/class';
3723
        $classfile = 'expedition';
3724
        $classname = 'Expedition';
3725
        $module = 'expedition';
3726
        $table_element = 'expedition';
3727
    } elseif ($elementType == 'delivery_note') {
3728
        $classpath = 'delivery/class';
3729
        $subelement = 'delivery';
3730
        $module = 'expedition';
3731
    } elseif ($elementType == 'delivery') {
3732
        $classpath = 'delivery/class';
3733
        $subelement = 'delivery';
3734
        $module = 'expedition';
3735
    } elseif ($elementType == 'supplier_proposal') {
3736
        $classpath = 'supplier_proposal/class';
3737
        $module = 'supplier_proposal';
3738
        $element = 'supplierproposal';
3739
        $classfile = 'supplier_proposal';
3740
        $subelement = 'supplierproposal';
3741
    } elseif ($elementType == 'contract') {
3742
        $classpath = 'contrat/class';
3743
        $module = 'contrat';
3744
        $subelement = 'contrat';
3745
        $table_element = 'contract';
3746
    } elseif ($elementType == 'mailing') {
3747
        $classpath = 'comm/mailing/class';
3748
        $module = 'mailing';
3749
        $classfile = 'mailing';
3750
        $classname = 'Mailing';
3751
        $subelement = '';
3752
    } elseif ($elementType == 'member') {
3753
        $classpath = 'adherents/class';
3754
        $module = 'adherent';
3755
        $subelement = 'adherent';
3756
        $table_element = 'adherent';
3757
    } elseif ($elementType == 'usergroup') {
3758
        $classpath = 'user/class';
3759
        $module = 'user';
3760
    } elseif ($elementType == 'mo') {
3761
        $classpath = 'mrp/class';
3762
        $classfile = 'mo';
3763
        $classname = 'Mo';
3764
        $module = 'mrp';
3765
        $subelement = '';
3766
        $table_element = 'mrp_mo';
3767
    } elseif ($elementType == 'cabinetmed_cons') {
3768
        $classpath = 'cabinetmed/class';
3769
        $module = 'cabinetmed';
3770
        $subelement = 'cabinetmedcons';
3771
        $table_element = 'cabinetmedcons';
3772
    } elseif ($elementType == 'fichinter') {
3773
        $classpath = 'fichinter/class';
3774
        $module = 'ficheinter';
3775
        $subelement = 'fichinter';
3776
        $table_element = 'fichinter';
3777
    } elseif ($elementType == 'dolresource' || $elementType == 'resource') {
3778
        $classpath = 'resource/class';
3779
        $module = 'resource';
3780
        $subelement = 'dolresource';
3781
        $table_element = 'resource';
3782
    } elseif ($elementType == 'propaldet') {
3783
        $classpath = 'comm/propal/class';
3784
        $module = 'propal';
3785
        $subelement = 'propaleligne';
3786
    } elseif ($elementType == 'opensurvey_sondage') {
3787
        $classpath = 'opensurvey/class';
3788
        $module = 'opensurvey';
3789
        $subelement = 'opensurveysondage';
3790
    } elseif ($elementType == 'order_supplier') {
3791
        $classpath = 'fourn/class';
3792
        $module = 'fournisseur';
3793
        $classfile = 'fournisseur.commande';
3794
        $element = 'order_supplier';
3795
        $subelement = '';
3796
        $classname = 'CommandeFournisseur';
3797
        $table_element = 'commande_fournisseur';
3798
    } elseif ($elementType == 'commande_fournisseurdet') {
3799
        $classpath = 'fourn/class';
3800
        $module = 'fournisseur';
3801
        $classfile = 'fournisseur.commande';
3802
        $element = 'commande_fournisseurdet';
3803
        $subelement = '';
3804
        $classname = 'CommandeFournisseurLigne';
3805
        $table_element = 'commande_fournisseurdet';
3806
    } elseif ($elementType == 'invoice_supplier') {
3807
        $classpath = 'fourn/class';
3808
        $module = 'fournisseur';
3809
        $classfile = 'fournisseur.facture';
3810
        $element = 'invoice_supplier';
3811
        $subelement = '';
3812
        $classname = 'FactureFournisseur';
3813
        $table_element = 'facture_fourn';
3814
    } elseif ($elementType == "service") {
3815
        $classpath = 'product/class';
3816
        $subelement = 'product';
3817
        $table_element = 'product';
3818
    } elseif ($elementType == 'salary') {
3819
        $classpath = 'salaries/class';
3820
        $module = 'salaries';
3821
    } elseif ($elementType == 'payment_salary') {
3822
        $classpath = 'salaries/class';
3823
        $classfile = 'paymentsalary';
3824
        $classname = 'PaymentSalary';
3825
        $module = 'salaries';
3826
    } elseif ($elementType == 'productlot') {
3827
        $module = 'productbatch';
3828
        $classpath = 'product/stock/class';
3829
        $classfile = 'productlot';
3830
        $classname = 'Productlot';
3831
        $element = 'productlot';
3832
        $subelement = '';
3833
        $table_element = 'product_lot';
3834
    } elseif ($elementType == 'societeaccount') {
3835
        $classpath = 'societe/class';
3836
        $classfile = 'societeaccount';
3837
        $classname = 'SocieteAccount';
3838
        $module = 'societe';
3839
    } elseif ($elementType == 'websitepage') {
3840
        $classpath = 'website/class';
3841
        $classfile = 'websitepage';
3842
        $classname = 'Websitepage';
3843
        $module = 'website';
3844
        $subelement = 'websitepage';
3845
        $table_element = 'website_page';
3846
    } elseif ($elementType == 'fiscalyear') {
3847
        $classpath = 'core/class';
3848
        $module = 'accounting';
3849
        $subelement = 'fiscalyear';
3850
    } elseif ($elementType == 'chargesociales') {
3851
        $classpath = 'compta/sociales/class';
3852
        $module = 'tax';
3853
        $table_element = 'chargesociales';
3854
    } elseif ($elementType == 'tva') {
3855
        $classpath = 'compta/tva/class';
3856
        $module = 'tax';
3857
        $subdir = '/vat';
3858
        $table_element = 'tva';
3859
    } elseif ($elementType == 'emailsenderprofile') {
3860
        $module = '';
3861
        $classpath = 'core/class';
3862
        $classfile = 'emailsenderprofile';
3863
        $classname = 'EmailSenderProfile';
3864
        $table_element = 'c_email_senderprofile';
3865
        $subelement = '';
3866
    } elseif ($elementType == 'conferenceorboothattendee') {
3867
        $classpath = 'eventorganization/class';
3868
        $classfile = 'conferenceorboothattendee';
3869
        $classname = 'ConferenceOrBoothAttendee';
3870
        $module = 'eventorganization';
3871
    } elseif ($elementType == 'conferenceorbooth') {
3872
        $classpath = 'eventorganization/class';
3873
        $classfile = 'conferenceorbooth';
3874
        $classname = 'ConferenceOrBooth';
3875
        $module = 'eventorganization';
3876
    } elseif ($elementType == 'ccountry') {
3877
        $module = '';
3878
        $classpath = 'core/class';
3879
        $classfile = 'ccountry';
3880
        $classname = 'Ccountry';
3881
        $table_element = 'c_country';
3882
        $subelement = '';
3883
    } elseif ($elementType == 'knowledgerecord') {
3884
        $module = '';
3885
        $classpath = 'knowledgemanagement/class';
3886
        $classfile = 'knowledgerecord';
3887
        $classname = 'KnowledgeRecord';
3888
        $table_element = 'knowledgemanagement_knowledgerecord';
3889
        $subelement = '';
3890
    }
3891
3892
    if (empty($classfile)) {
3893
        $classfile = strtolower($subelement);
3894
    }
3895
    if (empty($classname)) {
3896
        $classname = ucfirst($subelement);
3897
    }
3898
    if (empty($classpath)) {
3899
        $classpath = $module . '/class';
3900
    }
3901
3902
    //print 'getElementProperties subdir='.$subdir;
3903
3904
    // Set dir_output
3905
    if ($module && isset($conf->$module)) { // The generic case
3906
        if (!empty($conf->$module->multidir_output[$conf->entity])) {
3907
            $dir_output = $conf->$module->multidir_output[$conf->entity];
3908
        } elseif (!empty($conf->$module->output[$conf->entity])) {
3909
            $dir_output = $conf->$module->output[$conf->entity];
3910
        } elseif (!empty($conf->$module->dir_output)) {
3911
            $dir_output = $conf->$module->dir_output;
3912
        }
3913
    }
3914
3915
    // Overwrite value for special cases
3916
    if ($element == 'order_supplier') {
3917
        $dir_output = $conf->fournisseur->commande->dir_output;
3918
    } elseif ($element == 'invoice_supplier') {
3919
        $dir_output = $conf->fournisseur->facture->dir_output;
3920
    }
3921
    $dir_output .= $subdir;
3922
3923
    $elementProperties = array(
3924
        'module' => $module,
3925
        'element' => $element,
3926
        'table_element' => $table_element,
3927
        'subelement' => $subelement,
3928
        'classpath' => $classpath,
3929
        'classfile' => $classfile,
3930
        'classname' => $classname,
3931
        'dir_output' => $dir_output
3932
    );
3933
3934
3935
    // Add  hook
3936
    if (!is_object($hookmanager)) {
3937
        $hookmanager = new HookManager($db);
3938
    }
3939
    $hookmanager->initHooks(array('elementproperties'));
3940
3941
3942
    // Hook params
3943
    $parameters = array(
3944
        'elementType' => $elementType,
3945
        'elementProperties' => $elementProperties
3946
    );
3947
3948
    $reshook = $hookmanager->executeHooks('getElementProperties', $parameters);
3949
3950
    if ($reshook) {
3951
        $elementProperties = $hookmanager->resArray;
3952
    } elseif (!empty($hookmanager->resArray) && is_array($hookmanager->resArray)) { // resArray is always an array but for sécurity against misconfigured external modules
3953
        $elementProperties = array_replace($elementProperties, $hookmanager->resArray);
3954
    }
3955
3956
    // context of elementproperties doesn't need to exist out of this function so delete it to avoid elementproperties context is equal to all
3957
    if (($key = array_search('elementproperties', $hookmanager->contextarray)) !== false) {
3958
        unset($hookmanager->contextarray[$key]);
3959
    }
3960
3961
    return $elementProperties;
3962
}
3963
3964
/**
3965
 * Function dolGetBadge
3966
 *
3967
 * @param string $label label of badge no html : use in alt attribute for accessibility
3968
 * @param string $html optional : label of badge with html
3969
 * @param string $type type of badge : Primary Secondary Success Danger Warning Info Light Dark status0 status1 status2 status3 status4 status5 status6 status7 status8 status9
3970
 * @param ''|'pill'|'dot' $mode Default '' , 'pill', 'dot'
0 ignored issues
show
Documentation Bug introduced by
The doc comment ''|'pill'|'dot' at position 0 could not be parsed: Unknown type name '''' at position 0 in ''|'pill'|'dot'.
Loading history...
3971
 * @param string $url the url for link
3972
 * @param array<string,mixed> $params Various params for future : recommended rather than adding more function arguments. array('attr'=>array('title'=>'abc'))
3973
 * @return  string                          Html badge
3974
 */
3975
function dolGetBadge($label, $html = '', $type = 'primary', $mode = '', $url = '', $params = array())
3976
{
3977
    $csstouse = 'badge';
3978
    $csstouse .= (!empty($mode) ? ' badge-' . $mode : '');
3979
    $csstouse .= (!empty($type) ? ' badge-' . $type : '');
3980
    $csstouse .= (empty($params['css']) ? '' : ' ' . $params['css']);
3981
3982
    $attr = array(
3983
        'class' => $csstouse
3984
    );
3985
3986
    if (empty($html)) {
3987
        $html = $label;
3988
    }
3989
3990
    if (!empty($url)) {
3991
        $attr['href'] = $url;
3992
    }
3993
3994
    if ($mode === 'dot') {
3995
        $attr['class'] .= ' classfortooltip';
3996
        $attr['title'] = $html;
3997
        $attr['aria-label'] = $label;
3998
        $html = '';
3999
    }
4000
4001
    // Override attr
4002
    if (!empty($params['attr']) && is_array($params['attr'])) {
4003
        foreach ($params['attr'] as $key => $value) {
4004
            if ($key == 'class') {
4005
                $attr['class'] .= ' ' . $value;
4006
            } elseif ($key == 'classOverride') {
4007
                $attr['class'] = $value;
4008
            } else {
4009
                $attr[$key] = $value;
4010
            }
4011
        }
4012
    }
4013
4014
    // TODO: add hook
4015
4016
    // escape all attribute
4017
    $attr = array_map('dol_escape_htmltag', $attr);
4018
4019
    $TCompiledAttr = array();
4020
    foreach ($attr as $key => $value) {
4021
        $TCompiledAttr[] = $key . '="' . $value . '"';
4022
    }
4023
4024
    $compiledAttributes = !empty($TCompiledAttr) ? implode(' ', $TCompiledAttr) : '';
4025
4026
    $tag = !empty($url) ? 'a' : 'span';
4027
4028
    return '<' . $tag . ' ' . $compiledAttributes . '>' . $html . '</' . $tag . '>';
4029
}
4030
4031
4032
/**
4033
 * Output the badge of a status.
4034
 *
4035
 * @param string $statusLabel Label of badge no html : use in alt attribute for accessibility
4036
 * @param string $statusLabelShort Short label of badge no html
4037
 * @param string $html Optional : label of badge with html
4038
 * @param string $statusType status0 status1 status2 status3 status4 status5 status6 status7 status8 status9 : image name or badge name
4039
 * @param int<0,6> $displayMode 0=Long label, 1=Short label, 2=Picto + Short label, 3=Picto, 4=Picto + Long label, 5=Short label + Picto, 6=Long label + Picto
4040
 * @param string $url The url for link
4041
 * @param array<string,mixed> $params Various params. Example: array('tooltip'=>'no|...', 'badgeParams'=>...)
4042
 * @return  string                                  Html status string
4043
 */
4044
function dolGetStatus($statusLabel = '', $statusLabelShort = '', $html = '', $statusType = 'status0', $displayMode = 0, $url = '', $params = array())
4045
{
4046
    global $conf;
4047
4048
    $return = '';
4049
    $dolGetBadgeParams = array();
4050
4051
    if (!empty($params['badgeParams'])) {
4052
        $dolGetBadgeParams = $params['badgeParams'];
4053
    }
4054
4055
    // TODO : add a hook
4056
    if ($displayMode == 0) {
4057
        $return = !empty($html) ? $html : (empty($conf->dol_optimize_smallscreen) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort));
4058
    } elseif ($displayMode == 1) {
4059
        $return = !empty($html) ? $html : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort);
4060
    } elseif (getDolGlobalString('MAIN_STATUS_USES_IMAGES')) {
4061
        // Use status with images (for backward compatibility)
4062
        $return = '';
4063
        $htmlLabel = (in_array($displayMode, array(1, 2, 5)) ? '<span class="hideonsmartphone">' : '') . (!empty($html) ? $html : $statusLabel) . (in_array($displayMode, array(1, 2, 5)) ? '</span>' : '');
4064
        $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>' : '');
4065
4066
        // For small screen, we always use the short label instead of long label.
4067
        if (!empty($conf->dol_optimize_smallscreen)) {
4068
            if ($displayMode == 0) {
4069
                $displayMode = 1;
4070
            } elseif ($displayMode == 4) {
4071
                $displayMode = 2;
4072
            } elseif ($displayMode == 6) {
4073
                $displayMode = 5;
4074
            }
4075
        }
4076
4077
        // For backward compatibility. Image's filename are still in French, so we use this array to convert
4078
        $statusImg = array(
4079
            'status0' => 'statut0',
4080
            'status1' => 'statut1',
4081
            'status2' => 'statut2',
4082
            'status3' => 'statut3',
4083
            'status4' => 'statut4',
4084
            'status5' => 'statut5',
4085
            'status6' => 'statut6',
4086
            'status7' => 'statut7',
4087
            'status8' => 'statut8',
4088
            'status9' => 'statut9'
4089
        );
4090
4091
        if (!empty($statusImg[$statusType])) {
4092
            $htmlImg = img_picto($statusLabel, $statusImg[$statusType]);
4093
        } else {
4094
            $htmlImg = img_picto($statusLabel, $statusType);
4095
        }
4096
4097
        if ($displayMode === 2) {
4098
            $return = $htmlImg . ' ' . $htmlLabelShort;
4099
        } elseif ($displayMode === 3) {
4100
            $return = $htmlImg;
4101
        } elseif ($displayMode === 4) {
4102
            $return = $htmlImg . ' ' . $htmlLabel;
4103
        } elseif ($displayMode === 5) {
4104
            $return = $htmlLabelShort . ' ' . $htmlImg;
4105
        } else { // $displayMode >= 6
4106
            $return = $htmlLabel . ' ' . $htmlImg;
4107
        }
4108
    } elseif (!getDolGlobalString('MAIN_STATUS_USES_IMAGES') && !empty($displayMode)) {
4109
        // Use new badge
4110
        $statusLabelShort = (empty($statusLabelShort) ? $statusLabel : $statusLabelShort);
4111
4112
        $dolGetBadgeParams['attr']['class'] = 'badge-status';
4113
        if (empty($dolGetBadgeParams['attr']['title'])) {
4114
            $dolGetBadgeParams['attr']['title'] = empty($params['tooltip']) ? $statusLabel : ($params['tooltip'] != 'no' ? $params['tooltip'] : '');
4115
        } else {    // If a title was forced from $params['badgeParams']['attr']['title'], we set the class to get it as a tooltip.
4116
            $dolGetBadgeParams['attr']['class'] .= ' classfortooltip';
4117
            // And if we use tooltip, we can output title in HTML
4118
            $dolGetBadgeParams['attr']['title'] = dol_htmlentitiesbr($dolGetBadgeParams['attr']['title'], 1);
4119
        }
4120
4121
        if ($displayMode == 3) {
4122
            $return = dolGetBadge((empty($conf->dol_optimize_smallscreen) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort)), '', $statusType, 'dot', $url, $dolGetBadgeParams);
4123
        } elseif ($displayMode === 5) {
4124
            $return = dolGetBadge($statusLabelShort, $html, $statusType, '', $url, $dolGetBadgeParams);
4125
        } else {
4126
            $return = dolGetBadge((empty($conf->dol_optimize_smallscreen) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort)), $html, $statusType, '', $url, $dolGetBadgeParams);
4127
        }
4128
    }
4129
4130
    return $return;
4131
}
4132
4133
4134
/**
4135
 * Function dolGetButtonAction
4136
 *
4137
 * @param string $label Label or tooltip of button if $text is provided. Also used as tooltip in title attribute. Can be escaped HTML content or full simple text.
4138
 * @param string $text Optional : short label on button. Can be escaped HTML content or full simple text.
4139
 * @param string $actionType 'default', 'danger', 'email', 'clone', 'cancel', 'delete', ...
4140
 *
4141
 * @param string|array<int,array{lang:string,enabled:bool,perm:bool,label:string,url:string}> $url Url for link or array of subbutton description
4142
 *
4143
 *                                                                                                              Example when an array is used: $arrayforbutaction = array(
4144
 *                                                                                                              10 => array('lang'=>'propal', 'enabled'=>isModEnabled("propal"), 'perm'=>$user->hasRight('propal', 'creer'), 'label' => 'AddProp', 'url'=>'/comm/propal/card.php?action=create&amp;projectid='.$object->id.'&amp;socid='.$object->socid),
4145
 *                                                                                                              20 => array('lang'=>'orders', 'enabled'=>isModEnabled("order"), 'perm'=>$user->hasRight('commande', 'creer'), 'label' => 'CreateOrder', 'url'=>'/commande/card.php?action=create&amp;projectid='.$object->id.'&amp;socid='.$object->socid),
4146
 *                                                                                                              30 => array('lang'=>'bills', 'enabled'=>isModEnabled("invoice"), 'perm'=>$user->hasRight('facture', 'creer'), 'label' => 'CreateBill', 'url'=>'/compta/facture/card.php?action=create&amp;projectid='.$object->id.'&amp;socid='.$object->socid),
4147
 *                                                                                                              );
4148
 * @param string $id Attribute id of action button. Example 'action-delete'. This can be used for full ajax confirm if this code is reused into the ->formconfirm() method.
4149
 * @param int|boolean $userRight User action right
4150
 * // phpcs:disable
4151
 * @param array<string,mixed> $params = [ // Various params for future : recommended rather than adding more function arguments
4152
 *                                      'attr' => [ // to add or override button attributes
4153
 *                                      'xxxxx' => '', // your xxxxx attribute you want
4154
 *                                      'class' => 'reposition', // to add more css class to the button class attribute
4155
 *                                      'classOverride' => '' // to replace class attribute of the button
4156
 *                                      ],
4157
 *                                      'confirm' => [
4158
 *                                      'url' => 'http://', // Override Url to go when user click on action btn, if empty default url is $url.?confirm=yes, for no js compatibility use $url for fallback confirm.
4159
 *                                      'title' => '', // Override title of modal,  if empty default title use "ConfirmBtnCommonTitle" lang key
4160
 *                                      'action-btn-label' => '', // Override label of action button,  if empty default label use "Confirm" lang key
4161
 *                                      'cancel-btn-label' => '', // Override label of cancel button,  if empty default label use "CloseDialog" lang key
4162
 *                                      'content' => '', // Override text of content,  if empty default content use "ConfirmBtnCommonContent" lang key
4163
 *                                      'modal' => true, // true|false to display dialog as a modal (with dark background)
4164
 *                                      'isDropDrown' => false, // true|false to display dialog as a dropdown (with dark background)
4165
 *                                      ],
4166
 *                                      ]
4167
 * // phpcs:enable
4168
 * @return string                   html button
4169
 */
4170
function dolGetButtonAction($label, $text = '', $actionType = 'default', $url = '', $id = '', $userRight = 1, $params = array())
4171
{
4172
    global $hookmanager, $action, $object, $langs;
4173
4174
    // If $url is an array, we must build a dropdown button or recursively iterate over each value
4175
    if (is_array($url)) {
4176
        // Loop on $url array to remove entries of disabled modules
4177
        foreach ($url as $key => $subbutton) {
4178
            if (isset($subbutton['enabled']) && empty($subbutton['enabled'])) {
4179
                unset($url[$key]);
4180
            }
4181
        }
4182
4183
        $out = '';
4184
4185
        if (isset($params["areDropdownButtons"]) && $params["areDropdownButtons"] === false) {
4186
            foreach ($url as $button) {
4187
                if (!empty($button['lang'])) {
4188
                    $langs->load($button['lang']);
4189
                }
4190
                $label = $langs->trans($button['label']);
4191
                $text = $button['text'] ?? '';
4192
                $actionType = $button['actionType'] ?? '';
4193
                $tmpUrl = DOL_URL_ROOT . $button['url'] . (empty($params['backtopage']) ? '' : '&amp;backtopage=' . urlencode($params['backtopage']));
4194
                $id = $button['$id'] ?? '';
4195
                $userRight = $button['perm'] ?? 1;
4196
                $params = $button['$params'] ?? [];
4197
4198
                $out .= dolGetButtonAction($label, $text, $actionType, $tmpUrl, $id, $userRight, $params);
4199
            }
4200
            return $out;
4201
        }
4202
4203
        if (count($url) > 1) {
4204
            $out .= '<div class="dropdown inline-block dropdown-holder">';
4205
            $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>';
4206
            $out .= '<div class="dropdown-content">';
4207
            foreach ($url as $subbutton) {
4208
                if (!empty($subbutton['lang'])) {
4209
                    $langs->load($subbutton['lang']);
4210
                }
4211
                $tmpurl = DOL_URL_ROOT . $subbutton['url'] . (empty($params['backtopage']) ? '' : '&amp;backtopage=' . urlencode($params['backtopage']));
4212
                $out .= dolGetButtonAction('', $langs->trans($subbutton['label']), 'default', $tmpurl, '', $subbutton['perm'], array('isDropDown' => true));
4213
            }
4214
            $out .= "</div>";
4215
            $out .= "</div>";
4216
        } else {
4217
            foreach ($url as $subbutton) {  // Should loop on 1 record only
4218
                if (!empty($subbutton['lang'])) {
4219
                    $langs->load($subbutton['lang']);
4220
                }
4221
                $tmpurl = DOL_URL_ROOT . $subbutton['url'] . (empty($params['backtopage']) ? '' : '&amp;backtopage=' . urlencode($params['backtopage']));
4222
                $out .= dolGetButtonAction('', $langs->trans($subbutton['label']), 'default', $tmpurl, '', $subbutton['perm']);
4223
            }
4224
        }
4225
4226
        return $out;
4227
    }
4228
4229
    // Here, $url is a simple link
4230
4231
    if (!empty($params['isDropdown'])) {
4232
        $class = "dropdown-item";
4233
    } else {
4234
        $class = 'butAction';
4235
        if ($actionType == 'danger' || $actionType == 'delete') {
4236
            $class = 'butActionDelete';
4237
            if (!empty($url) && strpos($url, 'token=') === false) {
4238
                $url .= '&token=' . newToken();
4239
            }
4240
        }
4241
    }
4242
    $attr = array(
4243
        'class' => $class,
4244
        'href' => empty($url) ? '' : $url,
4245
        'title' => $label
4246
    );
4247
4248
    if (empty($text)) {
4249
        $text = $label;
4250
        $attr['title'] = ''; // if html not set, leave label on title is redundant
4251
    } else {
4252
        $attr['title'] = $label;
4253
        $attr['aria-label'] = $label;
4254
    }
4255
4256
    if (empty($userRight)) {
4257
        $attr['class'] = 'butActionRefused';
4258
        $attr['href'] = '';
4259
        $attr['title'] = (($label && $text && $label != $text) ? $label : $langs->trans('NotEnoughPermissions'));
4260
    }
4261
4262
    if (!empty($id)) {
4263
        $attr['id'] = $id;
4264
    }
4265
4266
    // Override attr
4267
    if (!empty($params['attr']) && is_array($params['attr'])) {
4268
        foreach ($params['attr'] as $key => $value) {
4269
            if ($key == 'class') {
4270
                $attr['class'] .= ' ' . $value;
4271
            } elseif ($key == 'classOverride') {
4272
                $attr['class'] = $value;
4273
            } else {
4274
                $attr[$key] = $value;
4275
            }
4276
        }
4277
    }
4278
4279
    // automatic add tooltip when title is detected
4280
    if (!empty($attr['title']) && !empty($attr['class']) && strpos($attr['class'], 'classfortooltip') === false) {
4281
        $attr['class'] .= ' classfortooltip';
4282
    }
4283
4284
    // Js Confirm button
4285
    if ($userRight && !empty($params['confirm'])) {
4286
        if (!is_array($params['confirm'])) {
4287
            $params['confirm'] = array();
4288
        }
4289
4290
        if (empty($params['confirm']['url'])) {
4291
            $params['confirm']['url'] = $url . (strpos($url, '?') > 0 ? '&' : '?') . 'confirm=yes';
4292
        }
4293
4294
        // for js disabled compatibility set $url as call to confirm action and $params['confirm']['url'] to confirmed action
4295
        $attr['data-confirm-url'] = $params['confirm']['url'];
4296
        $attr['data-confirm-title'] = !empty($params['confirm']['title']) ? $params['confirm']['title'] : $langs->trans('ConfirmBtnCommonTitle', $label);
4297
        $attr['data-confirm-content'] = !empty($params['confirm']['content']) ? $params['confirm']['content'] : $langs->trans('ConfirmBtnCommonContent', $label);
4298
        $attr['data-confirm-content'] = preg_replace("/\r|\n/", "", $attr['data-confirm-content']);
4299
        $attr['data-confirm-action-btn-label'] = !empty($params['confirm']['action-btn-label']) ? $params['confirm']['action-btn-label'] : $langs->trans('Confirm');
4300
        $attr['data-confirm-cancel-btn-label'] = !empty($params['confirm']['cancel-btn-label']) ? $params['confirm']['cancel-btn-label'] : $langs->trans('CloseDialog');
4301
        $attr['data-confirm-modal'] = !empty($params['confirm']['modal']) ? $params['confirm']['modal'] : true;
4302
4303
        $attr['class'] .= ' butActionConfirm';
4304
    }
4305
4306
    if (isset($attr['href']) && empty($attr['href'])) {
4307
        unset($attr['href']);
4308
    }
4309
4310
    // escape all attribute
4311
    $attr = array_map('dol_escape_htmltag', $attr);
4312
4313
    $TCompiledAttr = array();
4314
    foreach ($attr as $key => $value) {
4315
        $TCompiledAttr[] = $key . '= "' . $value . '"';
4316
    }
4317
4318
    $compiledAttributes = empty($TCompiledAttr) ? '' : implode(' ', $TCompiledAttr);
4319
4320
    $tag = !empty($attr['href']) ? 'a' : 'span';
4321
4322
4323
    $parameters = array(
4324
        'TCompiledAttr' => $TCompiledAttr,              // array
4325
        'compiledAttributes' => $compiledAttributes,    // string
4326
        'attr' => $attr,
4327
        'tag' => $tag,
4328
        'label' => $label,
4329
        'html' => $text,
4330
        'actionType' => $actionType,
4331
        'url' => $url,
4332
        'id' => $id,
4333
        'userRight' => $userRight,
4334
        'params' => $params
4335
    );
4336
4337
    $reshook = $hookmanager->executeHooks('dolGetButtonAction', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
4338
    if ($reshook < 0) {
4339
        setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
4340
    }
4341
4342
    if (empty($reshook)) {
4343
        if (dol_textishtml($text)) {    // If content already HTML encoded
4344
            return '<' . $tag . ' ' . $compiledAttributes . '>' . $text . '</' . $tag . '>';
4345
        } else {
4346
            return '<' . $tag . ' ' . $compiledAttributes . '>' . dol_escape_htmltag($text) . '</' . $tag . '>';
4347
        }
4348
    } else {
4349
        return $hookmanager->resPrint;
4350
    }
4351
}
4352
4353
4354
/**
4355
 * Return string with full Url. The file qualified is the one defined by relative path in $object->last_main_doc
4356
 *
4357
 * @param CommonObject $object Object
4358
 * @return  string                      Url string
4359
 */
4360
function showDirectDownloadLink($object)
4361
{
4362
    global $conf, $langs;
4363
4364
    $out = '';
4365
    $url = $object->getLastMainDocLink($object->element);
4366
4367
    $out .= img_picto($langs->trans("PublicDownloadLinkDesc"), 'globe') . ' <span class="opacitymedium">' . $langs->trans("DirectDownloadLink") . '</span><br>';
4368
    if ($url) {
4369
        $out .= '<div class="urllink"><input type="text" id="directdownloadlink" class="quatrevingtpercent" value="' . $url . '"></div>';
4370
        $out .= ajax_autoselect("directdownloadlink", 0);
4371
    } else {
4372
        $out .= '<div class="urllink">' . $langs->trans("FileNotShared") . '</div>';
4373
    }
4374
4375
    return $out;
4376
}
4377
4378
/**
4379
 * Set focus onto field with selector (similar behaviour of 'autofocus' HTML5 tag)
4380
 *
4381
 * @param string $selector Selector ('#id' or 'input[name="ref"]') to use to find the HTML input field that must get the autofocus. You must use a CSS selector, so unique id preceding with the '#' char.
4382
 * @return  void
4383
 */
4384
function dol_set_focus($selector)
4385
{
4386
    print "\n" . '<!-- Set focus onto a specific field -->' . "\n";
4387
    print '<script nonce="' . getNonce() . '">jQuery(document).ready(function() { jQuery("' . dol_escape_js($selector) . '").focus(); });</script>' . "\n";
4388
}
4389
4390
4391
/**
4392
 *  Complete or removed entries into a head array (used to build tabs).
4393
 *  For example, with value added by external modules. Such values are declared into $conf->modules_parts['tab'].
4394
 *  Or by change using hook completeTabsHead
4395
 *
4396
 * @param Conf $conf Object conf
4397
 * @param Translate $langs Object langs
4398
 * @param object|null $object Object object
4399
 * @param array<array<int,string>> $head List of head tabs (updated by this function)
4400
 * @param int $h New position to fill (updated by this function)
4401
 * @param string $type Value for object where objectvalue can be
4402
 *                                          'thirdparty'       to add a tab in third party view
4403
 *                                          'intervention'     to add a tab in intervention view
4404
 *                                          'supplier_order'   to add a tab in purchase order view
4405
 *                                          'supplier_invoice' to add a tab in purchase invoice view
4406
 *                                          'invoice'          to add a tab in sales invoice view
4407
 *                                          'order'            to add a tab in sales order view
4408
 *                                          'contract'         to add a table in contract view
4409
 *                                          'product'          to add a tab in product view
4410
 *                                          'propal'           to add a tab in propal view
4411
 *                                          'user'             to add a tab in user view
4412
 *                                          'group'            to add a tab in group view
4413
 *                                          'member'           to add a tab in foundation member view
4414
 *                                          'categories_x'     to add a tab in category view ('x': type of category (0=product, 1=supplier, 2=customer, 3=member)
4415
 *                                          'ecm'              to add a tab for another ecm view
4416
 *                                          'stock'            to add a tab for warehouse view
4417
 * @param string $mode 'add' to complete head, 'remove' to remove entries
4418
 * @param string $filterorigmodule Filter on module origin: 'external' will show only external modules. 'core' only core modules. No filter (default) will add both.
4419
 * @return void
4420
 */
4421
function complete_head_from_modules($conf, $langs, $object, &$head, &$h, $type, $mode = 'add', $filterorigmodule = '')
4422
{
4423
    global $hookmanager, $db;
4424
4425
    if (isset($conf->modules_parts['tabs'][$type]) && is_array($conf->modules_parts['tabs'][$type])) {
4426
        foreach ($conf->modules_parts['tabs'][$type] as $value) {
4427
            $values = explode(':', $value);
4428
4429
            $reg = array();
4430
            if ($mode == 'add' && !preg_match('/^\-/', $values[1])) {
4431
                $newtab = array();
4432
                $postab = $h;
4433
                // detect if position set in $values[1] ie : +(2)mytab@mymodule (first tab is 0, second is one, ...)
4434
                $str = $values[1];
4435
                $posstart = strpos($str, '(');
4436
                if ($posstart > 0) {
4437
                    $posend = strpos($str, ')');
4438
                    if ($posstart > 0) {
4439
                        $res1 = substr($str, $posstart + 1, $posend - $posstart - 1);
4440
                        if (is_numeric($res1)) {
4441
                            $postab = (int)$res1;
4442
                            $values[1] = '+' . substr($str, $posend + 1);
4443
                        }
4444
                    }
4445
                }
4446
                if (count($values) == 6) {
4447
                    // new declaration with permissions:
4448
                    // $value='objecttype:+tabname1:Title1:langfile@mymodule:$user->rights->mymodule->read:/mymodule/mynewtab1.php?id=__ID__'
4449
                    // $value='objecttype:+tabname1:Title1,class,pathfile,method:langfile@mymodule:$user->rights->mymodule->read:/mymodule/mynewtab1.php?id=__ID__'
4450
                    if ($values[0] != $type) {
4451
                        continue;
4452
                    }
4453
4454
                    if (verifCond($values[4], '2')) {
4455
                        if ($values[3]) {
4456
                            if ($filterorigmodule) {    // If a filter of module origin has been requested
4457
                                if (strpos($values[3], '@')) {  // This is an external module
4458
                                    if ($filterorigmodule != 'external') {
4459
                                        continue;
4460
                                    }
4461
                                } else {    // This looks a core module
4462
                                    if ($filterorigmodule != 'core') {
4463
                                        continue;
4464
                                    }
4465
                                }
4466
                            }
4467
                            $langs->load($values[3]);
4468
                        }
4469
                        if (preg_match('/SUBSTITUTION_([^_]+)/i', $values[2], $reg)) {
4470
                            // If label is "SUBSTITUION_..."
4471
                            $substitutionarray = array();
4472
                            complete_substitutions_array($substitutionarray, $langs, $object, array('needforkey' => $values[2]));
4473
                            $label = make_substitutions($reg[1], $substitutionarray);
4474
                        } else {
4475
                            // If label is "Label,Class,File,Method", we call the method to show content inside the badge
4476
                            $labeltemp = explode(',', $values[2]);
4477
                            $label = $langs->trans($labeltemp[0]);
4478
4479
                            if (!empty($labeltemp[1]) && is_object($object) && !empty($object->id)) {
4480
                                dol_include_once($labeltemp[2]);
4481
                                $classtoload = $labeltemp[1];
4482
                                if (class_exists($classtoload)) {
4483
                                    $obj = new $classtoload($db);
4484
                                    $function = $labeltemp[3];
4485
                                    if ($obj && $function && method_exists($obj, $function)) {
4486
                                        // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
4487
                                        $nbrec = $obj->$function($object->id, $obj);
4488
                                        if (!empty($nbrec)) {
4489
                                            $label .= '<span class="badge marginleftonlyshort">' . $nbrec . '</span>';
4490
                                        }
4491
                                    }
4492
                                }
4493
                            }
4494
                        }
4495
4496
                        $newtab[0] = dol_buildpath(preg_replace('/__ID__/i', ((is_object($object) && !empty($object->id)) ? $object->id : ''), $values[5]), 1);
4497
                        $newtab[1] = $label;
4498
                        $newtab[2] = str_replace('+', '', $values[1]);
4499
                        $h++;
4500
                    } else {
4501
                        continue;
4502
                    }
4503
                } elseif (count($values) == 5) {       // case deprecated
4504
                    dol_syslog('Passing 5 values in tabs module_parts is deprecated. Please update to 6 with permissions.', LOG_WARNING);
4505
4506
                    if ($values[0] != $type) {
4507
                        continue;
4508
                    }
4509
                    if ($values[3]) {
4510
                        if ($filterorigmodule) {    // If a filter of module origin has been requested
4511
                            if (strpos($values[3], '@')) {  // This is an external module
4512
                                if ($filterorigmodule != 'external') {
4513
                                    continue;
4514
                                }
4515
                            } else {    // This looks a core module
4516
                                if ($filterorigmodule != 'core') {
4517
                                    continue;
4518
                                }
4519
                            }
4520
                        }
4521
                        $langs->load($values[3]);
4522
                    }
4523
                    if (preg_match('/SUBSTITUTION_([^_]+)/i', $values[2], $reg)) {
4524
                        $substitutionarray = array();
4525
                        complete_substitutions_array($substitutionarray, $langs, $object, array('needforkey' => $values[2]));
4526
                        $label = make_substitutions($reg[1], $substitutionarray);
4527
                    } else {
4528
                        $label = $langs->trans($values[2]);
4529
                    }
4530
4531
                    $newtab[0] = dol_buildpath(preg_replace('/__ID__/i', ((is_object($object) && !empty($object->id)) ? $object->id : ''), $values[4]), 1);
4532
                    $newtab[1] = $label;
4533
                    $newtab[2] = str_replace('+', '', $values[1]);
4534
                    $h++;
4535
                }
4536
                // set tab at its position
4537
                $head = array_merge(array_slice($head, 0, $postab), array($newtab), array_slice($head, $postab));
4538
            } elseif ($mode == 'remove' && preg_match('/^\-/', $values[1])) {
4539
                if ($values[0] != $type) {
4540
                    continue;
4541
                }
4542
                $tabname = str_replace('-', '', $values[1]);
4543
                foreach ($head as $key => $val) {
4544
                    $condition = (!empty($values[3]) ? verifCond($values[3], '2') : 1);
4545
                    //var_dump($key.' - '.$tabname.' - '.$head[$key][2].' - '.$values[3].' - '.$condition);
4546
                    if ($head[$key][2] == $tabname && $condition) {
4547
                        unset($head[$key]);
4548
                        break;
4549
                    }
4550
                }
4551
            }
4552
        }
4553
    }
4554
4555
    // No need to make a return $head. Var is modified as a reference
4556
    if (!empty($hookmanager)) {
4557
        $parameters = array('object' => $object, 'mode' => $mode, 'head' => &$head, 'filterorigmodule' => $filterorigmodule);
4558
        $reshook = $hookmanager->executeHooks('completeTabsHead', $parameters, $object);
4559
        if ($reshook > 0) {     // Hook ask to replace completely the array
4560
            $head = $hookmanager->resArray;
4561
        } else {                // Hook
4562
            $head = array_merge($head, $hookmanager->resArray);
4563
        }
4564
        $h = count($head);
4565
    }
4566
}
4567
4568
/**
4569
 * Print common footer :
4570
 *      conf->global->MAIN_HTML_FOOTER
4571
 *      js for switch of menu hider
4572
 *      js for conf->global->MAIN_GOOGLE_AN_ID
4573
 *      js for conf->global->MAIN_SHOW_TUNING_INFO or $_SERVER["MAIN_SHOW_TUNING_INFO"]
4574
 *      js for conf->logbuffer
4575
 *
4576
 * @param string $zone 'private' (for private pages) or 'public' (for public pages)
4577
 * @return  void
4578
 */
4579
function printCommonFooter($zone = 'private')
4580
{
4581
    global $conf, $hookmanager, $user, $debugbar;
4582
    global $action;
4583
    global $micro_start_time;
4584
4585
    if ($zone == 'private') {
4586
        print "\n" . '<!-- Common footer for private page -->' . "\n";
4587
    } else {
4588
        print "\n" . '<!-- Common footer for public page -->' . "\n";
4589
    }
4590
4591
    // A div to store page_y POST parameter so we can read it using javascript
4592
    print "\n<!-- A div to store page_y POST parameter -->\n";
4593
    print '<div id="page_y" style="display: none;">' . (GETPOST('page_y') ? GETPOST('page_y') : '') . '</div>' . "\n";
4594
4595
    $parameters = array();
4596
    $reshook = $hookmanager->executeHooks('printCommonFooter', $parameters); // Note that $action and $object may have been modified by some hooks
4597
    if (empty($reshook)) {
4598
        if (getDolGlobalString('MAIN_HTML_FOOTER')) {
4599
            print getDolGlobalString('MAIN_HTML_FOOTER') . "\n";
4600
        }
4601
4602
        print "\n";
4603
        if (!empty($conf->use_javascript_ajax)) {
4604
            print "\n<!-- A script section to add menuhider handler on backoffice, manage focus and mandatory fields, tuning info, ... -->\n";
4605
            print '<script>' . "\n";
4606
            print 'jQuery(document).ready(function() {' . "\n";
4607
4608
            if ($zone == 'private' && empty($conf->dol_use_jmobile)) {
4609
                print "\n";
4610
                print '/* JS CODE TO ENABLE to manage handler to switch left menu page (menuhider) */' . "\n";
4611
                print 'jQuery("li.menuhider").click(function(event) {';
4612
                print '  if (!$( "body" ).hasClass( "sidebar-collapse" )){ event.preventDefault(); }' . "\n";
4613
                print '  console.log("We click on .menuhider");' . "\n";
4614
                print '  $("body").toggleClass("sidebar-collapse")' . "\n";
4615
                print '});' . "\n";
4616
            }
4617
4618
            // Management of focus and mandatory for fields
4619
            if ($action == 'create' || $action == 'edit' || (empty($action) && (preg_match('/new\.php/', $_SERVER["PHP_SELF"]))) || ((empty($action) || $action == 'addline') && (preg_match('/card\.php/', $_SERVER["PHP_SELF"])))) {
4620
                print '/* JS CODE TO ENABLE to manage focus and mandatory form fields */' . "\n";
4621
                $relativepathstring = $_SERVER["PHP_SELF"];
4622
                // Clean $relativepathstring
4623
                if (constant('DOL_URL_ROOT')) {
4624
                    $relativepathstring = preg_replace('/^' . preg_quote(constant('DOL_URL_ROOT'), '/') . '/', '', $relativepathstring);
4625
                }
4626
                $relativepathstring = preg_replace('/^\//', '', $relativepathstring);
4627
                $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
4628
                //$tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
4629
                if (!empty($user->default_values[$relativepathstring]['focus'])) {
4630
                    foreach ($user->default_values[$relativepathstring]['focus'] as $defkey => $defval) {
4631
                        $qualified = 0;
4632
                        if ($defkey != '_noquery_') {
4633
                            $tmpqueryarraytohave = explode('&', $defkey);
4634
                            $foundintru = 0;
4635
                            foreach ($tmpqueryarraytohave as $tmpquerytohave) {
4636
                                $tmpquerytohaveparam = explode('=', $tmpquerytohave);
4637
                                //print "console.log('".$tmpquerytohaveparam[0]." ".$tmpquerytohaveparam[1]." ".GETPOST($tmpquerytohaveparam[0])."');";
4638
                                if (!GETPOSTISSET($tmpquerytohaveparam[0]) || ($tmpquerytohaveparam[1] != GETPOST($tmpquerytohaveparam[0]))) {
4639
                                    $foundintru = 1;
4640
                                }
4641
                            }
4642
                            if (!$foundintru) {
4643
                                $qualified = 1;
4644
                            }
4645
                            //var_dump($defkey.'-'.$qualified);
4646
                        } else {
4647
                            $qualified = 1;
4648
                        }
4649
4650
                        if ($qualified) {
4651
                            foreach ($defval as $paramkey => $paramval) {
4652
                                // Set focus on field
4653
                                print 'jQuery("input[name=\'' . $paramkey . '\']").focus();' . "\n";
4654
                                print 'jQuery("textarea[name=\'' . $paramkey . '\']").focus();' . "\n";
4655
                                print 'jQuery("select[name=\'' . $paramkey . '\']").focus();' . "\n"; // Not really useful, but we keep it in case of.
4656
                            }
4657
                        }
4658
                    }
4659
                }
4660
                if (!empty($user->default_values[$relativepathstring]['mandatory'])) {
4661
                    foreach ($user->default_values[$relativepathstring]['mandatory'] as $defkey => $defval) {
4662
                        $qualified = 0;
4663
                        if ($defkey != '_noquery_') {
4664
                            $tmpqueryarraytohave = explode('&', $defkey);
4665
                            $foundintru = 0;
4666
                            foreach ($tmpqueryarraytohave as $tmpquerytohave) {
4667
                                $tmpquerytohaveparam = explode('=', $tmpquerytohave);
4668
                                //print "console.log('".$tmpquerytohaveparam[0]." ".$tmpquerytohaveparam[1]." ".GETPOST($tmpquerytohaveparam[0])."');";
4669
                                if (!GETPOSTISSET($tmpquerytohaveparam[0]) || ($tmpquerytohaveparam[1] != GETPOST($tmpquerytohaveparam[0]))) {
4670
                                    $foundintru = 1;
4671
                                }
4672
                            }
4673
                            if (!$foundintru) {
4674
                                $qualified = 1;
4675
                            }
4676
                            //var_dump($defkey.'-'.$qualified);
4677
                        } else {
4678
                            $qualified = 1;
4679
                        }
4680
4681
                        if ($qualified) {
4682
                            foreach ($defval as $paramkey => $paramval) {
4683
                                // Add property 'required' on input
4684
                                print 'jQuery("input[name=\'' . $paramkey . '\']").prop(\'required\',true);' . "\n";
4685
                                print 'jQuery("textarea[name=\'' . $paramkey . '\']").prop(\'required\',true);' . "\n";
4686
                                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";
4687
                                print 'jQuery("select[name=\'' . $paramkey . '\']").prop(\'required\',true);' . "\n";
4688
                                print 'jQuery("select[name=\'' . $paramkey . '\'] option[value=\'-1\']").prop(\'value\', \'\');' . "\n";
4689
                                print 'jQuery("select[name=\'' . $paramkey . '\'] option[value=\'0\']").prop(\'value\', \'\');' . "\n";
4690
4691
                                // Add 'field required' class on closest td for all input elements : input, textarea and select
4692
                                print 'jQuery(":input[name=\'' . $paramkey . '\']").closest("tr").find("td:first").addClass("fieldrequired");' . "\n";
4693
                            }
4694
                            // If we submit the cancel button we remove the required attributes
4695
                            print 'jQuery("input[name=\'cancel\']").click(function() {
4696
								console.log("We click on cancel button so removed all required attribute");
4697
								jQuery("input, textarea, select").each(function(){this.removeAttribute(\'required\');});
4698
								});' . "\n";
4699
                        }
4700
                    }
4701
                }
4702
            }
4703
4704
            print '});' . "\n";
4705
4706
            // End of tuning
4707
            if (!empty($_SERVER['MAIN_SHOW_TUNING_INFO']) || getDolGlobalString('MAIN_SHOW_TUNING_INFO')) {
4708
                print "\n";
4709
                print "/* JS CODE TO ENABLE to add memory info */\n";
4710
                print 'window.console && console.log("';
4711
                if (getDolGlobalString('MEMCACHED_SERVER')) {
4712
                    print 'MEMCACHED_SERVER=' . getDolGlobalString('MEMCACHED_SERVER') . ' - ';
4713
                }
4714
                print 'MAIN_OPTIMIZE_SPEED=' . getDolGlobalString('MAIN_OPTIMIZE_SPEED', 'off');
4715
                if (!empty($micro_start_time)) {   // Works only if MAIN_SHOW_TUNING_INFO is defined at $_SERVER level. Not in global variable.
4716
                    $micro_end_time = microtime(true);
4717
                    print ' - Build time: ' . ceil(1000 * ($micro_end_time - $micro_start_time)) . ' ms';
4718
                }
4719
4720
                if (function_exists("memory_get_usage")) {
4721
                    print ' - Mem: ' . memory_get_usage(); // Do not use true here, it seems it takes the peak amount
4722
                }
4723
                if (function_exists("memory_get_peak_usage")) {
4724
                    print ' - Real mem peak: ' . memory_get_peak_usage(true);
4725
                }
4726
                if (function_exists("zend_loader_file_encoded")) {
4727
                    print ' - Zend encoded file: ' . (zend_loader_file_encoded() ? 'yes' : 'no');
4728
                }
4729
                print '");' . "\n";
4730
            }
4731
4732
            print "\n" . '</script>' . "\n";
4733
4734
            // Google Analytics
4735
            // TODO Add a hook here
4736
            if (isModEnabled('google') && getDolGlobalString('MAIN_GOOGLE_AN_ID')) {
4737
                $tmptagarray = explode(',', getDolGlobalString('MAIN_GOOGLE_AN_ID'));
4738
                foreach ($tmptagarray as $tmptag) {
4739
                    print "\n";
4740
                    print "<!-- JS CODE TO ENABLE for google analtics tag -->\n";
4741
                    print '
4742
					<!-- Global site tag (gtag.js) - Google Analytics -->
4743
					<script nonce="' . getNonce() . '" async src="https://www.googletagmanager.com/gtag/js?id=' . trim($tmptag) . '"></script>
4744
					<script>
4745
					window.dataLayer = window.dataLayer || [];
4746
					function gtag(){dataLayer.push(arguments);}
4747
					gtag(\'js\', new Date());
4748
4749
					gtag(\'config\', \'' . trim($tmptag) . '\');
4750
					</script>';
4751
                    print "\n";
4752
                }
4753
            }
4754
        }
4755
4756
        // Add Xdebug coverage of code
4757
        if (defined('XDEBUGCOVERAGE')) {
4758
            print_r(xdebug_get_code_coverage());
4759
        }
4760
4761
        // Add DebugBar data
4762
        if ($user->hasRight('debugbar', 'read') && $debugbar instanceof DebugBar\DebugBar) {
4763
            if (isset($debugbar['time'])) {
4764
                // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
4765
                $debugbar['time']->stopMeasure('pageaftermaster');
0 ignored issues
show
Bug introduced by
The method stopMeasure() does not exist on DebugBar\DataCollector\DataCollectorInterface. It seems like you code against a sub-type of DebugBar\DataCollector\DataCollectorInterface such as DebugBar\DataCollector\TimeDataCollector. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

4765
                $debugbar['time']->/** @scrutinizer ignore-call */ 
4766
                                   stopMeasure('pageaftermaster');
Loading history...
4766
            }
4767
            print '<!-- Output debugbar data -->' . "\n";
4768
            $renderer = $debugbar->getJavascriptRenderer();
4769
            print $renderer->render();
4770
        } elseif (count($conf->logbuffer)) {    // If there is some logs in buffer to show
4771
            print "\n";
4772
            print "<!-- Start of log output\n";
4773
            //print '<div class="hidden">'."\n";
4774
            foreach ($conf->logbuffer as $logline) {
4775
                print $logline . "<br>\n";
4776
            }
4777
            //print '</div>'."\n";
4778
            print "End of log output -->\n";
4779
        }
4780
    }
4781
}
4782
4783
/**
4784
 *  Print formatted messages to output (Used to show messages on html output).
4785
 *  Note: Calling dol_htmloutput_events is done into pages by standard ViewMain::llxFooter() function, so there is
4786
 *  no need to call it explicitly.
4787
 *
4788
 * @param int $disabledoutputofmessages Clear all messages stored into session without displaying them
4789
 * @return void
4790
 * @see                                        dol_htmloutput_mesg()
4791
 */
4792
function dol_htmloutput_events($disabledoutputofmessages = 0)
4793
{
4794
    // Show mesgs
4795
    if (isset($_SESSION['dol_events']['mesgs'])) {
4796
        if (empty($disabledoutputofmessages)) {
4797
            dol_htmloutput_mesg('', $_SESSION['dol_events']['mesgs']);
4798
        }
4799
        unset($_SESSION['dol_events']['mesgs']);
4800
    }
4801
    // Show errors
4802
    if (isset($_SESSION['dol_events']['errors'])) {
4803
        if (empty($disabledoutputofmessages)) {
4804
            dol_htmloutput_mesg('', $_SESSION['dol_events']['errors'], 'error');
4805
        }
4806
        unset($_SESSION['dol_events']['errors']);
4807
    }
4808
4809
    // Show warnings
4810
    if (isset($_SESSION['dol_events']['warnings'])) {
4811
        if (empty($disabledoutputofmessages)) {
4812
            dol_htmloutput_mesg('', $_SESSION['dol_events']['warnings'], 'warning');
4813
        }
4814
        unset($_SESSION['dol_events']['warnings']);
4815
    }
4816
}
4817
4818
/**
4819
 *  Get formatted messages to output (Used to show messages on html output).
4820
 *  This include also the translation of the message key.
4821
 *
4822
 * @param string $mesgstring Message string or message key
4823
 * @param string[] $mesgarray Array of message strings or message keys
4824
 * @param string $style Style of message output ('ok' or 'error')
4825
 * @param int $keepembedded Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
4826
 * @return string                      Return html output
4827
 *
4828
 * @see    dol_print_error()
4829
 * @see    dol_htmloutput_errors()
4830
 * @see    setEventMessages()
4831
 */
4832
function get_htmloutput_mesg($mesgstring = '', $mesgarray = [], $style = 'ok', $keepembedded = 0)
4833
{
4834
    global $conf, $langs;
4835
4836
    $ret = 0;
4837
    $return = '';
4838
    $out = '';
4839
    $divstart = $divend = '';
4840
4841
    // If inline message with no format, we add it.
4842
    if ((empty($conf->use_javascript_ajax) || getDolGlobalString('MAIN_DISABLE_JQUERY_JNOTIFY') || $keepembedded) && !preg_match('/<div class=".*">/i', $out)) {
4843
        $divstart = '<div class="' . $style . ' clearboth">';
4844
        $divend = '</div>';
4845
    }
4846
4847
    if ((is_array($mesgarray) && count($mesgarray)) || $mesgstring) {
4848
        $langs->load("errors");
4849
        $out .= $divstart;
4850
        if (is_array($mesgarray) && count($mesgarray)) {
4851
            foreach ($mesgarray as $message) {
4852
                $ret++;
4853
                $out .= $langs->trans($message);
4854
                if ($ret < count($mesgarray)) {
4855
                    $out .= "<br>\n";
4856
                }
4857
            }
4858
        }
4859
        if ($mesgstring) {
4860
            $ret++;
4861
            $out .= $langs->trans($mesgstring);
4862
        }
4863
        $out .= $divend;
4864
    }
4865
4866
    if ($out) {
4867
        if (!empty($conf->use_javascript_ajax) && !getDolGlobalString('MAIN_DISABLE_JQUERY_JNOTIFY') && empty($keepembedded)) {
4868
            $return = '<script nonce="' . getNonce() . '">
4869
					$(document).ready(function() {
4870
						var block = ' . (getDolGlobalString('MAIN_USE_JQUERY_BLOCKUI') ? "true" : "false") . '
4871
						if (block) {
4872
							$.dolEventValid("","' . dol_escape_js($out) . '");
4873
						} else {
4874
							/* jnotify(message, preset of message type, keepmessage) */
4875
							$.jnotify("' . dol_escape_js($out) . '",
4876
							"' . ($style == "ok" ? 3000 : $style) . '",
4877
							' . ($style == "ok" ? "false" : "true") . ',
4878
							{ remove: function (){} } );
4879
						}
4880
					});
4881
				</script>';
4882
        } else {
4883
            $return = $out;
4884
        }
4885
    }
4886
4887
    return $return;
4888
}
4889
4890
/**
4891
 *  Get formatted error messages to output (Used to show messages on html output).
4892
 *
4893
 * @param string $mesgstring Error message
4894
 * @param string[] $mesgarray Error messages array
4895
 * @param int $keepembedded Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
4896
 * @return string                      Return html output
4897
 *
4898
 * @see    dol_print_error()
4899
 * @see    dol_htmloutput_mesg()
4900
 */
4901
function get_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0)
4902
{
4903
    return get_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
4904
}
4905
4906
/**
4907
 *  Print formatted messages to output (Used to show messages on html output).
4908
 *
4909
 * @param string $mesgstring Message string or message key
4910
 * @param string[] $mesgarray Array of message strings or message keys
4911
 * @param string $style Which style to use ('ok', 'warning', 'error')
4912
 * @param int $keepembedded Set to 1 if message must be kept embedded into its html place (this disable jnotify)
4913
 * @return void
4914
 *
4915
 * @see    dol_print_error()
4916
 * @see    dol_htmloutput_errors()
4917
 * @see    setEventMessages()
4918
 */
4919
function dol_htmloutput_mesg($mesgstring = '', $mesgarray = array(), $style = 'ok', $keepembedded = 0)
4920
{
4921
    if (empty($mesgstring) && (!is_array($mesgarray) || count($mesgarray) == 0)) {
4922
        return;
4923
    }
4924
4925
    $iserror = 0;
4926
    $iswarning = 0;
4927
    if (is_array($mesgarray)) {
4928
        foreach ($mesgarray as $val) {
4929
            if ($val && preg_match('/class="error"/i', $val)) {
4930
                $iserror++;
4931
                break;
4932
            }
4933
            if ($val && preg_match('/class="warning"/i', $val)) {
4934
                $iswarning++;
4935
                break;
4936
            }
4937
        }
4938
    } elseif ($mesgstring && preg_match('/class="error"/i', $mesgstring)) {
4939
        $iserror++;
4940
    } elseif ($mesgstring && preg_match('/class="warning"/i', $mesgstring)) {
4941
        $iswarning++;
4942
    }
4943
    if ($style == 'error') {
4944
        $iserror++;
4945
    }
4946
    if ($style == 'warning') {
4947
        $iswarning++;
4948
    }
4949
4950
    if ($iserror || $iswarning) {
4951
        // Remove div from texts
4952
        $mesgstring = preg_replace('/<\/div><div class="(error|warning)">/', '<br>', $mesgstring);
4953
        $mesgstring = preg_replace('/<div class="(error|warning)">/', '', $mesgstring);
4954
        $mesgstring = preg_replace('/<\/div>/', '', $mesgstring);
4955
        // Remove div from texts array
4956
        if (is_array($mesgarray)) {
4957
            $newmesgarray = array();
4958
            foreach ($mesgarray as $val) {
4959
                if (is_string($val)) {
4960
                    $tmpmesgstring = preg_replace('/<\/div><div class="(error|warning)">/', '<br>', $val);
4961
                    $tmpmesgstring = preg_replace('/<div class="(error|warning)">/', '', $tmpmesgstring);
4962
                    $tmpmesgstring = preg_replace('/<\/div>/', '', $tmpmesgstring);
4963
                    $newmesgarray[] = $tmpmesgstring;
4964
                } else {
4965
                    dol_syslog("Error call of dol_htmloutput_mesg with an array with a value that is not a string", LOG_WARNING);
4966
                }
4967
            }
4968
            $mesgarray = $newmesgarray;
4969
        }
4970
        print get_htmloutput_mesg($mesgstring, $mesgarray, ($iserror ? 'error' : 'warning'), $keepembedded);
4971
    } else {
4972
        print get_htmloutput_mesg($mesgstring, $mesgarray, 'ok', $keepembedded);
4973
    }
4974
}
4975
4976
/**
4977
 *  Print formatted error messages to output (Used to show messages on html output).
4978
 *
4979
 * @param string $mesgstring Error message
4980
 * @param string[] $mesgarray Error messages array
4981
 * @param int<0,1> $keepembedded Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
4982
 * @return void
4983
 *
4984
 * @see    dol_print_error()
4985
 * @see    dol_htmloutput_mesg()
4986
 */
4987
function dol_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0)
4988
{
4989
    dol_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
4990
}
4991
4992
/**
4993
 * Sanitize a HTML to remove js, dangerous content and external link.
4994
 * This function is used by dolPrintHTML... function for example.
4995
 *
4996
 * @param string $stringtoencode String to encode
4997
 * @param int $nouseofiframesandbox 0=Default, 1=Allow use of option MAIN_SECURITY_USE_SANDBOX_FOR_HTMLWITHNOJS for html sanitizing (not yet working)
4998
 * @param string $check 'restricthtmlnolink' or 'restricthtml' or 'restricthtmlallowclass' or 'restricthtmlallowunvalid'
4999
 * @return  string                              HTML sanitized
5000
 */
5001
function dol_htmlwithnojs($stringtoencode, $nouseofiframesandbox = 0, $check = 'restricthtml')
5002
{
5003
    if (empty($nouseofiframesandbox) && getDolGlobalString('MAIN_SECURITY_USE_SANDBOX_FOR_HTMLWITHNOJS')) {
5004
        // TODO using sandbox on inline html content is not possible yet with current browsers
5005
        //$s = '<iframe class="iframewithsandbox" sandbox><html><body>';
5006
        //$s .= $stringtoencode;
5007
        //$s .= '</body></html></iframe>';
5008
        return $stringtoencode;
5009
    } else {
5010
        $out = $stringtoencode;
5011
5012
        do {
5013
            $oldstringtoclean = $out;
5014
5015
            if (!empty($out) && getDolGlobalString('MAIN_RESTRICTHTML_ONLY_VALID_HTML') && $check != 'restricthtmlallowunvalid') {
5016
                try {
5017
                    libxml_use_internal_errors(false);  // Avoid to fill memory with xml errors
5018
                    if (LIBXML_VERSION < 20900) {
5019
                        // Avoid load of external entities (security problem).
5020
                        // Required only if LIBXML_VERSION < 20900
5021
                        // @phan-suppress-next-line PhanDeprecatedFunctionInternal
5022
                        libxml_disable_entity_loader(true);
5023
                    }
5024
5025
                    $dom = new DOMDocument();
5026
                    // Add a trick to solve pb with text without parent tag
5027
                    // like '<h1>Foo</h1><p>bar</p>' that wrongly ends up, without the trick, with '<h1>Foo<p>bar</p></h1>'
5028
                    // like 'abc' that wrongly ends up, without the trick, with '<p>abc</p>'
5029
5030
                    if (dol_textishtml($out)) {
5031
                        $out = '<?xml encoding="UTF-8"><div class="tricktoremove">' . $out . '</div>';
5032
                    } else {
5033
                        $out = '<?xml encoding="UTF-8"><div class="tricktoremove">' . dol_nl2br($out) . '</div>';
5034
                    }
5035
5036
                    $dom->loadHTML($out, LIBXML_HTML_NODEFDTD | LIBXML_ERR_NONE | LIBXML_HTML_NOIMPLIED | LIBXML_NONET | LIBXML_NOWARNING | LIBXML_NOERROR | LIBXML_NOXMLDECL);
5037
                    $out = trim($dom->saveHTML());
5038
5039
                    // Remove the trick added to solve pb with text without parent tag
5040
                    $out = preg_replace('/^<\?xml encoding="UTF-8"><div class="tricktoremove">/', '', $out);
5041
                    $out = preg_replace('/<\/div>$/', '', $out);
5042
                } catch (Exception $e) {
5043
                    // If error, invalid HTML string with no way to clean it
5044
                    //print $e->getMessage();
5045
                    $out = 'InvalidHTMLStringCantBeCleaned ' . $e->getMessage();
5046
                }
5047
            }
5048
5049
            if (!empty($out) && getDolGlobalString('MAIN_RESTRICTHTML_ONLY_VALID_HTML_TIDY') && $check != 'restricthtmlallowunvalid') {
5050
                try {
5051
                    // Try cleaning using tidy
5052
                    if (extension_loaded('tidy') && class_exists("tidy")) {
5053
                        //print "aaa".$out."\n";
5054
5055
                        // See options at https://tidy.sourceforge.net/docs/quickref.html
5056
                        $config = array(
5057
                            'clean' => false,
5058
                            'quote-marks' => false,     // do not replace " that are used for real text content (not a string symbol for html attribute) into &quot;
5059
                            'doctype' => 'strict',
5060
                            'show-body-only' => true,
5061
                            "indent-attributes" => false,
5062
                            "vertical-space" => false,
5063
                            //'ident' => false,         // Not always supported
5064
                            "wrap" => 0
5065
                            // HTML5 tags
5066
                            //'new-blocklevel-tags' => 'article aside audio bdi canvas details dialog figcaption figure footer header hgroup main menu menuitem nav section source summary template track video',
5067
                            //'new-blocklevel-tags' => 'footer header section menu menuitem'
5068
                            //'new-empty-tags' => 'command embed keygen source track wbr',
5069
                            //'new-inline-tags' => 'audio command datalist embed keygen mark menuitem meter output progress source time video wbr',
5070
                        );
5071
5072
                        // Tidy
5073
                        $tidy = new tidy();
5074
                        $out = $tidy->repairString($out, $config, 'utf8');
5075
5076
                        //print "xxx".$out;exit;
5077
                    }
5078
                } catch (Exception $e) {
5079
                    // If error, invalid HTML string with no way to clean it
5080
                    //print $e->getMessage();
5081
                    $out = 'InvalidHTMLStringCantBeCleaned ' . $e->getMessage();
5082
                }
5083
            }
5084
5085
            // Clean some html entities that are useless so text is cleaner
5086
            $out = preg_replace('/&(tab|newline);/i', ' ', $out);
5087
5088
            // Ckeditor uses the numeric entity for apostrophe so we force it to text entity (all other special chars are
5089
            // encoded using text entities) so we can then exclude all numeric entities.
5090
            $out = preg_replace('/&#39;/i', '&apos;', $out);
5091
5092
            // We replace chars from a/A to z/Z encoded with numeric HTML entities with the real char so we won't loose the chars at the next step (preg_replace).
5093
            // No need to use a loop here, this step is not to sanitize (this is done at next step, this is to try to save chars, even if they are
5094
            // using a non conventionnal way to be encoded, to not have them sanitized just after)
5095
            $out = preg_replace_callback(
5096
                '/&#(x?[0-9][0-9a-f]+;?)/i',
5097
                /**
5098
                 * @param string[] $m
5099
                 * @return string
5100
                 */
5101
                static function ($m) {
5102
                    return Filters::realCharForNumericEntities($m);
5103
                },
5104
                $out
5105
            );
5106
5107
            // Now we remove all remaining HTML entities starting with a number. We don't want such entities.
5108
            $out = preg_replace('/&#x?[0-9]+/i', '', $out); // For example if we have j&#x61vascript with an entities without the ; to hide the 'a' of 'javascript'.
5109
5110
            // Keep only some html tags and remove also some 'javascript:' strings
5111
            $out = dol_string_onlythesehtmltags($out, 0, ($check == 'restricthtmlallowclass' ? 0 : 1), 1);
5112
5113
            // Keep only some html attributes and exclude non expected HTML attributes and clean content of some attributes (keep only alt=, title=...).
5114
            if (getDolGlobalString('MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES')) {
5115
                $out = dol_string_onlythesehtmlattributes($out);
5116
            }
5117
5118
            // Restore entity &apos; into &#39; (restricthtml is for html content so we can use html entity)
5119
            $out = preg_replace('/&apos;/i', "&#39;", $out);
5120
        } while ($oldstringtoclean != $out);
5121
5122
        // Check the limit of external links that are automatically executed in a Rich text content. We count:
5123
        // '<img' to avoid <img src="http...">,  we can only accept "<img src="data:..."
5124
        // 'url(' to avoid inline style like background: url(http...
5125
        // '<link' to avoid <link href="http...">
5126
        $reg = array();
5127
        $tmpout = preg_replace('/<img src="data:/mi', '<__IMG_SRC_DATA__ src="data:', $out);
5128
        preg_match_all('/(<img|url\(|<link)/i', $tmpout, $reg);
5129
        $nblinks = count($reg[0]);
5130
        if ($nblinks > getDolGlobalInt("MAIN_SECURITY_MAX_IMG_IN_HTML_CONTENT", 1000)) {
5131
            $out = 'ErrorTooManyLinksIntoHTMLString';
5132
        }
5133
5134
        if (getDolGlobalInt('MAIN_DISALLOW_URL_INTO_DESCRIPTIONS') == 2 || $check == 'restricthtmlnolink') {
5135
            if ($nblinks > 0) {
5136
                $out = 'ErrorHTMLLinksNotAllowed';
5137
            }
5138
        } elseif (getDolGlobalInt('MAIN_DISALLOW_URL_INTO_DESCRIPTIONS') == 1) {
5139
            $nblinks = 0;
5140
            // Loop on each url in src= and url(
5141
            $pattern = '/src=["\']?(http[^"\']+)|url\(["\']?(http[^\)]+)/';
5142
5143
            $matches = array();
5144
            if (preg_match_all($pattern, $out, $matches)) {
5145
                // URLs are into $matches[1]
5146
                $urls = $matches[1];
5147
5148
                // Affiche les URLs
5149
                foreach ($urls as $url) {
5150
                    $nblinks++;
5151
                    echo "Found url = " . $url . "\n";
5152
                }
5153
                if ($nblinks > 0) {
5154
                    $out = 'ErrorHTMLExternalLinksNotAllowed';
5155
                }
5156
            }
5157
        }
5158
5159
        return $out;
5160
    }
5161
}
5162
5163
/**
5164
 *  This function is called to encode a string into a HTML string but differs from htmlentities because
5165
 *  a detection is done before to see if text is already HTML or not. Also, all entities but &,<,>," are converted.
5166
 *  This permits to encode special chars to entities with no double encoding for already encoded HTML strings.
5167
 *  This function also remove last EOL or BR if $removelasteolbr=1 (default).
5168
 *  For PDF usage, you can show text by 2 ways:
5169
 *        - writeHTMLCell -> param must be encoded into HTML.
5170
 *        - MultiCell -> param must not be encoded into HTML.
5171
 *        Because writeHTMLCell convert also \n into <br>, if function is used to build PDF, nl2brmode must be 1.
5172
 *  Note: When we output string on pages, we should use
5173
 *        - dolPrintHTML... that is dol_escape_htmltag(dol_htmlwithnojs(dol_string_onlythesehtmltags(dol_htmlentitiesbr(), 1, 1, 1), 1, 1) for notes or descriptions,
5174
 *        - dolPrintPassword that is abelhtmlspecialchars( , ENT_COMPAT, 'UTF-8') for passwords.
5175
 *
5176
 * @param string $stringtoencode String to encode
5177
 * @param int $nl2brmode 0=Adding br before \n, 1=Replacing \n by br (for use with FPDF writeHTMLCell function for example)
5178
 * @param string $pagecodefrom Pagecode stringtoencode is encoded
5179
 * @param int $removelasteolbr 1=Remove last br or lasts \n (default), 0=Do nothing
5180
 * @return string                      String encoded
5181
 * @see dol_escape_htmltag(), dolGetFirstLineOfText(), dol_string_onlythesehtmltags()
5182
 */
5183
function dol_htmlentitiesbr($stringtoencode, $nl2brmode = 0, $pagecodefrom = 'UTF-8', $removelasteolbr = 1)
5184
{
5185
    if (is_null($stringtoencode)) {
5186
        return '';
5187
    }
5188
5189
    $newstring = $stringtoencode;
5190
    if (dol_textishtml($stringtoencode)) {  // Check if text is already HTML or not
5191
        $newstring = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>/i', '<br>', $newstring); // Replace "<br type="_moz" />" by "<br>". It's same and avoid pb with FPDF.
5192
        if ($removelasteolbr) {
5193
            $newstring = preg_replace('/<br>$/i', '', $newstring); // Remove last <br> (remove only last one)
5194
        }
5195
        $newstring = strtr($newstring, array('&' => '__and__', '<' => '__lt__', '>' => '__gt__', '"' => '__dquot__'));
5196
        $newstring = dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom); // Make entity encoding
5197
        $newstring = strtr($newstring, array('__and__' => '&', '__lt__' => '<', '__gt__' => '>', '__dquot__' => '"'));
5198
    } else {
5199
        if ($removelasteolbr) {
5200
            $newstring = preg_replace('/(\r\n|\r|\n)$/i', '', $newstring); // Remove last \n (may remove several)
5201
        }
5202
        $newstring = dol_nl2br(dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom), $nl2brmode);
5203
    }
5204
    // Other substitutions that htmlentities does not do
5205
    //$newstring=str_replace(chr(128),'&euro;',$newstring); // 128 = 0x80. Not in html entity table.     // Seems useles with TCPDF. Make bug with UTF8 languages
5206
    return $newstring;
5207
}
5208
5209
/**
5210
 *  This function is called to decode a HTML string (it decodes entities and br tags)
5211
 *
5212
 * @param string $stringtodecode String to decode
5213
 * @param string $pagecodeto Page code for result
5214
 * @return string                      String decoded
5215
 */
5216
function dol_htmlentitiesbr_decode($stringtodecode, $pagecodeto = 'UTF-8')
5217
{
5218
    $ret = dol_html_entity_decode($stringtodecode, ENT_COMPAT | ENT_HTML5, $pagecodeto);
5219
    $ret = preg_replace('/' . "\r\n" . '<br(\s[\sa-zA-Z_="]*)?\/?>/i', "<br>", $ret);
5220
    $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>' . "\r\n" . '/i', "\r\n", $ret);
5221
    $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>' . "\n" . '/i', "\n", $ret);
5222
    $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>/i', "\n", $ret);
5223
    return $ret;
5224
}
5225
5226
/**
5227
 *  This function remove all ending \n and br at end
5228
 *
5229
 * @param string $stringtodecode String to decode
5230
 * @return string                      String decoded
5231
 */
5232
function dol_htmlcleanlastbr($stringtodecode)
5233
{
5234
    $ret = preg_replace('/&nbsp;$/i', "", $stringtodecode);     // Because wysiwyg editor may add a &nbsp; at end of last line
5235
    $ret = preg_replace('/(<br>|<br(\s[\sa-zA-Z_="]*)?\/?>|' . "\n" . '|' . "\r" . ')+$/i', "", $ret);
5236
    return $ret;
5237
}
5238
5239
/**
5240
 * Replace html_entity_decode functions to manage errors
5241
 *
5242
 * @param string $a Operand a
5243
 * @param string $b Operand b (ENT_QUOTES|ENT_HTML5=convert simple, double quotes, colon, e accent, ...)
5244
 * @param string $c Operand c
5245
 * @param int $keepsomeentities Entities but &, <, >, " are not converted.
5246
 * @return  string                      String decoded
5247
 */
5248
function dol_html_entity_decode($a, $b, $c = 'UTF-8', $keepsomeentities = 0)
5249
{
5250
    $newstring = $a;
5251
    if ($keepsomeentities) {
5252
        $newstring = strtr($newstring, array('&amp;' => '__andamp__', '&lt;' => '__andlt__', '&gt;' => '__andgt__', '"' => '__dquot__'));
5253
    }
5254
    $newstring = html_entity_decode((string)$newstring, (int)$b, (string)$c);
5255
    if ($keepsomeentities) {
5256
        $newstring = strtr($newstring, array('__andamp__' => '&amp;', '__andlt__' => '&lt;', '__andgt__' => '&gt;', '__dquot__' => '"'));
5257
    }
5258
    return $newstring;
5259
}
5260
5261
/**
5262
 * Replace htmlentities functions.
5263
 * Goal of this function is to be sure to have default values of htmlentities that match what we need.
5264
 *
5265
 * @param string $string The input string to encode
5266
 * @param int $flags Flags (see PHP doc above)
5267
 * @param string $encoding Encoding page code
5268
 * @param bool $double_encode When double_encode is turned off, PHP will not encode existing html entities
5269
 * @return  string  $ret            Encoded string
5270
 * @see dol_htmlentitiesbr()
5271
 */
5272
function dol_htmlentities($string, $flags = ENT_QUOTES | ENT_SUBSTITUTE, $encoding = 'UTF-8', $double_encode = false)
5273
{
5274
    return htmlentities($string, $flags, $encoding, $double_encode);
5275
}
5276
5277
/**
5278
 *  Clean a string from some undesirable HTML tags.
5279
 *  Note: Complementary to dol_string_onlythesehtmltags().
5280
 *  This method is used for example by dol_htmlwithnojs() when option MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES is set to 1.
5281
 *
5282
 * @param string $stringtoclean String to clean
5283
 * @param string[] $allowed_attributes Array of tags not allowed
5284
 * @return string                          String cleaned
5285
 *
5286
 * @see    dol_escape_htmltag() strip_tags() dol_string_nohtmltag() dol_string_onlythesehtmltags() dol_string_neverthesehtmltags()
5287
 * @phan-suppress PhanUndeclaredProperty
5288
 */
5289
function dol_string_onlythesehtmlattributes($stringtoclean, $allowed_attributes = null)
5290
{
5291
    if (is_null($allowed_attributes)) {
5292
        $allowed_attributes = array(
5293
            "allow", "allowfullscreen", "alt", "class", "contenteditable", "data-html", "frameborder", "height", "href", "id", "name", "src", "style", "target", "title", "width",
5294
            // HTML5
5295
            "header", "footer", "nav", "section", "menu", "menuitem"
5296
        );
5297
    }
5298
5299
    if (class_exists('DOMDocument') && !empty($stringtoclean)) {
5300
        $stringtoclean = '<?xml encoding="UTF-8"><html><body>' . $stringtoclean . '</body></html>';
5301
5302
        // Warning: loadHTML does not support HTML5 on old libxml versions.
5303
        $dom = new DOMDocument('', 'UTF-8');
5304
        // If $stringtoclean is wrong, it will generates warnings. So we disable warnings and restore them later.
5305
        $savwarning = error_reporting();
5306
        error_reporting(E_ALL & ~E_WARNING & ~E_NOTICE);
5307
        $dom->loadHTML($stringtoclean, LIBXML_ERR_NONE | LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD | LIBXML_NONET | LIBXML_NOWARNING | LIBXML_NOXMLDECL);
5308
        error_reporting($savwarning);
5309
5310
        if ($dom instanceof DOMDocument) {
0 ignored issues
show
introduced by
$dom is always a sub-type of DOMDocument.
Loading history...
5311
            for ($els = $dom->getElementsByTagname('*'), $i = $els->length - 1; $i >= 0; $i--) {
5312
                $el = $els->item($i);
5313
                if (!$el instanceof DOMElement) {
5314
                    continue;
5315
                }
5316
                $attrs = $el->attributes;
5317
                for ($ii = $attrs->length - 1; $ii >= 0; $ii--) {
5318
                    //var_dump($attrs->item($ii));
5319
                    if (!empty($attrs->item($ii)->name)) {
0 ignored issues
show
Bug introduced by
The method item() does not exist on null. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

5319
                    if (!empty($attrs->/** @scrutinizer ignore-call */ item($ii)->name)) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
5320
                        if (!in_array($attrs->item($ii)->name, $allowed_attributes)) {
5321
                            // Delete attribute if not into allowed_attributes  @phan-suppress-next-line PhanUndeclaredMethod
5322
                            $els->item($i)->removeAttribute($attrs->item($ii)->name);
5323
                        } elseif (in_array($attrs->item($ii)->name, array('style'))) {
5324
                            // If attribute is 'style'
5325
                            $valuetoclean = $attrs->item($ii)->value;
5326
5327
                            if (isset($valuetoclean)) {
5328
                                do {
5329
                                    $oldvaluetoclean = $valuetoclean;
5330
                                    $valuetoclean = preg_replace('/\/\*.*\*\//m', '', $valuetoclean);   // clean css comments
5331
                                    $valuetoclean = preg_replace('/position\s*:\s*[a-z]+/mi', '', $valuetoclean);
5332
                                    if ($els->item($i)->tagName == 'a') {   // more paranoiac cleaning for clickable tags.
5333
                                        $valuetoclean = preg_replace('/display\s*:/mi', '', $valuetoclean);
5334
                                        $valuetoclean = preg_replace('/z-index\s*:/mi', '', $valuetoclean);
5335
                                        $valuetoclean = preg_replace('/\s+(top|left|right|bottom)\s*:/mi', '', $valuetoclean);
5336
                                    }
5337
5338
                                    // We do not allow logout|passwordforgotten.php and action= into the content of a "style" tag
5339
                                    $valuetoclean = preg_replace('/(logout|passwordforgotten)\.php/mi', '', $valuetoclean);
5340
                                    $valuetoclean = preg_replace('/action=/mi', '', $valuetoclean);
5341
                                } while ($oldvaluetoclean != $valuetoclean);
5342
                            }
5343
5344
                            $attrs->item($ii)->value = $valuetoclean;
5345
                        }
5346
                    }
5347
                }
5348
            }
5349
        }
5350
5351
        $return = $dom->saveHTML(); // This may add a LF at end of lines, so we will trim later
5352
        //$return = '<html><body>aaaa</p>bb<p>ssdd</p>'."\n<p>aaa</p>aa<p>bb</p>";
5353
5354
        $return = preg_replace('/^' . preg_quote('<?xml encoding="UTF-8">', '/') . '/', '', $return);
5355
        $return = preg_replace('/^' . preg_quote('<html><body>', '/') . '/', '', $return);
5356
        $return = preg_replace('/' . preg_quote('</body></html>', '/') . '$/', '', $return);
5357
        return trim($return);
5358
    } else {
5359
        return $stringtoclean;
5360
    }
5361
}
5362
5363
/**
5364
 *  Return picto saying a field is required
5365
 *
5366
 * @return  string     Chaine avec picto obligatoire
5367
 */
5368
function picto_required()
5369
{
5370
    return '<span class="fieldrequired">*</span>';
5371
}
5372
5373
5374
/**
5375
 *  Clean a string from all HTML tags and entities.
5376
 *  This function differs from strip_tags because:
5377
 *  - <br> are replaced with \n if removelinefeed=0 or 1
5378
 *  - if entities are found, they are decoded BEFORE the strip
5379
 *  - you can decide to convert line feed into a space
5380
 *
5381
 * @param string $stringtoclean String to clean
5382
 * @param integer $removelinefeed 1=Replace all new lines by 1 space, 0=Only ending new lines are removed others are replaced with \n, 2=The ending new line is removed but others are kept with the same number of \n than the nb of <br> when there is both "...<br>\n..."
5383
 * @param string $pagecodeto Encoding of input/output string
5384
 * @param integer $strip_tags 0=Use internal strip, 1=Use strip_tags() php function (bugged when text contains a < char that is not for a html tag or when tags is not closed like '<img onload=aaa')
5385
 * @param integer $removedoublespaces Replace double space into one space
5386
 * @return string                      String cleaned
5387
 *
5388
 * @see    dol_escape_htmltag() strip_tags() dol_string_onlythesehtmltags() dol_string_neverthesehtmltags(), dolStripPhpCode()
5389
 */
5390
function dol_string_nohtmltag($stringtoclean, $removelinefeed = 1, $pagecodeto = 'UTF-8', $strip_tags = 0, $removedoublespaces = 1)
5391
{
5392
    if (is_null($stringtoclean)) {
5393
        return '';
5394
    }
5395
5396
    if ($removelinefeed == 2) {
5397
        $stringtoclean = preg_replace('/<br[^>]*>(\n|\r)+/ims', '<br>', $stringtoclean);
5398
    }
5399
    $temp = preg_replace('/<br[^>]*>/i', "\n", $stringtoclean);
5400
5401
    // We remove entities BEFORE stripping (in case of an open separator char that is entity encoded and not the closing other, the strip will fails)
5402
    $temp = dol_html_entity_decode($temp, ENT_COMPAT | ENT_HTML5, $pagecodeto);
5403
5404
    $temp = str_replace('< ', '__ltspace__', $temp);
5405
    $temp = str_replace('<:', '__lttwopoints__', $temp);
5406
5407
    if ($strip_tags) {
5408
        $temp = strip_tags($temp);
5409
    } else {
5410
        // Remove '<' into remaining, so remove non closing html tags like '<abc' or '<<abc'. Note: '<123abc' is not a html tag (can be kept), but '<abc123' is (must be removed).
5411
        $pattern = "/<[^<>]+>/";
5412
        // Example of $temp: <a href="/myurl" title="<u>A title</u>">0000-021</a>
5413
        // pass 1 - $temp after pass 1: <a href="/myurl" title="A title">0000-021
5414
        // pass 2 - $temp after pass 2: 0000-021
5415
        $tempbis = $temp;
5416
        do {
5417
            $temp = $tempbis;
5418
            $tempbis = str_replace('<>', '', $temp);    // No reason to have this into a text, except if value is to try bypass the next html cleaning
5419
            $tempbis = preg_replace($pattern, '', $tempbis);
5420
            //$idowhile++; print $temp.'-'.$tempbis."\n"; if ($idowhile > 100) break;
5421
        } while ($tempbis != $temp);
5422
5423
        $temp = $tempbis;
5424
5425
        // Remove '<' into remaining, so remove non closing html tags like '<abc' or '<<abc'. Note: '<123abc' is not a html tag (can be kept), but '<abc123' is (must be removed).
5426
        $temp = preg_replace('/<+([a-z]+)/i', '\1', $temp);
5427
    }
5428
5429
    $temp = dol_html_entity_decode($temp, ENT_COMPAT, $pagecodeto);
5430
5431
    // Remove also carriage returns
5432
    if ($removelinefeed == 1) {
5433
        $temp = str_replace(array("\r\n", "\r", "\n"), " ", $temp);
5434
    }
5435
5436
    // And double quotes
5437
    if ($removedoublespaces) {
5438
        while (strpos($temp, "  ")) {
5439
            $temp = str_replace("  ", " ", $temp);
5440
        }
5441
    }
5442
5443
    $temp = str_replace('__ltspace__', '< ', $temp);
5444
    $temp = str_replace('__lttwopoints__', '<:', $temp);
5445
5446
    return trim($temp);
5447
}
5448
5449
/**
5450
 *  Clean a string to keep only desirable HTML tags.
5451
 *  WARNING: This also clean HTML comments (because they can be used to obfuscate tag name).
5452
 *
5453
 * @param string $stringtoclean String to clean
5454
 * @param int $cleanalsosomestyles Remove absolute/fixed positioning from inline styles
5455
 * @param int $removeclassattribute 1=Remove the class attribute from tags
5456
 * @param int $cleanalsojavascript Remove also occurrence of 'javascript:'.
5457
 * @param int $allowiframe Allow iframe tags.
5458
 * @param string[] $allowed_tags List of allowed tags to replace the default list
5459
 * @param int $allowlink Allow "link" tags.
5460
 * @return string                          String cleaned
5461
 *
5462
 * @see    dol_htmlwithnojs() dol_escape_htmltag() strip_tags() dol_string_nohtmltag() dol_string_neverthesehtmltags()
5463
 */
5464
function dol_string_onlythesehtmltags($stringtoclean, $cleanalsosomestyles = 1, $removeclassattribute = 1, $cleanalsojavascript = 0, $allowiframe = 0, $allowed_tags = array(), $allowlink = 0)
5465
{
5466
    if (empty($allowed_tags)) {
5467
        $allowed_tags = array(
5468
            "html", "head", "meta", "body", "article", "a", "abbr", "b", "blockquote", "br", "cite", "div", "dl", "dd", "dt", "em", "font", "img", "ins", "hr", "i", "li",
5469
            "ol", "p", "q", "s", "span", "strike", "strong", "title", "table", "tr", "th", "td", "u", "ul", "sup", "sub", "blockquote", "pre", "h1", "h2", "h3", "h4", "h5", "h6",
5470
            "header", "footer", "nav", "section", "menu", "menuitem"    // html5 tags
5471
        );
5472
    }
5473
    $allowed_tags[] = "comment";        // this tags is added to manage comment <!--...--> that are replaced into <comment>...</comment>
5474
    if ($allowiframe) {
5475
        if (!in_array('iframe', $allowed_tags)) {
5476
            $allowed_tags[] = "iframe";
5477
        }
5478
    }
5479
    if ($allowlink) {
5480
        if (!in_array('link', $allowed_tags)) {
5481
            $allowed_tags[] = "link";
5482
        }
5483
    }
5484
5485
    $allowed_tags_string = implode("><", $allowed_tags);
5486
    $allowed_tags_string = '<' . $allowed_tags_string . '>';
5487
5488
    $stringtoclean = str_replace('<!DOCTYPE html>', '__!DOCTYPE_HTML__', $stringtoclean);   // Replace DOCTYPE to avoid to have it removed by the strip_tags
5489
5490
    $stringtoclean = dol_string_nounprintableascii($stringtoclean, 0);
5491
5492
    //$stringtoclean = preg_replace('/<!--[^>]*-->/', '', $stringtoclean);
5493
    $stringtoclean = preg_replace('/<!--([^>]*)-->/', '<comment>\1</comment>', $stringtoclean);
5494
5495
    $stringtoclean = preg_replace('/&colon;/i', ':', $stringtoclean);
5496
    $stringtoclean = preg_replace('/&#58;|&#0+58|&#x3A/i', '', $stringtoclean); // refused string ':' encoded (no reason to have a : encoded like this) to disable 'javascript:...'
5497
5498
    $temp = strip_tags($stringtoclean, $allowed_tags_string);   // Warning: This remove also undesired </>, so may changes string obfuscated with </> that pass the injection detection into a harmfull string
5499
5500
    if ($cleanalsosomestyles) { // Clean for remaining html tags
5501
        $temp = preg_replace('/position\s*:\s*(absolute|fixed)\s*!\s*important/i', '', $temp); // Note: If hacker try to introduce css comment into string to bypass this regex, the string must also be encoded by the dol_htmlentitiesbr during output so it become harmless
5502
    }
5503
    if ($removeclassattribute) {    // Clean for remaining html tags
5504
        $temp = preg_replace('/(<[^>]+)\s+class=((["\']).*?\\3|\\w*)/i', '\\1', $temp);
5505
    }
5506
5507
    // Remove 'javascript:' that we should not find into a text with
5508
    // Warning: This is not reliable to fight against obfuscated javascript, there is a lot of other solution to include js into a common html tag (only filtered by a GETPOST(.., powerfullfilter)).
5509
    if ($cleanalsojavascript) {
5510
        $temp = preg_replace('/j\s*a\s*v\s*a\s*s\s*c\s*r\s*i\s*p\s*t\s*:/i', '', $temp);
5511
    }
5512
5513
    $temp = str_replace('__!DOCTYPE_HTML__', '<!DOCTYPE html>', $temp); // Restore the DOCTYPE
5514
5515
    $temp = preg_replace('/<comment>([^>]*)<\/comment>/', '<!--\1-->', $temp);  // Restore html comments
5516
5517
5518
    return $temp;
5519
}
5520
5521
/**
5522
 *  Clean a string from some undesirable HTML tags.
5523
 *  Note: You should use instead dol_string_onlythesehtmltags() that is more secured if you can.
5524
 *
5525
 * @param string $stringtoclean String to clean
5526
 * @param array $disallowed_tags Array of tags not allowed
5527
 * @param int $cleanalsosomestyles Clean also some tags
5528
 * @return string                          String cleaned
5529
 *
5530
 * @see    dol_escape_htmltag() strip_tags() dol_string_nohtmltag() dol_string_onlythesehtmltags() dol_string_onlythesehtmlattributes()
5531
 */
5532
function dol_string_neverthesehtmltags($stringtoclean, $disallowed_tags = array('textarea'), $cleanalsosomestyles = 0)
5533
{
5534
    $temp = $stringtoclean;
5535
    foreach ($disallowed_tags as $tagtoremove) {
5536
        $temp = preg_replace('/<\/?' . $tagtoremove . '>/', '', $temp);
5537
        $temp = preg_replace('/<\/?' . $tagtoremove . '\s+[^>]*>/', '', $temp);
5538
    }
5539
5540
    if ($cleanalsosomestyles) {
5541
        $temp = preg_replace('/position\s*:\s*(absolute|fixed)\s*!\s*important/', '', $temp); // Note: If hacker try to introduce css comment into string to avoid this, string should be encoded by the dol_htmlentitiesbr so be harmless
5542
    }
5543
5544
    return $temp;
5545
}
5546
5547
5548
/**
5549
 *  Return yes or no in current language
5550
 *
5551
 * @param string|int $yesno Value to test (1, 'yes', 'true' or 0, 'no', 'false')
5552
 * @param integer $case 1=Yes/No, 0=yes/no, 2=Disabled checkbox, 3=Disabled checkbox + Yes/No
5553
 * @param int $color 0=texte only, 1=Text is formatted with a color font style ('ok' or 'error'), 2=Text is formatted with 'ok' color.
5554
 * @return string                      HTML string
5555
 */
5556
function yn($yesno, $case = 1, $color = 0)
5557
{
5558
    global $langs;
5559
5560
    $result = 'unknown';
5561
    $classname = '';
5562
    if ($yesno == 1 || (isset($yesno) && (strtolower($yesno) == 'yes' || strtolower($yesno) == 'true'))) {  // To set to 'no' before the test because of the '== 0'
5563
        $result = $langs->trans('yes');
5564
        if ($case == 1 || $case == 3) {
5565
            $result = $langs->trans("Yes");
5566
        }
5567
        if ($case == 2) {
5568
            $result = '<input type="checkbox" value="1" checked disabled>';
5569
        }
5570
        if ($case == 3) {
5571
            $result = '<input type="checkbox" value="1" checked disabled> ' . $result;
5572
        }
5573
        if ($case == 4) {
5574
            $result = img_picto('check', 'check');
5575
        }
5576
5577
        $classname = 'ok';
5578
    } elseif ($yesno == 0 || strtolower($yesno) == 'no' || strtolower($yesno) == 'false') {
5579
        $result = $langs->trans("no");
5580
        if ($case == 1 || $case == 3) {
5581
            $result = $langs->trans("No");
5582
        }
5583
        if ($case == 2) {
5584
            $result = '<input type="checkbox" value="0" disabled>';
5585
        }
5586
        if ($case == 3) {
5587
            $result = '<input type="checkbox" value="0" disabled> ' . $result;
5588
        }
5589
        if ($case == 4) {
5590
            $result = img_picto('uncheck', 'uncheck');
5591
        }
5592
5593
        if ($color == 2) {
5594
            $classname = 'ok';
5595
        } else {
5596
            $classname = 'error';
5597
        }
5598
    }
5599
    if ($color) {
5600
        return '<span class="' . $classname . '">' . $result . '</span>';
5601
    }
5602
    return $result;
5603
}
5604