Passed
Push — master ( 3cffbe...0f9140 )
by Alxarafe
23:50
created

Helpers/AlDolUtils.php (1 issue)

1
<?php
2
/* Copyright (C) 2000-2007	Rodolphe Quiedeville			<[email protected]>
3
 * Copyright (C) 2003		Jean-Louis Bergamo			<[email protected]>
4
 * Copyright (C) 2004-2018	Laurent Destailleur			<[email protected]>
5
 * Copyright (C) 2004		Sebastien Di Cintio			<[email protected]>
6
 * Copyright (C) 2004		Benoit Mortier				<[email protected]>
7
 * Copyright (C) 2004		Christophe Combelles			<[email protected]>
8
 * Copyright (C) 2005-2017	Regis Houssin				<[email protected]>
9
 * Copyright (C) 2008		Raphael Bertrand (Resultic)	<[email protected]>
10
 * Copyright (C) 2010-2018	Juanjo Menent				<[email protected]>
11
 * Copyright (C) 2013		Cédric Salvador				<[email protected]>
12
 * Copyright (C) 2013-2017	Alexandre Spangaro			<[email protected]>
13
 * Copyright (C) 2014		Cédric GROSS					<[email protected]>
14
 * Copyright (C) 2014-2015	Marcos García				<[email protected]>
15
 * Copyright (C) 2015		Jean-François Ferry			<[email protected]>
16
 * Copyright (C) 2018       Frédéric France             <[email protected]>
17
 * Copyright (C) 2018-2019  Alxarafe                    <[email protected]>
18
 *
19
 * This program is free software; you can redistribute it and/or modify
20
 * it under the terms of the GNU General Public License as published by
21
 * the Free Software Foundation; either version 3 of the License, or
22
 * (at your option) any later version.
23
 *
24
 * This program is distributed in the hope that it will be useful,
25
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
27
 * GNU General Public License for more details.
28
 *
29
 * You should have received a copy of the GNU General Public License
30
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
31
 * or see http://www.gnu.org/
32
 */
33
namespace Alixar\Helpers;
34
35
// include_once DOL_BASE_PATH . '/core/lib/json.lib.php';
36
use Alixar\Helpers\Security;
37
use Mobile_Detect;
38
39
class AlDolUtils
40
{
41
42
    /**
43
     * Function to return value of a static property when class
44
     * name is dynamically defined (not hard coded).
45
     * This is because $myclass::$myvar works from PHP 5.3.0+ only
46
     *
47
     * @param	string 	$class		Class name
48
     * @param 	string 	$member		Name of property
49
     * @return 	mixed				Return value of static property
50
     * @deprecated Dolibarr now requires 5.3.0+, use $class::$property syntax
51
     * @see https://php.net/manual/language.oop5.static.php
52
     */
53
    static function getStaticMember($class, $member)
54
    {
55
        AlDolUtils::dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);
56
57
        // This part is deprecated. Uncomment if for php 5.2.*, and comment next isset class::member
58
        /* if (version_compare(phpversion(), '5.3.0', '<'))
59
          {
60
          if (is_object($class)) $class = get_class($class);
61
          $classObj = new ReflectionClass($class);
62
          $result = null;
63
64
          $found=0;
65
          foreach($classObj->getStaticProperties() as $prop => $value)
66
          {
67
          if ($prop == $member)
68
          {
69
          $result = $value;
70
          $found++;
71
          break;
72
          }
73
          }
74
75
          if ($found) return $result;
76
          } */
77
78
        if (isset($class::$member))
79
            return $class::$member;
80
        dol_print_error('', 'Try to get a static member "' . $member . '" in class "' . $class . '" that does not exists or is not static.');
81
        return null;
82
    }
83
84
    /**
85
     * Return a DoliDB instance (database handler).
86
     *
87
     * @param   string	$type		Type of database (mysql, pgsql...)
88
     * @param	string	$host		Address of database server
89
     * @param	string	$user		Nom de l'utilisateur autorise
90
     * @param	string	$pass		Mot de passe
91
     * @param	string	$name		Nom de la database
92
     * @param	int		$port		Port of database server
93
     * @return	DoliDB				A DoliDB instance
94
     */
95
    static function getDoliDBInstance($type, $host, $user, $pass, $name, $port)
96
    {
97
        require_once DOL_BASE_PATH . "/core/db/" . $type . '.class.php';
98
99
        $class = 'DoliDB' . ucfirst($type);
100
        $dolidb = new $class($type, $host, $user, $pass, $name, $port);
101
        return $dolidb;
102
    }
103
104
    /**
105
     * 	Get list of entity id to use.
106
     *
107
     * 	@param	string	$element		Current element
108
     * 									'societe', 'socpeople', 'actioncomm', 'agenda', 'resource',
109
     * 									'product', 'productprice', 'stock',
110
     * 									'propal', 'supplier_proposal', 'invoice', 'facture_fourn', 'payment_various',
111
     * 									'categorie', 'bank_account', 'bank_account', 'adherent', 'user',
112
     * 									'commande', 'commande_fournisseur', 'expedition', 'intervention', 'survey',
113
     * 									'contract', 'tax', 'expensereport', 'holiday', 'multicurrency', 'project',
114
     * 									'email_template', 'event', 'donation'
115
     * 									'c_paiement', 'c_payment_term', ...
116
     * 	@param	int		$shared			0=Return id of current entity only,
117
     * 									1=Return id of current entity + shared entities (default)
118
     *  @param	object	$currentobject	Current object if needed
119
     * 	@return	mixed				Entity id(s) to use
120
     */
121
    static function getEntity($element, $shared = 1, $currentobject = null)
122
    {
123
        //// global Globals::$conf, $mc;
124
125
        if (is_object(Globals::$mc)) {
126
            return Globals::$mc->getEntity($element, $shared, $currentobject);
127
        } else {
128
            $out = '';
129
            $addzero = array('user', 'usergroup', 'c_email_templates', 'email_template', 'default_values');
130
            if (in_array($element, $addzero))
131
                $out .= '0,';
132
            $out .= Globals::$conf->entity;
133
            return $out;
134
        }
135
    }
136
137
    /**
138
     * Return information about user browser
139
     *
140
     * Returns array with the following format:
141
     * array(
142
     *  'browsername' => Browser name (firefox|chrome|iceweasel|epiphany|safari|opera|ie|unknown)
143
     *  'browserversion' => Browser version. Empty if unknown
144
     *  'browseros' => Set with mobile OS (android|blackberry|ios|palm|symbian|webos|maemo|windows|unknown)
145
     *  'layout' => (tablet|phone|classic)
146
     *  'phone' => empty if not mobile, (android|blackberry|ios|palm|unknown) if mobile
147
     *  'tablet' => true/false
148
     * )
149
     *
150
     * @param string $user_agent Content of $_SERVER["HTTP_USER_AGENT"] variable
151
     * @return	array Check function documentation
152
     */
153
    static function getBrowserInfo($user_agent)
154
    {
155
        //include_once BASE_PATH . '/vendor/mobiledetect/mobiledetectlib/Mobile_Detect.php';
156
157
        $name = 'unknown';
158
        $version = '';
159
        $os = 'unknown';
160
        $phone = '';
161
162
        $detectmobile = new Mobile_Detect(null, $user_agent);
163
        $tablet = $detectmobile->isTablet();
164
165
        if ($detectmobile->isMobile()) {
166
167
            $phone = 'unknown';
168
169
            // If phone/smartphone, we set phone os name.
170
            if ($detectmobile->is('AndroidOS')) {
171
                $os = $phone = 'android';
172
            } elseif ($detectmobile->is('BlackBerryOS')) {
173
                $os = $phone = 'blackberry';
174
            } elseif ($detectmobile->is('iOS')) {
175
                $os = 'ios';
176
                $phone = 'iphone';
177
            } elseif ($detectmobile->is('PalmOS')) {
178
                $os = $phone = 'palm';
179
            } elseif ($detectmobile->is('SymbianOS')) {
180
                $os = 'symbian';
181
            } elseif ($detectmobile->is('webOS')) {
182
                $os = 'webos';
183
            } elseif ($detectmobile->is('MaemoOS')) {
184
                $os = 'maemo';
185
            } elseif ($detectmobile->is('WindowsMobileOS') || $detectmobile->is('WindowsPhoneOS')) {
186
                $os = 'windows';
187
            }
188
        }
189
190
        // OS
191
        if (preg_match('/linux/i', $user_agent)) {
192
            $os = 'linux';
193
        } elseif (preg_match('/macintosh/i', $user_agent)) {
194
            $os = 'macintosh';
195
        } elseif (preg_match('/windows/i', $user_agent)) {
196
            $os = 'windows';
197
        }
198
199
        // Name
200
        if (preg_match('/firefox(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
201
            $name = 'firefox';
202
            $version = $reg[2];
203
        } elseif (preg_match('/edge(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
204
            $name = 'edge';
205
            $version = $reg[2];
206
        } elseif (preg_match('/chrome(\/|\s)([\d\.]+)/i', $user_agent, $reg)) {
207
            $name = 'chrome';
208
            $version = $reg[2];
209
        }    // we can have 'chrome (Mozilla...) chrome x.y' in one string
210
        elseif (preg_match('/chrome/i', $user_agent, $reg)) {
211
            $name = 'chrome';
212
        } elseif (preg_match('/iceweasel/i', $user_agent)) {
213
            $name = 'iceweasel';
214
        } elseif (preg_match('/epiphany/i', $user_agent)) {
215
            $name = 'epiphany';
216
        } elseif (preg_match('/safari(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
217
            $name = 'safari';
218
            $version = $reg[2];
219
        } // Safari is often present in string for mobile but its not.
220
        elseif (preg_match('/opera(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
221
            $name = 'opera';
222
            $version = $reg[2];
223
        } elseif (preg_match('/(MSIE\s([0-9]+\.[0-9]))|.*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) {
224
            $name = 'ie';
225
            $version = end($reg);
226
        }    // MS products at end
227
        elseif (preg_match('/(Windows NT\s([0-9]+\.[0-9])).*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) {
228
            $name = 'ie';
229
            $version = end($reg);
230
        }    // MS products at end
231
        elseif (preg_match('/l(i|y)n(x|ks)(\(|\/|\s)*([\d\.]+)/i', $user_agent, $reg)) {
232
            $name = 'lynxlinks';
233
            $version = $reg[4];
234
        }
235
236
        if ($tablet) {
237
            $layout = 'tablet';
238
        } elseif ($phone) {
239
            $layout = 'phone';
240
        } else {
241
            $layout = 'classic';
242
        }
243
244
        return array(
245
            'browsername' => $name,
246
            'browserversion' => $version,
247
            'browseros' => $os,
248
            'layout' => $layout,
249
            'phone' => $phone,
250
            'tablet' => $tablet
251
        );
252
    }
253
254
    /**
255
     *  Function called at end of web php process
256
     *
257
     *  @return	void
258
     */
259
    static function dol_shutdown()
260
    {
261
        // global Globals::$conf, $user, Globals::$langs, $db;
262
        $disconnectdone = false;
263
        $depth = 0;
264
        if (is_object($db) && !empty($db->connected)) {
265
            $depth = $db->transaction_opened;
266
            $disconnectdone = $db->close();
267
        }
268
        AlDolUtils::dol_syslog("--- End access to " . $_SERVER["PHP_SELF"] . (($disconnectdone && $depth) ? ' (Warn: db disconnection forced, transaction depth was ' . $depth . ')' : ''), (($disconnectdone && $depth) ? LOG_WARNING : LOG_INFO));
269
    }
270
271
    /**
272
     * Return true if we are in a context of submitting a parameter
273
     *
274
     * @param 	string	$paramname		Name or parameter to test
275
     * @return 	boolean					True if we have just submit a POST or GET request with the parameter provided (even if param is empty)
276
     */
277
    static function GETPOSTISSET($paramname)
278
    {
279
        return (isset($_POST[$paramname]) || isset($_GET[$paramname]));
280
    }
281
282
    /**
283
     *  Return value of a param into GET or POST supervariable.
284
     *  Use the property $user->default_values[path]['creatform'] and/or $user->default_values[path]['filters'] and/or $user->default_values[path]['sortorder']
285
     *  Note: The property $user->default_values is loaded by main.php when loading the user.
286
     *
287
     *  @param  string  $paramname   Name of parameter to found
288
     *  @param  string  $check	     Type of check
289
     *                               ''=no check (deprecated)
290
     *                               'none'=no check (only for param that should have very rich content)
291
     *                               'int'=check it's numeric (integer or float)
292
     *                               'intcomma'=check it's integer+comma ('1,2,3,4...')
293
     *                               'alpha'=check it's text and sign
294
     *                               'aZ'=check it's a-z only
295
     *                               'aZ09'=check it's simple alpha string (recommended for keys)
296
     *                               'array'=check it's array
297
     *                               'san_alpha'=Use filter_var with FILTER_SANITIZE_STRING (do not use this for free text string)
298
     *                               'nohtml', 'alphanohtml'=check there is no html content
299
     *                               'custom'= custom filter specify $filter and $options)
300
     *  @param	int		$method	     Type of method (0 = get then post, 1 = only get, 2 = only post, 3 = post then get, 4 = post then get then cookie)
301
     *  @param  int     $filter      Filter to apply when $check is set to 'custom'. (See http://php.net/manual/en/filter.filters.php for détails)
302
     *  @param  mixed   $options     Options to pass to filter_var when $check is set to 'custom'
303
     *  @param	string	$noreplace	 Force disable of replacement of __xxx__ strings.
304
     *  @return string|string[]      Value found (string or array), or '' if check fails
305
     */
306
    static function GETPOST($paramname, $check = 'none', $method = 0, $filter = null, $options = null, $noreplace = 0)
307
    {
308
        // global $mysoc, $user, Globals::$conf;
309
310
        if (empty($paramname)) {
311
            return 'BadFirstParameterForDolUtils::GETPOST';
312
        }
313
        if (empty($check)) {
314
            AlDolUtils::dol_syslog("Deprecated use of AlDolUtils::GETPOST, called with 1st param = " . $paramname . " and 2nd param is '', when calling page " . $_SERVER["PHP_SELF"], LOG_WARNING);
315
            // Enable this line to know who call the AlDolUtils::GETPOST with '' $check parameter.
316
            //var_dump(debug_backtrace()[0]);
317
        }
318
319
        switch ($method) {
320
            case 1:
321
                $out = isset($_GET[$paramname]) ? $_GET[$paramname] : '';
322
                break;
323
            case 2:
324
                $out = isset($_POST[$paramname]) ? $_POST[$paramname] : '';
325
                break;
326
            case 3:
327
                $out = isset($_POST[$paramname]) ? $_POST[$paramname] : (isset($_GET[$paramname]) ? $_GET[$paramname] : '');
328
                break;
329
            case 4:
330
                $out = isset($_POST[$paramname]) ? $_POST[$paramname] : (isset($_GET[$paramname]) ? $_GET[$paramname] : (isset($_COOKIE[$paramname]) ? $_COOKIE[$paramname] : ''));
331
                break;
332
            default:
333
                if (!empty($method)) {
334
                    return 'BadThirdParameterForDolUtils::GETPOST';
335
                }
336
                $out = isset($_GET[$paramname]) ? $_GET[$paramname] : (isset($_POST[$paramname]) ? $_POST[$paramname] : '');
337
        }
338
339
        if (empty($method) || $method == 3 || $method == 4) {
340
            $relativepathstring = $_SERVER["PHP_SELF"];
341
            // Clean $relativepathstring
342
            if (constant('DOL_BASE_URI')) {
343
                $relativepathstring = preg_replace('/^' . preg_quote(constant('DOL_BASE_URI'), '/') . '/', '', $relativepathstring);
344
            }
345
            $relativepathstring = preg_replace('/^\//', '', $relativepathstring);
346
            $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
347
            //var_dump($relativepathstring);
348
            //var_dump($user->default_values);
349
            // Code for search criteria persistence.
350
            // Retrieve values if restore_lastsearch_values
351
            if (!empty($_GET['restore_lastsearch_values'])) {        // Use $_GET here and not AlDolUtils::GETPOST
352
                if (!empty($_SESSION['lastsearch_values_' . $relativepathstring])) { // If there is saved values
353
                    $tmp = json_decode($_SESSION['lastsearch_values_' . $relativepathstring], true);
354
                    if (is_array($tmp)) {
355
                        foreach ($tmp as $key => $val) {
356
                            if ($key == $paramname) { // We are on the requested parameter
357
                                $out = $val;
358
                                break;
359
                            }
360
                        }
361
                    }
362
                }
363
                // If there is saved contextpage, page or limit
364
                if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_' . $relativepathstring])) {
365
                    $out = $_SESSION['lastsearch_contextpage_' . $relativepathstring];
366
                } elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_' . $relativepathstring])) {
367
                    $out = $_SESSION['lastsearch_page_' . $relativepathstring];
368
                } elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_' . $relativepathstring])) {
369
                    $out = $_SESSION['lastsearch_limit_' . $relativepathstring];
370
                }
371
            }
372
            // Else, retreive default values if we are not doing a sort
373
            else {
374
                if (!isset($_GET['sortfield'])) { // If we did a click on a field to sort, we do no apply default values. Same if option MAIN_ENABLE_DEFAULT_VALUES is not set
375
                    if (!empty($_GET['action']) && $_GET['action'] == 'create' && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
376
                        // Search default value from $object->field
377
                        // global $object;
378
                        if (isset($object) && is_object($object) && isset($object->fields[$paramname]['default'])) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $object seems to never exist and therefore isset should always be false.
Loading history...
379
                            $out = $object->fields[$paramname]['default'];
380
                        }
381
                    }
382
                    if (!empty(Globals::$conf->global->MAIN_ENABLE_DEFAULT_VALUES)) {
383
                        if (!empty($_GET['action']) && $_GET['action'] == 'create' && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
384
                            // Now search in setup to overwrite default values
385
                            if (!empty($user->default_values)) {  // $user->default_values defined from menu 'Setup - Default values'
386
                                if (isset($user->default_values[$relativepathstring]['createform'])) {
387
                                    foreach ($user->default_values[$relativepathstring]['createform'] as $defkey => $defval) {
388
                                        $qualified = 0;
389
                                        if ($defkey != '_noquery_') {
390
                                            $tmpqueryarraytohave = explode('&', $defkey);
391
                                            $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
392
                                            $foundintru = 0;
393
                                            foreach ($tmpqueryarraytohave as $tmpquerytohave) {
394
                                                if (!in_array($tmpquerytohave, $tmpqueryarraywehave))
395
                                                    $foundintru = 1;
396
                                            }
397
                                            if (!$foundintru)
398
                                                $qualified = 1;
399
                                            //var_dump($defkey.'-'.$qualified);
400
                                        } else
401
                                            $qualified = 1;
402
403
                                        if ($qualified) {
404
                                            //var_dump($user->default_values[$relativepathstring][$defkey]['createform']);
405
                                            if (isset($user->default_values[$relativepathstring]['createform'][$defkey][$paramname])) {
406
                                                $out = $user->default_values[$relativepathstring]['createform'][$defkey][$paramname];
407
                                                break;
408
                                            }
409
                                        }
410
                                    }
411
                                }
412
                            }
413
                        }
414
                        // Management of default search_filters and sort order
415
                        //elseif (preg_match('/list.php$/', $_SERVER["PHP_SELF"]) && ! empty($paramname) && ! isset($_GET[$paramname]) && ! isset($_POST[$paramname]))
416
                        elseif (!empty($paramname) && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
417
                            if (!empty($user->default_values)) {  // $user->default_values defined from menu 'Setup - Default values'
418
                                //var_dump($user->default_values[$relativepathstring]);
419
                                if ($paramname == 'sortfield' || $paramname == 'sortorder') {   // Sorted on which fields ? ASC or DESC ?
420
                                    if (isset($user->default_values[$relativepathstring]['sortorder'])) { // Even if paramname is sortfield, data are stored into ['sortorder...']
421
                                        foreach ($user->default_values[$relativepathstring]['sortorder'] as $defkey => $defval) {
422
                                            $qualified = 0;
423
                                            if ($defkey != '_noquery_') {
424
                                                $tmpqueryarraytohave = explode('&', $defkey);
425
                                                $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
426
                                                $foundintru = 0;
427
                                                foreach ($tmpqueryarraytohave as $tmpquerytohave) {
428
                                                    if (!in_array($tmpquerytohave, $tmpqueryarraywehave))
429
                                                        $foundintru = 1;
430
                                                }
431
                                                if (!$foundintru)
432
                                                    $qualified = 1;
433
                                                //var_dump($defkey.'-'.$qualified);
434
                                            } else
435
                                                $qualified = 1;
436
437
                                            if ($qualified) {
438
                                                $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "=");  // we accept _, -, . and ,
439
                                                foreach ($user->default_values[$relativepathstring]['sortorder'][$defkey] as $key => $val) {
440
                                                    if ($out)
441
                                                        $out .= ', ';
442
                                                    if ($paramname == 'sortfield') {
443
                                                        $out .= dol_string_nospecial($key, '', $forbidden_chars_to_replace);
444
                                                    }
445
                                                    if ($paramname == 'sortorder') {
446
                                                        $out .= dol_string_nospecial($val, '', $forbidden_chars_to_replace);
447
                                                    }
448
                                                }
449
                                                //break;	// No break for sortfield and sortorder so we can cumulate fields (is it realy usefull ?)
450
                                            }
451
                                        }
452
                                    }
453
                                } elseif (isset($user->default_values[$relativepathstring]['filters'])) {
454
                                    foreach ($user->default_values[$relativepathstring]['filters'] as $defkey => $defval) { // $defkey is a querystring like 'a=b&c=d', $defval is key of user
455
                                        $qualified = 0;
456
                                        if ($defkey != '_noquery_') {
457
                                            $tmpqueryarraytohave = explode('&', $defkey);
458
                                            $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
459
                                            $foundintru = 0;
460
                                            foreach ($tmpqueryarraytohave as $tmpquerytohave) {
461
                                                if (!in_array($tmpquerytohave, $tmpqueryarraywehave))
462
                                                    $foundintru = 1;
463
                                            }
464
                                            if (!$foundintru)
465
                                                $qualified = 1;
466
                                            //var_dump($defkey.'-'.$qualified);
467
                                        } else
468
                                            $qualified = 1;
469
470
                                        if ($qualified) {
471
                                            if (isset($_POST['sall']) || isset($_POST['search_all']) || isset($_GET['sall']) || isset($_GET['search_all'])) {
472
                                                // We made a search from quick search menu, do we still use default filter ?
473
                                                if (empty(Globals::$conf->global->MAIN_DISABLE_DEFAULT_FILTER_FOR_QUICK_SEARCH)) {
474
                                                    $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "=");  // we accept _, -, . and ,
475
                                                    $out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
476
                                                }
477
                                            } else {
478
                                                $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "=");  // we accept _, -, . and ,
479
                                                $out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
480
                                            }
481
                                            break;
482
                                        }
483
                                    }
484
                                }
485
                            }
486
                        }
487
                    }
488
                }
489
            }
490
        }
491
492
        // Substitution variables for AlDolUtils::GETPOST (used to get final url with variable parameters or final default value with variable paramaters)
493
        // Example of variables: __DAY__, __MONTH__, __YEAR__, __MYCOMPANY_COUNTRY_ID__, __USER_ID__, ...
494
        // We do this only if var is a GET. If it is a POST, may be we want to post the text with vars as the setup text.
495
        if (!is_array($out) && empty($_POST[$paramname]) && empty($noreplace)) {
496
            $maxloop = 20;
497
            $loopnb = 0;    // Protection against infinite loop
498
            while (preg_match('/__([A-Z0-9]+_?[A-Z0-9]+)__/i', $out, $reg) && ($loopnb < $maxloop)) {    // Detect '__ABCDEF__' as key 'ABCDEF' and '__ABC_DEF__' as key 'ABC_DEF'. Detection is also correct when 2 vars are side by side.
499
                $loopnb++;
500
                $newout = '';
501
502
                if ($reg[1] == 'DAY') {
503
                    $tmp = dol_getdate(dol_now(), true);
504
                    $newout = $tmp['mday'];
505
                } elseif ($reg[1] == 'MONTH') {
506
                    $tmp = dol_getdate(dol_now(), true);
507
                    $newout = $tmp['mon'];
508
                } elseif ($reg[1] == 'YEAR') {
509
                    $tmp = dol_getdate(dol_now(), true);
510
                    $newout = $tmp['year'];
511
                } elseif ($reg[1] == 'PREVIOUS_DAY') {
512
                    $tmp = dol_getdate(dol_now(), true);
513
                    $tmp2 = dol_get_prev_day($tmp['mday'], $tmp['mon'], $tmp['year']);
514
                    $newout = $tmp2['day'];
515
                } elseif ($reg[1] == 'PREVIOUS_MONTH') {
516
                    $tmp = dol_getdate(dol_now(), true);
517
                    $tmp2 = dol_get_prev_month($tmp['mon'], $tmp['year']);
518
                    $newout = $tmp2['month'];
519
                } elseif ($reg[1] == 'PREVIOUS_YEAR') {
520
                    $tmp = dol_getdate(dol_now(), true);
521
                    $newout = ($tmp['year'] - 1);
522
                } elseif ($reg[1] == 'NEXT_DAY') {
523
                    $tmp = dol_getdate(dol_now(), true);
524
                    $tmp2 = dol_get_next_day($tmp['mday'], $tmp['mon'], $tmp['year']);
525
                    $newout = $tmp2['day'];
526
                } elseif ($reg[1] == 'NEXT_MONTH') {
527
                    $tmp = dol_getdate(dol_now(), true);
528
                    $tmp2 = dol_get_next_month($tmp['mon'], $tmp['year']);
529
                    $newout = $tmp2['month'];
530
                } elseif ($reg[1] == 'NEXT_YEAR') {
531
                    $tmp = dol_getdate(dol_now(), true);
532
                    $newout = ($tmp['year'] + 1);
533
                } elseif ($reg[1] == 'MYCOMPANY_COUNTRY_ID' || $reg[1] == 'MYCOUNTRY_ID' || $reg[1] == 'MYCOUNTRYID') {
534
                    $newout = $mysoc->country_id;
535
                } elseif ($reg[1] == 'USER_ID' || $reg[1] == 'USERID') {
536
                    $newout = $user->id;
537
                } elseif ($reg[1] == 'USER_SUPERVISOR_ID' || $reg[1] == 'SUPERVISOR_ID' || $reg[1] == 'SUPERVISORID') {
538
                    $newout = $user->fk_user;
539
                } elseif ($reg[1] == 'ENTITY_ID' || $reg[1] == 'ENTITYID') {
540
                    $newout = Globals::$conf->entity;
541
                } else {
542
                    $newout = '';     // Key not found, we replace with empty string
543
                }
544
//var_dump('__'.$reg[1].'__ -> '.$newout);
545
                $out = preg_replace('/__' . preg_quote($reg[1], '/') . '__/', $newout, $out);
546
            }
547
        }
548
549
        // Check is done after replacement
550
        switch ($check) {
551
            case 'none':
552
                break;
553
            case 'int':    // Check param is a numeric value (integer but also float or hexadecimal)
554
                if (!is_numeric($out)) {
555
                    $out = '';
556
                }
557
                break;
558
            case 'intcomma':
559
                if (preg_match('/[^0-9,-]+/i', $out))
560
                    $out = '';
561
                break;
562
            case 'alpha':
563
                if (!is_array($out)) {
564
                    $out = trim($out);
565
                    // '"' is dangerous because param in url can close the href= or src= and add javascript functions.
566
                    // '../' is dangerous because it allows dir transversals
567
                    if (preg_match('/"/', $out))
568
                        $out = '';
569
                    else if (preg_match('/\.\.\//', $out))
570
                        $out = '';
571
                }
572
                break;
573
            case 'san_alpha':
574
                $out = filter_var($out, FILTER_SANITIZE_STRING);
575
                break;
576
            case 'aZ':
577
                if (!is_array($out)) {
578
                    $out = trim($out);
579
                    if (preg_match('/[^a-z]+/i', $out))
580
                        $out = '';
581
                }
582
                break;
583
            case 'aZ09':
584
                if (!is_array($out)) {
585
                    $out = trim($out);
586
                    if (preg_match('/[^a-z0-9_\-\.]+/i', $out))
587
                        $out = '';
588
                }
589
                break;
590
            case 'aZ09comma':  // great to sanitize sortfield or sortorder params that can be t.abc,t.def_gh
591
                if (!is_array($out)) {
592
                    $out = trim($out);
593
                    if (preg_match('/[^a-z0-9_\-\.,]+/i', $out))
594
                        $out = '';
595
                }
596
                break;
597
            case 'array':
598
                if (!is_array($out) || empty($out))
599
                    $out = array();
600
                break;
601
            case 'nohtml':  // Recommended for most scalar parameters
602
                $out = dol_string_nohtmltag($out, 0);
603
                break;
604
            case 'alphanohtml': // Recommended for search parameters
605
                if (!is_array($out)) {
606
                    $out = trim($out);
607
                    // '"' is dangerous because param in url can close the href= or src= and add javascript functions.
608
                    // '../' is dangerous because it allows dir transversals
609
                    if (preg_match('/"/', $out))
610
                        $out = '';
611
                    else if (preg_match('/\.\.\//', $out))
612
                        $out = '';
613
                    $out = dol_string_nohtmltag($out);
614
                }
615
                break;
616
            case 'custom':
617
                if (empty($filter))
618
                    return 'BadFourthParameterForDolUtils::GETPOST';
619
                $out = filter_var($out, $filter, $options);
620
                break;
621
        }
622
623
        // Code for search criteria persistence.
624
        // Save data into session if key start with 'search_' or is 'smonth', 'syear', 'month', 'year'
625
        if (empty($method) || $method == 3 || $method == 4) {
626
            if (preg_match('/^search_/', $paramname) || in_array($paramname, array('sortorder', 'sortfield'))) {
627
                //var_dump($paramname.' - '.$out.' '.$user->default_values[$relativepathstring]['filters'][$paramname]);
628
                // We save search key only if $out not empty that means:
629
                // - posted value not empty, or
630
                // - if posted value is empty and a default value exists that is not empty (it means we did a filter to an empty value when default was not).
631
632
                if ($out != '') {  // $out = '0' or 'abc', it is a search criteria to keep
633
                    $user->lastsearch_values_tmp[$relativepathstring][$paramname] = $out;
634
                }
635
            }
636
        }
637
638
        return $out;
639
    }
640
641
    /**
642
     *  Return a prefix to use for this Dolibarr instance, for session/cookie names or email id.
643
     *  The prefix for session is unique in a web context only and is unique for instance and avoid conflict
644
     *  between multi-instances, even when having two instances with one root dir or two instances in virtual servers.
645
     *  The prefix for email is unique if MAIL_PREFIX_FOR_EMAIL_ID is set to a value, otherwise value may be same than other instance.
646
     *
647
     *  @param  string  $mode                   '' (prefix for session name) or 'email' (prefix for email id)
648
     *  @return	string                          A calculated prefix
649
     */
650
    static function dol_getprefix($mode = '')
651
    {
652
        // If prefix is for email
653
        if ($mode == 'email') {
654
            if (empty(Globals::$conf->global->MAIL_PREFIX_FOR_EMAIL_ID)) {
655
                return AlSecurity::dol_hash(DOL_DOCUMENT_ROOT . DOL_BASE_URI);
656
            }
657
            // If MAIL_PREFIX_FOR_EMAIL_ID is set (a value initialized with a random value is recommended)
658
            if (Globals::$conf->global->MAIL_PREFIX_FOR_EMAIL_ID != 'SERVER_NAME') {
659
                return Globals::$conf->global->MAIL_PREFIX_FOR_EMAIL_ID;
660
            }
661
            if (isset($_SERVER["SERVER_NAME"])) {
662
                return $_SERVER["SERVER_NAME"];
663
            }
664
665
            return AlSecurity::dol_hash(DOL_DOCUMENT_ROOT . DOL_BASE_URI);
666
        }
667
668
        if (isset($_SERVER["SERVER_NAME"]) && isset($_SERVER["DOCUMENT_ROOT"])) {
669
            return AlSecurity::dol_hash($_SERVER["SERVER_NAME"] . $_SERVER["DOCUMENT_ROOT"] . DOL_DOCUMENT_ROOT . DOL_BASE_URI);
670
671
            // Use this for a "readable" cookie name
672
            //return dol_sanitizeFileName($_SERVER["SERVER_NAME"].$_SERVER["DOCUMENT_ROOT"].DOL_DOCUMENT_ROOT.DOL_BASE_URI);
673
        }
674
        return AlSecurity::dol_hash(DOL_DOCUMENT_ROOT . DOL_BASE_URI);
675
    }
676
677
    /**
678
     * 	Make an include_once using default root and alternate root if it fails.
679
     *  To link to a core file, use include(DOL_DOCUMENT_ROOT.'/pathtofile')
680
     *  To link to a module file from a module file, use include './mymodulefile';
681
     *  To link to a module file from a core file, then this function can be used (call by hook / trigger / speciales pages)
682
     *
683
     * 	@param	string	$relpath	Relative path to file (Ie: mydir/myfile, ../myfile, ...)
684
     * 	@param	string	$classname	Class name (deprecated)
685
     *  @return bool                True if load is a success, False if it fails
686
     */
687
    static function dol_include_once($relpath, $classname = '')
688
    {
689
        // global Globals::$conf, Globals::$langs, $user, $mysoc;   // Do not remove this. They must be defined for files we include. Other globals var must be retreived with $GLOBALS['var']
690
691
        $fullpath = dol_buildpath($relpath);
692
693
        if (!file_exists($fullpath)) {
694
            AlDolUtils::dol_syslog('functions::dol_include_once Tried to load unexisting file: ' . $relpath, LOG_ERR);
695
            return false;
696
        }
697
698
        if (!empty($classname) && !class_exists($classname)) {
699
            return include $fullpath;
700
        } else {
701
            return include_once $fullpath;
702
        }
703
    }
704
705
    /**
706
     * 	Return path of url or filesystem. Can check into alternate dir or alternate dir + main dir depending on value of $returnemptyifnotfound.
707
     *
708
     * 	@param	string	$path						Relative path to file (if mode=0) or relative url (if mode=1). Ie: mydir/myfile, ../myfile
709
     *  @param	int		$type						0=Used for a Filesystem path, 1=Used for an URL path (output relative), 2=Used for an URL path (output full path using same host that current url), 3=Used for an URL path (output full path using host defined into $dolibarr_main_url_root of conf file)
710
     *  @param	int		$returnemptyifnotfound		0:If $type==0 and if file was not found into alternate dir, return default path into main dir (no test on it)
711
     *  											1:If $type==0 and if file was not found into alternate dir, return empty string
712
     *  											2:If $type==0 and if file was not found into alternate dir, test into main dir, return default path if found, empty string if not found
713
     *  @return string								Full filesystem path (if path=0), Full url path (if mode=1)
714
     */
715
    static function dol_buildpath($path, $type = 0, $returnemptyifnotfound = 0)
716
    {
717
        // global Globals::$conf;
718
719
        $path = preg_replace('/^\//', '', $path);
720
721
        if ($type == 0 /* empty($type) */) { // For a filesystem path
722
//$res = DOL_BASE_PATH . '' . $path;  // Standard default path
723
            $res = DOL_BASE_PATH . '/' . $path;  // Standard default path
724
            if (isset(Globals::$conf->file->dol_document_root)) {
725
                foreach (Globals::$conf->file->dol_document_root as $key => $dirroot) { // ex: array(["main"]=>"/home/main/htdocs", ["alt0"]=>"/home/dirmod/htdocs", ...)
726
                    if ($key == 'main') {
727
                        continue;
728
                    }
729
                    if (file_exists($dirroot . '/' . $path)) {
730
                        $res = $dirroot . '/' . $path;
731
                        return $res;
732
                    }
733
                }
734
            }
735
            if ($returnemptyifnotfound) {        // Not found into alternate dir
736
                if ($returnemptyifnotfound == 1 || !file_exists($res))
737
                    return '';
738
            }
739
        }
740
        else {    // For an url path
741
// We try to get local path of file on filesystem from url
742
// Note that trying to know if a file on disk exist by forging path on disk from url
743
// works only for some web server and some setup. This is bugged when
744
// using proxy, rewriting, virtual path, etc...
745
            $res = '';
746
            if ($type == 1) {
747
                $res = /* DOL_BASE_URI */ DOL_BASE_URI . '/' . $path;   // Standard value
748
            }
749
            if ($type == 2) {
750
                $res = /* DOL_MAIN_URL_ROOT */ DOL_BASE_PATH . '/' . $path;  // Standard value
751
            }
752
            if ($type == 3) {
753
                $res = DOL_BASE_URI . '/' . $path;
754
            }
755
756
            foreach (Globals::$conf->file->dol_document_root as $key => $dirroot) { // ex: array(["main"]=>"/home/main/htdocs", ["alt0"]=>"/home/dirmod/htdocs", ...)
757
                if ($key == 'main') {
758
                    if ($type == 3) {
759
                        // global $dolibarr_main_url_root;
760
                        // Define $urlwithroot
761
                        // $urlwithouturlroot = preg_replace('/' . preg_quote(DOL_BASE_URI, '/') . '$/i', '', trim($dolibarr_main_url_root));
762
                        $urlwithouturlroot = preg_replace('/' . preg_quote(DOL_BASE_URI, '/') . '$/i', '', trim(DOL_BASE_URI));
763
764
                        $urlwithroot = $urlwithouturlroot . DOL_BASE_URI;  // This is to use external domain name found into config file
765
                        //$urlwithroot=DOL_MAIN_URL_ROOT;					// This is to use same domain name than current
766
767
                        $res = (preg_match('/^http/i', Globals::$conf->file->dol_url_root[$key]) ? '' : $urlwithroot) . '/' . $path;     // Test on start with http is for old conf syntax
768
                    }
769
                    continue;
770
                }
771
                preg_match('/^([^\?]+(\.css\.php|\.css|\.js\.php|\.js|\.png|\.jpg|\.php)?)/i', $path, $regs);    // Take part before '?'
772
                if (!empty($regs[1])) {
773
                    //print $key.'-'.$dirroot.'/'.$path.'-'.Globals::$conf->file->dol_url_root[$type].'<br>'."\n";
774
                    if (file_exists($dirroot . '/' . $regs[1])) {
775
                        if ($type == 1) {
776
                            $res = (preg_match('/^http/i', Globals::$conf->file->dol_url_root[$key]) ? '' : DOL_BASE_URI) . Globals::$conf->file->dol_url_root[$key] . '/' . $path;
777
                        }
778
                        if ($type == 2) {
779
                            $res = (preg_match('/^http/i', Globals::$conf->file->dol_url_root[$key]) ? '' : DOL_MAIN_URL_ROOT) . Globals::$conf->file->dol_url_root[$key] . '/' . $path;
780
                        }
781
                        if ($type == 3) {
782
                            // global $dolibarr_main_url_root;
783
                            // Define $urlwithroot
784
                            $urlwithouturlroot = preg_replace('/' . preg_quote(DOL_BASE_URI, '/') . '$/i', '', trim($dolibarr_main_url_root));
785
                            $urlwithroot = $urlwithouturlroot . DOL_BASE_URI;  // This is to use external domain name found into config file
786
                            //$urlwithroot=DOL_MAIN_URL_ROOT;					// This is to use same domain name than current
787
788
                            $res = (preg_match('/^http/i', Globals::$conf->file->dol_url_root[$key]) ? '' : $urlwithroot) . Globals::$conf->file->dol_url_root[$key] . '/' . $path;     // Test on start with http is for old conf syntax
789
                        }
790
                        break;
791
                    }
792
                }
793
            }
794
        }
795
796
        return $res;
797
    }
798
799
    /**
800
     * 	Create a clone of instance of object (new instance with same value for properties)
801
     *  With native = 0: Property that are reference are also new object (true clone). This means $this->db is not valid.
802
     *  With native = 1: Use PHP clone. Property that are reference are same pointer. This means $this->db is still valid.
803
     *
804
     * 	@param	object	$object		Object to clone
805
     *  @param	int		$native		Native method or true method
806
     * 	@return object				Object clone
807
     *  @see https://php.net/manual/language.oop5.cloning.php
808
     */
809
    static function dol_clone($object, $native = 0)
810
    {
811
//DolUtils::dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);
812
813
        if (empty($native)) {
814
            $myclone = unserialize(serialize($object));
815
        } else {
816
            $myclone = clone $object;     // PHP clone is a shallow copy only, not a real clone, so properties of references will keep references (refer to the same target/variable)
817
        }
818
819
        return $myclone;
820
    }
821
822
    /**
823
     * 	Optimize a size for some browsers (phone, smarphone, ...)
824
     *
825
     * 	@param	int		$size		Size we want
826
     * 	@param	string	$type		Type of optimizing:
827
     * 								'' = function used to define a size for truncation
828
     * 								'width' = function is used to define a width
829
     * 	@return int					New size after optimizing
830
     */
831
    static function dol_size($size, $type = '')
832
    {
833
        //global Globals::$conf;
834
        if (empty(Globals::$conf->dol_optimize_smallscreen))
835
            return $size;
836
        if ($type == 'width' && $size > 250)
837
            return 250;
838
        else
839
            return 10;
840
    }
841
842
    /**
843
     * 	Clean a string to use it as a file name
844
     *
845
     * 	@param	string	$str            String to clean
846
     * 	@param	string	$newstr			String to replace bad chars with
847
     *  @param	int	    $unaccent		1=Remove also accent (default), 0 do not remove them
848
     * 	@return string          		String cleaned (a-zA-Z_)
849
     *
850
     * 	@see        	dol_string_nospecial, dol_string_unaccent, dol_sanitizePathName
851
     */
852
    static function dol_sanitizeFileName($str, $newstr = '_', $unaccent = 1)
853
    {
854
        $filesystem_forbidden_chars = array('<', '>', '/', '\\', '?', '*', '|', '"', '°');
855
        return dol_string_nospecial($unaccent ? dol_string_unaccent($str) : $str, $newstr, $filesystem_forbidden_chars);
856
    }
857
858
    /**
859
     * 	Clean a string to use it as a path name
860
     *
861
     * 	@param	string	$str            String to clean
862
     * 	@param	string	$newstr			String to replace bad chars with
863
     *  @param	int	    $unaccent		1=Remove also accent (default), 0 do not remove them
864
     * 	@return string          		String cleaned (a-zA-Z_)
865
     *
866
     * 	@see        	dol_string_nospecial, dol_string_unaccent, dol_sanitizeFileName
867
     */
868
    static function dol_sanitizePathName($str, $newstr = '_', $unaccent = 1)
869
    {
870
        $filesystem_forbidden_chars = array('<', '>', '?', '*', '|', '"', '°');
871
        return dol_string_nospecial($unaccent ? dol_string_unaccent($str) : $str, $newstr, $filesystem_forbidden_chars);
872
    }
873
874
    /**
875
     * 	Clean a string from all accent characters to be used as ref, login or by dol_sanitizeFileName
876
     *
877
     * 	@param	string	$str			String to clean
878
     * 	@return string   	       		Cleaned string
879
     *
880
     * 	@see    		dol_sanitizeFilename, dol_string_nospecial
881
     */
882
    static function dol_string_unaccent($str)
883
    {
884
        if (utf8_check($str)) {
885
// See http://www.utf8-chartable.de/
886
            $string = rawurlencode($str);
887
            $replacements = array(
888
                '%C3%80' => 'A', '%C3%81' => 'A', '%C3%82' => 'A', '%C3%83' => 'A', '%C3%84' => 'A', '%C3%85' => 'A',
889
                '%C3%88' => 'E', '%C3%89' => 'E', '%C3%8A' => 'E', '%C3%8B' => 'E',
890
                '%C3%8C' => 'I', '%C3%8D' => 'I', '%C3%8E' => 'I', '%C3%8F' => 'I',
891
                '%C3%92' => 'O', '%C3%93' => 'O', '%C3%94' => 'O', '%C3%95' => 'O', '%C3%96' => 'O',
892
                '%C3%99' => 'U', '%C3%9A' => 'U', '%C3%9B' => 'U', '%C3%9C' => 'U',
893
                '%C3%A0' => 'a', '%C3%A1' => 'a', '%C3%A2' => 'a', '%C3%A3' => 'a', '%C3%A4' => 'a', '%C3%A5' => 'a',
894
                '%C3%A7' => 'c',
895
                '%C3%A8' => 'e', '%C3%A9' => 'e', '%C3%AA' => 'e', '%C3%AB' => 'e',
896
                '%C3%AC' => 'i', '%C3%AD' => 'i', '%C3%AE' => 'i', '%C3%AF' => 'i',
897
                '%C3%B1' => 'n',
898
                '%C3%B2' => 'o', '%C3%B3' => 'o', '%C3%B4' => 'o', '%C3%B5' => 'o', '%C3%B6' => 'o',
899
                '%C3%B9' => 'u', '%C3%BA' => 'u', '%C3%BB' => 'u', '%C3%BC' => 'u',
900
                '%C3%BF' => 'y'
901
            );
902
            $string = strtr($string, $replacements);
903
            return rawurldecode($string);
904
        } else {
905
// See http://www.ascii-code.com/
906
            $string = strtr(
907
                $str, "\xC0\xC1\xC2\xC3\xC4\xC5\xC7
908
			\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1
909
			\xD2\xD3\xD4\xD5\xD8\xD9\xDA\xDB\xDD
910
			\xE0\xE1\xE2\xE3\xE4\xE5\xE7\xE8\xE9\xEA\xEB
911
			\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF8
912
			\xF9\xFA\xFB\xFC\xFD\xFF", "AAAAAAC
913
			EEEEIIIIDN
914
			OOOOOUUUY
915
			aaaaaaceeee
916
			iiiidnooooo
917
			uuuuyy"
918
            );
919
            $string = strtr($string, array("\xC4" => "Ae", "\xC6" => "AE", "\xD6" => "Oe", "\xDC" => "Ue", "\xDE" => "TH", "\xDF" => "ss", "\xE4" => "ae", "\xE6" => "ae", "\xF6" => "oe", "\xFC" => "ue", "\xFE" => "th"));
920
            return $string;
921
        }
922
    }
923
924
    /**
925
     * 	Clean a string from all punctuation characters to use it as a ref or login.
926
     *  This is a more complete static function than dol_sanitizeFileName.
927
     *
928
     * 	@param	string	$str            	String to clean
929
     * 	@param	string	$newstr				String to replace forbidden chars with
930
     *  @param  array	$badcharstoreplace  List of forbidden characters
931
     * 	@return string          			Cleaned string
932
     *
933
     * 	@see    		dol_sanitizeFilename, dol_string_unaccent
934
     */
935
    static function dol_string_nospecial($str, $newstr = '_', $badcharstoreplace = '')
936
    {
937
        $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ",", ";", "=", '°');  // more complete than dol_sanitizeFileName
938
        $forbidden_chars_to_remove = array();
939
        if (is_array($badcharstoreplace))
940
            $forbidden_chars_to_replace = $badcharstoreplace;
941
//$forbidden_chars_to_remove=array("(",")");
942
943
        return str_replace($forbidden_chars_to_replace, $newstr, str_replace($forbidden_chars_to_remove, "", $str));
944
    }
945
946
    /**
947
     * Encode string for xml usage
948
     *
949
     * @param 	string	$string		String to encode
950
     * @return	string				String encoded
951
     */
952
    static function dolEscapeXML($string)
953
    {
954
        return strtr($string, array('\'' => '&apos;', '"' => '&quot;', '&' => '&amp;', '<' => '&lt;', '>' => '&gt;'));
955
    }
956
957
    /**
958
     *  Returns text escaped for inclusion into javascript code
959
     *
960
     *  @param      string		$stringtoescape		String to escape
961
     *  @param		int		$mode				0=Escape also ' and " into ', 1=Escape ' but not " for usage into 'string', 2=Escape " but not ' for usage into "string", 3=Escape ' and " with \
962
     *  @param		int		$noescapebackslashn	0=Escape also \n. 1=Do not escape \n.
963
     *  @return     string     		 				Escaped string. Both ' and " are escaped into ' if they are escaped.
964
     */
965
    static function dol_escape_js($stringtoescape, $mode = 0, $noescapebackslashn = 0)
966
    {
967
// escape quotes and backslashes, newlines, etc.
968
        $substitjs = array("&#039;" => "\\'", "\r" => '\\r');
969
//$substitjs['</']='<\/';	// We removed this. Should be useless.
970
        if (empty($noescapebackslashn)) {
971
            $substitjs["\n"] = '\\n';
972
            $substitjs['\\'] = '\\\\';
973
        }
974
        if (empty($mode)) {
975
            $substitjs["'"] = "\\'";
976
            $substitjs['"'] = "\\'";
977
        } else if ($mode == 1)
978
            $substitjs["'"] = "\\'";
979
        else if ($mode == 2) {
980
            $substitjs['"'] = '\\"';
981
        } else if ($mode == 3) {
982
            $substitjs["'"] = "\\'";
983
            $substitjs['"'] = "\\\"";
984
        }
985
        return strtr($stringtoescape, $substitjs);
986
    }
987
988
    /**
989
     *  Returns text escaped for inclusion in HTML alt or title tags, or into values of HTML input fields.
990
     *
991
     *  @param      string		$stringtoescape		String to escape
992
     *  @param		int			$keepb				1=Preserve b tags (otherwise, remove them)
993
     *  @param      int         $keepn              1=Preserve \r\n strings (otherwise, replace them with escaped value)
994
     *  @return     string     				 		Escaped string
995
     *  @see		dol_string_nohtmltag, dol_string_nospecial, dol_string_unaccent
996
     */
997
    static function dol_escape_htmltag($stringtoescape, $keepb = 0, $keepn = 0)
998
    {
999
// escape quotes and backslashes, newlines, etc.
1000
        $tmp = html_entity_decode($stringtoescape, ENT_COMPAT, 'UTF-8');  // TODO Use htmlspecialchars_decode instead, that make only required change for html tags
1001
        if (!$keepb)
1002
            $tmp = strtr($tmp, array("<b>" => '', '</b>' => ''));
1003
        if (!$keepn)
1004
            $tmp = strtr($tmp, array("\r" => '\\r', "\n" => '\\n'));
1005
        return htmlentities($tmp, ENT_COMPAT, 'UTF-8');      // TODO Use htmlspecialchars instead, that make only required change for html tags
1006
    }
1007
1008
    /**
1009
     * Convert a string to lower. Never use strtolower because it does not works with UTF8 strings.
1010
     *
1011
     * @param 	string		$utf8_string		String to encode
1012
     * @return 	string							String converted
1013
     */
1014
    static function dol_strtolower($utf8_string)
1015
    {
1016
        return mb_strtolower($utf8_string, "UTF-8");
1017
    }
1018
1019
    /**
1020
     * Convert a string to upper. Never use strtolower because it does not works with UTF8 strings.
1021
     *
1022
     * @param 	string		$utf8_string		String to encode
1023
     * @return 	string							String converted
1024
     */
1025
    static function dol_strtoupper($utf8_string)
1026
    {
1027
        return mb_strtoupper($utf8_string, "UTF-8");
1028
    }
1029
1030
    /**
1031
     * 	Write log message into outputs. Possible outputs can be:
1032
     * 	SYSLOG_HANDLERS = ["mod_syslog_file"]  		file name is then defined by SYSLOG_FILE
1033
     * 	SYSLOG_HANDLERS = ["mod_syslog_syslog"]  	facility is then defined by SYSLOG_FACILITY
1034
     *  Warning, syslog functions are bugged on Windows, generating memory protection faults. To solve
1035
     *  this, use logging to files instead of syslog (see setup of module).
1036
     *  Note: If constant 'SYSLOG_FILE_NO_ERROR' defined, we never output any error message when writing to log fails.
1037
     *  Note: You can get log message into html sources by adding parameter &logtohtml=1 (constant MAIN_LOGTOHTML must be set)
1038
     *  This static function works only if syslog module is enabled.
1039
     * 	This must not use any call to other static function calling AlDolUtils::dol_syslog (avoid infinite loop).
1040
     *
1041
     * 	@param  string		$message				Line to log. ''=Show nothing
1042
     *  @param  int			$level					Log level
1043
     * 												On Windows LOG_ERR=4, LOG_WARNING=5, LOG_NOTICE=LOG_INFO=6, LOG_DEBUG=6 si define_syslog_variables ou PHP 5.3+, 7 si dolibarr
1044
     * 												On Linux   LOG_ERR=3, LOG_WARNING=4, LOG_INFO=6, LOG_DEBUG=7
1045
     *  @param	int			$ident					1=Increase ident of 1, -1=Decrease ident of 1
1046
     *  @param	string		$suffixinfilename		When output is a file, append this suffix into default log filename.
1047
     *  @param	string		$restricttologhandler	Output log only for this log handler
1048
     *  @return	void
1049
     */
1050
    static function dol_syslog($message, $level = LOG_INFO, $ident = 0, $suffixinfilename = '', $restricttologhandler = '')
1051
    {
1052
        // global Globals::$conf, $user;
1053
// If syslog module enabled
1054
        if (empty(Globals::$conf->syslog->enabled))
1055
            return;
1056
1057
        if ($ident < 0) {
1058
            foreach (Globals::$conf->loghandlers as $loghandlerinstance) {
1059
                $loghandlerinstance->setIdent($ident);
1060
            }
1061
        }
1062
1063
        if (!empty($message)) {
1064
// Test log level
1065
            $logLevels = array(LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG);
1066
            if (!in_array($level, $logLevels, true)) {
1067
                throw new Exception('Incorrect log level');
1068
            }
1069
            if ($level > Globals::$conf->global->SYSLOG_LEVEL)
1070
                return;
1071
1072
            $message = preg_replace('/password=\'[^\']*\'/', 'password=\'hidden\'', $message); // protection to avoid to have value of password in log
1073
// If adding log inside HTML page is required
1074
            if (!empty($_REQUEST['logtohtml']) && (!empty(Globals::$conf->global->MAIN_ENABLE_LOG_TO_HTML) || !empty(Globals::$conf->global->MAIN_LOGTOHTML))) {   // MAIN_LOGTOHTML kept for backward compatibility
1075
                Globals::$conf->logbuffer[] = AlDolUtils::dol_print_date(time(), "%Y-%m-%d %H:%M:%S") . " " . $message;
1076
            }
1077
1078
//TODO: Remove this. MAIN_ENABLE_LOG_INLINE_HTML should be deprecated and use a log handler dedicated to HTML output
1079
// If html log tag enabled and url parameter log defined, we show output log on HTML comments
1080
            if (!empty(Globals::$conf->global->MAIN_ENABLE_LOG_INLINE_HTML) && !empty($_GET["log"])) {
1081
                print "\n\n<!-- Log start\n";
1082
                print $message . "\n";
1083
                print "Log end -->\n";
1084
            }
1085
1086
            $data = array(
1087
                'message' => $message,
1088
                'script' => (isset($_SERVER['PHP_SELF']) ? basename($_SERVER['PHP_SELF'], '.php') : false),
1089
                'level' => $level,
1090
                'user' => ((is_object($user) && $user->id) ? $user->login : false),
1091
                'ip' => false
1092
            );
1093
1094
// This is when server run behind a reverse proxy
1095
            if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']))
1096
                $data['ip'] = $_SERVER['HTTP_X_FORWARDED_FOR'] . (empty($_SERVER["REMOTE_ADDR"]) ? '' : '->' . $_SERVER['REMOTE_ADDR']);
1097
// This is when server run normally on a server
1098
            else if (!empty($_SERVER["REMOTE_ADDR"]))
1099
                $data['ip'] = $_SERVER['REMOTE_ADDR'];
1100
// This is when PHP session is ran inside a web server but not inside a client request (example: init code of apache)
1101
            else if (!empty($_SERVER['SERVER_ADDR']))
1102
                $data['ip'] = $_SERVER['SERVER_ADDR'];
1103
// This is when PHP session is ran outside a web server, like from Windows command line (Not always defined, but useful if OS defined it).
1104
            else if (!empty($_SERVER['COMPUTERNAME']))
1105
                $data['ip'] = $_SERVER['COMPUTERNAME'] . (empty($_SERVER['USERNAME']) ? '' : '@' . $_SERVER['USERNAME']);
1106
// This is when PHP session is ran outside a web server, like from Linux command line (Not always defined, but usefull if OS defined it).
1107
            else if (!empty($_SERVER['LOGNAME']))
1108
                $data['ip'] = '???@' . $_SERVER['LOGNAME'];
1109
// Loop on each log handler and send output
1110
            foreach (Globals::$conf->loghandlers as $loghandlerinstance) {
1111
                if ($restricttologhandler && $loghandlerinstance->code != $restricttologhandler)
1112
                    continue;
1113
                $loghandlerinstance->export($data, $suffixinfilename);
1114
            }
1115
            unset($data);
1116
        }
1117
1118
        if ($ident > 0) {
1119
            foreach (Globals::$conf->loghandlers as $loghandlerinstance) {
1120
                $loghandlerinstance->setIdent($ident);
1121
            }
1122
        }
1123
    }
1124
1125
    /**
1126
     * 	Show tab header of a card
1127
     *
1128
     * 	@param	array	$links				Array of tabs. Currently initialized by calling a static function xxx_admin_prepare_head
1129
     * 	@param	string	$active     		Active tab name (document', 'info', 'ldap', ....)
1130
     * 	@param  string	$title      		Title
1131
     * 	@param  int		$notab				-1 or 0=Add tab header, 1=no tab header. If you set this to 1, using dol_fiche_end() to close tab is not required.
1132
     * 	@param	string	$picto				Add a picto on tab title
1133
     * 	@param	int		$pictoisfullpath	If 1, image path is a full path. If you set this to 1, you can use url returned by dol_buildpath('/mymodyle/img/myimg.png',1) for $picto.
1134
     *  @param	string	$morehtmlright		Add more html content on right of tabs title
1135
     *  @param	string	$morecss			More Css
1136
     * 	@return	void
1137
     */
1138
    static function dol_fiche_head($links = array(), $active = '0', $title = '', $notab = 0, $picto = '', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '')
1139
    {
1140
        print dol_get_fiche_head($links, $active, $title, $notab, $picto, $pictoisfullpath, $morehtmlright, $morecss);
1141
    }
1142
1143
    /**
1144
     *  Show tab header of a card
1145
     *
1146
     * 	@param	array	$links				Array of tabs
1147
     * 	@param	string	$active     		Active tab name
1148
     * 	@param  string	$title      		Title
1149
     * 	@param  int		$notab				-1 or 0=Add tab header, 1=no tab header. If you set this to 1, using dol_fiche_end() to close tab is not required.
1150
     * 	@param	string	$picto				Add a picto on tab title
1151
     * 	@param	int		$pictoisfullpath	If 1, image path is a full path. If you set this to 1, you can use url returned by dol_buildpath('/mymodyle/img/myimg.png',1) for $picto.
1152
     *  @param	string	$morehtmlright		Add more html content on right of tabs title
1153
     *  @param	string	$morecss			More Css
1154
     * 	@return	string
1155
     */
1156
    static function dol_get_fiche_head($links = array(), $active = '', $title = '', $notab = 0, $picto = '', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '')
1157
    {
1158
        // global Globals::$conf, Globals::$langs, Globals::$hookManager;
1159
1160
        $out = "\n" . '<div class="tabs" data-role="controlgroup" data-type="horizontal">' . "\n";
1161
1162
        if ($morehtmlright) {
1163
            $out .= '<div class="inline-block floatright tabsElem">' . $morehtmlright . '</div>'; // Output right area first so when space is missing, text is in front of tabs and not under.
1164
        }
1165
1166
// Show title
1167
        $showtitle = 1;
1168
        if (!empty(Globals::$conf->dol_optimize_smallscreen))
1169
            $showtitle = 0;
1170
        if (!empty($title) && $showtitle) {
1171
            $limittitle = 30;
1172
            $out .= '<a class="tabTitle">';
1173
            if ($picto)
1174
                $out .= img_picto($title, ($pictoisfullpath ? '' : 'object_') . $picto, '', $pictoisfullpath) . ' ';
1175
            $out .= '<span class="tabTitleText">' . dol_trunc($title, $limittitle) . '</span>';
1176
            $out .= '</a>';
1177
        }
1178
1179
// Define max of key (max may be higher than sizeof because of hole due to module disabling some tabs).
1180
        $maxkey = -1;
1181
        if (is_array($links) && !empty($links)) {
1182
            $keys = array_keys($links);
1183
            if (count($keys))
1184
                $maxkey = max($keys);
1185
        }
1186
1187
        if (!empty(Globals::$conf->dol_optimize_smallscreen))
1188
            Globals::$conf->global->MAIN_MAXTABS_IN_CARD = 2;
1189
1190
// Show tabs
1191
        $bactive = false;
1192
// if =0 we don't use the feature
1193
        $limittoshow = (empty(Globals::$conf->global->MAIN_MAXTABS_IN_CARD) ? 99 : Globals::$conf->global->MAIN_MAXTABS_IN_CARD);
1194
        $displaytab = 0;
1195
        $nbintab = 0;
1196
        $popuptab = 0;
1197
        $outmore = '';
1198
        for ($i = 0; $i <= $maxkey; $i++) {
1199
            if ((is_numeric($active) && $i == $active) || (!empty($links[$i][2]) && !is_numeric($active) && $active == $links[$i][2])) {
1200
// If active tab is already present
1201
                if ($i >= $limittoshow)
1202
                    $limittoshow--;
1203
            }
1204
        }
1205
1206
        for ($i = 0; $i <= $maxkey; $i++) {
1207
            if ((is_numeric($active) && $i == $active) || (!empty($links[$i][2]) && !is_numeric($active) && $active == $links[$i][2])) {
1208
                $isactive = true;
1209
                $bactive = true;
1210
            } else {
1211
                $isactive = false;
1212
            }
1213
1214
            if ($i < $limittoshow || $isactive) {
1215
                $out .= '<div class="inline-block tabsElem' . ($isactive ? ' tabsElemActive' : '') . ((!$isactive && !empty(Globals::$conf->global->MAIN_HIDE_INACTIVETAB_ON_PRINT)) ? ' hideonprint' : '') . '"><!-- id tab = ' . (empty($links[$i][2]) ? '' : $links[$i][2]) . ' -->';
1216
                if (isset($links[$i][2]) && $links[$i][2] == 'image') {
1217
                    if (!empty($links[$i][0])) {
1218
                        $out .= '<a class="tabimage' . ($morecss ? ' ' . $morecss : '') . '" href="' . $links[$i][0] . '">' . $links[$i][1] . '</a>' . "\n";
1219
                    } else {
1220
                        $out .= '<span class="tabspan">' . $links[$i][1] . '</span>' . "\n";
1221
                    }
1222
                } else if (!empty($links[$i][1])) {
1223
                    //print "x $i $active ".$links[$i][2]." z";
1224
                    if ($isactive) {
1225
                        $out .= '<a' . (!empty($links[$i][2]) ? ' id="' . $links[$i][2] . '"' : '') . ' class="tabactive tab inline-block' . ($morecss ? ' ' . $morecss : '') . '" href="' . $links[$i][0] . '">';
1226
                        $out .= $links[$i][1];
1227
                        $out .= '</a>' . "\n";
1228
                    } else {
1229
                        $out .= '<a' . (!empty($links[$i][2]) ? ' id="' . $links[$i][2] . '"' : '') . ' class="tabunactive tab inline-block' . ($morecss ? ' ' . $morecss : '') . '" href="' . $links[$i][0] . '">';
1230
                        $out .= $links[$i][1];
1231
                        $out .= '</a>' . "\n";
1232
                    }
1233
                }
1234
                $out .= '</div>';
1235
            } else {
1236
// The popup with the other tabs
1237
                if (!$popuptab) {
1238
                    $popuptab = 1;
1239
                    $outmore .= '<div class="popuptabset wordwrap">'; // The css used to hide/show popup
1240
                }
1241
                $outmore .= '<div class="popuptab wordwrap" style="display:inherit;">';
1242
                if (isset($links[$i][2]) && $links[$i][2] == 'image') {
1243
                    if (!empty($links[$i][0]))
1244
                        $outmore .= '<a class="tabimage' . ($morecss ? ' ' . $morecss : '') . '" href="' . $links[$i][0] . '">' . $links[$i][1] . '</a>' . "\n";
1245
                    else
1246
                        $outmore .= '<span class="tabspan">' . $links[$i][1] . '</span>' . "\n";
1247
                }
1248
                else if (!empty($links[$i][1])) {
1249
                    $outmore .= '<a' . (!empty($links[$i][2]) ? ' id="' . $links[$i][2] . '"' : '') . ' class="wordwrap inline-block' . ($morecss ? ' ' . $morecss : '') . '" href="' . $links[$i][0] . '">';
1250
                    $outmore .= preg_replace('/([a-z])\/([a-z])/i', '\\1 / \\2', $links[$i][1]); // Replace x/y with x / y to allow wrap on long composed texts.
1251
                    $outmore .= '</a>' . "\n";
1252
                }
1253
                $outmore .= '</div>';
1254
1255
                $nbintab++;
1256
            }
1257
            $displaytab = $i;
1258
        }
1259
        if ($popuptab)
1260
            $outmore .= '</div>';
1261
1262
        if ($displaytab > $limittoshow) {
1263
            $left = (Globals::$langs->trans("DIRECTION") == 'rtl' ? 'right' : 'left');
1264
            $right = (Globals::$langs->trans("DIRECTION") == 'rtl' ? 'left' : 'right');
1265
1266
            $tabsname = str_replace("@", "", $picto);
1267
            $out .= '<div id="moretabs' . $tabsname . '" class="inline-block tabsElem">';
1268
            $out .= '<a href="#" class="tab moretab inline-block tabunactive reposition">' . Globals::$langs->trans("More") . '... (' . $nbintab . ')</a>';
1269
            $out .= '<div id="moretabsList' . $tabsname . '" style="position: absolute; ' . $left . ': -999em; text-align: ' . $left . '; margin:0px; padding:2px">';
1270
            $out .= $outmore;
1271
            $out .= '</div>';
1272
            $out .= '<div></div>';
1273
            $out .= "</div>\n";
1274
1275
            $out .= "<script>";
1276
            $out .= "$('#moretabs" . $tabsname . "').mouseenter( function() { console.log('mouseenter " . $left . "'); $('#moretabsList" . $tabsname . "').css('" . $left . "','auto');});";
1277
            $out .= "$('#moretabs" . $tabsname . "').mouseleave( function() { console.log('mouseleave " . $left . "'); $('#moretabsList" . $tabsname . "').css('" . $left . "','-999em');});";
1278
            $out .= "</script>";
1279
        }
1280
1281
        $out .= "</div>\n";
1282
1283
        if (!$notab || $notab == -1)
1284
            $out .= "\n" . '<div class="tabBar' . ($notab == -1 ? '' : ' tabBarWithBottom') . '">' . "\n";
1285
1286
        $parameters = array('tabname' => $active, 'out' => $out);
1287
        $reshook = Globals::$hookManager->executeHooks('printTabsHead', $parameters); // This hook usage is called just before output the head of tabs. Take also a look at "completeTabsHead"
1288
        if ($reshook > 0) {
1289
            $out = Globals::$hookManager->resPrint;
1290
        }
1291
1292
        return $out;
1293
    }
1294
1295
    /**
1296
     *  Show tab footer of a card
1297
     *
1298
     *  @param	int		$notab       -1 or 0=Add tab footer, 1=no tab footer
1299
     *  @return	void
1300
     */
1301
    static function dol_fiche_end($notab = 0)
1302
    {
1303
        print dol_get_fiche_end($notab);
1304
    }
1305
1306
    /**
1307
     * 	Return tab footer of a card
1308
     *
1309
     * 	@param  int		$notab		-1 or 0=Add tab footer, 1=no tab footer
1310
     *  @return	string
1311
     */
1312
    static function dol_get_fiche_end($notab = 0)
1313
    {
1314
        if (!$notab || $notab == -1)
1315
            return "\n</div>\n";
1316
        else
1317
            return '';
1318
    }
1319
1320
    /**
1321
     *  Show tab footer of a card.
1322
     *  Note: $object->next_prev_filter can be set to restrict select to find next or previous record by $form->showrefnav.
1323
     *
1324
     *  @param	Object	$object			Object to show
1325
     *  @param	string	$paramid   		Name of parameter to use to name the id into the URL next/previous link
1326
     *  @param	string	$morehtml  		More html content to output just before the nav bar
1327
     *  @param	int		$shownav	  	Show Condition (navigation is shown if value is 1)
1328
     *  @param	string	$fieldid   		Nom du champ en base a utiliser pour select next et previous (we make the select max and min on this field). Use 'none' for no prev/next search.
1329
     *  @param	string	$fieldref   	Nom du champ objet ref (object->ref) a utiliser pour select next et previous
1330
     *  @param	string	$morehtmlref  	More html to show after ref
1331
     *  @param	string	$moreparam  	More param to add in nav link url.
1332
     * 	@param	int		$nodbprefix		Do not include DB prefix to forge table name
1333
     * 	@param	string	$morehtmlleft	More html code to show before ref
1334
     * 	@param	string	$morehtmlstatus	More html code to show under navigation arrows
1335
     *  @param  int     $onlybanner     Put this to 1, if the card will contains only a banner (this add css 'arearefnobottom' on div)
1336
     * 	@param	string	$morehtmlright	More html code to show before navigation arrows
1337
     *  @return	void
1338
     */
1339
    static function dol_banner_tab($object, $paramid, $morehtml = '', $shownav = 1, $fieldid = 'rowid', $fieldref = 'ref', $morehtmlref = '', $moreparam = '', $nodbprefix = 0, $morehtmlleft = '', $morehtmlstatus = '', $onlybanner = 0, $morehtmlright = '')
1340
    {
1341
        // global Globals::$conf, $form, $user, Globals::$langs;
1342
1343
        $error = 0;
1344
1345
        $maxvisiblephotos = 1;
1346
        $showimage = 1;
1347
        $entity = (empty($object->entity) ? Globals::$conf->entity : $object->entity);
1348
        $showbarcode = empty(Globals::$conf->barcode->enabled) ? 0 : ($object->barcode ? 1 : 0);
1349
        if (!empty(Globals::$conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->barcode->lire_advance))
1350
            $showbarcode = 0;
1351
        $modulepart = 'unknown';
1352
1353
        if ($object->element == 'societe') {
1354
            $modulepart = 'societe';
1355
        }
1356
        if ($object->element == 'contact') {
1357
            $modulepart = 'contact';
1358
        }
1359
        if ($object->element == 'member') {
1360
            $modulepart = 'memberphoto';
1361
        }
1362
        if ($object->element == 'user') {
1363
            $modulepart = 'userphoto';
1364
        }
1365
        if ($object->element == 'product') {
1366
            $modulepart = 'product';
1367
        }
1368
1369
        if (class_exists("Imagick")) {
1370
            if ($object->element == 'propal') {
1371
                $modulepart = 'propal';
1372
            }
1373
            if ($object->element == 'commande') {
1374
                $modulepart = 'commande';
1375
            }
1376
            if ($object->element == 'facture') {
1377
                $modulepart = 'facture';
1378
            }
1379
            if ($object->element == 'fichinter') {
1380
                $modulepart = 'ficheinter';
1381
            }
1382
            if ($object->element == 'contrat') {
1383
                $modulepart = 'contract';
1384
            }
1385
            if ($object->element == 'supplier_proposal') {
1386
                $modulepart = 'supplier_proposal';
1387
            }
1388
            if ($object->element == 'order_supplier') {
1389
                $modulepart = 'supplier_order';
1390
            }
1391
            if ($object->element == 'invoice_supplier') {
1392
                $modulepart = 'supplier_invoice';
1393
            }
1394
            if ($object->element == 'expensereport') {
1395
                $modulepart = 'expensereport';
1396
            }
1397
        }
1398
1399
        if ($object->element == 'product') {
1400
            $width = 80;
1401
            $cssclass = 'photoref';
1402
            $showimage = $object->is_photo_available(Globals::$conf->product->multidir_output[$entity]);
1403
            $maxvisiblephotos = (isset(Globals::$conf->global->PRODUCT_MAX_VISIBLE_PHOTO) ? Globals::$conf->global->PRODUCT_MAX_VISIBLE_PHOTO : 5);
1404
            if (Globals::$conf->browser->layout == 'phone') {
1405
                $maxvisiblephotos = 1;
1406
            }
1407
            if ($showimage) {
1408
                $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">' . $object->show_photos('product', Globals::$conf->product->multidir_output[$entity], 'small', $maxvisiblephotos, 0, 0, 0, $width, 0) . '</div>';
1409
            } else {
1410
                if (!empty(Globals::$conf->global->PRODUCT_NODISPLAYIFNOPHOTO)) {
1411
                    $nophoto = '';
1412
                    $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
1413
                }
1414
//elseif (Globals::$conf->browser->layout != 'phone') {    // Show no photo link
1415
                $nophoto = '/public/theme/common/nophoto.png';
1416
// $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><img class="photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . '" alt="No photo" border="0"' . ($width ? ' width="' . $width . '"' : '') . ' src="' . DOL_BASE_URI . $nophoto . '"></div>';
1417
                $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><img class="photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . '" alt="No photo" border="0"' . ($width ? ' width="' . $width . '"' : '') . ' src="' . DOL_BASE_URI . $nophoto . '"></div>';
1418
//}
1419
            }
1420
        } elseif ($object->element == 'ticket') {
1421
            $width = 80;
1422
            $cssclass = 'photoref';
1423
            $showimage = $object->is_photo_available(Globals::$conf->ticket->multidir_output[$entity] . '/' . $object->track_id);
1424
            $maxvisiblephotos = (isset(Globals::$conf->global->TICKETSUP_MAX_VISIBLE_PHOTO) ? Globals::$conf->global->TICKETSUP_MAX_VISIBLE_PHOTO : 2);
1425
            if (Globals::$conf->browser->layout == 'phone') {
1426
                $maxvisiblephotos = 1;
1427
            }
1428
            if ($showimage) {
1429
                $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">' . $object->show_photos('ticket', Globals::$conf->ticket->multidir_output[$entity], 'small', $maxvisiblephotos, 0, 0, 0, $width, 0) . '</div>';
1430
            } else {
1431
                if (!empty(Globals::$conf->global->TICKETSUP_NODISPLAYIFNOPHOTO)) {
1432
                    $nophoto = '';
1433
                    $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
1434
                }
1435
//elseif (Globals::$conf->browser->layout != 'phone') {    // Show no photo link
1436
                $nophoto = '/public/theme/common/nophoto.png';
1437
//$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><img class="photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . '" alt="No photo" border="0"' . ($width ? ' width="' . $width . '"' : '') . ' src="' . DOL_BASE_URI . $nophoto . '"></div>';
1438
                $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><img class="photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . '" alt="No photo" border="0"' . ($width ? ' width="' . $width . '"' : '') . ' src="' . DOL_BASE_URI . $nophoto . '"></div>';
1439
//}
1440
            }
1441
        } else {
1442
            if ($showimage) {
1443
                if ($modulepart != 'unknown') {
1444
                    $phototoshow = '';
1445
                    // Check if a preview file is available
1446
                    if (in_array($modulepart, array('propal', 'commande', 'facture', 'ficheinter', 'contract', 'supplier_order', 'supplier_proposal', 'supplier_invoice', 'expensereport')) && class_exists("Imagick")) {
1447
                        $objectref = dol_sanitizeFileName($object->ref);
1448
                        $dir_output = (empty(Globals::$conf->$modulepart->multidir_output[$entity]) ? Globals::$conf->$modulepart->dir_output : Globals::$conf->$modulepart->multidir_output[$entity]) . "/";
1449
                        if (in_array($modulepart, array('invoice_supplier', 'supplier_invoice'))) {
1450
                            $subdir = get_exdir($object->id, 2, 0, 1, $object, $modulepart);
1451
                            $subdir .= ((!empty($subdir) && !preg_match('/\/$/', $subdir)) ? '/' : '') . $objectref;  // the objectref dir is not included into get_exdir when used with level=2, so we add it at end
1452
                        } else {
1453
                            $subdir = get_exdir($object->id, 0, 0, 1, $object, $modulepart);
1454
                        }
1455
                        if (empty($subdir))
1456
                            $subdir = 'errorgettingsubdirofobject'; // Protection to avoid to return empty path
1457
1458
                        $filepath = $dir_output . $subdir . "/";
1459
1460
                        $file = $filepath . $objectref . ".pdf";
1461
                        $relativepath = $subdir . '/' . $objectref . '.pdf';
1462
1463
                        // Define path to preview pdf file (preview precompiled "file.ext" are "file.ext_preview.png")
1464
                        $fileimage = $file . '_preview.png';              // If PDF has 1 page
1465
                        $fileimagebis = $file . '_preview-0.png';         // If PDF has more than one page
1466
                        $relativepathimage = $relativepath . '_preview.png';
1467
1468
                        // Si fichier PDF existe
1469
                        if (file_exists($file)) {
1470
                            $encfile = urlencode($file);
1471
                            // Conversion du PDF en image png si fichier png non existant
1472
                            if ((!file_exists($fileimage) || (filemtime($fileimage) < filemtime($file))) && (!file_exists($fileimagebis) || (filemtime($fileimagebis) < filemtime($file)))
1473
                            ) {
1474
                                if (empty(Globals::$conf->global->MAIN_DISABLE_PDF_THUMBS)) {  // If you experienc trouble with pdf thumb generation and imagick, you can disable here.
1475
                                    include_once DOL_BASE_PATH . '/core/lib/files.lib.php';
1476
                                    $ret = dol_convert_file($file, 'png', $fileimage);
1477
                                    if ($ret < 0)
1478
                                        $error++;
1479
                                }
1480
                            }
1481
1482
                            $heightforphotref = 70;
1483
                            if (!empty(Globals::$conf->dol_optimize_smallscreen))
1484
                                $heightforphotref = 60;
1485
                            // Si fichier png PDF d'1 page trouve
1486
                            if (file_exists($fileimage)) {
1487
                                $phototoshow = '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
1488
                                $phototoshow .= '<img height="' . $heightforphotref . '" class="photo photowithmargin photowithborder" src="' . DOL_BASE_URI . '/viewimage.php?modulepart=apercu' . $modulepart . '&amp;file=' . urlencode($relativepathimage) . '">';
1489
                                $phototoshow .= '</div></div>';
1490
                            }
1491
                            // Si fichier png PDF de plus d'1 page trouve
1492
                            elseif (file_exists($fileimagebis)) {
1493
                                $preview = preg_replace('/\.png/', '', $relativepathimage) . "-0.png";
1494
                                $phototoshow = '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
1495
                                $phototoshow .= '<img height="' . $heightforphotref . '" class="photo photowithmargin photowithborder" src="' . DOL_BASE_URI . '/viewimage.php?modulepart=apercu' . $modulepart . '&amp;file=' . urlencode($preview) . '"><p>';
1496
                                $phototoshow .= '</div></div>';
1497
                            }
1498
                        }
1499
                    } else if (!$phototoshow) {
1500
                        $phototoshow = $form->showphoto($modulepart, $object, 0, 0, 0, 'photoref', 'small', 1, 0, $maxvisiblephotos);
1501
                    }
1502
1503
                    if ($phototoshow) {
1504
                        $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">';
1505
                        $morehtmlleft .= $phototoshow;
1506
                        $morehtmlleft .= '</div>';
1507
                    }
1508
                }
1509
1510
                if (!$phototoshow) {      // Show No photo link (picto of pbject)
1511
                    $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">';
1512
                    if ($object->element == 'action') {
1513
                        $width = 80;
1514
                        $cssclass = 'photorefcenter';
1515
                        $nophoto = img_picto('', 'title_agenda', '', false, 1);
1516
                    } else {
1517
                        $width = 14;
1518
                        $cssclass = 'photorefcenter';
1519
                        $picto = $object->picto;
1520
                        if ($object->element == 'project' && !$object->public)
1521
                            $picto = 'project'; // instead of projectpub
1522
                        $nophoto = img_picto('', 'object_' . $picto, '', false, 1);
1523
                    }
1524
                    $morehtmlleft .= '<!-- No photo to show -->';
1525
                    $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref"><img class="photo' . $modulepart . ($cssclass ? ' ' . $cssclass : '') . '" alt="No photo" border="0"' . ($width ? ' width="' . $width . '"' : '') . ' src="' . $nophoto . '"></div></div>';
1526
1527
                    $morehtmlleft .= '</div>';
1528
                }
1529
            }
1530
        }
1531
1532
        if ($showbarcode)
1533
            $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">' . $form->showbarcode($object) . '</div>';
1534
1535
        if ($object->element == 'societe') {
1536
            if (!empty(Globals::$conf->use_javascript_ajax) && $user->rights->societe->creer && !empty(Globals::$conf->global->MAIN_DIRECT_STATUS_UPDATE)) {
1537
                $morehtmlstatus .= ajax_object_onoff($object, 'status', 'status', 'InActivity', 'ActivityCeased');
1538
            } else {
1539
                $morehtmlstatus .= $object->getLibStatut(6);
1540
            }
1541
        } elseif ($object->element == 'product') {
1542
//$morehtmlstatus.=Globals::$langs->trans("Status").' ('.Globals::$langs->trans("Sell").') ';
1543
            if (!empty(Globals::$conf->use_javascript_ajax) && $user->rights->produit->creer && !empty(Globals::$conf->global->MAIN_DIRECT_STATUS_UPDATE)) {
1544
                $morehtmlstatus .= ajax_object_onoff($object, 'status', 'tosell', 'ProductStatusOnSell', 'ProductStatusNotOnSell');
1545
            } else {
1546
                $morehtmlstatus .= '<span class="statusrefsell">' . $object->getLibStatut(5, 0) . '</span>';
1547
            }
1548
            $morehtmlstatus .= ' &nbsp; ';
1549
//$morehtmlstatus.=Globals::$langs->trans("Status").' ('.Globals::$langs->trans("Buy").') ';
1550
            if (!empty(Globals::$conf->use_javascript_ajax) && $user->rights->produit->creer && !empty(Globals::$conf->global->MAIN_DIRECT_STATUS_UPDATE)) {
1551
                $morehtmlstatus .= ajax_object_onoff($object, 'status_buy', 'tobuy', 'ProductStatusOnBuy', 'ProductStatusNotOnBuy');
1552
            } else {
1553
                $morehtmlstatus .= '<span class="statusrefbuy">' . $object->getLibStatut(5, 1) . '</span>';
1554
            }
1555
        } elseif (in_array($object->element, array('facture', 'invoice', 'invoice_supplier', 'chargesociales', 'loan'))) {
1556
            $tmptxt = $object->getLibStatut(6, $object->totalpaye);
1557
            if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3) || Globals::$conf->browser->layout == 'phone')
1558
                $tmptxt = $object->getLibStatut(5, $object->totalpaye);
1559
            $morehtmlstatus .= $tmptxt;
1560
        }
1561
        elseif ($object->element == 'contrat' || $object->element == 'contract') {
1562
            if ($object->statut == 0)
1563
                $morehtmlstatus .= $object->getLibStatut(5);
1564
            else
1565
                $morehtmlstatus .= $object->getLibStatut(4);
1566
        }
1567
        elseif ($object->element == 'facturerec') {
1568
            if ($object->frequency == 0)
1569
                $morehtmlstatus .= $object->getLibStatut(2);
1570
            else
1571
                $morehtmlstatus .= $object->getLibStatut(5);
1572
        }
1573
        elseif ($object->element == 'project_task') {
1574
            $object->fk_statut = 1;
1575
            if ($object->progress > 0)
1576
                $object->fk_statut = 2;
1577
            if ($object->progress >= 100)
1578
                $object->fk_statut = 3;
1579
            $tmptxt = $object->getLibStatut(5);
1580
            $morehtmlstatus .= $tmptxt;  // No status on task
1581
        }
1582
        else { // Generic case
1583
            $tmptxt = $object->getLibStatut(6);
1584
            if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3) || Globals::$conf->browser->layout == 'phone')
1585
                $tmptxt = $object->getLibStatut(5);
1586
            $morehtmlstatus .= $tmptxt;
1587
        }
1588
1589
// Add if object was dispatched "into accountancy"
1590
        if (!empty(Globals::$conf->accounting->enabled) && in_array($object->element, array('bank', 'facture', 'invoice', 'invoice_supplier', 'expensereport'))) {
1591
            if (method_exists($object, 'getVentilExportCompta')) {
1592
                $accounted = $object->getVentilExportCompta();
1593
                Globals::$langs->load("accountancy");
1594
                $morehtmlstatus .= '</div><div class="statusref statusrefbis">' . ($accounted > 0 ? Globals::$langs->trans("Accounted") : Globals::$langs->trans("NotYetAccounted"));
1595
            }
1596
        }
1597
1598
// Add alias for thirdparty
1599
        if (!empty($object->name_alias))
1600
            $morehtmlref .= '<div class="refidno">' . $object->name_alias . '</div>';
1601
1602
// Add label
1603
        if ($object->element == 'product' || $object->element == 'bank_account' || $object->element == 'project_task') {
1604
            if (!empty($object->label))
1605
                $morehtmlref .= '<div class="refidno">' . $object->label . '</div>';
1606
        }
1607
1608
        if (method_exists($object, 'getBannerAddress') && $object->element != 'product' && $object->element != 'bookmark' && $object->element != 'ecm_directories' && $object->element != 'ecm_files') {
1609
            $morehtmlref .= '<div class="refidno">';
1610
            $morehtmlref .= $object->getBannerAddress('refaddress', $object);
1611
            $morehtmlref .= '</div>';
1612
        }
1613
        if (!empty(Globals::$conf->global->MAIN_SHOW_TECHNICAL_ID) && in_array($object->element, array('societe', 'contact', 'member', 'product'))) {
1614
            $morehtmlref .= '<div style="clear: both;"></div><div class="refidno">';
1615
            $morehtmlref .= Globals::$langs->trans("TechnicalID") . ': ' . $object->id;
1616
            $morehtmlref .= '</div>';
1617
        }
1618
1619
        print '<div class="' . ($onlybanner ? 'arearefnobottom ' : 'arearef ') . 'heightref valignmiddle" width="100%">';
1620
        print $form->showrefnav($object, $paramid, $morehtml, $shownav, $fieldid, $fieldref, $morehtmlref, $moreparam, $nodbprefix, $morehtmlleft, $morehtmlstatus, $morehtmlright);
1621
        print '</div>';
1622
        print '<div class="underrefbanner clearboth"></div>';
1623
    }
1624
1625
    /**
1626
     * Show a string with the label tag dedicated to the HTML edit field.
1627
     *
1628
     * @param	string	$langkey		Translation key
1629
     * @param 	string	$fieldkey		Key of the html select field the text refers to
1630
     * @param	int		$fieldrequired	1=Field is mandatory
1631
     * @return string
1632
     * @deprecated Form::editfieldkey
1633
     */
1634
    static function fieldLabel($langkey, $fieldkey, $fieldrequired = 0)
1635
    {
1636
        // global Globals::$conf, Globals::$langs;
1637
        $ret = '';
1638
        if ($fieldrequired)
1639
            $ret .= '<span class="fieldrequired">';
1640
        if ((Globals::$conf->dol_use_jmobile != 4))
1641
            $ret .= '<label for="' . $fieldkey . '">';
1642
        $ret .= Globals::$langs->trans($langkey);
1643
        if ((Globals::$conf->dol_use_jmobile != 4))
1644
            $ret .= '</label>';
1645
        if ($fieldrequired)
1646
            $ret .= '</span>';
1647
        return $ret;
1648
    }
1649
1650
    /**
1651
     * Return string to add class property on html element with pair/impair.
1652
     *
1653
     * @param	string	$var			0 or 1
1654
     * @param	string	$moreclass		More class to add
1655
     * @return	string					String to add class onto HTML element
1656
     */
1657
    static function dol_bc($var, $moreclass = '')
1658
    {
1659
        // global $bc;
1660
        $ret = ' ' . $bc[$var];
1661
        if ($moreclass)
1662
            $ret = preg_replace('/class=\"/', 'class="' . $moreclass . ' ', $ret);
1663
        return $ret;
1664
    }
1665
1666
    /**
1667
     *      Return a formated address (part address/zip/town/state) according to country rules
1668
     *
1669
     *      @param  Object		$object			A company or contact object
1670
     * 	    @param	int			$withcountry		1=Add country into address string
1671
     *      @param	string		$sep				Separator to use to build string
1672
     *      @param	Translate	$outputlangs		Object lang that contains language for text translation.
1673
     *      @param	int		$mode		0=Standard output, 1=Remove address
1674
     *      @return string						Formated string
1675
     *      @see dol_print_address
1676
     */
1677
    static function dol_format_address($object, $withcountry = 0, $sep = "\n", $outputlangs = '', $mode = 0)
1678
    {
1679
        // global Globals::$conf, Globals::$langs;
1680
1681
        $ret = '';
1682
        $countriesusingstate = array('AU', 'CA', 'US', 'IN', 'GB', 'ES', 'UK', 'TR');    // See also MAIN_FORCE_STATE_INTO_ADDRESS
1683
// Address
1684
        if (empty($mode)) {
1685
            $ret .= $object->address;
1686
        }
1687
// Zip/Town/State
1688
        if (in_array($object->country_code, array('AU', 'CA', 'US')) || !empty(Globals::$conf->global->MAIN_FORCE_STATE_INTO_ADDRESS)) {    // US: title firstname name \n address lines \n town, state, zip \n country
1689
            $ret .= ($ret ? $sep : '' ) . $object->town;
1690
            if ($object->state) {
1691
                $ret .= ($ret ? ", " : '') . $object->state;
1692
            }
1693
            if ($object->zip)
1694
                $ret .= ($ret ? ", " : '') . $object->zip;
1695
        }
1696
        else if (in_array($object->country_code, array('GB', 'UK'))) { // UK: title firstname name \n address lines \n town state \n zip \n country
1697
            $ret .= ($ret ? $sep : '' ) . $object->town;
1698
            if ($object->state) {
1699
                $ret .= ($ret ? ", " : '') . $object->state;
1700
            }
1701
            if ($object->zip)
1702
                $ret .= ($ret ? $sep : '' ) . $object->zip;
1703
        }
1704
        else if (in_array($object->country_code, array('ES', 'TR'))) { // ES: title firstname name \n address lines \n zip town \n state \n country
1705
            $ret .= ($ret ? $sep : '' ) . $object->zip;
1706
            $ret .= ($object->town ? (($object->zip ? ' ' : '') . $object->town) : '');
1707
            if ($object->state) {
1708
                $ret .= "\n" . $object->state;
1709
            }
1710
        } else if (in_array($object->country_code, array('IT'))) { // IT: tile firstname name\n address lines \n zip (Code Departement) \n country
1711
            $ret .= ($ret ? $sep : '' ) . $object->zip;
1712
            $ret .= ($object->town ? (($object->zip ? ' ' : '') . $object->town) : '');
1713
            $ret .= ($object->departement_id ? (' (' . ($object->departement_id) . ')') : '');
1714
        } else {                                          // Other: title firstname name \n address lines \n zip town \n country
1715
            $ret .= $object->zip ? (($ret ? $sep : '' ) . $object->zip) : '';
1716
            $ret .= ($object->town ? (($object->zip ? ' ' : ($ret ? $sep : '' )) . $object->town) : '');
1717
            if ($object->state && in_array($object->country_code, $countriesusingstate)) {
1718
                $ret .= ($ret ? ", " : '') . $object->state;
1719
            }
1720
        }
1721
        if (!is_object($outputlangs))
1722
            $outputlangs = Globals::$langs;
1723
        if ($withcountry) {
1724
            Globals::$langs->load("dict");
1725
            $ret .= ($object->country_code ? ($ret ? $sep : '') . $outputlangs->convToOutputCharset($outputlangs->transnoentitiesnoconv("Country" . $object->country_code)) : '');
1726
        }
1727
1728
        return $ret;
1729
    }
1730
1731
    /**
1732
     * 	Format a string.
1733
     *
1734
     * 	@param	string	$fmt		Format of strftime static function (http://php.net/manual/fr/function.strftime.php)
1735
     *  @param	int		$ts			Timesamp (If is_gmt is true, timestamp is already includes timezone and daylight saving offset, if is_gmt is false, timestamp is a GMT timestamp and we must compensate with server PHP TZ)
1736
     *  @param	int		$is_gmt		See comment of timestamp parameter
1737
     * 	@return	string				A formatted string
1738
     */
1739
    static function dol_strftime($fmt, $ts = false, $is_gmt = false)
1740
    {
1741
        if ((abs($ts) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range
1742
            return ($is_gmt) ? @gmstrftime($fmt, $ts) : @strftime($fmt, $ts);
1743
        } else
1744
            return 'Error date into a not supported range';
1745
    }
1746
1747
    /**
1748
     * 	Output date in a string format according to outputlangs (or langs if not defined).
1749
     * 	Return charset is always UTF-8, except if encodetoouput is defined. In this case charset is output charset
1750
     *
1751
     * 	@param	int			$time			GM Timestamps date
1752
     * 	@param	string		$format      	Output date format (tag of strftime function)
1753
     * 										"%d %b %Y",
1754
     * 										"%d/%m/%Y %H:%M",
1755
     * 										"%d/%m/%Y %H:%M:%S",
1756
     *                                      "%B"=Long text of month, "%A"=Long text of day, "%b"=Short text of month, "%a"=Short text of day
1757
     * 										"day", "daytext", "dayhour", "dayhourldap", "dayhourtext", "dayrfc", "dayhourrfc", "...reduceformat"
1758
     * 	@param	string		$tzoutput		true or 'gmt' => string is for Greenwich location
1759
     * 										false or 'tzserver' => output string is for local PHP server TZ usage
1760
     * 										'tzuser' => output string is for user TZ (current browser TZ with current dst) => In a future, we should have same behaviour than 'tzuserrel'
1761
     *                                      'tzuserrel' => output string is for user TZ (current browser TZ with dst or not, depending on date position) (TODO not implemented yet)
1762
     * 	@param	Translate	$outputlangs	Object lang that contains language for text translation.
1763
     *  @param  boolean		$encodetooutput false=no convert into output pagecode
1764
     * 	@return string      				Formated date or '' if time is null
1765
     *
1766
     *  @see        dol_mktime, dol_stringtotime, dol_getdate
1767
     */
1768
    static function dol_print_date($time, $format = '', $tzoutput = 'tzserver', $outputlangs = '', $encodetooutput = false)
1769
    {
1770
        // global Globals::$conf, Globals::$langs;
1771
// Clean parameters
1772
        $to_gmt = false;
1773
        $offsettz = $offsetdst = 0;
1774
        if ($tzoutput) {
1775
            $to_gmt = true; // For backward compatibility
1776
            if (is_string($tzoutput)) {
1777
                if ($tzoutput == 'tzserver') {
1778
                    $to_gmt = false;
1779
                    $offsettzstring = @date_default_timezone_get();  // Example 'Europe/Berlin' or 'Indian/Reunion'
1780
                    $offsettz = 0;
1781
                    $offsetdst = 0;
1782
                } elseif ($tzoutput == 'tzuser' || $tzoutput == 'tzuserrel') {
1783
                    $to_gmt = true;
1784
                    $offsettzstring = (empty($_SESSION['dol_tz_string']) ? 'UTC' : $_SESSION['dol_tz_string']); // Example 'Europe/Berlin' or 'Indian/Reunion'
1785
                    $offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60;  // Will not be used anymore
1786
                    $offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60; // Will not be used anymore
1787
                }
1788
            }
1789
        }
1790
        if (!is_object($outputlangs))
1791
            $outputlangs = Globals::$langs;
1792
        if (!$format)
1793
            $format = 'daytextshort';
1794
        $reduceformat = (!empty(Globals::$conf->dol_optimize_smallscreen) && in_array($format, array('day', 'dayhour'))) ? 1 : 0;
1795
        $formatwithoutreduce = preg_replace('/reduceformat/', '', $format);
1796
        if ($formatwithoutreduce != $format) {
1797
            $format = $formatwithoutreduce;
1798
            $reduceformat = 1;
1799
        }  // so format 'dayreduceformat' is processed like day
1800
// Change predefined format into computer format. If found translation in lang file we use it, otherwise we use default.
1801
// TODO Add format daysmallyear and dayhoursmallyear
1802
        if ($format == 'day')
1803
            $format = ($outputlangs->trans("FormatDateShort") != "FormatDateShort" ? $outputlangs->trans("FormatDateShort") : Globals::$conf->format_date_short);
1804
        else if ($format == 'hour')
1805
            $format = ($outputlangs->trans("FormatHourShort") != "FormatHourShort" ? $outputlangs->trans("FormatHourShort") : Globals::$conf->format_hour_short);
1806
        else if ($format == 'hourduration')
1807
            $format = ($outputlangs->trans("FormatHourShortDuration") != "FormatHourShortDuration" ? $outputlangs->trans("FormatHourShortDuration") : Globals::$conf->format_hour_short_duration);
1808
        else if ($format == 'daytext')
1809
            $format = ($outputlangs->trans("FormatDateText") != "FormatDateText" ? $outputlangs->trans("FormatDateText") : Globals::$conf->format_date_text);
1810
        else if ($format == 'daytextshort')
1811
// Notice: Undefined property: stdClass::$format_date_text_short in \alixar\dolibarr\htdocs\core\lib\functions.lib.php on line 1781
1812
            $format = ($outputlangs->trans("FormatDateTextShort") != "FormatDateTextShort" ? $outputlangs->trans("FormatDateTextShort") : Globals::$conf->format_date_text_short);
1813
        else if ($format == 'dayhour')
1814
            $format = ($outputlangs->trans("FormatDateHourShort") != "FormatDateHourShort" ? $outputlangs->trans("FormatDateHourShort") : Globals::$conf->format_date_hour_short ?? "%d/%m/%Y %H:%M");
1815
        else if ($format == 'dayhoursec')
1816
            $format = ($outputlangs->trans("FormatDateHourSecShort") != "FormatDateHourSecShort" ? $outputlangs->trans("FormatDateHourSecShort") : Globals::$conf->format_date_hour_sec_short);
1817
        else if ($format == 'dayhourtext')
1818
            $format = ($outputlangs->trans("FormatDateHourText") != "FormatDateHourText" ? $outputlangs->trans("FormatDateHourText") : Globals::$conf->format_date_hour_text);
1819
        else if ($format == 'dayhourtextshort')
1820
            $format = ($outputlangs->trans("FormatDateHourTextShort") != "FormatDateHourTextShort" ? $outputlangs->trans("FormatDateHourTextShort") : Globals::$conf->format_date_hour_text_short);
1821
// Format not sensitive to language
1822
        else if ($format == 'dayhourlog')
1823
            $format = '%Y%m%d%H%M%S';
1824
        else if ($format == 'dayhourldap')
1825
            $format = '%Y%m%d%H%M%SZ';
1826
        else if ($format == 'dayhourxcard')
1827
            $format = '%Y%m%dT%H%M%SZ';
1828
        else if ($format == 'dayxcard')
1829
            $format = '%Y%m%d';
1830
        else if ($format == 'dayrfc')
1831
            $format = '%Y-%m-%d';             // DATE_RFC3339
1832
        else if ($format == 'dayhourrfc')
1833
            $format = '%Y-%m-%dT%H:%M:%SZ';   // DATETIME RFC3339
1834
        else if ($format == 'standard')
1835
            $format = '%Y-%m-%d %H:%M:%S';
1836
1837
        if ($reduceformat) {
1838
            $format = str_replace('%Y', '%y', $format);
1839
            $format = str_replace('yyyy', 'yy', $format);
1840
        }
1841
1842
// If date undefined or "", we return ""
1843
        if (AlDolUtils::dol_strlen($time) == 0)
1844
            return '';  // $time=0 allowed (it means 01/01/1970 00:00:00)
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
            
1898
// Clean format
1899
        if (preg_match('/%b/i', $format)) {  // There is some text to translate
1900
// We inhibate translation to text made by strftime functions. We will use trans instead later.
1901
            $format = str_replace('%b', '__b__', $format);
1902
            $format = str_replace('%B', '__B__', $format);
1903
        }
1904
        if (preg_match('/%a/i', $format)) {  // There is some text to translate
1905
// We inhibate translation to text made by strftime functions. We will use trans instead later.
1906
            $format = str_replace('%a', '__a__', $format);
1907
            $format = str_replace('%A', '__A__', $format);
1908
        }
1909
1910
// Analyze date
1911
        if (preg_match('/^([0-9]+)\-([0-9]+)\-([0-9]+) ?([0-9]+)?:?([0-9]+)?:?([0-9]+)?/i', $time, $reg) || preg_match('/^([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9])([0-9][0-9])([0-9][0-9])([0-9][0-9])$/i', $time, $reg)) { // Deprecated. Ex: 1970-01-01, 1970-01-01 01:00:00, 19700101010000
1912
// TODO Remove this.
1913
// This part of code should not be used.
1914
            AlDolUtils::dol_syslog("Functions.lib::DolUtils::dol_print_date static function call with deprecated value of time in page " . $_SERVER["PHP_SELF"], LOG_ERR);
1915
// Date has format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS' or 'YYYYMMDDHHMMSS'
1916
            $syear = (!empty($reg[1]) ? $reg[1] : '');
1917
            $smonth = (!empty($reg[2]) ? $reg[2] : '');
1918
            $sday = (!empty($reg[3]) ? $reg[3] : '');
1919
            $shour = (!empty($reg[4]) ? $reg[4] : '');
1920
            $smin = (!empty($reg[5]) ? $reg[5] : '');
1921
            $ssec = (!empty($reg[6]) ? $reg[6] : '');
1922
1923
            $time = AlDolUtils::dol_mktime($shour, $smin, $ssec, $smonth, $sday, $syear, true);
1924
            $ret = adodb_strftime($format, $time + $offsettz + $offsetdst, $to_gmt);
1925
        } else {
1926
// Date is a timestamps
1927
            if ($time < 100000000000) { // Protection against bad date values
1928
                $timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with static function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
1929
1930
                $ret = adodb_strftime($format, $timetouse, $to_gmt);
1931
            } else
1932
                $ret = 'Bad value ' . $time . ' for date';
1933
        }
1934
1935
        if (preg_match('/__b__/i', $format)) {
1936
            $timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with static function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
1937
// Here ret is string in PHP setup language (strftime was used). Now we convert to $outputlangs.
1938
            $month = adodb_strftime('%m', $timetouse);
1939
            $month = sprintf("%02d", $month); // $month may be return with format '06' on some installation and '6' on other, so we force it to '06'.
1940
            if ($encodetooutput) {
1941
                $monthtext = $outputlangs->transnoentities('Month' . $month);
1942
                $monthtextshort = $outputlangs->transnoentities('MonthShort' . $month);
1943
            } else {
1944
                $monthtext = $outputlangs->transnoentitiesnoconv('Month' . $month);
1945
                $monthtextshort = $outputlangs->transnoentitiesnoconv('MonthShort' . $month);
1946
            }
1947
//print 'monthtext='.$monthtext.' monthtextshort='.$monthtextshort;
1948
            $ret = str_replace('__b__', $monthtextshort, $ret);
1949
            $ret = str_replace('__B__', $monthtext, $ret);
1950
//print 'x'.$outputlangs->charset_output.'-'.$ret.'x';
1951
//return $ret;
1952
        }
1953
        if (preg_match('/__a__/i', $format)) {
1954
            $timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with static function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
1955
1956
            $w = adodb_strftime('%w', $timetouse);      // TODO Replace this with static function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
1957
            $dayweek = $outputlangs->transnoentitiesnoconv('Day' . $w);
1958
            $ret = str_replace('__A__', $dayweek, $ret);
1959
            $ret = str_replace('__a__', dol_substr($dayweek, 0, 3), $ret);
1960
        }
1961
1962
        return $ret;
1963
    }
1964
1965
    /**
1966
     * 	Return an array with locale date info.
1967
     *  PHP getdate is restricted to the years 1901-2038 on Unix and 1970-2038 on Windows
1968
     *  WARNING: This static function always use PHP server timezone to return locale informations !!!
1969
     *  Usage must be avoid.
1970
     *  FIXME: Replace this with PHP date static function and a parameter $gm
1971
     *
1972
     * 	@param	int			$timestamp      Timestamp
1973
     * 	@param	boolean		$fast           Fast mode
1974
     * 	@return	array						Array of informations
1975
     * 										If no fast mode:
1976
     * 										'seconds' => $secs,
1977
     * 										'minutes' => $min,
1978
     * 										'hours' => $hour,
1979
     * 										'mday' => $day,
1980
     * 										'wday' => $dow,		0=sunday, 6=saturday
1981
     * 										'mon' => $month,
1982
     * 										'year' => $year,
1983
     * 										'yday' => floor($secsInYear/$_day_power),
1984
     * 										'weekday' => gmdate('l',$_day_power*(3+$dow)),
1985
     * 										'month' => gmdate('F',mktime(0,0,0,$month,2,1971)),
1986
     * 										If fast mode:
1987
     * 										'seconds' => $secs,
1988
     * 										'minutes' => $min,
1989
     * 										'hours' => $hour,
1990
     * 										'mday' => $day,
1991
     * 										'mon' => $month,
1992
     * 										'year' => $year,
1993
     * 										'yday' => floor($secsInYear/$_day_power),
1994
     * 										'leap' => $leaf,
1995
     * 										'ndays' => $ndays
1996
     * 	@see 								DolUtils::dol_print_date, dol_stringtotime, dol_mktime
1997
     */
1998
    static function dol_getdate($timestamp, $fast = false)
1999
    {
2000
        // global Globals::$conf;
2001
2002
        $usealternatemethod = false;
2003
        if ($timestamp <= 0)
2004
            $usealternatemethod = true;    // <= 1970
2005
        if ($timestamp >= 2145913200)
2006
            $usealternatemethod = true;  // >= 2038
2007
2008
        if ($usealternatemethod) {
2009
            $arrayinfo = adodb_getdate($timestamp, $fast);
2010
        } else {
2011
            $arrayinfo = getdate($timestamp);
2012
        }
2013
2014
        return $arrayinfo;
2015
    }
2016
2017
    /**
2018
     * 	Return a timestamp date built from detailed informations (by default a local PHP server timestamp)
2019
     * 	Replace static function mktime not available under Windows if year < 1970
2020
     * 	PHP mktime is restricted to the years 1901-2038 on Unix and 1970-2038 on Windows
2021
     *
2022
     * 	@param	int			$hour			Hour	(can be -1 for undefined)
2023
     * 	@param	int			$minute			Minute	(can be -1 for undefined)
2024
     * 	@param	int			$second			Second	(can be -1 for undefined)
2025
     * 	@param	int			$month			Month (1 to 12)
2026
     * 	@param	int			$day			Day (1 to 31)
2027
     * 	@param	int			$year			Year
2028
     * 	@param	mixed		$gm				True or 1 or 'gmt'=Input informations are GMT values
2029
     * 										False or 0 or 'server' = local to server TZ
2030
     * 										'user' = local to user TZ
2031
     * 										'tz,TimeZone' = use specified timezone
2032
     * 	@param	int			$check			0=No check on parameters (Can use day 32, etc...)
2033
     * 	@return	int|string					Date as a timestamp, '' or false if error
2034
     * 	@see 								DolUtils::dol_print_date, dol_stringtotime, dol_getdate
2035
     */
2036
    static function dol_mktime($hour, $minute, $second, $month, $day, $year, $gm = false, $check = 1)
2037
    {
2038
        // global Globals::$conf;
2039
//print "- ".$hour.",".$minute.",".$second.",".$month.",".$day.",".$year.",".$_SERVER["WINDIR"]." -";
2040
// Clean parameters
2041
        if ($hour == -1 || empty($hour))
2042
            $hour = 0;
2043
        if ($minute == -1 || empty($minute))
2044
            $minute = 0;
2045
        if ($second == -1 || empty($second))
2046
            $second = 0;
2047
2048
// Check parameters
2049
        if ($check) {
2050
            if (!$month || !$day)
2051
                return '';
2052
            if ($day > 31)
2053
                return '';
2054
            if ($month > 12)
2055
                return '';
2056
            if ($hour < 0 || $hour > 24)
2057
                return '';
2058
            if ($minute < 0 || $minute > 60)
2059
                return '';
2060
            if ($second < 0 || $second > 60)
2061
                return '';
2062
        }
2063
2064
        if (method_exists('DateTime', 'getTimestamp')) {
2065
            if (empty($gm) || $gm === 'server') {
2066
                $default_timezone = @date_default_timezone_get();  // Example 'Europe/Berlin'
2067
                $localtz = new \DateTimeZone($default_timezone);
2068
            } else if ($gm === 'user') {
2069
// We use dol_tz_string first because it is more reliable.
2070
                $default_timezone = (empty($_SESSION["dol_tz_string"]) ? @date_default_timezone_get() : $_SESSION["dol_tz_string"]);  // Example 'Europe/Berlin'
2071
                try {
2072
                    $localtz = new \DateTimeZone($default_timezone);
2073
                } catch (Exception $e) {
2074
                    AlDolUtils::dol_syslog("Warning dol_tz_string contains an invalid value " . $_SESSION["dol_tz_string"], LOG_WARNING);
2075
                    $default_timezone = @date_default_timezone_get();
2076
                }
2077
            } else if (strrpos($gm, "tz,") !== false) {
2078
                $timezone = str_replace("tz,", "", $gm);  // Example 'tz,Europe/Berlin'
2079
                try {
2080
                    $localtz = new \DateTimeZone($timezone);
2081
                } catch (Exception $e) {
2082
                    AlDolUtils::dol_syslog("Warning passed timezone contains an invalid value " . $timezone, LOG_WARNING);
2083
                }
2084
            }
2085
2086
            if (empty($localtz)) {
2087
                $localtz = new \DateTimeZone('UTC');
2088
            }
2089
//var_dump($localtz);
2090
//var_dump($year.'-'.$month.'-'.$day.'-'.$hour.'-'.$minute);
2091
            $dt = new \DateTime(null, $localtz);
2092
            $dt->setDate($year, $month, $day);
2093
            $dt->setTime((int) $hour, (int) $minute, (int) $second);
2094
            $date = $dt->getTimestamp(); // should include daylight saving time
2095
//var_dump($date);
2096
            return $date;
2097
        } else {
2098
            dol_print_error('', 'PHP version must be 5.4+');
2099
            return '';
2100
        }
2101
    }
2102
2103
    /**
2104
     * 	Return date for now. In most cases, we use this static function without parameters (that means GMT time).
2105
     *
2106
     * 	@param	string		$mode	'gmt' => we return GMT timestamp,
2107
     * 								'tzserver' => we add the PHP server timezone
2108
     *  							'tzref' => we add the company timezone
2109
     * 								'tzuser' => we add the user timezone
2110
     * 	@return int   $date	Timestamp
2111
     */
2112
    static function dol_now($mode = 'gmt')
2113
    {
2114
        $ret = 0;
2115
2116
// Note that gmmktime and mktime return same value (GMT) when used without parameters
2117
//if ($mode == 'gmt') $ret=gmmktime(); // Strict Standards: gmmktime(): You should be using the time() static function instead
2118
        if ($mode == 'gmt')
2119
            $ret = time(); // Time for now at greenwich.
2120
        else if ($mode == 'tzserver') {  // Time for now with PHP server timezone added
2121
            require_once DOL_BASE_PATH . '/core/lib/date.lib.php';
2122
            $tzsecond = getServerTimeZoneInt('now');    // Contains tz+dayling saving time
2123
            $ret = (int) (dol_now('gmt') + ($tzsecond * 3600));
2124
        }
2125
        /* else if ($mode == 'tzref')				// Time for now with parent company timezone is added
2126
          {
2127
          require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
2128
          $tzsecond=getParentCompanyTimeZoneInt();    // Contains tz+dayling saving time
2129
          $ret=dol_now('gmt')+($tzsecond*3600);
2130
          } */ else if ($mode == 'tzuser') {    // Time for now with user timezone added
2131
//print 'time: '.time().'-'.mktime().'-'.gmmktime();
2132
            $offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60;
2133
            $offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60;
2134
            $ret = (int) (dol_now('gmt') + ($offsettz + $offsetdst));
2135
        }
2136
2137
        return $ret;
2138
    }
2139
2140
    /**
2141
     * Return string with formated size
2142
     *
2143
     * @param	int		$size		Size to print
2144
     * @param	int		$shortvalue	Tell if we want long value to use another unit (Ex: 1.5Kb instead of 1500b)
2145
     * @param	int		$shortunit	Use short label of size unit (for example 'b' instead of 'bytes')
2146
     * @return	string				Link
2147
     */
2148
    static function dol_print_size($size, $shortvalue = 0, $shortunit = 0)
2149
    {
2150
        // global Globals::$conf, Globals::$langs;
2151
        $level = 1024;
2152
2153
        if (!empty(Globals::$conf->dol_optimize_smallscreen))
2154
            $shortunit = 1;
2155
2156
// Set value text
2157
        if (empty($shortvalue) || $size < ($level * 10)) {
2158
            $ret = $size;
2159
            $textunitshort = Globals::$langs->trans("b");
2160
            $textunitlong = Globals::$langs->trans("Bytes");
2161
        } else {
2162
            $ret = round($size / $level, 0);
2163
            $textunitshort = Globals::$langs->trans("Kb");
2164
            $textunitlong = Globals::$langs->trans("KiloBytes");
2165
        }
2166
// Use long or short text unit
2167
        if (empty($shortunit)) {
2168
            $ret .= ' ' . $textunitlong;
2169
        } else {
2170
            $ret .= ' ' . $textunitshort;
2171
        }
2172
2173
        return $ret;
2174
    }
2175
2176
    /**
2177
     * Show Url link
2178
     *
2179
     * @param	string		$url		Url to show
2180
     * @param	string		$target		Target for link
2181
     * @param	int			$max		Max number of characters to show
2182
     * @param	int			$withpicto	With picto
2183
     * @return	string					HTML Link
2184
     */
2185
    static function dol_print_url($url, $target = '_blank', $max = 32, $withpicto = 0)
2186
    {
2187
        // global Globals::$langs;
2188
2189
        if (empty($url))
2190
            return '';
2191
2192
        $link = '<a href="';
2193
        if (!preg_match('/^http/i', $url))
2194
            $link .= 'http://';
2195
        $link .= $url;
2196
        $link .= '"';
2197
        if ($target)
2198
            $link .= ' target="' . $target . '"';
2199
        $link .= '>';
2200
        if (!preg_match('/^http/i', $url))
2201
            $link .= 'http://';
2202
        $link .= dol_trunc($url, $max);
2203
        $link .= '</a>';
2204
        return '<div class="nospan float" style="margin-right: 10px">' . ($withpicto ? img_picto(Globals::$langs->trans("Url"), 'object_globe.png') . ' ' : '') . $link . '</div>';
2205
    }
2206
2207
    /**
2208
     * Show EMail link
2209
     *
2210
     * @param	string		$email			EMail to show (only email, without 'Name of recipient' before)
2211
     * @param 	int			$cid 			Id of contact if known
2212
     * @param 	int			$socid 			Id of third party if known
2213
     * @param 	int			$addlink		0=no link, 1=email has a html email link (+ link to create action if constant AGENDA_ADDACTIONFOREMAIL is on)
2214
     * @param	int			$max			Max number of characters to show
2215
     * @param	int			$showinvalid	Show warning if syntax email is wrong
2216
     * @param	int			$withpicto		Show picto
2217
     * @return	string						HTML Link
2218
     */
2219
    static function dol_print_email($email, $cid = 0, $socid = 0, $addlink = 0, $max = 64, $showinvalid = 1, $withpicto = 0)
2220
    {
2221
        // global Globals::$conf, $user, Globals::$langs, Globals::$hookManager;
2222
2223
        $newemail = $email;
2224
2225
        if (empty($email))
2226
            return '&nbsp;';
2227
2228
        if (!empty($addlink)) {
2229
            $newemail = '<a style="text-overflow: ellipsis;" href="';
2230
            if (!preg_match('/^mailto:/i', $email))
2231
                $newemail .= 'mailto:';
2232
            $newemail .= $email;
2233
            $newemail .= '">';
2234
            $newemail .= dol_trunc($email, $max);
2235
            $newemail .= '</a>';
2236
            if ($showinvalid && !isValidEmail($email)) {
2237
                Globals::$langs->load("errors");
2238
                $newemail .= img_warning(Globals::$langs->trans("ErrorBadEMail", $email));
2239
            }
2240
2241
            if (($cid || $socid) && !empty(Globals::$conf->agenda->enabled) && $user->rights->agenda->myactions->create) {
2242
                $type = 'AC_EMAIL';
2243
                $link = '';
2244
                if (!empty(Globals::$conf->global->AGENDA_ADDACTIONFOREMAIL))
2245
                    $link = '<a href="' . DOL_BASE_URI . '/comm/action/card.php?action=create&amp;backtopage=1&amp;actioncode=' . $type . '&amp;contactid=' . $cid . '&amp;socid=' . $socid . '">' . img_object(Globals::$langs->trans("AddAction"), "calendar") . '</a>';
2246
                if ($link)
2247
                    $newemail = '<div>' . $newemail . ' ' . $link . '</div>';
2248
            }
2249
        }
2250
        else {
2251
            if ($showinvalid && !isValidEmail($email)) {
2252
                Globals::$langs->load("errors");
2253
                $newemail .= img_warning(Globals::$langs->trans("ErrorBadEMail", $email));
2254
            }
2255
        }
2256
2257
        $rep = '<div class="nospan float" style="margin-right: 10px">' . ($withpicto ? img_picto(Globals::$langs->trans("EMail"), 'object_email.png') . ' ' : '') . $newemail . '</div>';
2258
        if (Globals::$hookManager) {
2259
            $parameters = array('cid' => $cid, 'socid' => $socid, 'addlink' => $addlink, 'picto' => $withpicto);
2260
            $reshook = Globals::$hookManager->executeHooks('printEmail', $parameters, $email);
2261
            $rep .= Globals::$hookManager->resPrint;
2262
        }
2263
2264
        return $rep;
2265
    }
2266
2267
    /**
2268
     * Show social network link
2269
     *
2270
     * @param	string		$value			Skype to show (only skype, without 'Name of recipient' before)
2271
     * @param	int 		$cid 			Id of contact if known
2272
     * @param	int 		$socid 			Id of third party if known
2273
     * @param	string 		$type			'skype','facebook',...
2274
     * @return	string						HTML Link
2275
     */
2276
    static function dol_print_socialnetworks($value, $cid, $socid, $type)
2277
    {
2278
        // global Globals::$conf, $user, Globals::$langs;
2279
2280
        $newskype = $value;
2281
2282
        if (empty($value))
2283
            return '&nbsp;';
2284
2285
        if (!empty($type)) {
2286
            $newskype = '<div class="divsocialnetwork inline-block valignmiddle">';
2287
            $newskype .= img_picto(Globals::$langs->trans(strtoupper($type)), $type . '.png', '', false, 0, 0, '', 'paddingright');
2288
            $newskype .= $value;
2289
            if ($type == 'skype') {
2290
                $newskype .= '&nbsp;';
2291
                $newskype .= '<a href="skype:';
2292
                $newskype .= $value;
2293
                $newskype .= '?call" alt="' . Globals::$langs->trans("Call") . '&nbsp;' . $value . '" title="' . Globals::$langs->trans("Call") . '&nbsp;' . $value . '">';
2294
                $newskype .= '<img src="' . DOL_BASE_URI . '/theme/common/skype_callbutton.png" border="0">';
2295
                $newskype .= '</a><a href="skype:';
2296
                $newskype .= $value;
2297
                $newskype .= '?chat" alt="' . Globals::$langs->trans("Chat") . '&nbsp;' . $value . '" title="' . Globals::$langs->trans("Chat") . '&nbsp;' . $value . '">';
2298
                $newskype .= '<img class="paddingleft" src="' . DOL_BASE_URI . '/theme/common/skype_chatbutton.png" border="0">';
2299
                $newskype .= '</a>';
2300
            }
2301
            if (($cid || $socid) && !empty(Globals::$conf->agenda->enabled) && $user->rights->agenda->myactions->create && $type == 'skype') {
2302
                $addlink = 'AC_SKYPE';
2303
                $link = '';
2304
                if (!empty(Globals::$conf->global->AGENDA_ADDACTIONFORSKYPE))
2305
                    $link = '<a href="' . DOL_BASE_URI . '/comm/action/card.php?action=create&amp;backtopage=1&amp;actioncode=' . $addlink . '&amp;contactid=' . $cid . '&amp;socid=' . $socid . '">' . img_object(Globals::$langs->trans("AddAction"), "calendar") . '</a>';
2306
                $newskype .= ($link ? ' ' . $link : '');
2307
            }
2308
            $newskype .= '</div>';
2309
        }
2310
        else {
2311
            Globals::$langs->load("errors");
2312
            $newskype .= img_warning(Globals::$langs->trans("ErrorBadSocialNetworkValue", $value));
2313
        }
2314
        return $newskype;
2315
    }
2316
2317
    /**
2318
     * 	Format phone numbers according to country
2319
     *
2320
     * 	@param  string  $phone          Phone number to format
2321
     * 	@param  string  $countrycode    Country code to use for formatting
2322
     * 	@param 	int		$cid 		    Id of contact if known
2323
     * 	@param 	int		$socid          Id of third party if known
2324
     * 	@param 	string	$addlink	    ''=no link to create action, 'AC_TEL'=add link to clicktodial (if module enabled) and add link to create event (if conf->global->AGENDA_ADDACTIONFORPHONE set)
2325
     * 	@param 	string	$separ 		    Separation between numbers for a better visibility example : xx.xx.xx.xx.xx
2326
     *  @param	string  $withpicto      Show picto
2327
     *  @param	string	$titlealt	    Text to show on alt
2328
     *  @param  int     $adddivfloat    Add div float around phone.
2329
     * 	@return string 				    Formated phone number
2330
     */
2331
    static function dol_print_phone($phone, $countrycode = '', $cid = 0, $socid = 0, $addlink = '', $separ = "&nbsp;", $withpicto = '', $titlealt = '', $adddivfloat = 0)
2332
    {
2333
        // global Globals::$conf, $user, Globals::$langs, $mysoc, Globals::$hookManager;
2334
// Clean phone parameter
2335
        $phone = preg_replace("/[\s.-]/", "", trim($phone));
2336
        if (empty($phone)) {
2337
            return '';
2338
        }
2339
        if (empty($countrycode))
2340
            $countrycode = $mysoc->country_code;
2341
2342
// Short format for small screens
2343
        if (Globals::$conf->dol_optimize_smallscreen)
2344
            $separ = '';
2345
2346
        $newphone = $phone;
2347
        if (strtoupper($countrycode) == "FR") {
2348
// France
2349
            if (AlDolUtils::dol_strlen($phone) == 10) {
2350
                $newphone = substr($newphone, 0, 2) . $separ . substr($newphone, 2, 2) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2);
2351
            } elseif (AlDolUtils::dol_strlen($phone) == 7) {
2352
                $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 2);
2353
            } elseif (AlDolUtils::dol_strlen($phone) == 9) {
2354
                $newphone = substr($newphone, 0, 2) . $separ . substr($newphone, 2, 3) . $separ . substr($newphone, 5, 2) . $separ . substr($newphone, 7, 2);
2355
            } elseif (AlDolUtils::dol_strlen($phone) == 11) {
2356
                $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 2) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2);
2357
            } elseif (AlDolUtils::dol_strlen($phone) == 12) {
2358
                $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
2359
            }
2360
        } elseif (strtoupper($countrycode) == "CA") {
2361
            if (AlDolUtils::dol_strlen($phone) == 10) {
2362
                $newphone = ($separ != '' ? '(' : '') . substr($newphone, 0, 3) . ($separ != '' ? ')' : '') . $separ . substr($newphone, 3, 3) . ($separ != '' ? '-' : '') . substr($newphone, 6, 4);
2363
            }
2364
        } elseif (strtoupper($countrycode) == "PT") {//Portugal
2365
            if (AlDolUtils::dol_strlen($phone) == 13) {//ex: +351_ABC_DEF_GHI
2366
                $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
2367
            }
2368
        } elseif (strtoupper($countrycode) == "SR") {//Suriname
2369
            if (AlDolUtils::dol_strlen($phone) == 10) {//ex: +597_ABC_DEF
2370
                $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3);
2371
            } elseif (AlDolUtils::dol_strlen($phone) == 11) {//ex: +597_ABC_DEFG
2372
                $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 4);
2373
            }
2374
        } elseif (strtoupper($countrycode) == "DE") {//Allemagne
2375
            if (AlDolUtils::dol_strlen($phone) == 14) {//ex:  +49_ABCD_EFGH_IJK
2376
                $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4) . $separ . substr($newphone, 7, 4) . $separ . substr($newphone, 11, 3);
2377
            } elseif (AlDolUtils::dol_strlen($phone) == 13) {//ex: +49_ABC_DEFG_HIJ
2378
                $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 4) . $separ . substr($newphone, 10, 3);
2379
            }
2380
        } elseif (strtoupper($countrycode) == "ES") {//Espagne
2381
            if (AlDolUtils::dol_strlen($phone) == 12) {//ex:  +34_ABC_DEF_GHI
2382
                $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
2383
            }
2384
        } elseif (strtoupper($countrycode) == "BF") {// Burkina Faso
2385
            if (AlDolUtils::dol_strlen($phone) == 12) {//ex :  +22 A BC_DE_FG_HI
2386
                $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 1) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
2387
            }
2388
        } elseif (strtoupper($countrycode) == "RO") {// Roumanie
2389
            if (AlDolUtils::dol_strlen($phone) == 12) {//ex :  +40 AB_CDE_FG_HI
2390
                $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
2391
            }
2392
        } elseif (strtoupper($countrycode) == "TR") {//Turquie
2393
            if (AlDolUtils::dol_strlen($phone) == 13) {//ex :  +90 ABC_DEF_GHIJ
2394
                $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 4);
2395
            }
2396
        } elseif (strtoupper($countrycode) == "US") {//Etat-Unis
2397
            if (AlDolUtils::dol_strlen($phone) == 12) {//ex: +1 ABC_DEF_GHIJ
2398
                $newphone = substr($newphone, 0, 2) . $separ . substr($newphone, 2, 3) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 4);
2399
            }
2400
        } elseif (strtoupper($countrycode) == "MX") {//Mexique
2401
            if (AlDolUtils::dol_strlen($phone) == 12) {//ex: +52 ABCD_EFG_HI
2402
                $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 2);
2403
            } elseif (AlDolUtils::dol_strlen($phone) == 11) {//ex: +52 AB_CD_EF_GH
2404
                $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 2) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2);
2405
            } elseif (AlDolUtils::dol_strlen($phone) == 13) {//ex: +52 ABC_DEF_GHIJ
2406
                $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 4);
2407
            }
2408
        } elseif (strtoupper($countrycode) == "ML") {//Mali
2409
            if (AlDolUtils::dol_strlen($phone) == 12) {//ex: +223 AB_CD_EF_GH
2410
                $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
2411
            }
2412
        } elseif (strtoupper($countrycode) == "TH") {//Thaïlande
2413
            if (AlDolUtils::dol_strlen($phone) == 11) {//ex: +66_ABC_DE_FGH
2414
                $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 3);
2415
            } elseif (AlDolUtils::dol_strlen($phone) == 12) {//ex: +66_A_BCD_EF_GHI
2416
                $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 1) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 3);
2417
            }
2418
        } elseif (strtoupper($countrycode) == "MU") {//Maurice
2419
            if (AlDolUtils::dol_strlen($phone) == 11) {//ex: +230_ABC_DE_FG
2420
                $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2);
2421
            } elseif (AlDolUtils::dol_strlen($phone) == 12) {//ex: +230_ABCD_EF_GH
2422
                $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 4) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
2423
            }
2424
        } elseif (strtoupper($countrycode) == "ZA") {//Afrique du sud
2425
            if (AlDolUtils::dol_strlen($phone) == 12) {//ex: +27_AB_CDE_FG_HI
2426
                $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
2427
            }
2428
        } elseif (strtoupper($countrycode) == "SY") {//Syrie
2429
            if (AlDolUtils::dol_strlen($phone) == 12) {//ex: +963_AB_CD_EF_GH
2430
                $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
2431
            } elseif (AlDolUtils::dol_strlen($phone) == 13) {//ex: +963_AB_CD_EF_GHI
2432
                $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 3);
2433
            }
2434
        } elseif (strtoupper($countrycode) == "AE") {//Emirats Arabes Unis
2435
            if (AlDolUtils::dol_strlen($phone) == 12) {//ex: +971_ABC_DEF_GH
2436
                $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 2);
2437
            } elseif (AlDolUtils::dol_strlen($phone) == 13) {//ex: +971_ABC_DEF_GHI
2438
                $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
2439
            } elseif (AlDolUtils::dol_strlen($phone) == 14) {//ex: +971_ABC_DEF_GHIK
2440
                $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 4);
2441
            }
2442
        } elseif (strtoupper($countrycode) == "DZ") {//Algérie
2443
            if (AlDolUtils::dol_strlen($phone) == 13) {//ex: +213_ABC_DEF_GHI
2444
                $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
2445
            }
2446
        } elseif (strtoupper($countrycode) == "BE") {//Belgique
2447
            if (AlDolUtils::dol_strlen($phone) == 11) {//ex: +32_ABC_DE_FGH
2448
                $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 3);
2449
            } elseif (AlDolUtils::dol_strlen($phone) == 12) {//ex: +32_ABC_DEF_GHI
2450
                $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
2451
            }
2452
        } elseif (strtoupper($countrycode) == "PF") {//Polynésie française
2453
            if (AlDolUtils::dol_strlen($phone) == 12) {//ex: +689_AB_CD_EF_GH
2454
                $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
2455
            }
2456
        } elseif (strtoupper($countrycode) == "CO") {//Colombie
2457
            if (AlDolUtils::dol_strlen($phone) == 13) {//ex: +57_ABC_DEF_GH_IJ
2458
                $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 2) . $separ . substr($newphone, 11, 2);
2459
            }
2460
        } elseif (strtoupper($countrycode) == "JO") {//Jordanie
2461
            if (AlDolUtils::dol_strlen($phone) == 12) {//ex: +962_A_BCD_EF_GH
2462
                $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 1) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2);
2463
            }
2464
        } elseif (strtoupper($countrycode) == "MG") {//Madagascar
2465
            if (AlDolUtils::dol_strlen($phone) == 13) {//ex: +261_AB_CD_EF_GHI
2466
                $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 3);
2467
            }
2468
        } elseif (strtoupper($countrycode) == "GB") {//Royaume uni
2469
            if (AlDolUtils::dol_strlen($phone) == 13) {//ex: +44_ABCD_EFG_HIJ
2470
                $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
2471
            }
2472
        } elseif (strtoupper($countrycode) == "CH") {//Suisse
2473
            if (AlDolUtils::dol_strlen($phone) == 12) {//ex: +41_AB_CDE_FG_HI
2474
                $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
2475
            } elseif (AlDolUtils::dol_strlen($phone) == 15) {// +41_AB_CDE_FGH_IJKL
2476
                $newphone = $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 3) . $separ . substr($newphone, 11, 4);
2477
            }
2478
        } elseif (strtoupper($countrycode) == "TN") {//Tunisie
2479
            if (AlDolUtils::dol_strlen($phone) == 12) {//ex: +216_AB_CDE_FGH
2480
                $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
2481
            }
2482
        } elseif (strtoupper($countrycode) == "GF") {//Guyane francaise
2483
            if (AlDolUtils::dol_strlen($phone) == 13) {//ex: +594_ABC_DE_FG_HI  (ABC=594 de nouveau)
2484
                $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2) . $separ . substr($newphone, 11, 2);
2485
            }
2486
        } elseif (strtoupper($countrycode) == "GP") {//Guadeloupe
2487
            if (AlDolUtils::dol_strlen($phone) == 13) {//ex: +590_ABC_DE_FG_HI  (ABC=590 de nouveau)
2488
                $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2) . $separ . substr($newphone, 11, 2);
2489
            }
2490
        } elseif (strtoupper($countrycode) == "MQ") {//Martinique
2491
            if (AlDolUtils::dol_strlen($phone) == 13) {//ex: +596_ABC_DE_FG_HI  (ABC=596 de nouveau)
2492
                $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2) . $separ . substr($newphone, 11, 2);
2493
            }
2494
        } elseif (strtoupper($countrycode) == "IT") {//Italie
2495
            if (AlDolUtils::dol_strlen($phone) == 12) {//ex: +39_ABC_DEF_GHI
2496
                $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
2497
            } elseif (AlDolUtils::dol_strlen($phone) == 13) {//ex: +39_ABC_DEF_GH_IJ
2498
                $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 2) . $separ . substr($newphone, 11, 2);
2499
            }
2500
        } elseif (strtoupper($countrycode) == "AU") {//Australie
2501
            if (AlDolUtils::dol_strlen($phone) == 12) {//ex: +61_A_BCDE_FGHI
2502
                $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 1) . $separ . substr($newphone, 4, 4) . $separ . substr($newphone, 8, 4);
2503
            }
2504
        }
2505
        if (!empty($addlink)) { // Link on phone number (+ link to add action if conf->global->AGENDA_ADDACTIONFORPHONE set)
2506
            if (Globals::$conf->browser->layout == 'phone' || (!empty(Globals::$conf->clicktodial->enabled) && !empty(Globals::$conf->global->CLICKTODIAL_USE_TEL_LINK_ON_PHONE_NUMBERS))) { // If phone or option for, we use link of phone
2507
                $newphone = '<a href="tel:' . $phone . '"';
2508
                $newphone .= '>' . $phone . '</a>';
2509
            } else if (!empty(Globals::$conf->clicktodial->enabled) && $addlink == 'AC_TEL') {  // If click to dial, we use click to dial url
2510
                if (empty($user->clicktodial_loaded))
2511
                    $user->fetch_clicktodial();
2512
2513
// Define urlmask
2514
                $urlmask = 'ErrorClickToDialModuleNotConfigured';
2515
                if (!empty(Globals::$conf->global->CLICKTODIAL_URL))
2516
                    $urlmask = Globals::$conf->global->CLICKTODIAL_URL;
2517
                if (!empty($user->clicktodial_url))
2518
                    $urlmask = $user->clicktodial_url;
2519
2520
                $clicktodial_poste = (!empty($user->clicktodial_poste) ? urlencode($user->clicktodial_poste) : '');
2521
                $clicktodial_login = (!empty($user->clicktodial_login) ? urlencode($user->clicktodial_login) : '');
2522
                $clicktodial_password = (!empty($user->clicktodial_password) ? urlencode($user->clicktodial_password) : '');
2523
// This line is for backward compatibility
2524
                $url = sprintf($urlmask, urlencode($phone), $clicktodial_poste, $clicktodial_login, $clicktodial_password);
2525
// Thoose lines are for substitution
2526
                $substitarray = array('__PHONEFROM__' => $clicktodial_poste,
2527
                    '__PHONETO__' => urlencode($phone),
2528
                    '__LOGIN__' => $clicktodial_login,
2529
                    '__PASS__' => $clicktodial_password);
2530
                $url = make_substitutions($url, $substitarray);
2531
                $newphonesav = $newphone;
2532
                $newphone = '<a href="' . $url . '"';
2533
                if (!empty(Globals::$conf->global->CLICKTODIAL_FORCENEWTARGET))
2534
                    $newphone .= ' target="_blank"';
2535
                $newphone .= '>' . $newphonesav . '</a>';
2536
            }
2537
2538
//if (($cid || $socid) && ! empty(Globals::$conf->agenda->enabled) && $user->rights->agenda->myactions->create)
2539
            if (!empty(Globals::$conf->agenda->enabled) && $user->rights->agenda->myactions->create) {
2540
                $type = 'AC_TEL';
2541
                $link = '';
2542
                if ($addlink == 'AC_FAX')
2543
                    $type = 'AC_FAX';
2544
                if (!empty(Globals::$conf->global->AGENDA_ADDACTIONFORPHONE))
2545
                    $link = '<a href="' . DOL_BASE_URI . '/comm/action/card.php?action=create&amp;backtopage=1&amp;actioncode=' . $type . ($cid ? '&amp;contactid=' . $cid : '') . ($socid ? '&amp;socid=' . $socid : '') . '">' . img_object(Globals::$langs->trans("AddAction"), "calendar") . '</a>';
2546
                if ($link)
2547
                    $newphone = '<div>' . $newphone . ' ' . $link . '</div>';
2548
            }
2549
        }
2550
2551
        if (empty($titlealt)) {
2552
            $titlealt = ($withpicto == 'fax' ? Globals::$langs->trans("Fax") : Globals::$langs->trans("Phone"));
2553
        }
2554
        $rep = '';
2555
2556
        if (Globals::$hookManager) {
2557
            $parameters = array('countrycode' => $countrycode, 'cid' => $cid, 'socid' => $socid, 'titlealt' => $titlealt, 'picto' => $withpicto);
2558
            $reshook = Globals::$hookManager->executeHooks('printPhone', $parameters, $phone);
2559
            $rep .= Globals::$hookManager->resPrint;
2560
        }
2561
        if (empty($reshook)) {
2562
            $picto = '';
2563
            if ($withpicto) {
2564
                if ($withpicto == 'fax') {
2565
                    $picto = 'phoning_fax';
2566
                } elseif ($withpicto == 'phone') {
2567
                    $picto = 'phoning';
2568
                } elseif ($withpicto == 'mobile') {
2569
                    $picto = 'phoning_mobile';
2570
                } else {
2571
                    $picto = '';
2572
                }
2573
            }
2574
            if ($adddivfloat)
2575
                $rep .= '<div class="nospan float" style="margin-right: 10px">';
2576
            else
2577
                $rep .= '<span style="margin-right: 10px;">';
2578
            $rep .= ($withpicto ? img_picto($titlealt, 'object_' . $picto . '.png') . ' ' : '') . $newphone;
2579
            if ($adddivfloat)
2580
                $rep .= '</div>';
2581
            else
2582
                $rep .= '</span>';
2583
        }
2584
2585
        return $rep;
2586
    }
2587
2588
    /**
2589
     * 	Return an IP formated to be shown on screen
2590
     *
2591
     * 	@param	string	$ip			IP
2592
     * 	@param	int		$mode		0=return IP + country/flag, 1=return only country/flag, 2=return only IP
2593
     * 	@return string 				Formated IP, with country if GeoIP module is enabled
2594
     */
2595
    static function dol_print_ip($ip, $mode = 0)
2596
    {
2597
        // global Globals::$conf, Globals::$langs;
2598
2599
        $ret = '';
2600
2601
        if (empty($mode)) {
2602
            $ret .= $ip;
2603
        }
2604
2605
        echo ('<p>DOL_BASE_PATH: ' . DOL_BASE_PATH . '/DOL_BASE_URI: ' . DOL_BASE_URI . '</p>');
2606
2607
        if ($mode != 2) {
2608
            $countrycode = dolGetCountryCodeFromIp($ip);
2609
            if ($countrycode) { // If success, countrycode is us, fr, ...
2610
                if (file_exists(DOL_BASE_PATH . '/theme/common/flags/' . $countrycode . '.png')) {
2611
                    $ret .= ' ' . img_picto($countrycode . ' ' . Globals::$langs->trans("AccordingToGeoIPDatabase"), DOL_BASE_URI . '/theme/common/flags/' . $countrycode . '.png', '', 1);
2612
                } else
2613
                    $ret .= ' (' . $countrycode . ')';
2614
            }
2615
        }
2616
2617
        return $ret;
2618
    }
2619
2620
    /**
2621
     * Return the IP of remote user.
2622
     * Take HTTP_X_FORWARDED_FOR (defined when using proxy)
2623
     * Then HTTP_CLIENT_IP if defined (rare)
2624
     * Then REMOTE_ADDR (not way to be modified by user but may be wrong if using proxy)
2625
     *
2626
     * @return	string		Ip of remote user.
2627
     */
2628
    static function getUserRemoteIP()
2629
    {
2630
        $ip = empty($_SERVER['HTTP_X_FORWARDED_FOR']) ? (empty($_SERVER['HTTP_CLIENT_IP']) ? (empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR']) : $_SERVER['HTTP_CLIENT_IP']) : $_SERVER['HTTP_X_FORWARDED_FOR'];
2631
        return $ip;
2632
    }
2633
2634
    /**
2635
     * 	Return a country code from IP. Empty string if not found.
2636
     *
2637
     * 	@param	string	$ip			IP
2638
     * 	@return string 				Country code ('us', 'fr', ...)
2639
     */
2640
    static function dolGetCountryCodeFromIp($ip)
2641
    {
2642
        // global Globals::$conf;
2643
2644
        $countrycode = '';
2645
2646
        if (!empty(Globals::$conf->geoipmaxmind->enabled)) {
2647
            $datafile = Globals::$conf->global->GEOIPMAXMIND_COUNTRY_DATAFILE;
2648
//$ip='24.24.24.24';
2649
//$datafile='E:\Mes Sites\Web\Admin1\awstats\maxmind\GeoIP.dat';    Note that this must be downloaded datafile (not same than datafile provided with ubuntu packages)
2650
2651
            include_once DOL_BASE_PATH . '/core/class/dolgeoip.class.php';
2652
            $geoip = new DolGeoIP('country', $datafile);
2653
//print 'ip='.$ip.' databaseType='.$geoip->gi->databaseType." GEOIP_CITY_EDITION_REV1=".GEOIP_CITY_EDITION_REV1."\n";
2654
//print "geoip_country_id_by_addr=".geoip_country_id_by_addr($geoip->gi,$ip)."\n";
2655
            $countrycode = $geoip->getCountryCodeFromIP($ip);
2656
        }
2657
2658
        return $countrycode;
2659
    }
2660
2661
    /**
2662
     *  Return country code for current user.
2663
     *  If software is used inside a local network, detection may fails (we need a public ip)
2664
     *
2665
     *  @return     string      Country code (fr, es, it, us, ...)
2666
     */
2667
    static function dol_user_country()
2668
    {
2669
        // global Globals::$conf, Globals::$langs, $user;
2670
//$ret=$user->xxx;
2671
        $ret = '';
2672
        if (!empty(Globals::$conf->geoipmaxmind->enabled)) {
2673
            $ip = getUserRemoteIP();
2674
            $datafile = Globals::$conf->global->GEOIPMAXMIND_COUNTRY_DATAFILE;
2675
//$ip='24.24.24.24';
2676
//$datafile='E:\Mes Sites\Web\Admin1\awstats\maxmind\GeoIP.dat';
2677
            include_once DOL_BASE_PATH . '/core/class/dolgeoip.class.php';
2678
            $geoip = new DolGeoIP('country', $datafile);
2679
            $countrycode = $geoip->getCountryCodeFromIP($ip);
2680
            $ret = $countrycode;
2681
        }
2682
        return $ret;
2683
    }
2684
2685
    /**
2686
     *  Format address string
2687
     *
2688
     *  @param	string	$address    Address
2689
     *  @param  int		$htmlid     Html ID (for example 'gmap')
2690
     *  @param  int		$mode       thirdparty|contact|member|other
2691
     *  @param  int		$id         Id of object
2692
     *  @param	int		$noprint	No output. Result is the static function return
2693
     *  @param  string  $charfornl  Char to use instead of nl2br. '' means we use a standad nl2br.
2694
     *  @return string|void			Nothing if noprint is 0, formatted address if noprint is 1
2695
     *  @see dol_format_address
2696
     */
2697
    static function dol_print_address($address, $htmlid, $mode, $id, $noprint = 0, $charfornl = '')
2698
    {
2699
        // global Globals::$conf, $user, Globals::$langs, Globals::$hookManager;
2700
2701
        $out = '';
2702
2703
        if ($address) {
2704
            if (Globals::$hookManager) {
2705
                $parameters = array('element' => $mode, 'id' => $id);
2706
                $reshook = Globals::$hookManager->executeHooks('printAddress', $parameters, $address);
2707
                $out .= Globals::$hookManager->resPrint;
2708
            }
2709
            if (empty($reshook)) {
2710
                if (empty($charfornl))
2711
                    $out .= nl2br($address);
2712
                else
2713
                    $out .= preg_replace('/[\r\n]+/', $charfornl, $address);
2714
2715
                $showgmap = $showomap = 0;
2716
2717
// TODO Add a hook here
2718
                if (($mode == 'thirdparty' || $mode == 'societe') && !empty(Globals::$conf->google->enabled) && !empty(Globals::$conf->global->GOOGLE_ENABLE_GMAPS))
2719
                    $showgmap = 1;
2720
                if ($mode == 'contact' && !empty(Globals::$conf->google->enabled) && !empty(Globals::$conf->global->GOOGLE_ENABLE_GMAPS_CONTACTS))
2721
                    $showgmap = 1;
2722
                if ($mode == 'member' && !empty(Globals::$conf->google->enabled) && !empty(Globals::$conf->global->GOOGLE_ENABLE_GMAPS_MEMBERS))
2723
                    $showgmap = 1;
2724
                if (($mode == 'thirdparty' || $mode == 'societe') && !empty(Globals::$conf->openstreetmap->enabled) && !empty(Globals::$conf->global->OPENSTREETMAP_ENABLE_MAPS))
2725
                    $showomap = 1;
2726
                if ($mode == 'contact' && !empty(Globals::$conf->openstreetmap->enabled) && !empty(Globals::$conf->global->OPENSTREETMAP_ENABLE_MAPS_CONTACTS))
2727
                    $showomap = 1;
2728
                if ($mode == 'member' && !empty(Globals::$conf->openstreetmap->enabled) && !empty(Globals::$conf->global->OPENSTREETMAP_ENABLE_MAPS_MEMBERS))
2729
                    $showomap = 1;
2730
2731
                if ($showgmap) {
2732
                    $url = dol_buildpath('/google/gmaps.php?mode=' . $mode . '&id=' . $id, 1);
2733
                    $out .= ' <a href="' . $url . '" target="_gmaps"><img id="' . $htmlid . '" class="valigntextbottom" src="' . DOL_BASE_URI . '/theme/common/gmap.png"></a>';
2734
                }
2735
                if ($showomap) {
2736
                    $url = dol_buildpath('/openstreetmap/maps.php?mode=' . $mode . '&id=' . $id, 1);
2737
                    $out .= ' <a href="' . $url . '" target="_gmaps"><img id="' . $htmlid . '_openstreetmap" class="valigntextbottom" src="' . DOL_BASE_URI . '/theme/common/gmap.png"></a>';
2738
                }
2739
            }
2740
        }
2741
        if ($noprint)
2742
            return $out;
2743
        else
2744
            print $out;
2745
    }
2746
2747
    /**
2748
     * 	Return true if email syntax is ok
2749
     *
2750
     * 	@param	    string		$address    			email (Ex: "[email protected]", "John Do <[email protected]>")
2751
     *  @param		int			$acceptsupervisorkey	If 1, the special string '__SUPERVISOREMAIL__' is also accepted as valid
2752
     * 	@return     boolean     						true if email syntax is OK, false if KO or empty string
2753
     */
2754
    static function isValidEmail($address, $acceptsupervisorkey = 0)
2755
    {
2756
        if ($acceptsupervisorkey && $address == '__SUPERVISOREMAIL__')
2757
            return true;
2758
        if (filter_var($address, FILTER_VALIDATE_EMAIL))
2759
            return true;
2760
2761
        return false;
2762
    }
2763
2764
    /**
2765
     * 	Return if the domain name has a valid MX record.
2766
     *  WARNING: This need static function idn_to_ascii, checkdnsrr and getmxrr
2767
     *
2768
     * 	@param	    string		$domain	    			Domain name (Ex: "yahoo.com", "yhaoo.com", "dolibarr.fr")
2769
     * 	@return     int     							-1 if error (static function not available), 0=Not valid, 1=Valid
2770
     */
2771
    static function isValidMXRecord($domain)
2772
    {
2773
        if (function_exists('idn_to_ascii') && function_exists('checkdnsrr')) {
2774
            if (!checkdnsrr(idn_to_ascii($domain), 'MX')) {
2775
                return 0;
2776
            }
2777
            if (function_exists('getmxrr')) {
2778
                $mxhosts = array();
2779
                $weight = array();
2780
                getmxrr(idn_to_ascii($domain), $mxhosts, $weight);
2781
                if (count($mxhosts) > 1)
2782
                    return 1;
2783
                if (count($mxhosts) == 1 && !empty($mxhosts[0]))
2784
                    return 1;
2785
2786
                return 0;
2787
            }
2788
        }
2789
        return -1;
2790
    }
2791
2792
    /**
2793
     *  Return true if phone number syntax is ok
2794
     *  TODO Decide what to do with this
2795
     *
2796
     *  @param	string		$phone		phone (Ex: "0601010101")
2797
     *  @return boolean     			true if phone syntax is OK, false if KO or empty string
2798
     */
2799
    static function isValidPhone($phone)
2800
    {
2801
        return true;
2802
    }
2803
2804
    /**
2805
     * Make a strlen call. Works even if mbstring module not enabled
2806
     *
2807
     * @param   string		$string				String to calculate length
2808
     * @param   string		$stringencoding		Encoding of string
2809
     * @return  int								Length of string
2810
     */
2811
    static function dol_strlen($string, $stringencoding = 'UTF-8')
2812
    {
2813
        if (function_exists('mb_strlen'))
2814
            return mb_strlen($string, $stringencoding);
2815
        else
2816
            return strlen($string);
2817
    }
2818
2819
    /**
2820
     * Make a substring. Works even if mbstring module is not enabled for better compatibility.
2821
     *
2822
     * @param	string	$string				String to scan
2823
     * @param	string	$start				Start position
2824
     * @param	int		$length				Length (in nb of characters or nb of bytes depending on trunconbytes param)
2825
     * @param   string	$stringencoding		Page code used for input string encoding
2826
     * @param	int		$trunconbytes		1=Length is max of bytes instead of max of characters
2827
     * @return  string						substring
2828
     */
2829
    static function dol_substr($string, $start, $length, $stringencoding = '', $trunconbytes = 0)
2830
    {
2831
        // global Globals::$langs;
2832
2833
        if (empty($stringencoding))
2834
            $stringencoding = Globals::$langs->charset_output;
2835
2836
        $ret = '';
2837
        if (empty($trunconbytes)) {
2838
            if (function_exists('mb_substr')) {
2839
                $ret = mb_substr($string, $start, $length, $stringencoding);
2840
            } else {
2841
                $ret = substr($string, $start, $length);
2842
            }
2843
        } else {
2844
            if (function_exists('mb_strcut')) {
2845
                $ret = mb_strcut($string, $start, $length, $stringencoding);
2846
            } else {
2847
                $ret = substr($string, $start, $length);
2848
            }
2849
        }
2850
        return $ret;
2851
    }
2852
2853
    /**
2854
     * 	Truncate a string to a particular length adding '...' if string larger than length.
2855
     * 	If length = max length+1, we do no truncate to avoid having just 1 char replaced with '...'.
2856
     *  MAIN_DISABLE_TRUNC=1 can disable all truncings
2857
     *
2858
     * 	@param	string	$string				String to truncate
2859
     * 	@param  int		$size				Max string size visible (excluding ...). 0 for no limit. WARNING: Final string size can have 3 more chars (if we added ..., or if size was max+1 or max+2 or max+3 so it does not worse to replace with ...)
2860
     * 	@param	string	$trunc				Where to trunc: right, left, middle (size must be a 2 power), wrap
2861
     * 	@param	string	$stringencoding		Tell what is source string encoding
2862
     *  @param	int		$nodot				Truncation do not add ... after truncation. So it's an exact truncation.
2863
     *  @param  int     $display            Trunc is used to display data and can be changed for small screen. TODO Remove this param (must be dealt with CSS)
2864
     * 	@return string						Truncated string. WARNING: length is never higher than $size if $nodot is set, but can be 3 chars higher otherwise.
2865
     */
2866
    static function dol_trunc($string, $size = 40, $trunc = 'right', $stringencoding = 'UTF-8', $nodot = 0, $display = 0)
2867
    {
2868
        // global Globals::$conf;
2869
2870
        if ($size == 0 || !empty(Globals::$conf->global->MAIN_DISABLE_TRUNC))
2871
            return $string;
2872
2873
        if (empty($stringencoding))
2874
            $stringencoding = 'UTF-8';
2875
// reduce for small screen
2876
        if (Globals::$conf->dol_optimize_smallscreen == 1 && $display == 1)
2877
            $size = round($size / 3);
2878
2879
// We go always here
2880
        if ($trunc == 'right') {
2881
            $newstring = AlDolUtils::dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
2882
            if (AlDolUtils::dol_strlen($newstring, $stringencoding) > ($size + ($nodot ? 0 : 3)))    // If nodot is 0 and size is 1,2 or 3 chars more, we don't trunc and don't add ...
2883
                return dol_substr($newstring, 0, $size, $stringencoding) . ($nodot ? '' : '...');
2884
            else
2885
            //return 'u'.$size.'-'.$newstring.'-'.DolUtils::dol_strlen($newstring,$stringencoding).'-'.$string;
2886
                return $string;
2887
        }
2888
        elseif ($trunc == 'middle') {
2889
            $newstring = AlDolUtils::dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
2890
            if (AlDolUtils::dol_strlen($newstring, $stringencoding) > 2 && AlDolUtils::dol_strlen($newstring, $stringencoding) > ($size + 1)) {
2891
                $size1 = round($size / 2);
2892
                $size2 = round($size / 2);
2893
                return dol_substr($newstring, 0, $size1, $stringencoding) . '...' . dol_substr($newstring, AlDolUtils::dol_strlen($newstring, $stringencoding) - $size2, $size2, $stringencoding);
2894
            } else
2895
                return $string;
2896
        }
2897
        elseif ($trunc == 'left') {
2898
            $newstring = AlDolUtils::dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
2899
            if (AlDolUtils::dol_strlen($newstring, $stringencoding) > ($size + ($nodot ? 0 : 3)))    // If nodot is 0 and size is 1,2 or 3 chars more, we don't trunc and don't add ...
2900
                return '...' . dol_substr($newstring, AlDolUtils::dol_strlen($newstring, $stringencoding) - $size, $size, $stringencoding);
2901
            else
2902
                return $string;
2903
        }
2904
        elseif ($trunc == 'wrap') {
2905
            $newstring = AlDolUtils::dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
2906
            if (AlDolUtils::dol_strlen($newstring, $stringencoding) > ($size + 1))
2907
                return dol_substr($newstring, 0, $size, $stringencoding) . "\n" . dol_trunc(dol_substr($newstring, $size, AlDolUtils::dol_strlen($newstring, $stringencoding) - $size, $stringencoding), $size, $trunc);
2908
            else
2909
                return $string;
2910
        } else
2911
            return 'BadParam3CallingDolTrunc';
2912
    }
2913
2914
    /**
2915
     * 	Show picto whatever it's its name (generic function)
2916
     *
2917
     * 	@param      string		$titlealt         	Text on title tag for tooltip. Not used if param notitle is set to 1.
2918
     * 	@param      string		$picto       		Name of image file to show ('filenew', ...)
2919
     * 												If no extension provided, we use '.png'. Image must be stored into theme/xxx/img directory.
2920
     *                                  			Example: picto.png                  if picto.png is stored into htdocs/theme/mytheme/img
2921
     *                                  			Example: picto.png@mymodule         if picto.png is stored into htdocs/mymodule/img
2922
     *                                  			Example: /mydir/mysubdir/picto.png  if picto.png is stored into htdocs/mydir/mysubdir (pictoisfullpath must be set to 1)
2923
     * 	@param		string		$moreatt			Add more attribute on img tag (For example 'style="float: right"')
2924
     * 	@param		boolean|int	$pictoisfullpath	If true or 1, image path is a full path
2925
     * 	@param		int			$srconly			Return only content of the src attribute of img.
2926
     *  @param		int			$notitle			1=Disable tag title. Use it if you add js tooltip, to avoid duplicate tooltip.
2927
     *  @param		string		$alt				Force alt for bind people
2928
     *  @param		string		$morecss			Add more class css on img tag (For example 'myclascss'). Work only if $moreatt is empty.
2929
     *  @return     string       				    Return img tag
2930
     *  @see        #img_object, #img_picto_common
2931
     */
2932
    static function img_picto($titlealt, $picto, $moreatt = '', $pictoisfullpath = false, $srconly = 0, $notitle = 0, $alt = '', $morecss = '')
2933
    {
2934
        // global Globals::$conf, Globals::$langs;
2935
// We forge fullpathpicto for image to $path/img/$picto. By default, we take DOL_BASE_URI/theme/Globals::$conf->theme/img/$picto
2936
//$url = DOL_BASE_URI;
2937
        $url = DOL_BASE_URI;
2938
2939
        $theme = Globals::$conf->theme;
2940
        $path = 'theme/' . $theme;
2941
2942
// Define fullpathpicto to use into src
2943
        if ($pictoisfullpath) {
2944
// Clean parameters
2945
            if (!preg_match('/(\.png|\.gif|\.svg)$/i', $picto)) {
2946
                $picto .= '.png';
2947
            }
2948
            $fullpathpicto = $picto;
2949
        } else {
2950
            $pictowithoutext = preg_replace('/(\.png|\.gif|\.svg)$/', '', $picto);
2951
2952
//if (in_array($picto, array('switch_off', 'switch_on', 'off', 'on')))
2953
            if (empty($srconly) && in_array($pictowithoutext, array(
2954
                    'bank', 'close_title', 'delete', 'edit', 'ellipsis-h', 'filter', 'grip', 'grip_title', 'list', 'listlight', 'off', 'on', 'play', 'playdisabled', 'printer', 'resize',
2955
                    'note', 'switch_off', 'switch_on', 'unlink', 'uparrow', '1downarrow', '1uparrow',
2956
                    'jabber', 'skype', 'twitter', 'facebook'
2957
                    )
2958
                )) {
2959
                $fakey = $pictowithoutext;
2960
                $facolor = '';
2961
                $fasize = '';
2962
                $marginleftonlyshort = 2;
2963
                if ($pictowithoutext == 'switch_off') {
2964
                    $fakey = 'fa-toggle-off';
2965
                    $facolor = '#999';
2966
                    $fasize = '2em';
2967
                } elseif ($pictowithoutext == 'switch_on') {
2968
                    $fakey = 'fa-toggle-on';
2969
                    $facolor = '#227722';
2970
                    $fasize = '2em';
2971
                } elseif ($pictowithoutext == 'off') {
2972
                    $fakey = 'fa-square-o';
2973
                    $fasize = '1.3em';
2974
                } elseif ($pictowithoutext == 'on') {
2975
                    $fakey = 'fa-check-square-o';
2976
                    $fasize = '1.3em';
2977
                } elseif ($pictowithoutext == 'bank') {
2978
                    $fakey = 'fa-bank';
2979
                    $facolor = '#444';
2980
                } elseif ($pictowithoutext == 'close_title') {
2981
                    $fakey = 'fa-window-close';
2982
                } elseif ($pictowithoutext == 'delete') {
2983
                    $fakey = 'fa-trash';
2984
                    $facolor = '#444';
2985
                } elseif ($pictowithoutext == 'edit') {
2986
                    $fakey = 'fa-pencil';
2987
                    $facolor = '#444';
2988
                } elseif ($pictowithoutext == 'filter') {
2989
                    $fakey = 'fa-' . $pictowithoutext;
2990
                } elseif ($pictowithoutext == 'grip_title' || $pictowithoutext == 'grip') {
2991
                    $fakey = 'fa-arrows';
2992
                } elseif ($pictowithoutext == 'listlight') {
2993
                    $fakey = 'fa-download';
2994
                    $facolor = '#999';
2995
                    $marginleftonlyshort = 1;
2996
                } elseif ($pictowithoutext == 'printer') {
2997
                    $fakey = 'fa-print';
2998
                    $fasize = '1.2em';
2999
                    $facolor = '#444';
3000
                } elseif ($pictowithoutext == 'resize') {
3001
                    $fakey = 'fa-crop';
3002
                    $facolor = '#444';
3003
                } elseif ($pictowithoutext == 'note') {
3004
                    $fakey = 'fa-sticky-note-o';
3005
                    $facolor = '#999';
3006
                    $marginleftonlyshort = 1;
3007
                } elseif ($pictowithoutext == 'uparrow') {
3008
                    $fakey = 'fa-mail-forward';
3009
                    $facolor = '#555';
3010
                } elseif ($pictowithoutext == '1uparrow') {
3011
                    $fakey = 'fa-caret-up';
3012
                    $marginleftonlyshort = 1;
3013
                } elseif ($pictowithoutext == '1downarrow') {
3014
                    $fakey = 'fa-caret-down';
3015
                    $marginleftonlyshort = 1;
3016
                } elseif ($pictowithoutext == 'unlink') {
3017
                    $fakey = 'fa-chain-broken';
3018
                    $facolor = '#555';
3019
                } elseif ($pictowithoutext == 'playdisabled') {
3020
                    $fakey = 'fa-play';
3021
                    $facolor = '#ccc';
3022
                } elseif ($pictowithoutext == 'play') {
3023
                    $fakey = 'fa-play';
3024
                    $facolor = '#444';
3025
                } elseif ($pictowithoutext == 'jabber') {
3026
                    $fakey = 'fa-comment-o';
3027
                } else {
3028
                    $fakey = 'fa-' . $pictowithoutext;
3029
                    $facolor = '#444';
3030
                    $marginleftonlyshort = 0;
3031
                }
3032
3033
                if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
3034
                    $morecss .= ($morecss ? ' ' : '') . $reg[1];
3035
                }
3036
                $enabledisablehtml = '<span class="fa ' . $fakey . ' ' . ($marginleftonlyshort ? ($marginleftonlyshort == 1 ? 'marginleftonlyshort' : 'marginleftonly') : '') . ' valignmiddle' . ($morecss ? ' ' . $morecss : '') . '" style="' . ($fasize ? ('font-size: ' . $fasize . ';') : '') . ($facolor ? (' color: ' . $facolor . ';') : '') . '" alt="' . AlDolUtils::dol_escape_htmltag($titlealt) . '"' . (($notitle || empty($title)) ? '' : ' title="' . AlDolUtils::dol_escape_htmltag($title) . '"') . ($moreatt ? ' ' . $moreatt : '') . '>';
3037
                if (!empty(Globals::$conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
3038
                    $enabledisablehtml .= $titlealt;
3039
                }
3040
                $enabledisablehtml .= '</span>';
3041
3042
                return $enabledisablehtml;
3043
            }
3044
3045
            if (!empty(Globals::$conf->global->MAIN_OVERWRITE_THEME_PATH)) {
3046
                $path = Globals::$conf->global->MAIN_OVERWRITE_THEME_PATH . '/theme/' . $theme; // If the theme does not have the same name as the module
3047
            } else if (!empty(Globals::$conf->global->MAIN_OVERWRITE_THEME_RES)) {
3048
                $path = Globals::$conf->global->MAIN_OVERWRITE_THEME_RES . '/theme/' . Globals::$conf->global->MAIN_OVERWRITE_THEME_RES;  // To allow an external module to overwrite image resources whatever is activated theme
3049
            } else if (!empty(Globals::$conf->modules_parts['theme']) && array_key_exists($theme, Globals::$conf->modules_parts['theme'])) {
3050
                $path = $theme . '/theme/' . $theme; // If the theme have the same name as the module
3051
            }
3052
3053
// If we ask an image into $url/$mymodule/img (instead of default path)
3054
            if (preg_match('/^([^@]+)@([^@]+)$/i', $picto, $regs)) {
3055
                $picto = $regs[1];
3056
                $path = $regs[2]; // $path is $mymodule
3057
            }
3058
3059
// Clean parameters
3060
            if (!preg_match('/(\.png|\.gif|\.svg)$/i', $picto)) {
3061
                $picto .= '.png';
3062
            }
3063
// If alt path are defined, define url where img file is, according to physical path
3064
// ex: array(["main"]=>"/home/maindir/htdocs", ["alt0"]=>"/home/moddir0/htdocs", ...)
3065
            foreach (Globals::$conf->file->dol_document_root as $type => $dirroot) {
3066
                if ($type == 'main') {
3067
                    continue;
3068
                }
3069
// This need a lot of time, that's why enabling alternative dir like "custom" dir is not recommanded
3070
                if (file_exists($dirroot . '/' . $path . '/img/' . $picto)) {
3071
                    //$url = DOL_BASE_URI . Globals::$conf->file->dol_url_root[$type];
3072
                    $url = DOL_BASE_URI . Globals::$conf->file->dol_url_root[$type];
3073
                    break;
3074
                }
3075
            }
3076
3077
// $url is '' or '/custom', $path is current theme or
3078
            $fullpathpicto = $url . '/' . $path . '/img/' . $picto;
3079
        }
3080
3081
        if ($srconly) {
3082
            return $fullpathpicto;
3083
        }
3084
3085
// tag title is used for tooltip on <a>, tag alt can be used with very simple text on image for bind people
3086
        return '<img src="' . $fullpathpicto . '" alt="' . AlDolUtils::dol_escape_htmltag($alt) . '"' . (($notitle || empty($titlealt)) ? '' : ' title="' . AlDolUtils::dol_escape_htmltag($titlealt) . '"') . ($moreatt ? ' ' . $moreatt : ' class="inline-block' . ($morecss ? ' ' . $morecss : '') . '"') . '>'; // Alt is used for accessibility, title for popup
3087
    }
3088
3089
    /**
3090
     * 	Show a picto called object_picto (generic function)
3091
     *
3092
     * 	@param	string	$titlealt			Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3093
     * 	@param	string	$picto				Name of image to show object_picto (example: user, group, action, bill, contract, propal, product, ...)
3094
     * 										For external modules use imagename@mymodule to search into directory "img" of module.
3095
     * 	@param	string	$moreatt			Add more attribute on img tag (ie: class="datecallink")
3096
     * 	@param	int		$pictoisfullpath	If 1, image path is a full path
3097
     * 	@param	int		$srconly			Return only content of the src attribute of img.
3098
     *  @param	int		$notitle			1=Disable tag title. Use it if you add js tooltip, to avoid duplicate tooltip.
3099
     * 	@return	string						Return img tag
3100
     * 	@see	#img_picto, #img_picto_common
3101
     */
3102
    static function img_object($titlealt, $picto, $moreatt = '', $pictoisfullpath = false, $srconly = 0, $notitle = 0)
3103
    {
3104
        return img_picto($titlealt, 'object_' . $picto, $moreatt, $pictoisfullpath, $srconly, $notitle);
3105
    }
3106
3107
    /**
3108
     * 	Show weather picto
3109
     *
3110
     * 	@param      string		$titlealt         	Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3111
     * 	@param      string		$picto       		Name of image file to show (If no extension provided, we use '.png'). Image must be stored into htdocs/theme/common directory.
3112
     * 	@param		string		$moreatt			Add more attribute on img tag
3113
     * 	@param		int			$pictoisfullpath	If 1, image path is a full path
3114
     * 	@return     string      					Return img tag
3115
     *  @see        #img_object, #img_picto
3116
     */
3117
    static function img_weather($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0)
3118
    {
3119
        // global Globals::$conf;
3120
3121
        if (!preg_match('/(\.png|\.gif)$/i', $picto))
3122
            $picto .= '.png';
3123
3124
//$path = DOL_BASE_URI . '/theme/' . Globals::$conf->theme . '/img/weather/' . $picto;
3125
        $path = DOL_BASE_URI . '/theme/' . Globals::$conf->theme . '/img/weather/' . $picto;
3126
3127
        return img_picto($titlealt, $path, $moreatt, 1);
3128
    }
3129
3130
    /**
3131
     * 	Show picto (generic function)
3132
     *
3133
     * 	@param      string		$titlealt         	Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3134
     * 	@param      string		$picto       		Name of image file to show (If no extension provided, we use '.png'). Image must be stored into htdocs/theme/common directory.
3135
     * 	@param		string		$moreatt			Add more attribute on img tag
3136
     * 	@param		int			$pictoisfullpath	If 1, image path is a full path
3137
     * 	@return     string      					Return img tag
3138
     *  @see        #img_object, #img_picto
3139
     */
3140
    static function img_picto_common($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0)
3141
    {
3142
        // global Globals::$conf;
3143
3144
        if (!preg_match('/(\.png|\.gif)$/i', $picto)) {
3145
            $picto .= '.png';
3146
        }
3147
3148
        if ($pictoisfullpath) {
3149
            $path = $picto;
3150
        } else {
3151
            //$path = DOL_BASE_URI . '/theme/common/' . $picto;
3152
            $path = DOL_BASE_URI . '/theme/common/' . $picto;
3153
3154
            if (!empty(Globals::$conf->global->MAIN_MODULE_CAN_OVERWRITE_COMMONICONS)) {
3155
                $themepath = DOL_BASE_PATH . 'theme/' . Globals::$conf->theme . '/img/' . $picto;
3156
3157
                if (file_exists($themepath)) {
3158
                    $path = $themepath;
3159
                }
3160
            }
3161
        }
3162
3163
        return self::img_picto($titlealt, $path, $moreatt, 1);
3164
    }
3165
3166
    /**
3167
     * 	Show logo action
3168
     *
3169
     * 	@param	string		$titlealt       Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3170
     * 	@param  string		$numaction   	Action id or code to show
3171
     * 	@return string      				Return an img tag
3172
     */
3173
    static function img_action($titlealt, $numaction)
3174
    {
3175
        // global Globals::$conf, Globals::$langs;
3176
3177
        if (empty($titlealt) || $titlealt == 'default') {
3178
            if ($numaction == '-1' || $numaction == 'ST_NO') {
3179
                $numaction = -1;
3180
                $titlealt = Globals::$langs->transnoentitiesnoconv('ChangeDoNotContact');
3181
            } elseif ($numaction == '0' || $numaction == 'ST_NEVER') {
3182
                $numaction = 0;
3183
                $titlealt = Globals::$langs->transnoentitiesnoconv('ChangeNeverContacted');
3184
            } elseif ($numaction == '1' || $numaction == 'ST_TODO') {
3185
                $numaction = 1;
3186
                $titlealt = Globals::$langs->transnoentitiesnoconv('ChangeToContact');
3187
            } elseif ($numaction == '2' || $numaction == 'ST_PEND') {
3188
                $numaction = 2;
3189
                $titlealt = Globals::$langs->transnoentitiesnoconv('ChangeContactInProcess');
3190
            } elseif ($numaction == '3' || $numaction == 'ST_DONE') {
3191
                $numaction = 3;
3192
                $titlealt = Globals::$langs->transnoentitiesnoconv('ChangeContactDone');
3193
            } else {
3194
                $titlealt = Globals::$langs->transnoentitiesnoconv('ChangeStatus ' . $numaction);
3195
                $numaction = 0;
3196
            }
3197
        }
3198
        if (!is_numeric($numaction))
3199
            $numaction = 0;
3200
3201
        return img_picto($titlealt, 'stcomm' . $numaction . '.png');
3202
    }
3203
3204
    /**
3205
     *  Show pdf logo
3206
     *
3207
     *  @param	string		$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3208
     *  @param  int		    $size       Taille de l'icone : 3 = 16x16px , 2 = 14x14px
3209
     *  @return string      			Retourne tag img
3210
     */
3211
    static function img_pdf($titlealt = 'default', $size = 3)
3212
    {
3213
        // global Globals::$conf, Globals::$langs;
3214
3215
        if ($titlealt == 'default')
3216
            $titlealt = Globals::$langs->trans('Show');
3217
3218
        return img_picto($titlealt, 'pdf' . $size . '.png');
3219
    }
3220
3221
    /**
3222
     * 	Show logo +
3223
     *
3224
     * 	@param	string	$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3225
     * 	@param  string	$other      Add more attributes on img
3226
     * 	@return string      		Return tag img
3227
     */
3228
    static function img_edit_add($titlealt = 'default', $other = '')
3229
    {
3230
        // global Globals::$conf, Globals::$langs;
3231
3232
        if ($titlealt == 'default')
3233
            $titlealt = Globals::$langs->trans('Add');
3234
3235
        return img_picto($titlealt, 'edit_add.png', $other);
3236
    }
3237
3238
    /**
3239
     * 	Show logo -
3240
     *
3241
     * 	@param	string	$titlealt	Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3242
     * 	@param  string	$other      Add more attributes on img
3243
     * 	@return string      		Return tag img
3244
     */
3245
    static function img_edit_remove($titlealt = 'default', $other = '')
3246
    {
3247
        // global Globals::$conf, Globals::$langs;
3248
3249
        if ($titlealt == 'default')
3250
            $titlealt = Globals::$langs->trans('Remove');
3251
3252
        return img_picto($titlealt, 'edit_remove.png', $other);
3253
    }
3254
3255
    /**
3256
     * 	Show logo editer/modifier fiche
3257
     *
3258
     * 	@param  string	$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3259
     * 	@param  integer	$float      Si il faut y mettre le style "float: right"
3260
     * 	@param  string	$other		Add more attributes on img
3261
     * 	@return string      		Return tag img
3262
     */
3263
    static function img_edit($titlealt = 'default', $float = 0, $other = 'class="pictoedit"')
3264
    {
3265
        // global Globals::$conf, Globals::$langs;
3266
3267
        if ($titlealt == 'default')
3268
            $titlealt = Globals::$langs->trans('Modify');
3269
3270
        return img_picto($titlealt, 'edit.png', ($float ? 'style="float: ' . (Globals::$langs->tab_translate["DIRECTION"] == 'rtl' ? 'left' : 'right') . '"' : "") . ($other ? ' ' . $other : ''));
3271
    }
3272
3273
    /**
3274
     * 	Show logo view card
3275
     *
3276
     * 	@param	string	$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3277
     * 	@param  integer	$float      Si il faut y mettre le style "float: right"
3278
     * 	@param  string	$other		Add more attributes on img
3279
     * 	@return string      		Return tag img
3280
     */
3281
    static function img_view($titlealt = 'default', $float = 0, $other = '')
3282
    {
3283
        // global Globals::$conf, Globals::$langs;
3284
3285
        if ($titlealt == 'default')
3286
            $titlealt = Globals::$langs->trans('View');
3287
3288
        $moreatt = ($float ? 'style="float: right" ' : '') . $other;
3289
3290
        return img_picto($titlealt, 'view.png', $moreatt);
3291
    }
3292
3293
    /**
3294
     *  Show delete logo
3295
     *
3296
     *  @param	string	$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3297
     * 	@param  string	$other      Add more attributes on img
3298
     *  @return string      		Retourne tag img
3299
     */
3300
    static function img_delete($titlealt = 'default', $other = 'class="pictodelete"')
3301
    {
3302
        // global Globals::$conf, Globals::$langs;
3303
3304
        if ($titlealt == 'default')
3305
            $titlealt = Globals::$langs->trans('Delete');
3306
3307
        return img_picto($titlealt, 'delete.png', $other);
3308
//return '<span class="fa fa-trash fa-2x fa-fw" style="font-size: 1.7em;" title="'.$titlealt.'"></span>';
3309
    }
3310
3311
    /**
3312
     *  Show printer logo
3313
     *
3314
     *  @param  string  $titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3315
     *  @param  string  $other      Add more attributes on img
3316
     *  @return string              Retourne tag img
3317
     */
3318
    static function img_printer($titlealt = "default", $other = '')
3319
    {
3320
        // global Globals::$conf, Globals::$langs;
3321
        if ($titlealt == "default")
3322
            $titlealt = Globals::$langs->trans("Print");
3323
        return img_picto($titlealt, 'printer.png', $other);
3324
    }
3325
3326
    /**
3327
     *  Show split logo
3328
     *
3329
     *  @param	string	$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3330
     * 	@param  string	$other      Add more attributes on img
3331
     *  @return string      		Retourne tag img
3332
     */
3333
    static function img_split($titlealt = 'default', $other = 'class="pictosplit"')
3334
    {
3335
        // global Globals::$conf, Globals::$langs;
3336
3337
        if ($titlealt == 'default')
3338
            $titlealt = Globals::$langs->trans('Split');
3339
3340
        return img_picto($titlealt, 'split.png', $other);
3341
    }
3342
3343
    /**
3344
     * 	Show help logo with cursor "?"
3345
     *
3346
     * 	@param	int              	$usehelpcursor		1=Use help cursor, 2=Use click pointer cursor, 0=No specific cursor
3347
     * 	@param	int|string	        $usealttitle		Text to use as alt title
3348
     * 	@return string            	           			Return tag img
3349
     */
3350
    static function img_help($usehelpcursor = 1, $usealttitle = 1)
3351
    {
3352
        // global Globals::$conf, Globals::$langs;
3353
3354
        if ($usealttitle) {
3355
            if (is_string($usealttitle))
3356
                $usealttitle = AlDolUtils::dol_escape_htmltag($usealttitle);
3357
            else
3358
                $usealttitle = Globals::$langs->trans('Info');
3359
        }
3360
3361
        return img_picto($usealttitle, 'info.png', 'style="vertical-align: middle;' . ($usehelpcursor == 1 ? ' cursor: help' : ($usehelpcursor == 2 ? ' cursor: pointer' : '')) . '"');
3362
    }
3363
3364
    /**
3365
     * 	Show info logo
3366
     *
3367
     * 	@param	string	$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3368
     * 	@return string      		Return img tag
3369
     */
3370
    static function img_info($titlealt = 'default')
3371
    {
3372
        // global Globals::$conf, Globals::$langs;
3373
3374
        if ($titlealt == 'default')
3375
            $titlealt = Globals::$langs->trans('Informations');
3376
3377
        return img_picto($titlealt, 'info.png', 'style="vertical-align: middle;"');
3378
    }
3379
3380
    /**
3381
     * 	Show warning logo
3382
     *
3383
     * 	@param	string	$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3384
     * 	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"'). If 1, add float: right. Can't be "class" attribute.
3385
     * 	@return string      		Return img tag
3386
     */
3387
    static function img_warning($titlealt = 'default', $moreatt = '')
3388
    {
3389
        // global Globals::$conf, Globals::$langs;
3390
3391
        if ($titlealt == 'default')
3392
            $titlealt = Globals::$langs->trans('Warning');
3393
3394
//return '<div class="imglatecoin">'.img_picto($titlealt, 'warning_white.png', 'class="pictowarning valignmiddle"'.($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' '.$moreatt): '')).'</div>';
3395
        return img_picto($titlealt, 'warning.png', 'class="pictowarning valignmiddle"' . ($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' ' . $moreatt) : ''));
3396
    }
3397
3398
    /**
3399
     *  Show error logo
3400
     *
3401
     * 	@param	string	$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3402
     * 	@return string      		Return img tag
3403
     */
3404
    static function img_error($titlealt = 'default')
3405
    {
3406
        // global Globals::$conf, Globals::$langs;
3407
3408
        if ($titlealt == 'default')
3409
            $titlealt = Globals::$langs->trans('Error');
3410
3411
        return img_picto($titlealt, 'error.png', 'class="valigntextbottom"');
3412
    }
3413
3414
    /**
3415
     * 	Show next logo
3416
     *
3417
     * 	@param	string	$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3418
     * 	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
3419
     * 	@return string      		Return img tag
3420
     */
3421
    static function img_next($titlealt = 'default', $moreatt = '')
3422
    {
3423
        // global Globals::$conf, Globals::$langs;
3424
3425
        if ($titlealt == 'default')
3426
            $titlealt = Globals::$langs->trans('Next');
3427
3428
//return img_picto($titlealt, 'next.png', $moreatt);
3429
        return '<span class="fa fa-chevron-right paddingright paddingleft" title="' . AlDolUtils::dol_escape_htmltag($titlealt) . '"></span>';
3430
    }
3431
3432
    /**
3433
     * 	Show previous logo
3434
     *
3435
     * 	@param	string	$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3436
     * 	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
3437
     * 	@return string      		Return img tag
3438
     */
3439
    static function img_previous($titlealt = 'default', $moreatt = '')
3440
    {
3441
        // global Globals::$conf, Globals::$langs;
3442
3443
        if ($titlealt == 'default')
3444
            $titlealt = Globals::$langs->trans('Previous');
3445
3446
//return img_picto($titlealt, 'previous.png', $moreatt);
3447
        return '<span class="fa fa-chevron-left paddingright paddingleft" title="' . AlDolUtils::dol_escape_htmltag($titlealt) . '"></span>';
3448
    }
3449
3450
    /**
3451
     * 	Show down arrow logo
3452
     *
3453
     * 	@param	string	$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3454
     * 	@param  int		$selected   Selected
3455
     *  @param	string	$moreclass	Add more CSS classes
3456
     * 	@return string      		Return img tag
3457
     */
3458
    static function img_down($titlealt = 'default', $selected = 0, $moreclass = '')
3459
    {
3460
        // global Globals::$conf, Globals::$langs;
3461
3462
        if ($titlealt == 'default')
3463
            $titlealt = Globals::$langs->trans('Down');
3464
3465
        return img_picto($titlealt, ($selected ? '1downarrow_selected.png' : '1downarrow.png'), 'class="imgdown' . ($moreclass ? " " . $moreclass : "") . '"');
3466
    }
3467
3468
    /**
3469
     * 	Show top arrow logo
3470
     *
3471
     * 	@param	string	$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3472
     * 	@param  int		$selected	Selected
3473
     *  @param	string	$moreclass	Add more CSS classes
3474
     * 	@return string      		Return img tag
3475
     */
3476
    static function img_up($titlealt = 'default', $selected = 0, $moreclass = '')
3477
    {
3478
        // global Globals::$conf, Globals::$langs;
3479
3480
        if ($titlealt == 'default')
3481
            $titlealt = Globals::$langs->trans('Up');
3482
3483
        return img_picto($titlealt, ($selected ? '1uparrow_selected.png' : '1uparrow.png'), 'class="imgup' . ($moreclass ? " " . $moreclass : "") . '"');
3484
    }
3485
3486
    /**
3487
     * 	Show left arrow logo
3488
     *
3489
     * 	@param	string	$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3490
     * 	@param  int		$selected	Selected
3491
     * 	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
3492
     * 	@return string      		Return img tag
3493
     */
3494
    static function img_left($titlealt = 'default', $selected = 0, $moreatt = '')
3495
    {
3496
        // global Globals::$conf, Globals::$langs;
3497
3498
        if ($titlealt == 'default')
3499
            $titlealt = Globals::$langs->trans('Left');
3500
3501
        return img_picto($titlealt, ($selected ? '1leftarrow_selected.png' : '1leftarrow.png'), $moreatt);
3502
    }
3503
3504
    /**
3505
     * 	Show right arrow logo
3506
     *
3507
     * 	@param	string	$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3508
     * 	@param  int		$selected	Selected
3509
     * 	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
3510
     * 	@return string      		Return img tag
3511
     */
3512
    static function img_right($titlealt = 'default', $selected = 0, $moreatt = '')
3513
    {
3514
        // global Globals::$conf, Globals::$langs;
3515
3516
        if ($titlealt == 'default')
3517
            $titlealt = Globals::$langs->trans('Right');
3518
3519
        return img_picto($titlealt, ($selected ? '1rightarrow_selected.png' : '1rightarrow.png'), $moreatt);
3520
    }
3521
3522
    /**
3523
     * 	Show tick logo if allowed
3524
     *
3525
     * 	@param	string	$allow		Allow
3526
     * 	@param	string	$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3527
     * 	@return string      		Return img tag
3528
     */
3529
    static function img_allow($allow, $titlealt = 'default')
3530
    {
3531
        // global Globals::$conf, Globals::$langs;
3532
3533
        if ($titlealt == 'default')
3534
            $titlealt = Globals::$langs->trans('Active');
3535
3536
        if ($allow == 1)
3537
            return img_picto($titlealt, 'tick.png');
3538
3539
        return '-';
3540
    }
3541
3542
    /**
3543
     * 	Return image of a credit card according to its brand name
3544
     *
3545
     * 	@param	string	$brand		Brand name of credit card
3546
     * 	@return string     			Return img tag
3547
     */
3548
    static function img_credit_card($brand)
3549
    {
3550
        if ($brand == 'Visa') {
3551
            $brand = 'cc-visa';
3552
        } elseif ($brand == 'MasterCard') {
3553
            $brand = 'cc-mastercard';
3554
        } elseif ($brand == 'American Express') {
3555
            $brand = 'cc-amex';
3556
        } elseif ($brand == 'Discover') {
3557
            $brand = 'cc-discover';
3558
        } elseif ($brand == 'JCB') {
3559
            $brand = 'cc-jcb';
3560
        } elseif ($brand == 'Diners Club') {
3561
            $brand = 'cc-diners-club';
3562
        } elseif (!in_array($brand, array('cc-visa', 'cc-mastercard', 'cc-amex', 'cc-discover', 'cc-jcb', 'cc-diners-club'))) {
3563
            $brand = 'credit-card';
3564
        }
3565
3566
        return '<span class="fa fa-' . $brand . ' fa-2x fa-fw"></span>';
3567
    }
3568
3569
    /**
3570
     * 	Show MIME img of a file
3571
     *
3572
     * 	@param	string	$file		Filename
3573
     * 	@param	string	$titlealt	Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3574
     *  @param	string	$morecss	More css
3575
     * 	@return string     			Return img tag
3576
     */
3577
    static function img_mime($file, $titlealt = '', $morecss = '')
3578
    {
3579
        require_once DOL_BASE_PATH . '/core/lib/files.lib.php';
3580
3581
        $mimetype = dol_mimetype($file, '', 1);
3582
        $mimeimg = dol_mimetype($file, '', 2);
3583
        $mimefa = dol_mimetype($file, '', 4);
3584
3585
        if (empty($titlealt))
3586
            $titlealt = 'Mime type: ' . $mimetype;
3587
3588
//return img_picto_common($titlealt, 'mime/'.$mimeimg, 'class="'.$morecss.'"');
3589
        return '<i class="fa fa-' . $mimefa . ' paddingright"></i>';
3590
    }
3591
3592
    /**
3593
     * 	Show phone logo.
3594
     *  Use img_picto instead.
3595
     *
3596
     * 	@param	string	$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3597
     * 	@param  int		$option		Option
3598
     * 	@return string      		Return img tag
3599
     *  @deprecated
3600
     *  @see img_picto
3601
     */
3602
    static function img_phone($titlealt = 'default', $option = 0)
3603
    {
3604
        AlDolUtils::dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);
3605
3606
        // global Globals::$conf, Globals::$langs;
3607
3608
        if ($titlealt == 'default')
3609
            $titlealt = Globals::$langs->trans('Call');
3610
3611
        if ($option == 1)
3612
            $img = 'call';
3613
        else
3614
            $img = 'call_out';
3615
3616
        return img_picto($titlealt, $img);
3617
    }
3618
3619
    /**
3620
     *  Show search logo
3621
     *
3622
     *  @param	string	$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3623
     * 	@param  string	$other      Add more attributes on img
3624
     *  @return string      		Retourne tag img
3625
     */
3626
    static function img_search($titlealt = 'default', $other = '')
3627
    {
3628
        // global Globals::$conf, Globals::$langs;
3629
3630
        if ($titlealt == 'default')
3631
            $titlealt = Globals::$langs->trans('Search');
3632
3633
        $img = img_picto($titlealt, 'search.png', $other, false, 1);
3634
3635
        $input = '<input type="image" class="liste_titre" name="button_search" src="' . $img . '" ';
3636
        $input .= 'value="' . AlDolUtils::dol_escape_htmltag($titlealt) . '" title="' . AlDolUtils::dol_escape_htmltag($titlealt) . '" >';
3637
3638
        return $input;
3639
    }
3640
3641
    /**
3642
     *  Show search logo
3643
     *
3644
     *  @param	string	$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3645
     * 	@param  string	$other      Add more attributes on img
3646
     *  @return string      		Retourne tag img
3647
     */
3648
    static function img_searchclear($titlealt = 'default', $other = '')
3649
    {
3650
        // global Globals::$conf, Globals::$langs;
3651
3652
        if ($titlealt == 'default')
3653
            $titlealt = Globals::$langs->trans('Search');
3654
3655
        $img = img_picto($titlealt, 'searchclear.png', $other, false, 1);
3656
3657
        $input = '<input type="image" class="liste_titre" name="button_removefilter" src="' . $img . '" ';
3658
        $input .= 'value="' . AlDolUtils::dol_escape_htmltag($titlealt) . '" title="' . AlDolUtils::dol_escape_htmltag($titlealt) . '" >';
3659
3660
        return $input;
3661
    }
3662
3663
    /**
3664
     * 	Show information for admin users or standard users
3665
     *
3666
     * 	@param	string	$text			Text info
3667
     * 	@param  integer	$infoonimgalt	Info is shown only on alt of star picto, otherwise it is show on output after the star picto
3668
     * 	@param	int		$nodiv			No div
3669
     *  @param  string  $admin          '1'=Info for admin users. '0'=Info for standard users (change only the look), 'xxx'=Other
3670
     *  @param	string	$morecss		More CSS
3671
     * 	@return	string					String with info text
3672
     */
3673
    static function info_admin($text, $infoonimgalt = 0, $nodiv = 0, $admin = '1', $morecss = '')
3674
    {
3675
        // global Globals::$conf, Globals::$langs;
3676
3677
        if ($infoonimgalt) {
3678
            return img_picto($text, 'info', 'class="hideonsmartphone' . ($morecss ? ' ' . $morecss : '') . '"');
3679
        }
3680
3681
        return ($nodiv ? '' : '<div class="' . (empty($admin) ? '' : ($admin == '1' ? 'info' : $admin)) . ' hideonsmartphone' . ($morecss ? ' ' . $morecss : '') . '">') . '<span class="fa fa-info-circle" title="' . AlDolUtils::dol_escape_htmltag($admin ? Globals::$langs->trans('InfoAdmin') : Globals::$langs->trans('Note')) . '"></span> ' . $text . ($nodiv ? '' : '</div>');
3682
    }
3683
3684
    /**
3685
     * 	Affiche message erreur system avec toutes les informations pour faciliter le diagnostic et la remontee des bugs.
3686
     * 	On doit appeler cette fonction quand une erreur technique bloquante est rencontree.
3687
     * 	Toutefois, il faut essayer de ne l'appeler qu'au sein de pages php, les classes devant
3688
     * 	renvoyer leur erreur par l'intermediaire de leur propriete "error".
3689
     *
3690
     * 	@param	 	DoliDB	$db      	Database handler
3691
     * 	@param  	mixed	$error		String or array of errors strings to show
3692
     *  @param		array	$errors		Array of errors
3693
     * 	@return 	void
3694
     *  @see    	dol_htmloutput_errors
3695
     */
3696
    static function dol_print_error($dbError = '', $error = '', $errors = null)
3697
    {
3698
        // global Globals::$conf, Globals::$langs, $argv;
3699
        // global $dolibarr_main_prod;
3700
3701
        $out = '';
3702
        $syslog = '';
3703
3704
// Si erreur intervenue avant chargement langue
3705
        if (!Globals::$langs) {
3706
            //require_once DOL_BASE_PATH . '/core/class/translate.class.php';
3707
            Globals::$langs = new Translate('', Globals::$conf);
3708
            Globals::$langs->load("main");
3709
        }
3710
3711
// Load translation files required by the page
3712
        Globals::$langs->loadLangs(array('main', 'errors'));
3713
3714
        if ($_SERVER['DOCUMENT_ROOT']) {    // Mode web
3715
            $out .= Globals::$langs->trans("DolibarrHasDetectedError") . ".<br>\n";
3716
            if (!empty(Globals::$conf->global->MAIN_FEATURES_LEVEL)) {
3717
                $out .= "You use an experimental or develop level of features, so please do NOT report any bugs, except if problem is confirmed moving option MAIN_FEATURES_LEVEL back to 0.<br>\n";
3718
            }
3719
            $out .= Globals::$langs->trans("InformationToHelpDiagnose") . ":<br>\n";
3720
3721
            $out .= "<b>" . Globals::$langs->trans("Date") . ":</b> " . AlDolUtils::dol_print_date(time(), 'dayhourlog') . "<br>\n";
3722
            $out .= "<b>" . Globals::$langs->trans("Dolibarr") . ":</b> " . DOL_VERSION . "<br>\n";
3723
            if (isset(Globals::$conf->global->MAIN_FEATURES_LEVEL)) {
3724
                $out .= "<b>" . Globals::$langs->trans("LevelOfFeature") . ":</b> " . Globals::$conf->global->MAIN_FEATURES_LEVEL . "<br>\n";
3725
            }
3726
            if (function_exists("phpversion")) {
3727
                $out .= "<b>" . Globals::$langs->trans("PHP") . ":</b> " . phpversion() . "<br>\n";
3728
            }
3729
            $out .= "<b>" . Globals::$langs->trans("Server") . ":</b> " . $_SERVER["SERVER_SOFTWARE"] . "<br>\n";
3730
            if (function_exists("php_uname")) {
3731
                $out .= "<b>" . Globals::$langs->trans("OS") . ":</b> " . php_uname() . "<br>\n";
3732
            }
3733
            $out .= "<b>" . Globals::$langs->trans("UserAgent") . ":</b> " . $_SERVER["HTTP_USER_AGENT"] . "<br>\n";
3734
            $out .= "<br>\n";
3735
            $out .= "<b>" . Globals::$langs->trans("RequestedUrl") . ":</b> " . AlDolUtils::dol_htmlentities($_SERVER["REQUEST_URI"], ENT_COMPAT, 'UTF-8') . "<br>\n";
3736
            $out .= "<b>" . Globals::$langs->trans("Referer") . ":</b> " . (isset($_SERVER["HTTP_REFERER"]) ? AlDolUtils::dol_htmlentities($_SERVER["HTTP_REFERER"], ENT_COMPAT, 'UTF-8') : '') . "<br>\n";
3737
            $out .= "<b>" . Globals::$langs->trans("MenuManager") . ":</b> " . (isset(Globals::$conf->standard_menu) ? Globals::$conf->standard_menu : '') . "<br>\n";
3738
            $out .= "<br>\n";
3739
            $syslog .= "url=" . AlDolUtils::dol_escape_htmltag($_SERVER["REQUEST_URI"]);
3740
            $syslog .= ", query_string=" . AlDolUtils::dol_escape_htmltag($_SERVER["QUERY_STRING"]);
3741
        } else {                              // Mode CLI
3742
            $out .= '> ' . Globals::$langs->transnoentities("ErrorInternalErrorDetected") . ":\n" . $argv[0] . "\n";
3743
            $syslog .= "pid=" . dol_getmypid();
3744
        }
3745
3746
        if (is_object($dbError)) {
3747
            $out .= '<pre>' . print_r($dbError, true) . '</pre>';
3748
            /*
3749
              if ($_SERVER['DOCUMENT_ROOT']) {  // Mode web
3750
              $out .= "<b>" . Globals::$langs->trans("DatabaseTypeManager") . ":</b> " . $dbError->type . "<br>\n";
3751
              $out .= "<b>" . Globals::$langs->trans("RequestLastAccessInError") . ":</b> " . ($dbError->lastqueryerror() ? AlDolUtils::dol_escape_htmltag($db->lastqueryerror()) : Globals::$langs->trans("ErrorNoRequestInError")) . "<br>\n";
3752
              $out .= "<b>" . Globals::$langs->trans("ReturnCodeLastAccessInError") . ":</b> " . ($dbError->lasterrno() ? AlDolUtils::dol_escape_htmltag($db->lasterrno()) : Globals::$langs->trans("ErrorNoRequestInError")) . "<br>\n";
3753
              $out .= "<b>" . Globals::$langs->trans("InformationLastAccessInError") . ":</b> " . ($dbError->lasterror() ? AlDolUtils::dol_escape_htmltag($db->lasterror()) : Globals::$langs->trans("ErrorNoRequestInError")) . "<br>\n";
3754
              $out .= "<br>\n";
3755
              } else {                            // Mode CLI
3756
              // No AlDolUtils::dol_escape_htmltag for output, we are in CLI mode
3757
              $out .= '> ' . Globals::$langs->transnoentities("DatabaseTypeManager") . ":\n" . $db->type . "\n";
3758
              $out .= '> ' . Globals::$langs->transnoentities("RequestLastAccessInError") . ":\n" . ($db->lastqueryerror() ? $db->lastqueryerror() : Globals::$langs->transnoentities("ErrorNoRequestInError")) . "\n";
3759
              $out .= '> ' . Globals::$langs->transnoentities("ReturnCodeLastAccessInError") . ":\n" . ($db->lasterrno() ? $db->lasterrno() : Globals::$langs->transnoentities("ErrorNoRequestInError")) . "\n";
3760
              $out .= '> ' . Globals::$langs->transnoentities("InformationLastAccessInError") . ":\n" . ($db->lasterror() ? $db->lasterror() : Globals::$langs->transnoentities("ErrorNoRequestInError")) . "\n";
3761
              }
3762
              $syslog .= ", sql=" . $db->lastquery();
3763
              $syslog .= ", db_error=" . $db->lasterror();
3764
             */
3765
        }
3766
3767
        if ($error || $errors) {
3768
            Globals::$langs->load("errors");
3769
3770
// Merge all into $errors array
3771
            if (is_array($error) && is_array($errors)) {
3772
                $errors = array_merge($error, $errors);
3773
            } elseif (is_array($error)) {
3774
                $errors = $error;
3775
            } elseif (is_array($errors)) {
3776
                $errors = array_merge(array($error), $errors);
3777
            } else {
3778
                $errors = array_merge(array($error));
3779
            }
3780
3781
            foreach ($errors as $msg) {
3782
                if (empty($msg)) {
3783
                    continue;
3784
                }
3785
                if ($_SERVER['DOCUMENT_ROOT']) {  // Mode web
3786
                    $out .= "<b>" . Globals::$langs->trans("Message") . ":</b> " . AlDolUtils::dol_escape_htmltag($msg) . "<br>\n";
3787
                } else {                        // Mode CLI
3788
                    $out .= '> ' . Globals::$langs->transnoentities("Message") . ":\n" . $msg . "\n";
3789
                }
3790
                $syslog .= ", msg=" . $msg;
3791
            }
3792
        }
3793
        if (empty($dolibarr_main_prod) && $_SERVER['DOCUMENT_ROOT'] && function_exists('xdebug_print_function_stack') && function_exists('xdebug_call_file')) {
3794
            xdebug_print_function_stack();
3795
            $out .= '<b>XDebug informations:</b>' . "<br>\n";
3796
            $out .= 'File: ' . xdebug_call_file() . "<br>\n";
3797
            $out .= 'Line: ' . xdebug_call_line() . "<br>\n";
3798
            $out .= 'Function: ' . xdebug_call_function() . "<br>\n";
3799
            $out .= "<br>\n";
3800
        }
3801
3802
        if (empty($dolibarr_main_prod)) {
3803
            print $out;
3804
        } else {
3805
            print Globals::$langs->trans("DolibarrHasDetectedError") . '. ';
3806
            print Globals::$langs->trans("YouCanSetOptionDolibarrMainProdToZero");
3807
            define("MAIN_CORE_ERROR", 1);
3808
        }
3809
//else print 'Sorry, an error occured but the parameter $dolibarr_main_prod is defined in conf file so no message is reported to your browser. Please read the log file for error message.';
3810
        AlDolUtils::dol_syslog("Error " . $syslog, LOG_ERR);
3811
    }
3812
3813
    /**
3814
     * Show a public email and error code to contact if technical error
3815
     *
3816
     * @param	string	$prefixcode		Prefix of public error code
3817
     * @param   string  $errormessage   Complete error message
3818
     * @param	array	$errormessages	Array of error messages
3819
     * @param	string	$morecss		More css
3820
     * @param	string	$email			Email
3821
     * @return	void
3822
     */
3823
    static function dol_print_error_email($prefixcode, $errormessage = '', $errormessages = array(), $morecss = 'error', $email = '')
3824
    {
3825
        // global Globals::$langs, Globals::$conf;
3826
3827
        if (empty($email)) {
3828
            $email = Globals::$conf->global->MAIN_INFO_SOCIETE_MAIL;
3829
        }
3830
3831
        Globals::$langs->load("errors");
3832
        $now = dol_now();
3833
3834
        print '<br><div class="center login_main_message"><div class="' . $morecss . '">';
3835
        print Globals::$langs->trans("ErrorContactEMail", $email, $prefixcode . AlDolUtils::dol_print_date($now, '%Y%m%d'));
3836
        if ($errormessage) {
3837
            print '<br><br>' . $errormessage;
3838
        }
3839
        if (is_array($errormessages) && count($errormessages)) {
3840
            foreach ($errormessages as $mesgtoshow) {
3841
                print '<br><br>' . $mesgtoshow;
3842
            }
3843
        }
3844
        print '</div></div>';
3845
    }
3846
3847
    /**
3848
     * 	Show title line of an array
3849
     *
3850
     * 	@param	string	$name        Label of field
3851
     * 	@param	string	$file        Url used when we click on sort picto
3852
     * 	@param	string	$field       Field to use for new sorting
3853
     * 	@param	string	$begin       ("" by defaut)
3854
     * 	@param	string	$moreparam   Add more parameters on sort url links ("" by default)
3855
     * 	@param  string	$moreattrib  Options of attribute td ("" by defaut, example: 'align="center"')
3856
     * 	@param  string	$sortfield   Current field used to sort
3857
     * 	@param  string	$sortorder   Current sort order
3858
     *  @param	string	$prefix		 Prefix for css. Use space after prefix to add your own CSS tag.
3859
     *  @param	string	$tooltip	 Tooltip
3860
     * 	@return	void
3861
     */
3862
    static function print_liste_field_titre($name, $file = "", $field = "", $begin = "", $moreparam = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $tooltip = "")
3863
    {
3864
        print getTitleFieldOfList($name, 0, $file, $field, $begin, $moreparam, $moreattrib, $sortfield, $sortorder, $prefix, 0, $tooltip);
3865
    }
3866
3867
    /**
3868
     * 	Get title line of an array
3869
     *
3870
     * 	@param	string	$name        		Translation key of field
3871
     * 	@param	int		$thead		 		0=To use with standard table format, 1=To use inside <thead><tr>, 2=To use with <div>
3872
     * 	@param	string	$file        		Url used when we click on sort picto
3873
     * 	@param	string	$field       		Field to use for new sorting. Empty if this field is not sortable. Example "t.abc" or "t.abc,t.def"
3874
     * 	@param	string	$begin       		("" by defaut)
3875
     * 	@param	string	$moreparam   		Add more parameters on sort url links ("" by default)
3876
     * 	@param  string	$moreattrib  		Add more attributes on th ("" by defaut, example: 'align="center"'). To add more css class, use param $prefix.
3877
     * 	@param  string	$sortfield   		Current field used to sort (Ex: 'd.datep,d.id')
3878
     * 	@param  string	$sortorder   		Current sort order (Ex: 'asc,desc')
3879
     *  @param	string	$prefix		 		Prefix for css. Use space after prefix to add your own CSS tag, for example 'mycss '.
3880
     *  @param	string	$disablesortlink	1=Disable sort link
3881
     *  @param	string	$tooltip	 		Tooltip
3882
     * 	@return	string
3883
     */
3884
    static function getTitleFieldOfList($name, $thead = 0, $file = "", $field = "", $begin = "", $moreparam = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $disablesortlink = 0, $tooltip = '')
3885
    {
3886
        // global Globals::$conf, Globals::$langs, $form;
3887
//print "$name, $file, $field, $begin, $options, $moreattrib, $sortfield, $sortorder<br>\n";
3888
3889
        $sortorder = strtoupper($sortorder);
3890
        $out = '';
3891
        $sortimg = '';
3892
3893
        $tag = 'th';
3894
        if ($thead == 2) {
3895
            $tag = 'div';
3896
        }
3897
3898
        $tmpsortfield = explode(',', $sortfield);
3899
        $sortfield1 = trim($tmpsortfield[0]);    // If $sortfield is 'd.datep,d.id', it becomes 'd.datep'
3900
        $tmpfield = explode(',', $field);
3901
        $field1 = trim($tmpfield[0]);            // If $field is 'd.datep,d.id', it becomes 'd.datep'
3902
//var_dump('field='.$field.' field1='.$field1.' sortfield='.$sortfield.' sortfield1='.$sortfield1);
3903
// If field is used as sort criteria we use a specific css class liste_titre_sel
3904
// Example if (sortfield,field)=("nom","xxx.nom") or (sortfield,field)=("nom","nom")
3905
        if ($field1 && ($sortfield1 == $field1 || $sortfield1 == preg_replace("/^[^\.]+\./", "", $field1))) {
3906
            $out .= '<' . $tag . ' class="' . $prefix . 'liste_titre_sel" ' . $moreattrib . '>';
3907
        } else {
3908
            $out .= '<' . $tag . ' class="' . $prefix . 'liste_titre" ' . $moreattrib . '>';
3909
        }
3910
3911
        if (empty($thead) && $field && empty($disablesortlink)) {    // If this is a sort field
3912
            $options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', $moreparam);
3913
            $options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
3914
            $options = preg_replace('/&+/i', '&', $options);
3915
            if (!preg_match('/^&/', $options)) {
3916
                $options = '&' . $options;
3917
            }
3918
3919
            $sortordertouseinlink = '';
3920
            if ($field1 != $sortfield1) { // We are on another field than current sorted field
3921
                if (preg_match('/^DESC/i', $sortorder)) {
3922
                    $sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
3923
                } else {  // We reverse the var $sortordertouseinlink
3924
                    $sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
3925
                }
3926
            } else {                        // We are on field that is the first current sorting criteria
3927
                if (preg_match('/^ASC/i', $sortorder)) { // We reverse the var $sortordertouseinlink
3928
                    $sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
3929
                } else {
3930
                    $sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
3931
                }
3932
            }
3933
            $sortordertouseinlink = preg_replace('/,$/', '', $sortordertouseinlink);
3934
            $out .= '<a class="reposition" href="' . $file . '?sortfield=' . $field . '&sortorder=' . $sortordertouseinlink . '&begin=' . $begin . $options . '">';
3935
        }
3936
3937
        if ($tooltip) {
3938
            $out .= $form->textwithpicto(Globals::$langs->trans($name), Globals::$langs->trans($tooltip));
3939
        } else {
3940
            $out .= Globals::$langs->trans($name);
3941
        }
3942
3943
        if (empty($thead) && $field && empty($disablesortlink)) {    // If this is a sort field
3944
            $out .= '</a>';
3945
        }
3946
3947
        if (empty($thead) && $field) {    // If this is a sort field
3948
            $options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', $moreparam);
3949
            $options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
3950
            $options = preg_replace('/&+/i', '&', $options);
3951
            if (!preg_match('/^&/', $options)) {
3952
                $options = '&' . $options;
3953
            }
3954
3955
            if (!$sortorder || $field1 != $sortfield1) {
3956
//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",0).'</a>';
3957
//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",0).'</a>';
3958
            } else {
3959
                if (preg_match('/^DESC/', $sortorder)) {
3960
                    //$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",0).'</a>';
3961
                    //$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",1).'</a>';
3962
                    $sortimg .= '<span class="nowrap">' . img_up("Z-A", 0) . '</span>';
3963
                }
3964
                if (preg_match('/^ASC/', $sortorder)) {
3965
                    //$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",1).'</a>';
3966
                    //$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",0).'</a>';
3967
                    $sortimg .= '<span class="nowrap">' . img_down("A-Z", 0) . '</span>';
3968
                }
3969
            }
3970
        }
3971
3972
        $out .= $sortimg;
3973
3974
        $out .= '</' . $tag . '>';
3975
3976
        return $out;
3977
    }
3978
3979
    /**
3980
     * 	Show a title.
3981
     *
3982
     * 	@param	string	$title			Title to show
3983
     * 	@return	string					Title to show
3984
     *  @deprecated						Use load_fiche_titre instead
3985
     *  @see load_fiche_titre
3986
     */
3987
    static function print_titre($title)
3988
    {
3989
        AlDolUtils::dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);
3990
3991
        print '<div class="titre">' . $title . '</div>';
3992
    }
3993
3994
    /**
3995
     * 	Show a title with picto
3996
     *
3997
     * 	@param	string	$title				Title to show
3998
     * 	@param	string	$mesg				Added message to show on right
3999
     * 	@param	string	$picto				Icon to use before title (should be a 32x32 transparent png file)
4000
     * 	@param	int		$pictoisfullpath	1=Icon name is a full absolute url of image
4001
     * 	@param	int		$id					To force an id on html objects
4002
     * 	@return	void
4003
     *  @deprecated Use print load_fiche_titre instead
4004
     */
4005
    static function print_fiche_titre($title, $mesg = '', $picto = 'title_generic.png', $pictoisfullpath = 0, $id = '')
4006
    {
4007
        print load_fiche_titre($title, $mesg, $picto, $pictoisfullpath, $id);
4008
    }
4009
4010
    /**
4011
     * 	Load a title with picto
4012
     *
4013
     * 	@param	string	$titre				Title to show
4014
     * 	@param	string	$morehtmlright		Added message to show on right
4015
     * 	@param	string	$picto				Icon to use before title (should be a 32x32 transparent png file)
4016
     * 	@param	int		$pictoisfullpath	1=Icon name is a full absolute url of image
4017
     * 	@param	string	$id					To force an id on html objects
4018
     *  @param  string  $morecssontable     More css on table
4019
     * 	@param	string	$morehtmlcenter		Added message to show on center
4020
     * 	@return	string
4021
     *  @see print_barre_liste
4022
     */
4023
    static function load_fiche_titre($titre, $morehtmlright = '', $picto = 'title_generic.png', $pictoisfullpath = 0, $id = '', $morecssontable = '', $morehtmlcenter = '')
4024
    {
4025
        // global Globals::$conf;
4026
4027
        $return = '';
4028
4029
        if ($picto == 'setup') {
4030
            $picto = 'title_generic.png';
4031
        }
4032
4033
        $return .= "\n";
4034
        $return .= '<table ' . ($id ? 'id="' . $id . '" ' : '') . 'summary="" class="centpercent notopnoleftnoright' . ($morecssontable ? ' ' . $morecssontable : '') . '" style="margin-bottom: 6px;"><tr>'; // maring bottom must be same than into print_barre_list
4035
        if ($picto) {
4036
            $return .= '<td class="nobordernopadding widthpictotitle opacityhigh" valign="middle">' . AlDolUtils::img_picto('', $picto, 'class="valignmiddle widthpictotitle pictotitle"', $pictoisfullpath) . '</td>';
4037
        }
4038
        $return .= '<td class="nobordernopadding valignmiddle">';
4039
        $return .= '<div class="titre inline-block">' . $titre . '</div>';
4040
        $return .= '</td>';
4041
        if (AlDolUtils::dol_strlen($morehtmlcenter)) {
4042
            $return .= '<td class="nobordernopadding" align="center" valign="middle">' . $morehtmlcenter . '</td>';
4043
        }
4044
        if (AlDolUtils::dol_strlen($morehtmlright)) {
4045
            $return .= '<td class="nobordernopadding titre_right wordbreak" align="right" valign="middle">' . $morehtmlright . '</td>';
4046
        }
4047
        $return .= '</tr></table>' . "\n";
4048
4049
        return $return;
4050
    }
4051
4052
    /**
4053
     * 	Print a title with navigation controls for pagination
4054
     *
4055
     * 	@param	string	    $titre				Title to show (required)
4056
     * 	@param	int   	    $page				Numero of page to show in navigation links (required)
4057
     * 	@param	string	    $file				Url of page (required)
4058
     * 	@param	string	    $options         	More parameters for links ('' by default, does not include sortfield neither sortorder). Value must be 'urlencoded' before calling function.
4059
     * 	@param	string    	$sortfield       	Field to sort on ('' by default)
4060
     * 	@param	string	    $sortorder       	Order to sort ('' by default)
4061
     * 	@param	string	    $morehtmlcenter     String in the middle ('' by default). We often find here string $massaction comming from $form->selectMassAction()
4062
     * 	@param	int		    $num				Number of records found by select with limit+1
4063
     * 	@param	int|string  $totalnboflines		Total number of records/lines for all pages (if known). Use a negative value of number to not show number. Use '' if unknown.
4064
     * 	@param	string	    $picto				Icon to use before title (should be a 32x32 transparent png file)
4065
     * 	@param	int		    $pictoisfullpath	1=Icon name is a full absolute url of image
4066
     *  @param	string	    $morehtmlright			More html to show
4067
     *  @param  string      $morecss            More css to the table
4068
     *  @param  int         $limit              Max number of lines (-1 = use default, 0 = no limit, > 0 = limit).
4069
     *  @param  int         $hideselectlimit    Force to hide select limit
4070
     *  @param  int         $hidenavigation     Force to hide all navigation tools
4071
     * 	@return	void
4072
     */
4073
    static function print_barre_liste($titre, $page, $file, $options = '', $sortfield = '', $sortorder = '', $morehtmlcenter = '', $num = -1, $totalnboflines = '', $picto = 'title_generic.png', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limit = -1, $hideselectlimit = 0, $hidenavigation = 0)
4074
    {
4075
        // global Globals::$conf, Globals::$langs;
4076
4077
        $savlimit = $limit;
4078
        $savtotalnboflines = $totalnboflines;
4079
        $totalnboflines = abs($totalnboflines);
4080
4081
        if ($picto == 'setup')
4082
            $picto = 'title_setup.png';
4083
        if ((Globals::$conf->browser->name == 'ie') && $picto == 'title_generic.png')
4084
            $picto = 'title.gif';
4085
        if ($limit < 0)
4086
            $limit = Globals::$conf->liste_limit;
4087
        if ($savlimit != 0 && (($num > $limit) || ($num == -1) || ($limit == 0))) {
4088
            $nextpage = 1;
4089
        } else {
4090
            $nextpage = 0;
4091
        }
4092
//print 'totalnboflines='.$totalnboflines.'-savlimit='.$savlimit.'-limit='.$limit.'-num='.$num.'-nextpage='.$nextpage;
4093
4094
        print "\n";
4095
        print "<!-- Begin title '" . $titre . "' -->\n";
4096
        print '<table border="0" class="centpercent notopnoleftnoright' . ($morecss ? ' ' . $morecss : '') . '" style="margin-bottom: 6px;"><tr>'; // maring bottom must be same than into load_fiche_tire
4097
// Left
4098
//if ($picto && $titre) print '<td class="nobordernopadding hideonsmartphone" width="40" align="left" valign="middle">'.img_picto('', $picto, 'id="pictotitle"', $pictoisfullpath).'</td>';
4099
        print '<td class="nobordernopadding valignmiddle">';
4100
        if ($picto && $titre)
4101
            print img_picto('', $picto, 'class="hideonsmartphone valignmiddle opacityhigh pictotitle widthpictotitle"', $pictoisfullpath);
4102
        print '<div class="titre inline-block">' . $titre;
4103
        if (!empty($titre) && $savtotalnboflines >= 0 && (string) $savtotalnboflines != '')
4104
            print ' (' . $totalnboflines . ')';
4105
        print '</div></td>';
4106
4107
// Center
4108
        if ($morehtmlcenter) {
4109
            print '<td class="nobordernopadding center valignmiddle">' . $morehtmlcenter . '</td>';
4110
        }
4111
4112
// Right
4113
        print '<td class="nobordernopadding valignmiddle" align="right">';
4114
        if ($sortfield)
4115
            $options .= "&sortfield=" . urlencode($sortfield);
4116
        if ($sortorder)
4117
            $options .= "&sortorder=" . urlencode($sortorder);
4118
// Show navigation bar
4119
        $pagelist = '';
4120
        if ($savlimit != 0 && ($page > 0 || $num > $limit)) {
4121
            if ($totalnboflines) { // If we know total nb of lines
4122
// Define nb of extra page links before and after selected page + ... + first or last
4123
                $maxnbofpage = (empty(Globals::$conf->dol_optimize_smallscreen) ? 4 : 1);
4124
4125
                if ($limit > 0)
4126
                    $nbpages = ceil($totalnboflines / $limit);
4127
                else
4128
                    $nbpages = 1;
4129
                $cpt = ($page - $maxnbofpage);
4130
                if ($cpt < 0) {
4131
                    $cpt = 0;
4132
                }
4133
4134
                if ($cpt >= 1) {
4135
                    $pagelist .= '<li' . ((Globals::$conf->dol_use_jmobile != 4) ? ' class="pagination"' : '') . '><a href="' . $file . '?page=0' . $options . '">1</a></li>';
4136
                    if ($cpt > 2)
4137
                        $pagelist .= '<li' . ( (Globals::$conf->dol_use_jmobile != 4) ? ' class="pagination"' : '') . '><span ' . ((Globals::$conf->dol_use_jmobile != 4) ? 'class="inactive"' : '') . '>...</span></li>';
4138
                    else if ($cpt == 2)
4139
                        $pagelist .= '<li' . ( (Globals::$conf->dol_use_jmobile != 4) ? ' class="pagination"' : '') . '><a href="' . $file . '?page=1' . $options . '">2</a></li>';
4140
                }
4141
4142
                do {
4143
                    if ($cpt == $page) {
4144
                        $pagelist .= '<li' . ((Globals::$conf->dol_use_jmobile != 4) ? ' class="pagination"' : '') . '><span ' . ((Globals::$conf->dol_use_jmobile != 4) ? 'class="active"' : '') . '>' . ($page + 1) . '</span></li>';
4145
                    } else {
4146
                        $pagelist .= '<li' . ((Globals::$conf->dol_use_jmobile != 4) ? ' class="pagination"' : '') . '><a href="' . $file . '?page=' . $cpt . $options . '">' . ($cpt + 1) . '</a></li>';
4147
                    }
4148
                    $cpt++;
4149
                } while ($cpt < $nbpages && $cpt <= $page + $maxnbofpage);
4150
4151
                if ($cpt < $nbpages) {
4152
                    if ($cpt < $nbpages - 2)
4153
                        $pagelist .= '<li' . ( (Globals::$conf->dol_use_jmobile != 4) ? ' class="pagination"' : '') . '><span ' . ((Globals::$conf->dol_use_jmobile != 4) ? 'class="inactive"' : '') . '>...</span></li>';
4154
                    else if ($cpt == $nbpages - 2)
4155
                        $pagelist .= '<li' . ( (Globals::$conf->dol_use_jmobile != 4) ? ' class="pagination"' : '') . '><a href="' . $file . '?page=' . ($nbpages - 2) . $options . '">' . ($nbpages - 1) . '</a></li>';
4156
                    $pagelist .= '<li' . ((Globals::$conf->dol_use_jmobile != 4) ? ' class="pagination"' : '') . '><a href="' . $file . '?page=' . ($nbpages - 1) . $options . '">' . $nbpages . '</a></li>';
4157
                }
4158
            }
4159
            else {
4160
                $pagelist .= '<li' . ((Globals::$conf->dol_use_jmobile != 4) ? ' class="pagination"' : '') . '><span ' . ((Globals::$conf->dol_use_jmobile != 4) ? 'class="active"' : '') . '>' . ($page + 1) . "</li>";
4161
            }
4162
        }
4163
4164
        print_fleche_navigation($page, $file, $options, $nextpage, $pagelist, $morehtmlright, $savlimit, $totalnboflines, $hideselectlimit);  // output the div and ul for previous/last completed with page numbers into $pagelist
4165
4166
        print '</td>';
4167
4168
        print '</tr></table>' . "\n";
4169
        print "<!-- End title -->\n\n";
4170
    }
4171
4172
    /**
4173
     * 	Function to show navigation arrows into lists
4174
     *
4175
     * 	@param	int				$page				Number of page
4176
     * 	@param	string			$file				Page URL (in most cases provided with $_SERVER["PHP_SELF"])
4177
     * 	@param	string			$options         	Other url paramaters to propagate ("" by default, may include sortfield and sortorder)
4178
     * 	@param	integer			$nextpage	    	Do we show a next page button
4179
     * 	@param	string			$betweenarrows		HTML content to show between arrows. MUST contains '<li> </li>' tags or '<li><span> </span></li>'.
4180
     *  @param	string			$afterarrows		HTML content to show after arrows. Must NOT contains '<li> </li>' tags.
4181
     *  @param  int             $limit              Max nb of record to show  (-1 = no combo with limit, 0 = no limit, > 0 = limit)
4182
     * 	@param	int		        $totalnboflines		Total number of records/lines for all pages (if known)
4183
     *  @param  int             $hideselectlimit    Force to hide select limit
4184
     * 	@return	void
4185
     */
4186
    static function print_fleche_navigation($page, $file, $options = '', $nextpage = 0, $betweenarrows = '', $afterarrows = '', $limit = -1, $totalnboflines = 0, $hideselectlimit = 0)
4187
    {
4188
        // global Globals::$conf, Globals::$langs;
4189
4190
        print '<div class="pagination"><ul>';
4191
        if ((int) $limit >= 0 && empty($hideselectlimit)) {
4192
            $pagesizechoices = '10:10,15:15,20:20,30:30,40:40,50:50,100:100,250:250,500:500,1000:1000,5000:5000';
4193
//$pagesizechoices.=',0:'.Globals::$langs->trans("All");     // Not yet supported
4194
//$pagesizechoices.=',2:2';
4195
            if (!empty(Globals::$conf->global->MAIN_PAGESIZE_CHOICES))
4196
                $pagesizechoices = Globals::$conf->global->MAIN_PAGESIZE_CHOICES;
4197
4198
            print '<li class="pagination">';
4199
            print '<select class="flat selectlimit" name="limit" title="' . AlDolUtils::dol_escape_htmltag(Globals::$langs->trans("MaxNbOfRecordPerPage")) . '">';
4200
            $tmpchoice = explode(',', $pagesizechoices);
4201
            $tmpkey = $limit . ':' . $limit;
4202
            if (!in_array($tmpkey, $tmpchoice))
4203
                $tmpchoice[] = $tmpkey;
4204
            $tmpkey = Globals::$conf->liste_limit . ':' . Globals::$conf->liste_limit;
4205
            if (!in_array($tmpkey, $tmpchoice))
4206
                $tmpchoice[] = $tmpkey;
4207
            asort($tmpchoice, SORT_NUMERIC);
4208
            $found = false;
4209
            foreach ($tmpchoice as $val) {
4210
                $selected = '';
4211
                $tmp = explode(':', $val);
4212
                $key = $tmp[0];
4213
                $val = $tmp[1];
4214
                if ($key != '' && $val != '') {
4215
                    if ((int) $key == (int) $limit) {
4216
                        $selected = ' selected="selected"';
4217
                        $found = true;
4218
                    }
4219
                    print '<option name="' . $key . '"' . $selected . '>' . AlDolUtils::dol_escape_htmltag($val) . '</option>' . "\n";
4220
                }
4221
            }
4222
            print '</select>';
4223
            if (Globals::$conf->use_javascript_ajax) {
4224
                print '<!-- JS CODE TO ENABLE select limit to launch submit of page -->
4225
            		<script type="text/javascript">
4226
                	jQuery(document).ready(static function () {
4227
            	  		jQuery(".selectlimit").change(function() {
4228
                            console.log("Change limit. Send submit");
4229
                            $(this).parents(\'form:first\').submit();
4230
            	  		});
4231
                	});
4232
            		</script>
4233
                ';
4234
            }
4235
            print '</li>';
4236
        }
4237
        if ($page > 0) {
4238
            print '<li class="pagination"><a class="paginationprevious" href="' . $file . '?page=' . ($page - 1) . $options . '"><i class="fa fa-chevron-left" title="' . AlDolUtils::dol_escape_htmltag(Globals::$langs->trans("Previous")) . '"></i></a></li>';
4239
        }
4240
        if ($betweenarrows) {
4241
            print $betweenarrows;
4242
        }
4243
        if ($nextpage > 0) {
4244
            print '<li class="pagination"><a class="paginationnext" href="' . $file . '?page=' . ($page + 1) . $options . '"><i class="fa fa-chevron-right" title="' . AlDolUtils::dol_escape_htmltag(Globals::$langs->trans("Next")) . '"></i></a></li>';
4245
        }
4246
        if ($afterarrows) {
4247
            print '<li class="paginationafterarrows">';
4248
            print $afterarrows;
4249
            print '</li>';
4250
        }
4251
        print '</ul></div>' . "\n";
4252
    }
4253
4254
    /**
4255
     * 	Return a string with VAT rate label formated for view output
4256
     * 	Used into pdf and HTML pages
4257
     *
4258
     * 	@param	string	$rate			Rate value to format ('19.6', '19,6', '19.6%', '19,6%', '19.6 (CODEX)', ...)
4259
     *  @param	boolean	$addpercent		Add a percent % sign in output
4260
     * 	@param	int		$info_bits		Miscellaneous information on vat (0=Default, 1=French NPR vat)
4261
     * 	@param	int		$usestarfornpr	-1=Never show, 0 or 1=Use '*' for NPR vat rates
4262
     *  @return	string					String with formated amounts ('19,6' or '19,6%' or '8.5% (NPR)' or '8.5% *' or '19,6 (CODEX)')
4263
     */
4264
    static function vatrate($rate, $addpercent = false, $info_bits = 0, $usestarfornpr = 0)
4265
    {
4266
        $morelabel = '';
4267
4268
        if (preg_match('/%/', $rate)) {
4269
            $rate = str_replace('%', '', $rate);
4270
            $addpercent = true;
4271
        }
4272
        if (preg_match('/\((.*)\)/', $rate, $reg)) {
4273
            $morelabel = ' (' . $reg[1] . ')';
4274
            $rate = preg_replace('/\s*' . preg_quote($morelabel, '/') . '/', '', $rate);
4275
        }
4276
        if (preg_match('/\*/', $rate)) {
4277
            $rate = str_replace('*', '', $rate);
4278
            $info_bits |= 1;
4279
        }
4280
4281
// If rate is '9/9/9' we don't change it.  If rate is '9.000' we apply price()
4282
        if (!preg_match('/\//', $rate))
4283
            $ret = price($rate, 0, '', 0, 0) . ($addpercent ? '%' : '');
4284
        else {
4285
// TODO Split on / and output with a price2num to have clean numbers without ton of 000.
4286
            $ret = $rate . ($addpercent ? '%' : '');
4287
        }
4288
        if (($info_bits & 1) && $usestarfornpr >= 0)
4289
            $ret .= ' *';
4290
        $ret .= $morelabel;
4291
        return $ret;
4292
    }
4293
4294
    /**
4295
     * 		Function to format a value into an amount for visual output
4296
     * 		Function used into PDF and HTML pages
4297
     *
4298
     * 		@param	float		$amount			Amount to format
4299
     * 		@param	integer		$form			Type of format, HTML or not (not by default)
4300
     * 		@param	Translate	$outlangs		Object langs for output
4301
     * 		@param	int			$trunc			1=Truncate if there is more decimals than MAIN_MAX_DECIMALS_SHOWN (default), 0=Does not truncate. Deprecated because amount are rounded (to unit or total amount accurancy) before beeing inserted into database or after a computation, so this parameter should be useless.
4302
     * 		@param	int			$rounding		Minimum number of decimal to show. If 0, no change, if -1, we use min(Globals::$conf->global->MAIN_MAX_DECIMALS_UNIT,Globals::$conf->global->MAIN_MAX_DECIMALS_TOT)
4303
     * 		@param	int			$forcerounding	Force the number of decimal to forcerounding decimal (-1=do not force)
4304
     * 		@param	string		$currency_code	To add currency symbol (''=add nothing, 'auto'=Use default currency, 'XXX'=add currency symbols for XXX currency)
4305
     * 		@return	string						Chaine avec montant formate
4306
     *
4307
     * 		@see	price2num					Revert static function of price
4308
     */
4309
    static function price($amount, $form = 0, $outlangs = '', $trunc = 1, $rounding = -1, $forcerounding = -1, $currency_code = '')
4310
    {
4311
        // global Globals::$langs, Globals::$conf;
4312
// Clean parameters
4313
        if (empty($amount))
4314
            $amount = 0; // To have a numeric value if amount not defined or = ''
4315
        $amount = (is_numeric($amount) ? $amount : 0); // Check if amount is numeric, for example, an error occured when amount value = o (letter) instead 0 (number)
4316
        if ($rounding < 0)
4317
            $rounding = min(Globals::$conf->global->MAIN_MAX_DECIMALS_UNIT, Globals::$conf->global->MAIN_MAX_DECIMALS_TOT);
4318
        $nbdecimal = $rounding;
4319
4320
// Output separators by default (french)
4321
        $dec = ',';
4322
        $thousand = ' ';
4323
4324
// If $outlangs not forced, we use use language
4325
        if (!is_object($outlangs))
4326
            $outlangs = Globals::$langs;
4327
4328
        if ($outlangs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal")
4329
            $dec = $outlangs->transnoentitiesnoconv("SeparatorDecimal");
4330
        if ($outlangs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand")
4331
            $thousand = $outlangs->transnoentitiesnoconv("SeparatorThousand");
4332
        if ($thousand == 'None')
4333
            $thousand = '';
4334
        else if ($thousand == 'Space')
4335
            $thousand = ' ';
4336
//print "outlangs=".$outlangs->defaultlang." amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
4337
//print "amount=".$amount."-";
4338
        $amount = str_replace(',', '.', $amount); // should be useless
4339
//print $amount."-";
4340
        $datas = explode('.', $amount);
4341
        $decpart = isset($datas[1]) ? $datas[1] : '';
4342
        $decpart = preg_replace('/0+$/i', '', $decpart); // Supprime les 0 de fin de partie decimale
4343
//print "decpart=".$decpart."<br>";
4344
        $end = '';
4345
4346
// We increase nbdecimal if there is more decimal than asked (to not loose information)
4347
        if (AlDolUtils::dol_strlen($decpart) > $nbdecimal)
4348
            $nbdecimal = AlDolUtils::dol_strlen($decpart);
4349
// Si on depasse max
4350
        if ($trunc && $nbdecimal > Globals::$conf->global->MAIN_MAX_DECIMALS_SHOWN) {
4351
            $nbdecimal = Globals::$conf->global->MAIN_MAX_DECIMALS_SHOWN;
4352
            if (preg_match('/\.\.\./i', Globals::$conf->global->MAIN_MAX_DECIMALS_SHOWN)) {
4353
// Si un affichage est tronque, on montre des ...
4354
                $end = '...';
4355
            }
4356
        }
4357
4358
// If force rounding
4359
        if ($forcerounding >= 0)
4360
            $nbdecimal = $forcerounding;
4361
4362
// Format number
4363
        $output = number_format($amount, $nbdecimal, $dec, $thousand);
4364
        if ($form) {
4365
            $output = preg_replace('/\s/', '&nbsp;', $output);
4366
            $output = preg_replace('/\'/', '&#039;', $output);
4367
        }
4368
// Add symbol of currency if requested
4369
        $cursymbolbefore = $cursymbolafter = '';
4370
        if ($currency_code) {
4371
            if ($currency_code == 'auto')
4372
                $currency_code = Globals::$conf->currency;
4373
4374
            $listofcurrenciesbefore = array('USD', 'GBP', 'AUD', 'MXN', 'PEN', 'CNY');
4375
            if (in_array($currency_code, $listofcurrenciesbefore))
4376
                $cursymbolbefore .= $outlangs->getCurrencySymbol($currency_code);
4377
            else {
4378
                $tmpcur = $outlangs->getCurrencySymbol($currency_code);
4379
                $cursymbolafter .= ($tmpcur == $currency_code ? ' ' . $tmpcur : $tmpcur);
4380
            }
4381
        }
4382
        $output = $cursymbolbefore . $output . $end . ($cursymbolafter ? ' ' : '') . $cursymbolafter;
4383
4384
        return $output;
4385
    }
4386
4387
    /**
4388
     * 	Function that return a number with universal decimal format (decimal separator is '.') from an amount typed by a user.
4389
     * 	Function to use on each input amount before any numeric test or database insert
4390
     *
4391
     * 	@param	float	$amount			Amount to convert/clean
4392
     * 	@param	string	$rounding		''=No rounding
4393
     * 									'MU'=Round to Max unit price (MAIN_MAX_DECIMALS_UNIT)
4394
     * 									'MT'=Round to Max for totals with Tax (MAIN_MAX_DECIMALS_TOT)
4395
     * 									'MS'=Round to Max for stock quantity (MAIN_MAX_DECIMALS_STOCK)
4396
     * 									Numeric = Nb of digits for rounding
4397
     * 	@param	int		$alreadysqlnb	Put 1 if you know that content is already universal format number
4398
     * 	@return	string					Amount with universal numeric format (Example: '99.99999') or unchanged text if conversion fails. If amount is null or '', it returns ''.
4399
     *
4400
     * 	@see    price					Opposite static function of price2num
4401
     */
4402
    static function price2num($amount, $rounding = '', $alreadysqlnb = 0)
4403
    {
4404
        // global Globals::$langs, Globals::$conf;
4405
// Round PHP static function does not allow number like '1,234.56' nor '1.234,56' nor '1 234,56'
4406
// Numbers must be '1234.56'
4407
// Decimal delimiter for PHP and database SQL requests must be '.'
4408
        $dec = ',';
4409
        $thousand = ' ';
4410
        if (Globals::$langs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal")
4411
            $dec = Globals::$langs->transnoentitiesnoconv("SeparatorDecimal");
4412
        if (Globals::$langs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand")
4413
            $thousand = Globals::$langs->transnoentitiesnoconv("SeparatorThousand");
4414
        if ($thousand == 'None')
4415
            $thousand = '';
4416
        elseif ($thousand == 'Space')
4417
            $thousand = ' ';
4418
//print "amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
4419
// Convert value to universal number format (no thousand separator, '.' as decimal separator)
4420
        if ($alreadysqlnb != 1) { // If not a PHP number or unknown, we change format
4421
//print 'PP'.$amount.' - '.$dec.' - '.$thousand.' - '.intval($amount).'<br>';
4422
// Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
4423
// to format defined by LC_NUMERIC after a calculation and we want source format to be like defined by Dolibarr setup.
4424
            if (is_numeric($amount)) {
4425
// We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
4426
                $temps = sprintf("%0.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
4427
                $temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
4428
                $nbofdec = max(0, AlDolUtils::dol_strlen($temps) - 2); // -2 to remove "0."
4429
                $amount = number_format($amount, $nbofdec, $dec, $thousand);
4430
            }
4431
//print "QQ".$amount.'<br>';
4432
// Now make replace (the main goal of function)
4433
            if ($thousand != ',' && $thousand != '.')
4434
                $amount = str_replace(',', '.', $amount); // To accept 2 notations for french users
4435
            $amount = str_replace(' ', '', $amount);  // To avoid spaces
4436
            $amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
4437
            $amount = str_replace($dec, '.', $amount);
4438
        }
4439
4440
// Now, make a rounding if required
4441
        if ($rounding) {
4442
            $nbofdectoround = '';
4443
            if ($rounding == 'MU')
4444
                $nbofdectoround = Globals::$conf->global->MAIN_MAX_DECIMALS_UNIT;
4445
            elseif ($rounding == 'MT')
4446
                $nbofdectoround = Globals::$conf->global->MAIN_MAX_DECIMALS_TOT;
4447
            elseif ($rounding == 'MS')
4448
                $nbofdectoround = empty(Globals::$conf->global->MAIN_MAX_DECIMALS_STOCK) ? 5 : Globals::$conf->global->MAIN_MAX_DECIMALS_STOCK;
4449
            elseif (is_numeric($rounding))
4450
                $nbofdectoround = $rounding;
4451
//print "RR".$amount.' - '.$nbofdectoround.'<br>';
4452
            if (AlDolUtils::dol_strlen($nbofdectoround))
4453
                $amount = round($amount, $nbofdectoround); // $nbofdectoround can be 0.
4454
            else
4455
                return 'ErrorBadParameterProvidedToFunction';
4456
//print 'SS'.$amount.' - '.$nbofdec.' - '.$dec.' - '.$thousand.' - '.$nbofdectoround.'<br>';
4457
// Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
4458
// to format defined by LC_NUMERIC after a calculation and we want source format to be defined by Dolibarr setup.
4459
            if (is_numeric($amount)) {
4460
// We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
4461
                $temps = sprintf("%0.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
4462
                $temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
4463
                $nbofdec = max(0, AlDolUtils::dol_strlen($temps) - 2); // -2 to remove "0."
4464
                $amount = number_format($amount, min($nbofdec, $nbofdectoround), $dec, $thousand);  // Convert amount to format with dolibarr dec and thousand
4465
            }
4466
//print "TT".$amount.'<br>';
4467
// Always make replace because each math static function (like round) replace
4468
// with local values and we want a number that has a SQL string format x.y
4469
            if ($thousand != ',' && $thousand != '.')
4470
                $amount = str_replace(',', '.', $amount); // To accept 2 notations for french users
4471
            $amount = str_replace(' ', '', $amount);  // To avoid spaces
4472
            $amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
4473
            $amount = str_replace($dec, '.', $amount);
4474
        }
4475
4476
        return $amount;
4477
    }
4478
4479
    /**
4480
     * Output a dimension with best unit
4481
     *
4482
     * @param   float       $dimension      Dimension
4483
     * @param   int         $unit           Unit of dimension (0, -3, ...)
4484
     * @param   string      $type           'weight', 'volume', ...
4485
     * @param   Translate   $outputlangs    Translate language object
4486
     * @param   int         $round          -1 = non rounding, x = number of decimal
4487
     * @param   string      $forceunitoutput    'no' or numeric (-3, -6, ...) compared to $unit
4488
     * @return  string                      String to show dimensions
4489
     */
4490
    static function showDimensionInBestUnit($dimension, $unit, $type, $outputlangs, $round = -1, $forceunitoutput = 'no')
4491
    {
4492
        require_once DOL_BASE_PATH . '/core/lib/product.lib.php';
4493
4494
        if (($forceunitoutput == 'no' && $dimension < 1 / 10000) || (is_numeric($forceunitoutput) && $forceunitoutput == -6)) {
4495
            $dimension = $dimension * 1000000;
4496
            $unit = $unit - 6;
4497
        } elseif (($forceunitoutput == 'no' && $dimension < 1 / 10) || (is_numeric($forceunitoutput) && $forceunitoutput == -3)) {
4498
            $dimension = $dimension * 1000;
4499
            $unit = $unit - 3;
4500
        } elseif (($forceunitoutput == 'no' && $dimension > 100000000) || (is_numeric($forceunitoutput) && $forceunitoutput == 6)) {
4501
            $dimension = $dimension / 1000000;
4502
            $unit = $unit + 6;
4503
        } elseif (($forceunitoutput == 'no' && $dimension > 100000) || (is_numeric($forceunitoutput) && $forceunitoutput == 3)) {
4504
            $dimension = $dimension / 1000;
4505
            $unit = $unit + 3;
4506
        }
4507
4508
        $ret = price($dimension, 0, $outputlangs, 0, 0, $round) . ' ' . measuring_units_string($unit, $type);
4509
4510
        return $ret;
4511
    }
4512
4513
    /**
4514
     * 	Return localtax rate for a particular vat, when selling a product with vat $vatrate, from a $thirdparty_buyer to a $thirdparty_seller
4515
     *  Note: This static function applies same rules than get_default_tva
4516
     *
4517
     * 	@param	float		$vatrate		        Vat rate. Can be '8.5' or '8.5 (VATCODEX)' for example
4518
     * 	@param  int			$local		         	Local tax to search and return (1 or 2 return only tax rate 1 or tax rate 2)
4519
     *  @param  Societe		$thirdparty_buyer    	Object of buying third party
4520
     *  @param	Societe		$thirdparty_seller		Object of selling third party ($mysoc if not defined)
4521
     *  @param	int			$vatnpr					If vat rate is NPR or not
4522
     * 	@return	mixed			   					0 if not found, localtax rate if found
4523
     *  @see get_default_tva
4524
     */
4525
    static function get_localtax($vatrate, $local, $thirdparty_buyer = "", $thirdparty_seller = "", $vatnpr = 0)
4526
    {
4527
        // global $db, Globals::$conf, $mysoc;
4528
4529
        if (empty($thirdparty_seller) || !is_object($thirdparty_seller))
4530
            $thirdparty_seller = $mysoc;
4531
4532
        AlDolUtils::dol_syslog("get_localtax tva=" . $vatrate . " local=" . $local . " thirdparty_buyer id=" . (is_object($thirdparty_buyer) ? $thirdparty_buyer->id : '') . "/country_code=" . (is_object($thirdparty_buyer) ? $thirdparty_buyer->country_code : '') . " thirdparty_seller id=" . $thirdparty_seller->id . "/country_code=" . $thirdparty_seller->country_code . " thirdparty_seller localtax1_assuj=" . $thirdparty_seller->localtax1_assuj . "  thirdparty_seller localtax2_assuj=" . $thirdparty_seller->localtax2_assuj);
4533
4534
        $vatratecleaned = $vatrate;
4535
        if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) {      // If vat is "xx (yy)"
4536
            $vatratecleaned = trim($reg[1]);
4537
            $vatratecode = $reg[2];
4538
        }
4539
4540
        /* if ($thirdparty_buyer->country_code != $thirdparty_seller->country_code)
4541
          {
4542
          return 0;
4543
          } */
4544
4545
// Some test to guess with no need to make database access
4546
        if ($mysoc->country_code == 'ES') { // For spain localtaxes 1 and 2, tax is qualified if buyer use local tax
4547
            if ($local == 1) {
4548
                if (!$mysoc->localtax1_assuj || (string) $vatratecleaned == "0")
4549
                    return 0;
4550
                if ($thirdparty_seller->id == $mysoc->id) {
4551
                    if (!$thirdparty_buyer->localtax1_assuj)
4552
                        return 0;
4553
                }
4554
                else {
4555
                    if (!$thirdparty_seller->localtax1_assuj)
4556
                        return 0;
4557
                }
4558
            }
4559
4560
            if ($local == 2) {
4561
//if (! $mysoc->localtax2_assuj || (string) $vatratecleaned == "0") return 0;
4562
                if (!$mysoc->localtax2_assuj)
4563
                    return 0;  // If main vat is 0, IRPF may be different than 0.
4564
                if ($thirdparty_seller->id == $mysoc->id) {
4565
                    if (!$thirdparty_buyer->localtax2_assuj)
4566
                        return 0;
4567
                }
4568
                else {
4569
                    if (!$thirdparty_seller->localtax2_assuj)
4570
                        return 0;
4571
                }
4572
            }
4573
        }
4574
        else {
4575
            if ($local == 1 && !$thirdparty_seller->localtax1_assuj)
4576
                return 0;
4577
            if ($local == 2 && !$thirdparty_seller->localtax2_assuj)
4578
                return 0;
4579
        }
4580
4581
// For some country MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY is forced to on.
4582
        if (in_array($mysoc->country_code, array('ES'))) {
4583
            Globals::$conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY = 1;
4584
        }
4585
4586
// Search local taxes
4587
        if (!empty(Globals::$conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY)) {
4588
            if ($local == 1) {
4589
                if ($thirdparty_seller != $mysoc) {
4590
                    if (!isOnlyOneLocalTax($local)) {  // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
4591
                        return $thirdparty_seller->localtax1_value;
4592
                    }
4593
                } else {  // i am the seller
4594
                    if (!isOnlyOneLocalTax($local)) {  // TODO If seller is me, why not always returning this, even if there is only one locatax vat.
4595
                        return Globals::$conf->global->MAIN_INFO_VALUE_LOCALTAX1;
4596
                    }
4597
                }
4598
            }
4599
            if ($local == 2) {
4600
                if ($thirdparty_seller != $mysoc) {
4601
                    if (!isOnlyOneLocalTax($local)) {  // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
4602
                        // TODO We should also return value defined on thirdparty only if defined
4603
                        return $thirdparty_seller->localtax2_value;
4604
                    }
4605
                } else {  // i am the seller
4606
                    if (in_array($mysoc->country_code, array('ES'))) {
4607
                        return $thirdparty_buyer->localtax2_value;
4608
                    } else {
4609
                        return Globals::$conf->global->MAIN_INFO_VALUE_LOCALTAX2;
4610
                    }
4611
                }
4612
            }
4613
        }
4614
4615
// By default, search value of local tax on line of common tax
4616
        $sql = "SELECT t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
4617
        $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t, " . MAIN_DB_PREFIX . "c_country as c";
4618
        $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $thirdparty_seller->country_code . "'";
4619
        $sql .= " AND t.taux = " . ((float) $vatratecleaned) . " AND t.active = 1";
4620
        if ($vatratecode)
4621
            $sql .= " AND t.code ='" . $vatratecode . "'";  // If we have the code, we use it in priority
4622
        else
4623
            $sql .= " AND t.recuperableonly ='" . $vatnpr . "'";
4624
        AlDolUtils::dol_syslog("get_localtax", LOG_DEBUG);
4625
        $resql = $db->query($sql);
4626
4627
        if ($resql) {
4628
            $obj = $db->fetch_object($resql);
4629
            if ($local == 1)
4630
                return $obj->localtax1;
4631
            elseif ($local == 2)
4632
                return $obj->localtax2;
4633
        }
4634
4635
        return 0;
4636
    }
4637
4638
    /**
4639
     * Return true if LocalTax (1 or 2) is unique.
4640
     * Example: If localtax1 is 5 on line with highest common vat rate, return true
4641
     * Example: If localtax1 is 5:8:15 on line with highest common vat rate, return false
4642
     *
4643
     * @param   int 	$local	Local tax to test (1 or 2)
4644
     * @return  boolean 		True if LocalTax have multiple values, False if not
4645
     */
4646
    static function isOnlyOneLocalTax($local)
4647
    {
4648
        $tax = get_localtax_by_third($local);
4649
4650
        $valors = explode(":", $tax);
4651
4652
        if (count($valors) > 1) {
4653
            return false;
4654
        } else {
4655
            return true;
4656
        }
4657
    }
4658
4659
    /**
4660
     * Get values of localtaxes (1 or 2) for company country for the common vat with the highest value
4661
     *
4662
     * @param	int		$local 	LocalTax to get
4663
     * @return	number			Values of localtax
4664
     */
4665
    static function get_localtax_by_third($local)
4666
    {
4667
        // global $db, $mysoc;
4668
        $sql = "SELECT t.localtax1, t.localtax2 ";
4669
        $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t inner join " . MAIN_DB_PREFIX . "c_country as c ON c.rowid=t.fk_pays";
4670
        $sql .= " WHERE c.code = '" . $mysoc->country_code . "' AND t.active = 1 AND t.taux=(";
4671
        $sql .= "  SELECT max(tt.taux) FROM " . MAIN_DB_PREFIX . "c_tva as tt inner join " . MAIN_DB_PREFIX . "c_country as c ON c.rowid=tt.fk_pays";
4672
        $sql .= "  WHERE c.code = '" . $mysoc->country_code . "' AND tt.active = 1";
4673
        $sql .= "  )";
4674
4675
        $resql = $db->query($sql);
4676
        if ($resql) {
4677
            $obj = $db->fetch_object($resql);
4678
            if ($local == 1)
4679
                return $obj->localtax1;
4680
            elseif ($local == 2)
4681
                return $obj->localtax2;
4682
        }
4683
4684
        return 0;
4685
    }
4686
4687
    /**
4688
     *  Get vat main information from Id.
4689
     *  You can call getLocalTaxesFromRate after to get other fields.
4690
     *
4691
     *  @param	int|string  $vatrate		    VAT ID or Rate. Value can be value or the string with code into parenthesis or rowid if $firstparamisid is 1. Example: '8.5' or '8.5 (8.5NPR)' or 123.
4692
     *  @param	Societe	    $buyer         		Company object
4693
     *  @param	Societe	    $seller        		Company object
4694
     *  @param  int         $firstparamisid     1 if first param is id into table (use this if you can)
4695
     *  @return	array       	  				array('rowid'=> , 'code'=> ...)
4696
     *  @see getLocalTaxesFromRate
4697
     */
4698
    static function getTaxesFromId($vatrate, $buyer = null, $seller = null, $firstparamisid = 1)
4699
    {
4700
        // global $db, $mysoc;
4701
4702
        AlDolUtils::dol_syslog("getTaxesFromId vat id or rate = " . $vatrate);
4703
4704
// Search local taxes
4705
        $sql = "SELECT t.rowid, t.code, t.taux as rate, t.recuperableonly as npr, t.accountancy_code_sell, t.accountancy_code_buy";
4706
        $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t";
4707
        if ($firstparamisid)
4708
            $sql .= " WHERE t.rowid = " . (int) $vatrate;
4709
        else {
4710
            $vatratecleaned = $vatrate;
4711
            $vatratecode = '';
4712
            if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) {      // If vat is "xx (yy)"
4713
                $vatratecleaned = $reg[1];
4714
                $vatratecode = $reg[2];
4715
            }
4716
4717
            $sql .= ", " . MAIN_DB_PREFIX . "c_country as c";
4718
            /* if ($mysoc->country_code == 'ES') $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$buyer->country_code."'";    // vat in spain use the buyer country ??
4719
              else $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$seller->country_code."'"; */
4720
            $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $seller->country_code . "'";
4721
            $sql .= " AND t.taux = " . ((float) $vatratecleaned) . " AND t.active = 1";
4722
            if ($vatratecode)
4723
                $sql .= " AND t.code = '" . $vatratecode . "'";
4724
        }
4725
4726
        $resql = $db->query($sql);
4727
        if ($resql) {
4728
            $obj = $db->fetch_object($resql);
4729
            if ($obj)
4730
                return array('rowid' => $obj->rowid, 'code' => $obj->code, 'rate' => $obj->rate, 'npr' => $obj->npr, 'accountancy_code_sell' => $obj->accountancy_code_sell, 'accountancy_code_buy' => $obj->accountancy_code_buy);
4731
            else
4732
                return array();
4733
        } else
4734
            dol_print_error($db);
4735
4736
        return array();
4737
    }
4738
4739
    /**
4740
     *  Get type and rate of localtaxes for a particular vat rate/country of a thirdparty.
4741
     *  This does not take into account the seller setup if subject to vat or not, only country.
4742
     *  TODO
4743
     *  This static function is ALSO called to retrieve type for building PDF. Such call of static function must be removed.
4744
     *  Instead this static function must be called when adding a line to get the array of localtax and type, and then
4745
     *  provide it to the static function calcul_price_total.
4746
     *
4747
     *  @param	int|string  $vatrate			VAT ID or Rate+Code. Value can be value or the string with code into parenthesis or rowid if $firstparamisid is 1. Example: '8.5' or '8.5 (8.5NPR)' or 123.
4748
     *  @param	int		    $local              Number of localtax (1 or 2, or 0 to return 1 & 2)
4749
     *  @param	Societe	    $buyer         		Company object
4750
     *  @param	Societe	    $seller        		Company object
4751
     *  @param  int         $firstparamisid     1 if first param is ID into table instead of Rate+code (use this if you can)
4752
     *  @return	array    	    				array(localtax_type1(1-6/0 if not found), rate localtax1, localtax_type2, rate localtax2, accountancycodecust, accountancycodesupp)
4753
     *  @see getTaxesFromId
4754
     */
4755
    static function getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid = 0)
4756
    {
4757
        // global $db, $mysoc;
4758
4759
        AlDolUtils::dol_syslog("getLocalTaxesFromRate vatrate=" . $vatrate . " local=" . $local);
4760
4761
// Search local taxes
4762
        $sql = "SELECT t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type, t.accountancy_code_sell, t.accountancy_code_buy";
4763
        $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t";
4764
        if ($firstparamisid)
4765
            $sql .= " WHERE t.rowid = " . (int) $vatrate;
4766
        else {
4767
            $vatratecleaned = $vatrate;
4768
            $vatratecode = '';
4769
            if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) {      // If vat is "x.x (yy)"
4770
                $vatratecleaned = $reg[1];
4771
                $vatratecode = $reg[2];
4772
            }
4773
4774
            $sql .= ", " . MAIN_DB_PREFIX . "c_country as c";
4775
            if ($mysoc->country_code == 'ES')
4776
                $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $buyer->country_code . "'";    // local tax in spain use the buyer country ??
4777
            else
4778
                $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $seller->country_code . "'";
4779
            $sql .= " AND t.taux = " . ((float) $vatratecleaned) . " AND t.active = 1";
4780
            if ($vatratecode)
4781
                $sql .= " AND t.code = '" . $vatratecode . "'";
4782
        }
4783
4784
        $resql = $db->query($sql);
4785
        if ($resql) {
4786
            $obj = $db->fetch_object($resql);
4787
            if ($local == 1) {
4788
                return array($obj->localtax1_type, get_localtax($vatrate, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
4789
            } elseif ($local == 2) {
4790
                return array($obj->localtax2_type, get_localtax($vatrate, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
4791
            } else {
4792
                return array($obj->localtax1_type, get_localtax($vatrate, 1, $buyer, $seller), $obj->localtax2_type, get_localtax($vatrate, 2, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
4793
            }
4794
        }
4795
4796
        return 0;
4797
    }
4798
4799
    /**
4800
     * 	Return vat rate of a product in a particular selling country or default country vat if product is unknown
4801
     *  Function called by get_default_tva
4802
     *
4803
     *  @param	int			$idprod          	Id of product or 0 if not a predefined product
4804
     *  @param  Societe		$thirdparty_seller  Thirdparty with a ->country_code defined (FR, US, IT, ...)
4805
     * 	@param	int			$idprodfournprice	Id product_fournisseur_price (for "supplier" proposal/order/invoice)
4806
     *  @return float|string   				    Vat rate to use with format 5.0 or '5.0 (XXX)'
4807
     *  @see get_product_localtax_for_country
4808
     */
4809
    static function get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice = 0)
4810
    {
4811
        // global $db, Globals::$conf, $mysoc;
4812
4813
        require_once DOL_BASE_PATH . '/product/class/product.class.php';
4814
4815
        $ret = 0;
4816
        $found = 0;
4817
4818
        if ($idprod > 0) {
4819
// Load product
4820
            $product = new Product($db);
4821
            $result = $product->fetch($idprod);
4822
4823
            if ($mysoc->country_code == $thirdparty_seller->country_code) { // If selling country is ours
4824
                if ($idprodfournprice > 0) {     // We want vat for product for a "supplier" object
4825
                    $product->get_buyprice($idprodfournprice, 0, 0, 0);
4826
                    $ret = $product->vatrate_supplier;
4827
                    if ($product->default_vat_code)
4828
                        $ret .= ' (' . $product->default_vat_code . ')';
4829
                }
4830
                else {
4831
                    $ret = $product->tva_tx;    // Default vat of product we defined
4832
                    if ($product->default_vat_code)
4833
                        $ret .= ' (' . $product->default_vat_code . ')';
4834
                }
4835
                $found = 1;
4836
            }
4837
            else {
4838
// TODO Read default product vat according to countrycode and product. Vat for couple countrycode/product is a feature not implemeted yet.
4839
// May be usefull/required if hidden option SERVICE_ARE_ECOMMERCE_200238EC is on
4840
            }
4841
        }
4842
4843
        if (!$found) {
4844
            if (empty(Globals::$conf->global->MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS)) {
4845
// If vat of product for the country not found or not defined, we return the first higher vat of country.
4846
                $sql = "SELECT t.taux as vat_rate, t.code as default_vat_code";
4847
                $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t, " . MAIN_DB_PREFIX . "c_country as c";
4848
                $sql .= " WHERE t.active=1 AND t.fk_pays = c.rowid AND c.code='" . $thirdparty_seller->country_code . "'";
4849
                $sql .= " ORDER BY t.taux DESC, t.code ASC, t.recuperableonly ASC";
4850
                $sql .= $db->plimit(1);
4851
4852
                $resql = $db->query($sql);
4853
                if ($resql) {
4854
                    $obj = $db->fetch_object($resql);
4855
                    if ($obj) {
4856
                        $ret = $obj->vat_rate;
4857
                        if ($obj->default_vat_code)
4858
                            $ret .= ' (' . $obj->default_vat_code . ')';
4859
                    }
4860
                    $db->free($sql);
4861
                } else
4862
                    dol_print_error($db);
4863
            } else
4864
                $ret = Globals::$conf->global->MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS;    // Forced value if autodetect fails
4865
        }
4866
4867
        AlDolUtils::dol_syslog("get_product_vat_for_country: ret=" . $ret);
4868
        return $ret;
4869
    }
4870
4871
    /**
4872
     * 	Return localtax vat rate of a product in a particular selling country or default country vat if product is unknown
4873
     *
4874
     *  @param	int		$idprod         		Id of product
4875
     *  @param  int		$local          		1 for localtax1, 2 for localtax 2
4876
     *  @param  Societe	$thirdparty_seller    	Thirdparty with a ->country_code defined (FR, US, IT, ...)
4877
     *  @return int             				<0 if KO, Vat rate if OK
4878
     *  @see get_product_vat_for_country
4879
     */
4880
    static function get_product_localtax_for_country($idprod, $local, $thirdparty_seller)
4881
    {
4882
        // global $db, $mysoc;
4883
4884
        if (!class_exists('Product')) {
4885
            require_once DOL_BASE_PATH . 'product/class/product.class.php';
4886
        }
4887
4888
        $ret = 0;
4889
        $found = 0;
4890
4891
        if ($idprod > 0) {
4892
// Load product
4893
            $product = new Product($db);
4894
            $result = $product->fetch($idprod);
4895
4896
            if ($mysoc->country_code == $thirdparty_seller->country_code) { // If selling country is ours
4897
                /* Not defined yet, so we don't use this
4898
                  if ($local==1) $ret=$product->localtax1_tx;
4899
                  elseif ($local==2) $ret=$product->localtax2_tx;
4900
                  $found=1;
4901
                 */
4902
            } else {
4903
// TODO Read default product vat according to countrycode and product
4904
            }
4905
        }
4906
4907
        if (!$found) {
4908
// If vat of product for the country not found or not defined, we return higher vat of country.
4909
            $sql = "SELECT taux as vat_rate, localtax1, localtax2";
4910
            $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t, " . MAIN_DB_PREFIX . "c_country as c";
4911
            $sql .= " WHERE t.active=1 AND t.fk_pays = c.rowid AND c.code='" . $thirdparty_seller->country_code . "'";
4912
            $sql .= " ORDER BY t.taux DESC, t.recuperableonly ASC";
4913
            $sql .= $db->plimit(1);
4914
4915
            $resql = $db->query($sql);
4916
            if ($resql) {
4917
                $obj = $db->fetch_object($resql);
4918
                if ($obj) {
4919
                    if ($local == 1)
4920
                        $ret = $obj->localtax1;
4921
                    elseif ($local == 2)
4922
                        $ret = $obj->localtax2;
4923
                }
4924
            } else
4925
                dol_print_error($db);
4926
        }
4927
4928
        AlDolUtils::dol_syslog("get_product_localtax_for_country: ret=" . $ret);
4929
        return $ret;
4930
    }
4931
4932
    /**
4933
     * 	Function that return vat rate of a product line (according to seller, buyer and product vat rate)
4934
     *   Si vendeur non assujeti a TVA, TVA par defaut=0. Fin de regle.
4935
     * 	 Si le (pays vendeur = pays acheteur) alors TVA par defaut=TVA du produit vendu. Fin de regle.
4936
     * 	 Si (vendeur et acheteur dans Communaute europeenne) et (bien vendu = moyen de transports neuf comme auto, bateau, avion) alors TVA par defaut=0 (La TVA doit etre paye par acheteur au centre d'impots de son pays et non au vendeur). Fin de regle.
4937
     * 	 Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = particulier ou entreprise sans num TVA intra) alors TVA par defaut=TVA du produit vendu. Fin de regle
4938
     * 	 Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = entreprise avec num TVA) intra alors TVA par defaut=0. Fin de regle
4939
     * 	 Sinon TVA proposee par defaut=0. Fin de regle.
4940
     *
4941
     * 	@param	Societe		$thirdparty_seller    	Objet societe vendeuse
4942
     * 	@param  Societe		$thirdparty_buyer   	Objet societe acheteuse
4943
     * 	@param  int			$idprod					Id product
4944
     * 	@param	int			$idprodfournprice		Id product_fournisseur_price (for supplier order/invoice)
4945
     * 	@return float|string   				      	Vat rate to use with format 5.0 or '5.0 (XXX)', -1 if we can't guess it
4946
     *  @see get_default_npr, get_default_localtax
4947
     */
4948
    static function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
4949
    {
4950
        // global Globals::$conf;
4951
4952
        require_once DOL_BASE_PATH . '/core/lib/company.lib.php';
4953
4954
// Note: possible values for tva_assuj are 0/1 or franchise/reel
4955
        $seller_use_vat = ((is_numeric($thirdparty_seller->tva_assuj) && !$thirdparty_seller->tva_assuj) || (!is_numeric($thirdparty_seller->tva_assuj) && $thirdparty_seller->tva_assuj == 'franchise')) ? 0 : 1;
4956
4957
        $seller_country_code = $thirdparty_seller->country_code;
4958
        $seller_in_cee = isInEEC($thirdparty_seller);
4959
4960
        $buyer_country_code = $thirdparty_buyer->country_code;
4961
        $buyer_in_cee = isInEEC($thirdparty_buyer);
4962
4963
        AlDolUtils::dol_syslog("get_default_tva: seller use vat=" . $seller_use_vat . ", seller country=" . $seller_country_code . ", seller in cee=" . $seller_in_cee . ", buyer vat number=" . $thirdparty_buyer->tva_intra . " buyer country=" . $buyer_country_code . ", buyer in cee=" . $buyer_in_cee . ", idprod=" . $idprod . ", idprodfournprice=" . $idprodfournprice . ", SERVICE_ARE_ECOMMERCE_200238EC=" . (!empty(Globals::$conf->global->SERVICES_ARE_ECOMMERCE_200238EC) ? Globals::$conf->global->SERVICES_ARE_ECOMMERCE_200238EC : ''));
4964
4965
// If services are eServices according to EU Council Directive 2002/38/EC (http://ec.europa.eu/taxation_customs/taxation/vat/traders/e-commerce/article_1610_en.htm)
4966
// we use the buyer VAT.
4967
        if (!empty(Globals::$conf->global->SERVICE_ARE_ECOMMERCE_200238EC)) {
4968
            if ($seller_in_cee && $buyer_in_cee && !$thirdparty_buyer->isACompany()) {
4969
//print 'VATRULE 0';
4970
                return get_product_vat_for_country($idprod, $thirdparty_buyer, $idprodfournprice);
4971
            }
4972
        }
4973
4974
// If seller does not use VAT
4975
        if (!$seller_use_vat) {
4976
//print 'VATRULE 1';
4977
            return 0;
4978
        }
4979
4980
// Le test ci-dessus ne devrait pas etre necessaire. Me signaler l'exemple du cas juridique concerne si le test suivant n'est pas suffisant.
4981
// Si le (pays vendeur = pays acheteur) alors la TVA par defaut=TVA du produit vendu. Fin de regle.
4982
        if (($seller_country_code == $buyer_country_code) || (in_array($seller_country_code, array('FR,MC')) && in_array($buyer_country_code, array('FR', 'MC')))) { // Warning ->country_code not always defined
4983
//print 'VATRULE 2';
4984
            return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
4985
        }
4986
4987
// Si (vendeur et acheteur dans Communaute europeenne) et (bien vendu = moyen de transports neuf comme auto, bateau, avion) alors TVA par defaut=0 (La TVA doit etre paye par l'acheteur au centre d'impots de son pays et non au vendeur). Fin de regle.
4988
// Not supported
4989
// Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = entreprise) alors TVA par defaut=0. Fin de regle
4990
// Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = particulier) alors TVA par defaut=TVA du produit vendu. Fin de regle
4991
        if (($seller_in_cee && $buyer_in_cee)) {
4992
            $isacompany = $thirdparty_buyer->isACompany();
4993
            if ($isacompany) {
4994
//print 'VATRULE 3';
4995
                return 0;
4996
            } else {
4997
//print 'VATRULE 4';
4998
                return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
4999
            }
5000
        }
5001
5002
// Si (vendeur en France et acheteur hors Communaute europeenne et acheteur particulier) alors TVA par defaut=TVA du produit vendu. Fin de regle
5003
        if (!empty(Globals::$conf->global->MAIN_USE_VAT_OF_PRODUCT_FOR_INDIVIDUAL_CUSTOMER_OUT_OF_EEC) && empty($buyer_in_cee) && !$thirdparty_buyer->isACompany()) {
5004
            return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
5005
        }
5006
5007
// Sinon la TVA proposee par defaut=0. Fin de regle.
5008
// Rem: Cela signifie qu'au moins un des 2 est hors Communaute europeenne et que le pays differe
5009
//print 'VATRULE 5';
5010
        return 0;
5011
    }
5012
5013
    /**
5014
     * 	Fonction qui renvoie si tva doit etre tva percue recuperable
5015
     *
5016
     * 	@param	Societe		$thirdparty_seller    	Thirdparty seller
5017
     * 	@param  Societe		$thirdparty_buyer   	Thirdparty buyer
5018
     *  @param  int			$idprod                 Id product
5019
     *  @param	int			$idprodfournprice		Id supplier price for product
5020
     * 	@return float       			        	0 or 1
5021
     *  @see get_default_tva, get_default_localtax
5022
     */
5023
    static function get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
5024
    {
5025
        // global $db;
5026
5027
        if ($idprodfournprice > 0) {
5028
            if (!class_exists('ProductFournisseur'))
5029
                require_once DOL_BASE_PATH . '/fourn/class/fournisseur.product.class.php';
5030
            $prodprice = new ProductFournisseur($db);
5031
            $prodprice->fetch_product_fournisseur_price($idprodfournprice);
5032
            return $prodprice->fourn_tva_npr;
5033
        }
5034
        elseif ($idprod > 0) {
5035
            if (!class_exists('Product'))
5036
                require_once DOL_BASE_PATH . 'product/class/product.class.php';
5037
            $prod = new Product($db);
5038
            $prod->fetch($idprod);
5039
            return $prod->tva_npr;
5040
        }
5041
5042
        return 0;
5043
    }
5044
5045
    /**
5046
     * 	Function that return localtax of a product line (according to seller, buyer and product vat rate)
5047
     *   Si vendeur non assujeti a TVA, TVA par defaut=0. Fin de regle.
5048
     * 	 Si le (pays vendeur = pays acheteur) alors TVA par defaut=TVA du produit vendu. Fin de regle.
5049
     * 	 Sinon TVA proposee par defaut=0. Fin de regle.
5050
     *
5051
     * 	@param	Societe		$thirdparty_seller    	Thirdparty seller
5052
     * 	@param  Societe		$thirdparty_buyer   	Thirdparty buyer
5053
     *  @param	int			$local					Localtax to process (1 or 2)
5054
     * 	@param  int			$idprod					Id product
5055
     * 	@return integer        				       	localtax, -1 si ne peut etre determine
5056
     *  @see get_default_tva, get_default_npr
5057
     */
5058
    static function get_default_localtax($thirdparty_seller, $thirdparty_buyer, $local, $idprod = 0)
5059
    {
5060
        // global $mysoc;
5061
5062
        if (!is_object($thirdparty_seller))
5063
            return -1;
5064
        if (!is_object($thirdparty_buyer))
5065
            return -1;
5066
5067
        if ($local == 1) { // Localtax 1
5068
            if ($mysoc->country_code == 'ES') {
5069
                if (is_numeric($thirdparty_buyer->localtax1_assuj) && !$thirdparty_buyer->localtax1_assuj)
5070
                    return 0;
5071
            }
5072
            else {
5073
// Si vendeur non assujeti a Localtax1, localtax1 par default=0
5074
                if (is_numeric($thirdparty_seller->localtax1_assuj) && !$thirdparty_seller->localtax1_assuj)
5075
                    return 0;
5076
                if (!is_numeric($thirdparty_seller->localtax1_assuj) && $thirdparty_seller->localtax1_assuj == 'localtax1off')
5077
                    return 0;
5078
            }
5079
        }
5080
        elseif ($local == 2) { //I Localtax 2
5081
// Si vendeur non assujeti a Localtax2, localtax2 par default=0
5082
            if (is_numeric($thirdparty_seller->localtax2_assuj) && !$thirdparty_seller->localtax2_assuj)
5083
                return 0;
5084
            if (!is_numeric($thirdparty_seller->localtax2_assuj) && $thirdparty_seller->localtax2_assuj == 'localtax2off')
5085
                return 0;
5086
        }
5087
5088
        if ($thirdparty_seller->country_code == $thirdparty_buyer->country_code) {
5089
            return get_product_localtax_for_country($idprod, $local, $thirdparty_seller);
5090
        }
5091
5092
        return 0;
5093
    }
5094
5095
    /**
5096
     * 	Return yes or no in current language
5097
     *
5098
     * 	@param	string	$yesno			Value to test (1, 'yes', 'true' or 0, 'no', 'false')
5099
     * 	@param	integer	$case			1=Yes/No, 0=yes/no, 2=Disabled checkbox, 3=Disabled checkbox + Yes/No
5100
     * 	@param	int		$color			0=texte only, 1=Text is formated with a color font style ('ok' or 'error'), 2=Text is formated with 'ok' color.
5101
     * 	@return	string					HTML string
5102
     */
5103
    static function yn($yesno, $case = 1, $color = 0)
5104
    {
5105
        // global Globals::$langs;
5106
        $result = 'unknown';
5107
        $classname = '';
5108
        if ($yesno == 1 || strtolower($yesno) == 'yes' || strtolower($yesno) == 'true') {  // A mettre avant test sur no a cause du == 0
5109
            $result = Globals::$langs->trans('yes');
5110
            if ($case == 1 || $case == 3)
5111
                $result = Globals::$langs->trans("Yes");
5112
            if ($case == 2)
5113
                $result = '<input type="checkbox" value="1" checked disabled>';
5114
            if ($case == 3)
5115
                $result = '<input type="checkbox" value="1" checked disabled> ' . $result;
5116
5117
            $classname = 'ok';
5118
        }
5119
        elseif ($yesno == 0 || strtolower($yesno) == 'no' || strtolower($yesno) == 'false') {
5120
            $result = Globals::$langs->trans("no");
5121
            if ($case == 1 || $case == 3)
5122
                $result = Globals::$langs->trans("No");
5123
            if ($case == 2)
5124
                $result = '<input type="checkbox" value="0" disabled>';
5125
            if ($case == 3)
5126
                $result = '<input type="checkbox" value="0" disabled> ' . $result;
5127
5128
            if ($color == 2)
5129
                $classname = 'ok';
5130
            else
5131
                $classname = 'error';
5132
        }
5133
        if ($color)
5134
            return '<font class="' . $classname . '">' . $result . '</font>';
5135
        return $result;
5136
    }
5137
5138
    /**
5139
     * 	Return a path to have a the directory according to object where files are stored.
5140
     *  New usage:       Globals::$conf->module->multidir_output[$object->entity].'/'.get_exdir(0, 0, 0, 1, $object, $modulepart)
5141
     *         or:       Globals::$conf->module->dir_output.'/'.get_exdir(0, 0, 0, 1, $object, $modulepart)     if multidir_output not defined.
5142
     *  Example our with new usage:       $object is invoice -> 'INYYMM-ABCD'
5143
     *  Example our with old usage:       '015' with level 3->"0/1/5/", '015' with level 1->"5/", 'ABC-1' with level 3 ->"0/0/1/"
5144
     *
5145
     * 	@param	string	$num            Id of object (deprecated, $object will be used in future)
5146
     * 	@param  int		$level		    Level of subdirs to return (1, 2 or 3 levels). (deprecated,// global option will be used in future)
5147
     * 	@param	int		$alpha		    0=Keep number only to forge path, 1=Use alpha part afer the - (By default, use 0). (deprecated,// global option will be used in future)
5148
     *  @param  int		$withoutslash   0=With slash at end (except if '/', we return ''), 1=without slash at end
5149
     *  @param	Object	$object			Object
5150
     *  @param	string	$modulepart		Type of object ('invoice_supplier, 'donation', 'invoice', ...')
5151
     *  @return	string					Dir to use ending. Example '' or '1/' or '1/2/'
5152
     */
5153
    static function get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart)
5154
    {
5155
        // global Globals::$conf;
5156
5157
        $path = '';
5158
5159
        $arrayforoldpath = array('cheque', 'user', 'category', 'holiday', 'supplier_invoice', 'invoice_supplier', 'mailing', 'supplier_payment');
5160
        if (!empty(Globals::$conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO))
5161
            $arrayforoldpath[] = 'product';
5162
        if (!empty($level) && in_array($modulepart, $arrayforoldpath)) {
5163
// This part should be removed once all code is using "get_exdir" to forge path, with all parameters provided.
5164
            if (empty($alpha))
5165
                $num = preg_replace('/([^0-9])/i', '', $num);
5166
            else
5167
                $num = preg_replace('/^.*\-/i', '', $num);
5168
            $num = substr("000" . $num, -$level);
5169
            if ($level == 1)
5170
                $path = substr($num, 0, 1);
5171
            if ($level == 2)
5172
                $path = substr($num, 1, 1) . '/' . substr($num, 0, 1);
5173
            if ($level == 3)
5174
                $path = substr($num, 2, 1) . '/' . substr($num, 1, 1) . '/' . substr($num, 0, 1);
5175
        }
5176
        else {
5177
// TODO
5178
// We will enhance here a common way of forging path for document storage
5179
// Here, object->id, object->ref and modulepart are required.
5180
//var_dump($modulepart);
5181
            if (in_array($modulepart, array('thirdparty', 'contact', 'member', 'propal', 'proposal', 'commande', 'order', 'facture', 'invoice',
5182
                    'supplier_order', 'supplier_proposal', 'shipment', 'contract', 'expensereport'))) {
5183
                $path = ($object->ref ? $object->ref : $object->id);
5184
            }
5185
        }
5186
5187
        if (empty($withoutslash) && !empty($path))
5188
            $path .= '/';
5189
5190
        return $path;
5191
    }
5192
5193
    /**
5194
     * 	Creation of a directory (this can create recursive subdir)
5195
     *
5196
     * 	@param	string	$dir		Directory to create (Separator must be '/'. Example: '/mydir/mysubdir')
5197
     * 	@param	string	$dataroot	Data root directory (To avoid having the data root in the loop. Using this will also lost the warning on first dir PHP has no permission when open_basedir is used)
5198
     *  @param	int		$newmask	Mask for new file (Defaults to Globals::$conf->global->MAIN_UMASK or 0755 if unavailable). Example: '0444'
5199
     * 	@return int         		< 0 if KO, 0 = already exists, > 0 if OK
5200
     */
5201
    static function dol_mkdir($dir, $dataroot = '', $newmask = null)
5202
    {
5203
        // global Globals::$conf;
5204
5205
        AlDolUtils::dol_syslog("functions.lib::dol_mkdir: dir=" . $dir, LOG_INFO);
5206
5207
        $dir_osencoded = dol_osencode($dir);
5208
        if (@is_dir($dir_osencoded))
5209
            return 0;
5210
5211
        $nberr = 0;
5212
        $nbcreated = 0;
5213
5214
        $ccdir = '';
5215
        if (!empty($dataroot)) {
5216
// Remove data root from loop
5217
            $dir = str_replace($dataroot . '/', '', $dir);
5218
            $ccdir = $dataroot . '/';
5219
        }
5220
5221
        $cdir = explode("/", $dir);
5222
        $num = count($cdir);
5223
        for ($i = 0; $i < $num; $i++) {
5224
            if ($i > 0)
5225
                $ccdir .= '/' . $cdir[$i];
5226
            else
5227
                $ccdir .= $cdir[$i];
5228
            if (preg_match("/^.:$/", $ccdir, $regs))
5229
                continue; // Si chemin Windows incomplet, on poursuit par rep suivant
5230
5231
5232
5233
5234
5235
5236
5237
5238
5239
5240
5241
5242
5243
5244
5245
5246
5247
5248
5249
5250
5251
5252
5253
5254
5255
5256
5257
5258
5259
5260
5261
5262
5263
5264
5265
5266
5267
5268
5269
5270
5271
5272
5273
5274
5275
5276
5277
5278
5279
5280
5281
5282
                
5283
// Attention, le is_dir() peut echouer bien que le rep existe.
5284
// (ex selon config de open_basedir)
5285
            if ($ccdir) {
5286
                $ccdir_osencoded = dol_osencode($ccdir);
5287
                if (!@is_dir($ccdir_osencoded)) {
5288
                    AlDolUtils::dol_syslog("functions.lib::dol_mkdir: Directory '" . $ccdir . "' does not exists or is outside open_basedir PHP setting.", LOG_DEBUG);
5289
5290
                    umask(0);
5291
                    $dirmaskdec = octdec($newmask);
5292
                    if (empty($newmask)) {
5293
                        $dirmaskdec = empty(Globals::$conf->global->MAIN_UMASK) ? octdec('0755') : octdec(Globals::$conf->global->MAIN_UMASK);
5294
                    }
5295
                    $dirmaskdec |= octdec('0111');  // Set x bit required for directories
5296
                    if (!@mkdir($ccdir_osencoded, $dirmaskdec)) {
5297
                        // Si le is_dir a renvoye une fausse info, alors on passe ici.
5298
                        AlDolUtils::dol_syslog("functions.lib::dol_mkdir: Fails to create directory '" . $ccdir . "' or directory already exists.", LOG_WARNING);
5299
                        $nberr++;
5300
                    } else {
5301
                        AlDolUtils::dol_syslog("functions.lib::dol_mkdir: Directory '" . $ccdir . "' created", LOG_DEBUG);
5302
                        $nberr = 0; // On remet a zero car si on arrive ici, cela veut dire que les echecs precedents peuvent etre ignore
5303
                        $nbcreated++;
5304
                    }
5305
                } else {
5306
                    $nberr = 0; // On remet a zero car si on arrive ici, cela veut dire que les echecs precedents peuvent etre ignores
5307
                }
5308
            }
5309
        }
5310
        return ($nberr ? -$nberr : $nbcreated);
5311
    }
5312
5313
    /**
5314
     * 	Return picto saying a field is required
5315
     *
5316
     * 	@return  string		Chaine avec picto obligatoire
5317
     */
5318
    static function picto_required()
5319
    {
5320
        return '<span class="fieldrequired">*</span>';
5321
    }
5322
5323
    /**
5324
     * 	Clean a string from all HTML tags and entities.
5325
     *  This static function differs from strip_tags because:
5326
     *  - <br> are replaced with \n if removelinefeed=0 or 1
5327
     *  - if entities are found, they are decoded BEFORE the strip
5328
     *  - you can decide to convert line feed into a space
5329
     *
5330
     * 	@param	string	$stringtoclean		String to clean
5331
     * 	@param	integer	$removelinefeed		1=Replace all new lines by 1 space, 0=Only ending new lines are removed others are replaced with \n, 2=Ending new lines are removed but others are kept with a same number of \n than nb of <br> when there is both "...<br>\n..."
5332
     *  @param  string	$pagecodeto      	Encoding of input/output string
5333
     *  @param	integer	$strip_tags			0=Use internal strip, 1=Use strip_tags() php static function (bugged when text contains a < char that is not for a html tag)
5334
     * 	@return string	    				String cleaned
5335
     *
5336
     * 	@see	DolUtils::dol_escape_htmltag strip_tags dol_string_onlythesehtmltags dol_string_neverthesehtmltags
5337
     */
5338
    static function dol_string_nohtmltag($stringtoclean, $removelinefeed = 1, $pagecodeto = 'UTF-8', $strip_tags = 0)
5339
    {
5340
        if ($removelinefeed == 2)
5341
            $stringtoclean = preg_replace('/<br[^>]*>\n+/ims', '<br>', $stringtoclean);
5342
        $temp = preg_replace('/<br[^>]*>/i', "\n", $stringtoclean);
5343
5344
        if ($strip_tags) {
5345
            $temp = strip_tags($temp);
5346
        } else {
5347
            $pattern = "/<[^<>]+>/";
5348
// Exemple of $temp: <a href="/myurl" title="<u>A title</u>">0000-021</a>
5349
            $temp = preg_replace($pattern, "", $temp);    // pass 1
5350
// $temp after pass 1: <a href="/myurl" title="A title">0000-021
5351
            $temp = preg_replace($pattern, "", $temp);    // pass 2
5352
// $temp after pass 2: 0000-021
5353
        }
5354
5355
        $temp = dol_html_entity_decode($temp, ENT_COMPAT, $pagecodeto);
5356
5357
// Supprime aussi les retours
5358
        if ($removelinefeed == 1)
5359
            $temp = str_replace(array("\r\n", "\r", "\n"), " ", $temp);
5360
5361
// et les espaces doubles
5362
        while (strpos($temp, "  ")) {
5363
            $temp = str_replace("  ", " ", $temp);
5364
        }
5365
5366
        return trim($temp);
5367
    }
5368
5369
    /**
5370
     * 	Clean a string to keep only desirable HTML tags.
5371
     *
5372
     * 	@param	string	$stringtoclean		String to clean
5373
     * 	@return string	    				String cleaned
5374
     *
5375
     * 	@see	DolUtils::dol_escape_htmltag strip_tags dol_string_nohtmltag dol_string_neverthesehtmltags
5376
     */
5377
    static function dol_string_onlythesehtmltags($stringtoclean)
5378
    {
5379
        $allowed_tags = array(
5380
            "html", "head", "meta", "body", "article", "a", "b", "br", "div", "em", "font", "img", "ins", "hr", "i", "li", "link",
5381
            "ol", "p", "s", "section", "span", "strong", "title",
5382
            "table", "tr", "th", "td", "u", "ul"
5383
        );
5384
5385
        $allowed_tags_string = join("><", $allowed_tags);
5386
        $allowed_tags_string = preg_replace('/^>/', '', $allowed_tags_string);
5387
        $allowed_tags_string = preg_replace('/<$/', '', $allowed_tags_string);
5388
5389
        $temp = strip_tags($stringtoclean, $allowed_tags_string);
5390
5391
        return $temp;
5392
    }
5393
5394
    /**
5395
     * 	Clean a string from some undesirable HTML tags.
5396
     *
5397
     * 	@param	string	$stringtoclean		String to clean
5398
     *  @param	array	$disallowed_tags	Array of tags not allowed
5399
     * 	@return string	    				String cleaned
5400
     *
5401
     * 	@see	DolUtils::dol_escape_htmltag strip_tags dol_string_nohtmltag dol_string_onlythesehtmltags
5402
     */
5403
    static function dol_string_neverthesehtmltags($stringtoclean, $disallowed_tags = array('textarea'))
5404
    {
5405
        $temp = $stringtoclean;
5406
        foreach ($disallowed_tags as $tagtoremove) {
5407
            $temp = preg_replace('/<\/?' . $tagtoremove . '>/', '', $temp);
5408
            $temp = preg_replace('/<\/?' . $tagtoremove . '\s+[^>]*>/', '', $temp);
5409
        }
5410
        return $temp;
5411
    }
5412
5413
    /**
5414
     * Return first line of text. Cut will depends if content is HTML or not.
5415
     *
5416
     * @param 	string	$text		Input text
5417
     * @param	int		$nboflines  Nb of lines to get (default is 1 = first line only)
5418
     * @return	string				Output text
5419
     * @see dol_nboflines_bis, dol_string_nohtmltag, AlDolUtils::dol_escape_htmltag
5420
     */
5421
    static function dolGetFirstLineOfText($text, $nboflines = 1)
5422
    {
5423
        if ($nboflines == 1) {
5424
            if (AlDolUtils::dol_textishtml($text)) {
5425
                $firstline = preg_replace('/<br[^>]*>.*$/s', '', $text);  // The s pattern modifier means the . can match newline characters
5426
                $firstline = preg_replace('/<div[^>]*>.*$/s', '', $firstline); // The s pattern modifier means the . can match newline characters
5427
            } else {
5428
                $firstline = preg_replace('/[\n\r].*/', '', $text);
5429
            }
5430
            return $firstline . ((strlen($firstline) != strlen($text)) ? '...' : '');
5431
        } else {
5432
            $ishtml = 0;
5433
            if (AlDolUtils::dol_textishtml($text)) {
5434
                $text = preg_replace('/\n/', '', $text);
5435
                $ishtml = 1;
5436
                $repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
5437
            } else {
5438
                $repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
5439
            }
5440
5441
            $text = strtr($text, $repTable);
5442
            if ($charset == 'UTF-8') {
5443
                $pattern = '/(<br[^>]*>)/Uu';
5444
            } // /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
5445
            else
5446
                $pattern = '/(<br[^>]*>)/U';       // /U is to have UNGREEDY regex to limit to one html tag.
5447
            $a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
5448
5449
            $firstline = '';
5450
            $i = 0;
5451
            $nba = count($a); // 2x nb of lines in $a because $a contains also a line for each new line separator
5452
            while (($i < $nba) && ($i < ($nboflines * 2))) {
5453
                if ($i % 2 == 0)
5454
                    $firstline .= $a[$i];
5455
                elseif (($i < (($nboflines * 2) - 1)) && ($i < ($nba - 1)))
5456
                    $firstline .= ($ishtml ? "<br>\n" : "\n");
5457
                $i++;
5458
            }
5459
            unset($a);
5460
            return $firstline . (($i < $nba) ? '...' : '');
5461
        }
5462
    }
5463
5464
    /**
5465
     * Replace CRLF in string with a HTML BR tag
5466
     *
5467
     * @param	string	$stringtoencode		String to encode
5468
     * @param	int     $nl2brmode			0=Adding br before \n, 1=Replacing \n by br
5469
     * @param   bool	$forxml             false=Use <br>, true=Use <br />
5470
     * @return	string						String encoded
5471
     * @see dol_nboflines, dolGetFirstLineOfText
5472
     */
5473
    static function dol_nl2br($stringtoencode, $nl2brmode = 0, $forxml = false)
5474
    {
5475
        if (!$nl2brmode) {
5476
            return nl2br($stringtoencode, $forxml);
5477
        } else {
5478
            $ret = preg_replace('/(\r\n|\r|\n)/i', ($forxml ? '<br />' : '<br>'), $stringtoencode);
5479
            return $ret;
5480
        }
5481
    }
5482
5483
    /**
5484
     * 	This static function is called to encode a string into a HTML string but differs from htmlentities because
5485
     * 	a detection is done before to see if text is already HTML or not. Also, all entities but &,<,> are converted.
5486
     *  This permits to encode special chars to entities with no double encoding for already encoded HTML strings.
5487
     * 	This static function also remove last EOL or BR if $removelasteolbr=1 (default).
5488
     *  For PDF usage, you can show text by 2 ways:
5489
     *              - writeHTMLCell -> param must be encoded into HTML.
5490
     *              - MultiCell -> param must not be encoded into HTML.
5491
     *              Because writeHTMLCell convert also \n into <br>, if function
5492
     *              is used to build PDF, nl2brmode must be 1.
5493
     *
5494
     * 	@param	string	$stringtoencode		String to encode
5495
     * 	@param	int		$nl2brmode			0=Adding br before \n, 1=Replacing \n by br (for use with FPDF writeHTMLCell static function for example)
5496
     *  @param  string	$pagecodefrom       Pagecode stringtoencode is encoded
5497
     *  @param	int		$removelasteolbr	1=Remove last br or lasts \n (default), 0=Do nothing
5498
     *  @return	string						String encoded
5499
     */
5500
    static function dol_htmlentitiesbr($stringtoencode, $nl2brmode = 0, $pagecodefrom = 'UTF-8', $removelasteolbr = 1)
5501
    {
5502
        $newstring = $stringtoencode;
5503
        if (AlDolUtils::dol_textishtml($stringtoencode)) { // Check if text is already HTML or not
5504
            $newstring = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>/i', '<br>', $newstring); // Replace "<br type="_moz" />" by "<br>". It's same and avoid pb with FPDF.
5505
            if ($removelasteolbr)
5506
                $newstring = preg_replace('/<br>$/i', '', $newstring); // Remove last <br> (remove only last one)
5507
            $newstring = strtr($newstring, array('&' => '__and__', '<' => '__lt__', '>' => '__gt__', '"' => '__dquot__'));
5508
            $newstring = AlDolUtils::dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom); // Make entity encoding
5509
            $newstring = strtr($newstring, array('__and__' => '&', '__lt__' => '<', '__gt__' => '>', '__dquot__' => '"'));
5510
        }
5511
        else {
5512
            if ($removelasteolbr)
5513
                $newstring = preg_replace('/(\r\n|\r|\n)$/i', '', $newstring); // Remove last \n (may remove several)
5514
            $newstring = dol_nl2br(AlDolUtils::dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom), $nl2brmode);
5515
        }
5516
// Other substitutions that htmlentities does not do
5517
//$newstring=str_replace(chr(128),'&euro;',$newstring);	// 128 = 0x80. Not in html entity table.     // Seems useles with TCPDF. Make bug with UTF8 languages
5518
        return $newstring;
5519
    }
5520
5521
    /**
5522
     * 	This static function is called to decode a HTML string (it decodes entities and br tags)
5523
     *
5524
     * 	@param	string	$stringtodecode		String to decode
5525
     * 	@param	string	$pagecodeto			Page code for result
5526
     * 	@return	string						String decoded
5527
     */
5528
    static function dol_htmlentitiesbr_decode($stringtodecode, $pagecodeto = 'UTF-8')
5529
    {
5530
        $ret = dol_html_entity_decode($stringtodecode, ENT_COMPAT, $pagecodeto);
5531
        $ret = preg_replace('/' . "\r\n" . '<br(\s[\sa-zA-Z_="]*)?\/?>/i', "<br>", $ret);
5532
        $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>' . "\r\n" . '/i', "\r\n", $ret);
5533
        $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>' . "\n" . '/i', "\n", $ret);
5534
        $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>/i', "\n", $ret);
5535
        return $ret;
5536
    }
5537
5538
    /**
5539
     * 	This static function remove all ending \n and br at end
5540
     *
5541
     * 	@param	string	$stringtodecode		String to decode
5542
     * 	@return	string						String decoded
5543
     */
5544
    static function dol_htmlcleanlastbr($stringtodecode)
5545
    {
5546
        $ret = preg_replace('/(<br>|<br(\s[\sa-zA-Z_="]*)?\/?>|' . "\n" . '|' . "\r" . ')+$/i', "", $stringtodecode);
5547
        return $ret;
5548
    }
5549
5550
    /**
5551
     * Replace html_entity_decode functions to manage errors
5552
     *
5553
     * @param   string	$a		Operand a
5554
     * @param   string	$b		Operand b (ENT_QUOTES=convert simple and double quotes)
5555
     * @param   string	$c		Operand c
5556
     * @return  string			String decoded
5557
     */
5558
    static function dol_html_entity_decode($a, $b, $c = 'UTF-8')
5559
    {
5560
        return html_entity_decode($a, $b, $c);
5561
    }
5562
5563
    /**
5564
     * Replace htmlentities functions.
5565
     * Goal of this static function is to be sure to have default values of htmlentities that match what we need.
5566
     *
5567
     * @param   string  $string         The input string to encode
5568
     * @param   int     $flags          Flags (see PHP doc above)
5569
     * @param   string  $encoding       Encoding page code
5570
     * @param   bool    $double_encode  When double_encode is turned off, PHP will not encode existing html entities
5571
     * @return  string  $ret            Encoded string
5572
     */
5573
    static function dol_htmlentities($string, $flags = null, $encoding = 'UTF-8', $double_encode = false)
5574
    {
5575
        return htmlentities($string, $flags, $encoding, $double_encode);
5576
    }
5577
5578
    /**
5579
     * 	Check if a string is a correct iso string
5580
     * 	If not, it will we considered not HTML encoded even if it is by FPDF.
5581
     * 	Example, if string contains euro symbol that has ascii code 128
5582
     *
5583
     * 	@param	string	$s      String to check
5584
     * 	@return	int     		0 if bad iso, 1 if good iso
5585
     */
5586
    static function dol_string_is_good_iso($s)
5587
    {
5588
        $len = AlDolUtils::dol_strlen($s);
5589
        $ok = 1;
5590
        for ($scursor = 0; $scursor < $len; $scursor++) {
5591
            $ordchar = ord($s{$scursor});
5592
//print $scursor.'-'.$ordchar.'<br>';
5593
            if ($ordchar < 32 && $ordchar != 13 && $ordchar != 10) {
5594
                $ok = 0;
5595
                break;
5596
            }
5597
            if ($ordchar > 126 && $ordchar < 160) {
5598
                $ok = 0;
5599
                break;
5600
            }
5601
        }
5602
        return $ok;
5603
    }
5604
5605
    /**
5606
     * 	Return nb of lines of a clear text
5607
     *
5608
     * 	@param	string	$s			String to check
5609
     * 	@param	int     $maxchar	Not yet used
5610
     * 	@return	int					Number of lines
5611
     *  @see	dol_nboflines_bis, dolGetFirstLineOfText
5612
     */
5613
    static function dol_nboflines($s, $maxchar = 0)
5614
    {
5615
        if ($s == '')
5616
            return 0;
5617
        $arraystring = explode("\n", $s);
5618
        $nb = count($arraystring);
5619
5620
        return $nb;
5621
    }
5622
5623
    /**
5624
     * 	Return nb of lines of a formated text with \n and <br> (WARNING: string must not have mixed \n and br separators)
5625
     *
5626
     * 	@param	string	$text      		Text
5627
     * 	@param	int		$maxlinesize  	Largeur de ligne en caracteres (ou 0 si pas de limite - defaut)
5628
     * 	@param	string	$charset		Give the charset used to encode the $text variable in memory.
5629
     * 	@return int						Number of lines
5630
     * 	@see	dol_nboflines, dolGetFirstLineOfText
5631
     */
5632
    static function dol_nboflines_bis($text, $maxlinesize = 0, $charset = 'UTF-8')
5633
    {
5634
        $repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
5635
        if (AlDolUtils::dol_textishtml($text))
5636
            $repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
5637
5638
        $text = strtr($text, $repTable);
5639
        if ($charset == 'UTF-8') {
5640
            $pattern = '/(<br[^>]*>)/Uu';
5641
        } // /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
5642
        else
5643
            $pattern = '/(<br[^>]*>)/U';       // /U is to have UNGREEDY regex to limit to one html tag.
5644
        $a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
5645
5646
        $nblines = (int) floor((count($a) + 1) / 2);
5647
// count possible auto line breaks
5648
        if ($maxlinesize) {
5649
            foreach ($a as $line) {
5650
                if (AlDolUtils::dol_strlen($line) > $maxlinesize) {
5651
                    //$line_dec = html_entity_decode(strip_tags($line));
5652
                    $line_dec = html_entity_decode($line);
5653
                    if (AlDolUtils::dol_strlen($line_dec) > $maxlinesize) {
5654
                        $line_dec = wordwrap($line_dec, $maxlinesize, '\n', true);
5655
                        $nblines += substr_count($line_dec, '\n');
5656
                    }
5657
                }
5658
            }
5659
        }
5660
5661
        unset($a);
5662
        return $nblines;
5663
    }
5664
5665
    /**
5666
     * 	 Same static function than microtime in PHP 5 but compatible with PHP4
5667
     *
5668
     * @return		float		Time (millisecondes) with microsecondes in decimal part
5669
     * @deprecated Dolibarr does not support PHP4, you should use native function
5670
     * @see microtime()
5671
     */
5672
    static function dol_microtime_float()
5673
    {
5674
        AlDolUtils::dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);
5675
5676
        return microtime(true);
5677
    }
5678
5679
    /**
5680
     * 	Return if a text is a html content
5681
     *
5682
     * 	@param	string	$msg		Content to check
5683
     * 	@param	int		$option		0=Full detection, 1=Fast check
5684
     * 	@return	boolean				true/false
5685
     * 	@see	dol_concatdesc
5686
     */
5687
    static function dol_textishtml($msg, $option = 0)
5688
    {
5689
        if ($option == 1) {
5690
            if (preg_match('/<html/i', $msg))
5691
                return true;
5692
            elseif (preg_match('/<body/i', $msg))
5693
                return true;
5694
            elseif (preg_match('/<br/i', $msg))
5695
                return true;
5696
            return false;
5697
        }
5698
        else {
5699
            if (preg_match('/<html/i', $msg))
5700
                return true;
5701
            elseif (preg_match('/<body/i', $msg))
5702
                return true;
5703
            elseif (preg_match('/<(b|em|i|u)>/i', $msg))
5704
                return true;
5705
            elseif (preg_match('/<br\/>/i', $msg))
5706
                return true;
5707
            elseif (preg_match('/<(br|div|font|li|p|span|strong|table)>/i', $msg))
5708
                return true;
5709
            elseif (preg_match('/<(br|div|font|li|p|span|strong|table)\s+[^<>\/]*>/i', $msg))
5710
                return true;
5711
            elseif (preg_match('/<(br|div|font|li|p|span|strong|table)\s+[^<>\/]*\/>/i', $msg))
5712
                return true;
5713
            elseif (preg_match('/<img\s+[^<>]*src[^<>]*>/i', $msg))
5714
                return true; // must accept <img src="http://example.com/aaa.png" />
5715
            elseif (preg_match('/<a\s+[^<>]*href[^<>]*>/i', $msg))
5716
                return true; // must accept <a href="http://example.com/aaa.png" />
5717
            elseif (preg_match('/<h[0-9]>/i', $msg))
5718
                return true;
5719
            elseif (preg_match('/&[A-Z0-9]{1,6};/i', $msg))
5720
                return true;    // Html entities names (http://www.w3schools.com/tags/ref_entities.asp)
5721
            elseif (preg_match('/&#[0-9]{2,3};/i', $msg))
5722
                return true;    // Html entities numbers (http://www.w3schools.com/tags/ref_entities.asp)
5723
5724
            return false;
5725
        }
5726
    }
5727
5728
    /**
5729
     *  Concat 2 descriptions with a new line between them (second operand after first one with appropriate new line separator)
5730
     *  text1 html + text2 html => text1 + '<br>' + text2
5731
     *  text1 html + text2 txt  => text1 + '<br>' + dol_nl2br(text2)
5732
     *  text1 txt  + text2 html => dol_nl2br(text1) + '<br>' + text2
5733
     *  text1 txt  + text2 txt  => text1 + '\n' + text2
5734
     *
5735
     *  @param  string  $text1          Text 1
5736
     *  @param  string  $text2          Text 2
5737
     *  @param  bool    $forxml         false=Use <br>instead of \n if html content detected, true=Use <br /> instead of \n if html content detected
5738
     *  @param  bool    $invert         invert order of description lines if CONF CHANGE_ORDER_CONCAT_DESCRIPTION is active
5739
     *  @return string                  Text 1 + new line + Text2
5740
     *  @see    AlDolUtils::dol_textishtml
5741
     */
5742
    static function dol_concatdesc($text1, $text2, $forxml = false, $invert = false)
5743
    {
5744
        if (!empty($invert)) {
5745
            $tmp = $text1;
5746
            $text1 = $text2;
5747
            $text2 = $tmp;
5748
        }
5749
5750
        $ret = '';
5751
        $ret .= (!DolUtils::dol_textishtml($text1) && AlDolUtils::dol_textishtml($text2)) ? dol_nl2br($text1, 0, $forxml) : $text1;
5752
        $ret .= (!empty($text1) && !empty($text2)) ? ((AlDolUtils::dol_textishtml($text1) || AlDolUtils::dol_textishtml($text2)) ? ($forxml ? "<br \>\n" : "<br>\n") : "\n") : "";
5753
        $ret .= (AlDolUtils::dol_textishtml($text1) && !DolUtils::dol_textishtml($text2)) ? dol_nl2br($text2, 0, $forxml) : $text2;
5754
        return $ret;
5755
    }
5756
5757
    /**
5758
     * Return array of possible common substitutions. This includes several families like: 'system', 'mycompany', 'object', 'objectamount', 'date', 'user'
5759
     *
5760
     * @param	Translate	$outputlangs	Output language
5761
     * @param   int         $onlykey        1=Do not calculate some heavy values of keys (performance enhancement when we need only the keys), 2=Values are trunc and html sanitized (to use for help tooltip)
5762
     * @param   array       $exclude        Array of family keys we want to exclude. For example array('system', 'mycompany', 'object', 'objectamount', 'date', 'user', ...)
5763
     * @param   Object      $object         Object for keys on object
5764
     * @return	array						Array of substitutions
5765
     * @see setSubstitFromObject
5766
     */
5767
    static function getCommonSubstitutionArray($outputlangs, $onlykey = 0, $exclude = null, $object = null)
5768
    {
5769
        // global $db, Globals::$conf, $mysoc, $user, $extrafields;
5770
5771
        $substitutionarray = array();
5772
5773
        if (empty($exclude) || !in_array('user', $exclude)) {
5774
// Add SIGNATURE into substitutionarray first, so, when we will make the substitution,
5775
// this will include signature content first and then replace var found into content of signature
5776
            $signature = $user->signature;
5777
            $substitutionarray = array_merge($substitutionarray, array(
5778
                '__USER_SIGNATURE__' => (string) (($signature && empty(Globals::$conf->global->MAIN_MAIL_DO_NOT_USE_SIGN)) ? ($onlykey == 2 ? dol_trunc(dol_string_nohtmltag($signature), 30) : $signature) : '')
5779
                )
5780
            );
5781
// For backward compatibility
5782
            if ($onlykey != 2) {
5783
                $substitutionarray['__SIGNATURE__'] = (string) (($signature && empty(Globals::$conf->global->MAIN_MAIL_DO_NOT_USE_SIGN)) ? ($onlykey == 2 ? dol_trunc(dol_string_nohtmltag($signature), 30) : $signature) : '');
5784
            }
5785
5786
            $substitutionarray = array_merge($substitutionarray, array(
5787
                '__USER_ID__' => (string) $user->id,
5788
                '__USER_LOGIN__' => (string) $user->login,
5789
                '__USER_LASTNAME__' => (string) $user->lastname,
5790
                '__USER_FIRSTNAME__' => (string) $user->firstname,
5791
                '__USER_FULLNAME__' => (string) $user->getFullName($outputlangs),
5792
                '__USER_SUPERVISOR_ID__' => (string) ($user->fk_user ? $user->fk_user : '0'),
5793
                '__USER_REMOTE_IP__' => (string) getUserRemoteIP()
5794
                )
5795
            );
5796
        }
5797
        if ((empty($exclude) || !in_array('mycompany', $exclude)) && is_object($mysoc)) {
5798
            $substitutionarray = array_merge($substitutionarray, array(
5799
                '__MYCOMPANY_NAME__' => $mysoc->name,
5800
                '__MYCOMPANY_EMAIL__' => $mysoc->email,
5801
                '__MYCOMPANY_PROFID1__' => $mysoc->idprof1,
5802
                '__MYCOMPANY_PROFID2__' => $mysoc->idprof2,
5803
                '__MYCOMPANY_PROFID3__' => $mysoc->idprof3,
5804
                '__MYCOMPANY_PROFID4__' => $mysoc->idprof4,
5805
                '__MYCOMPANY_PROFID5__' => $mysoc->idprof5,
5806
                '__MYCOMPANY_PROFID6__' => $mysoc->idprof6,
5807
                '__MYCOMPANY_CAPITAL__' => $mysoc->capital,
5808
                '__MYCOMPANY_FULLADDRESS__' => $mysoc->getFullAddress(1, ', '),
5809
                '__MYCOMPANY_ADDRESS__' => $mysoc->address,
5810
                '__MYCOMPANY_ZIP__' => $mysoc->zip,
5811
                '__MYCOMPANY_TOWN__' => $mysoc->town,
5812
                '__MYCOMPANY_COUNTRY__' => $mysoc->country,
5813
                '__MYCOMPANY_COUNTRY_ID__' => $mysoc->country_id,
5814
                '__MYCOMPANY_CURRENCY_CODE__' => Globals::$conf->currency
5815
            ));
5816
        }
5817
5818
        if (($onlykey || is_object($object)) && (empty($exclude) || !in_array('object', $exclude))) {
5819
            if ($onlykey) {
5820
                $substitutionarray['__ID__'] = '__ID__';
5821
                $substitutionarray['__REF__'] = '__REF__';
5822
                $substitutionarray['__REFCLIENT__'] = '__REFCLIENT__';
5823
                $substitutionarray['__REFSUPPLIER__'] = '__REFSUPPLIER__';
5824
                $substitutionarray['__EXTRAFIELD_XXX__'] = '__EXTRAFIELD_XXX__';
5825
5826
                $substitutionarray['__THIRDPARTY_ID__'] = '__THIRDPARTY_ID__';
5827
                $substitutionarray['__THIRDPARTY_NAME__'] = '__THIRDPARTY_NAME__';
5828
                $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = '__THIRDPARTY_NAME_ALIAS__';
5829
                $substitutionarray['__THIRDPARTY_EMAIL__'] = '__THIRDPARTY_EMAIL__';
5830
5831
                if (is_object($object) && $object->element == 'member') {
5832
                    $substitutionarray['__MEMBER_ID__'] = '__MEMBER_ID__';
5833
                    $substitutionarray['__MEMBER_CIVILITY__'] = '__MEMBER_CIVILITY__';
5834
                    $substitutionarray['__MEMBER_FIRSTNAME__'] = '__MEMBER_FIRSTNAME__';
5835
                    $substitutionarray['__MEMBER_LASTNAME__'] = '__MEMBER_LASTNAME__';
5836
                }
5837
                $substitutionarray['__PROJECT_ID__'] = '__PROJECT_ID__';
5838
                $substitutionarray['__PROJECT_REF__'] = '__PROJECT_REF__';
5839
                $substitutionarray['__PROJECT_NAME__'] = '__PROJECT_NAME__';
5840
5841
                $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = 'Highest date planned for a service start';
5842
                $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = 'Highest date and hour planned for service start';
5843
                $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = 'Lowest data for planned expiration of service';
5844
                $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = 'Lowest date and hour for planned expiration of service';
5845
5846
                $substitutionarray['__ONLINE_PAYMENT_URL__'] = 'UrlToPayOnlineIfApplicable';
5847
                $substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = 'TextAndUrlToPayOnlineIfApplicable';
5848
                $substitutionarray['__SECUREKEYPAYMENT__'] = 'Security key (if key is not unique per record)';
5849
                $substitutionarray['__SECUREKEYPAYMENT_MEMBER__'] = 'Security key for payment on a member subscription (one key per member)';
5850
                $substitutionarray['__SECUREKEYPAYMENT_ORDER__'] = 'Security key for payment on an order';
5851
                $substitutionarray['__SECUREKEYPAYMENT_INVOICE__'] = 'Security key for payment on an invoice';
5852
                $substitutionarray['__SECUREKEYPAYMENT_CONTRACTLINE__'] = 'Security key for payment on a a service';
5853
5854
                $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = 'Direct download url of a proposal';
5855
                $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = 'Direct download url of an order';
5856
                $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = 'Direct download url of an invoice';
5857
5858
                if (is_object($object) && $object->element == 'shipping') {
5859
                    $substitutionarray['__SHIPPINGTRACKNUM__'] = 'Shipping tacking number';
5860
                    $substitutionarray['__SHIPPINGTRACKNUMURL__'] = 'Shipping tracking url';
5861
                }
5862
            } else {
5863
                $substitutionarray['__ID__'] = $object->id;
5864
                $substitutionarray['__REF__'] = $object->ref;
5865
                $substitutionarray['__REFCLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : ''));
5866
                $substitutionarray['__REFSUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : '');
5867
5868
// TODO Remove this
5869
                $msgishtml = 0;
5870
5871
                $birthday = AlDolUtils::dol_print_date($object->birth, 'day');
5872
5873
                $substitutionarray['__MEMBER_ID__'] = $object->id;
5874
                if (method_exists($object, 'getCivilityLabel'))
5875
                    $substitutionarray['__MEMBER_CIVILITY__'] = $object->getCivilityLabel();
5876
                $substitutionarray['__MEMBER_FIRSTNAME__'] = $msgishtml ? AlDolUtils::dol_htmlentitiesbr($object->firstname) : $object->firstname;
5877
                $substitutionarray['__MEMBER_LASTNAME__'] = $msgishtml ? AlDolUtils::dol_htmlentitiesbr($object->lastname) : $object->lastname;
5878
                if (method_exists($object, 'getFullName'))
5879
                    $substitutionarray['__MEMBER_FULLNAME__'] = $msgishtml ? AlDolUtils::dol_htmlentitiesbr($object->getFullName($outputlangs)) : $object->getFullName($outputlangs);
5880
                $substitutionarray['__MEMBER_COMPANY__'] = $msgishtml ? AlDolUtils::dol_htmlentitiesbr($object->societe) : $object->societe;
5881
                $substitutionarray['__MEMBER_ADDRESS__'] = $msgishtml ? AlDolUtils::dol_htmlentitiesbr($object->address) : $object->address;
5882
                $substitutionarray['__MEMBER_ZIP__'] = $msgishtml ? AlDolUtils::dol_htmlentitiesbr($object->zip) : $object->zip;
5883
                $substitutionarray['__MEMBER_TOWN__'] = $msgishtml ? AlDolUtils::dol_htmlentitiesbr($object->town) : $object->town;
5884
                $substitutionarray['__MEMBER_COUNTRY__'] = $msgishtml ? AlDolUtils::dol_htmlentitiesbr($object->country) : $object->country;
5885
                $substitutionarray['__MEMBER_EMAIL__'] = $msgishtml ? AlDolUtils::dol_htmlentitiesbr($object->email) : $object->email;
5886
                $substitutionarray['__MEMBER_BIRTH__'] = $msgishtml ? AlDolUtils::dol_htmlentitiesbr($birthday) : $birthday;
5887
                $substitutionarray['__MEMBER_PHOTO__'] = $msgishtml ? AlDolUtils::dol_htmlentitiesbr($object->photo) : $object->photo;
5888
                $substitutionarray['__MEMBER_LOGIN__'] = $msgishtml ? AlDolUtils::dol_htmlentitiesbr($object->login) : $object->login;
5889
                $substitutionarray['__MEMBER_PASSWORD__'] = $msgishtml ? AlDolUtils::dol_htmlentitiesbr($object->pass) : $object->pass;
5890
                $substitutionarray['__MEMBER_PHONE__'] = $msgishtml ? AlDolUtils::dol_htmlentitiesbr($object->phone) : $object->phone;
5891
                $substitutionarray['__MEMBER_PHONEPRO__'] = $msgishtml ? AlDolUtils::dol_htmlentitiesbr($object->phone_perso) : $object->phone_perso;
5892
                $substitutionarray['__MEMBER_PHONEMOBILE__'] = $msgishtml ? AlDolUtils::dol_htmlentitiesbr($object->phone_mobile) : $object->phone_mobile;
5893
                $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE__'] = AlDolUtils::dol_print_date($object->first_subscription_date, 'dayrfc');
5894
                $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_START__'] = AlDolUtils::dol_print_date($object->first_subscription_date_start, 'dayrfc');
5895
                $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_END__'] = AlDolUtils::dol_print_date($object->first_subscription_date_end, 'dayrfc');
5896
                $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE__'] = AlDolUtils::dol_print_date($object->last_subscription_date, 'dayrfc');
5897
                $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_START__'] = AlDolUtils::dol_print_date($object->last_subscription_date_start, 'dayrfc');
5898
                $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_END__'] = AlDolUtils::dol_print_date($object->last_subscription_date_end, 'dayrfc');
5899
5900
                if (is_object($object) && $object->element == 'societe') {
5901
                    $substitutionarray['__THIRDPARTY_ID__'] = (is_object($object) ? $object->id : '');
5902
                    $substitutionarray['__THIRDPARTY_NAME__'] = (is_object($object) ? $object->name : '');
5903
                    $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = (is_object($object) ? $object->name_alias : '');
5904
                    $substitutionarray['__THIRDPARTY_EMAIL__'] = (is_object($object) ? $object->email : '');
5905
                } elseif (is_object($object->thirdparty) && $object->thirdparty->id > 0) {
5906
                    $substitutionarray['__THIRDPARTY_ID__'] = (is_object($object->thirdparty) ? $object->thirdparty->id : '');
5907
                    $substitutionarray['__THIRDPARTY_NAME__'] = (is_object($object->thirdparty) ? $object->thirdparty->name : '');
5908
                    $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = (is_object($object->thirdparty) ? $object->thirdparty->name_alias : '');
5909
                    $substitutionarray['__THIRDPARTY_EMAIL__'] = (is_object($object->thirdparty) ? $object->thirdparty->email : '');
5910
                }
5911
5912
                if (is_object($object->projet) && $object->projet->id > 0) {
5913
                    $substitutionarray['__PROJECT_ID__'] = (is_object($object->projet) ? $object->projet->id : '');
5914
                    $substitutionarray['__PROJECT_REF__'] = (is_object($object->projet) ? $object->projet->ref : '');
5915
                    $substitutionarray['__PROJECT_NAME__'] = (is_object($object->projet) ? $object->projet->title : '');
5916
                }
5917
5918
                if (is_object($object) && $object->element == 'shipping') {
5919
                    $substitutionarray['__SHIPPINGTRACKNUM__'] = $object->tracking_number;
5920
                    $substitutionarray['__SHIPPINGTRACKNUMURL__'] = $object->tracking_url;
5921
                }
5922
5923
                if (is_object($object) && $object->element == 'contrat' && is_array($object->lines)) {
5924
                    $dateplannedstart = '';
5925
                    $datenextexpiration = '';
5926
                    foreach ($object->lines as $line) {
5927
                        if ($line->date_ouverture_prevue > $dateplannedstart)
5928
                            $dateplannedstart = $line->date_ouverture_prevue;
5929
                        if ($line->statut == 4 && $line->date_fin_prevue && (!$datenextexpiration || $line->date_fin_prevue < $datenextexpiration))
5930
                            $datenextexpiration = $line->date_fin_prevue;
5931
                    }
5932
                    $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = AlDolUtils::dol_print_date($dateplannedstart, 'dayrfc');
5933
                    $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = AlDolUtils::dol_print_date($dateplannedstart, 'standard');
5934
                    $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = AlDolUtils::dol_print_date($datenextexpiration, 'dayrfc');
5935
                    $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = AlDolUtils::dol_print_date($datenextexpiration, 'standard');
5936
                }
5937
5938
// Create dynamic tags for __EXTRAFIELD_FIELD__
5939
                if ($object->table_element && $object->id > 0) {
5940
                    if (!is_object($extrafields))
5941
                        $extrafields = new ExtraFields($db);
5942
                    $extrafields->fetch_name_optionals_label($object->table_element, true);
5943
5944
                    if ($object->fetch_optionals() > 0) {
5945
                        if (is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label']) > 0) {
5946
                            foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) {
5947
                                $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = $object->array_options['options_' . $key];
5948
                            }
5949
                        }
5950
                    }
5951
                }
5952
5953
// Complete substitution array with the url to make online payment
5954
                $paymenturl = '';
5955
                if (empty($substitutionarray['__REF__'])) {
5956
                    $paymenturl = '';
5957
                } else {
5958
                    // Set the online payment url link into __ONLINE_PAYMENT_URL__ key
5959
                    require_once DOL_BASE_PATH . '/core/lib/payments.lib.php';
5960
                    $outputlangs->loadLangs(array('paypal', 'other'));
5961
                    $typeforonlinepayment = 'free';
5962
                    if (is_object($object) && $object->element == 'commande')
5963
                        $typeforonlinepayment = 'order';
5964
                    if (is_object($object) && $object->element == 'facture')
5965
                        $typeforonlinepayment = 'invoice';
5966
                    if (is_object($object) && $object->element == 'member')
5967
                        $typeforonlinepayment = 'member';
5968
                    $url = getOnlinePaymentUrl(0, $typeforonlinepayment, $substitutionarray['__REF__']);
5969
                    $paymenturl = $url;
5970
                }
5971
5972
                $substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = ($paymenturl ? str_replace('\n', "\n", $outputlangs->trans("PredefinedMailContentLink", $paymenturl)) : '');
5973
                $substitutionarray['__ONLINE_PAYMENT_URL__'] = $paymenturl;
5974
5975
                if (!empty(Globals::$conf->global->PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'propal') {
5976
                    $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = $object->getLastMainDocLink($object->element);
5977
                } else
5978
                    $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = '';
5979
                if (!empty(Globals::$conf->global->ORDER_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'commande') {
5980
                    $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = $object->getLastMainDocLink($object->element);
5981
                } else
5982
                    $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = '';
5983
                if (!empty(Globals::$conf->global->INVOICE_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'facture') {
5984
                    $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = $object->getLastMainDocLink($object->element);
5985
                } else
5986
                    $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = '';
5987
            }
5988
        }
5989
        if (empty($exclude) || !in_array('objectamount', $exclude)) {
5990
            $substitutionarray['__DATE_YMD__'] = is_object($object) ? (isset($object->date) ? AlDolUtils::dol_print_date($object->date, 'day', 0, $outputlangs) : '') : '';
5991
            $substitutionarray['__DATE_DUE_YMD__'] = is_object($object) ? (isset($object->date_lim_reglement) ? AlDolUtils::dol_print_date($object->date_lim_reglement, 'day', 0, $outputlangs) : '') : '';
5992
5993
            $substitutionarray['__AMOUNT__'] = is_object($object) ? $object->total_ttc : '';
5994
            $substitutionarray['__AMOUNT_EXCL_TAX__'] = is_object($object) ? $object->total_ht : '';
5995
            $substitutionarray['__AMOUNT_VAT__'] = is_object($object) ? ($object->total_vat ? $object->total_vat : $object->total_tva) : '';
5996
            if ($onlykey != 2 || $mysoc->useLocalTax(1))
5997
                $substitutionarray['__AMOUNT_TAX2__'] = is_object($object) ? $object->total_localtax1 : '';
5998
            if ($onlykey != 2 || $mysoc->useLocalTax(2))
5999
                $substitutionarray['__AMOUNT_TAX3__'] = is_object($object) ? $object->total_localtax2 : '';
6000
6001
            $substitutionarray['__AMOUNT_FORMATED__'] = is_object($object) ? price($object->total_ttc, 0, $outputlangs, 0, 0, -1, Globals::$conf->currency) : '';
6002
            $substitutionarray['__AMOUNT_EXCL_TAX_FORMATED__'] = is_object($object) ? price($object->total_ht, 0, $outputlangs, 0, 0, -1, Globals::$conf->currency) : '';
6003
            $substitutionarray['__AMOUNT_VAT_FORMATED__'] = is_object($object) ? ($object->total_vat ? price($object->total_vat, 0, $outputlangs, 0, 0, -1, Globals::$conf->currency) : price($object->total_tva, 0, $outputlangs, 0, 0, -1, Globals::$conf->currency)) : '';
6004
            if ($onlykey != 2 || $mysoc->useLocalTax(1))
6005
                $substitutionarray['__AMOUNT_TAX2_FORMATED__'] = is_object($object) ? price($object->total_localtax1, 0, $outputlangs, 0, 0, -1, Globals::$conf->currency) : '';
6006
            if ($onlykey != 2 || $mysoc->useLocalTax(2))
6007
                $substitutionarray['__AMOUNT_TAX3_FORMATED__'] = is_object($object) ? price($object->total_localtax2, 0, $outputlangs, 0, 0, -1, Globals::$conf->currency) : '';
6008
6009
// TODO Add keys for foreign multicurrency
6010
// For backward compatibility
6011
            if ($onlykey != 2) {
6012
                $substitutionarray['__TOTAL_TTC__'] = is_object($object) ? $object->total_ttc : '';
6013
                $substitutionarray['__TOTAL_HT__'] = is_object($object) ? $object->total_ht : '';
6014
                $substitutionarray['__TOTAL_VAT__'] = is_object($object) ? ($object->total_vat ? $object->total_vat : $object->total_tva) : '';
6015
            }
6016
        }
6017
6018
//var_dump($substitutionarray['__AMOUNT_FORMATED__']);
6019
        if (empty($exclude) || !in_array('date', $exclude)) {
6020
            include_once DOL_BASE_PATH . '/core/lib/date.lib.php';
6021
6022
            $tmp = dol_getdate(dol_now(), true);
6023
            $tmp2 = dol_get_prev_day($tmp['mday'], $tmp['mon'], $tmp['year']);
6024
            $tmp3 = dol_get_prev_month($tmp['mon'], $tmp['year']);
6025
            $tmp4 = dol_get_next_day($tmp['mday'], $tmp['mon'], $tmp['year']);
6026
            $tmp5 = dol_get_next_month($tmp['mon'], $tmp['year']);
6027
6028
            $substitutionarray = array_merge($substitutionarray, array(
6029
                '__DAY__' => (string) $tmp['mday'],
6030
                '__DAY_TEXT__' => $outputlangs->trans('Day' . $tmp['wday']), // Monday
6031
                '__DAY_TEXT_SHORT__' => $outputlangs->trans($tmp['weekday'] . 'Min'), // Mon
6032
                '__DAY_TEXT_MIN__' => $outputlangs->trans('Short' . $tmp['weekday']), // M
6033
                '__MONTH__' => (string) $tmp['mon'],
6034
                '__MONTH_TEXT__' => $outputlangs->trans('Month' . sprintf("%02d", $tmp['mon'])),
6035
                '__MONTH_TEXT_SHORT__' => $outputlangs->trans('MonthShort' . sprintf("%02d", $tmp['mon'])),
6036
                '__MONTH_TEXT_MIN__' => $outputlangs->trans('MonthVeryShort' . sprintf("%02d", $tmp['mon'])),
6037
                '__YEAR__' => (string) $tmp['year'],
6038
                '__PREVIOUS_DAY__' => (string) $tmp2['day'],
6039
                '__PREVIOUS_MONTH__' => (string) $tmp3['month'],
6040
                '__PREVIOUS_YEAR__' => (string) ($tmp['year'] - 1),
6041
                '__NEXT_DAY__' => (string) $tmp4['day'],
6042
                '__NEXT_MONTH__' => (string) $tmp5['month'],
6043
                '__NEXT_YEAR__' => (string) ($tmp['year'] + 1),
6044
            ));
6045
        }
6046
6047
        if (!empty(Globals::$conf->multicompany->enabled)) {
6048
            $substitutionarray = array_merge($substitutionarray, array('__ENTITY_ID__' => Globals::$conf->entity));
6049
        }
6050
        if (empty($exclude) || !in_array('system', $exclude)) {
6051
            $substitutionarray['__DOL_MAIN_URL_ROOT__'] = DOL_MAIN_URL_ROOT;
6052
            $substitutionarray['__(AnyTranslationKey)__'] = $outputlangs->trans('TranslationOfKey');
6053
            $substitutionarray['__[AnyConstantKey]__'] = $outputlangs->trans('ValueOfConstantKey');
6054
        }
6055
6056
        return $substitutionarray;
6057
    }
6058
6059
    /**
6060
     *  Make substitution into a text string, replacing keys with vals from $substitutionarray (oldval=>newval),
6061
     *  and texts like __(TranslationKey|langfile)__ and __[ConstantKey]__ are also replaced.
6062
     *  Example of usage:
6063
     *  $substitutionarray = getCommonSubstitutionArray(Globals::$langs, 0, null, $thirdparty);
6064
     *  complete_substitutions_array($substitutionarray, Globals::$langs, $thirdparty);
6065
     *  $mesg = make_substitutions($mesg, $substitutionarray, Globals::$langs);
6066
     *
6067
     *  @param	string		$text	      			Source string in which we must do substitution
6068
     *  @param  array		$substitutionarray		Array with key->val to substitute. Example: array('__MYKEY__' => 'MyVal', ...)
6069
     *  @param	Translate	$outputlangs			Output language
6070
     * 	@return string  		    				Output string after substitutions
6071
     *  @see	complete_substitutions_array, getCommonSubstitutionArray
6072
     */
6073
    static function make_substitutions($text, $substitutionarray, $outputlangs = null)
6074
    {
6075
        // global Globals::$conf, Globals::$langs;
6076
6077
        if (!is_array($substitutionarray))
6078
            return 'ErrorBadParameterSubstitutionArrayWhenCalling_make_substitutions';
6079
6080
        if (empty($outputlangs))
6081
            $outputlangs = Globals::$langs;
6082
6083
// Make substitution for language keys
6084
        if (is_object($outputlangs)) {
6085
            while (preg_match('/__\(([^\)]+)\)__/', $text, $reg)) {
6086
                $msgishtml = 0;
6087
                if (AlDolUtils::dol_textishtml($text, 1))
6088
                    $msgishtml = 1;
6089
6090
// If key is __(TranslationKey|langfile)__, then force load of langfile.lang
6091
                $tmp = explode('|', $reg[1]);
6092
                if (!empty($tmp[1]))
6093
                    $outputlangs->load($tmp[1]);
6094
6095
                $text = preg_replace('/__\(' . preg_quote($reg[1], '/') . '\)__/', $msgishtml ? AlDolUtils::dol_htmlentitiesbr($outputlangs->transnoentitiesnoconv($reg[1])) : $outputlangs->transnoentitiesnoconv($reg[1]), $text);
6096
            }
6097
        }
6098
6099
// Make substitution for constant keys. Must be after the substitution of translation, so if text of translation contains a constant,
6100
// it is also converted.
6101
        while (preg_match('/__\[([^\]]+)\]__/', $text, $reg)) {
6102
            $msgishtml = 0;
6103
            if (AlDolUtils::dol_textishtml($text, 1))
6104
                $msgishtml = 1;
6105
6106
            $keyfound = $reg[1];
6107
            if (preg_match('/(_pass|password|secret|_key|key$)/i', $keyfound))
6108
                $newval = '*****forbidden*****';
6109
            else
6110
                $newval = empty(Globals::$conf->global->$keyfound) ? '' : Globals::$conf->global->$keyfound;
6111
            $text = preg_replace('/__\[' . preg_quote($keyfound, '/') . '\]__/', $msgishtml ? AlDolUtils::dol_htmlentitiesbr($newval) : $newval, $text);
6112
        }
6113
6114
// Make substitition for array $substitutionarray
6115
        foreach ($substitutionarray as $key => $value) {
6116
            if ($key == '__SIGNATURE__' && (!empty(Globals::$conf->global->MAIN_MAIL_DO_NOT_USE_SIGN)))
6117
                $value = '';  // Protection
6118
            if ($key == '__USER_SIGNATURE__' && (!empty(Globals::$conf->global->MAIN_MAIL_DO_NOT_USE_SIGN)))
6119
                $value = ''; // Protection
6120
6121
            $text = str_replace("$key", "$value", $text); // We must keep the " to work when value is 123.5 for example
6122
        }
6123
6124
        return $text;
6125
    }
6126
6127
    /**
6128
     *  Complete the $substitutionarray with more entries coming from external module that had set the "substitutions=1" into module_part array.
6129
     *  In this case, method completesubstitutionarray provided by module is called.
6130
     *
6131
     *  @param  array		$substitutionarray		Array substitution old value => new value value
6132
     *  @param  Translate	$outputlangs            Output language
6133
     *  @param  Object		$object                 Source object
6134
     *  @param  mixed		$parameters       		Add more parameters (useful to pass product lines)
6135
     *  @param  string      $callfunc               What is the name of the custom static function that will be called? (default: completesubstitutionarray)
6136
     *  @return	void
6137
     *  @see 	make_substitutions
6138
     */
6139
    static function complete_substitutions_array(&$substitutionarray, $outputlangs, $object = null, $parameters = null, $callfunc = "completesubstitutionarray")
6140
    {
6141
        // global Globals::$conf, $user;
6142
6143
        require_once DOL_BASE_PATH . '/core/lib/files.lib.php';
6144
6145
// Add a substitution key for each extrafields, using key __EXTRA_XXX__
6146
// TODO Remove this. Already available into the getCommonSubstitutionArray used to build the substitution array.
6147
        /* if (is_object($object) && is_array($object->array_options))
6148
          {
6149
          foreach($object->array_options as $key => $val)
6150
          {
6151
          $keyshort=preg_replace('/^(options|extra)_/','',$key);
6152
          $substitutionarray['__EXTRAFIELD_'.$keyshort.'__']=$val;
6153
          // For backward compatibiliy
6154
          $substitutionarray['%EXTRA_'.$keyshort.'%']=$val;
6155
          }
6156
          } */
6157
6158
// Check if there is external substitution to do, requested by plugins
6159
        $dirsubstitutions = array_merge(array(), (array) Globals::$conf->modules_parts['substitutions']);
6160
6161
        foreach ($dirsubstitutions as $reldir) {
6162
            $dir = dol_buildpath($reldir, 0);
6163
6164
// Check if directory exists
6165
            if (!dol_is_dir($dir))
6166
                continue;
6167
6168
            $substitfiles = dol_dir_list($dir, 'files', 0, 'functions_');
6169
            foreach ($substitfiles as $substitfile) {
6170
                if (preg_match('/functions_(.*)\.lib\.php/i', $substitfile['name'], $reg)) {
6171
                    $module = $reg[1];
6172
6173
                    AlDolUtils::dol_syslog("Library " . $substitfile['name'] . " found into " . $dir);
6174
                    // Include the user's functions file
6175
                    require_once $dir . $substitfile['name'];
6176
                    // Call the user's function, and only if it is defined
6177
                    $function_name = $module . "_" . $callfunc;
6178
                    if (function_exists($function_name))
6179
                        $function_name($substitutionarray, $outputlangs, $object, $parameters);
6180
                }
6181
            }
6182
        }
6183
    }
6184
6185
    /**
6186
     *    Format output for start and end date
6187
     *
6188
     *    @param	int	$date_start    Start date
6189
     *    @param    int	$date_end      End date
6190
     *    @param    string		$format        Output format
6191
     *    @param	Translate	$outputlangs   Output language
6192
     *    @return	void
6193
     */
6194
    static function print_date_range($date_start, $date_end, $format = '', $outputlangs = '')
6195
    {
6196
        print get_date_range($date_start, $date_end, $format, $outputlangs);
6197
    }
6198
6199
    /**
6200
     *    Format output for start and end date
6201
     *
6202
     *    @param	int			$date_start    		Start date
6203
     *    @param    int			$date_end      		End date
6204
     *    @param    string		$format        		Output format
6205
     *    @param	Translate	$outputlangs   		Output language
6206
     *    @param	integer		$withparenthesis	1=Add parenthesis, 0=non parenthesis
6207
     *    @return	string							String
6208
     */
6209
    static function get_date_range($date_start, $date_end, $format = '', $outputlangs = '', $withparenthesis = 1)
6210
    {
6211
        // global Globals::$langs;
6212
6213
        $out = '';
6214
6215
        if (!is_object($outputlangs))
6216
            $outputlangs = Globals::$langs;
6217
6218
        if ($date_start && $date_end) {
6219
            $out .= ($withparenthesis ? ' (' : '') . $outputlangs->transnoentitiesnoconv('DateFromTo', AlDolUtils::dol_print_date($date_start, $format, false, $outputlangs), AlDolUtils::dol_print_date($date_end, $format, false, $outputlangs)) . ($withparenthesis ? ')' : '');
6220
        }
6221
        if ($date_start && !$date_end) {
6222
            $out .= ($withparenthesis ? ' (' : '') . $outputlangs->transnoentitiesnoconv('DateFrom', AlDolUtils::dol_print_date($date_start, $format, false, $outputlangs)) . ($withparenthesis ? ')' : '');
6223
        }
6224
        if (!$date_start && $date_end) {
6225
            $out .= ($withparenthesis ? ' (' : '') . $outputlangs->transnoentitiesnoconv('DateUntil', AlDolUtils::dol_print_date($date_end, $format, false, $outputlangs)) . ($withparenthesis ? ')' : '');
6226
        }
6227
6228
        return $out;
6229
    }
6230
6231
    /**
6232
     * Return firstname and lastname in correct order
6233
     *
6234
     * @param	string	$firstname		Firstname
6235
     * @param	string	$lastname		Lastname
6236
     * @param	int		$nameorder		-1=Auto, 0=Lastname+Firstname, 1=Firstname+Lastname, 2=Firstname
6237
     * @return	string					Firstname + lastname or Lastname + firstname
6238
     */
6239
    static function dolGetFirstLastname($firstname, $lastname, $nameorder = -1)
6240
    {
6241
        // global Globals::$conf;
6242
6243
        $ret = '';
6244
// If order not defined, we use the setup
6245
        if ($nameorder < 0)
6246
            $nameorder = (empty(Globals::$conf->global->MAIN_FIRSTNAME_NAME_POSITION) ? 1 : 0);
6247
        if ($nameorder && ((string) $nameorder != '2')) {
6248
            $ret .= $firstname;
6249
            if ($firstname && $lastname)
6250
                $ret .= ' ';
6251
            $ret .= $lastname;
6252
        }
6253
        else if ($nameorder == 2) {
6254
            $ret .= $firstname;
6255
        } else {
6256
            $ret .= $lastname;
6257
            if ($firstname && $lastname)
6258
                $ret .= ' ';
6259
            $ret .= $firstname;
6260
        }
6261
        return $ret;
6262
    }
6263
6264
    /**
6265
     * 	Set event message in dol_events session object. Will be output by calling dol_htmloutput_events.
6266
     *  Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function.
6267
     *  Note: Prefer to use setEventMessages instead.
6268
     *
6269
     * 	@param	mixed	$mesgs			Message string or array
6270
     *  @param  string	$style      	Which style to use ('mesgs' by default, 'warnings', 'errors')
6271
     *  @return	void
6272
     *  @see	dol_htmloutput_events
6273
     */
6274
    static function setEventMessage($mesgs, $style = 'mesgs')
6275
    {
6276
//DolUtils::dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);		This is not deprecated, it is used by setEventMessages function
6277
        if (!is_array($mesgs)) {  // If mesgs is a string
6278
            if ($mesgs)
6279
                $_SESSION['dol_events'][$style][] = $mesgs;
6280
        }
6281
        else {      // If mesgs is an array
6282
            foreach ($mesgs as $mesg) {
6283
                if ($mesg)
6284
                    $_SESSION['dol_events'][$style][] = $mesg;
6285
            }
6286
        }
6287
    }
6288
6289
    /**
6290
     * 	Set event messages in dol_events session object. Will be output by calling dol_htmloutput_events.
6291
     *  Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function.
6292
     *
6293
     * 	@param	string	$mesg			Message string
6294
     * 	@param	array	$mesgs			Message array
6295
     *  @param  string	$style      	Which style to use ('mesgs' by default, 'warnings', 'errors')
6296
     *  @return	void
6297
     *  @see	dol_htmloutput_events
6298
     */
6299
    static function setEventMessages($mesg, $mesgs, $style = 'mesgs')
6300
    {
6301
        if (empty($mesg) && empty($mesgs)) {
6302
            AlDolUtils::dol_syslog("Try to add a message in stack with empty message", LOG_WARNING);
6303
        } else {
6304
            if (!in_array((string) $style, array('mesgs', 'warnings', 'errors')))
6305
                dol_print_error('', 'Bad parameter style=' . $style . ' for setEventMessages');
6306
            if (empty($mesgs))
6307
                setEventMessage($mesg, $style);
6308
            else {
6309
                if (!empty($mesg) && !in_array($mesg, $mesgs))
6310
                    setEventMessage($mesg, $style); // Add message string if not already into array
6311
                setEventMessage($mesgs, $style);
6312
            }
6313
        }
6314
    }
6315
6316
    /**
6317
     * 	Print formated messages to output (Used to show messages on html output).
6318
     *  Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function, so there is
6319
     *  no need to call it explicitely.
6320
     *
6321
     *  @param	int		$disabledoutputofmessages	Clear all messages stored into session without diplaying them
6322
     *  @return	void
6323
     *  @see    									dol_htmloutput_mesg
6324
     */
6325
    static function dol_htmloutput_events($disabledoutputofmessages = 0)
6326
    {
6327
// Show mesgs
6328
        if (isset($_SESSION['dol_events']['mesgs'])) {
6329
            if (empty($disabledoutputofmessages))
6330
                dol_htmloutput_mesg('', $_SESSION['dol_events']['mesgs']);
6331
            unset($_SESSION['dol_events']['mesgs']);
6332
        }
6333
6334
// Show errors
6335
        if (isset($_SESSION['dol_events']['errors'])) {
6336
            if (empty($disabledoutputofmessages))
6337
                dol_htmloutput_mesg('', $_SESSION['dol_events']['errors'], 'error');
6338
            unset($_SESSION['dol_events']['errors']);
6339
        }
6340
6341
// Show warnings
6342
        if (isset($_SESSION['dol_events']['warnings'])) {
6343
            if (empty($disabledoutputofmessages))
6344
                dol_htmloutput_mesg('', $_SESSION['dol_events']['warnings'], 'warning');
6345
            unset($_SESSION['dol_events']['warnings']);
6346
        }
6347
    }
6348
6349
    /**
6350
     * 	Get formated messages to output (Used to show messages on html output).
6351
     *  This include also the translation of the message key.
6352
     *
6353
     * 	@param	string		$mesgstring		Message string or message key
6354
     * 	@param	string[]	$mesgarray      Array of message strings or message keys
6355
     *  @param  string		$style          Style of message output ('ok' or 'error')
6356
     *  @param  int			$keepembedded   Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
6357
     * 	@return	string						Return html output
6358
     *
6359
     *  @see    dol_print_error
6360
     *  @see    dol_htmloutput_errors
6361
     *  @see    setEventMessages
6362
     */
6363
    static function get_htmloutput_mesg($mesgstring = '', $mesgarray = '', $style = 'ok', $keepembedded = 0)
6364
    {
6365
        // global Globals::$conf, Globals::$langs;
6366
6367
        $ret = 0;
6368
        $return = '';
6369
        $out = '';
6370
        $divstart = $divend = '';
6371
6372
// If inline message with no format, we add it.
6373
        if ((empty(Globals::$conf->use_javascript_ajax) || !empty(Globals::$conf->global->MAIN_DISABLE_JQUERY_JNOTIFY) || $keepembedded) && !preg_match('/<div class=".*">/i', $out)) {
6374
            $divstart = '<div class="' . $style . ' clearboth">';
6375
            $divend = '</div>';
6376
        }
6377
6378
        if ((is_array($mesgarray) && count($mesgarray)) || $mesgstring) {
6379
            Globals::$langs->load("errors");
6380
            $out .= $divstart;
6381
            if (is_array($mesgarray) && count($mesgarray)) {
6382
                foreach ($mesgarray as $message) {
6383
                    $ret++;
6384
                    $out .= Globals::$langs->trans($message);
6385
                    if ($ret < count($mesgarray))
6386
                        $out .= "<br>\n";
6387
                }
6388
            }
6389
            if ($mesgstring) {
6390
                Globals::$langs->load("errors");
6391
                $ret++;
6392
                $out .= Globals::$langs->trans($mesgstring);
6393
            }
6394
            $out .= $divend;
6395
        }
6396
6397
        if ($out) {
6398
            if (!empty(Globals::$conf->use_javascript_ajax) && empty(Globals::$conf->global->MAIN_DISABLE_JQUERY_JNOTIFY) && empty($keepembedded)) {
6399
                $return = '<script type="text/javascript">
6400
					$(document).ready(function() {
6401
						var block = ' . (!empty(Globals::$conf->global->MAIN_USE_JQUERY_BLOCKUI) ? "true" : "false") . '
6402
						if (block) {
6403
							$.dolEventValid("","' . dol_escape_js($out) . '");
6404
						} else {
6405
							/* jnotify(message, preset of message type, keepmessage) */
6406
							$.jnotify("' . dol_escape_js($out) . '",
6407
							"' . ($style == "ok" ? 3000 : $style) . '",
6408
							' . ($style == "ok" ? "false" : "true") . ',
6409
							{ remove: static function (){} } );
6410
						}
6411
					});
6412
				</script>';
6413
            } else {
6414
                $return = $out;
6415
            }
6416
        }
6417
6418
        return $return;
6419
    }
6420
6421
    /**
6422
     *  Get formated error messages to output (Used to show messages on html output).
6423
     *
6424
     *  @param  string	$mesgstring         Error message
6425
     *  @param  array	$mesgarray          Error messages array
6426
     *  @param  int		$keepembedded       Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
6427
     *  @return string                		Return html output
6428
     *
6429
     *  @see    dol_print_error
6430
     *  @see    dol_htmloutput_mesg
6431
     */
6432
    static function get_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0)
6433
    {
6434
        return get_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
6435
    }
6436
6437
    /**
6438
     * 	Print formated messages to output (Used to show messages on html output).
6439
     *
6440
     * 	@param	string		$mesgstring		Message string or message key
6441
     * 	@param	string[]	$mesgarray      Array of message strings or message keys
6442
     * 	@param  string      $style          Which style to use ('ok', 'warning', 'error')
6443
     * 	@param  int         $keepembedded   Set to 1 if message must be kept embedded into its html place (this disable jnotify)
6444
     * 	@return	void
6445
     *
6446
     * 	@see    dol_print_error
6447
     * 	@see    dol_htmloutput_errors
6448
     * 	@see    setEventMessages
6449
     */
6450
    static function dol_htmloutput_mesg($mesgstring = '', $mesgarray = array(), $style = 'ok', $keepembedded = 0)
6451
    {
6452
        if (empty($mesgstring) && (!is_array($mesgarray) || count($mesgarray) == 0))
6453
            return;
6454
6455
        $iserror = 0;
6456
        $iswarning = 0;
6457
        if (is_array($mesgarray)) {
6458
            foreach ($mesgarray as $val) {
6459
                if ($val && preg_match('/class="error"/i', $val)) {
6460
                    $iserror++;
6461
                    break;
6462
                }
6463
                if ($val && preg_match('/class="warning"/i', $val)) {
6464
                    $iswarning++;
6465
                    break;
6466
                }
6467
            }
6468
        } else if ($mesgstring && preg_match('/class="error"/i', $mesgstring))
6469
            $iserror++;
6470
        else if ($mesgstring && preg_match('/class="warning"/i', $mesgstring))
6471
            $iswarning++;
6472
        if ($style == 'error')
6473
            $iserror++;
6474
        if ($style == 'warning')
6475
            $iswarning++;
6476
6477
        if ($iserror || $iswarning) {
6478
// Remove div from texts
6479
            $mesgstring = preg_replace('/<\/div><div class="(error|warning)">/', '<br>', $mesgstring);
6480
            $mesgstring = preg_replace('/<div class="(error|warning)">/', '', $mesgstring);
6481
            $mesgstring = preg_replace('/<\/div>/', '', $mesgstring);
6482
// Remove div from texts array
6483
            if (is_array($mesgarray)) {
6484
                $newmesgarray = array();
6485
                foreach ($mesgarray as $val) {
6486
                    $tmpmesgstring = preg_replace('/<\/div><div class="(error|warning)">/', '<br>', $val);
6487
                    $tmpmesgstring = preg_replace('/<div class="(error|warning)">/', '', $tmpmesgstring);
6488
                    $tmpmesgstring = preg_replace('/<\/div>/', '', $tmpmesgstring);
6489
                    $newmesgarray[] = $tmpmesgstring;
6490
                }
6491
                $mesgarray = $newmesgarray;
6492
            }
6493
            print get_htmloutput_mesg($mesgstring, $mesgarray, ($iserror ? 'error' : 'warning'), $keepembedded);
6494
        } else
6495
            print get_htmloutput_mesg($mesgstring, $mesgarray, 'ok', $keepembedded);
6496
    }
6497
6498
    /**
6499
     *  Print formated error messages to output (Used to show messages on html output).
6500
     *
6501
     *  @param	string	$mesgstring          Error message
6502
     *  @param  array	$mesgarray           Error messages array
6503
     *  @param  int		$keepembedded        Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
6504
     *  @return	void
6505
     *
6506
     *  @see    dol_print_error
6507
     *  @see    dol_htmloutput_mesg
6508
     */
6509
    static function dol_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0)
6510
    {
6511
        dol_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
6512
    }
6513
6514
    /**
6515
     * 	Advanced sort array by second index function, which produces ascending (default)
6516
     *  or descending output and uses optionally natural case insensitive sorting (which
6517
     *  can be optionally case sensitive as well).
6518
     *
6519
     *  @param      array		$array      		Array to sort (array of array('key','otherkey1','otherkey2'...))
6520
     *  @param      string		$index				Key in array to use for sorting criteria
6521
     *  @param      int			$order				Sort order ('asc' or 'desc')
6522
     *  @param      int			$natsort			1=use "natural" sort (natsort), 0=use "standard" sort (asort)
6523
     *  @param      int			$case_sensitive		1=sort is case sensitive, 0=not case sensitive
6524
     *  @param		int			$keepindex			If 0 and index key of array to sort is a numeric, than index will be rewrote. If 1 or index key is not numeric, key for index is kept after sorting.
6525
     *  @return     array							Sorted array
6526
     */
6527
    static function dol_sort_array(&$array, $index, $order = 'asc', $natsort = 0, $case_sensitive = 0, $keepindex = 0)
6528
    {
6529
// Clean parameters
6530
        $order = strtolower($order);
6531
6532
        if (is_array($array)) {
6533
            $sizearray = count($array);
6534
            if ($sizearray > 0) {
6535
                $temp = array();
6536
                foreach (array_keys($array) as $key)
6537
                    $temp[$key] = $array[$key][$index];
6538
6539
                if (!$natsort)
6540
                    ($order == 'asc') ? asort($temp) : arsort($temp);
6541
                else {
6542
                    ($case_sensitive) ? natsort($temp) : natcasesort($temp);
6543
                    if ($order != 'asc')
6544
                        $temp = array_reverse($temp, true);
6545
                }
6546
6547
                $sorted = array();
6548
6549
                foreach (array_keys($temp) as $key) {
6550
                    (is_numeric($key) && empty($keepindex)) ? $sorted[] = $array[$key] : $sorted[$key] = $array[$key];
6551
                }
6552
6553
                return $sorted;
6554
            }
6555
        }
6556
        return $array;
6557
    }
6558
6559
    /**
6560
     *      Check if a string is in UTF8
6561
     *
6562
     *      @param	string	$str        String to check
6563
     * 		@return	boolean				True if string is UTF8 or ISO compatible with UTF8, False if not (ISO with special char or Binary)
6564
     */
6565
    static function utf8_check($str)
6566
    {
6567
        // We must use here a binary strlen static function (so not AlDolUtils::dol_strlen)
6568
        $strLength = AlDolUtils::dol_strlen($str);
6569
        for ($i = 0; $i < $strLength; $i++) {
6570
            if (ord($str[$i]) < 0x80)
6571
                continue; // 0bbbbbbb
6572
            elseif ((ord($str[$i]) & 0xE0) == 0xC0)
6573
                $n = 1; // 110bbbbb
6574
            elseif ((ord($str[$i]) & 0xF0) == 0xE0)
6575
                $n = 2; // 1110bbbb
6576
            elseif ((ord($str[$i]) & 0xF8) == 0xF0)
6577
                $n = 3; // 11110bbb
6578
            elseif ((ord($str[$i]) & 0xFC) == 0xF8)
6579
                $n = 4; // 111110bb
6580
            elseif ((ord($str[$i]) & 0xFE) == 0xFC)
6581
                $n = 5; // 1111110b
6582
            else
6583
                return false; // Does not match any model
6584
            for ($j = 0; $j < $n; $j++) { // n bytes matching 10bbbbbb follow ?
6585
                if (( ++$i == strlen($str)) || ((ord($str[$i]) & 0xC0) != 0x80))
6586
                    return false;
6587
            }
6588
        }
6589
        return true;
6590
    }
6591
6592
    /**
6593
     *      Return a string encoded into OS filesystem encoding. This static function is used to define
6594
     * 	    value to pass to filesystem PHP functions.
6595
     *
6596
     *      @param	string	$str        String to encode (UTF-8)
6597
     * 		@return	string				Encoded string (UTF-8, ISO-8859-1)
6598
     */
6599
    function dol_osencode($str)
6600
    {
6601
        // global Globals::$conf;
6602
6603
        $tmp = ini_get("unicode.filesystem_encoding");      // Disponible avec PHP 6.0
6604
        if (empty($tmp) && !empty($_SERVER["WINDIR"]))
6605
            $tmp = 'iso-8859-1'; // By default for windows
6606
        if (empty($tmp))
6607
            $tmp = 'utf-8';          // By default for other
6608
        if (!empty(Globals::$conf->global->MAIN_FILESYSTEM_ENCODING))
6609
            $tmp = Globals::$conf->global->MAIN_FILESYSTEM_ENCODING;
6610
6611
        if ($tmp == 'iso-8859-1')
6612
            return utf8_decode($str);
6613
        return $str;
6614
    }
6615
6616
    /**
6617
     *      Return an id or code from a code or id.
6618
     *      Store also Code-Id into a cache to speed up next request on same key.
6619
     *
6620
     * 		@param	DoliDB	$db				Database handler
6621
     * 		@param	string	$key			Code or Id to get Id or Code
6622
     * 		@param	string	$tablename		Table name without prefix
6623
     * 		@param	string	$fieldkey		Field to search the key into
6624
     * 		@param	string	$fieldid		Field to get
6625
     *      @param  int		$entityfilter	Filter by entity
6626
     *      @return int						<0 if KO, Id of code if OK
6627
     *      @see Globals::$langs->getLabelFromKey
6628
     */
6629
    static function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid = 'id', $entityfilter = 0)
6630
    {
6631
        // global $cache_codes;
6632
// If key empty
6633
        if ($key == '')
6634
            return '';
6635
6636
// Check in cache
6637
        if (isset($cache_codes[$tablename][$key][$fieldid])) { // Can be defined to 0 or ''
6638
            return $cache_codes[$tablename][$key][$fieldid];   // Found in cache
6639
        }
6640
6641
        AlDolUtils::dol_syslog('dol_getIdFromCode (value not found into cache)', LOG_DEBUG);
6642
6643
        $sql = "SELECT " . $fieldid . " as valuetoget";
6644
        $sql .= " FROM " . MAIN_DB_PREFIX . $tablename;
6645
        $sql .= " WHERE " . $fieldkey . " = '" . $db->escape($key) . "'";
6646
        if (!empty($entityfilter))
6647
            $sql .= " AND entity IN (" . getEntity($tablename) . ")";
6648
6649
        $resql = $db->query($sql);
6650
        if ($resql) {
6651
            $obj = $db->fetch_object($resql);
6652
            if ($obj)
6653
                $cache_codes[$tablename][$key][$fieldid] = $obj->valuetoget;
6654
            else
6655
                $cache_codes[$tablename][$key][$fieldid] = '';
6656
            $db->free($resql);
6657
            return $cache_codes[$tablename][$key][$fieldid];
6658
        }
6659
        else {
6660
            return -1;
6661
        }
6662
    }
6663
6664
    /**
6665
     * Verify if condition in string is ok or not
6666
     *
6667
     * @param 	string		$strRights		String with condition to check
6668
     * @return 	boolean						True or False. Return True if strRights is ''
6669
     */
6670
    static function verifCond($strRights)
6671
    {
6672
        // global $user, Globals::$conf, Globals::$langs;
6673
        // global $leftmenu;
6674
        // global $rights;    // To export to dol_eval function
6675
//print $strRights."<br>\n";
6676
        $rights = true;
6677
        if ($strRights != '') {
6678
            $str = 'if(!(' . $strRights . ')) { $rights = false; }';
6679
            self::dol_eval($str);  // The dol_eval must contains all the// global $xxx used into a condition
6680
        }
6681
        return $rights;
6682
    }
6683
6684
    /**
6685
     * Replace eval static function to add more security.
6686
     * This static function is called by verifCond() or trans() and transnoentitiesnoconv().
6687
     *
6688
     * @param 	string	$s				String to evaluate
6689
     * @param	int		$returnvalue	0=No return (used to execute eval($a=something)). 1=Value of eval is returned (used to eval($something)).
6690
     * @param   int     $hideerrors     1=Hide errors
6691
     * @return	mixed					Nothing or return of eval
6692
     */
6693
    static function dol_eval($s, $returnvalue = 0, $hideerrors = 1)
6694
    {
6695
// Only// global variables can be changed by eval static function and returned to caller
6696
        // global $db, Globals::$langs, $user, Globals::$conf, $website, $websitepage;
6697
        // global $action, $mainmenu, $leftmenu;
6698
        // global $rights;
6699
        // global $object;
6700
        // global $mysoc;
6701
        // global $obj;       // To get $obj used into list when dol_eval is used for computed fields and $obj is not yet $object
6702
        // global $soc;       // For backward compatibility
6703
//print $s."<br>\n";
6704
        if ($returnvalue) {
6705
            if ($hideerrors)
6706
                return @eval('return ' . $s . ';');
6707
            else
6708
                return eval('return ' . $s . ';');
6709
        }
6710
        else {
6711
            if ($hideerrors)
6712
                @eval($s);
6713
            else
6714
                eval($s);
6715
        }
6716
    }
6717
6718
    /**
6719
     * Return if var element is ok
6720
     *
6721
     * @param   string      $element    Variable to check
6722
     * @return  boolean                 Return true of variable is not empty
6723
     */
6724
    static function dol_validElement($element)
6725
    {
6726
        return (trim($element) != '');
6727
    }
6728
6729
    /**
6730
     * 	Return img flag of country for a language code or country code
6731
     *
6732
     * 	@param	string	$codelang	Language code (en_IN, fr_CA...) or Country code (IN, FR)
6733
     *  @param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
6734
     * 	@return	string				HTML img string with flag.
6735
     */
6736
    static function picto_from_langcode($codelang, $moreatt = '')
6737
    {
6738
        // global Globals::$langs;
6739
6740
        if (empty($codelang))
6741
            return '';
6742
6743
        if ($codelang == 'auto') {
6744
            return '<span class="fa fa-globe"></span>';
6745
        }
6746
6747
        $langtocountryflag = array(
6748
            'ar_AR' => '',
6749
            'ca_ES' => 'catalonia',
6750
            'da_DA' => 'dk',
6751
            'fr_CA' => 'mq',
6752
            'sv_SV' => 'se'
6753
        );
6754
6755
        if (isset($langtocountryflag[$codelang]))
6756
            $flagImage = $langtocountryflag[$codelang];
6757
        else {
6758
            $tmparray = explode('_', $codelang);
6759
            $flagImage = empty($tmparray[1]) ? $tmparray[0] : $tmparray[1];
6760
        }
6761
6762
        return img_picto_common($codelang, 'flags/' . strtolower($flagImage) . '.png', $moreatt);
6763
    }
6764
6765
    /**
6766
     * Return default language from country code
6767
     *
6768
     * @param 	string 	$countrycode	Country code like 'US', 'FR', 'CA', ...
6769
     * @return	string					Value of locale like 'en_US', 'fr_FR', ...
6770
     */
6771
    static function getLanguageCodeFromCountryCode($countrycode)
6772
    {
6773
        // global $mysoc;
6774
6775
        if (strtoupper($countrycode) == 'MQ')
6776
            return 'fr_CA';
6777
        if (strtoupper($countrycode) == 'SE')
6778
            return 'sv_SE'; // se_SE is Sami/Sweden, and we want in priority sv_SE for SE country
6779
        if (strtoupper($countrycode) == 'CH') {
6780
            if ($mysoc->country_code == 'FR')
6781
                return 'fr_CH';
6782
            if ($mysoc->country_code == 'DE')
6783
                return 'de_CH';
6784
        }
6785
6786
// Locale list taken from:
6787
// http://stackoverflow.com/questions/3191664/
6788
// list-of-all-locales-and-their-short-codes
6789
        $locales = array(
6790
            'af-ZA',
6791
            'am-ET',
6792
            'ar-AE',
6793
            'ar-BH',
6794
            'ar-DZ',
6795
            'ar-EG',
6796
            'ar-IQ',
6797
            'ar-JO',
6798
            'ar-KW',
6799
            'ar-LB',
6800
            'ar-LY',
6801
            'ar-MA',
6802
            'ar-OM',
6803
            'ar-QA',
6804
            'ar-SA',
6805
            'ar-SY',
6806
            'ar-TN',
6807
            'ar-YE',
6808
            'as-IN',
6809
            'ba-RU',
6810
            'be-BY',
6811
            'bg-BG',
6812
            'bn-BD',
6813
            'bn-IN',
6814
            'bo-CN',
6815
            'br-FR',
6816
            'ca-ES',
6817
            'co-FR',
6818
            'cs-CZ',
6819
            'cy-GB',
6820
            'da-DK',
6821
            'de-AT',
6822
            'de-CH',
6823
            'de-DE',
6824
            'de-LI',
6825
            'de-LU',
6826
            'dv-MV',
6827
            'el-GR',
6828
            'en-AU',
6829
            'en-BZ',
6830
            'en-CA',
6831
            'en-GB',
6832
            'en-IE',
6833
            'en-IN',
6834
            'en-JM',
6835
            'en-MY',
6836
            'en-NZ',
6837
            'en-PH',
6838
            'en-SG',
6839
            'en-TT',
6840
            'en-US',
6841
            'en-ZA',
6842
            'en-ZW',
6843
            'es-AR',
6844
            'es-BO',
6845
            'es-CL',
6846
            'es-CO',
6847
            'es-CR',
6848
            'es-DO',
6849
            'es-EC',
6850
            'es-ES',
6851
            'es-GT',
6852
            'es-HN',
6853
            'es-MX',
6854
            'es-NI',
6855
            'es-PA',
6856
            'es-PE',
6857
            'es-PR',
6858
            'es-PY',
6859
            'es-SV',
6860
            'es-US',
6861
            'es-UY',
6862
            'es-VE',
6863
            'et-EE',
6864
            'eu-ES',
6865
            'fa-IR',
6866
            'fi-FI',
6867
            'fo-FO',
6868
            'fr-BE',
6869
            'fr-CA',
6870
            'fr-CH',
6871
            'fr-FR',
6872
            'fr-LU',
6873
            'fr-MC',
6874
            'fy-NL',
6875
            'ga-IE',
6876
            'gd-GB',
6877
            'gl-ES',
6878
            'gu-IN',
6879
            'he-IL',
6880
            'hi-IN',
6881
            'hr-BA',
6882
            'hr-HR',
6883
            'hu-HU',
6884
            'hy-AM',
6885
            'id-ID',
6886
            'ig-NG',
6887
            'ii-CN',
6888
            'is-IS',
6889
            'it-CH',
6890
            'it-IT',
6891
            'ja-JP',
6892
            'ka-GE',
6893
            'kk-KZ',
6894
            'kl-GL',
6895
            'km-KH',
6896
            'kn-IN',
6897
            'ko-KR',
6898
            'ky-KG',
6899
            'lb-LU',
6900
            'lo-LA',
6901
            'lt-LT',
6902
            'lv-LV',
6903
            'mi-NZ',
6904
            'mk-MK',
6905
            'ml-IN',
6906
            'mn-MN',
6907
            'mr-IN',
6908
            'ms-BN',
6909
            'ms-MY',
6910
            'mt-MT',
6911
            'nb-NO',
6912
            'ne-NP',
6913
            'nl-BE',
6914
            'nl-NL',
6915
            'nn-NO',
6916
            'oc-FR',
6917
            'or-IN',
6918
            'pa-IN',
6919
            'pl-PL',
6920
            'ps-AF',
6921
            'pt-BR',
6922
            'pt-PT',
6923
            'rm-CH',
6924
            'ro-RO',
6925
            'ru-RU',
6926
            'rw-RW',
6927
            'sa-IN',
6928
            'se-FI',
6929
            'se-NO',
6930
            'se-SE',
6931
            'si-LK',
6932
            'sk-SK',
6933
            'sl-SI',
6934
            'sq-AL',
6935
            'sv-FI',
6936
            'sv-SE',
6937
            'sw-KE',
6938
            'ta-IN',
6939
            'te-IN',
6940
            'th-TH',
6941
            'tk-TM',
6942
            'tn-ZA',
6943
            'tr-TR',
6944
            'tt-RU',
6945
            'ug-CN',
6946
            'uk-UA',
6947
            'ur-PK',
6948
            'vi-VN',
6949
            'wo-SN',
6950
            'xh-ZA',
6951
            'yo-NG',
6952
            'zh-CN',
6953
            'zh-HK',
6954
            'zh-MO',
6955
            'zh-SG',
6956
            'zh-TW',
6957
            'zu-ZA',
6958
        );
6959
6960
        $buildprimarykeytotest = strtolower($countrycode) . '-' . strtoupper($countrycode);
6961
        if (in_array($buildprimarykeytotest, $locales))
6962
            return strtolower($countrycode) . '_' . strtoupper($countrycode);
6963
6964
        foreach ($locales as $locale) {
6965
            $locale_language = locale_get_primary_language($locale);
6966
            $locale_region = locale_get_region($locale);
6967
            if (strtoupper($countrycode) == $locale_region) {
6968
//var_dump($locale.'-'.$locale_language.'-'.$locale_region);
6969
                return strtolower($locale_language) . '_' . strtoupper($locale_region);
6970
            }
6971
        }
6972
6973
        return null;
6974
    }
6975
6976
    /**
6977
     *  Complete or removed entries into a head array (used to build tabs).
6978
     *  For example, with value added by external modules. Such values are declared into Globals::$conf->modules_parts['tab'].
6979
     *  Or by change using hook completeTabsHead
6980
     *
6981
     *  @param	Conf			$conf           Object conf
6982
     *  @param  Translate		Globals::$langs          Object langs
6983
     *  @param  object|null		$object         Object object
6984
     *  @param  array			$head          	Object head
6985
     *  @param  int				$h				New position to fill
6986
     *  @param  string			$type           Value for object where objectvalue can be
6987
     *                              			'thirdparty'       to add a tab in third party view
6988
     * 		                        	      	'intervention'     to add a tab in intervention view
6989
     *     		                    	     	'supplier_order'   to add a tab in supplier order view
6990
     *          		            	        'supplier_invoice' to add a tab in supplier invoice view
6991
     *                  		    	        'invoice'          to add a tab in customer invoice view
6992
     *                          			    'order'            to add a tab in customer order view
6993
     *                          				'contract'		   to add a tabl in contract view
6994
     *                      			        'product'          to add a tab in product view
6995
     *                              			'propal'           to add a tab in propal view
6996
     *                              			'user'             to add a tab in user view
6997
     *                              			'group'            to add a tab in group view
6998
     * 		        	               	     	'member'           to add a tab in fundation member view
6999
     *      		                        	'categories_x'	   to add a tab in category view ('x': type of category (0=product, 1=supplier, 2=customer, 3=member)
7000
     *      									'ecm'			   to add a tab for another ecm view
7001
     *                                          'stock'            to add a tab for warehouse view
7002
     *  @param  string		$mode  	        	'add' to complete head, 'remove' to remove entries
7003
     * 	@return	void
7004
     */
7005
    static function complete_head_from_modules($conf, $langs, $object, &$head, &$h, $type, $mode = 'add')
7006
    {
7007
        // global Globals::$hookManager;
7008
7009
        if (isset(Globals::$conf->modules_parts['tabs'][$type]) && is_array(Globals::$conf->modules_parts['tabs'][$type])) {
7010
            foreach (Globals::$conf->modules_parts['tabs'][$type] as $value) {
7011
7012
                $values = explode(':', $value);
7013
7014
                if ($mode == 'add' && !preg_match('/^\-/', $values[1])) {
7015
                    if (count($values) == 6) {       // new declaration with permissions:  $value='objecttype:+tabname1:Title1:langfile@mymodule:$user->rights->mymodule->read:/mymodule/mynewtab1.php?id=__ID__'
7016
                        if ($values[0] != $type)
7017
                            continue;
7018
7019
                        if (verifCond($values[4])) {
7020
                            if ($values[3])
7021
                                Globals::$langs->load($values[3]);
7022
                            if (preg_match('/SUBSTITUTION_([^_]+)/i', $values[2], $reg)) {
7023
                                $substitutionarray = array();
7024
                                complete_substitutions_array($substitutionarray, Globals::$langs, $object, array('needforkey' => $values[2]));
7025
                                $label = make_substitutions($reg[1], $substitutionarray);
7026
                            } else
7027
                                $label = Globals::$langs->trans($values[2]);
7028
7029
                            //$head[$h][0] = dol_buildpath(preg_replace('/__ID__/i', ((is_object($object) && !empty($object->id)) ? $object->id : ''), $values[5]), 1);
7030
                            $head[$h][0] = BASE_URI . preg_replace('/__ID__/i', ((is_object($object) && !empty($object->id)) ? $object->id : ''), $values[5]);
7031
                            $head[$h][1] = $label;
7032
                            $head[$h][2] = str_replace('+', '', $values[1]);
7033
                            $h++;
7034
                        }
7035
                    }
7036
                    else if (count($values) == 5) {       // deprecated
7037
                        AlDolUtils::dol_syslog('Passing 5 values in tabs module_parts is deprecated. Please update to 6 with permissions.', LOG_WARNING);
7038
7039
                        if ($values[0] != $type)
7040
                            continue;
7041
                        if ($values[3])
7042
                            Globals::$langs->load($values[3]);
7043
                        if (preg_match('/SUBSTITUTION_([^_]+)/i', $values[2], $reg)) {
7044
                            $substitutionarray = array();
7045
                            complete_substitutions_array($substitutionarray, Globals::$langs, $object, array('needforkey' => $values[2]));
7046
                            $label = make_substitutions($reg[1], $substitutionarray);
7047
                        } else
7048
                            $label = Globals::$langs->trans($values[2]);
7049
7050
                        $head[$h][0] = dol_buildpath(preg_replace('/__ID__/i', ((is_object($object) && !empty($object->id)) ? $object->id : ''), $values[4]), 1);
7051
                        $head[$h][1] = $label;
7052
                        $head[$h][2] = str_replace('+', '', $values[1]);
7053
                        $h++;
7054
                    }
7055
                }
7056
                else if ($mode == 'remove' && preg_match('/^\-/', $values[1])) {
7057
                    if ($values[0] != $type)
7058
                        continue;
7059
                    $tabname = str_replace('-', '', $values[1]);
7060
                    foreach ($head as $key => $val) {
7061
                        $condition = (!empty($values[3]) ? verifCond($values[3]) : 1);
7062
                        //var_dump($key.' - '.$tabname.' - '.$head[$key][2].' - '.$values[3].' - '.$condition);
7063
                        if ($head[$key][2] == $tabname && $condition) {
7064
                            unset($head[$key]);
7065
                            break;
7066
                        }
7067
                    }
7068
                }
7069
            }
7070
        }
7071
7072
// No need to make a return $head. Var is modified as a reference
7073
        if (!empty(Globals::$hookManager)) {
7074
            $parameters = array('object' => $object, 'mode' => $mode, 'head' => $head);
7075
            $reshook = Globals::$hookManager->executeHooks('completeTabsHead', $parameters);
7076
            if ($reshook > 0) {
7077
                $head = Globals::$hookManager->resArray;
7078
                $h = count($head);
7079
            }
7080
        }
7081
    }
7082
7083
    /**
7084
     * Print common footer :
7085
     * 		conf->global->MAIN_HTML_FOOTER
7086
     *      js for switch of menu hider
7087
     * 		js for conf->global->MAIN_GOOGLE_AN_ID
7088
     * 		js for conf->global->MAIN_SHOW_TUNING_INFO or $_SERVER["MAIN_SHOW_TUNING_INFO"]
7089
     * 		js for conf->logbuffer
7090
     *
7091
     * @param	string	$zone	'private' (for private pages) or 'public' (for public pages)
7092
     * @return	void
7093
     */
7094
    static function printCommonFooter($zone = 'private')
7095
    {
7096
        // global Globals::$conf, Globals::$hookManager, $user;
7097
        // global $action;
7098
        // global $micro_start_time;
7099
7100
        if ($zone == 'private')
7101
            print "\n" . '<!-- Common footer for private page -->' . "\n";
7102
        else
7103
            print "\n" . '<!-- Common footer for public page -->' . "\n";
7104
7105
// A div to store page_y POST parameter so we can read it using javascript
7106
        print "\n<!-- A div to store page_y POST paramater -->\n";
7107
        print '<div id="page_y" style="display: none;">' . filter_input(INPUT_POST, 'page_y') . '</div>' . "\n";
7108
7109
        $parameters = array();
7110
        $reshook = Globals::$hookManager->executeHooks('printCommonFooter', $parameters);    // Note that $action and $object may have been modified by some hooks
7111
        if (empty($reshook)) {
7112
            if (!empty(Globals::$conf->global->MAIN_HTML_FOOTER))
7113
                print Globals::$conf->global->MAIN_HTML_FOOTER . "\n";
7114
7115
            print "\n";
7116
            if (!empty(Globals::$conf->use_javascript_ajax)) {
7117
                print '<script type="text/javascript" language="javascript">' . "\n";
7118
                print 'jQuery(document).ready(function() {' . "\n";
7119
7120
                if ($zone == 'private' && empty(Globals::$conf->dol_use_jmobile)) {
7121
                    print "\n";
7122
                    print '/* JS CODE TO ENABLE to manage handler to switch left menu page (menuhider) */' . "\n";
7123
                    print 'jQuery(".menuhider").click(function() {';
7124
                    print '  console.log("We click on .menuhider");' . "\n";
7125
                    //print "  $('.side-nav').animate({width:'toggle'},200);\n";     // OK with eldy theme but not with md
7126
                    print "  $('.side-nav').toggle()\n";
7127
                    print "  $('.login_block').toggle()\n";
7128
                    print '});' . "\n";
7129
                }
7130
7131
// Management of focus and mandatory for fields
7132
                if ($action == 'create' || $action == 'edit' || (empty($action) && (preg_match('/new\.php/', $_SERVER["PHP_SELF"])))) {
7133
                    print '/* JS CODE TO ENABLE to manage focus and mandatory form fields */' . "\n";
7134
                    $relativepathstring = $_SERVER["PHP_SELF"];
7135
                    // Clean $relativepathstring
7136
                    if (constant('DOL_BASE_URI'))
7137
                        $relativepathstring = preg_replace('/^' . preg_quote(constant('DOL_BASE_URI'), '/') . '/', '', $relativepathstring);
7138
                    $relativepathstring = preg_replace('/^\//', '', $relativepathstring);
7139
                    $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
7140
                    $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
7141
                    if (!empty($user->default_values[$relativepathstring]['focus'])) {
7142
                        foreach ($user->default_values[$relativepathstring]['focus'] as $defkey => $defval) {
7143
                            $qualified = 0;
7144
                            if ($defkey != '_noquery_') {
7145
                                $tmpqueryarraytohave = explode('&', $defkey);
7146
                                $foundintru = 0;
7147
                                foreach ($tmpqueryarraytohave as $tmpquerytohave) {
7148
                                    if (!in_array($tmpquerytohave, $tmpqueryarraywehave))
7149
                                        $foundintru = 1;
7150
                                }
7151
                                if (!$foundintru)
7152
                                    $qualified = 1;
7153
                                //var_dump($defkey.'-'.$qualified);
7154
                            } else
7155
                                $qualified = 1;
7156
7157
                            if ($qualified) {
7158
                                foreach ($defval as $paramkey => $paramval) {
7159
                                    // Set focus on field
7160
                                    print 'jQuery("input[name=\'' . $paramkey . '\']").focus();' . "\n";
7161
                                    print 'jQuery("textarea[name=\'' . $paramkey . '\']").focus();' . "\n";
7162
                                    print 'jQuery("select[name=\'' . $paramkey . '\']").focus();' . "\n";  // Not really usefull, but we keep it in case of.
7163
                                }
7164
                            }
7165
                        }
7166
                    }
7167
                    if (!empty($user->default_values[$relativepathstring]['mandatory'])) {
7168
                        foreach ($user->default_values[$relativepathstring]['mandatory'] as $defkey => $defval) {
7169
                            $qualified = 0;
7170
                            if ($defkey != '_noquery_') {
7171
                                $tmpqueryarraytohave = explode('&', $defkey);
7172
                                $foundintru = 0;
7173
                                foreach ($tmpqueryarraytohave as $tmpquerytohave) {
7174
                                    if (!in_array($tmpquerytohave, $tmpqueryarraywehave))
7175
                                        $foundintru = 1;
7176
                                }
7177
                                if (!$foundintru)
7178
                                    $qualified = 1;
7179
                                //var_dump($defkey.'-'.$qualified);
7180
                            } else
7181
                                $qualified = 1;
7182
7183
                            if ($qualified) {
7184
                                foreach ($defval as $paramkey => $paramval) {
7185
                                    // Add property 'required' on input
7186
                                    print 'jQuery("input[name=\'' . $paramkey . '\']").prop(\'required\',true);' . "\n";
7187
                                    print 'jQuery("textarea[name=\'' . $paramkey . '\']").prop(\'required\',true);' . "\n";
7188
                                    print 'jQuery("select[name=\'' . $paramkey . '\']").prop(\'required\',true);' . "\n";  // required on a select works only if key is "", this does not happen in Dolibarr
7189
                                }
7190
                            }
7191
                        }
7192
                    }
7193
                }
7194
7195
                print '});' . "\n";
7196
7197
// Google Analytics
7198
// TODO Add a hook here
7199
                if (!empty(Globals::$conf->google->enabled) && !empty(Globals::$conf->global->MAIN_GOOGLE_AN_ID)) {
7200
                    if ((Globals::$conf->dol_use_jmobile != 4)) {
7201
                        print "\n";
7202
                        print "/* JS CODE TO ENABLE for google analtics tag */\n";
7203
                        print '  var _gaq = _gaq || [];' . "\n";
7204
                        print '  _gaq.push([\'_setAccount\', \'' . Globals::$conf->global->MAIN_GOOGLE_AN_ID . '\']);' . "\n";
7205
                        print '  _gaq.push([\'_trackPageview\']);' . "\n";
7206
                        print '' . "\n";
7207
                        print '  (function() {' . "\n";
7208
                        print '    var ga = document.createElement(\'script\'); ga.type = \'text/javascript\'; ga.async = true;' . "\n";
7209
                        print '    ga.src = (\'https:\' == document.location.protocol ? \'https://ssl\' : \'http://www\') + \'.google-analytics.com/ga.js\';' . "\n";
7210
                        print '    var s = document.getElementsByTagName(\'script\')[0]; s.parentNode.insertBefore(ga, s);' . "\n";
7211
                        print '  })();' . "\n";
7212
                    }
7213
                }
7214
7215
// End of tuning
7216
                if (!empty($_SERVER['MAIN_SHOW_TUNING_INFO']) || !empty(Globals::$conf->global->MAIN_SHOW_TUNING_INFO)) {
7217
                    print "\n";
7218
                    print "/* JS CODE TO ENABLE to add memory info */\n";
7219
                    print 'window.console && console.log("';
7220
                    if (!empty(Globals::$conf->global->MEMCACHED_SERVER))
7221
                        print 'MEMCACHED_SERVER=' . Globals::$conf->global->MEMCACHED_SERVER . ' - ';
7222
                    print 'MAIN_OPTIMIZE_SPEED=' . (isset(Globals::$conf->global->MAIN_OPTIMIZE_SPEED) ? Globals::$conf->global->MAIN_OPTIMIZE_SPEED : 'off');
7223
                    if (!empty($micro_start_time)) {   // Works only if MAIN_SHOW_TUNING_INFO is defined at $_SERVER level. Not in// global variable.
7224
                        $micro_end_time = microtime(true);
7225
                        print ' - Build time: ' . ceil(1000 * ($micro_end_time - $micro_start_time)) . ' ms';
7226
                    }
7227
                    if (function_exists("memory_get_usage")) {
7228
                        print ' - Mem: ' . memory_get_usage();
7229
                    }
7230
                    if (function_exists("xdebug_memory_usage")) {
7231
                        print ' - XDebug time: ' . ceil(1000 * xdebug_time_index()) . ' ms';
7232
                        print ' - XDebug mem: ' . xdebug_memory_usage();
7233
                        print ' - XDebug mem peak: ' . xdebug_peak_memory_usage();
7234
                    }
7235
                    if (function_exists("zend_loader_file_encoded")) {
7236
                        print ' - Zend encoded file: ' . (zend_loader_file_encoded() ? 'yes' : 'no');
7237
                    }
7238
                    print '");' . "\n";
7239
                }
7240
7241
                print "\n" . '</script>' . "\n";
7242
            }
7243
7244
// Add Xdebug coverage of code
7245
            if (defined('XDEBUGCOVERAGE')) {
7246
                print_r(xdebug_get_code_coverage());
7247
            }
7248
7249
// If there is some logs in buffer to show
7250
            if (count(Globals::$conf->logbuffer)) {
7251
                print "\n";
7252
                print "<!-- Start of log output\n";
7253
//print '<div class="hidden">'."\n";
7254
                foreach (Globals::$conf->logbuffer as $logline) {
7255
                    print $logline . "<br>\n";
7256
                }
7257
//print '</div>'."\n";
7258
                print "End of log output -->\n";
7259
            }
7260
        }
7261
    }
7262
7263
    /**
7264
     * Split a string with 2 keys into key array.
7265
     * For example: "A=1;B=2;C=2" is exploded into array('A'=>1,'B'=>2,'C'=>3)
7266
     *
7267
     * @param 	string	$string		String to explode
7268
     * @param 	string	$delimiter	Delimiter between each couple of data
7269
     * @param 	string	$kv			Delimiter between key and value
7270
     * @return	array				Array of data exploded
7271
     */
7272
    static function dolExplodeIntoArray($string, $delimiter = ';', $kv = '=')
7273
    {
7274
        if ($a = explode($delimiter, $string)) {
7275
            $ka = array();
7276
            foreach ($a as $s) { // each part
7277
                if ($s) {
7278
                    if ($pos = strpos($s, $kv)) { // key/value delimiter
7279
                        $ka[trim(substr($s, 0, $pos))] = trim(substr($s, $pos + strlen($kv)));
7280
                    } else { // key delimiter not found
7281
                        $ka[] = trim($s);
7282
                    }
7283
                }
7284
            }
7285
            return $ka;
7286
        }
7287
        return array();
7288
    }
7289
7290
    /**
7291
     * Set focus onto field with selector (similar behaviour of 'autofocus' HTML5 tag)
7292
     *
7293
     * @param 	string	$selector	Selector ('#id' or 'input[name="ref"]') to use to find the HTML input field that must get the autofocus. You must use a CSS selector, so unique id preceding with the '#' char.
7294
     * @return	string				HTML code to set focus
7295
     */
7296
    static function dol_set_focus($selector)
7297
    {
7298
        print "\n" . '<!-- Set focus onto a specific field -->' . "\n";
7299
        print '<script type="text/javascript" language="javascript">jQuery(document).ready(function() { jQuery("' . dol_escape_js($selector) . '").focus(); });</script>' . "\n";
7300
    }
7301
7302
    /**
7303
     * Return getmypid() or random PID when static function is disabled
7304
     * Some web hosts disable this php static function for security reasons
7305
     * and sometimes we can't redeclare function
7306
     *
7307
     * @return	int
7308
     */
7309
    static function dol_getmypid()
7310
    {
7311
        if (!function_exists('getmypid')) {
7312
            return mt_rand(1, 32768);
7313
        } else {
7314
            return getmypid();
7315
        }
7316
    }
7317
7318
    /**
7319
     * Generate natural SQL search string for a criteria (this criteria can be tested on one or several fields)
7320
     *
7321
     * @param   string|string[]	$fields 	String or array of strings, filled with the name of all fields in the SQL query we must check (combined with a OR). Example: array("p.field1","p.field2")
7322
     * @param   string 			$value 		The value to look for.
7323
     *                          		    If param $mode is 0, can contains several keywords separated with a space or |
7324
     *                                      like "keyword1 keyword2" = We want record field like keyword1 AND field like keyword2
7325
     *                                      or like "keyword1|keyword2" = We want record field like keyword1 OR field like keyword2
7326
     *                             			If param $mode is 1, can contains an operator <, > or = like "<10" or ">=100.5 < 1000"
7327
     *                             			If param $mode is 2, can contains a list of int id separated by comma like "1,3,4"
7328
     *                             			If param $mode is 3, can contains a list of string separated by comma like "a,b,c"
7329
     * @param	integer			$mode		0=value is list of keyword strings, 1=value is a numeric test (Example ">5.5 <10"), 2=value is a list of ID separated with comma (Example '1,3,4')
7330
     * 										3=value is list of string separated with comma (Example 'text 1,text 2'), 4=value is a list of ID separated with comma (Example '1,3,4') for search into a multiselect string ('1,2')
7331
     * @param	integer			$nofirstand	1=Do not output the first 'AND'
7332
     * @return 	string 			$res 		The statement to append to the SQL query
7333
     */
7334
    static function natural_search($fields, $value, $mode = 0, $nofirstand = 0)
7335
    {
7336
        // global $db, Globals::$langs;
7337
7338
        $value = trim($value);
7339
7340
        if ($mode == 0) {
7341
            $value = preg_replace('/\*/', '%', $value); // Replace * with %
7342
        }
7343
        if ($mode == 1) {
7344
            $value = preg_replace('/([<>=]+)\s+([0-9' . preg_quote(Globals::$langs->trans("DecimalSeparator"), '/') . '\-])/', '\1\2', $value); // Clean string '< 10' into '<10' so we can the explode on space to get all tests to do
7345
        }
7346
7347
        $value = preg_replace('/\s*\|\s*/', '|', $value);
7348
7349
        $crits = explode(' ', $value);
7350
        $res = '';
7351
        if (!is_array($fields))
7352
            $fields = array($fields);
7353
7354
        $nboffields = count($fields);
7355
        $end2 = count($crits);
7356
        $j = 0;
7357
        foreach ($crits as $crit) {
7358
            $i = 0;
7359
            $i2 = 0;
7360
            $newres = '';
7361
            foreach ($fields as $field) {
7362
                if ($mode == 1) {
7363
                    $operator = '=';
7364
                    $newcrit = preg_replace('/([<>=]+)/', '', trim($crit));
7365
7366
                    preg_match('/([<>=]+)/', trim($crit), $reg);
7367
                    if ($reg[1]) {
7368
                        $operator = $reg[1];
7369
                    }
7370
                    if ($newcrit != '') {
7371
                        $numnewcrit = price2num($newcrit);
7372
                        if (is_numeric($numnewcrit)) {
7373
                            $newres .= ($i2 > 0 ? ' OR ' : '') . $field . ' ' . $operator . ' ' . $numnewcrit;
7374
                        } else {
7375
                            $newres .= ($i2 > 0 ? ' OR ' : '') . '1 = 2'; // force false
7376
                        }
7377
                        $i2++; // a criteria was added to string
7378
                    }
7379
                } else if ($mode == 2) {
7380
                    $newres .= ($i2 > 0 ? ' OR ' : '') . $field . " IN (" . $db->escape(trim($crit)) . ")";
7381
                    $i2++; // a criteria was added to string
7382
                } else if ($mode == 3) {
7383
                    $tmparray = explode(',', trim($crit));
7384
                    if (count($tmparray)) {
7385
                        $listofcodes = '';
7386
                        foreach ($tmparray as $val) {
7387
                            if ($val) {
7388
                                $listofcodes .= ($listofcodes ? ',' : '');
7389
                                $listofcodes .= "'" . $db->escape(trim($val)) . "'";
7390
                            }
7391
                        }
7392
                        $newres .= ($i2 > 0 ? ' OR ' : '') . $field . " IN (" . $listofcodes . ")";
7393
                        $i2++; // a criteria was added to string
7394
                    }
7395
                } else if ($mode == 4) {
7396
                    $tmparray = explode(',', trim($crit));
7397
                    if (count($tmparray)) {
7398
                        $listofcodes = '';
7399
                        foreach ($tmparray as $val) {
7400
                            if ($val) {
7401
                                $newres .= ($i2 > 0 ? ' OR (' : '(') . $field . ' LIKE \'' . $db->escape(trim($val)) . ',%\'';
7402
                                $newres .= ' OR ' . $field . ' = \'' . $db->escape(trim($val)) . '\'';
7403
                                $newres .= ' OR ' . $field . ' LIKE \'%,' . $db->escape(trim($val)) . '\'';
7404
                                $newres .= ' OR ' . $field . ' LIKE \'%,' . $db->escape(trim($val)) . ',%\'';
7405
                                $newres .= ')';
7406
                                $i2++;
7407
                            }
7408
                        }
7409
                    }
7410
                } else {    // $mode=0
7411
                    $textcrit = '';
7412
                    $tmpcrits = explode('|', $crit);
7413
                    $i3 = 0;
7414
                    foreach ($tmpcrits as $tmpcrit) {
7415
                        if (empty($tmpcrit))
7416
                            continue;
7417
7418
                        $newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : '');
7419
7420
                        if (preg_match('/\.(id|rowid)$/', $field)) { // Special case for rowid that is sometimes a ref so used as a search field
7421
                            $newres .= $field . " = " . (is_numeric(trim($tmpcrit)) ? trim($tmpcrit) : '0');
7422
                        } else {
7423
                            $newres .= $field . " LIKE '";
7424
7425
                            $tmpcrit = trim($tmpcrit);
7426
                            $tmpcrit2 = $tmpcrit;
7427
                            $tmpbefore = '%';
7428
                            $tmpafter = '%';
7429
                            if (preg_match('/^[\^\$]/', $tmpcrit)) {
7430
                                $tmpbefore = '';
7431
                                $tmpcrit2 = preg_replace('/^[\^\$]/', '', $tmpcrit2);
7432
                            }
7433
                            if (preg_match('/[\^\$]$/', $tmpcrit)) {
7434
                                $tmpafter = '';
7435
                                $tmpcrit2 = preg_replace('/[\^\$]$/', '', $tmpcrit2);
7436
                            }
7437
                            $newres .= $tmpbefore;
7438
                            $newres .= $db->escape($tmpcrit2);
7439
                            $newres .= $tmpafter;
7440
                            $newres .= "'";
7441
                            if ($tmpcrit2 == '') {
7442
                                $newres .= ' OR ' . $field . " IS NULL";
7443
                            }
7444
                        }
7445
7446
                        $i3++;
7447
                    }
7448
                    $i2++; // a criteria was added to string
7449
                }
7450
                $i++;
7451
            }
7452
            if ($newres)
7453
                $res = $res . ($res ? ' AND ' : '') . ($i2 > 1 ? '(' : '') . $newres . ($i2 > 1 ? ')' : '');
7454
            $j++;
7455
        }
7456
        $res = ($nofirstand ? "" : " AND ") . "(" . $res . ")";
7457
//print 'xx'.$res.'yy';
7458
        return $res;
7459
    }
7460
7461
    /**
7462
     * Return string with full Url. The file qualified is the one defined by relative path in $object->last_main_doc
7463
     *
7464
     * @param   Object	$object				Object
7465
     * @return	string						Url string
7466
     */
7467
    static function showDirectDownloadLink($object)
7468
    {
7469
        // global Globals::$conf, Globals::$langs;
7470
7471
        $out = '';
7472
        $url = $object->getLastMainDocLink($object->element);
7473
7474
        if ($url) {
7475
            $out .= img_picto('', 'object_globe.png') . ' ' . Globals::$langs->trans("DirectDownloadLink") . '<br>';
7476
            $out .= '<input type="text" id="directdownloadlink" class="quatrevingtpercent" value="' . $url . '">';
7477
            $out .= ajax_autoselect("directdownloadlink", 0);
7478
        }
7479
        return $out;
7480
    }
7481
7482
    /**
7483
     * Return the filename of file to get the thumbs
7484
     *
7485
     * @param   string  $file           Original filename (full or relative path)
7486
     * @param   string  $extName        Extension to differenciate thumb file name ('', '_small', '_mini')
7487
     * @param   string  $extImgTarget   Force image extension for thumbs. Use '' to keep same extension than original image (default).
7488
     * @return  string                  New file name (full or relative path, including the thumbs/)
7489
     */
7490
    static function getImageFileNameForSize($file, $extName, $extImgTarget = '')
7491
    {
7492
        $dirName = dirname($file);
7493
        if ($dirName == '.')
7494
            $dirName = '';
7495
7496
        $fileName = preg_replace('/(\.gif|\.jpeg|\.jpg|\.png|\.bmp)$/i', '', $file); // We remove extension, whatever is its case
7497
        $fileName = basename($fileName);
7498
7499
        if (empty($extImgTarget))
7500
            $extImgTarget = (preg_match('/\.jpg$/i', $file) ? '.jpg' : '');
7501
        if (empty($extImgTarget))
7502
            $extImgTarget = (preg_match('/\.jpeg$/i', $file) ? '.jpeg' : '');
7503
        if (empty($extImgTarget))
7504
            $extImgTarget = (preg_match('/\.gif$/i', $file) ? '.gif' : '');
7505
        if (empty($extImgTarget))
7506
            $extImgTarget = (preg_match('/\.png$/i', $file) ? '.png' : '');
7507
        if (empty($extImgTarget))
7508
            $extImgTarget = (preg_match('/\.bmp$/i', $file) ? '.bmp' : '');
7509
7510
        if (!$extImgTarget)
7511
            return $file;
7512
7513
        $subdir = '';
7514
        if ($extName)
7515
            $subdir = 'thumbs/';
7516
7517
        return ($dirName ? $dirName . '/' : '') . $subdir . $fileName . $extName . $extImgTarget; // New filename for thumb
7518
    }
7519
7520
    /**
7521
     * Return URL we can use for advanced preview links
7522
     *
7523
     * @param   string    $modulepart     propal, facture, facture_fourn, ...
7524
     * @param   string    $relativepath   Relative path of docs.
7525
     * @param	int		  $alldata		  Return array with all components (1 is recommended, then use a simple a href link with the class, target and mime attribute added. 'documentpreview' css class is handled by jquery code into main.inc.php)
7526
     * @param	string	  $param		  More param on http links
7527
     * @return  string|array              Output string with href link or array with all components of link
7528
     */
7529
    static function getAdvancedPreviewUrl($modulepart, $relativepath, $alldata = 0, $param = '')
7530
    {
7531
        // global Globals::$conf, Globals::$langs;
7532
7533
        if (empty(Globals::$conf->use_javascript_ajax))
7534
            return '';
7535
7536
        $mime_preview = array('bmp', 'jpeg', 'png', 'gif', 'tiff', 'pdf', 'plain', 'css', 'svg+xml');
7537
//$mime_preview[]='vnd.oasis.opendocument.presentation';
7538
//$mime_preview[]='archive';
7539
        $num_mime = array_search(dol_mimetype($relativepath, '', 1), $mime_preview);
7540
7541
        if ($alldata == 1) {
7542
            if ($num_mime !== false)
7543
                return array('target' => '_blank', 'css' => 'documentpreview', 'url' => DOL_BASE_URI . '/document.php?modulepart=' . $modulepart . '&attachment=0&file=' . urlencode($relativepath) . ($param ? '&' . $param : ''), 'mime' => dol_mimetype($relativepath),);
7544
            else
7545
                return array();
7546
        }
7547
7548
// old behavior
7549
        if ($num_mime !== false)
7550
            return 'javascript:document_preview(\'' . dol_escape_js(DOL_BASE_URI . '/document.php?modulepart=' . $modulepart . '&attachment=0&file=' . urlencode($relativepath) . ($param ? '&' . $param : '')) . '\', \'' . dol_mimetype($relativepath) . '\', \'' . dol_escape_js(Globals::$langs->trans('Preview')) . '\')';
7551
        else
7552
            return '';
7553
    }
7554
7555
    /**
7556
     * Make content of an input box selected when we click into input field.
7557
     *
7558
     * @param string	$htmlname	Id of html object
7559
     * @param string	$addlink	Add a 'link to' after
7560
     * @return string
7561
     */
7562
    static function ajax_autoselect($htmlname, $addlink = '')
7563
    {
7564
        // global Globals::$langs;
7565
        $out = '<script type="text/javascript">
7566
               jQuery(document).ready(static function () {
7567
				    jQuery("#' . $htmlname . '").click(function() { jQuery(this).select(); } );
7568
				});
7569
		    </script>';
7570
        if ($addlink)
7571
            $out .= ' <a href="' . $addlink . '" target="_blank">' . Globals::$langs->trans("Link") . '</a>';
7572
        return $out;
7573
    }
7574
7575
    /**
7576
     * 	Return mime type of a file
7577
     *
7578
     * 	@param	string	$file		Filename we looking for MIME type
7579
     *  @param  string	$default    Default mime type if extension not found in known list
7580
     * 	@param	int		$mode    	0=Return full mime, 1=otherwise short mime string, 2=image for mime type, 3=source language, 4=css of font fa
7581
     * 	@return string 		    	Return a mime type family (text/xxx, application/xxx, image/xxx, audio, video, archive)
7582
     *  @see    image_format_supported (images.lib.php)
7583
     */
7584
    static function dol_mimetype($file, $default = 'application/octet-stream', $mode = 0)
7585
    {
7586
        $mime = $default;
7587
        $imgmime = 'other.png';
7588
        $famime = 'file-o';
7589
        $srclang = '';
7590
7591
        $tmpfile = preg_replace('/\.noexe$/', '', $file);
7592
7593
// Text files
7594
        if (preg_match('/\.txt$/i', $tmpfile)) {
7595
            $mime = 'text/plain';
7596
            $imgmime = 'text.png';
7597
            $famime = 'file-text-o';
7598
        }
7599
        if (preg_match('/\.rtx$/i', $tmpfile)) {
7600
            $mime = 'text/richtext';
7601
            $imgmime = 'text.png';
7602
            $famime = 'file-text-o';
7603
        }
7604
        if (preg_match('/\.csv$/i', $tmpfile)) {
7605
            $mime = 'text/csv';
7606
            $imgmime = 'text.png';
7607
            $famime = 'file-text-o';
7608
        }
7609
        if (preg_match('/\.tsv$/i', $tmpfile)) {
7610
            $mime = 'text/tab-separated-values';
7611
            $imgmime = 'text.png';
7612
            $famime = 'file-text-o';
7613
        }
7614
        if (preg_match('/\.(cf|conf|log)$/i', $tmpfile)) {
7615
            $mime = 'text/plain';
7616
            $imgmime = 'text.png';
7617
            $famime = 'file-text-o';
7618
        }
7619
        if (preg_match('/\.ini$/i', $tmpfile)) {
7620
            $mime = 'text/plain';
7621
            $imgmime = 'text.png';
7622
            $srclang = 'ini';
7623
            $famime = 'file-text-o';
7624
        }
7625
        if (preg_match('/\.css$/i', $tmpfile)) {
7626
            $mime = 'text/css';
7627
            $imgmime = 'css.png';
7628
            $srclang = 'css';
7629
            $famime = 'file-text-o';
7630
        }
7631
// Certificate files
7632
        if (preg_match('/\.(crt|cer|key|pub)$/i', $tmpfile)) {
7633
            $mime = 'text/plain';
7634
            $imgmime = 'text.png';
7635
            $famime = 'file-text-o';
7636
        }
7637
// HTML/XML
7638
        if (preg_match('/\.(html|htm|shtml)$/i', $tmpfile)) {
7639
            $mime = 'text/html';
7640
            $imgmime = 'html.png';
7641
            $srclang = 'html';
7642
            $famime = 'file-text-o';
7643
        }
7644
        if (preg_match('/\.(xml|xhtml)$/i', $tmpfile)) {
7645
            $mime = 'text/xml';
7646
            $imgmime = 'other.png';
7647
            $srclang = 'xml';
7648
            $famime = 'file-text-o';
7649
        }
7650
// Languages
7651
        if (preg_match('/\.bas$/i', $tmpfile)) {
7652
            $mime = 'text/plain';
7653
            $imgmime = 'text.png';
7654
            $srclang = 'bas';
7655
            $famime = 'file-code-o';
7656
        }
7657
        if (preg_match('/\.(c)$/i', $tmpfile)) {
7658
            $mime = 'text/plain';
7659
            $imgmime = 'text.png';
7660
            $srclang = 'c';
7661
            $famime = 'file-code-o';
7662
        }
7663
        if (preg_match('/\.(cpp)$/i', $tmpfile)) {
7664
            $mime = 'text/plain';
7665
            $imgmime = 'text.png';
7666
            $srclang = 'cpp';
7667
            $famime = 'file-code-o';
7668
        }
7669
        if (preg_match('/\.(h)$/i', $tmpfile)) {
7670
            $mime = 'text/plain';
7671
            $imgmime = 'text.png';
7672
            $srclang = 'h';
7673
            $famime = 'file-code-o';
7674
        }
7675
        if (preg_match('/\.(java|jsp)$/i', $tmpfile)) {
7676
            $mime = 'text/plain';
7677
            $imgmime = 'text.png';
7678
            $srclang = 'java';
7679
            $famime = 'file-code-o';
7680
        }
7681
        if (preg_match('/\.php([0-9]{1})?$/i', $tmpfile)) {
7682
            $mime = 'text/plain';
7683
            $imgmime = 'php.png';
7684
            $srclang = 'php';
7685
            $famime = 'file-code-o';
7686
        }
7687
        if (preg_match('/\.phtml$/i', $tmpfile)) {
7688
            $mime = 'text/plain';
7689
            $imgmime = 'php.png';
7690
            $srclang = 'php';
7691
            $famime = 'file-code-o';
7692
        }
7693
        if (preg_match('/\.(pl|pm)$/i', $tmpfile)) {
7694
            $mime = 'text/plain';
7695
            $imgmime = 'pl.png';
7696
            $srclang = 'perl';
7697
            $famime = 'file-code-o';
7698
        }
7699
        if (preg_match('/\.sql$/i', $tmpfile)) {
7700
            $mime = 'text/plain';
7701
            $imgmime = 'text.png';
7702
            $srclang = 'sql';
7703
            $famime = 'file-code-o';
7704
        }
7705
        if (preg_match('/\.js$/i', $tmpfile)) {
7706
            $mime = 'text/x-javascript';
7707
            $imgmime = 'jscript.png';
7708
            $srclang = 'js';
7709
            $famime = 'file-code-o';
7710
        }
7711
// Open office
7712
        if (preg_match('/\.odp$/i', $tmpfile)) {
7713
            $mime = 'application/vnd.oasis.opendocument.presentation';
7714
            $imgmime = 'ooffice.png';
7715
            $famime = 'file-powerpoint-o';
7716
        }
7717
        if (preg_match('/\.ods$/i', $tmpfile)) {
7718
            $mime = 'application/vnd.oasis.opendocument.spreadsheet';
7719
            $imgmime = 'ooffice.png';
7720
            $famime = 'file-excel-o';
7721
        }
7722
        if (preg_match('/\.odt$/i', $tmpfile)) {
7723
            $mime = 'application/vnd.oasis.opendocument.text';
7724
            $imgmime = 'ooffice.png';
7725
            $famime = 'file-word-o';
7726
        }
7727
// MS Office
7728
        if (preg_match('/\.mdb$/i', $tmpfile)) {
7729
            $mime = 'application/msaccess';
7730
            $imgmime = 'mdb.png';
7731
            $famime = 'file-o';
7732
        }
7733
        if (preg_match('/\.doc(x|m)?$/i', $tmpfile)) {
7734
            $mime = 'application/msword';
7735
            $imgmime = 'doc.png';
7736
            $famime = 'file-word-o';
7737
        }
7738
        if (preg_match('/\.dot(x|m)?$/i', $tmpfile)) {
7739
            $mime = 'application/msword';
7740
            $imgmime = 'doc.png';
7741
            $famime = 'file-word-o';
7742
        }
7743
        if (preg_match('/\.xlt(x)?$/i', $tmpfile)) {
7744
            $mime = 'application/vnd.ms-excel';
7745
            $imgmime = 'xls.png';
7746
            $famime = 'file-excel-o';
7747
        }
7748
        if (preg_match('/\.xla(m)?$/i', $tmpfile)) {
7749
            $mime = 'application/vnd.ms-excel';
7750
            $imgmime = 'xls.png';
7751
            $famime = 'file-excel-o';
7752
        }
7753
        if (preg_match('/\.xls$/i', $tmpfile)) {
7754
            $mime = 'application/vnd.ms-excel';
7755
            $imgmime = 'xls.png';
7756
            $famime = 'file-excel-o';
7757
        }
7758
        if (preg_match('/\.xls(b|m|x)$/i', $tmpfile)) {
7759
            $mime = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
7760
            $imgmime = 'xls.png';
7761
            $famime = 'file-excel-o';
7762
        }
7763
        if (preg_match('/\.pps(m|x)?$/i', $tmpfile)) {
7764
            $mime = 'application/vnd.ms-powerpoint';
7765
            $imgmime = 'ppt.png';
7766
            $famime = 'file-powerpoint-o';
7767
        }
7768
        if (preg_match('/\.ppt(m|x)?$/i', $tmpfile)) {
7769
            $mime = 'application/x-mspowerpoint';
7770
            $imgmime = 'ppt.png';
7771
            $famime = 'file-powerpoint-o';
7772
        }
7773
// Other
7774
        if (preg_match('/\.pdf$/i', $tmpfile)) {
7775
            $mime = 'application/pdf';
7776
            $imgmime = 'pdf.png';
7777
            $famime = 'file-pdf-o';
7778
        }
7779
// Scripts
7780
        if (preg_match('/\.bat$/i', $tmpfile)) {
7781
            $mime = 'text/x-bat';
7782
            $imgmime = 'script.png';
7783
            $srclang = 'dos';
7784
            $famime = 'file-code-o';
7785
        }
7786
        if (preg_match('/\.sh$/i', $tmpfile)) {
7787
            $mime = 'text/x-sh';
7788
            $imgmime = 'script.png';
7789
            $srclang = 'bash';
7790
            $famime = 'file-code-o';
7791
        }
7792
        if (preg_match('/\.ksh$/i', $tmpfile)) {
7793
            $mime = 'text/x-ksh';
7794
            $imgmime = 'script.png';
7795
            $srclang = 'bash';
7796
            $famime = 'file-code-o';
7797
        }
7798
        if (preg_match('/\.bash$/i', $tmpfile)) {
7799
            $mime = 'text/x-bash';
7800
            $imgmime = 'script.png';
7801
            $srclang = 'bash';
7802
            $famime = 'file-code-o';
7803
        }
7804
// Images
7805
        if (preg_match('/\.ico$/i', $tmpfile)) {
7806
            $mime = 'image/x-icon';
7807
            $imgmime = 'image.png';
7808
            $famime = 'file-image-o';
7809
        }
7810
        if (preg_match('/\.(jpg|jpeg)$/i', $tmpfile)) {
7811
            $mime = 'image/jpeg';
7812
            $imgmime = 'image.png';
7813
            $famime = 'file-image-o';
7814
        }
7815
        if (preg_match('/\.png$/i', $tmpfile)) {
7816
            $mime = 'image/png';
7817
            $imgmime = 'image.png';
7818
            $famime = 'file-image-o';
7819
        }
7820
        if (preg_match('/\.gif$/i', $tmpfile)) {
7821
            $mime = 'image/gif';
7822
            $imgmime = 'image.png';
7823
            $famime = 'file-image-o';
7824
        }
7825
        if (preg_match('/\.bmp$/i', $tmpfile)) {
7826
            $mime = 'image/bmp';
7827
            $imgmime = 'image.png';
7828
            $famime = 'file-image-o';
7829
        }
7830
        if (preg_match('/\.(tif|tiff)$/i', $tmpfile)) {
7831
            $mime = 'image/tiff';
7832
            $imgmime = 'image.png';
7833
            $famime = 'file-image-o';
7834
        }
7835
        if (preg_match('/\.svg$/i', $tmpfile)) {
7836
            $mime = 'image/svg+xml';
7837
            $imgmime = 'image.png';
7838
            $famime = 'file-image-o';
7839
        }
7840
// Calendar
7841
        if (preg_match('/\.vcs$/i', $tmpfile)) {
7842
            $mime = 'text/calendar';
7843
            $imgmime = 'other.png';
7844
            $famime = 'file-text-o';
7845
        }
7846
        if (preg_match('/\.ics$/i', $tmpfile)) {
7847
            $mime = 'text/calendar';
7848
            $imgmime = 'other.png';
7849
            $famime = 'file-text-o';
7850
        }
7851
// Other
7852
        if (preg_match('/\.torrent$/i', $tmpfile)) {
7853
            $mime = 'application/x-bittorrent';
7854
            $imgmime = 'other.png';
7855
            $famime = 'file-o';
7856
        }
7857
// Audio
7858
        if (preg_match('/\.(mp3|ogg|au|wav|wma|mid)$/i', $tmpfile)) {
7859
            $mime = 'audio';
7860
            $imgmime = 'audio.png';
7861
            $famime = 'file-audio-o';
7862
        }
7863
// Video
7864
        if (preg_match('/\.ogv$/i', $tmpfile)) {
7865
            $mime = 'video/ogg';
7866
            $imgmime = 'video.png';
7867
            $famime = 'file-video-o';
7868
        }
7869
        if (preg_match('/\.webm$/i', $tmpfile)) {
7870
            $mime = 'video/webm';
7871
            $imgmime = 'video.png';
7872
            $famime = 'file-video-o';
7873
        }
7874
        if (preg_match('/\.avi$/i', $tmpfile)) {
7875
            $mime = 'video/x-msvideo';
7876
            $imgmime = 'video.png';
7877
            $famime = 'file-video-o';
7878
        }
7879
        if (preg_match('/\.divx$/i', $tmpfile)) {
7880
            $mime = 'video/divx';
7881
            $imgmime = 'video.png';
7882
            $famime = 'file-video-o';
7883
        }
7884
        if (preg_match('/\.xvid$/i', $tmpfile)) {
7885
            $mime = 'video/xvid';
7886
            $imgmime = 'video.png';
7887
            $famime = 'file-video-o';
7888
        }
7889
        if (preg_match('/\.(wmv|mpg|mpeg)$/i', $tmpfile)) {
7890
            $mime = 'video';
7891
            $imgmime = 'video.png';
7892
            $famime = 'file-video-o';
7893
        }
7894
// Archive
7895
        if (preg_match('/\.(zip|rar|gz|tgz|z|cab|bz2|7z|tar|lzh)$/i', $tmpfile)) {
7896
            $mime = 'archive';
7897
            $imgmime = 'archive.png';
7898
            $famime = 'file-archive-o';
7899
        }    // application/xxx where zzz is zip, ...
7900
// Exe
7901
        if (preg_match('/\.(exe|com)$/i', $tmpfile)) {
7902
            $mime = 'application/octet-stream';
7903
            $imgmime = 'other.png';
7904
            $famime = 'file-o';
7905
        }
7906
// Lib
7907
        if (preg_match('/\.(dll|lib|o|so|a)$/i', $tmpfile)) {
7908
            $mime = 'library';
7909
            $imgmime = 'library.png';
7910
            $famime = 'file-o';
7911
        }
7912
// Err
7913
        if (preg_match('/\.err$/i', $tmpfile)) {
7914
            $mime = 'error';
7915
            $imgmime = 'error.png';
7916
            $famime = 'file-text-o';
7917
        }
7918
7919
// Return string
7920
        if ($mode == 1) {
7921
            $tmp = explode('/', $mime);
7922
            return (!empty($tmp[1]) ? $tmp[1] : $tmp[0]);
7923
        }
7924
        if ($mode == 2) {
7925
            return $imgmime;
7926
        }
7927
        if ($mode == 3) {
7928
            return $srclang;
7929
        }
7930
        if ($mode == 4) {
7931
            return $famime;
7932
        }
7933
        return $mime;
7934
    }
7935
7936
    /**
7937
     * Return value from dictionary
7938
     *
7939
     * @param string	$tablename		name of dictionary
7940
     * @param string	$field			the value to return
7941
     * @param int		$id				id of line
7942
     * @param bool		$checkentity	add filter on entity
7943
     * @param string	$rowidfield		name of the column rowid
7944
     * @return string
7945
     */
7946
    static function getDictvalue($tablename, $field, $id, $checkentity = false, $rowidfield = 'rowid')
7947
    {
7948
        // global $dictvalues, $db, Globals::$langs;
7949
7950
        if (!isset($dictvalues[$tablename])) {
7951
            $dictvalues[$tablename] = array();
7952
            $sql = 'SELECT * FROM ' . $tablename . ' WHERE 1';
7953
            if ($checkentity)
7954
                $sql .= ' AND entity IN (0,' . getEntity($tablename) . ')';
7955
7956
            $resql = $db->query($sql);
7957
            if ($resql) {
7958
                while ($obj = $db->fetch_object($resql)) {
7959
                    $dictvalues[$tablename][$obj->{$rowidfield}] = $obj;
7960
                }
7961
            } else {
7962
                dol_print_error($db);
7963
            }
7964
        }
7965
7966
        if (!empty($dictvalues[$tablename][$id]))
7967
            return $dictvalues[$tablename][$id]->{$field}; // Found
7968
        else { // Not found
7969
            if ($id > 0)
7970
                return $id;
7971
            return '';
7972
        }
7973
    }
7974
7975
    /**
7976
     * 	Return true if the color is light
7977
     *
7978
     *  @param	string	$stringcolor		String with hex (FFFFFF) or comma RGB ('255,255,255')
7979
     *  @return	int							-1 : Error with argument passed |0 : color is dark | 1 : color is light
7980
     */
7981
    static function colorIsLight($stringcolor)
7982
    {
7983
        $res = -1;
7984
        if (!empty($stringcolor)) {
7985
            $res = 0;
7986
            $tmp = explode(',', $stringcolor);
7987
            if (count($tmp) > 1) {   // This is a comma RGB ('255','255','255')
7988
                $r = $tmp[0];
7989
                $g = $tmp[1];
7990
                $b = $tmp[2];
7991
            } else {
7992
                $hexr = $stringcolor[0] . $stringcolor[1];
7993
                $hexg = $stringcolor[2] . $stringcolor[3];
7994
                $hexb = $stringcolor[4] . $stringcolor[5];
7995
                $r = hexdec($hexr);
7996
                $g = hexdec($hexg);
7997
                $b = hexdec($hexb);
7998
            }
7999
            $bright = (max($r, $g, $b) + min($r, $g, $b)) / 510.0;    // HSL algorithm
8000
            if ($bright > 0.6)
8001
                $res = 1;
8002
        }
8003
        return $res;
8004
    }
8005
8006
    /**
8007
     * Function to test if an entry is enabled or not
8008
     *
8009
     * @param	string		$type_user					0=We test for internal user, 1=We test for external user
8010
     * @param	array		$menuentry					Array for feature entry to test
8011
     * @param	array		$listofmodulesforexternal	Array with list of modules allowed to external users
8012
     * @return	int										0=Hide, 1=Show, 2=Show gray
8013
     */
8014
    static function isVisibleToUserType($type_user, &$menuentry, &$listofmodulesforexternal)
8015
    {
8016
        // global Globals::$conf;
8017
//print 'type_user='.$type_user.' module='.$menuentry['module'].' enabled='.$menuentry['enabled'].' perms='.$menuentry['perms'];
8018
//print 'ok='.in_array($menuentry['module'], $listofmodulesforexternal);
8019
        if (empty($menuentry['enabled']))
8020
            return 0; // Entry disabled by condition
8021
        if ($type_user && $menuentry['module']) {
8022
            $tmploops = explode('|', $menuentry['module']);
8023
            $found = 0;
8024
            foreach ($tmploops as $tmploop) {
8025
                if (in_array($tmploop, $listofmodulesforexternal)) {
8026
                    $found++;
8027
                    break;
8028
                }
8029
            }
8030
            if (!$found)
8031
                return 0; // Entry is for menus all excluded to external users
8032
        }
8033
        if (!$menuentry['perms'] && $type_user)
8034
            return 0;            // No permissions and user is external
8035
        if (!$menuentry['perms'] && !empty(Globals::$conf->global->MAIN_MENU_HIDE_UNAUTHORIZED))
8036
            return 0; // No permissions and option to hide when not allowed, even for internal user, is on
8037
        if (!$menuentry['perms'])
8038
            return 2;               // No permissions and user is external
8039
        return 1;
8040
    }
8041
8042
    /**
8043
     * Round to next multiple.
8044
     *
8045
     * @param 	double		$n		Number to round up
8046
     * @param 	integer		$x		Multiple. For example 60 to round up to nearest exact minute for a date with seconds.
8047
     * @return 	integer				Value rounded.
8048
     */
8049
    static function roundUpToNextMultiple($n, $x = 5)
8050
    {
8051
        return (ceil($n) % $x === 0) ? ceil($n) : round(($n + $x / 2) / $x) * $x;
8052
    }
8053
}
8054