Passed
Push — GENERAL_BUG_REVIEW_240927 ( 5b3eaa )
by Rafael
49:18
created

getConstants()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 23
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
eloc 16
c 0
b 0
f 0
nc 3
nop 0
dl 0
loc 23
rs 9.7333
1
<?php
2
3
/* Copyright (C) 2002-2007  Rodolphe Quiedeville    <[email protected]>
4
 * Copyright (C) 2003       Xavier Dutoit           <[email protected]>
5
 * Copyright (C) 2004-2021  Laurent Destailleur     <[email protected]>
6
 * Copyright (C) 2004       Sebastien Di Cintio     <[email protected]>
7
 * Copyright (C) 2004       Benoit Mortier          <[email protected]>
8
 * Copyright (C) 2005-2021  Regis Houssin           <[email protected]>
9
 * Copyright (C) 2011-2014  Philippe Grand          <[email protected]>
10
 * Copyright (C) 2008       Matteli
11
 * Copyright (C) 2011-2016  Juanjo Menent           <[email protected]>
12
 * Copyright (C) 2012       Christophe Battarel     <[email protected]>
13
 * Copyright (C) 2014-2015  Marcos García           <[email protected]>
14
 * Copyright (C) 2015       Raphaël Doursenaud      <[email protected]>
15
 * Copyright (C) 2020       Demarest Maxime         <[email protected]>
16
 * Copyright (C) 2020       Charlene Benke          <[email protected]>
17
 * Copyright (C) 2021-2024  Frédéric France         <[email protected]>
18
 * Copyright (C) 2021       Alexandre Spangaro      <[email protected]>
19
 * Copyright (C) 2023       Joachim Küter      		<[email protected]>
20
 * Copyright (C) 2023       Eric Seigne      		<[email protected]>
21
 * Copyright (C) 2024		MDW							<[email protected]>
22
 * Copyright (C) 2024       Rafael San José             <[email protected]>
23
 *
24
 * This program is free software; you can redistribute it and/or modify
25
 * it under the terms of the GNU General Public License as published by
26
 * the Free Software Foundation; either version 3 of the License, or
27
 * (at your option) any later version.
28
 *
29
 * This program is distributed in the hope that it will be useful,
30
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
32
 * GNU General Public License for more details.
33
 *
34
 * You should have received a copy of the GNU General Public License
35
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
36
 */
37
38
use Dolibarr\Code\Core\Classes\Form;
39
use Dolibarr\Code\Core\Classes\HookManager;
40
use Dolibarr\Code\Core\Classes\Translate;
41
42
/**
43
 *  \file       htdocs/main.inc.php
44
 *  \ingroup    core
45
 *  \brief      File that defines environment for Dolibarr GUI pages only (file not required by scripts)
46
 */
47
48
//@ini_set('memory_limit', '128M'); // This may be useless if memory is hard limited by your PHP
49
50
// For optional tuning. Enabled if environment variable MAIN_SHOW_TUNING_INFO is defined.
51
52
$micro_start_time = 0;
53
if (!empty($_SERVER['MAIN_SHOW_TUNING_INFO'])) {
54
    list($usec, $sec) = explode(" ", microtime());
55
    $micro_start_time = ((float)$usec + (float)$sec);
56
    // Add Xdebug code coverage
57
    //define('XDEBUGCOVERAGE',1);
58
    if (defined('XDEBUGCOVERAGE')) {
59
        xdebug_start_code_coverage();
60
    }
61
}
62
63
/**
64
 * Return array of Emojis. We can't move this function inside a common lib because we need it for security before loading any file.
65
 *
66
 * @return  array<string,array<string>>         Array of Emojis in hexadecimal
67
 * @see getArrayOfEmojiBis()
68
 */
69
function getArrayOfEmoji()
70
{
71
    $arrayofcommonemoji = array(
72
        'misc' => array('2600', '26FF'),        // Miscellaneous Symbols
73
        'ding' => array('2700', '27BF'),        // Dingbats
74
        '????' => array('9989', '9989'),        // Variation Selectors
75
        'vars' => array('FE00', 'FE0F'),        // Variation Selectors
76
        'pict' => array('1F300', '1F5FF'),      // Miscellaneous Symbols and Pictographs
77
        'emot' => array('1F600', '1F64F'),      // Emoticons
78
        'tran' => array('1F680', '1F6FF'),      // Transport and Map Symbols
79
        'flag' => array('1F1E0', '1F1FF'),      // Flags (note: may be 1F1E6 instead of 1F1E0)
80
        'supp' => array('1F900', '1F9FF'),      // Supplemental Symbols and Pictographs
81
    );
82
83
    return $arrayofcommonemoji;
84
}
85
86
/**
87
 * Return the real char for a numeric entities.
88
 * WARNING: This function is required by testSqlAndScriptInject() and the GETPOST 'restricthtml'. Regex calling must be similar.
89
 *
90
 * @param array<int,string> $matches Array with a decimal numeric entity into key 0, value without the &# into the key 1
91
 * @return  string                                  New value
92
 */
93
function realCharForNumericEntities($matches)
94
{
95
    $newstringnumentity = preg_replace('/;$/', '', $matches[1]);
96
    //print  ' $newstringnumentity='.$newstringnumentity;
97
98
    if (preg_match('/^x/i', $newstringnumentity)) {     // if numeric is hexadecimal
99
        $newstringnumentity = hexdec(preg_replace('/^x/i', '', $newstringnumentity));
100
    } else {
101
        $newstringnumentity = (int)$newstringnumentity;
102
    }
103
104
    // The numeric values we don't want as entities because they encode ascii char, and why using html entities on ascii except for haking ?
105
    if (($newstringnumentity >= 65 && $newstringnumentity <= 90) || ($newstringnumentity >= 97 && $newstringnumentity <= 122)) {
106
        return chr((int)$newstringnumentity);
107
    }
108
109
    // The numeric values we want in UTF8 instead of entities because it is emoji
110
    $arrayofemojis = getArrayOfEmoji();
111
    foreach ($arrayofemojis as $valarray) {
112
        if ($newstringnumentity >= hexdec($valarray[0]) && $newstringnumentity <= hexdec($valarray[1])) {
113
            // This is a known emoji
114
            return html_entity_decode($matches[0], ENT_COMPAT | ENT_HTML5, 'UTF-8');
115
        }
116
    }
117
118
    return '&#' . $matches[1]; // Value will be unchanged because regex was /&#(  )/
119
}
120
121
/**
122
 * Security: WAF layer for SQL Injection and XSS Injection (scripts) protection (Filters on GET, POST, PHP_SELF).
123
 * Warning: Such a protection can't be enough. It is not reliable as it will always be possible to bypass this. Good protection can
124
 * only be guaranteed by escaping data during output.
125
 *
126
 * @param string $val Brute value found into $_GET, $_POST or PHP_SELF
127
 * @param string $type 0=POST, 1=GET, 2=PHP_SELF, 3=GET without sql reserved keywords (the less tolerant test)
128
 * @return      int                     >0 if there is an injection, 0 if none
129
 */
130
function testSqlAndScriptInject($val, $type)
131
{
132
    // Decode string first because a lot of things are obfuscated by encoding or multiple encoding.
133
    // So <svg o&#110;load='console.log(&quot;123&quot;)' become <svg onload='console.log(&quot;123&quot;)'
134
    // So "&colon;&apos;" become ":'" (due to ENT_HTML5)
135
    // So "&Tab;&NewLine;" become ""
136
    // So "&lpar;&rpar;" become "()"
137
138
    // Loop to decode until no more things to decode.
139
    //print "before decoding $val\n";
140
    do {
141
        $oldval = $val;
142
        $val = html_entity_decode($val, ENT_QUOTES | ENT_HTML5);    // Decode '&colon;', '&apos;', '&Tab;', '&NewLine', ...
143
        // Sometimes we have entities without the ; at end so html_entity_decode does not work but entities is still interpreted by browser.
144
        $val = preg_replace_callback(
145
            '/&#(x?[0-9][0-9a-f]+;?)/i',
146
            /**
147
             * @param string[] $m
148
             * @return string
149
             */
150
            static function ($m) {
151
                // Decode '&#110;', ...
152
                return realCharForNumericEntities($m);
153
            },
154
            $val
155
        );
156
157
        // We clean html comments because some hacks try to obfuscate evil strings by inserting HTML comments. Example: on<!-- -->error=alert(1)
158
        $val = preg_replace('/<!--[^>]*-->/', '', $val);
159
        $val = preg_replace('/[\r\n\t]/', '', $val);
160
    } while ($oldval != $val);
161
    //print "type = ".$type." after decoding: ".$val."\n";
162
163
    $inj = 0;
164
165
    // We check string because some hacks try to obfuscate evil strings by inserting non printable chars. Example: 'java(ascci09)scr(ascii00)ipt' is processed like 'javascript' (whatever is place of evil ascii char)
166
    // We should use dol_string_nounprintableascii but function is not yet loaded/available
167
    // Example of valid UTF8 chars:
168
    // utf8=utf8mb3:    '\x09', '\x0A', '\x0D', '\x7E'
169
    // utf8=utf8mb3:    '\xE0\xA0\x80'
170
    // utf8mb4:         '\xF0\x9D\x84\x9E'   (but this may be refused by the database insert if pagecode is utf8=utf8mb3)
171
    $newval = preg_replace('/[\x00-\x08\x0B-\x0C\x0E-\x1F\x7F]/u', '', $val); // /u operator makes UTF8 valid characters being ignored so are not included into the replace
172
173
    // Note that $newval may also be completely empty '' when non valid UTF8 are found.
174
    if ($newval != $val) {
175
        // If $val has changed after removing non valid UTF8 chars, it means we have an evil string.
176
        $inj += 1;
177
    }
178
    //print 'inj='.$inj.'-type='.$type.'-val='.$val.'-newval='.$newval."\n";
179
180
    // For SQL Injection (only GET are used to scan for such injection strings)
181
    if ($type == 1 || $type == 3) {
182
        // Note the \s+ is replaced into \s* because some spaces may have been modified in previous loop
183
        $inj += preg_match('/delete\s*from/i', $val);
184
        $inj += preg_match('/create\s*table/i', $val);
185
        $inj += preg_match('/insert\s*into/i', $val);
186
        $inj += preg_match('/select\s*from/i', $val);
187
        $inj += preg_match('/into\s*(outfile|dumpfile)/i', $val);
188
        $inj += preg_match('/user\s*\(/i', $val); // avoid to use function user() or mysql_user() that return current database login
189
        $inj += preg_match('/information_schema/i', $val); // avoid to use request that read information_schema database
190
        $inj += preg_match('/<svg/i', $val); // <svg can be allowed in POST
191
        $inj += preg_match('/update[^&=\w].*set.+=/i', $val);   // the [^&=\w] test is to avoid error when request is like action=update&...set... or &updatemodule=...set...
192
        $inj += preg_match('/union.+select/i', $val);
193
    }
194
    if ($type == 3) {
195
        // Note the \s+ is replaced into \s* because some spaces may have been modified in previous loop
196
        $inj += preg_match('/select|update|delete|truncate|replace|group\s*by|concat|count|from|union/i', $val);
197
    }
198
    if ($type != 2) {   // Not common key strings, so we can check them both on GET and POST
199
        $inj += preg_match('/updatexml\(/i', $val);
200
        $inj += preg_match('/(\.\.%2f)+/i', $val);
201
        $inj += preg_match('/\s@@/', $val);
202
    }
203
    // For XSS Injection done by closing textarea to execute content into a textarea field
204
    $inj += preg_match('/<\/textarea/i', $val);
205
    // For XSS Injection done by adding javascript with script
206
    // This is all cases a browser consider text is javascript:
207
    // When it found '<script', 'javascript:', '<style', 'onload\s=' on body tag, '="&' on a tag size with old browsers
208
    // All examples on page: http://ha.ckers.org/xss.html#XSScalc
209
    // More on https://www.owasp.org/index.php/XSS_Filter_Evasion_Cheat_Sheet
210
    $inj += preg_match('/<audio/i', $val);
211
    $inj += preg_match('/<embed/i', $val);
212
    $inj += preg_match('/<iframe/i', $val);
213
    $inj += preg_match('/<object/i', $val);
214
    $inj += preg_match('/<script/i', $val);
215
    $inj += preg_match('/Set\.constructor/i', $val); // ECMA script 6
216
    if (!defined('NOSTYLECHECK')) {
217
        $inj += preg_match('/<style/i', $val);
218
    }
219
    $inj += preg_match('/base\s+href/si', $val);
220
    $inj += preg_match('/=data:/si', $val);
221
    // List of dom events is on https://www.w3schools.com/jsref/dom_obj_event.asp and https://developer.mozilla.org/en-US/docs/Web/Events
222
    $inj += preg_match('/on(mouse|drag|key|load|touch|pointer|select|transition)[a-z]*\s*=/i', $val); // onmousexxx can be set on img or any html tag like <img title='...' onmouseover=alert(1)>
223
    $inj += preg_match('/on(abort|after|animation|auxclick|before|blur|cancel|canplay|canplaythrough|change|click|close|contextmenu|cuechange|copy|cut)[a-z]*\s*=/i', $val);
224
    $inj += preg_match('/on(dblclick|drop|durationchange|emptied|end|ended|error|focus|focusin|focusout|formdata|gotpointercapture|hashchange|input|invalid)[a-z]*\s*=/i', $val);
225
    $inj += preg_match('/on(lostpointercapture|offline|online|pagehide|pageshow)[a-z]*\s*=/i', $val);
226
    $inj += preg_match('/on(paste|pause|play|playing|progress|ratechange|reset|resize|scroll|search|seeked|seeking|show|stalled|start|submit|suspend)[a-z]*\s*=/i', $val);
227
    $inj += preg_match('/on(timeupdate|toggle|unload|volumechange|waiting|wheel)[a-z]*\s*=/i', $val);
228
    // More not into the previous list
229
230
    $inj += preg_match('/on(repeat|begin|finish|beforeinput)[a-z]*\s*=/i', $val);
231
232
    // We refuse html into html because some hacks try to obfuscate evil strings by inserting HTML into HTML. Example: <img on<a>error=alert(1) to bypass test on onerror
233
    $tmpval = preg_replace('/<[^<]+>/', '', $val);
234
    // List of dom events is on https://www.w3schools.com/jsref/dom_obj_event.asp and https://developer.mozilla.org/en-US/docs/Web/Events
235
    $inj += preg_match('/on(mouse|drag|key|load|touch|pointer|select|transition)[a-z]*\s*=/i', $tmpval); // onmousexxx can be set on img or any html tag like <img title='...' onmouseover=alert(1)>
236
    $inj += preg_match('/on(abort|after|animation|auxclick|before|blur|cancel|canplay|canplaythrough|change|click|close|contextmenu|cuechange|copy|cut)[a-z]*\s*=/i', $tmpval);
237
    $inj += preg_match('/on(dblclick|drop|durationchange|emptied|end|ended|error|focus|focusin|focusout|formdata|gotpointercapture|hashchange|input|invalid)[a-z]*\s*=/i', $tmpval);
238
    $inj += preg_match('/on(lostpointercapture|offline|online|pagehide|pageshow)[a-z]*\s*=/i', $tmpval);
239
    $inj += preg_match('/on(paste|pause|play|playing|progress|ratechange|reset|resize|scroll|search|seeked|seeking|show|stalled|start|submit|suspend)[a-z]*\s*=/i', $tmpval);
240
    $inj += preg_match('/on(timeupdate|toggle|unload|volumechange|waiting|wheel)[a-z]*\s*=/i', $tmpval);
241
    // More not into the previous list
242
    $inj += preg_match('/on(repeat|begin|finish|beforeinput)[a-z]*\s*=/i', $tmpval);
243
244
    //$inj += preg_match('/on[A-Z][a-z]+\*=/', $val);   // To lock event handlers onAbort(), ...
245
    $inj += preg_match('/&#58;|&#0000058|&#x3A/i', $val); // refused string ':' encoded (no reason to have it encoded) to lock 'javascript:...'
246
    $inj += preg_match('/j\s*a\s*v\s*a\s*s\s*c\s*r\s*i\s*p\s*t\s*:/i', $val);
247
    $inj += preg_match('/vbscript\s*:/i', $val);
248
    // For XSS Injection done by adding javascript closing html tags like with onmousemove, etc... (closing a src or href tag with not cleaned param)
249
    if ($type == 1 || $type == 3) {
250
        $val = str_replace('enclosure="', 'enclosure=X', $val); // We accept enclosure=" for the export/import module
251
        $inj += preg_match('/"/i', $val); // We refused " in GET parameters value.
252
    }
253
    if ($type == 2) {
254
        $inj += preg_match('/[:;"\'<>\?\(\){}\$%]/', $val); // PHP_SELF is a file system (or url path without parameters). It can contains spaces.
255
    }
256
257
    return $inj;
258
}
259
260
/**
261
 * Return true if security check on parameters are OK, false otherwise.
262
 *
263
 * @param string|array<string,string> $var Variable name
264
 * @param int<0,2> $type 1=GET, 0=POST, 2=PHP_SELF
265
 * @param int<0,1> $stopcode 0=No stop code, 1=Stop code (default) if injection found
266
 * @return      boolean                     True if there is no injection.
267
 */
268
function analyseVarsForSqlAndScriptsInjection(&$var, $type, $stopcode = 1)
269
{
270
    if (is_array($var)) {
271
        foreach ($var as $key => $value) {  // Warning, $key may also be used for attacks
272
            // Exclude check for some variable keys
273
            if ($type === 0 && defined('NOSCANPOSTFORINJECTION') && is_array(constant('NOSCANPOSTFORINJECTION')) && in_array($key, constant('NOSCANPOSTFORINJECTION'))) {
274
                continue;
275
            }
276
277
            if (analyseVarsForSqlAndScriptsInjection($key, $type, $stopcode) && analyseVarsForSqlAndScriptsInjection($value, $type, $stopcode)) {
278
                //$var[$key] = $value;  // This is useless
279
            } else {
280
                http_response_code(403);
281
282
                // Get remote IP: PS: We do not use getRemoteIP(), function is not yet loaded and we need a value that can't be spoofed
283
                $ip = (empty($_SERVER['REMOTE_ADDR']) ? 'unknown' : $_SERVER['REMOTE_ADDR']);
284
285
                if ($stopcode) {
286
                    $errormessage = 'Access refused to ' . htmlentities($ip, ENT_COMPAT, 'UTF-8') . ' by SQL or Script injection protection in main.inc.php:analyseVarsForSqlAndScriptsInjection type=' . htmlentities((string)$type, ENT_COMPAT, 'UTF-8');
287
                    //$errormessage .= ' paramkey='.htmlentities($key, ENT_COMPAT, 'UTF-8');    // Disabled to avoid text injection
288
289
                    $errormessage2 = 'page=' . htmlentities((empty($_SERVER["REQUEST_URI"]) ? '' : $_SERVER["REQUEST_URI"]), ENT_COMPAT, 'UTF-8');
290
                    $errormessage2 .= ' paramtype=' . htmlentities((string)$type, ENT_COMPAT, 'UTF-8');
291
                    $errormessage2 .= ' paramkey=' . htmlentities($key, ENT_COMPAT, 'UTF-8');
292
                    $errormessage2 .= ' paramvalue=' . htmlentities($value, ENT_COMPAT, 'UTF-8');
293
294
                    print $errormessage;
295
                    print "<br>\n";
296
                    print 'Try to go back, fix data of your form and resubmit it. You can contact also your technical support.';
297
298
                    print "\n" . '<!--' . "\n";
299
                    print $errormessage2;
300
                    print "\n" . '-->';
301
302
                    // Add entry into the PHP server error log
303
                    if (function_exists('error_log')) {
304
                        error_log($errormessage . ' ' . substr($errormessage2, 2000));
305
                    }
306
307
                    // Note: No addition into security audit table is done because we don't want to execute code in such a case.
308
                    // Detection of too many such requests can be done with a fail2ban rule on 403 error code or into the PHP server error log.
309
310
311
                    if (class_exists('PHPUnit\Framework\TestSuite')) {
312
                        $message = $errormessage . ' ' . substr($errormessage2, 2000);
313
                        throw new Exception("Security injection exception: $message");
314
                    }
315
                    exit;
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...
316
                } else {
317
                    return false;
318
                }
319
            }
320
        }
321
        return true;
322
    } else {
323
        return (testSqlAndScriptInject($var, $type) <= 0);
324
    }
325
}
326
327
function getConstants()
328
{
329
    global $conf, $db, $user;
330
331
    $sql = "SELECT";
332
    $sql .= " rowid";
333
    $sql .= ", " . $db->decrypt('name') . " as name";
334
    $sql .= ", " . $db->decrypt('value') . " as value";
335
    $sql .= ", type";
336
    $sql .= ", note";
337
    $sql .= ", entity";
338
    $sql .= " FROM " . MAIN_DB_PREFIX . "const";
339
    if (!isModEnabled('multicompany')) {
340
        // If no multicompany mode, admins can see global and their constantes
341
        $sql .= " WHERE entity IN (0," . $conf->entity . ")";
342
    } else {
343
        // If multicompany mode, superadmin (user->entity=0) can see everything, admin are limited to their entities.
344
        if ($user->entity) {
345
            $sql .= " WHERE entity IN (" . $db->sanitize($user->entity . "," . $conf->entity) . ")";
346
        }
347
    }
348
    $sql .= " ORDER BY entity, name ASC";
349
    return $db->query($sql);
350
}
351
352
// To disable the WAF for GET and POST and PHP_SELF, uncomment this
353
//define('NOSCANPHPSELFFORINJECTION', 1);
354
//define('NOSCANGETFORINJECTION', 1);
355
//define('NOSCANPOSTFORINJECTION', 1 or 2);
356
357
// Check consistency of NOREQUIREXXX DEFINES
358
if ((defined('NOREQUIREDB') || defined('NOREQUIRETRAN')) && !defined('NOREQUIREMENU')) {
359
    print 'If define NOREQUIREDB or NOREQUIRETRAN are set, you must also set NOREQUIREMENU or not set them.';
360
    exit;
361
}
362
if (defined('NOREQUIREUSER') && !defined('NOREQUIREMENU')) {
363
    print 'If define NOREQUIREUSER is set, you must also set NOREQUIREMENU or not set it.';
364
    exit;
365
}
366
367
// Sanity check on URL
368
/*
369
if (!defined('NOSCANPHPSELFFORINJECTION') && !empty($_SERVER["PHP_SELF"])) {
370
    $morevaltochecklikepost = array($_SERVER["PHP_SELF"]);
371
    analyseVarsForSqlAndScriptsInjection($morevaltochecklikepost, 2);
372
}
373
*/
374
375
// Sanity check on GET parameters
376
if (!defined('NOSCANGETFORINJECTION') && !empty($_SERVER["QUERY_STRING"])) {
377
    // Note: QUERY_STRING is url encoded, but $_GET and $_POST are already decoded
378
    // Because the analyseVarsForSqlAndScriptsInjection is designed for already url decoded value, we must decode QUERY_STRING
379
    // Another solution is to provide $_GET as parameter with analyseVarsForSqlAndScriptsInjection($_GET, 1);
380
    $morevaltochecklikeget = array(urldecode($_SERVER["QUERY_STRING"]));
381
    analyseVarsForSqlAndScriptsInjection($morevaltochecklikeget, 1);
382
}
383
// Sanity check on POST
384
if (!defined('NOSCANPOSTFORINJECTION') || is_array(constant('NOSCANPOSTFORINJECTION'))) {
385
    analyseVarsForSqlAndScriptsInjection($_POST, 0);
386
}
387
388
// This is to make Dolibarr working with Plesk
389
if (!empty($_SERVER['DOCUMENT_ROOT']) && substr($_SERVER['DOCUMENT_ROOT'], -6) !== 'htdocs') {
390
    set_include_path($_SERVER['DOCUMENT_ROOT'] . '/htdocs');
391
}
392
393
// Include the conf.php and functions.lib.php and security.lib.php. This defined the constants like DOL_DOCUMENT_ROOT, DOL_DATA_ROOT, DOL_URL_ROOT...
394
require_once 'filefunc.inc.php';
395
396
// If there is a POST parameter to tell to save automatically some POST parameters into cookies, we do it.
397
// This is used for example by form of boxes to save personalization of some options.
398
// DOL_AUTOSET_COOKIE=cookiename:val1,val2 and  cookiename_val1=aaa cookiename_val2=bbb will set cookie_name with value json_encode(array('val1'=> , ))
399
if (GETPOST("DOL_AUTOSET_COOKIE")) {
400
    $tmpautoset = explode(':', GETPOST("DOL_AUTOSET_COOKIE"), 2);
401
    $tmplist = explode(',', $tmpautoset[1]);
402
    $cookiearrayvalue = array();
403
    foreach ($tmplist as $tmpkey) {
404
        $postkey = $tmpautoset[0] . '_' . $tmpkey;
405
        //var_dump('tmpkey='.$tmpkey.' postkey='.$postkey.' value='.GETPOST($postkey);
406
        if (GETPOST($postkey)) {
407
            $cookiearrayvalue[$tmpkey] = GETPOST($postkey);
408
        }
409
    }
410
    $cookiename = $tmpautoset[0];
411
    $cookievalue = json_encode($cookiearrayvalue);
412
    //var_dump('setcookie cookiename='.$cookiename.' cookievalue='.$cookievalue);
413
    if (PHP_VERSION_ID < 70300) {
414
        setcookie($cookiename, empty($cookievalue) ? '' : $cookievalue, empty($cookievalue) ? 0 : (time() + (86400 * 354)), '/', '', ((empty($dolibarr_main_force_https) && isHTTPS() === false) ? false : true), true); // keep cookie 1 year and add tag httponly
415
    } else {
416
        // Only available for php >= 7.3
417
        $cookieparams = array(
418
            'expires' => empty($cookievalue) ? 0 : (time() + (86400 * 354)),
419
            'path' => '/',
420
            //'domain' => '.mywebsite.com', // the dot at the beginning allows compatibility with subdomains
421
            'secure' => ((empty($dolibarr_main_force_https) && isHTTPS() === false) ? false : true),
422
            'httponly' => true,
423
            'samesite' => 'Lax' // None || Lax  || Strict
424
        );
425
        setcookie($cookiename, empty($cookievalue) ? '' : $cookievalue, $cookieparams);
426
    }
427
    if (empty($cookievalue)) {
428
        unset($_COOKIE[$cookiename]);
429
    }
430
}
431
432
// Set the handler of session
433
// if (ini_get('session.save_handler') == 'user')
434
if (!empty($php_session_save_handler) && $php_session_save_handler == 'db') {
435
    require_once 'core/lib/phpsessionin' . $php_session_save_handler . '.lib.php';
436
}
437
438
// Init session. Name of session is specific to Dolibarr instance.
439
// Must be done after the include of filefunc.inc.php so global variables of conf file are defined (like $dolibarr_main_instance_unique_id or $dolibarr_main_force_https).
440
// Note: the function dol_getprefix() is defined into functions.lib.php but may have been defined to return a different key to manage another area to protect.
441
$prefix = dol_getprefix('');
442
$sessionname = 'DOLSESSID_' . $prefix;
443
$sessiontimeout = 'DOLSESSTIMEOUT_' . $prefix;
444
if (!empty($_COOKIE[$sessiontimeout])) {
445
    ini_set('session.gc_maxlifetime', $_COOKIE[$sessiontimeout]);
446
}
447
448
// This create lock, released by session_write_close() or end of page.
449
// We need this lock as long as we read/write $_SESSION ['vars']. We can remove lock when finished.
450
if (!defined('NOSESSION')) {
451
    if (PHP_VERSION_ID < 70300) {
452
        session_set_cookie_params(0, '/', null, ((empty($dolibarr_main_force_https) && isHTTPS() === false) ? false : true), true); // Add tag secure and httponly on session cookie (same as setting session.cookie_httponly into php.ini). Must be called before the session_start.
453
    } else {
454
        // Only available for php >= 7.3
455
        $sessioncookieparams = array(
456
            'lifetime' => 0,
457
            'path' => '/',
458
            //'domain' => '.mywebsite.com', // the dot at the beginning allows compatibility with subdomains
459
            'secure' => ((empty($dolibarr_main_force_https) && isHTTPS() === false) ? false : true),
460
            'httponly' => true,
461
            'samesite' => 'Lax' // None || Lax  || Strict
462
        );
463
        session_set_cookie_params($sessioncookieparams);
464
    }
465
    session_name($sessionname);
466
    dol_session_start();    // This call the open and read of session handler
467
    //exit; // this exist generates a call to write and close
468
}
469
470
471
// Init the 6 global objects, this include will make the 'new Xxx()' and set properties for: $conf, $db, $langs, $user, $mysoc, $hookmanager
472
require_once 'master.inc.php';
473
474
// Uncomment this and set session.save_handler = user to use local session storing
475
// include DOL_DOCUMENT_ROOT.'/core/lib/phpsessionindb.inc.php
476
477
// If software has been locked. Only login $conf->global->MAIN_ONLY_LOGIN_ALLOWED is allowed.
478
if (getDolGlobalString('MAIN_ONLY_LOGIN_ALLOWED')) {
479
    $ok = 0;
480
    if ((!session_id() || !isset($_SESSION["dol_login"])) && !isset($_POST["username"]) && !empty($_SERVER["GATEWAY_INTERFACE"])) {
481
        $ok = 1; // We let working pages if not logged and inside a web browser (login form, to allow login by admin)
482
    } elseif (isset($_POST["username"]) && $_POST["username"] == $conf->global->MAIN_ONLY_LOGIN_ALLOWED) {
483
        $ok = 1; // We let working pages that is a login submission (login submit, to allow login by admin)
484
    } elseif (defined('NOREQUIREDB')) {
485
        $ok = 1; // We let working pages that don't need database access (xxx.css.php)
486
    } elseif (defined('EVEN_IF_ONLY_LOGIN_ALLOWED')) {
487
        $ok = 1; // We let working pages that ask to work even if only login enabled (logout.php)
488
    } elseif (session_id() && isset($_SESSION["dol_login"]) && $_SESSION["dol_login"] == $conf->global->MAIN_ONLY_LOGIN_ALLOWED) {
489
        $ok = 1; // We let working if user is allowed admin
490
    }
491
    if (!$ok) {
492
        if (session_id() && isset($_SESSION["dol_login"]) && $_SESSION["dol_login"] != $conf->global->MAIN_ONLY_LOGIN_ALLOWED) {
493
            print 'Sorry, your application is offline.' . "\n";
494
            print 'You are logged with user "' . $_SESSION["dol_login"] . '" and only administrator user "' . getDolGlobalString('MAIN_ONLY_LOGIN_ALLOWED') . '" is allowed to connect for the moment.' . "\n";
495
            $nexturl = constant('BASE_URL') . '/user/logout.php?token=' . newToken();
496
            print 'Please try later or <a href="' . $nexturl . '">click here to disconnect and change login user</a>...' . "\n";
497
        } else {
498
            print 'Sorry, your application is offline. Only administrator user "' . getDolGlobalString('MAIN_ONLY_LOGIN_ALLOWED') . '" is allowed to connect for the moment.' . "\n";
499
            $nexturl = constant('BASE_URL') . '/';
500
            print 'Please try later or <a href="' . $nexturl . '">click here to change login user</a>...' . "\n";
501
        }
502
        exit;
503
    }
504
}
505
506
507
// Activate end of page function
508
register_shutdown_function('dol_shutdown');
509
510
// Load debugbar
511
if (isModEnabled('debugbar') && !GETPOST('dol_use_jmobile') && empty($_SESSION['dol_use_jmobile'])) {
512
    global $debugbar;
513
    include_once DOL_DOCUMENT_ROOT . '/debugbar/class/DebugBar.php';
514
515
    $debugbar = new DolibarrDebugBar();
516
    $renderer = $debugbar->getJavascriptRenderer();
517
    if (!getDolGlobalString('MAIN_HTML_HEADER')) {
518
        $conf->global->MAIN_HTML_HEADER = '';
519
    }
520
    $conf->global->MAIN_HTML_HEADER .= $renderer->renderHead();
521
522
    $debugbar['time']->startMeasure('pageaftermaster', 'Page generation (after environment init)');
0 ignored issues
show
Bug introduced by
The method startMeasure() 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

522
    $debugbar['time']->/** @scrutinizer ignore-call */ 
523
                       startMeasure('pageaftermaster', 'Page generation (after environment init)');
Loading history...
523
}
524
525
// Detection browser
526
if (isset($_SERVER["HTTP_USER_AGENT"])) {
527
    $tmp = getBrowserInfo($_SERVER["HTTP_USER_AGENT"]);
528
    $conf->browser->name = $tmp['browsername'];
529
    $conf->browser->os = $tmp['browseros'];
530
    $conf->browser->version = $tmp['browserversion'];
531
    $conf->browser->ua = $tmp['browserua'];
532
    $conf->browser->layout = $tmp['layout']; // 'classic', 'phone', 'tablet'
533
    //var_dump($conf->browser);
534
535
    if ($conf->browser->layout == 'phone') {
536
        $conf->dol_no_mouse_hover = 1;
537
    }
538
}
539
540
// If theme is forced
541
if (GETPOST('theme', 'aZ09')) {
542
    $conf->theme = GETPOST('theme', 'aZ09');
543
    $conf->css = "/theme/" . $conf->theme . "/style.css.php";
544
}
545
546
// Set global MAIN_OPTIMIZEFORTEXTBROWSER (must be before login part)
547
if (GETPOSTINT('textbrowser') || (!empty($conf->browser->name) && $conf->browser->name == 'lynxlinks')) {   // If we must enable text browser
548
    $conf->global->MAIN_OPTIMIZEFORTEXTBROWSER = 2;
549
}
550
551
// Force HTTPS if required ($conf->file->main_force_https is 0/1 or 'https dolibarr root url')
552
// $_SERVER["HTTPS"] is 'on' when link is https, otherwise $_SERVER["HTTPS"] is empty or 'off'
553
if (!empty($conf->file->main_force_https) && !isHTTPS() && !defined('NOHTTPSREDIRECT')) {
554
    $newurl = '';
555
    if (is_numeric($conf->file->main_force_https)) {
556
        if ($conf->file->main_force_https == '1' && !empty($_SERVER["SCRIPT_URI"])) {   // If SCRIPT_URI supported by server
557
            if (preg_match('/^http:/i', $_SERVER["SCRIPT_URI"]) && !preg_match('/^https:/i', $_SERVER["SCRIPT_URI"])) { // If link is http
558
                $newurl = preg_replace('/^http:/i', 'https:', $_SERVER["SCRIPT_URI"]);
559
            }
560
        } else {
561
            // If HTTPS is not defined in DOL_MAIN_URL_ROOT,
562
            // Check HTTPS environment variable (Apache/mod_ssl only)
563
            $newurl = preg_replace('/^http:/i', 'https:', DOL_MAIN_URL_ROOT) . $_SERVER["REQUEST_URI"];
564
        }
565
    } else {
566
        // Check HTTPS environment variable (Apache/mod_ssl only)
567
        $newurl = $conf->file->main_force_https . $_SERVER["REQUEST_URI"];
568
    }
569
    // Start redirect
570
    if ($newurl) {
571
        header_remove(); // Clean header already set to be sure to remove any header like "Set-Cookie: DOLSESSID_..." from non HTTPS answers
572
        dol_syslog("main.inc: dolibarr_main_force_https is on, we make a redirect to " . $newurl);
573
        header("Location: " . $newurl);
574
        exit;
575
    } else {
576
        dol_syslog("main.inc: dolibarr_main_force_https is on but we failed to forge new https url so no redirect is done", LOG_WARNING);
577
    }
578
}
579
580
if (!defined('NOLOGIN') && !defined('NOIPCHECK') && !empty($dolibarr_main_restrict_ip)) {
581
    $listofip = explode(',', $dolibarr_main_restrict_ip);
582
    $found = false;
583
    foreach ($listofip as $ip) {
584
        $ip = trim($ip);
585
        if ($ip == $_SERVER['REMOTE_ADDR']) {
586
            $found = true;
587
            break;
588
        }
589
    }
590
    if (!$found) {
591
        print 'Access refused by IP protection. Your detected IP is ' . $_SERVER['REMOTE_ADDR'];
592
        exit;
593
    }
594
}
595
596
// Loading of additional presentation includes
597
if (!defined('NOREQUIREAJAX')) {
598
    require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/ajax.lib.php'; // Need 22ko memory
599
}
600
601
// If install or upgrade process not done or not completely finished, we call the install page.
602
if (getDolGlobalString('MAIN_NOT_INSTALLED') || getDolGlobalString('MAIN_NOT_UPGRADED')) {
603
    dol_syslog("main.inc: A previous install or upgrade was not complete. Redirect to install page.", LOG_WARNING);
604
    header("Location: " . constant('BASE_URL') . "/install/index.php");
605
    exit;
606
}
607
// If an upgrade process is required, we call the install page.
608
$checkifupgraderequired = false;
609
if (getDolGlobalString('MAIN_VERSION_LAST_UPGRADE') && getDolGlobalString('MAIN_VERSION_LAST_UPGRADE') != DOL_VERSION) {
610
    $checkifupgraderequired = true;
611
}
612
if (!getDolGlobalString('MAIN_VERSION_LAST_UPGRADE') && getDolGlobalString('MAIN_VERSION_LAST_INSTALL') && getDolGlobalString('MAIN_VERSION_LAST_INSTALL') != DOL_VERSION) {
613
    $checkifupgraderequired = true;
614
}
615
if ($checkifupgraderequired) {
616
    $versiontocompare = getDolGlobalString('MAIN_VERSION_LAST_UPGRADE', getDolGlobalString('MAIN_VERSION_LAST_INSTALL'));
617
    require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/admin.lib.php';
618
    $dolibarrversionlastupgrade = preg_split('/[.-]/', $versiontocompare);
619
    $dolibarrversionprogram = preg_split('/[.-]/', DOL_VERSION);
620
    $rescomp = versioncompare($dolibarrversionprogram, $dolibarrversionlastupgrade);
621
    if ($rescomp > 0) {   // Programs have a version higher than database.
622
        if (!getDolGlobalString('MAIN_NO_UPGRADE_REDIRECT_ON_LEVEL_3_CHANGE') || $rescomp < 3) {
623
            // We did not add "&& $rescomp < 3" because we want upgrade process for build upgrades
624
            dol_syslog("main.inc: database version " . $versiontocompare . " is lower than programs version " . DOL_VERSION . ". Redirect to install/upgrade page.", LOG_WARNING);
625
            if (php_sapi_name() === "cli") {
626
                print "main.inc: database version " . $versiontocompare . " is lower than programs version " . DOL_VERSION . ". Try to run upgrade process.\n";
627
            } else {
628
                header("Location: " . constant('BASE_URL') . "/install/index.php");
629
            }
630
            exit;
631
        }
632
    }
633
}
634
635
// Creation of a token against CSRF vulnerabilities
636
if (!defined('NOTOKENRENEWAL') && !defined('NOSESSION')) {
637
    // No token renewal on .css.php, .js.php and .json.php (even if the NOTOKENRENEWAL was not provided)
638
    if (!preg_match('/\.(css|js|json)\.php$/', $_SERVER["PHP_SELF"])) {
639
        // Rolling token at each call ($_SESSION['token'] contains token of previous page)
640
        if (isset($_SESSION['newtoken'])) {
641
            $_SESSION['token'] = $_SESSION['newtoken'];
642
        }
643
644
        if (!isset($_SESSION['newtoken']) || getDolGlobalInt('MAIN_SECURITY_CSRF_TOKEN_RENEWAL_ON_EACH_CALL')) {
645
            // Note: Using MAIN_SECURITY_CSRF_TOKEN_RENEWAL_ON_EACH_CALL is not recommended: if a user succeed in entering a data from
646
            // a public page with a link that make a token regeneration, it can make use of the backoffice no more possible !
647
            // Save in $_SESSION['newtoken'] what will be next token. Into forms, we will add param token = $_SESSION['newtoken']
648
            $token = dol_hash(uniqid((string)mt_rand(), false), 'md5'); // Generates a hash of a random number. We don't need a secured hash, just a changing random value.
649
            $_SESSION['newtoken'] = $token;
650
            dol_syslog("NEW TOKEN generated by : " . $_SERVER['PHP_SELF'], LOG_DEBUG);
651
        }
652
    }
653
}
654
655
//dol_syslog("CSRF info: ".defined('NOCSRFCHECK')." - ".$dolibarr_nocsrfcheck." - ".$conf->global->MAIN_SECURITY_CSRF_WITH_TOKEN." - ".$_SERVER['REQUEST_METHOD']." - ".GETPOST('token', 'alpha'));
656
657
// Check validity of token, only if option MAIN_SECURITY_CSRF_WITH_TOKEN enabled or if constant CSRFCHECK_WITH_TOKEN is set into page
658
if ((!defined('NOCSRFCHECK') && empty($dolibarr_nocsrfcheck) && getDolGlobalInt('MAIN_SECURITY_CSRF_WITH_TOKEN')) || defined('CSRFCHECK_WITH_TOKEN')) {
659
    // Array of action code where CSRFCHECK with token will be forced (so token must be provided on url request)
660
    $sensitiveget = false;
661
    if ((GETPOSTISSET('massaction') || GETPOST('action', 'aZ09')) && getDolGlobalInt('MAIN_SECURITY_CSRF_WITH_TOKEN') >= 3) {
662
        // All GET actions (except the listed exceptions that are usually post for pre-actions and not real action) and mass actions are processed as sensitive.
663
        if (GETPOSTISSET('massaction') || !in_array(GETPOST('action', 'aZ09'), array('create', 'createsite', 'createcard', 'edit', 'editvalidator', 'file_manager', 'presend', 'presend_addmessage', 'preview', 'specimen'))) { // We exclude some action that are not sensitive so legitimate
664
            $sensitiveget = true;
665
        }
666
    } elseif (getDolGlobalInt('MAIN_SECURITY_CSRF_WITH_TOKEN') >= 2) {
667
        // Few GET actions coded with a &token into url are also processed as sensitive.
668
        $arrayofactiontoforcetokencheck = array(
669
            'activate',
670
            'doprev', 'donext', 'dvprev', 'dvnext',
671
            'freezone', 'install',
672
            'reopen'
673
        );
674
        if (in_array(GETPOST('action', 'aZ09'), $arrayofactiontoforcetokencheck)) {
675
            $sensitiveget = true;
676
        }
677
        // We also need a valid token for actions matching one of these values
678
        if (preg_match('/^(confirm_)?(add|classify|close|confirm|copy|del|disable|enable|remove|set|unset|update|save)/', GETPOST('action', 'aZ09'))) {
679
            $sensitiveget = true;
680
        }
681
    }
682
683
    // Check a token is provided for all cases that need a mandatory token
684
    // (all POST actions + all sensitive GET actions + all mass actions + all login/actions/logout on pages with CSRFCHECK_WITH_TOKEN set)
685
    if (
686
        (!empty($_SERVER['REQUEST_METHOD']) && $_SERVER['REQUEST_METHOD'] == 'POST') ||
687
        $sensitiveget ||
688
        GETPOSTISSET('massaction') ||
689
        ((GETPOSTISSET('actionlogin') || GETPOSTISSET('action')) && defined('CSRFCHECK_WITH_TOKEN'))
690
    ) {
691
        // If token is not provided or empty, error (we are in case it is mandatory)
692
        if (!GETPOST('token', 'alpha') || GETPOST('token', 'alpha') == 'notrequired') {
693
            top_httphead();
694
            if (GETPOSTINT('uploadform')) {
695
                dol_syslog("--- Access to " . (empty($_SERVER["REQUEST_METHOD"]) ? '' : $_SERVER["REQUEST_METHOD"] . ' ') . $_SERVER["PHP_SELF"] . " refused. File size too large or not provided.");
696
                $langs->loadLangs(array("errors", "install"));
697
                print $langs->trans("ErrorFileSizeTooLarge") . ' ';
698
                print $langs->trans("ErrorGoBackAndCorrectParameters");
699
            } else {
700
                http_response_code(403);
701
                if (defined('CSRFCHECK_WITH_TOKEN')) {
702
                    dd(['debug_backtrace in CSRF error in main.inc.php ' => debug_backtrace()]);
703
                    dol_syslog("--- Access to " . (empty($_SERVER["REQUEST_METHOD"]) ? '' : $_SERVER["REQUEST_METHOD"] . ' ') . $_SERVER["PHP_SELF"] . " refused by CSRF protection (CSRFCHECK_WITH_TOKEN protection) in main.inc.php. Token not provided.", LOG_WARNING);
704
                    print "Access to a page that needs a token (constant CSRFCHECK_WITH_TOKEN is defined) is refused by CSRF protection in main.inc.php. Token not provided.\n";
705
                } else {
706
                    dol_syslog("--- Access to " . (empty($_SERVER["REQUEST_METHOD"]) ? '' : $_SERVER["REQUEST_METHOD"] . ' ') . $_SERVER["PHP_SELF"] . " refused by CSRF protection (POST method or GET with a sensible value for 'action' parameter) in main.inc.php. Token not provided.", LOG_WARNING);
707
                    print "Access to this page this way (POST method or GET with a sensible value for 'action' parameter) is refused by CSRF protection in main.inc.php. Token not provided.\n";
708
                    print "If you access your server behind a proxy using url rewriting and the parameter is provided by caller, you might check that all HTTP header are propagated (or add the line \$dolibarr_nocsrfcheck=1 into your conf.php file or MAIN_SECURITY_CSRF_WITH_TOKEN to 0";
709
                    if (getDolGlobalString('MAIN_SECURITY_CSRF_WITH_TOKEN')) {
710
                        print " instead of " . getDolGlobalString('MAIN_SECURITY_CSRF_WITH_TOKEN');
711
                    }
712
                    print " into setup).\n";
713
                }
714
            }
715
            die;
716
        }
717
    }
718
719
    $sessiontokenforthisurl = (empty($_SESSION['token']) ? '' : $_SESSION['token']);
720
    // TODO Get the sessiontokenforthisurl into an array of session token (one array per base URL so we can use the CSRF per page and we keep ability for several tabs per url in a browser)
721
    if (GETPOSTISSET('token') && GETPOST('token') != 'notrequired' && GETPOST('token', 'alpha') != $sessiontokenforthisurl) {
722
        dol_syslog("--- Access to " . (empty($_SERVER["REQUEST_METHOD"]) ? '' : $_SERVER["REQUEST_METHOD"] . ' ') . $_SERVER["PHP_SELF"] . " refused by CSRF protection (invalid token), so we disable POST and some GET parameters - referrer=" . (empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER']) . ", action=" . GETPOST('action', 'aZ09') . ", _GET|POST['token']=" . GETPOST('token', 'alpha'), LOG_WARNING);
723
        //dol_syslog("_SESSION['token']=".$sessiontokenforthisurl, LOG_DEBUG);
724
        // Do not output anything on standard output because this create problems when using the BACK button on browsers. So we just set a message into session.
725
        if (!defined('NOTOKENRENEWAL')) {
726
            // If the page is not a page that disable the token renewal, we report a warning message to explain token has epired.
727
            setEventMessages('SecurityTokenHasExpiredSoActionHasBeenCanceledPleaseRetry', null, 'warnings', '', 1);
728
        }
729
        $savid = null;
730
        if (isset($_POST['id'])) {
731
            $savid = ((int)$_POST['id']);
732
        }
733
        unset($_POST);
734
        unset($_GET['confirm']);
735
        unset($_GET['action']);
736
        unset($_GET['confirmmassaction']);
737
        unset($_GET['massaction']);
738
        unset($_GET['token']);          // TODO Make a redirect if we have a token in url to remove it ?
739
        if (isset($savid)) {
740
            $_POST['id'] = ((int)$savid);
741
        }
742
        // So rest of code can know something was wrong here
743
        $_GET['errorcode'] = 'InvalidToken';
744
    }
745
746
    // Note: There is another CSRF protection into the filefunc.inc.php
747
}
748
749
// Disable modules (this must be after session_start and after conf has been loaded)
750
if (GETPOSTISSET('disablemodules')) {
751
    $_SESSION["disablemodules"] = GETPOST('disablemodules', 'alpha');
752
}
753
if (!empty($_SESSION["disablemodules"])) {
754
    $modulepartkeys = array('css', 'js', 'tabs', 'triggers', 'login', 'substitutions', 'menus', 'theme', 'sms', 'tpl', 'barcode', 'models', 'societe', 'hooks', 'dir', 'syslog', 'tpllinkable', 'contactelement', 'moduleforexternal', 'websitetemplates');
755
756
    $disabled_modules = explode(',', $_SESSION["disablemodules"]);
757
    foreach ($disabled_modules as $module) {
758
        if ($module) {
759
            if (empty($conf->$module)) {
760
                $conf->$module = new stdClass(); // To avoid warnings
761
            }
762
            $conf->$module->enabled = false;
763
            foreach ($modulepartkeys as $modulepartkey) {
764
                unset($conf->modules_parts[$modulepartkey][$module]);
765
            }
766
            if ($module == 'fournisseur') {     // Special case
767
                $conf->supplier_order->enabled = 0;
768
                $conf->supplier_invoice->enabled = 0;
769
            }
770
        }
771
    }
772
}
773
774
// Set current modulepart
775
$modulepart = explode("/", $_SERVER["PHP_SELF"]);
776
if (is_array($modulepart) && count($modulepart) > 0) {
777
    foreach ($conf->modules as $module) {
778
        if (in_array($module, $modulepart)) {
779
            $modulepart = $module;
780
            break;
781
        }
782
    }
783
}
784
if (is_array($modulepart)) {
785
    $modulepart = '';
786
}
787
788
789
/*
790
 * Phase authentication / login
791
 */
792
793
$login = '';
794
$error = 0;
795
if (!defined('NOLOGIN')) {
796
    // $authmode lists the different method of identification to be tested in order of preference.
797
    // Example: 'http', 'dolibarr', 'ldap', 'http,forceuser', '...'
798
799
    if (defined('MAIN_AUTHENTICATION_MODE')) {
800
        $dolibarr_main_authentication = constant('MAIN_AUTHENTICATION_MODE');
801
    } else {
802
        // Authentication mode
803
        if (empty($dolibarr_main_authentication)) {
804
            $dolibarr_main_authentication = 'dolibarr';
805
        }
806
        // Authentication mode: forceuser
807
        if ($dolibarr_main_authentication == 'forceuser' && empty($dolibarr_auto_user)) {
808
            $dolibarr_auto_user = 'auto';
809
        }
810
    }
811
    // Set authmode
812
    $authmode = explode(',', $dolibarr_main_authentication);
813
814
    // No authentication mode
815
    if (!count($authmode)) {
816
        $langs->load('main');
817
        dol_print_error(null, $langs->trans("ErrorConfigParameterNotDefined", 'dolibarr_main_authentication'));
818
        exit;
819
    }
820
821
    // If login request was already post, we retrieve login from the session
822
    // Call module if not realized that his request.
823
    // At the end of this phase, the variable $login is defined.
824
    $resultFetchUser = '';
825
    $test = true;
826
    if (!isset($_SESSION["dol_login"])) {
827
        // It is not already authenticated and it requests the login / password
828
        include_once DOL_DOCUMENT_ROOT . '/core/lib/security2.lib.php';
829
830
        $dol_dst_observed = GETPOSTINT("dst_observed", 3);
831
        $dol_dst_first = GETPOSTINT("dst_first", 3);
832
        $dol_dst_second = GETPOSTINT("dst_second", 3);
833
        $dol_screenwidth = GETPOSTINT("screenwidth", 3);
834
        $dol_screenheight = GETPOSTINT("screenheight", 3);
835
        $dol_hide_topmenu = GETPOSTINT('dol_hide_topmenu', 3);
836
        $dol_hide_leftmenu = GETPOSTINT('dol_hide_leftmenu', 3);
837
        $dol_optimize_smallscreen = GETPOSTINT('dol_optimize_smallscreen', 3);
838
        $dol_no_mouse_hover = GETPOSTINT('dol_no_mouse_hover', 3);
839
        $dol_use_jmobile = GETPOSTINT('dol_use_jmobile', 3); // 0=default, 1=to say we use app from a webview app, 2=to say we use app from a webview app and keep ajax
840
841
        // If in demo mode, we check we go to home page through the public/demo/index.php page
842
        if (!empty($dolibarr_main_demo) && $_SERVER['PHP_SELF'] == constant('BASE_URL') . '/index.php') {  // We ask index page
843
            if (empty($_SERVER['HTTP_REFERER']) || !preg_match('/public/', $_SERVER['HTTP_REFERER'])) {
844
                dol_syslog("Call index page from another url than demo page (call is done from page " . (empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFER']) . ")");
845
                $url = '';
846
                $url .= ($url ? '&' : '') . ($dol_hide_topmenu ? 'dol_hide_topmenu=' . $dol_hide_topmenu : '');
847
                $url .= ($url ? '&' : '') . ($dol_hide_leftmenu ? 'dol_hide_leftmenu=' . $dol_hide_leftmenu : '');
848
                $url .= ($url ? '&' : '') . ($dol_optimize_smallscreen ? 'dol_optimize_smallscreen=' . $dol_optimize_smallscreen : '');
849
                $url .= ($url ? '&' : '') . ($dol_no_mouse_hover ? 'dol_no_mouse_hover=' . $dol_no_mouse_hover : '');
850
                $url .= ($url ? '&' : '') . ($dol_use_jmobile ? 'dol_use_jmobile=' . $dol_use_jmobile : '');
851
                $url = constant('BASE_URL') . '/public/demo/index.php' . ($url ? '?' . $url : '');
852
                header("Location: " . $url);
853
                exit;
854
            }
855
        }
856
857
        // Hooks for security access
858
        $action = '';
859
        $hookmanager->initHooks(array('login'));
860
        $parameters = array();
861
        $reshook = $hookmanager->executeHooks('beforeLoginAuthentication', $parameters, $user, $action); // Note that $action and $object may have been modified by some hooks
862
        if ($reshook < 0) {
863
            $test = false;
864
            $error++;
865
        }
866
867
        // Verification security graphic code
868
        if ($test && GETPOST("username", "alpha", 2) && getDolGlobalString('MAIN_SECURITY_ENABLECAPTCHA') && !isset($_SESSION['dol_bypass_antispam'])) {
869
            $sessionkey = 'dol_antispam_value';
870
            $ok = (array_key_exists($sessionkey, $_SESSION) === true && (strtolower($_SESSION[$sessionkey]) === strtolower(GETPOST('code', 'restricthtml'))));
871
872
            // Check code
873
            if (!$ok) {
874
                dol_syslog('Bad value for code, connection refused', LOG_NOTICE);
875
                // Load translation files required by page
876
                $langs->loadLangs(array('main', 'errors'));
877
878
                $_SESSION["dol_loginmesg"] = $langs->transnoentitiesnoconv("ErrorBadValueForCode");
879
                $test = false;
880
881
                // Call trigger for the "security events" log
882
                $user->context['audit'] = 'ErrorBadValueForCode - login=' . GETPOST("username", "alpha", 2);
883
884
                // Call trigger
885
                $result = $user->call_trigger('USER_LOGIN_FAILED', $user);
886
                if ($result < 0) {
887
                    $error++;
888
                }
889
                // End call triggers
890
891
                // Hooks on failed login
892
                $action = '';
893
                $hookmanager->initHooks(array('login'));
894
                $parameters = array('dol_authmode' => $authmode, 'dol_loginmesg' => $_SESSION["dol_loginmesg"]);
895
                $reshook = $hookmanager->executeHooks('afterLoginFailed', $parameters, $user, $action); // Note that $action and $object may have been modified by some hooks
896
                if ($reshook < 0) {
897
                    $error++;
898
                }
899
900
                // Note: exit is done later
901
            }
902
        }
903
904
        $allowedmethodtopostusername = 3;
905
        if (defined('MAIN_AUTHENTICATION_POST_METHOD')) {
906
            $allowedmethodtopostusername = constant('MAIN_AUTHENTICATION_POST_METHOD'); // Note a value of 2 is not compatible with some authentication methods that put username as GET parameter
907
        }
908
        // TODO Remove use of $_COOKIE['login_dolibarr'] ? Replace $usertotest = with $usertotest = GETPOST("username", "alpha", $allowedmethodtopostusername);
909
        $usertotest = (!empty($_COOKIE['login_dolibarr']) ? preg_replace('/[^a-zA-Z0-9_@\-\.]/', '', $_COOKIE['login_dolibarr']) : GETPOST("username", "alpha", $allowedmethodtopostusername));
910
        $passwordtotest = GETPOST('password', 'none', $allowedmethodtopostusername);
911
        $entitytotest = (GETPOSTINT('entity') ? GETPOSTINT('entity') : (!empty($conf->entity) ? $conf->entity : 1));
912
913
        // Define if we received the correct data to go into the test of the login with the checkLoginPassEntity().
914
        $goontestloop = false;
915
        if (isset($_SERVER["REMOTE_USER"]) && in_array('http', $authmode)) {    // For http basic login test
916
            $goontestloop = true;
917
        }
918
        if ($dolibarr_main_authentication == 'forceuser' && !empty($dolibarr_auto_user)) {  // For automatic login with a forced user
919
            $goontestloop = true;
920
        }
921
        if (GETPOST("username", "alpha", $allowedmethodtopostusername)) {   // For posting the login form
922
            $goontestloop = true;
923
        }
924
        if (GETPOST('openid_mode', 'alpha', 1)) {   // For openid_connect ?
925
            $goontestloop = true;
926
        }
927
        if (GETPOST('beforeoauthloginredirect') || GETPOST('afteroauthloginreturn')) {  // For oauth login
928
            $goontestloop = true;
929
        }
930
        if (!empty($_COOKIE['login_dolibarr'])) {   // TODO For ? Remove this ?
931
            $goontestloop = true;
932
        }
933
934
        if (!is_object($langs)) { // This can occurs when calling page with NOREQUIRETRAN defined, however we need langs for error messages.
935
            $langs = new Translate("", $conf);
936
            $langcode = (GETPOST('lang', 'aZ09', 1) ? GETPOST('lang', 'aZ09', 1) : getDolGlobalString('MAIN_LANG_DEFAULT', 'auto'));
937
            if (defined('MAIN_LANG_DEFAULT')) {
938
                $langcode = constant('MAIN_LANG_DEFAULT');
939
            }
940
            $langs->setDefaultLang($langcode);
941
        }
942
943
        // Validation of login/pass/entity
944
        // If ok, the variable login will be returned
945
        // If error, we will put error message in session under the name dol_loginmesg
946
        if ($test && $goontestloop && (GETPOST('actionlogin', 'aZ09') == 'login' || $dolibarr_main_authentication != 'dolibarr')) {
947
            // Loop on each test mode defined into $authmode
948
            // $authmode is an array for example: array('0'=>'dolibarr', '1'=>'googleoauth');
949
            $oauthmodetotestarray = array('google');
950
            foreach ($oauthmodetotestarray as $oauthmodetotest) {
951
                if (in_array($oauthmodetotest . 'oauth', $authmode)) {    // This is an authmode that is currently qualified. Do we have to remove it ?
952
                    // If we click on the link to use OAuth authentication or if we goes after callback return, we do nothing
953
                    if (GETPOST('beforeoauthloginredirect') == $oauthmodetotest || GETPOST('afteroauthloginreturn')) {
954
                        // TODO Use: if (GETPOST('beforeoauthloginredirect') == $oauthmodetotest || GETPOST('afteroauthloginreturn') == $oauthmodetotest) {
955
                        continue;
956
                    }
957
                    dol_syslog("User did not click on link for OAuth or is not on the OAuth return, so we disable check using " . $oauthmodetotest);
958
                    foreach ($authmode as $tmpkey => $tmpval) {
959
                        if ($tmpval == $oauthmodetotest . 'oauth') {
960
                            unset($authmode[$tmpkey]);
961
                            break;
962
                        }
963
                    }
964
                }
965
            }
966
967
            // Check login for all qualified modes in array $authmode.
968
            $login = checkLoginPassEntity($usertotest, $passwordtotest, $entitytotest, $authmode);
969
            if ($login === '--bad-login-validity--') {
970
                $login = '';
971
            }
972
973
            $dol_authmode = '';
974
975
            if ($login) {
976
                $dol_authmode = $conf->authmode; // This properties is defined only when logged, to say what mode was successfully used
977
                $dol_tz = empty($_POST["tz"]) ? (empty($_SESSION["tz"]) ? '' : $_SESSION["tz"]) : $_POST["tz"];
978
                $dol_tz_string = empty($_POST["tz_string"]) ? (empty($_SESSION["tz_string"]) ? '' : $_SESSION["tz_string"]) : $_POST["tz_string"];
979
                $dol_tz_string = preg_replace('/\s*\(.+\)$/', '', $dol_tz_string);
980
                $dol_tz_string = preg_replace('/,/', '/', $dol_tz_string);
981
                $dol_tz_string = preg_replace('/\s/', '_', $dol_tz_string);
982
                $dol_dst = 0;
983
                // Keep $_POST here. Do not use GETPOSTISSET
984
                $dol_dst_first = empty($_POST["dst_first"]) ? (empty($_SESSION["dst_first"]) ? '' : $_SESSION["dst_first"]) : $_POST["dst_first"];
985
                $dol_dst_second = empty($_POST["dst_second"]) ? (empty($_SESSION["dst_second"]) ? '' : $_SESSION["dst_second"]) : $_POST["dst_second"];
986
                if ($dol_dst_first && $dol_dst_second) {
987
                    include_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
988
                    $datenow = dol_now();
989
                    $datefirst = dol_stringtotime($dol_dst_first);
990
                    $datesecond = dol_stringtotime($dol_dst_second);
991
                    if ($datenow >= $datefirst && $datenow < $datesecond) {
992
                        $dol_dst = 1;
993
                    }
994
                }
995
                $dol_screenheight = empty($_POST["screenheight"]) ? (empty($_SESSION["dol_screenheight"]) ? '' : $_SESSION["dol_screenheight"]) : $_POST["screenheight"];
996
                $dol_screenwidth = empty($_POST["screenwidth"]) ? (empty($_SESSION["dol_screenwidth"]) ? '' : $_SESSION["dol_screenwidth"]) : $_POST["screenwidth"];
997
                //print $datefirst.'-'.$datesecond.'-'.$datenow.'-'.$dol_tz.'-'.$dol_tzstring.'-'.$dol_dst.'-'.sdol_screenheight.'-'.sdol_screenwidth; exit;
998
            }
999
1000
            if (!$login) {
1001
                dol_syslog('Bad password, connection refused (see a previous notice message for more info)', LOG_NOTICE);
1002
                // Load translation files required by page
1003
                $langs->loadLangs(array('main', 'errors'));
1004
1005
                // Bad password. No authmode has found a good password.
1006
                // We set a generic message if not defined inside function checkLoginPassEntity or subfunctions
1007
                if (empty($_SESSION["dol_loginmesg"])) {
1008
                    $_SESSION["dol_loginmesg"] = $langs->transnoentitiesnoconv("ErrorBadLoginPassword");
1009
                }
1010
1011
                // Call trigger for the "security events" log
1012
                $user->context['audit'] = $langs->trans("ErrorBadLoginPassword") . ' - login=' . GETPOST("username", "alpha", 2);
1013
1014
                // Call trigger
1015
                $result = $user->call_trigger('USER_LOGIN_FAILED', $user);
1016
                if ($result < 0) {
1017
                    $error++;
1018
                }
1019
                // End call triggers
1020
1021
                // Hooks on failed login
1022
                $action = '';
1023
                $hookmanager->initHooks(array('login'));
1024
                $parameters = array('dol_authmode' => $dol_authmode, 'dol_loginmesg' => $_SESSION["dol_loginmesg"]);
1025
                $reshook = $hookmanager->executeHooks('afterLoginFailed', $parameters, $user, $action); // Note that $action and $object may have been modified by some hooks
1026
                if ($reshook < 0) {
1027
                    $error++;
1028
                }
1029
1030
                // Note: exit is done in next chapter
1031
            }
1032
        }
1033
1034
        // End test login / passwords
1035
        if (!$login || (in_array('ldap', $authmode) && empty($passwordtotest))) {   // With LDAP we refused empty password because some LDAP are "opened" for anonymous access so connection is a success.
1036
            // No data to test login, so we show the login page.
1037
            dol_syslog("--- Access to " . (empty($_SERVER["REQUEST_METHOD"]) ? '' : $_SERVER["REQUEST_METHOD"] . ' ') . $_SERVER["PHP_SELF"] . " - action=" . GETPOST('action', 'aZ09') . " - actionlogin=" . GETPOST('actionlogin', 'aZ09') . " - showing the login form and exit", LOG_NOTICE);
1038
            if (defined('NOREDIRECTBYMAINTOLOGIN')) {
1039
                // When used with NOREDIRECTBYMAINTOLOGIN set, the http header must already be set when including the main.
1040
                // See example with selectsearchbox.php. This case is reserved for the selectesearchbox.php so we can
1041
                // report a message to ask to login when search ajax component is used after a timeout.
1042
                //top_httphead();
1043
                return 'ERROR_NOT_LOGGED';
1044
            } else {
1045
                if (!empty($_SERVER["HTTP_USER_AGENT"]) && $_SERVER["HTTP_USER_AGENT"] == 'securitytest') {
1046
                    http_response_code(401); // It makes easier to understand if session was broken during security tests
1047
                }
1048
                dol_loginfunction($langs, $conf, (!empty($mysoc) ? $mysoc : ''));   // This include http headers
1049
            }
1050
            exit;
1051
        }
1052
1053
        $resultFetchUser = $user->fetch('', $login, '', 1, ($entitytotest > 0 ? $entitytotest : -1)); // value for $login was retrieved previously when checking password.
1054
        if ($resultFetchUser <= 0 || $user->isNotIntoValidityDateRange()) {
1055
            dol_syslog('User not found or not valid, connection refused');
1056
            session_destroy();
1057
            session_set_cookie_params(0, '/', null, (empty($dolibarr_main_force_https) ? false : true), true); // Add tag secure and httponly on session cookie
1058
            session_name($sessionname);
1059
            dol_session_start();
1060
1061
            if ($resultFetchUser == 0) {
1062
                // Load translation files required by page
1063
                $langs->loadLangs(array('main', 'errors'));
1064
1065
                $_SESSION["dol_loginmesg"] = $langs->transnoentitiesnoconv("ErrorCantLoadUserFromDolibarrDatabase", $login);
1066
1067
                $user->context['audit'] = 'ErrorCantLoadUserFromDolibarrDatabase - login=' . $login;
1068
            } elseif ($resultFetchUser < 0) {
1069
                $_SESSION["dol_loginmesg"] = $user->error;
1070
1071
                $user->context['audit'] = $user->error;
1072
            } else {
1073
                // Load translation files required by the page
1074
                $langs->loadLangs(array('main', 'errors'));
1075
1076
                $_SESSION["dol_loginmesg"] = $langs->transnoentitiesnoconv("ErrorLoginDateValidity");
1077
1078
                $user->context['audit'] = $langs->trans("ErrorLoginDateValidity") . ' - login=' . $login;
1079
            }
1080
1081
            // Call trigger
1082
            $result = $user->call_trigger('USER_LOGIN_FAILED', $user);
1083
            if ($result < 0) {
1084
                $error++;
1085
            }
1086
            // End call triggers
1087
1088
1089
            // Hooks on failed login
1090
            $action = '';
1091
            $hookmanager->initHooks(array('login'));
1092
            $parameters = array('dol_authmode' => $dol_authmode, 'dol_loginmesg' => $_SESSION["dol_loginmesg"]);
1093
            $reshook = $hookmanager->executeHooks('afterLoginFailed', $parameters, $user, $action); // Note that $action and $object may have been modified by some hooks
1094
            if ($reshook < 0) {
1095
                $error++;
1096
            }
1097
1098
            $paramsurl = array();
1099
            if (GETPOSTINT('textbrowser')) {
1100
                $paramsurl[] = 'textbrowser=' . GETPOSTINT('textbrowser');
1101
            }
1102
            if (GETPOSTINT('nojs')) {
1103
                $paramsurl[] = 'nojs=' . GETPOSTINT('nojs');
1104
            }
1105
            if (GETPOST('lang', 'aZ09')) {
1106
                $paramsurl[] = 'lang=' . GETPOST('lang', 'aZ09');
1107
            }
1108
            header('Location: ' . constant('BASE_URL') . '/index.php' . (count($paramsurl) ? '?' . implode('&', $paramsurl) : ''));
1109
            exit;
1110
        } else {
1111
            // User is loaded, we may need to change language for him according to its choice
1112
            if (!empty($user->conf->MAIN_LANG_DEFAULT)) {
1113
                $langs->setDefaultLang($user->conf->MAIN_LANG_DEFAULT);
1114
            }
1115
        }
1116
    } else {
1117
        // We are already into an authenticated session
1118
        $login = $_SESSION["dol_login"];
1119
        $entity = isset($_SESSION["dol_entity"]) ? $_SESSION["dol_entity"] : 0;
1120
        dol_syslog("- This is an already logged session. _SESSION['dol_login']=" . $login . " _SESSION['dol_entity']=" . $entity, LOG_DEBUG);
1121
1122
        $resultFetchUser = $user->fetch('', $login, '', 1, ($entity > 0 ? $entity : -1));
1123
1124
        //var_dump(dol_print_date($user->flagdelsessionsbefore, 'dayhour', 'gmt')." ".dol_print_date($_SESSION["dol_logindate"], 'dayhour', 'gmt'));
1125
1126
        if (
1127
            $resultFetchUser <= 0
1128
            || ($user->flagdelsessionsbefore && !empty($_SESSION["dol_logindate"]) && $user->flagdelsessionsbefore > $_SESSION["dol_logindate"])
1129
            || ($user->status != $user::STATUS_ENABLED)
1130
            || ($user->isNotIntoValidityDateRange())
1131
        ) {
1132
            if ($resultFetchUser <= 0) {
1133
                // Account has been removed after login
1134
                dol_syslog("Can't load user even if session logged. _SESSION['dol_login']=" . $login, LOG_WARNING);
1135
            } elseif ($user->flagdelsessionsbefore && !empty($_SESSION["dol_logindate"]) && $user->flagdelsessionsbefore > $_SESSION["dol_logindate"]) {
1136
                // Session is no more valid
1137
                dol_syslog("The user has a date for session invalidation = " . $user->flagdelsessionsbefore . " and a session date = " . $_SESSION["dol_logindate"] . ". We must invalidate its sessions.");
1138
            } elseif ($user->status != $user::STATUS_ENABLED) {
1139
                // User is not enabled
1140
                dol_syslog("The user login is disabled");
1141
            } else {
1142
                // User validity dates are no more valid
1143
                dol_syslog("The user login has a validity between [" . $user->datestartvalidity . " and " . $user->dateendvalidity . "], current date is " . dol_now());
1144
            }
1145
            session_destroy();
1146
            session_set_cookie_params(0, '/', null, (empty($dolibarr_main_force_https) ? false : true), true); // Add tag secure and httponly on session cookie
1147
            session_name($sessionname);
1148
            dol_session_start();
1149
1150
            if ($resultFetchUser == 0) {
1151
                $langs->loadLangs(array('main', 'errors'));
1152
1153
                $_SESSION["dol_loginmesg"] = $langs->transnoentitiesnoconv("ErrorCantLoadUserFromDolibarrDatabase", $login);
1154
1155
                $user->context['audit'] = 'ErrorCantLoadUserFromDolibarrDatabase - login=' . $login;
1156
            } elseif ($resultFetchUser < 0) {
1157
                $_SESSION["dol_loginmesg"] = $user->error;
1158
1159
                $user->context['audit'] = $user->error;
1160
            } else {
1161
                $langs->loadLangs(array('main', 'errors'));
1162
1163
                $_SESSION["dol_loginmesg"] = $langs->transnoentitiesnoconv("ErrorSessionInvalidatedAfterPasswordChange");
1164
1165
                $user->context['audit'] = 'ErrorUserSessionWasInvalidated - login=' . $login;
1166
            }
1167
1168
            // Call trigger
1169
            $result = $user->call_trigger('USER_LOGIN_FAILED', $user);
1170
            if ($result < 0) {
1171
                $error++;
1172
            }
1173
            // End call triggers
1174
1175
            // Hooks on failed login
1176
            $action = '';
1177
            $hookmanager->initHooks(array('login'));
1178
            $parameters = array('dol_authmode' => (isset($dol_authmode) ? $dol_authmode : ''), 'dol_loginmesg' => $_SESSION["dol_loginmesg"]);
1179
            $reshook = $hookmanager->executeHooks('afterLoginFailed', $parameters, $user, $action); // Note that $action and $object may have been modified by some hooks
1180
            if ($reshook < 0) {
1181
                $error++;
1182
            }
1183
1184
            $paramsurl = array();
1185
            if (GETPOSTINT('textbrowser')) {
1186
                $paramsurl[] = 'textbrowser=' . GETPOSTINT('textbrowser');
1187
            }
1188
            if (GETPOSTINT('nojs')) {
1189
                $paramsurl[] = 'nojs=' . GETPOSTINT('nojs');
1190
            }
1191
            if (GETPOST('lang', 'aZ09')) {
1192
                $paramsurl[] = 'lang=' . GETPOST('lang', 'aZ09');
1193
            }
1194
1195
            header('Location: ' . constant('BASE_URL') . '/index.php' . (count($paramsurl) ? '?' . implode('&', $paramsurl) : ''));
1196
            exit;
1197
        } else {
1198
            // Initialize technical object to manage hooks of page. Note that conf->hooks_modules contains array of hook context
1199
            $hookmanager->initHooks(array('main'));
1200
1201
            // Code for search criteria persistence.
1202
            if (!empty($_GET['save_lastsearch_values']) && !empty($_SERVER["HTTP_REFERER"])) {    // We must use $_GET here
1203
                $relativepathstring = preg_replace('/\?.*$/', '', $_SERVER["HTTP_REFERER"]);
1204
                $relativepathstring = preg_replace('/^https?:\/\/[^\/]*/', '', $relativepathstring); // Get full path except host server
1205
                // Clean $relativepathstring
1206
                if (constant('DOL_URL_ROOT')) {
1207
                    $relativepathstring = preg_replace('/^' . preg_quote(constant('DOL_URL_ROOT'), '/') . '/', '', $relativepathstring);
1208
                }
1209
                $relativepathstring = preg_replace('/^\//', '', $relativepathstring);
1210
                $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
1211
                //var_dump($relativepathstring);
1212
1213
                // We click on a link that leave a page we have to save search criteria, contextpage, limit and page and mode. We save them from tmp to no tmp
1214
                if (!empty($_SESSION['lastsearch_values_tmp_' . $relativepathstring])) {
1215
                    $_SESSION['lastsearch_values_' . $relativepathstring] = $_SESSION['lastsearch_values_tmp_' . $relativepathstring];
1216
                    unset($_SESSION['lastsearch_values_tmp_' . $relativepathstring]);
1217
                }
1218
                if (!empty($_SESSION['lastsearch_contextpage_tmp_' . $relativepathstring])) {
1219
                    $_SESSION['lastsearch_contextpage_' . $relativepathstring] = $_SESSION['lastsearch_contextpage_tmp_' . $relativepathstring];
1220
                    unset($_SESSION['lastsearch_contextpage_tmp_' . $relativepathstring]);
1221
                }
1222
                if (!empty($_SESSION['lastsearch_limit_tmp_' . $relativepathstring]) && $_SESSION['lastsearch_limit_tmp_' . $relativepathstring] != $conf->liste_limit) {
1223
                    $_SESSION['lastsearch_limit_' . $relativepathstring] = $_SESSION['lastsearch_limit_tmp_' . $relativepathstring];
1224
                    unset($_SESSION['lastsearch_limit_tmp_' . $relativepathstring]);
1225
                }
1226
                if (!empty($_SESSION['lastsearch_page_tmp_' . $relativepathstring]) && $_SESSION['lastsearch_page_tmp_' . $relativepathstring] > 0) {
1227
                    $_SESSION['lastsearch_page_' . $relativepathstring] = $_SESSION['lastsearch_page_tmp_' . $relativepathstring];
1228
                    unset($_SESSION['lastsearch_page_tmp_' . $relativepathstring]);
1229
                }
1230
                if (!empty($_SESSION['lastsearch_mode_tmp_' . $relativepathstring])) {
1231
                    $_SESSION['lastsearch_mode_' . $relativepathstring] = $_SESSION['lastsearch_mode_tmp_' . $relativepathstring];
1232
                    unset($_SESSION['lastsearch_mode_tmp_' . $relativepathstring]);
1233
                }
1234
            }
1235
            if (!empty($_GET['save_pageforbacktolist']) && !empty($_SERVER["HTTP_REFERER"])) {    // We must use $_GET here
1236
                if (empty($_SESSION['pageforbacktolist'])) {
1237
                    $pageforbacktolistarray = array();
1238
                } else {
1239
                    $pageforbacktolistarray = $_SESSION['pageforbacktolist'];
1240
                }
1241
                $tmparray = explode(':', $_GET['save_pageforbacktolist'], 2);
1242
                if (!empty($tmparray[0]) && !empty($tmparray[1])) {
1243
                    $pageforbacktolistarray[$tmparray[0]] = $tmparray[1];
1244
                    $_SESSION['pageforbacktolist'] = $pageforbacktolistarray;
1245
                }
1246
            }
1247
1248
            $action = '';
1249
            $parameters = array();
1250
            $reshook = $hookmanager->executeHooks('updateSession', $parameters, $user, $action);
1251
            if ($reshook < 0) {
1252
                setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
1253
            }
1254
        }
1255
    }
1256
1257
    // Is it a new session that has started ?
1258
    // If we are here, this means authentication was successful.
1259
    if (!isset($_SESSION["dol_login"])) {
1260
        // New session for this login has started.
1261
        $error = 0;
1262
1263
        // Store value into session (values always stored)
1264
        $_SESSION["dol_login"] = $user->login;
1265
        $_SESSION["dol_logindate"] = dol_now('gmt');
1266
        $_SESSION["dol_authmode"] = isset($dol_authmode) ? $dol_authmode : '';
1267
        $_SESSION["dol_tz"] = isset($dol_tz) ? $dol_tz : '';
1268
        $_SESSION["dol_tz_string"] = isset($dol_tz_string) ? $dol_tz_string : '';
1269
        $_SESSION["dol_dst"] = isset($dol_dst) ? $dol_dst : '';
1270
        $_SESSION["dol_dst_observed"] = isset($dol_dst_observed) ? $dol_dst_observed : '';
1271
        $_SESSION["dol_dst_first"] = isset($dol_dst_first) ? $dol_dst_first : '';
1272
        $_SESSION["dol_dst_second"] = isset($dol_dst_second) ? $dol_dst_second : '';
1273
        $_SESSION["dol_screenwidth"] = isset($dol_screenwidth) ? $dol_screenwidth : '';
1274
        $_SESSION["dol_screenheight"] = isset($dol_screenheight) ? $dol_screenheight : '';
1275
        $_SESSION["dol_company"] = getDolGlobalString("MAIN_INFO_SOCIETE_NOM");
1276
        $_SESSION["dol_entity"] = $conf->entity;
1277
        // Store value into session (values stored only if defined)
1278
        if (!empty($dol_hide_topmenu)) {
1279
            $_SESSION['dol_hide_topmenu'] = $dol_hide_topmenu;
1280
        }
1281
        if (!empty($dol_hide_leftmenu)) {
1282
            $_SESSION['dol_hide_leftmenu'] = $dol_hide_leftmenu;
1283
        }
1284
        if (!empty($dol_optimize_smallscreen)) {
1285
            $_SESSION['dol_optimize_smallscreen'] = $dol_optimize_smallscreen;
1286
        }
1287
        if (!empty($dol_no_mouse_hover)) {
1288
            $_SESSION['dol_no_mouse_hover'] = $dol_no_mouse_hover;
1289
        }
1290
        if (!empty($dol_use_jmobile)) {
1291
            $_SESSION['dol_use_jmobile'] = $dol_use_jmobile;
1292
        }
1293
1294
        dol_syslog("This is a new started user session. _SESSION['dol_login']=" . $_SESSION["dol_login"] . " Session id=" . session_id());
1295
1296
        $db->begin();
1297
1298
        $user->update_last_login_date();
1299
1300
        $loginfo = 'TZ=' . $_SESSION["dol_tz"] . ';TZString=' . $_SESSION["dol_tz_string"] . ';Screen=' . $_SESSION["dol_screenwidth"] . 'x' . $_SESSION["dol_screenheight"];
1301
        $loginfo .= ' - authmode=' . $dol_authmode . ' - entity=' . $conf->entity;
1302
1303
        // Call triggers for the "security events" log
1304
        $user->context['audit'] = $loginfo;
1305
        $user->context['authentication_method'] = $dol_authmode;
1306
1307
        // Call trigger
1308
        $result = $user->call_trigger('USER_LOGIN', $user);
1309
        if ($result < 0) {
1310
            $error++;
1311
        }
1312
        // End call triggers
1313
1314
        // Hooks on successful login
1315
        $action = '';
1316
        $hookmanager->initHooks(array('login'));
1317
        $parameters = array('dol_authmode' => $dol_authmode, 'dol_loginfo' => $loginfo);
1318
        $reshook = $hookmanager->executeHooks('afterLogin', $parameters, $user, $action); // Note that $action and $object may have been modified by some hooks
1319
        if ($reshook < 0) {
1320
            $error++;
1321
        }
1322
1323
        if ($error) {
1324
            $db->rollback();
1325
            session_destroy();
1326
            dol_print_error($db, 'Error in some triggers USER_LOGIN or in some hooks afterLogin');
1327
            exit;
1328
        } else {
1329
            $db->commit();
1330
        }
1331
1332
        // Change landing page if defined.
1333
        $landingpage = (empty($user->conf->MAIN_LANDING_PAGE) ? (!getDolGlobalString('MAIN_LANDING_PAGE') ? '' : $conf->global->MAIN_LANDING_PAGE) : $user->conf->MAIN_LANDING_PAGE);
1334
        if (!empty($landingpage)) {    // Example: /index.php
1335
            $newpath = dol_buildpath($landingpage, 1);
1336
            if ($_SERVER["PHP_SELF"] != $newpath) {   // not already on landing page (avoid infinite loop)
1337
                header('Location: ' . $newpath);
1338
                exit;
1339
            }
1340
        }
1341
    }
1342
1343
1344
    // If user admin, we force the rights-based modules
1345
    if ($user->admin) {
1346
        $user->rights->user->user->lire = 1;
1347
        $user->rights->user->user->creer = 1;
1348
        $user->rights->user->user->password = 1;
1349
        $user->rights->user->user->supprimer = 1;
1350
        $user->rights->user->self->creer = 1;
1351
        $user->rights->user->self->password = 1;
1352
1353
        //Required if advanced permissions are used with MAIN_USE_ADVANCED_PERMS
1354
        if (getDolGlobalString('MAIN_USE_ADVANCED_PERMS')) {
1355
            if (!$user->hasRight('user', 'user_advance')) {
1356
                $user->rights->user->user_advance = new stdClass(); // To avoid warnings
1357
            }
1358
            if (!$user->hasRight('user', 'self_advance')) {
1359
                $user->rights->user->self_advance = new stdClass(); // To avoid warnings
1360
            }
1361
            if (!$user->hasRight('user', 'group_advance')) {
1362
                $user->rights->user->group_advance = new stdClass(); // To avoid warnings
1363
            }
1364
1365
            $user->rights->user->user_advance->readperms = 1;
1366
            $user->rights->user->user_advance->write = 1;
1367
            $user->rights->user->self_advance->readperms = 1;
1368
            $user->rights->user->self_advance->writeperms = 1;
1369
            $user->rights->user->group_advance->read = 1;
1370
            $user->rights->user->group_advance->readperms = 1;
1371
            $user->rights->user->group_advance->write = 1;
1372
            $user->rights->user->group_advance->delete = 1;
1373
        }
1374
    }
1375
1376
    /*
1377
     * Overwrite some configs globals (try to avoid this and have code to use instead $user->conf->xxx)
1378
     */
1379
1380
    // Set liste_limit
1381
    if (isset($user->conf->MAIN_SIZE_LISTE_LIMIT)) {
1382
        $conf->liste_limit = $user->conf->MAIN_SIZE_LISTE_LIMIT; // Can be 0
1383
    }
1384
    if (isset($user->conf->PRODUIT_LIMIT_SIZE)) {
1385
        $conf->product->limit_size = $user->conf->PRODUIT_LIMIT_SIZE; // Can be 0
1386
    }
1387
1388
    // Replace conf->css by personalized value if theme not forced
1389
    if (!getDolGlobalString('MAIN_FORCETHEME') && !empty($user->conf->MAIN_THEME)) {
1390
        $conf->theme = $user->conf->MAIN_THEME;
1391
        $conf->css = "/theme/" . $conf->theme . "/style.css.php";
1392
    }
1393
} else {
1394
    // We may have NOLOGIN set, but NOREQUIREUSER not
1395
    if (!empty($user) && method_exists($user, 'loadDefaultValues') && !defined('NODEFAULTVALUES')) {
1396
        $user->loadDefaultValues();     // Load default values for everybody (works even if $user->id = 0
1397
    }
1398
}
1399
1400
1401
// Case forcing style from url
1402
if (GETPOST('theme', 'aZ09')) {
1403
    $conf->theme = GETPOST('theme', 'aZ09', 1);
1404
    $conf->css = "/theme/" . $conf->theme . "/style.css.php";
1405
}
1406
1407
// Set javascript option
1408
if (GETPOSTINT('nojs')) {  // If javascript was not disabled on URL
1409
    $conf->use_javascript_ajax = 0;
1410
} else {
1411
    if (!empty($user->conf->MAIN_DISABLE_JAVASCRIPT)) {
1412
        $conf->use_javascript_ajax = !$user->conf->MAIN_DISABLE_JAVASCRIPT;
1413
    }
1414
}
1415
1416
// Set MAIN_OPTIMIZEFORTEXTBROWSER for user (must be after login part)
1417
if (!getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER') && !empty($user->conf->MAIN_OPTIMIZEFORTEXTBROWSER)) {
1418
    $conf->global->MAIN_OPTIMIZEFORTEXTBROWSER = $user->conf->MAIN_OPTIMIZEFORTEXTBROWSER;
1419
    if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER') == 1) {
1420
        $conf->global->THEME_TOPMENU_DISABLE_IMAGE = 1;
1421
    }
1422
}
1423
//var_dump($conf->global->THEME_TOPMENU_DISABLE_IMAGE);
1424
//var_dump($user->conf->THEME_TOPMENU_DISABLE_IMAGE);
1425
1426
// set MAIN_OPTIMIZEFORCOLORBLIND for user
1427
$conf->global->MAIN_OPTIMIZEFORCOLORBLIND = empty($user->conf->MAIN_OPTIMIZEFORCOLORBLIND) ? '' : $user->conf->MAIN_OPTIMIZEFORCOLORBLIND;
1428
1429
// Set terminal output option according to conf->browser.
1430
if (GETPOSTINT('dol_hide_leftmenu') || !empty($_SESSION['dol_hide_leftmenu'])) {
1431
    $conf->dol_hide_leftmenu = 1;
1432
}
1433
if (GETPOSTINT('dol_hide_topmenu') || !empty($_SESSION['dol_hide_topmenu'])) {
1434
    $conf->dol_hide_topmenu = 1;
1435
}
1436
if (GETPOSTINT('dol_optimize_smallscreen') || !empty($_SESSION['dol_optimize_smallscreen'])) {
1437
    $conf->dol_optimize_smallscreen = 1;
1438
}
1439
if (GETPOSTINT('dol_no_mouse_hover') || !empty($_SESSION['dol_no_mouse_hover'])) {
1440
    $conf->dol_no_mouse_hover = 1;
1441
}
1442
if (GETPOSTINT('dol_use_jmobile') || !empty($_SESSION['dol_use_jmobile'])) {
1443
    $conf->dol_use_jmobile = 1;
1444
}
1445
// If not on Desktop
1446
if (!empty($conf->browser->layout) && $conf->browser->layout != 'classic') {
1447
    $conf->dol_no_mouse_hover = 1;
1448
}
1449
1450
// If on smartphone or optimized for small screen
1451
if (
1452
    (!empty($conf->browser->layout) && $conf->browser->layout == 'phone')
1453
    || (!empty($_SESSION['dol_screenwidth']) && $_SESSION['dol_screenwidth'] < 400)
1454
    || (!empty($_SESSION['dol_screenheight']) && $_SESSION['dol_screenheight'] < 400
1455
        || getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER'))
1456
) {
1457
    $conf->dol_optimize_smallscreen = 1;
1458
1459
    if (getDolGlobalInt('PRODUIT_DESC_IN_FORM') == 1) {
1460
        $conf->global->PRODUIT_DESC_IN_FORM_ACCORDING_TO_DEVICE = 0;
1461
    }
1462
}
1463
// Replace themes bugged with jmobile with eldy
1464
if (!empty($conf->dol_use_jmobile) && in_array($conf->theme, array('bureau2crea', 'cameleo', 'amarok'))) {
1465
    $conf->theme = 'eldy';
1466
    $conf->css = "/theme/" . $conf->theme . "/style.css.php";
1467
}
1468
1469
if (!defined('NOREQUIRETRAN')) {
1470
    if (!GETPOST('lang', 'aZ09')) { // If language was not forced on URL
1471
        // If user has chosen its own language
1472
        if (!empty($user->conf->MAIN_LANG_DEFAULT)) {
1473
            // If different than current language
1474
            //print ">>>".$langs->getDefaultLang()."-".$user->conf->MAIN_LANG_DEFAULT;
1475
            if ($langs->getDefaultLang() != $user->conf->MAIN_LANG_DEFAULT) {
1476
                $langs->setDefaultLang($user->conf->MAIN_LANG_DEFAULT);
1477
            }
1478
        }
1479
    }
1480
}
1481
1482
if (!defined('NOLOGIN')) {
1483
    // If the login is not recovered, it is identified with an account that does not exist.
1484
    // Hacking attempt?
1485
    if (!$user->login) {
1486
        accessforbidden();
1487
    }
1488
1489
    // Check if user is active
1490
    if ($user->statut < 1) {
1491
        // If not active, we refuse the user
1492
        $langs->loadLangs(array("errors", "other"));
1493
        dol_syslog("Authentication KO as login is disabled", LOG_NOTICE);
1494
        accessforbidden("ErrorLoginDisabled");
1495
    }
1496
1497
    // Load permissions
1498
    $user->getrights();
1499
}
1500
1501
dol_syslog("--- Access to " . (empty($_SERVER["REQUEST_METHOD"]) ? '' : $_SERVER["REQUEST_METHOD"] . ' ') . $_SERVER["PHP_SELF"] . ' - action=' . GETPOST('action', 'aZ09') . ', massaction=' . GETPOST('massaction', 'aZ09') . (defined('NOTOKENRENEWAL') ? ' NOTOKENRENEWAL=' . constant('NOTOKENRENEWAL') : ''), LOG_NOTICE);
1502
//Another call for easy debug
1503
//dol_syslog("Access to ".$_SERVER["PHP_SELF"].' '.$_SERVER["HTTP_REFERER"].' GET='.join(',',array_keys($_GET)).'->'.join(',',$_GET).' POST:'.join(',',array_keys($_POST)).'->'.join(',',$_POST));
1504
1505
// Load main languages files
1506
if (!defined('NOREQUIRETRAN')) {
1507
    // Load translation files required by page
1508
    $langs->loadLangs(array('main', 'dict'));
1509
}
1510
1511
// Define some constants used for style of arrays
1512
$bc = array(0 => 'class="impair"', 1 => 'class="pair"');
1513
$bcdd = array(0 => 'class="drag drop oddeven"', 1 => 'class="drag drop oddeven"');
1514
$bcnd = array(0 => 'class="nodrag nodrop nohover"', 1 => 'class="nodrag nodrop nohoverpair"'); // Used for tr to add new lines
1515
$bctag = array(0 => 'class="impair tagtr"', 1 => 'class="pair tagtr"');
1516
1517
// Define messages variables
1518
$mesg = '';
1519
$warning = '';
1520
$error = 0;
1521
// deprecated, see setEventMessages() and dol_htmloutput_events()
1522
$mesgs = array();
1523
$warnings = array();
1524
$errors = array();
1525
1526
// Constants used to defined number of lines in textarea
1527
if (empty($conf->browser->firefox)) {
1528
    define('ROWS_1', 1);
1529
    define('ROWS_2', 2);
1530
    define('ROWS_3', 3);
1531
    define('ROWS_4', 4);
1532
    define('ROWS_5', 5);
1533
    define('ROWS_6', 6);
1534
    define('ROWS_7', 7);
1535
    define('ROWS_8', 8);
1536
    define('ROWS_9', 9);
1537
} else {
1538
    define('ROWS_1', 0);
1539
    define('ROWS_2', 1);
1540
    define('ROWS_3', 2);
1541
    define('ROWS_4', 3);
1542
    define('ROWS_5', 4);
1543
    define('ROWS_6', 5);
1544
    define('ROWS_7', 6);
1545
    define('ROWS_8', 7);
1546
    define('ROWS_9', 8);
1547
}
1548
1549
$heightforframes = 50;
1550
1551
// Init menu manager
1552
if (!defined('NOREQUIREMENU')) {
1553
    if (empty($user->socid)) {    // If internal user or not defined
1554
        $conf->standard_menu = (!getDolGlobalString('MAIN_MENU_STANDARD_FORCED') ? (!getDolGlobalString('MAIN_MENU_STANDARD') ? 'eldy_menu.php' : $conf->global->MAIN_MENU_STANDARD) : $conf->global->MAIN_MENU_STANDARD_FORCED);
1555
    } else {
1556
        // If external user
1557
        $conf->standard_menu = (!getDolGlobalString('MAIN_MENUFRONT_STANDARD_FORCED') ? (!getDolGlobalString('MAIN_MENUFRONT_STANDARD') ? 'eldy_menu.php' : $conf->global->MAIN_MENUFRONT_STANDARD) : $conf->global->MAIN_MENUFRONT_STANDARD_FORCED);
1558
    }
1559
1560
    // Load the menu manager (only if not already done)
1561
    $file_menu = $conf->standard_menu;
1562
    if (GETPOST('menu', 'alpha')) {
1563
        $file_menu = GETPOST('menu', 'alpha'); // example: menu=eldy_menu.php
1564
    }
1565
    if (!class_exists('MenuManager')) {
1566
        $menufound = 0;
1567
        $dirmenus = array_merge(array("/core/menus/"), (array)$conf->modules_parts['menus']);
1568
        foreach ($dirmenus as $dirmenu) {
1569
            $menufound = dol_include_once($dirmenu . "standard/" . $file_menu);
1570
            if (class_exists('MenuManager')) {
1571
                break;
1572
            }
1573
        }
1574
        if (!class_exists('MenuManager')) { // If failed to include, we try with standard eldy_menu.php
1575
            dol_syslog("You define a menu manager '" . $file_menu . "' that can not be loaded.", LOG_WARNING);
1576
            $file_menu = 'eldy_menu.php';
1577
            include_once DOL_DOCUMENT_ROOT . "/core/menus/standard/" . $file_menu;
1578
        }
1579
    }
1580
    $menumanager = new MenuManager($db, empty($user->socid) ? 0 : 1);
1581
    $menumanager->loadMenu();
1582
}
1583
1584
if (!empty(GETPOST('seteventmessages', 'alpha'))) {
1585
    $message = GETPOST('seteventmessages', 'alpha');
1586
    $messages = explode(',', $message);
1587
    foreach ($messages as $key => $msg) {
1588
        $tmp = explode(':', $msg);
1589
        setEventMessages($tmp[0], null, !empty($tmp[1]) ? $tmp[1] : 'mesgs');
1590
    }
1591
}
1592
1593
// Functions
1594
1595
if (!function_exists("llxHeader")) {
1596
    /**
1597
     *  Show HTML header HTML + BODY + Top menu + left menu + DIV
1598
     *
1599
     * @param string $head Optional head lines
1600
     * @param string $title HTML title
1601
     * @param string $help_url Url links to help page
1602
     *                                              Syntax is: For a wiki page: EN:EnglishPage|FR:FrenchPage|ES:SpanishPage|DE:GermanPage
1603
     *                                              For other external page: http://server/url
1604
     * @param string $target Target to use on links
1605
     * @param int $disablejs More content into html header
1606
     * @param int $disablehead More content into html header
1607
     * @param array|string $arrayofjs Array of complementary js files
1608
     * @param array|string $arrayofcss Array of complementary css files
1609
     * @param string $morequerystring Query string to add to the link "print" to get same parameters (use only if autodetect fails)
1610
     * @param string $morecssonbody More CSS on body tag. For example 'classforhorizontalscrolloftabs'.
1611
     * @param string $replacemainareaby Replace call to main_area() by a print of this string
1612
     * @param int $disablenofollow Disable the "nofollow" on meta robot header
1613
     * @param int $disablenoindex Disable the "noindex" on meta robot header
1614
     * @return  void
1615
     */
1616
    function llxHeader($head = '', $title = '', $help_url = '', $target = '', $disablejs = 0, $disablehead = 0, $arrayofjs = '', $arrayofcss = '', $morequerystring = '', $morecssonbody = '', $replacemainareaby = '', $disablenofollow = 0, $disablenoindex = 0)
1617
    {
1618
        global $conf, $hookmanager;
1619
1620
        $parameters = array(
1621
            'head' => & $head,
1622
            'title' => & $title,
1623
            'help_url' => & $help_url,
1624
            'target' => & $target,
1625
            'disablejs' => & $disablejs,
1626
            'disablehead' => & $disablehead,
1627
            'arrayofjs' => & $arrayofjs,
1628
            'arrayofcss' => & $arrayofcss,
1629
            'morequerystring' => & $morequerystring,
1630
            'morecssonbody' => & $morecssonbody,
1631
            'replacemainareaby' => & $replacemainareaby,
1632
            'disablenofollow' => & $disablenofollow,
1633
            'disablenoindex' => & $disablenoindex
1634
1635
        );
1636
        $reshook = $hookmanager->executeHooks('llxHeader', $parameters);
1637
        if ($reshook > 0) {
1638
            print $hookmanager->resPrint;
1639
            return;
1640
        }
1641
1642
        // html header
1643
        top_htmlhead($head, $title, $disablejs, $disablehead, $arrayofjs, $arrayofcss, 0, $disablenofollow, $disablenoindex);
1644
1645
        $tmpcsstouse = 'sidebar-collapse' . ($morecssonbody ? ' ' . $morecssonbody : '');
1646
        // If theme MD and classic layer, we open the menulayer by default.
1647
        if ($conf->theme == 'md' && !in_array($conf->browser->layout, array('phone', 'tablet')) && !getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
1648
            global $mainmenu;
1649
            if ($mainmenu != 'website') {
1650
                $tmpcsstouse = $morecssonbody; // We do not use sidebar-collpase by default to have menuhider open by default.
1651
            }
1652
        }
1653
1654
        if (getDolGlobalString('MAIN_OPTIMIZEFORCOLORBLIND')) {
1655
            $tmpcsstouse .= ' colorblind-' . strip_tags($conf->global->MAIN_OPTIMIZEFORCOLORBLIND);
1656
        }
1657
1658
        print '<body id="mainbody" class="' . $tmpcsstouse . '">' . "\n";
1659
1660
        // top menu and left menu area
1661
        if ((empty($conf->dol_hide_topmenu) || GETPOSTINT('dol_invisible_topmenu')) && !GETPOSTINT('dol_openinpopup')) {
1662
            top_menu($head, $title, $target, $disablejs, $disablehead, $arrayofjs, $arrayofcss, $morequerystring, $help_url);
1663
        }
1664
1665
        if (empty($conf->dol_hide_leftmenu) && !GETPOST('dol_openinpopup', 'aZ09')) {
1666
            left_menu(array(), $help_url, '', '', 1, $title, 1); // $menumanager is retrieved with a global $menumanager inside this function
1667
        }
1668
1669
        // main area
1670
        if ($replacemainareaby) {
1671
            print $replacemainareaby;
1672
            return;
1673
        }
1674
        main_area($title);
1675
    }
1676
}
1677
1678
1679
/**
1680
 *  Show HTTP header. Called by top_htmlhead().
1681
 *
1682
 * @param string $contenttype Content type. For example, 'text/html'
1683
 * @param int<0,1> $forcenocache Force disabling of cache for the page
1684
 * @return void
1685
 */
1686
function top_httphead($contenttype = 'text/html', $forcenocache = 0)
1687
{
1688
    global $db, $conf, $hookmanager;
1689
1690
    if ($contenttype == 'text/html') {
1691
        header("Content-Type: text/html; charset=" . $conf->file->character_set_client);
1692
    } else {
1693
        header("Content-Type: " . $contenttype);
1694
    }
1695
1696
    // Security options
1697
1698
    // X-Content-Type-Options
1699
    header("X-Content-Type-Options: nosniff"); // With the nosniff option, if the server says the content is text/html, the browser will render it as text/html (note that most browsers now force this option to on)
1700
1701
    // X-Frame-Options
1702
    if (!defined('XFRAMEOPTIONS_ALLOWALL')) {
1703
        header("X-Frame-Options: SAMEORIGIN"); // By default, frames allowed only if on same domain (stop some XSS attacks)
1704
    } else {
1705
        header("X-Frame-Options: ALLOWALL");
1706
    }
1707
1708
    if (getDolGlobalString('MAIN_SECURITY_FORCE_ACCESS_CONTROL_ALLOW_ORIGIN')) {
1709
        $tmpurl = constant('DOL_MAIN_URL_ROOT');
1710
        $tmpurl = preg_replace('/^(https?:\/\/[^\/]+)\/.*$/', '\1', $tmpurl);
1711
        header('Access-Control-Allow-Origin: ' . $tmpurl);
1712
        header('Vary: Origin');
1713
    }
1714
1715
    // X-XSS-Protection
1716
    //header("X-XSS-Protection: 1");            // XSS filtering protection of some browsers (note: use of Content-Security-Policy is more efficient). Disabled as deprecated.
1717
1718
    // Content-Security-Policy-Report-Only
1719
    if (!defined('MAIN_SECURITY_FORCECSPRO')) {
1720
        // If CSP not forced from the page
1721
1722
        // A default security policy that keep usage of js external component like ckeditor, stripe, google, working
1723
        // For example: to restrict to only local resources, except for css (cloudflare+google), and js (transifex + google tags) and object/iframe (youtube)
1724
        // default-src 'self'; style-src: https://cdnjs.cloudflare.com https://fonts.googleapis.com; script-src: https://cdn.transifex.com https://www.googletagmanager.com; object-src https://youtube.com; frame-src https://youtube.com; img-src: *;
1725
        // For example, to restrict everything to itself except img that can be on other servers:
1726
        // default-src 'self'; img-src *;
1727
        // Pre-existing site that uses too much js code to fix but wants to ensure resources are loaded only over https and disable plugins:
1728
        // default-src https: 'unsafe-inline' 'unsafe-eval'; object-src 'none'
1729
        //
1730
        // $contentsecuritypolicy = "frame-ancestors 'self'; img-src * data:; font-src *; default-src 'self' 'unsafe-inline' 'unsafe-eval' *.paypal.com *.stripe.com *.google.com *.googleapis.com *.google-analytics.com *.googletagmanager.com;";
1731
        // $contentsecuritypolicy = "frame-ancestors 'self'; img-src * data:; font-src *; default-src *; script-src 'self' 'unsafe-inline' *.paypal.com *.stripe.com *.google.com *.googleapis.com *.google-analytics.com *.googletagmanager.com; style-src 'self' 'unsafe-inline'; connect-src 'self';";
1732
        $contentsecuritypolicy = getDolGlobalString('MAIN_SECURITY_FORCECSPRO');
1733
1734
        if (!is_object($hookmanager)) {
1735
            $hookmanager = new HookManager($db);
1736
        }
1737
        $hookmanager->initHooks(array("main"));
1738
1739
        $parameters = array('contentsecuritypolicy' => $contentsecuritypolicy, 'mode' => 'reportonly');
1740
        $result = $hookmanager->executeHooks('setContentSecurityPolicy', $parameters); // Note that $action and $object may have been modified by some hooks
1741
        if ($result > 0) {
1742
            $contentsecuritypolicy = $hookmanager->resPrint; // Replace CSP
1743
        } else {
1744
            $contentsecuritypolicy .= $hookmanager->resPrint; // Concat CSP
1745
        }
1746
1747
        if (!empty($contentsecuritypolicy)) {
1748
            header("Content-Security-Policy-Report-Only: " . $contentsecuritypolicy);
1749
        }
1750
    } else {
1751
        header("Content-Security-Policy: " . constant('MAIN_SECURITY_FORCECSPRO'));
1752
    }
1753
1754
    // Content-Security-Policy
1755
    if (!defined('MAIN_SECURITY_FORCECSP')) {
1756
        // If CSP not forced from the page
1757
1758
        // A default security policy that keep usage of js external component like ckeditor, stripe, google, working
1759
        // For example: to restrict to only local resources, except for css (cloudflare+google), and js (transifex + google tags) and object/iframe (youtube)
1760
        // default-src 'self'; style-src: https://cdnjs.cloudflare.com https://fonts.googleapis.com; script-src: https://cdn.transifex.com https://www.googletagmanager.com; object-src https://youtube.com; frame-src https://youtube.com; img-src: *;
1761
        // For example, to restrict everything to itself except img that can be on other servers:
1762
        // default-src 'self'; img-src *;
1763
        // Pre-existing site that uses too much js code to fix but wants to ensure resources are loaded only over https and disable plugins:
1764
        // default-src https: 'unsafe-inline' 'unsafe-eval'; object-src 'none'
1765
        //
1766
        // $contentsecuritypolicy = "frame-ancestors 'self'; img-src * data:; font-src *; default-src 'self' 'unsafe-inline' 'unsafe-eval' *.paypal.com *.stripe.com *.google.com *.googleapis.com *.google-analytics.com *.googletagmanager.com;";
1767
        // $contentsecuritypolicy = "frame-ancestors 'self'; img-src * data:; font-src *; default-src *; script-src 'self' 'unsafe-inline' *.paypal.com *.stripe.com *.google.com *.googleapis.com *.google-analytics.com *.googletagmanager.com; style-src 'self' 'unsafe-inline'; connect-src 'self';";
1768
        $contentsecuritypolicy = getDolGlobalString('MAIN_SECURITY_FORCECSP');
1769
1770
        if (!is_object($hookmanager)) {
1771
            $hookmanager = new HookManager($db);
1772
        }
1773
        $hookmanager->initHooks(array("main"));
1774
1775
        $parameters = array('contentsecuritypolicy' => $contentsecuritypolicy, 'mode' => 'active');
1776
        $result = $hookmanager->executeHooks('setContentSecurityPolicy', $parameters); // Note that $action and $object may have been modified by some hooks
1777
        if ($result > 0) {
1778
            $contentsecuritypolicy = $hookmanager->resPrint; // Replace CSP
1779
        } else {
1780
            $contentsecuritypolicy .= $hookmanager->resPrint; // Concat CSP
1781
        }
1782
1783
        if (!empty($contentsecuritypolicy)) {
1784
            header("Content-Security-Policy: " . $contentsecuritypolicy);
1785
        }
1786
    } else {
1787
        header("Content-Security-Policy: " . constant('MAIN_SECURITY_FORCECSP'));
1788
    }
1789
1790
    // Referrer-Policy
1791
    // Say if we must provide the referrer when we jump onto another web page.
1792
    // Default browser are 'strict-origin-when-cross-origin' (only domain is sent on other domain switching), we want more so we use 'same-origin' so browser doesn't send any referrer at all when going into another web site domain.
1793
    // Note that we do not use 'strict-origin' as this breaks feature to restore filters when clicking on "back to page" link on some cases.
1794
    if (!defined('MAIN_SECURITY_FORCERP')) {
1795
        $referrerpolicy = getDolGlobalString('MAIN_SECURITY_FORCERP', "same-origin");
1796
1797
        header("Referrer-Policy: " . $referrerpolicy);
1798
    }
1799
1800
    if ($forcenocache) {
1801
        header("Cache-Control: no-cache, no-store, must-revalidate, max-age=0");
1802
    }
1803
1804
    // No need to add this token in header, we use instead the one into the forms.
1805
    //header("anti-csrf-token: ".newToken());
1806
}
1807
1808
/**
1809
 * Output html header of a page. It calls also top_httphead()
1810
 * This code is also duplicated into security2.lib.php::dol_loginfunction
1811
 *
1812
 * @param string $head Optional head lines
1813
 * @param string $title HTML title
1814
 * @param int<0,1> $disablejs Disable js output
1815
 * @param int<0,1> $disablehead Disable head output
1816
 * @param string[] $arrayofjs Array of complementary js files
1817
 * @param string[] $arrayofcss Array of complementary css files
1818
 * @param int<0,1> $disableforlogin Do not load heavy js and css for login pages
1819
 * @param int<0,1> $disablenofollow Disable nofollow tag for meta robots
1820
 * @param int<0,1> $disablenoindex Disable noindex tag for meta robots
1821
 * @return  void
1822
 */
1823
function top_htmlhead($head, $title = '', $disablejs = 0, $disablehead = 0, $arrayofjs = array(), $arrayofcss = array(), $disableforlogin = 0, $disablenofollow = 0, $disablenoindex = 0)
1824
{
1825
    global $db, $conf, $langs, $user, $mysoc, $hookmanager;
1826
1827
    top_httphead();
1828
1829
    if (empty($conf->css)) {
1830
        $conf->css = '/theme/eldy/style.css.php'; // If not defined, eldy by default
1831
    }
1832
1833
    print '<!doctype html>' . "\n";
1834
1835
    print '<html lang="' . substr($langs->defaultlang, 0, 2) . '">' . "\n";
1836
1837
    //print '<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="fr">'."\n";
1838
    if (empty($disablehead)) {
1839
        if (!is_object($hookmanager)) {
1840
            $hookmanager = new HookManager($db);
1841
        }
1842
        $hookmanager->initHooks(array("main"));
1843
1844
        $ext = 'layout=' . (empty($conf->browser->layout) ? '' : $conf->browser->layout) . '&amp;version=' . urlencode(DOL_VERSION);
1845
1846
        print "<head>\n";
1847
1848
        if (GETPOST('dol_basehref', 'alpha')) {
1849
            print '<base href="' . dol_escape_htmltag(GETPOST('dol_basehref', 'alpha')) . '">' . "\n";
1850
        }
1851
1852
        // Displays meta
1853
        print '<meta charset="utf-8">' . "\n";
1854
        print '<meta name="robots" content="' . ($disablenoindex ? 'index' : 'noindex') . ($disablenofollow ? ',follow' : ',nofollow') . '">' . "\n"; // Do not index
1855
        print '<meta name="viewport" content="width=device-width, initial-scale=1.0">' . "\n"; // Scale for mobile device
1856
        print '<meta name="author" content="Dolibarr Development Team">' . "\n";
1857
        print '<meta name="anti-csrf-newtoken" content="' . newToken() . '">' . "\n";
1858
        print '<meta name="anti-csrf-currenttoken" content="' . currentToken() . '">' . "\n";
1859
        if (getDolGlobalInt('MAIN_FEATURES_LEVEL')) {
1860
            print '<meta name="MAIN_FEATURES_LEVEL" content="' . getDolGlobalInt('MAIN_FEATURES_LEVEL') . '">' . "\n";
1861
        }
1862
        // Favicon
1863
        $favicon = constant('DOL_URL_ROOT') . '/theme/alixar_square_logo_256x256_color.png';
1864
        if (!empty($mysoc->logo_squarred_mini)) {
1865
            $favicon = constant('BASE_URL') . '/viewimage.php?cache=1&modulepart=mycompany&file=' . urlencode('logos/thumbs/' . $mysoc->logo_squarred_mini);
1866
        }
1867
        if (getDolGlobalString('MAIN_FAVICON_URL')) {
1868
            $favicon = getDolGlobalString('MAIN_FAVICON_URL');
1869
        }
1870
        if (empty($conf->dol_use_jmobile)) {
1871
            print '<link rel="shortcut icon" type="image/x-icon" href="' . $favicon . '"/>' . "\n"; // Not required into an Android webview
1872
        }
1873
1874
        // Mobile appli like icon
1875
        $manifest = constant('DOL_URL_ROOT') . '/theme/' . $conf->theme . '/manifest.json.php';
1876
        $parameters = array('manifest' => $manifest);
1877
        $resHook = $hookmanager->executeHooks('hookSetManifest', $parameters); // Note that $action and $object may have been modified by some hooks
1878
        if ($resHook > 0) {
1879
            $manifest = $hookmanager->resPrint; // Replace manifest.json
1880
        } else {
1881
            $manifest .= $hookmanager->resPrint; // Concat to actual manifest declaration
1882
        }
1883
        if (!empty($manifest)) {
1884
            print '<link rel="manifest" href="' . $manifest . '" />' . "\n";
1885
        }
1886
1887
        if (getDolGlobalString('THEME_ELDY_TOPMENU_BACK1')) {
1888
            // TODO: use auto theme color switch
1889
            print '<meta name="theme-color" content="rgb(' . getDolGlobalString('THEME_ELDY_TOPMENU_BACK1') . ')">' . "\n";
1890
        }
1891
1892
        // Auto refresh page
1893
        if (GETPOSTINT('autorefresh') > 0) {
1894
            print '<meta http-equiv="refresh" content="' . GETPOSTINT('autorefresh') . '">';
1895
        }
1896
1897
        // Displays title
1898
        $appli = constant('DOL_APPLICATION_TITLE');
1899
        if (getDolGlobalString('MAIN_APPLICATION_TITLE')) {
1900
            $appli = getDolGlobalString('MAIN_APPLICATION_TITLE');
1901
        }
1902
1903
        print '<title>';
1904
        $titletoshow = '';
1905
        if ($title && preg_match('/showapp/', getDolGlobalString('MAIN_HTML_TITLE'))) {
1906
            $titletoshow = dol_htmlentities($appli . ' - ' . $title);
1907
        } elseif ($title) {
1908
            $titletoshow = dol_htmlentities($title);
1909
        } else {
1910
            $titletoshow = dol_htmlentities($appli);
1911
        }
1912
1913
        $parameters = array('title' => $titletoshow);
1914
        $result = $hookmanager->executeHooks('setHtmlTitle', $parameters); // Note that $action and $object may have been modified by some hooks
1915
        if ($result > 0) {
1916
            $titletoshow = $hookmanager->resPrint; // Replace Title to show
1917
        } else {
1918
            $titletoshow .= $hookmanager->resPrint; // Concat to Title to show
1919
        }
1920
1921
        print $titletoshow;
1922
        print '</title>';
1923
1924
        print "\n";
1925
1926
        if (GETPOSTINT('version')) {
1927
            $ext = 'version=' . GETPOSTINT('version'); // useful to force no cache on css/js
1928
        }
1929
        // Refresh value of MAIN_IHM_PARAMS_REV before forging the parameter line.
1930
        if (GETPOST('dol_resetcache')) {
1931
            dolibarr_set_const($db, "MAIN_IHM_PARAMS_REV", getDolGlobalInt('MAIN_IHM_PARAMS_REV') + 1, 'chaine', 0, '', $conf->entity);
1932
        }
1933
1934
        $themeparam = '?lang=' . $langs->defaultlang . '&amp;theme=' . $conf->theme . (GETPOST('optioncss', 'aZ09') ? '&amp;optioncss=' . GETPOST('optioncss', 'aZ09', 1) : '') . (empty($user->id) ? '' : ('&amp;userid=' . $user->id)) . '&amp;entity=' . $conf->entity;
1935
1936
        $themeparam .= ($ext ? '&amp;' . $ext : '') . '&amp;revision=' . getDolGlobalInt("MAIN_IHM_PARAMS_REV");
1937
        if (GETPOSTISSET('dol_hide_topmenu')) {
1938
            $themeparam .= '&amp;dol_hide_topmenu=' . GETPOSTINT('dol_hide_topmenu');
1939
        }
1940
        if (GETPOSTISSET('dol_hide_leftmenu')) {
1941
            $themeparam .= '&amp;dol_hide_leftmenu=' . GETPOSTINT('dol_hide_leftmenu');
1942
        }
1943
        if (GETPOSTISSET('dol_optimize_smallscreen')) {
1944
            $themeparam .= '&amp;dol_optimize_smallscreen=' . GETPOSTINT('dol_optimize_smallscreen');
1945
        }
1946
        if (GETPOSTISSET('dol_no_mouse_hover')) {
1947
            $themeparam .= '&amp;dol_no_mouse_hover=' . GETPOSTINT('dol_no_mouse_hover');
1948
        }
1949
        if (GETPOSTISSET('dol_use_jmobile')) {
1950
            $themeparam .= '&amp;dol_use_jmobile=' . GETPOSTINT('dol_use_jmobile');
1951
            $conf->dol_use_jmobile = GETPOSTINT('dol_use_jmobile');
1952
        }
1953
        if (GETPOSTISSET('THEME_DARKMODEENABLED')) {
1954
            $themeparam .= '&amp;THEME_DARKMODEENABLED=' . GETPOSTINT('THEME_DARKMODEENABLED');
1955
        }
1956
        if (GETPOSTISSET('THEME_SATURATE_RATIO')) {
1957
            $themeparam .= '&amp;THEME_SATURATE_RATIO=' . GETPOSTINT('THEME_SATURATE_RATIO');
1958
        }
1959
1960
        if (getDolGlobalString('MAIN_ENABLE_FONT_ROBOTO')) {
1961
            print '<link rel="preconnect" href="https://fonts.gstatic.com">' . "\n";
1962
            print '<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@200;300;400;500;600&display=swap" rel="stylesheet">' . "\n";
1963
        }
1964
1965
        if (!defined('DISABLE_JQUERY') && !$disablejs && $conf->use_javascript_ajax) {
1966
            print '<!-- Includes CSS for JQuery (Ajax library) -->' . "\n";
1967
            $jquerytheme = 'base';
1968
            if (getDolGlobalString('MAIN_USE_JQUERY_THEME')) {
1969
                $jquerytheme = getDolGlobalString('MAIN_USE_JQUERY_THEME');
1970
            }
1971
            if (constant('JS_JQUERY_UI')) {
1972
                print '<link rel="stylesheet" type="text/css" href="' . JS_JQUERY_UI . 'css/' . $jquerytheme . '/jquery-ui.min.css' . ($ext ? '?' . $ext : '') . '">' . "\n"; // Forced JQuery
1973
            } else {
1974
                print '<link rel="stylesheet" type="text/css" href="' . constant('DOL_URL_ROOT') . '/includes/jquery/css/' . $jquerytheme . '/jquery-ui.css' . ($ext ? '?' . $ext : '') . '">' . "\n"; // JQuery
1975
            }
1976
            if (!defined('DISABLE_JQUERY_JNOTIFY')) {
1977
                print '<link rel="stylesheet" type="text/css" href="' . constant('DOL_URL_ROOT') . '/includes/jquery/plugins/jnotify/jquery.jnotify-alt.min.css' . ($ext ? '?' . $ext : '') . '">' . "\n"; // JNotify
1978
            }
1979
            if (!defined('DISABLE_SELECT2') && (getDolGlobalString('MAIN_USE_JQUERY_MULTISELECT') || defined('REQUIRE_JQUERY_MULTISELECT'))) {     // jQuery plugin "mutiselect", "multiple-select", "select2"...
1980
                $tmpplugin = !getDolGlobalString('MAIN_USE_JQUERY_MULTISELECT') ? constant('REQUIRE_JQUERY_MULTISELECT') : $conf->global->MAIN_USE_JQUERY_MULTISELECT;
1981
                print '<link rel="stylesheet" type="text/css" href="' . constant('DOL_URL_ROOT') . '/includes/jquery/plugins/' . $tmpplugin . '/dist/css/' . $tmpplugin . '.css' . ($ext ? '?' . $ext : '') . '">' . "\n";
1982
            }
1983
        }
1984
1985
        if (!defined('DISABLE_FONT_AWSOME')) {
1986
            print '<!-- Includes CSS for font awesome -->' . "\n";
1987
            $fontawesome_directory = getDolGlobalString('MAIN_FONTAWESOME_DIRECTORY', '/theme/common/fontawesome-5');
1988
            print '<link rel="stylesheet" type="text/css" href="' . constant('DOL_URL_ROOT') . $fontawesome_directory . '/css/all.min.css' . ($ext ? '?' . $ext : '') . '">' . "\n";
1989
        }
1990
1991
        print '<!-- Includes CSS for Dolibarr theme -->' . "\n";
1992
        // Output style sheets (optioncss='print' or ''). Note: $conf->css looks like '/theme/eldy/style.css.php'
1993
        $themepath = dol_buildpath($conf->css, 1);
1994
        $themesubdir = '';
1995
        if (!empty($conf->modules_parts['theme'])) {    // This slow down
1996
            foreach ($conf->modules_parts['theme'] as $reldir) {
1997
                if (file_exists(dol_buildpath($reldir . $conf->css, 0))) {
1998
                    $themepath = dol_buildpath($reldir . $conf->css, 1);
1999
                    $themesubdir = $reldir;
2000
                    break;
2001
                }
2002
            }
2003
        }
2004
2005
        //print 'themepath='.$themepath.' themeparam='.$themeparam;exit;
2006
        print '<link rel="stylesheet" type="text/css" href="' . $themepath . $themeparam . '">' . "\n";
2007
        if (getDolGlobalString('MAIN_FIX_FLASH_ON_CHROME')) {
2008
            print '<!-- Includes CSS that does not exists as a workaround of flash bug of chrome -->' . "\n" . '<link rel="stylesheet" type="text/css" href="filethatdoesnotexiststosolvechromeflashbug">' . "\n";
2009
        }
2010
2011
        // LEAFLET AND GEOMAN
2012
        if (getDolGlobalString('MAIN_USE_GEOPHP')) {
2013
            print '<link rel="stylesheet" href="' . constant('BASE_URL') . '/includes/leaflet/leaflet.css' . ($ext ? '?' . $ext : '') . "\">\n";
2014
            print '<link rel="stylesheet" href="' . constant('BASE_URL') . '/includes/leaflet/leaflet-geoman.css' . ($ext ? '?' . $ext : '') . "\">\n";
2015
        }
2016
2017
        // CSS forced by modules (relative url starting with /)
2018
        if (!empty($conf->modules_parts['css'])) {
2019
            $arraycss = (array)$conf->modules_parts['css'];
2020
            foreach ($arraycss as $modcss => $filescss) {
2021
                $filescss = (array)$filescss; // To be sure filecss is an array
2022
                foreach ($filescss as $cssfile) {
2023
                    if (empty($cssfile)) {
2024
                        dol_syslog("Warning: module " . $modcss . " declared a css path file into its descriptor that is empty.", LOG_WARNING);
2025
                    }
2026
                    // cssfile is a relative path
2027
                    $urlforcss = dol_buildpath($cssfile, 1);
2028
                    if ($urlforcss && $urlforcss != '/') {
2029
                        print '<!-- Includes CSS added by module ' . $modcss . ' -->' . "\n" . '<link rel="stylesheet" type="text/css" href="' . $urlforcss;
2030
                        // We add params only if page is not static, because some web server setup does not return content type text/css if url has parameters, so browser cache is not used.
2031
                        if (!preg_match('/\.css$/i', $cssfile)) {
2032
                            print $themeparam;
2033
                        }
2034
                        print '">' . "\n";
2035
                    } else {
2036
                        dol_syslog("Warning: module " . $modcss . " declared a css path file for a file we can't find.", LOG_WARNING);
2037
                    }
2038
                }
2039
            }
2040
        }
2041
        // CSS forced by page in top_htmlhead call (relative url starting with /)
2042
        if (is_array($arrayofcss)) {
2043
            foreach ($arrayofcss as $cssfile) {
2044
                // $cssfile = '/htdocs' . $cssfile;
2045
                if (preg_match('/^(http|\/\/)/i', $cssfile)) {
2046
                    $urltofile = $cssfile;
2047
                } else {
2048
                    $urltofile = dol_buildpath($cssfile, 1);
2049
                }
2050
                print '<!-- Includes CSS added by page -->' . "\n" . '<link rel="stylesheet" type="text/css" title="default" href="' . $urltofile;
2051
                // We add params only if page is not static, because some web server setup does not return content type text/css if url has parameters and browser cache is not used.
2052
                if (!preg_match('/\.css$/i', $cssfile)) {
2053
                    print $themeparam;
2054
                }
2055
                print '">' . "\n";
2056
            }
2057
        }
2058
2059
        // Custom CSS
2060
        if (getDolGlobalString('MAIN_IHM_CUSTOM_CSS')) {
2061
            // If a custom CSS was set, we add link to the custom css php file
2062
            print '<link rel="stylesheet" type="text/css" href="' . constant('DOL_URL_ROOT') . '/theme/custom.css.php' . ($ext ? '?' . $ext : '') . '&amp;revision=' . getDolGlobalInt("MAIN_IHM_PARAMS_REV") . '">' . "\n";
2063
        }
2064
2065
        // Output standard javascript links
2066
        if (!defined('DISABLE_JQUERY') && !$disablejs && !empty($conf->use_javascript_ajax)) {
2067
            // JQuery. Must be before other includes
2068
            print '<!-- Includes JS for JQuery -->' . "\n";
2069
            if (defined('JS_JQUERY') && constant('JS_JQUERY')) {
2070
                print '<script nonce="' . getNonce() . '" src="' . JS_JQUERY . 'jquery.min.js' . ($ext ? '?' . $ext : '') . '"></script>' . "\n";
2071
            } else {
2072
                print '<script nonce="' . getNonce() . '" src="' . constant('DOL_URL_ROOT') . '/includes/jquery/js/jquery.min.js' . ($ext ? '?' . $ext : '') . '"></script>' . "\n";
2073
            }
2074
            if (defined('JS_JQUERY_UI') && constant('JS_JQUERY_UI')) {
2075
                print '<script nonce="' . getNonce() . '" src="' . JS_JQUERY_UI . 'jquery-ui.min.js' . ($ext ? '?' . $ext : '') . '"></script>' . "\n";
2076
            } else {
2077
                print '<script nonce="' . getNonce() . '" src="' . constant('DOL_URL_ROOT') . '/includes/jquery/js/jquery-ui.min.js' . ($ext ? '?' . $ext : '') . '"></script>' . "\n";
2078
            }
2079
            // jQuery jnotify
2080
            if (!getDolGlobalString('MAIN_DISABLE_JQUERY_JNOTIFY') && !defined('DISABLE_JQUERY_JNOTIFY')) {
2081
                print '<script nonce="' . getNonce() . '" src="' . constant('DOL_URL_ROOT') . '/includes/jquery/plugins/jnotify/jquery.jnotify.min.js' . ($ext ? '?' . $ext : '') . '"></script>' . "\n";
2082
            }
2083
            // Table drag and drop lines
2084
            if (empty($disableforlogin) && !defined('DISABLE_JQUERY_TABLEDND')) {
2085
                print '<script nonce="' . getNonce() . '" src="' . constant('DOL_URL_ROOT') . '/includes/jquery/plugins/tablednd/jquery.tablednd.min.js' . ($ext ? '?' . $ext : '') . '"></script>' . "\n";
2086
            }
2087
            // Chart
2088
            if (empty($disableforlogin) && (!getDolGlobalString('MAIN_JS_GRAPH') || getDolGlobalString('MAIN_JS_GRAPH') == 'chart') && !defined('DISABLE_JS_GRAPH')) {
2089
                print '<script nonce="' . getNonce() . '" src="' . constant('DOL_URL_ROOT') . '/includes/nnnick/chartjs/dist/chart.min.js' . ($ext ? '?' . $ext : '') . '"></script>' . "\n";
2090
            }
2091
2092
            // jQuery jeditable for Edit In Place features
2093
            if (getDolGlobalString('MAIN_USE_JQUERY_JEDITABLE') && !defined('DISABLE_JQUERY_JEDITABLE')) {
2094
                print '<!-- JS to manage editInPlace feature -->' . "\n";
2095
                print '<script nonce="' . getNonce() . '" src="' . constant('DOL_URL_ROOT') . '/includes/jquery/plugins/jeditable/jquery.jeditable.js' . ($ext ? '?' . $ext : '') . '"></script>' . "\n";
2096
                print '<script nonce="' . getNonce() . '" src="' . constant('DOL_URL_ROOT') . '/includes/jquery/plugins/jeditable/jquery.jeditable.ui-datepicker.js' . ($ext ? '?' . $ext : '') . '"></script>' . "\n";
2097
                print '<script nonce="' . getNonce() . '" src="' . constant('DOL_URL_ROOT') . '/includes/jquery/plugins/jeditable/jquery.jeditable.ui-autocomplete.js' . ($ext ? '?' . $ext : '') . '"></script>' . "\n";
2098
                print '<script>' . "\n";
2099
                print 'var urlSaveInPlace = \'' . constant('DOL_URL_ROOT') . '/core/ajax/saveinplace.php\';' . "\n";
2100
                print 'var urlLoadInPlace = \'' . constant('DOL_URL_ROOT') . '/core/ajax/loadinplace.php\';' . "\n";
2101
                print 'var tooltipInPlace = \'' . $langs->transnoentities('ClickToEdit') . '\';' . "\n"; // Added in title attribute of span
2102
                print 'var placeholderInPlace = \'&nbsp;\';' . "\n"; // If we put another string than $langs->trans("ClickToEdit") here, nothing is shown. If we put empty string, there is error, Why ?
2103
                print 'var cancelInPlace = \'' . $langs->trans("Cancel") . '\';' . "\n";
2104
                print 'var submitInPlace = \'' . $langs->trans('Ok') . '\';' . "\n";
2105
                print 'var indicatorInPlace = \'<img src="' . constant('DOL_URL_ROOT') . "/theme/" . $conf->theme . "/img/working.gif" . '">\';' . "\n";
2106
                print 'var withInPlace = 300;'; // width in pixel for default string edit
2107
                print '</script>' . "\n";
2108
                print '<script nonce="' . getNonce() . '" src="' . constant('DOL_URL_ROOT') . '/core/js/editinplace.js' . ($ext ? '?' . $ext : '') . '"></script>' . "\n";
2109
                print '<script nonce="' . getNonce() . '" src="' . constant('DOL_URL_ROOT') . '/includes/jquery/plugins/jeditable/jquery.jeditable.ckeditor.js' . ($ext ? '?' . $ext : '') . '"></script>' . "\n";
2110
            }
2111
            // jQuery Timepicker
2112
            if (getDolGlobalString('MAIN_USE_JQUERY_TIMEPICKER') || defined('REQUIRE_JQUERY_TIMEPICKER')) {
2113
                print '<script nonce="' . getNonce() . '" src="' . constant('DOL_URL_ROOT') . '/includes/jquery/plugins/timepicker/jquery-ui-timepicker-addon.js' . ($ext ? '?' . $ext : '') . '"></script>' . "\n";
2114
                print '<script nonce="' . getNonce() . '" src="' . constant('DOL_URL_ROOT') . '/core/js/timepicker.js.php?lang=' . $langs->defaultlang . ($ext ? '&amp;' . $ext : '') . '"></script>' . "\n";
2115
            }
2116
            if (!defined('DISABLE_SELECT2') && (getDolGlobalString('MAIN_USE_JQUERY_MULTISELECT') || defined('REQUIRE_JQUERY_MULTISELECT'))) {
2117
                // jQuery plugin "mutiselect", "multiple-select", "select2", ...
2118
                $tmpplugin = !getDolGlobalString('MAIN_USE_JQUERY_MULTISELECT') ? constant('REQUIRE_JQUERY_MULTISELECT') : $conf->global->MAIN_USE_JQUERY_MULTISELECT;
2119
                print '<script nonce="' . getNonce() . '" src="' . constant('DOL_URL_ROOT') . '/includes/jquery/plugins/' . $tmpplugin . '/dist/js/' . $tmpplugin . '.full.min.js' . ($ext ? '?' . $ext : '') . '"></script>' . "\n"; // We include full because we need the support of containerCssClass
2120
            }
2121
            if (!defined('DISABLE_MULTISELECT')) {     // jQuery plugin "mutiselect" to select with checkboxes. Can be removed once we have an enhanced search tool
2122
                print '<script nonce="' . getNonce() . '" src="' . constant('DOL_URL_ROOT') . '/includes/jquery/plugins/multiselect/jquery.multi-select.js' . ($ext ? '?' . $ext : '') . '"></script>' . "\n";
2123
            }
2124
        }
2125
2126
        if (!$disablejs && !empty($conf->use_javascript_ajax)) {
2127
            // CKEditor
2128
            if (empty($disableforlogin) && (isModEnabled('fckeditor') && (!getDolGlobalString('FCKEDITOR_EDITORNAME') || getDolGlobalString('FCKEDITOR_EDITORNAME') == 'ckeditor') && !defined('DISABLE_CKEDITOR')) || defined('FORCE_CKEDITOR')) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (empty($disableforlogin)...fined('FORCE_CKEDITOR'), Probably Intended Meaning: empty($disableforlogin) ...ined('FORCE_CKEDITOR'))
Loading history...
2129
                print '<!-- Includes JS for CKEditor -->' . "\n";
2130
                $pathckeditor = constant('DOL_URL_ROOT') . '/includes/ckeditor/ckeditor/';
2131
                $jsckeditor = 'ckeditor.js';
2132
                if (constant('JS_CKEDITOR')) {
2133
                    // To use external ckeditor 4 js lib
2134
                    $pathckeditor = constant('JS_CKEDITOR');
2135
                }
2136
                print '<script nonce="' . getNonce() . '">';
2137
                print '/* enable ckeditor by main.inc.php */';
2138
                print 'var CKEDITOR_BASEPATH = \'' . dol_escape_js($pathckeditor) . '\';' . "\n";
2139
                print 'var ckeditorConfig = \'' . dol_escape_js(dol_buildpath('/htdocs' . $themesubdir . '/theme/' . $conf->theme . '/ckeditor/config.js' . ($ext ? '?' . $ext : ''), 1)) . '\';' . "\n"; // $themesubdir='' in standard usage
2140
                print 'var ckeditorFilebrowserBrowseUrl = \'' . constant('DOL_URL_ROOT') . '/core/filemanagerdol/browser/default/browser.php?Connector=' . constant('BASE_URL') . '/core/filemanagerdol/connectors/php/connector.php\';' . "\n";
2141
                print 'var ckeditorFilebrowserImageBrowseUrl = \'' . constant('DOL_URL_ROOT') . '/core/filemanagerdol/browser/default/browser.php?Type=Image&Connector=' . constant('BASE_URL') . '/core/filemanagerdol/connectors/php/connector.php\';' . "\n";
2142
                print '</script>' . "\n";
2143
                print '<script src="' . $pathckeditor . $jsckeditor . ($ext ? '?' . $ext : '') . '"></script>' . "\n";
2144
                print '<script>';
2145
                if (GETPOST('mode', 'aZ09') == 'Full_inline') {
2146
                    print 'CKEDITOR.disableAutoInline = false;' . "\n";
2147
                } else {
2148
                    print 'CKEDITOR.disableAutoInline = true;' . "\n";
2149
                }
2150
                print '</script>' . "\n";
2151
            }
2152
2153
            // Browser notifications (if NOREQUIREMENU is on, it is mostly a page for popup, so we do not enable notif too. We hide also for public pages).
2154
            if (!defined('NOBROWSERNOTIF') && !defined('NOREQUIREMENU') && !defined('NOLOGIN')) {
2155
                $enablebrowsernotif = false;
2156
                if (isModEnabled('agenda') && getDolGlobalString('AGENDA_REMINDER_BROWSER')) {
2157
                    $enablebrowsernotif = true;
2158
                }
2159
                if ($conf->browser->layout == 'phone') {
2160
                    $enablebrowsernotif = false;
2161
                }
2162
                if ($enablebrowsernotif) {
2163
                    print '<!-- Includes JS of Dolibarr (browser layout = ' . $conf->browser->layout . ')-->' . "\n";
2164
                    print '<script nonce="' . getNonce() . '" src="' . constant('DOL_URL_ROOT') . '/core/js/lib_notification.js.php' . ($ext ? '?' . $ext : '') . '"></script>' . "\n";
2165
                }
2166
            }
2167
2168
            // Global js function
2169
            print '<!-- Includes JS of Dolibarr -->' . "\n";
2170
            print '<script nonce="' . getNonce() . '" src="' . BASE_URL . '/core/js/lib_head.js.php?lang=' . $langs->defaultlang . ($ext ? '&amp;' . $ext : '') . '"></script>' . "\n";
2171
2172
            // Leaflet TODO use dolibarr files
2173
            if (getDolGlobalString('MAIN_USE_GEOPHP')) {
2174
                print '<script nonce="' . getNonce() . '" src="' . constant('DOL_URL_ROOT') . '/includes/leaflet/leaflet.js' . ($ext ? '?' . $ext : '') . '"></script>' . "\n";
2175
                print '<script nonce="' . getNonce() . '" src="' . constant('DOL_URL_ROOT') . '/includes/leaflet/leaflet-geoman.min.js' . ($ext ? '?' . $ext : '') . '"></script>' . "\n";
2176
            }
2177
2178
            // JS forced by modules (relative url starting with /)
2179
            if (!empty($conf->modules_parts['js'])) {       // $conf->modules_parts['js'] is array('module'=>array('file1','file2'))
2180
                $arrayjs = (array)$conf->modules_parts['js'];
2181
                foreach ($arrayjs as $modjs => $filesjs) {
2182
                    $filesjs = (array)$filesjs; // To be sure filejs is an array
2183
                    foreach ($filesjs as $jsfile) {
2184
                        // jsfile is a relative path
2185
                        $urlforjs = dol_buildpath($jsfile, 3);
2186
                        if ($urlforjs && $urlforjs != '/') {
2187
                            print '<!-- Include JS added by module ' . $modjs . '-->' . "\n";
2188
                            print '<script nonce="' . getNonce() . '" src="' . $urlforjs . ((strpos($jsfile, '?') === false) ? '?' : '&amp;') . 'lang=' . $langs->defaultlang . '"></script>' . "\n";
2189
                        } else {
2190
                            dol_syslog("Warning: module " . $modjs . " declared a js path file for a file we can't find.", LOG_WARNING);
2191
                        }
2192
                    }
2193
                }
2194
            }
2195
            // JS forced by page in top_htmlhead (relative url starting with /)
2196
            if (is_array($arrayofjs)) {
2197
                print '<!-- Includes JS added by page -->' . "\n";
2198
                foreach ($arrayofjs as $jsfile) {
2199
                    // $jsfile = '/htdocs' . $jsfile;
2200
                    if (preg_match('/^(http|\/\/)/i', $jsfile)) {
2201
                        print '<script nonce="' . getNonce() . '" src="' . $jsfile . ((strpos($jsfile, '?') === false) ? '?' : '&amp;') . 'lang=' . $langs->defaultlang . '"></script>' . "\n";
2202
                    } else {
2203
                        print '<script nonce="' . getNonce() . '" src="' . dol_buildpath($jsfile, 3) . ((strpos($jsfile, '?') === false) ? '?' : '&amp;') . 'lang=' . $langs->defaultlang . '"></script>' . "\n";
2204
                    }
2205
                }
2206
            }
2207
        }
2208
2209
        //If you want to load custom javascript file from your selected theme directory
2210
        if (getDolGlobalString('ALLOW_THEME_JS')) {
2211
            $theme_js = dol_buildpath('/theme/' . $conf->theme . '/' . $conf->theme . '.js', 0);
2212
            if (file_exists($theme_js)) {
2213
                print '<script nonce="' . getNonce() . '" src="' . constant('DOL_URL_ROOT') . '/theme/' . $conf->theme . '/' . $conf->theme . '.js' . ($ext ? '?' . $ext : '') . '"></script>' . "\n";
2214
            }
2215
        }
2216
2217
        if (!empty($head)) {
2218
            print $head . "\n";
2219
        }
2220
        if (getDolGlobalString('MAIN_HTML_HEADER')) {
2221
            print getDolGlobalString('MAIN_HTML_HEADER') . "\n";
2222
        }
2223
2224
        $parameters = array();
2225
        $result = $hookmanager->executeHooks('addHtmlHeader', $parameters); // Note that $action and $object may have been modified by some hooks
2226
        print $hookmanager->resPrint; // Replace Title to show
2227
2228
        print "</head>\n\n";
2229
    }
2230
2231
    $conf->headerdone = 1; // To tell header was output
2232
}
2233
2234
2235
/**
2236
 *  Show an HTML header + a BODY + The top menu bar
2237
 *
2238
 * @param string $head Lines in the HEAD
2239
 * @param string $title Title of web page
2240
 * @param string $target Target to use in menu links (Example: '' or '_top')
2241
 * @param int<0,1> $disablejs Do not output links to js (Ex: qd fonction utilisee par sous formulaire Ajax)
2242
 * @param int<0,1> $disablehead Do not output head section
2243
 * @param string[] $arrayofjs Array of js files to add in header
2244
 * @param string[] $arrayofcss Array of css files to add in header
2245
 * @param string $morequerystring Query string to add to the link "print" to get same parameters (use only if autodetect fails)
2246
 * @param string $helppagename Name of wiki page for help ('' by default).
2247
 *                                                  Syntax is: For a wiki page: EN:EnglishPage|FR:FrenchPage|ES:SpanishPage|DE:GermanPage
2248
 *                                                  For other external page: http://server/url
2249
 * @return     void
2250
 */
2251
function top_menu($head, $title = '', $target = '', $disablejs = 0, $disablehead = 0, $arrayofjs = array(), $arrayofcss = array(), $morequerystring = '', $helppagename = '')
2252
{
2253
    global $user, $conf, $langs, $db, $form;
2254
    global $dolibarr_main_authentication, $dolibarr_main_demo;
2255
    global $hookmanager, $menumanager;
2256
2257
    $searchform = '';
2258
2259
    // Instantiate hooks for external modules
2260
    $hookmanager->initHooks(array('toprightmenu'));
2261
2262
    $toprightmenu = '';
2263
2264
    // For backward compatibility with old modules
2265
    if (empty($conf->headerdone)) {
2266
        $disablenofollow = 0;
2267
        top_htmlhead($head, $title, $disablejs, $disablehead, $arrayofjs, $arrayofcss, 0, $disablenofollow);
2268
        print '<body id="mainbody">';
2269
    }
2270
2271
    /*
2272
     * Top menu
2273
     */
2274
    if ((empty($conf->dol_hide_topmenu) || GETPOSTINT('dol_invisible_topmenu')) && (!defined('NOREQUIREMENU') || !constant('NOREQUIREMENU'))) {
2275
        if (!isset($form) || !is_object($form)) {
2276
            $form = new Form($db);
2277
        }
2278
2279
        print "\n" . '<!-- Start top horizontal -->' . "\n";
2280
2281
        print '<header id="id-top" class="side-nav-vert' . (GETPOSTINT('dol_invisible_topmenu') ? ' hidden' : '') . '">'; // dol_invisible_topmenu differs from dol_hide_topmenu: dol_invisible_topmenu means we output menu but we make it invisible.
2282
2283
        // Show menu entries
2284
        print '<div id="tmenu_tooltip' . (!getDolGlobalString('MAIN_MENU_INVERT') ? '' : 'invert') . '" class="tmenu">' . "\n";
2285
        $menumanager->atarget = $target;
2286
        $menumanager->showmenu('top', array('searchform' => $searchform)); // This contains a \n
2287
        print "</div>\n";
2288
2289
        // Define link to login card
2290
        $appli = constant('DOL_APPLICATION_TITLE');
2291
        if (getDolGlobalString('MAIN_APPLICATION_TITLE')) {
2292
            $appli = getDolGlobalString('MAIN_APPLICATION_TITLE');
2293
            if (preg_match('/\d\.\d/', $appli)) {
2294
                if (!preg_match('/' . preg_quote(DOL_VERSION) . '/', $appli)) {
2295
                    $appli .= " (" . DOL_VERSION . ")"; // If new title contains a version that is different than core
2296
                }
2297
            } else {
2298
                $appli .= " " . DOL_VERSION;
2299
            }
2300
        } else {
2301
            $appli .= " " . DOL_VERSION;
2302
        }
2303
2304
        if (getDolGlobalInt('MAIN_FEATURES_LEVEL')) {
2305
            $appli .= "<br>" . $langs->trans("LevelOfFeature") . ': ' . getDolGlobalInt('MAIN_FEATURES_LEVEL');
2306
        }
2307
2308
        $logouttext = '';
2309
        $logouthtmltext = '';
2310
        if (!getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2311
            //$logouthtmltext=$appli.'<br>';
2312
            $stringforfirstkey = $langs->trans("KeyboardShortcut");
2313
            if ($conf->browser->name == 'chrome') {
2314
                $stringforfirstkey .= ' ALT +';
2315
            } elseif ($conf->browser->name == 'firefox') {
2316
                $stringforfirstkey .= ' ALT + SHIFT +';
2317
            } else {
2318
                $stringforfirstkey .= ' CTL +';
2319
            }
2320
            if ($_SESSION["dol_authmode"] != 'forceuser' && $_SESSION["dol_authmode"] != 'http') {
2321
                $logouthtmltext .= $langs->trans("Logout") . '<br>';
2322
                $logouttext .= '<a accesskey="l" href="' . constant('BASE_URL') . '/user/logout.php?token=' . newToken() . '">';
2323
                $logouttext .= img_picto($langs->trans('Logout') . ' (' . $stringforfirstkey . ' l)', 'sign-out', '', false, 0, 0, '', 'atoplogin valignmiddle');
2324
                $logouttext .= '</a>';
2325
            } else {
2326
                $logouthtmltext .= $langs->trans("NoLogoutProcessWithAuthMode", $_SESSION["dol_authmode"]);
2327
                $logouttext .= img_picto($langs->trans('Logout') . ' (' . $stringforfirstkey . ' l)', 'sign-out', '', false, 0, 0, '', 'atoplogin valignmiddle opacitymedium');
2328
            }
2329
        }
2330
2331
        print '<div class="login_block usedropdown">' . "\n";
2332
2333
        $toprightmenu .= '<div class="login_block_other">';
2334
2335
        // Execute hook printTopRightMenu (hooks should output string like '<div class="login"><a href="">mylink</a></div>')
2336
        $parameters = array();
2337
        $result = $hookmanager->executeHooks('printTopRightMenu', $parameters); // Note that $action and $object may have been modified by some hooks
2338
        if (is_numeric($result)) {
2339
            if ($result == 0) {
2340
                $toprightmenu .= $hookmanager->resPrint; // add
2341
            } else {
2342
                $toprightmenu = $hookmanager->resPrint; // replace
2343
            }
2344
        } else {
2345
            $toprightmenu .= $result; // For backward compatibility
2346
        }
2347
2348
        // Link to module builder
2349
        if (isModEnabled('modulebuilder')) {
2350
            $text = '<a href="' . constant('BASE_URL') . '/modulebuilder/index.php?mainmenu=home&leftmenu=admintools" target="modulebuilder">';
2351
            //$text.= img_picto(":".$langs->trans("ModuleBuilder"), 'printer_top.png', 'class="printer"');
2352
            $text .= '<span class="fa fa-bug atoplogin valignmiddle"></span>';
2353
            $text .= '</a>';
2354
            // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
2355
            $toprightmenu .= $form->textwithtooltip('', $langs->trans("ModuleBuilder"), 2, 1, $text, 'login_block_elem', 2);
2356
        }
2357
2358
        // Link to print main content area (optioncss=print)
2359
        if (!getDolGlobalString('MAIN_PRINT_DISABLELINK') && !getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2360
            $qs = dol_escape_htmltag($_SERVER["QUERY_STRING"]);
2361
2362
            if (isset($_POST) && is_array($_POST)) {
2363
                foreach ($_POST as $key => $value) {
2364
                    $key = preg_replace('/[^a-z0-9_\.\-\[\]]/i', '', $key);
2365
                    if (in_array($key, array('action', 'massaction', 'password'))) {
2366
                        continue;
2367
                    }
2368
                    if (!is_array($value)) {
2369
                        if ($value !== '') {
2370
                            $qs .= '&' . urlencode($key) . '=' . urlencode($value);
2371
                        }
2372
                    } else {
2373
                        foreach ($value as $value2) {
2374
                            if (($value2 !== '') && (!is_array($value2))) {
2375
                                $qs .= '&' . urlencode($key) . '[]=' . urlencode($value2);
2376
                            }
2377
                        }
2378
                    }
2379
                }
2380
            }
2381
            $qs .= (($qs && $morequerystring) ? '&' : '') . $morequerystring;
2382
            $text = '<a href="' . dol_escape_htmltag($_SERVER["PHP_SELF"]) . '?' . $qs . ($qs ? '&' : '') . 'optioncss=print" target="_blank" rel="noopener noreferrer">';
2383
            //$text.= img_picto(":".$langs->trans("PrintContentArea"), 'printer_top.png', 'class="printer"');
2384
            $text .= '<span class="fa fa-print atoplogin valignmiddle"></span>';
2385
            $text .= '</a>';
2386
            // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
2387
            $toprightmenu .= $form->textwithtooltip('', $langs->trans("PrintContentArea"), 2, 1, $text, 'login_block_elem', 2);
2388
        }
2389
2390
        // Link to Dolibarr wiki pages
2391
        if (!getDolGlobalString('MAIN_HELP_DISABLELINK') && !getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2392
            $langs->load("help");
2393
2394
            $helpbaseurl = '';
2395
            $helppage = '';
2396
            $mode = '';
2397
            $helppresent = '';
2398
2399
            if (empty($helppagename)) {
2400
                $helppagename = 'EN:User_documentation|FR:Documentation_utilisateur|ES:Documentación_usuarios|DE:Benutzerdokumentation';
2401
            } else {
2402
                $helppresent = 'helppresent';
2403
            }
2404
2405
            // Get helpbaseurl, helppage and mode from helppagename and langs
2406
            $arrayres = getHelpParamFor($helppagename, $langs);
2407
            $helpbaseurl = $arrayres['helpbaseurl'];
2408
            $helppage = $arrayres['helppage'];
2409
            $mode = $arrayres['mode'];
2410
2411
            // Link to help pages
2412
            if ($helpbaseurl && $helppage) {
2413
                $text = '';
2414
                $title = $langs->trans($mode == 'wiki' ? 'GoToWikiHelpPage' : 'GoToHelpPage') . ', ';
2415
                if ($mode == 'wiki') {
2416
                    $title .= '<br>' . img_picto('', 'globe', 'class="pictofixedwidth"') . $langs->trans("PageWiki") . ' ' . dol_escape_htmltag('"' . strtr($helppage, '_', ' ') . '"');
2417
                    if ($helppresent) {
2418
                        $title .= ' <span class="opacitymedium">(' . $langs->trans("DedicatedPageAvailable") . ')</span>';
2419
                    } else {
2420
                        $title .= ' <span class="opacitymedium">(' . $langs->trans("HomePage") . ')</span>';
2421
                    }
2422
                }
2423
                $text .= '<a class="help" target="_blank" rel="noopener noreferrer" href="';
2424
                if ($mode == 'wiki') {
2425
                    // @phan-suppress-next-line PhanPluginPrintfVariableFormatString
2426
                    $text .= sprintf($helpbaseurl, urlencode(html_entity_decode($helppage)));
2427
                } else {
2428
                    // @phan-suppress-next-line PhanPluginPrintfVariableFormatString
2429
                    $text .= sprintf($helpbaseurl, $helppage);
2430
                }
2431
                $text .= '">';
2432
                $text .= '<span class="fa fa-question-circle atoplogin valignmiddle' . ($helppresent ? ' ' . $helppresent : '') . '"></span>';
2433
                $text .= '<span class="fa fa-long-arrow-alt-up helppresentcircle' . ($helppresent ? '' : ' unvisible') . '"></span>';
2434
                $text .= '</a>';
2435
                // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
2436
                $toprightmenu .= $form->textwithtooltip('', $title, 2, 1, $text, 'login_block_elem', 2);
2437
            }
2438
2439
            // Version
2440
            if (getDolGlobalString('MAIN_SHOWDATABASENAMEINHELPPAGESLINK')) {
2441
                $langs->load('admin');
2442
                $appli .= '<br>' . $langs->trans("Database") . ': ' . $db->database_name;
2443
            }
2444
        }
2445
2446
        if (!getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2447
            $text = '<span class="aversion"><span class="hideonsmartphone small">' . DOL_VERSION . '</span></span>';
2448
            // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
2449
            $toprightmenu .= $form->textwithtooltip('', $appli, 2, 1, $text, 'login_block_elem', 2);
2450
        }
2451
2452
        // Logout link
2453
        $toprightmenu .= $form->textwithtooltip('', $logouthtmltext, 2, 1, $logouttext, 'login_block_elem logout-btn', 2);
2454
2455
        $toprightmenu .= '</div>'; // end div class="login_block_other"
2456
2457
2458
        // Add login user link
2459
        $toprightmenu .= '<div class="login_block_user">';
2460
2461
        // Login name with photo and tooltip
2462
        $mode = -1;
2463
        $toprightmenu .= '<div class="inline-block login_block_elem login_block_elem_name nowrap centpercent" style="padding: 0px;">';
2464
2465
        if (getDolGlobalString('MAIN_USE_TOP_MENU_SEARCH_DROPDOWN')) {
2466
            // Add search dropdown
2467
            $toprightmenu .= top_menu_search();
2468
        }
2469
2470
        if (getDolGlobalString('MAIN_USE_TOP_MENU_QUICKADD_DROPDOWN')) {
2471
            // Add search dropdown
2472
            $toprightmenu .= top_menu_quickadd();
2473
        }
2474
2475
        // Add bookmark dropdown
2476
        $toprightmenu .= top_menu_bookmark();
2477
2478
        // Add user dropdown
2479
        $toprightmenu .= top_menu_user();
2480
2481
        $toprightmenu .= '</div>';
2482
2483
        $toprightmenu .= '</div>' . "\n";
2484
2485
2486
        print $toprightmenu;
2487
2488
        print "</div>\n"; // end div class="login_block"
2489
2490
        print '</header>';
2491
        //print '<header class="header2">&nbsp;</header>';
2492
2493
        print '<div style="clear: both;"></div>';
2494
        print "<!-- End top horizontal menu -->\n\n";
2495
    }
2496
2497
    if (empty($conf->dol_hide_leftmenu) && empty($conf->dol_use_jmobile)) {
2498
        print '<!-- Begin div id-container --><div id="id-container" class="id-container">';
2499
    }
2500
}
2501
2502
2503
/**
2504
 * Build the tooltip on user login
2505
 *
2506
 * @param int<0,1> $hideloginname Hide login name. Show only the image.
2507
 * @param string $urllogout URL for logout (Will use DOL_URL_ROOT.'/user/logout.php?token=...' if empty)
2508
 * @return  string                          HTML content
2509
 */
2510
function top_menu_user($hideloginname = 0, $urllogout = '')
2511
{
2512
    global $langs, $conf, $db, $hookmanager, $user, $mysoc;
2513
    global $dolibarr_main_authentication, $dolibarr_main_demo;
2514
    global $menumanager;
2515
2516
    $langs->load('companies');
2517
2518
    $userImage = $userDropDownImage = '';
2519
    if (!empty($user->photo)) {
2520
        $userImage = Form::showphoto('userphoto', $user, 0, 0, 0, 'photouserphoto userphoto', 'small', 0, 1);
2521
        $userDropDownImage = Form::showphoto('userphoto', $user, 0, 0, 0, 'dropdown-user-image', 'small', 0, 1);
2522
    } else {
2523
        $nophoto = '/public/theme/common/user_anonymous.png';
2524
        if ($user->gender == 'man') {
2525
            $nophoto = '/public/theme/common/user_man.png';
2526
        }
2527
        if ($user->gender == 'woman') {
2528
            $nophoto = '/public/theme/common/user_woman.png';
2529
        }
2530
2531
        $userImage = '<img class="photo photouserphoto userphoto" alt="" src="' . constant('DOL_URL_ROOT') . $nophoto . '">';
2532
        $userDropDownImage = '<img class="photo dropdown-user-image" alt="" src="' . constant('DOL_URL_ROOT') . $nophoto . '">';
2533
    }
2534
2535
    $dropdownBody = '';
2536
    $dropdownBody .= '<span id="topmenulogincompanyinfo-btn"><i class="fa fa-caret-right"></i> ' . $langs->trans("ShowCompanyInfos") . '</span>';
2537
    $dropdownBody .= '<div id="topmenulogincompanyinfo" >';
2538
2539
    $dropdownBody .= '<br><b>' . $langs->trans("Company") . '</b>: <span>' . dol_escape_htmltag($mysoc->name) . '</span>';
2540
    if ($langs->transcountry("ProfId1", $mysoc->country_code) != '-') {
2541
        $dropdownBody .= '<br><b>' . $langs->transcountry("ProfId1", $mysoc->country_code) . '</b>: <span>' . dol_print_profids(getDolGlobalString("MAIN_INFO_SIREN"), 1) . '</span>';
2542
    }
2543
    if ($langs->transcountry("ProfId2", $mysoc->country_code) != '-') {
2544
        $dropdownBody .= '<br><b>' . $langs->transcountry("ProfId2", $mysoc->country_code) . '</b>: <span>' . dol_print_profids(getDolGlobalString("MAIN_INFO_SIRET"), 2) . '</span>';
2545
    }
2546
    if ($langs->transcountry("ProfId3", $mysoc->country_code) != '-') {
2547
        $dropdownBody .= '<br><b>' . $langs->transcountry("ProfId3", $mysoc->country_code) . '</b>: <span>' . dol_print_profids(getDolGlobalString("MAIN_INFO_APE"), 3) . '</span>';
2548
    }
2549
    if ($langs->transcountry("ProfId4", $mysoc->country_code) != '-') {
2550
        $dropdownBody .= '<br><b>' . $langs->transcountry("ProfId4", $mysoc->country_code) . '</b>: <span>' . dol_print_profids(getDolGlobalString("MAIN_INFO_RCS"), 4) . '</span>';
2551
    }
2552
    if ($langs->transcountry("ProfId5", $mysoc->country_code) != '-') {
2553
        $dropdownBody .= '<br><b>' . $langs->transcountry("ProfId5", $mysoc->country_code) . '</b>: <span>' . dol_print_profids(getDolGlobalString("MAIN_INFO_PROFID5"), 5) . '</span>';
2554
    }
2555
    if ($langs->transcountry("ProfId6", $mysoc->country_code) != '-') {
2556
        $dropdownBody .= '<br><b>' . $langs->transcountry("ProfId6", $mysoc->country_code) . '</b>: <span>' . dol_print_profids(getDolGlobalString("MAIN_INFO_PROFID6"), 6) . '</span>';
2557
    }
2558
    $dropdownBody .= '<br><b>' . $langs->trans("VATIntraShort") . '</b>: <span>' . dol_print_profids(getDolGlobalString("MAIN_INFO_TVAINTRA"), 'VAT') . '</span>';
2559
    $dropdownBody .= '<br><b>' . $langs->trans("Country") . '</b>: <span>' . ($mysoc->country_code ? $langs->trans("Country" . $mysoc->country_code) : '') . '</span>';
2560
    if (isModEnabled('multicurrency')) {
2561
        $dropdownBody .= '<br><b>' . $langs->trans("Currency") . '</b>: <span>' . $conf->currency . '</span>';
2562
    }
2563
    $dropdownBody .= '</div>';
2564
2565
    $dropdownBody .= '<br>';
2566
    $dropdownBody .= '<span id="topmenuloginmoreinfo-btn"><i class="fa fa-caret-right"></i> ' . $langs->trans("ShowMoreInfos") . '</span>';
2567
    $dropdownBody .= '<div id="topmenuloginmoreinfo" >';
2568
2569
    // login infos
2570
    if (!empty($user->admin)) {
2571
        $dropdownBody .= '<br><b>' . $langs->trans("Administrator") . '</b>: ' . yn($user->admin);
2572
    }
2573
    if (!empty($user->socid)) { // Add thirdparty for external users
2574
        $thirdpartystatic = new Societe($db);
2575
        $thirdpartystatic->fetch($user->socid);
2576
        $companylink = ' ' . $thirdpartystatic->getNomUrl(2); // picto only of company
2577
        $company = ' (' . $langs->trans("Company") . ': ' . $thirdpartystatic->name . ')';
2578
    }
2579
    $type = ($user->socid ? $langs->trans("External") . $company : $langs->trans("Internal"));
2580
    $dropdownBody .= '<br><b>' . $langs->trans("Type") . ':</b> ' . $type;
2581
    $dropdownBody .= '<br><b>' . $langs->trans("Status") . '</b>: ' . $user->getLibStatut(0);
2582
    $dropdownBody .= '<br>';
2583
2584
    $dropdownBody .= '<br><u>' . $langs->trans("Session") . '</u>';
2585
    $dropdownBody .= '<br><b>' . $langs->trans("IPAddress") . '</b>: ' . dol_escape_htmltag($_SERVER["REMOTE_ADDR"]);
2586
    if (getDolGlobalString('MAIN_MODULE_MULTICOMPANY')) {
2587
        $dropdownBody .= '<br><b>' . $langs->trans("ConnectedOnMultiCompany") . ':</b> ' . $conf->entity . ' (user entity ' . $user->entity . ')';
2588
    }
2589
    $dropdownBody .= '<br><b>' . $langs->trans("AuthenticationMode") . ':</b> ' . $_SESSION["dol_authmode"] . (empty($dolibarr_main_demo) ? '' : ' (demo)');
2590
    $dropdownBody .= '<br><b>' . $langs->trans("ConnectedSince") . ':</b> ' . dol_print_date($user->datelastlogin, "dayhour", 'tzuser');
2591
    $dropdownBody .= '<br><b>' . $langs->trans("PreviousConnexion") . ':</b> ' . dol_print_date($user->datepreviouslogin, "dayhour", 'tzuser');
2592
    $dropdownBody .= '<br><b>' . $langs->trans("CurrentTheme") . ':</b> ' . $conf->theme;
2593
    $dropdownBody .= '<br><b>' . $langs->trans("CurrentMenuManager") . ':</b> ' . (isset($menumanager) ? $menumanager->name : 'unknown');
2594
    $langFlag = picto_from_langcode($langs->getDefaultLang());
2595
    $dropdownBody .= '<br><b>' . $langs->trans("CurrentUserLanguage") . ':</b> ' . ($langFlag ? $langFlag . ' ' : '') . $langs->getDefaultLang();
2596
2597
    $tz = (int)$_SESSION['dol_tz'] + (int)$_SESSION['dol_dst'];
2598
    $dropdownBody .= '<br><b>' . $langs->trans("ClientTZ") . ':</b> ' . ($tz ? ($tz >= 0 ? '+' : '') . $tz : '');
2599
    $dropdownBody .= ' (' . $_SESSION['dol_tz_string'] . ')';
2600
    //$dropdownBody .= ' &nbsp; &nbsp; &nbsp; '.$langs->trans("DaylingSavingTime").': ';
2601
    //if ($_SESSION['dol_dst'] > 0) $dropdownBody .= yn(1);
2602
    //else $dropdownBody .= yn(0);
2603
2604
    $dropdownBody .= '<br><b>' . $langs->trans("Browser") . ':</b> ' . $conf->browser->name . ($conf->browser->version ? ' ' . $conf->browser->version : '') . ' <small class="opacitymedium">(' . dol_escape_htmltag($_SERVER['HTTP_USER_AGENT']) . ')</small>';
2605
    $dropdownBody .= '<br><b>' . $langs->trans("Layout") . ':</b> ' . $conf->browser->layout;
2606
    $dropdownBody .= '<br><b>' . $langs->trans("Screen") . ':</b> ' . $_SESSION['dol_screenwidth'] . ' x ' . $_SESSION['dol_screenheight'];
2607
    if ($conf->browser->layout == 'phone') {
2608
        $dropdownBody .= '<br><b>' . $langs->trans("Phone") . ':</b> ' . $langs->trans("Yes");
2609
    }
2610
    if (!empty($_SESSION["disablemodules"])) {
2611
        $dropdownBody .= '<br><b>' . $langs->trans("DisabledModules") . ':</b> <br>' . implode(', ', explode(',', $_SESSION["disablemodules"]));
2612
    }
2613
    $dropdownBody .= '</div>';
2614
2615
    // Execute hook
2616
    $parameters = array('user' => $user, 'langs' => $langs);
2617
    $result = $hookmanager->executeHooks('printTopRightMenuLoginDropdownBody', $parameters); // Note that $action and $object may have been modified by some hooks
2618
    if (is_numeric($result)) {
2619
        if ($result == 0) {
2620
            $dropdownBody .= $hookmanager->resPrint; // add
2621
        } else {
2622
            $dropdownBody = $hookmanager->resPrint; // replace
2623
        }
2624
    }
2625
2626
    if (empty($urllogout)) {
2627
        $urllogout = BASE_URL . '/user/logout.php?token=' . newToken();
2628
    }
2629
2630
    // accesskey is for Windows or Linux:  ALT + key for chrome, ALT + SHIFT + KEY for firefox
2631
    // accesskey is for Mac:               CTRL + key for all browsers
2632
    $stringforfirstkey = $langs->trans("KeyboardShortcut");
2633
    if ($conf->browser->name == 'chrome') {
2634
        $stringforfirstkey .= ' ALT +';
2635
    } elseif ($conf->browser->name == 'firefox') {
2636
        $stringforfirstkey .= ' ALT + SHIFT +';
2637
    } else {
2638
        $stringforfirstkey .= ' CTL +';
2639
    }
2640
2641
    // Defined the links for bottom of card
2642
    $profilLink = '<a accesskey="u" href="' . BASE_URL . '/user/card.php?id=' . $user->id . '" class="button-top-menu-dropdown" title="' . dol_escape_htmltag($langs->trans("YourUserFile") . ' (' . $stringforfirstkey . ' u)') . '"><i class="fa fa-user"></i>  ' . $langs->trans("Card") . '</a>';
2643
    $urltovirtualcard = '/user/virtualcard.php?id=' . ((int)$user->id);
2644
    $virtuelcardLink = dolButtonToOpenUrlInDialogPopup('publicvirtualcardmenu', $langs->transnoentitiesnoconv("PublicVirtualCardUrl") . (is_object($user) ? ' - ' . $user->getFullName($langs) : '') . ' (' . $stringforfirstkey . ' v)', img_picto($langs->trans("PublicVirtualCardUrl") . ' (' . $stringforfirstkey . ' v)', 'card', ''), $urltovirtualcard, '', 'button-top-menu-dropdown marginleftonly nohover', "closeTopMenuLoginDropdown()", '', 'v');
2645
    $logoutLink = '<a accesskey="l" href="' . $urllogout . '" class="button-top-menu-dropdown" title="' . dol_escape_htmltag($langs->trans("Logout") . ' (' . $stringforfirstkey . ' l)') . '"><i class="fa fa-sign-out-alt padingright"></i><span class="hideonsmartphone">' . $langs->trans("Logout") . '</span></a>';
2646
2647
    $profilName = $user->getFullName($langs) . ' (' . $user->login . ')';
2648
    if (!empty($user->admin)) {
2649
        $profilName = '<i class="far fa-star classfortooltip" title="' . $langs->trans("Administrator") . '" ></i> ' . $profilName;
2650
    }
2651
2652
    // Define version to show
2653
    $appli = constant('DOL_APPLICATION_TITLE');
2654
    if (getDolGlobalString('MAIN_APPLICATION_TITLE')) {
2655
        $appli = getDolGlobalString('MAIN_APPLICATION_TITLE');
2656
        if (preg_match('/\d\.\d/', $appli)) {
2657
            if (!preg_match('/' . preg_quote(DOL_VERSION) . '/', $appli)) {
2658
                $appli .= " (" . DOL_VERSION . ")"; // If new title contains a version that is different than core
2659
            }
2660
        } else {
2661
            $appli .= " " . DOL_VERSION;
2662
        }
2663
    } else {
2664
        $appli .= " " . DOL_VERSION;
2665
    }
2666
2667
    if (!getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2668
        $btnUser = '<!-- div for user link -->
2669
	    <div id="topmenu-login-dropdown" class="userimg atoplogin dropdown user user-menu inline-block">
2670
	        <a href="' . constant('BASE_URL') . '/user/card.php?id=' . $user->id . '" class="dropdown-toggle login-dropdown-a valignmiddle" data-toggle="dropdown">
2671
	            ' . $userImage . (empty($user->photo) ? '<!-- no photo so show also the login --><span class="hidden-xs maxwidth200 atoploginusername hideonsmartphone paddingleft valignmiddle small">' . dol_trunc($user->firstname ? $user->firstname : $user->login, 10) . '</span>' : '') . '
2672
	        </a>
2673
	        <div class="dropdown-menu">
2674
	            <!-- User image -->
2675
	            <div class="user-header">
2676
	                ' . $userDropDownImage . '
2677
	                <p>
2678
	                    ' . $profilName . '<br>';
2679
        if ($user->datelastlogin) {
2680
            $title = $langs->trans("ConnectedSince") . ' : ' . dol_print_date($user->datelastlogin, "dayhour", 'tzuser');
2681
            if ($user->datepreviouslogin) {
2682
                $title .= '<br>' . $langs->trans("PreviousConnexion") . ' : ' . dol_print_date($user->datepreviouslogin, "dayhour", 'tzuser');
2683
            }
2684
        }
2685
        $btnUser .= '<small class="classfortooltip" title="' . dol_escape_htmltag($title) . '" ><i class="fa fa-user-clock"></i> ' . dol_print_date($user->datelastlogin, "dayhour", 'tzuser') . '</small><br>';
2686
        if ($user->datepreviouslogin) {
2687
            $btnUser .= '<small class="classfortooltip" title="' . dol_escape_htmltag($title) . '" ><i class="fa fa-user-clock opacitymedium"></i> ' . dol_print_date($user->datepreviouslogin, "dayhour", 'tzuser') . '</small><br>';
2688
        }
2689
2690
        //$btnUser .= '<small class="classfortooltip"><i class="fa fa-cog"></i> '.$langs->trans("Version").' '.$appli.'</small>';
2691
        $btnUser .= '
2692
	                </p>
2693
	            </div>
2694
2695
	            <!-- Menu Body user-->
2696
	            <div class="user-body">' . $dropdownBody . '</div>
2697
2698
	            <!-- Menu Footer-->
2699
	            <div class="user-footer">
2700
	                <div class="pull-left">
2701
	                    ' . $profilLink . '
2702
	                </div>
2703
	                <div class="pull-left">
2704
	                    ' . $virtuelcardLink . '
2705
	                </div>
2706
	                <div class="pull-right">
2707
	                    ' . $logoutLink . '
2708
	                </div>
2709
	                <div class="clearboth"></div>
2710
	            </div>
2711
2712
	        </div>
2713
	    </div>';
2714
    } else {
2715
        $btnUser = '<!-- div for user link text browser -->
2716
	    <div id="topmenu-login-dropdown" class="userimg atoplogin dropdown user user-menu inline-block">
2717
	    	<a href="' . constant('BASE_URL') . '/user/card.php?id=' . $user->id . '" class="valignmiddle" alt="' . $langs->trans("MyUserCard") . '">
2718
	    	' . $userImage . (empty($user->photo) ? '<span class="hidden-xs maxwidth200 atoploginusername hideonsmartphone paddingleft small">' . dol_trunc($user->firstname ? $user->firstname : $user->login, 10) . '</span>' : '') . '
2719
	    	</a>
2720
		</div>';
2721
    }
2722
2723
    if (!defined('JS_JQUERY_DISABLE_DROPDOWN') && !empty($conf->use_javascript_ajax)) {    // This may be set by some pages that use different jquery version to avoid errors
2724
        $btnUser .= '
2725
        <!-- Code to show/hide the user drop-down -->
2726
        <script>
2727
		function closeTopMenuLoginDropdown() {
2728
			//console.log("close login dropdown");	// This is call at each click on page, so we disable the log
2729
			// Hide the menus.
2730
            jQuery("#topmenu-login-dropdown").removeClass("open");
2731
		}
2732
        jQuery(document).ready(function() {
2733
            jQuery(document).on("click", function(event) {
2734
				// console.log("Click somewhere on screen");
2735
                if (!$(event.target).closest("#topmenu-login-dropdown").length) {
2736
					closeTopMenuLoginDropdown();
2737
                }
2738
            });
2739
		';
2740
2741
2742
        //if ($conf->theme != 'md') {
2743
        $btnUser .= '
2744
	            jQuery("#topmenu-login-dropdown .dropdown-toggle").on("click", function(event) {
2745
					console.log("Click on #topmenu-login-dropdown .dropdown-toggle");
2746
					event.preventDefault();
2747
	                jQuery("#topmenu-login-dropdown").toggleClass("open");
2748
	            });
2749
2750
	            jQuery("#topmenulogincompanyinfo-btn").on("click", function() {
2751
					console.log("Click on #topmenulogincompanyinfo-btn");
2752
	                jQuery("#topmenulogincompanyinfo").slideToggle();
2753
	            });
2754
2755
	            jQuery("#topmenuloginmoreinfo-btn").on("click", function() {
2756
					console.log("Click on #topmenuloginmoreinfo-btn");
2757
	                jQuery("#topmenuloginmoreinfo").slideToggle();
2758
	            });';
2759
        //}
2760
2761
        $btnUser .= '
2762
        });
2763
        </script>
2764
        ';
2765
    }
2766
2767
    return $btnUser;
2768
}
2769
2770
/**
2771
 * Build the tooltip on top menu quick add
2772
 *
2773
 * @return  string                  HTML content
2774
 */
2775
function top_menu_quickadd()
2776
{
2777
    global $conf, $langs;
2778
2779
    // Button disabled on text browser
2780
    if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
2781
        return '';
2782
    }
2783
2784
    $html = '';
2785
2786
    // accesskey is for Windows or Linux:  ALT + key for chrome, ALT + SHIFT + KEY for firefox
2787
    // accesskey is for Mac:               CTRL + key for all browsers
2788
    $stringforfirstkey = $langs->trans("KeyboardShortcut");
2789
    if ($conf->browser->os === 'macintosh') {
2790
        $stringforfirstkey .= ' CTL +';
2791
    } else {
2792
        if ($conf->browser->name == 'chrome') {
2793
            $stringforfirstkey .= ' ALT +';
2794
        } elseif ($conf->browser->name == 'firefox') {
2795
            $stringforfirstkey .= ' ALT + SHIFT +';
2796
        } else {
2797
            $stringforfirstkey .= ' CTL +';
2798
        }
2799
    }
2800
2801
    if (!empty($conf->use_javascript_ajax)) {
2802
        $html .= '<!-- div for quick add link -->
2803
    <div id="topmenu-quickadd-dropdown" class="atoplogin dropdown inline-block">
2804
        <a accesskey="a" class="dropdown-toggle login-dropdown-a nofocusvisible" data-toggle="dropdown" href="#" title="' . $langs->trans('QuickAdd') . ' (' . $stringforfirstkey . ' a)"><i class="fa fa-plus-circle"></i></a>
2805
        <div class="dropdown-menu">' . printDropdownQuickadd() . '</div>
2806
    </div>';
2807
        if (!defined('JS_JQUERY_DISABLE_DROPDOWN')) {    // This may be set by some pages that use different jquery version to avoid errors
2808
            $html .= '
2809
        <!-- Code to show/hide the user drop-down for the quick add -->
2810
        <script>
2811
        jQuery(document).ready(function() {
2812
            jQuery(document).on("click", function(event) {
2813
                if (!$(event.target).closest("#topmenu-quickadd-dropdown").length) {
2814
                    // Hide the menus.
2815
                    $("#topmenu-quickadd-dropdown").removeClass("open");
2816
                }
2817
            });
2818
            $("#topmenu-quickadd-dropdown .dropdown-toggle").on("click", function(event) {
2819
				console.log("Click on #topmenu-quickadd-dropdown .dropdown-toggle");
2820
                openQuickAddDropDown(event);
2821
            });
2822
2823
            // Key map shortcut
2824
            $(document).keydown(function(event){
2825
				var ostype = \'' . dol_escape_js($conf->browser->os) . '\';
2826
				if (ostype === "macintosh") {
2827
					if ( event.which === 65 && event.ctrlKey ) {
2828
						console.log(\'control + a : trigger open quick add dropdown\');
2829
						openQuickAddDropDown(event);
2830
					}
2831
				} else {
2832
					if ( event.which === 65 && event.ctrlKey && event.shiftKey ) {
2833
						console.log(\'control + shift + a : trigger open quick add dropdown\');
2834
						openQuickAddDropDown(event);
2835
					}
2836
				}
2837
            });
2838
2839
            var openQuickAddDropDown = function(event) {
2840
                event.preventDefault();
2841
                $("#topmenu-quickadd-dropdown").toggleClass("open");
2842
                //$("#top-quickadd-search-input").focus();
2843
            }
2844
        });
2845
        </script>
2846
        ';
2847
        }
2848
    }
2849
2850
    return $html;
2851
}
2852
2853
/**
2854
 * Generate list of quickadd items
2855
 *
2856
 * @return string HTML output
2857
 */
2858
function printDropdownQuickadd()
2859
{
2860
    global $user, $langs, $hookmanager;
2861
2862
    $items = array(
2863
        'items' => array(
2864
            array(
2865
                "url" => "/adherents/card.php?action=create&amp;mainmenu=members",
2866
                "title" => "MenuNewMember@members",
2867
                "name" => "Adherent@members",
2868
                "picto" => "object_member",
2869
                "activation" => isModEnabled('member') && $user->hasRight("adherent", "write"), // vs hooking
2870
                "position" => 5,
2871
            ),
2872
            array(
2873
                "url" => "/societe/card.php?action=create&amp;mainmenu=companies",
2874
                "title" => "MenuNewThirdParty@companies",
2875
                "name" => "ThirdParty@companies",
2876
                "picto" => "object_company",
2877
                "activation" => isModEnabled("societe") && $user->hasRight("societe", "write"), // vs hooking
2878
                "position" => 10,
2879
            ),
2880
            array(
2881
                "url" => "/contact/card.php?action=create&amp;mainmenu=companies",
2882
                "title" => "NewContactAddress@companies",
2883
                "name" => "Contact@companies",
2884
                "picto" => "object_contact",
2885
                "activation" => isModEnabled("societe") && $user->hasRight("societe", "contact", "write"), // vs hooking
2886
                "position" => 20,
2887
            ),
2888
            array(
2889
                "url" => "/comm/propal/card.php?action=create&amp;mainmenu=commercial",
2890
                "title" => "NewPropal@propal",
2891
                "name" => "Proposal@propal",
2892
                "picto" => "object_propal",
2893
                "activation" => isModEnabled("propal") && $user->hasRight("propal", "write"), // vs hooking
2894
                "position" => 30,
2895
            ),
2896
2897
            array(
2898
                "url" => "/commande/card.php?action=create&amp;mainmenu=commercial",
2899
                "title" => "NewOrder@orders",
2900
                "name" => "Order@orders",
2901
                "picto" => "object_order",
2902
                "activation" => isModEnabled('order') && $user->hasRight("commande", "write"), // vs hooking
2903
                "position" => 40,
2904
            ),
2905
            array(
2906
                "url" => "/compta/facture/card.php?action=create&amp;mainmenu=billing",
2907
                "title" => "NewBill@bills",
2908
                "name" => "Bill@bills",
2909
                "picto" => "object_bill",
2910
                "activation" => isModEnabled('invoice') && $user->hasRight("facture", "write"), // vs hooking
2911
                "position" => 50,
2912
            ),
2913
            array(
2914
                "url" => "/contrat/card.php?action=create&amp;mainmenu=commercial",
2915
                "title" => "NewContractSubscription@contracts",
2916
                "name" => "Contract@contracts",
2917
                "picto" => "object_contract",
2918
                "activation" => isModEnabled('contract') && $user->hasRight("contrat", "write"), // vs hooking
2919
                "position" => 60,
2920
            ),
2921
            array(
2922
                "url" => "/supplier_proposal/card.php?action=create&amp;mainmenu=commercial",
2923
                "title" => "SupplierProposalNew@supplier_proposal",
2924
                "name" => "SupplierProposal@supplier_proposal",
2925
                "picto" => "supplier_proposal",
2926
                "activation" => isModEnabled('supplier_proposal') && $user->hasRight("supplier_invoice", "write"), // vs hooking
2927
                "position" => 70,
2928
            ),
2929
            array(
2930
                "url" => "/fourn/commande/card.php?action=create&amp;mainmenu=commercial",
2931
                "title" => "NewSupplierOrderShort@orders",
2932
                "name" => "SupplierOrder@orders",
2933
                "picto" => "supplier_order",
2934
                "activation" => (isModEnabled("fournisseur") && !getDolGlobalString('MAIN_USE_NEW_SUPPLIERMOD') && $user->hasRight("fournisseur", "commande", "write")) || (isModEnabled("supplier_order") && $user->hasRight("supplier_invoice", "write")), // vs hooking
2935
                "position" => 80,
2936
            ),
2937
            array(
2938
                "url" => "/fourn/facture/card.php?action=create&amp;mainmenu=billing",
2939
                "title" => "NewBill@bills",
2940
                "name" => "SupplierBill@bills",
2941
                "picto" => "supplier_invoice",
2942
                "activation" => (isModEnabled("fournisseur") && !getDolGlobalString('MAIN_USE_NEW_SUPPLIERMOD') && $user->hasRight("fournisseur", "facture", "write")) || (isModEnabled("supplier_invoice") && $user->hasRight("supplier_invoice", "write")), // vs hooking
2943
                "position" => 90,
2944
            ),
2945
            array(
2946
                "url" => "/ticket/card.php?action=create&amp;mainmenu=ticket",
2947
                "title" => "NewTicket@ticket",
2948
                "name" => "Ticket@ticket",
2949
                "picto" => "ticket",
2950
                "activation" => isModEnabled('ticket') && $user->hasRight("ticket", "write"), // vs hooking
2951
                "position" => 100,
2952
            ),
2953
            array(
2954
                "url" => "/fichinter/card.php?action=create&mainmenu=commercial",
2955
                "title" => "NewIntervention@interventions",
2956
                "name" => "Intervention@interventions",
2957
                "picto" => "intervention",
2958
                "activation" => isModEnabled('intervention') && $user->hasRight("ficheinter", "creer"), // vs hooking
2959
                "position" => 110,
2960
            ),
2961
            array(
2962
                "url" => "/product/card.php?action=create&amp;type=0&amp;mainmenu=products",
2963
                "title" => "NewProduct@products",
2964
                "name" => "Product@products",
2965
                "picto" => "object_product",
2966
                "activation" => isModEnabled("product") && $user->hasRight("produit", "write"), // vs hooking
2967
                "position" => 400,
2968
            ),
2969
            array(
2970
                "url" => "/product/card.php?action=create&amp;type=1&amp;mainmenu=products",
2971
                "title" => "NewService@products",
2972
                "name" => "Service@products",
2973
                "picto" => "object_service",
2974
                "activation" => isModEnabled("service") && $user->hasRight("service", "write"), // vs hooking
2975
                "position" => 410,
2976
            ),
2977
            array(
2978
                "url" => "/user/card.php?action=create&amp;type=1&amp;mainmenu=home",
2979
                "title" => "AddUser@users",
2980
                "name" => "User@users",
2981
                "picto" => "user",
2982
                "activation" => $user->hasRight("user", "user", "write"), // vs hooking
2983
                "position" => 500,
2984
            ),
2985
        ),
2986
    );
2987
2988
    $dropDownQuickAddHtml = '';
2989
2990
    // Define $dropDownQuickAddHtml
2991
    $dropDownQuickAddHtml .= '<div class="quickadd-body dropdown-body">';
2992
    $dropDownQuickAddHtml .= '<div class="dropdown-quickadd-list">';
2993
2994
    // Allow the $items of the menu to be manipulated by modules
2995
    $parameters = array();
2996
    $hook_items = $items;
2997
    $reshook = $hookmanager->executeHooks('menuDropdownQuickaddItems', $parameters, $hook_items); // Note that $action and $object may have been modified by some hooks
2998
    if (is_numeric($reshook) && !empty($hookmanager->resArray) && is_array($hookmanager->resArray)) {
2999
        if ($reshook == 0) {
3000
            $items['items'] = array_merge($items['items'], $hookmanager->resArray); // add
3001
        } else {
3002
            $items = $hookmanager->resArray; // replace
3003
        }
3004
3005
        // Sort menu items by 'position' value
3006
        $position = array();
3007
        foreach ($items['items'] as $key => $row) {
3008
            $position[$key] = $row['position'];
3009
        }
3010
        $array1_sort_order = SORT_ASC;
3011
        array_multisort($position, $array1_sort_order, $items['items']);
3012
    }
3013
3014
    foreach ($items['items'] as $item) {
3015
        if (!$item['activation']) {
3016
            continue;
3017
        }
3018
        $langs->load(explode('@', $item['title'])[1]);
3019
        $langs->load(explode('@', $item['name'])[1]);
3020
        $dropDownQuickAddHtml .= '
3021
			<a class="dropdown-item quickadd-item" href="' . constant('DOL_URL_ROOT') . $item['url'] . '" title="' . $langs->trans(explode('@', $item['title'])[0]) . '">
3022
			' . img_picto('', $item['picto'], 'style="width:18px;"') . ' ' . $langs->trans(explode('@', $item['name'])[0]) . '</a>
3023
		';
3024
    }
3025
3026
    $dropDownQuickAddHtml .= '</div>';
3027
    $dropDownQuickAddHtml .= '</div>';
3028
3029
    return $dropDownQuickAddHtml;
3030
}
3031
3032
/**
3033
 * Build the tooltip on top menu bookmark
3034
 *
3035
 * @return  string                  HTML content
3036
 */
3037
function top_menu_bookmark()
3038
{
3039
    global $langs, $conf, $db, $user;
3040
3041
    $html = '';
3042
3043
    // Define $bookmarks
3044
    if (!isModEnabled('bookmark') || !$user->hasRight('bookmark', 'lire')) {
3045
        return $html;
3046
    }
3047
3048
    // accesskey is for Windows or Linux:  ALT + key for chrome, ALT + SHIFT + KEY for firefox
3049
    // accesskey is for Mac:               CTRL + key for all browsers
3050
    $stringforfirstkey = $langs->trans("KeyboardShortcut");
3051
    if ($conf->browser->os === 'macintosh') {
3052
        $stringforfirstkey .= ' CTL +';
3053
    } else {
3054
        if ($conf->browser->name == 'chrome') {
3055
            $stringforfirstkey .= ' ALT +';
3056
        } elseif ($conf->browser->name == 'firefox') {
3057
            $stringforfirstkey .= ' ALT + SHIFT +';
3058
        } else {
3059
            $stringforfirstkey .= ' CTL +';
3060
        }
3061
    }
3062
3063
    if (!defined('JS_JQUERY_DISABLE_DROPDOWN') && !empty($conf->use_javascript_ajax)) {     // This may be set by some pages that use different jquery version to avoid errors
3064
        include_once DOL_DOCUMENT_ROOT . '/bookmarks/lib/bookmarks.lib.php';
3065
        $langs->load("bookmarks");
3066
3067
        if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER')) {
3068
            $html .= '<div id="topmenu-bookmark-dropdown" class="dropdown inline-block">';
3069
            $html .= printDropdownBookmarksList();
3070
            $html .= '</div>';
3071
        } else {
3072
            $html .= '<!-- div for bookmark link -->
3073
	        <div id="topmenu-bookmark-dropdown" class="dropdown inline-block">
3074
	            <a accesskey="b" class="dropdown-toggle login-dropdown-a nofocusvisible" data-toggle="dropdown" href="#" title="' . $langs->trans('Bookmarks') . ' (' . $stringforfirstkey . ' b)"><i class="fa fa-star"></i></a>
3075
	            <div class="dropdown-menu">
3076
	                ' . printDropdownBookmarksList() . '
3077
	            </div>
3078
	        </div>';
3079
3080
            $html .= '
3081
	        <!-- Code to show/hide the bookmark drop-down -->
3082
	        <script>
3083
	        jQuery(document).ready(function() {
3084
	            jQuery(document).on("click", function(event) {
3085
	                if (!$(event.target).closest("#topmenu-bookmark-dropdown").length) {
3086
						//console.log("close bookmark dropdown - we click outside");
3087
	                    // Hide the menus.
3088
	                    $("#topmenu-bookmark-dropdown").removeClass("open");
3089
	                }
3090
	            });
3091
3092
	            jQuery("#topmenu-bookmark-dropdown .dropdown-toggle").on("click", function(event) {
3093
					console.log("Click on #topmenu-bookmark-dropdown .dropdown-toggle");
3094
					openBookMarkDropDown(event);
3095
	            });
3096
3097
	            // Key map shortcut
3098
	            jQuery(document).keydown(function(event) {
3099
					var ostype = \'' . dol_escape_js($conf->browser->os) . '\';
3100
					if (ostype === "macintosh") {
3101
						if ( event.which === 66 && event.ctrlKey ) {
3102
							console.log("Click on control + b : trigger open bookmark dropdown");
3103
							openBookMarkDropDown(event);
3104
						}
3105
					} else {
3106
						if ( event.which === 66 && event.ctrlKey && event.shiftKey ) {
3107
							console.log("Click on control + shift + b : trigger open bookmark dropdown");
3108
							openBookMarkDropDown(event);
3109
						}
3110
					}
3111
	            });
3112
3113
	            var openBookMarkDropDown = function(event) {
3114
	                event.preventDefault();
3115
	                jQuery("#topmenu-bookmark-dropdown").toggleClass("open");
3116
	                jQuery("#top-bookmark-search-input").focus();
3117
	            }
3118
3119
	        });
3120
	        </script>
3121
	        ';
3122
        }
3123
    }
3124
    return $html;
3125
}
3126
3127
/**
3128
 * Build the tooltip on top menu tsearch
3129
 *
3130
 * @return  string                  HTML content
3131
 */
3132
function top_menu_search()
3133
{
3134
    global $langs, $conf, $db, $user, $hookmanager;
3135
3136
    $html = '';
3137
3138
    $usedbyinclude = 1;
3139
    $arrayresult = array();
3140
    include DOL_DOCUMENT_ROOT . '/core/ajax/selectsearchbox.php'; // This sets $arrayresult
3141
3142
    // accesskey is for Windows or Linux:  ALT + key for chrome, ALT + SHIFT + KEY for firefox
3143
    // accesskey is for Mac:               CTRL + key for all browsers
3144
    $stringforfirstkey = $langs->trans("KeyboardShortcut");
3145
    if ($conf->browser->name == 'chrome') {
3146
        $stringforfirstkey .= ' ALT +';
3147
    } elseif ($conf->browser->name == 'firefox') {
3148
        $stringforfirstkey .= ' ALT + SHIFT +';
3149
    } else {
3150
        $stringforfirstkey .= ' CTL +';
3151
    }
3152
3153
    $searchInput = '<input type="search" name="search_all"' . ($stringforfirstkey ? ' title="' . dol_escape_htmltag($stringforfirstkey . ' s') . '"' : '') . ' id="top-global-search-input" class="dropdown-search-input search_component_input" placeholder="' . $langs->trans('Search') . '" autocomplete="off">';
3154
3155
    $defaultAction = '';
3156
    $buttonList = '<div class="dropdown-global-search-button-list" >';
3157
    // Menu with all searchable items
3158
    foreach ($arrayresult as $keyItem => $item) {
3159
        if (empty($defaultAction)) {
3160
            $defaultAction = $item['url'];
3161
        }
3162
        $buttonList .= '<button class="dropdown-item global-search-item tdoverflowmax300" data-target="' . dol_escape_htmltag($item['url']) . '" >';
3163
        $buttonList .= $item['text'];
3164
        $buttonList .= '</button>';
3165
    }
3166
    $buttonList .= '</div>';
3167
3168
    $dropDownHtml = '<form role="search" id="top-menu-action-search" name="actionsearch" method="GET" action="' . $defaultAction . '">';
3169
3170
    $dropDownHtml .= '
3171
        <!-- search input -->
3172
        <div class="dropdown-header search-dropdown-header">
3173
            ' . $searchInput . '
3174
        </div>
3175
    ';
3176
3177
    $dropDownHtml .= '
3178
        <!-- Menu Body search -->
3179
        <div class="dropdown-body search-dropdown-body">
3180
        ' . $buttonList . '
3181
        </div>
3182
        ';
3183
3184
    $dropDownHtml .= '</form>';
3185
3186
    // accesskey is for Windows or Linux:  ALT + key for chrome, ALT + SHIFT + KEY for firefox
3187
    // accesskey is for Mac:               CTRL + key for all browsers
3188
    $stringforfirstkey = $langs->trans("KeyboardShortcut");
3189
    if ($conf->browser->name == 'chrome') {
3190
        $stringforfirstkey .= ' ALT +';
3191
    } elseif ($conf->browser->name == 'firefox') {
3192
        $stringforfirstkey .= ' ALT + SHIFT +';
3193
    } else {
3194
        $stringforfirstkey .= ' CTL +';
3195
    }
3196
3197
    $html .= '<!-- div for Global Search -->
3198
    <div id="topmenu-global-search-dropdown" class="atoplogin dropdown inline-block">
3199
        <a accesskey="s" class="dropdown-toggle login-dropdown-a nofocusvisible" data-toggle="dropdown" href="#" title="' . $langs->trans('Search') . ' (' . $stringforfirstkey . ' s)">
3200
            <i class="fa fa-search" aria-hidden="true" ></i>
3201
        </a>
3202
        <div class="dropdown-menu dropdown-search">
3203
            ' . $dropDownHtml . '
3204
        </div>
3205
    </div>';
3206
3207
    $html .= '
3208
    <!-- Code to show/hide the user drop-down -->
3209
    <script>
3210
    jQuery(document).ready(function() {
3211
3212
        // prevent submitting form on press ENTER
3213
        jQuery("#top-global-search-input").keydown(function (e) {
3214
            if (e.keyCode == 13 || e.keyCode == 40) {
3215
                var inputs = $(this).parents("form").eq(0).find(":button");
3216
                if (inputs[inputs.index(this) + 1] != null) {
3217
                    inputs[inputs.index(this) + 1].focus();
3218
					 if (e.keyCode == 13){
3219
						 inputs[inputs.index(this) + 1].trigger("click");
3220
					 }
3221
3222
                }
3223
                e.preventDefault();
3224
                return false;
3225
            }
3226
        });
3227
3228
        // arrow key nav
3229
        jQuery(document).keydown(function(e) {
3230
			// Get the focused element:
3231
			var $focused = $(":focus");
3232
			if($focused.length && $focused.hasClass("global-search-item")){
3233
3234
           		// UP - move to the previous line
3235
				if (e.keyCode == 38) {
3236
				    e.preventDefault();
3237
					$focused.prev().focus();
3238
				}
3239
3240
				// DOWN - move to the next line
3241
				if (e.keyCode == 40) {
3242
				    e.preventDefault();
3243
					$focused.next().focus();
3244
				}
3245
			}
3246
        });
3247
3248
3249
        // submit form action
3250
        jQuery(".dropdown-global-search-button-list .global-search-item").on("click", function(event) {
3251
            jQuery("#top-menu-action-search").attr("action", $(this).data("target"));
3252
            jQuery("#top-menu-action-search").submit();
3253
        });
3254
3255
        // close drop down
3256
        jQuery(document).on("click", function(event) {
3257
			if (!$(event.target).closest("#topmenu-global-search-dropdown").length) {
3258
				console.log("click close search - we click outside");
3259
                // Hide the menus.
3260
                jQuery("#topmenu-global-search-dropdown").removeClass("open");
3261
            }
3262
        });
3263
3264
        // Open drop down
3265
        jQuery("#topmenu-global-search-dropdown .dropdown-toggle").on("click", function(event) {
3266
			console.log("click on toggle #topmenu-global-search-dropdown .dropdown-toggle");
3267
            openGlobalSearchDropDown();
3268
        });
3269
3270
        // Key map shortcut
3271
        jQuery(document).keydown(function(e){
3272
              if ( e.which === 70 && e.ctrlKey && e.shiftKey ) {
3273
                 console.log(\'control + shift + f : trigger open global-search dropdown\');
3274
                 openGlobalSearchDropDown();
3275
              }
3276
              if ( e.which === 70 && e.alKey ) {
3277
                 console.log(\'alt + f : trigger open global-search dropdown\');
3278
                 openGlobalSearchDropDown();
3279
              }
3280
        });
3281
3282
        var openGlobalSearchDropDown = function() {
3283
            jQuery("#topmenu-global-search-dropdown").toggleClass("open");
3284
            jQuery("#top-global-search-input").focus();
3285
        }
3286
3287
    });
3288
    </script>
3289
    ';
3290
3291
    return $html;
3292
}
3293
3294
/**
3295
 *  Show left menu bar
3296
 *
3297
 * @param string $menu_array_before Table of menu entries to show before entries of menu handler. This param is deprecated and must be provided to ''.
3298
 * @param string $helppagename Name of wiki page for help ('' by default).
3299
 *                                                  Syntax is: For a wiki page: EN:EnglishPage|FR:FrenchPage|ES:SpanishPage|DE:GermanPage
3300
 *                                                  For other external page: http://server/url
3301
 * @param string $notused Deprecated. Used in past to add content into left menu. Hooks can be used now.
3302
 * @param array $menu_array_after Table of menu entries to show after entries of menu handler
3303
 * @param int $leftmenuwithoutmainarea Must be set to 1. 0 by default for backward compatibility with old modules.
3304
 * @param string $title Title of web page
3305
 * @param int<0,1> $acceptdelayedhtml 1 if caller request to have html delayed content not returned but saved into global $delayedhtmlcontent (so caller can show it at end of page to avoid flash FOUC effect)
3306
 * @return void
3307
 */
3308
function left_menu($menu_array_before, $helppagename = '', $notused = '', $menu_array_after = array(), $leftmenuwithoutmainarea = 0, $title = '', $acceptdelayedhtml = 0)
3309
{
3310
    global $user, $conf, $langs, $db, $form;
3311
    global $hookmanager, $menumanager;
3312
3313
    $searchform = '';
3314
3315
    if (!empty($menu_array_before)) {
3316
        dol_syslog("Deprecated parameter menu_array_before was used when calling main::left_menu function. Menu entries of module should now be defined into module descriptor and not provided when calling left_menu.", LOG_WARNING);
3317
    }
3318
3319
    if (empty($conf->dol_hide_leftmenu) && (!defined('NOREQUIREMENU') || !constant('NOREQUIREMENU'))) {
3320
        // Instantiate hooks for external modules
3321
        $hookmanager->initHooks(array('leftblock'));
3322
3323
        print "\n" . '<!-- Begin side-nav id-left -->' . "\n" . '<div class="side-nav"><div id="id-left">' . "\n";
3324
        print "\n";
3325
3326
        if (!is_object($form)) {
3327
            $form = new Form($db);
3328
        }
3329
        $selected = -1;
3330
        if (!getDolGlobalString('MAIN_USE_TOP_MENU_SEARCH_DROPDOWN')) {
3331
            // Select with select2 is awful on smartphone. TODO Is this still true with select2 v4 ?
3332
            if ($conf->browser->layout == 'phone') {
3333
                $conf->global->MAIN_USE_OLD_SEARCH_FORM = 1;
3334
            }
3335
3336
            $usedbyinclude = 1;
3337
            $arrayresult = array();
3338
            include DOL_DOCUMENT_ROOT . '/core/ajax/selectsearchbox.php'; // This make initHooks('searchform') then set $arrayresult
3339
3340
            if ($conf->use_javascript_ajax && !getDolGlobalString('MAIN_USE_OLD_SEARCH_FORM')) {
3341
                // accesskey is for Windows or Linux:  ALT + key for chrome, ALT + SHIFT + KEY for firefox
3342
                // accesskey is for Mac:               CTRL + key for all browsers
3343
                $stringforfirstkey = $langs->trans("KeyboardShortcut");
3344
                if ($conf->browser->name == 'chrome') {
3345
                    $stringforfirstkey .= ' ALT +';
3346
                } elseif ($conf->browser->name == 'firefox') {
3347
                    $stringforfirstkey .= ' ALT + SHIFT +';
3348
                } else {
3349
                    $stringforfirstkey .= ' CTL +';
3350
                }
3351
3352
                //$textsearch = $langs->trans("Search");
3353
                $textsearch = '<span class="fa fa-search paddingright pictofixedwidth"></span>' . $langs->trans("Search");
3354
                $searchform .= $form->selectArrayFilter('searchselectcombo', $arrayresult, $selected, 'accesskey="s"', 1, 0, (!getDolGlobalString('MAIN_SEARCHBOX_CONTENT_LOADED_BEFORE_KEY') ? 1 : 0), 'vmenusearchselectcombo', 1, $textsearch, 1, $stringforfirstkey . ' s');
3355
            } else {
3356
                if (is_array($arrayresult)) {
3357
                    foreach ($arrayresult as $key => $val) {
3358
                        $searchform .= printSearchForm($val['url'], $val['url'], $val['label'], 'maxwidth125', 'search_all', (empty($val['shortcut']) ? '' : $val['shortcut']), 'searchleft' . $key, $val['img']);
3359
                    }
3360
                }
3361
            }
3362
3363
            // Execute hook printSearchForm
3364
            $parameters = array('searchform' => $searchform);
3365
            $reshook = $hookmanager->executeHooks('printSearchForm', $parameters); // Note that $action and $object may have been modified by some hooks
3366
            if (empty($reshook)) {
3367
                $searchform .= $hookmanager->resPrint;
3368
            } else {
3369
                $searchform = $hookmanager->resPrint;
3370
            }
3371
3372
            // Force special value for $searchform for text browsers or very old search form
3373
            if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER') || empty($conf->use_javascript_ajax)) {
3374
                $urltosearch = constant('BASE_URL') . '/core/search_page.php?showtitlebefore=1';
3375
                $searchform = '<div class="blockvmenuimpair blockvmenusearchphone"><div id="divsearchforms1"><a href="' . $urltosearch . '" accesskey="s" alt="' . dol_escape_htmltag($langs->trans("ShowSearchFields")) . '">' . $langs->trans("Search") . '...</a></div></div>';
3376
            } elseif ($conf->use_javascript_ajax && getDolGlobalString('MAIN_USE_OLD_SEARCH_FORM')) {
3377
                $searchform = '<div class="blockvmenuimpair blockvmenusearchphone"><div id="divsearchforms1"><a href="#" alt="' . dol_escape_htmltag($langs->trans("ShowSearchFields")) . '">' . $langs->trans("Search") . '...</a></div><div id="divsearchforms2" style="display: none">' . $searchform . '</div>';
3378
                $searchform .= '<script>
3379
            	jQuery(document).ready(function () {
3380
            		jQuery("#divsearchforms1").click(function(){
3381
	                   jQuery("#divsearchforms2").toggle();
3382
	               });
3383
            	});
3384
                </script>' . "\n";
3385
                $searchform .= '</div>';
3386
            }
3387
3388
            // Key map shortcut
3389
            $searchform .= '<script>
3390
				jQuery(document).keydown(function(e){
3391
					if( e.which === 70 && e.ctrlKey && e.shiftKey ){
3392
						console.log(\'control + shift + f : trigger open global-search dropdown\');
3393
		                openGlobalSearchDropDown();
3394
		            }
3395
		            if( (e.which === 83 || e.which === 115) && e.altKey ){
3396
		                console.log(\'alt + s : trigger open global-search dropdown\');
3397
		                openGlobalSearchDropDown();
3398
		            }
3399
		        });
3400
3401
		        var openGlobalSearchDropDown = function() {
3402
		            jQuery("#searchselectcombo").select2(\'open\');
3403
		        }
3404
			</script>';
3405
        }
3406
3407
        // Left column
3408
        print '<!-- Begin left menu -->' . "\n";
3409
3410
        print '<div class="vmenu"' . (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER') ? ' alt="Left menu"' : '') . '>' . "\n\n";
3411
3412
        // Show left menu with other forms
3413
        $menumanager->menu_array = $menu_array_before;
3414
        $menumanager->menu_array_after = $menu_array_after;
3415
        $menumanager->showmenu('left', array('searchform' => $searchform)); // output menu_array and menu found in database
3416
3417
        // Dolibarr version + help + bug report link
3418
        print "\n";
3419
        print "<!-- Begin Help Block-->\n";
3420
        print '<div id="blockvmenuhelp" class="blockvmenuhelp">' . "\n";
3421
3422
        // Version
3423
        if (getDolGlobalString('MAIN_SHOW_VERSION')) {    // Version is already on help picto and on login page.
3424
            $doliurl = 'https://www.dolibarr.org';
3425
            //local communities
3426
            if (preg_match('/fr/i', $langs->defaultlang)) {
3427
                $doliurl = 'https://www.dolibarr.fr';
3428
            }
3429
            if (preg_match('/es/i', $langs->defaultlang)) {
3430
                $doliurl = 'https://www.dolibarr.es';
3431
            }
3432
            if (preg_match('/de/i', $langs->defaultlang)) {
3433
                $doliurl = 'https://www.dolibarr.de';
3434
            }
3435
            if (preg_match('/it/i', $langs->defaultlang)) {
3436
                $doliurl = 'https://www.dolibarr.it';
3437
            }
3438
            if (preg_match('/gr/i', $langs->defaultlang)) {
3439
                $doliurl = 'https://www.dolibarr.gr';
3440
            }
3441
3442
            $appli = constant('DOL_APPLICATION_TITLE');
3443
            if (getDolGlobalString('MAIN_APPLICATION_TITLE')) {
3444
                $appli = getDolGlobalString('MAIN_APPLICATION_TITLE');
3445
                $doliurl = '';
3446
                if (preg_match('/\d\.\d/', $appli)) {
3447
                    if (!preg_match('/' . preg_quote(DOL_VERSION) . '/', $appli)) {
3448
                        $appli .= " (" . DOL_VERSION . ")"; // If new title contains a version that is different than core
3449
                    }
3450
                } else {
3451
                    $appli .= " " . DOL_VERSION;
3452
                }
3453
            } else {
3454
                $appli .= " " . DOL_VERSION;
3455
            }
3456
            print '<div id="blockvmenuhelpapp" class="blockvmenuhelp">';
3457
            if ($doliurl) {
3458
                print '<a class="help" target="_blank" rel="noopener noreferrer" href="' . $doliurl . '">';
3459
            } else {
3460
                print '<span class="help">';
3461
            }
3462
            print $appli;
3463
            if ($doliurl) {
3464
                print '</a>';
3465
            } else {
3466
                print '</span>';
3467
            }
3468
            print '</div>' . "\n";
3469
        }
3470
3471
        // Link to bugtrack
3472
        if (getDolGlobalString('MAIN_BUGTRACK_ENABLELINK')) {
3473
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/functions2.lib.php';
3474
3475
            if (getDolGlobalString('MAIN_BUGTRACK_ENABLELINK') == 'github') {
3476
                $bugbaseurl = 'https://github.com/Dolibarr/dolibarr/issues/new?labels=Bug';
3477
                $bugbaseurl .= '&title=';
3478
                $bugbaseurl .= urlencode("Bug: ");
3479
                $bugbaseurl .= '&body=';
3480
                $bugbaseurl .= urlencode("# Instructions\n");
3481
                $bugbaseurl .= urlencode("*This is a template to help you report good issues. You may use [Github Markdown](https://help.github.com/articles/getting-started-with-writing-and-formatting-on-github/) syntax to format your issue report.*\n");
3482
                $bugbaseurl .= urlencode("*Please:*\n");
3483
                $bugbaseurl .= urlencode("- *replace the bracket enclosed texts with meaningful information*\n");
3484
                $bugbaseurl .= urlencode("- *remove any unused sub-section*\n");
3485
                $bugbaseurl .= urlencode("\n");
3486
                $bugbaseurl .= urlencode("\n");
3487
                $bugbaseurl .= urlencode("# Bug\n");
3488
                $bugbaseurl .= urlencode("[*Short description*]\n");
3489
                $bugbaseurl .= urlencode("\n");
3490
                $bugbaseurl .= urlencode("## Environment\n");
3491
                $bugbaseurl .= urlencode("- **Version**: " . DOL_VERSION . "\n");
3492
                $bugbaseurl .= urlencode("- **OS**: " . php_uname('s') . "\n");
3493
                $bugbaseurl .= urlencode("- **Web server**: " . $_SERVER["SERVER_SOFTWARE"] . "\n");
3494
                $bugbaseurl .= urlencode("- **PHP**: " . php_sapi_name() . ' ' . phpversion() . "\n");
3495
                $bugbaseurl .= urlencode("- **Database**: " . $db::LABEL . ' ' . $db->getVersion() . "\n");
3496
                $bugbaseurl .= urlencode("- **URL(s)**: " . $_SERVER["REQUEST_URI"] . "\n");
3497
                $bugbaseurl .= urlencode("\n");
3498
                $bugbaseurl .= urlencode("## Expected and actual behavior\n");
3499
                $bugbaseurl .= urlencode("[*Verbose description*]\n");
3500
                $bugbaseurl .= urlencode("\n");
3501
                $bugbaseurl .= urlencode("## Steps to reproduce the behavior\n");
3502
                $bugbaseurl .= urlencode("[*Verbose description*]\n");
3503
                $bugbaseurl .= urlencode("\n");
3504
                $bugbaseurl .= urlencode("## [Attached files](https://help.github.com/articles/issue-attachments) (Screenshots, screencasts, alixar.log, debugging information…)\n");
3505
                $bugbaseurl .= urlencode("[*Files*]\n");
3506
                $bugbaseurl .= urlencode("\n");
3507
3508
                $bugbaseurl .= urlencode("\n");
3509
                $bugbaseurl .= urlencode("## Report\n");
3510
            } elseif (getDolGlobalString('MAIN_BUGTRACK_ENABLELINK')) {
3511
                $bugbaseurl = getDolGlobalString('MAIN_BUGTRACK_ENABLELINK');
3512
            } else {
3513
                $bugbaseurl = "";
3514
            }
3515
3516
            // Execute hook printBugtrackInfo
3517
            $parameters = array('bugbaseurl' => $bugbaseurl);
3518
            $reshook = $hookmanager->executeHooks('printBugtrackInfo', $parameters); // Note that $action and $object may have been modified by some hooks
3519
            if (empty($reshook)) {
3520
                $bugbaseurl .= $hookmanager->resPrint;
3521
            } else {
3522
                $bugbaseurl = $hookmanager->resPrint;
3523
            }
3524
3525
            print '<div id="blockvmenuhelpbugreport" class="blockvmenuhelp">';
3526
            print '<a class="help" target="_blank" rel="noopener noreferrer" href="' . $bugbaseurl . '"><i class="fas fa-bug"></i> ' . $langs->trans("FindBug") . '</a>';
3527
            print '</div>';
3528
        }
3529
3530
        print "</div>\n";
3531
        print "<!-- End Help Block-->\n";
3532
        print "\n";
3533
3534
        print "</div>\n";
3535
        print "<!-- End left menu -->\n";
3536
        print "\n";
3537
3538
        // Execute hook printLeftBlock
3539
        $parameters = array();
3540
        $reshook = $hookmanager->executeHooks('printLeftBlock', $parameters); // Note that $action and $object may have been modified by some hooks
3541
        print $hookmanager->resPrint;
3542
3543
        print '</div></div> <!-- End side-nav id-left -->'; // End div id="side-nav" div id="id-left"
3544
    }
3545
3546
    print "\n";
3547
    print '<!-- Begin right area -->' . "\n";
3548
3549
    if (empty($leftmenuwithoutmainarea)) {
3550
        main_area($title);
3551
    }
3552
}
3553
3554
3555
/**
3556
 *  Begin main area
3557
 *
3558
 * @param string $title Title
3559
 * @return void
3560
 */
3561
function main_area($title = '')
3562
{
3563
    global $conf, $langs, $hookmanager;
3564
3565
    if (empty($conf->dol_hide_leftmenu) && !GETPOST('dol_openinpopup')) {
3566
        print '<div id="id-right">';
3567
    }
3568
3569
    print "\n";
3570
3571
    print '<!-- Begin div class="fiche" -->' . "\n" . '<div class="fiche">' . "\n";
3572
3573
    $hookmanager->initHooks(array('main'));
3574
    $parameters = array();
3575
    $reshook = $hookmanager->executeHooks('printMainArea', $parameters); // Note that $action and $object may have been modified by some hooks
3576
    print $hookmanager->resPrint;
3577
3578
    if (getDolGlobalString('MAIN_ONLY_LOGIN_ALLOWED')) {
3579
        print info_admin($langs->trans("WarningYouAreInMaintenanceMode", getDolGlobalString('MAIN_ONLY_LOGIN_ALLOWED')), 0, 0, 1, 'warning maintenancemode');
3580
    }
3581
3582
    // Permit to add user company information on each printed document by setting SHOW_SOCINFO_ON_PRINT
3583
    if (getDolGlobalString('SHOW_SOCINFO_ON_PRINT') && GETPOST('optioncss', 'aZ09') == 'print' && empty(GETPOST('disable_show_socinfo_on_print', 'aZ09'))) {
3584
        $parameters = array();
3585
        $reshook = $hookmanager->executeHooks('showSocinfoOnPrint', $parameters);
3586
        if (empty($reshook)) {
3587
            print '<!-- Begin show mysoc info header -->' . "\n";
3588
            print '<div id="mysoc-info-header">' . "\n";
3589
            print '<table class="centpercent div-table-responsive">' . "\n";
3590
            print '<tbody>';
3591
            print '<tr><td rowspan="0" class="width20p">';
3592
            if (getDolGlobalString('MAIN_SHOW_LOGO') && !getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER') && getDolGlobalString('MAIN_INFO_SOCIETE_LOGO')) {
3593
                print '<img id="mysoc-info-header-logo" style="max-width:100%" alt="" src="' . constant('BASE_URL') . '/viewimage.php?cache=1&modulepart=mycompany&file=' . urlencode('logos/' . dol_escape_htmltag(getDolGlobalString('MAIN_INFO_SOCIETE_LOGO'))) . '">';
3594
            }
3595
            print '</td><td  rowspan="0" class="width50p"></td></tr>' . "\n";
3596
            print '<tr><td class="titre bold">' . dol_escape_htmltag(getDolGlobalString('MAIN_INFO_SOCIETE_NOM')) . '</td></tr>' . "\n";
3597
            print '<tr><td>' . dol_escape_htmltag(getDolGlobalString('MAIN_INFO_SOCIETE_ADDRESS')) . '<br>' . dol_escape_htmltag(getDolGlobalString('MAIN_INFO_SOCIETE_ZIP')) . ' ' . dol_escape_htmltag(getDolGlobalString('MAIN_INFO_SOCIETE_TOWN')) . '</td></tr>' . "\n";
3598
            if (getDolGlobalString('MAIN_INFO_SOCIETE_TEL')) {
3599
                print '<tr><td style="padding-left: 1em" class="small">' . $langs->trans("Phone") . ' : ' . dol_escape_htmltag(getDolGlobalString('MAIN_INFO_SOCIETE_TEL')) . '</td></tr>';
3600
            }
3601
            if (getDolGlobalString('MAIN_INFO_SOCIETE_MAIL')) {
3602
                print '<tr><td style="padding-left: 1em" class="small">' . $langs->trans("Email") . ' : ' . dol_escape_htmltag(getDolGlobalString('MAIN_INFO_SOCIETE_MAIL')) . '</td></tr>';
3603
            }
3604
            if (getDolGlobalString('MAIN_INFO_SOCIETE_WEB')) {
3605
                print '<tr><td style="padding-left: 1em" class="small">' . $langs->trans("Web") . ' : ' . dol_escape_htmltag(getDolGlobalString('MAIN_INFO_SOCIETE_WEB')) . '</td></tr>';
3606
            }
3607
            print '</tbody>';
3608
            print '</table>' . "\n";
3609
            print '</div>' . "\n";
3610
            print '<!-- End show mysoc info header -->' . "\n";
3611
        }
3612
    }
3613
}
3614
3615
3616
/**
3617
 *  Return helpbaseurl, helppage and mode
3618
 *
3619
 * @param string $helppagename Page name ('EN:xxx,ES:eee,FR:fff,DE:ddd...' or 'http://localpage')
3620
 * @param Translate $langs Language
3621
 * @return array{helpbaseurl:string,helppage:string,mode:string}   Array of help urls
3622
 */
3623
function getHelpParamFor($helppagename, $langs)
3624
{
3625
    $helpbaseurl = '';
3626
    $helppage = '';
3627
    $mode = '';
3628
3629
    if (preg_match('/^http/i', $helppagename)) {
3630
        // If complete URL
3631
        $helpbaseurl = '%s';
3632
        $helppage = $helppagename;
3633
        $mode = 'local';
3634
    } else {
3635
        // If WIKI URL
3636
        $reg = array();
3637
        if (preg_match('/^es/i', $langs->defaultlang)) {
3638
            $helpbaseurl = 'http://wiki.dolibarr.org/index.php/%s';
3639
            if (preg_match('/ES:([^|]+)/i', $helppagename, $reg)) {
3640
                $helppage = $reg[1];
3641
            }
3642
        }
3643
        if (preg_match('/^fr/i', $langs->defaultlang)) {
3644
            $helpbaseurl = 'http://wiki.dolibarr.org/index.php/%s';
3645
            if (preg_match('/FR:([^|]+)/i', $helppagename, $reg)) {
3646
                $helppage = $reg[1];
3647
            }
3648
        }
3649
        if (preg_match('/^de/i', $langs->defaultlang)) {
3650
            $helpbaseurl = 'http://wiki.dolibarr.org/index.php/%s';
3651
            if (preg_match('/DE:([^|]+)/i', $helppagename, $reg)) {
3652
                $helppage = $reg[1];
3653
            }
3654
        }
3655
        if (empty($helppage)) { // If help page not already found
3656
            $helpbaseurl = 'http://wiki.dolibarr.org/index.php/%s';
3657
            if (preg_match('/EN:([^|]+)/i', $helppagename, $reg)) {
3658
                $helppage = $reg[1];
3659
            }
3660
        }
3661
        $mode = 'wiki';
3662
    }
3663
    return array('helpbaseurl' => $helpbaseurl, 'helppage' => $helppage, 'mode' => $mode);
3664
}
3665
3666
3667
/**
3668
 *  Show a search area.
3669
 *  Used when the javascript quick search is not used.
3670
 *
3671
 * @param string $urlaction Url post
3672
 * @param string $urlobject Url of the link under the search box
3673
 * @param string $title Title search area
3674
 * @param string $htmlmorecss Add more css
3675
 * @param string $htmlinputname Field Name input form
3676
 * @param string $accesskey Accesskey
3677
 * @param string $prefhtmlinputname Complement for id to avoid multiple same id in the page
3678
 * @param string $img Image to use
3679
 * @param int $showtitlebefore Show title before input text instead of into placeholder. This can be set when output is dedicated for text browsers.
3680
 * @param int $autofocus Set autofocus on field
3681
 * @return string
3682
 */
3683
function printSearchForm($urlaction, $urlobject, $title, $htmlmorecss, $htmlinputname, $accesskey = '', $prefhtmlinputname = '', $img = '', $showtitlebefore = 0, $autofocus = 0)
3684
{
3685
    global $langs, $user;
3686
3687
    $ret = '';
3688
    $ret .= '<form action="' . $urlaction . '" method="post" class="searchform nowraponall tagtr">';
3689
    $ret .= '<input type="hidden" name="token" value="' . newToken() . '">';
3690
    $ret .= '<input type="hidden" name="savelogin" value="' . dol_escape_htmltag($user->login) . '">';
3691
    if ($showtitlebefore) {
3692
        $ret .= '<div class="tagtd left">' . $title . '</div> ';
3693
    }
3694
    $ret .= '<div class="tagtd">';
3695
    $ret .= img_picto('', $img, '', false, 0, 0, '', 'paddingright width20');
3696
    $ret .= '<input type="text" class="flat ' . $htmlmorecss . '"';
3697
    $ret .= ' style="background-repeat: no-repeat; background-position: 3px;"';
3698
    $ret .= ($accesskey ? ' accesskey="' . $accesskey . '"' : '');
3699
    $ret .= ' placeholder="' . strip_tags($title) . '"';
3700
    $ret .= ($autofocus ? ' autofocus' : '');
3701
    $ret .= ' name="' . $htmlinputname . '" id="' . $prefhtmlinputname . $htmlinputname . '" />';
3702
    $ret .= '<button type="submit" class="button bordertransp" style="padding-top: 4px; padding-bottom: 4px; padding-left: 6px; padding-right: 6px">';
3703
    $ret .= '<span class="fa fa-search"></span>';
3704
    $ret .= '</button>';
3705
    $ret .= '</div>';
3706
    $ret .= "</form>\n";
3707
    return $ret;
3708
}
3709
3710
3711
if (!function_exists("llxFooter")) {
3712
    /**
3713
     * Show HTML footer
3714
     * Close div /DIV class=fiche + /DIV id-right + /DIV id-container + /BODY + /HTML.
3715
     * If global var $delayedhtmlcontent was filled, we output it just before closing the body.
3716
     *
3717
     * @param string $comment A text to add as HTML comment into HTML generated page
3718
     * @param string $zone 'private' (for private pages) or 'public' (for public pages)
3719
     * @param int $disabledoutputofmessages Clear all messages stored into session without displaying them
3720
     * @return  void
3721
     */
3722
    function llxFooter($comment = '', $zone = 'private', $disabledoutputofmessages = 0)
3723
    {
3724
        global $conf, $db, $langs, $user, $mysoc, $object, $hookmanager, $action;
3725
        global $delayedhtmlcontent;
3726
        global $contextpage, $page, $limit, $mode;
3727
        global $dolibarr_distrib;
3728
3729
        $ext = 'layout=' . urlencode($conf->browser->layout) . '&version=' . urlencode(DOL_VERSION);
3730
3731
        // Hook to add more things on all pages within fiche DIV
3732
        $llxfooter = '';
3733
        $parameters = array();
3734
        $reshook = $hookmanager->executeHooks('llxFooter', $parameters, $object, $action); // Note that $action and $object may have been modified by hook
3735
        if (empty($reshook)) {
3736
            $llxfooter .= $hookmanager->resPrint;
3737
        } elseif ($reshook > 0) {
3738
            $llxfooter = $hookmanager->resPrint;
3739
        }
3740
        if ($llxfooter) {
3741
            print $llxfooter;
3742
        }
3743
3744
        // Global html output events ($mesgs, $errors, $warnings)
3745
        dol_htmloutput_events($disabledoutputofmessages);
3746
3747
        // Code for search criteria persistence.
3748
        // $user->lastsearch_values was set by the GETPOST when form field search_xxx exists
3749
        if (is_object($user) && !empty($user->lastsearch_values_tmp) && is_array($user->lastsearch_values_tmp)) {
3750
            // Clean and save data
3751
            foreach ($user->lastsearch_values_tmp as $key => $val) {
3752
                unset($_SESSION['lastsearch_values_tmp_' . $key]); // Clean array to rebuild it just after
3753
                if (count($val) && empty($_POST['button_removefilter']) && empty($_POST['button_removefilter_x'])) {
3754
                    if (empty($val['sortfield'])) {
3755
                        unset($val['sortfield']);
3756
                    }
3757
                    if (empty($val['sortorder'])) {
3758
                        unset($val['sortorder']);
3759
                    }
3760
                    dol_syslog('Save lastsearch_values_tmp_' . $key . '=' . json_encode($val, 0) . " (systematic recording of last search criteria)");
3761
                    $_SESSION['lastsearch_values_tmp_' . $key] = json_encode($val);
3762
                    unset($_SESSION['lastsearch_values_' . $key]);
3763
                }
3764
            }
3765
        }
3766
3767
3768
        $relativepathstring = $_SERVER["PHP_SELF"];
3769
        // Clean $relativepathstring
3770
        if (constant('DOL_URL_ROOT')) {
3771
            $relativepathstring = preg_replace('/^' . preg_quote(constant('DOL_URL_ROOT'), '/') . '/', '', $relativepathstring);
3772
        }
3773
        $relativepathstring = preg_replace('/^\//', '', $relativepathstring);
3774
        $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
3775
        if (preg_match('/list\.php$/', $relativepathstring)) {
3776
            unset($_SESSION['lastsearch_contextpage_tmp_' . $relativepathstring]);
3777
            unset($_SESSION['lastsearch_page_tmp_' . $relativepathstring]);
3778
            unset($_SESSION['lastsearch_limit_tmp_' . $relativepathstring]);
3779
            unset($_SESSION['lastsearch_mode_tmp_' . $relativepathstring]);
3780
3781
            if (!empty($contextpage)) {
3782
                $_SESSION['lastsearch_contextpage_tmp_' . $relativepathstring] = $contextpage;
3783
            }
3784
            if (!empty($page) && $page > 0) {
3785
                $_SESSION['lastsearch_page_tmp_' . $relativepathstring] = $page;
3786
            }
3787
            if (!empty($limit) && $limit != $conf->liste_limit) {
3788
                $_SESSION['lastsearch_limit_tmp_' . $relativepathstring] = $limit;
3789
            }
3790
            if (!empty($mode)) {
3791
                $_SESSION['lastsearch_mode_tmp_' . $relativepathstring] = $mode;
3792
            }
3793
3794
            unset($_SESSION['lastsearch_contextpage_' . $relativepathstring]);
3795
            unset($_SESSION['lastsearch_page_' . $relativepathstring]);
3796
            unset($_SESSION['lastsearch_limit_' . $relativepathstring]);
3797
            unset($_SESSION['lastsearch_mode_' . $relativepathstring]);
3798
        }
3799
3800
        // Core error message
3801
        if (getDolGlobalString('MAIN_CORE_ERROR')) {
3802
            // Ajax version
3803
            if ($conf->use_javascript_ajax) {
3804
                $title = img_warning() . ' ' . $langs->trans('CoreErrorTitle');
3805
                print ajax_dialog($title, $langs->trans('CoreErrorMessage'));
3806
            } else {
3807
                // html version
3808
                $msg = img_warning() . ' ' . $langs->trans('CoreErrorMessage');
3809
                print '<div class="error">' . $msg . '</div>';
3810
            }
3811
3812
            //define("MAIN_CORE_ERROR",0);      // Constant was defined and we can't change value of a constant
3813
        }
3814
3815
        print "\n\n";
3816
3817
        print '</div> <!-- End div class="fiche" -->' . "\n"; // End div fiche
3818
3819
        if (empty($conf->dol_hide_leftmenu) && !GETPOST('dol_openinpopup')) {
3820
            print '</div> <!-- End div id-right -->' . "\n"; // End div id-right
3821
        }
3822
3823
        if (empty($conf->dol_hide_leftmenu) && empty($conf->dol_use_jmobile)) {
3824
            print '</div> <!-- End div id-container -->' . "\n"; // End div container
3825
        }
3826
3827
        print "\n";
3828
        if ($comment) {
3829
            print '<!-- ' . $comment . ' -->' . "\n";
3830
        }
3831
3832
        printCommonFooter($zone);
3833
3834
        if (!empty($delayedhtmlcontent)) {
3835
            print $delayedhtmlcontent;
3836
        }
3837
3838
        if (!empty($conf->use_javascript_ajax)) {
3839
            print "\n" . '<!-- Includes JS Footer of Dolibarr -->' . "\n";
3840
            print '<script src="' . constant('BASE_URL') . '/core/js/lib_foot.js.php?lang=' . $langs->defaultlang . ($ext ? '&' . $ext : '') . '"></script>' . "\n";
3841
        }
3842
3843
        // Wrapper to add log when clicking on download or preview
3844
        if (isModEnabled('blockedlog') && is_object($object) && !empty($object->id) && $object->id > 0) {
3845
            if (in_array($object->element, array('facture')) && $object->statut > 0) {       // Restrict for the moment to element 'facture'
3846
                print "\n<!-- JS CODE TO ENABLE log when making a download or a preview of a document -->\n";
3847
                ?>
3848
                <script>
3849
                    jQuery(document).ready(function () {
3850
                        $('a.documentpreview').click(function () {
3851
                            console.log("Call /blockedlog/ajax/block-add on a.documentpreview");
3852
                            $.post('<?php echo DOL_URL_ROOT . "/blockedlog/ajax/block-add.php" ?>'
3853
                                , {
3854
                                    id:<?php echo $object->id; ?>
3855
                                    , element: '<?php echo dol_escape_js($object->element) ?>'
3856
                                    , action: 'DOC_PREVIEW'
3857
                                    , token: '<?php echo currentToken(); ?>'
3858
                                }
3859
                            );
3860
                        });
3861
                        $('a.documentdownload').click(function () {
3862
                            console.log("Call /blockedlog/ajax/block-add a.documentdownload");
3863
                            $.post('<?php echo DOL_URL_ROOT . "/blockedlog/ajax/block-add.php" ?>'
3864
                                , {
3865
                                    id:<?php echo $object->id; ?>
3866
                                    , element: '<?php echo dol_escape_js($object->element) ?>'
3867
                                    , action: 'DOC_DOWNLOAD'
3868
                                    , token: '<?php echo currentToken(); ?>'
3869
                                }
3870
                            );
3871
                        });
3872
                    });
3873
                </script>
3874
                <?php
3875
            }
3876
        }
3877
3878
        // A div for the address popup
3879
        print "\n<!-- A div to allow dialog popup by jQuery('#dialogforpopup').dialog() -->\n";
3880
        print '<div id="dialogforpopup" style="display: none;"></div>' . "\n";
3881
3882
        // Add code for the asynchronous anonymous first ping (for telemetry)
3883
        // You can use &forceping=1 in parameters to force the ping if the ping was already sent.
3884
        $forceping = GETPOST('forceping', 'alpha');
3885
        if (($_SERVER["PHP_SELF"] == constant('BASE_URL') . '/index.php') || $forceping) {
3886
            //print '<!-- instance_unique_id='.$conf->file->instance_unique_id.' MAIN_FIRST_PING_OK_ID='.$conf->global->MAIN_FIRST_PING_OK_ID.' -->';
3887
            $hash_unique_id = dol_hash('dolibarr' . $conf->file->instance_unique_id, 'sha256');   // Note: if the global salt changes, this hash changes too so ping may be counted twice. We don't mind. It is for statistics purpose only.
3888
3889
            if (
3890
                !getDolGlobalString('MAIN_FIRST_PING_OK_DATE')
3891
                || (!empty($conf->file->instance_unique_id) && ($hash_unique_id != $conf->global->MAIN_FIRST_PING_OK_ID) && (getDolGlobalString('MAIN_FIRST_PING_OK_ID') != 'disabled'))
3892
                || $forceping
3893
            ) {
3894
                // No ping done if we are into an alpha version
3895
                if (strpos('alpha', DOL_VERSION) > 0 && !$forceping) {
3896
                    print "\n<!-- NO JS CODE TO ENABLE the anonymous Ping. It is an alpha version -->\n";
3897
                } elseif (empty($_COOKIE['DOLINSTALLNOPING_' . $hash_unique_id]) || $forceping) { // Cookie is set when we uncheck the checkbox in the installation wizard.
3898
                    // MAIN_LAST_PING_KO_DATE
3899
                    // Disable ping if MAIN_LAST_PING_KO_DATE is set and is recent (this month)
3900
                    if (getDolGlobalString('MAIN_LAST_PING_KO_DATE') && substr($conf->global->MAIN_LAST_PING_KO_DATE, 0, 6) == dol_print_date(dol_now(), '%Y%m') && !$forceping) {
3901
                        print "\n<!-- NO JS CODE TO ENABLE the anonymous Ping. An error already occurred this month, we will try later. -->\n";
3902
                    } else {
3903
                        include_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php';
3904
3905
                        print "\n" . '<!-- Includes JS for Ping of Dolibarr forceping=' . $forceping . ' MAIN_FIRST_PING_OK_DATE=' . getDolGlobalString("MAIN_FIRST_PING_OK_DATE") . ' MAIN_FIRST_PING_OK_ID=' . getDolGlobalString("MAIN_FIRST_PING_OK_ID") . ' MAIN_LAST_PING_KO_DATE=' . getDolGlobalString("MAIN_LAST_PING_KO_DATE") . ' -->' . "\n";
3906
                        print "\n<!-- JS CODE TO ENABLE the anonymous Ping -->\n";
3907
                        $url_for_ping = getDolGlobalString('MAIN_URL_FOR_PING', "https://ping.dolibarr.org/");
3908
                        // Try to guess the distrib used
3909
                        $distrib = 'standard';
3910
                        if ($_SERVER["SERVER_ADMIN"] == 'doliwamp@localhost') {
3911
                            $distrib = 'doliwamp';
3912
                        }
3913
                        if (!empty($dolibarr_distrib)) {
3914
                            $distrib = $dolibarr_distrib;
3915
                        }
3916
                        ?>
3917
                        <script>
3918
                            jQuery(document).ready(function (tmp) {
3919
                                console.log("Try Ping with hash_unique_id is dol_hash('dolibarr'+instance_unique_id, 'sha256')");
3920
                                $.ajax({
3921
                                    method: "POST",
3922
                                    url: "<?php echo $url_for_ping ?>",
3923
                                    timeout: 500,     // timeout milliseconds
3924
                                    cache: false,
3925
                                    data: {
3926
                                        hash_algo: 'dol_hash-sha256',
3927
                                        hash_unique_id: '<?php echo dol_escape_js($hash_unique_id); ?>',
3928
                                        action: 'dolibarrping',
3929
                                        version: '<?php echo (float)DOL_VERSION; ?>',
3930
                                        entity: '<?php echo (int)$conf->entity; ?>',
3931
                                        dbtype: '<?php echo dol_escape_js($db->type); ?>',
3932
                                        country_code: '<?php echo $mysoc->country_code ? dol_escape_js($mysoc->country_code) : 'unknown'; ?>',
3933
                                        php_version: '<?php echo dol_escape_js(phpversion()); ?>',
3934
                                        os_version: '<?php echo dol_escape_js(version_os('smr')); ?>',
3935
                                        db_version: '<?php echo dol_escape_js(version_db()); ?>',
3936
                                        distrib: '<?php echo $distrib ? dol_escape_js($distrib) : 'unknown'; ?>',
3937
                                        token: 'notrequired'
3938
                                    },
3939
                                    success: function (data, status, xhr) {   // success callback function (data contains body of response)
3940
                                        console.log("Ping ok");
3941
                                        $.ajax({
3942
                                            method: 'GET',
3943
                                            url: '<?php echo constant('BASE_URL') . '/core/ajax/pingresult.php'; ?>',
3944
                                            timeout: 500,     // timeout milliseconds
3945
                                            cache: false,
3946
                                            data: {
3947
                                                hash_algo: 'dol_hash-sha256',
3948
                                                hash_unique_id: '<?php echo dol_escape_js($hash_unique_id); ?>',
3949
                                                action: 'firstpingok',
3950
                                                token: '<?php echo currentToken(); ?>'
3951
                                            }, // for update
3952
                                        });
3953
                                    },
3954
                                    error: function (data, status, xhr) {   // error callback function
3955
                                        console.log("Ping ko: " + data);
3956
                                        $.ajax({
3957
                                            method: 'GET',
3958
                                            url: '<?php echo constant('BASE_URL') . '/core/ajax/pingresult.php'; ?>',
3959
                                            timeout: 500,     // timeout milliseconds
3960
                                            cache: false,
3961
                                            data: {
3962
                                                hash_algo: 'dol_hash-sha256',
3963
                                                hash_unique_id: '<?php echo dol_escape_js($hash_unique_id); ?>',
3964
                                                action: 'firstpingko',
3965
                                                token: '<?php echo currentToken(); ?>'
3966
                                            },
3967
                                        });
3968
                                    }
3969
                                });
3970
                            });
3971
                        </script>
3972
                        <?php
3973
                    }
3974
                } else {
3975
                    $now = dol_now();
3976
                    print "\n<!-- NO JS CODE TO ENABLE the anonymous Ping. It was disabled -->\n";
3977
                    include_once DOL_DOCUMENT_ROOT . '/core/lib/admin.lib.php';
3978
                    dolibarr_set_const($db, 'MAIN_FIRST_PING_OK_DATE', dol_print_date($now, 'dayhourlog', 'gmt'), 'chaine', 0, '', $conf->entity);
3979
                    dolibarr_set_const($db, 'MAIN_FIRST_PING_OK_ID', 'disabled', 'chaine', 0, '', $conf->entity);
3980
                }
3981
            }
3982
        }
3983
3984
        $parameters = array();
3985
        $reshook = $hookmanager->executeHooks('beforeBodyClose', $parameters); // Note that $action and $object may have been modified by some hooks
3986
        if ($reshook > 0) {
3987
            print $hookmanager->resPrint;
3988
        }
3989
3990
        print "</body>\n";
3991
        print "</html>\n";
3992
    }
3993
}
3994