1<?php
2/* Copyright (C) 2000-2007	Rodolphe Quiedeville		<rodolphe@quiedeville.org>
3 * Copyright (C) 2003		Jean-Louis Bergamo			<jlb@j1b.org>
4 * Copyright (C) 2004-2018	Laurent Destailleur			<eldy@users.sourceforge.net>
5 * Copyright (C) 2004		Sebastien Di Cintio			<sdicintio@ressource-toi.org>
6 * Copyright (C) 2004		Benoit Mortier				<benoit.mortier@opensides.be>
7 * Copyright (C) 2004		Christophe Combelles		<ccomb@free.fr>
8 * Copyright (C) 2005-2019	Regis Houssin				<regis.houssin@inodbox.com>
9 * Copyright (C) 2008		Raphael Bertrand (Resultic)	<raphael.bertrand@resultic.fr>
10 * Copyright (C) 2010-2018	Juanjo Menent				<jmenent@2byte.es>
11 * Copyright (C) 2013		Cédric Salvador				<csalvador@gpcsolutions.fr>
12 * Copyright (C) 2013-2021	Alexandre Spangaro			<aspangaro@open-dsi.fr>
13 * Copyright (C) 2014		Cédric GROSS				<c.gross@kreiz-it.fr>
14 * Copyright (C) 2014-2015	Marcos García				<marcosgdf@gmail.com>
15 * Copyright (C) 2015		Jean-François Ferry			<jfefe@aternatik.fr>
16 * Copyright (C) 2018-2021  Frédéric France             <frederic.france@netlogic.fr>
17 * Copyright (C) 2019       Thibault Foucart            <support@ptibogxiv.net>
18 * Copyright (C) 2020       Open-Dsi         			<support@open-dsi.fr>
19 * Copyright (C) 2021       Gauthier VERDOL         	<gauthier.verdol@atm-consulting.fr>
20 *
21 * This program is free software; you can redistribute it and/or modify
22 * it under the terms of the GNU General Public License as published by
23 * the Free Software Foundation; either version 3 of the License, or
24 * (at your option) any later version.
25 *
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
29 * GNU General Public License for more details.
30 *
31 * You should have received a copy of the GNU General Public License
32 * along with this program. If not, see <https://www.gnu.org/licenses/>.
33 * or see https://www.gnu.org/
34 */
35
36/**
37 *	\file			htdocs/core/lib/functions.lib.php
38 *	\brief			A set of functions for Dolibarr
39 *					This file contains all frequently used functions.
40 */
41
42include_once DOL_DOCUMENT_ROOT.'/core/lib/json.lib.php';
43
44/**
45 * Return dolibarr global constant string value
46 * @param string $key key to return value, return '' if not set
47 * @return string
48 */
49function getDolGlobalString($key)
50{
51	global $conf;
52	// return $conf->global->$key ?? '';
53	return (string) (empty($conf->global->$key) ? '' : $conf->global->$key);
54}
55
56/**
57 * Return dolibarr global constant int value
58 * @param string $key key to return value, return 0 if not set
59 * @return int
60 */
61function getDolGlobalInt($key)
62{
63	global $conf;
64	// return $conf->global->$key ?? 0;
65	return (int) (empty($conf->global->$key) ? 0 : $conf->global->$key);
66}
67
68/**
69 * Return a DoliDB instance (database handler).
70 *
71 * @param   string	$type		Type of database (mysql, pgsql...)
72 * @param	string	$host		Address of database server
73 * @param	string	$user		Authorized username
74 * @param	string	$pass		Password
75 * @param	string	$name		Name of database
76 * @param	int		$port		Port of database server
77 * @return	DoliDB				A DoliDB instance
78 */
79function getDoliDBInstance($type, $host, $user, $pass, $name, $port)
80{
81	require_once DOL_DOCUMENT_ROOT."/core/db/".$type.'.class.php';
82
83	$class = 'DoliDB'.ucfirst($type);
84	$dolidb = new $class($type, $host, $user, $pass, $name, $port);
85	return $dolidb;
86}
87
88/**
89 * 	Get list of entity id to use.
90 *
91 * 	@param	string	$element		Current element
92 *									'societe', 'socpeople', 'actioncomm', 'agenda', 'resource',
93 *									'product', 'productprice', 'stock', 'bom', 'mo',
94 *									'propal', 'supplier_proposal', 'invoice', 'supplier_invoice', 'payment_various',
95 *									'categorie', 'bank_account', 'bank_account', 'adherent', 'user',
96 *									'commande', 'supplier_order', 'expedition', 'intervention', 'survey',
97 *									'contract', 'tax', 'expensereport', 'holiday', 'multicurrency', 'project',
98 *									'email_template', 'event', 'donation'
99 *									'c_paiement', 'c_payment_term', ...
100 * 	@param	int		$shared			0=Return id of current entity only,
101 * 									1=Return id of current entity + shared entities (default)
102 *  @param	object	$currentobject	Current object if needed
103 * 	@return	mixed					Entity id(s) to use ( eg. entity IN ('.getEntity(elementname).')' )
104 */
105function getEntity($element, $shared = 1, $currentobject = null)
106{
107	global $conf, $mc;
108
109	// fix different element names (France to English)
110	switch ($element) {
111		case 'contrat':
112			$element = 'contract';
113			break; // "/contrat/class/contrat.class.php"
114		case 'order_supplier':
115			$element = 'supplier_order';
116			break; // "/fourn/class/fournisseur.commande.class.php"
117	}
118
119	if (is_object($mc)) {
120		return $mc->getEntity($element, $shared, $currentobject);
121	} else {
122		$out = '';
123		$addzero = array('user', 'usergroup', 'c_email_templates', 'email_template', 'default_values');
124		if (in_array($element, $addzero)) {
125			$out .= '0,';
126		}
127		$out .= ((int) $conf->entity);
128		return $out;
129	}
130}
131
132/**
133 * 	Set entity id to use when to create an object
134 *
135 * 	@param	object	$currentobject	Current object
136 * 	@return	mixed					Entity id to use ( eg. entity = '.setEntity($object) )
137 */
138function setEntity($currentobject)
139{
140	global $conf, $mc;
141
142	if (is_object($mc) && method_exists($mc, 'setEntity')) {
143		return $mc->setEntity($currentobject);
144	} else {
145		return ((is_object($currentobject) && $currentobject->id > 0 && $currentobject->entity > 0) ? $currentobject->entity : $conf->entity);
146	}
147}
148
149/**
150 * 	Return if string has a name dedicated to store a secret
151 *
152 * 	@param	string	$keyname	Name of key to test
153 * 	@return	boolean				True if key is used to store a secret
154 */
155function isASecretKey($keyname)
156{
157	return preg_match('/(_pass|password|_pw|_key|securekey|serverkey|secret\d?|p12key|exportkey|_PW_[a-z]+|token)$/i', $keyname);
158}
159
160/**
161 * Return information about user browser
162 *
163 * Returns array with the following format:
164 * array(
165 *  'browsername' => Browser name (firefox|chrome|iceweasel|epiphany|safari|opera|ie|unknown)
166 *  'browserversion' => Browser version. Empty if unknown
167 *  'browseros' => Set with mobile OS (android|blackberry|ios|palm|symbian|webos|maemo|windows|unknown)
168 *  'layout' => (tablet|phone|classic)
169 *  'phone' => empty if not mobile, (android|blackberry|ios|palm|unknown) if mobile
170 *  'tablet' => true/false
171 * )
172 *
173 * @param string $user_agent Content of $_SERVER["HTTP_USER_AGENT"] variable
174 * @return	array Check function documentation
175 */
176function getBrowserInfo($user_agent)
177{
178	include_once DOL_DOCUMENT_ROOT.'/includes/mobiledetect/mobiledetectlib/Mobile_Detect.php';
179
180	$name = 'unknown';
181	$version = '';
182	$os = 'unknown';
183	$phone = '';
184
185	$user_agent = substr($user_agent, 0, 512);	// Avoid to process too large user agent
186
187	$detectmobile = new Mobile_Detect(null, $user_agent);
188	$tablet = $detectmobile->isTablet();
189
190	if ($detectmobile->isMobile()) {
191		$phone = 'unknown';
192
193		// If phone/smartphone, we set phone os name.
194		if ($detectmobile->is('AndroidOS')) {
195			$os = $phone = 'android';
196		} elseif ($detectmobile->is('BlackBerryOS')) {
197			$os = $phone = 'blackberry';
198		} elseif ($detectmobile->is('iOS')) {
199			$os = 'ios';
200			$phone = 'iphone';
201		} elseif ($detectmobile->is('PalmOS')) {
202			$os = $phone = 'palm';
203		} elseif ($detectmobile->is('SymbianOS')) {
204			$os = 'symbian';
205		} elseif ($detectmobile->is('webOS')) {
206			$os = 'webos';
207		} elseif ($detectmobile->is('MaemoOS')) {
208			$os = 'maemo';
209		} elseif ($detectmobile->is('WindowsMobileOS') || $detectmobile->is('WindowsPhoneOS')) {
210			$os = 'windows';
211		}
212	}
213
214	// OS
215	if (preg_match('/linux/i', $user_agent)) {
216		$os = 'linux';
217	} elseif (preg_match('/macintosh/i', $user_agent)) {
218		$os = 'macintosh';
219	} elseif (preg_match('/windows/i', $user_agent)) {
220		$os = 'windows';
221	}
222
223	// Name
224	$reg = array();
225	if (preg_match('/firefox(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
226		$name = 'firefox';
227		$version = $reg[2];
228	} elseif (preg_match('/edge(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
229		$name = 'edge';
230		$version = $reg[2];
231	} elseif (preg_match('/chrome(\/|\s)([\d\.]+)/i', $user_agent, $reg)) {
232		$name = 'chrome';
233		$version = $reg[2];
234	} elseif (preg_match('/chrome/i', $user_agent, $reg)) {
235		// we can have 'chrome (Mozilla...) chrome x.y' in one string
236		$name = 'chrome';
237	} elseif (preg_match('/iceweasel/i', $user_agent)) {
238		$name = 'iceweasel';
239	} elseif (preg_match('/epiphany/i', $user_agent)) {
240		$name = 'epiphany';
241	} elseif (preg_match('/safari(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
242		$name = 'safari';
243		$version = $reg[2];
244	} elseif (preg_match('/opera(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
245		// Safari is often present in string for mobile but its not.
246		$name = 'opera';
247		$version = $reg[2];
248	} elseif (preg_match('/(MSIE\s([0-9]+\.[0-9]))|.*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) {
249		$name = 'ie';
250		$version = end($reg);
251	} elseif (preg_match('/(Windows NT\s([0-9]+\.[0-9])).*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) {
252		// MS products at end
253		$name = 'ie';
254		$version = end($reg);
255	} elseif (preg_match('/l(i|y)n(x|ks)(\(|\/|\s)*([\d\.]+)/i', $user_agent, $reg)) {
256		// MS products at end
257		$name = 'lynxlinks';
258		$version = $reg[4];
259	}
260
261	if ($tablet) {
262		$layout = 'tablet';
263	} elseif ($phone) {
264		$layout = 'phone';
265	} else {
266		$layout = 'classic';
267	}
268
269	return array(
270		'browsername' => $name,
271		'browserversion' => $version,
272		'browseros' => $os,
273		'layout' => $layout,
274		'phone' => $phone,
275		'tablet' => $tablet
276	);
277}
278
279/**
280 *  Function called at end of web php process
281 *
282 *  @return	void
283 */
284function dol_shutdown()
285{
286	global $conf, $user, $langs, $db;
287	$disconnectdone = false;
288	$depth = 0;
289	if (is_object($db) && !empty($db->connected)) {
290		$depth = $db->transaction_opened;
291		$disconnectdone = $db->close();
292	}
293	dol_syslog("--- End access to ".$_SERVER["PHP_SELF"].(($disconnectdone && $depth) ? ' (Warn: db disconnection forced, transaction depth was '.$depth.')' : ''), (($disconnectdone && $depth) ?LOG_WARNING:LOG_INFO));
294}
295
296/**
297 * Return true if we are in a context of submitting the parameter $paramname from a POST of a form.
298 *
299 * @param 	string	$paramname		Name or parameter to test
300 * @return 	boolean					True if we have just submit a POST or GET request with the parameter provided (even if param is empty)
301 */
302function GETPOSTISSET($paramname)
303{
304	$isset = false;
305
306	$relativepathstring = $_SERVER["PHP_SELF"];
307	// Clean $relativepathstring
308	if (constant('DOL_URL_ROOT')) {
309		$relativepathstring = preg_replace('/^'.preg_quote(constant('DOL_URL_ROOT'), '/').'/', '', $relativepathstring);
310	}
311	$relativepathstring = preg_replace('/^\//', '', $relativepathstring);
312	$relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
313	//var_dump($relativepathstring);
314	//var_dump($user->default_values);
315
316	// Code for search criteria persistence.
317	// Retrieve values if restore_lastsearch_values
318	if (!empty($_GET['restore_lastsearch_values'])) {        // Use $_GET here and not GETPOST
319		if (!empty($_SESSION['lastsearch_values_'.$relativepathstring])) {	// If there is saved values
320			$tmp = json_decode($_SESSION['lastsearch_values_'.$relativepathstring], true);
321			if (is_array($tmp)) {
322				foreach ($tmp as $key => $val) {
323					if ($key == $paramname) {	// We are on the requested parameter
324						$isset = true;
325						break;
326					}
327				}
328			}
329		}
330		// If there is saved contextpage, page or limit
331		if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_'.$relativepathstring])) {
332			$isset = true;
333		} elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_'.$relativepathstring])) {
334			$isset = true;
335		} elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_'.$relativepathstring])) {
336			$isset = true;
337		}
338	} else {
339		$isset = (isset($_POST[$paramname]) || isset($_GET[$paramname])); // We must keep $_POST and $_GET here
340	}
341
342	return $isset;
343}
344
345/**
346 *  Return value of a param into GET or POST supervariable.
347 *  Use the property $user->default_values[path]['createform'] and/or $user->default_values[path]['filters'] and/or $user->default_values[path]['sortorder']
348 *  Note: The property $user->default_values is loaded by main.php when loading the user.
349 *
350 *  @param  string  $paramname   Name of parameter to found
351 *  @param  string  $check	     Type of check
352 *                               ''=no check (deprecated)
353 *                               'none'=no check (only for param that should have very rich content)
354 *                               'array', 'array:restricthtml' or 'array:aZ09' to check it's an array
355 *                               'int'=check it's numeric (integer or float)
356 *                               'intcomma'=check it's integer+comma ('1,2,3,4...')
357 *                               'alpha'=Same than alphanohtml since v13
358 *                               'alphawithlgt'=alpha with lgt
359 *                               'alphanohtml'=check there is no html content and no " and no ../
360 *                               'aZ'=check it's a-z only
361 *                               'aZ09'=check it's simple alpha string (recommended for keys)
362 *                               'san_alpha'=Use filter_var with FILTER_SANITIZE_STRING (do not use this for free text string)
363 *                               'nohtml'=check there is no html content and no " and no ../
364 *                               'restricthtml'=check html content is restricted to some tags only
365 *                               'custom'= custom filter specify $filter and $options)
366 *  @param	int		$method	     Type of method (0 = get then post, 1 = only get, 2 = only post, 3 = post then get)
367 *  @param  int     $filter      Filter to apply when $check is set to 'custom'. (See http://php.net/manual/en/filter.filters.php for détails)
368 *  @param  mixed   $options     Options to pass to filter_var when $check is set to 'custom'
369 *  @param	string	$noreplace	 Force disable of replacement of __xxx__ strings.
370 *  @return string|array         Value found (string or array), or '' if check fails
371 */
372function GETPOST($paramname, $check = 'alphanohtml', $method = 0, $filter = null, $options = null, $noreplace = 0)
373{
374	global $mysoc, $user, $conf;
375
376	if (empty($paramname)) {
377		return 'BadFirstParameterForGETPOST';
378	}
379	if (empty($check)) {
380		dol_syslog("Deprecated use of GETPOST, called with 1st param = ".$paramname." and 2nd param is '', when calling page ".$_SERVER["PHP_SELF"], LOG_WARNING);
381		// Enable this line to know who call the GETPOST with '' $check parameter.
382		//var_dump(debug_backtrace()[0]);
383	}
384
385	if (empty($method)) {
386		$out = isset($_GET[$paramname]) ? $_GET[$paramname] : (isset($_POST[$paramname]) ? $_POST[$paramname] : '');
387	} elseif ($method == 1) {
388		$out = isset($_GET[$paramname]) ? $_GET[$paramname] : '';
389	} elseif ($method == 2) {
390		$out = isset($_POST[$paramname]) ? $_POST[$paramname] : '';
391	} elseif ($method == 3) {
392		$out = isset($_POST[$paramname]) ? $_POST[$paramname] : (isset($_GET[$paramname]) ? $_GET[$paramname] : '');
393	} else {
394		return 'BadThirdParameterForGETPOST';
395	}
396
397	if (empty($method) || $method == 3 || $method == 4) {
398		$relativepathstring = $_SERVER["PHP_SELF"];
399		// Clean $relativepathstring
400		if (constant('DOL_URL_ROOT')) {
401			$relativepathstring = preg_replace('/^'.preg_quote(constant('DOL_URL_ROOT'), '/').'/', '', $relativepathstring);
402		}
403		$relativepathstring = preg_replace('/^\//', '', $relativepathstring);
404		$relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
405		//var_dump($relativepathstring);
406		//var_dump($user->default_values);
407
408		// Code for search criteria persistence.
409		// Retrieve values if restore_lastsearch_values
410		if (!empty($_GET['restore_lastsearch_values'])) {        // Use $_GET here and not GETPOST
411			if (!empty($_SESSION['lastsearch_values_'.$relativepathstring])) {	// If there is saved values
412				$tmp = json_decode($_SESSION['lastsearch_values_'.$relativepathstring], true);
413				if (is_array($tmp)) {
414					foreach ($tmp as $key => $val) {
415						if ($key == $paramname) {	// We are on the requested parameter
416							$out = $val;
417							break;
418						}
419					}
420				}
421			}
422			// If there is saved contextpage, page or limit
423			if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_'.$relativepathstring])) {
424				$out = $_SESSION['lastsearch_contextpage_'.$relativepathstring];
425			} elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_'.$relativepathstring])) {
426				$out = $_SESSION['lastsearch_page_'.$relativepathstring];
427			} elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_'.$relativepathstring])) {
428				$out = $_SESSION['lastsearch_limit_'.$relativepathstring];
429			}
430		} elseif (!isset($_GET['sortfield'])) {
431			// Else, retrieve default values if we are not doing a sort
432			// 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
433			if (!empty($_GET['action']) && $_GET['action'] == 'create' && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
434				// Search default value from $object->field
435				global $object;
436				if (is_object($object) && isset($object->fields[$paramname]['default'])) {
437					$out = $object->fields[$paramname]['default'];
438				}
439			}
440			if (!empty($conf->global->MAIN_ENABLE_DEFAULT_VALUES)) {
441				if (!empty($_GET['action']) && (preg_match('/^create/', $_GET['action']) || preg_match('/^presend/', $_GET['action'])) && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
442					// Now search in setup to overwrite default values
443					if (!empty($user->default_values)) {		// $user->default_values defined from menu 'Setup - Default values'
444						if (isset($user->default_values[$relativepathstring]['createform'])) {
445							foreach ($user->default_values[$relativepathstring]['createform'] as $defkey => $defval) {
446								$qualified = 0;
447								if ($defkey != '_noquery_') {
448									$tmpqueryarraytohave = explode('&', $defkey);
449									$tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
450									$foundintru = 0;
451									foreach ($tmpqueryarraytohave as $tmpquerytohave) {
452										if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
453											$foundintru = 1;
454										}
455									}
456									if (!$foundintru) {
457										$qualified = 1;
458									}
459									//var_dump($defkey.'-'.$qualified);
460								} else {
461									$qualified = 1;
462								}
463
464								if ($qualified) {
465									if (isset($user->default_values[$relativepathstring]['createform'][$defkey][$paramname])) {
466										$out = $user->default_values[$relativepathstring]['createform'][$defkey][$paramname];
467										break;
468									}
469								}
470							}
471						}
472					}
473				} elseif (!empty($paramname) && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
474					// Management of default search_filters and sort order
475					if (!empty($user->default_values)) {
476						// $user->default_values defined from menu 'Setup - Default values'
477						//var_dump($user->default_values[$relativepathstring]);
478						if ($paramname == 'sortfield' || $paramname == 'sortorder') {
479							// Sorted on which fields ? ASC or DESC ?
480							if (isset($user->default_values[$relativepathstring]['sortorder'])) {
481								// Even if paramname is sortfield, data are stored into ['sortorder...']
482								foreach ($user->default_values[$relativepathstring]['sortorder'] as $defkey => $defval) {
483									$qualified = 0;
484									if ($defkey != '_noquery_') {
485										$tmpqueryarraytohave = explode('&', $defkey);
486										$tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
487										$foundintru = 0;
488										foreach ($tmpqueryarraytohave as $tmpquerytohave) {
489											if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
490												$foundintru = 1;
491											}
492										}
493										if (!$foundintru) {
494											$qualified = 1;
495										}
496										//var_dump($defkey.'-'.$qualified);
497									} else {
498										$qualified = 1;
499									}
500
501									if ($qualified) {
502										$forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
503										foreach ($user->default_values[$relativepathstring]['sortorder'][$defkey] as $key => $val) {
504											if ($out) {
505												$out .= ', ';
506											}
507											if ($paramname == 'sortfield') {
508												$out .= dol_string_nospecial($key, '', $forbidden_chars_to_replace);
509											}
510											if ($paramname == 'sortorder') {
511												$out .= dol_string_nospecial($val, '', $forbidden_chars_to_replace);
512											}
513										}
514										//break;	// No break for sortfield and sortorder so we can cumulate fields (is it realy usefull ?)
515									}
516								}
517							}
518						} elseif (isset($user->default_values[$relativepathstring]['filters'])) {
519							foreach ($user->default_values[$relativepathstring]['filters'] as $defkey => $defval) {	// $defkey is a querystring like 'a=b&c=d', $defval is key of user
520								$qualified = 0;
521								if ($defkey != '_noquery_') {
522									$tmpqueryarraytohave = explode('&', $defkey);
523									$tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
524									$foundintru = 0;
525									foreach ($tmpqueryarraytohave as $tmpquerytohave) {
526										if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
527											$foundintru = 1;
528										}
529									}
530									if (!$foundintru) {
531										$qualified = 1;
532									}
533									//var_dump($defkey.'-'.$qualified);
534								} else {
535									$qualified = 1;
536								}
537
538								if ($qualified) {
539									// We must keep $_POST and $_GET here
540									if (isset($_POST['sall']) || isset($_POST['search_all']) || isset($_GET['sall']) || isset($_GET['search_all'])) {
541										// We made a search from quick search menu, do we still use default filter ?
542										if (empty($conf->global->MAIN_DISABLE_DEFAULT_FILTER_FOR_QUICK_SEARCH)) {
543											$forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
544											$out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
545										}
546									} else {
547										$forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
548										$out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
549									}
550									break;
551								}
552							}
553						}
554					}
555				}
556			}
557		}
558	}
559
560	// Substitution variables for GETPOST (used to get final url with variable parameters or final default value with variable parameters)
561	// Example of variables: __DAY__, __MONTH__, __YEAR__, __MYCOMPANY_COUNTRY_ID__, __USER_ID__, ...
562	// 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.
563	if (!is_array($out) && empty($_POST[$paramname]) && empty($noreplace)) {
564		$reg = array();
565		$maxloop = 20;
566		$loopnb = 0; // Protection against infinite loop
567		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.
568			$loopnb++;
569			$newout = '';
570
571			if ($reg[1] == 'DAY') {
572				$tmp = dol_getdate(dol_now(), true);
573				$newout = $tmp['mday'];
574			} elseif ($reg[1] == 'MONTH') {
575				$tmp = dol_getdate(dol_now(), true);
576				$newout = $tmp['mon'];
577			} elseif ($reg[1] == 'YEAR') {
578				$tmp = dol_getdate(dol_now(), true);
579				$newout = $tmp['year'];
580			} elseif ($reg[1] == 'PREVIOUS_DAY') {
581				$tmp = dol_getdate(dol_now(), true);
582				$tmp2 = dol_get_prev_day($tmp['mday'], $tmp['mon'], $tmp['year']);
583				$newout = $tmp2['day'];
584			} elseif ($reg[1] == 'PREVIOUS_MONTH') {
585				$tmp = dol_getdate(dol_now(), true);
586				$tmp2 = dol_get_prev_month($tmp['mon'], $tmp['year']);
587				$newout = $tmp2['month'];
588			} elseif ($reg[1] == 'PREVIOUS_YEAR') {
589				$tmp = dol_getdate(dol_now(), true);
590				$newout = ($tmp['year'] - 1);
591			} elseif ($reg[1] == 'NEXT_DAY') {
592				$tmp = dol_getdate(dol_now(), true);
593				$tmp2 = dol_get_next_day($tmp['mday'], $tmp['mon'], $tmp['year']);
594				$newout = $tmp2['day'];
595			} elseif ($reg[1] == 'NEXT_MONTH') {
596				$tmp = dol_getdate(dol_now(), true);
597				$tmp2 = dol_get_next_month($tmp['mon'], $tmp['year']);
598				$newout = $tmp2['month'];
599			} elseif ($reg[1] == 'NEXT_YEAR') {
600				$tmp = dol_getdate(dol_now(), true);
601				$newout = ($tmp['year'] + 1);
602			} elseif ($reg[1] == 'MYCOMPANY_COUNTRY_ID' || $reg[1] == 'MYCOUNTRY_ID' || $reg[1] == 'MYCOUNTRYID') {
603				$newout = $mysoc->country_id;
604			} elseif ($reg[1] == 'USER_ID' || $reg[1] == 'USERID') {
605				$newout = $user->id;
606			} elseif ($reg[1] == 'USER_SUPERVISOR_ID' || $reg[1] == 'SUPERVISOR_ID' || $reg[1] == 'SUPERVISORID') {
607				$newout = $user->fk_user;
608			} elseif ($reg[1] == 'ENTITY_ID' || $reg[1] == 'ENTITYID') {
609				$newout = $conf->entity;
610			} else {
611				$newout = ''; // Key not found, we replace with empty string
612			}
613			//var_dump('__'.$reg[1].'__ -> '.$newout);
614			$out = preg_replace('/__'.preg_quote($reg[1], '/').'__/', $newout, $out);
615		}
616	}
617
618	// Check rule
619	if (preg_match('/^array/', $check)) {	// If 'array' or 'array:restricthtml' or 'array:aZ09'
620		if (!is_array($out) || empty($out)) {
621			$out = array();
622		} else {
623			$tmparray = explode(':', $check);
624			if (!empty($tmparray[1])) {
625				$tmpcheck = $tmparray[1];
626			} else {
627				$tmpcheck = 'alphanohtml';
628			}
629			foreach ($out as $outkey => $outval) {
630				$out[$outkey] = checkVal($outval, $tmpcheck, $filter, $options);
631			}
632		}
633	} else {
634		$out = checkVal($out, $check, $filter, $options);
635	}
636
637	// Sanitizing for special parameters.
638	// Note: There is no reason to allow the backtopage, backtolist or backtourl parameter to contains an external URL.
639	if ($paramname == 'backtopage' || $paramname == 'backtolist' || $paramname == 'backtourl') {
640		$out = str_replace('\\', '/', $out);					// Can be before the loop because only 1 char is replaced. No risk to get it after other replacements.
641		$out = str_replace(array(':', ';', '@'), '', $out);		// Can be before the loop because only 1 char is replaced. No risk to get it after other replacements.
642		do {
643			$oldstringtoclean = $out;
644			$out = str_ireplace(array('javascript', 'vbscript', '&colon', '&#'), '', $out);
645		} while ($oldstringtoclean != $out);
646
647		$out = preg_replace(array('/^[a-z]*\/\/+/i'), '', $out);	// We remove schema*// to remove external URL
648	}
649
650	// Code for search criteria persistence.
651	// Save data into session if key start with 'search_' or is 'smonth', 'syear', 'month', 'year'
652	if (empty($method) || $method == 3 || $method == 4) {
653		if (preg_match('/^search_/', $paramname) || in_array($paramname, array('sortorder', 'sortfield'))) {
654			//var_dump($paramname.' - '.$out.' '.$user->default_values[$relativepathstring]['filters'][$paramname]);
655
656			// We save search key only if $out not empty that means:
657			// - posted value not empty, or
658			// - 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).
659
660			if ($out != '') {		// $out = '0' or 'abc', it is a search criteria to keep
661				$user->lastsearch_values_tmp[$relativepathstring][$paramname] = $out;
662			}
663		}
664	}
665
666	return $out;
667}
668
669/**
670 *  Return value of a param into GET or POST supervariable.
671 *  Use the property $user->default_values[path]['creatform'] and/or $user->default_values[path]['filters'] and/or $user->default_values[path]['sortorder']
672 *  Note: The property $user->default_values is loaded by main.php when loading the user.
673 *
674 *  @param  string  $paramname   Name of parameter to found
675 *  @param	int		$method	     Type of method (0 = get then post, 1 = only get, 2 = only post, 3 = post then get)
676 *  @param  int     $filter      Filter to apply when $check is set to 'custom'. (See http://php.net/manual/en/filter.filters.php for détails)
677 *  @param  mixed   $options     Options to pass to filter_var when $check is set to 'custom'
678 *  @param	string	$noreplace   Force disable of replacement of __xxx__ strings.
679 *  @return int                  Value found (int)
680 */
681function GETPOSTINT($paramname, $method = 0, $filter = null, $options = null, $noreplace = 0)
682{
683	return (int) GETPOST($paramname, 'int', $method, $filter, $options, $noreplace);
684}
685
686/**
687 *  Return a value after checking on a rule. A sanitization may also have been done.
688 *
689 *  @param  string  $out	     Value to check/clear.
690 *  @param  string  $check	     Type of check/sanitizing
691 *  @param  int     $filter      Filter to apply when $check is set to 'custom'. (See http://php.net/manual/en/filter.filters.php for détails)
692 *  @param  mixed   $options     Options to pass to filter_var when $check is set to 'custom'
693 *  @return string|array         Value sanitized (string or array). It may be '' if format check fails.
694 */
695function checkVal($out = '', $check = 'alphanohtml', $filter = null, $options = null)
696{
697	global $conf;
698
699	// Check is done after replacement
700	switch ($check) {
701		case 'none':
702			break;
703		case 'int':    // Check param is a numeric value (integer but also float or hexadecimal)
704			if (!is_numeric($out)) {
705				$out = '';
706			}
707			break;
708		case 'intcomma':
709			if (preg_match('/[^0-9,-]+/i', $out)) {
710				$out = '';
711			}
712			break;
713		case 'san_alpha':
714			$out = filter_var($out, FILTER_SANITIZE_STRING);
715			break;
716		case 'email':
717			$out = filter_var($out, FILTER_SANITIZE_EMAIL);
718			break;
719		case 'aZ':
720			if (!is_array($out)) {
721				$out = trim($out);
722				if (preg_match('/[^a-z]+/i', $out)) {
723					$out = '';
724				}
725			}
726			break;
727		case 'aZ09':
728			if (!is_array($out)) {
729				$out = trim($out);
730				if (preg_match('/[^a-z0-9_\-\.]+/i', $out)) {
731					$out = '';
732				}
733			}
734			break;
735		case 'aZ09comma':		// great to sanitize sortfield or sortorder params that can be t.abc,t.def_gh
736			if (!is_array($out)) {
737				$out = trim($out);
738				if (preg_match('/[^a-z0-9_\-\.,]+/i', $out)) {
739					$out = '';
740				}
741			}
742			break;
743		case 'nohtml':		// No html
744			$out = dol_string_nohtmltag($out, 0);
745			break;
746		case 'alpha':		// No html and no ../ and "
747		case 'alphanohtml':	// Recommended for most scalar parameters and search parameters
748			if (!is_array($out)) {
749				$out = trim($out);
750				do {
751					$oldstringtoclean = $out;
752					// Remove html tags
753					$out = dol_string_nohtmltag($out, 0);
754					// Remove also other dangerous string sequences
755					// '"' is dangerous because param in url can close the href= or src= and add javascript functions.
756					// '../' or '..\' is dangerous because it allows dir transversals
757					// Note &#38, '&#0000038', '&#x26'... is a simple char like '&' alone but there is no reason to accept such way to encode input data.
758					$out = str_ireplace(array('&#38', '&#0000038', '&#x26', '&quot', '&#34', '&#0000034', '&#x22', '"', '&#47', '&#0000047', '&#92', '&#0000092', '&#x2F', '../', '..\\'), '', $out);
759				} while ($oldstringtoclean != $out);
760				// keep lines feed
761			}
762			break;
763		case 'alphawithlgt':		// No " and no ../ but we keep balanced < > tags with no special chars inside. Can be used for email string like "Name <email>"
764			if (!is_array($out)) {
765				$out = trim($out);
766				do {
767					$oldstringtoclean = $out;
768					// Remove html tags
769					$out = dol_html_entity_decode($out, ENT_COMPAT | ENT_HTML5, 'UTF-8');
770					// '"' is dangerous because param in url can close the href= or src= and add javascript functions.
771					// '../' or '..\' is dangerous because it allows dir transversals
772					// Note &#38, '&#0000038', '&#x26'... is a simple char like '&' alone but there is no reason to accept such way to encode input data.
773					$out = str_ireplace(array('&#38', '&#0000038', '&#x26', '&quot', '&#34', '&#0000034', '&#x22', '"', '&#47', '&#0000047', '&#92', '&#0000092', '&#x2F', '../', '..\\'), '', $out);
774				} while ($oldstringtoclean != $out);
775			}
776			break;
777		case 'restricthtml':		// Recommended for most html textarea
778		case 'restricthtmlallowunvalid':
779			do {
780				$oldstringtoclean = $out;
781
782				if (!empty($out) && !empty($conf->global->MAIN_RESTRICTHTML_ONLY_VALID_HTML) && $check != 'restricthtmlallowunvalid') {
783					try {
784						$dom = new DOMDocument;
785						// Add a trick to solve pb with text without parent tag
786						// like '<h1>Foo</h1><p>bar</p>' that ends up with '<h1>Foo<p>bar</p></h1>'
787						// like 'abc' that ends up with '<p>abc</p>'
788						$out = '<div class="tricktoremove">'.$out.'</div>';
789
790						$dom->loadHTML($out, LIBXML_ERR_NONE|LIBXML_HTML_NOIMPLIED|LIBXML_HTML_NODEFDTD|LIBXML_NONET|LIBXML_NOWARNING|LIBXML_NOXMLDECL);
791						$out = trim($dom->saveHTML());
792
793						// Remove the trick added to solve pb with text without parent tag
794						$out = preg_replace('/^<div class="tricktoremove">/', '', $out);
795						$out = preg_replace('/<\/div>$/', '', $out);
796					} catch (Exception $e) {
797						//print $e->getMessage();
798						return 'InvalidHTMLString';
799					}
800				}
801
802				// Ckeditor use the numeric entitic for apostrophe so we force it to text entity (all other special chars are
803				// encoded using text entities) so we can then exclude all numeric entities.
804				$out = preg_replace('/&#39;/i', '&apos;', $out);
805
806				// We replace chars from a/A to z/Z encoded with numeric HTML entities with the real char so we won't loose the chars at the next step (preg_replace).
807				// No need to use a loop here, this step is not to sanitize (this is done at next step, this is to try to save chars, even if they are
808				// using a non coventionnel way to be encoded, to not have them sanitized just after)
809				$out = preg_replace_callback('/&#(x?[0-9][0-9a-f]+;?)/i', 'realCharForNumericEntities', $out);
810
811				// Now we remove all remaining HTML entities starting with a number. We don't want such entities.
812				$out = preg_replace('/&#x?[0-9]+/i', '', $out);	// For example if we have j&#x61vascript with an entities without the ; to hide the 'a' of 'javascript'.
813
814				$out = dol_string_onlythesehtmltags($out, 0, 1, 1);
815
816				// We should also exclude non expected attributes
817				if (!empty($conf->global->MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES)) {
818					// Warning, the function may add a LF so we are forced to trim to compare with old $out without having always a difference and an infinit loop.
819					$out = trim(dol_string_onlythesehtmlattributes($out));
820				}
821
822				// Restore entity &apos; into &#39; (restricthtml is for html content so we can use html entity)
823				$out = preg_replace('/&apos;/i', "&#39;", $out);
824			} while ($oldstringtoclean != $out);
825			break;
826		case 'custom':
827			if (empty($filter)) {
828				return 'BadFourthParameterForGETPOST';
829			}
830			$out = filter_var($out, $filter, $options);
831			break;
832	}
833
834	return $out;
835}
836
837
838if (!function_exists('dol_getprefix')) {
839	/**
840	 *  Return a prefix to use for this Dolibarr instance, for session/cookie names or email id.
841	 *  The prefix is unique for instance and avoid conflict between multi-instances, even when having two instances with same root dir
842	 *  or two instances in same virtual servers.
843	 *
844	 *  @param  string  $mode                   '' (prefix for session name) or 'email' (prefix for email id)
845	 *  @return	string                          A calculated prefix
846	 */
847	function dol_getprefix($mode = '')
848	{
849		// If prefix is for email (we need to have $conf alreayd loaded for this case)
850		if ($mode == 'email') {
851			global $conf;
852
853			if (!empty($conf->global->MAIL_PREFIX_FOR_EMAIL_ID)) {	// If MAIL_PREFIX_FOR_EMAIL_ID is set (a value initialized with a random value is recommended)
854				if ($conf->global->MAIL_PREFIX_FOR_EMAIL_ID != 'SERVER_NAME') {
855					return $conf->global->MAIL_PREFIX_FOR_EMAIL_ID;
856				} elseif (isset($_SERVER["SERVER_NAME"])) {
857					return $_SERVER["SERVER_NAME"];
858				}
859			}
860
861			// The recommended value (may be not defined for old versions)
862			if (!empty($conf->file->instance_unique_id)) {
863				return $conf->file->instance_unique_id;
864			}
865
866			// For backward compatibility
867			return dol_hash(DOL_DOCUMENT_ROOT.DOL_URL_ROOT, '3');
868		}
869
870		// If prefix is for session (no need to have $conf loaded)
871		global $dolibarr_main_instance_unique_id, $dolibarr_main_cookie_cryptkey;	// This is loaded by filefunc.inc.php
872		$tmp_instance_unique_id = empty($dolibarr_main_instance_unique_id) ? (empty($dolibarr_main_cookie_cryptkey) ? '' : $dolibarr_main_cookie_cryptkey) : $dolibarr_main_instance_unique_id; // Unique id of instance
873
874		// The recommended value (may be not defined for old versions)
875		if (!empty($tmp_instance_unique_id)) {
876			return $tmp_instance_unique_id;
877		}
878
879		// For backward compatibility
880		if (isset($_SERVER["SERVER_NAME"]) && isset($_SERVER["DOCUMENT_ROOT"])) {
881			return dol_hash($_SERVER["SERVER_NAME"].$_SERVER["DOCUMENT_ROOT"].DOL_DOCUMENT_ROOT.DOL_URL_ROOT, '3');
882		}
883
884		return dol_hash(DOL_DOCUMENT_ROOT.DOL_URL_ROOT, '3');
885	}
886}
887
888/**
889 *	Make an include_once using default root and alternate root if it fails.
890 *  To link to a core file, use include(DOL_DOCUMENT_ROOT.'/pathtofile')
891 *  To link to a module file from a module file, use include './mymodulefile';
892 *  To link to a module file from a core file, then this function can be used (call by hook / trigger / speciales pages)
893 *
894 * 	@param	string	$relpath	Relative path to file (Ie: mydir/myfile, ../myfile, ...)
895 * 	@param	string	$classname	Class name (deprecated)
896 *  @return bool                True if load is a success, False if it fails
897 */
898function dol_include_once($relpath, $classname = '')
899{
900	global $conf, $langs, $user, $mysoc; // Do not remove this. They must be defined for files we include. Other globals var must be retrieved with $GLOBALS['var']
901
902	$fullpath = dol_buildpath($relpath);
903
904	if (!file_exists($fullpath)) {
905		dol_syslog('functions::dol_include_once Tried to load unexisting file: '.$relpath, LOG_WARNING);
906		return false;
907	}
908
909	if (!empty($classname) && !class_exists($classname)) {
910		return include $fullpath;
911	} else {
912		return include_once $fullpath;
913	}
914}
915
916
917/**
918 *	Return path of url or filesystem. Can check into alternate dir or alternate dir + main dir depending on value of $returnemptyifnotfound.
919 *
920 * 	@param	string	$path						Relative path to file (if mode=0) or relative url (if mode=1). Ie: mydir/myfile, ../myfile
921 *  @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)
922 *  @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)
923 *  											1:If $type==0 and if file was not found into alternate dir, return empty string
924 *  											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
925 *  @return string								Full filesystem path (if path=0) or '' if file not found, Full url path (if mode=1)
926 */
927function dol_buildpath($path, $type = 0, $returnemptyifnotfound = 0)
928{
929	global $conf;
930
931	$path = preg_replace('/^\//', '', $path);
932
933	if (empty($type)) {	// For a filesystem path
934		$res = DOL_DOCUMENT_ROOT.'/'.$path; // Standard default path
935		if (is_array($conf->file->dol_document_root)) {
936			foreach ($conf->file->dol_document_root as $key => $dirroot) {	// ex: array("main"=>"/home/main/htdocs", "alt0"=>"/home/dirmod/htdocs", ...)
937				if ($key == 'main') {
938					continue;
939				}
940				if (file_exists($dirroot.'/'.$path)) {
941					$res = $dirroot.'/'.$path;
942					return $res;
943				}
944			}
945		}
946		if ($returnemptyifnotfound) {
947			// Not found into alternate dir
948			if ($returnemptyifnotfound == 1 || !file_exists($res)) {
949				return '';
950			}
951		}
952	} else {
953		// For an url path
954		// We try to get local path of file on filesystem from url
955		// Note that trying to know if a file on disk exist by forging path on disk from url
956		// works only for some web server and some setup. This is bugged when
957		// using proxy, rewriting, virtual path, etc...
958		$res = '';
959		if ($type == 1) {
960			$res = DOL_URL_ROOT.'/'.$path; // Standard value
961		}
962		if ($type == 2) {
963			$res = DOL_MAIN_URL_ROOT.'/'.$path; // Standard value
964		}
965		if ($type == 3) {
966			$res = DOL_URL_ROOT.'/'.$path;
967		}
968
969		foreach ($conf->file->dol_document_root as $key => $dirroot) {	// ex: array(["main"]=>"/home/main/htdocs", ["alt0"]=>"/home/dirmod/htdocs", ...)
970			if ($key == 'main') {
971				if ($type == 3) {
972					global $dolibarr_main_url_root;
973
974					// Define $urlwithroot
975					$urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
976					$urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file
977					//$urlwithroot=DOL_MAIN_URL_ROOT;					// This is to use same domain name than current
978
979					$res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : $urlwithroot).'/'.$path; // Test on start with http is for old conf syntax
980				}
981				continue;
982			}
983			preg_match('/^([^\?]+(\.css\.php|\.css|\.js\.php|\.js|\.png|\.jpg|\.php)?)/i', $path, $regs); // Take part before '?'
984			if (!empty($regs[1])) {
985				//print $key.'-'.$dirroot.'/'.$path.'-'.$conf->file->dol_url_root[$type].'<br>'."\n";
986				if (file_exists($dirroot.'/'.$regs[1])) {
987					if ($type == 1) {
988						$res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_URL_ROOT).$conf->file->dol_url_root[$key].'/'.$path;
989					}
990					if ($type == 2) {
991						$res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_MAIN_URL_ROOT).$conf->file->dol_url_root[$key].'/'.$path;
992					}
993					if ($type == 3) {
994						global $dolibarr_main_url_root;
995
996						// Define $urlwithroot
997						$urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
998						$urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file
999						//$urlwithroot=DOL_MAIN_URL_ROOT;					// This is to use same domain name than current
1000
1001						$res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : $urlwithroot).$conf->file->dol_url_root[$key].'/'.$path; // Test on start with http is for old conf syntax
1002					}
1003					break;
1004				}
1005			}
1006		}
1007	}
1008
1009	return $res;
1010}
1011
1012/**
1013 *	Create a clone of instance of object (new instance with same value for properties)
1014 *  With native = 0: Property that are reference are also new object (full isolation clone). This means $this->db of new object is not valid.
1015 *  With native = 1: Use PHP clone. Property that are reference are same pointer. This means $this->db of new object is still valid but point to same this->db than original object.
1016 *
1017 * 	@param	object	$object		Object to clone
1018 *  @param	int		$native		0=Full isolation method, 1=Native PHP method
1019 *	@return object				Clone object
1020 *  @see https://php.net/manual/language.oop5.cloning.php
1021 */
1022function dol_clone($object, $native = 0)
1023{
1024	if (empty($native)) {
1025		$myclone = unserialize(serialize($object));	// serialize then unserialize is hack to be sure to have a new object for all fields
1026	} else {
1027		$myclone = clone $object; // PHP clone is a shallow copy only, not a real clone, so properties of references will keep the reference (refering to the same target/variable)
1028	}
1029
1030	return $myclone;
1031}
1032
1033/**
1034 *	Optimize a size for some browsers (phone, smarphone, ...)
1035 *
1036 * 	@param	int		$size		Size we want
1037 * 	@param	string	$type		Type of optimizing:
1038 * 								'' = function used to define a size for truncation
1039 * 								'width' = function is used to define a width
1040 *	@return int					New size after optimizing
1041 */
1042function dol_size($size, $type = '')
1043{
1044	global $conf;
1045	if (empty($conf->dol_optimize_smallscreen)) {
1046		return $size;
1047	}
1048	if ($type == 'width' && $size > 250) {
1049		return 250;
1050	} else {
1051		return 10;
1052	}
1053}
1054
1055
1056/**
1057 *	Clean a string to use it as a file name.
1058 *  Replace also '--' and ' -' strings, they are used for parameters separation (Note: ' - ' is allowed).
1059 *
1060 *	@param	string	$str            String to clean
1061 * 	@param	string	$newstr			String to replace bad chars with.
1062 *  @param	int	    $unaccent		1=Remove also accent (default), 0 do not remove them
1063 *	@return string          		String cleaned (a-zA-Z_)
1064 *
1065 * 	@see        	dol_string_nospecial(), dol_string_unaccent(), dol_sanitizePathName()
1066 */
1067function dol_sanitizeFileName($str, $newstr = '_', $unaccent = 1)
1068{
1069	// List of special chars for filenames in windows are defined on page https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
1070	// Char '>' '<' '|' '$' and ';' are special chars for shells.
1071	// Char '/' and '\' are file delimiters.
1072	// -- car can be used into filename to inject special paramaters like --use-compress-program to make command with file as parameter making remote execution of command
1073	$filesystem_forbidden_chars = array('<', '>', '/', '\\', '?', '*', '|', '"', ':', '°', '$', ';');
1074	$tmp = dol_string_nospecial($unaccent ? dol_string_unaccent($str) : $str, $newstr, $filesystem_forbidden_chars);
1075	$tmp = preg_replace('/\-\-+/', '_', $tmp);
1076	$tmp = preg_replace('/\s+\-([^\s])/', ' _$1', $tmp);
1077	return $tmp;
1078}
1079
1080/**
1081 *	Clean a string to use it as a path name.
1082 *  Replace also '--' and ' -' strings, they are used for parameters separation (Note: ' - ' is allowed).
1083 *
1084 *	@param	string	$str            String to clean
1085 * 	@param	string	$newstr			String to replace bad chars with
1086 *  @param	int	    $unaccent		1=Remove also accent (default), 0 do not remove them
1087 *	@return string          		String cleaned (a-zA-Z_)
1088 *
1089 * 	@see        	dol_string_nospecial(), dol_string_unaccent(), dol_sanitizeFileName()
1090 */
1091function dol_sanitizePathName($str, $newstr = '_', $unaccent = 1)
1092{
1093	$filesystem_forbidden_chars = array('<', '>', '?', '*', '|', '"', '°');
1094	$tmp = dol_string_nospecial($unaccent ? dol_string_unaccent($str) : $str, $newstr, $filesystem_forbidden_chars);
1095	$tmp = preg_replace('/\-\-+/', '_', $tmp);
1096	$tmp = preg_replace('/\s+\-([^\s])/', ' _$1', $tmp);
1097	return $tmp;
1098}
1099
1100/**
1101 *  Clean a string to use it as an URL (into a href or src attribute)
1102 *
1103 *  @param      string		$stringtoclean		String to clean
1104 *  @param		int			$type				0=Accept all Url, 1=Clean external Url (keep only relative Url)
1105 *  @return     string     		 				Escaped string.
1106 */
1107function dol_sanitizeUrl($stringtoclean, $type = 1)
1108{
1109	// We clean string because some hacks try to obfuscate evil strings by inserting non printable chars. Example: 'java(ascci09)scr(ascii00)ipt' is processed like 'javascript' (whatever is place of evil ascii char)
1110	// We should use dol_string_nounprintableascii but function may not be yet loaded/available
1111	$stringtoclean = preg_replace('/[\x00-\x1F\x7F]/u', '', $stringtoclean); // /u operator makes UTF8 valid characters being ignored so are not included into the replace
1112	// We clean html comments because some hacks try to obfuscate evil strings by inserting HTML comments. Example: on<!-- -->error=alert(1)
1113	$stringtoclean = preg_replace('/<!--[^>]*-->/', '', $stringtoclean);
1114
1115	$stringtoclean = str_replace('\\', '/', $stringtoclean);
1116	if ($type == 1) {
1117		// removing : should disable links to external url like http:aaa)
1118		// removing ';' should disable "named" html entities encode into an url (we should not have this into an url)
1119		$stringtoclean = str_replace(array(':', ';', '@'), '', $stringtoclean);
1120	}
1121
1122	do {
1123		$oldstringtoclean = $stringtoclean;
1124		// removing '&colon' should disable links to external url like http:aaa)
1125		// removing '&#' should disable "numeric" html entities encode into an url (we should not have this into an url)
1126		$stringtoclean = str_ireplace(array('javascript', 'vbscript', '&colon', '&#'), '', $stringtoclean);
1127	} while ($oldstringtoclean != $stringtoclean);
1128
1129	if ($type == 1) {
1130		// removing '//' should disable links to external url like //aaa or http//)
1131		$stringtoclean = preg_replace(array('/^[a-z]*\/\/+/i'), '', $stringtoclean);
1132	}
1133
1134	return $stringtoclean;
1135}
1136
1137/**
1138 *	Clean a string from all accent characters to be used as ref, login or by dol_sanitizeFileName
1139 *
1140 *	@param	string	$str			String to clean
1141 *	@return string   	       		Cleaned string
1142 *
1143 * 	@see    		dol_sanitizeFilename(), dol_string_nospecial()
1144 */
1145function dol_string_unaccent($str)
1146{
1147	if (utf8_check($str)) {
1148		// See http://www.utf8-chartable.de/
1149		$string = rawurlencode($str);
1150		$replacements = array(
1151		'%C3%80' => 'A', '%C3%81' => 'A', '%C3%82' => 'A', '%C3%83' => 'A', '%C3%84' => 'A', '%C3%85' => 'A',
1152		'%C3%88' => 'E', '%C3%89' => 'E', '%C3%8A' => 'E', '%C3%8B' => 'E',
1153		'%C3%8C' => 'I', '%C3%8D' => 'I', '%C3%8E' => 'I', '%C3%8F' => 'I',
1154		'%C3%92' => 'O', '%C3%93' => 'O', '%C3%94' => 'O', '%C3%95' => 'O', '%C3%96' => 'O',
1155		'%C3%99' => 'U', '%C3%9A' => 'U', '%C3%9B' => 'U', '%C3%9C' => 'U',
1156		'%C3%A0' => 'a', '%C3%A1' => 'a', '%C3%A2' => 'a', '%C3%A3' => 'a', '%C3%A4' => 'a', '%C3%A5' => 'a',
1157		'%C3%A7' => 'c',
1158		'%C3%A8' => 'e', '%C3%A9' => 'e', '%C3%AA' => 'e', '%C3%AB' => 'e',
1159		'%C3%AC' => 'i', '%C3%AD' => 'i', '%C3%AE' => 'i', '%C3%AF' => 'i',
1160		'%C3%B1' => 'n',
1161		'%C3%B2' => 'o', '%C3%B3' => 'o', '%C3%B4' => 'o', '%C3%B5' => 'o', '%C3%B6' => 'o',
1162		'%C3%B9' => 'u', '%C3%BA' => 'u', '%C3%BB' => 'u', '%C3%BC' => 'u',
1163		'%C3%BF' => 'y'
1164		);
1165		$string = strtr($string, $replacements);
1166		return rawurldecode($string);
1167	} else {
1168		// See http://www.ascii-code.com/
1169		$string = strtr(
1170			$str,
1171			"\xC0\xC1\xC2\xC3\xC4\xC5\xC7
1172			\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1
1173			\xD2\xD3\xD4\xD5\xD8\xD9\xDA\xDB\xDD
1174			\xE0\xE1\xE2\xE3\xE4\xE5\xE7\xE8\xE9\xEA\xEB
1175			\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF8
1176			\xF9\xFA\xFB\xFC\xFD\xFF",
1177			"AAAAAAC
1178			EEEEIIIIDN
1179			OOOOOUUUY
1180			aaaaaaceeee
1181			iiiidnooooo
1182			uuuuyy"
1183		);
1184		$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"));
1185		return $string;
1186	}
1187}
1188
1189/**
1190 *	Clean a string from all punctuation characters to use it as a ref or login.
1191 *  This is a more complete function than dol_sanitizeFileName.
1192 *
1193 *	@param	string			$str            	String to clean
1194 * 	@param	string			$newstr				String to replace forbidden chars with
1195 *  @param  array|string	$badcharstoreplace  List of forbidden characters to replace
1196 *  @param  array|string	$badcharstoremove   List of forbidden characters to remove
1197 * 	@return string          					Cleaned string
1198 *
1199 * 	@see    		dol_sanitizeFilename(), dol_string_unaccent(), dol_string_nounprintableascii()
1200 */
1201function dol_string_nospecial($str, $newstr = '_', $badcharstoreplace = '', $badcharstoremove = '')
1202{
1203	$forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ",", ";", "=", '°'); // more complete than dol_sanitizeFileName
1204	$forbidden_chars_to_remove = array();
1205	//$forbidden_chars_to_remove=array("(",")");
1206
1207	if (is_array($badcharstoreplace)) {
1208		$forbidden_chars_to_replace = $badcharstoreplace;
1209	}
1210	if (is_array($badcharstoremove)) {
1211		$forbidden_chars_to_remove = $badcharstoremove;
1212	}
1213
1214	return str_replace($forbidden_chars_to_replace, $newstr, str_replace($forbidden_chars_to_remove, "", $str));
1215}
1216
1217
1218/**
1219 *	Clean a string from all non printable ASCII chars (0x00-0x1F and 0x7F). It can also removes also Tab-CR-LF. UTF8 chars remains.
1220 *  This can be used to sanitize a string and view its real content. Some hacks try to obfuscate attacks by inserting non printable chars.
1221 *  Note, for information: UTF8 on 1 byte are: \x00-\7F
1222 *                                 2 bytes are: byte 1 \xc0-\xdf, byte 2 = \x80-\xbf
1223 *                                 3 bytes are: byte 1 \xe0-\xef, byte 2 = \x80-\xbf, byte 3 = \x80-\xbf
1224 *                                 4 bytes are: byte 1 \xf0-\xf7, byte 2 = \x80-\xbf, byte 3 = \x80-\xbf, byte 4 = \x80-\xbf
1225 *	@param	string	$str            	String to clean
1226 *  @param	int		$removetabcrlf		Remove also CR-LF
1227 * 	@return string          			Cleaned string
1228 *
1229 * 	@see    		dol_sanitizeFilename(), dol_string_unaccent(), dol_string_nospecial()
1230 */
1231function dol_string_nounprintableascii($str, $removetabcrlf = 1)
1232{
1233	if ($removetabcrlf) {
1234		return preg_replace('/[\x00-\x1F\x7F]/u', '', $str); // /u operator makes UTF8 valid characters being ignored so are not included into the replace
1235	} else {
1236		return preg_replace('/[\x00-\x08\x11-\x12\x14-\x1F\x7F]/u', '', $str); // /u operator should make UTF8 valid characters being ignored so are not included into the replace
1237	}
1238}
1239
1240/**
1241 *  Returns text escaped for inclusion into javascript code
1242 *
1243 *  @param      string		$stringtoescape		String to escape
1244 *  @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 \
1245 *  @param		int		$noescapebackslashn	0=Escape also \n. 1=Do not escape \n.
1246 *  @return     string     		 				Escaped string. Both ' and " are escaped into ' if they are escaped.
1247 */
1248function dol_escape_js($stringtoescape, $mode = 0, $noescapebackslashn = 0)
1249{
1250	// escape quotes and backslashes, newlines, etc.
1251	$substitjs = array("&#039;"=>"\\'", "\r"=>'\\r');
1252	//$substitjs['</']='<\/';	// We removed this. Should be useless.
1253	if (empty($noescapebackslashn)) {
1254		$substitjs["\n"] = '\\n';
1255		$substitjs['\\'] = '\\\\';
1256	}
1257	if (empty($mode)) {
1258		$substitjs["'"] = "\\'";
1259		$substitjs['"'] = "\\'";
1260	} elseif ($mode == 1) {
1261		$substitjs["'"] = "\\'";
1262	} elseif ($mode == 2) {
1263		$substitjs['"'] = '\\"';
1264	} elseif ($mode == 3) {
1265		$substitjs["'"] = "\\'";
1266		$substitjs['"'] = "\\\"";
1267	}
1268	return strtr($stringtoescape, $substitjs);
1269}
1270
1271/**
1272 *  Returns text escaped for inclusion into javascript code
1273 *
1274 *  @param      string		$stringtoescape		String to escape
1275 *  @return     string     		 				Escaped string for json content.
1276 */
1277function dol_escape_json($stringtoescape)
1278{
1279	return str_replace('"', '\"', $stringtoescape);
1280}
1281
1282/**
1283 *  Returns text escaped for inclusion in HTML alt or title tags, or into values of HTML input fields.
1284 *
1285 *  @param      string		$stringtoescape			String to escape
1286 *  @param		int			$keepb					1=Keep b tags, 0=remove them completeley
1287 *  @param      int         $keepn              	1=Preserve \r\n strings (otherwise, replace them with escaped value). Set to 1 when escaping for a <textarea>.
1288 *  @param		string		$noescapetags			'' or 'common' or list of tags to not escape
1289 *  @param		int			$escapeonlyhtmltags		1=Escape only html tags, not the special chars like accents.
1290 *  @return     string     				 			Escaped string
1291 *  @see		dol_string_nohtmltag(), dol_string_nospecial(), dol_string_unaccent()
1292 */
1293function dol_escape_htmltag($stringtoescape, $keepb = 0, $keepn = 0, $noescapetags = '', $escapeonlyhtmltags = 0)
1294{
1295	if ($noescapetags == 'common') {
1296		$noescapetags = 'html,body,a,b,em,i,u,ul,li,br,div,img,font,p,span,strong,table,tr,td,th,tbody';
1297	}
1298
1299	// escape quotes and backslashes, newlines, etc.
1300	if ($escapeonlyhtmltags) {
1301		$tmp = htmlspecialchars_decode($stringtoescape, ENT_COMPAT);
1302	} else {
1303		$tmp = html_entity_decode($stringtoescape, ENT_COMPAT, 'UTF-8');
1304	}
1305	if (!$keepb) {
1306		$tmp = strtr($tmp, array("<b>"=>'', '</b>'=>''));
1307	}
1308	if (!$keepn) {
1309		$tmp = strtr($tmp, array("\r"=>'\\r', "\n"=>'\\n'));
1310	}
1311
1312	if ($escapeonlyhtmltags) {
1313		return htmlspecialchars($tmp, ENT_COMPAT, 'UTF-8');
1314	} else {
1315		// Escape tags to keep
1316		$tmparrayoftags = array();
1317		if ($noescapetags) {
1318			$tmparrayoftags = explode(',', $noescapetags);
1319		}
1320
1321		if (count($tmparrayoftags)) {
1322			foreach ($tmparrayoftags as $tagtoreplace) {
1323				$tmp = str_ireplace('<'.$tagtoreplace.'>', '__BEGINTAGTOREPLACE'.$tagtoreplace.'__', $tmp);
1324				$tmp = str_ireplace('</'.$tagtoreplace.'>', '__ENDTAGTOREPLACE'.$tagtoreplace.'__', $tmp);
1325			}
1326		}
1327
1328		$result = htmlentities($tmp, ENT_COMPAT, 'UTF-8');
1329
1330		if (count($tmparrayoftags)) {
1331			foreach ($tmparrayoftags as $tagtoreplace) {
1332				$result = str_ireplace('__BEGINTAGTOREPLACE'.$tagtoreplace.'__', '<'.$tagtoreplace.'>', $result);
1333				$result = str_ireplace('__ENDTAGTOREPLACE'.$tagtoreplace.'__', '</'.$tagtoreplace.'>', $result);
1334			}
1335		}
1336
1337		return $result;
1338	}
1339}
1340
1341/**
1342 * Convert a string to lower. Never use strtolower because it does not works with UTF8 strings.
1343 *
1344 * @param 	string		$string		        String to encode
1345 * @param   string      $encoding           Character set encoding
1346 * @return 	string							String converted
1347 */
1348function dol_strtolower($string, $encoding = "UTF-8")
1349{
1350	if (function_exists('mb_strtolower')) {
1351		return mb_strtolower($string, $encoding);
1352	} else {
1353		return strtolower($string);
1354	}
1355}
1356
1357/**
1358 * Convert a string to upper. Never use strtolower because it does not works with UTF8 strings.
1359 *
1360 * @param 	string		$string		        String to encode
1361 * @param   string      $encoding           Character set encoding
1362 * @return 	string							String converted
1363 */
1364function dol_strtoupper($string, $encoding = "UTF-8")
1365{
1366	if (function_exists('mb_strtoupper')) {
1367		return mb_strtoupper($string, $encoding);
1368	} else {
1369		return strtoupper($string);
1370	}
1371}
1372
1373/**
1374 * Convert first character of the first word of a string to upper. Never use ucfirst because it does not works with UTF8 strings.
1375 *
1376 * @param   string      $string         String to encode
1377 * @param   string      $encoding       Character set encodign
1378 * @return  string                      String converted
1379 */
1380function dol_ucfirst($string, $encoding = "UTF-8")
1381{
1382	if (function_exists('mb_substr')) {
1383		return mb_strtoupper(mb_substr($string, 0, 1, $encoding), $encoding).mb_substr($string, 1, null, $encoding);
1384	} else {
1385		return ucfirst($string);
1386	}
1387}
1388
1389/**
1390 * Convert first character of all the words of a string to upper. Never use ucfirst because it does not works with UTF8 strings.
1391 *
1392 * @param   string      $string         String to encode
1393 * @param   string      $encoding       Character set encodign
1394 * @return  string                      String converted
1395 */
1396function dol_ucwords($string, $encoding = "UTF-8")
1397{
1398	if (function_exists('mb_convert_case')) {
1399		return mb_convert_case($string, MB_CASE_TITLE, $encoding);
1400	} else {
1401		return ucwords($string);
1402	}
1403}
1404
1405/**
1406 *	Write log message into outputs. Possible outputs can be:
1407 *	SYSLOG_HANDLERS = ["mod_syslog_file"]  		file name is then defined by SYSLOG_FILE
1408 *	SYSLOG_HANDLERS = ["mod_syslog_syslog"]  	facility is then defined by SYSLOG_FACILITY
1409 *  Warning, syslog functions are bugged on Windows, generating memory protection faults. To solve
1410 *  this, use logging to files instead of syslog (see setup of module).
1411 *  Note: If constant 'SYSLOG_FILE_NO_ERROR' defined, we never output any error message when writing to log fails.
1412 *  Note: You can get log message into html sources by adding parameter &logtohtml=1 (constant MAIN_LOGTOHTML must be set)
1413 *  This function works only if syslog module is enabled.
1414 * 	This must not use any call to other function calling dol_syslog (avoid infinite loop).
1415 *
1416 * 	@param  string		$message				Line to log. ''=Show nothing
1417 *  @param  int			$level					Log level
1418 *												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
1419 *												On Linux   LOG_ERR=3, LOG_WARNING=4, LOG_NOTICE=5, LOG_INFO=6, LOG_DEBUG=7
1420 *  @param	int			$ident					1=Increase ident of 1, -1=Decrease ident of 1
1421 *  @param	string		$suffixinfilename		When output is a file, append this suffix into default log filename.
1422 *  @param	string		$restricttologhandler	Force output of log only to this log handler
1423 *  @param	array|null	$logcontext				If defined, an array with extra informations (can be used by some log handlers)
1424 *  @return	void
1425 */
1426function dol_syslog($message, $level = LOG_INFO, $ident = 0, $suffixinfilename = '', $restricttologhandler = '', $logcontext = null)
1427{
1428	global $conf, $user, $debugbar;
1429
1430	// If syslog module enabled
1431	if (empty($conf->syslog->enabled)) {
1432		return;
1433	}
1434
1435	// Check if we are into execution of code of a website
1436	if (defined('USEEXTERNALSERVER') && !defined('USEDOLIBARRSERVER') && !defined('USEDOLIBARREDITOR')) {
1437		global $website, $websitekey;
1438		if (is_object($website) && !empty($website->ref)) {
1439			$suffixinfilename .= '_website_'.$website->ref;
1440		} elseif (!empty($websitekey)) {
1441			$suffixinfilename .= '_website_'.$websitekey;
1442		}
1443	}
1444
1445	if ($ident < 0) {
1446		foreach ($conf->loghandlers as $loghandlerinstance) {
1447			$loghandlerinstance->setIdent($ident);
1448		}
1449	}
1450
1451	if (!empty($message)) {
1452		// Test log level
1453		$logLevels = array(LOG_EMERG=>'EMERG', LOG_ALERT=>'ALERT', LOG_CRIT=>'CRITICAL', LOG_ERR=>'ERR', LOG_WARNING=>'WARN', LOG_NOTICE=>'NOTICE', LOG_INFO=>'INFO', LOG_DEBUG=>'DEBUG');
1454		if (!array_key_exists($level, $logLevels)) {
1455			throw new Exception('Incorrect log level');
1456		}
1457		if ($level > $conf->global->SYSLOG_LEVEL) {
1458			return;
1459		}
1460
1461		if (empty($conf->global->MAIN_SHOW_PASSWORD_INTO_LOG)) {
1462			$message = preg_replace('/password=\'[^\']*\'/', 'password=\'hidden\'', $message); // protection to avoid to have value of password in log
1463		}
1464
1465		// If adding log inside HTML page is required
1466		if ((!empty($_REQUEST['logtohtml']) && !empty($conf->global->MAIN_ENABLE_LOG_TO_HTML))
1467			|| (!empty($user->rights->debugbar->read) && is_object($debugbar))) {
1468			$conf->logbuffer[] = dol_print_date(time(), "%Y-%m-%d %H:%M:%S")." ".$logLevels[$level]." ".$message;
1469		}
1470
1471		//TODO: Remove this. MAIN_ENABLE_LOG_INLINE_HTML should be deprecated and use a log handler dedicated to HTML output
1472		// If html log tag enabled and url parameter log defined, we show output log on HTML comments
1473		if (!empty($conf->global->MAIN_ENABLE_LOG_INLINE_HTML) && !empty($_GET["log"])) {
1474			print "\n\n<!-- Log start\n";
1475			print dol_escape_htmltag($message)."\n";
1476			print "Log end -->\n";
1477		}
1478
1479		$data = array(
1480			'message' => $message,
1481			'script' => (isset($_SERVER['PHP_SELF']) ? basename($_SERVER['PHP_SELF'], '.php') : false),
1482			'level' => $level,
1483			'user' => ((is_object($user) && $user->id) ? $user->login : false),
1484			'ip' => false
1485		);
1486
1487		$remoteip = getUserRemoteIP(); // Get ip when page run on a web server
1488		if (!empty($remoteip)) {
1489			$data['ip'] = $remoteip;
1490			// This is when server run behind a reverse proxy
1491			if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR'] != $remoteip) {
1492				$data['ip'] = $_SERVER['HTTP_X_FORWARDED_FOR'].' -> '.$data['ip'];
1493			} elseif (!empty($_SERVER['HTTP_CLIENT_IP']) && $_SERVER['HTTP_CLIENT_IP'] != $remoteip) {
1494				$data['ip'] = $_SERVER['HTTP_CLIENT_IP'].' -> '.$data['ip'];
1495			}
1496		} elseif (!empty($_SERVER['SERVER_ADDR'])) {
1497			// This is when PHP session is ran inside a web server but not inside a client request (example: init code of apache)
1498			$data['ip'] = $_SERVER['SERVER_ADDR'];
1499		} elseif (!empty($_SERVER['COMPUTERNAME'])) {
1500			// 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).
1501			$data['ip'] = $_SERVER['COMPUTERNAME'].(empty($_SERVER['USERNAME']) ? '' : '@'.$_SERVER['USERNAME']);
1502		} elseif (!empty($_SERVER['LOGNAME'])) {
1503			// 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).
1504			$data['ip'] = '???@'.$_SERVER['LOGNAME'];
1505		}
1506		// Loop on each log handler and send output
1507		foreach ($conf->loghandlers as $loghandlerinstance) {
1508			if ($restricttologhandler && $loghandlerinstance->code != $restricttologhandler) {
1509				continue;
1510			}
1511			$loghandlerinstance->export($data, $suffixinfilename);
1512		}
1513		unset($data);
1514	}
1515
1516	if ($ident > 0) {
1517		foreach ($conf->loghandlers as $loghandlerinstance) {
1518			$loghandlerinstance->setIdent($ident);
1519		}
1520	}
1521}
1522
1523/**
1524 *	Return HTML code to output a button to open a dialog popup box.
1525 *  Such buttons must be included inside a HTML form.
1526 *
1527 *	@param	string	$name				A name for the html component
1528 *	@param	string	$label 	    		Label of button
1529 *	@param  string	$buttonstring  		button string
1530 *	@param  string	$url				Url to open
1531 *  @param	string	$disabled			Disabled text
1532 * 	@return	string						HTML component with button
1533 */
1534function dolButtonToOpenUrlInDialogPopup($name, $label, $buttonstring, $url, $disabled = '')
1535{
1536	if (strpos($url, '?') > 0) {
1537		$url .= '&dol_hide_topmenu=1&dol_hide_leftmenu=1&dol_openinpopup=1';
1538	} else {
1539		$url .= '?dol_hide_menuinpopup=1&dol_hide_leftmenu=1&dol_openinpopup=1';
1540	}
1541
1542	//print '<input type="submit" class="button bordertransp"'.$disabled.' value="'.dol_escape_htmltag($langs->trans("MediaFiles")).'" name="file_manager">';
1543	$out = '<a class="button bordertransp button_'.$name.'"'.$disabled.' title="'.dol_escape_htmltag($label).'">'.$buttonstring.'</a>';
1544	$out .= '<!-- Add js code to open dialog popup on dialog -->';
1545	$out .= '<script language="javascript">
1546				jQuery(document).ready(function () {
1547					jQuery(".button_'.$name.'").click(function () {
1548						console.log("Open popup with jQuery(...).dialog() on URL '.dol_escape_js(DOL_URL_ROOT.$url).'")
1549						var $dialog = $(\'<div></div>\').html(\'<iframe class="iframedialog" style="border: 0px;" src="'.DOL_URL_ROOT.$url.'" width="100%" height="98%"></iframe>\')
1550							.dialog({
1551								autoOpen: false,
1552							 	modal: true,
1553							 	height: (window.innerHeight - 150),
1554							 	width: \'80%\',
1555							 	title: "'.dol_escape_js($label).'"
1556							});
1557						$dialog.dialog(\'open\');
1558					});
1559				});
1560			</script>';
1561	return $out;
1562}
1563
1564/**
1565 *	Show tab header of a card
1566 *
1567 *	@param	array	$links				Array of tabs. Currently initialized by calling a function xxx_admin_prepare_head
1568 *	@param	string	$active     		Active tab name (document', 'info', 'ldap', ....)
1569 *	@param  string	$title      		Title
1570 *	@param  int		$notab				-1 or 0=Add tab header, 1=no tab header (if you set this to 1, using print dol_get_fiche_end() to close tab is not required), -2=Add tab header with no seaparation under tab (to start a tab just after)
1571 * 	@param	string	$picto				Add a picto on tab title
1572 *	@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.
1573 *  @param	string	$morehtmlright		Add more html content on right of tabs title
1574 *  @param	string	$morecss			More Css
1575 *  @param	int		$limittoshow		Limit number of tabs to show. Use 0 to use automatic default value.
1576 *  @param	string	$moretabssuffix		A suffix to use when you have several dol_get_fiche_head() in same page
1577 * 	@return	void
1578 *  @deprecated Use print dol_get_fiche_head() instead
1579 */
1580function dol_fiche_head($links = array(), $active = '0', $title = '', $notab = 0, $picto = '', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limittoshow = 0, $moretabssuffix = '')
1581{
1582	print dol_get_fiche_head($links, $active, $title, $notab, $picto, $pictoisfullpath, $morehtmlright, $morecss, $limittoshow, $moretabssuffix);
1583}
1584
1585/**
1586 *  Show tabs of a record
1587 *
1588 *	@param	array	$links				Array of tabs
1589 *	@param	string	$active     		Active tab name
1590 *	@param  string	$title      		Title
1591 *	@param  int		$notab				-1 or 0=Add tab header, 1=no tab header (if you set this to 1, using print dol_get_fiche_end() to close tab is not required), -2=Add tab header with no seaparation under tab (to start a tab just after)
1592 * 	@param	string	$picto				Add a picto on tab title
1593 *	@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.
1594 *  @param	string	$morehtmlright		Add more html content on right of tabs title
1595 *  @param	string	$morecss			More Css
1596 *  @param	int		$limittoshow		Limit number of tabs to show. Use 0 to use automatic default value.
1597 *  @param	string	$moretabssuffix		A suffix to use when you have several dol_get_fiche_head() in same page
1598 * 	@return	string
1599 */
1600function dol_get_fiche_head($links = array(), $active = '', $title = '', $notab = 0, $picto = '', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limittoshow = 0, $moretabssuffix = '')
1601{
1602	global $conf, $langs, $hookmanager;
1603
1604	// Show title
1605	$showtitle = 1;
1606	if (!empty($conf->dol_optimize_smallscreen)) {
1607		$showtitle = 0;
1608	}
1609
1610	$out = "\n".'<!-- dol_fiche_head - dol_get_fiche_head -->';
1611
1612	if ((!empty($title) && $showtitle) || $morehtmlright || !empty($links)) {
1613		$out .= '<div class="tabs'.($picto ? '' : ' nopaddingleft').'" data-role="controlgroup" data-type="horizontal">'."\n";
1614	}
1615
1616	// Show right part
1617	if ($morehtmlright) {
1618		$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.
1619	}
1620
1621	// Show title
1622	if (!empty($title) && $showtitle && empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
1623		$limittitle = 30;
1624		$out .= '<a class="tabTitle">';
1625		if ($picto) {
1626			$out .= img_picto($title, ($pictoisfullpath ? '' : 'object_').$picto, '', $pictoisfullpath, 0, 0, '', 'imgTabTitle').' ';
1627		}
1628		$out .= '<span class="tabTitleText">'.dol_escape_htmltag(dol_trunc($title, $limittitle)).'</span>';
1629		$out .= '</a>';
1630	}
1631
1632	// Show tabs
1633
1634	// Define max of key (max may be higher than sizeof because of hole due to module disabling some tabs).
1635	$maxkey = -1;
1636	if (is_array($links) && !empty($links)) {
1637		$keys = array_keys($links);
1638		if (count($keys)) {
1639			$maxkey = max($keys);
1640		}
1641	}
1642
1643	// Show tabs
1644	// if =0 we don't use the feature
1645	if (empty($limittoshow)) {
1646		$limittoshow = (empty($conf->global->MAIN_MAXTABS_IN_CARD) ? 99 : $conf->global->MAIN_MAXTABS_IN_CARD);
1647	}
1648	if (!empty($conf->dol_optimize_smallscreen)) {
1649		$limittoshow = 2;
1650	}
1651
1652	$displaytab = 0;
1653	$nbintab = 0;
1654	$popuptab = 0;
1655	$outmore = '';
1656	for ($i = 0; $i <= $maxkey; $i++) {
1657		if ((is_numeric($active) && $i == $active) || (!empty($links[$i][2]) && !is_numeric($active) && $active == $links[$i][2])) {
1658			// If active tab is already present
1659			if ($i >= $limittoshow) {
1660				$limittoshow--;
1661			}
1662		}
1663	}
1664
1665	for ($i = 0; $i <= $maxkey; $i++) {
1666		if ((is_numeric($active) && $i == $active) || (!empty($links[$i][2]) && !is_numeric($active) && $active == $links[$i][2])) {
1667			$isactive = true;
1668		} else {
1669			$isactive = false;
1670		}
1671
1672		if ($i < $limittoshow || $isactive) {
1673			// Add a new entry
1674			$out .= '<div class="inline-block tabsElem'.($isactive ? ' tabsElemActive' : '').((!$isactive && !empty($conf->global->MAIN_HIDE_INACTIVETAB_ON_PRINT)) ? ' hideonprint' : '').'"><!-- id tab = '.(empty($links[$i][2]) ? '' : $links[$i][2]).' -->';
1675
1676			if (isset($links[$i][2]) && $links[$i][2] == 'image') {
1677				if (!empty($links[$i][0])) {
1678					$out .= '<a class="tabimage'.($morecss ? ' '.$morecss : '').'" href="'.$links[$i][0].'">'.$links[$i][1].'</a>'."\n";
1679				} else {
1680					$out .= '<span class="tabspan">'.$links[$i][1].'</span>'."\n";
1681				}
1682			} elseif (!empty($links[$i][1])) {
1683				//print "x $i $active ".$links[$i][2]." z";
1684				$out .= '<div class="tab tab'.($isactive?'active':'unactive').'" style="margin: 0 !important">';
1685				if (!empty($links[$i][0])) {
1686					$out .= '<a'.(!empty($links[$i][2]) ? ' id="'.$links[$i][2].'"' : '').' class="tab inline-block'.($morecss ? ' '.$morecss : '').'" href="'.$links[$i][0].'">';
1687				}
1688				$out .= $links[$i][1];
1689				if (!empty($links[$i][0])) {
1690					$out .= '</a>'."\n";
1691				}
1692				$out .= empty($links[$i][4]) ? '' : $links[$i][4];
1693				$out .= '</div>';
1694			}
1695
1696			$out .= '</div>';
1697		} else {
1698			// The popup with the other tabs
1699			if (!$popuptab) {
1700				$popuptab = 1;
1701				$outmore .= '<div class="popuptabset wordwrap">'; // The css used to hide/show popup
1702			}
1703			$outmore .= '<div class="popuptab wordwrap" style="display:inherit;">';
1704			if (isset($links[$i][2]) && $links[$i][2] == 'image') {
1705				if (!empty($links[$i][0])) {
1706					$outmore .= '<a class="tabimage'.($morecss ? ' '.$morecss : '').'" href="'.$links[$i][0].'">'.$links[$i][1].'</a>'."\n";
1707				} else {
1708					$outmore .= '<span class="tabspan">'.$links[$i][1].'</span>'."\n";
1709				}
1710			} elseif (!empty($links[$i][1])) {
1711				$outmore .= '<a'.(!empty($links[$i][2]) ? ' id="'.$links[$i][2].'"' : '').' class="wordwrap inline-block'.($morecss ? ' '.$morecss : '').'" href="'.$links[$i][0].'">';
1712				$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.
1713				$outmore .= '</a>'."\n";
1714			}
1715			$outmore .= '</div>';
1716
1717			$nbintab++;
1718		}
1719		$displaytab = $i;
1720	}
1721	if ($popuptab) {
1722		$outmore .= '</div>';
1723	}
1724
1725	if ($popuptab) {	// If there is some tabs not shown
1726		$left = ($langs->trans("DIRECTION") == 'rtl' ? 'right' : 'left');
1727		$right = ($langs->trans("DIRECTION") == 'rtl' ? 'left' : 'right');
1728		$widthofpopup = 200;
1729
1730		$tabsname = $moretabssuffix;
1731		if (empty($tabsname)) {
1732			$tabsname = str_replace("@", "", $picto);
1733		}
1734		$out .= '<div id="moretabs'.$tabsname.'" class="inline-block tabsElem">';
1735		$out .= '<a href="#" class="tab moretab inline-block tabunactive"><span class="hideonsmartphone">'.$langs->trans("More").'</span>... ('.$nbintab.')</a>'; // Do not use "reposition" class in the "More".
1736		$out .= '<div id="moretabsList'.$tabsname.'" style="width: '.$widthofpopup.'px; position: absolute; '.$left.': -999em; text-align: '.$left.'; margin:0px; padding:2px; z-index:10;">';
1737		$out .= $outmore;
1738		$out .= '</div>';
1739		$out .= '<div></div>';
1740		$out .= "</div>\n";
1741
1742		$out .= "<script>";
1743		$out .= "$('#moretabs".$tabsname."').mouseenter( function() {
1744			var x = this.offsetLeft, y = this.offsetTop;
1745			console.log('mouseenter ".$left." x='+x+' y='+y+' window.innerWidth='+window.innerWidth);
1746			if ((window.innerWidth - x) < ".($widthofpopup + 10).") {
1747				$('#moretabsList".$tabsname."').css('".$right."','8px');
1748			}
1749			$('#moretabsList".$tabsname."').css('".$left."','auto');
1750			});
1751		";
1752		$out .= "$('#moretabs".$tabsname."').mouseleave( function() { console.log('mouseleave ".$left."'); $('#moretabsList".$tabsname."').css('".$left."','-999em');});";
1753		$out .= "</script>";
1754	}
1755
1756	if ((!empty($title) && $showtitle) || $morehtmlright || !empty($links)) {
1757		$out .= "</div>\n";
1758	}
1759
1760	if (!$notab || $notab == -1 || $notab == -2) {
1761		$out .= "\n".'<div class="tabBar'.($notab == -1 ? '' : ($notab == -2 ? ' tabBarNoTop' : ' tabBarWithBottom')).'">'."\n";
1762	}
1763
1764	$parameters = array('tabname' => $active, 'out' => $out);
1765	$reshook = $hookmanager->executeHooks('printTabsHead', $parameters); // This hook usage is called just before output the head of tabs. Take also a look at "completeTabsHead"
1766	if ($reshook > 0) {
1767		$out = $hookmanager->resPrint;
1768	}
1769
1770	return $out;
1771}
1772
1773/**
1774 *  Show tab footer of a card
1775 *
1776 *  @param	int		$notab       -1 or 0=Add tab footer, 1=no tab footer
1777 *  @return	void
1778 *  @deprecated Use print dol_get_fiche_end() instead
1779 */
1780function dol_fiche_end($notab = 0)
1781{
1782	print dol_get_fiche_end($notab);
1783}
1784
1785/**
1786 *	Return tab footer of a card
1787 *
1788 *	@param  int		$notab		-1 or 0=Add tab footer, 1=no tab footer
1789 *  @return	string
1790 */
1791function dol_get_fiche_end($notab = 0)
1792{
1793	if (!$notab || $notab == -1) {
1794		return "\n</div>\n";
1795	} else {
1796		return '';
1797	}
1798}
1799
1800/**
1801 *  Show tab footer of a card.
1802 *  Note: $object->next_prev_filter can be set to restrict select to find next or previous record by $form->showrefnav.
1803 *
1804 *  @param	Object	$object			Object to show
1805 *  @param	string	$paramid   		Name of parameter to use to name the id into the URL next/previous link
1806 *  @param	string	$morehtml  		More html content to output just before the nav bar
1807 *  @param	int		$shownav	  	Show Condition (navigation is shown if value is 1)
1808 *  @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.
1809 *  @param	string	$fieldref   	Nom du champ objet ref (object->ref) a utiliser pour select next et previous
1810 *  @param	string	$morehtmlref  	More html to show after the ref (see $morehtmlleft for before)
1811 *  @param	string	$moreparam  	More param to add in nav link url.
1812 *	@param	int		$nodbprefix		Do not include DB prefix to forge table name
1813 *	@param	string	$morehtmlleft	More html code to show before the ref (see $morehtmlref for after)
1814 *	@param	string	$morehtmlstatus	More html code to show under navigation arrows
1815 *  @param  int     $onlybanner     Put this to 1, if the card will contains only a banner (this add css 'arearefnobottom' on div)
1816 *	@param	string	$morehtmlright	More html code to show before navigation arrows
1817 *  @return	void
1818 */
1819function dol_banner_tab($object, $paramid, $morehtml = '', $shownav = 1, $fieldid = 'rowid', $fieldref = 'ref', $morehtmlref = '', $moreparam = '', $nodbprefix = 0, $morehtmlleft = '', $morehtmlstatus = '', $onlybanner = 0, $morehtmlright = '')
1820{
1821	global $conf, $form, $user, $langs;
1822
1823	$error = 0;
1824
1825	$maxvisiblephotos = 1;
1826	$showimage = 1;
1827	$entity = (empty($object->entity) ? $conf->entity : $object->entity);
1828	$showbarcode = empty($conf->barcode->enabled) ? 0 : (empty($object->barcode) ? 0 : 1);
1829	if (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->barcode->lire_advance)) {
1830		$showbarcode = 0;
1831	}
1832	$modulepart = 'unknown';
1833
1834	if ($object->element == 'societe' || $object->element == 'contact' || $object->element == 'product' || $object->element == 'ticket') {
1835		$modulepart = $object->element;
1836	} elseif ($object->element == 'member') {
1837		$modulepart = 'memberphoto';
1838	} elseif ($object->element == 'user') {
1839		$modulepart = 'userphoto';
1840	}
1841
1842	if (class_exists("Imagick")) {
1843		if ($object->element == 'expensereport' || $object->element == 'propal' || $object->element == 'commande' || $object->element == 'facture' || $object->element == 'supplier_proposal') {
1844			$modulepart = $object->element;
1845		} elseif ($object->element == 'fichinter') {
1846			$modulepart = 'ficheinter';
1847		} elseif ($object->element == 'contrat') {
1848			$modulepart = 'contract';
1849		} elseif ($object->element == 'order_supplier') {
1850			$modulepart = 'supplier_order';
1851		} elseif ($object->element == 'invoice_supplier') {
1852			$modulepart = 'supplier_invoice';
1853		}
1854	}
1855
1856	if ($object->element == 'product') {
1857		$width = 80;
1858		$cssclass = 'photoref';
1859		$showimage = $object->is_photo_available($conf->product->multidir_output[$entity]);
1860		$maxvisiblephotos = (isset($conf->global->PRODUCT_MAX_VISIBLE_PHOTO) ? $conf->global->PRODUCT_MAX_VISIBLE_PHOTO : 5);
1861		if ($conf->browser->layout == 'phone') {
1862			$maxvisiblephotos = 1;
1863		}
1864		if ($showimage) {
1865			$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">'.$object->show_photos('product', $conf->product->multidir_output[$entity], 'small', $maxvisiblephotos, 0, 0, 0, $width, 0).'</div>';
1866		} else {
1867			if (!empty($conf->global->PRODUCT_NODISPLAYIFNOPHOTO)) {
1868				$nophoto = '';
1869				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
1870			} else {    // Show no photo link
1871				$nophoto = '/public/theme/common/nophoto.png';
1872				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><img class="photo'.$modulepart.($cssclass ? ' '.$cssclass : '').'" alt="No photo"'.($width ? ' style="width: '.$width.'px"' : '').' src="'.DOL_URL_ROOT.$nophoto.'"></div>';
1873			}
1874		}
1875	} elseif ($object->element == 'ticket') {
1876		$width = 80;
1877		$cssclass = 'photoref';
1878		$showimage = $object->is_photo_available($conf->ticket->multidir_output[$entity].'/'.$object->ref);
1879		$maxvisiblephotos = (isset($conf->global->TICKET_MAX_VISIBLE_PHOTO) ? $conf->global->TICKET_MAX_VISIBLE_PHOTO : 2);
1880		if ($conf->browser->layout == 'phone') {
1881			$maxvisiblephotos = 1;
1882		}
1883
1884		if ($showimage) {
1885			$showphoto = $object->show_photos('ticket', $conf->ticket->multidir_output[$entity], 'small', $maxvisiblephotos, 0, 0, 0, $width, 0);
1886			if ($object->nbphoto > 0) {
1887				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">'.$showphoto.'</div>';
1888			} else {
1889				$showimage = 0;
1890			}
1891		}
1892		if (!$showimage) {
1893			if (!empty($conf->global->TICKET_NODISPLAYIFNOPHOTO)) {
1894				$nophoto = '';
1895				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
1896			} else {    // Show no photo link
1897				$nophoto = img_picto('No photo', 'object_ticket');
1898				$morehtmlleft .= '<!-- No photo to show -->';
1899				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
1900				$morehtmlleft .= $nophoto;
1901				$morehtmlleft .= '</div></div>';
1902			}
1903		}
1904	} else {
1905		if ($showimage) {
1906			if ($modulepart != 'unknown') {
1907				$phototoshow = '';
1908				// Check if a preview file is available
1909				if (in_array($modulepart, array('propal', 'commande', 'facture', 'ficheinter', 'contract', 'supplier_order', 'supplier_proposal', 'supplier_invoice', 'expensereport')) && class_exists("Imagick")) {
1910					$objectref = dol_sanitizeFileName($object->ref);
1911					$dir_output = (empty($conf->$modulepart->multidir_output[$entity]) ? $conf->$modulepart->dir_output : $conf->$modulepart->multidir_output[$entity])."/";
1912					if (in_array($modulepart, array('invoice_supplier', 'supplier_invoice'))) {
1913						$subdir = get_exdir($object->id, 2, 0, 1, $object, $modulepart);
1914						$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
1915					} else {
1916						$subdir = get_exdir($object->id, 0, 0, 1, $object, $modulepart);
1917					}
1918					if (empty($subdir)) {
1919						$subdir = 'errorgettingsubdirofobject'; // Protection to avoid to return empty path
1920					}
1921
1922					$filepath = $dir_output.$subdir."/";
1923
1924					$filepdf = $filepath.$objectref.".pdf";
1925					$relativepath = $subdir.'/'.$objectref.'.pdf';
1926
1927					// Define path to preview pdf file (preview precompiled "file.ext" are "file.ext_preview.png")
1928					$fileimage = $filepdf.'_preview.png';
1929					$relativepathimage = $relativepath.'_preview.png';
1930
1931					$pdfexists = file_exists($filepdf);
1932
1933					// If PDF file exists
1934					if ($pdfexists) {
1935						// Conversion du PDF en image png si fichier png non existant
1936						if (!file_exists($fileimage) || (filemtime($fileimage) < filemtime($filepdf))) {
1937							if (empty($conf->global->MAIN_DISABLE_PDF_THUMBS)) {		// If you experience trouble with pdf thumb generation and imagick, you can disable here.
1938								include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1939								$ret = dol_convert_file($filepdf, 'png', $fileimage, '0'); // Convert first page of PDF into a file _preview.png
1940								if ($ret < 0) {
1941									$error++;
1942								}
1943							}
1944						}
1945					}
1946
1947					if ($pdfexists && !$error) {
1948						$heightforphotref = 80;
1949						if (!empty($conf->dol_optimize_smallscreen)) {
1950							$heightforphotref = 60;
1951						}
1952						// If the preview file is found
1953						if (file_exists($fileimage)) {
1954							$phototoshow = '<div class="photoref">';
1955							$phototoshow .= '<img height="'.$heightforphotref.'" class="photo photowithmargin photowithborder" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart=apercu'.$modulepart.'&amp;file='.urlencode($relativepathimage).'">';
1956							$phototoshow .= '</div>';
1957						}
1958					}
1959				} elseif (!$phototoshow) { // example if modulepart = 'societe' or 'photo'
1960					$phototoshow .= $form->showphoto($modulepart, $object, 0, 0, 0, 'photoref', 'small', 1, 0, $maxvisiblephotos);
1961				}
1962
1963				if ($phototoshow) {
1964					$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">';
1965					$morehtmlleft .= $phototoshow;
1966					$morehtmlleft .= '</div>';
1967				}
1968			}
1969
1970			if (empty($phototoshow)) {      // Show No photo link (picto of object)
1971				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">';
1972				if ($object->element == 'action') {
1973					$width = 80;
1974					$cssclass = 'photorefcenter';
1975					$nophoto = img_picto('No photo', 'title_agenda');
1976				} else {
1977					$width = 14;
1978					$cssclass = 'photorefcenter';
1979					$picto = $object->picto;
1980					if ($object->element == 'project' && !$object->public) {
1981						$picto = 'project'; // instead of projectpub
1982					}
1983					$nophoto = img_picto('No photo', 'object_'.$picto);
1984				}
1985				$morehtmlleft .= '<!-- No photo to show -->';
1986				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
1987				$morehtmlleft .= $nophoto;
1988				$morehtmlleft .= '</div></div>';
1989
1990				$morehtmlleft .= '</div>';
1991			}
1992		}
1993	}
1994
1995	if ($showbarcode) {
1996		$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">'.$form->showbarcode($object, 100, 'photoref').'</div>';
1997	}
1998
1999	if ($object->element == 'societe') {
2000		if (!empty($conf->use_javascript_ajax) && $user->rights->societe->creer && !empty($conf->global->MAIN_DIRECT_STATUS_UPDATE)) {
2001			$morehtmlstatus .= ajax_object_onoff($object, 'status', 'status', 'InActivity', 'ActivityCeased');
2002		} else {
2003			$morehtmlstatus .= $object->getLibStatut(6);
2004		}
2005	} elseif ($object->element == 'product') {
2006		//$morehtmlstatus.=$langs->trans("Status").' ('.$langs->trans("Sell").') ';
2007		if (!empty($conf->use_javascript_ajax) && $user->rights->produit->creer && !empty($conf->global->MAIN_DIRECT_STATUS_UPDATE)) {
2008			$morehtmlstatus .= ajax_object_onoff($object, 'status', 'tosell', 'ProductStatusOnSell', 'ProductStatusNotOnSell');
2009		} else {
2010			$morehtmlstatus .= '<span class="statusrefsell">'.$object->getLibStatut(6, 0).'</span>';
2011		}
2012		$morehtmlstatus .= ' &nbsp; ';
2013		//$morehtmlstatus.=$langs->trans("Status").' ('.$langs->trans("Buy").') ';
2014		if (!empty($conf->use_javascript_ajax) && $user->rights->produit->creer && !empty($conf->global->MAIN_DIRECT_STATUS_UPDATE)) {
2015			$morehtmlstatus .= ajax_object_onoff($object, 'status_buy', 'tobuy', 'ProductStatusOnBuy', 'ProductStatusNotOnBuy');
2016		} else {
2017			$morehtmlstatus .= '<span class="statusrefbuy">'.$object->getLibStatut(6, 1).'</span>';
2018		}
2019	} elseif (in_array($object->element, array('facture', 'invoice', 'invoice_supplier', 'chargesociales', 'loan', 'tva', 'salary'))) {
2020		$tmptxt = $object->getLibStatut(6, $object->totalpaye);
2021		if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
2022			$tmptxt = $object->getLibStatut(5, $object->totalpaye);
2023		}
2024		$morehtmlstatus .= $tmptxt;
2025	} elseif ($object->element == 'contrat' || $object->element == 'contract') {
2026		if ($object->statut == 0) {
2027			$morehtmlstatus .= $object->getLibStatut(5);
2028		} else {
2029			$morehtmlstatus .= $object->getLibStatut(4);
2030		}
2031	} elseif ($object->element == 'facturerec') {
2032		if ($object->frequency == 0) {
2033			$morehtmlstatus .= $object->getLibStatut(2);
2034		} else {
2035			$morehtmlstatus .= $object->getLibStatut(5);
2036		}
2037	} elseif ($object->element == 'project_task') {
2038		$object->fk_statut = 1;
2039		if ($object->progress > 0) {
2040			$object->fk_statut = 2;
2041		}
2042		if ($object->progress >= 100) {
2043			$object->fk_statut = 3;
2044		}
2045		$tmptxt = $object->getLibStatut(5);
2046		$morehtmlstatus .= $tmptxt; // No status on task
2047	} else { // Generic case
2048		$tmptxt = $object->getLibStatut(6);
2049		if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
2050			$tmptxt = $object->getLibStatut(5);
2051		}
2052		$morehtmlstatus .= $tmptxt;
2053	}
2054
2055	// Add if object was dispatched "into accountancy"
2056	if (!empty($conf->accounting->enabled) && in_array($object->element, array('bank', 'paiementcharge', 'facture', 'invoice', 'invoice_supplier', 'expensereport', 'payment_various'))) {
2057		// Note: For 'chargesociales', 'salaries'... this is the payments that are dispatched (so element = 'bank')
2058		if (method_exists($object, 'getVentilExportCompta')) {
2059			$accounted = $object->getVentilExportCompta();
2060			$langs->load("accountancy");
2061			$morehtmlstatus .= '</div><div class="statusref statusrefbis"><span class="opacitymedium">'.($accounted > 0 ? $langs->trans("Accounted") : $langs->trans("NotYetAccounted")).'</span>';
2062		}
2063	}
2064
2065	// Add alias for thirdparty
2066	if (!empty($object->name_alias)) {
2067		$morehtmlref .= '<div class="refidno">'.$object->name_alias.'</div>';
2068	}
2069
2070	// Add label
2071	if (in_array($object->element, array('product', 'bank_account', 'project_task'))) {
2072		if (!empty($object->label)) {
2073			$morehtmlref .= '<div class="refidno">'.$object->label.'</div>';
2074		}
2075	}
2076
2077	if (method_exists($object, 'getBannerAddress') && !in_array($object->element, array('product', 'bookmark', 'ecm_directories', 'ecm_files'))) {
2078		$moreaddress = $object->getBannerAddress('refaddress', $object);
2079		if ($moreaddress) {
2080			$morehtmlref .= '<div class="refidno">';
2081			$morehtmlref .= $moreaddress;
2082			$morehtmlref .= '</div>';
2083		}
2084	}
2085	if (!empty($conf->global->MAIN_SHOW_TECHNICAL_ID) && ($conf->global->MAIN_SHOW_TECHNICAL_ID == '1' || preg_match('/'.preg_quote($object->element, '/').'/i', $conf->global->MAIN_SHOW_TECHNICAL_ID)) && !empty($object->id)) {
2086		$morehtmlref .= '<div style="clear: both;"></div>';
2087		$morehtmlref .= '<div class="refidno">';
2088		$morehtmlref .= $langs->trans("TechnicalID").': '.$object->id;
2089		$morehtmlref .= '</div>';
2090	}
2091
2092	print '<div class="'.($onlybanner ? 'arearefnobottom ' : 'arearef ').'heightref valignmiddle centpercent">';
2093	print $form->showrefnav($object, $paramid, $morehtml, $shownav, $fieldid, $fieldref, $morehtmlref, $moreparam, $nodbprefix, $morehtmlleft, $morehtmlstatus, $morehtmlright);
2094	print '</div>';
2095	print '<div class="underrefbanner clearboth"></div>';
2096}
2097
2098/**
2099 * Show a string with the label tag dedicated to the HTML edit field.
2100 *
2101 * @param	string	$langkey		Translation key
2102 * @param 	string	$fieldkey		Key of the html select field the text refers to
2103 * @param	int		$fieldrequired	1=Field is mandatory
2104 * @return string
2105 * @deprecated Form::editfieldkey
2106 */
2107function fieldLabel($langkey, $fieldkey, $fieldrequired = 0)
2108{
2109	global $langs;
2110	$ret = '';
2111	if ($fieldrequired) {
2112		$ret .= '<span class="fieldrequired">';
2113	}
2114	$ret .= '<label for="'.$fieldkey.'">';
2115	$ret .= $langs->trans($langkey);
2116	$ret .= '</label>';
2117	if ($fieldrequired) {
2118		$ret .= '</span>';
2119	}
2120	return $ret;
2121}
2122
2123/**
2124 * Return string to add class property on html element with pair/impair.
2125 *
2126 * @param	string	$var			0 or 1
2127 * @param	string	$moreclass		More class to add
2128 * @return	string					String to add class onto HTML element
2129 */
2130function dol_bc($var, $moreclass = '')
2131{
2132	global $bc;
2133	$ret = ' '.$bc[$var];
2134	if ($moreclass) {
2135		$ret = preg_replace('/class=\"/', 'class="'.$moreclass.' ', $ret);
2136	}
2137	return $ret;
2138}
2139
2140/**
2141 *      Return a formated address (part address/zip/town/state) according to country rules.
2142 *      See https://en.wikipedia.org/wiki/Address
2143 *
2144 *      @param  Object		$object			A company or contact object
2145 * 	    @param	int			$withcountry	1=Add country into address string
2146 *      @param	string		$sep			Separator to use to build string
2147 *      @param	Translate	$outputlangs	Object lang that contains language for text translation.
2148 *      @param	int			$mode			0=Standard output, 1=Remove address
2149 *  	@param	string		$extralangcode	User extralanguage $langcode as values for address, town
2150 *      @return string						Formated string
2151 *      @see dol_print_address()
2152 */
2153function dol_format_address($object, $withcountry = 0, $sep = "\n", $outputlangs = '', $mode = 0, $extralangcode = '')
2154{
2155	global $conf, $langs;
2156
2157	$ret = '';
2158	$countriesusingstate = array('AU', 'CA', 'US', 'IN', 'GB', 'ES', 'UK', 'TR'); // See also MAIN_FORCE_STATE_INTO_ADDRESS
2159
2160	// See format of addresses on https://en.wikipedia.org/wiki/Address
2161	// Address
2162	if (empty($mode)) {
2163		$ret .= ($extralangcode ? $object->array_languages['address'][$extralangcode] : (empty($object->address) ? '' : $object->address));
2164	}
2165	// Zip/Town/State
2166	if (isset($object->country_code) && in_array($object->country_code, array('AU', 'CA', 'US')) || !empty($conf->global->MAIN_FORCE_STATE_INTO_ADDRESS)) {
2167		// US: title firstname name \n address lines \n town, state, zip \n country
2168		$town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2169		$ret .= ($ret ? $sep : '').$town;
2170		if (!empty($object->state))	{
2171			$ret .= ($ret ? ", " : '').$object->state;
2172		}
2173		if (!empty($object->zip)) {
2174			$ret .= ($ret ? ", " : '').$object->zip;
2175		}
2176	} elseif (isset($object->country_code) && in_array($object->country_code, array('GB', 'UK'))) {
2177		// UK: title firstname name \n address lines \n town state \n zip \n country
2178		$town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2179		$ret .= ($ret ? $sep : '').$town;
2180		if (!empty($object->state)) {
2181			$ret .= ($ret ? ", " : '').$object->state;
2182		}
2183		if (!empty($object->zip)) {
2184			$ret .= ($ret ? $sep : '').$object->zip;
2185		}
2186	} elseif (isset($object->country_code) && in_array($object->country_code, array('ES', 'TR'))) {
2187		// ES: title firstname name \n address lines \n zip town \n state \n country
2188		$ret .= ($ret ? $sep : '').$object->zip;
2189		$town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2190		$ret .= ($town ? (($object->zip ? ' ' : '').$town) : '');
2191		if (!empty($object->state)) {
2192			$ret .= "\n".$object->state;
2193		}
2194	} elseif (isset($object->country_code) && in_array($object->country_code, array('JP'))) {
2195		// JP: In romaji, title firstname name\n address lines \n [state,] town zip \n country
2196		// See https://www.sljfaq.org/afaq/addresses.html
2197		$town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2198		$ret .= ($ret ? $sep : '').($object->state ? $object->state.', ' : '').$town.($object->zip ? ' ' : '').$object->zip;
2199	} elseif (isset($object->country_code) && in_array($object->country_code, array('IT'))) {
2200		// IT: title firstname name\n address lines \n zip town state_code \n country
2201		$ret .= ($ret ? $sep : '').$object->zip;
2202		$town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2203		$ret .= ($town ? (($object->zip ? ' ' : '').$town) : '');
2204		$ret .= (empty($object->state_code) ? '' : (' '.$object->state_code));
2205	} else {
2206		// Other: title firstname name \n address lines \n zip town[, state] \n country
2207		$town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2208		$ret .= !empty($object->zip) ? (($ret ? $sep : '').$object->zip) : '';
2209		$ret .= ($town ? (($object->zip ? ' ' : ($ret ? $sep : '')).$town) : '');
2210		if (!empty($object->state) && in_array($object->country_code, $countriesusingstate)) {
2211			$ret .= ($ret ? ", " : '').$object->state;
2212		}
2213	}
2214	if (!is_object($outputlangs)) {
2215		$outputlangs = $langs;
2216	}
2217	if ($withcountry) {
2218		$langs->load("dict");
2219		$ret .= (empty($object->country_code) ? '' : ($ret ? $sep : '').$outputlangs->convToOutputCharset($outputlangs->transnoentitiesnoconv("Country".$object->country_code)));
2220	}
2221
2222	return $ret;
2223}
2224
2225
2226
2227/**
2228 *	Format a string.
2229 *
2230 *	@param	string	$fmt		Format of strftime function (http://php.net/manual/fr/function.strftime.php)
2231 *  @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)
2232 *  @param	int		$is_gmt		See comment of timestamp parameter
2233 *	@return	string				A formatted string
2234 */
2235function dol_strftime($fmt, $ts = false, $is_gmt = false)
2236{
2237	if ((abs($ts) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range
2238		return ($is_gmt) ? @gmstrftime($fmt, $ts) : @strftime($fmt, $ts);
2239	} else {
2240		return 'Error date into a not supported range';
2241	}
2242}
2243
2244/**
2245 *	Output date in a string format according to outputlangs (or langs if not defined).
2246 * 	Return charset is always UTF-8, except if encodetoouput is defined. In this case charset is output charset
2247 *
2248 *	@param	int			$time			GM Timestamps date
2249 *	@param	string		$format      	Output date format (tag of strftime function)
2250 *										"%d %b %Y",
2251 *										"%d/%m/%Y %H:%M",
2252 *										"%d/%m/%Y %H:%M:%S",
2253 *                                      "%B"=Long text of month, "%A"=Long text of day, "%b"=Short text of month, "%a"=Short text of day
2254 *										"day", "daytext", "dayhour", "dayhourldap", "dayhourtext", "dayrfc", "dayhourrfc", "...inputnoreduce", "...reduceformat"
2255 * 	@param	string		$tzoutput		true or 'gmt' => string is for Greenwich location
2256 * 										false or 'tzserver' => output string is for local PHP server TZ usage
2257 * 										'tzuser' => output string is for user TZ (current browser TZ with current dst) => In a future, we should have same behaviour than 'tzuserrel'
2258 *                                      'tzuserrel' => output string is for user TZ (current browser TZ with dst or not, depending on date position) (TODO not implemented yet)
2259 *	@param	Translate	$outputlangs	Object lang that contains language for text translation.
2260 *  @param  boolean		$encodetooutput false=no convert into output pagecode
2261 * 	@return string      				Formated date or '' if time is null
2262 *
2263 *  @see        dol_mktime(), dol_stringtotime(), dol_getdate()
2264 */
2265function dol_print_date($time, $format = '', $tzoutput = 'auto', $outputlangs = '', $encodetooutput = false)
2266{
2267	global $conf, $langs;
2268
2269	if ($tzoutput === 'auto') {
2270		$tzoutput = (empty($conf) ? 'tzserver' : (isset($conf->tzuserinputkey) ? $conf->tzuserinputkey : 'tzserver'));
2271	}
2272
2273	// Clean parameters
2274	$to_gmt = false;
2275	$offsettz = $offsetdst = 0;
2276	if ($tzoutput) {
2277		$to_gmt = true; // For backward compatibility
2278		if (is_string($tzoutput)) {
2279			if ($tzoutput == 'tzserver') {
2280				$to_gmt = false;
2281				$offsettzstring = @date_default_timezone_get(); // Example 'Europe/Berlin' or 'Indian/Reunion'
2282				$offsettz = 0;	// Timezone offset with server timezone, so 0
2283				$offsetdst = 0;	// Dst offset with server timezone, so 0
2284			} elseif ($tzoutput == 'tzuser' || $tzoutput == 'tzuserrel') {
2285				$to_gmt = true;
2286				$offsettzstring = (empty($_SESSION['dol_tz_string']) ? 'UTC' : $_SESSION['dol_tz_string']); // Example 'Europe/Berlin' or 'Indian/Reunion'
2287
2288				if (class_exists('DateTimeZone')) {
2289					$user_date_tz = new DateTimeZone($offsettzstring);
2290					$user_dt = new DateTime();
2291					$user_dt->setTimezone($user_date_tz);
2292					$user_dt->setTimestamp($tzoutput == 'tzuser' ? dol_now() : $time);
2293					$offsettz = $user_dt->getOffset();
2294				} else {	// old method (The 'tzuser' was processed like the 'tzuserrel')
2295					$offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60; // Will not be used anymore
2296					$offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60; // Will not be used anymore
2297				}
2298			}
2299		}
2300	}
2301	if (!is_object($outputlangs)) {
2302		$outputlangs = $langs;
2303	}
2304	if (!$format) {
2305		$format = 'daytextshort';
2306	}
2307
2308	// Do we have to reduce the length of date (year on 2 chars) to save space.
2309	// Note: dayinputnoreduce is same than day but no reduction of year length will be done
2310	$reduceformat = (!empty($conf->dol_optimize_smallscreen) && in_array($format, array('day', 'dayhour'))) ? 1 : 0;	// Test on original $format param.
2311	$format = preg_replace('/inputnoreduce/', '', $format);	// so format 'dayinputnoreduce' is processed like day
2312	$formatwithoutreduce = preg_replace('/reduceformat/', '', $format);
2313	if ($formatwithoutreduce != $format) {
2314		$format = $formatwithoutreduce;
2315		$reduceformat = 1;
2316	}  // so format 'dayreduceformat' is processed like day
2317
2318	// Change predefined format into computer format. If found translation in lang file we use it, otherwise we use default.
2319	// TODO Add format daysmallyear and dayhoursmallyear
2320	if ($format == 'day') {
2321		$format = ($outputlangs->trans("FormatDateShort") != "FormatDateShort" ? $outputlangs->trans("FormatDateShort") : $conf->format_date_short);
2322	} elseif ($format == 'hour') {
2323		$format = ($outputlangs->trans("FormatHourShort") != "FormatHourShort" ? $outputlangs->trans("FormatHourShort") : $conf->format_hour_short);
2324	} elseif ($format == 'hourduration') {
2325		$format = ($outputlangs->trans("FormatHourShortDuration") != "FormatHourShortDuration" ? $outputlangs->trans("FormatHourShortDuration") : $conf->format_hour_short_duration);
2326	} elseif ($format == 'daytext') {
2327		$format = ($outputlangs->trans("FormatDateText") != "FormatDateText" ? $outputlangs->trans("FormatDateText") : $conf->format_date_text);
2328	} elseif ($format == 'daytextshort') {
2329		$format = ($outputlangs->trans("FormatDateTextShort") != "FormatDateTextShort" ? $outputlangs->trans("FormatDateTextShort") : $conf->format_date_text_short);
2330	} elseif ($format == 'dayhour') {
2331		$format = ($outputlangs->trans("FormatDateHourShort") != "FormatDateHourShort" ? $outputlangs->trans("FormatDateHourShort") : $conf->format_date_hour_short);
2332	} elseif ($format == 'dayhoursec') {
2333		$format = ($outputlangs->trans("FormatDateHourSecShort") != "FormatDateHourSecShort" ? $outputlangs->trans("FormatDateHourSecShort") : $conf->format_date_hour_sec_short);
2334	} elseif ($format == 'dayhourtext') {
2335		$format = ($outputlangs->trans("FormatDateHourText") != "FormatDateHourText" ? $outputlangs->trans("FormatDateHourText") : $conf->format_date_hour_text);
2336	} elseif ($format == 'dayhourtextshort') {
2337		$format = ($outputlangs->trans("FormatDateHourTextShort") != "FormatDateHourTextShort" ? $outputlangs->trans("FormatDateHourTextShort") : $conf->format_date_hour_text_short);
2338	} elseif ($format == 'dayhourlog') {
2339		// Format not sensitive to language
2340		$format = '%Y%m%d%H%M%S';
2341	} elseif ($format == 'dayhourldap') {
2342		$format = '%Y%m%d%H%M%SZ';
2343	} elseif ($format == 'dayhourxcard') {
2344		$format = '%Y%m%dT%H%M%SZ';
2345	} elseif ($format == 'dayxcard') {
2346		$format = '%Y%m%d';
2347	} elseif ($format == 'dayrfc') {
2348		$format = '%Y-%m-%d'; // DATE_RFC3339
2349	} elseif ($format == 'dayhourrfc') {
2350		$format = '%Y-%m-%dT%H:%M:%SZ'; // DATETIME RFC3339
2351	} elseif ($format == 'standard') {
2352		$format = '%Y-%m-%d %H:%M:%S';
2353	}
2354
2355	if ($reduceformat) {
2356		$format = str_replace('%Y', '%y', $format);
2357		$format = str_replace('yyyy', 'yy', $format);
2358	}
2359
2360	// If date undefined or "", we return ""
2361	if (dol_strlen($time) == 0) {
2362		return ''; // $time=0 allowed (it means 01/01/1970 00:00:00)
2363	}
2364
2365	// Clean format
2366	if (preg_match('/%b/i', $format)) {		// There is some text to translate
2367		// We inhibate translation to text made by strftime functions. We will use trans instead later.
2368		$format = str_replace('%b', '__b__', $format);
2369		$format = str_replace('%B', '__B__', $format);
2370	}
2371	if (preg_match('/%a/i', $format)) {		// There is some text to translate
2372		// We inhibate translation to text made by strftime functions. We will use trans instead later.
2373		$format = str_replace('%a', '__a__', $format);
2374		$format = str_replace('%A', '__A__', $format);
2375	}
2376
2377
2378	// Analyze date
2379	$reg = array();
2380	if (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
2381		dol_print_error("Functions.lib::dol_print_date function called with a bad value from page ".$_SERVER["PHP_SELF"]);
2382		return '';
2383	} elseif (preg_match('/^([0-9]+)\-([0-9]+)\-([0-9]+) ?([0-9]+)?:?([0-9]+)?:?([0-9]+)?/i', $time, $reg)) {    // Still available to solve problems in extrafields of type date
2384		// This part of code should not be used anymore.
2385		dol_syslog("Functions.lib::dol_print_date function called with a bad value from page ".$_SERVER["PHP_SELF"], LOG_WARNING);
2386		//if (function_exists('debug_print_backtrace')) debug_print_backtrace();
2387		// Date has format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'
2388		$syear	= (!empty($reg[1]) ? $reg[1] : '');
2389		$smonth = (!empty($reg[2]) ? $reg[2] : '');
2390		$sday	= (!empty($reg[3]) ? $reg[3] : '');
2391		$shour	= (!empty($reg[4]) ? $reg[4] : '');
2392		$smin	= (!empty($reg[5]) ? $reg[5] : '');
2393		$ssec	= (!empty($reg[6]) ? $reg[6] : '');
2394
2395		$time = dol_mktime($shour, $smin, $ssec, $smonth, $sday, $syear, true);
2396		$ret = adodb_strftime($format, $time + $offsettz + $offsetdst, $to_gmt);
2397	} else {
2398		// Date is a timestamps
2399		if ($time < 100000000000) {	// Protection against bad date values
2400			$timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
2401
2402			$ret = adodb_strftime($format, $timetouse, $to_gmt);	// If to_gmt = false then adodb_strftime use TZ of server
2403		} else {
2404			$ret = 'Bad value '.$time.' for date';
2405		}
2406	}
2407
2408	if (preg_match('/__b__/i', $format)) {
2409		$timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
2410
2411		// Here ret is string in PHP setup language (strftime was used). Now we convert to $outputlangs.
2412		$month = adodb_strftime('%m', $timetouse, $to_gmt);		// If to_gmt = false then adodb_strftime use TZ of server
2413		$month = sprintf("%02d", $month); // $month may be return with format '06' on some installation and '6' on other, so we force it to '06'.
2414		if ($encodetooutput) {
2415			$monthtext = $outputlangs->transnoentities('Month'.$month);
2416			$monthtextshort = $outputlangs->transnoentities('MonthShort'.$month);
2417		} else {
2418			$monthtext = $outputlangs->transnoentitiesnoconv('Month'.$month);
2419			$monthtextshort = $outputlangs->transnoentitiesnoconv('MonthShort'.$month);
2420		}
2421		//print 'monthtext='.$monthtext.' monthtextshort='.$monthtextshort;
2422		$ret = str_replace('__b__', $monthtextshort, $ret);
2423		$ret = str_replace('__B__', $monthtext, $ret);
2424		//print 'x'.$outputlangs->charset_output.'-'.$ret.'x';
2425		//return $ret;
2426	}
2427	if (preg_match('/__a__/i', $format)) {
2428		//print "time=$time offsettz=$offsettz offsetdst=$offsetdst offsettzstring=$offsettzstring";
2429		$timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
2430
2431		$w = adodb_strftime('%w', $timetouse, $to_gmt);		// If to_gmt = false then adodb_strftime use TZ of server
2432		$dayweek = $outputlangs->transnoentitiesnoconv('Day'.$w);
2433		$ret = str_replace('__A__', $dayweek, $ret);
2434		$ret = str_replace('__a__', dol_substr($dayweek, 0, 3), $ret);
2435	}
2436
2437	return $ret;
2438}
2439
2440
2441/**
2442 *  Return an array with locale date info.
2443 *  WARNING: This function use PHP server timezone by default to return locale informations.
2444 *  Be aware to add the third parameter to "UTC" if you need to work on UTC.
2445 *
2446 *	@param	int			$timestamp      Timestamp
2447 *	@param	boolean		$fast           Fast mode. deprecated.
2448 *  @param	string		$forcetimezone	'' to use the PHP server timezone. Or use a form like 'gmt', 'Europe/Paris' or '+0200' to force timezone.
2449 *	@return	array						Array of informations
2450 *										'seconds' => $secs,
2451 *										'minutes' => $min,
2452 *										'hours' => $hour,
2453 *										'mday' => $day,
2454 *										'wday' => $dow,		0=sunday, 6=saturday
2455 *										'mon' => $month,
2456 *										'year' => $year,
2457 *										'yday' => floor($secsInYear/$_day_power)
2458 *										'0' => original timestamp
2459 * 	@see 								dol_print_date(), dol_stringtotime(), dol_mktime()
2460 */
2461function dol_getdate($timestamp, $fast = false, $forcetimezone = '')
2462{
2463	//$datetimeobj = new DateTime('@'.$timestamp);
2464	$datetimeobj = new DateTime();
2465	$datetimeobj->setTimestamp($timestamp); // Use local PHP server timezone
2466	if ($forcetimezone) {
2467		$datetimeobj->setTimezone(new DateTimeZone($forcetimezone == 'gmt' ? 'UTC' : $forcetimezone)); //  (add timezone relative to the date entered)
2468	}
2469	$arrayinfo = array(
2470		'year'=>((int) date_format($datetimeobj, 'Y')),
2471		'mon'=>((int) date_format($datetimeobj, 'm')),
2472		'mday'=>((int) date_format($datetimeobj, 'd')),
2473		'wday'=>((int) date_format($datetimeobj, 'w')),
2474		'yday'=>((int) date_format($datetimeobj, 'z')),
2475		'hours'=>((int) date_format($datetimeobj, 'H')),
2476		'minutes'=>((int) date_format($datetimeobj, 'i')),
2477		'seconds'=>((int) date_format($datetimeobj, 's')),
2478		'0'=>$timestamp
2479	);
2480
2481	return $arrayinfo;
2482}
2483
2484/**
2485 *	Return a timestamp date built from detailed informations (by default a local PHP server timestamp)
2486 * 	Replace function mktime not available under Windows if year < 1970
2487 *	PHP mktime is restricted to the years 1901-2038 on Unix and 1970-2038 on Windows
2488 *
2489 * 	@param	int			$hour			Hour	(can be -1 for undefined)
2490 *	@param	int			$minute			Minute	(can be -1 for undefined)
2491 *	@param	int			$second			Second	(can be -1 for undefined)
2492 *	@param	int			$month			Month (1 to 12)
2493 *	@param	int			$day			Day (1 to 31)
2494 *	@param	int			$year			Year
2495 *	@param	mixed		$gm				True or 1 or 'gmt'=Input informations are GMT values
2496 *										False or 0 or 'tzserver' = local to server TZ
2497 *										'auto'
2498 *										'tzuser' = local to user TZ taking dst into account at the current date. Not yet implemented.
2499 *										'tzuserrel' = local to user TZ taking dst into account at the given date. Use this one to convert date input from user into a GMT date.
2500 *										'tz,TimeZone' = use specified timezone
2501 *	@param	int			$check			0=No check on parameters (Can use day 32, etc...)
2502 *	@return	int|string					Date as a timestamp, '' or false if error
2503 * 	@see 								dol_print_date(), dol_stringtotime(), dol_getdate()
2504 */
2505function dol_mktime($hour, $minute, $second, $month, $day, $year, $gm = 'auto', $check = 1)
2506{
2507	global $conf;
2508	//print "- ".$hour.",".$minute.",".$second.",".$month.",".$day.",".$year.",".$_SERVER["WINDIR"]." -";
2509	//print 'gm:'.$gm.' gm==auto:'.($gm == 'auto').'<br>';
2510
2511	if ($gm === 'auto') {
2512		$gm = (empty($conf) ? 'tzserver' : $conf->tzuserinputkey);
2513	}
2514
2515	// Clean parameters
2516	if ($hour == -1 || empty($hour)) {
2517		$hour = 0;
2518	}
2519	if ($minute == -1 || empty($minute)) {
2520		$minute = 0;
2521	}
2522	if ($second == -1 || empty($second)) {
2523		$second = 0;
2524	}
2525
2526	// Check parameters
2527	if ($check) {
2528		if (!$month || !$day) {
2529			return '';
2530		}
2531		if ($day > 31) {
2532			return '';
2533		}
2534		if ($month > 12) {
2535			return '';
2536		}
2537		if ($hour < 0 || $hour > 24) {
2538			return '';
2539		}
2540		if ($minute < 0 || $minute > 60) {
2541			return '';
2542		}
2543		if ($second < 0 || $second > 60) {
2544			return '';
2545		}
2546	}
2547
2548	if (empty($gm) || ($gm === 'server' || $gm === 'tzserver')) {
2549		$default_timezone = @date_default_timezone_get(); // Example 'Europe/Berlin'
2550		$localtz = new DateTimeZone($default_timezone);
2551	} elseif ($gm === 'user' || $gm === 'tzuser' || $gm === 'tzuserrel') {
2552		// We use dol_tz_string first because it is more reliable.
2553		$default_timezone = (empty($_SESSION["dol_tz_string"]) ? @date_default_timezone_get() : $_SESSION["dol_tz_string"]); // Example 'Europe/Berlin'
2554		try {
2555			$localtz = new DateTimeZone($default_timezone);
2556		} catch (Exception $e) {
2557			dol_syslog("Warning dol_tz_string contains an invalid value ".$_SESSION["dol_tz_string"], LOG_WARNING);
2558			$default_timezone = @date_default_timezone_get();
2559		}
2560	} elseif (strrpos($gm, "tz,") !== false) {
2561		$timezone = str_replace("tz,", "", $gm); // Example 'tz,Europe/Berlin'
2562		try {
2563			$localtz = new DateTimeZone($timezone);
2564		} catch (Exception $e) {
2565			dol_syslog("Warning passed timezone contains an invalid value ".$timezone, LOG_WARNING);
2566		}
2567	}
2568
2569	if (empty($localtz)) {
2570		$localtz = new DateTimeZone('UTC');
2571	}
2572	//var_dump($localtz);
2573	//var_dump($year.'-'.$month.'-'.$day.'-'.$hour.'-'.$minute);
2574	$dt = new DateTime(null, $localtz);
2575	$dt->setDate((int) $year, (int) $month, (int) $day);
2576	$dt->setTime((int) $hour, (int) $minute, (int) $second);
2577	$date = $dt->getTimestamp(); // should include daylight saving time
2578	//var_dump($date);
2579	return $date;
2580}
2581
2582
2583/**
2584 *  Return date for now. In most cases, we use this function without parameters (that means GMT time).
2585 *
2586 *  @param	string		$mode	'auto' => for backward compatibility (avoid this),
2587 *  							'gmt' => we return GMT timestamp,
2588 * 								'tzserver' => we add the PHP server timezone
2589 *  							'tzref' => we add the company timezone. Not implemented.
2590 * 								'tzuser' or 'tzuserrel' => we add the user timezone
2591 *	@return int   $date	Timestamp
2592 */
2593function dol_now($mode = 'auto')
2594{
2595	$ret = 0;
2596
2597	if ($mode === 'auto') {
2598		$mode = 'gmt';
2599	}
2600
2601	if ($mode == 'gmt') {
2602		$ret = time(); // Time for now at greenwich.
2603	} elseif ($mode == 'tzserver') {		// Time for now with PHP server timezone added
2604		require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
2605		$tzsecond = getServerTimeZoneInt('now'); // Contains tz+dayling saving time
2606		$ret = (int) (dol_now('gmt') + ($tzsecond * 3600));
2607		//} elseif ($mode == 'tzref') {// Time for now with parent company timezone is added
2608		//	require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
2609		//	$tzsecond=getParentCompanyTimeZoneInt();    // Contains tz+dayling saving time
2610		//	$ret=dol_now('gmt')+($tzsecond*3600);
2611		//}
2612	} elseif ($mode == 'tzuser' || $mode == 'tzuserrel') {
2613		// Time for now with user timezone added
2614		//print 'time: '.time();
2615		$offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60;
2616		$offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60;
2617		$ret = (int) (dol_now('gmt') + ($offsettz + $offsetdst));
2618	}
2619
2620	return $ret;
2621}
2622
2623
2624/**
2625 * Return string with formated size
2626 *
2627 * @param	int		$size		Size to print
2628 * @param	int		$shortvalue	Tell if we want long value to use another unit (Ex: 1.5Kb instead of 1500b)
2629 * @param	int		$shortunit	Use short label of size unit (for example 'b' instead of 'bytes')
2630 * @return	string				Link
2631 */
2632function dol_print_size($size, $shortvalue = 0, $shortunit = 0)
2633{
2634	global $conf, $langs;
2635	$level = 1024;
2636
2637	if (!empty($conf->dol_optimize_smallscreen)) {
2638		$shortunit = 1;
2639	}
2640
2641	// Set value text
2642	if (empty($shortvalue) || $size < ($level * 10)) {
2643		$ret = $size;
2644		$textunitshort = $langs->trans("b");
2645		$textunitlong = $langs->trans("Bytes");
2646	} else {
2647		$ret = round($size / $level, 0);
2648		$textunitshort = $langs->trans("Kb");
2649		$textunitlong = $langs->trans("KiloBytes");
2650	}
2651	// Use long or short text unit
2652	if (empty($shortunit)) {
2653		$ret .= ' '.$textunitlong;
2654	} else {
2655		$ret .= ' '.$textunitshort;
2656	}
2657
2658	return $ret;
2659}
2660
2661/**
2662 * Show Url link
2663 *
2664 * @param	string		$url		Url to show
2665 * @param	string		$target		Target for link
2666 * @param	int			$max		Max number of characters to show
2667 * @param	int			$withpicto	With picto
2668 * @return	string					HTML Link
2669 */
2670function dol_print_url($url, $target = '_blank', $max = 32, $withpicto = 0)
2671{
2672	global $langs;
2673
2674	if (empty($url)) {
2675		return '';
2676	}
2677
2678	$link = '<a href="';
2679	if (!preg_match('/^http/i', $url)) {
2680		$link .= 'http://';
2681	}
2682	$link .= $url;
2683	$link .= '"';
2684	if ($target) {
2685		$link .= ' target="'.$target.'"';
2686	}
2687	$link .= '>';
2688	if (!preg_match('/^http/i', $url)) {
2689		$link .= 'http://';
2690	}
2691	$link .= dol_trunc($url, $max);
2692	$link .= '</a>';
2693	return '<div class="nospan float" style="margin-right: 10px">'.($withpicto ?img_picto($langs->trans("Url"), 'globe').' ' : '').$link.'</div>';
2694}
2695
2696/**
2697 * Show EMail link formatted for HTML output.
2698 *
2699 * @param	string		$email			EMail to show (only email, without 'Name of recipient' before)
2700 * @param 	int			$cid 			Id of contact if known
2701 * @param 	int			$socid 			Id of third party if known
2702 * @param 	int			$addlink		0=no link, 1=email has a html email link (+ link to create action if constant AGENDA_ADDACTIONFOREMAIL is on)
2703 * @param	int			$max			Max number of characters to show
2704 * @param	int			$showinvalid	1=Show warning if syntax email is wrong
2705 * @param	int			$withpicto		Show picto
2706 * @return	string						HTML Link
2707 */
2708function dol_print_email($email, $cid = 0, $socid = 0, $addlink = 0, $max = 64, $showinvalid = 1, $withpicto = 0)
2709{
2710	global $conf, $user, $langs, $hookmanager;
2711
2712	$newemail = dol_escape_htmltag($email);
2713
2714	if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER) && $withpicto) {
2715		$withpicto = 0;
2716	}
2717
2718	if (empty($email)) {
2719		return '&nbsp;';
2720	}
2721
2722	if (!empty($addlink)) {
2723		$newemail = '<a style="text-overflow: ellipsis;" href="';
2724		if (!preg_match('/^mailto:/i', $email)) {
2725			$newemail .= 'mailto:';
2726		}
2727		$newemail .= $email;
2728		$newemail .= '">';
2729		$newemail .= dol_trunc($email, $max);
2730		$newemail .= '</a>';
2731		if ($showinvalid && !isValidEmail($email)) {
2732			$langs->load("errors");
2733			$newemail .= img_warning($langs->trans("ErrorBadEMail", $email));
2734		}
2735
2736		if (($cid || $socid) && !empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create) {
2737			$type = 'AC_EMAIL';
2738			$link = '';
2739			if (!empty($conf->global->AGENDA_ADDACTIONFOREMAIL)) {
2740				$link = '<a href="'.DOL_URL_ROOT.'/comm/action/card.php?action=create&amp;backtopage=1&amp;actioncode='.$type.'&amp;contactid='.$cid.'&amp;socid='.$socid.'">'.img_object($langs->trans("AddAction"), "calendar").'</a>';
2741			}
2742			if ($link) {
2743				$newemail = '<div>'.$newemail.' '.$link.'</div>';
2744			}
2745		}
2746	} else {
2747		if ($showinvalid && !isValidEmail($email)) {
2748			$langs->load("errors");
2749			$newemail .= img_warning($langs->trans("ErrorBadEMail", $email));
2750		}
2751	}
2752
2753	//$rep = '<div class="nospan" style="margin-right: 10px">';
2754	$rep = ($withpicto ? img_picto($langs->trans("EMail").' : '.$email, 'object_email.png').' ' : '').$newemail;
2755	//$rep .= '</div>';
2756	if ($hookmanager) {
2757		$parameters = array('cid' => $cid, 'socid' => $socid, 'addlink' => $addlink, 'picto' => $withpicto);
2758		$reshook = $hookmanager->executeHooks('printEmail', $parameters, $email);
2759		if ($reshook > 0) {
2760			$rep = '';
2761		}
2762		$rep .= $hookmanager->resPrint;
2763	}
2764
2765	return $rep;
2766}
2767
2768/**
2769 * Get array of social network dictionary
2770 *
2771 * @return  array       Array of Social Networks Dictionary
2772 */
2773function getArrayOfSocialNetworks()
2774{
2775	global $conf, $db;
2776
2777	$socialnetworks = array();
2778	// Enable caching of array
2779	require_once DOL_DOCUMENT_ROOT.'/core/lib/memory.lib.php';
2780	$cachekey = 'socialnetworks_' . $conf->entity;
2781	$dataretrieved = dol_getcache($cachekey);
2782	if (!is_null($dataretrieved)) {
2783		$socialnetworks = $dataretrieved;
2784	} else {
2785		$sql = "SELECT rowid, code, label, url, icon, active FROM ".MAIN_DB_PREFIX."c_socialnetworks";
2786		$sql .= " WHERE entity=".$conf->entity;
2787		$resql = $db->query($sql);
2788		if ($resql) {
2789			while ($obj = $db->fetch_object($resql)) {
2790				$socialnetworks[$obj->code] = array(
2791					'rowid' => $obj->rowid,
2792					'label' => $obj->label,
2793					'url' => $obj->url,
2794					'icon' => $obj->icon,
2795					'active' => $obj->active,
2796				);
2797			}
2798		}
2799		dol_setcache($cachekey, $socialnetworks); // If setting cache fails, this is not a problem, so we do not test result.
2800	}
2801
2802	return $socialnetworks;
2803}
2804
2805/**
2806 * Show social network link
2807 *
2808 * @param	string		$value				Skype to show (only skype, without 'Name of recipient' before)
2809 * @param	int 		$cid 				Id of contact if known
2810 * @param	int 		$socid 				Id of third party if known
2811 * @param	string 		$type				'skype','facebook',...
2812 * @param	array		$dictsocialnetworks socialnetworks availables
2813 * @return	string							HTML Link
2814 */
2815function dol_print_socialnetworks($value, $cid, $socid, $type, $dictsocialnetworks = array())
2816{
2817	global $conf, $user, $langs;
2818
2819	$htmllink = $value;
2820
2821	if (empty($value)) {
2822		return '&nbsp;';
2823	}
2824
2825	if (!empty($type)) {
2826		$htmllink = '<div class="divsocialnetwork inline-block valignmiddle">';
2827		// Use dictionary definition for picto $dictsocialnetworks[$type]['icon']
2828		$htmllink .= '<span class="fa paddingright '.($dictsocialnetworks[$type]['icon'] ? $dictsocialnetworks[$type]['icon'] : 'fa-link').'"></span>';
2829		if ($type == 'skype') {
2830			$htmllink .= $value;
2831			$htmllink .= '&nbsp;';
2832			$htmllink .= '<a href="skype:';
2833			$htmllink .= $value;
2834			$htmllink .= '?call" alt="'.$langs->trans("Call").'&nbsp;'.$value.'" title="'.$langs->trans("Call").'&nbsp;'.$value.'">';
2835			$htmllink .= '<img src="'.DOL_URL_ROOT.'/theme/common/skype_callbutton.png" border="0">';
2836			$htmllink .= '</a><a href="skype:';
2837			$htmllink .= $value;
2838			$htmllink .= '?chat" alt="'.$langs->trans("Chat").'&nbsp;'.$value.'" title="'.$langs->trans("Chat").'&nbsp;'.$value.'">';
2839			$htmllink .= '<img class="paddingleft" src="'.DOL_URL_ROOT.'/theme/common/skype_chatbutton.png" border="0">';
2840			$htmllink .= '</a>';
2841			if (($cid || $socid) && !empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create) {
2842				$addlink = 'AC_SKYPE';
2843				$link = '';
2844				if (!empty($conf->global->AGENDA_ADDACTIONFORSKYPE)) {
2845					$link = '<a href="'.DOL_URL_ROOT.'/comm/action/card.php?action=create&amp;backtopage=1&amp;actioncode='.$addlink.'&amp;contactid='.$cid.'&amp;socid='.$socid.'">'.img_object($langs->trans("AddAction"), "calendar").'</a>';
2846				}
2847				$htmllink .= ($link ? ' '.$link : '');
2848			}
2849		} else {
2850			if (!empty($dictsocialnetworks[$type]['url'])) {
2851				$link = str_replace('{socialid}', $value, $dictsocialnetworks[$type]['url']);
2852				$htmllink .= '&nbsp;<a href="'.$link.'" target="_blank">'.$value.'</a>';
2853			} else {
2854				$htmllink .= $value;
2855			}
2856		}
2857		$htmllink .= '</div>';
2858	} else {
2859		$langs->load("errors");
2860		$htmllink .= img_warning($langs->trans("ErrorBadSocialNetworkValue", $value));
2861	}
2862	return $htmllink;
2863}
2864
2865/**
2866 * 	Format phone numbers according to country
2867 *
2868 * 	@param  string  $phone          Phone number to format
2869 * 	@param  string  $countrycode    Country code to use for formatting
2870 * 	@param 	int		$cid 		    Id of contact if known
2871 * 	@param 	int		$socid          Id of third party if known
2872 * 	@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)
2873 * 	@param 	string	$separ 		    Separation between numbers for a better visibility example : xx.xx.xx.xx.xx
2874 *  @param	string  $withpicto      Show picto
2875 *  @param	string	$titlealt	    Text to show on alt
2876 *  @param  int     $adddivfloat    Add div float around phone.
2877 * 	@return string 				    Formated phone number
2878 */
2879function dol_print_phone($phone, $countrycode = '', $cid = 0, $socid = 0, $addlink = '', $separ = "&nbsp;", $withpicto = '', $titlealt = '', $adddivfloat = 0)
2880{
2881	global $conf, $user, $langs, $mysoc, $hookmanager;
2882
2883	// Clean phone parameter
2884	$phone = preg_replace("/[\s.-]/", "", trim($phone));
2885	if (empty($phone)) {
2886		return '';
2887	}
2888	if (!empty($conf->global->MAIN_PHONE_SEPAR)) {
2889		$separ = $conf->global->MAIN_PHONE_SEPAR;
2890	}
2891	if (empty($countrycode)) {
2892		$countrycode = $mysoc->country_code;
2893	}
2894
2895	// Short format for small screens
2896	if ($conf->dol_optimize_smallscreen) {
2897		$separ = '';
2898	}
2899
2900	$newphone = $phone;
2901	if (strtoupper($countrycode) == "FR") {
2902		// France
2903		if (dol_strlen($phone) == 10) {
2904			$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);
2905		} elseif (dol_strlen($phone) == 7) {
2906			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 2).$separ.substr($newphone, 5, 2);
2907		} elseif (dol_strlen($phone) == 9) {
2908			$newphone = substr($newphone, 0, 2).$separ.substr($newphone, 2, 3).$separ.substr($newphone, 5, 2).$separ.substr($newphone, 7, 2);
2909		} elseif (dol_strlen($phone) == 11) {
2910			$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);
2911		} elseif (dol_strlen($phone) == 12) {
2912			$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);
2913		}
2914	} elseif (strtoupper($countrycode) == "CA") {
2915		if (dol_strlen($phone) == 10) {
2916			$newphone = ($separ != '' ? '(' : '').substr($newphone, 0, 3).($separ != '' ? ')' : '').$separ.substr($newphone, 3, 3).($separ != '' ? '-' : '').substr($newphone, 6, 4);
2917		}
2918	} elseif (strtoupper($countrycode) == "PT") {//Portugal
2919		if (dol_strlen($phone) == 13) {//ex: +351_ABC_DEF_GHI
2920			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
2921		}
2922	} elseif (strtoupper($countrycode) == "SR") {//Suriname
2923		if (dol_strlen($phone) == 10) {//ex: +597_ABC_DEF
2924			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3);
2925		} elseif (dol_strlen($phone) == 11) {//ex: +597_ABC_DEFG
2926			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 4);
2927		}
2928	} elseif (strtoupper($countrycode) == "DE") {//Allemagne
2929		if (dol_strlen($phone) == 14) {//ex:  +49_ABCD_EFGH_IJK
2930			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 4).$separ.substr($newphone, 7, 4).$separ.substr($newphone, 11, 3);
2931		} elseif (dol_strlen($phone) == 13) {//ex: +49_ABC_DEFG_HIJ
2932			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 4).$separ.substr($newphone, 10, 3);
2933		}
2934	} elseif (strtoupper($countrycode) == "ES") {//Espagne
2935		if (dol_strlen($phone) == 12) {//ex:  +34_ABC_DEF_GHI
2936			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
2937		}
2938	} elseif (strtoupper($countrycode) == "BF") {// Burkina Faso
2939		if (dol_strlen($phone) == 12) {//ex :  +22 A BC_DE_FG_HI
2940			$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);
2941		}
2942	} elseif (strtoupper($countrycode) == "RO") {// Roumanie
2943		if (dol_strlen($phone) == 12) {//ex :  +40 AB_CDE_FG_HI
2944			$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);
2945		}
2946	} elseif (strtoupper($countrycode) == "TR") {//Turquie
2947		if (dol_strlen($phone) == 13) {//ex :  +90 ABC_DEF_GHIJ
2948			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 4);
2949		}
2950	} elseif (strtoupper($countrycode) == "US") {//Etat-Unis
2951		if (dol_strlen($phone) == 12) {//ex: +1 ABC_DEF_GHIJ
2952			$newphone = substr($newphone, 0, 2).$separ.substr($newphone, 2, 3).$separ.substr($newphone, 5, 3).$separ.substr($newphone, 8, 4);
2953		}
2954	} elseif (strtoupper($countrycode) == "MX") {//Mexique
2955		if (dol_strlen($phone) == 12) {//ex: +52 ABCD_EFG_HI
2956			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 4).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 2);
2957		} elseif (dol_strlen($phone) == 11) {//ex: +52 AB_CD_EF_GH
2958			$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);
2959		} elseif (dol_strlen($phone) == 13) {//ex: +52 ABC_DEF_GHIJ
2960			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 4);
2961		}
2962	} elseif (strtoupper($countrycode) == "ML") {//Mali
2963		if (dol_strlen($phone) == 12) {//ex: +223 AB_CD_EF_GH
2964			$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);
2965		}
2966	} elseif (strtoupper($countrycode) == "TH") {//Thaïlande
2967		if (dol_strlen($phone) == 11) {//ex: +66_ABC_DE_FGH
2968			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 2).$separ.substr($newphone, 8, 3);
2969		} elseif (dol_strlen($phone) == 12) {//ex: +66_A_BCD_EF_GHI
2970			$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);
2971		}
2972	} elseif (strtoupper($countrycode) == "MU") {
2973		//Maurice
2974		if (dol_strlen($phone) == 11) {//ex: +230_ABC_DE_FG
2975			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 2).$separ.substr($newphone, 9, 2);
2976		} elseif (dol_strlen($phone) == 12) {//ex: +230_ABCD_EF_GH
2977			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 4).$separ.substr($newphone, 8, 2).$separ.substr($newphone, 10, 2);
2978		}
2979	} elseif (strtoupper($countrycode) == "ZA") {//Afrique du sud
2980		if (dol_strlen($phone) == 12) {//ex: +27_AB_CDE_FG_HI
2981			$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);
2982		}
2983	} elseif (strtoupper($countrycode) == "SY") {//Syrie
2984		if (dol_strlen($phone) == 12) {//ex: +963_AB_CD_EF_GH
2985			$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);
2986		} elseif (dol_strlen($phone) == 13) {//ex: +963_AB_CD_EF_GHI
2987			$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);
2988		}
2989	} elseif (strtoupper($countrycode) == "AE") {//Emirats Arabes Unis
2990		if (dol_strlen($phone) == 12) {//ex: +971_ABC_DEF_GH
2991			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 2);
2992		} elseif (dol_strlen($phone) == 13) {//ex: +971_ABC_DEF_GHI
2993			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
2994		} elseif (dol_strlen($phone) == 14) {//ex: +971_ABC_DEF_GHIK
2995			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 4);
2996		}
2997	} elseif (strtoupper($countrycode) == "DZ") {//Algérie
2998		if (dol_strlen($phone) == 13) {//ex: +213_ABC_DEF_GHI
2999			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
3000		}
3001	} elseif (strtoupper($countrycode) == "BE") {//Belgique
3002		if (dol_strlen($phone) == 11) {//ex: +32_ABC_DE_FGH
3003			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 2).$separ.substr($newphone, 8, 3);
3004		} elseif (dol_strlen($phone) == 12) {//ex: +32_ABC_DEF_GHI
3005			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
3006		}
3007	} elseif (strtoupper($countrycode) == "PF") {//Polynésie française
3008		if (dol_strlen($phone) == 12) {//ex: +689_AB_CD_EF_GH
3009			$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);
3010		}
3011	} elseif (strtoupper($countrycode) == "CO") {//Colombie
3012		if (dol_strlen($phone) == 13) {//ex: +57_ABC_DEF_GH_IJ
3013			$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);
3014		}
3015	} elseif (strtoupper($countrycode) == "JO") {//Jordanie
3016		if (dol_strlen($phone) == 12) {//ex: +962_A_BCD_EF_GH
3017			$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);
3018		}
3019	} elseif (strtoupper($countrycode) == "JM") {//Jamaïque
3020		if (dol_strlen($newphone) == 12) {//ex: +1867_ABC_DEFG
3021			$newphone = substr($newphone, 0, 5).$separ.substr($newphone, 5, 3).$separ.substr($newphone, 8, 4);
3022		}
3023	} elseif (strtoupper($countrycode) == "MG") {//Madagascar
3024		if (dol_strlen($phone) == 13) {//ex: +261_AB_CD_EF_GHI
3025			$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);
3026		}
3027	} elseif (strtoupper($countrycode) == "GB") {//Royaume uni
3028		if (dol_strlen($phone) == 13) {//ex: +44_ABCD_EFG_HIJ
3029			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 4).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
3030		}
3031	} elseif (strtoupper($countrycode) == "CH") {//Suisse
3032		if (dol_strlen($phone) == 12) {//ex: +41_AB_CDE_FG_HI
3033			$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);
3034		} elseif (dol_strlen($phone) == 15) {// +41_AB_CDE_FGH_IJKL
3035			$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);
3036		}
3037	} elseif (strtoupper($countrycode) == "TN") {//Tunisie
3038		if (dol_strlen($phone) == 12) {//ex: +216_AB_CDE_FGH
3039			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 2).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
3040		}
3041	} elseif (strtoupper($countrycode) == "GF") {//Guyane francaise
3042		if (dol_strlen($phone) == 13) {//ex: +594_ABC_DE_FG_HI  (ABC=594 de nouveau)
3043			$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);
3044		}
3045	} elseif (strtoupper($countrycode) == "GP") {//Guadeloupe
3046		if (dol_strlen($phone) == 13) {//ex: +590_ABC_DE_FG_HI  (ABC=590 de nouveau)
3047			$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);
3048		}
3049	} elseif (strtoupper($countrycode) == "MQ") {//Martinique
3050		if (dol_strlen($phone) == 13) {//ex: +596_ABC_DE_FG_HI  (ABC=596 de nouveau)
3051			$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);
3052		}
3053	} elseif (strtoupper($countrycode) == "IT") {//Italie
3054		if (dol_strlen($phone) == 12) {//ex: +39_ABC_DEF_GHI
3055			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
3056		} elseif (dol_strlen($phone) == 13) {//ex: +39_ABC_DEF_GH_IJ
3057			$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);
3058		}
3059	} elseif (strtoupper($countrycode) == "AU") {
3060		//Australie
3061		if (dol_strlen($phone) == 12) {
3062			//ex: +61_A_BCDE_FGHI
3063			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 1).$separ.substr($newphone, 4, 4).$separ.substr($newphone, 8, 4);
3064		}
3065	}
3066	if (!empty($addlink)) {	// Link on phone number (+ link to add action if conf->global->AGENDA_ADDACTIONFORPHONE set)
3067		if ($conf->browser->layout == 'phone' || (!empty($conf->clicktodial->enabled) && !empty($conf->global->CLICKTODIAL_USE_TEL_LINK_ON_PHONE_NUMBERS))) {	// If phone or option for, we use link of phone
3068			$newphoneform = $newphone;
3069			$newphone = '<a href="tel:'.$phone.'"';
3070			$newphone .= '>'.$newphoneform.'</a>';
3071		} elseif (!empty($conf->clicktodial->enabled) && $addlink == 'AC_TEL') {		// If click to dial, we use click to dial url
3072			if (empty($user->clicktodial_loaded)) {
3073				$user->fetch_clicktodial();
3074			}
3075
3076			// Define urlmask
3077			$urlmask = 'ErrorClickToDialModuleNotConfigured';
3078			if (!empty($conf->global->CLICKTODIAL_URL)) {
3079				$urlmask = $conf->global->CLICKTODIAL_URL;
3080			}
3081			if (!empty($user->clicktodial_url)) {
3082				$urlmask = $user->clicktodial_url;
3083			}
3084
3085			$clicktodial_poste = (!empty($user->clicktodial_poste) ?urlencode($user->clicktodial_poste) : '');
3086			$clicktodial_login = (!empty($user->clicktodial_login) ?urlencode($user->clicktodial_login) : '');
3087			$clicktodial_password = (!empty($user->clicktodial_password) ?urlencode($user->clicktodial_password) : '');
3088			// This line is for backward compatibility
3089			$url = sprintf($urlmask, urlencode($phone), $clicktodial_poste, $clicktodial_login, $clicktodial_password);
3090			// Thoose lines are for substitution
3091			$substitarray = array('__PHONEFROM__'=>$clicktodial_poste,
3092								'__PHONETO__'=>urlencode($phone),
3093								'__LOGIN__'=>$clicktodial_login,
3094								'__PASS__'=>$clicktodial_password);
3095			$url = make_substitutions($url, $substitarray);
3096			$newphonesav = $newphone;
3097			$newphone = '<a href="'.$url.'"';
3098			if (!empty($conf->global->CLICKTODIAL_FORCENEWTARGET)) {
3099				$newphone .= ' target="_blank"';
3100			}
3101			$newphone .= '>'.$newphonesav.'</a>';
3102		}
3103
3104		//if (($cid || $socid) && ! empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create)
3105		if (!empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create) {
3106			$type = 'AC_TEL';
3107			$link = '';
3108			if ($addlink == 'AC_FAX') {
3109				$type = 'AC_FAX';
3110			}
3111			if (!empty($conf->global->AGENDA_ADDACTIONFORPHONE)) {
3112				$link = '<a href="'.DOL_URL_ROOT.'/comm/action/card.php?action=create&amp;backtopage=1&amp;actioncode='.$type.($cid ? '&amp;contactid='.$cid : '').($socid ? '&amp;socid='.$socid : '').'">'.img_object($langs->trans("AddAction"), "calendar").'</a>';
3113			}
3114			if ($link) {
3115				$newphone = '<div>'.$newphone.' '.$link.'</div>';
3116			}
3117		}
3118	}
3119
3120	if (empty($titlealt)) {
3121		$titlealt = ($withpicto == 'fax' ? $langs->trans("Fax") : $langs->trans("Phone"));
3122	}
3123	$rep = '';
3124
3125	if ($hookmanager) {
3126		$parameters = array('countrycode' => $countrycode, 'cid' => $cid, 'socid' => $socid, 'titlealt' => $titlealt, 'picto' => $withpicto);
3127		$reshook = $hookmanager->executeHooks('printPhone', $parameters, $phone);
3128		$rep .= $hookmanager->resPrint;
3129	}
3130	if (empty($reshook)) {
3131		$picto = '';
3132		if ($withpicto) {
3133			if ($withpicto == 'fax') {
3134				$picto = 'phoning_fax';
3135			} elseif ($withpicto == 'phone') {
3136				$picto = 'phoning';
3137			} elseif ($withpicto == 'mobile') {
3138				$picto = 'phoning_mobile';
3139			} else {
3140				$picto = '';
3141			}
3142		}
3143		if ($adddivfloat) {
3144			$rep .= '<div class="nospan float" style="margin-right: 10px">';
3145		} else {
3146			$rep .= '<span style="margin-right: 10px;">';
3147		}
3148		$rep .= ($withpicto ?img_picto($titlealt, 'object_'.$picto.'.png').' ' : '').$newphone;
3149		if ($adddivfloat) {
3150			$rep .= '</div>';
3151		} else {
3152			$rep .= '</span>';
3153		}
3154	}
3155
3156	return $rep;
3157}
3158
3159/**
3160 * 	Return an IP formated to be shown on screen
3161 *
3162 * 	@param	string	$ip			IP
3163 * 	@param	int		$mode		0=return IP + country/flag, 1=return only country/flag, 2=return only IP
3164 * 	@return string 				Formated IP, with country if GeoIP module is enabled
3165 */
3166function dol_print_ip($ip, $mode = 0)
3167{
3168	global $conf, $langs;
3169
3170	$ret = '';
3171
3172	if (empty($mode)) {
3173		$ret .= $ip;
3174	}
3175
3176	if ($mode != 2) {
3177		$countrycode = dolGetCountryCodeFromIp($ip);
3178		if ($countrycode) {	// If success, countrycode is us, fr, ...
3179			if (file_exists(DOL_DOCUMENT_ROOT.'/theme/common/flags/'.$countrycode.'.png')) {
3180				$ret .= ' '.img_picto($countrycode.' '.$langs->trans("AccordingToGeoIPDatabase"), DOL_URL_ROOT.'/theme/common/flags/'.$countrycode.'.png', '', 1);
3181			} else {
3182				$ret .= ' ('.$countrycode.')';
3183			}
3184		} else {
3185			// Nothing
3186		}
3187	}
3188
3189	return $ret;
3190}
3191
3192/**
3193 * Return the IP of remote user.
3194 * Take HTTP_X_FORWARDED_FOR (defined when using proxy)
3195 * Then HTTP_CLIENT_IP if defined (rare)
3196 * Then REMOTE_ADDR (no way to be modified by user but may be wrong if user is using a proxy)
3197 *
3198 * @return	string		Ip of remote user.
3199 */
3200function getUserRemoteIP()
3201{
3202	if (empty($_SERVER['HTTP_X_FORWARDED_FOR']) || preg_match('/[^0-9\.\:,\[\]]/', $_SERVER['HTTP_X_FORWARDED_FOR'])) {
3203		if (empty($_SERVER['HTTP_CLIENT_IP']) || preg_match('/[^0-9\.\:,\[\]]/', $_SERVER['HTTP_CLIENT_IP'])) {
3204			if (empty($_SERVER["HTTP_CF_CONNECTING_IP"])) {
3205				$ip = (empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR']);	// value may have been the IP of the proxy and not the client
3206			} else {
3207				$ip = $_SERVER["HTTP_CF_CONNECTING_IP"];	// value here may have been forged by client
3208			}
3209		} else {
3210			$ip = $_SERVER['HTTP_CLIENT_IP']; // value is clean here but may have been forged by proxy
3211		}
3212	} else {
3213		$ip = $_SERVER['HTTP_X_FORWARDED_FOR']; // value is clean here but may have been forged by proxy
3214	}
3215	return $ip;
3216}
3217
3218/**
3219 * Return if we are using a HTTPS connexion
3220 * Check HTTPS (no way to be modified by user but may be empty or wrong if user is using a proxy)
3221 * Take HTTP_X_FORWARDED_PROTO (defined when using proxy)
3222 * Then HTTP_X_FORWARDED_SSL
3223 *
3224 * @return	boolean		True if user is using HTTPS
3225 */
3226function isHTTPS()
3227{
3228	$isSecure = false;
3229	if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
3230		$isSecure = true;
3231	} elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' || !empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on') {
3232		$isSecure = true;
3233	}
3234	return $isSecure;
3235}
3236
3237/**
3238 * 	Return a country code from IP. Empty string if not found.
3239 *
3240 * 	@param	string	$ip			IP
3241 * 	@return string 				Country code ('us', 'fr', ...)
3242 */
3243function dolGetCountryCodeFromIp($ip)
3244{
3245	global $conf;
3246
3247	$countrycode = '';
3248
3249	if (!empty($conf->geoipmaxmind->enabled)) {
3250		$datafile = $conf->global->GEOIPMAXMIND_COUNTRY_DATAFILE;
3251		//$ip='24.24.24.24';
3252		//$datafile='/usr/share/GeoIP/GeoIP.dat';    Note that this must be downloaded datafile (not same than datafile provided with ubuntu packages)
3253		include_once DOL_DOCUMENT_ROOT.'/core/class/dolgeoip.class.php';
3254		$geoip = new DolGeoIP('country', $datafile);
3255		//print 'ip='.$ip.' databaseType='.$geoip->gi->databaseType." GEOIP_CITY_EDITION_REV1=".GEOIP_CITY_EDITION_REV1."\n";
3256		$countrycode = $geoip->getCountryCodeFromIP($ip);
3257	}
3258
3259	return $countrycode;
3260}
3261
3262
3263/**
3264 *  Return country code for current user.
3265 *  If software is used inside a local network, detection may fails (we need a public ip)
3266 *
3267 *  @return     string      Country code (fr, es, it, us, ...)
3268 */
3269function dol_user_country()
3270{
3271	global $conf, $langs, $user;
3272
3273	//$ret=$user->xxx;
3274	$ret = '';
3275	if (!empty($conf->geoipmaxmind->enabled)) {
3276		$ip = getUserRemoteIP();
3277		$datafile = $conf->global->GEOIPMAXMIND_COUNTRY_DATAFILE;
3278		//$ip='24.24.24.24';
3279		//$datafile='E:\Mes Sites\Web\Admin1\awstats\maxmind\GeoIP.dat';
3280		include_once DOL_DOCUMENT_ROOT.'/core/class/dolgeoip.class.php';
3281		$geoip = new DolGeoIP('country', $datafile);
3282		$countrycode = $geoip->getCountryCodeFromIP($ip);
3283		$ret = $countrycode;
3284	}
3285	return $ret;
3286}
3287
3288/**
3289 *  Format address string
3290 *
3291 *  @param	string	$address    Address string, already formatted with dol_format_address()
3292 *  @param  int		$htmlid     Html ID (for example 'gmap')
3293 *  @param  int		$element    'thirdparty'|'contact'|'member'|'other'
3294 *  @param  int		$id         Id of object
3295 *  @param	int		$noprint	No output. Result is the function return
3296 *  @param  string  $charfornl  Char to use instead of nl2br. '' means we use a standad nl2br.
3297 *  @return string|void			Nothing if noprint is 0, formatted address if noprint is 1
3298 *  @see dol_format_address()
3299 */
3300function dol_print_address($address, $htmlid, $element, $id, $noprint = 0, $charfornl = '')
3301{
3302	global $conf, $user, $langs, $hookmanager;
3303
3304	$out = '';
3305
3306	if ($address) {
3307		if ($hookmanager) {
3308			$parameters = array('element' => $element, 'id' => $id);
3309			$reshook = $hookmanager->executeHooks('printAddress', $parameters, $address);
3310			$out .= $hookmanager->resPrint;
3311		}
3312		if (empty($reshook)) {
3313			if (empty($charfornl)) {
3314				$out .= nl2br($address);
3315			} else {
3316				$out .= preg_replace('/[\r\n]+/', $charfornl, $address);
3317			}
3318
3319			// TODO Remove this block, we can add this using the hook now
3320			$showgmap = $showomap = 0;
3321			if (($element == 'thirdparty' || $element == 'societe') && !empty($conf->google->enabled) && !empty($conf->global->GOOGLE_ENABLE_GMAPS)) {
3322				$showgmap = 1;
3323			}
3324			if ($element == 'contact' && !empty($conf->google->enabled) && !empty($conf->global->GOOGLE_ENABLE_GMAPS_CONTACTS)) {
3325				$showgmap = 1;
3326			}
3327			if ($element == 'member' && !empty($conf->google->enabled) && !empty($conf->global->GOOGLE_ENABLE_GMAPS_MEMBERS)) {
3328				$showgmap = 1;
3329			}
3330			if (($element == 'thirdparty' || $element == 'societe') && !empty($conf->openstreetmap->enabled) && !empty($conf->global->OPENSTREETMAP_ENABLE_MAPS)) {
3331				$showomap = 1;
3332			}
3333			if ($element == 'contact' && !empty($conf->openstreetmap->enabled) && !empty($conf->global->OPENSTREETMAP_ENABLE_MAPS_CONTACTS)) {
3334				$showomap = 1;
3335			}
3336			if ($element == 'member' && !empty($conf->openstreetmap->enabled) && !empty($conf->global->OPENSTREETMAP_ENABLE_MAPS_MEMBERS)) {
3337				$showomap = 1;
3338			}
3339			if ($showgmap) {
3340				$url = dol_buildpath('/google/gmaps.php?mode='.$element.'&id='.$id, 1);
3341				$out .= ' <a href="'.$url.'" target="_gmaps"><img id="'.$htmlid.'" class="valigntextbottom" src="'.DOL_URL_ROOT.'/theme/common/gmap.png"></a>';
3342			}
3343			if ($showomap) {
3344				$url = dol_buildpath('/openstreetmap/maps.php?mode='.$element.'&id='.$id, 1);
3345				$out .= ' <a href="'.$url.'" target="_gmaps"><img id="'.$htmlid.'_openstreetmap" class="valigntextbottom" src="'.DOL_URL_ROOT.'/theme/common/gmap.png"></a>';
3346			}
3347		}
3348	}
3349	if ($noprint) {
3350		return $out;
3351	} else {
3352		print $out;
3353	}
3354}
3355
3356
3357/**
3358 *	Return true if email syntax is ok.
3359 *
3360 *	@param	    string		$address    			email (Ex: "toto@examle.com". Long form "John Do <johndo@example.com>" will be false)
3361 *  @param		int			$acceptsupervisorkey	If 1, the special string '__SUPERVISOREMAIL__' is also accepted as valid
3362 *	@return     boolean     						true if email syntax is OK, false if KO or empty string
3363 *  @see isValidMXRecord()
3364 */
3365function isValidEmail($address, $acceptsupervisorkey = 0)
3366{
3367	if ($acceptsupervisorkey && $address == '__SUPERVISOREMAIL__') {
3368		return true;
3369	}
3370	if (filter_var($address, FILTER_VALIDATE_EMAIL)) {
3371		return true;
3372	}
3373
3374	return false;
3375}
3376
3377/**
3378 *	Return if the domain name has a valid MX record.
3379 *  WARNING: This need function idn_to_ascii, checkdnsrr and getmxrr
3380 *
3381 *	@param	    string		$domain	    			Domain name (Ex: "yahoo.com", "yhaoo.com", "dolibarr.fr")
3382 *	@return     int     							-1 if error (function not available), 0=Not valid, 1=Valid
3383 *  @see isValidEmail()
3384 */
3385function isValidMXRecord($domain)
3386{
3387	if (function_exists('idn_to_ascii') && function_exists('checkdnsrr')) {
3388		if (!checkdnsrr(idn_to_ascii($domain), 'MX')) {
3389			return 0;
3390		}
3391		if (function_exists('getmxrr')) {
3392			$mxhosts = array();
3393			$weight = array();
3394			getmxrr(idn_to_ascii($domain), $mxhosts, $weight);
3395			if (count($mxhosts) > 1) {
3396				return 1;
3397			}
3398			if (count($mxhosts) == 1 && !empty($mxhosts[0])) {
3399				return 1;
3400			}
3401
3402			return 0;
3403		}
3404	}
3405	return -1;
3406}
3407
3408/**
3409 *  Return true if phone number syntax is ok
3410 *  TODO Decide what to do with this
3411 *
3412 *  @param	string		$phone		phone (Ex: "0601010101")
3413 *  @return boolean     			true if phone syntax is OK, false if KO or empty string
3414 */
3415function isValidPhone($phone)
3416{
3417	return true;
3418}
3419
3420
3421/**
3422 * Make a strlen call. Works even if mbstring module not enabled
3423 *
3424 * @param   string		$string				String to calculate length
3425 * @param   string		$stringencoding		Encoding of string
3426 * @return  int								Length of string
3427 */
3428function dol_strlen($string, $stringencoding = 'UTF-8')
3429{
3430	if (function_exists('mb_strlen')) {
3431		return mb_strlen($string, $stringencoding);
3432	} else {
3433		return strlen($string);
3434	}
3435}
3436
3437/**
3438 * Make a substring. Works even if mbstring module is not enabled for better compatibility.
3439 *
3440 * @param	string	$string				String to scan
3441 * @param	string	$start				Start position
3442 * @param	int		$length				Length (in nb of characters or nb of bytes depending on trunconbytes param)
3443 * @param   string	$stringencoding		Page code used for input string encoding
3444 * @param	int		$trunconbytes		1=Length is max of bytes instead of max of characters
3445 * @return  string						substring
3446 */
3447function dol_substr($string, $start, $length, $stringencoding = '', $trunconbytes = 0)
3448{
3449	global $langs;
3450
3451	if (empty($stringencoding)) {
3452		$stringencoding = $langs->charset_output;
3453	}
3454
3455	$ret = '';
3456	if (empty($trunconbytes)) {
3457		if (function_exists('mb_substr')) {
3458			$ret = mb_substr($string, $start, $length, $stringencoding);
3459		} else {
3460			$ret = substr($string, $start, $length);
3461		}
3462	} else {
3463		if (function_exists('mb_strcut')) {
3464			$ret = mb_strcut($string, $start, $length, $stringencoding);
3465		} else {
3466			$ret = substr($string, $start, $length);
3467		}
3468	}
3469	return $ret;
3470}
3471
3472
3473/**
3474 *	Truncate a string to a particular length adding '…' if string larger than length.
3475 * 	If length = max length+1, we do no truncate to avoid having just 1 char replaced with '…'.
3476 *  MAIN_DISABLE_TRUNC=1 can disable all truncings
3477 *
3478 *	@param	string	$string				String to truncate
3479 *	@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 so it does not worse to replace with ...)
3480 *	@param	string	$trunc				Where to trunc: 'right', 'left', 'middle' (size must be a 2 power), 'wrap'
3481 * 	@param	string	$stringencoding		Tell what is source string encoding
3482 *  @param	int		$nodot				Truncation do not add … after truncation. So it's an exact truncation.
3483 *  @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)
3484 *	@return string						Truncated string. WARNING: length is never higher than $size if $nodot is set, but can be 3 chars higher otherwise.
3485 */
3486function dol_trunc($string, $size = 40, $trunc = 'right', $stringencoding = 'UTF-8', $nodot = 0, $display = 0)
3487{
3488	global $conf;
3489
3490	if (empty($size) || !empty($conf->global->MAIN_DISABLE_TRUNC)) {
3491		return $string;
3492	}
3493
3494	if (empty($stringencoding)) {
3495		$stringencoding = 'UTF-8';
3496	}
3497	// reduce for small screen
3498	if ($conf->dol_optimize_smallscreen == 1 && $display == 1) {
3499		$size = round($size / 3);
3500	}
3501
3502	// We go always here
3503	if ($trunc == 'right') {
3504		$newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
3505		if (dol_strlen($newstring, $stringencoding) > ($size + ($nodot ? 0 : 1))) {
3506			// If nodot is 0 and size is 1 chars more, we don't trunc and don't add …
3507			return dol_substr($newstring, 0, $size, $stringencoding).($nodot ? '' : '…');
3508		} else {
3509			//return 'u'.$size.'-'.$newstring.'-'.dol_strlen($newstring,$stringencoding).'-'.$string;
3510			return $string;
3511		}
3512	} elseif ($trunc == 'middle') {
3513		$newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
3514		if (dol_strlen($newstring, $stringencoding) > 2 && dol_strlen($newstring, $stringencoding) > ($size + 1)) {
3515			$size1 = round($size / 2);
3516			$size2 = round($size / 2);
3517			return dol_substr($newstring, 0, $size1, $stringencoding).'…'.dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size2, $size2, $stringencoding);
3518		} else {
3519			return $string;
3520		}
3521	} elseif ($trunc == 'left') {
3522		$newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
3523		if (dol_strlen($newstring, $stringencoding) > ($size + ($nodot ? 0 : 1))) {
3524			// If nodot is 0 and size is 1 chars more, we don't trunc and don't add …
3525			return '…'.dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size, $size, $stringencoding);
3526		} else {
3527			return $string;
3528		}
3529	} elseif ($trunc == 'wrap') {
3530		$newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
3531		if (dol_strlen($newstring, $stringencoding) > ($size + 1)) {
3532			return dol_substr($newstring, 0, $size, $stringencoding)."\n".dol_trunc(dol_substr($newstring, $size, dol_strlen($newstring, $stringencoding) - $size, $stringencoding), $size, $trunc);
3533		} else {
3534			return $string;
3535		}
3536	} else {
3537		return 'BadParam3CallingDolTrunc';
3538	}
3539}
3540
3541/**
3542 *	Show picto whatever it's its name (generic function)
3543 *
3544 *	@param      string		$titlealt         		Text on title tag for tooltip. Not used if param notitle is set to 1.
3545 *	@param      string		$picto       			Name of image file to show ('filenew', ...)
3546 *													If no extension provided, we use '.png'. Image must be stored into theme/xxx/img directory.
3547 *                                  				Example: picto.png                  if picto.png is stored into htdocs/theme/mytheme/img
3548 *                                  				Example: picto.png@mymodule         if picto.png is stored into htdocs/mymodule/img
3549 *                                  				Example: /mydir/mysubdir/picto.png  if picto.png is stored into htdocs/mydir/mysubdir (pictoisfullpath must be set to 1)
3550 *	@param		string		$moreatt				Add more attribute on img tag (For example 'class="pictofixedwidth"')
3551 *	@param		boolean|int	$pictoisfullpath		If true or 1, image path is a full path
3552 *	@param		int			$srconly				Return only content of the src attribute of img.
3553 *  @param		int			$notitle				1=Disable tag title. Use it if you add js tooltip, to avoid duplicate tooltip.
3554 *  @param		string		$alt					Force alt for bind people
3555 *  @param		string		$morecss				Add more class css on img tag (For example 'myclascss').
3556 *  @param		string		$marginleftonlyshort	1 = Add a short left margin on picto, 2 = Add a larger left margin on picto, 0 = No margin left. Works for fontawesome picto only.
3557 *  @return     string       				    	Return img tag
3558 *  @see        img_object(), img_picto_common()
3559 */
3560function img_picto($titlealt, $picto, $moreatt = '', $pictoisfullpath = false, $srconly = 0, $notitle = 0, $alt = '', $morecss = '', $marginleftonlyshort = 2)
3561{
3562	global $conf, $langs;
3563	// We forge fullpathpicto for image to $path/img/$picto. By default, we take DOL_URL_ROOT/theme/$conf->theme/img/$picto
3564	$url = DOL_URL_ROOT;
3565	$theme = isset($conf->theme) ? $conf->theme : null;
3566	$path = 'theme/'.$theme;
3567	// Define fullpathpicto to use into src
3568	if ($pictoisfullpath) {
3569		// Clean parameters
3570		if (!preg_match('/(\.png|\.gif|\.svg)$/i', $picto)) {
3571			$picto .= '.png';
3572		}
3573		$fullpathpicto = $picto;
3574		$reg = array();
3575		if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
3576			$morecss .= ($morecss ? ' ' : '').$reg[1];
3577			$moreatt = str_replace('class="'.$reg[1].'"', '', $moreatt);
3578		}
3579	} else {
3580		$pictowithouttext = preg_replace('/(\.png|\.gif|\.svg)$/', '', $picto);
3581		$pictowithouttext = str_replace('object_', '', $pictowithouttext);
3582		if (empty($srconly) && in_array($pictowithouttext, array(
3583				'1downarrow', '1uparrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected',
3584				'accountancy', 'account', 'accountline', 'action', 'add', 'address', 'angle-double-down', 'angle-double-up', 'asset',
3585				'bank_account', 'barcode', 'bank', 'bill', 'billa', 'billr', 'billd', 'bookmark', 'bom', 'bug', 'building',
3586				'calendar', 'calendarmonth', 'calendarweek', 'calendarday', 'calendarperuser', 'calendarpertype',
3587				'cash-register', 'category', 'chart', 'check', 'clock', 'close_title', 'cog', 'collab', 'company', 'contact', 'country', 'contract', 'conversation', 'cron', 'cubes',
3588				'multicurrency',
3589				'delete', 'dolly', 'dollyrevert', 'donation', 'download', 'dynamicprice',
3590				'edit', 'ellipsis-h', 'email', 'eraser', 'establishment', 'expensereport', 'external-link-alt', 'external-link-square-alt',
3591				'filter', 'file-code', 'file-export', 'file-import', 'file-upload', 'autofill', 'folder', 'folder-open', 'folder-plus',
3592				'generate', 'globe', 'globe-americas', 'graph', 'grip', 'grip_title', 'group',
3593				'help', 'holiday',
3594				'images', 'incoterm', 'info', 'intervention', 'inventory', 'intracommreport', 'knowledgemanagement',
3595				'label', 'language', 'link', 'list', 'list-alt', 'listlight', 'loan', 'lot', 'long-arrow-alt-right',
3596				'margin', 'map-marker-alt', 'member', 'meeting', 'money-bill-alt', 'movement', 'mrp', 'note', 'next',
3597				'off', 'on', 'order',
3598				'paiment', 'paragraph', 'play', 'pdf', 'phone', 'phoning', 'phoning_mobile', 'phoning_fax', 'playdisabled', 'previous', 'poll', 'pos', 'printer', 'product', 'propal', 'stock', 'resize', 'service', 'stats', 'trip',
3599				'security', 'setup', 'share-alt', 'sign-out', 'split', 'stripe', 'stripe-s', 'switch_off', 'switch_on', 'tools', 'unlink', 'uparrow', 'user', 'vcard', 'wrench',
3600				'github', 'jabber', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'youtube', 'google-plus-g', 'whatsapp',
3601				'chevron-left', 'chevron-right', 'chevron-down', 'chevron-top', 'commercial', 'companies',
3602				'generic', 'home', 'hrm', 'members', 'products', 'invoicing',
3603				'partnership', 'payment', 'pencil-ruler', 'preview', 'project', 'projectpub', 'projecttask', 'question', 'refresh', 'region',
3604				'salary', 'shipment', 'state', 'supplier_invoice', 'supplier_invoicea', 'supplier_invoicer', 'supplier_invoiced',
3605				'technic', 'ticket',
3606				'error', 'warning',
3607				'recent', 'reception', 'recruitmentcandidature', 'recruitmentjobposition', 'resource',
3608				'shapes', 'supplier', 'supplier_proposal', 'supplier_order', 'supplier_invoice',
3609				'timespent', 'title_setup', 'title_accountancy', 'title_bank', 'title_hrm', 'title_agenda',
3610				'uncheck', 'user-cog', 'website', 'workstation',
3611				'conferenceorbooth', 'eventorganization'
3612			))) {
3613			$fakey = $pictowithouttext;
3614			$facolor = '';
3615			$fasize = '';
3616			$fa = 'fas';
3617			if (in_array($pictowithouttext, array('clock', 'establishment', 'generic', 'minus-square', 'object_generic', 'pdf', 'plus-square', 'timespent', 'note', 'off', 'on', 'object_bookmark', 'bookmark', 'vcard'))) {
3618				$fa = 'far';
3619			}
3620			if (in_array($pictowithouttext, array('black-tie', 'github', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'stripe', 'stripe-s', 'youtube', 'google-plus-g', 'whatsapp'))) {
3621				$fa = 'fab';
3622			}
3623
3624			$arrayconvpictotofa = array(
3625				'account'=>'university', 'accountline'=>'receipt', 'accountancy'=>'search-dollar', 'action'=>'calendar-alt', 'add'=>'plus-circle', 'address'=> 'address-book', 'asset'=>'money-check-alt', 'autofill'=>'fill',
3626				'bank_account'=>'university',
3627				'bill'=>'file-invoice-dollar', 'billa'=>'file-excel', 'billr'=>'file-invoice-dollar', 'billd'=>'file-medical',
3628				'supplier_invoice'=>'file-invoice-dollar', 'supplier_invoicea'=>'file-excel', 'supplier_invoicer'=>'file-invoice-dollar', 'supplier_invoiced'=>'file-medical',
3629				'bom'=>'shapes',
3630				'chart'=>'chart-line', 'company'=>'building', 'contact'=>'address-book', 'contract'=>'suitcase', 'collab'=>'people-arrows', 'conversation'=>'comments', 'country'=>'globe-americas', 'cron'=>'business-time',
3631				'donation'=>'file-alt', 'dynamicprice'=>'hand-holding-usd',
3632				'setup'=>'cog', 'companies'=>'building', 'products'=>'cube', 'commercial'=>'suitcase', 'invoicing'=>'coins',
3633				'accounting'=>'search-dollar', 'category'=>'tag', 'dollyrevert'=>'dolly',
3634				'generate'=>'plus-square', 'hrm'=>'user-tie', 'incoterm'=>'truck-loading',
3635				'margin'=>'calculator', 'members'=>'user-friends', 'ticket'=>'ticket-alt', 'globe'=>'external-link-alt', 'lot'=>'barcode',
3636				'email'=>'at', 'establishment'=>'building',
3637				'edit'=>'pencil-alt', 'graph'=>'chart-line', 'grip_title'=>'arrows-alt', 'grip'=>'arrows-alt', 'help'=>'question-circle',
3638				'generic'=>'file', 'holiday'=>'umbrella-beach',
3639				'info'=>'info-circle', 'inventory'=>'boxes', 'intracommreport'=>'globe-europe', 'knowledgemanagement'=>'ticket-alt', 'label'=>'layer-group', 'loan'=>'money-bill-alt',
3640				'member'=>'user-alt', 'meeting'=>'chalkboard-teacher', 'mrp'=>'cubes', 'next'=>'arrow-alt-circle-right',
3641				'trip'=>'wallet', 'expensereport'=>'wallet', 'group'=>'users', 'movement'=>'people-carry',
3642				'sign-out'=>'sign-out-alt',
3643				'switch_off'=>'toggle-off', 'switch_on'=>'toggle-on', 'check'=>'check', 'bookmark'=>'star', 'bookmark'=>'star',
3644				'bank'=>'university', 'close_title'=>'times', 'delete'=>'trash', 'edit'=>'pencil-alt', 'filter'=>'filter',
3645				'list-alt'=>'list-alt', 'calendar'=>'calendar-alt', 'calendarmonth'=>'calendar-alt', 'calendarweek'=>'calendar-week', 'calendarmonth'=>'calendar-alt', 'calendarday'=>'calendar-day', 'calendarperuser'=>'table',
3646				'intervention'=>'ambulance', 'invoice'=>'file-invoice-dollar', 'multicurrency'=>'dollar-sign', 'order'=>'file-invoice',
3647				'error'=>'exclamation-triangle', 'warning'=>'exclamation-triangle',
3648				'other'=>'square',
3649				'playdisabled'=>'play', 'pdf'=>'file-pdf',  'poll'=>'check-double', 'pos'=>'cash-register', 'preview'=>'binoculars', 'project'=>'project-diagram', 'projectpub'=>'project-diagram', 'projecttask'=>'tasks', 'propal'=>'file-signature',
3650				'partnership'=>'handshake', 'payment'=>'money-check-alt', 'phoning'=>'phone', 'phoning_mobile'=>'mobile-alt', 'phoning_fax'=>'fax', 'previous'=>'arrow-alt-circle-left', 'printer'=>'print', 'product'=>'cube', 'service'=>'concierge-bell',
3651				'recent' => 'question', 'reception'=>'dolly', 'recruitmentjobposition'=>'id-card-alt', 'recruitmentcandidature'=>'id-badge',
3652				'resize'=>'crop', 'supplier_order'=>'dol-order_supplier', 'supplier_proposal'=>'file-signature',
3653				'refresh'=>'redo', 'region'=>'map-marked', 'resource'=>'laptop-house',
3654				'state'=>'map-marked-alt', 'security'=>'key', 'salary'=>'wallet', 'shipment'=>'dolly', 'stock'=>'box-open', 'stats' => 'chart-bar', 'split'=>'code-branch', 'stripe'=>'stripe-s',
3655				'supplier'=>'building', 'supplier_invoice'=>'file-invoice-dollar', 'technic'=>'cogs', 'ticket'=>'ticket-alt',
3656				'timespent'=>'clock', 'title_setup'=>'tools', 'title_accountancy'=>'money-check-alt', 'title_bank'=>'university', 'title_hrm'=>'umbrella-beach',
3657				'title_agenda'=>'calendar-alt',
3658				'uncheck'=>'times', 'uparrow'=>'share', 'vcard'=>'address-card',
3659				'jabber'=>'comment-o',
3660				'website'=>'globe-americas', 'workstation'=>'pallet',
3661				'conferenceorbooth'=>'chalkboard-teacher', 'eventorganization'=>'project-diagram'
3662			);
3663			if ($pictowithouttext == 'off') {
3664				$fakey = 'fa-square';
3665				$fasize = '1.3em';
3666			} elseif ($pictowithouttext == 'on') {
3667				$fakey = 'fa-check-square';
3668				$fasize = '1.3em';
3669			} elseif ($pictowithouttext == 'listlight') {
3670				$fakey = 'fa-download';
3671				$marginleftonlyshort = 1;
3672			} elseif ($pictowithouttext == 'printer') {
3673				$fakey = 'fa-print';
3674				$fasize = '1.2em';
3675			} elseif ($pictowithouttext == 'note') {
3676				$fakey = 'fa-sticky-note';
3677				$marginleftonlyshort = 1;
3678			} elseif (in_array($pictowithouttext, array('1uparrow', '1downarrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected'))) {
3679				$convertarray = array('1uparrow'=>'caret-up', '1downarrow'=>'caret-down', '1leftarrow'=>'caret-left', '1rightarrow'=>'caret-right', '1uparrow_selected'=>'caret-up', '1downarrow_selected'=>'caret-down', '1leftarrow_selected'=>'caret-left', '1rightarrow_selected'=>'caret-right');
3680				$fakey = 'fa-'.$convertarray[$pictowithouttext];
3681				if (preg_match('/selected/', $pictowithouttext)) {
3682					$facolor = '#888';
3683				}
3684				$marginleftonlyshort = 1;
3685			} elseif (!empty($arrayconvpictotofa[$pictowithouttext])) {
3686				$fakey = 'fa-'.$arrayconvpictotofa[$pictowithouttext];
3687			} else {
3688				$fakey = 'fa-'.$pictowithouttext;
3689			}
3690
3691			if (in_array($pictowithouttext, array('dollyrevert', 'member', 'members', 'contract', 'group', 'resource', 'shipment'))) {
3692				$morecss .= ' em092';
3693			}
3694			if (in_array($pictowithouttext, array('conferenceorbooth', 'collab', 'eventorganization', 'holiday', 'info', 'project', 'workstation'))) {
3695				$morecss .= ' em088';
3696			}
3697			if (in_array($pictowithouttext, array('asset', 'intervention', 'payment', 'loan', 'partnership', 'stock', 'technic'))) {
3698				$morecss .= ' em080';
3699			}
3700
3701			// Define $marginleftonlyshort
3702			$arrayconvpictotomarginleftonly = array(
3703				'bank', 'check', 'delete', 'generic', 'grip', 'grip_title', 'jabber',
3704				'grip_title', 'grip', 'listlight', 'note', 'on', 'off', 'playdisabled', 'printer', 'resize', 'sign-out', 'stats', 'switch_on', 'switch_off',
3705				'uparrow', '1uparrow', '1downarrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected'
3706			);
3707			if (!isset($arrayconvpictotomarginleftonly[$pictowithouttext])) {
3708				$marginleftonlyshort = 0;
3709			}
3710
3711			// Add CSS
3712			$arrayconvpictotomorcess = array(
3713				'action'=>'infobox-action', 'account'=>'infobox-bank_account', 'accountline'=>'infobox-bank_account', 'accountancy'=>'infobox-bank_account', 'asset'=>'infobox-bank_account',
3714				'bank_account'=>'bg-infobox-bank_account',
3715				'bill'=>'infobox-commande', 'billa'=>'infobox-commande', 'billr'=>'infobox-commande', 'billd'=>'infobox-commande',
3716				'margin'=>'infobox-bank_account', 'conferenceorbooth'=>'infobox-project',
3717				'cash-register'=>'infobox-bank_account', 'contract'=>'infobox-contrat', 'check'=>'font-status4', 'collab'=>'infobox-action', 'conversation'=>'infobox-contrat',
3718				'donation'=>'infobox-commande', 'dolly'=>'infobox-commande',  'dollyrevert'=>'flip infobox-order_supplier',
3719				'ecm'=>'infobox-action', 'eventorganization'=>'infobox-project',
3720				'hrm'=>'infobox-adherent', 'group'=>'infobox-adherent', 'intervention'=>'infobox-contrat',
3721				'incoterm'=>'infobox-supplier_proposal',
3722				'multicurrency'=>'infobox-bank_account',
3723				'members'=>'infobox-adherent', 'member'=>'infobox-adherent', 'money-bill-alt'=>'infobox-bank_account',
3724				'order'=>'infobox-commande',
3725				'user'=>'infobox-adherent', 'users'=>'infobox-adherent',
3726				'error'=>'pictoerror', 'warning'=>'pictowarning', 'switch_on'=>'font-status4',
3727				'holiday'=>'infobox-holiday', 'info'=>'opacityhigh', 'invoice'=>'infobox-commande',
3728				'knowledgemanagement'=>'infobox-contrat rotate90', 'loan'=>'infobox-bank_account',
3729				'payment'=>'infobox-bank_account', 'poll'=>'infobox-adherent', 'pos'=>'infobox-bank_account', 'project'=>'infobox-project', 'projecttask'=>'infobox-project', 'propal'=>'infobox-propal',
3730				'reception'=>'flip', 'recruitmentjobposition'=>'infobox-adherent', 'recruitmentcandidature'=>'infobox-adherent',
3731				'resource'=>'infobox-action',
3732				'salary'=>'infobox-bank_account', 'shipment'=>'infobox-commande', 'supplier_invoice'=>'infobox-order_supplier', 'supplier_invoicea'=>'infobox-order_supplier', 'supplier_invoiced'=>'infobox-order_supplier',
3733				'supplier'=>'infobox-order_supplier', 'supplier_order'=>'infobox-order_supplier', 'supplier_proposal'=>'infobox-supplier_proposal',
3734				'ticket'=>'infobox-contrat', 'title_accountancy'=>'infobox-bank_account', 'title_hrm'=>'infobox-holiday', 'expensereport'=>'infobox-expensereport', 'trip'=>'infobox-expensereport', 'title_agenda'=>'infobox-action',
3735				//'title_setup'=>'infobox-action', 'tools'=>'infobox-action',
3736				'list-alt'=>'imgforviewmode', 'calendar'=>'imgforviewmode', 'calendarweek'=>'imgforviewmode', 'calendarmonth'=>'imgforviewmode', 'calendarday'=>'imgforviewmode', 'calendarperuser'=>'imgforviewmode'
3737			);
3738			if (!empty($arrayconvpictotomorcess[$pictowithouttext])) {
3739				$morecss .= ($morecss ? ' ' : '').$arrayconvpictotomorcess[$pictowithouttext];
3740			}
3741
3742			// Define $color
3743			$arrayconvpictotocolor = array(
3744				'address'=>'#6c6aa8', 'building'=>'#6c6aa8', 'bom'=>'#a69944',
3745				'cog'=>'#999', 'companies'=>'#6c6aa8', 'company'=>'#6c6aa8', 'contact'=>'#6c6aa8', 'cron'=>'#555',
3746				'dynamicprice'=>'#a69944',
3747				'edit'=>'#444', 'note'=>'#999', 'error'=>'', 'help'=>'#bbb', 'listlight'=>'#999', 'language'=>'#555',
3748				//'dolly'=>'#a69944', 'dollyrevert'=>'#a69944',
3749				'lot'=>'#a69944',
3750				'map-marker-alt'=>'#aaa', 'mrp'=>'#a69944', 'product'=>'#a69944', 'service'=>'#a69944', 'inventory'=>'#a69944', 'stock'=>'#a69944', 'movement'=>'#a69944',
3751				'other'=>'#ddd',
3752				'partnership'=>'#6c6aa8', 'playdisabled'=>'#ccc', 'printer'=>'#444', 'projectpub'=>'#986c6a', 'reception'=>'#a69944', 'resize'=>'#444', 'rss'=>'#cba',
3753				//'shipment'=>'#a69944',
3754				'security'=>'#999', 'stats'=>'#444', 'switch_off'=>'#999', 'technic'=>'#999', 'timespent'=>'#555',
3755				'uncheck'=>'#800', 'uparrow'=>'#555', 'user-cog'=>'#999', 'country'=>'#aaa', 'globe-americas'=>'#aaa', 'region'=>'#aaa', 'state'=>'#aaa',
3756				'website'=>'#304', 'workstation'=>'#a69944'
3757			);
3758			if (isset($arrayconvpictotocolor[$pictowithouttext])) {
3759				$facolor = $arrayconvpictotocolor[$pictowithouttext];
3760			}
3761
3762			// This snippet only needed since function img_edit accepts only one additional parameter: no separate one for css only.
3763			// class/style need to be extracted to avoid duplicate class/style validation errors when $moreatt is added to the end of the attributes.
3764			$morestyle = '';
3765			$reg = array();
3766			if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
3767				$morecss .= ($morecss ? ' ' : '').$reg[1];
3768				$moreatt = str_replace('class="'.$reg[1].'"', '', $moreatt);
3769			}
3770			if (preg_match('/style="([^"]+)"/', $moreatt, $reg)) {
3771				$morestyle = $reg[1];
3772				$moreatt = str_replace('style="'.$reg[1].'"', '', $moreatt);
3773			}
3774			$moreatt = trim($moreatt);
3775
3776			$enabledisablehtml = '<span class="'.$fa.' '.$fakey.($marginleftonlyshort ? ($marginleftonlyshort == 1 ? ' marginleftonlyshort' : ' marginleftonly') : '');
3777			$enabledisablehtml .= ($morecss ? ' '.$morecss : '').'" style="'.($fasize ? ('font-size: '.$fasize.';') : '').($facolor ? (' color: '.$facolor.';') : '').($morestyle ? ' '.$morestyle : '').'"'.(($notitle || empty($titlealt)) ? '' : ' title="'.dol_escape_htmltag($titlealt).'"').($moreatt ? ' '.$moreatt : '').'>';
3778			/*if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
3779				$enabledisablehtml .= $titlealt;
3780			}*/
3781			$enabledisablehtml .= '</span>';
3782
3783			return $enabledisablehtml;
3784		}
3785
3786		if (!empty($conf->global->MAIN_OVERWRITE_THEME_PATH)) {
3787			$path = $conf->global->MAIN_OVERWRITE_THEME_PATH.'/theme/'.$theme; // If the theme does not have the same name as the module
3788		} elseif (!empty($conf->global->MAIN_OVERWRITE_THEME_RES)) {
3789			$path = $conf->global->MAIN_OVERWRITE_THEME_RES.'/theme/'.$conf->global->MAIN_OVERWRITE_THEME_RES; // To allow an external module to overwrite image resources whatever is activated theme
3790		} elseif (!empty($conf->modules_parts['theme']) && array_key_exists($theme, $conf->modules_parts['theme'])) {
3791			$path = $theme.'/theme/'.$theme; // If the theme have the same name as the module
3792		}
3793
3794		// If we ask an image into $url/$mymodule/img (instead of default path)
3795		$regs = array();
3796		if (preg_match('/^([^@]+)@([^@]+)$/i', $picto, $regs)) {
3797			$picto = $regs[1];
3798			$path = $regs[2]; // $path is $mymodule
3799		}
3800
3801		// Clean parameters
3802		if (!preg_match('/(\.png|\.gif|\.svg)$/i', $picto)) {
3803			$picto .= '.png';
3804		}
3805		// If alt path are defined, define url where img file is, according to physical path
3806		// ex: array(["main"]=>"/home/maindir/htdocs", ["alt0"]=>"/home/moddir0/htdocs", ...)
3807		foreach ($conf->file->dol_document_root as $type => $dirroot) {
3808			if ($type == 'main') {
3809				continue;
3810			}
3811			// This need a lot of time, that's why enabling alternative dir like "custom" dir is not recommanded
3812			if (file_exists($dirroot.'/'.$path.'/img/'.$picto)) {
3813				$url = DOL_URL_ROOT.$conf->file->dol_url_root[$type];
3814				break;
3815			}
3816		}
3817
3818		// $url is '' or '/custom', $path is current theme or
3819		$fullpathpicto = $url.'/'.$path.'/img/'.$picto;
3820	}
3821
3822	if ($srconly) {
3823		return $fullpathpicto;
3824	}
3825		// tag title is used for tooltip on <a>, tag alt can be used with very simple text on image for blind people
3826	return '<img src="'.$fullpathpicto.'" alt="'.dol_escape_htmltag($alt).'"'.(($notitle || empty($titlealt)) ? '' : ' title="'.dol_escape_htmltag($titlealt).'"').($moreatt ? ' '.$moreatt.($morecss ? ' class="'.$morecss.'"' : '') : ' class="inline-block'.($morecss ? ' '.$morecss : '').'"').'>'; // Alt is used for accessibility, title for popup
3827}
3828
3829/**
3830 *	Show a picto called object_picto (generic function)
3831 *
3832 *	@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.
3833 *	@param	string	$picto				Name of image to show object_picto (example: user, group, action, bill, contract, propal, product, ...)
3834 *										For external modules use imagename@mymodule to search into directory "img" of module.
3835 *	@param	string	$moreatt			Add more attribute on img tag (ie: class="datecallink")
3836 *	@param	int		$pictoisfullpath	If 1, image path is a full path
3837 *	@param	int		$srconly			Return only content of the src attribute of img.
3838 *  @param	int		$notitle			1=Disable tag title. Use it if you add js tooltip, to avoid duplicate tooltip.
3839 *	@return	string						Return img tag
3840 *	@see	img_picto(), img_picto_common()
3841 */
3842function img_object($titlealt, $picto, $moreatt = '', $pictoisfullpath = false, $srconly = 0, $notitle = 0)
3843{
3844	if (strpos($picto, '^') === 0) {
3845		return img_picto($titlealt, str_replace('^', '', $picto), $moreatt, $pictoisfullpath, $srconly, $notitle);
3846	} else {
3847		return img_picto($titlealt, 'object_'.$picto, $moreatt, $pictoisfullpath, $srconly, $notitle);
3848	}
3849}
3850
3851/**
3852 *	Show weather picto
3853 *
3854 *	@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.
3855 *	@param      string|int	$picto       		Name of image file to show (If no extension provided, we use '.png'). Image must be stored into htdocs/theme/common directory. Or level of meteo image (0-4).
3856 *	@param		string		$moreatt			Add more attribute on img tag
3857 *	@param		int			$pictoisfullpath	If 1, image path is a full path
3858 *  @param      string      $morecss            More CSS
3859 *	@return     string      					Return img tag
3860 *  @see        img_object(), img_picto()
3861 */
3862function img_weather($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $morecss = '')
3863{
3864	global $conf;
3865
3866	if (is_numeric($picto)) {
3867		//$leveltopicto = array(0=>'weather-clear.png', 1=>'weather-few-clouds.png', 2=>'weather-clouds.png', 3=>'weather-many-clouds.png', 4=>'weather-storm.png');
3868		//$picto = $leveltopicto[$picto];
3869		return '<i class="fa fa-weather-level'.$picto.'"></i>';
3870	} elseif (!preg_match('/(\.png|\.gif)$/i', $picto)) {
3871		$picto .= '.png';
3872	}
3873
3874	$path = DOL_URL_ROOT.'/theme/'.$conf->theme.'/img/weather/'.$picto;
3875
3876	return img_picto($titlealt, $path, $moreatt, 1, 0, 0, '', $morecss);
3877}
3878
3879/**
3880 *	Show picto (generic function)
3881 *
3882 *	@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.
3883 *	@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.
3884 *	@param		string		$moreatt			Add more attribute on img tag
3885 *	@param		int			$pictoisfullpath	If 1, image path is a full path
3886 *	@return     string      					Return img tag
3887 *  @see        img_object(), img_picto()
3888 */
3889function img_picto_common($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0)
3890{
3891	global $conf;
3892
3893	if (!preg_match('/(\.png|\.gif)$/i', $picto)) {
3894		$picto .= '.png';
3895	}
3896
3897	if ($pictoisfullpath) {
3898		$path = $picto;
3899	} else {
3900		$path = DOL_URL_ROOT.'/theme/common/'.$picto;
3901
3902		if (!empty($conf->global->MAIN_MODULE_CAN_OVERWRITE_COMMONICONS)) {
3903			$themepath = DOL_DOCUMENT_ROOT.'/theme/'.$conf->theme.'/img/'.$picto;
3904
3905			if (file_exists($themepath)) {
3906				$path = $themepath;
3907			}
3908		}
3909	}
3910
3911	return img_picto($titlealt, $path, $moreatt, 1);
3912}
3913
3914/**
3915 *	Show logo action
3916 *
3917 *	@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.
3918 *	@param  string		$numaction   	Action id or code to show
3919 *	@param 	string		$picto      	Name of image file to show ('filenew', ...)
3920 *                                      If no extension provided, we use '.png'. Image must be stored into theme/xxx/img directory.
3921 *                                      Example: picto.png                  if picto.png is stored into htdocs/theme/mytheme/img
3922 *                                      Example: picto.png@mymodule         if picto.png is stored into htdocs/mymodule/img
3923 *                                      Example: /mydir/mysubdir/picto.png  if picto.png is stored into htdocs/mydir/mysubdir (pictoisfullpath must be set to 1)
3924 *	@return string      				Return an img tag
3925 */
3926function img_action($titlealt, $numaction, $picto = '')
3927{
3928	global $langs;
3929
3930	if (empty($titlealt) || $titlealt == 'default') {
3931		if ($numaction == '-1' || $numaction == 'ST_NO') {
3932			$numaction = -1;
3933			$titlealt = $langs->transnoentitiesnoconv('ChangeDoNotContact');
3934		} elseif ($numaction == '0' || $numaction == 'ST_NEVER') {
3935			$numaction = 0;
3936			$titlealt = $langs->transnoentitiesnoconv('ChangeNeverContacted');
3937		} elseif ($numaction == '1' || $numaction == 'ST_TODO') {
3938			$numaction = 1;
3939			$titlealt = $langs->transnoentitiesnoconv('ChangeToContact');
3940		} elseif ($numaction == '2' || $numaction == 'ST_PEND') {
3941			$numaction = 2;
3942			$titlealt = $langs->transnoentitiesnoconv('ChangeContactInProcess');
3943		} elseif ($numaction == '3' || $numaction == 'ST_DONE') {
3944			$numaction = 3;
3945			$titlealt = $langs->transnoentitiesnoconv('ChangeContactDone');
3946		} else {
3947			$titlealt = $langs->transnoentitiesnoconv('ChangeStatus '.$numaction);
3948			$numaction = 0;
3949		}
3950	}
3951	if (!is_numeric($numaction)) {
3952		$numaction = 0;
3953	}
3954
3955	return img_picto($titlealt, !empty($picto) ? $picto : 'stcomm'.$numaction.'.png');
3956}
3957
3958/**
3959 *  Show pdf logo
3960 *
3961 *  @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.
3962 *  @param  int		    $size       Taille de l'icone : 3 = 16x16px , 2 = 14x14px
3963 *  @return string      			Retourne tag img
3964 */
3965function img_pdf($titlealt = 'default', $size = 3)
3966{
3967	global $langs;
3968
3969	if ($titlealt == 'default') {
3970		$titlealt = $langs->trans('Show');
3971	}
3972
3973	return img_picto($titlealt, 'pdf'.$size.'.png');
3974}
3975
3976/**
3977 *	Show logo +
3978 *
3979 *	@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.
3980 *	@param  string	$other      Add more attributes on img
3981 *	@return string      		Return tag img
3982 */
3983function img_edit_add($titlealt = 'default', $other = '')
3984{
3985	global $langs;
3986
3987	if ($titlealt == 'default') {
3988		$titlealt = $langs->trans('Add');
3989	}
3990
3991	return img_picto($titlealt, 'edit_add.png', $other);
3992}
3993/**
3994 *	Show logo -
3995 *
3996 *	@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.
3997 *	@param  string	$other      Add more attributes on img
3998 *	@return string      		Return tag img
3999 */
4000function img_edit_remove($titlealt = 'default', $other = '')
4001{
4002	global $langs;
4003
4004	if ($titlealt == 'default') {
4005		$titlealt = $langs->trans('Remove');
4006	}
4007
4008	return img_picto($titlealt, 'edit_remove.png', $other);
4009}
4010
4011/**
4012 *	Show logo editer/modifier fiche
4013 *
4014 *	@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.
4015 *	@param  integer	$float      If you have to put the style "float: right"
4016 *	@param  string	$other		Add more attributes on img
4017 *	@return string      		Return tag img
4018 */
4019function img_edit($titlealt = 'default', $float = 0, $other = '')
4020{
4021	global $langs;
4022
4023	if ($titlealt == 'default') {
4024		$titlealt = $langs->trans('Modify');
4025	}
4026
4027	return img_picto($titlealt, 'edit.png', ($float ? 'style="float: '.($langs->tab_translate["DIRECTION"] == 'rtl' ? 'left' : 'right').'"' : "").($other ? ' '.$other : ''));
4028}
4029
4030/**
4031 *	Show logo view card
4032 *
4033 *	@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.
4034 *	@param  integer	$float      If you have to put the style "float: right"
4035 *	@param  string	$other		Add more attributes on img
4036 *	@return string      		Return tag img
4037 */
4038function img_view($titlealt = 'default', $float = 0, $other = 'class="valignmiddle"')
4039{
4040	global $langs;
4041
4042	if ($titlealt == 'default') {
4043		$titlealt = $langs->trans('View');
4044	}
4045
4046	$moreatt = ($float ? 'style="float: right" ' : '').$other;
4047
4048	return img_picto($titlealt, 'view.png', $moreatt);
4049}
4050
4051/**
4052 *  Show delete logo
4053 *
4054 *  @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.
4055 *	@param  string	$other      Add more attributes on img
4056 *  @param	string	$morecss	More CSS
4057 *  @return string      		Retourne tag img
4058 */
4059function img_delete($titlealt = 'default', $other = 'class="pictodelete"', $morecss = '')
4060{
4061	global $langs;
4062
4063	if ($titlealt == 'default') {
4064		$titlealt = $langs->trans('Delete');
4065	}
4066
4067	return img_picto($titlealt, 'delete.png', $other, false, 0, 0, '', $morecss);
4068}
4069
4070/**
4071 *  Show printer logo
4072 *
4073 *  @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.
4074 *  @param  string  $other      Add more attributes on img
4075 *  @return string              Retourne tag img
4076 */
4077function img_printer($titlealt = "default", $other = '')
4078{
4079	global $langs;
4080	if ($titlealt == "default") {
4081		$titlealt = $langs->trans("Print");
4082	}
4083	return img_picto($titlealt, 'printer.png', $other);
4084}
4085
4086/**
4087 *  Show split logo
4088 *
4089 *  @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.
4090 *	@param  string	$other      Add more attributes on img
4091 *  @return string      		Retourne tag img
4092 */
4093function img_split($titlealt = 'default', $other = 'class="pictosplit"')
4094{
4095	global $langs;
4096
4097	if ($titlealt == 'default') {
4098		$titlealt = $langs->trans('Split');
4099	}
4100
4101	return img_picto($titlealt, 'split.png', $other);
4102}
4103
4104/**
4105 *	Show help logo with cursor "?"
4106 *
4107 * 	@param	int              	$usehelpcursor		1=Use help cursor, 2=Use click pointer cursor, 0=No specific cursor
4108 * 	@param	int|string	        $usealttitle		Text to use as alt title
4109 * 	@return string            	           			Return tag img
4110 */
4111function img_help($usehelpcursor = 1, $usealttitle = 1)
4112{
4113	global $langs;
4114
4115	if ($usealttitle) {
4116		if (is_string($usealttitle)) {
4117			$usealttitle = dol_escape_htmltag($usealttitle);
4118		} else {
4119			$usealttitle = $langs->trans('Info');
4120		}
4121	}
4122
4123	return img_picto($usealttitle, 'info.png', 'style="vertical-align: middle;'.($usehelpcursor == 1 ? ' cursor: help' : ($usehelpcursor == 2 ? ' cursor: pointer' : '')).'"');
4124}
4125
4126/**
4127 *	Show info logo
4128 *
4129 *	@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.
4130 *	@return string      		Return img tag
4131 */
4132function img_info($titlealt = 'default')
4133{
4134	global $langs;
4135
4136	if ($titlealt == 'default') {
4137		$titlealt = $langs->trans('Informations');
4138	}
4139
4140	return img_picto($titlealt, 'info.png', 'style="vertical-align: middle;"');
4141}
4142
4143/**
4144 *	Show warning logo
4145 *
4146 *	@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.
4147 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"'). If 1, add float: right. Can't be "class" attribute.
4148 *  @param	string  $morecss	Add more CSS
4149 *	@return string      		Return img tag
4150 */
4151function img_warning($titlealt = 'default', $moreatt = '', $morecss = 'pictowarning')
4152{
4153	global $langs;
4154
4155	if ($titlealt == 'default') {
4156		$titlealt = $langs->trans('Warning');
4157	}
4158
4159	//return '<div class="imglatecoin">'.img_picto($titlealt, 'warning_white.png', 'class="pictowarning valignmiddle"'.($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' '.$moreatt): '')).'</div>';
4160	return img_picto($titlealt, 'warning.png', 'class="'.$morecss.'"'.($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' '.$moreatt) : ''));
4161}
4162
4163/**
4164 *  Show error logo
4165 *
4166 *	@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.
4167 *	@return string      		Return img tag
4168 */
4169function img_error($titlealt = 'default')
4170{
4171	global $langs;
4172
4173	if ($titlealt == 'default') {
4174		$titlealt = $langs->trans('Error');
4175	}
4176
4177	return img_picto($titlealt, 'error.png');
4178}
4179
4180/**
4181 *	Show next logo
4182 *
4183 *	@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.
4184*	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
4185 *	@return string      		Return img tag
4186 */
4187function img_next($titlealt = 'default', $moreatt = '')
4188{
4189	global $langs;
4190
4191	if ($titlealt == 'default') {
4192		$titlealt = $langs->trans('Next');
4193	}
4194
4195	//return img_picto($titlealt, 'next.png', $moreatt);
4196	return '<span class="fa fa-chevron-right paddingright paddingleft" title="'.dol_escape_htmltag($titlealt).'"></span>';
4197}
4198
4199/**
4200 *	Show previous logo
4201 *
4202 *	@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.
4203 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
4204 *	@return string      		Return img tag
4205 */
4206function img_previous($titlealt = 'default', $moreatt = '')
4207{
4208	global $langs;
4209
4210	if ($titlealt == 'default') {
4211		$titlealt = $langs->trans('Previous');
4212	}
4213
4214	//return img_picto($titlealt, 'previous.png', $moreatt);
4215	return '<span class="fa fa-chevron-left paddingright paddingleft" title="'.dol_escape_htmltag($titlealt).'"></span>';
4216}
4217
4218/**
4219 *	Show down arrow logo
4220 *
4221 *	@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.
4222 *	@param  int		$selected   Selected
4223 *  @param	string	$moreclass	Add more CSS classes
4224 *	@return string      		Return img tag
4225 */
4226function img_down($titlealt = 'default', $selected = 0, $moreclass = '')
4227{
4228	global $langs;
4229
4230	if ($titlealt == 'default') {
4231		$titlealt = $langs->trans('Down');
4232	}
4233
4234	return img_picto($titlealt, ($selected ? '1downarrow_selected.png' : '1downarrow.png'), 'class="imgdown'.($moreclass ? " ".$moreclass : "").'"');
4235}
4236
4237/**
4238 *	Show top arrow logo
4239 *
4240 *	@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.
4241 *	@param  int		$selected	Selected
4242 *  @param	string	$moreclass	Add more CSS classes
4243 *	@return string      		Return img tag
4244 */
4245function img_up($titlealt = 'default', $selected = 0, $moreclass = '')
4246{
4247	global $langs;
4248
4249	if ($titlealt == 'default') {
4250		$titlealt = $langs->trans('Up');
4251	}
4252
4253	return img_picto($titlealt, ($selected ? '1uparrow_selected.png' : '1uparrow.png'), 'class="imgup'.($moreclass ? " ".$moreclass : "").'"');
4254}
4255
4256/**
4257 *	Show left arrow logo
4258 *
4259 *	@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.
4260 *	@param  int		$selected	Selected
4261 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
4262 *	@return string      		Return img tag
4263 */
4264function img_left($titlealt = 'default', $selected = 0, $moreatt = '')
4265{
4266	global $langs;
4267
4268	if ($titlealt == 'default') {
4269		$titlealt = $langs->trans('Left');
4270	}
4271
4272	return img_picto($titlealt, ($selected ? '1leftarrow_selected.png' : '1leftarrow.png'), $moreatt);
4273}
4274
4275/**
4276 *	Show right arrow logo
4277 *
4278 *	@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.
4279 *	@param  int		$selected	Selected
4280 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
4281 *	@return string      		Return img tag
4282 */
4283function img_right($titlealt = 'default', $selected = 0, $moreatt = '')
4284{
4285	global $langs;
4286
4287	if ($titlealt == 'default') {
4288		$titlealt = $langs->trans('Right');
4289	}
4290
4291	return img_picto($titlealt, ($selected ? '1rightarrow_selected.png' : '1rightarrow.png'), $moreatt);
4292}
4293
4294/**
4295 *	Show tick logo if allowed
4296 *
4297 *	@param	string	$allow		Allow
4298 *	@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.
4299 *	@return string      		Return img tag
4300 */
4301function img_allow($allow, $titlealt = 'default')
4302{
4303	global $langs;
4304
4305	if ($titlealt == 'default') {
4306		$titlealt = $langs->trans('Active');
4307	}
4308
4309	if ($allow == 1) {
4310		return img_picto($titlealt, 'tick.png');
4311	}
4312
4313	return '-';
4314}
4315
4316/**
4317 *	Return image of a credit card according to its brand name
4318 *
4319 *	@param  string	$brand		Brand name of credit card
4320 *  @param  string	$morecss	More CSS
4321 *	@return string     			Return img tag
4322 */
4323function img_credit_card($brand, $morecss = null)
4324{
4325	if (is_null($morecss)) {
4326		$morecss = 'fa-2x';
4327	}
4328
4329	if ($brand == 'visa' || $brand == 'Visa') {
4330		$brand = 'cc-visa';
4331	} elseif ($brand == 'mastercard' || $brand == 'MasterCard') {
4332		$brand = 'cc-mastercard';
4333	} elseif ($brand == 'amex' || $brand == 'American Express') {
4334		$brand = 'cc-amex';
4335	} elseif ($brand == 'discover' || $brand == 'Discover') {
4336		$brand = 'cc-discover';
4337	} elseif ($brand == 'jcb' || $brand == 'JCB') {
4338		$brand = 'cc-jcb';
4339	} elseif ($brand == 'diners' || $brand == 'Diners club') {
4340		$brand = 'cc-diners-club';
4341	} elseif (!in_array($brand, array('cc-visa', 'cc-mastercard', 'cc-amex', 'cc-discover', 'cc-jcb', 'cc-diners-club'))) {
4342		$brand = 'credit-card';
4343	}
4344
4345	return '<span class="fa fa-'.$brand.' fa-fw'.($morecss ? ' '.$morecss : '').'"></span>';
4346}
4347
4348/**
4349 *	Show MIME img of a file
4350 *
4351 *	@param	string	$file		Filename
4352 * 	@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.
4353 *  @param	string	$morecss	More css
4354 *	@return string     			Return img tag
4355 */
4356function img_mime($file, $titlealt = '', $morecss = '')
4357{
4358	require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
4359
4360	$mimetype = dol_mimetype($file, '', 1);
4361	$mimeimg = dol_mimetype($file, '', 2);
4362	$mimefa = dol_mimetype($file, '', 4);
4363
4364	if (empty($titlealt)) {
4365		$titlealt = 'Mime type: '.$mimetype;
4366	}
4367
4368	//return img_picto_common($titlealt, 'mime/'.$mimeimg, 'class="'.$morecss.'"');
4369	return '<i class="fa fa-'.$mimefa.' paddingright"'.($titlealt ? ' title="'.$titlealt.'"' : '').'></i>';
4370}
4371
4372
4373/**
4374 *  Show search logo
4375 *
4376 *  @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.
4377 *	@param  string	$other      Add more attributes on img
4378 *  @return string      		Retourne tag img
4379 */
4380function img_search($titlealt = 'default', $other = '')
4381{
4382	global $conf, $langs;
4383
4384	if ($titlealt == 'default') {
4385		$titlealt = $langs->trans('Search');
4386	}
4387
4388	$img = img_picto($titlealt, 'search.png', $other, false, 1);
4389
4390	$input = '<input type="image" class="liste_titre" name="button_search" src="'.$img.'" ';
4391	$input .= 'value="'.dol_escape_htmltag($titlealt).'" title="'.dol_escape_htmltag($titlealt).'" >';
4392
4393	return $input;
4394}
4395
4396/**
4397 *  Show search logo
4398 *
4399 *  @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.
4400 *	@param  string	$other      Add more attributes on img
4401 *  @return string      		Retourne tag img
4402 */
4403function img_searchclear($titlealt = 'default', $other = '')
4404{
4405	global $conf, $langs;
4406
4407	if ($titlealt == 'default') {
4408		$titlealt = $langs->trans('Search');
4409	}
4410
4411	$img = img_picto($titlealt, 'searchclear.png', $other, false, 1);
4412
4413	$input = '<input type="image" class="liste_titre" name="button_removefilter" src="'.$img.'" ';
4414	$input .= 'value="'.dol_escape_htmltag($titlealt).'" title="'.dol_escape_htmltag($titlealt).'" >';
4415
4416	return $input;
4417}
4418
4419/**
4420 *	Show information for admin users or standard users
4421 *
4422 *	@param	string	$text				Text info
4423 *	@param  integer	$infoonimgalt		Info is shown only on alt of star picto, otherwise it is show on output after the star picto
4424 *	@param	int		$nodiv				No div
4425 *  @param  string  $admin      	    '1'=Info for admin users. '0'=Info for standard users (change only the look), 'error', 'warning', 'xxx'=Other
4426 *  @param	string	$morecss			More CSS ('', 'warning', 'error')
4427 *  @param	string	$textfordropdown	Show a text to click to dropdown the info box.
4428 *	@return	string						String with info text
4429 */
4430function info_admin($text, $infoonimgalt = 0, $nodiv = 0, $admin = '1', $morecss = '', $textfordropdown = '')
4431{
4432	global $conf, $langs;
4433
4434	if ($infoonimgalt) {
4435		$result = img_picto($text, 'info', 'class="hideonsmartphone'.($morecss ? ' '.$morecss : '').'"');
4436	} else {
4437		if (empty($conf->use_javascript_ajax)) {
4438			$textfordropdown = '';
4439		}
4440
4441		$class = (empty($admin) ? 'undefined' : ($admin == '1' ? 'info' : $admin));
4442		$result = ($nodiv ? '' : '<div class="'.$class.' hideonsmartphone'.($morecss ? ' '.$morecss : '').($textfordropdown ? ' hidden' : '').'">').'<span class="fa fa-info-circle" title="'.dol_escape_htmltag($admin ? $langs->trans('InfoAdmin') : $langs->trans('Note')).'"></span> '.$text.($nodiv ? '' : '</div>');
4443
4444		if ($textfordropdown) {
4445			$tmpresult .= '<span class="'.$class.'text opacitymedium cursorpointer">'.$langs->trans($textfordropdown).' '.img_picto($langs->trans($textfordropdown), '1downarrow').'</span>';
4446			$tmpresult .= '<script type="text/javascript" language="javascript">
4447				jQuery(document).ready(function() {
4448					jQuery(".'.$class.'text").click(function() {
4449						console.log("toggle text");
4450						jQuery(".'.$class.'").toggle();
4451					});
4452				});
4453				</script>';
4454
4455			$result = $tmpresult.$result;
4456		}
4457	}
4458
4459	return $result;
4460}
4461
4462
4463/**
4464 *  Displays error message system with all the information to facilitate the diagnosis and the escalation of the bugs.
4465 *  This function must be called when a blocking technical error is encountered.
4466 *  However, one must try to call it only within php pages, classes must return their error through their property "error".
4467 *
4468 *	@param	 	DoliDB          $db      	Database handler
4469 *	@param  	string|string[] $error		String or array of errors strings to show
4470 *  @param		array           $errors		Array of errors
4471 *	@return 	void
4472 *  @see    	dol_htmloutput_errors()
4473 */
4474function dol_print_error($db = '', $error = '', $errors = null)
4475{
4476	global $conf, $langs, $argv;
4477	global $dolibarr_main_prod;
4478
4479	$out = '';
4480	$syslog = '';
4481
4482	// If error occurs before the $lang object was loaded
4483	if (!$langs) {
4484		require_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
4485		$langs = new Translate('', $conf);
4486		$langs->load("main");
4487	}
4488
4489	// Load translation files required by the error messages
4490	$langs->loadLangs(array('main', 'errors'));
4491
4492	if ($_SERVER['DOCUMENT_ROOT']) {    // Mode web
4493		$out .= $langs->trans("DolibarrHasDetectedError").".<br>\n";
4494		if (!empty($conf->global->MAIN_FEATURES_LEVEL)) {
4495			$out .= "You use an experimental or develop level of features, so please do NOT report any bugs or vulnerability, except if problem is confirmed after moving option MAIN_FEATURES_LEVEL back to 0.<br>\n";
4496		}
4497		$out .= $langs->trans("InformationToHelpDiagnose").":<br>\n";
4498
4499		$out .= "<b>".$langs->trans("Date").":</b> ".dol_print_date(time(), 'dayhourlog')."<br>\n";
4500		$out .= "<b>".$langs->trans("Dolibarr").":</b> ".DOL_VERSION." - https://www.dolibarr.org<br>\n";
4501		if (isset($conf->global->MAIN_FEATURES_LEVEL)) {
4502			$out .= "<b>".$langs->trans("LevelOfFeature").":</b> ".dol_htmlentities($conf->global->MAIN_FEATURES_LEVEL, ENT_COMPAT)."<br>\n";
4503		}
4504		if (function_exists("phpversion")) {
4505			$out .= "<b>".$langs->trans("PHP").":</b> ".phpversion()."<br>\n";
4506		}
4507		$out .= "<b>".$langs->trans("Server").":</b> ".(isset($_SERVER["SERVER_SOFTWARE"]) ? dol_htmlentities($_SERVER["SERVER_SOFTWARE"], ENT_COMPAT) : '')."<br>\n";
4508		if (function_exists("php_uname")) {
4509			$out .= "<b>".$langs->trans("OS").":</b> ".php_uname()."<br>\n";
4510		}
4511		$out .= "<b>".$langs->trans("UserAgent").":</b> ".(isset($_SERVER["HTTP_USER_AGENT"]) ? dol_htmlentities($_SERVER["HTTP_USER_AGENT"], ENT_COMPAT) : '')."<br>\n";
4512		$out .= "<br>\n";
4513		$out .= "<b>".$langs->trans("RequestedUrl").":</b> ".dol_htmlentities($_SERVER["REQUEST_URI"], ENT_COMPAT)."<br>\n";
4514		$out .= "<b>".$langs->trans("Referer").":</b> ".(isset($_SERVER["HTTP_REFERER"]) ? dol_htmlentities($_SERVER["HTTP_REFERER"], ENT_COMPAT) : '')."<br>\n";
4515		$out .= "<b>".$langs->trans("MenuManager").":</b> ".(isset($conf->standard_menu) ? dol_htmlentities($conf->standard_menu, ENT_COMPAT) : '')."<br>\n";
4516		$out .= "<br>\n";
4517		$syslog .= "url=".dol_escape_htmltag($_SERVER["REQUEST_URI"]);
4518		$syslog .= ", query_string=".dol_escape_htmltag($_SERVER["QUERY_STRING"]);
4519	} else // Mode CLI
4520	{
4521		$out .= '> '.$langs->transnoentities("ErrorInternalErrorDetected").":\n".$argv[0]."\n";
4522		$syslog .= "pid=".dol_getmypid();
4523	}
4524
4525	if (!empty($conf->modules)) {
4526		$out .= "<b>".$langs->trans("Modules").":</b> ".join(', ', $conf->modules)."<br>\n";
4527	}
4528
4529	if (is_object($db)) {
4530		if ($_SERVER['DOCUMENT_ROOT']) {  // Mode web
4531			$out .= "<b>".$langs->trans("DatabaseTypeManager").":</b> ".$db->type."<br>\n";
4532			$out .= "<b>".$langs->trans("RequestLastAccessInError").":</b> ".($db->lastqueryerror() ? dol_escape_htmltag($db->lastqueryerror()) : $langs->trans("ErrorNoRequestInError"))."<br>\n";
4533			$out .= "<b>".$langs->trans("ReturnCodeLastAccessInError").":</b> ".($db->lasterrno() ? dol_escape_htmltag($db->lasterrno()) : $langs->trans("ErrorNoRequestInError"))."<br>\n";
4534			$out .= "<b>".$langs->trans("InformationLastAccessInError").":</b> ".($db->lasterror() ? dol_escape_htmltag($db->lasterror()) : $langs->trans("ErrorNoRequestInError"))."<br>\n";
4535			$out .= "<br>\n";
4536		} else // Mode CLI
4537		{
4538			// No dol_escape_htmltag for output, we are in CLI mode
4539			$out .= '> '.$langs->transnoentities("DatabaseTypeManager").":\n".$db->type."\n";
4540			$out .= '> '.$langs->transnoentities("RequestLastAccessInError").":\n".($db->lastqueryerror() ? $db->lastqueryerror() : $langs->transnoentities("ErrorNoRequestInError"))."\n";
4541			$out .= '> '.$langs->transnoentities("ReturnCodeLastAccessInError").":\n".($db->lasterrno() ? $db->lasterrno() : $langs->transnoentities("ErrorNoRequestInError"))."\n";
4542			$out .= '> '.$langs->transnoentities("InformationLastAccessInError").":\n".($db->lasterror() ? $db->lasterror() : $langs->transnoentities("ErrorNoRequestInError"))."\n";
4543		}
4544		$syslog .= ", sql=".$db->lastquery();
4545		$syslog .= ", db_error=".$db->lasterror();
4546	}
4547
4548	if ($error || $errors) {
4549		$langs->load("errors");
4550
4551		// Merge all into $errors array
4552		if (is_array($error) && is_array($errors)) {
4553			$errors = array_merge($error, $errors);
4554		} elseif (is_array($error)) {
4555			$errors = $error;
4556		} elseif (is_array($errors)) {
4557			$errors = array_merge(array($error), $errors);
4558		} else {
4559			$errors = array_merge(array($error));
4560		}
4561
4562		foreach ($errors as $msg) {
4563			if (empty($msg)) {
4564				continue;
4565			}
4566			if ($_SERVER['DOCUMENT_ROOT']) {  // Mode web
4567				$out .= "<b>".$langs->trans("Message").":</b> ".dol_escape_htmltag($msg)."<br>\n";
4568			} else // Mode CLI
4569			{
4570				$out .= '> '.$langs->transnoentities("Message").":\n".$msg."\n";
4571			}
4572			$syslog .= ", msg=".$msg;
4573		}
4574	}
4575	if (empty($dolibarr_main_prod) && $_SERVER['DOCUMENT_ROOT'] && function_exists('xdebug_print_function_stack') && function_exists('xdebug_call_file')) {
4576		xdebug_print_function_stack();
4577		$out .= '<b>XDebug informations:</b>'."<br>\n";
4578		$out .= 'File: '.xdebug_call_file()."<br>\n";
4579		$out .= 'Line: '.xdebug_call_line()."<br>\n";
4580		$out .= 'Function: '.xdebug_call_function()."<br>\n";
4581		$out .= "<br>\n";
4582	}
4583
4584	// Return a http error code if possible
4585	if (!headers_sent()) {
4586		http_response_code(500);
4587	}
4588
4589	if (empty($dolibarr_main_prod)) {
4590		print $out;
4591	} else {
4592		if (empty($langs->defaultlang)) {
4593			$langs->setDefaultLang();
4594		}
4595		$langs->loadLangs(array("main", "errors")); // Reload main because language may have been set only on previous line so we have to reload files we need.
4596		// This should not happen, except if there is a bug somewhere. Enabled and check log in such case.
4597		print 'This website or feature is currently temporarly not available or failed after a technical error.<br><br>This may be due to a maintenance operation. Current status of operation ('.dol_print_date(dol_now(), 'dayhourrfc').') are on next line...<br><br>'."\n";
4598		print $langs->trans("DolibarrHasDetectedError").'. ';
4599		print $langs->trans("YouCanSetOptionDolibarrMainProdToZero");
4600		define("MAIN_CORE_ERROR", 1);
4601	}
4602
4603	dol_syslog("Error ".$syslog, LOG_ERR);
4604}
4605
4606/**
4607 * Show a public email and error code to contact if technical error
4608 *
4609 * @param	string	$prefixcode		Prefix of public error code
4610 * @param   string  $errormessage   Complete error message
4611 * @param	array	$errormessages	Array of error messages
4612 * @param	string	$morecss		More css
4613 * @param	string	$email			Email
4614 * @return	void
4615 */
4616function dol_print_error_email($prefixcode, $errormessage = '', $errormessages = array(), $morecss = 'error', $email = '')
4617{
4618	global $langs, $conf;
4619
4620	if (empty($email)) {
4621		$email = $conf->global->MAIN_INFO_SOCIETE_MAIL;
4622	}
4623
4624	$langs->load("errors");
4625	$now = dol_now();
4626
4627	print '<br><div class="center login_main_message"><div class="'.$morecss.'">';
4628	print $langs->trans("ErrorContactEMail", $email, $prefixcode.dol_print_date($now, '%Y%m%d%H%M%S'));
4629	if ($errormessage) {
4630		print '<br><br>'.$errormessage;
4631	}
4632	if (is_array($errormessages) && count($errormessages)) {
4633		foreach ($errormessages as $mesgtoshow) {
4634			print '<br><br>'.$mesgtoshow;
4635		}
4636	}
4637	print '</div></div>';
4638}
4639
4640/**
4641 *	Show title line of an array
4642 *
4643 *	@param	string	$name        Label of field
4644 *	@param	string	$file        Url used when we click on sort picto
4645 *	@param	string	$field       Field to use for new sorting
4646 *	@param	string	$begin       ("" by defaut)
4647 *	@param	string	$moreparam   Add more parameters on sort url links ("" by default)
4648 *	@param  string	$moreattrib  Options of attribute td ("" by defaut, example: 'align="center"')
4649 *	@param  string	$sortfield   Current field used to sort
4650 *	@param  string	$sortorder   Current sort order
4651 *  @param	string	$prefix		 Prefix for css. Use space after prefix to add your own CSS tag.
4652 *  @param	string	$tooltip	 Tooltip
4653 *  @param	string	$forcenowrapcolumntitle		No need for use 'wrapcolumntitle' css style
4654 *	@return	void
4655 */
4656function print_liste_field_titre($name, $file = "", $field = "", $begin = "", $moreparam = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $tooltip = "", $forcenowrapcolumntitle = 0)
4657{
4658	print getTitleFieldOfList($name, 0, $file, $field, $begin, $moreparam, $moreattrib, $sortfield, $sortorder, $prefix, 0, $tooltip, $forcenowrapcolumntitle);
4659}
4660
4661/**
4662 *	Get title line of an array
4663 *
4664 *	@param	string	$name        		Translation key of field to show or complete HTML string to show
4665 *	@param	int		$thead		 		0=To use with standard table format, 1=To use inside <thead><tr>, 2=To use with <div>
4666 *	@param	string	$file        		Url used when we click on sort picto
4667 *	@param	string	$field       		Field to use for new sorting. Empty if this field is not sortable. Example "t.abc" or "t.abc,t.def"
4668 *	@param	string	$begin       		("" by defaut)
4669 *	@param	string	$moreparam   		Add more parameters on sort url links ("" by default)
4670 *	@param  string	$moreattrib  		Add more attributes on th ("" by defaut, example: 'align="center"'). To add more css class, use param $prefix.
4671 *	@param  string	$sortfield   		Current field used to sort (Ex: 'd.datep,d.id')
4672 *	@param  string	$sortorder   		Current sort order (Ex: 'asc,desc')
4673 *  @param	string	$prefix		 		Prefix for css. Use space after prefix to add your own CSS tag, for example 'mycss '.
4674 *  @param	string	$disablesortlink	1=Disable sort link
4675 *  @param	string	$tooltip	 		Tooltip
4676 *  @param	string	$forcenowrapcolumntitle		No need for use 'wrapcolumntitle' css style
4677 *	@return	string
4678 */
4679function getTitleFieldOfList($name, $thead = 0, $file = "", $field = "", $begin = "", $moreparam = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $disablesortlink = 0, $tooltip = '', $forcenowrapcolumntitle = 0)
4680{
4681	global $conf, $langs, $form;
4682	//print "$name, $file, $field, $begin, $options, $moreattrib, $sortfield, $sortorder<br>\n";
4683
4684	if ($moreattrib == 'class="right"') {
4685		$prefix .= 'right '; // For backward compatibility
4686	}
4687
4688	$sortorder = strtoupper($sortorder);
4689	$out = '';
4690	$sortimg = '';
4691
4692	$tag = 'th';
4693	if ($thead == 2) {
4694		$tag = 'div';
4695	}
4696
4697	$tmpsortfield = explode(',', $sortfield);
4698	$sortfield1 = trim($tmpsortfield[0]); // If $sortfield is 'd.datep,d.id', it becomes 'd.datep'
4699	$tmpfield = explode(',', $field);
4700	$field1 = trim($tmpfield[0]); // If $field is 'd.datep,d.id', it becomes 'd.datep'
4701
4702	if (empty($conf->global->MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE) && empty($forcenowrapcolumntitle)) {
4703		$prefix = 'wrapcolumntitle '.$prefix;
4704	}
4705
4706	//var_dump('field='.$field.' field1='.$field1.' sortfield='.$sortfield.' sortfield1='.$sortfield1);
4707	// If field is used as sort criteria we use a specific css class liste_titre_sel
4708	// Example if (sortfield,field)=("nom","xxx.nom") or (sortfield,field)=("nom","nom")
4709	$liste_titre = 'liste_titre';
4710	if ($field1 && ($sortfield1 == $field1 || $sortfield1 == preg_replace("/^[^\.]+\./", "", $field1))) {
4711		$liste_titre = 'liste_titre_sel';
4712	}
4713	$out .= '<'.$tag.' class="'.$prefix.$liste_titre.'" '.$moreattrib;
4714	//$out .= (($field && empty($conf->global->MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE) && preg_match('/^[a-zA-Z_0-9\s\.\-:&;]*$/', $name)) ? ' title="'.dol_escape_htmltag($langs->trans($name)).'"' : '');
4715	$out .= ($name && empty($conf->global->MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE) && empty($forcenowrapcolumntitle) && !dol_textishtml($name)) ? ' title="'.dol_escape_htmltag($langs->trans($name)).'"' : '';
4716	$out .= '>';
4717
4718	if (empty($thead) && $field && empty($disablesortlink)) {    // If this is a sort field
4719		$options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', (is_scalar($moreparam) ? $moreparam : ''));
4720		$options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
4721		$options = preg_replace('/&+/i', '&', $options);
4722		if (!preg_match('/^&/', $options)) {
4723			$options = '&'.$options;
4724		}
4725
4726		$sortordertouseinlink = '';
4727		if ($field1 != $sortfield1) { // We are on another field than current sorted field
4728			if (preg_match('/^DESC/i', $sortorder)) {
4729				$sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
4730			} else // We reverse the var $sortordertouseinlink
4731			{
4732				$sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
4733			}
4734		} else // We are on field that is the first current sorting criteria
4735		{
4736			if (preg_match('/^ASC/i', $sortorder)) {	// We reverse the var $sortordertouseinlink
4737				$sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
4738			} else {
4739				$sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
4740			}
4741		}
4742		$sortordertouseinlink = preg_replace('/,$/', '', $sortordertouseinlink);
4743		$out .= '<a class="reposition" href="'.$file.'?sortfield='.$field.'&sortorder='.$sortordertouseinlink.'&begin='.$begin.$options.'"';
4744		//$out .= (empty($conf->global->MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE) ? ' title="'.dol_escape_htmltag($langs->trans($name)).'"' : '');
4745		$out .= '>';
4746	}
4747
4748	if ($tooltip) {
4749		// You can also use 'TranslationString:keyfortooltiponlick' for a tooltip on click.
4750		$tmptooltip = explode(':', $tooltip);
4751		$out .= $form->textwithpicto($langs->trans($name), $langs->trans($tmptooltip[0]), 1, 'help', '', 0, 3, (empty($tmptooltip[1]) ? '' : 'extra_'.str_replace('.', '_', $field).'_'.$tmptooltip[1]));
4752	} else {
4753		$out .= $langs->trans($name);
4754	}
4755
4756	if (empty($thead) && $field && empty($disablesortlink)) {    // If this is a sort field
4757		$out .= '</a>';
4758	}
4759
4760	if (empty($thead) && $field) {    // If this is a sort field
4761		$options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', (is_scalar($moreparam) ? $moreparam : ''));
4762		$options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
4763		$options = preg_replace('/&+/i', '&', $options);
4764		if (!preg_match('/^&/', $options)) {
4765			$options = '&'.$options;
4766		}
4767
4768		if (!$sortorder || $field1 != $sortfield1) {
4769			//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",0).'</a>';
4770			//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",0).'</a>';
4771		} else {
4772			if (preg_match('/^DESC/', $sortorder)) {
4773				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",0).'</a>';
4774				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",1).'</a>';
4775				$sortimg .= '<span class="nowrap">'.img_up("Z-A", 0, 'paddingleft').'</span>';
4776			}
4777			if (preg_match('/^ASC/', $sortorder)) {
4778				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",1).'</a>';
4779				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",0).'</a>';
4780				$sortimg .= '<span class="nowrap">'.img_down("A-Z", 0, 'paddingleft').'</span>';
4781			}
4782		}
4783	}
4784
4785	$out .= $sortimg;
4786
4787	$out .= '</'.$tag.'>';
4788
4789	return $out;
4790}
4791
4792/**
4793 *	Show a title.
4794 *
4795 *	@param	string	$title			Title to show
4796 *	@return	string					Title to show
4797 *  @deprecated						Use load_fiche_titre instead
4798 *  @see load_fiche_titre()
4799 */
4800function print_titre($title)
4801{
4802	dol_syslog(__FUNCTION__." is deprecated", LOG_WARNING);
4803
4804	print '<div class="titre">'.$title.'</div>';
4805}
4806
4807/**
4808 *	Show a title with picto
4809 *
4810 *	@param	string	$title				Title to show
4811 *	@param	string	$mesg				Added message to show on right
4812 *	@param	string	$picto				Icon to use before title (should be a 32x32 transparent png file)
4813 *	@param	int		$pictoisfullpath	1=Icon name is a full absolute url of image
4814 * 	@param	int		$id					To force an id on html objects
4815 * 	@return	void
4816 *  @deprecated Use print load_fiche_titre instead
4817 */
4818function print_fiche_titre($title, $mesg = '', $picto = 'generic', $pictoisfullpath = 0, $id = '')
4819{
4820	print load_fiche_titre($title, $mesg, $picto, $pictoisfullpath, $id);
4821}
4822
4823/**
4824 *	Load a title with picto
4825 *
4826 *	@param	string	$titre				Title to show
4827 *	@param	string	$morehtmlright		Added message to show on right
4828 *	@param	string	$picto				Icon to use before title (should be a 32x32 transparent png file)
4829 *	@param	int		$pictoisfullpath	1=Icon name is a full absolute url of image
4830 * 	@param	string	$id					To force an id on html objects
4831 *  @param  string  $morecssontable     More css on table
4832 *	@param	string	$morehtmlcenter		Added message to show on center
4833 * 	@return	string
4834 *  @see print_barre_liste()
4835 */
4836function load_fiche_titre($titre, $morehtmlright = '', $picto = 'generic', $pictoisfullpath = 0, $id = '', $morecssontable = '', $morehtmlcenter = '')
4837{
4838	global $conf;
4839
4840	$return = '';
4841
4842	if ($picto == 'setup') {
4843		$picto = 'generic';
4844	}
4845
4846	$return .= "\n";
4847	$return .= '<table '.($id ? 'id="'.$id.'" ' : '').'class="centpercent notopnoleftnoright table-fiche-title'.($morecssontable ? ' '.$morecssontable : '').'">'; // maring bottom must be same than into print_barre_list
4848	$return .= '<tr class="titre">';
4849	if ($picto) {
4850		$return .= '<td class="nobordernopadding widthpictotitle valignmiddle col-picto">'.img_picto('', $picto, 'class="valignmiddle widthpictotitle pictotitle"', $pictoisfullpath).'</td>';
4851	}
4852	$return .= '<td class="nobordernopadding valignmiddle col-title">';
4853	$return .= '<div class="titre inline-block">'.$titre.'</div>';
4854	$return .= '</td>';
4855	if (dol_strlen($morehtmlcenter)) {
4856		$return .= '<td class="nobordernopadding center valignmiddle">'.$morehtmlcenter.'</td>';
4857	}
4858	if (dol_strlen($morehtmlright)) {
4859		$return .= '<td class="nobordernopadding titre_right wordbreakimp right valignmiddle">'.$morehtmlright.'</td>';
4860	}
4861	$return .= '</tr></table>'."\n";
4862
4863	return $return;
4864}
4865
4866/**
4867 *	Print a title with navigation controls for pagination
4868 *
4869 *	@param	string	    $titre				Title to show (required)
4870 *	@param	int   	    $page				Numero of page to show in navigation links (required)
4871 *	@param	string	    $file				Url of page (required)
4872 *	@param	string	    $options         	More parameters for links ('' by default, does not include sortfield neither sortorder). Value must be 'urlencoded' before calling function.
4873 *	@param	string    	$sortfield       	Field to sort on ('' by default)
4874 *	@param	string	    $sortorder       	Order to sort ('' by default)
4875 *	@param	string	    $morehtmlcenter     String in the middle ('' by default). We often find here string $massaction comming from $form->selectMassAction()
4876 *	@param	int		    $num				Number of records found by select with limit+1
4877 *	@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.
4878 *	@param	string	    $picto				Icon to use before title (should be a 32x32 transparent png file)
4879 *	@param	int		    $pictoisfullpath	1=Icon name is a full absolute url of image
4880 *  @param	string	    $morehtmlright		More html to show (after arrows)
4881 *  @param  string      $morecss            More css to the table
4882 *  @param  int         $limit              Max number of lines (-1 = use default, 0 = no limit, > 0 = limit).
4883 *  @param  int         $hideselectlimit    Force to hide select limit
4884 *  @param  int         $hidenavigation     Force to hide all navigation tools
4885 *  @param  int			$pagenavastextinput 1=Do not suggest list of pages to navigate but suggest the page number into an input field.
4886 *  @param	string		$morehtmlrightbeforearrow	More html to show (before arrows)
4887 *	@return	void
4888 */
4889function print_barre_liste($titre, $page, $file, $options = '', $sortfield = '', $sortorder = '', $morehtmlcenter = '', $num = -1, $totalnboflines = '', $picto = 'generic', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limit = -1, $hideselectlimit = 0, $hidenavigation = 0, $pagenavastextinput = 0, $morehtmlrightbeforearrow = '')
4890{
4891	global $conf, $langs;
4892
4893	$savlimit = $limit;
4894	$savtotalnboflines = $totalnboflines;
4895	$totalnboflines = abs((int) $totalnboflines);
4896
4897	if ($picto == 'setup') {
4898		$picto = 'title_setup.png';
4899	}
4900	if (($conf->browser->name == 'ie') && $picto == 'generic') {
4901		$picto = 'title.gif';
4902	}
4903	if ($limit < 0) {
4904		$limit = $conf->liste_limit;
4905	}
4906	if ($savlimit != 0 && (($num > $limit) || ($num == -1) || ($limit == 0))) {
4907		$nextpage = 1;
4908	} else {
4909		$nextpage = 0;
4910	}
4911	//print 'totalnboflines='.$totalnboflines.'-savlimit='.$savlimit.'-limit='.$limit.'-num='.$num.'-nextpage='.$nextpage;
4912
4913	print "\n";
4914	print "<!-- Begin title -->\n";
4915	print '<table class="centpercent notopnoleftnoright table-fiche-title'.($morecss ? ' '.$morecss : '').'"><tr>'; // maring bottom must be same than into load_fiche_tire
4916
4917	// Left
4918
4919	if ($picto && $titre) {
4920		print '<td class="nobordernopadding widthpictotitle valignmiddle col-picto">'.img_picto('', $picto, 'class="valignmiddle pictotitle widthpictotitle"', $pictoisfullpath).'</td>';
4921	}
4922	print '<td class="nobordernopadding valignmiddle col-title">';
4923	print '<div class="titre inline-block">'.$titre;
4924	if (!empty($titre) && $savtotalnboflines >= 0 && (string) $savtotalnboflines != '') {
4925		print '<span class="opacitymedium colorblack paddingleft">('.$totalnboflines.')</span>';
4926	}
4927	print '</div></td>';
4928
4929	// Center
4930	if ($morehtmlcenter) {
4931		print '<td class="nobordernopadding center valignmiddle">'.$morehtmlcenter.'</td>';
4932	}
4933
4934	// Right
4935	print '<td class="nobordernopadding valignmiddle right">';
4936	print '<input type="hidden" name="pageplusoneold" value="'.((int) $page + 1).'">';
4937	if ($sortfield) {
4938		$options .= "&sortfield=".urlencode($sortfield);
4939	}
4940	if ($sortorder) {
4941		$options .= "&sortorder=".urlencode($sortorder);
4942	}
4943	// Show navigation bar
4944	$pagelist = '';
4945	if ($savlimit != 0 && ($page > 0 || $num > $limit)) {
4946		if ($totalnboflines) {	// If we know total nb of lines
4947			// Define nb of extra page links before and after selected page + ... + first or last
4948			$maxnbofpage = (empty($conf->dol_optimize_smallscreen) ? 4 : 0);
4949
4950			if ($limit > 0) {
4951				$nbpages = ceil($totalnboflines / $limit);
4952			} else {
4953				$nbpages = 1;
4954			}
4955			$cpt = ($page - $maxnbofpage);
4956			if ($cpt < 0) {
4957				$cpt = 0;
4958			}
4959
4960			if ($cpt >= 1) {
4961				if (empty($pagenavastextinput)) {
4962					$pagelist .= '<li class="pagination"><a href="'.$file.'?page=0'.$options.'">1</a></li>';
4963					if ($cpt > 2) {
4964						$pagelist .= '<li class="pagination"><span class="inactive">...</span></li>';
4965					} elseif ($cpt == 2) {
4966						$pagelist .= '<li class="pagination"><a href="'.$file.'?page=1'.$options.'">2</a></li>';
4967					}
4968				}
4969			}
4970
4971			do {
4972				if ($pagenavastextinput) {
4973					if ($cpt == $page) {
4974						$pagelist .= '<li class="pagination"><input type="text" class="width25 center pageplusone" name="pageplusone" value="'.($page + 1).'"></li>';
4975						$pagelist .= '/';
4976						//if (($cpt + 1) < $nbpages) $pagelist .= '/';
4977					}
4978				} else {
4979					if ($cpt == $page) {
4980						$pagelist .= '<li class="pagination"><span class="active">'.($page + 1).'</span></li>';
4981					} else {
4982						$pagelist .= '<li class="pagination"><a href="'.$file.'?page='.$cpt.$options.'">'.($cpt + 1).'</a></li>';
4983					}
4984				}
4985				$cpt++;
4986			} while ($cpt < $nbpages && $cpt <= ($page + $maxnbofpage));
4987
4988			if (empty($pagenavastextinput)) {
4989				if ($cpt < $nbpages) {
4990					if ($cpt < $nbpages - 2) {
4991						$pagelist .= '<li class="pagination"><span class="inactive">...</span></li>';
4992					} elseif ($cpt == $nbpages - 2) {
4993						$pagelist .= '<li class="pagination"><a href="'.$file.'?page='.($nbpages - 2).$options.'">'.($nbpages - 1).'</a></li>';
4994					}
4995					$pagelist .= '<li class="pagination"><a href="'.$file.'?page='.($nbpages - 1).$options.'">'.$nbpages.'</a></li>';
4996				}
4997			} else {
4998				//var_dump($page.' '.$cpt.' '.$nbpages);
4999				//if (($page + 1) < $nbpages) {
5000					$pagelist .= '<li class="pagination"><a href="'.$file.'?page='.($nbpages - 1).$options.'">'.$nbpages.'</a></li>';
5001				//}
5002			}
5003		} else {
5004			$pagelist .= '<li class="pagination"><span class="active">'.($page + 1)."</li>";
5005		}
5006	}
5007
5008	if ($savlimit || $morehtmlright || $morehtmlrightbeforearrow) {
5009		print_fleche_navigation($page, $file, $options, $nextpage, $pagelist, $morehtmlright, $savlimit, $totalnboflines, $hideselectlimit, $morehtmlrightbeforearrow); // output the div and ul for previous/last completed with page numbers into $pagelist
5010	}
5011
5012	// js to autoselect page field on focus
5013	if ($pagenavastextinput) {
5014		print ajax_autoselect('.pageplusone');
5015	}
5016
5017	print '</td>';
5018
5019	print '</tr></table>'."\n";
5020	print "<!-- End title -->\n\n";
5021}
5022
5023/**
5024 *	Function to show navigation arrows into lists
5025 *
5026 *	@param	int				$page				Number of page
5027 *	@param	string			$file				Page URL (in most cases provided with $_SERVER["PHP_SELF"])
5028 *	@param	string			$options         	Other url parameters to propagate ("" by default, may include sortfield and sortorder)
5029 *	@param	integer			$nextpage	    	Do we show a next page button
5030 *	@param	string			$betweenarrows		HTML content to show between arrows. MUST contains '<li> </li>' tags or '<li><span> </span></li>'.
5031 *  @param	string			$afterarrows		HTML content to show after arrows. Must NOT contains '<li> </li>' tags.
5032 *  @param  int             $limit              Max nb of record to show  (-1 = no combo with limit, 0 = no limit, > 0 = limit)
5033 *	@param	int		        $totalnboflines		Total number of records/lines for all pages (if known)
5034 *  @param  int             $hideselectlimit    Force to hide select limit
5035 *  @param	string			$beforearrows		HTML content to show before arrows. Must NOT contains '<li> </li>' tags.
5036 *	@return	void
5037 */
5038function print_fleche_navigation($page, $file, $options = '', $nextpage = 0, $betweenarrows = '', $afterarrows = '', $limit = -1, $totalnboflines = 0, $hideselectlimit = 0, $beforearrows = '')
5039{
5040	global $conf, $langs;
5041
5042	print '<div class="pagination"><ul>';
5043	if ($beforearrows) {
5044		print '<li class="paginationbeforearrows">';
5045		print $beforearrows;
5046		print '</li>';
5047	}
5048	if ((int) $limit > 0 && empty($hideselectlimit)) {
5049		$pagesizechoices = '10:10,15:15,20:20,30:30,40:40,50:50,100:100,250:250,500:500,1000:1000,5000:5000,25000:25000';
5050		//$pagesizechoices.=',0:'.$langs->trans("All");     // Not yet supported
5051		//$pagesizechoices.=',2:2';
5052		if (!empty($conf->global->MAIN_PAGESIZE_CHOICES)) {
5053			$pagesizechoices = $conf->global->MAIN_PAGESIZE_CHOICES;
5054		}
5055
5056		print '<li class="pagination">';
5057		print '<select class="flat selectlimit" name="limit" title="'.dol_escape_htmltag($langs->trans("MaxNbOfRecordPerPage")).'">';
5058		$tmpchoice = explode(',', $pagesizechoices);
5059		$tmpkey = $limit.':'.$limit;
5060		if (!in_array($tmpkey, $tmpchoice)) {
5061			$tmpchoice[] = $tmpkey;
5062		}
5063		$tmpkey = $conf->liste_limit.':'.$conf->liste_limit;
5064		if (!in_array($tmpkey, $tmpchoice)) {
5065			$tmpchoice[] = $tmpkey;
5066		}
5067		asort($tmpchoice, SORT_NUMERIC);
5068		foreach ($tmpchoice as $val) {
5069			$selected = '';
5070			$tmp = explode(':', $val);
5071			$key = $tmp[0];
5072			$val = $tmp[1];
5073			if ($key != '' && $val != '') {
5074				if ((int) $key == (int) $limit) {
5075					$selected = ' selected="selected"';
5076				}
5077				print '<option name="'.$key.'"'.$selected.'>'.dol_escape_htmltag($val).'</option>'."\n";
5078			}
5079		}
5080		print '</select>';
5081		if ($conf->use_javascript_ajax) {
5082			print '<!-- JS CODE TO ENABLE select limit to launch submit of page -->
5083            		<script>
5084                	jQuery(document).ready(function () {
5085            	  		jQuery(".selectlimit").change(function() {
5086                            console.log("Change limit. Send submit");
5087                            $(this).parents(\'form:first\').submit();
5088            	  		});
5089                	});
5090            		</script>
5091                ';
5092		}
5093		print '</li>';
5094	}
5095	if ($page > 0) {
5096		print '<li class="pagination paginationpage paginationpageleft"><a class="paginationprevious" href="'.$file.'?page='.($page - 1).$options.'"><i class="fa fa-chevron-left" title="'.dol_escape_htmltag($langs->trans("Previous")).'"></i></a></li>';
5097	}
5098	if ($betweenarrows) {
5099		print '<!--<div class="betweenarrows nowraponall inline-block">-->';
5100		print $betweenarrows;
5101		print '<!--</div>-->';
5102	}
5103	if ($nextpage > 0) {
5104		print '<li class="pagination paginationpage paginationpageright"><a class="paginationnext" href="'.$file.'?page='.($page + 1).$options.'"><i class="fa fa-chevron-right" title="'.dol_escape_htmltag($langs->trans("Next")).'"></i></a></li>';
5105	}
5106	if ($afterarrows) {
5107		print '<li class="paginationafterarrows">';
5108		print $afterarrows;
5109		print '</li>';
5110	}
5111	print '</ul></div>'."\n";
5112}
5113
5114
5115/**
5116 *	Return a string with VAT rate label formated for view output
5117 *	Used into pdf and HTML pages
5118 *
5119 *	@param	string	$rate			Rate value to format ('19.6', '19,6', '19.6%', '19,6%', '19.6 (CODEX)', ...)
5120 *  @param	boolean	$addpercent		Add a percent % sign in output
5121 *	@param	int		$info_bits		Miscellaneous information on vat (0=Default, 1=French NPR vat)
5122 *	@param	int		$usestarfornpr	-1=Never show, 0 or 1=Use '*' for NPR vat rates
5123 *  @return	string					String with formated amounts ('19,6' or '19,6%' or '8.5% (NPR)' or '8.5% *' or '19,6 (CODEX)')
5124 */
5125function vatrate($rate, $addpercent = false, $info_bits = 0, $usestarfornpr = 0)
5126{
5127	$morelabel = '';
5128
5129	if (preg_match('/%/', $rate)) {
5130		$rate = str_replace('%', '', $rate);
5131		$addpercent = true;
5132	}
5133	if (preg_match('/\((.*)\)/', $rate, $reg)) {
5134		$morelabel = ' ('.$reg[1].')';
5135		$rate = preg_replace('/\s*'.preg_quote($morelabel, '/').'/', '', $rate);
5136	}
5137	if (preg_match('/\*/', $rate)) {
5138		$rate = str_replace('*', '', $rate);
5139		$info_bits |= 1;
5140	}
5141
5142	// If rate is '9/9/9' we don't change it.  If rate is '9.000' we apply price()
5143	if (!preg_match('/\//', $rate)) {
5144		$ret = price($rate, 0, '', 0, 0).($addpercent ? '%' : '');
5145	} else {
5146		// TODO Split on / and output with a price2num to have clean numbers without ton of 000.
5147		$ret = $rate.($addpercent ? '%' : '');
5148	}
5149	if (($info_bits & 1) && $usestarfornpr >= 0) {
5150		$ret .= ' *';
5151	}
5152	$ret .= $morelabel;
5153	return $ret;
5154}
5155
5156
5157/**
5158 *		Function to format a value into an amount for visual output
5159 *		Function used into PDF and HTML pages
5160 *
5161 *		@param	float		$amount			Amount to format
5162 *		@param	integer		$form			Type of format, HTML or not (not by default)
5163 *		@param	Translate	$outlangs		Object langs for output
5164 *		@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.
5165 *		@param	int			$rounding		Minimum number of decimal to show. If 0, no change, if -1, we use min($conf->global->MAIN_MAX_DECIMALS_UNIT,$conf->global->MAIN_MAX_DECIMALS_TOT)
5166 *		@param	int			$forcerounding	Force the number of decimal to forcerounding decimal (-1=do not force)
5167 *		@param	string		$currency_code	To add currency symbol (''=add nothing, 'auto'=Use default currency, 'XXX'=add currency symbols for XXX currency)
5168 *		@return	string						Chaine avec montant formate
5169 *
5170 *		@see	price2num()					Revert function of price
5171 */
5172function price($amount, $form = 0, $outlangs = '', $trunc = 1, $rounding = -1, $forcerounding = -1, $currency_code = '')
5173{
5174	global $langs, $conf;
5175
5176	// Clean parameters
5177	if (empty($amount)) {
5178		$amount = 0; // To have a numeric value if amount not defined or = ''
5179	}
5180	$amount = (is_numeric($amount) ? $amount : 0); // Check if amount is numeric, for example, an error occured when amount value = o (letter) instead 0 (number)
5181	if ($rounding < 0) {
5182		$rounding = min($conf->global->MAIN_MAX_DECIMALS_UNIT, $conf->global->MAIN_MAX_DECIMALS_TOT);
5183	}
5184	$nbdecimal = $rounding;
5185
5186	// Output separators by default (french)
5187	$dec = ',';
5188	$thousand = ' ';
5189
5190	// If $outlangs not forced, we use use language
5191	if (!is_object($outlangs)) {
5192		$outlangs = $langs;
5193	}
5194
5195	if ($outlangs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal") {
5196		$dec = $outlangs->transnoentitiesnoconv("SeparatorDecimal");
5197	}
5198	if ($outlangs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") {
5199		$thousand = $outlangs->transnoentitiesnoconv("SeparatorThousand");
5200	}
5201	if ($thousand == 'None') {
5202		$thousand = '';
5203	} elseif ($thousand == 'Space') {
5204		$thousand = ' ';
5205	}
5206	//print "outlangs=".$outlangs->defaultlang." amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
5207
5208	//print "amount=".$amount."-";
5209	$amount = str_replace(',', '.', $amount); // should be useless
5210	//print $amount."-";
5211	$datas = explode('.', $amount);
5212	$decpart = isset($datas[1]) ? $datas[1] : '';
5213	$decpart = preg_replace('/0+$/i', '', $decpart); // Supprime les 0 de fin de partie decimale
5214	//print "decpart=".$decpart."<br>";
5215	$end = '';
5216
5217	// We increase nbdecimal if there is more decimal than asked (to not loose information)
5218	if (dol_strlen($decpart) > $nbdecimal) {
5219		$nbdecimal = dol_strlen($decpart);
5220	}
5221	// Si on depasse max
5222	if ($trunc && $nbdecimal > $conf->global->MAIN_MAX_DECIMALS_SHOWN) {
5223		$nbdecimal = $conf->global->MAIN_MAX_DECIMALS_SHOWN;
5224		if (preg_match('/\.\.\./i', $conf->global->MAIN_MAX_DECIMALS_SHOWN)) {
5225			// Si un affichage est tronque, on montre des ...
5226			$end = '...';
5227		}
5228	}
5229
5230	// If force rounding
5231	if ($forcerounding >= 0) {
5232		$nbdecimal = $forcerounding;
5233	}
5234
5235	// Format number
5236	$output = number_format($amount, $nbdecimal, $dec, $thousand);
5237	if ($form) {
5238		$output = preg_replace('/\s/', '&nbsp;', $output);
5239		$output = preg_replace('/\'/', '&#039;', $output);
5240	}
5241	// Add symbol of currency if requested
5242	$cursymbolbefore = $cursymbolafter = '';
5243	if ($currency_code) {
5244		if ($currency_code == 'auto') {
5245			$currency_code = $conf->currency;
5246		}
5247
5248		$listofcurrenciesbefore = array('AUD', 'CAD', 'CNY', 'COP', 'CLP', 'GBP', 'HKD', 'MXN', 'PEN', 'USD');
5249		$listoflanguagesbefore = array('nl_NL');
5250		if (in_array($currency_code, $listofcurrenciesbefore) || in_array($outlangs->defaultlang, $listoflanguagesbefore)) {
5251			$cursymbolbefore .= $outlangs->getCurrencySymbol($currency_code);
5252		} else {
5253			$tmpcur = $outlangs->getCurrencySymbol($currency_code);
5254			$cursymbolafter .= ($tmpcur == $currency_code ? ' '.$tmpcur : $tmpcur);
5255		}
5256	}
5257	$output = $cursymbolbefore.$output.$end.($cursymbolafter ? ' ' : '').$cursymbolafter;
5258
5259	return $output;
5260}
5261
5262/**
5263 *	Function that return a number with universal decimal format (decimal separator is '.') from an amount typed by a user.
5264 *	Function to use on each input amount before any numeric test or database insert. A better name for this function
5265 *  should be roundtext2num().
5266 *
5267 *	@param	string|float	$amount			Amount to convert/clean or round
5268 *	@param	string|int		$rounding		''=No rounding
5269 * 											'MU'=Round to Max unit price (MAIN_MAX_DECIMALS_UNIT)
5270 *											'MT'=Round to Max for totals with Tax (MAIN_MAX_DECIMALS_TOT)
5271 *											'MS'=Round to Max for stock quantity (MAIN_MAX_DECIMALS_STOCK)
5272 *      		                            'CU'=Round to Max unit price of foreign currency accuracy
5273 *      		                            'CT'=Round to Max for totals with Tax of foreign currency accuracy
5274 *											Numeric = Nb of digits for rounding (For example 2 for a percentage)
5275 * 	@param	int				$option			Put 1 if you know that content is already universal format number (so no correction on decimal will be done)
5276 * 											Put 2 if you know that number is a user input (so we know we don't have to fix decimal separator).
5277 *	@return	string							Amount with universal numeric format (Example: '99.99999').
5278 *											If conversion fails, it return text unchanged if ($rounding = '' and $option = 1) or '0' if ($rounding is defined and $option = 1).
5279 *											If amount is null or '', it returns '' if $rounding = '' or '0' if $rounding is defined..
5280 *
5281 *	@see    price()							Opposite function of price2num
5282 */
5283function price2num($amount, $rounding = '', $option = 0)
5284{
5285	global $langs, $conf;
5286
5287	// Round PHP function does not allow number like '1,234.56' nor '1.234,56' nor '1 234,56'
5288	// Numbers must be '1234.56'
5289	// Decimal delimiter for PHP and database SQL requests must be '.'
5290	$dec = ',';
5291	$thousand = ' ';
5292	if ($langs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal") {
5293		$dec = $langs->transnoentitiesnoconv("SeparatorDecimal");
5294	}
5295	if ($langs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") {
5296		$thousand = $langs->transnoentitiesnoconv("SeparatorThousand");
5297	}
5298	if ($thousand == 'None') {
5299		$thousand = '';
5300	} elseif ($thousand == 'Space') {
5301		$thousand = ' ';
5302	}
5303	//print "amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
5304
5305	// Convert value to universal number format (no thousand separator, '.' as decimal separator)
5306	if ($option != 1) {	// If not a PHP number or unknown, we change or clean format
5307		//print "\n".'PP'.$amount.' - '.$dec.' - '.$thousand.' - '.intval($amount).'<br>';
5308		if (!is_numeric($amount)) {
5309			$amount = preg_replace('/[a-zA-Z\/\\\*\(\)\<\>\_]/', '', $amount);
5310		}
5311
5312		if ($option == 2 && $thousand == '.' && preg_match('/\.(\d\d\d)$/', (string) $amount)) {	// It means the . is used as a thousand separator and string come from input data, so 1.123 is 1123
5313			$amount = str_replace($thousand, '', $amount);
5314		}
5315
5316		// Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
5317		// to format defined by LC_NUMERIC after a calculation and we want source format to be like defined by Dolibarr setup.
5318		// So if number was already a good number, it is converted into local Dolibarr setup.
5319		if (is_numeric($amount)) {
5320			// We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
5321			$temps = sprintf("%0.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
5322			$temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
5323			$nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
5324			$amount = number_format($amount, $nbofdec, $dec, $thousand);
5325		}
5326		//print "QQ".$amount."<br>\n";
5327
5328		// Now make replace (the main goal of function)
5329		if ($thousand != ',' && $thousand != '.') {
5330			$amount = str_replace(',', '.', $amount); // To accept 2 notations for french users
5331		}
5332		$amount = str_replace(' ', '', $amount); // To avoid spaces
5333		$amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
5334		$amount = str_replace($dec, '.', $amount);
5335	}
5336	//print ' XX'.$amount.' '.$rounding;
5337
5338	// Now, make a rounding if required
5339	if ($rounding) {
5340		$nbofdectoround = '';
5341		if ($rounding == 'MU') {
5342			$nbofdectoround = $conf->global->MAIN_MAX_DECIMALS_UNIT;
5343		} elseif ($rounding == 'MT') {
5344			$nbofdectoround = $conf->global->MAIN_MAX_DECIMALS_TOT;
5345		} elseif ($rounding == 'MS') {
5346			$nbofdectoround = empty($conf->global->MAIN_MAX_DECIMALS_STOCK) ? 5 : $conf->global->MAIN_MAX_DECIMALS_STOCK;
5347		} elseif ($rounding == 'CU') {
5348			$nbofdectoround = max($conf->global->MAIN_MAX_DECIMALS_UNIT, 8);	// TODO Use param of currency
5349		} elseif ($rounding == 'CT') {
5350			$nbofdectoround = max($conf->global->MAIN_MAX_DECIMALS_TOT, 8);		// TODO Use param of currency
5351		} elseif (is_numeric($rounding)) {
5352			$nbofdectoround = (int) $rounding;
5353		}
5354		//print " RR".$amount.' - '.$nbofdectoround.'<br>';
5355		if (dol_strlen($nbofdectoround)) {
5356			$amount = round(is_string($amount) ? (float) $amount : $amount, $nbofdectoround); // $nbofdectoround can be 0.
5357		} else {
5358			return 'ErrorBadParameterProvidedToFunction';
5359		}
5360		//print ' SS'.$amount.' - '.$nbofdec.' - '.$dec.' - '.$thousand.' - '.$nbofdectoround.'<br>';
5361
5362		// Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
5363		// to format defined by LC_NUMERIC after a calculation and we want source format to be defined by Dolibarr setup.
5364		if (is_numeric($amount)) {
5365			// We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
5366			$temps = sprintf("%0.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
5367			$temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
5368			$nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
5369			$amount = number_format($amount, min($nbofdec, $nbofdectoround), $dec, $thousand); // Convert amount to format with dolibarr dec and thousand
5370		}
5371		//print "TT".$amount.'<br>';
5372
5373		// Always make replace because each math function (like round) replace
5374		// with local values and we want a number that has a SQL string format x.y
5375		if ($thousand != ',' && $thousand != '.') {
5376			$amount = str_replace(',', '.', $amount); // To accept 2 notations for french users
5377		}
5378		$amount = str_replace(' ', '', $amount); // To avoid spaces
5379		$amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
5380		$amount = str_replace($dec, '.', $amount);
5381	}
5382
5383	return $amount;
5384}
5385
5386/**
5387 * Output a dimension with best unit
5388 *
5389 * @param   float       $dimension      Dimension
5390 * @param   int         $unit           Unit scale of dimension (Example: 0=kg, -3=g, -6=mg, 98=ounce, 99=pound, ...)
5391 * @param   string      $type           'weight', 'volume', ...
5392 * @param   Translate   $outputlangs    Translate language object
5393 * @param   int         $round          -1 = non rounding, x = number of decimal
5394 * @param   string      $forceunitoutput    'no' or numeric (-3, -6, ...) compared to $unit (In most case, this value is value defined into $conf->global->MAIN_WEIGHT_DEFAULT_UNIT)
5395 * @return  string                      String to show dimensions
5396 */
5397function showDimensionInBestUnit($dimension, $unit, $type, $outputlangs, $round = -1, $forceunitoutput = 'no')
5398{
5399	require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
5400
5401	if (($forceunitoutput == 'no' && $dimension < 1 / 10000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -6)) {
5402		$dimension = $dimension * 1000000;
5403		$unit = $unit - 6;
5404	} elseif (($forceunitoutput == 'no' && $dimension < 1 / 10 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -3)) {
5405		$dimension = $dimension * 1000;
5406		$unit = $unit - 3;
5407	} elseif (($forceunitoutput == 'no' && $dimension > 100000000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 6)) {
5408		$dimension = $dimension / 1000000;
5409		$unit = $unit + 6;
5410	} elseif (($forceunitoutput == 'no' && $dimension > 100000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 3)) {
5411		$dimension = $dimension / 1000;
5412		$unit = $unit + 3;
5413	}
5414	// Special case when we want output unit into pound or ounce
5415	/* TODO
5416	if ($unit < 90 && $type == 'weight' && is_numeric($forceunitoutput) && (($forceunitoutput == 98) || ($forceunitoutput == 99))
5417	{
5418		$dimension = // convert dimension from standard unit into ounce or pound
5419		$unit = $forceunitoutput;
5420	}
5421	if ($unit > 90 && $type == 'weight' && is_numeric($forceunitoutput) && $forceunitoutput < 90)
5422	{
5423		$dimension = // convert dimension from standard unit into ounce or pound
5424		$unit = $forceunitoutput;
5425	}*/
5426
5427	$ret = price($dimension, 0, $outputlangs, 0, 0, $round).' '.measuringUnitString(0, $type, $unit);
5428
5429	return $ret;
5430}
5431
5432
5433/**
5434 *	Return localtax rate for a particular vat, when selling a product with vat $vatrate, from a $thirdparty_buyer to a $thirdparty_seller
5435 *  Note: This function applies same rules than get_default_tva
5436 *
5437 * 	@param	float		$vatrate		        Vat rate. Can be '8.5' or '8.5 (VATCODEX)' for example
5438 * 	@param  int			$local		         	Local tax to search and return (1 or 2 return only tax rate 1 or tax rate 2)
5439 *  @param  Societe		$thirdparty_buyer    	Object of buying third party
5440 *  @param	Societe		$thirdparty_seller		Object of selling third party ($mysoc if not defined)
5441 *  @param	int			$vatnpr					If vat rate is NPR or not
5442 * 	@return	mixed			   					0 if not found, localtax rate if found
5443 *  @see get_default_tva()
5444 */
5445function get_localtax($vatrate, $local, $thirdparty_buyer = "", $thirdparty_seller = "", $vatnpr = 0)
5446{
5447	global $db, $conf, $mysoc;
5448
5449	if (empty($thirdparty_seller) || !is_object($thirdparty_seller)) {
5450		$thirdparty_seller = $mysoc;
5451	}
5452
5453	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);
5454
5455	$vatratecleaned = $vatrate;
5456	$reg = array();
5457	if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) {     // If vat is "xx (yy)"
5458		$vatratecleaned = trim($reg[1]);
5459		$vatratecode = $reg[2];
5460	}
5461
5462	/*if ($thirdparty_buyer->country_code != $thirdparty_seller->country_code)
5463	{
5464		return 0;
5465	}*/
5466
5467	// Some test to guess with no need to make database access
5468	if ($mysoc->country_code == 'ES') { // For spain localtaxes 1 and 2, tax is qualified if buyer use local tax
5469		if ($local == 1) {
5470			if (!$mysoc->localtax1_assuj || (string) $vatratecleaned == "0") {
5471				return 0;
5472			}
5473			if ($thirdparty_seller->id == $mysoc->id) {
5474				if (!$thirdparty_buyer->localtax1_assuj) {
5475					return 0;
5476				}
5477			} else {
5478				if (!$thirdparty_seller->localtax1_assuj) {
5479					return 0;
5480				}
5481			}
5482		}
5483
5484		if ($local == 2) {
5485			//if (! $mysoc->localtax2_assuj || (string) $vatratecleaned == "0") return 0;
5486			if (!$mysoc->localtax2_assuj) {
5487				return 0; // If main vat is 0, IRPF may be different than 0.
5488			}
5489			if ($thirdparty_seller->id == $mysoc->id) {
5490				if (!$thirdparty_buyer->localtax2_assuj) {
5491					return 0;
5492				}
5493			} else {
5494				if (!$thirdparty_seller->localtax2_assuj) {
5495					return 0;
5496				}
5497			}
5498		}
5499	} else {
5500		if ($local == 1 && !$thirdparty_seller->localtax1_assuj) {
5501			return 0;
5502		}
5503		if ($local == 2 && !$thirdparty_seller->localtax2_assuj) {
5504			return 0;
5505		}
5506	}
5507
5508	// For some country MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY is forced to on.
5509	if (in_array($mysoc->country_code, array('ES'))) {
5510		$conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY = 1;
5511	}
5512
5513	// Search local taxes
5514	if (!empty($conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY)) {
5515		if ($local == 1) {
5516			if ($thirdparty_seller != $mysoc) {
5517				if (!isOnlyOneLocalTax($local)) {  // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
5518					return $thirdparty_seller->localtax1_value;
5519				}
5520			} else { // i am the seller
5521				if (!isOnlyOneLocalTax($local)) { // TODO If seller is me, why not always returning this, even if there is only one locatax vat.
5522					return $conf->global->MAIN_INFO_VALUE_LOCALTAX1;
5523				}
5524			}
5525		}
5526		if ($local == 2) {
5527			if ($thirdparty_seller != $mysoc) {
5528				if (!isOnlyOneLocalTax($local)) {  // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
5529					// TODO We should also return value defined on thirdparty only if defined
5530					return $thirdparty_seller->localtax2_value;
5531				}
5532			} else { // i am the seller
5533				if (in_array($mysoc->country_code, array('ES'))) {
5534					return $thirdparty_buyer->localtax2_value;
5535				} else {
5536					return $conf->global->MAIN_INFO_VALUE_LOCALTAX2;
5537				}
5538			}
5539		}
5540	}
5541
5542	// By default, search value of local tax on line of common tax
5543	$sql = "SELECT t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
5544	$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
5545	$sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($thirdparty_seller->country_code)."'";
5546	$sql .= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
5547	if (!empty($vatratecode)) {
5548		$sql .= " AND t.code ='".$db->escape($vatratecode)."'"; // If we have the code, we use it in priority
5549	} else {
5550		$sql .= " AND t.recuperableonly = '".$db->escape($vatnpr)."'";
5551	}
5552
5553	$resql = $db->query($sql);
5554
5555	if ($resql) {
5556		$obj = $db->fetch_object($resql);
5557		if ($obj) {
5558			if ($local == 1) {
5559				return $obj->localtax1;
5560			} elseif ($local == 2) {
5561				return $obj->localtax2;
5562			}
5563		}
5564	}
5565
5566	return 0;
5567}
5568
5569
5570/**
5571 * Return true if LocalTax (1 or 2) is unique.
5572 * Example: If localtax1 is 5 on line with highest common vat rate, return true
5573 * Example: If localtax1 is 5:8:15 on line with highest common vat rate, return false
5574 *
5575 * @param   int 	$local	Local tax to test (1 or 2)
5576 * @return  boolean 		True if LocalTax have multiple values, False if not
5577 */
5578function isOnlyOneLocalTax($local)
5579{
5580	$tax = get_localtax_by_third($local);
5581
5582	$valors = explode(":", $tax);
5583
5584	if (count($valors) > 1) {
5585		return false;
5586	} else {
5587		return true;
5588	}
5589}
5590
5591/**
5592 * Get values of localtaxes (1 or 2) for company country for the common vat with the highest value
5593 *
5594 * @param	int		$local 	LocalTax to get
5595 * @return	number			Values of localtax
5596 */
5597function get_localtax_by_third($local)
5598{
5599	global $db, $mysoc;
5600	$sql = "SELECT t.localtax1, t.localtax2 ";
5601	$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t inner join ".MAIN_DB_PREFIX."c_country as c ON c.rowid=t.fk_pays";
5602	$sql .= " WHERE c.code = '".$db->escape($mysoc->country_code)."' AND t.active = 1 AND t.taux=(";
5603	$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";
5604	$sql .= "  WHERE c.code = '".$db->escape($mysoc->country_code)."' AND tt.active = 1";
5605	$sql .= "  )";
5606
5607	$resql = $db->query($sql);
5608	if ($resql) {
5609		$obj = $db->fetch_object($resql);
5610		if ($local == 1) {
5611			return $obj->localtax1;
5612		} elseif ($local == 2) {
5613			return $obj->localtax2;
5614		}
5615	}
5616
5617	return 0;
5618}
5619
5620
5621/**
5622 *  Get tax (VAT) main information from Id.
5623 *  You can also call getLocalTaxesFromRate() after to get only localtax fields.
5624 *
5625 *  @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.
5626 *  @param	Societe	    $buyer         		Company object
5627 *  @param	Societe	    $seller        		Company object
5628 *  @param  int         $firstparamisid     1 if first param is id into table (use this if you can)
5629 *  @return	array       	  				array('rowid'=> , 'code'=> ...)
5630 *  @see getLocalTaxesFromRate()
5631 */
5632function getTaxesFromId($vatrate, $buyer = null, $seller = null, $firstparamisid = 1)
5633{
5634	global $db, $mysoc;
5635
5636	dol_syslog("getTaxesFromId vat id or rate = ".$vatrate);
5637
5638	// Search local taxes
5639	$sql = "SELECT t.rowid, t.code, t.taux as rate, t.recuperableonly as npr, t.accountancy_code_sell, t.accountancy_code_buy,";
5640	$sql .= " t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type";
5641	$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t";
5642	if ($firstparamisid) {
5643		$sql .= " WHERE t.rowid = ".(int) $vatrate;
5644	} else {
5645		$vatratecleaned = $vatrate;
5646		$vatratecode = '';
5647		$reg = array();
5648		if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) {      // If vat is "xx (yy)"
5649			$vatratecleaned = $reg[1];
5650			$vatratecode = $reg[2];
5651		}
5652
5653		$sql .= ", ".MAIN_DB_PREFIX."c_country as c";
5654		/*if ($mysoc->country_code == 'ES') $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($buyer->country_code)."'";    // vat in spain use the buyer country ??
5655		else $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($seller->country_code)."'";*/
5656		$sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($seller->country_code)."'";
5657		$sql .= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
5658		if ($vatratecode) {
5659			$sql .= " AND t.code = '".$db->escape($vatratecode)."'";
5660		}
5661	}
5662
5663	$resql = $db->query($sql);
5664	if ($resql) {
5665		$obj = $db->fetch_object($resql);
5666		if ($obj) {
5667			return array(
5668			'rowid'=>$obj->rowid,
5669			'code'=>$obj->code,
5670			'rate'=>$obj->rate,
5671			'localtax1'=>$obj->localtax1,
5672			'localtax1_type'=>$obj->localtax1_type,
5673			'localtax2'=>$obj->localtax2,
5674			'localtax2_type'=>$obj->localtax2_type,
5675			'npr'=>$obj->npr,
5676			'accountancy_code_sell'=>$obj->accountancy_code_sell,
5677			'accountancy_code_buy'=>$obj->accountancy_code_buy
5678			);
5679		} else {
5680			return array();
5681		}
5682	} else {
5683		dol_print_error($db);
5684	}
5685
5686	return array();
5687}
5688
5689/**
5690 *  Get type and rate of localtaxes for a particular vat rate/country of a thirdparty.
5691 *  This does not take into account the seller setup if subject to vat or not, only country.
5692 *
5693 *  TODO This function is ALSO called to retrieve type for building PDF. Such call of function must be removed.
5694 *  Instead this function must be called when adding a line to get the array of possible values for localtax and type, and then
5695 *  provide the selected value to the function calcul_price_total.
5696 *
5697 *  @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.
5698 *  @param	int		    $local              Number of localtax (1 or 2, or 0 to return 1 & 2)
5699 *  @param	Societe	    $buyer         		Company object
5700 *  @param	Societe	    $seller        		Company object
5701 *  @param  int         $firstparamisid     1 if first param is ID into table instead of Rate+code (use this if you can)
5702 *  @return	array    	    				array(localtax_type1(1-6 or 0 if not found), rate localtax1, localtax_type2, rate localtax2, accountancycodecust, accountancycodesupp)
5703 *  @see getTaxesFromId()
5704 */
5705function getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid = 0)
5706{
5707	global $db, $mysoc;
5708
5709	dol_syslog("getLocalTaxesFromRate vatrate=".$vatrate." local=".$local);
5710
5711	// Search local taxes
5712	$sql  = "SELECT t.taux as rate, t.code, t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type, t.accountancy_code_sell, t.accountancy_code_buy";
5713	$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t";
5714	if ($firstparamisid) {
5715		$sql .= " WHERE t.rowid = ".(int) $vatrate;
5716	} else {
5717		$vatratecleaned = $vatrate;
5718		$vatratecode = '';
5719		$reg = array();
5720		if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) {     // If vat is "x.x (yy)"
5721			$vatratecleaned = $reg[1];
5722			$vatratecode = $reg[2];
5723		}
5724
5725		$sql .= ", ".MAIN_DB_PREFIX."c_country as c";
5726		if ($mysoc->country_code == 'ES') {
5727			$sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($buyer->country_code)."'"; // local tax in spain use the buyer country ??
5728		} else {
5729			$sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape(empty($seller->country_code) ? $mysoc->country_code : $seller->country_code)."'";
5730		}
5731		$sql .= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
5732		if ($vatratecode) {
5733			$sql .= " AND t.code = '".$db->escape($vatratecode)."'";
5734		}
5735	}
5736
5737	$resql = $db->query($sql);
5738	if ($resql) {
5739		$obj = $db->fetch_object($resql);
5740
5741		if ($obj) {
5742			$vateratestring = $obj->rate.($obj->code ? ' ('.$obj->code.')' : '');
5743
5744			if ($local == 1) {
5745				return array($obj->localtax1_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
5746			} elseif ($local == 2) {
5747				return array($obj->localtax2_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
5748			} else {
5749				return array($obj->localtax1_type, get_localtax($vateratestring, 1, $buyer, $seller), $obj->localtax2_type, get_localtax($vateratestring, 2, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
5750			}
5751		}
5752	}
5753
5754	return array();
5755}
5756
5757/**
5758 *	Return vat rate of a product in a particular country, or default country vat if product is unknown.
5759 *  Function called by get_default_tva().
5760 *
5761 *  @param	int			$idprod          	Id of product or 0 if not a predefined product
5762 *  @param  Societe		$thirdpartytouse  	Thirdparty with a ->country_code defined (FR, US, IT, ...)
5763 *	@param	int			$idprodfournprice	Id product_fournisseur_price (for "supplier" proposal/order/invoice)
5764 *  @return float|string   				    Vat rate to use with format 5.0 or '5.0 (XXX)'
5765 *  @see get_product_localtax_for_country()
5766 */
5767function get_product_vat_for_country($idprod, $thirdpartytouse, $idprodfournprice = 0)
5768{
5769	global $db, $conf, $mysoc;
5770
5771	require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
5772
5773	$ret = 0;
5774	$found = 0;
5775
5776	if ($idprod > 0) {
5777		// Load product
5778		$product = new Product($db);
5779		$result = $product->fetch($idprod);
5780
5781		if ($mysoc->country_code == $thirdpartytouse->country_code) { // If country to consider is ours
5782			if ($idprodfournprice > 0) {     // We want vat for product for a "supplier" object
5783				$product->get_buyprice($idprodfournprice, 0, 0, 0);
5784				$ret = $product->vatrate_supplier;
5785				if ($product->default_vat_code) {
5786					$ret .= ' ('.$product->default_vat_code.')';
5787				}
5788			} else {
5789				$ret = $product->tva_tx; // Default vat of product we defined
5790				if ($product->default_vat_code) {
5791					$ret .= ' ('.$product->default_vat_code.')';
5792				}
5793			}
5794			$found = 1;
5795		} else {
5796			// TODO Read default product vat according to product and another countrycode.
5797			// Vat for couple anothercountrycode/product is data that is not managed and store yet, so we will fallback on next rule.
5798		}
5799	}
5800
5801	if (!$found) {
5802		if (empty($conf->global->MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS)) {
5803			// If vat of product for the country not found or not defined, we return the first higher vat of country.
5804			$sql = "SELECT t.taux as vat_rate, t.code as default_vat_code";
5805			$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
5806			$sql .= " WHERE t.active=1 AND t.fk_pays = c.rowid AND c.code='".$db->escape($thirdpartytouse->country_code)."'";
5807			$sql .= " ORDER BY t.taux DESC, t.code ASC, t.recuperableonly ASC";
5808			$sql .= $db->plimit(1);
5809
5810			$resql = $db->query($sql);
5811			if ($resql) {
5812				$obj = $db->fetch_object($resql);
5813				if ($obj) {
5814					$ret = $obj->vat_rate;
5815					if ($obj->default_vat_code) {
5816						$ret .= ' ('.$obj->default_vat_code.')';
5817					}
5818				}
5819				$db->free($sql);
5820			} else {
5821				dol_print_error($db);
5822			}
5823		} else {
5824			$ret = $conf->global->MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS; // Forced value if autodetect fails
5825		}
5826	}
5827
5828	dol_syslog("get_product_vat_for_country: ret=".$ret);
5829	return $ret;
5830}
5831
5832/**
5833 *	Return localtax vat rate of a product in a particular country or default country vat if product is unknown
5834 *
5835 *  @param	int		$idprod         		Id of product
5836 *  @param  int		$local          		1 for localtax1, 2 for localtax 2
5837 *  @param  Societe	$thirdpartytouse    	Thirdparty with a ->country_code defined (FR, US, IT, ...)
5838 *  @return int             				<0 if KO, Vat rate if OK
5839 *  @see get_product_vat_for_country()
5840 */
5841function get_product_localtax_for_country($idprod, $local, $thirdpartytouse)
5842{
5843	global $db, $mysoc;
5844
5845	if (!class_exists('Product')) {
5846		require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
5847	}
5848
5849	$ret = 0;
5850	$found = 0;
5851
5852	if ($idprod > 0) {
5853		// Load product
5854		$product = new Product($db);
5855		$result = $product->fetch($idprod);
5856
5857		if ($mysoc->country_code == $thirdpartytouse->country_code) { // If selling country is ours
5858			/* Not defined yet, so we don't use this
5859			if ($local==1) $ret=$product->localtax1_tx;
5860			elseif ($local==2) $ret=$product->localtax2_tx;
5861			$found=1;
5862			*/
5863		} else {
5864			// TODO Read default product vat according to product and another countrycode.
5865			// Vat for couple anothercountrycode/product is data that is not managed and store yet, so we will fallback on next rule.
5866		}
5867	}
5868
5869	if (!$found) {
5870		// If vat of product for the country not found or not defined, we return higher vat of country.
5871		$sql = "SELECT taux as vat_rate, localtax1, localtax2";
5872		$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
5873		$sql .= " WHERE t.active=1 AND t.fk_pays = c.rowid AND c.code='".$db->escape($thirdpartytouse->country_code)."'";
5874		$sql .= " ORDER BY t.taux DESC, t.recuperableonly ASC";
5875		$sql .= $db->plimit(1);
5876
5877		$resql = $db->query($sql);
5878		if ($resql) {
5879			$obj = $db->fetch_object($resql);
5880			if ($obj) {
5881				if ($local == 1) {
5882					$ret = $obj->localtax1;
5883				} elseif ($local == 2) {
5884					$ret = $obj->localtax2;
5885				}
5886			}
5887		} else {
5888			dol_print_error($db);
5889		}
5890	}
5891
5892	dol_syslog("get_product_localtax_for_country: ret=".$ret);
5893	return $ret;
5894}
5895
5896/**
5897 *	Function that return vat rate of a product line (according to seller, buyer and product vat rate)
5898 *   VATRULE 1: Si vendeur non assujeti a TVA, TVA par defaut=0. Fin de regle.
5899 *	 VATRULE 2: Si le (pays vendeur = pays acheteur) alors TVA par defaut=TVA du produit vendu. Fin de regle.
5900 *	 VATRULE 3: 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.
5901 *	 VATRULE 4: Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = particulier) alors TVA par defaut=TVA du produit vendu. Fin de regle
5902 *	 VATRULE 5: Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = entreprise) alors TVA par defaut=0. Fin de regle
5903 *	 VATRULE 6: Sinon TVA proposee par defaut=0. Fin de regle.
5904 *
5905 *	@param	Societe		$thirdparty_seller    	Objet societe vendeuse
5906 *	@param  Societe		$thirdparty_buyer   	Objet societe acheteuse
5907 *	@param  int			$idprod					Id product
5908 *	@param	int			$idprodfournprice		Id product_fournisseur_price (for supplier order/invoice)
5909 *	@return float|string   				      	Vat rate to use with format 5.0 or '5.0 (XXX)', -1 if we can't guess it
5910 *  @see get_default_npr(), get_default_localtax()
5911 */
5912function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
5913{
5914	global $conf;
5915
5916	require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
5917
5918	// Note: possible values for tva_assuj are 0/1 or franchise/reel
5919	$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;
5920
5921	$seller_country_code = $thirdparty_seller->country_code;
5922	$seller_in_cee = isInEEC($thirdparty_seller);
5923
5924	$buyer_country_code = $thirdparty_buyer->country_code;
5925	$buyer_in_cee = isInEEC($thirdparty_buyer);
5926
5927	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($conf->global->SERVICES_ARE_ECOMMERCE_200238EC) ? $conf->global->SERVICES_ARE_ECOMMERCE_200238EC : ''));
5928
5929	// 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)
5930	// we use the buyer VAT.
5931	if (!empty($conf->global->SERVICE_ARE_ECOMMERCE_200238EC)) {
5932		if ($seller_in_cee && $buyer_in_cee) {
5933			$isacompany = $thirdparty_buyer->isACompany();
5934			if ($isacompany && !empty($conf->global->MAIN_USE_VAT_COMPANIES_IN_EEC_WITH_INVALID_VAT_ID_ARE_INDIVIDUAL)) {
5935				require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
5936				if (!isValidVATID($thirdparty_buyer)) {
5937					$isacompany = 0;
5938				}
5939			}
5940
5941			if (!$isacompany) {
5942				//print 'VATRULE 0';
5943				return get_product_vat_for_country($idprod, $thirdparty_buyer, $idprodfournprice);
5944			}
5945		}
5946	}
5947
5948	// If seller does not use VAT
5949	if (!$seller_use_vat) {
5950		//print 'VATRULE 1';
5951		return 0;
5952	}
5953
5954	// 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.
5955
5956	// Si le (pays vendeur = pays acheteur) alors la TVA par defaut=TVA du produit vendu. Fin de regle.
5957	if (($seller_country_code == $buyer_country_code)
5958	|| (in_array($seller_country_code, array('FR,MC')) && in_array($buyer_country_code, array('FR', 'MC')))) { // Warning ->country_code not always defined
5959		//print 'VATRULE 2';
5960		return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
5961	}
5962
5963	// 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.
5964	// 'VATRULE 3' - Not supported
5965
5966	// Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = entreprise) alors TVA par defaut=0. Fin de regle
5967	// Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = particulier) alors TVA par defaut=TVA du produit vendu. Fin de regle
5968	if (($seller_in_cee && $buyer_in_cee)) {
5969		$isacompany = $thirdparty_buyer->isACompany();
5970		if ($isacompany && !empty($conf->global->MAIN_USE_VAT_COMPANIES_IN_EEC_WITH_INVALID_VAT_ID_ARE_INDIVIDUAL)) {
5971			require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
5972			if (!isValidVATID($thirdparty_buyer)) {
5973				$isacompany = 0;
5974			}
5975		}
5976
5977		if (!$isacompany) {
5978			//print 'VATRULE 4';
5979			return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
5980		} else {
5981			//print 'VATRULE 5';
5982			return 0;
5983		}
5984	}
5985
5986	// Si (vendeur dans Communaute europeene et acheteur hors Communaute europeenne et acheteur particulier) alors TVA par defaut=TVA du produit vendu. Fin de regle
5987	// I don't see any use case that need this rule.
5988	if (!empty($conf->global->MAIN_USE_VAT_OF_PRODUCT_FOR_INDIVIDUAL_CUSTOMER_OUT_OF_EEC) && empty($buyer_in_cee)) {
5989		$isacompany = $thirdparty_buyer->isACompany();
5990		if (!$isacompany) {
5991			return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
5992			//print 'VATRULE extra';
5993		}
5994	}
5995
5996	// Sinon la TVA proposee par defaut=0. Fin de regle.
5997	// Rem: Cela signifie qu'au moins un des 2 est hors Communaute europeenne et que le pays differe
5998	//print 'VATRULE 6';
5999	return 0;
6000}
6001
6002
6003/**
6004 *	Fonction qui renvoie si tva doit etre tva percue recuperable
6005 *
6006 *	@param	Societe		$thirdparty_seller    	Thirdparty seller
6007 *	@param  Societe		$thirdparty_buyer   	Thirdparty buyer
6008 *  @param  int			$idprod                 Id product
6009 *  @param	int			$idprodfournprice		Id supplier price for product
6010 *	@return float       			        	0 or 1
6011 *  @see get_default_tva(), get_default_localtax()
6012 */
6013function get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
6014{
6015	global $db;
6016
6017	if ($idprodfournprice > 0) {
6018		if (!class_exists('ProductFournisseur')) {
6019			require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
6020		}
6021		$prodprice = new ProductFournisseur($db);
6022		$prodprice->fetch_product_fournisseur_price($idprodfournprice);
6023		return $prodprice->fourn_tva_npr;
6024	} elseif ($idprod > 0) {
6025		if (!class_exists('Product')) {
6026			require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
6027		}
6028		$prod = new Product($db);
6029		$prod->fetch($idprod);
6030		return $prod->tva_npr;
6031	}
6032
6033	return 0;
6034}
6035
6036/**
6037 *	Function that return localtax of a product line (according to seller, buyer and product vat rate)
6038 *   Si vendeur non assujeti a TVA, TVA par defaut=0. Fin de regle.
6039 *	 Si le (pays vendeur = pays acheteur) alors TVA par defaut=TVA du produit vendu. Fin de regle.
6040 *	 Sinon TVA proposee par defaut=0. Fin de regle.
6041 *
6042 *	@param	Societe		$thirdparty_seller    	Thirdparty seller
6043 *	@param  Societe		$thirdparty_buyer   	Thirdparty buyer
6044 *  @param	int			$local					Localtax to process (1 or 2)
6045 *	@param  int			$idprod					Id product
6046 *	@return integer        				       	localtax, -1 si ne peut etre determine
6047 *  @see get_default_tva(), get_default_npr()
6048 */
6049function get_default_localtax($thirdparty_seller, $thirdparty_buyer, $local, $idprod = 0)
6050{
6051	global $mysoc;
6052
6053	if (!is_object($thirdparty_seller)) {
6054		return -1;
6055	}
6056	if (!is_object($thirdparty_buyer)) {
6057		return -1;
6058	}
6059
6060	if ($local == 1) { // Localtax 1
6061		if ($mysoc->country_code == 'ES') {
6062			if (is_numeric($thirdparty_buyer->localtax1_assuj) && !$thirdparty_buyer->localtax1_assuj) {
6063				return 0;
6064			}
6065		} else {
6066			// Si vendeur non assujeti a Localtax1, localtax1 par default=0
6067			if (is_numeric($thirdparty_seller->localtax1_assuj) && !$thirdparty_seller->localtax1_assuj) {
6068				return 0;
6069			}
6070			if (!is_numeric($thirdparty_seller->localtax1_assuj) && $thirdparty_seller->localtax1_assuj == 'localtax1off') {
6071				return 0;
6072			}
6073		}
6074	} elseif ($local == 2) { //I Localtax 2
6075		// Si vendeur non assujeti a Localtax2, localtax2 par default=0
6076		if (is_numeric($thirdparty_seller->localtax2_assuj) && !$thirdparty_seller->localtax2_assuj) {
6077			return 0;
6078		}
6079		if (!is_numeric($thirdparty_seller->localtax2_assuj) && $thirdparty_seller->localtax2_assuj == 'localtax2off') {
6080			return 0;
6081		}
6082	}
6083
6084	if ($thirdparty_seller->country_code == $thirdparty_buyer->country_code) {
6085		return get_product_localtax_for_country($idprod, $local, $thirdparty_seller);
6086	}
6087
6088	return 0;
6089}
6090
6091/**
6092 *	Return yes or no in current language
6093 *
6094 *	@param	string|int	$yesno			Value to test (1, 'yes', 'true' or 0, 'no', 'false')
6095 *	@param	integer		$case			1=Yes/No, 0=yes/no, 2=Disabled checkbox, 3=Disabled checkbox + Yes/No
6096 *	@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.
6097 *	@return	string						HTML string
6098 */
6099function yn($yesno, $case = 1, $color = 0)
6100{
6101	global $langs;
6102	$result = 'unknown';
6103	$classname = '';
6104	if ($yesno == 1 || strtolower($yesno) == 'yes' || strtolower($yesno) == 'true') { 	// A mettre avant test sur no a cause du == 0
6105		$result = $langs->trans('yes');
6106		if ($case == 1 || $case == 3) {
6107			$result = $langs->trans("Yes");
6108		}
6109		if ($case == 2) {
6110			$result = '<input type="checkbox" value="1" checked disabled>';
6111		}
6112		if ($case == 3) {
6113			$result = '<input type="checkbox" value="1" checked disabled> '.$result;
6114		}
6115
6116		$classname = 'ok';
6117	} elseif ($yesno == 0 || strtolower($yesno) == 'no' || strtolower($yesno) == 'false') {
6118		$result = $langs->trans("no");
6119		if ($case == 1 || $case == 3) {
6120			$result = $langs->trans("No");
6121		}
6122		if ($case == 2) {
6123			$result = '<input type="checkbox" value="0" disabled>';
6124		}
6125		if ($case == 3) {
6126			$result = '<input type="checkbox" value="0" disabled> '.$result;
6127		}
6128
6129		if ($color == 2) {
6130			$classname = 'ok';
6131		} else {
6132			$classname = 'error';
6133		}
6134	}
6135	if ($color) {
6136		return '<font class="'.$classname.'">'.$result.'</font>';
6137	}
6138	return $result;
6139}
6140
6141/**
6142 *	Return a path to have a the directory according to object where files are stored.
6143 *  New usage:       $conf->module->multidir_output[$object->entity].'/'.get_exdir(0, 0, 0, 1, $object, '').'/'
6144 *         or:       $conf->module->dir_output.'/'.get_exdir(0, 0, 0, 0, $object, '')     if multidir_output not defined.
6145 *  Example out with new usage:       $object is invoice -> 'INYYMM-ABCD'
6146 *  Example out with old usage:       '015' with level 3->"0/1/5/", '015' with level 1->"5/", 'ABC-1' with level 3 ->"0/0/1/"
6147 *
6148 *	@param	string|int	$num            Id of object (deprecated, $object will be used in future)
6149 *	@param  int			$level		    Level of subdirs to return (1, 2 or 3 levels). (deprecated, global option will be used in future)
6150 * 	@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)
6151 *  @param  int			$withoutslash   0=With slash at end (except if '/', we return ''), 1=without slash at end
6152 *  @param	Object		$object			Object to use to get ref to forge the path.
6153 *  @param	string		$modulepart		Type of object ('invoice_supplier, 'donation', 'invoice', ...'). Use '' for autodetect from $object.
6154 *  @return	string						Dir to use ending. Example '' or '1/' or '1/2/'
6155 */
6156function get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart = '')
6157{
6158	global $conf;
6159
6160	if (empty($modulepart) && !empty($object->module)) {
6161		$modulepart = $object->module;
6162	}
6163
6164	$path = '';
6165
6166	$arrayforoldpath = array('cheque', 'category', 'holiday', 'supplier_invoice', 'invoice_supplier', 'mailing', 'supplier_payment');
6167	if (!empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO)) {
6168		$arrayforoldpath[] = 'product';
6169	}
6170	if (!empty($level) && in_array($modulepart, $arrayforoldpath)) {
6171		// This part should be removed once all code is using "get_exdir" to forge path, with parameter $object and $modulepart provided.
6172		if (empty($alpha)) {
6173			$num = preg_replace('/([^0-9])/i', '', $num);
6174		} else {
6175			$num = preg_replace('/^.*\-/i', '', $num);
6176		}
6177		$num = substr("000".$num, -$level);
6178		if ($level == 1) {
6179			$path = substr($num, 0, 1);
6180		}
6181		if ($level == 2) {
6182			$path = substr($num, 1, 1).'/'.substr($num, 0, 1);
6183		}
6184		if ($level == 3) {
6185			$path = substr($num, 2, 1).'/'.substr($num, 1, 1).'/'.substr($num, 0, 1);
6186		}
6187	} else {
6188		// We will enhance here a common way of forging path for document storage.
6189		// In a future, we may distribute directories on several levels depending on setup and object.
6190		// Here, $object->id, $object->ref and $modulepart are required.
6191		//var_dump($modulepart);
6192		$path = dol_sanitizeFileName(empty($object->ref) ? (string) $object->id : $object->ref);
6193	}
6194
6195	if (empty($withoutslash) && !empty($path)) {
6196		$path .= '/';
6197	}
6198
6199	return $path;
6200}
6201
6202/**
6203 *	Creation of a directory (this can create recursive subdir)
6204 *
6205 *	@param	string		$dir		Directory to create (Separator must be '/'. Example: '/mydir/mysubdir')
6206 *	@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)
6207 *  @param	string|null	$newmask	Mask for new file (Defaults to $conf->global->MAIN_UMASK or 0755 if unavailable). Example: '0444'
6208 *	@return int         			< 0 if KO, 0 = already exists, > 0 if OK
6209 */
6210function dol_mkdir($dir, $dataroot = '', $newmask = null)
6211{
6212	global $conf;
6213
6214	dol_syslog("functions.lib::dol_mkdir: dir=".$dir, LOG_INFO);
6215
6216	$dir_osencoded = dol_osencode($dir);
6217	if (@is_dir($dir_osencoded)) {
6218		return 0;
6219	}
6220
6221	$nberr = 0;
6222	$nbcreated = 0;
6223
6224	$ccdir = '';
6225	if (!empty($dataroot)) {
6226		// Remove data root from loop
6227		$dir = str_replace($dataroot.'/', '', $dir);
6228		$ccdir = $dataroot.'/';
6229	}
6230
6231	$cdir = explode("/", $dir);
6232	$num = count($cdir);
6233	for ($i = 0; $i < $num; $i++) {
6234		if ($i > 0) {
6235			$ccdir .= '/'.$cdir[$i];
6236		} else {
6237			$ccdir .= $cdir[$i];
6238		}
6239		if (preg_match("/^.:$/", $ccdir, $regs)) {
6240			continue; // Si chemin Windows incomplet, on poursuit par rep suivant
6241		}
6242
6243		// Attention, le is_dir() peut echouer bien que le rep existe.
6244		// (ex selon config de open_basedir)
6245		if ($ccdir) {
6246			$ccdir_osencoded = dol_osencode($ccdir);
6247			if (!@is_dir($ccdir_osencoded)) {
6248				dol_syslog("functions.lib::dol_mkdir: Directory '".$ccdir."' does not exists or is outside open_basedir PHP setting.", LOG_DEBUG);
6249
6250				umask(0);
6251				$dirmaskdec = octdec($newmask);
6252				if (empty($newmask)) {
6253					$dirmaskdec = empty($conf->global->MAIN_UMASK) ? octdec('0755') : octdec($conf->global->MAIN_UMASK);
6254				}
6255				$dirmaskdec |= octdec('0111'); // Set x bit required for directories
6256				if (!@mkdir($ccdir_osencoded, $dirmaskdec)) {
6257					// Si le is_dir a renvoye une fausse info, alors on passe ici.
6258					dol_syslog("functions.lib::dol_mkdir: Fails to create directory '".$ccdir."' or directory already exists.", LOG_WARNING);
6259					$nberr++;
6260				} else {
6261					dol_syslog("functions.lib::dol_mkdir: Directory '".$ccdir."' created", LOG_DEBUG);
6262					$nberr = 0; // On remet a zero car si on arrive ici, cela veut dire que les echecs precedents peuvent etre ignore
6263					$nbcreated++;
6264				}
6265			} else {
6266				$nberr = 0; // On remet a zero car si on arrive ici, cela veut dire que les echecs precedents peuvent etre ignores
6267			}
6268		}
6269	}
6270	return ($nberr ? -$nberr : $nbcreated);
6271}
6272
6273
6274/**
6275 *	Return picto saying a field is required
6276 *
6277 *	@return  string		Chaine avec picto obligatoire
6278 */
6279function picto_required()
6280{
6281	return '<span class="fieldrequired">*</span>';
6282}
6283
6284
6285/**
6286 *	Clean a string from all HTML tags and entities.
6287 *  This function differs from strip_tags because:
6288 *  - <br> are replaced with \n if removelinefeed=0 or 1
6289 *  - if entities are found, they are decoded BEFORE the strip
6290 *  - you can decide to convert line feed into a space
6291 *
6292 *	@param	string	$stringtoclean		String to clean
6293 *	@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..."
6294 *  @param  string	$pagecodeto      	Encoding of input/output string
6295 *  @param	integer	$strip_tags			0=Use internal strip, 1=Use strip_tags() php function (bugged when text contains a < char that is not for a html tag or when tags is not closed like '<img onload=aaa')
6296 *  @param	integer	$removedoublespaces	Replace double space into one space
6297 *	@return string	    				String cleaned
6298 *
6299 * 	@see	dol_escape_htmltag() strip_tags() dol_string_onlythesehtmltags() dol_string_neverthesehtmltags(), dolStripPhpCode()
6300 */
6301function dol_string_nohtmltag($stringtoclean, $removelinefeed = 1, $pagecodeto = 'UTF-8', $strip_tags = 0, $removedoublespaces = 1)
6302{
6303	if ($removelinefeed == 2) {
6304		$stringtoclean = preg_replace('/<br[^>]*>(\n|\r)+/ims', '<br>', $stringtoclean);
6305	}
6306	$temp = preg_replace('/<br[^>]*>/i', "\n", $stringtoclean);
6307
6308	// We remove entities BEFORE stripping (in case of an open separator char that is entity encoded and not the closing other, the strip will fails)
6309	$temp = dol_html_entity_decode($temp, ENT_COMPAT | ENT_HTML5, $pagecodeto);
6310
6311	$temp = str_replace('< ', '__ltspace__', $temp);
6312
6313	if ($strip_tags) {
6314		$temp = strip_tags($temp);
6315	} else {
6316		$temp = str_replace('<>', '', $temp);	// No reason to have this into a text, except if value is to try bypass the next html cleaning
6317		$pattern = "/<[^<>]+>/";
6318		// Example of $temp: <a href="/myurl" title="<u>A title</u>">0000-021</a>
6319		$temp = preg_replace($pattern, "", $temp); // pass 1 - $temp after pass 1: <a href="/myurl" title="A title">0000-021
6320		$temp = preg_replace($pattern, "", $temp); // pass 2 - $temp after pass 2: 0000-021
6321		// Remove '<' into remainging, so remove non closing html tags like '<abc' or '<<abc'. Note: '<123abc' is not a html tag (can be kept), but '<abc123' is (must be removed).
6322		$temp = preg_replace('/<+([a-z]+)/i', '\1', $temp);
6323	}
6324
6325	$temp = dol_html_entity_decode($temp, ENT_COMPAT, $pagecodeto);
6326
6327	// Remove also carriage returns
6328	if ($removelinefeed == 1) {
6329		$temp = str_replace(array("\r\n", "\r", "\n"), " ", $temp);
6330	}
6331
6332	// And double quotes
6333	if ($removedoublespaces) {
6334		while (strpos($temp, "  ")) {
6335			$temp = str_replace("  ", " ", $temp);
6336		}
6337	}
6338
6339	$temp = str_replace('__ltspace__', '< ', $temp);
6340
6341	return trim($temp);
6342}
6343
6344/**
6345 *	Clean a string to keep only desirable HTML tags.
6346 *  WARNING: This also clean HTML comments (used to obfuscate tag name).
6347 *
6348 *	@param	string	$stringtoclean			String to clean
6349 *  @param	int		$cleanalsosomestyles	Remove absolute/fixed positioning from inline styles
6350 *  @param	int		$removeclassattribute	1=Remove the class attribute from tags
6351 *  @param	int		$cleanalsojavascript	Remove also occurence of 'javascript:'.
6352 *  @param	int		$allowiframe			Allow iframe tags.
6353 *	@return string	    					String cleaned
6354 *
6355 * 	@see	dol_escape_htmltag() strip_tags() dol_string_nohtmltag() dol_string_neverthesehtmltags()
6356 */
6357function dol_string_onlythesehtmltags($stringtoclean, $cleanalsosomestyles = 1, $removeclassattribute = 1, $cleanalsojavascript = 0, $allowiframe = 0)
6358{
6359	$allowed_tags = array(
6360		"html", "head", "meta", "body", "article", "a", "abbr", "b", "blockquote", "br", "cite", "div", "dl", "dd", "dt", "em", "font", "img", "ins", "hr", "i", "li", "link",
6361		"ol", "p", "q", "s", "section", "span", "strike", "strong", "title", "table", "tr", "th", "td", "u", "ul", "sup", "sub", "blockquote", "pre", "h1", "h2", "h3", "h4", "h5", "h6"
6362	);
6363	if ($allowiframe) {
6364		$allowed_tags[] = "iframe";
6365	}
6366
6367	$allowed_tags_string = join("><", $allowed_tags);
6368	$allowed_tags_string = '<'.$allowed_tags_string.'>';
6369
6370	$stringtoclean = str_replace('<!DOCTYPE html>', '__!DOCTYPE_HTML__', $stringtoclean);	// Replace DOCTYPE to avoid to have it removed by the strip_tags
6371
6372	$stringtoclean = dol_string_nounprintableascii($stringtoclean, 0);
6373
6374	$stringtoclean = preg_replace('/<!--[^>]*-->/', '', $stringtoclean);
6375
6376	$stringtoclean = preg_replace('/&colon;/i', ':', $stringtoclean);
6377	$stringtoclean = preg_replace('/&#58;|&#0+58|&#x3A/i', '', $stringtoclean); // refused string ':' encoded (no reason to have a : encoded like this) to disable 'javascript:...'
6378	$stringtoclean = preg_replace('/javascript\s*:/i', '', $stringtoclean);
6379
6380	$temp = strip_tags($stringtoclean, $allowed_tags_string);	// Warning: This remove also undesired </> changing string obfuscated with </> that pass injection detection into harmfull string
6381
6382	if ($cleanalsosomestyles) {	// Clean for remaining html tags
6383		$temp = preg_replace('/position\s*:\s*(absolute|fixed)\s*!\s*important/i', '', $temp); // Note: If hacker try to introduce css comment into string to bypass this regex, the string must also be encoded by the dol_htmlentitiesbr during output so it become harmless
6384	}
6385	if ($removeclassattribute) {	// Clean for remaining html tags
6386		$temp = preg_replace('/(<[^>]+)\s+class=((["\']).*?\\3|\\w*)/i', '\\1', $temp);
6387	}
6388
6389	// Remove 'javascript:' that we should not find into a text with
6390	// Warning: This is not reliable to fight against obfuscated javascript, there is a lot of other solution to include js into a common html tag (only filtered by a GETPOST(.., powerfullfilter)).
6391	if ($cleanalsojavascript) {
6392		$temp = preg_replace('/javascript\s*:/i', '', $temp);
6393	}
6394
6395	$temp = str_replace('__!DOCTYPE_HTML__', '<!DOCTYPE html>', $temp);	// Restore the DOCTYPE
6396
6397	return $temp;
6398}
6399
6400
6401/**
6402 *	Clean a string from some undesirable HTML tags.
6403 *  Note. Not as secured as dol_string_onlythesehtmltags().
6404 *
6405 *	@param	string	$stringtoclean			String to clean
6406 *  @param	array	$allowed_attributes		Array of tags not allowed
6407 *	@return string	    					String cleaned
6408 *
6409 * 	@see	dol_escape_htmltag() strip_tags() dol_string_nohtmltag() dol_string_onlythesehtmltags() dol_string_neverthesehtmltags()
6410 */
6411function dol_string_onlythesehtmlattributes($stringtoclean, $allowed_attributes = array("allow", "allowfullscreen", "alt", "class", "contenteditable", "data-html", "frameborder", "height", "href", "id", "name", "src", "style", "target", "title", "width"))
6412{
6413	if (class_exists('DOMDocument') && !empty($stringtoclean)) {
6414		$stringtoclean = '<html><body>'.$stringtoclean.'</body></html>';
6415
6416		$dom = new DOMDocument();
6417		$dom->loadHTML($stringtoclean, LIBXML_ERR_NONE|LIBXML_HTML_NOIMPLIED|LIBXML_HTML_NODEFDTD|LIBXML_NONET|LIBXML_NOWARNING|LIBXML_NOXMLDECL);
6418		if (is_object($dom)) {
6419			for ($els = $dom->getElementsByTagname('*'), $i = $els->length - 1; $i >= 0; $i--) {
6420				for ($attrs = $els->item($i)->attributes, $ii = $attrs->length - 1; $ii >= 0; $ii--) {
6421					// Delete attribute if not into allowed_attributes
6422					if (! empty($attrs->item($ii)->name) && ! in_array($attrs->item($ii)->name, $allowed_attributes)) {
6423						$els->item($i)->removeAttribute($attrs->item($ii)->name);
6424					}
6425				}
6426			}
6427		}
6428
6429		$return = $dom->saveHTML();
6430		//$return = '<html><body>aaaa</p>bb<p>ssdd</p>'."\n<p>aaa</p>aa<p>bb</p>";
6431
6432		$return = preg_replace('/^<html><body>/', '', $return);
6433		$return = preg_replace('/<\/body><\/html>$/', '', $return);
6434		return $return;
6435	} else {
6436		return $stringtoclean;
6437	}
6438}
6439
6440/**
6441 *	Clean a string from some undesirable HTML tags.
6442 *  Note. Not as secured as dol_string_onlythesehtmltags().
6443 *
6444 *	@param	string	$stringtoclean			String to clean
6445 *  @param	array	$disallowed_tags		Array of tags not allowed
6446 *  @param	string	$cleanalsosomestyles	Clean also some tags
6447 *	@return string	    					String cleaned
6448 *
6449 * 	@see	dol_escape_htmltag() strip_tags() dol_string_nohtmltag() dol_string_onlythesehtmltags() dol_string_onlythesehtmlattributes()
6450 */
6451function dol_string_neverthesehtmltags($stringtoclean, $disallowed_tags = array('textarea'), $cleanalsosomestyles = 0)
6452{
6453	$temp = $stringtoclean;
6454	foreach ($disallowed_tags as $tagtoremove) {
6455		$temp = preg_replace('/<\/?'.$tagtoremove.'>/', '', $temp);
6456		$temp = preg_replace('/<\/?'.$tagtoremove.'\s+[^>]*>/', '', $temp);
6457	}
6458
6459	if ($cleanalsosomestyles) {
6460		$temp = preg_replace('/position\s*:\s*(absolute|fixed)\s*!\s*important/', '', $temp); // Note: If hacker try to introduce css comment into string to avoid this, string should be encoded by the dol_htmlentitiesbr so be harmless
6461	}
6462
6463	return $temp;
6464}
6465
6466
6467/**
6468 * Return first line of text. Cut will depends if content is HTML or not.
6469 *
6470 * @param 	string	$text		Input text
6471 * @param	int		$nboflines  Nb of lines to get (default is 1 = first line only)
6472 * @param   string  $charset    Charset of $text string (UTF-8 by default)
6473 * @return	string				Output text
6474 * @see dol_nboflines_bis(), dol_string_nohtmltag(), dol_escape_htmltag()
6475 */
6476function dolGetFirstLineOfText($text, $nboflines = 1, $charset = 'UTF-8')
6477{
6478	if ($nboflines == 1) {
6479		if (dol_textishtml($text)) {
6480			$firstline = preg_replace('/<br[^>]*>.*$/s', '', $text); // The s pattern modifier means the . can match newline characters
6481			$firstline = preg_replace('/<div[^>]*>.*$/s', '', $firstline); // The s pattern modifier means the . can match newline characters
6482		} else {
6483			$firstline = preg_replace('/[\n\r].*/', '', $text);
6484		}
6485		return $firstline.((strlen($firstline) != strlen($text)) ? '...' : '');
6486	} else {
6487		$ishtml = 0;
6488		if (dol_textishtml($text)) {
6489			$text = preg_replace('/\n/', '', $text);
6490			$ishtml = 1;
6491			$repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
6492		} else {
6493			$repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
6494		}
6495
6496		$text = strtr($text, $repTable);
6497		if ($charset == 'UTF-8') {
6498			$pattern = '/(<br[^>]*>)/Uu';
6499		} else {
6500			// /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
6501			$pattern = '/(<br[^>]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
6502		}
6503		$a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
6504
6505		$firstline = '';
6506		$i = 0;
6507		$nba = count($a); // 2x nb of lines in $a because $a contains also a line for each new line separator
6508		while (($i < $nba) && ($i < ($nboflines * 2))) {
6509			if ($i % 2 == 0) {
6510				$firstline .= $a[$i];
6511			} elseif (($i < (($nboflines * 2) - 1)) && ($i < ($nba - 1))) {
6512				$firstline .= ($ishtml ? "<br>\n" : "\n");
6513			}
6514			$i++;
6515		}
6516		unset($a);
6517		return $firstline.(($i < $nba) ? '...' : '');
6518	}
6519}
6520
6521
6522/**
6523 * Replace CRLF in string with a HTML BR tag.
6524 * WARNING: The content after operation contains some HTML tags (the <br>) so be sure to also have encode the special chars of stringtoencode into HTML before.
6525 *
6526 * @param	string	$stringtoencode		String to encode
6527 * @param	int     $nl2brmode			0=Adding br before \n, 1=Replacing \n by br
6528 * @param   bool	$forxml             false=Use <br>, true=Use <br />
6529 * @return	string						String encoded
6530 * @see dol_nboflines(), dolGetFirstLineOfText()
6531 */
6532function dol_nl2br($stringtoencode, $nl2brmode = 0, $forxml = false)
6533{
6534	if (!$nl2brmode) {
6535		return nl2br($stringtoencode, $forxml);
6536	} else {
6537		$ret = preg_replace('/(\r\n|\r|\n)/i', ($forxml ? '<br />' : '<br>'), $stringtoencode);
6538		return $ret;
6539	}
6540}
6541
6542
6543/**
6544 *	This function is called to encode a string into a HTML string but differs from htmlentities because
6545 * 	a detection is done before to see if text is already HTML or not. Also, all entities but &,<,>," are converted.
6546 *  This permits to encode special chars to entities with no double encoding for already encoded HTML strings.
6547 * 	This function also remove last EOL or BR if $removelasteolbr=1 (default).
6548 *  For PDF usage, you can show text by 2 ways:
6549 *              - writeHTMLCell -> param must be encoded into HTML.
6550 *              - MultiCell -> param must not be encoded into HTML.
6551 *              Because writeHTMLCell convert also \n into <br>, if function
6552 *              is used to build PDF, nl2brmode must be 1.
6553 *
6554 *	@param	string	$stringtoencode		String to encode
6555 *	@param	int		$nl2brmode			0=Adding br before \n, 1=Replacing \n by br (for use with FPDF writeHTMLCell function for example)
6556 *  @param  string	$pagecodefrom       Pagecode stringtoencode is encoded
6557 *  @param	int		$removelasteolbr	1=Remove last br or lasts \n (default), 0=Do nothing
6558 *  @return	string						String encoded
6559 */
6560function dol_htmlentitiesbr($stringtoencode, $nl2brmode = 0, $pagecodefrom = 'UTF-8', $removelasteolbr = 1)
6561{
6562	$newstring = $stringtoencode;
6563	if (dol_textishtml($stringtoencode)) {	// Check if text is already HTML or not
6564		$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.
6565		if ($removelasteolbr) {
6566			$newstring = preg_replace('/<br>$/i', '', $newstring); // Remove last <br> (remove only last one)
6567		}
6568		$newstring = strtr($newstring, array('&'=>'__and__', '<'=>'__lt__', '>'=>'__gt__', '"'=>'__dquot__'));
6569		$newstring = dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom); // Make entity encoding
6570		$newstring = strtr($newstring, array('__and__'=>'&', '__lt__'=>'<', '__gt__'=>'>', '__dquot__'=>'"'));
6571	} else {
6572		if ($removelasteolbr) {
6573			$newstring = preg_replace('/(\r\n|\r|\n)$/i', '', $newstring); // Remove last \n (may remove several)
6574		}
6575		$newstring = dol_nl2br(dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom), $nl2brmode);
6576	}
6577	// Other substitutions that htmlentities does not do
6578	//$newstring=str_replace(chr(128),'&euro;',$newstring);	// 128 = 0x80. Not in html entity table.     // Seems useles with TCPDF. Make bug with UTF8 languages
6579	return $newstring;
6580}
6581
6582/**
6583 *	This function is called to decode a HTML string (it decodes entities and br tags)
6584 *
6585 *	@param	string	$stringtodecode		String to decode
6586 *	@param	string	$pagecodeto			Page code for result
6587 *	@return	string						String decoded
6588 */
6589function dol_htmlentitiesbr_decode($stringtodecode, $pagecodeto = 'UTF-8')
6590{
6591	$ret = dol_html_entity_decode($stringtodecode, ENT_COMPAT | ENT_HTML5, $pagecodeto);
6592	$ret = preg_replace('/'."\r\n".'<br(\s[\sa-zA-Z_="]*)?\/?>/i', "<br>", $ret);
6593	$ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>'."\r\n".'/i', "\r\n", $ret);
6594	$ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>'."\n".'/i', "\n", $ret);
6595	$ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>/i', "\n", $ret);
6596	return $ret;
6597}
6598
6599/**
6600 *	This function remove all ending \n and br at end
6601 *
6602 *	@param	string	$stringtodecode		String to decode
6603 *	@return	string						String decoded
6604 */
6605function dol_htmlcleanlastbr($stringtodecode)
6606{
6607	$ret = preg_replace('/(<br>|<br(\s[\sa-zA-Z_="]*)?\/?>|'."\n".'|'."\r".')+$/i', "", $stringtodecode);
6608	return $ret;
6609}
6610
6611/**
6612 * Replace html_entity_decode functions to manage errors
6613 *
6614 * @param   string	$a					Operand a
6615 * @param   string	$b					Operand b (ENT_QUOTES|ENT_HTML5=convert simple, double quotes, colon, e accent, ...)
6616 * @param   string	$c					Operand c
6617 * @param	string	$keepsomeentities	Entities but &, <, >, " are not converted.
6618 * @return  string						String decoded
6619 */
6620function dol_html_entity_decode($a, $b, $c = 'UTF-8', $keepsomeentities = 0)
6621{
6622	$newstring = $a;
6623	if ($keepsomeentities) {
6624		$newstring = strtr($newstring, array('&amp;'=>'__andamp__', '&lt;'=>'__andlt__', '&gt;'=>'__andgt__', '"'=>'__dquot__'));
6625	}
6626	$newstring = html_entity_decode($newstring, $b, $c);
6627	if ($keepsomeentities) {
6628		$newstring = strtr($newstring, array('__andamp__'=>'&amp;', '__andlt__'=>'&lt;', '__andgt__'=>'&gt;', '__dquot__'=>'"'));
6629	}
6630	return $newstring;
6631}
6632
6633/**
6634 * Replace htmlentities functions.
6635 * Goal of this function is to be sure to have default values of htmlentities that match what we need.
6636 *
6637 * @param   string  $string         The input string to encode
6638 * @param   int     $flags          Flags (see PHP doc above)
6639 * @param   string  $encoding       Encoding page code
6640 * @param   bool    $double_encode  When double_encode is turned off, PHP will not encode existing html entities
6641 * @return  string  $ret            Encoded string
6642 */
6643function dol_htmlentities($string, $flags = null, $encoding = 'UTF-8', $double_encode = false)
6644{
6645	return htmlentities($string, $flags, $encoding, $double_encode);
6646}
6647
6648/**
6649 *	Check if a string is a correct iso string
6650 *	If not, it will we considered not HTML encoded even if it is by FPDF.
6651 *	Example, if string contains euro symbol that has ascii code 128
6652 *
6653 *	@param	string		$s      	String to check
6654 *  @param	string		$clean		Clean if it is not an ISO. Warning, if file is utf8, you will get a bad formated file.
6655 *	@return	int|string  	   		0 if bad iso, 1 if good iso, Or the clean string if $clean is 1
6656 */
6657function dol_string_is_good_iso($s, $clean = 0)
6658{
6659	$len = dol_strlen($s);
6660	$out = '';
6661	$ok = 1;
6662	for ($scursor = 0; $scursor < $len; $scursor++) {
6663		$ordchar = ord($s[$scursor]);
6664		//print $scursor.'-'.$ordchar.'<br>';
6665		if ($ordchar < 32 && $ordchar != 13 && $ordchar != 10) {
6666			$ok = 0;
6667			break;
6668		} elseif ($ordchar > 126 && $ordchar < 160) {
6669			$ok = 0;
6670			break;
6671		} elseif ($clean) {
6672			$out .= $s[$scursor];
6673		}
6674	}
6675	if ($clean) {
6676		return $out;
6677	}
6678	return $ok;
6679}
6680
6681/**
6682 *	Return nb of lines of a clear text
6683 *
6684 *	@param	string	$s			String to check
6685 * 	@param	int     $maxchar	Not yet used
6686 *	@return	int					Number of lines
6687 *  @see	dol_nboflines_bis(), dolGetFirstLineOfText()
6688 */
6689function dol_nboflines($s, $maxchar = 0)
6690{
6691	if ($s == '') {
6692		return 0;
6693	}
6694	$arraystring = explode("\n", $s);
6695	$nb = count($arraystring);
6696
6697	return $nb;
6698}
6699
6700
6701/**
6702 *	Return nb of lines of a formated text with \n and <br> (WARNING: string must not have mixed \n and br separators)
6703 *
6704 *	@param	string	$text      		Text
6705 *	@param	int		$maxlinesize  	Largeur de ligne en caracteres (ou 0 si pas de limite - defaut)
6706 * 	@param	string	$charset		Give the charset used to encode the $text variable in memory.
6707 *	@return int						Number of lines
6708 *	@see	dol_nboflines(), dolGetFirstLineOfText()
6709 */
6710function dol_nboflines_bis($text, $maxlinesize = 0, $charset = 'UTF-8')
6711{
6712	$repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
6713	if (dol_textishtml($text)) {
6714		$repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
6715	}
6716
6717	$text = strtr($text, $repTable);
6718	if ($charset == 'UTF-8') {
6719		$pattern = '/(<br[^>]*>)/Uu';
6720	} else {
6721		// /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
6722		$pattern = '/(<br[^>]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
6723	}
6724	$a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
6725
6726	$nblines = (int) floor((count($a) + 1) / 2);
6727	// count possible auto line breaks
6728	if ($maxlinesize) {
6729		foreach ($a as $line) {
6730			if (dol_strlen($line) > $maxlinesize) {
6731				//$line_dec = html_entity_decode(strip_tags($line));
6732				$line_dec = html_entity_decode($line);
6733				if (dol_strlen($line_dec) > $maxlinesize) {
6734					$line_dec = wordwrap($line_dec, $maxlinesize, '\n', true);
6735					$nblines += substr_count($line_dec, '\n');
6736				}
6737			}
6738		}
6739	}
6740
6741	unset($a);
6742	return $nblines;
6743}
6744
6745/**
6746 *	Return if a text is a html content
6747 *
6748 *	@param	string	$msg		Content to check
6749 *	@param	int		$option		0=Full detection, 1=Fast check
6750 *	@return	boolean				true/false
6751 *	@see	dol_concatdesc()
6752 */
6753function dol_textishtml($msg, $option = 0)
6754{
6755	if ($option == 1) {
6756		if (preg_match('/<html/i', $msg)) {
6757			return true;
6758		} elseif (preg_match('/<body/i', $msg)) {
6759			return true;
6760		} elseif (preg_match('/<\/textarea/i', $msg)) {
6761			return true;
6762		} elseif (preg_match('/<(b|em|i|u)>/i', $msg)) {
6763			return true;
6764		} elseif (preg_match('/<br/i', $msg)) {
6765			return true;
6766		}
6767		return false;
6768	} else {
6769		if (preg_match('/<html/i', $msg)) {
6770			return true;
6771		} elseif (preg_match('/<body/i', $msg)) {
6772			return true;
6773		} elseif (preg_match('/<\/textarea/i', $msg)) {
6774			return true;
6775		} elseif (preg_match('/<(b|em|i|u)>/i', $msg)) {
6776			return true;
6777		} elseif (preg_match('/<br\/>/i', $msg)) {
6778			return true;
6779		} elseif (preg_match('/<(br|div|font|li|p|span|strong|table)>/i', $msg)) {
6780			return true;
6781		} elseif (preg_match('/<(br|div|font|li|p|span|strong|table)\s+[^<>\/]*\/?>/i', $msg)) {
6782			return true;
6783		} elseif (preg_match('/<img\s+[^<>]*src[^<>]*>/i', $msg)) {
6784			return true; // must accept <img src="http://example.com/aaa.png" />
6785		} elseif (preg_match('/<a\s+[^<>]*href[^<>]*>/i', $msg)) {
6786			return true; // must accept <a href="http://example.com/aaa.png" />
6787		} elseif (preg_match('/<h[0-9]>/i', $msg)) {
6788			return true;
6789		} elseif (preg_match('/&[A-Z0-9]{1,6};/i', $msg)) {
6790			return true; // Html entities names (http://www.w3schools.com/tags/ref_entities.asp)
6791		} elseif (preg_match('/&#[0-9]{2,3};/i', $msg)) {
6792			return true; // Html entities numbers (http://www.w3schools.com/tags/ref_entities.asp)
6793		}
6794
6795		return false;
6796	}
6797}
6798
6799/**
6800 *  Concat 2 descriptions with a new line between them (second operand after first one with appropriate new line separator)
6801 *  text1 html + text2 html => text1 + '<br>' + text2
6802 *  text1 html + text2 txt  => text1 + '<br>' + dol_nl2br(text2)
6803 *  text1 txt  + text2 html => dol_nl2br(text1) + '<br>' + text2
6804 *  text1 txt  + text2 txt  => text1 + '\n' + text2
6805 *
6806 *  @param  string  $text1          Text 1
6807 *  @param  string  $text2          Text 2
6808 *  @param  bool    $forxml         true=Use <br /> instead of <br> if we have to add a br tag
6809 *  @param  bool    $invert         invert order of description lines (we often use config MAIN_CHANGE_ORDER_CONCAT_DESCRIPTION in this parameter)
6810 *  @return string                  Text 1 + new line + Text2
6811 *  @see    dol_textishtml()
6812 */
6813function dol_concatdesc($text1, $text2, $forxml = false, $invert = false)
6814{
6815	if (!empty($invert)) {
6816			$tmp = $text1;
6817			$text1 = $text2;
6818			$text2 = $tmp;
6819	}
6820
6821	$ret = '';
6822	$ret .= (!dol_textishtml($text1) && dol_textishtml($text2)) ? dol_nl2br(dol_escape_htmltag($text1, 0, 1, '', 1), 0, $forxml) : $text1;
6823	$ret .= (!empty($text1) && !empty($text2)) ? ((dol_textishtml($text1) || dol_textishtml($text2)) ? ($forxml ? "<br \>\n" : "<br>\n") : "\n") : "";
6824	$ret .= (dol_textishtml($text1) && !dol_textishtml($text2)) ? dol_nl2br(dol_escape_htmltag($text2, 0, 1, '', 1), 0, $forxml) : $text2;
6825	return $ret;
6826}
6827
6828
6829
6830/**
6831 * Return array of possible common substitutions. This includes several families like: 'system', 'mycompany', 'object', 'objectamount', 'date', 'user'
6832 *
6833 * @param	Translate	$outputlangs	Output language
6834 * @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)
6835 * @param   array       $exclude        Array of family keys we want to exclude. For example array('system', 'mycompany', 'object', 'objectamount', 'date', 'user', ...)
6836 * @param   Object      $object         Object for keys on object
6837 * @return	array						Array of substitutions
6838 * @see setSubstitFromObject()
6839 */
6840function getCommonSubstitutionArray($outputlangs, $onlykey = 0, $exclude = null, $object = null)
6841{
6842	global $db, $conf, $mysoc, $user, $extrafields;
6843
6844	$substitutionarray = array();
6845
6846	if (empty($exclude) || !in_array('user', $exclude)) {
6847		// Add SIGNATURE into substitutionarray first, so, when we will make the substitution,
6848		// this will include signature content first and then replace var found into content of signature
6849		$signature = $user->signature;
6850		$substitutionarray = array_merge($substitutionarray, array(
6851			'__USER_SIGNATURE__' => (string) (($signature && empty($conf->global->MAIN_MAIL_DO_NOT_USE_SIGN)) ? ($onlykey == 2 ? dol_trunc(dol_string_nohtmltag($signature), 30) : $signature) : '')
6852		));
6853
6854		if (is_object($user)) {
6855			$substitutionarray = array_merge($substitutionarray, array(
6856				'__USER_ID__' => (string) $user->id,
6857				'__USER_LOGIN__' => (string) $user->login,
6858				'__USER_EMAIL__' => (string) $user->email,
6859				'__USER_LASTNAME__' => (string) $user->lastname,
6860				'__USER_FIRSTNAME__' => (string) $user->firstname,
6861				'__USER_FULLNAME__' => (string) $user->getFullName($outputlangs),
6862				'__USER_SUPERVISOR_ID__' => (string) ($user->fk_user ? $user->fk_user : '0'),
6863				'__USER_REMOTE_IP__' => (string) getUserRemoteIP()
6864				));
6865		}
6866	}
6867	if ((empty($exclude) || !in_array('mycompany', $exclude)) && is_object($mysoc)) {
6868		$substitutionarray = array_merge($substitutionarray, array(
6869			'__MYCOMPANY_NAME__'    => $mysoc->name,
6870			'__MYCOMPANY_EMAIL__'   => $mysoc->email,
6871			'__MYCOMPANY_PHONE__'   => $mysoc->phone,
6872			'__MYCOMPANY_FAX__'     => $mysoc->fax,
6873			'__MYCOMPANY_PROFID1__' => $mysoc->idprof1,
6874			'__MYCOMPANY_PROFID2__' => $mysoc->idprof2,
6875			'__MYCOMPANY_PROFID3__' => $mysoc->idprof3,
6876			'__MYCOMPANY_PROFID4__' => $mysoc->idprof4,
6877			'__MYCOMPANY_PROFID5__' => $mysoc->idprof5,
6878			'__MYCOMPANY_PROFID6__' => $mysoc->idprof6,
6879			'__MYCOMPANY_CAPITAL__' => $mysoc->capital,
6880			'__MYCOMPANY_FULLADDRESS__' => (method_exists($mysoc, 'getFullAddress') ? $mysoc->getFullAddress(1, ', ') : ''),	// $mysoc may be stdClass
6881			'__MYCOMPANY_ADDRESS__' => $mysoc->address,
6882			'__MYCOMPANY_ZIP__'     => $mysoc->zip,
6883			'__MYCOMPANY_TOWN__'    => $mysoc->town,
6884			'__MYCOMPANY_COUNTRY__'    => $mysoc->country,
6885			'__MYCOMPANY_COUNTRY_ID__' => $mysoc->country_id,
6886			'__MYCOMPANY_COUNTRY_CODE__' => $mysoc->country_code,
6887			'__MYCOMPANY_CURRENCY_CODE__' => $conf->currency
6888		));
6889	}
6890
6891	if (($onlykey || is_object($object)) && (empty($exclude) || !in_array('object', $exclude))) {
6892		if ($onlykey) {
6893			$substitutionarray['__ID__'] = '__ID__';
6894			$substitutionarray['__REF__'] = '__REF__';
6895			$substitutionarray['__REF_CLIENT__'] = '__REF_CLIENT__';
6896			$substitutionarray['__REF_SUPPLIER__'] = '__REF_SUPPLIER__';
6897			$substitutionarray['__NOTE_PUBLIC__'] = '__NOTE_PUBLIC__';
6898			$substitutionarray['__NOTE_PRIVATE__'] = '__NOTE_PRIVATE__';
6899			$substitutionarray['__EXTRAFIELD_XXX__'] = '__EXTRAFIELD_XXX__';
6900
6901			if (!empty($conf->societe->enabled)) {	// Most objects are concerned
6902				$substitutionarray['__THIRDPARTY_ID__'] = '__THIRDPARTY_ID__';
6903				$substitutionarray['__THIRDPARTY_NAME__'] = '__THIRDPARTY_NAME__';
6904				$substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = '__THIRDPARTY_NAME_ALIAS__';
6905				$substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = '__THIRDPARTY_CODE_CLIENT__';
6906				$substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = '__THIRDPARTY_CODE_FOURNISSEUR__';
6907				$substitutionarray['__THIRDPARTY_EMAIL__'] = '__THIRDPARTY_EMAIL__';
6908				$substitutionarray['__THIRDPARTY_PHONE__'] = '__THIRDPARTY_PHONE__';
6909				$substitutionarray['__THIRDPARTY_FAX__'] = '__THIRDPARTY_FAX__';
6910				$substitutionarray['__THIRDPARTY_ADDRESS__'] = '__THIRDPARTY_ADDRESS__';
6911				$substitutionarray['__THIRDPARTY_ZIP__'] = '__THIRDPARTY_ZIP__';
6912				$substitutionarray['__THIRDPARTY_TOWN__'] = '__THIRDPARTY_TOWN__';
6913				$substitutionarray['__THIRDPARTY_IDPROF1__'] = '__THIRDPARTY_IDPROF1__';
6914				$substitutionarray['__THIRDPARTY_IDPROF2__'] = '__THIRDPARTY_IDPROF2__';
6915				$substitutionarray['__THIRDPARTY_IDPROF3__'] = '__THIRDPARTY_IDPROF3__';
6916				$substitutionarray['__THIRDPARTY_IDPROF4__'] = '__THIRDPARTY_IDPROF4__';
6917				$substitutionarray['__THIRDPARTY_IDPROF5__'] = '__THIRDPARTY_IDPROF5__';
6918				$substitutionarray['__THIRDPARTY_IDPROF6__'] = '__THIRDPARTY_IDPROF6__';
6919				$substitutionarray['__THIRDPARTY_TVAINTRA__'] = '__THIRDPARTY_TVAINTRA__';
6920				$substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = '__THIRDPARTY_NOTE_PUBLIC__';
6921				$substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = '__THIRDPARTY_NOTE_PRIVATE__';
6922			}
6923			if (!empty($conf->adherent->enabled) && (!is_object($object) || $object->element == 'adherent')) {
6924				$substitutionarray['__MEMBER_ID__'] = '__MEMBER_ID__';
6925				$substitutionarray['__MEMBER_CIVILITY__'] = '__MEMBER_CIVILITY__';
6926				$substitutionarray['__MEMBER_FIRSTNAME__'] = '__MEMBER_FIRSTNAME__';
6927				$substitutionarray['__MEMBER_LASTNAME__'] = '__MEMBER_LASTNAME__';
6928				$substitutionarray['__MEMBER_USER_LOGIN_INFORMATION__'] = 'Login and pass of the external user account';
6929				/*$substitutionarray['__MEMBER_NOTE_PUBLIC__'] = '__MEMBER_NOTE_PUBLIC__';
6930				$substitutionarray['__MEMBER_NOTE_PRIVATE__'] = '__MEMBER_NOTE_PRIVATE__';*/
6931			}
6932			if (!empty($conf->recruitment->enabled) && (!is_object($object) || $object->element == 'candidature')) {
6933				$substitutionarray['__CANDIDATE_FULLNAME__'] = '__CANDIDATE_FULLNAME__';
6934				$substitutionarray['__CANDIDATE_FIRSTNAME__'] = '__CANDIDATE_FIRSTNAME__';
6935				$substitutionarray['__CANDIDATE_LASTNAME__'] = '__CANDIDATE_LASTNAME__';
6936			}
6937			if (!empty($conf->projet->enabled)) {		// Most objects
6938				$substitutionarray['__PROJECT_ID__'] = '__PROJECT_ID__';
6939				$substitutionarray['__PROJECT_REF__'] = '__PROJECT_REF__';
6940				$substitutionarray['__PROJECT_NAME__'] = '__PROJECT_NAME__';
6941				/*$substitutionarray['__PROJECT_NOTE_PUBLIC__'] = '__PROJECT_NOTE_PUBLIC__';
6942				$substitutionarray['__PROJECT_NOTE_PRIVATE__'] = '__PROJECT_NOTE_PRIVATE__';*/
6943			}
6944			if (!empty($conf->contrat->enabled) && (!is_object($object) || $object->element == 'contract')) {
6945				$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = 'Highest date planned for a service start';
6946				$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = 'Highest date and hour planned for service start';
6947				$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = 'Lowest data for planned expiration of service';
6948				$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = 'Lowest date and hour for planned expiration of service';
6949			}
6950			$substitutionarray['__ONLINE_PAYMENT_URL__'] = 'UrlToPayOnlineIfApplicable';
6951			$substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = 'TextAndUrlToPayOnlineIfApplicable';
6952			$substitutionarray['__SECUREKEYPAYMENT__'] = 'Security key (if key is not unique per record)';
6953			$substitutionarray['__SECUREKEYPAYMENT_MEMBER__'] = 'Security key for payment on a member subscription (one key per member)';
6954			$substitutionarray['__SECUREKEYPAYMENT_ORDER__'] = 'Security key for payment on an order';
6955			$substitutionarray['__SECUREKEYPAYMENT_INVOICE__'] = 'Security key for payment on an invoice';
6956			$substitutionarray['__SECUREKEYPAYMENT_CONTRACTLINE__'] = 'Security key for payment on a service of a contract';
6957
6958			$substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = 'Direct download url of a proposal';
6959			$substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = 'Direct download url of an order';
6960			$substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = 'Direct download url of an invoice';
6961			$substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = 'Direct download url of a contract';
6962			$substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = 'Direct download url of a supplier proposal';
6963
6964			if (!empty($conf->expedition->enabled) && (!is_object($object) || $object->element == 'shipping')) {
6965				$substitutionarray['__SHIPPINGTRACKNUM__'] = 'Shipping tracking number';
6966				$substitutionarray['__SHIPPINGTRACKNUMURL__'] = 'Shipping tracking url';
6967			}
6968			if (!empty($conf->reception->enabled) && (!is_object($object) || $object->element == 'reception')) {
6969				$substitutionarray['__RECEPTIONTRACKNUM__'] = 'Shippin tracking number of shipment';
6970				$substitutionarray['__RECEPTIONTRACKNUMURL__'] = 'Shipping tracking url';
6971			}
6972		} else {
6973			$substitutionarray['__ID__'] = $object->id;
6974			$substitutionarray['__REF__'] = $object->ref;
6975			$substitutionarray['__REF_CLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
6976			$substitutionarray['__REF_SUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
6977			$substitutionarray['__NOTE_PUBLIC__'] = (isset($object->note_public) ? $object->note_public : null);
6978			$substitutionarray['__NOTE_PRIVATE__'] = (isset($object->note_private) ? $object->note_private : null);
6979			$substitutionarray['__DATE_DELIVERY__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, 'day', 0, $outputlangs) : '');
6980			$substitutionarray['__DATE_DELIVERY_DAY__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, "%d") : '');
6981			$substitutionarray['__DATE_DELIVERY_DAY_TEXT__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, "%A") : '');
6982			$substitutionarray['__DATE_DELIVERY_MON__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, "%m") : '');
6983			$substitutionarray['__DATE_DELIVERY_MON_TEXT__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, "%b") : '');
6984			$substitutionarray['__DATE_DELIVERY_YEAR__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, "%Y") : '');
6985			$substitutionarray['__DATE_DELIVERY_HH__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, "%H") : '');
6986			$substitutionarray['__DATE_DELIVERY_MM__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, "%M") : '');
6987			$substitutionarray['__DATE_DELIVERY_SS__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, "%S") : '');
6988
6989			// For backward compatibility
6990			$substitutionarray['__REFCLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
6991			$substitutionarray['__REFSUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
6992			$substitutionarray['__SUPPLIER_ORDER_DATE_DELIVERY__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, 'day', 0, $outputlangs) : '');
6993			$substitutionarray['__SUPPLIER_ORDER_DELAY_DELIVERY__'] = (isset($object->availability_code) ? ($outputlangs->transnoentities("AvailabilityType".$object->availability_code) != ('AvailabilityType'.$object->availability_code) ? $outputlangs->transnoentities("AvailabilityType".$object->availability_code) : $outputlangs->convToOutputCharset(isset($object->availability) ? $object->availability : '')) : '');
6994
6995			if (is_object($object) && ($object->element == 'adherent' || $object->element == 'member') && $object->id > 0) {
6996				$birthday = (empty($object->birth) ? '' : dol_print_date($object->birth, 'day'));
6997
6998				$substitutionarray['__MEMBER_ID__'] = (isset($object->id) ? $object->id : '');
6999				if (method_exists($object, 'getCivilityLabel')) {
7000					$substitutionarray['__MEMBER_CIVILITY__'] = $object->getCivilityLabel();
7001				}
7002				$substitutionarray['__MEMBER_FIRSTNAME__'] = (isset($object->firstname) ? $object->firstname : '');
7003				$substitutionarray['__MEMBER_LASTNAME__'] = (isset($object->lastname) ? $object->lastname : '');
7004				$substitutionarray['__MEMBER_USER_LOGIN_INFORMATION__'] = '';
7005				if (method_exists($object, 'getFullName')) {
7006					$substitutionarray['__MEMBER_FULLNAME__'] = $object->getFullName($outputlangs);
7007				}
7008				$substitutionarray['__MEMBER_COMPANY__'] = (isset($object->societe) ? $object->societe : '');
7009				$substitutionarray['__MEMBER_ADDRESS__'] = (isset($object->address) ? $object->address : '');
7010				$substitutionarray['__MEMBER_ZIP__'] = (isset($object->zip) ? $object->zip : '');
7011				$substitutionarray['__MEMBER_TOWN__'] = (isset($object->town) ? $object->town : '');
7012				$substitutionarray['__MEMBER_COUNTRY__'] = (isset($object->country) ? $object->country : '');
7013				$substitutionarray['__MEMBER_EMAIL__'] = (isset($object->email) ? $object->email : '');
7014				$substitutionarray['__MEMBER_BIRTH__'] = (isset($birthday) ? $birthday : '');
7015				$substitutionarray['__MEMBER_PHOTO__'] = (isset($object->photo) ? $object->photo : '');
7016				$substitutionarray['__MEMBER_LOGIN__'] = (isset($object->login) ? $object->login : '');
7017				$substitutionarray['__MEMBER_PASSWORD__'] = (isset($object->pass) ? $object->pass : '');
7018				$substitutionarray['__MEMBER_PHONE__'] = (isset($object->phone) ? $object->phone : '');
7019				$substitutionarray['__MEMBER_PHONEPRO__'] = (isset($object->phone_perso) ? $object->phone_perso : '');
7020				$substitutionarray['__MEMBER_PHONEMOBILE__'] = (isset($object->phone_mobile) ? $object->phone_mobile : '');
7021				$substitutionarray['__MEMBER_TYPE__'] = (isset($object->type) ? $object->type : '');
7022				$substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE__']       = dol_print_date($object->first_subscription_date, 'dayrfc');
7023				$substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_START__'] = dol_print_date($object->first_subscription_date_start, 'dayrfc');
7024				$substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_END__']   = dol_print_date($object->first_subscription_date_end, 'dayrfc');
7025				$substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE__']        = dol_print_date($object->last_subscription_date, 'dayrfc');
7026				$substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_START__']  = dol_print_date($object->last_subscription_date_start, 'dayrfc');
7027				$substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_END__']    = dol_print_date($object->last_subscription_date_end, 'dayrfc');
7028			}
7029
7030			if (is_object($object) && $object->element == 'societe') {
7031				$substitutionarray['__THIRDPARTY_ID__'] = (is_object($object) ? $object->id : '');
7032				$substitutionarray['__THIRDPARTY_NAME__'] = (is_object($object) ? $object->name : '');
7033				$substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = (is_object($object) ? $object->name_alias : '');
7034				$substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = (is_object($object) ? $object->code_client : '');
7035				$substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = (is_object($object) ? $object->code_fournisseur : '');
7036				$substitutionarray['__THIRDPARTY_EMAIL__'] = (is_object($object) ? $object->email : '');
7037				$substitutionarray['__THIRDPARTY_PHONE__'] = (is_object($object) ? $object->phone : '');
7038				$substitutionarray['__THIRDPARTY_FAX__'] = (is_object($object) ? $object->fax : '');
7039				$substitutionarray['__THIRDPARTY_ADDRESS__'] = (is_object($object) ? $object->address : '');
7040				$substitutionarray['__THIRDPARTY_ZIP__'] = (is_object($object) ? $object->zip : '');
7041				$substitutionarray['__THIRDPARTY_TOWN__'] = (is_object($object) ? $object->town : '');
7042				$substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = (is_object($object) ? $object->country_id : '');
7043				$substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = (is_object($object) ? $object->country_code : '');
7044				$substitutionarray['__THIRDPARTY_IDPROF1__'] = (is_object($object) ? $object->idprof1 : '');
7045				$substitutionarray['__THIRDPARTY_IDPROF2__'] = (is_object($object) ? $object->idprof2 : '');
7046				$substitutionarray['__THIRDPARTY_IDPROF3__'] = (is_object($object) ? $object->idprof3 : '');
7047				$substitutionarray['__THIRDPARTY_IDPROF4__'] = (is_object($object) ? $object->idprof4 : '');
7048				$substitutionarray['__THIRDPARTY_IDPROF5__'] = (is_object($object) ? $object->idprof5 : '');
7049				$substitutionarray['__THIRDPARTY_IDPROF6__'] = (is_object($object) ? $object->idprof6 : '');
7050				$substitutionarray['__THIRDPARTY_TVAINTRA__'] = (is_object($object) ? $object->tva_intra : '');
7051				$substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = (is_object($object) ? dol_htmlentitiesbr($object->note_public) : '');
7052				$substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = (is_object($object) ? dol_htmlentitiesbr($object->note_private) : '');
7053			} elseif (is_object($object->thirdparty)) {
7054				$substitutionarray['__THIRDPARTY_ID__'] = (is_object($object->thirdparty) ? $object->thirdparty->id : '');
7055				$substitutionarray['__THIRDPARTY_NAME__'] = (is_object($object->thirdparty) ? $object->thirdparty->name : '');
7056				$substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = (is_object($object->thirdparty) ? $object->thirdparty->name_alias : '');
7057				$substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = (is_object($object->thirdparty) ? $object->thirdparty->code_client : '');
7058				$substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = (is_object($object->thirdparty) ? $object->thirdparty->code_fournisseur : '');
7059				$substitutionarray['__THIRDPARTY_EMAIL__'] = (is_object($object->thirdparty) ? $object->thirdparty->email : '');
7060				$substitutionarray['__THIRDPARTY_PHONE__'] = (is_object($object->thirdparty) ? $object->thirdparty->phone : '');
7061				$substitutionarray['__THIRDPARTY_FAX__'] = (is_object($object->thirdparty) ? $object->thirdparty->fax : '');
7062				$substitutionarray['__THIRDPARTY_ADDRESS__'] = (is_object($object->thirdparty) ? $object->thirdparty->address : '');
7063				$substitutionarray['__THIRDPARTY_ZIP__'] = (is_object($object->thirdparty) ? $object->thirdparty->zip : '');
7064				$substitutionarray['__THIRDPARTY_TOWN__'] = (is_object($object->thirdparty) ? $object->thirdparty->town : '');
7065				$substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = (is_object($object->thirdparty) ? $object->thirdparty->country_id : '');
7066				$substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = (is_object($object->thirdparty) ? $object->thirdparty->country_code : '');
7067				$substitutionarray['__THIRDPARTY_IDPROF1__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof1 : '');
7068				$substitutionarray['__THIRDPARTY_IDPROF2__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof2 : '');
7069				$substitutionarray['__THIRDPARTY_IDPROF3__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof3 : '');
7070				$substitutionarray['__THIRDPARTY_IDPROF4__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof4 : '');
7071				$substitutionarray['__THIRDPARTY_IDPROF5__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof5 : '');
7072				$substitutionarray['__THIRDPARTY_IDPROF6__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof6 : '');
7073				$substitutionarray['__THIRDPARTY_TVAINTRA__'] = (is_object($object->thirdparty) ? $object->thirdparty->tva_intra : '');
7074				$substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = (is_object($object->thirdparty) ? dol_htmlentitiesbr($object->thirdparty->note_public) : '');
7075				$substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = (is_object($object->thirdparty) ? dol_htmlentitiesbr($object->thirdparty->note_private) : '');
7076			}
7077
7078			if (is_object($object) && $object->element == 'recruitmentcandidature') {
7079				$substitutionarray['__CANDIDATE_FULLNAME__'] = $object->getFullName($outputlangs);
7080				$substitutionarray['__CANDIDATE_FIRSTNAME__'] = $object->firstname;
7081				$substitutionarray['__CANDIDATE_LASTNAME__'] = $object->lastname;
7082			}
7083
7084			if (is_object($object->project)) {
7085				$substitutionarray['__PROJECT_ID__'] = (is_object($object->project) ? $object->project->id : '');
7086				$substitutionarray['__PROJECT_REF__'] = (is_object($object->project) ? $object->project->ref : '');
7087				$substitutionarray['__PROJECT_NAME__'] = (is_object($object->project) ? $object->project->title : '');
7088			}
7089			if (is_object($object->projet)) {	// Deprecated, for backward compatibility
7090				$substitutionarray['__PROJECT_ID__'] = (is_object($object->projet) ? $object->projet->id : '');
7091				$substitutionarray['__PROJECT_REF__'] = (is_object($object->projet) ? $object->projet->ref : '');
7092				$substitutionarray['__PROJECT_NAME__'] = (is_object($object->projet) ? $object->projet->title : '');
7093			}
7094
7095			if (is_object($object) && $object->element == 'shipping') {
7096				$substitutionarray['__SHIPPINGTRACKNUM__'] = $object->tracking_number;
7097				$substitutionarray['__SHIPPINGTRACKNUMURL__'] = $object->tracking_url;
7098			}
7099			if (is_object($object) && $object->element == 'reception') {
7100				$substitutionarray['__RECEPTIONTRACKNUM__'] = $object->tracking_number;
7101				$substitutionarray['__RECEPTIONTRACKNUMURL__'] = $object->tracking_url;
7102			}
7103
7104			if (is_object($object) && $object->element == 'contrat' && $object->id > 0 && is_array($object->lines)) {
7105				$dateplannedstart = '';
7106				$datenextexpiration = '';
7107				foreach ($object->lines as $line) {
7108					if ($line->date_ouverture_prevue > $dateplannedstart) {
7109						$dateplannedstart = $line->date_ouverture_prevue;
7110					}
7111					if ($line->statut == 4 && $line->date_fin_prevue && (!$datenextexpiration || $line->date_fin_prevue < $datenextexpiration)) {
7112						$datenextexpiration = $line->date_fin_prevue;
7113					}
7114				}
7115				$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = dol_print_date($dateplannedstart, 'dayrfc');
7116				$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = dol_print_date($dateplannedstart, 'standard');
7117				$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = dol_print_date($datenextexpiration, 'dayrfc');
7118				$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = dol_print_date($datenextexpiration, 'standard');
7119			}
7120
7121			// Create dynamic tags for __EXTRAFIELD_FIELD__
7122			if ($object->table_element && $object->id > 0) {
7123				if (!is_object($extrafields)) {
7124					$extrafields = new ExtraFields($db);
7125				}
7126				$extrafields->fetch_name_optionals_label($object->table_element, true);
7127
7128				if ($object->fetch_optionals() > 0) {
7129					if (is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label']) > 0) {
7130						foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) {
7131							$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'__'] = $object->array_options['options_'.$key];
7132							if ($extrafields->attributes[$object->table_element]['type'][$key] == 'date') {
7133								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'__'] = dol_print_date($object->array_options['options_'.$key], 'day');
7134								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_LOCALE__'] = dol_print_date($object->array_options['options_'.$key], 'day', 'tzserver', $outputlangs);
7135								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_RFC__'] = dol_print_date($object->array_options['options_'.$key], 'dayrfc');
7136							} elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'datetime') {
7137								$datetime = $object->array_options['options_'.$key];
7138								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhour') : '');
7139								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_LOCALE__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhour', 'tzserver', $outputlangs) : '');
7140								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_DAY_LOCALE__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'day', 'tzserver', $outputlangs) : '');
7141								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_RFC__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhourrfc') : '');
7142							}
7143						}
7144					}
7145				}
7146			}
7147
7148			// Complete substitution array with the url to make online payment
7149			$paymenturl = '';
7150			if (empty($substitutionarray['__REF__'])) {
7151				$paymenturl = '';
7152			} else {
7153				// Set the online payment url link into __ONLINE_PAYMENT_URL__ key
7154				require_once DOL_DOCUMENT_ROOT.'/core/lib/payments.lib.php';
7155				$outputlangs->loadLangs(array('paypal', 'other'));
7156				$typeforonlinepayment = 'free';
7157				if (is_object($object) && $object->element == 'commande') {
7158					$typeforonlinepayment = 'order';
7159				}
7160				if (is_object($object) && $object->element == 'facture') {
7161					$typeforonlinepayment = 'invoice';
7162				}
7163				if (is_object($object) && $object->element == 'member') {
7164					$typeforonlinepayment = 'member';
7165				}
7166				if (is_object($object) && $object->element == 'contrat') {
7167					$typeforonlinepayment = 'contract';
7168				}
7169				$url = getOnlinePaymentUrl(0, $typeforonlinepayment, $substitutionarray['__REF__']);
7170				$paymenturl = $url;
7171			}
7172
7173			if ($object->id > 0) {
7174				$substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = ($paymenturl ?str_replace('\n', "\n", $outputlangs->trans("PredefinedMailContentLink", $paymenturl)) : '');
7175				$substitutionarray['__ONLINE_PAYMENT_URL__'] = $paymenturl;
7176
7177				if (!empty($conf->global->PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'propal') {
7178					$substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = $object->getLastMainDocLink($object->element);
7179				} else {
7180					$substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = '';
7181				}
7182				if (!empty($conf->global->ORDER_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'commande') {
7183					$substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = $object->getLastMainDocLink($object->element);
7184				} else {
7185					$substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = '';
7186				}
7187				if (!empty($conf->global->INVOICE_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'facture') {
7188					$substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = $object->getLastMainDocLink($object->element);
7189				} else {
7190					$substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = '';
7191				}
7192				if (!empty($conf->global->CONTRACT_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'contrat') {
7193					$substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = $object->getLastMainDocLink($object->element);
7194				} else {
7195					$substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = '';
7196				}
7197				if (!empty($conf->global->SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'supplier_proposal') {
7198					$substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = $object->getLastMainDocLink($object->element);
7199				} else {
7200					$substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = '';
7201				}
7202
7203				if (is_object($object) && $object->element == 'propal') {
7204					$substitutionarray['__URL_PROPOSAL__'] = DOL_MAIN_URL_ROOT."/comm/propal/card.php?id=".$object->id;
7205				}
7206				if (is_object($object) && $object->element == 'commande') {
7207					$substitutionarray['__URL_ORDER__'] = DOL_MAIN_URL_ROOT."/commande/card.php?id=".$object->id;
7208				}
7209				if (is_object($object) && $object->element == 'facture') {
7210					$substitutionarray['__URL_INVOICE__'] = DOL_MAIN_URL_ROOT."/compta/facture/card.php?id=".$object->id;
7211				}
7212				if (is_object($object) && $object->element == 'contrat') {
7213					$substitutionarray['__URL_CONTRACT__'] = DOL_MAIN_URL_ROOT."/contrat/card.php?id=".$object->id;
7214				}
7215				if (is_object($object) && $object->element == 'supplier_proposal') {
7216					$substitutionarray['__URL_SUPPLIER_PROPOSAL__'] = DOL_MAIN_URL_ROOT."/supplier_proposal/card.php?id=".$object->id;
7217				}
7218			}
7219
7220			if (is_object($object) && $object->element == 'action') {
7221				$substitutionarray['__EVENT_LABEL__'] = $object->label;
7222				$substitutionarray['__EVENT_DATE__'] = dol_print_date($object->datep, '%A %d %b %Y');
7223				$substitutionarray['__EVENT_TIME__'] = dol_print_date($object->datep, '%H:%M:%S');
7224			}
7225		}
7226	}
7227	if (empty($exclude) || !in_array('objectamount', $exclude)) {
7228		include_once DOL_DOCUMENT_ROOT.'/core/lib/functionsnumtoword.lib.php';
7229
7230		$substitutionarray['__DATE_YMD__']        = is_object($object) ? (isset($object->date) ? dol_print_date($object->date, 'day', 0, $outputlangs) : null) : '';
7231		$substitutionarray['__DATE_DUE_YMD__']    = is_object($object) ? (isset($object->date_lim_reglement) ? dol_print_date($object->date_lim_reglement, 'day', 0, $outputlangs) : null) : '';
7232
7233		$substitutionarray['__AMOUNT__']          = is_object($object) ? $object->total_ttc : '';
7234		$substitutionarray['__AMOUNT_TEXT__']     = is_object($object) ? dol_convertToWord($object->total_ttc, $outputlangs, '', true) : '';
7235		$substitutionarray['__AMOUNT_TEXTCURRENCY__'] = is_object($object) ? dol_convertToWord($object->total_ttc, $outputlangs, $conf->currency, true) : '';
7236		$substitutionarray['__AMOUNT_EXCL_TAX__'] = is_object($object) ? $object->total_ht : '';
7237		$substitutionarray['__AMOUNT_VAT__']      = is_object($object) ? (isset($object->total_vat) ? $object->total_vat : $object->total_tva) : '';
7238		$substitutionarray['__AMOUNT_VAT_TEXT__']      = is_object($object) ? (isset($object->total_vat) ? dol_convertToWord($object->total_vat, $outputlangs, '', true) : dol_convertToWord($object->total_tva, $outputlangs, '', true)) : '';
7239		$substitutionarray['__AMOUNT_VAT_TEXTCURRENCY__']      = is_object($object) ? (isset($object->total_vat) ? dol_convertToWord($object->total_vat, $outputlangs, $conf->currency, true) : dol_convertToWord($object->total_tva, $outputlangs, $conf->currency, true)) : '';
7240		if ($onlykey != 2 || $mysoc->useLocalTax(1)) {
7241			$substitutionarray['__AMOUNT_TAX2__']     = is_object($object) ? $object->total_localtax1 : '';
7242		}
7243		if ($onlykey != 2 || $mysoc->useLocalTax(2)) {
7244			$substitutionarray['__AMOUNT_TAX3__']     = is_object($object) ? $object->total_localtax2 : '';
7245		}
7246
7247		$substitutionarray['__AMOUNT_FORMATED__']          = is_object($object) ? ($object->total_ttc ? price($object->total_ttc, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
7248		$substitutionarray['__AMOUNT_EXCL_TAX_FORMATED__'] = is_object($object) ? ($object->total_ht ? price($object->total_ht, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
7249		$substitutionarray['__AMOUNT_VAT_FORMATED__']      = is_object($object) ? (isset($object->total_vat) ? price($object->total_vat, 0, $outputlangs, 0, -1, -1, $conf->currency) : ($object->total_tva ? price($object->total_tva, 0, $outputlangs, 0, -1, -1, $conf->currency) : null)) : '';
7250		if ($onlykey != 2 || $mysoc->useLocalTax(1)) {
7251			$substitutionarray['__AMOUNT_TAX2_FORMATED__']     = is_object($object) ? ($object->total_localtax1 ? price($object->total_localtax1, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
7252		}
7253		if ($onlykey != 2 || $mysoc->useLocalTax(2)) {
7254			$substitutionarray['__AMOUNT_TAX3_FORMATED__']     = is_object($object) ? ($object->total_localtax2 ? price($object->total_localtax2, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
7255		}
7256
7257		$substitutionarray['__AMOUNT_MULTICURRENCY__']          = (is_object($object) && isset($object->multicurrency_total_ttc)) ? $object->multicurrency_total_ttc : '';
7258		$substitutionarray['__AMOUNT_MULTICURRENCY_TEXT__']     = (is_object($object) && isset($object->multicurrency_total_ttc)) ? dol_convertToWord($object->multicurrency_total_ttc, $outputlangs, '', true) : '';
7259		$substitutionarray['__AMOUNT_MULTICURRENCY_TEXTCURRENCY__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? dol_convertToWord($object->multicurrency_total_ttc, $outputlangs, $object->multicurrency_code, true) : '';
7260		// TODO Add other keys for foreign multicurrency
7261
7262		// For backward compatibility
7263		if ($onlykey != 2) {
7264			$substitutionarray['__TOTAL_TTC__']    = is_object($object) ? $object->total_ttc : '';
7265			$substitutionarray['__TOTAL_HT__']     = is_object($object) ? $object->total_ht : '';
7266			$substitutionarray['__TOTAL_VAT__']    = is_object($object) ? (isset($object->total_vat) ? $object->total_vat : $object->total_tva) : '';
7267		}
7268	}
7269
7270	//var_dump($substitutionarray['__AMOUNT_FORMATED__']);
7271	if (empty($exclude) || !in_array('date', $exclude)) {
7272		include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
7273
7274		$tmp = dol_getdate(dol_now(), true);
7275		$tmp2 = dol_get_prev_day($tmp['mday'], $tmp['mon'], $tmp['year']);
7276		$tmp3 = dol_get_prev_month($tmp['mon'], $tmp['year']);
7277		$tmp4 = dol_get_next_day($tmp['mday'], $tmp['mon'], $tmp['year']);
7278		$tmp5 = dol_get_next_month($tmp['mon'], $tmp['year']);
7279
7280		$daytext = $outputlangs->trans('Day'.$tmp['wday']);
7281
7282		$substitutionarray = array_merge($substitutionarray, array(
7283			'__DAY__' => (string) $tmp['mday'],
7284			'__DAY_TEXT__' => $daytext, // Monday
7285			'__DAY_TEXT_SHORT__' => dol_trunc($daytext, 3, 'right', 'UTF-8', 1), // Mon
7286			'__DAY_TEXT_MIN__' => dol_trunc($daytext, 1, 'right', 'UTF-8', 1), // M
7287			'__MONTH__' => (string) $tmp['mon'],
7288			'__MONTH_TEXT__' => $outputlangs->trans('Month'.sprintf("%02d", $tmp['mon'])),
7289			'__MONTH_TEXT_SHORT__' => $outputlangs->trans('MonthShort'.sprintf("%02d", $tmp['mon'])),
7290			'__MONTH_TEXT_MIN__' => $outputlangs->trans('MonthVeryShort'.sprintf("%02d", $tmp['mon'])),
7291			'__YEAR__' => (string) $tmp['year'],
7292			'__PREVIOUS_DAY__' => (string) $tmp2['day'],
7293			'__PREVIOUS_MONTH__' => (string) $tmp3['month'],
7294			'__PREVIOUS_YEAR__' => (string) ($tmp['year'] - 1),
7295			'__NEXT_DAY__' => (string) $tmp4['day'],
7296			'__NEXT_MONTH__' => (string) $tmp5['month'],
7297			'__NEXT_YEAR__' => (string) ($tmp['year'] + 1),
7298		));
7299	}
7300
7301	if (!empty($conf->multicompany->enabled)) {
7302		$substitutionarray = array_merge($substitutionarray, array('__ENTITY_ID__' => $conf->entity));
7303	}
7304	if (empty($exclude) || !in_array('system', $exclude)) {
7305		$substitutionarray['__DOL_MAIN_URL_ROOT__'] = DOL_MAIN_URL_ROOT;
7306		$substitutionarray['__(AnyTranslationKey)__'] = $outputlangs->trans('TranslationOfKey');
7307		$substitutionarray['__(AnyTranslationKey|langfile)__'] = $outputlangs->trans('TranslationOfKey').' (load also language file before)';
7308		$substitutionarray['__[AnyConstantKey]__'] = $outputlangs->trans('ValueOfConstantKey');
7309	}
7310
7311	return $substitutionarray;
7312}
7313
7314/**
7315 *  Make substitution into a text string, replacing keys with vals from $substitutionarray (oldval=>newval),
7316 *  and texts like __(TranslationKey|langfile)__ and __[ConstantKey]__ are also replaced.
7317 *  Example of usage:
7318 *  $substitutionarray = getCommonSubstitutionArray($langs, 0, null, $thirdparty);
7319 *  complete_substitutions_array($substitutionarray, $langs, $thirdparty);
7320 *  $mesg = make_substitutions($mesg, $substitutionarray, $langs);
7321 *
7322 *  @param	string		$text	      					Source string in which we must do substitution
7323 *  @param  array		$substitutionarray				Array with key->val to substitute. Example: array('__MYKEY__' => 'MyVal', ...)
7324 *  @param	Translate	$outputlangs					Output language
7325 *  @param	int			$converttextinhtmlifnecessary	0=Convert only value into HTML if text is already in HTML
7326 *  													1=Will also convert initial $text into HTML if we try to insert one value that is HTML
7327 * 	@return string  		    						Output string after substitutions
7328 *  @see	complete_substitutions_array(), getCommonSubstitutionArray()
7329 */
7330function make_substitutions($text, $substitutionarray, $outputlangs = null, $converttextinhtmlifnecessary = 0)
7331{
7332	global $conf, $langs;
7333
7334	if (!is_array($substitutionarray)) {
7335		return 'ErrorBadParameterSubstitutionArrayWhenCalling_make_substitutions';
7336	}
7337
7338	if (empty($outputlangs)) {
7339		$outputlangs = $langs;
7340	}
7341
7342	// Is initial text HTML or simple text ?
7343	$msgishtml = 0;
7344	if (dol_textishtml($text, 1)) {
7345		$msgishtml = 1;
7346	}
7347
7348	// Make substitution for language keys: __(AnyTranslationKey)__ or __(AnyTranslationKey|langfile)__
7349	if (is_object($outputlangs)) {
7350		$reg = array();
7351		while (preg_match('/__\(([^\)]+)\)__/', $text, $reg)) {
7352			// If key is __(TranslationKey|langfile)__, then force load of langfile.lang
7353			$tmp = explode('|', $reg[1]);
7354			if (!empty($tmp[1])) {
7355				$outputlangs->load($tmp[1]);
7356			}
7357
7358			$value = $outputlangs->transnoentitiesnoconv($reg[1]);
7359
7360			if (empty($converttextinhtmlifnecessary)) {
7361				// convert $newval into HTML is necessary
7362				$text = preg_replace('/__\('.preg_quote($reg[1], '/').'\)__/', $msgishtml ? dol_htmlentitiesbr($value) : $value, $text);
7363			} else {
7364				if (! $msgishtml) {
7365					$valueishtml = dol_textishtml($value, 1);
7366
7367					if ($valueishtml) {
7368						$text = dol_htmlentitiesbr($text);
7369						$msgishtml = 1;
7370					}
7371				} else {
7372					$value = dol_nl2br("$value");
7373				}
7374
7375				$text = preg_replace('/__\('.preg_quote($reg[1], '/').'\)__/', $value, $text);
7376			}
7377		}
7378	}
7379
7380	// Make substitution for constant keys.
7381	// Must be after the substitution of translation, so if the text of translation contains a string __[xxx]__, it is also converted.
7382	$reg = array();
7383	while (preg_match('/__\[([^\]]+)\]__/', $text, $reg)) {
7384		$keyfound = $reg[1];
7385		if (isASecretKey($keyfound)) {
7386			$value = '*****forbidden*****';
7387		} else {
7388			$value = empty($conf->global->$keyfound) ? '' : $conf->global->$keyfound;
7389		}
7390
7391		if (empty($converttextinhtmlifnecessary)) {
7392			// convert $newval into HTML is necessary
7393			$text = preg_replace('/__\['.preg_quote($keyfound, '/').'\]__/', $msgishtml ? dol_htmlentitiesbr($value) : $value, $text);
7394		} else {
7395			if (! $msgishtml) {
7396				$valueishtml = dol_textishtml($value, 1);
7397
7398				if ($valueishtml) {
7399					$text = dol_htmlentitiesbr($text);
7400					$msgishtml = 1;
7401				}
7402			} else {
7403				$value = dol_nl2br("$value");
7404			}
7405
7406			$text = preg_replace('/__\['.preg_quote($keyfound, '/').'\]__/', $value, $text);
7407		}
7408	}
7409
7410	// Make substitition for array $substitutionarray
7411	foreach ($substitutionarray as $key => $value) {
7412		if (!isset($value)) {
7413			continue; // If value is null, it same than not having substitution key at all into array, we do not replace.
7414		}
7415
7416		if ($key == '__USER_SIGNATURE__' && (!empty($conf->global->MAIN_MAIL_DO_NOT_USE_SIGN))) {
7417			$value = ''; // Protection
7418		}
7419
7420		if (empty($converttextinhtmlifnecessary)) {
7421			$text = str_replace("$key", "$value", $text); // We must keep the " to work when value is 123.5 for example
7422		} else {
7423			if (! $msgishtml) {
7424				$valueishtml = dol_textishtml($value, 1);
7425
7426				if ($valueishtml) {
7427					$text = dol_htmlentitiesbr($text);
7428					$msgishtml = 1;
7429				}
7430			} else {
7431				$value = dol_nl2br("$value");
7432			}
7433			$text = str_replace("$key", "$value", $text); // We must keep the " to work when value is 123.5 for example
7434		}
7435	}
7436
7437	return $text;
7438}
7439
7440/**
7441 *  Complete the $substitutionarray with more entries coming from external module that had set the "substitutions=1" into module_part array.
7442 *  In this case, method completesubstitutionarray provided by module is called.
7443 *
7444 *  @param  array		$substitutionarray		Array substitution old value => new value value
7445 *  @param  Translate	$outputlangs            Output language
7446 *  @param  Object		$object                 Source object
7447 *  @param  mixed		$parameters       		Add more parameters (useful to pass product lines)
7448 *  @param  string      $callfunc               What is the name of the custom function that will be called? (default: completesubstitutionarray)
7449 *  @return	void
7450 *  @see 	make_substitutions()
7451 */
7452function complete_substitutions_array(&$substitutionarray, $outputlangs, $object = null, $parameters = null, $callfunc = "completesubstitutionarray")
7453{
7454	global $conf, $user;
7455
7456	require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
7457
7458	// Note: substitution key for each extrafields, using key __EXTRA_XXX__ is already available into the getCommonSubstitutionArray used to build the substitution array.
7459
7460	// Check if there is external substitution to do, requested by plugins
7461	$dirsubstitutions = array_merge(array(), (array) $conf->modules_parts['substitutions']);
7462
7463	foreach ($dirsubstitutions as $reldir) {
7464		$dir = dol_buildpath($reldir, 0);
7465
7466		// Check if directory exists
7467		if (!dol_is_dir($dir)) {
7468			continue;
7469		}
7470
7471		$substitfiles = dol_dir_list($dir, 'files', 0, 'functions_');
7472		foreach ($substitfiles as $substitfile) {
7473			$reg = array();
7474			if (preg_match('/functions_(.*)\.lib\.php/i', $substitfile['name'], $reg)) {
7475				$module = $reg[1];
7476
7477				dol_syslog("Library ".$substitfile['name']." found into ".$dir);
7478				// Include the user's functions file
7479				require_once $dir.$substitfile['name'];
7480				// Call the user's function, and only if it is defined
7481				$function_name = $module."_".$callfunc;
7482				if (function_exists($function_name)) {
7483					$function_name($substitutionarray, $outputlangs, $object, $parameters);
7484				}
7485			}
7486		}
7487	}
7488	if (!empty($conf->global->ODT_ENABLE_ALL_TAGS_IN_SUBSTITUTIONS)) {
7489		// to list all tags in odt template
7490		$tags = '';
7491		foreach ($substitutionarray as $key => $value) {
7492			$tags .= '{'.$key.'} => '.$value."\n";
7493		}
7494		$substitutionarray = array_merge($substitutionarray, array('__ALL_TAGS__' => $tags));
7495	}
7496}
7497
7498/**
7499 *    Format output for start and end date
7500 *
7501 *    @param	int	$date_start    Start date
7502 *    @param    int	$date_end      End date
7503 *    @param    string		$format        Output format
7504 *    @param	Translate	$outputlangs   Output language
7505 *    @return	void
7506 */
7507function print_date_range($date_start, $date_end, $format = '', $outputlangs = '')
7508{
7509	print get_date_range($date_start, $date_end, $format, $outputlangs);
7510}
7511
7512/**
7513 *    Format output for start and end date
7514 *
7515 *    @param	int			$date_start    		Start date
7516 *    @param    int			$date_end      		End date
7517 *    @param    string		$format        		Output format
7518 *    @param	Translate	$outputlangs   		Output language
7519 *    @param	integer		$withparenthesis	1=Add parenthesis, 0=no parenthesis
7520 *    @return	string							String
7521 */
7522function get_date_range($date_start, $date_end, $format = '', $outputlangs = '', $withparenthesis = 1)
7523{
7524	global $langs;
7525
7526	$out = '';
7527
7528	if (!is_object($outputlangs)) {
7529		$outputlangs = $langs;
7530	}
7531
7532	if ($date_start && $date_end) {
7533		$out .= ($withparenthesis ? ' (' : '').$outputlangs->transnoentitiesnoconv('DateFromTo', dol_print_date($date_start, $format, false, $outputlangs), dol_print_date($date_end, $format, false, $outputlangs)).($withparenthesis ? ')' : '');
7534	}
7535	if ($date_start && !$date_end) {
7536		$out .= ($withparenthesis ? ' (' : '').$outputlangs->transnoentitiesnoconv('DateFrom', dol_print_date($date_start, $format, false, $outputlangs)).($withparenthesis ? ')' : '');
7537	}
7538	if (!$date_start && $date_end) {
7539		$out .= ($withparenthesis ? ' (' : '').$outputlangs->transnoentitiesnoconv('DateUntil', dol_print_date($date_end, $format, false, $outputlangs)).($withparenthesis ? ')' : '');
7540	}
7541
7542	return $out;
7543}
7544
7545/**
7546 * Return firstname and lastname in correct order
7547 *
7548 * @param	string	$firstname		Firstname
7549 * @param	string	$lastname		Lastname
7550 * @param	int		$nameorder		-1=Auto, 0=Lastname+Firstname, 1=Firstname+Lastname, 2=Firstname, 3=Firstname if defined else lastname, 4=Lastname, 5=Lastname if defined else firstname
7551 * @return	string					Firstname + lastname or Lastname + firstname
7552 */
7553function dolGetFirstLastname($firstname, $lastname, $nameorder = -1)
7554{
7555	global $conf;
7556
7557	$ret = '';
7558	// If order not defined, we use the setup
7559	if ($nameorder < 0) {
7560		$nameorder = (empty($conf->global->MAIN_FIRSTNAME_NAME_POSITION) ? 1 : 0);
7561	}
7562	if ($nameorder == 1) {
7563		$ret .= $firstname;
7564		if ($firstname && $lastname) {
7565			$ret .= ' ';
7566		}
7567		$ret .= $lastname;
7568	} elseif ($nameorder == 2 || $nameorder == 3) {
7569		$ret .= $firstname;
7570		if (empty($ret) && $nameorder == 3) {
7571			$ret .= $lastname;
7572		}
7573	} else {	// 0, 4 or 5
7574		$ret .= $lastname;
7575		if (empty($ret) && $nameorder == 5) {
7576			$ret .= $firstname;
7577		}
7578		if ($nameorder == 0) {
7579			if ($firstname && $lastname) {
7580				$ret .= ' ';
7581			}
7582			$ret .= $firstname;
7583		}
7584	}
7585	return $ret;
7586}
7587
7588
7589/**
7590 *	Set event message in dol_events session object. Will be output by calling dol_htmloutput_events.
7591 *  Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function.
7592 *  Note: Prefer to use setEventMessages instead.
7593 *
7594 *	@param	string|string[] $mesgs			Message string or array
7595 *  @param  string          $style      	Which style to use ('mesgs' by default, 'warnings', 'errors')
7596 *  @return	void
7597 *  @see	dol_htmloutput_events()
7598 */
7599function setEventMessage($mesgs, $style = 'mesgs')
7600{
7601	//dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);		This is not deprecated, it is used by setEventMessages function
7602	if (!is_array($mesgs)) {
7603		// If mesgs is a string
7604		if ($mesgs) {
7605			$_SESSION['dol_events'][$style][] = $mesgs;
7606		}
7607	} else {
7608		// If mesgs is an array
7609		foreach ($mesgs as $mesg) {
7610			if ($mesg) {
7611				$_SESSION['dol_events'][$style][] = $mesg;
7612			}
7613		}
7614	}
7615}
7616
7617/**
7618 *	Set event messages in dol_events session object. Will be output by calling dol_htmloutput_events.
7619 *  Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function.
7620 *
7621 *	@param	string	$mesg			Message string
7622 *	@param	array	$mesgs			Message array
7623 *  @param  string	$style      	Which style to use ('mesgs' by default, 'warnings', 'errors')
7624 *  @param	string	$messagekey		A key to be used to allow the feature "Never show this message again"
7625 *  @return	void
7626 *  @see	dol_htmloutput_events()
7627 */
7628function setEventMessages($mesg, $mesgs, $style = 'mesgs', $messagekey = '')
7629{
7630	if (empty($mesg) && empty($mesgs)) {
7631		dol_syslog("Try to add a message in stack with empty message", LOG_WARNING);
7632	} else {
7633		if ($messagekey) {
7634			// Complete message with a js link to set a cookie "DOLHIDEMESSAGE".$messagekey;
7635			// TODO
7636			$mesg .= '';
7637		}
7638		if (empty($messagekey) || empty($_COOKIE["DOLHIDEMESSAGE".$messagekey])) {
7639			if (!in_array((string) $style, array('mesgs', 'warnings', 'errors'))) {
7640				dol_print_error('', 'Bad parameter style='.$style.' for setEventMessages');
7641			}
7642			if (empty($mesgs)) {
7643				setEventMessage($mesg, $style);
7644			} else {
7645				if (!empty($mesg) && !in_array($mesg, $mesgs)) {
7646					setEventMessage($mesg, $style); // Add message string if not already into array
7647				}
7648				setEventMessage($mesgs, $style);
7649			}
7650		}
7651	}
7652}
7653
7654/**
7655 *	Print formated messages to output (Used to show messages on html output).
7656 *  Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function, so there is
7657 *  no need to call it explicitely.
7658 *
7659 *  @param	int		$disabledoutputofmessages	Clear all messages stored into session without diplaying them
7660 *  @return	void
7661 *  @see    									dol_htmloutput_mesg()
7662 */
7663function dol_htmloutput_events($disabledoutputofmessages = 0)
7664{
7665	// Show mesgs
7666	if (isset($_SESSION['dol_events']['mesgs'])) {
7667		if (empty($disabledoutputofmessages)) {
7668			dol_htmloutput_mesg('', $_SESSION['dol_events']['mesgs']);
7669		}
7670		unset($_SESSION['dol_events']['mesgs']);
7671	}
7672
7673	// Show errors
7674	if (isset($_SESSION['dol_events']['errors'])) {
7675		if (empty($disabledoutputofmessages)) {
7676			dol_htmloutput_mesg('', $_SESSION['dol_events']['errors'], 'error');
7677		}
7678		unset($_SESSION['dol_events']['errors']);
7679	}
7680
7681	// Show warnings
7682	if (isset($_SESSION['dol_events']['warnings'])) {
7683		if (empty($disabledoutputofmessages)) {
7684			dol_htmloutput_mesg('', $_SESSION['dol_events']['warnings'], 'warning');
7685		}
7686		unset($_SESSION['dol_events']['warnings']);
7687	}
7688}
7689
7690/**
7691 *	Get formated messages to output (Used to show messages on html output).
7692 *  This include also the translation of the message key.
7693 *
7694 *	@param	string		$mesgstring		Message string or message key
7695 *	@param	string[]	$mesgarray      Array of message strings or message keys
7696 *  @param  string		$style          Style of message output ('ok' or 'error')
7697 *  @param  int			$keepembedded   Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
7698 *	@return	string						Return html output
7699 *
7700 *  @see    dol_print_error()
7701 *  @see    dol_htmloutput_errors()
7702 *  @see    setEventMessages()
7703 */
7704function get_htmloutput_mesg($mesgstring = '', $mesgarray = '', $style = 'ok', $keepembedded = 0)
7705{
7706	global $conf, $langs;
7707
7708	$ret = 0;
7709	$return = '';
7710	$out = '';
7711	$divstart = $divend = '';
7712
7713	// If inline message with no format, we add it.
7714	if ((empty($conf->use_javascript_ajax) || !empty($conf->global->MAIN_DISABLE_JQUERY_JNOTIFY) || $keepembedded) && !preg_match('/<div class=".*">/i', $out)) {
7715		$divstart = '<div class="'.$style.' clearboth">';
7716		$divend = '</div>';
7717	}
7718
7719	if ((is_array($mesgarray) && count($mesgarray)) || $mesgstring) {
7720		$langs->load("errors");
7721		$out .= $divstart;
7722		if (is_array($mesgarray) && count($mesgarray)) {
7723			foreach ($mesgarray as $message) {
7724				$ret++;
7725				$out .= $langs->trans($message);
7726				if ($ret < count($mesgarray)) {
7727					$out .= "<br>\n";
7728				}
7729			}
7730		}
7731		if ($mesgstring) {
7732			$langs->load("errors");
7733			$ret++;
7734			$out .= $langs->trans($mesgstring);
7735		}
7736		$out .= $divend;
7737	}
7738
7739	if ($out) {
7740		if (!empty($conf->use_javascript_ajax) && empty($conf->global->MAIN_DISABLE_JQUERY_JNOTIFY) && empty($keepembedded)) {
7741			$return = '<script>
7742					$(document).ready(function() {
7743						var block = '.(!empty($conf->global->MAIN_USE_JQUERY_BLOCKUI) ? "true" : "false").'
7744						if (block) {
7745							$.dolEventValid("","'.dol_escape_js($out).'");
7746						} else {
7747							/* jnotify(message, preset of message type, keepmessage) */
7748							$.jnotify("'.dol_escape_js($out).'",
7749							"'.($style == "ok" ? 3000 : $style).'",
7750							'.($style == "ok" ? "false" : "true").',
7751							{ remove: function (){} } );
7752						}
7753					});
7754				</script>';
7755		} else {
7756			$return = $out;
7757		}
7758	}
7759
7760	return $return;
7761}
7762
7763/**
7764 *  Get formated error messages to output (Used to show messages on html output).
7765 *
7766 *  @param  string	$mesgstring         Error message
7767 *  @param  array	$mesgarray          Error messages array
7768 *  @param  int		$keepembedded       Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
7769 *  @return string                		Return html output
7770 *
7771 *  @see    dol_print_error()
7772 *  @see    dol_htmloutput_mesg()
7773 */
7774function get_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0)
7775{
7776	return get_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
7777}
7778
7779/**
7780 *	Print formated messages to output (Used to show messages on html output).
7781 *
7782 *	@param	string		$mesgstring		Message string or message key
7783 *	@param	string[]	$mesgarray      Array of message strings or message keys
7784 *	@param  string      $style          Which style to use ('ok', 'warning', 'error')
7785 *	@param  int         $keepembedded   Set to 1 if message must be kept embedded into its html place (this disable jnotify)
7786 *	@return	void
7787 *
7788 *	@see    dol_print_error()
7789 *	@see    dol_htmloutput_errors()
7790 *	@see    setEventMessages()
7791 */
7792function dol_htmloutput_mesg($mesgstring = '', $mesgarray = array(), $style = 'ok', $keepembedded = 0)
7793{
7794	if (empty($mesgstring) && (!is_array($mesgarray) || count($mesgarray) == 0)) {
7795		return;
7796	}
7797
7798	$iserror = 0;
7799	$iswarning = 0;
7800	if (is_array($mesgarray)) {
7801		foreach ($mesgarray as $val) {
7802			if ($val && preg_match('/class="error"/i', $val)) {
7803				$iserror++;
7804				break;
7805			}
7806			if ($val && preg_match('/class="warning"/i', $val)) {
7807				$iswarning++;
7808				break;
7809			}
7810		}
7811	} elseif ($mesgstring && preg_match('/class="error"/i', $mesgstring)) {
7812		$iserror++;
7813	} elseif ($mesgstring && preg_match('/class="warning"/i', $mesgstring)) {
7814		$iswarning++;
7815	}
7816	if ($style == 'error') {
7817		$iserror++;
7818	}
7819	if ($style == 'warning') {
7820		$iswarning++;
7821	}
7822
7823	if ($iserror || $iswarning) {
7824		// Remove div from texts
7825		$mesgstring = preg_replace('/<\/div><div class="(error|warning)">/', '<br>', $mesgstring);
7826		$mesgstring = preg_replace('/<div class="(error|warning)">/', '', $mesgstring);
7827		$mesgstring = preg_replace('/<\/div>/', '', $mesgstring);
7828		// Remove div from texts array
7829		if (is_array($mesgarray)) {
7830			$newmesgarray = array();
7831			foreach ($mesgarray as $val) {
7832				if (is_string($val)) {
7833					$tmpmesgstring = preg_replace('/<\/div><div class="(error|warning)">/', '<br>', $val);
7834					$tmpmesgstring = preg_replace('/<div class="(error|warning)">/', '', $tmpmesgstring);
7835					$tmpmesgstring = preg_replace('/<\/div>/', '', $tmpmesgstring);
7836					$newmesgarray[] = $tmpmesgstring;
7837				} else {
7838					dol_syslog("Error call of dol_htmloutput_mesg with an array with a value that is not a string", LOG_WARNING);
7839				}
7840			}
7841			$mesgarray = $newmesgarray;
7842		}
7843		print get_htmloutput_mesg($mesgstring, $mesgarray, ($iserror ? 'error' : 'warning'), $keepembedded);
7844	} else {
7845		print get_htmloutput_mesg($mesgstring, $mesgarray, 'ok', $keepembedded);
7846	}
7847}
7848
7849/**
7850 *  Print formated error messages to output (Used to show messages on html output).
7851 *
7852 *  @param	string	$mesgstring          Error message
7853 *  @param  array	$mesgarray           Error messages array
7854 *  @param  int		$keepembedded        Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
7855 *  @return	void
7856 *
7857 *  @see    dol_print_error()
7858 *  @see    dol_htmloutput_mesg()
7859 */
7860function dol_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0)
7861{
7862	dol_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
7863}
7864
7865/**
7866 * 	Advanced sort array by second index function, which produces ascending (default)
7867 *  or descending output and uses optionally natural case insensitive sorting (which
7868 *  can be optionally case sensitive as well).
7869 *
7870 *  @param      array		$array      		Array to sort (array of array('key1'=>val1,'key2'=>val2,'key3'...) or array of objects)
7871 *  @param      string		$index				Key in array to use for sorting criteria
7872 *  @param      int			$order				Sort order ('asc' or 'desc')
7873 *  @param      int			$natsort			1=use "natural" sort (natsort) for a search criteria thats is strings or unknown, 0=use "standard" sort (asort) for numbers
7874 *  @param      int			$case_sensitive		1=sort is case sensitive, 0=not case sensitive
7875 *  @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.
7876 *  @return     array							Sorted array
7877 */
7878function dol_sort_array(&$array, $index, $order = 'asc', $natsort = 0, $case_sensitive = 0, $keepindex = 0)
7879{
7880	// Clean parameters
7881	$order = strtolower($order);
7882
7883	if (is_array($array)) {
7884		$sizearray = count($array);
7885		if ($sizearray > 0) {
7886			$temp = array();
7887			foreach (array_keys($array) as $key) {
7888				if (is_object($array[$key])) {
7889					$temp[$key] = empty($array[$key]->$index) ? 0 : $array[$key]->$index;
7890				} else {
7891					$temp[$key] = empty($array[$key][$index]) ? 0 : $array[$key][$index];
7892				}
7893			}
7894
7895			if (!$natsort) {
7896				if ($order == 'asc') {
7897					asort($temp);
7898				} else {
7899					arsort($temp);
7900				}
7901			} else {
7902				if ($case_sensitive) {
7903					natsort($temp);
7904				} else {
7905					natcasesort($temp);	// natecasesort is not sensible to case
7906				}
7907				if ($order != 'asc') {
7908					$temp = array_reverse($temp, true);
7909				}
7910			}
7911
7912			$sorted = array();
7913
7914			foreach (array_keys($temp) as $key) {
7915				(is_numeric($key) && empty($keepindex)) ? $sorted[] = $array[$key] : $sorted[$key] = $array[$key];
7916			}
7917
7918			return $sorted;
7919		}
7920	}
7921	return $array;
7922}
7923
7924
7925/**
7926 *      Check if a string is in UTF8
7927 *
7928 *      @param	string	$str        String to check
7929 * 		@return	boolean				True if string is UTF8 or ISO compatible with UTF8, False if not (ISO with special char or Binary)
7930 */
7931function utf8_check($str)
7932{
7933	$str = (string) $str;	// Sometimes string is an int.
7934
7935	// We must use here a binary strlen function (so not dol_strlen)
7936	$strLength = dol_strlen($str);
7937	for ($i = 0; $i < $strLength; $i++) {
7938		if (ord($str[$i]) < 0x80) {
7939			continue; // 0bbbbbbb
7940		} elseif ((ord($str[$i]) & 0xE0) == 0xC0) {
7941			$n = 1; // 110bbbbb
7942		} elseif ((ord($str[$i]) & 0xF0) == 0xE0) {
7943			$n = 2; // 1110bbbb
7944		} elseif ((ord($str[$i]) & 0xF8) == 0xF0) {
7945			$n = 3; // 11110bbb
7946		} elseif ((ord($str[$i]) & 0xFC) == 0xF8) {
7947			$n = 4; // 111110bb
7948		} elseif ((ord($str[$i]) & 0xFE) == 0xFC) {
7949			$n = 5; // 1111110b
7950		} else {
7951			return false; // Does not match any model
7952		}
7953		for ($j = 0; $j < $n; $j++) { // n bytes matching 10bbbbbb follow ?
7954			if ((++$i == strlen($str)) || ((ord($str[$i]) & 0xC0) != 0x80)) {
7955				return false;
7956			}
7957		}
7958	}
7959	return true;
7960}
7961
7962/**
7963 *      Check if a string is in ASCII
7964 *
7965 *      @param	string	$str        String to check
7966 * 		@return	boolean				True if string is ASCII, False if not (byte value > 0x7F)
7967 */
7968function ascii_check($str)
7969{
7970	if (function_exists('mb_check_encoding')) {
7971		//if (mb_detect_encoding($str, 'ASCII', true) return false;
7972		if (!mb_check_encoding($str, 'ASCII')) {
7973			return false;
7974		}
7975	} else {
7976		if (preg_match('/[^\x00-\x7f]/', $str)) {
7977			return false; // Contains a byte > 7f
7978		}
7979	}
7980
7981	return true;
7982}
7983
7984
7985/**
7986 *      Return a string encoded into OS filesystem encoding. This function is used to define
7987 * 	    value to pass to filesystem PHP functions.
7988 *
7989 *      @param	string	$str        String to encode (UTF-8)
7990 * 		@return	string				Encoded string (UTF-8, ISO-8859-1)
7991 */
7992function dol_osencode($str)
7993{
7994	global $conf;
7995
7996	$tmp = ini_get("unicode.filesystem_encoding"); // Disponible avec PHP 6.0
7997	if (empty($tmp) && !empty($_SERVER["WINDIR"])) {
7998		$tmp = 'iso-8859-1'; // By default for windows
7999	}
8000	if (empty($tmp)) {
8001		$tmp = 'utf-8'; // By default for other
8002	}
8003	if (!empty($conf->global->MAIN_FILESYSTEM_ENCODING)) {
8004		$tmp = $conf->global->MAIN_FILESYSTEM_ENCODING;
8005	}
8006
8007	if ($tmp == 'iso-8859-1') {
8008		return utf8_decode($str);
8009	}
8010	return $str;
8011}
8012
8013
8014/**
8015 *      Return an id or code from a code or id.
8016 *      Store also Code-Id into a cache to speed up next request on same key.
8017 *
8018 * 		@param	DoliDB	$db				Database handler
8019 * 		@param	string	$key			Code or Id to get Id or Code
8020 * 		@param	string	$tablename		Table name without prefix
8021 * 		@param	string	$fieldkey		Field to search the key into
8022 * 		@param	string	$fieldid		Field to get
8023 *      @param  int		$entityfilter	Filter by entity
8024 *      @return int						<0 if KO, Id of code if OK
8025 *      @see $langs->getLabelFromKey
8026 */
8027function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid = 'id', $entityfilter = 0)
8028{
8029	global $cache_codes;
8030
8031	// If key empty
8032	if ($key == '') {
8033		return '';
8034	}
8035
8036	// Check in cache
8037	if (isset($cache_codes[$tablename][$key][$fieldid])) {	// Can be defined to 0 or ''
8038		return $cache_codes[$tablename][$key][$fieldid]; // Found in cache
8039	}
8040
8041	dol_syslog('dol_getIdFromCode (value for field '.$fieldid.' from key '.$key.' not found into cache)', LOG_DEBUG);
8042
8043	$sql = "SELECT ".$fieldid." as valuetoget";
8044	$sql .= " FROM ".MAIN_DB_PREFIX.$tablename;
8045	$sql .= " WHERE ".$fieldkey." = '".$db->escape($key)."'";
8046	if (!empty($entityfilter)) {
8047		$sql .= " AND entity IN (".getEntity($tablename).")";
8048	}
8049
8050	$resql = $db->query($sql);
8051	if ($resql) {
8052		$obj = $db->fetch_object($resql);
8053		if ($obj) {
8054			$cache_codes[$tablename][$key][$fieldid] = $obj->valuetoget;
8055		} else {
8056			$cache_codes[$tablename][$key][$fieldid] = '';
8057		}
8058		$db->free($resql);
8059		return $cache_codes[$tablename][$key][$fieldid];
8060	} else {
8061		return -1;
8062	}
8063}
8064
8065/**
8066 * Verify if condition in string is ok or not
8067 *
8068 * @param 	string		$strRights		String with condition to check
8069 * @return 	boolean						True or False. Return True if strRights is ''
8070 */
8071function verifCond($strRights)
8072{
8073	global $user, $conf, $langs;
8074	global $leftmenu;
8075	global $rights; // To export to dol_eval function
8076
8077	//print $strRights."<br>\n";
8078	$rights = true;
8079	if ($strRights != '') {
8080		$str = 'if(!('.$strRights.')) { $rights = false; }';
8081		dol_eval($str); // The dol_eval must contains all the global $xxx used into a condition
8082	}
8083	return $rights;
8084}
8085
8086/**
8087 * Replace eval function to add more security.
8088 * This function is called by verifCond() or trans() and transnoentitiesnoconv().
8089 *
8090 * @param 	string	$s				String to evaluate
8091 * @param	int		$returnvalue	0=No return (used to execute eval($a=something)). 1=Value of eval is returned (used to eval($something)).
8092 * @param   int     $hideerrors     1=Hide errors
8093 * @return	mixed					Nothing or return result of eval
8094 */
8095function dol_eval($s, $returnvalue = 0, $hideerrors = 1)
8096{
8097	// Only global variables can be changed by eval function and returned to caller
8098	global $db, $langs, $user, $conf, $website, $websitepage;
8099	global $action, $mainmenu, $leftmenu;
8100	global $rights;
8101	global $object;
8102	global $mysoc;
8103
8104	global $obj; // To get $obj used into list when dol_eval is used for computed fields and $obj is not yet $object
8105	global $soc; // For backward compatibility
8106
8107	// Replace dangerous char (used for RCE), we allow only PHP variable testing.
8108	if (strpos($s, '`') !== false) {
8109		return 'Bad string syntax to evaluate: '.$s;
8110	}
8111
8112	// We block using of php exec or php file functions
8113	$forbiddenphpstrings = array("exec(", "passthru(", "shell_exec(", "system(", "proc_open(", "popen(", "eval(", "dol_eval(", "executeCLI(");
8114	$forbiddenphpstrings = array_merge($forbiddenphpstrings, array("fopen(", "file_put_contents(", "fputs(", "fputscsv(", "fwrite(", "fpassthru(", "unlink(", "mkdir(", "rmdir(", "symlink(", "touch(", "umask("));
8115	$forbiddenphpstrings = array_merge($forbiddenphpstrings, array('function(', '$$', 'call_user_func('));
8116	$forbiddenphpstrings = array_merge($forbiddenphpstrings, array('_ENV', '_SESSION', '_COOKIE', '_GET', '_POST', '_REQUEST'));
8117	$forbiddenphpregex = 'global\s+\$';
8118	do {
8119		$oldstringtoclean = $s;
8120		$s = str_ireplace($forbiddenphpstrings, '__forbiddenstring__', $s);
8121		$s = preg_replace('/'.$forbiddenphpregex.'/', '__forbiddenstring__', $s);
8122		//$s = preg_replace('/\$[a-zA-Z0-9_\->\$]+\(/i', '', $s);	// Remove $function( call and $mycall->mymethod(
8123	} while ($oldstringtoclean != $s);
8124
8125	if (strpos($s, '__forbiddenstring__') !== false) {
8126		dol_syslog('Bad string syntax to evaluate: '.$s, LOG_WARNING);
8127		return 'Bad string syntax to evaluate: '.$s;
8128	}
8129
8130	//print $s."<br>\n";
8131	if ($returnvalue) {
8132		if ($hideerrors) {
8133			return @eval('return '.$s.';');
8134		} else {
8135			return eval('return '.$s.';');
8136		}
8137	} else {
8138		if ($hideerrors) {
8139			@eval($s);
8140		} else {
8141			eval($s);
8142		}
8143	}
8144}
8145
8146/**
8147 * Return if var element is ok
8148 *
8149 * @param   string      $element    Variable to check
8150 * @return  boolean                 Return true of variable is not empty
8151 */
8152function dol_validElement($element)
8153{
8154	return (trim($element) != '');
8155}
8156
8157/**
8158 * 	Return img flag of country for a language code or country code.
8159 *
8160 * 	@param	string	$codelang	Language code ('en_IN', 'fr_CA', ...) or ISO Country code on 2 characters in uppercase ('IN', 'FR')
8161 *  @param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"' or 'class="saturatemedium"')
8162 * 	@return	string				HTML img string with flag.
8163 */
8164function picto_from_langcode($codelang, $moreatt = '')
8165{
8166	if (empty($codelang)) {
8167		return '';
8168	}
8169
8170	if ($codelang == 'auto') {
8171		return '<span class="fa fa-language"></span>';
8172	}
8173
8174	$langtocountryflag = array(
8175		'ar_AR' => '',
8176		'ca_ES' => 'catalonia',
8177		'da_DA' => 'dk',
8178		'fr_CA' => 'mq',
8179		'sv_SV' => 'se',
8180		'sw_SW' => 'unknown',
8181		'AQ' => 'unknown',
8182		'CW' => 'unknown',
8183		'IM' => 'unknown',
8184		'JE' => 'unknown',
8185		'MF' => 'unknown',
8186		'BL' => 'unknown',
8187		'SX' => 'unknown'
8188	);
8189
8190	if (isset($langtocountryflag[$codelang])) {
8191		$flagImage = $langtocountryflag[$codelang];
8192	} else {
8193		$tmparray = explode('_', $codelang);
8194		$flagImage = empty($tmparray[1]) ? $tmparray[0] : $tmparray[1];
8195	}
8196
8197	return img_picto_common($codelang, 'flags/'.strtolower($flagImage).'.png', $moreatt);
8198}
8199
8200/**
8201 * Return default language from country code.
8202 * Return null if not found.
8203 *
8204 * @param 	string 	$countrycode	Country code like 'US', 'FR', 'CA', 'ES', 'IN', 'MX', ...
8205 * @return	string					Value of locale like 'en_US', 'fr_FR', ... or null if not found
8206 */
8207function getLanguageCodeFromCountryCode($countrycode)
8208{
8209	global $mysoc;
8210
8211	if (empty($countrycode)) {
8212		return null;
8213	}
8214
8215	if (strtoupper($countrycode) == 'MQ') {
8216		return 'fr_CA';
8217	}
8218	if (strtoupper($countrycode) == 'SE') {
8219		return 'sv_SE'; // se_SE is Sami/Sweden, and we want in priority sv_SE for SE country
8220	}
8221	if (strtoupper($countrycode) == 'CH') {
8222		if ($mysoc->country_code == 'FR') {
8223			return 'fr_CH';
8224		}
8225		if ($mysoc->country_code == 'DE') {
8226			return 'de_CH';
8227		}
8228		if ($mysoc->country_code == 'IT') {
8229			return 'it_CH';
8230		}
8231	}
8232
8233	// Locale list taken from:
8234	// http://stackoverflow.com/questions/3191664/
8235	// list-of-all-locales-and-their-short-codes
8236	$locales = array(
8237		'af-ZA',
8238		'am-ET',
8239		'ar-AE',
8240		'ar-BH',
8241		'ar-DZ',
8242		'ar-EG',
8243		'ar-IQ',
8244		'ar-JO',
8245		'ar-KW',
8246		'ar-LB',
8247		'ar-LY',
8248		'ar-MA',
8249		'ar-OM',
8250		'ar-QA',
8251		'ar-SA',
8252		'ar-SY',
8253		'ar-TN',
8254		'ar-YE',
8255		//'as-IN',		// Moved after en-IN
8256		'ba-RU',
8257		'be-BY',
8258		'bg-BG',
8259		'bn-BD',
8260		//'bn-IN',		// Moved after en-IN
8261		'bo-CN',
8262		'br-FR',
8263		'ca-ES',
8264		'co-FR',
8265		'cs-CZ',
8266		'cy-GB',
8267		'da-DK',
8268		'de-AT',
8269		'de-CH',
8270		'de-DE',
8271		'de-LI',
8272		'de-LU',
8273		'dv-MV',
8274		'el-GR',
8275		'en-AU',
8276		'en-BZ',
8277		'en-CA',
8278		'en-GB',
8279		'en-IE',
8280		'en-IN',
8281		'as-IN',	// as-IN must be after en-IN (en in priority if country is IN)
8282		'bn-IN',	// bn-IN must be after en-IN (en in priority if country is IN)
8283		'en-JM',
8284		'en-MY',
8285		'en-NZ',
8286		'en-PH',
8287		'en-SG',
8288		'en-TT',
8289		'en-US',
8290		'en-ZA',
8291		'en-ZW',
8292		'es-AR',
8293		'es-BO',
8294		'es-CL',
8295		'es-CO',
8296		'es-CR',
8297		'es-DO',
8298		'es-EC',
8299		'es-ES',
8300		'es-GT',
8301		'es-HN',
8302		'es-MX',
8303		'es-NI',
8304		'es-PA',
8305		'es-PE',
8306		'es-PR',
8307		'es-PY',
8308		'es-SV',
8309		'es-US',
8310		'es-UY',
8311		'es-VE',
8312		'et-EE',
8313		'eu-ES',
8314		'fa-IR',
8315		'fi-FI',
8316		'fo-FO',
8317		'fr-BE',
8318		'fr-CA',
8319		'fr-CH',
8320		'fr-FR',
8321		'fr-LU',
8322		'fr-MC',
8323		'fy-NL',
8324		'ga-IE',
8325		'gd-GB',
8326		'gl-ES',
8327		'gu-IN',
8328		'he-IL',
8329		'hi-IN',
8330		'hr-BA',
8331		'hr-HR',
8332		'hu-HU',
8333		'hy-AM',
8334		'id-ID',
8335		'ig-NG',
8336		'ii-CN',
8337		'is-IS',
8338		'it-CH',
8339		'it-IT',
8340		'ja-JP',
8341		'ka-GE',
8342		'kk-KZ',
8343		'kl-GL',
8344		'km-KH',
8345		'kn-IN',
8346		'ko-KR',
8347		'ky-KG',
8348		'lb-LU',
8349		'lo-LA',
8350		'lt-LT',
8351		'lv-LV',
8352		'mi-NZ',
8353		'mk-MK',
8354		'ml-IN',
8355		'mn-MN',
8356		'mr-IN',
8357		'ms-BN',
8358		'ms-MY',
8359		'mt-MT',
8360		'nb-NO',
8361		'ne-NP',
8362		'nl-BE',
8363		'nl-NL',
8364		'nn-NO',
8365		'oc-FR',
8366		'or-IN',
8367		'pa-IN',
8368		'pl-PL',
8369		'ps-AF',
8370		'pt-BR',
8371		'pt-PT',
8372		'rm-CH',
8373		'ro-MD',
8374		'ro-RO',
8375		'ru-RU',
8376		'rw-RW',
8377		'sa-IN',
8378		'se-FI',
8379		'se-NO',
8380		'se-SE',
8381		'si-LK',
8382		'sk-SK',
8383		'sl-SI',
8384		'sq-AL',
8385		'sv-FI',
8386		'sv-SE',
8387		'sw-KE',
8388		'ta-IN',
8389		'te-IN',
8390		'th-TH',
8391		'tk-TM',
8392		'tn-ZA',
8393		'tr-TR',
8394		'tt-RU',
8395		'ug-CN',
8396		'uk-UA',
8397		'ur-PK',
8398		'vi-VN',
8399		'wo-SN',
8400		'xh-ZA',
8401		'yo-NG',
8402		'zh-CN',
8403		'zh-HK',
8404		'zh-MO',
8405		'zh-SG',
8406		'zh-TW',
8407		'zu-ZA',
8408	);
8409
8410	$buildprimarykeytotest = strtolower($countrycode).'-'.strtoupper($countrycode);
8411	if (in_array($buildprimarykeytotest, $locales)) {
8412		return strtolower($countrycode).'_'.strtoupper($countrycode);
8413	}
8414
8415	if (function_exists('locale_get_primary_language') && function_exists('locale_get_region')) {    // Need extension php-intl
8416		foreach ($locales as $locale) {
8417			$locale_language = locale_get_primary_language($locale);
8418			$locale_region = locale_get_region($locale);
8419			if (strtoupper($countrycode) == $locale_region) {
8420				//var_dump($locale.' - '.$locale_language.' - '.$locale_region);
8421				return strtolower($locale_language).'_'.strtoupper($locale_region);
8422			}
8423		}
8424	} else {
8425		dol_syslog("Warning Exention php-intl is not available", LOG_WARNING);
8426	}
8427
8428	return null;
8429}
8430
8431/**
8432 *  Complete or removed entries into a head array (used to build tabs).
8433 *  For example, with value added by external modules. Such values are declared into $conf->modules_parts['tab'].
8434 *  Or by change using hook completeTabsHead
8435 *
8436 *  @param	Conf			$conf           Object conf
8437 *  @param  Translate		$langs          Object langs
8438 *  @param  object|null		$object         Object object
8439 *  @param  array			$head          	Object head
8440 *  @param  int				$h				New position to fill
8441 *  @param  string			$type           Value for object where objectvalue can be
8442 *                              			'thirdparty'       to add a tab in third party view
8443 *		                        	      	'intervention'     to add a tab in intervention view
8444 *     		                    	     	'supplier_order'   to add a tab in supplier order view
8445 *          		            	        'supplier_invoice' to add a tab in supplier invoice view
8446 *                  		    	        'invoice'          to add a tab in customer invoice view
8447 *                          			    'order'            to add a tab in customer order view
8448 *                          				'contract'		   to add a tabl in contract view
8449 *                      			        'product'          to add a tab in product view
8450 *                              			'propal'           to add a tab in propal view
8451 *                              			'user'             to add a tab in user view
8452 *                              			'group'            to add a tab in group view
8453 * 		        	               	     	'member'           to add a tab in fundation member view
8454 *      		                        	'categories_x'	   to add a tab in category view ('x': type of category (0=product, 1=supplier, 2=customer, 3=member)
8455 *      									'ecm'			   to add a tab for another ecm view
8456 *                                          'stock'            to add a tab for warehouse view
8457 *  @param  string		$mode  	        	'add' to complete head, 'remove' to remove entries
8458 *	@return	void
8459 */
8460function complete_head_from_modules($conf, $langs, $object, &$head, &$h, $type, $mode = 'add')
8461{
8462	global $hookmanager;
8463
8464	if (isset($conf->modules_parts['tabs'][$type]) && is_array($conf->modules_parts['tabs'][$type])) {
8465		foreach ($conf->modules_parts['tabs'][$type] as $value) {
8466			$values = explode(':', $value);
8467
8468			if ($mode == 'add' && !preg_match('/^\-/', $values[1])) {
8469				if (count($values) == 6) {       // new declaration with permissions:  $value='objecttype:+tabname1:Title1:langfile@mymodule:$user->rights->mymodule->read:/mymodule/mynewtab1.php?id=__ID__'
8470					if ($values[0] != $type) {
8471						continue;
8472					}
8473
8474					if (verifCond($values[4])) {
8475						if ($values[3]) {
8476							$langs->load($values[3]);
8477						}
8478						if (preg_match('/SUBSTITUTION_([^_]+)/i', $values[2], $reg)) {
8479							$substitutionarray = array();
8480							complete_substitutions_array($substitutionarray, $langs, $object, array('needforkey'=>$values[2]));
8481							$label = make_substitutions($reg[1], $substitutionarray);
8482						} else {
8483							$label = $langs->trans($values[2]);
8484						}
8485
8486						$head[$h][0] = dol_buildpath(preg_replace('/__ID__/i', ((is_object($object) && !empty($object->id)) ? $object->id : ''), $values[5]), 1);
8487						$head[$h][1] = $label;
8488						$head[$h][2] = str_replace('+', '', $values[1]);
8489						$h++;
8490					}
8491				} elseif (count($values) == 5) {       // deprecated
8492					dol_syslog('Passing 5 values in tabs module_parts is deprecated. Please update to 6 with permissions.', LOG_WARNING);
8493
8494					if ($values[0] != $type) {
8495						continue;
8496					}
8497					if ($values[3]) {
8498						$langs->load($values[3]);
8499					}
8500					if (preg_match('/SUBSTITUTION_([^_]+)/i', $values[2], $reg)) {
8501						$substitutionarray = array();
8502						complete_substitutions_array($substitutionarray, $langs, $object, array('needforkey'=>$values[2]));
8503						$label = make_substitutions($reg[1], $substitutionarray);
8504					} else {
8505						$label = $langs->trans($values[2]);
8506					}
8507
8508					$head[$h][0] = dol_buildpath(preg_replace('/__ID__/i', ((is_object($object) && !empty($object->id)) ? $object->id : ''), $values[4]), 1);
8509					$head[$h][1] = $label;
8510					$head[$h][2] = str_replace('+', '', $values[1]);
8511					$h++;
8512				}
8513			} elseif ($mode == 'remove' && preg_match('/^\-/', $values[1])) {
8514				if ($values[0] != $type) {
8515					continue;
8516				}
8517				$tabname = str_replace('-', '', $values[1]);
8518				foreach ($head as $key => $val) {
8519					$condition = (!empty($values[3]) ? verifCond($values[3]) : 1);
8520					//var_dump($key.' - '.$tabname.' - '.$head[$key][2].' - '.$values[3].' - '.$condition);
8521					if ($head[$key][2] == $tabname && $condition) {
8522						unset($head[$key]);
8523						break;
8524					}
8525				}
8526			}
8527		}
8528	}
8529
8530	// No need to make a return $head. Var is modified as a reference
8531	if (!empty($hookmanager)) {
8532		$parameters = array('object' => $object, 'mode' => $mode, 'head' => &$head);
8533		$reshook = $hookmanager->executeHooks('completeTabsHead', $parameters);
8534		if ($reshook > 0) {		// Hook ask to replace completely the array
8535			$head = $hookmanager->resArray;
8536		} else {				// Hook
8537			$head = array_merge($head, $hookmanager->resArray);
8538		}
8539		$h = count($head);
8540	}
8541}
8542
8543/**
8544 * Print common footer :
8545 * 		conf->global->MAIN_HTML_FOOTER
8546 *      js for switch of menu hider
8547 * 		js for conf->global->MAIN_GOOGLE_AN_ID
8548 * 		js for conf->global->MAIN_SHOW_TUNING_INFO or $_SERVER["MAIN_SHOW_TUNING_INFO"]
8549 * 		js for conf->logbuffer
8550 *
8551 * @param	string	$zone	'private' (for private pages) or 'public' (for public pages)
8552 * @return	void
8553 */
8554function printCommonFooter($zone = 'private')
8555{
8556	global $conf, $hookmanager, $user, $debugbar;
8557	global $action;
8558	global $micro_start_time;
8559
8560	if ($zone == 'private') {
8561		print "\n".'<!-- Common footer for private page -->'."\n";
8562	} else {
8563		print "\n".'<!-- Common footer for public page -->'."\n";
8564	}
8565
8566	// A div to store page_y POST parameter so we can read it using javascript
8567	print "\n<!-- A div to store page_y POST parameter -->\n";
8568	print '<div id="page_y" style="display: none;">'.(empty($_POST['page_y']) ? '' : $_POST['page_y']).'</div>'."\n";
8569
8570	$parameters = array();
8571	$reshook = $hookmanager->executeHooks('printCommonFooter', $parameters); // Note that $action and $object may have been modified by some hooks
8572	if (empty($reshook)) {
8573		if (!empty($conf->global->MAIN_HTML_FOOTER)) {
8574			print $conf->global->MAIN_HTML_FOOTER."\n";
8575		}
8576
8577		print "\n";
8578		if (!empty($conf->use_javascript_ajax)) {
8579			print '<script>'."\n";
8580			print 'jQuery(document).ready(function() {'."\n";
8581
8582			if ($zone == 'private' && empty($conf->dol_use_jmobile)) {
8583				print "\n";
8584				print '/* JS CODE TO ENABLE to manage handler to switch left menu page (menuhider) */'."\n";
8585				print 'jQuery("li.menuhider").click(function(event) {';
8586				print '  if (!$( "body" ).hasClass( "sidebar-collapse" )){ event.preventDefault(); }'."\n";
8587				print '  console.log("We click on .menuhider");'."\n";
8588				print '  $("body").toggleClass("sidebar-collapse")'."\n";
8589				print '});'."\n";
8590			}
8591
8592			// Management of focus and mandatory for fields
8593			if ($action == 'create' || $action == 'edit' || (empty($action) && (preg_match('/new\.php/', $_SERVER["PHP_SELF"])))) {
8594				print '/* JS CODE TO ENABLE to manage focus and mandatory form fields */'."\n";
8595				$relativepathstring = $_SERVER["PHP_SELF"];
8596				// Clean $relativepathstring
8597				if (constant('DOL_URL_ROOT')) {
8598					$relativepathstring = preg_replace('/^'.preg_quote(constant('DOL_URL_ROOT'), '/').'/', '', $relativepathstring);
8599				}
8600				$relativepathstring = preg_replace('/^\//', '', $relativepathstring);
8601				$relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
8602				//$tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
8603				if (!empty($user->default_values[$relativepathstring]['focus'])) {
8604					foreach ($user->default_values[$relativepathstring]['focus'] as $defkey => $defval) {
8605						$qualified = 0;
8606						if ($defkey != '_noquery_') {
8607							$tmpqueryarraytohave = explode('&', $defkey);
8608							$foundintru = 0;
8609							foreach ($tmpqueryarraytohave as $tmpquerytohave) {
8610								$tmpquerytohaveparam = explode('=', $tmpquerytohave);
8611								//print "console.log('".$tmpquerytohaveparam[0]." ".$tmpquerytohaveparam[1]." ".GETPOST($tmpquerytohaveparam[0])."');";
8612								if (!GETPOSTISSET($tmpquerytohaveparam[0]) || ($tmpquerytohaveparam[1] != GETPOST($tmpquerytohaveparam[0]))) {
8613									$foundintru = 1;
8614								}
8615							}
8616							if (!$foundintru) {
8617								$qualified = 1;
8618							}
8619							//var_dump($defkey.'-'.$qualified);
8620						} else {
8621							$qualified = 1;
8622						}
8623
8624						if ($qualified) {
8625							foreach ($defval as $paramkey => $paramval) {
8626								// Set focus on field
8627								print 'jQuery("input[name=\''.$paramkey.'\']").focus();'."\n";
8628								print 'jQuery("textarea[name=\''.$paramkey.'\']").focus();'."\n";
8629								print 'jQuery("select[name=\''.$paramkey.'\']").focus();'."\n"; // Not really usefull, but we keep it in case of.
8630							}
8631						}
8632					}
8633				}
8634				if (!empty($user->default_values[$relativepathstring]['mandatory'])) {
8635					foreach ($user->default_values[$relativepathstring]['mandatory'] as $defkey => $defval) {
8636						$qualified = 0;
8637						if ($defkey != '_noquery_') {
8638							$tmpqueryarraytohave = explode('&', $defkey);
8639							$foundintru = 0;
8640							foreach ($tmpqueryarraytohave as $tmpquerytohave) {
8641								$tmpquerytohaveparam = explode('=', $tmpquerytohave);
8642								//print "console.log('".$tmpquerytohaveparam[0]." ".$tmpquerytohaveparam[1]." ".GETPOST($tmpquerytohaveparam[0])."');";
8643								if (!GETPOSTISSET($tmpquerytohaveparam[0]) || ($tmpquerytohaveparam[1] != GETPOST($tmpquerytohaveparam[0]))) {
8644									$foundintru = 1;
8645								}
8646							}
8647							if (!$foundintru) {
8648								$qualified = 1;
8649							}
8650							//var_dump($defkey.'-'.$qualified);
8651						} else {
8652							$qualified = 1;
8653						}
8654
8655						if ($qualified) {
8656							foreach ($defval as $paramkey => $paramval) {
8657								// Add property 'required' on input
8658								print 'jQuery("input[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";
8659								print 'jQuery("textarea[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";
8660								print '// required on a select works only if key is "", so we add the required attributes but also we reset the key -1 or 0 to an empty string'."\n";
8661								print 'jQuery("select[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";
8662								print 'jQuery("select[name=\''.$paramkey.'\'] option[value=\'-1\']").prop(\'value\', \'\');'."\n";
8663								print 'jQuery("select[name=\''.$paramkey.'\'] option[value=\'0\']").prop(\'value\', \'\');'."\n";
8664							}
8665						}
8666					}
8667				}
8668			}
8669
8670			print '});'."\n";
8671
8672			// End of tuning
8673			if (!empty($_SERVER['MAIN_SHOW_TUNING_INFO']) || !empty($conf->global->MAIN_SHOW_TUNING_INFO)) {
8674				print "\n";
8675				print "/* JS CODE TO ENABLE to add memory info */\n";
8676				print 'window.console && console.log("';
8677				if (!empty($conf->global->MEMCACHED_SERVER)) {
8678					print 'MEMCACHED_SERVER='.$conf->global->MEMCACHED_SERVER.' - ';
8679				}
8680				print 'MAIN_OPTIMIZE_SPEED='.(isset($conf->global->MAIN_OPTIMIZE_SPEED) ? $conf->global->MAIN_OPTIMIZE_SPEED : 'off');
8681				if (!empty($micro_start_time)) {   // Works only if MAIN_SHOW_TUNING_INFO is defined at $_SERVER level. Not in global variable.
8682					$micro_end_time = microtime(true);
8683					print ' - Build time: '.ceil(1000 * ($micro_end_time - $micro_start_time)).' ms';
8684				}
8685
8686				if (function_exists("memory_get_usage")) {
8687					print ' - Mem: '.memory_get_usage(); // Do not use true here, it seems it takes the peak amount
8688				}
8689				if (function_exists("memory_get_peak_usage")) {
8690					print ' - Real mem peak: '.memory_get_peak_usage(true);
8691				}
8692				if (function_exists("zend_loader_file_encoded")) {
8693					print ' - Zend encoded file: '.(zend_loader_file_encoded() ? 'yes' : 'no');
8694				}
8695				print '");'."\n";
8696			}
8697
8698			print "\n".'</script>'."\n";
8699
8700			// Google Analytics
8701			// TODO Add a hook here
8702			if (!empty($conf->google->enabled) && !empty($conf->global->MAIN_GOOGLE_AN_ID)) {
8703				$tmptagarray = explode(',', $conf->global->MAIN_GOOGLE_AN_ID);
8704				foreach ($tmptagarray as $tmptag) {
8705					print "\n";
8706					print "<!-- JS CODE TO ENABLE for google analtics tag -->\n";
8707					print "
8708					<!-- Global site tag (gtag.js) - Google Analytics -->
8709					<script async src=\"https://www.googletagmanager.com/gtag/js?id=".trim($tmptag)."\"></script>
8710					<script>
8711					window.dataLayer = window.dataLayer || [];
8712					function gtag(){dataLayer.push(arguments);}
8713					gtag('js', new Date());
8714
8715					gtag('config', '".trim($tmptag)."');
8716					</script>";
8717					print "\n";
8718				}
8719			}
8720		}
8721
8722		// Add Xdebug coverage of code
8723		if (defined('XDEBUGCOVERAGE')) {
8724			print_r(xdebug_get_code_coverage());
8725		}
8726
8727		// Add DebugBar data
8728		if (!empty($user->rights->debugbar->read) && is_object($debugbar)) {
8729			$debugbar['time']->stopMeasure('pageaftermaster');
8730			print '<!-- Output debugbar data -->'."\n";
8731			$renderer = $debugbar->getRenderer();
8732			print $debugbar->getRenderer()->render();
8733		} elseif (count($conf->logbuffer)) {    // If there is some logs in buffer to show
8734			print "\n";
8735			print "<!-- Start of log output\n";
8736			//print '<div class="hidden">'."\n";
8737			foreach ($conf->logbuffer as $logline) {
8738				print $logline."<br>\n";
8739			}
8740			//print '</div>'."\n";
8741			print "End of log output -->\n";
8742		}
8743	}
8744}
8745
8746/**
8747 * Split a string with 2 keys into key array.
8748 * For example: "A=1;B=2;C=2" is exploded into array('A'=>1,'B'=>2,'C'=>3)
8749 *
8750 * @param 	string	$string		String to explode
8751 * @param 	string	$delimiter	Delimiter between each couple of data
8752 * @param 	string	$kv			Delimiter between key and value
8753 * @return	array				Array of data exploded
8754 */
8755function dolExplodeIntoArray($string, $delimiter = ';', $kv = '=')
8756{
8757	if ($a = explode($delimiter, $string)) {
8758		$ka = array();
8759		foreach ($a as $s) { // each part
8760			if ($s) {
8761				if ($pos = strpos($s, $kv)) { // key/value delimiter
8762					$ka[trim(substr($s, 0, $pos))] = trim(substr($s, $pos + strlen($kv)));
8763				} else { // key delimiter not found
8764					$ka[] = trim($s);
8765				}
8766			}
8767		}
8768		return $ka;
8769	}
8770	return array();
8771}
8772
8773
8774/**
8775 * Set focus onto field with selector (similar behaviour of 'autofocus' HTML5 tag)
8776 *
8777 * @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.
8778 * @return	string				HTML code to set focus
8779 */
8780function dol_set_focus($selector)
8781{
8782	print "\n".'<!-- Set focus onto a specific field -->'."\n";
8783	print '<script>jQuery(document).ready(function() { jQuery("'.dol_escape_js($selector).'").focus(); });</script>'."\n";
8784}
8785
8786
8787/**
8788 * Return getmypid() or random PID when function is disabled
8789 * Some web hosts disable this php function for security reasons
8790 * and sometimes we can't redeclare function
8791 *
8792 * @return	int
8793 */
8794function dol_getmypid()
8795{
8796	if (!function_exists('getmypid')) {
8797		return mt_rand(1, 32768);
8798	} else {
8799		return getmypid();
8800	}
8801}
8802
8803
8804/**
8805 * Generate natural SQL search string for a criteria (this criteria can be tested on one or several fields)
8806 *
8807 * @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")
8808 * @param   string 			$value 		The value to look for.
8809 *                          		    If param $mode is 0, can contains several keywords separated with a space or |
8810 *                                      like "keyword1 keyword2" = We want record field like keyword1 AND field like keyword2
8811 *                                      or like "keyword1|keyword2" = We want record field like keyword1 OR field like keyword2
8812 *                             			If param $mode is 1, can contains an operator <, > or = like "<10" or ">=100.5 < 1000"
8813 *                             			If param $mode is 2, can contains a list of int id separated by comma like "1,3,4"
8814 *                             			If param $mode is 3, can contains a list of string separated by comma like "a,b,c"
8815 * @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')
8816 * 										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 '2,7') to be used to search into a multiselect string '1,2,3,4'
8817 * @param	integer			$nofirstand	1=Do not output the first 'AND'
8818 * @return 	string 			$res 		The statement to append to the SQL query
8819 */
8820function natural_search($fields, $value, $mode = 0, $nofirstand = 0)
8821{
8822	global $db, $langs;
8823
8824	$value = trim($value);
8825
8826	if ($mode == 0) {
8827		$value = preg_replace('/\*/', '%', $value); // Replace * with %
8828	}
8829	if ($mode == 1) {
8830		$value = preg_replace('/([<>=]+)\s+([0-9'.preg_quote($langs->trans("DecimalSeparator"), '/').'\-])/', '\1\2', $value); // Clean string '< 10' into '<10' so we can the explode on space to get all tests to do
8831	}
8832
8833	$value = preg_replace('/\s*\|\s*/', '|', $value);
8834
8835	$crits = explode(' ', $value);
8836	$res = '';
8837	if (!is_array($fields)) {
8838		$fields = array($fields);
8839	}
8840
8841	$j = 0;
8842	foreach ($crits as $crit) {
8843		$crit = trim($crit);
8844		$i = 0;
8845		$i2 = 0;
8846		$newres = '';
8847		foreach ($fields as $field) {
8848			if ($mode == 1) {
8849				$operator = '=';
8850				$newcrit = preg_replace('/([<>=]+)/', '', $crit);
8851
8852				$reg = array();
8853				preg_match('/([<>=]+)/', $crit, $reg);
8854				if ($reg[1]) {
8855					$operator = $reg[1];
8856				}
8857				if ($newcrit != '') {
8858					$numnewcrit = price2num($newcrit);
8859					if (is_numeric($numnewcrit)) {
8860						$newres .= ($i2 > 0 ? ' OR ' : '').$field.' '.$operator.' '.((float) $numnewcrit); // should be a numeric
8861					} else {
8862						$newres .= ($i2 > 0 ? ' OR ' : '').'1 = 2'; // force false
8863					}
8864					$i2++; // a criteria was added to string
8865				}
8866			} elseif ($mode == 2 || $mode == -2) {
8867				$crit = preg_replace('/[^0-9,]/', '', $crit); // ID are always integer
8868				$newres .= ($i2 > 0 ? ' OR ' : '').$field." ".($mode == -2 ? 'NOT ' : '');
8869				$newres .= $crit ? "IN (".$db->sanitize($db->escape($crit)).")" : "IN (0)";
8870				if ($mode == -2) {
8871					$newres .= ' OR '.$field.' IS NULL';
8872				}
8873				$i2++; // a criteria was added to string
8874			} elseif ($mode == 3 || $mode == -3) {
8875				$tmparray = explode(',', $crit);
8876				if (count($tmparray)) {
8877					$listofcodes = '';
8878					foreach ($tmparray as $val) {
8879						$val = trim($val);
8880						if ($val) {
8881							$listofcodes .= ($listofcodes ? ',' : '');
8882							$listofcodes .= "'".$db->escape($val)."'";
8883						}
8884					}
8885					$newres .= ($i2 > 0 ? ' OR ' : '').$field." ".($mode == -3 ? 'NOT ' : '')."IN (".$db->sanitize($listofcodes, 1).")";
8886					$i2++; // a criteria was added to string
8887				}
8888				if ($mode == -3) {
8889					$newres .= ' OR '.$field.' IS NULL';
8890				}
8891			} elseif ($mode == 4) {
8892				$tmparray = explode(',', $crit);
8893				if (count($tmparray)) {
8894					$listofcodes = '';
8895					foreach ($tmparray as $val) {
8896						$val = trim($val);
8897						if ($val) {
8898							$newres .= ($i2 > 0 ? ' OR (' : '(').$field.' LIKE \''.$db->escape($val).',%\'';
8899							$newres .= ' OR '.$field.' = \''.$db->escape($val).'\'';
8900							$newres .= ' OR '.$field.' LIKE \'%,'.$db->escape($val).'\'';
8901							$newres .= ' OR '.$field.' LIKE \'%,'.$db->escape($val).',%\'';
8902							$newres .= ')';
8903							$i2++;
8904						}
8905					}
8906				}
8907			} else // $mode=0
8908			{
8909				$tmpcrits = explode('|', $crit);
8910				$i3 = 0;
8911				foreach ($tmpcrits as $tmpcrit) {
8912					if ($tmpcrit !== '0' && empty($tmpcrit)) {
8913						continue;
8914					}
8915
8916					$newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : '');
8917
8918					if (preg_match('/\.(id|rowid)$/', $field)) {	// Special case for rowid that is sometimes a ref so used as a search field
8919						$newres .= $field." = ".(is_numeric(trim($tmpcrit)) ? ((float) trim($tmpcrit)) : '0');
8920					} else {
8921						$newres .= $field." LIKE '";
8922
8923						$tmpcrit = trim($tmpcrit);
8924						$tmpcrit2 = $tmpcrit;
8925						$tmpbefore = '%';
8926						$tmpafter = '%';
8927						if (preg_match('/^[\^\$]/', $tmpcrit)) {
8928							$tmpbefore = '';
8929							$tmpcrit2 = preg_replace('/^[\^\$]/', '', $tmpcrit2);
8930						}
8931						if (preg_match('/[\^\$]$/', $tmpcrit)) {
8932							$tmpafter = '';
8933							$tmpcrit2 = preg_replace('/[\^\$]$/', '', $tmpcrit2);
8934						}
8935						$newres .= $tmpbefore;
8936						$newres .= $db->escape($tmpcrit2);
8937						$newres .= $tmpafter;
8938						$newres .= "'";
8939						if ($tmpcrit2 == '') {
8940							$newres .= ' OR '.$field." IS NULL";
8941						}
8942					}
8943
8944					$i3++;
8945				}
8946				$i2++; // a criteria was added to string
8947			}
8948			$i++;
8949		}
8950		if ($newres) {
8951			$res = $res.($res ? ' AND ' : '').($i2 > 1 ? '(' : '').$newres.($i2 > 1 ? ')' : '');
8952		}
8953		$j++;
8954	}
8955	$res = ($nofirstand ? "" : " AND ")."(".$res.")";
8956	//print 'xx'.$res.'yy';
8957	return $res;
8958}
8959
8960/**
8961 * Return string with full Url. The file qualified is the one defined by relative path in $object->last_main_doc
8962 *
8963 * @param   Object	$object				Object
8964 * @return	string						Url string
8965 */
8966function showDirectDownloadLink($object)
8967{
8968	global $conf, $langs;
8969
8970	$out = '';
8971	$url = $object->getLastMainDocLink($object->element);
8972
8973	$out .= img_picto($langs->trans("PublicDownloadLinkDesc"), 'globe').' <span class="opacitymedium">'.$langs->trans("DirectDownloadLink").'</span><br>';
8974	if ($url) {
8975		$out .= '<div class="urllink"><input type="text" id="directdownloadlink" class="quatrevingtpercent" value="'.$url.'"></div>';
8976		$out .= ajax_autoselect("directdownloadlink", 0);
8977	} else {
8978		$out .= '<div class="urllink">'.$langs->trans("FileNotShared").'</div>';
8979	}
8980
8981	return $out;
8982}
8983
8984/**
8985 * Return the filename of file to get the thumbs
8986 *
8987 * @param   string  $file           Original filename (full or relative path)
8988 * @param   string  $extName        Extension to differenciate thumb file name ('', '_small', '_mini')
8989 * @param   string  $extImgTarget   Force image extension for thumbs. Use '' to keep same extension than original image (default).
8990 * @return  string                  New file name (full or relative path, including the thumbs/)
8991 */
8992function getImageFileNameForSize($file, $extName, $extImgTarget = '')
8993{
8994	$dirName = dirname($file);
8995	if ($dirName == '.') {
8996		$dirName = '';
8997	}
8998
8999	$fileName = preg_replace('/(\.gif|\.jpeg|\.jpg|\.png|\.bmp|\.webp)$/i', '', $file); // We remove extension, whatever is its case
9000	$fileName = basename($fileName);
9001
9002	if (empty($extImgTarget)) {
9003		$extImgTarget = (preg_match('/\.jpg$/i', $file) ? '.jpg' : '');
9004	}
9005	if (empty($extImgTarget)) {
9006		$extImgTarget = (preg_match('/\.jpeg$/i', $file) ? '.jpeg' : '');
9007	}
9008	if (empty($extImgTarget)) {
9009		$extImgTarget = (preg_match('/\.gif$/i', $file) ? '.gif' : '');
9010	}
9011	if (empty($extImgTarget)) {
9012		$extImgTarget = (preg_match('/\.png$/i', $file) ? '.png' : '');
9013	}
9014	if (empty($extImgTarget)) {
9015		$extImgTarget = (preg_match('/\.bmp$/i', $file) ? '.bmp' : '');
9016	}
9017	if (empty($extImgTarget)) {
9018		$extImgTarget = (preg_match('/\.webp$/i', $file) ? '.webp' : '');
9019	}
9020
9021	if (!$extImgTarget) {
9022		return $file;
9023	}
9024
9025	$subdir = '';
9026	if ($extName) {
9027		$subdir = 'thumbs/';
9028	}
9029
9030	return ($dirName ? $dirName.'/' : '').$subdir.$fileName.$extName.$extImgTarget; // New filename for thumb
9031}
9032
9033
9034/**
9035 * Return URL we can use for advanced preview links
9036 *
9037 * @param   string    $modulepart     propal, facture, facture_fourn, ...
9038 * @param   string    $relativepath   Relative path of docs.
9039 * @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)
9040 * @param	string	  $param		  More param on http links
9041 * @return  string|array              Output string with href link or array with all components of link
9042 */
9043function getAdvancedPreviewUrl($modulepart, $relativepath, $alldata = 0, $param = '')
9044{
9045	global $conf, $langs;
9046
9047	if (empty($conf->use_javascript_ajax)) {
9048		return '';
9049	}
9050
9051	$isAllowedForPreview = dolIsAllowedForPreview($relativepath);
9052
9053	if ($alldata == 1) {
9054		if ($isAllowedForPreview) {
9055			return array('target'=>'_blank', 'css'=>'documentpreview', 'url'=>DOL_URL_ROOT.'/document.php?modulepart='.$modulepart.'&attachment=0&file='.urlencode($relativepath).($param ? '&'.$param : ''), 'mime'=>dol_mimetype($relativepath));
9056		} else {
9057			return array();
9058		}
9059	}
9060
9061	// old behavior, return a string
9062	if ($isAllowedForPreview) {
9063		return 'javascript:document_preview(\''.dol_escape_js(DOL_URL_ROOT.'/document.php?modulepart='.$modulepart.'&attachment=0&file='.urlencode($relativepath).($param ? '&'.$param : '')).'\', \''.dol_mimetype($relativepath).'\', \''.dol_escape_js($langs->trans('Preview')).'\')';
9064	} else {
9065		return '';
9066	}
9067}
9068
9069
9070/**
9071 * Make content of an input box selected when we click into input field.
9072 *
9073 * @param string	$htmlname	Id of html object ('#idvalue' or '.classvalue')
9074 * @param string	$addlink	Add a 'link to' after
9075 * @return string
9076 */
9077function ajax_autoselect($htmlname, $addlink = '')
9078{
9079	global $langs;
9080	$out = '<script>
9081               jQuery(document).ready(function () {
9082				    jQuery("'.((strpos($htmlname, '.') === 0 ? '' : '#').$htmlname).'").click(function() { jQuery(this).select(); } );
9083				});
9084		    </script>';
9085	if ($addlink) {
9086		$out .= ' <a href="'.$addlink.'" target="_blank">'.$langs->trans("Link").'</a>';
9087	}
9088	return $out;
9089}
9090
9091/**
9092 *	Return if a file is qualified for preview
9093 *
9094 *	@param	string	$file		Filename we looking for information
9095 *	@return int					1 If allowed, 0 otherwise
9096 *  @see    dol_mimetype(), image_format_supported() from images.lib.php
9097 */
9098function dolIsAllowedForPreview($file)
9099{
9100	global $conf;
9101
9102	// Check .noexe extension in filename
9103	if (preg_match('/\.noexe$/i', $file)) {
9104		return 0;
9105	}
9106
9107	// Check mime types
9108	$mime_preview = array('bmp', 'jpeg', 'png', 'gif', 'tiff', 'pdf', 'plain', 'css', 'webp');
9109	if (!empty($conf->global->MAIN_ALLOW_SVG_FILES_AS_IMAGES)) {
9110		$mime_preview[] = 'svg+xml';
9111	}
9112	//$mime_preview[]='vnd.oasis.opendocument.presentation';
9113	//$mime_preview[]='archive';
9114	$num_mime = array_search(dol_mimetype($file, '', 1), $mime_preview);
9115	if ($num_mime !== false) {
9116		return 1;
9117	}
9118
9119	// By default, not allowed for preview
9120	return 0;
9121}
9122
9123
9124/**
9125 *	Return mime type of a file
9126 *
9127 *	@param	string	$file		Filename we looking for MIME type
9128 *  @param  string	$default    Default mime type if extension not found in known list
9129 * 	@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
9130 *	@return string 		    	Return a mime type family (text/xxx, application/xxx, image/xxx, audio, video, archive)
9131 *  @see    dolIsAllowedForPreview(), image_format_supported() from images.lib.php
9132 */
9133function dol_mimetype($file, $default = 'application/octet-stream', $mode = 0)
9134{
9135	$mime = $default;
9136	$imgmime = 'other.png';
9137	$famime = 'file-o';
9138	$srclang = '';
9139
9140	$tmpfile = preg_replace('/\.noexe$/', '', $file);
9141
9142	// Plain text files
9143	if (preg_match('/\.txt$/i', $tmpfile)) {
9144		$mime = 'text/plain';
9145		$imgmime = 'text.png';
9146		$famime = 'file-text-o';
9147	}
9148	if (preg_match('/\.rtx$/i', $tmpfile)) {
9149		$mime = 'text/richtext';
9150		$imgmime = 'text.png';
9151		$famime = 'file-text-o';
9152	}
9153	if (preg_match('/\.csv$/i', $tmpfile)) {
9154		$mime = 'text/csv';
9155		$imgmime = 'text.png';
9156		$famime = 'file-text-o';
9157	}
9158	if (preg_match('/\.tsv$/i', $tmpfile)) {
9159		$mime = 'text/tab-separated-values';
9160		$imgmime = 'text.png';
9161		$famime = 'file-text-o';
9162	}
9163	if (preg_match('/\.(cf|conf|log)$/i', $tmpfile)) {
9164		$mime = 'text/plain';
9165		$imgmime = 'text.png';
9166		$famime = 'file-text-o';
9167	}
9168	if (preg_match('/\.ini$/i', $tmpfile)) {
9169		$mime = 'text/plain';
9170		$imgmime = 'text.png';
9171		$srclang = 'ini';
9172		$famime = 'file-text-o';
9173	}
9174	if (preg_match('/\.md$/i', $tmpfile)) {
9175		$mime = 'text/plain';
9176		$imgmime = 'text.png';
9177		$srclang = 'md';
9178		$famime = 'file-text-o';
9179	}
9180	if (preg_match('/\.css$/i', $tmpfile)) {
9181		$mime = 'text/css';
9182		$imgmime = 'css.png';
9183		$srclang = 'css';
9184		$famime = 'file-text-o';
9185	}
9186	if (preg_match('/\.lang$/i', $tmpfile)) {
9187		$mime = 'text/plain';
9188		$imgmime = 'text.png';
9189		$srclang = 'lang';
9190		$famime = 'file-text-o';
9191	}
9192	// Certificate files
9193	if (preg_match('/\.(crt|cer|key|pub)$/i', $tmpfile)) {
9194		$mime = 'text/plain';
9195		$imgmime = 'text.png';
9196		$famime = 'file-text-o';
9197	}
9198	// XML based (HTML/XML/XAML)
9199	if (preg_match('/\.(html|htm|shtml)$/i', $tmpfile)) {
9200		$mime = 'text/html';
9201		$imgmime = 'html.png';
9202		$srclang = 'html';
9203		$famime = 'file-text-o';
9204	}
9205	if (preg_match('/\.(xml|xhtml)$/i', $tmpfile)) {
9206		$mime = 'text/xml';
9207		$imgmime = 'other.png';
9208		$srclang = 'xml';
9209		$famime = 'file-text-o';
9210	}
9211	if (preg_match('/\.xaml$/i', $tmpfile)) {
9212		$mime = 'text/xml';
9213		$imgmime = 'other.png';
9214		$srclang = 'xaml';
9215		$famime = 'file-text-o';
9216	}
9217	// Languages
9218	if (preg_match('/\.bas$/i', $tmpfile)) {
9219		$mime = 'text/plain';
9220		$imgmime = 'text.png';
9221		$srclang = 'bas';
9222		$famime = 'file-code-o';
9223	}
9224	if (preg_match('/\.(c)$/i', $tmpfile)) {
9225		$mime = 'text/plain';
9226		$imgmime = 'text.png';
9227		$srclang = 'c';
9228		$famime = 'file-code-o';
9229	}
9230	if (preg_match('/\.(cpp)$/i', $tmpfile)) {
9231		$mime = 'text/plain';
9232		$imgmime = 'text.png';
9233		$srclang = 'cpp';
9234		$famime = 'file-code-o';
9235	}
9236	if (preg_match('/\.cs$/i', $tmpfile)) {
9237		$mime = 'text/plain';
9238		$imgmime = 'text.png';
9239		$srclang = 'cs';
9240		$famime = 'file-code-o';
9241	}
9242	if (preg_match('/\.(h)$/i', $tmpfile)) {
9243		$mime = 'text/plain';
9244		$imgmime = 'text.png';
9245		$srclang = 'h';
9246		$famime = 'file-code-o';
9247	}
9248	if (preg_match('/\.(java|jsp)$/i', $tmpfile)) {
9249		$mime = 'text/plain';
9250		$imgmime = 'text.png';
9251		$srclang = 'java';
9252		$famime = 'file-code-o';
9253	}
9254	if (preg_match('/\.php([0-9]{1})?$/i', $tmpfile)) {
9255		$mime = 'text/plain';
9256		$imgmime = 'php.png';
9257		$srclang = 'php';
9258		$famime = 'file-code-o';
9259	}
9260	if (preg_match('/\.phtml$/i', $tmpfile)) {
9261		$mime = 'text/plain';
9262		$imgmime = 'php.png';
9263		$srclang = 'php';
9264		$famime = 'file-code-o';
9265	}
9266	if (preg_match('/\.(pl|pm)$/i', $tmpfile)) {
9267		$mime = 'text/plain';
9268		$imgmime = 'pl.png';
9269		$srclang = 'perl';
9270		$famime = 'file-code-o';
9271	}
9272	if (preg_match('/\.sql$/i', $tmpfile)) {
9273		$mime = 'text/plain';
9274		$imgmime = 'text.png';
9275		$srclang = 'sql';
9276		$famime = 'file-code-o';
9277	}
9278	if (preg_match('/\.js$/i', $tmpfile)) {
9279		$mime = 'text/x-javascript';
9280		$imgmime = 'jscript.png';
9281		$srclang = 'js';
9282		$famime = 'file-code-o';
9283	}
9284	// Open office
9285	if (preg_match('/\.odp$/i', $tmpfile)) {
9286		$mime = 'application/vnd.oasis.opendocument.presentation';
9287		$imgmime = 'ooffice.png';
9288		$famime = 'file-powerpoint-o';
9289	}
9290	if (preg_match('/\.ods$/i', $tmpfile)) {
9291		$mime = 'application/vnd.oasis.opendocument.spreadsheet';
9292		$imgmime = 'ooffice.png';
9293		$famime = 'file-excel-o';
9294	}
9295	if (preg_match('/\.odt$/i', $tmpfile)) {
9296		$mime = 'application/vnd.oasis.opendocument.text';
9297		$imgmime = 'ooffice.png';
9298		$famime = 'file-word-o';
9299	}
9300	// MS Office
9301	if (preg_match('/\.mdb$/i', $tmpfile)) {
9302		$mime = 'application/msaccess';
9303		$imgmime = 'mdb.png';
9304		$famime = 'file-o';
9305	}
9306	if (preg_match('/\.doc(x|m)?$/i', $tmpfile)) {
9307		$mime = 'application/msword';
9308		$imgmime = 'doc.png';
9309		$famime = 'file-word-o';
9310	}
9311	if (preg_match('/\.dot(x|m)?$/i', $tmpfile)) {
9312		$mime = 'application/msword';
9313		$imgmime = 'doc.png';
9314		$famime = 'file-word-o';
9315	}
9316	if (preg_match('/\.xlt(x)?$/i', $tmpfile)) {
9317		$mime = 'application/vnd.ms-excel';
9318		$imgmime = 'xls.png';
9319		$famime = 'file-excel-o';
9320	}
9321	if (preg_match('/\.xla(m)?$/i', $tmpfile)) {
9322		$mime = 'application/vnd.ms-excel';
9323		$imgmime = 'xls.png';
9324		$famime = 'file-excel-o';
9325	}
9326	if (preg_match('/\.xls$/i', $tmpfile)) {
9327		$mime = 'application/vnd.ms-excel';
9328		$imgmime = 'xls.png';
9329		$famime = 'file-excel-o';
9330	}
9331	if (preg_match('/\.xls(b|m|x)$/i', $tmpfile)) {
9332		$mime = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
9333		$imgmime = 'xls.png';
9334		$famime = 'file-excel-o';
9335	}
9336	if (preg_match('/\.pps(m|x)?$/i', $tmpfile)) {
9337		$mime = 'application/vnd.ms-powerpoint';
9338		$imgmime = 'ppt.png';
9339		$famime = 'file-powerpoint-o';
9340	}
9341	if (preg_match('/\.ppt(m|x)?$/i', $tmpfile)) {
9342		$mime = 'application/x-mspowerpoint';
9343		$imgmime = 'ppt.png';
9344		$famime = 'file-powerpoint-o';
9345	}
9346	// Other
9347	if (preg_match('/\.pdf$/i', $tmpfile)) {
9348		$mime = 'application/pdf';
9349		$imgmime = 'pdf.png';
9350		$famime = 'file-pdf-o';
9351	}
9352	// Scripts
9353	if (preg_match('/\.bat$/i', $tmpfile)) {
9354		$mime = 'text/x-bat';
9355		$imgmime = 'script.png';
9356		$srclang = 'dos';
9357		$famime = 'file-code-o';
9358	}
9359	if (preg_match('/\.sh$/i', $tmpfile)) {
9360		$mime = 'text/x-sh';
9361		$imgmime = 'script.png';
9362		$srclang = 'bash';
9363		$famime = 'file-code-o';
9364	}
9365	if (preg_match('/\.ksh$/i', $tmpfile)) {
9366		$mime = 'text/x-ksh';
9367		$imgmime = 'script.png';
9368		$srclang = 'bash';
9369		$famime = 'file-code-o';
9370	}
9371	if (preg_match('/\.bash$/i', $tmpfile)) {
9372		$mime = 'text/x-bash';
9373		$imgmime = 'script.png';
9374		$srclang = 'bash';
9375		$famime = 'file-code-o';
9376	}
9377	// Images
9378	if (preg_match('/\.ico$/i', $tmpfile)) {
9379		$mime = 'image/x-icon';
9380		$imgmime = 'image.png';
9381		$famime = 'file-image-o';
9382	}
9383	if (preg_match('/\.(jpg|jpeg)$/i', $tmpfile)) {
9384		$mime = 'image/jpeg';
9385		$imgmime = 'image.png';
9386		$famime = 'file-image-o';
9387	}
9388	if (preg_match('/\.png$/i', $tmpfile)) {
9389		$mime = 'image/png';
9390		$imgmime = 'image.png';
9391		$famime = 'file-image-o';
9392	}
9393	if (preg_match('/\.gif$/i', $tmpfile)) {
9394		$mime = 'image/gif';
9395		$imgmime = 'image.png';
9396		$famime = 'file-image-o';
9397	}
9398	if (preg_match('/\.bmp$/i', $tmpfile)) {
9399		$mime = 'image/bmp';
9400		$imgmime = 'image.png';
9401		$famime = 'file-image-o';
9402	}
9403	if (preg_match('/\.(tif|tiff)$/i', $tmpfile)) {
9404		$mime = 'image/tiff';
9405		$imgmime = 'image.png';
9406		$famime = 'file-image-o';
9407	}
9408	if (preg_match('/\.svg$/i', $tmpfile)) {
9409		$mime = 'image/svg+xml';
9410		$imgmime = 'image.png';
9411		$famime = 'file-image-o';
9412	}
9413	if (preg_match('/\.webp$/i', $tmpfile)) {
9414		$mime = 'image/webp';
9415		$imgmime = 'image.png';
9416		$famime = 'file-image-o';
9417	}
9418	// Calendar
9419	if (preg_match('/\.vcs$/i', $tmpfile)) {
9420		$mime = 'text/calendar';
9421		$imgmime = 'other.png';
9422		$famime = 'file-text-o';
9423	}
9424	if (preg_match('/\.ics$/i', $tmpfile)) {
9425		$mime = 'text/calendar';
9426		$imgmime = 'other.png';
9427		$famime = 'file-text-o';
9428	}
9429	// Other
9430	if (preg_match('/\.torrent$/i', $tmpfile)) {
9431		$mime = 'application/x-bittorrent';
9432		$imgmime = 'other.png';
9433		$famime = 'file-o';
9434	}
9435	// Audio
9436	if (preg_match('/\.(mp3|ogg|au|wav|wma|mid)$/i', $tmpfile)) {
9437		$mime = 'audio';
9438		$imgmime = 'audio.png';
9439		$famime = 'file-audio-o';
9440	}
9441	// Video
9442	if (preg_match('/\.mp4$/i', $tmpfile)) {
9443		$mime = 'video/mp4';
9444		$imgmime = 'video.png';
9445		$famime = 'file-video-o';
9446	}
9447	if (preg_match('/\.ogv$/i', $tmpfile)) {
9448		$mime = 'video/ogg';
9449		$imgmime = 'video.png';
9450		$famime = 'file-video-o';
9451	}
9452	if (preg_match('/\.webm$/i', $tmpfile)) {
9453		$mime = 'video/webm';
9454		$imgmime = 'video.png';
9455		$famime = 'file-video-o';
9456	}
9457	if (preg_match('/\.avi$/i', $tmpfile)) {
9458		$mime = 'video/x-msvideo';
9459		$imgmime = 'video.png';
9460		$famime = 'file-video-o';
9461	}
9462	if (preg_match('/\.divx$/i', $tmpfile)) {
9463		$mime = 'video/divx';
9464		$imgmime = 'video.png';
9465		$famime = 'file-video-o';
9466	}
9467	if (preg_match('/\.xvid$/i', $tmpfile)) {
9468		$mime = 'video/xvid';
9469		$imgmime = 'video.png';
9470		$famime = 'file-video-o';
9471	}
9472	if (preg_match('/\.(wmv|mpg|mpeg)$/i', $tmpfile)) {
9473		$mime = 'video';
9474		$imgmime = 'video.png';
9475		$famime = 'file-video-o';
9476	}
9477	// Archive
9478	if (preg_match('/\.(zip|rar|gz|tgz|z|cab|bz2|7z|tar|lzh|zst)$/i', $tmpfile)) {
9479		$mime = 'archive';
9480		$imgmime = 'archive.png';
9481		$famime = 'file-archive-o';
9482	}    // application/xxx where zzz is zip, ...
9483	// Exe
9484	if (preg_match('/\.(exe|com)$/i', $tmpfile)) {
9485		$mime = 'application/octet-stream';
9486		$imgmime = 'other.png';
9487		$famime = 'file-o';
9488	}
9489	// Lib
9490	if (preg_match('/\.(dll|lib|o|so|a)$/i', $tmpfile)) {
9491		$mime = 'library';
9492		$imgmime = 'library.png';
9493		$famime = 'file-o';
9494	}
9495	// Err
9496	if (preg_match('/\.err$/i', $tmpfile)) {
9497		$mime = 'error';
9498		$imgmime = 'error.png';
9499		$famime = 'file-text-o';
9500	}
9501
9502	// Return string
9503	if ($mode == 1) {
9504		$tmp = explode('/', $mime);
9505		return (!empty($tmp[1]) ? $tmp[1] : $tmp[0]);
9506	}
9507	if ($mode == 2) {
9508		return $imgmime;
9509	}
9510	if ($mode == 3) {
9511		return $srclang;
9512	}
9513	if ($mode == 4) {
9514		return $famime;
9515	}
9516	return $mime;
9517}
9518
9519/**
9520 * Return value from dictionary
9521 *
9522 * @param string	$tablename		name of dictionary
9523 * @param string	$field			the value to return
9524 * @param int		$id				id of line
9525 * @param bool		$checkentity	add filter on entity
9526 * @param string	$rowidfield		name of the column rowid
9527 * @return string
9528 */
9529function getDictvalue($tablename, $field, $id, $checkentity = false, $rowidfield = 'rowid')
9530{
9531	global $dictvalues, $db, $langs;
9532
9533	if (!isset($dictvalues[$tablename])) {
9534		$dictvalues[$tablename] = array();
9535
9536		$sql = 'SELECT * FROM '.$tablename.' WHERE 1 = 1'; // Here select * is allowed as it is generic code and we don't have list of fields
9537		if ($checkentity) {
9538			$sql .= ' AND entity IN (0,'.getEntity($tablename).')';
9539		}
9540
9541		$resql = $db->query($sql);
9542		if ($resql) {
9543			while ($obj = $db->fetch_object($resql)) {
9544				$dictvalues[$tablename][$obj->{$rowidfield}] = $obj;
9545			}
9546		} else {
9547			dol_print_error($db);
9548		}
9549	}
9550
9551	if (!empty($dictvalues[$tablename][$id])) {
9552		return $dictvalues[$tablename][$id]->{$field}; // Found
9553	} else // Not found
9554	{
9555		if ($id > 0) {
9556			return $id;
9557		}
9558		return '';
9559	}
9560}
9561
9562/**
9563 *	Return true if the color is light
9564 *
9565 *  @param	string	$stringcolor		String with hex (FFFFFF) or comma RGB ('255,255,255')
9566 *  @return	int							-1 : Error with argument passed |0 : color is dark | 1 : color is light
9567 */
9568function colorIsLight($stringcolor)
9569{
9570	$stringcolor = str_replace('#', '', $stringcolor);
9571	$res = -1;
9572	if (!empty($stringcolor)) {
9573		$res = 0;
9574		$tmp = explode(',', $stringcolor);
9575		if (count($tmp) > 1) {   // This is a comma RGB ('255','255','255')
9576			$r = $tmp[0];
9577			$g = $tmp[1];
9578			$b = $tmp[2];
9579		} else {
9580			$hexr = $stringcolor[0].$stringcolor[1];
9581			$hexg = $stringcolor[2].$stringcolor[3];
9582			$hexb = $stringcolor[4].$stringcolor[5];
9583			$r = hexdec($hexr);
9584			$g = hexdec($hexg);
9585			$b = hexdec($hexb);
9586		}
9587		$bright = (max($r, $g, $b) + min($r, $g, $b)) / 510.0; // HSL algorithm
9588		if ($bright > 0.6) {
9589			$res = 1;
9590		}
9591	}
9592	return $res;
9593}
9594
9595/**
9596 * Function to test if an entry is enabled or not
9597 *
9598 * @param	string		$type_user					0=We test for internal user, 1=We test for external user
9599 * @param	array		$menuentry					Array for feature entry to test
9600 * @param	array		$listofmodulesforexternal	Array with list of modules allowed to external users
9601 * @return	int										0=Hide, 1=Show, 2=Show gray
9602 */
9603function isVisibleToUserType($type_user, &$menuentry, &$listofmodulesforexternal)
9604{
9605	global $conf;
9606
9607	//print 'type_user='.$type_user.' module='.$menuentry['module'].' enabled='.$menuentry['enabled'].' perms='.$menuentry['perms'];
9608	//print 'ok='.in_array($menuentry['module'], $listofmodulesforexternal);
9609	if (empty($menuentry['enabled'])) {
9610		return 0; // Entry disabled by condition
9611	}
9612	if ($type_user && $menuentry['module']) {
9613		$tmploops = explode('|', $menuentry['module']);
9614		$found = 0;
9615		foreach ($tmploops as $tmploop) {
9616			if (in_array($tmploop, $listofmodulesforexternal)) {
9617				$found++;
9618				break;
9619			}
9620		}
9621		if (!$found) {
9622			return 0; // Entry is for menus all excluded to external users
9623		}
9624	}
9625	if (!$menuentry['perms'] && $type_user) {
9626		return 0; // No permissions and user is external
9627	}
9628	if (!$menuentry['perms'] && !empty($conf->global->MAIN_MENU_HIDE_UNAUTHORIZED)) {
9629		return 0; // No permissions and option to hide when not allowed, even for internal user, is on
9630	}
9631	if (!$menuentry['perms']) {
9632		return 2; // No permissions and user is external
9633	}
9634	return 1;
9635}
9636
9637/**
9638 * Round to next multiple.
9639 *
9640 * @param 	double		$n		Number to round up
9641 * @param 	integer		$x		Multiple. For example 60 to round up to nearest exact minute for a date with seconds.
9642 * @return 	integer				Value rounded.
9643 */
9644function roundUpToNextMultiple($n, $x = 5)
9645{
9646	return (ceil($n) % $x === 0) ? ceil($n) : round(($n + $x / 2) / $x) * $x;
9647}
9648
9649/**
9650 * Function dolGetBadge
9651 *
9652 * @param   string  $label      label of badge no html : use in alt attribute for accessibility
9653 * @param   string  $html       optional : label of badge with html
9654 * @param   string  $type       type of badge : Primary Secondary Success Danger Warning Info Light Dark status0 status1 status2 status3 status4 status5 status6 status7 status8 status9
9655 * @param   string  $mode       default '' , 'pill', 'dot'
9656 * @param   string  $url        the url for link
9657 * @param   array   $params     various params for future : recommended rather than adding more fuction arguments. array('attr'=>array('title'=>'abc'))
9658 * @return  string              Html badge
9659 */
9660function dolGetBadge($label, $html = '', $type = 'primary', $mode = '', $url = '', $params = array())
9661{
9662	$attr = array(
9663		'class'=>'badge '.(!empty($mode) ? ' badge-'.$mode : '').(!empty($type) ? ' badge-'.$type : '').(empty($params['css']) ? '' : ' '.$params['css'])
9664	);
9665
9666	if (empty($html)) {
9667		$html = $label;
9668	}
9669
9670	if (!empty($url)) {
9671		$attr['href'] = $url;
9672	}
9673
9674	if ($mode === 'dot') {
9675		$attr['class'] .= ' classfortooltip';
9676		$attr['title'] = $html;
9677		$attr['aria-label'] = $label;
9678		$html = '';
9679	}
9680
9681	// Override attr
9682	if (!empty($params['attr']) && is_array($params['attr'])) {
9683		foreach ($params['attr'] as $key => $value) {
9684			if ($key == 'class') {
9685				$attr['class'] .= ' '.$value;
9686			} elseif ($key == 'classOverride') {
9687				$attr['class'] = $value;
9688			} else {
9689				$attr[$key] = $value;
9690			}
9691		}
9692	}
9693
9694	// TODO: add hook
9695
9696	// escape all attribute
9697	$attr = array_map('dol_escape_htmltag', $attr);
9698
9699	$TCompiledAttr = array();
9700	foreach ($attr as $key => $value) {
9701		$TCompiledAttr[] = $key.'="'.$value.'"';
9702	}
9703
9704	$compiledAttributes = !empty($TCompiledAttr) ?implode(' ', $TCompiledAttr) : '';
9705
9706	$tag = !empty($url) ? 'a' : 'span';
9707
9708	return '<'.$tag.' '.$compiledAttributes.'>'.$html.'</'.$tag.'>';
9709}
9710
9711
9712/**
9713 * Output the badge of a status.
9714 *
9715 * @param   string  $statusLabel       Label of badge no html : use in alt attribute for accessibility
9716 * @param   string  $statusLabelShort  Short label of badge no html
9717 * @param   string  $html              Optional : label of badge with html
9718 * @param   string  $statusType        status0 status1 status2 status3 status4 status5 status6 status7 status8 status9 : image name or badge name
9719 * @param   int	    $displayMode       0=Long label, 1=Short label, 2=Picto + Short label, 3=Picto, 4=Picto + Long label, 5=Short label + Picto, 6=Long label + Picto
9720 * @param   string  $url               The url for link
9721 * @param   array   $params            Various params. Example: array('tooltip'=>'no|...', 'badgeParams'=>...)
9722 * @return  string                     Html status string
9723 */
9724function dolGetStatus($statusLabel = '', $statusLabelShort = '', $html = '', $statusType = 'status0', $displayMode = 0, $url = '', $params = array())
9725{
9726	global $conf;
9727
9728	$return = '';
9729	$dolGetBadgeParams = array();
9730
9731	if (!empty($params['badgeParams'])) {
9732		$dolGetBadgeParams = $params['badgeParams'];
9733	}
9734
9735	// TODO : add a hook
9736	if ($displayMode == 0) {
9737		$return = !empty($html) ? $html : (empty($conf->dol_optimize_smallscreen) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort));
9738	} elseif ($displayMode == 1) {
9739		$return = !empty($html) ? $html : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort);
9740	} elseif (!empty($conf->global->MAIN_STATUS_USES_IMAGES)) {
9741		// Use status with images (for backward compatibility)
9742		$return = '';
9743		$htmlLabel      = (in_array($displayMode, array(1, 2, 5)) ? '<span class="hideonsmartphone">' : '').(!empty($html) ? $html : $statusLabel).(in_array($displayMode, array(1, 2, 5)) ? '</span>' : '');
9744		$htmlLabelShort = (in_array($displayMode, array(1, 2, 5)) ? '<span class="hideonsmartphone">' : '').(!empty($html) ? $html : (!empty($statusLabelShort) ? $statusLabelShort : $statusLabel)).(in_array($displayMode, array(1, 2, 5)) ? '</span>' : '');
9745
9746		// For small screen, we always use the short label instead of long label.
9747		if (!empty($conf->dol_optimize_smallscreen)) {
9748			if ($displayMode == 0) {
9749				$displayMode = 1;
9750			} elseif ($displayMode == 4) {
9751				$displayMode = 2;
9752			} elseif ($displayMode == 6) {
9753				$displayMode = 5;
9754			}
9755		}
9756
9757		// For backward compatibility. Image's filename are still in French, so we use this array to convert
9758		$statusImg = array(
9759			'status0' => 'statut0',
9760			'status1' => 'statut1',
9761			'status2' => 'statut2',
9762			'status3' => 'statut3',
9763			'status4' => 'statut4',
9764			'status5' => 'statut5',
9765			'status6' => 'statut6',
9766			'status7' => 'statut7',
9767			'status8' => 'statut8',
9768			'status9' => 'statut9'
9769		);
9770
9771		if (!empty($statusImg[$statusType])) {
9772			$htmlImg = img_picto($statusLabel, $statusImg[$statusType]);
9773		} else {
9774			$htmlImg = img_picto($statusLabel, $statusType);
9775		}
9776
9777		if ($displayMode === 2) {
9778			$return = $htmlImg.' '.$htmlLabelShort;
9779		} elseif ($displayMode === 3) {
9780			$return = $htmlImg;
9781		} elseif ($displayMode === 4) {
9782			$return = $htmlImg.' '.$htmlLabel;
9783		} elseif ($displayMode === 5) {
9784			$return = $htmlLabelShort.' '.$htmlImg;
9785		} else { // $displayMode >= 6
9786			$return = $htmlLabel.' '.$htmlImg;
9787		}
9788	} elseif (empty($conf->global->MAIN_STATUS_USES_IMAGES) && !empty($displayMode)) {
9789		// Use new badge
9790		$statusLabelShort = (empty($statusLabelShort) ? $statusLabel : $statusLabelShort);
9791
9792		$dolGetBadgeParams['attr']['class'] = 'badge-status';
9793		$dolGetBadgeParams['attr']['title'] = empty($params['tooltip']) ? $statusLabel : ($params['tooltip'] != 'no' ? $params['tooltip'] : '');
9794
9795		if ($displayMode == 3) {
9796			$return = dolGetBadge((empty($conf->dol_optimize_smallscreen) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort)), '', $statusType, 'dot', $url, $dolGetBadgeParams);
9797		} elseif ($displayMode === 5) {
9798			$return = dolGetBadge($statusLabelShort, $html, $statusType, '', $url, $dolGetBadgeParams);
9799		} else {
9800			$return = dolGetBadge((empty($conf->dol_optimize_smallscreen) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort)), $html, $statusType, '', $url, $dolGetBadgeParams);
9801		}
9802	}
9803
9804	return $return;
9805}
9806
9807
9808/**
9809 * Function dolGetButtonAction
9810 *
9811 * @param string    $label      label of button without HTML : use in alt attribute for accessibility $html is not empty
9812 * @param string    $html       optional : content with html
9813 * @param string    $actionType default, delete, danger
9814 * @param string    $url        the url for link
9815 * @param string    $id         attribute id of button
9816 * @param int       $userRight  user action right
9817 * @param array     $params     various params for future : recommended rather than adding more function arguments
9818 * @return string               html button
9819 */
9820function dolGetButtonAction($label, $html = '', $actionType = 'default', $url = '', $id = '', $userRight = 1, $params = array())
9821{
9822	$class = 'butAction';
9823	if ($actionType == 'danger' || $actionType == 'delete') {
9824		$class = 'butActionDelete';
9825		if (strpos($url, 'token=') === false) $url .= '&token='.newToken();
9826	}
9827
9828	$attr = array(
9829		'class' => $class,
9830		'href' => empty($url) ? '' : $url,
9831		'title' => $label
9832	);
9833
9834	if (empty($html)) {
9835		$html = $label;
9836	} else {
9837		$attr['aria-label'] = $label;
9838	}
9839
9840	if (empty($userRight)) {
9841		$attr['class'] = 'butActionRefused';
9842		$attr['href'] = '';
9843	}
9844
9845	if (!empty($id)) {
9846		$attr['id'] = $id;
9847	}
9848
9849	// Override attr
9850	if (!empty($params['attr']) && is_array($params['attr'])) {
9851		foreach ($params['attr'] as $key => $value) {
9852			if ($key == 'class') {
9853				$attr['class'] .= ' '.$value;
9854			} elseif ($key == 'classOverride') {
9855				$attr['class'] = $value;
9856			} else {
9857				$attr[$key] = $value;
9858			}
9859		}
9860	}
9861
9862	if (isset($attr['href']) && empty($attr['href'])) {
9863		unset($attr['href']);
9864	}
9865
9866	// TODO : add a hook
9867
9868	// escape all attribute
9869	$attr = array_map('dol_escape_htmltag', $attr);
9870
9871	$TCompiledAttr = array();
9872	foreach ($attr as $key => $value) {
9873		$TCompiledAttr[] = $key.'="'.$value.'"';
9874	}
9875
9876	$compiledAttributes = !empty($TCompiledAttr) ?implode(' ', $TCompiledAttr) : '';
9877
9878	$tag = !empty($attr['href']) ? 'a' : 'span';
9879
9880	return '<'.$tag.' '.$compiledAttributes.'>'.$html.'</'.$tag.'>';
9881}
9882
9883/**
9884 * Add space between dolGetButtonTitle
9885 *
9886 * @param  string $moreClass 	more css class label
9887 * @return string 				html of title separator
9888 */
9889function dolGetButtonTitleSeparator($moreClass = "")
9890{
9891	return '<span class="button-title-separator '.$moreClass.'" ></span>';
9892}
9893
9894/**
9895 * Function dolGetButtonTitle : this kind of buttons are used in title in list
9896 *
9897 * @param string    $label      label of button
9898 * @param string    $helpText   optional : content for help tooltip
9899 * @param string    $iconClass  class for icon element (Example: 'fa fa-file')
9900 * @param string    $url        the url for link
9901 * @param string    $id         attribute id of button
9902 * @param int       $status     0 no user rights, 1 active, 2 current action or selected, -1 Feature Disabled, -2 disable Other reason use helpText as tooltip
9903 * @param array     $params     various params for future : recommended rather than adding more function arguments
9904 * @return string               html button
9905 */
9906function dolGetButtonTitle($label, $helpText = '', $iconClass = 'fa fa-file', $url = '', $id = '', $status = 1, $params = array())
9907{
9908	global $langs, $conf, $user;
9909
9910	// Actually this conf is used in css too for external module compatibility and smooth transition to this function
9911	if (!empty($conf->global->MAIN_BUTTON_HIDE_UNAUTHORIZED) && (!$user->admin) && $status <= 0) {
9912		return '';
9913	}
9914
9915	$class = 'btnTitle';
9916	if (in_array($iconClass, array('fa fa-plus-circle', 'fa fa-comment-dots'))) {
9917		$class .= ' btnTitlePlus';
9918	}
9919	$useclassfortooltip = 1;
9920
9921	if (!empty($params['morecss'])) {
9922		$class .= ' '.$params['morecss'];
9923	}
9924
9925	$attr = array(
9926		'class' => $class,
9927		'href' => empty($url) ? '' : $url
9928	);
9929
9930	if (!empty($helpText)) {
9931		$attr['title'] = dol_escape_htmltag($helpText);
9932	} elseif (empty($attr['title']) && $label) {
9933		$attr['title'] = $label;
9934		$useclassfortooltip = 0;
9935	}
9936
9937	if ($status == 2) {
9938		$attr['class'] .= ' btnTitleSelected';
9939	} elseif ($status <= 0) {
9940		$attr['class'] .= ' refused';
9941
9942		$attr['href'] = '';
9943
9944		if ($status == -1) { // disable
9945			$attr['title'] = dol_escape_htmltag($langs->transnoentitiesnoconv("FeatureDisabled"));
9946		} elseif ($status == 0) { // Not enough permissions
9947			$attr['title'] = dol_escape_htmltag($langs->transnoentitiesnoconv("NotEnoughPermissions"));
9948		}
9949	}
9950
9951	if (!empty($attr['title']) && $useclassfortooltip) {
9952		$attr['class'] .= ' classfortooltip';
9953	}
9954
9955	if (!empty($id)) {
9956		$attr['id'] = $id;
9957	}
9958
9959	// Override attr
9960	if (!empty($params['attr']) && is_array($params['attr'])) {
9961		foreach ($params['attr'] as $key => $value) {
9962			if ($key == 'class') {
9963				$attr['class'] .= ' '.$value;
9964			} elseif ($key == 'classOverride') {
9965				$attr['class'] = $value;
9966			} else {
9967				$attr[$key] = $value;
9968			}
9969		}
9970	}
9971
9972	if (isset($attr['href']) && empty($attr['href'])) {
9973		unset($attr['href']);
9974	}
9975
9976	// TODO : add a hook
9977
9978	// escape all attribute
9979	$attr = array_map('dol_escape_htmltag', $attr);
9980
9981	$TCompiledAttr = array();
9982	foreach ($attr as $key => $value) {
9983		$TCompiledAttr[] = $key.'="'.$value.'"';
9984	}
9985
9986	$compiledAttributes = (empty($TCompiledAttr) ? '' : implode(' ', $TCompiledAttr));
9987
9988	$tag = (empty($attr['href']) ? 'span' : 'a');
9989
9990	$button = '<'.$tag.' '.$compiledAttributes.'>';
9991	$button .= '<span class="'.$iconClass.' valignmiddle btnTitle-icon"></span>';
9992	if (!empty($params['forcenohideoftext'])) {
9993		$button .= '<span class="valignmiddle text-plus-circle btnTitle-label'.(empty($params['forcenohideoftext']) ? ' hideonsmartphone' : '').'">'.$label.'</span>';
9994	}
9995	$button .= '</'.$tag.'>';
9996
9997	return $button;
9998}
9999
10000/**
10001 * Get an array with properties of an element.
10002 * Called by fetchObjectByElement.
10003 *
10004 * @param   string 	$element_type 	Element type (Value of $object->element). Example: 'action', 'facture', 'project_task' or 'object@mymodule'...
10005 * @return  array					(module, classpath, element, subelement, classfile, classname)
10006 */
10007function getElementProperties($element_type)
10008{
10009	$regs = array();
10010
10011	$classfile = $classname = $classpath = '';
10012
10013	// Parse element/subelement (ex: project_task)
10014	$module = $element_type;
10015	$element = $element_type;
10016	$subelement = $element_type;
10017
10018	// If we ask an resource form external module (instead of default path)
10019	if (preg_match('/^([^@]+)@([^@]+)$/i', $element_type, $regs)) {
10020		$element = $subelement = $regs[1];
10021		$module = $regs[2];
10022	}
10023
10024	//print '<br>1. element : '.$element.' - module : '.$module .'<br>';
10025	if (preg_match('/^([^_]+)_([^_]+)/i', $element, $regs)) {
10026		$module = $element = $regs[1];
10027		$subelement = $regs[2];
10028	}
10029
10030	// For compat
10031	if ($element_type == "action") {
10032		$classpath = 'comm/action/class';
10033		$subelement = 'Actioncomm';
10034		$module = 'agenda';
10035	}
10036
10037	// To work with non standard path
10038	if ($element_type == 'facture' || $element_type == 'invoice') {
10039		$classpath = 'compta/facture/class';
10040		$module = 'facture';
10041		$subelement = 'facture';
10042	}
10043	if ($element_type == 'commande' || $element_type == 'order') {
10044		$classpath = 'commande/class';
10045		$module = 'commande';
10046		$subelement = 'commande';
10047	}
10048	if ($element_type == 'propal') {
10049		$classpath = 'comm/propal/class';
10050	}
10051	if ($element_type == 'supplier_proposal') {
10052		$classpath = 'supplier_proposal/class';
10053	}
10054	if ($element_type == 'shipping') {
10055		$classpath = 'expedition/class';
10056		$subelement = 'expedition';
10057		$module = 'expedition_bon';
10058	}
10059	if ($element_type == 'delivery') {
10060		$classpath = 'delivery/class';
10061		$subelement = 'delivery';
10062		$module = 'delivery_note';
10063	}
10064	if ($element_type == 'contract') {
10065		$classpath = 'contrat/class';
10066		$module = 'contrat';
10067		$subelement = 'contrat';
10068	}
10069	if ($element_type == 'member') {
10070		$classpath = 'adherents/class';
10071		$module = 'adherent';
10072		$subelement = 'adherent';
10073	}
10074	if ($element_type == 'cabinetmed_cons') {
10075		$classpath = 'cabinetmed/class';
10076		$module = 'cabinetmed';
10077		$subelement = 'cabinetmedcons';
10078	}
10079	if ($element_type == 'fichinter') {
10080		$classpath = 'fichinter/class';
10081		$module = 'ficheinter';
10082		$subelement = 'fichinter';
10083	}
10084	if ($element_type == 'dolresource' || $element_type == 'resource') {
10085		$classpath = 'resource/class';
10086		$module = 'resource';
10087		$subelement = 'dolresource';
10088	}
10089	if ($element_type == 'propaldet') {
10090		$classpath = 'comm/propal/class';
10091		$module = 'propal';
10092		$subelement = 'propaleligne';
10093	}
10094	if ($element_type == 'order_supplier') {
10095		$classpath = 'fourn/class';
10096		$module = 'fournisseur';
10097		$subelement = 'commandefournisseur';
10098		$classfile = 'fournisseur.commande';
10099	}
10100	if ($element_type == 'invoice_supplier') {
10101		$classpath = 'fourn/class';
10102		$module = 'fournisseur';
10103		$subelement = 'facturefournisseur';
10104		$classfile = 'fournisseur.facture';
10105	}
10106	if ($element_type == "service") {
10107		$classpath = 'product/class';
10108		$subelement = 'product';
10109	}
10110
10111	if (empty($classfile)) {
10112		$classfile = strtolower($subelement);
10113	}
10114	if (empty($classname)) {
10115		$classname = ucfirst($subelement);
10116	}
10117	if (empty($classpath)) {
10118		$classpath = $module.'/class';
10119	}
10120
10121	$element_properties = array(
10122		'module' => $module,
10123		'classpath' => $classpath,
10124		'element' => $element,
10125		'subelement' => $subelement,
10126		'classfile' => $classfile,
10127		'classname' => $classname
10128	);
10129	return $element_properties;
10130}
10131
10132/**
10133 * Fetch an object from its id and element_type
10134 * Inclusion of classes is automatic
10135 *
10136 * @param	int     	$element_id 	Element id
10137 * @param	string  	$element_type 	Element type
10138 * @param	string     	$element_ref 	Element ref (Use this or element_id but not both)
10139 * @return 	int|object 					object || 0 || -1 if error
10140 */
10141function fetchObjectByElement($element_id, $element_type, $element_ref = '')
10142{
10143	global $conf, $db;
10144
10145	$element_prop = getElementProperties($element_type);
10146	if (is_array($element_prop) && $conf->{$element_prop['module']}->enabled) {
10147		dol_include_once('/'.$element_prop['classpath'].'/'.$element_prop['classfile'].'.class.php');
10148
10149		$objecttmp = new $element_prop['classname']($db);
10150		$ret = $objecttmp->fetch($element_id, $element_ref);
10151		if ($ret >= 0) {
10152			return $objecttmp;
10153		}
10154	}
10155	return 0;
10156}
10157
10158/**
10159 * Return if a file can contains executable content
10160 *
10161 * @param   string  $filename       File name to test
10162 * @return  boolean                 True if yes, False if no
10163 */
10164function isAFileWithExecutableContent($filename)
10165{
10166	if (preg_match('/\.(htm|html|js|phar|php|php\d+|phtml|pht|pl|py|cgi|ksh|sh|shtml|bash|bat|cmd|wpk|exe|dmg)$/i', $filename)) {
10167		return true;
10168	}
10169
10170	return false;
10171}
10172
10173/**
10174 * Return the value of token currently saved into session with name 'newtoken'.
10175 * This token must be send by any POST as it will be used by next page for comparison with value in session.
10176 *
10177 * @return  string
10178 */
10179function newToken()
10180{
10181	return $_SESSION['newtoken'];
10182}
10183
10184/**
10185 * Return the value of token currently saved into session with name 'token'.
10186 *
10187 * @return  string
10188 */
10189function currentToken()
10190{
10191	return $_SESSION['token'];
10192}
10193
10194/**
10195 * Start a table with headers and a optinal clickable number (don't forget to use "finishSimpleTable()" after the last table row)
10196 *
10197 * @param string	$header		The first left header of the table (automatic translated)
10198 * @param string	$link		(optional) The link to a internal dolibarr page, when click on the number (without the first "/")
10199 * @param string	$arguments	(optional) Additional arguments for the link (e.g. "search_status=0")
10200 * @param integer	$emptyRows	(optional) The count of empty rows after the first header
10201 * @param integer	$number		(optional) The number that is shown right after the first header, when not set the link is shown on the right side of the header as "FullList"
10202 * @return void
10203 *
10204 * @see finishSimpleTable()
10205 */
10206function startSimpleTable($header, $link = "", $arguments = "", $emptyRows = 0, $number = -1)
10207{
10208	global $langs;
10209
10210	print '<div class="div-table-responsive-no-min">';
10211	print '<table class="noborder centpercent">';
10212	print '<tr class="liste_titre">';
10213
10214	print $emptyRows < 1 ? '<th>' : '<th colspan="'.($emptyRows + 1).'">';
10215
10216	print $langs->trans($header);
10217
10218	// extra space between the first header and the number
10219	if ($number > -1) {
10220		print ' ';
10221	}
10222
10223	if (!empty($link)) {
10224		if (!empty($arguments)) {
10225			print '<a href="'.DOL_URL_ROOT.'/'.$link.'?'.$arguments.'">';
10226		} else {
10227			print '<a href="'.DOL_URL_ROOT.'/'.$link.'">';
10228		}
10229	}
10230
10231	if ($number > -1) {
10232		print '<span class="badge">'.$number.'</span>';
10233	}
10234
10235	if (!empty($link)) {
10236		print '</a>';
10237	}
10238
10239	print '</th>';
10240
10241	if ($number < 0 && !empty($link)) {
10242		print '<th class="right">';
10243
10244		if (!empty($arguments)) {
10245			print '<a class="commonlink" href="'.DOL_URL_ROOT.'/'.$link.'?'.$arguments.'">';
10246		} else {
10247			print '<a class="commonlink" href="'.DOL_URL_ROOT.'/'.$link.'">';
10248		}
10249
10250		print $langs->trans("FullList");
10251		print '</a>';
10252		print '</th>';
10253	}
10254
10255	print '</tr>';
10256}
10257
10258/**
10259 * Add the correct HTML close tags for "startSimpleTable(...)" (use after the last table line)
10260 *
10261 * @param 	bool 	$addLineBreak	(optional) Add a extra line break after the complete table (\<br\>)
10262 * @return 	void
10263 *
10264 * @see startSimpleTable()
10265 */
10266function finishSimpleTable($addLineBreak = false)
10267{
10268	print '</table>';
10269	print '</div>';
10270
10271	if ($addLineBreak) {
10272		print '<br>';
10273	}
10274}
10275
10276/**
10277 * Add a summary line to the current open table ("None", "XMoreLines" or "Total xxx")
10278 *
10279 * @param integer	$tableColumnCount		The complete count columns of the table
10280 * @param integer	$num					The count of the rows of the table, when it is zero (0) the "$noneWord" is shown instead
10281 * @param integer	$nbofloop				(optional)	The maximum count of rows thaht the table show (when it is zero (0) no summary line will show, expect "$noneWord" when $num === 0)
10282 * @param integer	$total					(optional)	The total value thaht is shown after when the table has minimum of one entire
10283 * @param string	$noneWord				(optional)	The word that is shown when the table has no entires ($num === 0)
10284 * @param boolean	$extraRightColumn		(optional)	Add a addtional column after the summary word and total number
10285 * @return void
10286 */
10287function addSummaryTableLine($tableColumnCount, $num, $nbofloop = 0, $total = 0, $noneWord = "None", $extraRightColumn = false)
10288{
10289	global $langs;
10290
10291	if ($num === 0) {
10292		print '<tr class="oddeven">';
10293		print '<td colspan="'.$tableColumnCount.'" class="opacitymedium">'.$langs->trans($noneWord).'</td>';
10294		print '</tr>';
10295		return;
10296	}
10297
10298	if ($nbofloop === 0) {
10299		// don't show a summary line
10300		return;
10301	}
10302
10303	if ($num === 0) {
10304		$colspan = $tableColumnCount;
10305	} elseif ($num > $nbofloop) {
10306		$colspan = $tableColumnCount;
10307	} else {
10308		$colspan = $tableColumnCount - 1;
10309	}
10310
10311	if ($extraRightColumn) {
10312		$colspan--;
10313	}
10314
10315	print '<tr class="liste_total">';
10316
10317	if ($nbofloop > 0 && $num > $nbofloop) {
10318		print '<td colspan="'.$colspan.'" class="right">'.$langs->trans("XMoreLines", ($num - $nbofloop)).'</td>';
10319	} else {
10320		print '<td colspan="'.$colspan.'" class="right"> '.$langs->trans("Total").'</td>';
10321		print '<td class="right" width="100">'.price($total).'</td>';
10322	}
10323
10324	if ($extraRightColumn) {
10325		print '<td></td>';
10326	}
10327
10328	print '</tr>';
10329}
10330
10331/**
10332 *  Return a file on output using a low memory. It can return very large files with no need of memory.
10333 *  WARNING: This close output buffers.
10334 *
10335 *  @param	string	$fullpath_original_file_osencoded		Full path of file to return.
10336 *  @param	int		$method									-1 automatic, 0=readfile, 1=fread, 2=stream_copy_to_stream
10337 *  @return void
10338 */
10339function readfileLowMemory($fullpath_original_file_osencoded, $method = -1)
10340{
10341	global $conf;
10342
10343	if ($method == -1) {
10344		$method = 0;
10345		if (!empty($conf->global->MAIN_FORCE_READFILE_WITH_FREAD)) {
10346			$method = 1;
10347		}
10348		if (!empty($conf->global->MAIN_FORCE_READFILE_WITH_STREAM_COPY)) {
10349			$method = 2;
10350		}
10351	}
10352
10353	// Be sure we don't have output buffering enabled to have readfile working correctly
10354	while (ob_get_level()) {
10355		ob_end_flush();
10356	}
10357
10358	// Solution 0
10359	if ($method == 0) {
10360		readfile($fullpath_original_file_osencoded);
10361	} elseif ($method == 1) {
10362		// Solution 1
10363		$handle = fopen($fullpath_original_file_osencoded, "rb");
10364		while (!feof($handle)) {
10365			print fread($handle, 8192);
10366		}
10367		fclose($handle);
10368	} elseif ($method == 2) {
10369		// Solution 2
10370		$handle1 = fopen($fullpath_original_file_osencoded, "rb");
10371		$handle2 = fopen("php://output", "wb");
10372		stream_copy_to_stream($handle1, $handle2);
10373		fclose($handle1);
10374		fclose($handle2);
10375	}
10376}
10377
10378/**
10379 * Create a button to copy $valuetocopy in the clipboard.
10380 * Code that handle the click is inside lib_foot.jsp.php
10381 *
10382 * @param 	string 	$valuetocopy 		The value to print
10383 * @param	int		$showonlyonhover	Show the copy-paste button only on hover
10384 * @param	string	$texttoshow			Replace the value to show with this text
10385 * @return 	string 						The string to print for the button
10386 */
10387function showValueWithClipboardCPButton($valuetocopy, $showonlyonhover = 1, $texttoshow = '')
10388{
10389	/*
10390	global $conf;
10391
10392	if (!empty($conf->dol_no_mouse_hover)) {
10393		$showonlyonhover = 0;
10394	}*/
10395
10396	if ($texttoshow) {
10397		$result = '<span class="clipboardCP'.($showonlyonhover ? ' clipboardCPShowOnHover' : '').'"><span class="clipboardCPValue hidewithsize">'.$valuetocopy.'</span><span class="clipboardCPValueToPrint">'.$texttoshow.'</span><span class="clipboardCPButton far fa-clipboard opacitymedium paddingleft paddingright"></span><span class="clipboardCPText opacitymedium"></span></span>';
10398	} else {
10399		$result = '<span class="clipboardCP'.($showonlyonhover ? ' clipboardCPShowOnHover' : '').'"><span class="clipboardCPValue">'.$valuetocopy.'</span><span class="clipboardCPButton far fa-clipboard opacitymedium paddingleft paddingright"></span><span class="clipboardCPText opacitymedium"></span></span>';
10400	}
10401
10402	return $result;
10403}
10404
10405
10406/**
10407 * Decode an encode string. The string can be encoded in json format (recommended) or with serialize (avoid this)
10408 *
10409 * @param 	string	$stringtodecode		String to decode (json or serialize coded)
10410 * @return	mixed						The decoded object.
10411 */
10412function jsonOrUnserialize($stringtodecode)
10413{
10414	$result = json_decode($stringtodecode);
10415	if ($result === null) {
10416		$result = unserialize($stringtodecode);
10417	}
10418
10419	return $result;
10420}
10421