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-2017	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-2020  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 *
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 3 of the License, or
23 * (at your option) any later version.
24 *
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
28 * GNU General Public License for more details.
29 *
30 * You should have received a copy of the GNU General Public License
31 * along with this program. If not, see <https://www.gnu.org/licenses/>.
32 * or see https://www.gnu.org/
33 */
34
35/**
36 *	\file			htdocs/core/lib/functions.lib.php
37 *	\brief			A set of functions for Dolibarr
38 *					This file contains all frequently used functions.
39 */
40
41include_once DOL_DOCUMENT_ROOT.'/core/lib/json.lib.php';
42
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':			$element = 'contract'; break; // "/contrat/class/contrat.class.php"
112		case 'order_supplier':	$element = 'supplier_order'; break; // "/fourn/class/fournisseur.commande.class.php"
113	}
114
115	if (is_object($mc))
116	{
117		return $mc->getEntity($element, $shared, $currentobject);
118	} else {
119		$out = '';
120		$addzero = array('user', 'usergroup', 'c_email_templates', 'email_template', 'default_values');
121		if (in_array($element, $addzero)) $out .= '0,';
122		$out .= ((int) $conf->entity);
123		return $out;
124	}
125}
126
127/**
128 * 	Set entity id to use when to create an object
129 *
130 * 	@param	object	$currentobject	Current object
131 * 	@return	mixed					Entity id to use ( eg. entity = '.setEntity($object) )
132 */
133function setEntity($currentobject)
134{
135	global $conf, $mc;
136
137	if (is_object($mc) && method_exists($mc, 'setEntity'))
138	{
139		return $mc->setEntity($currentobject);
140	} else {
141		return ((is_object($currentobject) && $currentobject->id > 0 && $currentobject->entity > 0) ? $currentobject->entity : $conf->entity);
142	}
143}
144
145/**
146 * 	Return if string has a name dedicated to store a secret
147 *
148 * 	@param	string	$keyname	Name of key to test
149 * 	@return	boolean				True if key is used to store a secret
150 */
151function isASecretKey($keyname)
152{
153	return preg_match('/(_pass|password|_pw|_key|securekey|serverkey|secret\d?|p12key|exportkey|_PW_[a-z]+|token)$/i', $keyname);
154}
155
156/**
157 * Return information about user browser
158 *
159 * Returns array with the following format:
160 * array(
161 *  'browsername' => Browser name (firefox|chrome|iceweasel|epiphany|safari|opera|ie|unknown)
162 *  'browserversion' => Browser version. Empty if unknown
163 *  'browseros' => Set with mobile OS (android|blackberry|ios|palm|symbian|webos|maemo|windows|unknown)
164 *  'layout' => (tablet|phone|classic)
165 *  'phone' => empty if not mobile, (android|blackberry|ios|palm|unknown) if mobile
166 *  'tablet' => true/false
167 * )
168 *
169 * @param string $user_agent Content of $_SERVER["HTTP_USER_AGENT"] variable
170 * @return	array Check function documentation
171 */
172function getBrowserInfo($user_agent)
173{
174	include_once DOL_DOCUMENT_ROOT.'/includes/mobiledetect/mobiledetectlib/Mobile_Detect.php';
175
176	$name = 'unknown';
177	$version = '';
178	$os = 'unknown';
179	$phone = '';
180
181	$user_agent = substr($user_agent, 0, 512);	// Avoid to process too large user agent
182
183	$detectmobile = new Mobile_Detect(null, $user_agent);
184	$tablet = $detectmobile->isTablet();
185
186	if ($detectmobile->isMobile()) {
187		$phone = 'unknown';
188
189		// If phone/smartphone, we set phone os name.
190		if ($detectmobile->is('AndroidOS')) {
191			$os = $phone = 'android';
192		} elseif ($detectmobile->is('BlackBerryOS')) {
193			$os = $phone = 'blackberry';
194		} elseif ($detectmobile->is('iOS')) {
195			$os = 'ios';
196			$phone = 'iphone';
197		} elseif ($detectmobile->is('PalmOS')) {
198			$os = $phone = 'palm';
199		} elseif ($detectmobile->is('SymbianOS')) {
200			$os = 'symbian';
201		} elseif ($detectmobile->is('webOS')) {
202			$os = 'webos';
203		} elseif ($detectmobile->is('MaemoOS')) {
204			$os = 'maemo';
205		} elseif ($detectmobile->is('WindowsMobileOS') || $detectmobile->is('WindowsPhoneOS')) {
206			$os = 'windows';
207		}
208	}
209
210	// OS
211	if (preg_match('/linux/i', $user_agent)) { $os = 'linux'; } elseif (preg_match('/macintosh/i', $user_agent)) { $os = 'macintosh'; } elseif (preg_match('/windows/i', $user_agent)) { $os = 'windows'; }
212
213	// Name
214	$reg = array();
215	if (preg_match('/firefox(\/|\s)([\d\.]*)/i', $user_agent, $reg)) { $name = 'firefox'; $version = $reg[2]; } elseif (preg_match('/edge(\/|\s)([\d\.]*)/i', $user_agent, $reg)) { $name = 'edge'; $version = $reg[2]; } elseif (preg_match('/chrome(\/|\s)([\d\.]+)/i', $user_agent, $reg)) { $name = 'chrome'; $version = $reg[2]; } // we can have 'chrome (Mozilla...) chrome x.y' in one string
216	elseif (preg_match('/chrome/i', $user_agent, $reg)) { $name = 'chrome'; } elseif (preg_match('/iceweasel/i', $user_agent)) { $name = 'iceweasel'; } elseif (preg_match('/epiphany/i', $user_agent)) { $name = 'epiphany'; } elseif (preg_match('/safari(\/|\s)([\d\.]*)/i', $user_agent, $reg)) { $name = 'safari'; $version = $reg[2]; } // Safari is often present in string for mobile but its not.
217	elseif (preg_match('/opera(\/|\s)([\d\.]*)/i', $user_agent, $reg)) { $name = 'opera'; $version = $reg[2]; } elseif (preg_match('/(MSIE\s([0-9]+\.[0-9]))|.*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) { $name = 'ie'; $version = end($reg); } // MS products at end
218	elseif (preg_match('/(Windows NT\s([0-9]+\.[0-9])).*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) { $name = 'ie'; $version = end($reg); } // MS products at end
219	elseif (preg_match('/l(i|y)n(x|ks)(\(|\/|\s)*([\d\.]+)/i', $user_agent, $reg)) { $name = 'lynxlinks'; $version = $reg[4]; }
220
221	if ($tablet) {
222		$layout = 'tablet';
223	} elseif ($phone) {
224		$layout = 'phone';
225	} else {
226		$layout = 'classic';
227	}
228
229	return array(
230		'browsername' => $name,
231		'browserversion' => $version,
232		'browseros' => $os,
233		'layout' => $layout,
234		'phone' => $phone,
235		'tablet' => $tablet
236	);
237}
238
239/**
240 *  Function called at end of web php process
241 *
242 *  @return	void
243 */
244function dol_shutdown()
245{
246	global $conf, $user, $langs, $db;
247	$disconnectdone = false; $depth = 0;
248	if (is_object($db) && !empty($db->connected)) { $depth = $db->transaction_opened; $disconnectdone = $db->close(); }
249	dol_syslog("--- End access to ".$_SERVER["PHP_SELF"].(($disconnectdone && $depth) ? ' (Warn: db disconnection forced, transaction depth was '.$depth.')' : ''), (($disconnectdone && $depth) ?LOG_WARNING:LOG_INFO));
250}
251
252/**
253 * Return true if we are in a context of submitting the parameter $paramname
254 *
255 * @param 	string	$paramname		Name or parameter to test
256 * @return 	boolean					True if we have just submit a POST or GET request with the parameter provided (even if param is empty)
257 */
258function GETPOSTISSET($paramname)
259{
260	$isset = false;
261
262	$relativepathstring = $_SERVER["PHP_SELF"];
263	// Clean $relativepathstring
264	if (constant('DOL_URL_ROOT')) $relativepathstring = preg_replace('/^'.preg_quote(constant('DOL_URL_ROOT'), '/').'/', '', $relativepathstring);
265	$relativepathstring = preg_replace('/^\//', '', $relativepathstring);
266	$relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
267	//var_dump($relativepathstring);
268	//var_dump($user->default_values);
269
270	// Code for search criteria persistence.
271	// Retrieve values if restore_lastsearch_values
272	if (!empty($_GET['restore_lastsearch_values']))        // Use $_GET here and not GETPOST
273	{
274		if (!empty($_SESSION['lastsearch_values_'.$relativepathstring]))	// If there is saved values
275		{
276			$tmp = json_decode($_SESSION['lastsearch_values_'.$relativepathstring], true);
277			if (is_array($tmp))
278			{
279				foreach ($tmp as $key => $val)
280				{
281					if ($key == $paramname)	// We are on the requested parameter
282					{
283						$isset = true;
284						break;
285					}
286				}
287			}
288		}
289		// If there is saved contextpage, page or limit
290		if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_'.$relativepathstring]))
291		{
292			$isset = true;
293		} elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_'.$relativepathstring]))
294		{
295			$isset = true;
296		} elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_'.$relativepathstring]))
297		{
298			$isset = true;
299		}
300	} else {
301		$isset = (isset($_POST[$paramname]) || isset($_GET[$paramname])); // We must keep $_POST and $_GET here
302	}
303
304	return $isset;
305}
306
307/**
308 *  Return value of a param into GET or POST supervariable.
309 *  Use the property $user->default_values[path]['creatform'] and/or $user->default_values[path]['filters'] and/or $user->default_values[path]['sortorder']
310 *  Note: The property $user->default_values is loaded by main.php when loading the user.
311 *
312 *  @param  string  $paramname   Name of parameter to found
313 *  @param  string  $check	     Type of check
314 *                               ''=no check (deprecated)
315 *                               'none'=no check (only for param that should have very rich content)
316 *                               'array', 'array:restricthtml' or 'array:aZ09' to check it's an array
317 *                               'int'=check it's numeric (integer or float)
318 *                               'intcomma'=check it's integer+comma ('1,2,3,4...')
319 *                               'alpha'=Same than alphanohtml since v13
320 *                               'alphanohtml'=check there is no html content and no " and no ../
321 *                               'aZ'=check it's a-z only
322 *                               'aZ09'=check it's simple alpha string (recommended for keys)
323 *                               'san_alpha'=Use filter_var with FILTER_SANITIZE_STRING (do not use this for free text string)
324 *                               'nohtml'=check there is no html content and no " and no ../
325 *                               'restricthtml'=check html content is restricted to some tags only
326 *                               'custom'= custom filter specify $filter and $options)
327 *  @param	int		$method	     Type of method (0 = get then post, 1 = only get, 2 = only post, 3 = post then get)
328 *  @param  int     $filter      Filter to apply when $check is set to 'custom'. (See http://php.net/manual/en/filter.filters.php for détails)
329 *  @param  mixed   $options     Options to pass to filter_var when $check is set to 'custom'
330 *  @param	string	$noreplace	 Force disable of replacement of __xxx__ strings.
331 *  @return string|array         Value found (string or array), or '' if check fails
332 */
333function GETPOST($paramname, $check = 'alphanohtml', $method = 0, $filter = null, $options = null, $noreplace = 0)
334{
335	global $mysoc, $user, $conf;
336
337	if (empty($paramname)) return 'BadFirstParameterForGETPOST';
338	if (empty($check))
339	{
340		dol_syslog("Deprecated use of GETPOST, called with 1st param = ".$paramname." and 2nd param is '', when calling page ".$_SERVER["PHP_SELF"], LOG_WARNING);
341		// Enable this line to know who call the GETPOST with '' $check parameter.
342		//var_dump(debug_backtrace()[0]);
343	}
344
345	if (empty($method)) $out = isset($_GET[$paramname]) ? $_GET[$paramname] : (isset($_POST[$paramname]) ? $_POST[$paramname] : '');
346	elseif ($method == 1) $out = isset($_GET[$paramname]) ? $_GET[$paramname] : '';
347	elseif ($method == 2) $out = isset($_POST[$paramname]) ? $_POST[$paramname] : '';
348	elseif ($method == 3) $out = isset($_POST[$paramname]) ? $_POST[$paramname] : (isset($_GET[$paramname]) ? $_GET[$paramname] : '');
349	else return 'BadThirdParameterForGETPOST';
350
351	if (empty($method) || $method == 3 || $method == 4)
352	{
353		$relativepathstring = $_SERVER["PHP_SELF"];
354		// Clean $relativepathstring
355		if (constant('DOL_URL_ROOT')) $relativepathstring = preg_replace('/^'.preg_quote(constant('DOL_URL_ROOT'), '/').'/', '', $relativepathstring);
356		$relativepathstring = preg_replace('/^\//', '', $relativepathstring);
357		$relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
358		//var_dump($relativepathstring);
359		//var_dump($user->default_values);
360
361		// Code for search criteria persistence.
362		// Retrieve values if restore_lastsearch_values
363		if (!empty($_GET['restore_lastsearch_values']))        // Use $_GET here and not GETPOST
364		{
365			if (!empty($_SESSION['lastsearch_values_'.$relativepathstring]))	// If there is saved values
366			{
367				$tmp = json_decode($_SESSION['lastsearch_values_'.$relativepathstring], true);
368				if (is_array($tmp))
369				{
370					foreach ($tmp as $key => $val)
371					{
372						if ($key == $paramname)	// We are on the requested parameter
373						{
374							$out = $val;
375							break;
376						}
377					}
378				}
379			}
380			// If there is saved contextpage, page or limit
381			if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_'.$relativepathstring]))
382			{
383				$out = $_SESSION['lastsearch_contextpage_'.$relativepathstring];
384			} elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_'.$relativepathstring]))
385			{
386				$out = $_SESSION['lastsearch_page_'.$relativepathstring];
387			} elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_'.$relativepathstring]))
388			{
389				$out = $_SESSION['lastsearch_limit_'.$relativepathstring];
390			}
391		} // Else, retrieve default values if we are not doing a sort
392		elseif (!isset($_GET['sortfield']))	// If we did a click on a field to sort, we do no apply default values. Same if option MAIN_ENABLE_DEFAULT_VALUES is not set
393		{
394			if (!empty($_GET['action']) && $_GET['action'] == 'create' && !isset($_GET[$paramname]) && !isset($_POST[$paramname]))
395			{
396				// Search default value from $object->field
397				global $object;
398				if (is_object($object) && isset($object->fields[$paramname]['default']))
399				{
400					$out = $object->fields[$paramname]['default'];
401				}
402			}
403			if (!empty($conf->global->MAIN_ENABLE_DEFAULT_VALUES))
404			{
405				if (!empty($_GET['action']) && (preg_match('/^create/', $_GET['action']) || preg_match('/^presend/', $_GET['action'])) && !isset($_GET[$paramname]) && !isset($_POST[$paramname]))
406				{
407					// Now search in setup to overwrite default values
408					if (!empty($user->default_values))		// $user->default_values defined from menu 'Setup - Default values'
409					{
410						if (isset($user->default_values[$relativepathstring]['createform']))
411						{
412							foreach ($user->default_values[$relativepathstring]['createform'] as $defkey => $defval)
413							{
414								$qualified = 0;
415								if ($defkey != '_noquery_')
416								{
417									$tmpqueryarraytohave = explode('&', $defkey);
418									$tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
419									$foundintru = 0;
420									foreach ($tmpqueryarraytohave as $tmpquerytohave)
421									{
422										if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) $foundintru = 1;
423									}
424									if (!$foundintru) $qualified = 1;
425									//var_dump($defkey.'-'.$qualified);
426								} else $qualified = 1;
427
428								if ($qualified)
429								{
430									if (isset($user->default_values[$relativepathstring]['createform'][$defkey][$paramname]))
431									{
432										$out = $user->default_values[$relativepathstring]['createform'][$defkey][$paramname];
433										break;
434									}
435								}
436							}
437						}
438					}
439				} // Management of default search_filters and sort order
440				elseif (!empty($paramname) && !isset($_GET[$paramname]) && !isset($_POST[$paramname]))
441				{
442					if (!empty($user->default_values))		// $user->default_values defined from menu 'Setup - Default values'
443					{
444						//var_dump($user->default_values[$relativepathstring]);
445						if ($paramname == 'sortfield' || $paramname == 'sortorder')			// Sorted on which fields ? ASC or DESC ?
446						{
447							if (isset($user->default_values[$relativepathstring]['sortorder']))	// Even if paramname is sortfield, data are stored into ['sortorder...']
448							{
449								foreach ($user->default_values[$relativepathstring]['sortorder'] as $defkey => $defval)
450								{
451									$qualified = 0;
452									if ($defkey != '_noquery_')
453									{
454										$tmpqueryarraytohave = explode('&', $defkey);
455										$tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
456										$foundintru = 0;
457										foreach ($tmpqueryarraytohave as $tmpquerytohave)
458										{
459											if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) $foundintru = 1;
460										}
461										if (!$foundintru) $qualified = 1;
462										//var_dump($defkey.'-'.$qualified);
463									} else $qualified = 1;
464
465									if ($qualified)
466									{
467										$forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
468										foreach ($user->default_values[$relativepathstring]['sortorder'][$defkey] as $key => $val)
469										{
470											if ($out) $out .= ', ';
471											if ($paramname == 'sortfield')
472											{
473												$out .= dol_string_nospecial($key, '', $forbidden_chars_to_replace);
474											}
475											if ($paramname == 'sortorder')
476											{
477												$out .= dol_string_nospecial($val, '', $forbidden_chars_to_replace);
478											}
479										}
480										//break;	// No break for sortfield and sortorder so we can cumulate fields (is it realy usefull ?)
481									}
482								}
483							}
484						} elseif (isset($user->default_values[$relativepathstring]['filters']))
485						{
486							foreach ($user->default_values[$relativepathstring]['filters'] as $defkey => $defval)	// $defkey is a querystring like 'a=b&c=d', $defval is key of user
487							{
488								$qualified = 0;
489								if ($defkey != '_noquery_')
490								{
491									$tmpqueryarraytohave = explode('&', $defkey);
492									$tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
493									$foundintru = 0;
494									foreach ($tmpqueryarraytohave as $tmpquerytohave)
495									{
496										if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) $foundintru = 1;
497									}
498									if (!$foundintru) $qualified = 1;
499									//var_dump($defkey.'-'.$qualified);
500								} else $qualified = 1;
501
502								if ($qualified)
503								{
504									// We must keep $_POST and $_GET here
505									if (isset($_POST['sall']) || isset($_POST['search_all']) || isset($_GET['sall']) || isset($_GET['search_all']))
506									{
507										// We made a search from quick search menu, do we still use default filter ?
508										if (empty($conf->global->MAIN_DISABLE_DEFAULT_FILTER_FOR_QUICK_SEARCH))
509										{
510											$forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
511											$out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
512										}
513									} else {
514										$forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
515										$out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
516									}
517									break;
518								}
519							}
520						}
521					}
522				}
523			}
524		}
525	}
526
527	// Substitution variables for GETPOST (used to get final url with variable parameters or final default value with variable parameters)
528	// Example of variables: __DAY__, __MONTH__, __YEAR__, __MYCOMPANY_COUNTRY_ID__, __USER_ID__, ...
529	// 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.
530	if (!is_array($out) && empty($_POST[$paramname]) && empty($noreplace))
531	{
532		$reg = array();
533		$maxloop = 20; $loopnb = 0; // Protection against infinite loop
534		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.
535		{
536			$loopnb++; $newout = '';
537
538			if ($reg[1] == 'DAY') {
539				$tmp = dol_getdate(dol_now(), true);
540				$newout = $tmp['mday'];
541			} elseif ($reg[1] == 'MONTH') {
542				$tmp = dol_getdate(dol_now(), true);
543				$newout = $tmp['mon'];
544			} elseif ($reg[1] == 'YEAR') {
545				$tmp = dol_getdate(dol_now(), true);
546				$newout = $tmp['year'];
547			} elseif ($reg[1] == 'PREVIOUS_DAY') {
548				$tmp = dol_getdate(dol_now(), true);
549				$tmp2 = dol_get_prev_day($tmp['mday'], $tmp['mon'], $tmp['year']);
550				$newout = $tmp2['day'];
551			} elseif ($reg[1] == 'PREVIOUS_MONTH') {
552				$tmp = dol_getdate(dol_now(), true);
553				$tmp2 = dol_get_prev_month($tmp['mon'], $tmp['year']);
554				$newout = $tmp2['month'];
555			} elseif ($reg[1] == 'PREVIOUS_YEAR') {
556				$tmp = dol_getdate(dol_now(), true);
557				$newout = ($tmp['year'] - 1);
558			} elseif ($reg[1] == 'NEXT_DAY') {
559				$tmp = dol_getdate(dol_now(), true);
560				$tmp2 = dol_get_next_day($tmp['mday'], $tmp['mon'], $tmp['year']);
561				$newout = $tmp2['day'];
562			} elseif ($reg[1] == 'NEXT_MONTH') {
563				$tmp = dol_getdate(dol_now(), true);
564				$tmp2 = dol_get_next_month($tmp['mon'], $tmp['year']);
565				$newout = $tmp2['month'];
566			} elseif ($reg[1] == 'NEXT_YEAR') {
567				$tmp = dol_getdate(dol_now(), true);
568				$newout = ($tmp['year'] + 1);
569			} elseif ($reg[1] == 'MYCOMPANY_COUNTRY_ID' || $reg[1] == 'MYCOUNTRY_ID' || $reg[1] == 'MYCOUNTRYID') {
570				$newout = $mysoc->country_id;
571			} elseif ($reg[1] == 'USER_ID' || $reg[1] == 'USERID') {
572				$newout = $user->id;
573			} elseif ($reg[1] == 'USER_SUPERVISOR_ID' || $reg[1] == 'SUPERVISOR_ID' || $reg[1] == 'SUPERVISORID') {
574				$newout = $user->fk_user;
575			} elseif ($reg[1] == 'ENTITY_ID' || $reg[1] == 'ENTITYID') {
576				$newout = $conf->entity;
577			} else {
578				$newout = ''; // Key not found, we replace with empty string
579			}
580			//var_dump('__'.$reg[1].'__ -> '.$newout);
581			$out = preg_replace('/__'.preg_quote($reg[1], '/').'__/', $newout, $out);
582		}
583	}
584
585	// Check rule
586	if (preg_match('/^array/', $check)) {	// If 'array' or 'array:restricthtml' or 'array:aZ09'
587		if (!is_array($out) || empty($out)) {
588			$out = array();
589		} else {
590			$tmparray = explode(':', $check);
591			if (!empty($tmparray[1])) {
592				$tmpcheck = $tmparray[1];
593			} else {
594				$tmpcheck = 'alphanohtml';
595			}
596			foreach ($out as $outkey => $outval) {
597				$out[$outkey] = checkVal($outval, $tmpcheck, $filter, $options);
598			}
599		}
600	}
601	else {
602		$out = checkVal($out, $check, $filter, $options);
603	}
604
605	// Sanitizing for special parameters. There is no reason to allow the backtopage parameter to contains an external URL.
606	if ($paramname == 'backtopage') {
607		$out = str_replace('\\', '/', $out);
608		$out = preg_replace(array('/^\/\/+/', '/^[a-z]*:/i'), '', $out);
609	}
610
611	// Code for search criteria persistence.
612	// Save data into session if key start with 'search_' or is 'smonth', 'syear', 'month', 'year'
613	if (empty($method) || $method == 3 || $method == 4)
614	{
615		if (preg_match('/^search_/', $paramname) || in_array($paramname, array('sortorder', 'sortfield')))
616		{
617			//var_dump($paramname.' - '.$out.' '.$user->default_values[$relativepathstring]['filters'][$paramname]);
618
619			// We save search key only if $out not empty that means:
620			// - posted value not empty, or
621			// - 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).
622
623			if ($out != '')		// $out = '0' or 'abc', it is a search criteria to keep
624			{
625				$user->lastsearch_values_tmp[$relativepathstring][$paramname] = $out;
626			}
627		}
628	}
629
630	return $out;
631}
632
633/**
634 *  Return value of a param into GET or POST supervariable.
635 *  Use the property $user->default_values[path]['creatform'] and/or $user->default_values[path]['filters'] and/or $user->default_values[path]['sortorder']
636 *  Note: The property $user->default_values is loaded by main.php when loading the user.
637 *
638 *  @param  string  $paramname   Name of parameter to found
639 *  @param	int		$method	     Type of method (0 = get then post, 1 = only get, 2 = only post, 3 = post then get)
640 *  @param  int     $filter      Filter to apply when $check is set to 'custom'. (See http://php.net/manual/en/filter.filters.php for détails)
641 *  @param  mixed   $options     Options to pass to filter_var when $check is set to 'custom'
642 *  @param	string	$noreplace   Force disable of replacement of __xxx__ strings.
643 *  @return int                  Value found (int)
644 */
645function GETPOSTINT($paramname, $method = 0, $filter = null, $options = null, $noreplace = 0)
646{
647	return (int) GETPOST($paramname, 'int', $method, $filter, $options, $noreplace);
648}
649
650/**
651 *  Return a value after checking on a rule.
652 *
653 *  @param  string  $out	     Value to get/check
654 *  @param  string  $check	     Type of check
655 *  @param  int     $filter      Filter to apply when $check is set to 'custom'. (See http://php.net/manual/en/filter.filters.php for détails)
656 *  @param  mixed   $options     Options to pass to filter_var when $check is set to 'custom'
657 *  @return string|array         Value found (string or array), or '' if check fails
658 */
659function checkVal($out = '', $check = 'alphanohtml', $filter = null, $options = null)
660{
661	// Check is done after replacement
662	switch ($check)
663	{
664		case 'none':
665			break;
666		case 'int':    // Check param is a numeric value (integer but also float or hexadecimal)
667			if (!is_numeric($out)) { $out = ''; }
668			break;
669		case 'intcomma':
670			if (preg_match('/[^0-9,-]+/i', $out)) $out = '';
671			break;
672		case 'san_alpha':
673			$out = filter_var($out, FILTER_SANITIZE_STRING);
674			break;
675		case 'email':
676			$out = filter_var($out, FILTER_SANITIZE_EMAIL);
677			break;
678		case 'aZ':
679			if (!is_array($out))
680			{
681				$out = trim($out);
682				if (preg_match('/[^a-z]+/i', $out)) $out = '';
683			}
684			break;
685		case 'aZ09':
686			if (!is_array($out))
687			{
688				$out = trim($out);
689				if (preg_match('/[^a-z0-9_\-\.]+/i', $out)) $out = '';
690			}
691			break;
692		case 'aZ09comma':		// great to sanitize sortfield or sortorder params that can be t.abc,t.def_gh
693			if (!is_array($out))
694			{
695				$out = trim($out);
696				if (preg_match('/[^a-z0-9_\-\.,]+/i', $out)) $out = '';
697			}
698			break;
699		case 'nohtml':		// No html
700			$out = dol_string_nohtmltag($out, 0);
701			break;
702		case 'alpha':		// No html and no ../ and "
703		case 'alphanohtml':	// Recommended for most scalar parameters and search parameters
704			if (!is_array($out)) {
705				// '"' is dangerous because param in url can close the href= or src= and add javascript functions.
706				// '../' is dangerous because it allows dir transversals
707				$out = str_replace(array('&quot;', '"'), '', trim($out));
708				$out = str_replace(array('../'), '', $out);
709				// keep lines feed
710				$out = dol_string_nohtmltag($out, 0);
711			}
712			break;
713		case 'alphawithlgt':		// No " and no ../ but we keep balanced < > tags with no special chars inside. Can be used for email string like "Name <email>"
714			if (!is_array($out)) {
715				// '"' is dangerous because param in url can close the href= or src= and add javascript functions.
716				// '../' is dangerous because it allows dir transversals
717				$out = str_replace(array('&quot;', '"'), '', trim($out));
718				$out = str_replace(array('../'), '', $out);
719			}
720			break;
721		case 'restricthtml':		// Recommended for most html textarea
722			$out = dol_string_onlythesehtmltags($out, 0, 1, 1);
723			break;
724		case 'custom':
725			if (empty($filter)) return 'BadFourthParameterForGETPOST';
726			$out = filter_var($out, $filter, $options);
727			break;
728	}
729
730	return $out;
731}
732
733
734
735if (!function_exists('dol_getprefix'))
736{
737	/**
738	 *  Return a prefix to use for this Dolibarr instance, for session/cookie names or email id.
739	 *  The prefix is unique for instance and avoid conflict between multi-instances, even when having two instances with same root dir
740	 *  or two instances in same virtual servers.
741	 *
742	 *  @param  string  $mode                   '' (prefix for session name) or 'email' (prefix for email id)
743	 *  @return	string                          A calculated prefix
744	 */
745	function dol_getprefix($mode = '')
746	{
747		// If prefix is for email (we need to have $conf alreayd loaded for this case)
748		if ($mode == 'email')
749		{
750			global $conf;
751
752			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)
753			{
754				if ($conf->global->MAIL_PREFIX_FOR_EMAIL_ID != 'SERVER_NAME') return $conf->global->MAIL_PREFIX_FOR_EMAIL_ID;
755				elseif (isset($_SERVER["SERVER_NAME"])) return $_SERVER["SERVER_NAME"];
756			}
757
758			// The recommended value (may be not defined for old versions)
759			if (!empty($conf->file->instance_unique_id)) return $conf->file->instance_unique_id;
760
761			// For backward compatibility
762			return dol_hash(DOL_DOCUMENT_ROOT.DOL_URL_ROOT, '3');
763		}
764
765		// If prefix is for session (no need to have $conf loaded)
766		global $dolibarr_main_instance_unique_id, $dolibarr_main_cookie_cryptkey;	// This is loaded by filefunc.inc.php
767		$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
768
769		// The recommended value (may be not defined for old versions)
770		if (!empty($tmp_instance_unique_id)) {
771			return $tmp_instance_unique_id;
772		}
773
774		// For backward compatibility
775		if (isset($_SERVER["SERVER_NAME"]) && isset($_SERVER["DOCUMENT_ROOT"])) {
776			return dol_hash($_SERVER["SERVER_NAME"].$_SERVER["DOCUMENT_ROOT"].DOL_DOCUMENT_ROOT.DOL_URL_ROOT, '3');
777		}
778
779		return dol_hash(DOL_DOCUMENT_ROOT.DOL_URL_ROOT, '3');
780	}
781}
782
783/**
784 *	Make an include_once using default root and alternate root if it fails.
785 *  To link to a core file, use include(DOL_DOCUMENT_ROOT.'/pathtofile')
786 *  To link to a module file from a module file, use include './mymodulefile';
787 *  To link to a module file from a core file, then this function can be used (call by hook / trigger / speciales pages)
788 *
789 * 	@param	string	$relpath	Relative path to file (Ie: mydir/myfile, ../myfile, ...)
790 * 	@param	string	$classname	Class name (deprecated)
791 *  @return bool                True if load is a success, False if it fails
792 */
793function dol_include_once($relpath, $classname = '')
794{
795	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']
796
797	$fullpath = dol_buildpath($relpath);
798
799	if (!file_exists($fullpath)) {
800		dol_syslog('functions::dol_include_once Tried to load unexisting file: '.$relpath, LOG_WARNING);
801		return false;
802	}
803
804	if (!empty($classname) && !class_exists($classname)) {
805		return include $fullpath;
806	} else {
807		return include_once $fullpath;
808	}
809}
810
811
812/**
813 *	Return path of url or filesystem. Can check into alternate dir or alternate dir + main dir depending on value of $returnemptyifnotfound.
814 *
815 * 	@param	string	$path						Relative path to file (if mode=0) or relative url (if mode=1). Ie: mydir/myfile, ../myfile
816 *  @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)
817 *  @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)
818 *  											1:If $type==0 and if file was not found into alternate dir, return empty string
819 *  											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
820 *  @return string								Full filesystem path (if path=0) or '' if file not found, Full url path (if mode=1)
821 */
822function dol_buildpath($path, $type = 0, $returnemptyifnotfound = 0)
823{
824	global $conf;
825
826	$path = preg_replace('/^\//', '', $path);
827
828	if (empty($type))	// For a filesystem path
829	{
830		$res = DOL_DOCUMENT_ROOT.'/'.$path; // Standard default path
831		if (is_array($conf->file->dol_document_root))
832		{
833			foreach ($conf->file->dol_document_root as $key => $dirroot)	// ex: array("main"=>"/home/main/htdocs", "alt0"=>"/home/dirmod/htdocs", ...)
834			{
835				if ($key == 'main')
836				{
837					continue;
838				}
839				if (file_exists($dirroot.'/'.$path))
840				{
841					$res = $dirroot.'/'.$path;
842					return $res;
843				}
844			}
845		}
846		if ($returnemptyifnotfound)								// Not found into alternate dir
847		{
848			if ($returnemptyifnotfound == 1 || !file_exists($res)) return '';
849		}
850	} else // For an url path
851	{
852		// We try to get local path of file on filesystem from url
853		// Note that trying to know if a file on disk exist by forging path on disk from url
854		// works only for some web server and some setup. This is bugged when
855		// using proxy, rewriting, virtual path, etc...
856		$res = '';
857		if ($type == 1) $res = DOL_URL_ROOT.'/'.$path; // Standard value
858		if ($type == 2) $res = DOL_MAIN_URL_ROOT.'/'.$path; // Standard value
859		if ($type == 3) $res = DOL_URL_ROOT.'/'.$path;
860
861		foreach ($conf->file->dol_document_root as $key => $dirroot)	// ex: array(["main"]=>"/home/main/htdocs", ["alt0"]=>"/home/dirmod/htdocs", ...)
862		{
863			if ($key == 'main')
864			{
865				if ($type == 3)
866				{
867					global $dolibarr_main_url_root;
868
869					// Define $urlwithroot
870					$urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
871					$urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file
872					//$urlwithroot=DOL_MAIN_URL_ROOT;					// This is to use same domain name than current
873
874					$res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : $urlwithroot).'/'.$path; // Test on start with http is for old conf syntax
875				}
876				continue;
877			}
878			preg_match('/^([^\?]+(\.css\.php|\.css|\.js\.php|\.js|\.png|\.jpg|\.php)?)/i', $path, $regs); // Take part before '?'
879			if (!empty($regs[1]))
880			{
881				//print $key.'-'.$dirroot.'/'.$path.'-'.$conf->file->dol_url_root[$type].'<br>'."\n";
882				if (file_exists($dirroot.'/'.$regs[1]))
883				{
884					if ($type == 1)
885					{
886						$res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_URL_ROOT).$conf->file->dol_url_root[$key].'/'.$path;
887					}
888					if ($type == 2)
889					{
890						$res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_MAIN_URL_ROOT).$conf->file->dol_url_root[$key].'/'.$path;
891					}
892					if ($type == 3)
893					{
894						global $dolibarr_main_url_root;
895
896						// Define $urlwithroot
897						$urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
898						$urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file
899						//$urlwithroot=DOL_MAIN_URL_ROOT;					// This is to use same domain name than current
900
901						$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
902					}
903					break;
904				}
905			}
906		}
907	}
908
909	return $res;
910}
911
912/**
913 *	Create a clone of instance of object (new instance with same value for properties)
914 *  With native = 0: Property that are reference are also new object (full isolation clone). This means $this->db of new object is not valid.
915 *  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.
916 *
917 * 	@param	object	$object		Object to clone
918 *  @param	int		$native		0=Full isolation method, 1=Native PHP method
919 *	@return object				Clone object
920 *  @see https://php.net/manual/language.oop5.cloning.php
921 */
922function dol_clone($object, $native = 0)
923{
924	if (empty($native))
925	{
926		$myclone = unserialize(serialize($object));
927	} else {
928		$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)
929	}
930
931	return $myclone;
932}
933
934/**
935 *	Optimize a size for some browsers (phone, smarphone, ...)
936 *
937 * 	@param	int		$size		Size we want
938 * 	@param	string	$type		Type of optimizing:
939 * 								'' = function used to define a size for truncation
940 * 								'width' = function is used to define a width
941 *	@return int					New size after optimizing
942 */
943function dol_size($size, $type = '')
944{
945	global $conf;
946	if (empty($conf->dol_optimize_smallscreen)) return $size;
947	if ($type == 'width' && $size > 250) return 250;
948	else return 10;
949}
950
951
952/**
953 *	Clean a string to use it as a file name
954 *
955 *	@param	string	$str            String to clean
956 * 	@param	string	$newstr			String to replace bad chars with
957 *  @param	int	    $unaccent		1=Remove also accent (default), 0 do not remove them
958 *	@return string          		String cleaned (a-zA-Z_)
959 *
960 * 	@see        	dol_string_nospecial(), dol_string_unaccent(), dol_sanitizePathName()
961 */
962function dol_sanitizeFileName($str, $newstr = '_', $unaccent = 1)
963{
964	// List of special chars for filenames in windows are defined on page https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
965	// Char '>' '<' '|' '$' and ';' are special chars for shells.
966	// Char '/' and '\' are file delimiters.
967	// -- 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
968	$filesystem_forbidden_chars = array('<', '>', '/', '\\', '?', '*', '|', '"', ':', '°', '$', ';', '--');
969	return dol_string_nospecial($unaccent ? dol_string_unaccent($str) : $str, $newstr, $filesystem_forbidden_chars);
970}
971
972/**
973 *	Clean a string to use it as a path name
974 *
975 *	@param	string	$str            String to clean
976 * 	@param	string	$newstr			String to replace bad chars with
977 *  @param	int	    $unaccent		1=Remove also accent (default), 0 do not remove them
978 *	@return string          		String cleaned (a-zA-Z_)
979 *
980 * 	@see        	dol_string_nospecial(), dol_string_unaccent(), dol_sanitizeFileName()
981 */
982function dol_sanitizePathName($str, $newstr = '_', $unaccent = 1)
983{
984	$filesystem_forbidden_chars = array('<', '>', '?', '*', '|', '"', '°');
985	return dol_string_nospecial($unaccent ? dol_string_unaccent($str) : $str, $newstr, $filesystem_forbidden_chars);
986}
987
988/**
989 *	Clean a string from all accent characters to be used as ref, login or by dol_sanitizeFileName
990 *
991 *	@param	string	$str			String to clean
992 *	@return string   	       		Cleaned string
993 *
994 * 	@see    		dol_sanitizeFilename(), dol_string_nospecial()
995 */
996function dol_string_unaccent($str)
997{
998	if (utf8_check($str))
999	{
1000		// See http://www.utf8-chartable.de/
1001		$string = rawurlencode($str);
1002		$replacements = array(
1003		'%C3%80' => 'A', '%C3%81' => 'A', '%C3%82' => 'A', '%C3%83' => 'A', '%C3%84' => 'A', '%C3%85' => 'A',
1004		'%C3%88' => 'E', '%C3%89' => 'E', '%C3%8A' => 'E', '%C3%8B' => 'E',
1005		'%C3%8C' => 'I', '%C3%8D' => 'I', '%C3%8E' => 'I', '%C3%8F' => 'I',
1006		'%C3%92' => 'O', '%C3%93' => 'O', '%C3%94' => 'O', '%C3%95' => 'O', '%C3%96' => 'O',
1007		'%C3%99' => 'U', '%C3%9A' => 'U', '%C3%9B' => 'U', '%C3%9C' => 'U',
1008		'%C3%A0' => 'a', '%C3%A1' => 'a', '%C3%A2' => 'a', '%C3%A3' => 'a', '%C3%A4' => 'a', '%C3%A5' => 'a',
1009		'%C3%A7' => 'c',
1010		'%C3%A8' => 'e', '%C3%A9' => 'e', '%C3%AA' => 'e', '%C3%AB' => 'e',
1011		'%C3%AC' => 'i', '%C3%AD' => 'i', '%C3%AE' => 'i', '%C3%AF' => 'i',
1012		'%C3%B1' => 'n',
1013		'%C3%B2' => 'o', '%C3%B3' => 'o', '%C3%B4' => 'o', '%C3%B5' => 'o', '%C3%B6' => 'o',
1014		'%C3%B9' => 'u', '%C3%BA' => 'u', '%C3%BB' => 'u', '%C3%BC' => 'u',
1015		'%C3%BF' => 'y'
1016		);
1017		$string = strtr($string, $replacements);
1018		return rawurldecode($string);
1019	} else {
1020		// See http://www.ascii-code.com/
1021		$string = strtr(
1022			$str,
1023			"\xC0\xC1\xC2\xC3\xC4\xC5\xC7
1024			\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1
1025			\xD2\xD3\xD4\xD5\xD8\xD9\xDA\xDB\xDD
1026			\xE0\xE1\xE2\xE3\xE4\xE5\xE7\xE8\xE9\xEA\xEB
1027			\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF8
1028			\xF9\xFA\xFB\xFC\xFD\xFF",
1029			"AAAAAAC
1030			EEEEIIIIDN
1031			OOOOOUUUY
1032			aaaaaaceeee
1033			iiiidnooooo
1034			uuuuyy"
1035		);
1036		$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"));
1037		return $string;
1038	}
1039}
1040
1041/**
1042 *	Clean a string from all punctuation characters to use it as a ref or login.
1043 *  This is a more complete function than dol_sanitizeFileName.
1044 *
1045 *	@param	string	$str            	String to clean
1046 * 	@param	string	$newstr				String to replace forbidden chars with
1047 *  @param  array	$badcharstoreplace  List of forbidden characters
1048 * 	@return string          			Cleaned string
1049 *
1050 * 	@see    		dol_sanitizeFilename(), dol_string_unaccent(), dol_string_nounprintableascii()
1051 */
1052function dol_string_nospecial($str, $newstr = '_', $badcharstoreplace = '')
1053{
1054	$forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ",", ";", "=", '°'); // more complete than dol_sanitizeFileName
1055	$forbidden_chars_to_remove = array();
1056	if (is_array($badcharstoreplace)) $forbidden_chars_to_replace = $badcharstoreplace;
1057	//$forbidden_chars_to_remove=array("(",")");
1058
1059	return str_replace($forbidden_chars_to_replace, $newstr, str_replace($forbidden_chars_to_remove, "", $str));
1060}
1061
1062
1063/**
1064 *	Clean a string from all non printable ASCII chars (0x00-0x1F and 0x7F). It can also removes also Tab-CR-LF. UTF8 chars remains.
1065 *  This can be used to sanitize a string and view its real content. Some hacks try to obfuscate attacks by inserting non printable chars.
1066 *  Note, for information: UTF8 on 1 byte are: \x00-\7F
1067 *                                 2 bytes are: byte 1 \xc0-\xdf, byte 2 = \x80-\xbf
1068 *                                 3 bytes are: byte 1 \xe0-\xef, byte 2 = \x80-\xbf, byte 3 = \x80-\xbf
1069 *                                 4 bytes are: byte 1 \xf0-\xf7, byte 2 = \x80-\xbf, byte 3 = \x80-\xbf, byte 4 = \x80-\xbf
1070 *	@param	string	$str            	String to clean
1071 *  @param	int		$removetabcrlf		Remove also CR-LF
1072 * 	@return string          			Cleaned string
1073 *
1074 * 	@see    		dol_sanitizeFilename(), dol_string_unaccent(), dol_string_nospecial()
1075 */
1076function dol_string_nounprintableascii($str, $removetabcrlf = 1)
1077{
1078	if ($removetabcrlf) {
1079		return preg_replace('/[\x00-\x1F\x7F]/u', '', $str); // /u operator makes UTF8 valid characters being ignored so are not included into the replace
1080	} else {
1081		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
1082	}
1083}
1084
1085
1086/**
1087 *  Returns text escaped for inclusion into javascript code
1088 *
1089 *  @param      string		$stringtoescape		String to escape
1090 *  @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 \
1091 *  @param		int		$noescapebackslashn	0=Escape also \n. 1=Do not escape \n.
1092 *  @return     string     		 				Escaped string. Both ' and " are escaped into ' if they are escaped.
1093 */
1094function dol_escape_js($stringtoescape, $mode = 0, $noescapebackslashn = 0)
1095{
1096	// escape quotes and backslashes, newlines, etc.
1097	$substitjs = array("&#039;"=>"\\'", "\r"=>'\\r');
1098	//$substitjs['</']='<\/';	// We removed this. Should be useless.
1099	if (empty($noescapebackslashn)) { $substitjs["\n"] = '\\n'; $substitjs['\\'] = '\\\\'; }
1100	if (empty($mode)) { $substitjs["'"] = "\\'"; $substitjs['"'] = "\\'"; } elseif ($mode == 1) $substitjs["'"] = "\\'";
1101	elseif ($mode == 2) { $substitjs['"'] = '\\"'; } elseif ($mode == 3) { $substitjs["'"] = "\\'"; $substitjs['"'] = "\\\""; }
1102	return strtr($stringtoescape, $substitjs);
1103}
1104
1105/**
1106 *  Returns text escaped for inclusion into javascript code
1107 *
1108 *  @param      string		$stringtoescape		String to escape
1109 *  @return     string     		 				Escaped string for json content.
1110 */
1111function dol_escape_json($stringtoescape)
1112{
1113	return str_replace('"', '\"', $stringtoescape);
1114}
1115
1116/**
1117 *  Returns text escaped for inclusion in HTML alt or title tags, or into values of HTML input fields.
1118 *
1119 *  @param      string		$stringtoescape			String to escape
1120 *  @param		int			$keepb					1=Keep b tags and escape them, 0=remove them
1121 *  @param      int         $keepn              	1=Preserve \r\n strings (otherwise, replace them with escaped value). Set to 1 when escaping for a <textarea>.
1122 *  @param		string		$keepmoretags			'' or 'common' or list of tags
1123 *  @param		int			$escapeonlyhtmltags		1=Escape only html tags, not the special chars like accents.
1124 *  @return     string     				 			Escaped string
1125 *  @see		dol_string_nohtmltag(), dol_string_nospecial(), dol_string_unaccent()
1126 */
1127function dol_escape_htmltag($stringtoescape, $keepb = 0, $keepn = 0, $keepmoretags = '', $escapeonlyhtmltags = 0)
1128{
1129	if ($keepmoretags == 'common') $keepmoretags = 'html,body,a,b,em,i,u,ul,li,br,div,img,font,p,span,strong,table,tr,td,th,tbody';
1130	// TODO Implement $keepmoretags
1131
1132	// escape quotes and backslashes, newlines, etc.
1133	if ($escapeonlyhtmltags) {
1134		$tmp = htmlspecialchars_decode($stringtoescape, ENT_COMPAT);
1135	} else {
1136		$tmp = html_entity_decode($stringtoescape, ENT_COMPAT, 'UTF-8');
1137	}
1138	if (!$keepb) $tmp = strtr($tmp, array("<b>"=>'', '</b>'=>''));
1139	if (!$keepn) $tmp = strtr($tmp, array("\r"=>'\\r', "\n"=>'\\n'));
1140	if ($escapeonlyhtmltags) {
1141		return htmlspecialchars($tmp, ENT_COMPAT, 'UTF-8');
1142	} else {
1143		return htmlentities($tmp, ENT_COMPAT, 'UTF-8');
1144	}
1145}
1146
1147/**
1148 * Convert a string to lower. Never use strtolower because it does not works with UTF8 strings.
1149 *
1150 * @param 	string		$string		        String to encode
1151 * @param   string      $encoding           Character set encoding
1152 * @return 	string							String converted
1153 */
1154function dol_strtolower($string, $encoding = "UTF-8")
1155{
1156	if (function_exists('mb_strtolower')) {
1157		return mb_strtolower($string, $encoding);
1158	} else {
1159		return strtolower($string);
1160	}
1161}
1162
1163/**
1164 * Convert a string to upper. Never use strtolower because it does not works with UTF8 strings.
1165 *
1166 * @param 	string		$string		        String to encode
1167 * @param   string      $encoding           Character set encoding
1168 * @return 	string							String converted
1169 */
1170function dol_strtoupper($string, $encoding = "UTF-8")
1171{
1172	if (function_exists('mb_strtoupper')) {
1173		return mb_strtoupper($string, $encoding);
1174	} else {
1175		return strtoupper($string);
1176	}
1177}
1178
1179/**
1180 * Convert first character of the first word of a string to upper. Never use ucfirst because it does not works with UTF8 strings.
1181 *
1182 * @param   string      $string         String to encode
1183 * @param   string      $encoding       Character set encodign
1184 * @return  string                      String converted
1185 */
1186function dol_ucfirst($string, $encoding = "UTF-8")
1187{
1188	if (function_exists('mb_substr')) {
1189		return mb_strtoupper(mb_substr($string, 0, 1, $encoding), $encoding).mb_substr($string, 1, null, $encoding);
1190	} else {
1191		return ucfirst($string);
1192	}
1193}
1194
1195/**
1196 * Convert first character of all the words of a string to upper. Never use ucfirst because it does not works with UTF8 strings.
1197 *
1198 * @param   string      $string         String to encode
1199 * @param   string      $encoding       Character set encodign
1200 * @return  string                      String converted
1201 */
1202function dol_ucwords($string, $encoding = "UTF-8")
1203{
1204	if (function_exists('mb_convert_case')) {
1205		return mb_convert_case($string, MB_CASE_TITLE, $encoding);
1206	} else {
1207		return ucwords($string);
1208	}
1209}
1210
1211/**
1212 *	Write log message into outputs. Possible outputs can be:
1213 *	SYSLOG_HANDLERS = ["mod_syslog_file"]  		file name is then defined by SYSLOG_FILE
1214 *	SYSLOG_HANDLERS = ["mod_syslog_syslog"]  	facility is then defined by SYSLOG_FACILITY
1215 *  Warning, syslog functions are bugged on Windows, generating memory protection faults. To solve
1216 *  this, use logging to files instead of syslog (see setup of module).
1217 *  Note: If constant 'SYSLOG_FILE_NO_ERROR' defined, we never output any error message when writing to log fails.
1218 *  Note: You can get log message into html sources by adding parameter &logtohtml=1 (constant MAIN_LOGTOHTML must be set)
1219 *  This function works only if syslog module is enabled.
1220 * 	This must not use any call to other function calling dol_syslog (avoid infinite loop).
1221 *
1222 * 	@param  string		$message				Line to log. ''=Show nothing
1223 *  @param  int			$level					Log level
1224 *												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
1225 *												On Linux   LOG_ERR=3, LOG_WARNING=4, LOG_INFO=6, LOG_DEBUG=7
1226 *  @param	int			$ident					1=Increase ident of 1, -1=Decrease ident of 1
1227 *  @param	string		$suffixinfilename		When output is a file, append this suffix into default log filename.
1228 *  @param	string		$restricttologhandler	Force output of log only to this log handler
1229 *  @param	array|null	$logcontext				If defined, an array with extra informations (can be used by some log handlers)
1230 *  @return	void
1231 */
1232function dol_syslog($message, $level = LOG_INFO, $ident = 0, $suffixinfilename = '', $restricttologhandler = '', $logcontext = null)
1233{
1234	global $conf, $user, $debugbar;
1235
1236	// If syslog module enabled
1237	if (empty($conf->syslog->enabled)) return;
1238
1239	// Check if we are into execution of code of a website
1240	if (defined('USEEXTERNALSERVER') && !defined('USEDOLIBARRSERVER') && !defined('USEDOLIBARREDITOR')) {
1241		global $website, $websitekey;
1242		if (is_object($website) && !empty($website->ref)) $suffixinfilename .= '_website_'.$website->ref;
1243		elseif (!empty($websitekey)) $suffixinfilename .= '_website_'.$websitekey;
1244	}
1245
1246	if ($ident < 0)
1247	{
1248		foreach ($conf->loghandlers as $loghandlerinstance)
1249		{
1250			$loghandlerinstance->setIdent($ident);
1251		}
1252	}
1253
1254	if (!empty($message))
1255	{
1256		// Test log level
1257		$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');
1258		if (!array_key_exists($level, $logLevels))
1259		{
1260			throw new Exception('Incorrect log level');
1261		}
1262		if ($level > $conf->global->SYSLOG_LEVEL) return;
1263
1264		$message = preg_replace('/password=\'[^\']*\'/', 'password=\'hidden\'', $message); // protection to avoid to have value of password in log
1265
1266		// If adding log inside HTML page is required
1267		if ((!empty($_REQUEST['logtohtml']) && !empty($conf->global->MAIN_ENABLE_LOG_TO_HTML))
1268			|| (!empty($user->rights->debugbar->read) && is_object($debugbar)))
1269		{
1270			$conf->logbuffer[] = dol_print_date(time(), "%Y-%m-%d %H:%M:%S")." ".$logLevels[$level]." ".$message;
1271		}
1272
1273		//TODO: Remove this. MAIN_ENABLE_LOG_INLINE_HTML should be deprecated and use a log handler dedicated to HTML output
1274		// If html log tag enabled and url parameter log defined, we show output log on HTML comments
1275		if (!empty($conf->global->MAIN_ENABLE_LOG_INLINE_HTML) && !empty($_GET["log"]))
1276		{
1277			print "\n\n<!-- Log start\n";
1278			print $message."\n";
1279			print "Log end -->\n";
1280		}
1281
1282		$data = array(
1283			'message' => $message,
1284			'script' => (isset($_SERVER['PHP_SELF']) ? basename($_SERVER['PHP_SELF'], '.php') : false),
1285			'level' => $level,
1286			'user' => ((is_object($user) && $user->id) ? $user->login : false),
1287			'ip' => false
1288		);
1289
1290		$remoteip = getUserRemoteIP(); // Get ip when page run on a web server
1291		if (!empty($remoteip)) {
1292			$data['ip'] = $remoteip;
1293			// This is when server run behind a reverse proxy
1294			if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR'] != $remoteip) $data['ip'] = $_SERVER['HTTP_X_FORWARDED_FOR'].' -> '.$data['ip'];
1295			elseif (!empty($_SERVER['HTTP_CLIENT_IP']) && $_SERVER['HTTP_CLIENT_IP'] != $remoteip) $data['ip'] = $_SERVER['HTTP_CLIENT_IP'].' -> '.$data['ip'];
1296		} // This is when PHP session is ran inside a web server but not inside a client request (example: init code of apache)
1297		elseif (!empty($_SERVER['SERVER_ADDR'])) $data['ip'] = $_SERVER['SERVER_ADDR'];
1298		// 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).
1299		elseif (!empty($_SERVER['COMPUTERNAME'])) $data['ip'] = $_SERVER['COMPUTERNAME'].(empty($_SERVER['USERNAME']) ? '' : '@'.$_SERVER['USERNAME']);
1300		// 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).
1301		elseif (!empty($_SERVER['LOGNAME'])) $data['ip'] = '???@'.$_SERVER['LOGNAME'];
1302		// Loop on each log handler and send output
1303		foreach ($conf->loghandlers as $loghandlerinstance)
1304		{
1305			if ($restricttologhandler && $loghandlerinstance->code != $restricttologhandler) continue;
1306			$loghandlerinstance->export($data, $suffixinfilename);
1307		}
1308		unset($data);
1309	}
1310
1311	if ($ident > 0)
1312	{
1313		foreach ($conf->loghandlers as $loghandlerinstance)
1314		{
1315			$loghandlerinstance->setIdent($ident);
1316		}
1317	}
1318}
1319
1320/**
1321 *	Return HTML code to output a button to open a dialog popup box.
1322 *  Such buttons must be included inside a HTML form.
1323 *
1324 *	@param	string	$name				A name for the html component
1325 *	@param	string	$label 	    		Label of button
1326 *	@param  string	$buttonstring  		button string
1327 *	@param  string	$url				Url to open
1328 *  @param	string	$disabled			Disabled text
1329 * 	@return	string						HTML component with button
1330 */
1331function dolButtonToOpenUrlInDialogPopup($name, $label, $buttonstring, $url, $disabled = '')
1332{
1333	if (strpos($url, '?') > 0) {
1334		$url .= '&dol_hide_topmenu=1&dol_hide_leftmenu=1&dol_openinpopup=1';
1335	} else {
1336		$url .= '?dol_hide_menuinpopup=1&dol_hide_leftmenu=1&dol_openinpopup=1';
1337	}
1338
1339	//print '<input type="submit" class="button bordertransp"'.$disabled.' value="'.dol_escape_htmltag($langs->trans("MediaFiles")).'" name="file_manager">';
1340	$out = '<a class="button bordertransp button_'.$name.'"'.$disabled.' title="'.dol_escape_htmltag($label).'">'.$buttonstring.'</a>';
1341	$out .= '<script language="javascript">
1342				 jQuery(document).ready(function () {
1343					 jQuery(".button_'.$name.'").click(function () {
1344						 var $dialog = $(\'<div></div>\').html(\'<iframe class="iframedialog" style="border: 0px;" src="'.DOL_URL_ROOT.$url.'" width="100%" height="98%"></iframe>\')
1345						 .dialog({
1346						 	autoOpen: false,
1347						 	modal: true,
1348						 	height: (window.innerHeight - 150),
1349						 	width: \'80%\',
1350						 	title: "'.dol_escape_js($label).'"
1351						 });
1352						 $dialog.dialog(\'open\');
1353					 });
1354				 });
1355				 </script>';
1356	return $out;
1357}
1358
1359/**
1360 *	Show tab header of a card
1361 *
1362 *	@param	array	$links				Array of tabs. Currently initialized by calling a function xxx_admin_prepare_head
1363 *	@param	string	$active     		Active tab name (document', 'info', 'ldap', ....)
1364 *	@param  string	$title      		Title
1365 *	@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)
1366 * 	@param	string	$picto				Add a picto on tab title
1367 *	@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.
1368 *  @param	string	$morehtmlright		Add more html content on right of tabs title
1369 *  @param	string	$morecss			More Css
1370 *  @param	int		$limittoshow		Limit number of tabs to show. Use 0 to use automatic default value.
1371 *  @param	string	$moretabssuffix		A suffix to use when you have several dol_get_fiche_head() in same page
1372 * 	@return	void
1373 *  @deprecated Use print dol_get_fiche_head() instead
1374 */
1375function dol_fiche_head($links = array(), $active = '0', $title = '', $notab = 0, $picto = '', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limittoshow = 0, $moretabssuffix = '')
1376{
1377	print dol_get_fiche_head($links, $active, $title, $notab, $picto, $pictoisfullpath, $morehtmlright, $morecss, $limittoshow, $moretabssuffix);
1378}
1379
1380/**
1381 *  Show tabs of a record
1382 *
1383 *	@param	array	$links				Array of tabs
1384 *	@param	string	$active     		Active tab name
1385 *	@param  string	$title      		Title
1386 *	@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)
1387 * 	@param	string	$picto				Add a picto on tab title
1388 *	@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.
1389 *  @param	string	$morehtmlright		Add more html content on right of tabs title
1390 *  @param	string	$morecss			More Css
1391 *  @param	int		$limittoshow		Limit number of tabs to show. Use 0 to use automatic default value.
1392 *  @param	string	$moretabssuffix		A suffix to use when you have several dol_get_fiche_head() in same page
1393 * 	@return	string
1394 */
1395function dol_get_fiche_head($links = array(), $active = '', $title = '', $notab = 0, $picto = '', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limittoshow = 0, $moretabssuffix = '')
1396{
1397	global $conf, $langs, $hookmanager;
1398
1399	// Show title
1400	$showtitle = 1;
1401	if (!empty($conf->dol_optimize_smallscreen)) $showtitle = 0;
1402
1403	$out = "\n".'<!-- dol_fiche_head - dol_get_fiche_head -->';
1404
1405	if ((!empty($title) && $showtitle) || $morehtmlright || !empty($links)) {
1406		$out .= '<div class="tabs'.($picto ? '' : ' nopaddingleft').'" data-role="controlgroup" data-type="horizontal">'."\n";
1407	}
1408
1409	// Show right part
1410	if ($morehtmlright) $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.
1411
1412	// Show title
1413	if (!empty($title) && $showtitle && empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER))
1414	{
1415		$limittitle = 30;
1416		$out .= '<a class="tabTitle">';
1417		if ($picto) $out .= img_picto($title, ($pictoisfullpath ? '' : 'object_').$picto, '', $pictoisfullpath, 0, 0, '', 'imgTabTitle').' ';
1418		$out .= '<span class="tabTitleText">'.dol_trunc($title, $limittitle).'</span>';
1419		$out .= '</a>';
1420	}
1421
1422	// Show tabs
1423
1424	// Define max of key (max may be higher than sizeof because of hole due to module disabling some tabs).
1425	$maxkey = -1;
1426	if (is_array($links) && !empty($links))
1427	{
1428		$keys = array_keys($links);
1429		if (count($keys)) $maxkey = max($keys);
1430	}
1431
1432	// Show tabs
1433	// if =0 we don't use the feature
1434	if (empty($limittoshow)) {
1435		$limittoshow = (empty($conf->global->MAIN_MAXTABS_IN_CARD) ? 99 : $conf->global->MAIN_MAXTABS_IN_CARD);
1436	}
1437	if (!empty($conf->dol_optimize_smallscreen)) $limittoshow = 2;
1438
1439	$displaytab = 0;
1440	$nbintab = 0;
1441	$popuptab = 0;
1442	$outmore = '';
1443	for ($i = 0; $i <= $maxkey; $i++)
1444	{
1445		if ((is_numeric($active) && $i == $active) || (!empty($links[$i][2]) && !is_numeric($active) && $active == $links[$i][2])) {
1446			// If active tab is already present
1447			if ($i >= $limittoshow) $limittoshow--;
1448		}
1449	}
1450
1451	for ($i = 0; $i <= $maxkey; $i++)
1452	{
1453		if ((is_numeric($active) && $i == $active) || (!empty($links[$i][2]) && !is_numeric($active) && $active == $links[$i][2])) {
1454			$isactive = true;
1455		} else {
1456			$isactive = false;
1457		}
1458
1459		if ($i < $limittoshow || $isactive)
1460		{
1461			$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]).' -->';
1462			if (isset($links[$i][2]) && $links[$i][2] == 'image')
1463			{
1464				if (!empty($links[$i][0]))
1465				{
1466					$out .= '<a class="tabimage'.($morecss ? ' '.$morecss : '').'" href="'.$links[$i][0].'">'.$links[$i][1].'</a>'."\n";
1467				} else {
1468					$out .= '<span class="tabspan">'.$links[$i][1].'</span>'."\n";
1469				}
1470			} elseif (!empty($links[$i][1]))
1471			{
1472				//print "x $i $active ".$links[$i][2]." z";
1473				if ($isactive)
1474				{
1475					$out .= '<a'.(!empty($links[$i][2]) ? ' id="'.$links[$i][2].'"' : '').' class="tabactive tab inline-block'.($morecss ? ' '.$morecss : '').'" href="'.$links[$i][0].'">';
1476					$out .= $links[$i][1];
1477					$out .= '</a>'."\n";
1478				} else {
1479					$out .= '<a'.(!empty($links[$i][2]) ? ' id="'.$links[$i][2].'"' : '').' class="tabunactive tab inline-block'.($morecss ? ' '.$morecss : '').'" href="'.$links[$i][0].'">';
1480					$out .= $links[$i][1];
1481					$out .= '</a>'."\n";
1482				}
1483			}
1484			$out .= '</div>';
1485		} else {
1486			// The popup with the other tabs
1487			if (!$popuptab)
1488			{
1489				$popuptab = 1;
1490				$outmore .= '<div class="popuptabset wordwrap">'; // The css used to hide/show popup
1491			}
1492			$outmore .= '<div class="popuptab wordwrap" style="display:inherit;">';
1493			if (isset($links[$i][2]) && $links[$i][2] == 'image')
1494			{
1495				if (!empty($links[$i][0]))
1496					$outmore .= '<a class="tabimage'.($morecss ? ' '.$morecss : '').'" href="'.$links[$i][0].'">'.$links[$i][1].'</a>'."\n";
1497				else $outmore .= '<span class="tabspan">'.$links[$i][1].'</span>'."\n";
1498			} elseif (!empty($links[$i][1]))
1499			{
1500				$outmore .= '<a'.(!empty($links[$i][2]) ? ' id="'.$links[$i][2].'"' : '').' class="wordwrap inline-block'.($morecss ? ' '.$morecss : '').'" href="'.$links[$i][0].'">';
1501				$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.
1502				$outmore .= '</a>'."\n";
1503			}
1504			$outmore .= '</div>';
1505
1506			$nbintab++;
1507		}
1508		$displaytab = $i;
1509	}
1510	if ($popuptab) $outmore .= '</div>';
1511
1512	if ($popuptab)	// If there is some tabs not shown
1513	{
1514		$left = ($langs->trans("DIRECTION") == 'rtl' ? 'right' : 'left');
1515		$right = ($langs->trans("DIRECTION") == 'rtl' ? 'left' : 'right');
1516		$widthofpopup = 200;
1517
1518		$tabsname = $moretabssuffix;
1519		if (empty($tabsname)) { $tabsname = str_replace("@", "", $picto); }
1520		$out .= '<div id="moretabs'.$tabsname.'" class="inline-block tabsElem">';
1521		$out .= '<a href="#" class="tab moretab inline-block tabunactive">'.$langs->trans("More").'... ('.$nbintab.')</a>'; // Do not use "reposition" class in the "More".
1522		$out .= '<div id="moretabsList'.$tabsname.'" style="width: '.$widthofpopup.'px; position: absolute; '.$left.': -999em; text-align: '.$left.'; margin:0px; padding:2px; z-index:10;">';
1523		$out .= $outmore;
1524		$out .= '</div>';
1525		$out .= '<div></div>';
1526		$out .= "</div>\n";
1527
1528		$out .= "<script>";
1529		$out .= "$('#moretabs".$tabsname."').mouseenter( function() {
1530			var x = this.offsetLeft, y = this.offsetTop;
1531			console.log('mouseenter ".$left." x='+x+' y='+y+' window.innerWidth='+window.innerWidth);
1532			if ((window.innerWidth - x) < ".($widthofpopup + 10).") {
1533				$('#moretabsList".$tabsname."').css('".$right."','8px');
1534			}
1535			$('#moretabsList".$tabsname."').css('".$left."','auto');
1536			});
1537		";
1538		$out .= "$('#moretabs".$tabsname."').mouseleave( function() { console.log('mouseleave ".$left."'); $('#moretabsList".$tabsname."').css('".$left."','-999em');});";
1539		$out .= "</script>";
1540	}
1541
1542	if ((!empty($title) && $showtitle) || $morehtmlright || !empty($links)) {
1543		$out .= "</div>\n";
1544	}
1545
1546	if (!$notab || $notab == -1 || $notab == -2) $out .= "\n".'<div class="tabBar'.($notab == -1 ? '' : ($notab == -2 ? ' tabBarNoTop' : ' tabBarWithBottom')).'">'."\n";
1547
1548	$parameters = array('tabname' => $active, 'out' => $out);
1549	$reshook = $hookmanager->executeHooks('printTabsHead', $parameters); // This hook usage is called just before output the head of tabs. Take also a look at "completeTabsHead"
1550	if ($reshook > 0)
1551	{
1552		$out = $hookmanager->resPrint;
1553	}
1554
1555	return $out;
1556}
1557
1558/**
1559 *  Show tab footer of a card
1560 *
1561 *  @param	int		$notab       -1 or 0=Add tab footer, 1=no tab footer
1562 *  @return	void
1563 *  @deprecated Use print dol_get_fiche_end() instead
1564 */
1565function dol_fiche_end($notab = 0)
1566{
1567	print dol_get_fiche_end($notab);
1568}
1569
1570/**
1571 *	Return tab footer of a card
1572 *
1573 *	@param  int		$notab		-1 or 0=Add tab footer, 1=no tab footer
1574 *  @return	string
1575 */
1576function dol_get_fiche_end($notab = 0)
1577{
1578	if (!$notab || $notab == -1) return "\n</div>\n";
1579	else return '';
1580}
1581
1582/**
1583 *  Show tab footer of a card.
1584 *  Note: $object->next_prev_filter can be set to restrict select to find next or previous record by $form->showrefnav.
1585 *
1586 *  @param	Object	$object			Object to show
1587 *  @param	string	$paramid   		Name of parameter to use to name the id into the URL next/previous link
1588 *  @param	string	$morehtml  		More html content to output just before the nav bar
1589 *  @param	int		$shownav	  	Show Condition (navigation is shown if value is 1)
1590 *  @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.
1591 *  @param	string	$fieldref   	Nom du champ objet ref (object->ref) a utiliser pour select next et previous
1592 *  @param	string	$morehtmlref  	More html to show after ref
1593 *  @param	string	$moreparam  	More param to add in nav link url.
1594 *	@param	int		$nodbprefix		Do not include DB prefix to forge table name
1595 *	@param	string	$morehtmlleft	More html code to show before ref
1596 *	@param	string	$morehtmlstatus	More html code to show under navigation arrows
1597 *  @param  int     $onlybanner     Put this to 1, if the card will contains only a banner (this add css 'arearefnobottom' on div)
1598 *	@param	string	$morehtmlright	More html code to show before navigation arrows
1599 *  @return	void
1600 */
1601function dol_banner_tab($object, $paramid, $morehtml = '', $shownav = 1, $fieldid = 'rowid', $fieldref = 'ref', $morehtmlref = '', $moreparam = '', $nodbprefix = 0, $morehtmlleft = '', $morehtmlstatus = '', $onlybanner = 0, $morehtmlright = '')
1602{
1603	global $conf, $form, $user, $langs;
1604
1605	$error = 0;
1606
1607	$maxvisiblephotos = 1;
1608	$showimage = 1;
1609	$entity = (empty($object->entity) ? $conf->entity : $object->entity);
1610	$showbarcode = empty($conf->barcode->enabled) ? 0 : ($object->barcode ? 1 : 0);
1611	if (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->barcode->lire_advance)) $showbarcode = 0;
1612	$modulepart = 'unknown';
1613
1614	if ($object->element == 'societe')         $modulepart = 'societe';
1615	if ($object->element == 'contact')         $modulepart = 'contact';
1616	if ($object->element == 'member')          $modulepart = 'memberphoto';
1617	if ($object->element == 'user')            $modulepart = 'userphoto';
1618	if ($object->element == 'product')         $modulepart = 'product';
1619	if ($object->element == 'ticket')          $modulepart = 'ticket';
1620
1621	if (class_exists("Imagick"))
1622	{
1623		if ($object->element == 'propal')            $modulepart = 'propal';
1624		if ($object->element == 'commande')          $modulepart = 'commande';
1625		if ($object->element == 'facture')           $modulepart = 'facture';
1626		if ($object->element == 'fichinter')         $modulepart = 'ficheinter';
1627		if ($object->element == 'contrat')           $modulepart = 'contract';
1628		if ($object->element == 'supplier_proposal') $modulepart = 'supplier_proposal';
1629		if ($object->element == 'order_supplier')    $modulepart = 'supplier_order';
1630		if ($object->element == 'invoice_supplier')  $modulepart = 'supplier_invoice';
1631		if ($object->element == 'expensereport')     $modulepart = 'expensereport';
1632	}
1633
1634	if ($object->element == 'product')
1635	{
1636		$width = 80; $cssclass = 'photoref';
1637		$showimage = $object->is_photo_available($conf->product->multidir_output[$entity]);
1638		$maxvisiblephotos = (isset($conf->global->PRODUCT_MAX_VISIBLE_PHOTO) ? $conf->global->PRODUCT_MAX_VISIBLE_PHOTO : 5);
1639		if ($conf->browser->layout == 'phone') $maxvisiblephotos = 1;
1640		if ($showimage) {
1641			$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>';
1642		} else {
1643			if (!empty($conf->global->PRODUCT_NODISPLAYIFNOPHOTO)) {
1644				$nophoto = '';
1645				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
1646			} else {    // Show no photo link
1647				$nophoto = '/public/theme/common/nophoto.png';
1648				$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>';
1649			}
1650		}
1651	} elseif ($object->element == 'ticket') {
1652		$width = 80; $cssclass = 'photoref';
1653		$showimage = $object->is_photo_available($conf->ticket->multidir_output[$entity].'/'.$object->ref);
1654		$maxvisiblephotos = (isset($conf->global->TICKET_MAX_VISIBLE_PHOTO) ? $conf->global->TICKET_MAX_VISIBLE_PHOTO : 2);
1655		if ($conf->browser->layout == 'phone') $maxvisiblephotos = 1;
1656
1657		if ($showimage)
1658		{
1659			$showphoto = $object->show_photos('ticket', $conf->ticket->multidir_output[$entity], 'small', $maxvisiblephotos, 0, 0, 0, $width, 0);
1660			if ($object->nbphoto > 0)
1661			{
1662				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">'.$showphoto.'</div>';
1663			} else {
1664				$showimage = 0;
1665			}
1666		}
1667		if (!$showimage)
1668		{
1669			if (!empty($conf->global->TICKET_NODISPLAYIFNOPHOTO)) {
1670				$nophoto = '';
1671				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
1672			} else {    // Show no photo link
1673				$nophoto = img_picto('No photo', 'object_ticket');
1674				$morehtmlleft .= '<!-- No photo to show -->';
1675				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
1676				$morehtmlleft .= $nophoto;
1677				$morehtmlleft .= '</div></div>';
1678			}
1679		}
1680	} else {
1681		if ($showimage)
1682		{
1683			if ($modulepart != 'unknown')
1684			{
1685				$phototoshow = '';
1686				// Check if a preview file is available
1687				if (in_array($modulepart, array('propal', 'commande', 'facture', 'ficheinter', 'contract', 'supplier_order', 'supplier_proposal', 'supplier_invoice', 'expensereport')) && class_exists("Imagick"))
1688				{
1689					$objectref = dol_sanitizeFileName($object->ref);
1690					$dir_output = (empty($conf->$modulepart->multidir_output[$entity]) ? $conf->$modulepart->dir_output : $conf->$modulepart->multidir_output[$entity])."/";
1691					if (in_array($modulepart, array('invoice_supplier', 'supplier_invoice')))
1692					{
1693						$subdir = get_exdir($object->id, 2, 0, 1, $object, $modulepart);
1694						$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
1695					} else {
1696						$subdir = get_exdir($object->id, 0, 0, 1, $object, $modulepart);
1697					}
1698					if (empty($subdir)) $subdir = 'errorgettingsubdirofobject'; // Protection to avoid to return empty path
1699
1700					$filepath = $dir_output.$subdir."/";
1701
1702					$filepdf = $filepath.$objectref.".pdf";
1703					$relativepath = $subdir.'/'.$objectref.'.pdf';
1704
1705					// Define path to preview pdf file (preview precompiled "file.ext" are "file.ext_preview.png")
1706					$fileimage = $filepdf.'_preview.png';
1707					$relativepathimage = $relativepath.'_preview.png';
1708
1709					$pdfexists = file_exists($filepdf);
1710
1711					// If PDF file exists
1712					if ($pdfexists)
1713					{
1714						// Conversion du PDF en image png si fichier png non existant
1715						if (!file_exists($fileimage) || (filemtime($fileimage) < filemtime($filepdf)))
1716						{
1717							if (empty($conf->global->MAIN_DISABLE_PDF_THUMBS))		// If you experience trouble with pdf thumb generation and imagick, you can disable here.
1718							{
1719								include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1720								$ret = dol_convert_file($filepdf, 'png', $fileimage, '0'); // Convert first page of PDF into a file _preview.png
1721								if ($ret < 0) $error++;
1722							}
1723						}
1724					}
1725
1726					if ($pdfexists && !$error)
1727					{
1728						$heightforphotref = 80;
1729						if (!empty($conf->dol_optimize_smallscreen)) $heightforphotref = 60;
1730						// If the preview file is found
1731						if (file_exists($fileimage))
1732						{
1733							$phototoshow = '<div class="photoref">';
1734							$phototoshow .= '<img height="'.$heightforphotref.'" class="photo photowithmargin photowithborder" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart=apercu'.$modulepart.'&amp;file='.urlencode($relativepathimage).'">';
1735							$phototoshow .= '</div>';
1736						}
1737					}
1738				} elseif (!$phototoshow) { // example if modulepart = 'photo'
1739					$phototoshow .= $form->showphoto($modulepart, $object, 0, 0, 0, 'photoref', 'small', 1, 0, $maxvisiblephotos);
1740				}
1741
1742				if ($phototoshow) {
1743					$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">';
1744					$morehtmlleft .= $phototoshow;
1745					$morehtmlleft .= '</div>';
1746				}
1747			}
1748
1749			if (!$phototoshow)      // Show No photo link (picto of object)
1750			{
1751				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">';
1752				if ($object->element == 'action')
1753				{
1754					$width = 80;
1755					$cssclass = 'photorefcenter';
1756					$nophoto = img_picto('No photo', 'title_agenda');
1757				} else {
1758					$width = 14; $cssclass = 'photorefcenter';
1759					$picto = $object->picto;
1760					if ($object->element == 'project' && !$object->public) $picto = 'project'; // instead of projectpub
1761					$nophoto = img_picto('No photo', 'object_'.$picto);
1762				}
1763				$morehtmlleft .= '<!-- No photo to show -->';
1764				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
1765				$morehtmlleft .= $nophoto;
1766				$morehtmlleft .= '</div></div>';
1767
1768				$morehtmlleft .= '</div>';
1769			}
1770		}
1771	}
1772
1773	if ($showbarcode) $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">'.$form->showbarcode($object).'</div>';
1774
1775	if ($object->element == 'societe')
1776	{
1777		if (!empty($conf->use_javascript_ajax) && $user->rights->societe->creer && !empty($conf->global->MAIN_DIRECT_STATUS_UPDATE))
1778		{
1779		   	$morehtmlstatus .= ajax_object_onoff($object, 'status', 'status', 'InActivity', 'ActivityCeased');
1780		} else {
1781			$morehtmlstatus .= $object->getLibStatut(6);
1782		}
1783	} elseif ($object->element == 'product')
1784	{
1785		//$morehtmlstatus.=$langs->trans("Status").' ('.$langs->trans("Sell").') ';
1786		if (!empty($conf->use_javascript_ajax) && $user->rights->produit->creer && !empty($conf->global->MAIN_DIRECT_STATUS_UPDATE)) {
1787			$morehtmlstatus .= ajax_object_onoff($object, 'status', 'tosell', 'ProductStatusOnSell', 'ProductStatusNotOnSell');
1788		} else {
1789			$morehtmlstatus .= '<span class="statusrefsell">'.$object->getLibStatut(6, 0).'</span>';
1790		}
1791		$morehtmlstatus .= ' &nbsp; ';
1792		//$morehtmlstatus.=$langs->trans("Status").' ('.$langs->trans("Buy").') ';
1793		if (!empty($conf->use_javascript_ajax) && $user->rights->produit->creer && !empty($conf->global->MAIN_DIRECT_STATUS_UPDATE)) {
1794			$morehtmlstatus .= ajax_object_onoff($object, 'status_buy', 'tobuy', 'ProductStatusOnBuy', 'ProductStatusNotOnBuy');
1795		} else {
1796			$morehtmlstatus .= '<span class="statusrefbuy">'.$object->getLibStatut(6, 1).'</span>';
1797		}
1798	} elseif (in_array($object->element, array('facture', 'invoice', 'invoice_supplier', 'chargesociales', 'loan'))) {
1799		$tmptxt = $object->getLibStatut(6, $object->totalpaye);
1800		if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) $tmptxt = $object->getLibStatut(5, $object->totalpaye);
1801		$morehtmlstatus .= $tmptxt;
1802	} elseif ($object->element == 'contrat' || $object->element == 'contract') {
1803		if ($object->statut == 0) $morehtmlstatus .= $object->getLibStatut(5);
1804		else $morehtmlstatus .= $object->getLibStatut(4);
1805	} elseif ($object->element == 'facturerec') {
1806		if ($object->frequency == 0) $morehtmlstatus .= $object->getLibStatut(2);
1807		else $morehtmlstatus .= $object->getLibStatut(5);
1808	} elseif ($object->element == 'project_task') {
1809		$object->fk_statut = 1;
1810		if ($object->progress > 0) $object->fk_statut = 2;
1811		if ($object->progress >= 100) $object->fk_statut = 3;
1812		$tmptxt = $object->getLibStatut(5);
1813		$morehtmlstatus .= $tmptxt; // No status on task
1814	} else { // Generic case
1815		$tmptxt = $object->getLibStatut(6);
1816		if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) $tmptxt = $object->getLibStatut(5);
1817		$morehtmlstatus .= $tmptxt;
1818	}
1819
1820	// Add if object was dispatched "into accountancy"
1821	if (!empty($conf->accounting->enabled) && in_array($object->element, array('bank', 'paiementcharge', 'facture', 'invoice', 'invoice_supplier', 'expensereport', 'payment_various')))
1822	{
1823		// Note: For 'chargesociales', 'salaries'... this is the payments that are dispatched (so element = 'bank')
1824		if (method_exists($object, 'getVentilExportCompta'))
1825		{
1826			$accounted = $object->getVentilExportCompta();
1827			$langs->load("accountancy");
1828			$morehtmlstatus .= '</div><div class="statusref statusrefbis"><span class="opacitymedium">'.($accounted > 0 ? $langs->trans("Accounted") : $langs->trans("NotYetAccounted")).'</span>';
1829		}
1830	}
1831
1832	// Add alias for thirdparty
1833	if (!empty($object->name_alias)) $morehtmlref .= '<div class="refidno">'.$object->name_alias.'</div>';
1834
1835	// Add label
1836	if (in_array($object->element, array('product', 'bank_account', 'project_task')))
1837	{
1838		if (!empty($object->label)) $morehtmlref .= '<div class="refidno">'.$object->label.'</div>';
1839	}
1840
1841	if (method_exists($object, 'getBannerAddress') && !in_array($object->element, array('product', 'bookmark', 'ecm_directories', 'ecm_files')))
1842	{
1843		$moreaddress = $object->getBannerAddress('refaddress', $object);
1844		if ($moreaddress) {
1845			$morehtmlref .= '<div class="refidno">';
1846			$morehtmlref .= $moreaddress;
1847			$morehtmlref .= '</div>';
1848		}
1849	}
1850	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))
1851	{
1852		$morehtmlref .= '<div style="clear: both;"></div>';
1853		$morehtmlref .= '<div class="refidno">';
1854		$morehtmlref .= $langs->trans("TechnicalID").': '.$object->id;
1855		$morehtmlref .= '</div>';
1856	}
1857
1858	print '<div class="'.($onlybanner ? 'arearefnobottom ' : 'arearef ').'heightref valignmiddle centpercent">';
1859	print $form->showrefnav($object, $paramid, $morehtml, $shownav, $fieldid, $fieldref, $morehtmlref, $moreparam, $nodbprefix, $morehtmlleft, $morehtmlstatus, $morehtmlright);
1860	print '</div>';
1861	print '<div class="underrefbanner clearboth"></div>';
1862}
1863
1864/**
1865 * Show a string with the label tag dedicated to the HTML edit field.
1866 *
1867 * @param	string	$langkey		Translation key
1868 * @param 	string	$fieldkey		Key of the html select field the text refers to
1869 * @param	int		$fieldrequired	1=Field is mandatory
1870 * @return string
1871 * @deprecated Form::editfieldkey
1872 */
1873function fieldLabel($langkey, $fieldkey, $fieldrequired = 0)
1874{
1875	global $langs;
1876	$ret = '';
1877	if ($fieldrequired) $ret .= '<span class="fieldrequired">';
1878	$ret .= '<label for="'.$fieldkey.'">';
1879	$ret .= $langs->trans($langkey);
1880	$ret .= '</label>';
1881	if ($fieldrequired) $ret .= '</span>';
1882	return $ret;
1883}
1884
1885/**
1886 * Return string to add class property on html element with pair/impair.
1887 *
1888 * @param	string	$var			0 or 1
1889 * @param	string	$moreclass		More class to add
1890 * @return	string					String to add class onto HTML element
1891 */
1892function dol_bc($var, $moreclass = '')
1893{
1894	global $bc;
1895	$ret = ' '.$bc[$var];
1896	if ($moreclass) $ret = preg_replace('/class=\"/', 'class="'.$moreclass.' ', $ret);
1897	return $ret;
1898}
1899
1900/**
1901 *      Return a formated address (part address/zip/town/state) according to country rules.
1902 *      See https://en.wikipedia.org/wiki/Address
1903 *
1904 *      @param  Object		$object			A company or contact object
1905 * 	    @param	int			$withcountry	1=Add country into address string
1906 *      @param	string		$sep			Separator to use to build string
1907 *      @param	Translate	$outputlangs	Object lang that contains language for text translation.
1908 *      @param	int			$mode			0=Standard output, 1=Remove address
1909 *  	@param	string		$extralangcode	User extralanguage $langcode as values for address, town
1910 *      @return string						Formated string
1911 *      @see dol_print_address()
1912 */
1913function dol_format_address($object, $withcountry = 0, $sep = "\n", $outputlangs = '', $mode = 0, $extralangcode = '')
1914{
1915	global $conf, $langs;
1916
1917	$ret = '';
1918	$countriesusingstate = array('AU', 'CA', 'US', 'IN', 'GB', 'ES', 'UK', 'TR'); // See also MAIN_FORCE_STATE_INTO_ADDRESS
1919
1920	// See format of addresses on https://en.wikipedia.org/wiki/Address
1921	// Address
1922	if (empty($mode)) {
1923		$ret .= ($extralangcode ? $object->array_languages['address'][$extralangcode] : $object->address);
1924	}
1925	// Zip/Town/State
1926	if (isset($object->country_code) && in_array($object->country_code, array('AU', 'CA', 'US')) || !empty($conf->global->MAIN_FORCE_STATE_INTO_ADDRESS)) {  	// US: title firstname name \n address lines \n town, state, zip \n country
1927		$town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : $object->town);
1928		$ret .= ($ret ? $sep : '').$town;
1929		if (!empty($object->state))	{
1930			$ret .= ($ret ? ", " : '').$object->state;
1931		}
1932		if ($object->zip) $ret .= ($ret ? ", " : '').$object->zip;
1933	} elseif (isset($object->country_code) && in_array($object->country_code, array('GB', 'UK'))) { // UK: title firstname name \n address lines \n town state \n zip \n country
1934		$town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : $object->town);
1935		$ret .= ($ret ? $sep : '').$town;
1936		if (!empty($object->state)) {
1937			$ret .= ($ret ? ", " : '').$object->state;
1938		}
1939		if ($object->zip) $ret .= ($ret ? $sep : '').$object->zip;
1940	} elseif (isset($object->country_code) && in_array($object->country_code, array('ES', 'TR'))) { // ES: title firstname name \n address lines \n zip town \n state \n country
1941		$ret .= ($ret ? $sep : '').$object->zip;
1942		$town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : $object->town);
1943		$ret .= ($town ? (($object->zip ? ' ' : '').$town) : '');
1944		if (!empty($object->state)) {
1945			$ret .= "\n".$object->state;
1946		}
1947	} elseif (isset($object->country_code) && in_array($object->country_code, array('IT'))) { // IT: tile firstname name\n address lines \n zip (Code Departement) \n country
1948		$ret .= ($ret ? $sep : '').$object->zip;
1949		$town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : $object->town);
1950		$ret .= ($town ? (($object->zip ? ' ' : '').$town) : '');
1951		$ret .= (empty($object->state_code) ? '' : (' '.$object->state_code));
1952	} else { // Other: title firstname name \n address lines \n zip town \n country
1953		$town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : $object->town);
1954		$ret .= $object->zip ? (($ret ? $sep : '').$object->zip) : '';
1955		$ret .= ($town ? (($object->zip ? ' ' : ($ret ? $sep : '')).$town) : '');
1956		if (!empty($object->state) && in_array($object->country_code, $countriesusingstate)) {
1957			$ret .= ($ret ? ", " : '').$object->state;
1958		}
1959	}
1960	if (!is_object($outputlangs)) $outputlangs = $langs;
1961	if ($withcountry) {
1962		$langs->load("dict");
1963		$ret .= (empty($object->country_code) ? '' : ($ret ? $sep : '').$outputlangs->convToOutputCharset($outputlangs->transnoentitiesnoconv("Country".$object->country_code)));
1964	}
1965
1966	return $ret;
1967}
1968
1969
1970
1971/**
1972 *	Format a string.
1973 *
1974 *	@param	string	$fmt		Format of strftime function (http://php.net/manual/fr/function.strftime.php)
1975 *  @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)
1976 *  @param	int		$is_gmt		See comment of timestamp parameter
1977 *	@return	string				A formatted string
1978 */
1979function dol_strftime($fmt, $ts = false, $is_gmt = false)
1980{
1981	if ((abs($ts) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range
1982		return ($is_gmt) ? @gmstrftime($fmt, $ts) : @strftime($fmt, $ts);
1983	} else return 'Error date into a not supported range';
1984}
1985
1986/**
1987 *	Output date in a string format according to outputlangs (or langs if not defined).
1988 * 	Return charset is always UTF-8, except if encodetoouput is defined. In this case charset is output charset
1989 *
1990 *	@param	int			$time			GM Timestamps date
1991 *	@param	string		$format      	Output date format (tag of strftime function)
1992 *										"%d %b %Y",
1993 *										"%d/%m/%Y %H:%M",
1994 *										"%d/%m/%Y %H:%M:%S",
1995 *                                      "%B"=Long text of month, "%A"=Long text of day, "%b"=Short text of month, "%a"=Short text of day
1996 *										"day", "daytext", "dayhour", "dayhourldap", "dayhourtext", "dayrfc", "dayhourrfc", "...inputnoreduce", "...reduceformat"
1997 * 	@param	string		$tzoutput		true or 'gmt' => string is for Greenwich location
1998 * 										false or 'tzserver' => output string is for local PHP server TZ usage
1999 * 										'tzuser' => output string is for user TZ (current browser TZ with current dst) => In a future, we should have same behaviour than 'tzuserrel'
2000 *                                      'tzuserrel' => output string is for user TZ (current browser TZ with dst or not, depending on date position) (TODO not implemented yet)
2001 *	@param	Translate	$outputlangs	Object lang that contains language for text translation.
2002 *  @param  boolean		$encodetooutput false=no convert into output pagecode
2003 * 	@return string      				Formated date or '' if time is null
2004 *
2005 *  @see        dol_mktime(), dol_stringtotime(), dol_getdate()
2006 */
2007function dol_print_date($time, $format = '', $tzoutput = 'auto', $outputlangs = '', $encodetooutput = false)
2008{
2009	global $conf, $langs;
2010
2011	if ($tzoutput === 'auto') {
2012		$tzoutput = (empty($conf) ? 'tzserver' : (isset($conf->tzuserinputkey) ? $conf->tzuserinputkey : 'tzserver'));
2013	}
2014
2015	// Clean parameters
2016	$to_gmt = false;
2017	$offsettz = $offsetdst = 0;
2018	if ($tzoutput) {
2019		$to_gmt = true; // For backward compatibility
2020		if (is_string($tzoutput)) {
2021			if ($tzoutput == 'tzserver') {
2022				$to_gmt = false;
2023				$offsettzstring = @date_default_timezone_get(); // Example 'Europe/Berlin' or 'Indian/Reunion'
2024				$offsettz = 0;	// Timezone offset with server timezone, so 0
2025				$offsetdst = 0;	// Dst offset with server timezone, so 0
2026			} elseif ($tzoutput == 'tzuser' || $tzoutput == 'tzuserrel') {
2027				$to_gmt = true;
2028				$offsettzstring = (empty($_SESSION['dol_tz_string']) ? 'UTC' : $_SESSION['dol_tz_string']); // Example 'Europe/Berlin' or 'Indian/Reunion'
2029				$offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60; // Will not be used anymore
2030				$offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60; // Will not be used anymore
2031			}
2032		}
2033	}
2034	if (!is_object($outputlangs)) {
2035		$outputlangs = $langs;
2036	}
2037	if (!$format) {
2038		$format = 'daytextshort';
2039	}
2040
2041	// Do we have to reduce the length of date (year on 2 chars) to save space.
2042	// Note: dayinputnoreduce is same than day but no reduction of year length will be done
2043	$reduceformat = (!empty($conf->dol_optimize_smallscreen) && in_array($format, array('day', 'dayhour'))) ? 1 : 0;	// Test on original $format param.
2044	$format = preg_replace('/inputnoreduce/', '', $format);	// so format 'dayinputnoreduce' is processed like day
2045	$formatwithoutreduce = preg_replace('/reduceformat/', '', $format);
2046	if ($formatwithoutreduce != $format) {
2047		$format = $formatwithoutreduce;
2048		$reduceformat = 1;
2049	}  // so format 'dayreduceformat' is processed like day
2050
2051	// Change predefined format into computer format. If found translation in lang file we use it, otherwise we use default.
2052	// TODO Add format daysmallyear and dayhoursmallyear
2053	if ($format == 'day') {
2054		$format = ($outputlangs->trans("FormatDateShort") != "FormatDateShort" ? $outputlangs->trans("FormatDateShort") : $conf->format_date_short);
2055	} elseif ($format == 'hour') {
2056		$format = ($outputlangs->trans("FormatHourShort") != "FormatHourShort" ? $outputlangs->trans("FormatHourShort") : $conf->format_hour_short);
2057	} elseif ($format == 'hourduration') {
2058		$format = ($outputlangs->trans("FormatHourShortDuration") != "FormatHourShortDuration" ? $outputlangs->trans("FormatHourShortDuration") : $conf->format_hour_short_duration);
2059	} elseif ($format == 'daytext') {
2060		$format = ($outputlangs->trans("FormatDateText") != "FormatDateText" ? $outputlangs->trans("FormatDateText") : $conf->format_date_text);
2061	} elseif ($format == 'daytextshort') {
2062		$format = ($outputlangs->trans("FormatDateTextShort") != "FormatDateTextShort" ? $outputlangs->trans("FormatDateTextShort") : $conf->format_date_text_short);
2063	} elseif ($format == 'dayhour') {
2064		$format = ($outputlangs->trans("FormatDateHourShort") != "FormatDateHourShort" ? $outputlangs->trans("FormatDateHourShort") : $conf->format_date_hour_short);
2065	} elseif ($format == 'dayhoursec') {
2066		$format = ($outputlangs->trans("FormatDateHourSecShort") != "FormatDateHourSecShort" ? $outputlangs->trans("FormatDateHourSecShort") : $conf->format_date_hour_sec_short);
2067	} elseif ($format == 'dayhourtext') {
2068		$format = ($outputlangs->trans("FormatDateHourText") != "FormatDateHourText" ? $outputlangs->trans("FormatDateHourText") : $conf->format_date_hour_text);
2069	} elseif ($format == 'dayhourtextshort') {
2070		$format = ($outputlangs->trans("FormatDateHourTextShort") != "FormatDateHourTextShort" ? $outputlangs->trans("FormatDateHourTextShort") : $conf->format_date_hour_text_short);
2071	} elseif ($format == 'dayhourlog') {
2072		// Format not sensitive to language
2073		$format = '%Y%m%d%H%M%S';
2074	} elseif ($format == 'dayhourldap') {
2075		$format = '%Y%m%d%H%M%SZ';
2076	} elseif ($format == 'dayhourxcard') {
2077		$format = '%Y%m%dT%H%M%SZ';
2078	} elseif ($format == 'dayxcard') {
2079		$format = '%Y%m%d';
2080	} elseif ($format == 'dayrfc') {
2081		$format = '%Y-%m-%d'; // DATE_RFC3339
2082	} elseif ($format == 'dayhourrfc') {
2083		$format = '%Y-%m-%dT%H:%M:%SZ'; // DATETIME RFC3339
2084	} elseif ($format == 'standard') {
2085		$format = '%Y-%m-%d %H:%M:%S';
2086	}
2087
2088	if ($reduceformat) {
2089		$format = str_replace('%Y', '%y', $format);
2090		$format = str_replace('yyyy', 'yy', $format);
2091	}
2092
2093	// If date undefined or "", we return ""
2094	if (dol_strlen($time) == 0) {
2095		return ''; // $time=0 allowed (it means 01/01/1970 00:00:00)
2096	}
2097
2098	// Clean format
2099	if (preg_match('/%b/i', $format)) {		// There is some text to translate
2100		// We inhibate translation to text made by strftime functions. We will use trans instead later.
2101		$format = str_replace('%b', '__b__', $format);
2102		$format = str_replace('%B', '__B__', $format);
2103	}
2104	if (preg_match('/%a/i', $format)) {		// There is some text to translate
2105		// We inhibate translation to text made by strftime functions. We will use trans instead later.
2106		$format = str_replace('%a', '__a__', $format);
2107		$format = str_replace('%A', '__A__', $format);
2108	}
2109
2110
2111	// Analyze date
2112	$reg = array();
2113	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
2114		dol_print_error("Functions.lib::dol_print_date function called with a bad value from page ".$_SERVER["PHP_SELF"]);
2115		return '';
2116	} 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
2117		// This part of code should not be used anymore.
2118		dol_syslog("Functions.lib::dol_print_date function called with a bad value from page ".$_SERVER["PHP_SELF"], LOG_WARNING);
2119		//if (function_exists('debug_print_backtrace')) debug_print_backtrace();
2120		// Date has format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'
2121		$syear	= (!empty($reg[1]) ? $reg[1] : '');
2122		$smonth = (!empty($reg[2]) ? $reg[2] : '');
2123		$sday	= (!empty($reg[3]) ? $reg[3] : '');
2124		$shour	= (!empty($reg[4]) ? $reg[4] : '');
2125		$smin	= (!empty($reg[5]) ? $reg[5] : '');
2126		$ssec	= (!empty($reg[6]) ? $reg[6] : '');
2127
2128		$time = dol_mktime($shour, $smin, $ssec, $smonth, $sday, $syear, true);
2129		$ret = adodb_strftime($format, $time + $offsettz + $offsetdst, $to_gmt);
2130	} else {
2131		// Date is a timestamps
2132		if ($time < 100000000000) {	// Protection against bad date values
2133			$timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
2134
2135			$ret = adodb_strftime($format, $timetouse, $to_gmt);	// If to_gmt = false then adodb_strftime use TZ of server
2136		} else {
2137			$ret = 'Bad value '.$time.' for date';
2138		}
2139	}
2140
2141	if (preg_match('/__b__/i', $format)) {
2142		$timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
2143
2144		// Here ret is string in PHP setup language (strftime was used). Now we convert to $outputlangs.
2145		$month = adodb_strftime('%m', $timetouse, $to_gmt);		// If to_gmt = false then adodb_strftime use TZ of server
2146		$month = sprintf("%02d", $month); // $month may be return with format '06' on some installation and '6' on other, so we force it to '06'.
2147		if ($encodetooutput) {
2148			$monthtext = $outputlangs->transnoentities('Month'.$month);
2149			$monthtextshort = $outputlangs->transnoentities('MonthShort'.$month);
2150		} else {
2151			$monthtext = $outputlangs->transnoentitiesnoconv('Month'.$month);
2152			$monthtextshort = $outputlangs->transnoentitiesnoconv('MonthShort'.$month);
2153		}
2154		//print 'monthtext='.$monthtext.' monthtextshort='.$monthtextshort;
2155		$ret = str_replace('__b__', $monthtextshort, $ret);
2156		$ret = str_replace('__B__', $monthtext, $ret);
2157		//print 'x'.$outputlangs->charset_output.'-'.$ret.'x';
2158		//return $ret;
2159	}
2160	if (preg_match('/__a__/i', $format)) {
2161		//print "time=$time offsettz=$offsettz offsetdst=$offsetdst offsettzstring=$offsettzstring";
2162		$timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
2163
2164		$w = adodb_strftime('%w', $timetouse, $to_gmt);		// If to_gmt = false then adodb_strftime use TZ of server
2165		$dayweek = $outputlangs->transnoentitiesnoconv('Day'.$w);
2166		$ret = str_replace('__A__', $dayweek, $ret);
2167		$ret = str_replace('__a__', dol_substr($dayweek, 0, 3), $ret);
2168	}
2169
2170	return $ret;
2171}
2172
2173
2174/**
2175 *  Return an array with locale date info.
2176 *  WARNING: This function use PHP server timezone by default to return locale informations.
2177 *  Be aware to add the third parameter to "UTC" if you need to work on UTC.
2178 *
2179 *	@param	int			$timestamp      Timestamp
2180 *	@param	boolean		$fast           Fast mode. deprecated.
2181 *  @param	string		$forcetimezone	'' to use the PHP server timezone. Or use a form like 'Europe/Paris' or '+0200' to force timezone.
2182 *	@return	array						Array of informations
2183 *										'seconds' => $secs,
2184 *										'minutes' => $min,
2185 *										'hours' => $hour,
2186 *										'mday' => $day,
2187 *										'wday' => $dow,		0=sunday, 6=saturday
2188 *										'mon' => $month,
2189 *										'year' => $year,
2190 *										'yday' => floor($secsInYear/$_day_power)
2191 *										'0' => original timestamp
2192 * 	@see 								dol_print_date(), dol_stringtotime(), dol_mktime()
2193 */
2194function dol_getdate($timestamp, $fast = false, $forcetimezone = '')
2195{
2196	global $conf;
2197
2198	if (empty($conf->global->MAIN_USE_OLD_FUNCTIONS_FOR_GETDATE)) {
2199		//$datetimeobj = new DateTime('@'.$timestamp);
2200		$datetimeobj = new DateTime();
2201		$datetimeobj->setTimestamp($timestamp); // Use local PHP server timezone
2202		if ($forcetimezone) $datetimeobj->setTimezone(new DateTimeZone($forcetimezone)); //  (add timezone relative to the date entered)
2203		$arrayinfo = array(
2204			'year'=>((int) date_format($datetimeobj, 'Y')),
2205			'mon'=>((int) date_format($datetimeobj, 'm')),
2206			'mday'=>((int) date_format($datetimeobj, 'd')),
2207			'wday'=>((int) date_format($datetimeobj, 'w')),
2208			'yday'=>((int) date_format($datetimeobj, 'z')),
2209			'hours'=>((int) date_format($datetimeobj, 'H')),
2210			'minutes'=>((int) date_format($datetimeobj, 'i')),
2211			'seconds'=>((int) date_format($datetimeobj, 's')),
2212			'0'=>$timestamp
2213		);
2214	} else {
2215		// PHP getdate is restricted to the years 1901-2038 on Unix and 1970-2038 on Windows
2216		$usealternatemethod = false;
2217		if ($timestamp <= 0) $usealternatemethod = true; // <= 1970
2218		if ($timestamp >= 2145913200) $usealternatemethod = true; // >= 2038
2219
2220		if ($usealternatemethod)
2221		{
2222			$arrayinfo = adodb_getdate($timestamp, $fast);
2223		} else {
2224			$arrayinfo = getdate($timestamp);
2225		}
2226	}
2227
2228	return $arrayinfo;
2229}
2230
2231/**
2232 *	Return a timestamp date built from detailed informations (by default a local PHP server timestamp)
2233 * 	Replace function mktime not available under Windows if year < 1970
2234 *	PHP mktime is restricted to the years 1901-2038 on Unix and 1970-2038 on Windows
2235 *
2236 * 	@param	int			$hour			Hour	(can be -1 for undefined)
2237 *	@param	int			$minute			Minute	(can be -1 for undefined)
2238 *	@param	int			$second			Second	(can be -1 for undefined)
2239 *	@param	int			$month			Month (1 to 12)
2240 *	@param	int			$day			Day (1 to 31)
2241 *	@param	int			$year			Year
2242 *	@param	mixed		$gm				True or 1 or 'gmt'=Input informations are GMT values
2243 *										False or 0 or 'tzserver' = local to server TZ
2244 *										'auto'
2245 *										'tzuser' = local to user TZ taking dst into account at the current date. Not yet implemented.
2246 *										'tzuserrel' = local to user TZ taking dst into account at the given date. Use this one to convert date input from user.
2247 *										'tz,TimeZone' = use specified timezone
2248 *	@param	int			$check			0=No check on parameters (Can use day 32, etc...)
2249 *	@return	int|string					Date as a timestamp, '' or false if error
2250 * 	@see 								dol_print_date(), dol_stringtotime(), dol_getdate()
2251 */
2252function dol_mktime($hour, $minute, $second, $month, $day, $year, $gm = 'auto', $check = 1)
2253{
2254	global $conf;
2255	//print "- ".$hour.",".$minute.",".$second.",".$month.",".$day.",".$year.",".$_SERVER["WINDIR"]." -";
2256	//print 'gm:'.$gm.' gm==auto:'.($gm == 'auto').'<br>';
2257
2258	if ($gm === 'auto') {
2259		$gm = (empty($conf) ? 'tzserver' : $conf->tzuserinputkey);
2260	}
2261
2262	// Clean parameters
2263	if ($hour == -1 || empty($hour)) $hour = 0;
2264	if ($minute == -1 || empty($minute)) $minute = 0;
2265	if ($second == -1 || empty($second)) $second = 0;
2266
2267	// Check parameters
2268	if ($check)
2269	{
2270		if (!$month || !$day)  return '';
2271		if ($day > 31) return '';
2272		if ($month > 12) return '';
2273		if ($hour < 0 || $hour > 24) return '';
2274		if ($minute < 0 || $minute > 60) return '';
2275		if ($second < 0 || $second > 60) return '';
2276	}
2277
2278	if (empty($gm) || ($gm === 'server' || $gm === 'tzserver'))
2279	{
2280		$default_timezone = @date_default_timezone_get(); // Example 'Europe/Berlin'
2281		$localtz = new DateTimeZone($default_timezone);
2282	} elseif ($gm === 'user' || $gm === 'tzuser' || $gm === 'tzuserrel')
2283	{
2284		// We use dol_tz_string first because it is more reliable.
2285		$default_timezone = (empty($_SESSION["dol_tz_string"]) ? @date_default_timezone_get() : $_SESSION["dol_tz_string"]); // Example 'Europe/Berlin'
2286		try {
2287			$localtz = new DateTimeZone($default_timezone);
2288		} catch (Exception $e)
2289		{
2290			dol_syslog("Warning dol_tz_string contains an invalid value ".$_SESSION["dol_tz_string"], LOG_WARNING);
2291			$default_timezone = @date_default_timezone_get();
2292		}
2293	} elseif (strrpos($gm, "tz,") !== false)
2294	{
2295		$timezone = str_replace("tz,", "", $gm); // Example 'tz,Europe/Berlin'
2296		try {
2297			$localtz = new DateTimeZone($timezone);
2298		} catch (Exception $e)
2299		{
2300			dol_syslog("Warning passed timezone contains an invalid value ".$timezone, LOG_WARNING);
2301		}
2302	}
2303
2304	if (empty($localtz)) {
2305		$localtz = new DateTimeZone('UTC');
2306	}
2307	//var_dump($localtz);
2308	//var_dump($year.'-'.$month.'-'.$day.'-'.$hour.'-'.$minute);
2309	$dt = new DateTime(null, $localtz);
2310	$dt->setDate((int) $year, (int) $month, (int) $day);
2311	$dt->setTime((int) $hour, (int) $minute, (int) $second);
2312	$date = $dt->getTimestamp(); // should include daylight saving time
2313	//var_dump($date);
2314	return $date;
2315}
2316
2317
2318/**
2319 *  Return date for now. In most cases, we use this function without parameters (that means GMT time).
2320 *
2321 *  @param	string		$mode	'auto' => for backward compatibility (avoid this),
2322 *  							'gmt' => we return GMT timestamp,
2323 * 								'tzserver' => we add the PHP server timezone
2324 *  							'tzref' => we add the company timezone. Not implemented.
2325 * 								'tzuser' or 'tzuserrel' => we add the user timezone
2326 *	@return int   $date	Timestamp
2327 */
2328function dol_now($mode = 'auto')
2329{
2330	$ret = 0;
2331
2332	if ($mode === 'auto') {
2333		$mode = 'gmt';
2334	}
2335
2336	if ($mode == 'gmt') $ret = time(); // Time for now at greenwich.
2337	elseif ($mode == 'tzserver')		// Time for now with PHP server timezone added
2338	{
2339		require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
2340		$tzsecond = getServerTimeZoneInt('now'); // Contains tz+dayling saving time
2341		$ret = (int) (dol_now('gmt') + ($tzsecond * 3600));
2342	} /*elseif ($mode == 'tzref')				// Time for now with parent company timezone is added
2343	{
2344		require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
2345		$tzsecond=getParentCompanyTimeZoneInt();    // Contains tz+dayling saving time
2346		$ret=dol_now('gmt')+($tzsecond*3600);
2347	}*/
2348	elseif ($mode == 'tzuser' || $mode == 'tzuserrel')				// Time for now with user timezone added
2349	{
2350		//print 'time: '.time();
2351		$offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60;
2352		$offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60;
2353		$ret = (int) (dol_now('gmt') + ($offsettz + $offsetdst));
2354	}
2355
2356	return $ret;
2357}
2358
2359
2360/**
2361 * Return string with formated size
2362 *
2363 * @param	int		$size		Size to print
2364 * @param	int		$shortvalue	Tell if we want long value to use another unit (Ex: 1.5Kb instead of 1500b)
2365 * @param	int		$shortunit	Use short label of size unit (for example 'b' instead of 'bytes')
2366 * @return	string				Link
2367 */
2368function dol_print_size($size, $shortvalue = 0, $shortunit = 0)
2369{
2370	global $conf, $langs;
2371	$level = 1024;
2372
2373	if (!empty($conf->dol_optimize_smallscreen)) $shortunit = 1;
2374
2375	// Set value text
2376	if (empty($shortvalue) || $size < ($level * 10))
2377	{
2378		$ret = $size;
2379		$textunitshort = $langs->trans("b");
2380		$textunitlong = $langs->trans("Bytes");
2381	} else {
2382		$ret = round($size / $level, 0);
2383		$textunitshort = $langs->trans("Kb");
2384		$textunitlong = $langs->trans("KiloBytes");
2385	}
2386	// Use long or short text unit
2387	if (empty($shortunit)) { $ret .= ' '.$textunitlong; } else { $ret .= ' '.$textunitshort; }
2388
2389	return $ret;
2390}
2391
2392/**
2393 * Show Url link
2394 *
2395 * @param	string		$url		Url to show
2396 * @param	string		$target		Target for link
2397 * @param	int			$max		Max number of characters to show
2398 * @param	int			$withpicto	With picto
2399 * @return	string					HTML Link
2400 */
2401function dol_print_url($url, $target = '_blank', $max = 32, $withpicto = 0)
2402{
2403	global $langs;
2404
2405	if (empty($url)) return '';
2406
2407	$link = '<a href="';
2408	if (!preg_match('/^http/i', $url)) $link .= 'http://';
2409	$link .= $url;
2410	$link .= '"';
2411	if ($target) $link .= ' target="'.$target.'"';
2412	$link .= '>';
2413	if (!preg_match('/^http/i', $url)) $link .= 'http://';
2414	$link .= dol_trunc($url, $max);
2415	$link .= '</a>';
2416	return '<div class="nospan float" style="margin-right: 10px">'.($withpicto ?img_picto($langs->trans("Url"), 'globe').' ' : '').$link.'</div>';
2417}
2418
2419/**
2420 * Show EMail link formatted for HTML output.
2421 *
2422 * @param	string		$email			EMail to show (only email, without 'Name of recipient' before)
2423 * @param 	int			$cid 			Id of contact if known
2424 * @param 	int			$socid 			Id of third party if known
2425 * @param 	int			$addlink		0=no link, 1=email has a html email link (+ link to create action if constant AGENDA_ADDACTIONFOREMAIL is on)
2426 * @param	int			$max			Max number of characters to show
2427 * @param	int			$showinvalid	1=Show warning if syntax email is wrong
2428 * @param	int			$withpicto		Show picto
2429 * @return	string						HTML Link
2430 */
2431function dol_print_email($email, $cid = 0, $socid = 0, $addlink = 0, $max = 64, $showinvalid = 1, $withpicto = 0)
2432{
2433	global $conf, $user, $langs, $hookmanager;
2434
2435	$newemail = dol_escape_htmltag($email);
2436
2437	if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER) && $withpicto) $withpicto = 0;
2438
2439	if (empty($email)) return '&nbsp;';
2440
2441	if (!empty($addlink))
2442	{
2443		$newemail = '<a style="text-overflow: ellipsis;" href="';
2444		if (!preg_match('/^mailto:/i', $email)) $newemail .= 'mailto:';
2445		$newemail .= $email;
2446		$newemail .= '">';
2447		$newemail .= dol_trunc($email, $max);
2448		$newemail .= '</a>';
2449		if ($showinvalid && !isValidEmail($email))
2450		{
2451			$langs->load("errors");
2452			$newemail .= img_warning($langs->trans("ErrorBadEMail", $email));
2453		}
2454
2455		if (($cid || $socid) && !empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create)
2456		{
2457			$type = 'AC_EMAIL'; $link = '';
2458			if (!empty($conf->global->AGENDA_ADDACTIONFOREMAIL)) $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>';
2459			if ($link) $newemail = '<div>'.$newemail.' '.$link.'</div>';
2460		}
2461	} else {
2462		if ($showinvalid && !isValidEmail($email))
2463		{
2464			$langs->load("errors");
2465			$newemail .= img_warning($langs->trans("ErrorBadEMail", $email));
2466		}
2467	}
2468
2469	//$rep = '<div class="nospan" style="margin-right: 10px">';
2470	$rep = ($withpicto ? img_picto($langs->trans("EMail").' : '.$email, 'object_email.png').' ' : '').$newemail;
2471	//$rep .= '</div>';
2472	if ($hookmanager) {
2473		$parameters = array('cid' => $cid, 'socid' => $socid, 'addlink' => $addlink, 'picto' => $withpicto);
2474		$reshook = $hookmanager->executeHooks('printEmail', $parameters, $email);
2475		if ($reshook > 0) {
2476			$rep = '';
2477		}
2478		$rep .= $hookmanager->resPrint;
2479	}
2480
2481	return $rep;
2482}
2483
2484/**
2485 * Get array of social network dictionary
2486 *
2487 * @return  array       Array of Social Networks Dictionary
2488 */
2489function getArrayOfSocialNetworks()
2490{
2491	global $conf, $db;
2492	$sql = "SELECT rowid, code, label, url, icon, active FROM ".MAIN_DB_PREFIX."c_socialnetworks";
2493	$sql .= " WHERE entity=".$conf->entity;
2494	$socialnetworks = array();
2495	$resql = $db->query($sql);
2496	if ($resql) {
2497		while ($obj = $db->fetch_object($resql)) {
2498			$socialnetworks[$obj->code] = array(
2499				'rowid' => $obj->rowid,
2500				'label' => $obj->label,
2501				'url' => $obj->url,
2502				'icon' => $obj->icon,
2503				'active' => $obj->active,
2504			);
2505		}
2506	}
2507	return $socialnetworks;
2508}
2509
2510/**
2511 * Show social network link
2512 *
2513 * @param	string		$value				Skype to show (only skype, without 'Name of recipient' before)
2514 * @param	int 		$cid 				Id of contact if known
2515 * @param	int 		$socid 				Id of third party if known
2516 * @param	string 		$type				'skype','facebook',...
2517 * @param	array		$dictsocialnetworks socialnetworks availables
2518 * @return	string							HTML Link
2519 */
2520function dol_print_socialnetworks($value, $cid, $socid, $type, $dictsocialnetworks = array())
2521{
2522	global $conf, $user, $langs;
2523
2524	$htmllink = $value;
2525
2526	if (empty($value)) return '&nbsp;';
2527
2528	if (!empty($type)) {
2529		$htmllink = '<div class="divsocialnetwork inline-block valignmiddle">';
2530		// TODO use dictionary definition for picto $dictsocialnetworks[$type]['icon']
2531		$htmllink .= img_picto($langs->trans(dol_ucfirst($type)), $type.'.png', '', false, 0, 0, '', 'paddingright', 0);
2532		if ($type == 'skype') {
2533			$htmllink .= $value;
2534			$htmllink .= '&nbsp;';
2535			$htmllink .= '<a href="skype:';
2536			$htmllink .= $value;
2537			$htmllink .= '?call" alt="'.$langs->trans("Call").'&nbsp;'.$value.'" title="'.$langs->trans("Call").'&nbsp;'.$value.'">';
2538			$htmllink .= '<img src="'.DOL_URL_ROOT.'/theme/common/skype_callbutton.png" border="0">';
2539			$htmllink .= '</a><a href="skype:';
2540			$htmllink .= $value;
2541			$htmllink .= '?chat" alt="'.$langs->trans("Chat").'&nbsp;'.$value.'" title="'.$langs->trans("Chat").'&nbsp;'.$value.'">';
2542			$htmllink .= '<img class="paddingleft" src="'.DOL_URL_ROOT.'/theme/common/skype_chatbutton.png" border="0">';
2543			$htmllink .= '</a>';
2544			if (($cid || $socid) && !empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create) {
2545				$addlink = 'AC_SKYPE';
2546				$link = '';
2547				if (!empty($conf->global->AGENDA_ADDACTIONFORSKYPE)) $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>';
2548				$htmllink .= ($link ? ' '.$link : '');
2549			}
2550		} else {
2551			if (!empty($dictsocialnetworks[$type]['url'])) {
2552				$link = str_replace('{socialid}', $value, $dictsocialnetworks[$type]['url']);
2553				$htmllink .= '&nbsp;<a href="'.$link.'" target="_blank">'.$value.'</a>';
2554			} else {
2555				$htmllink .= $value;
2556			}
2557		}
2558		$htmllink .= '</div>';
2559	} else {
2560		$langs->load("errors");
2561		$htmllink .= img_warning($langs->trans("ErrorBadSocialNetworkValue", $value));
2562	}
2563	return $htmllink;
2564}
2565
2566/**
2567 * 	Format phone numbers according to country
2568 *
2569 * 	@param  string  $phone          Phone number to format
2570 * 	@param  string  $countrycode    Country code to use for formatting
2571 * 	@param 	int		$cid 		    Id of contact if known
2572 * 	@param 	int		$socid          Id of third party if known
2573 * 	@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)
2574 * 	@param 	string	$separ 		    Separation between numbers for a better visibility example : xx.xx.xx.xx.xx
2575 *  @param	string  $withpicto      Show picto
2576 *  @param	string	$titlealt	    Text to show on alt
2577 *  @param  int     $adddivfloat    Add div float around phone.
2578 * 	@return string 				    Formated phone number
2579 */
2580function dol_print_phone($phone, $countrycode = '', $cid = 0, $socid = 0, $addlink = '', $separ = "&nbsp;", $withpicto = '', $titlealt = '', $adddivfloat = 0)
2581{
2582	global $conf, $user, $langs, $mysoc, $hookmanager;
2583
2584	// Clean phone parameter
2585	$phone = preg_replace("/[\s.-]/", "", trim($phone));
2586	if (empty($phone)) { return ''; }
2587	if (!empty($conf->global->MAIN_PHONE_SEPAR)) $separ = $conf->global->MAIN_PHONE_SEPAR;
2588	if (empty($countrycode)) $countrycode = $mysoc->country_code;
2589
2590	// Short format for small screens
2591	if ($conf->dol_optimize_smallscreen) $separ = '';
2592
2593	$newphone = $phone;
2594	if (strtoupper($countrycode) == "FR")
2595	{
2596		// France
2597		if (dol_strlen($phone) == 10) {
2598			$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);
2599		} elseif (dol_strlen($phone) == 7)
2600		{
2601			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 2).$separ.substr($newphone, 5, 2);
2602		} elseif (dol_strlen($phone) == 9)
2603		{
2604			$newphone = substr($newphone, 0, 2).$separ.substr($newphone, 2, 3).$separ.substr($newphone, 5, 2).$separ.substr($newphone, 7, 2);
2605		} elseif (dol_strlen($phone) == 11)
2606		{
2607			$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);
2608		} elseif (dol_strlen($phone) == 12)
2609		{
2610			$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);
2611		}
2612	} elseif (strtoupper($countrycode) == "CA")
2613	{
2614		if (dol_strlen($phone) == 10) {
2615			$newphone = ($separ != '' ? '(' : '').substr($newphone, 0, 3).($separ != '' ? ')' : '').$separ.substr($newphone, 3, 3).($separ != '' ? '-' : '').substr($newphone, 6, 4);
2616		}
2617	} elseif (strtoupper($countrycode) == "PT")
2618	{//Portugal
2619		if (dol_strlen($phone) == 13)
2620		{//ex: +351_ABC_DEF_GHI
2621			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
2622		}
2623	} elseif (strtoupper($countrycode) == "SR")
2624	{//Suriname
2625		if (dol_strlen($phone) == 10)
2626		{//ex: +597_ABC_DEF
2627			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3);
2628		} elseif (dol_strlen($phone) == 11)
2629		{//ex: +597_ABC_DEFG
2630			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 4);
2631		}
2632	} elseif (strtoupper($countrycode) == "DE")
2633	{//Allemagne
2634		if (dol_strlen($phone) == 14)
2635		{//ex:  +49_ABCD_EFGH_IJK
2636			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 4).$separ.substr($newphone, 7, 4).$separ.substr($newphone, 11, 3);
2637		} elseif (dol_strlen($phone) == 13)
2638		{//ex: +49_ABC_DEFG_HIJ
2639			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 4).$separ.substr($newphone, 10, 3);
2640		}
2641	} elseif (strtoupper($countrycode) == "ES")
2642	{//Espagne
2643		if (dol_strlen($phone) == 12)
2644		{//ex:  +34_ABC_DEF_GHI
2645			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
2646		}
2647	} elseif (strtoupper($countrycode) == "BF")
2648	{// Burkina Faso
2649		if (dol_strlen($phone) == 12)
2650		{//ex :  +22 A BC_DE_FG_HI
2651			$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);
2652		}
2653	} elseif (strtoupper($countrycode) == "RO")
2654	{// Roumanie
2655		if (dol_strlen($phone) == 12)
2656		{//ex :  +40 AB_CDE_FG_HI
2657			$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);
2658		}
2659	} elseif (strtoupper($countrycode) == "TR")
2660	{//Turquie
2661		if (dol_strlen($phone) == 13)
2662		{//ex :  +90 ABC_DEF_GHIJ
2663			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 4);
2664		}
2665	} elseif (strtoupper($countrycode) == "US")
2666	{//Etat-Unis
2667		if (dol_strlen($phone) == 12)
2668		{//ex: +1 ABC_DEF_GHIJ
2669			$newphone = substr($newphone, 0, 2).$separ.substr($newphone, 2, 3).$separ.substr($newphone, 5, 3).$separ.substr($newphone, 8, 4);
2670		}
2671	} elseif (strtoupper($countrycode) == "MX")
2672	{//Mexique
2673		if (dol_strlen($phone) == 12)
2674		{//ex: +52 ABCD_EFG_HI
2675			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 4).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 2);
2676		} elseif (dol_strlen($phone) == 11)
2677		{//ex: +52 AB_CD_EF_GH
2678			$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);
2679		} elseif (dol_strlen($phone) == 13)
2680		{//ex: +52 ABC_DEF_GHIJ
2681			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 4);
2682		}
2683	} elseif (strtoupper($countrycode) == "ML")
2684	{//Mali
2685		if (dol_strlen($phone) == 12)
2686		{//ex: +223 AB_CD_EF_GH
2687			$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);
2688		}
2689	} elseif (strtoupper($countrycode) == "TH")
2690	{//Thaïlande
2691		if (dol_strlen($phone) == 11)
2692		{//ex: +66_ABC_DE_FGH
2693			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 2).$separ.substr($newphone, 8, 3);
2694		} elseif (dol_strlen($phone) == 12)
2695		{//ex: +66_A_BCD_EF_GHI
2696			$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);
2697		}
2698	} elseif (strtoupper($countrycode) == "MU")
2699	{
2700		//Maurice
2701		if (dol_strlen($phone) == 11)
2702		{//ex: +230_ABC_DE_FG
2703			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 2).$separ.substr($newphone, 9, 2);
2704		} elseif (dol_strlen($phone) == 12)
2705		{//ex: +230_ABCD_EF_GH
2706			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 4).$separ.substr($newphone, 8, 2).$separ.substr($newphone, 10, 2);
2707		}
2708	} elseif (strtoupper($countrycode) == "ZA")
2709	{//Afrique du sud
2710		if (dol_strlen($phone) == 12)
2711		{//ex: +27_AB_CDE_FG_HI
2712			$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);
2713		}
2714	} elseif (strtoupper($countrycode) == "SY")
2715	{//Syrie
2716		if (dol_strlen($phone) == 12)
2717		{//ex: +963_AB_CD_EF_GH
2718			$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);
2719		} elseif (dol_strlen($phone) == 13)
2720		{//ex: +963_AB_CD_EF_GHI
2721			$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);
2722		}
2723	} elseif (strtoupper($countrycode) == "AE")
2724	{//Emirats Arabes Unis
2725		if (dol_strlen($phone) == 12)
2726		{//ex: +971_ABC_DEF_GH
2727			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 2);
2728		} elseif (dol_strlen($phone) == 13)
2729		{//ex: +971_ABC_DEF_GHI
2730			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
2731		} elseif (dol_strlen($phone) == 14)
2732		{//ex: +971_ABC_DEF_GHIK
2733			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 4);
2734		}
2735	} elseif (strtoupper($countrycode) == "DZ")
2736	{//Algérie
2737		if (dol_strlen($phone) == 13)
2738		{//ex: +213_ABC_DEF_GHI
2739			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
2740		}
2741	} elseif (strtoupper($countrycode) == "BE")
2742	{//Belgique
2743		if (dol_strlen($phone) == 11)
2744		{//ex: +32_ABC_DE_FGH
2745			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 2).$separ.substr($newphone, 8, 3);
2746		} elseif (dol_strlen($phone) == 12)
2747		{//ex: +32_ABC_DEF_GHI
2748			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
2749		}
2750	} elseif (strtoupper($countrycode) == "PF")
2751	{//Polynésie française
2752		if (dol_strlen($phone) == 12)
2753		{//ex: +689_AB_CD_EF_GH
2754			$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);
2755		}
2756	} elseif (strtoupper($countrycode) == "CO")
2757	{//Colombie
2758		if (dol_strlen($phone) == 13)
2759		{//ex: +57_ABC_DEF_GH_IJ
2760			$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);
2761		}
2762	} elseif (strtoupper($countrycode) == "JO")
2763	{//Jordanie
2764		if (dol_strlen($phone) == 12)
2765		{//ex: +962_A_BCD_EF_GH
2766			$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);
2767		}
2768	} elseif (strtoupper($countrycode) == "JM")
2769	{//Jamaïque
2770		if (dol_strlen($newphone) == 12)
2771		{//ex: +1867_ABC_DEFG
2772			$newphone = substr($newphone, 0, 5).$separ.substr($newphone, 5, 3).$separ.substr($newphone, 8, 4);
2773		}
2774	} elseif (strtoupper($countrycode) == "MG")
2775	{//Madagascar
2776		if (dol_strlen($phone) == 13)
2777		{//ex: +261_AB_CD_EF_GHI
2778			$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);
2779		}
2780	} elseif (strtoupper($countrycode) == "GB")
2781	{//Royaume uni
2782		if (dol_strlen($phone) == 13)
2783		{//ex: +44_ABCD_EFG_HIJ
2784			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 4).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
2785		}
2786	} elseif (strtoupper($countrycode) == "CH")
2787	{//Suisse
2788		if (dol_strlen($phone) == 12)
2789		{//ex: +41_AB_CDE_FG_HI
2790			$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);
2791		} elseif (dol_strlen($phone) == 15)
2792		{// +41_AB_CDE_FGH_IJKL
2793			$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);
2794		}
2795	} elseif (strtoupper($countrycode) == "TN")
2796	{//Tunisie
2797		if (dol_strlen($phone) == 12)
2798		{//ex: +216_AB_CDE_FGH
2799			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 2).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
2800		}
2801	} elseif (strtoupper($countrycode) == "GF")
2802	{//Guyane francaise
2803		if (dol_strlen($phone) == 13)
2804		{//ex: +594_ABC_DE_FG_HI  (ABC=594 de nouveau)
2805			$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);
2806		}
2807	} elseif (strtoupper($countrycode) == "GP")
2808	{//Guadeloupe
2809		if (dol_strlen($phone) == 13)
2810		{//ex: +590_ABC_DE_FG_HI  (ABC=590 de nouveau)
2811			$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);
2812		}
2813	} elseif (strtoupper($countrycode) == "MQ")
2814	{//Martinique
2815		if (dol_strlen($phone) == 13)
2816		{//ex: +596_ABC_DE_FG_HI  (ABC=596 de nouveau)
2817			$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);
2818		}
2819	} elseif (strtoupper($countrycode) == "IT")
2820	{//Italie
2821		if (dol_strlen($phone) == 12)
2822		{//ex: +39_ABC_DEF_GHI
2823			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
2824		} elseif (dol_strlen($phone) == 13)
2825		{//ex: +39_ABC_DEF_GH_IJ
2826			$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);
2827		}
2828	} elseif (strtoupper($countrycode) == "AU")
2829	{
2830		//Australie
2831		if (dol_strlen($phone) == 12)
2832		{
2833			//ex: +61_A_BCDE_FGHI
2834			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 1).$separ.substr($newphone, 4, 4).$separ.substr($newphone, 8, 4);
2835		}
2836	}
2837	if (!empty($addlink))	// Link on phone number (+ link to add action if conf->global->AGENDA_ADDACTIONFORPHONE set)
2838	{
2839		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
2840		{
2841			$newphoneform = $newphone;
2842			$newphone = '<a href="tel:'.$phone.'"';
2843			$newphone .= '>'.$newphoneform.'</a>';
2844		} elseif (!empty($conf->clicktodial->enabled) && $addlink == 'AC_TEL')		// If click to dial, we use click to dial url
2845		{
2846			if (empty($user->clicktodial_loaded)) $user->fetch_clicktodial();
2847
2848			// Define urlmask
2849			$urlmask = 'ErrorClickToDialModuleNotConfigured';
2850			if (!empty($conf->global->CLICKTODIAL_URL)) $urlmask = $conf->global->CLICKTODIAL_URL;
2851			if (!empty($user->clicktodial_url)) $urlmask = $user->clicktodial_url;
2852
2853			$clicktodial_poste = (!empty($user->clicktodial_poste) ?urlencode($user->clicktodial_poste) : '');
2854			$clicktodial_login = (!empty($user->clicktodial_login) ?urlencode($user->clicktodial_login) : '');
2855			$clicktodial_password = (!empty($user->clicktodial_password) ?urlencode($user->clicktodial_password) : '');
2856			// This line is for backward compatibility
2857			$url = sprintf($urlmask, urlencode($phone), $clicktodial_poste, $clicktodial_login, $clicktodial_password);
2858			// Thoose lines are for substitution
2859			$substitarray = array('__PHONEFROM__'=>$clicktodial_poste,
2860								'__PHONETO__'=>urlencode($phone),
2861								'__LOGIN__'=>$clicktodial_login,
2862								'__PASS__'=>$clicktodial_password);
2863			$url = make_substitutions($url, $substitarray);
2864			$newphonesav = $newphone;
2865			$newphone = '<a href="'.$url.'"';
2866			if (!empty($conf->global->CLICKTODIAL_FORCENEWTARGET)) $newphone .= ' target="_blank"';
2867			$newphone .= '>'.$newphonesav.'</a>';
2868		}
2869
2870		//if (($cid || $socid) && ! empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create)
2871		if (!empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create)
2872		{
2873			$type = 'AC_TEL'; $link = '';
2874			if ($addlink == 'AC_FAX') $type = 'AC_FAX';
2875			if (!empty($conf->global->AGENDA_ADDACTIONFORPHONE)) $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>';
2876			if ($link) $newphone = '<div>'.$newphone.' '.$link.'</div>';
2877		}
2878	}
2879
2880	if (empty($titlealt))
2881	{
2882		$titlealt = ($withpicto == 'fax' ? $langs->trans("Fax") : $langs->trans("Phone"));
2883	}
2884	$rep = '';
2885
2886	if ($hookmanager) {
2887		$parameters = array('countrycode' => $countrycode, 'cid' => $cid, 'socid' => $socid, 'titlealt' => $titlealt, 'picto' => $withpicto);
2888		$reshook = $hookmanager->executeHooks('printPhone', $parameters, $phone);
2889		$rep .= $hookmanager->resPrint;
2890	}
2891	if (empty($reshook))
2892	{
2893		$picto = '';
2894		if ($withpicto) {
2895			if ($withpicto == 'fax') {
2896				$picto = 'phoning_fax';
2897			} elseif ($withpicto == 'phone') {
2898				$picto = 'phoning';
2899			} elseif ($withpicto == 'mobile') {
2900				$picto = 'phoning_mobile';
2901			} else {
2902				$picto = '';
2903			}
2904		}
2905		if ($adddivfloat) $rep .= '<div class="nospan float" style="margin-right: 10px">';
2906		else $rep .= '<span style="margin-right: 10px;">';
2907		$rep .= ($withpicto ?img_picto($titlealt, 'object_'.$picto.'.png').' ' : '').$newphone;
2908		if ($adddivfloat) $rep .= '</div>';
2909		else $rep .= '</span>';
2910	}
2911
2912	return $rep;
2913}
2914
2915/**
2916 * 	Return an IP formated to be shown on screen
2917 *
2918 * 	@param	string	$ip			IP
2919 * 	@param	int		$mode		0=return IP + country/flag, 1=return only country/flag, 2=return only IP
2920 * 	@return string 				Formated IP, with country if GeoIP module is enabled
2921 */
2922function dol_print_ip($ip, $mode = 0)
2923{
2924	global $conf, $langs;
2925
2926	$ret = '';
2927
2928	if (empty($mode)) $ret .= $ip;
2929
2930	if ($mode != 2)
2931	{
2932		$countrycode = dolGetCountryCodeFromIp($ip);
2933		if ($countrycode)	// If success, countrycode is us, fr, ...
2934		{
2935			if (file_exists(DOL_DOCUMENT_ROOT.'/theme/common/flags/'.$countrycode.'.png'))
2936			{
2937				$ret .= ' '.img_picto($countrycode.' '.$langs->trans("AccordingToGeoIPDatabase"), DOL_URL_ROOT.'/theme/common/flags/'.$countrycode.'.png', '', 1);
2938			} else $ret .= ' ('.$countrycode.')';
2939		} else {
2940			// Nothing
2941		}
2942	}
2943
2944	return $ret;
2945}
2946
2947/**
2948 * Return the IP of remote user.
2949 * Take HTTP_X_FORWARDED_FOR (defined when using proxy)
2950 * Then HTTP_CLIENT_IP if defined (rare)
2951 * Then REMOTE_ADDR (no way to be modified by user but may be wrong if user is using a proxy)
2952 *
2953 * @return	string		Ip of remote user.
2954 */
2955function getUserRemoteIP()
2956{
2957	if (empty($_SERVER['HTTP_X_FORWARDED_FOR']) || preg_match('/[^0-9\.\:,\[\]]/', $_SERVER['HTTP_X_FORWARDED_FOR'])) {
2958		if (empty($_SERVER['HTTP_CLIENT_IP']) || preg_match('/[^0-9\.\:,\[\]]/', $_SERVER['HTTP_CLIENT_IP'])) {
2959			if (empty($_SERVER["HTTP_CF_CONNECTING_IP"])) {
2960				$ip = (empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR']);	// value may have been forged by client
2961			} else {
2962				$ip = $_SERVER["HTTP_CF_CONNECTING_IP"];	// value here may have been forged by client
2963			}
2964		} else {
2965			$ip = $_SERVER['HTTP_CLIENT_IP']; // value is clean here but may have been forged by proxy
2966		}
2967	} else {
2968		$ip = $_SERVER['HTTP_X_FORWARDED_FOR']; // value is clean here but may have been forged by proxy
2969	}
2970	return $ip;
2971}
2972
2973/**
2974 * Return if we are using a HTTPS connexion
2975 * Check HTTPS (no way to be modified by user but may be empty or wrong if user is using a proxy)
2976 * Take HTTP_X_FORWARDED_PROTO (defined when using proxy)
2977 * Then HTTP_X_FORWARDED_SSL
2978 *
2979 * @return	boolean		True if user is using HTTPS
2980 */
2981function isHTTPS()
2982{
2983	$isSecure = false;
2984	if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
2985		$isSecure = true;
2986	}
2987	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') {
2988		$isSecure = true;
2989	}
2990	return $isSecure;
2991}
2992
2993/**
2994 * 	Return a country code from IP. Empty string if not found.
2995 *
2996 * 	@param	string	$ip			IP
2997 * 	@return string 				Country code ('us', 'fr', ...)
2998 */
2999function dolGetCountryCodeFromIp($ip)
3000{
3001	global $conf;
3002
3003	$countrycode = '';
3004
3005	if (!empty($conf->geoipmaxmind->enabled))
3006	{
3007		$datafile = $conf->global->GEOIPMAXMIND_COUNTRY_DATAFILE;
3008		//$ip='24.24.24.24';
3009		//$datafile='/usr/share/GeoIP/GeoIP.dat';    Note that this must be downloaded datafile (not same than datafile provided with ubuntu packages)
3010		include_once DOL_DOCUMENT_ROOT.'/core/class/dolgeoip.class.php';
3011		$geoip = new DolGeoIP('country', $datafile);
3012		//print 'ip='.$ip.' databaseType='.$geoip->gi->databaseType." GEOIP_CITY_EDITION_REV1=".GEOIP_CITY_EDITION_REV1."\n";
3013		$countrycode = $geoip->getCountryCodeFromIP($ip);
3014	}
3015
3016	return $countrycode;
3017}
3018
3019
3020/**
3021 *  Return country code for current user.
3022 *  If software is used inside a local network, detection may fails (we need a public ip)
3023 *
3024 *  @return     string      Country code (fr, es, it, us, ...)
3025 */
3026function dol_user_country()
3027{
3028	global $conf, $langs, $user;
3029
3030	//$ret=$user->xxx;
3031	$ret = '';
3032	if (!empty($conf->geoipmaxmind->enabled))
3033	{
3034		$ip = getUserRemoteIP();
3035		$datafile = $conf->global->GEOIPMAXMIND_COUNTRY_DATAFILE;
3036		//$ip='24.24.24.24';
3037		//$datafile='E:\Mes Sites\Web\Admin1\awstats\maxmind\GeoIP.dat';
3038		include_once DOL_DOCUMENT_ROOT.'/core/class/dolgeoip.class.php';
3039		$geoip = new DolGeoIP('country', $datafile);
3040		$countrycode = $geoip->getCountryCodeFromIP($ip);
3041		$ret = $countrycode;
3042	}
3043	return $ret;
3044}
3045
3046/**
3047 *  Format address string
3048 *
3049 *  @param	string	$address    Address string, already formatted with dol_format_address()
3050 *  @param  int		$htmlid     Html ID (for example 'gmap')
3051 *  @param  int		$element    'thirdparty'|'contact'|'member'|'other'
3052 *  @param  int		$id         Id of object
3053 *  @param	int		$noprint	No output. Result is the function return
3054 *  @param  string  $charfornl  Char to use instead of nl2br. '' means we use a standad nl2br.
3055 *  @return string|void			Nothing if noprint is 0, formatted address if noprint is 1
3056 *  @see dol_format_address()
3057 */
3058function dol_print_address($address, $htmlid, $element, $id, $noprint = 0, $charfornl = '')
3059{
3060	global $conf, $user, $langs, $hookmanager;
3061
3062	$out = '';
3063
3064	if ($address)
3065	{
3066		if ($hookmanager) {
3067			$parameters = array('element' => $element, 'id' => $id);
3068			$reshook = $hookmanager->executeHooks('printAddress', $parameters, $address);
3069			$out .= $hookmanager->resPrint;
3070		}
3071		if (empty($reshook))
3072		{
3073			if (empty($charfornl)) $out .= nl2br($address);
3074			else $out .= preg_replace('/[\r\n]+/', $charfornl, $address);
3075
3076			// TODO Remove this block, we can add this using the hook now
3077			$showgmap = $showomap = 0;
3078			if (($element == 'thirdparty' || $element == 'societe') && !empty($conf->google->enabled) && !empty($conf->global->GOOGLE_ENABLE_GMAPS)) $showgmap = 1;
3079			if ($element == 'contact' && !empty($conf->google->enabled) && !empty($conf->global->GOOGLE_ENABLE_GMAPS_CONTACTS)) $showgmap = 1;
3080			if ($element == 'member' && !empty($conf->google->enabled) && !empty($conf->global->GOOGLE_ENABLE_GMAPS_MEMBERS)) $showgmap = 1;
3081			if (($element == 'thirdparty' || $element == 'societe') && !empty($conf->openstreetmap->enabled) && !empty($conf->global->OPENSTREETMAP_ENABLE_MAPS)) $showomap = 1;
3082			if ($element == 'contact' && !empty($conf->openstreetmap->enabled) && !empty($conf->global->OPENSTREETMAP_ENABLE_MAPS_CONTACTS)) $showomap = 1;
3083			if ($element == 'member' && !empty($conf->openstreetmap->enabled) && !empty($conf->global->OPENSTREETMAP_ENABLE_MAPS_MEMBERS)) $showomap = 1;
3084			if ($showgmap)
3085			{
3086				$url = dol_buildpath('/google/gmaps.php?mode='.$element.'&id='.$id, 1);
3087				$out .= ' <a href="'.$url.'" target="_gmaps"><img id="'.$htmlid.'" class="valigntextbottom" src="'.DOL_URL_ROOT.'/theme/common/gmap.png"></a>';
3088			}
3089			if ($showomap)
3090			{
3091				$url = dol_buildpath('/openstreetmap/maps.php?mode='.$element.'&id='.$id, 1);
3092				$out .= ' <a href="'.$url.'" target="_gmaps"><img id="'.$htmlid.'_openstreetmap" class="valigntextbottom" src="'.DOL_URL_ROOT.'/theme/common/gmap.png"></a>';
3093			}
3094		}
3095	}
3096	if ($noprint) return $out;
3097	else print $out;
3098}
3099
3100
3101/**
3102 *	Return true if email syntax is ok.
3103 *
3104 *	@param	    string		$address    			email (Ex: "toto@examle.com". Long form "John Do <johndo@example.com>" will be false)
3105 *  @param		int			$acceptsupervisorkey	If 1, the special string '__SUPERVISOREMAIL__' is also accepted as valid
3106 *	@return     boolean     						true if email syntax is OK, false if KO or empty string
3107 *  @see isValidMXRecord()
3108 */
3109function isValidEmail($address, $acceptsupervisorkey = 0)
3110{
3111	if ($acceptsupervisorkey && $address == '__SUPERVISOREMAIL__') return true;
3112	if (filter_var($address, FILTER_VALIDATE_EMAIL)) return true;
3113
3114	return false;
3115}
3116
3117/**
3118 *	Return if the domain name has a valid MX record.
3119 *  WARNING: This need function idn_to_ascii, checkdnsrr and getmxrr
3120 *
3121 *	@param	    string		$domain	    			Domain name (Ex: "yahoo.com", "yhaoo.com", "dolibarr.fr")
3122 *	@return     int     							-1 if error (function not available), 0=Not valid, 1=Valid
3123 *  @see isValidEmail()
3124 */
3125function isValidMXRecord($domain)
3126{
3127	if (function_exists('idn_to_ascii') && function_exists('checkdnsrr'))
3128	{
3129		if (!checkdnsrr(idn_to_ascii($domain), 'MX'))
3130		{
3131			return 0;
3132		}
3133		if (function_exists('getmxrr'))
3134		{
3135			$mxhosts = array();
3136			$weight = array();
3137			getmxrr(idn_to_ascii($domain), $mxhosts, $weight);
3138			if (count($mxhosts) > 1) return 1;
3139			if (count($mxhosts) == 1 && !empty($mxhosts[0])) return 1;
3140
3141			return 0;
3142		}
3143	}
3144	return -1;
3145}
3146
3147/**
3148 *  Return true if phone number syntax is ok
3149 *  TODO Decide what to do with this
3150 *
3151 *  @param	string		$phone		phone (Ex: "0601010101")
3152 *  @return boolean     			true if phone syntax is OK, false if KO or empty string
3153 */
3154function isValidPhone($phone)
3155{
3156	return true;
3157}
3158
3159
3160/**
3161 * Make a strlen call. Works even if mbstring module not enabled
3162 *
3163 * @param   string		$string				String to calculate length
3164 * @param   string		$stringencoding		Encoding of string
3165 * @return  int								Length of string
3166 */
3167function dol_strlen($string, $stringencoding = 'UTF-8')
3168{
3169	if (function_exists('mb_strlen')) return mb_strlen($string, $stringencoding);
3170	else return strlen($string);
3171}
3172
3173/**
3174 * Make a substring. Works even if mbstring module is not enabled for better compatibility.
3175 *
3176 * @param	string	$string				String to scan
3177 * @param	string	$start				Start position
3178 * @param	int		$length				Length (in nb of characters or nb of bytes depending on trunconbytes param)
3179 * @param   string	$stringencoding		Page code used for input string encoding
3180 * @param	int		$trunconbytes		1=Length is max of bytes instead of max of characters
3181 * @return  string						substring
3182 */
3183function dol_substr($string, $start, $length, $stringencoding = '', $trunconbytes = 0)
3184{
3185	global $langs;
3186
3187	if (empty($stringencoding)) $stringencoding = $langs->charset_output;
3188
3189	$ret = '';
3190	if (empty($trunconbytes))
3191	{
3192		if (function_exists('mb_substr'))
3193		{
3194			$ret = mb_substr($string, $start, $length, $stringencoding);
3195		} else {
3196			$ret = substr($string, $start, $length);
3197		}
3198	} else {
3199		if (function_exists('mb_strcut'))
3200		{
3201			$ret = mb_strcut($string, $start, $length, $stringencoding);
3202		} else {
3203			$ret = substr($string, $start, $length);
3204		}
3205	}
3206	return $ret;
3207}
3208
3209
3210/**
3211 *	Truncate a string to a particular length adding '...' if string larger than length.
3212 * 	If length = max length+1, we do no truncate to avoid having just 1 char replaced with '...'.
3213 *  MAIN_DISABLE_TRUNC=1 can disable all truncings
3214 *
3215 *	@param	string	$string				String to truncate
3216 *	@param  int		$size				Max string size visible (excluding ...). 0 for no limit. WARNING: Final string size can have 3 more chars (if we added ..., or if size was max+1 or max+2 or max+3 so it does not worse to replace with ...)
3217 *	@param	string	$trunc				Where to trunc: 'right', 'left', 'middle' (size must be a 2 power), 'wrap'
3218 * 	@param	string	$stringencoding		Tell what is source string encoding
3219 *  @param	int		$nodot				Truncation do not add ... after truncation. So it's an exact truncation.
3220 *  @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)
3221 *	@return string						Truncated string. WARNING: length is never higher than $size if $nodot is set, but can be 3 chars higher otherwise.
3222 */
3223function dol_trunc($string, $size = 40, $trunc = 'right', $stringencoding = 'UTF-8', $nodot = 0, $display = 0)
3224{
3225	global $conf;
3226
3227	if ($size == 0 || !empty($conf->global->MAIN_DISABLE_TRUNC)) return $string;
3228
3229	if (empty($stringencoding)) $stringencoding = 'UTF-8';
3230	// reduce for small screen
3231	if ($conf->dol_optimize_smallscreen == 1 && $display == 1) $size = round($size / 3);
3232
3233	// We go always here
3234	if ($trunc == 'right')
3235	{
3236		$newstring = dol_textishtml($string) ?dol_string_nohtmltag($string, 1) : $string;
3237		if (dol_strlen($newstring, $stringencoding) > ($size + ($nodot ? 0 : 3)))    // If nodot is 0 and size is 1,2 or 3 chars more, we don't trunc and don't add ...
3238		return dol_substr($newstring, 0, $size, $stringencoding).($nodot ? '' : '...');
3239		else //return 'u'.$size.'-'.$newstring.'-'.dol_strlen($newstring,$stringencoding).'-'.$string;
3240		return $string;
3241	} elseif ($trunc == 'middle')
3242	{
3243		$newstring = dol_textishtml($string) ?dol_string_nohtmltag($string, 1) : $string;
3244		if (dol_strlen($newstring, $stringencoding) > 2 && dol_strlen($newstring, $stringencoding) > ($size + 1))
3245		{
3246			$size1 = round($size / 2);
3247			$size2 = round($size / 2);
3248			return dol_substr($newstring, 0, $size1, $stringencoding).'...'.dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size2, $size2, $stringencoding);
3249		} else return $string;
3250	} elseif ($trunc == 'left')
3251	{
3252		$newstring = dol_textishtml($string) ?dol_string_nohtmltag($string, 1) : $string;
3253		if (dol_strlen($newstring, $stringencoding) > ($size + ($nodot ? 0 : 3)))    // If nodot is 0 and size is 1,2 or 3 chars more, we don't trunc and don't add ...
3254		return '...'.dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size, $size, $stringencoding);
3255		else return $string;
3256	} elseif ($trunc == 'wrap')
3257	{
3258		$newstring = dol_textishtml($string) ?dol_string_nohtmltag($string, 1) : $string;
3259		if (dol_strlen($newstring, $stringencoding) > ($size + 1))
3260		return dol_substr($newstring, 0, $size, $stringencoding)."\n".dol_trunc(dol_substr($newstring, $size, dol_strlen($newstring, $stringencoding) - $size, $stringencoding), $size, $trunc);
3261		else return $string;
3262	} else return 'BadParam3CallingDolTrunc';
3263}
3264
3265/**
3266 *	Show picto whatever it's its name (generic function)
3267 *
3268 *	@param      string		$titlealt         		Text on title tag for tooltip. Not used if param notitle is set to 1.
3269 *	@param      string		$picto       			Name of image file to show ('filenew', ...)
3270 *													If no extension provided, we use '.png'. Image must be stored into theme/xxx/img directory.
3271 *                                  				Example: picto.png                  if picto.png is stored into htdocs/theme/mytheme/img
3272 *                                  				Example: picto.png@mymodule         if picto.png is stored into htdocs/mymodule/img
3273 *                                  				Example: /mydir/mysubdir/picto.png  if picto.png is stored into htdocs/mydir/mysubdir (pictoisfullpath must be set to 1)
3274 *	@param		string		$moreatt				Add more attribute on img tag (For example 'style="float: right"')
3275 *	@param		boolean|int	$pictoisfullpath		If true or 1, image path is a full path
3276 *	@param		int			$srconly				Return only content of the src attribute of img.
3277 *  @param		int			$notitle				1=Disable tag title. Use it if you add js tooltip, to avoid duplicate tooltip.
3278 *  @param		string		$alt					Force alt for bind people
3279 *  @param		string		$morecss				Add more class css on img tag (For example 'myclascss').
3280 *  @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.
3281 *  @return     string       				    	Return img tag
3282 *  @see        img_object(), img_picto_common()
3283 */
3284function img_picto($titlealt, $picto, $moreatt = '', $pictoisfullpath = false, $srconly = 0, $notitle = 0, $alt = '', $morecss = '', $marginleftonlyshort = 2)
3285{
3286	global $conf, $langs;
3287
3288	// We forge fullpathpicto for image to $path/img/$picto. By default, we take DOL_URL_ROOT/theme/$conf->theme/img/$picto
3289	$url = DOL_URL_ROOT;
3290	$theme = isset($conf->theme) ? $conf->theme : null;
3291	$path = 'theme/'.$theme;
3292	// Define fullpathpicto to use into src
3293	if ($pictoisfullpath) {
3294		// Clean parameters
3295		if (!preg_match('/(\.png|\.gif|\.svg)$/i', $picto)) {
3296			$picto .= '.png';
3297		}
3298		$fullpathpicto = $picto;
3299		$reg = array();
3300		if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
3301			$morecss .= ($morecss ? ' ' : '').$reg[1];
3302			$moreatt = str_replace('class="'.$reg[1].'"', '', $moreatt);
3303		}
3304	} else {
3305		$pictowithouttext = preg_replace('/(\.png|\.gif|\.svg)$/', '', $picto);
3306		if (empty($srconly) && in_array($pictowithouttext, array(
3307				'1downarrow', '1uparrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected',
3308				'accountancy', 'account', 'accountline', 'action', 'add', 'address', 'bank_account', 'barcode', 'bank', 'bill', 'billa', 'billr', 'billd', 'bookmark', 'bom', 'building',
3309				'cash-register', 'category', 'check', 'clock', 'close_title', 'company', 'contact', 'contract', 'cron', 'cubes',
3310				'delete', 'dolly', 'dollyrevert', 'donation', 'download', 'edit', 'ellipsis-h', 'email', 'eraser', 'external-link-alt', 'external-link-square-alt',
3311				'filter', 'file-code', 'file-export', 'file-import', 'file-upload', 'folder', 'folder-open', 'globe', 'globe-americas', 'grip', 'grip_title', 'group',
3312				'help', 'holiday',
3313				'intervention', 'label', 'language', 'link', 'list', 'listlight', 'lot',
3314				'map-marker-alt', 'member', 'money-bill-alt', 'mrp', 'note', 'next',
3315				'object_accounting', 'object_account', 'object_accountline', 'object_action', 'object_barcode', 'object_bill', 'object_billa', 'object_billr', 'object_billd', 'object_bom',
3316				'object_category', 'object_conversation', 'object_bookmark', 'object_bug', 'object_clock', 'object_dolly', 'object_dollyrevert', 'object_generic', 'object_folder',
3317				'object_list-alt', 'object_calendar', 'object_calendarweek', 'object_calendarmonth', 'object_calendarday', 'object_calendarperuser',
3318				'object_cash-register', 'object_company', 'object_contact', 'object_contract', 'object_donation', 'object_dynamicprice',
3319				'object_globe', 'object_holiday', 'object_hrm', 'object_invoice', 'object_intervention', 'object_label',
3320				'object_margin', 'object_money-bill-alt', 'object_multicurrency', 'object_order', 'object_payment',
3321				'object_lot', 'object_mrp', 'object_other',
3322				'object_payment', 'object_pdf', 'object_product', 'object_propal',
3323				'object_paragraph', 'object_poll', 'object_printer', 'object_project', 'object_projectpub', 'object_propal', 'object_resource', 'object_rss', 'object_projecttask',
3324				'object_recruitmentjobposition', 'object_recruitmentcandidature',
3325				'object_shipment', 'object_share-alt', 'object_supplier_invoice', 'object_supplier_invoicea', 'object_supplier_invoiced', 'object_supplier_order', 'object_supplier_proposal', 'object_service', 'object_stock',
3326				'object_technic', 'object_ticket', 'object_trip', 'object_user', 'object_group', 'object_member',
3327				'object_phoning', 'object_phoning_mobile', 'object_phoning_fax', 'object_email', 'object_website', 'object_movement',
3328				'off', 'on', 'order',
3329				'paiment', 'play', 'pdf', 'playdisabled', 'previous', 'poll', 'printer', 'product', 'propal', 'projecttask', 'stock', 'resize', 'service', 'stats', 'trip',
3330				'setup', 'share-alt', 'sign-out', 'split', 'stripe-s', 'switch_off', 'switch_on', 'tools', 'unlink', 'uparrow', 'user', 'vcard', 'wrench',
3331				'jabber', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'youtube', 'google-plus-g', 'whatsapp',
3332				'chevron-left', 'chevron-right', 'chevron-down', 'chevron-top', 'commercial', 'companies',
3333				'generic', 'home', 'hrm', 'members', 'products', 'invoicing',
3334				'payment', 'pencil-ruler', 'preview', 'project', 'projectpub', 'refresh', 'supplier_invoice', 'ticket',
3335				'error', 'warning',
3336				'recruitmentcandidature', 'recruitmentjobposition', 'resource',
3337				'supplier_proposal', 'supplier_order', 'supplier_invoice',
3338				'title_setup', 'title_accountancy', 'title_bank', 'title_hrm', 'title_agenda'
3339			)
3340		)) {
3341			$pictowithouttext = str_replace('object_', '', $pictowithouttext);
3342
3343			$fakey = $pictowithouttext;
3344			$facolor = ''; $fasize = '';
3345			$fa = 'fas';
3346			if (in_array($pictowithouttext, array('clock', 'generic', 'minus-square', 'object_generic', 'pdf', 'plus-square', 'note', 'off', 'on', 'object_bookmark', 'bookmark', 'vcard'))) {
3347				$fa = 'far';
3348			}
3349			if (in_array($pictowithouttext, array('black-tie', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'stripe-s', 'youtube', 'google-plus-g', 'whatsapp'))) {
3350				$fa = 'fab';
3351			}
3352
3353			$arrayconvpictotofa = array(
3354				'account'=>'university', 'accountline'=>'receipt', 'accountancy'=>'money-check-alt', 'action'=>'calendar-alt', 'add'=>'plus-circle', 'address'=> 'address-book',
3355				'bank_account'=>'university', 'bill'=>'file-invoice-dollar', 'billa'=>'file-excel', 'billr'=>'file-invoice-dollar', 'supplier_invoicea'=>'file-excel', 'billd'=>'file-medical', 'supplier_invoiced'=>'file-medical', 'bom'=>'cubes',
3356				'company'=>'building', 'contact'=>'address-book', 'contract'=>'suitcase', 'conversation'=>'comments', 'donation'=>'file-alt', 'dynamicprice'=>'hand-holding-usd',
3357				'setup'=>'cog', 'companies'=>'building', 'products'=>'cube', 'commercial'=>'suitcase', 'invoicing'=>'coins',
3358				'accounting'=>'chart-line', 'category'=>'tag', 'dollyrevert'=>'dolly',
3359				'hrm'=>'user-tie', 'margin'=>'calculator', 'members'=>'users', 'ticket'=>'ticket-alt', 'globe'=>'external-link-alt', 'lot'=>'barcode',
3360				'email'=>'at',
3361				'edit'=>'pencil-alt', 'grip_title'=>'arrows-alt', 'grip'=>'arrows-alt', 'help'=>'question-circle',
3362				'generic'=>'file', 'holiday'=>'umbrella-beach', 'label'=>'layer-group',
3363				'member'=>'users', 'mrp'=>'cubes', 'next'=>'arrow-alt-circle-right',
3364				'trip'=>'wallet', 'group'=>'users', 'movement'=>'people-carry',
3365				'sign-out'=>'sign-out-alt',
3366				'switch_off'=>'toggle-off', 'switch_on'=>'toggle-on', 'check'=>'check', 'bookmark'=>'star', 'bookmark'=>'star',
3367				'bank'=>'university', 'close_title'=>'times', 'delete'=>'trash', 'edit'=>'pencil-alt', 'filter'=>'filter',
3368				'list-alt'=>'list-alt', 'calendar'=>'calendar-alt', 'calendarweek'=>'calendar-week', 'calendarmonth'=>'calendar-alt', 'calendarday'=>'calendar-day', 'calendarperuser'=>'table',
3369				'intervention'=>'ambulance', 'invoice'=>'file-invoice-dollar', 'multicurrency'=>'dollar-sign', 'order'=>'file-invoice',
3370				'error'=>'exclamation-triangle', 'warning'=>'exclamation-triangle',
3371				'other'=>'square',
3372				'playdisabled'=>'play', 'pdf'=>'file-pdf',  'poll'=>'check-double', 'preview'=>'binoculars', 'project'=>'sitemap', 'projectpub'=>'sitemap', 'projecttask'=>'tasks', 'propal'=>'file-signature',
3373				'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',
3374				'recruitmentjobposition'=>'id-card-alt', 'recruitmentcandidature'=>'id-badge',
3375				'resize'=>'crop', 'supplier_order'=>'dol-order_supplier', 'supplier_proposal'=>'file-signature',
3376				'refresh'=>'redo', 'resource'=>'laptop-house',
3377				'shipment'=>'dolly', 'stock'=>'box-open', 'stats' => 'chart-bar', 'split'=>'code-branch', 'supplier_invoice'=>'file-invoice-dollar', 'technic'=>'cogs', 'ticket'=>'ticket-alt',
3378				'title_setup'=>'tools', 'title_accountancy'=>'money-check-alt', 'title_bank'=>'university', 'title_hrm'=>'umbrella-beach',
3379				'title_agenda'=>'calendar-alt',
3380				'uparrow'=>'share', 'vcard'=>'address-card',
3381				'jabber'=>'comment-o',
3382				'website'=>'globe-americas'
3383			);
3384			if ($pictowithouttext == 'off') {
3385				$fakey = 'fa-square';
3386				$fasize = '1.3em';
3387			} elseif ($pictowithouttext == 'on') {
3388				$fakey = 'fa-check-square';
3389				$fasize = '1.3em';
3390			} elseif ($pictowithouttext == 'listlight') {
3391				$fakey = 'fa-download';
3392				$marginleftonlyshort = 1;
3393			} elseif ($pictowithouttext == 'printer') {
3394				$fakey = 'fa-print';
3395				$fasize = '1.2em';
3396			} elseif ($pictowithouttext == 'note') {
3397				$fakey = 'fa-sticky-note';
3398				$marginleftonlyshort = 1;
3399			} elseif (in_array($pictowithouttext, array('1uparrow', '1downarrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected'))) {
3400				$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');
3401				$fakey = 'fa-'.$convertarray[$pictowithouttext];
3402				if (preg_match('/selected/', $pictowithouttext)) $facolor = '#888';
3403				$marginleftonlyshort = 1;
3404			} elseif (!empty($arrayconvpictotofa[$pictowithouttext])) {
3405				$fakey = 'fa-'.$arrayconvpictotofa[$pictowithouttext];
3406			} else {
3407				$fakey = 'fa-'.$pictowithouttext;
3408			}
3409
3410			// Define $marginleftonlyshort
3411			$arrayconvpictotomarginleftonly = array(
3412				'bank', 'check', 'delete', 'generic', 'grip', 'grip_title', 'jabber',
3413				'grip_title', 'grip', 'listlight', 'note', 'on', 'off', 'playdisabled', 'printer', 'resize', 'sign-out', 'stats', 'switch_on', 'switch_off',
3414				'uparrow', '1uparrow', '1downarrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected'
3415			);
3416			if (!isset($arrayconvpictotomarginleftonly[$pictowithouttext])) {
3417				$marginleftonlyshort = 0;
3418			}
3419
3420			// Add CSS
3421			$arrayconvpictotomorcess = array(
3422				'action'=>'infobox-action', 'account'=>'infobox-bank_account', 'accountline'=>'infobox-bank_account', 'accountancy'=>'infobox-bank_account',
3423				'bank_account'=>'bg-infobox-bank_account',
3424				'bill'=>'infobox-commande', 'billa'=>'infobox-commande', 'billr'=>'infobox-commande', 'billd'=>'infobox-commande',
3425				'cash-register'=>'infobox-bank_account', 'contract'=>'infobox-contrat', 'check'=>'font-status4', 'conversation'=>'infobox-contrat',
3426				'donation'=>'infobox-commande', 'dollyrevert'=>'flip', 'ecm'=>'infobox-action',
3427				'hrm'=>'infobox-adherent', 'group'=>'infobox-adherent', 'intervention'=>'infobox-contrat',
3428				'multicurrency'=>'infobox-bank_account',
3429				'members'=>'infobox-adherent', 'member'=>'infobox-adherent', 'money-bill-alt'=>'infobox-bank_account',
3430				'order'=>'infobox-commande',
3431				'user'=>'infobox-adherent', 'users'=>'infobox-adherent',
3432				'error'=>'pictoerror', 'warning'=>'pictowarning', 'switch_on'=>'font-status4',
3433				'holiday'=>'infobox-holiday', 'invoice'=>'infobox-commande',
3434				'payment'=>'infobox-bank_account', 'poll'=>'infobox-adherent', 'project'=>'infobox-project', 'projecttask'=>'infobox-project', 'propal'=>'infobox-propal',
3435				'recruitmentjobposition'=>'infobox-adherent', 'recruitmentcandidature'=>'infobox-adherent',
3436				'resource'=>'infobox-action',
3437				'supplier_invoice'=>'infobox-order_supplier', 'supplier_invoicea'=>'infobox-order_supplier', 'supplier_invoiced'=>'infobox-order_supplier',
3438				'supplier_order'=>'infobox-order_supplier', 'supplier_proposal'=>'infobox-supplier_proposal',
3439				'ticket'=>'infobox-contrat', 'title_accountancy'=>'infobox-bank_account', 'title_hrm'=>'infobox-holiday', 'trip'=>'infobox-expensereport', 'title_agenda'=>'infobox-action',
3440				//'title_setup'=>'infobox-action', 'tools'=>'infobox-action',
3441				'list-alt'=>'imgforviewmode', 'calendar'=>'imgforviewmode', 'calendarweek'=>'imgforviewmode', 'calendarmonth'=>'imgforviewmode', 'calendarday'=>'imgforviewmode', 'calendarperuser'=>'imgforviewmode'
3442			);
3443			if (!empty($arrayconvpictotomorcess[$pictowithouttext])) {
3444				$morecss .= ($morecss ? ' ' : '').$arrayconvpictotomorcess[$pictowithouttext];
3445			}
3446
3447			// Define $color
3448			$arrayconvpictotocolor = array(
3449				'address'=>'#6c6aa8', 'building'=>'#6c6aa8', 'bom'=>'#a69944',
3450				'companies'=>'#6c6aa8', 'company'=>'#6c6aa8', 'contact'=>'#6c6aa8', 'dynamicprice'=>'#a69944',
3451				'edit'=>'#444', 'note'=>'#999', 'error'=>'', 'help'=>'#bbb', 'listlight'=>'#999',
3452				'dolly'=>'#a69944', 'dollyrevert'=>'#a69944', 'lot'=>'#a69944',
3453				'map-marker-alt'=>'#aaa', 'mrp'=>'#a69944', 'product'=>'#a69944', 'service'=>'#a69944', 'stock'=>'#a69944', 'movement'=>'#a69944',
3454				'other'=>'#ddd',
3455				'playdisabled'=>'#ccc', 'printer'=>'#444', 'projectpub'=>'#986c6a', 'resize'=>'#444', 'rss'=>'#cba',
3456				'shipment'=>'#a69944', 'stats'=>'#444', 'switch_off'=>'#999', 'uparrow'=>'#555', 'globe-americas'=>'#aaa',
3457				'website'=>'#304'
3458			);
3459			if (isset($arrayconvpictotocolor[$pictowithouttext])) {
3460				$facolor = $arrayconvpictotocolor[$pictowithouttext];
3461			}
3462
3463			// This snippet only needed since function img_edit accepts only one additional parameter: no separate one for css only.
3464			// class/style need to be extracted to avoid duplicate class/style validation errors when $moreatt is added to the end of the attributes.
3465			$morestyle = '';
3466			$reg = array();
3467			if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
3468				$morecss .= ($morecss ? ' ' : '').$reg[1];
3469				$moreatt = str_replace('class="'.$reg[1].'"', '', $moreatt);
3470			}
3471			if (preg_match('/style="([^"]+)"/', $moreatt, $reg)) {
3472				$morestyle = $reg[1];
3473				$moreatt = str_replace('style="'.$reg[1].'"', '', $moreatt);
3474			}
3475			$moreatt = trim($moreatt);
3476
3477			$enabledisablehtml = '<span class="'.$fa.' '.$fakey.($marginleftonlyshort ? ($marginleftonlyshort == 1 ? ' marginleftonlyshort' : ' marginleftonly') : '');
3478			$enabledisablehtml .= ($morecss ? ' '.$morecss : '').'" style="'.($fasize ? ('font-size: '.$fasize.';') : '').($facolor ? (' color: '.$facolor.';') : '').($morestyle ? ' '.$morestyle : '').'"'.(($notitle || empty($titlealt)) ? '' : ' title="'.dol_escape_htmltag($titlealt).'"').($moreatt ? ' '.$moreatt : '').'>';
3479			/*if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
3480				$enabledisablehtml .= $titlealt;
3481			}*/
3482			$enabledisablehtml .= '</span>';
3483
3484			return $enabledisablehtml;
3485		}
3486
3487		if (!empty($conf->global->MAIN_OVERWRITE_THEME_PATH)) {
3488			$path = $conf->global->MAIN_OVERWRITE_THEME_PATH.'/theme/'.$theme; // If the theme does not have the same name as the module
3489		} elseif (!empty($conf->global->MAIN_OVERWRITE_THEME_RES)) {
3490			$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
3491		} elseif (!empty($conf->modules_parts['theme']) && array_key_exists($theme, $conf->modules_parts['theme'])) {
3492			$path = $theme.'/theme/'.$theme; // If the theme have the same name as the module
3493		}
3494
3495		// If we ask an image into $url/$mymodule/img (instead of default path)
3496		$regs = array();
3497		if (preg_match('/^([^@]+)@([^@]+)$/i', $picto, $regs)) {
3498			$picto = $regs[1];
3499			$path = $regs[2]; // $path is $mymodule
3500		}
3501
3502		// Clean parameters
3503		if (!preg_match('/(\.png|\.gif|\.svg)$/i', $picto)) {
3504			$picto .= '.png';
3505		}
3506		// If alt path are defined, define url where img file is, according to physical path
3507		// ex: array(["main"]=>"/home/maindir/htdocs", ["alt0"]=>"/home/moddir0/htdocs", ...)
3508		foreach ($conf->file->dol_document_root as $type => $dirroot) {
3509			if ($type == 'main') {
3510				continue;
3511			}
3512			// This need a lot of time, that's why enabling alternative dir like "custom" dir is not recommanded
3513			if (file_exists($dirroot.'/'.$path.'/img/'.$picto)) {
3514				$url = DOL_URL_ROOT.$conf->file->dol_url_root[$type];
3515				break;
3516			}
3517		}
3518
3519		// $url is '' or '/custom', $path is current theme or
3520		$fullpathpicto = $url.'/'.$path.'/img/'.$picto;
3521	}
3522
3523	if ($srconly) {
3524		return $fullpathpicto;
3525	}
3526		// tag title is used for tooltip on <a>, tag alt can be used with very simple text on image for blind people
3527	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
3528}
3529
3530/**
3531 *	Show a picto called object_picto (generic function)
3532 *
3533 *	@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.
3534 *	@param	string	$picto				Name of image to show object_picto (example: user, group, action, bill, contract, propal, product, ...)
3535 *										For external modules use imagename@mymodule to search into directory "img" of module.
3536 *	@param	string	$moreatt			Add more attribute on img tag (ie: class="datecallink")
3537 *	@param	int		$pictoisfullpath	If 1, image path is a full path
3538 *	@param	int		$srconly			Return only content of the src attribute of img.
3539 *  @param	int		$notitle			1=Disable tag title. Use it if you add js tooltip, to avoid duplicate tooltip.
3540 *	@return	string						Return img tag
3541 *	@see	img_picto(), img_picto_common()
3542 */
3543function img_object($titlealt, $picto, $moreatt = '', $pictoisfullpath = false, $srconly = 0, $notitle = 0)
3544{
3545	if (strpos($picto, '^') === 0) return img_picto($titlealt, str_replace('^', '', $picto), $moreatt, $pictoisfullpath, $srconly, $notitle);
3546	else return img_picto($titlealt, 'object_'.$picto, $moreatt, $pictoisfullpath, $srconly, $notitle);
3547}
3548
3549/**
3550 *	Show weather picto
3551 *
3552 *	@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.
3553 *	@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).
3554 *	@param		string		$moreatt			Add more attribute on img tag
3555 *	@param		int			$pictoisfullpath	If 1, image path is a full path
3556 *  @param      string      $morecss            More CSS
3557 *	@return     string      					Return img tag
3558 *  @see        img_object(), img_picto()
3559 */
3560function img_weather($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $morecss = '')
3561{
3562	global $conf;
3563
3564	if (is_numeric($picto)) {
3565		//$leveltopicto = array(0=>'weather-clear.png', 1=>'weather-few-clouds.png', 2=>'weather-clouds.png', 3=>'weather-many-clouds.png', 4=>'weather-storm.png');
3566		//$picto = $leveltopicto[$picto];
3567		return '<i class="fa fa-weather-level'.$picto.'"></i>';
3568	} elseif (!preg_match('/(\.png|\.gif)$/i', $picto)) {
3569		$picto .= '.png';
3570	}
3571
3572	$path = DOL_URL_ROOT.'/theme/'.$conf->theme.'/img/weather/'.$picto;
3573
3574	return img_picto($titlealt, $path, $moreatt, 1, 0, 0, '', $morecss);
3575}
3576
3577/**
3578 *	Show picto (generic function)
3579 *
3580 *	@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.
3581 *	@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.
3582 *	@param		string		$moreatt			Add more attribute on img tag
3583 *	@param		int			$pictoisfullpath	If 1, image path is a full path
3584 *	@return     string      					Return img tag
3585 *  @see        img_object(), img_picto()
3586 */
3587function img_picto_common($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0)
3588{
3589	global $conf;
3590
3591	if (!preg_match('/(\.png|\.gif)$/i', $picto)) $picto .= '.png';
3592
3593	if ($pictoisfullpath) $path = $picto;
3594	else {
3595		$path = DOL_URL_ROOT.'/theme/common/'.$picto;
3596
3597		if (!empty($conf->global->MAIN_MODULE_CAN_OVERWRITE_COMMONICONS))
3598		{
3599			$themepath = DOL_DOCUMENT_ROOT.'/theme/'.$conf->theme.'/img/'.$picto;
3600
3601			if (file_exists($themepath)) $path = $themepath;
3602		}
3603	}
3604
3605	return img_picto($titlealt, $path, $moreatt, 1);
3606}
3607
3608/**
3609 *	Show logo action
3610 *
3611 *	@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.
3612 *	@param  string		$numaction   	Action id or code to show
3613 *	@param 	string		$picto      	Name of image file to show ('filenew', ...)
3614 *                                      If no extension provided, we use '.png'. Image must be stored into theme/xxx/img directory.
3615 *                                      Example: picto.png                  if picto.png is stored into htdocs/theme/mytheme/img
3616 *                                      Example: picto.png@mymodule         if picto.png is stored into htdocs/mymodule/img
3617 *                                      Example: /mydir/mysubdir/picto.png  if picto.png is stored into htdocs/mydir/mysubdir (pictoisfullpath must be set to 1)
3618 *	@return string      				Return an img tag
3619 */
3620function img_action($titlealt, $numaction, $picto = '')
3621{
3622	global $langs;
3623
3624	if (empty($titlealt) || $titlealt == 'default')
3625	{
3626		if ($numaction == '-1' || $numaction == 'ST_NO') {
3627			$numaction = -1;
3628			$titlealt = $langs->transnoentitiesnoconv('ChangeDoNotContact');
3629		} elseif ($numaction == '0' || $numaction == 'ST_NEVER') {
3630			$numaction = 0;
3631			$titlealt = $langs->transnoentitiesnoconv('ChangeNeverContacted');
3632		} elseif ($numaction == '1' || $numaction == 'ST_TODO') {
3633			$numaction = 1;
3634			$titlealt = $langs->transnoentitiesnoconv('ChangeToContact');
3635		} elseif ($numaction == '2' || $numaction == 'ST_PEND') {
3636			$numaction = 2;
3637			$titlealt = $langs->transnoentitiesnoconv('ChangeContactInProcess');
3638		} elseif ($numaction == '3' || $numaction == 'ST_DONE') {
3639			$numaction = 3;
3640			$titlealt = $langs->transnoentitiesnoconv('ChangeContactDone');
3641		} else {
3642			$titlealt = $langs->transnoentitiesnoconv('ChangeStatus '.$numaction);
3643			$numaction = 0;
3644		}
3645	}
3646	if (!is_numeric($numaction)) $numaction = 0;
3647
3648	return img_picto($titlealt, !empty($picto) ? $picto : 'stcomm'.$numaction.'.png');
3649}
3650
3651/**
3652 *  Show pdf logo
3653 *
3654 *  @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.
3655 *  @param  int		    $size       Taille de l'icone : 3 = 16x16px , 2 = 14x14px
3656 *  @return string      			Retourne tag img
3657 */
3658function img_pdf($titlealt = 'default', $size = 3)
3659{
3660	global $langs;
3661
3662	if ($titlealt == 'default') $titlealt = $langs->trans('Show');
3663
3664	return img_picto($titlealt, 'pdf'.$size.'.png');
3665}
3666
3667/**
3668 *	Show logo +
3669 *
3670 *	@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.
3671 *	@param  string	$other      Add more attributes on img
3672 *	@return string      		Return tag img
3673 */
3674function img_edit_add($titlealt = 'default', $other = '')
3675{
3676	global $langs;
3677
3678	if ($titlealt == 'default') $titlealt = $langs->trans('Add');
3679
3680	return img_picto($titlealt, 'edit_add.png', $other);
3681}
3682/**
3683 *	Show logo -
3684 *
3685 *	@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.
3686 *	@param  string	$other      Add more attributes on img
3687 *	@return string      		Return tag img
3688 */
3689function img_edit_remove($titlealt = 'default', $other = '')
3690{
3691	global $langs;
3692
3693	if ($titlealt == 'default') $titlealt = $langs->trans('Remove');
3694
3695	return img_picto($titlealt, 'edit_remove.png', $other);
3696}
3697
3698/**
3699 *	Show logo editer/modifier fiche
3700 *
3701 *	@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.
3702 *	@param  integer	$float      If you have to put the style "float: right"
3703 *	@param  string	$other		Add more attributes on img
3704 *	@return string      		Return tag img
3705 */
3706function img_edit($titlealt = 'default', $float = 0, $other = '')
3707{
3708	global $langs;
3709
3710	if ($titlealt == 'default') $titlealt = $langs->trans('Modify');
3711
3712	return img_picto($titlealt, 'edit.png', ($float ? 'style="float: '.($langs->tab_translate["DIRECTION"] == 'rtl' ? 'left' : 'right').'"' : "").($other ? ' '.$other : ''));
3713}
3714
3715/**
3716 *	Show logo view card
3717 *
3718 *	@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.
3719 *	@param  integer	$float      If you have to put the style "float: right"
3720 *	@param  string	$other		Add more attributes on img
3721 *	@return string      		Return tag img
3722 */
3723function img_view($titlealt = 'default', $float = 0, $other = '')
3724{
3725	global $langs;
3726
3727	if ($titlealt == 'default') $titlealt = $langs->trans('View');
3728
3729	$moreatt = ($float ? 'style="float: right" ' : '').$other;
3730
3731	return img_picto($titlealt, 'view.png', $moreatt);
3732}
3733
3734/**
3735 *  Show delete logo
3736 *
3737 *  @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.
3738 *	@param  string	$other      Add more attributes on img
3739 *  @param	string	$morecss	More CSS
3740 *  @return string      		Retourne tag img
3741 */
3742function img_delete($titlealt = 'default', $other = 'class="pictodelete"', $morecss = '')
3743{
3744	global $langs;
3745
3746	if ($titlealt == 'default') $titlealt = $langs->trans('Delete');
3747
3748	return img_picto($titlealt, 'delete.png', $other, false, 0, 0, '', $morecss);
3749}
3750
3751/**
3752 *  Show printer logo
3753 *
3754 *  @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.
3755 *  @param  string  $other      Add more attributes on img
3756 *  @return string              Retourne tag img
3757 */
3758function img_printer($titlealt = "default", $other = '')
3759{
3760	global $langs;
3761	if ($titlealt == "default") $titlealt = $langs->trans("Print");
3762	return img_picto($titlealt, 'printer.png', $other);
3763}
3764
3765/**
3766 *  Show split logo
3767 *
3768 *  @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.
3769 *	@param  string	$other      Add more attributes on img
3770 *  @return string      		Retourne tag img
3771 */
3772function img_split($titlealt = 'default', $other = 'class="pictosplit"')
3773{
3774	global $langs;
3775
3776	if ($titlealt == 'default') $titlealt = $langs->trans('Split');
3777
3778	return img_picto($titlealt, 'split.png', $other);
3779}
3780
3781/**
3782 *	Show help logo with cursor "?"
3783 *
3784 * 	@param	int              	$usehelpcursor		1=Use help cursor, 2=Use click pointer cursor, 0=No specific cursor
3785 * 	@param	int|string	        $usealttitle		Text to use as alt title
3786 * 	@return string            	           			Return tag img
3787 */
3788function img_help($usehelpcursor = 1, $usealttitle = 1)
3789{
3790	global $langs;
3791
3792	if ($usealttitle)
3793	{
3794		if (is_string($usealttitle)) $usealttitle = dol_escape_htmltag($usealttitle);
3795		else $usealttitle = $langs->trans('Info');
3796	}
3797
3798	return img_picto($usealttitle, 'info.png', 'style="vertical-align: middle;'.($usehelpcursor == 1 ? ' cursor: help' : ($usehelpcursor == 2 ? ' cursor: pointer' : '')).'"');
3799}
3800
3801/**
3802 *	Show info logo
3803 *
3804 *	@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.
3805 *	@return string      		Return img tag
3806 */
3807function img_info($titlealt = 'default')
3808{
3809	global $langs;
3810
3811	if ($titlealt == 'default') $titlealt = $langs->trans('Informations');
3812
3813	return img_picto($titlealt, 'info.png', 'style="vertical-align: middle;"');
3814}
3815
3816/**
3817 *	Show warning logo
3818 *
3819 *	@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.
3820 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"'). If 1, add float: right. Can't be "class" attribute.
3821 *  @param	string  $morecss	Add more CSS
3822 *	@return string      		Return img tag
3823 */
3824function img_warning($titlealt = 'default', $moreatt = '', $morecss = 'pictowarning')
3825{
3826	global $langs;
3827
3828	if ($titlealt == 'default') $titlealt = $langs->trans('Warning');
3829
3830	//return '<div class="imglatecoin">'.img_picto($titlealt, 'warning_white.png', 'class="pictowarning valignmiddle"'.($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' '.$moreatt): '')).'</div>';
3831	return img_picto($titlealt, 'warning.png', 'class="'.$morecss.'"'.($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' '.$moreatt) : ''));
3832}
3833
3834/**
3835 *  Show error logo
3836 *
3837 *	@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.
3838 *	@return string      		Return img tag
3839 */
3840function img_error($titlealt = 'default')
3841{
3842	global $langs;
3843
3844	if ($titlealt == 'default') $titlealt = $langs->trans('Error');
3845
3846	return img_picto($titlealt, 'error.png');
3847}
3848
3849/**
3850 *	Show next logo
3851 *
3852 *	@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.
3853*	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
3854 *	@return string      		Return img tag
3855 */
3856function img_next($titlealt = 'default', $moreatt = '')
3857{
3858	global $langs;
3859
3860	if ($titlealt == 'default') $titlealt = $langs->trans('Next');
3861
3862	//return img_picto($titlealt, 'next.png', $moreatt);
3863	return '<span class="fa fa-chevron-right paddingright paddingleft" title="'.dol_escape_htmltag($titlealt).'"></span>';
3864}
3865
3866/**
3867 *	Show previous logo
3868 *
3869 *	@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.
3870 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
3871 *	@return string      		Return img tag
3872 */
3873function img_previous($titlealt = 'default', $moreatt = '')
3874{
3875	global $langs;
3876
3877	if ($titlealt == 'default') $titlealt = $langs->trans('Previous');
3878
3879	//return img_picto($titlealt, 'previous.png', $moreatt);
3880	return '<span class="fa fa-chevron-left paddingright paddingleft" title="'.dol_escape_htmltag($titlealt).'"></span>';
3881}
3882
3883/**
3884 *	Show down arrow logo
3885 *
3886 *	@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.
3887 *	@param  int		$selected   Selected
3888 *  @param	string	$moreclass	Add more CSS classes
3889 *	@return string      		Return img tag
3890 */
3891function img_down($titlealt = 'default', $selected = 0, $moreclass = '')
3892{
3893	global $langs;
3894
3895	if ($titlealt == 'default') $titlealt = $langs->trans('Down');
3896
3897	return img_picto($titlealt, ($selected ? '1downarrow_selected.png' : '1downarrow.png'), 'class="imgdown'.($moreclass ? " ".$moreclass : "").'"');
3898}
3899
3900/**
3901 *	Show top arrow logo
3902 *
3903 *	@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.
3904 *	@param  int		$selected	Selected
3905 *  @param	string	$moreclass	Add more CSS classes
3906 *	@return string      		Return img tag
3907 */
3908function img_up($titlealt = 'default', $selected = 0, $moreclass = '')
3909{
3910	global $langs;
3911
3912	if ($titlealt == 'default') $titlealt = $langs->trans('Up');
3913
3914	return img_picto($titlealt, ($selected ? '1uparrow_selected.png' : '1uparrow.png'), 'class="imgup'.($moreclass ? " ".$moreclass : "").'"');
3915}
3916
3917/**
3918 *	Show left arrow logo
3919 *
3920 *	@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.
3921 *	@param  int		$selected	Selected
3922 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
3923 *	@return string      		Return img tag
3924 */
3925function img_left($titlealt = 'default', $selected = 0, $moreatt = '')
3926{
3927	global $langs;
3928
3929	if ($titlealt == 'default') {
3930		$titlealt = $langs->trans('Left');
3931	}
3932
3933	return img_picto($titlealt, ($selected ? '1leftarrow_selected.png' : '1leftarrow.png'), $moreatt);
3934}
3935
3936/**
3937 *	Show right arrow logo
3938 *
3939 *	@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.
3940 *	@param  int		$selected	Selected
3941 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
3942 *	@return string      		Return img tag
3943 */
3944function img_right($titlealt = 'default', $selected = 0, $moreatt = '')
3945{
3946	global $langs;
3947
3948	if ($titlealt == 'default') $titlealt = $langs->trans('Right');
3949
3950	return img_picto($titlealt, ($selected ? '1rightarrow_selected.png' : '1rightarrow.png'), $moreatt);
3951}
3952
3953/**
3954 *	Show tick logo if allowed
3955 *
3956 *	@param	string	$allow		Allow
3957 *	@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.
3958 *	@return string      		Return img tag
3959 */
3960function img_allow($allow, $titlealt = 'default')
3961{
3962	global $langs;
3963
3964	if ($titlealt == 'default') $titlealt = $langs->trans('Active');
3965
3966	if ($allow == 1) return img_picto($titlealt, 'tick.png');
3967
3968	return '-';
3969}
3970
3971/**
3972 *	Return image of a credit card according to its brand name
3973 *
3974 *	@param  string	$brand		Brand name of credit card
3975 *  @param  string	$morecss	More CSS
3976 *	@return string     			Return img tag
3977 */
3978function img_credit_card($brand, $morecss = null)
3979{
3980	if (is_null($morecss)) $morecss = 'fa-2x';
3981
3982	if ($brand == 'visa' || $brand == 'Visa') {
3983		$brand = 'cc-visa';
3984	} elseif ($brand == 'mastercard' || $brand == 'MasterCard') {
3985		$brand = 'cc-mastercard';
3986	} elseif ($brand == 'amex' || $brand == 'American Express') {
3987		$brand = 'cc-amex';
3988	} elseif ($brand == 'discover' || $brand == 'Discover') {
3989		$brand = 'cc-discover';
3990	} elseif ($brand == 'jcb' || $brand == 'JCB') {
3991		$brand = 'cc-jcb';
3992	} elseif ($brand == 'diners' || $brand == 'Diners club') {
3993		$brand = 'cc-diners-club';
3994	} elseif (!in_array($brand, array('cc-visa', 'cc-mastercard', 'cc-amex', 'cc-discover', 'cc-jcb', 'cc-diners-club'))) {
3995		$brand = 'credit-card';
3996	}
3997
3998	return '<span class="fa fa-'.$brand.' fa-fw'.($morecss ? ' '.$morecss : '').'"></span>';
3999}
4000
4001/**
4002 *	Show MIME img of a file
4003 *
4004 *	@param	string	$file		Filename
4005 * 	@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.
4006 *  @param	string	$morecss	More css
4007 *	@return string     			Return img tag
4008 */
4009function img_mime($file, $titlealt = '', $morecss = '')
4010{
4011	require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
4012
4013	$mimetype = dol_mimetype($file, '', 1);
4014	$mimeimg = dol_mimetype($file, '', 2);
4015	$mimefa = dol_mimetype($file, '', 4);
4016
4017	if (empty($titlealt)) $titlealt = 'Mime type: '.$mimetype;
4018
4019	//return img_picto_common($titlealt, 'mime/'.$mimeimg, 'class="'.$morecss.'"');
4020	return '<i class="fa fa-'.$mimefa.' paddingright"'.($titlealt ? ' title="'.$titlealt.'"' : '').'></i>';
4021}
4022
4023
4024/**
4025 *  Show search logo
4026 *
4027 *  @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.
4028 *	@param  string	$other      Add more attributes on img
4029 *  @return string      		Retourne tag img
4030 */
4031function img_search($titlealt = 'default', $other = '')
4032{
4033	global $conf, $langs;
4034
4035	if ($titlealt == 'default') $titlealt = $langs->trans('Search');
4036
4037	$img = img_picto($titlealt, 'search.png', $other, false, 1);
4038
4039	$input = '<input type="image" class="liste_titre" name="button_search" src="'.$img.'" ';
4040	$input .= 'value="'.dol_escape_htmltag($titlealt).'" title="'.dol_escape_htmltag($titlealt).'" >';
4041
4042	return $input;
4043}
4044
4045/**
4046 *  Show search logo
4047 *
4048 *  @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.
4049 *	@param  string	$other      Add more attributes on img
4050 *  @return string      		Retourne tag img
4051 */
4052function img_searchclear($titlealt = 'default', $other = '')
4053{
4054	global $conf, $langs;
4055
4056	if ($titlealt == 'default') $titlealt = $langs->trans('Search');
4057
4058	$img = img_picto($titlealt, 'searchclear.png', $other, false, 1);
4059
4060	$input = '<input type="image" class="liste_titre" name="button_removefilter" src="'.$img.'" ';
4061	$input .= 'value="'.dol_escape_htmltag($titlealt).'" title="'.dol_escape_htmltag($titlealt).'" >';
4062
4063	return $input;
4064}
4065
4066/**
4067 *	Show information for admin users or standard users
4068 *
4069 *	@param	string	$text				Text info
4070 *	@param  integer	$infoonimgalt		Info is shown only on alt of star picto, otherwise it is show on output after the star picto
4071 *	@param	int		$nodiv				No div
4072 *  @param  string  $admin      	    '1'=Info for admin users. '0'=Info for standard users (change only the look), 'error', 'warning', 'xxx'=Other
4073 *  @param	string	$morecss			More CSS ('', 'warning', 'error')
4074 *  @param	string	$textfordropdown	Show a text to click to dropdown the info box.
4075 *	@return	string						String with info text
4076 */
4077function info_admin($text, $infoonimgalt = 0, $nodiv = 0, $admin = '1', $morecss = '', $textfordropdown = '')
4078{
4079	global $conf, $langs;
4080
4081	if ($infoonimgalt)
4082	{
4083		$result = img_picto($text, 'info', 'class="hideonsmartphone'.($morecss ? ' '.$morecss : '').'"');
4084	} else {
4085		if (empty($conf->use_javascript_ajax)) $textfordropdown = '';
4086
4087		$class = (empty($admin) ? 'undefined' : ($admin == '1' ? 'info' : $admin));
4088		$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>');
4089
4090		if ($textfordropdown) {
4091			$tmpresult .= '<span class="'.$class.'text opacitymedium cursorpointer">'.$langs->trans($textfordropdown).' '.img_picto($langs->trans($textfordropdown), '1downarrow').'</span>';
4092			$tmpresult .= '<script type="text/javascript" language="javascript">
4093				jQuery(document).ready(function() {
4094					jQuery(".'.$class.'text").click(function() {
4095						console.log("toggle text");
4096						jQuery(".'.$class.'").toggle();
4097					});
4098				});
4099				</script>';
4100
4101			$result = $tmpresult.$result;
4102		}
4103	}
4104
4105	return $result;
4106}
4107
4108
4109/**
4110 *  Displays error message system with all the information to facilitate the diagnosis and the escalation of the bugs.
4111 *  This function must be called when a blocking technical error is encountered.
4112 *  However, one must try to call it only within php pages, classes must return their error through their property "error".
4113 *
4114 *	@param	 	DoliDB          $db      	Database handler
4115 *	@param  	string|string[] $error		String or array of errors strings to show
4116 *  @param		array           $errors		Array of errors
4117 *	@return 	void
4118 *  @see    	dol_htmloutput_errors()
4119 */
4120function dol_print_error($db = '', $error = '', $errors = null)
4121{
4122	global $conf, $langs, $argv;
4123	global $dolibarr_main_prod;
4124
4125	$out = '';
4126	$syslog = '';
4127
4128	// If error occurs before the $lang object was loaded
4129	if (!$langs)
4130	{
4131		require_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
4132		$langs = new Translate('', $conf);
4133		$langs->load("main");
4134	}
4135
4136	// Load translation files required by the error messages
4137	$langs->loadLangs(array('main', 'errors'));
4138
4139	if ($_SERVER['DOCUMENT_ROOT'])    // Mode web
4140	{
4141		$out .= $langs->trans("DolibarrHasDetectedError").".<br>\n";
4142		if (!empty($conf->global->MAIN_FEATURES_LEVEL)) $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";
4143		$out .= $langs->trans("InformationToHelpDiagnose").":<br>\n";
4144
4145		$out .= "<b>".$langs->trans("Date").":</b> ".dol_print_date(time(), 'dayhourlog')."<br>\n";
4146		$out .= "<b>".$langs->trans("Dolibarr").":</b> ".DOL_VERSION." - https://www.dolibarr.org<br>\n";
4147		if (isset($conf->global->MAIN_FEATURES_LEVEL)) $out .= "<b>".$langs->trans("LevelOfFeature").":</b> ".$conf->global->MAIN_FEATURES_LEVEL."<br>\n";
4148		if (function_exists("phpversion"))
4149		{
4150			$out .= "<b>".$langs->trans("PHP").":</b> ".phpversion()."<br>\n";
4151		}
4152		$out .= "<b>".$langs->trans("Server").":</b> ".dol_htmlentities($_SERVER["SERVER_SOFTWARE"])."<br>\n";
4153		if (function_exists("php_uname"))
4154		{
4155			$out .= "<b>".$langs->trans("OS").":</b> ".php_uname()."<br>\n";
4156		}
4157		$out .= "<b>".$langs->trans("UserAgent").":</b> ".dol_htmlentities($_SERVER["HTTP_USER_AGENT"], ENT_COMPAT, 'UTF-8')."<br>\n";
4158		$out .= "<br>\n";
4159		$out .= "<b>".$langs->trans("RequestedUrl").":</b> ".dol_htmlentities($_SERVER["REQUEST_URI"], ENT_COMPAT, 'UTF-8')."<br>\n";
4160		$out .= "<b>".$langs->trans("Referer").":</b> ".(isset($_SERVER["HTTP_REFERER"]) ? dol_htmlentities($_SERVER["HTTP_REFERER"], ENT_COMPAT, 'UTF-8') : '')."<br>\n";
4161		$out .= "<b>".$langs->trans("MenuManager").":</b> ".(isset($conf->standard_menu) ? dol_htmlentities($conf->standard_menu) : '')."<br>\n";
4162		$out .= "<br>\n";
4163		$syslog .= "url=".dol_escape_htmltag($_SERVER["REQUEST_URI"]);
4164		$syslog .= ", query_string=".dol_escape_htmltag($_SERVER["QUERY_STRING"]);
4165	} else // Mode CLI
4166	{
4167		$out .= '> '.$langs->transnoentities("ErrorInternalErrorDetected").":\n".$argv[0]."\n";
4168		$syslog .= "pid=".dol_getmypid();
4169	}
4170
4171	if (!empty($conf->modules))
4172	{
4173		$out .= "<b>".$langs->trans("Modules").":</b> ".join(', ', $conf->modules)."<br>\n";
4174	}
4175
4176	if (is_object($db))
4177	{
4178		if ($_SERVER['DOCUMENT_ROOT'])  // Mode web
4179		{
4180			$out .= "<b>".$langs->trans("DatabaseTypeManager").":</b> ".$db->type."<br>\n";
4181			$out .= "<b>".$langs->trans("RequestLastAccessInError").":</b> ".($db->lastqueryerror() ? dol_escape_htmltag($db->lastqueryerror()) : $langs->trans("ErrorNoRequestInError"))."<br>\n";
4182			$out .= "<b>".$langs->trans("ReturnCodeLastAccessInError").":</b> ".($db->lasterrno() ? dol_escape_htmltag($db->lasterrno()) : $langs->trans("ErrorNoRequestInError"))."<br>\n";
4183			$out .= "<b>".$langs->trans("InformationLastAccessInError").":</b> ".($db->lasterror() ? dol_escape_htmltag($db->lasterror()) : $langs->trans("ErrorNoRequestInError"))."<br>\n";
4184			$out .= "<br>\n";
4185		} else // Mode CLI
4186		{
4187			// No dol_escape_htmltag for output, we are in CLI mode
4188			$out .= '> '.$langs->transnoentities("DatabaseTypeManager").":\n".$db->type."\n";
4189			$out .= '> '.$langs->transnoentities("RequestLastAccessInError").":\n".($db->lastqueryerror() ? $db->lastqueryerror() : $langs->transnoentities("ErrorNoRequestInError"))."\n";
4190			$out .= '> '.$langs->transnoentities("ReturnCodeLastAccessInError").":\n".($db->lasterrno() ? $db->lasterrno() : $langs->transnoentities("ErrorNoRequestInError"))."\n";
4191			$out .= '> '.$langs->transnoentities("InformationLastAccessInError").":\n".($db->lasterror() ? $db->lasterror() : $langs->transnoentities("ErrorNoRequestInError"))."\n";
4192		}
4193		$syslog .= ", sql=".$db->lastquery();
4194		$syslog .= ", db_error=".$db->lasterror();
4195	}
4196
4197	if ($error || $errors)
4198	{
4199		$langs->load("errors");
4200
4201		// Merge all into $errors array
4202		if (is_array($error) && is_array($errors)) $errors = array_merge($error, $errors);
4203		elseif (is_array($error)) $errors = $error;
4204		elseif (is_array($errors)) $errors = array_merge(array($error), $errors);
4205		else $errors = array_merge(array($error));
4206
4207		foreach ($errors as $msg)
4208		{
4209			if (empty($msg)) continue;
4210			if ($_SERVER['DOCUMENT_ROOT'])  // Mode web
4211			{
4212				$out .= "<b>".$langs->trans("Message").":</b> ".dol_escape_htmltag($msg)."<br>\n";
4213			} else // Mode CLI
4214			{
4215				$out .= '> '.$langs->transnoentities("Message").":\n".$msg."\n";
4216			}
4217			$syslog .= ", msg=".$msg;
4218		}
4219	}
4220	if (empty($dolibarr_main_prod) && $_SERVER['DOCUMENT_ROOT'] && function_exists('xdebug_print_function_stack') && function_exists('xdebug_call_file'))
4221	{
4222		xdebug_print_function_stack();
4223		$out .= '<b>XDebug informations:</b>'."<br>\n";
4224		$out .= 'File: '.xdebug_call_file()."<br>\n";
4225		$out .= 'Line: '.xdebug_call_line()."<br>\n";
4226		$out .= 'Function: '.xdebug_call_function()."<br>\n";
4227		$out .= "<br>\n";
4228	}
4229
4230	// Return a http error code if possible
4231	if (!headers_sent()) {
4232		http_response_code(500);
4233	}
4234
4235	if (empty($dolibarr_main_prod)) {
4236		print $out;
4237	} else {
4238		if (empty($langs->defaultlang)) $langs->setDefaultLang();
4239		$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.
4240		// This should not happen, except if there is a bug somewhere. Enabled and check log in such case.
4241		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 are on next line...<br><br>'."\n";
4242		print $langs->trans("DolibarrHasDetectedError").'. ';
4243		print $langs->trans("YouCanSetOptionDolibarrMainProdToZero");
4244		define("MAIN_CORE_ERROR", 1);
4245	}
4246
4247	dol_syslog("Error ".$syslog, LOG_ERR);
4248}
4249
4250/**
4251 * Show a public email and error code to contact if technical error
4252 *
4253 * @param	string	$prefixcode		Prefix of public error code
4254 * @param   string  $errormessage   Complete error message
4255 * @param	array	$errormessages	Array of error messages
4256 * @param	string	$morecss		More css
4257 * @param	string	$email			Email
4258 * @return	void
4259 */
4260function dol_print_error_email($prefixcode, $errormessage = '', $errormessages = array(), $morecss = 'error', $email = '')
4261{
4262	global $langs, $conf;
4263
4264	if (empty($email)) $email = $conf->global->MAIN_INFO_SOCIETE_MAIL;
4265
4266	$langs->load("errors");
4267	$now = dol_now();
4268
4269	print '<br><div class="center login_main_message"><div class="'.$morecss.'">';
4270	print $langs->trans("ErrorContactEMail", $email, $prefixcode.dol_print_date($now, '%Y%m%d%H%M%S'));
4271	if ($errormessage) print '<br><br>'.$errormessage;
4272	if (is_array($errormessages) && count($errormessages))
4273	{
4274		foreach ($errormessages as $mesgtoshow)
4275		{
4276			print '<br><br>'.$mesgtoshow;
4277		}
4278	}
4279	print '</div></div>';
4280}
4281
4282/**
4283 *	Show title line of an array
4284 *
4285 *	@param	string	$name        Label of field
4286 *	@param	string	$file        Url used when we click on sort picto
4287 *	@param	string	$field       Field to use for new sorting
4288 *	@param	string	$begin       ("" by defaut)
4289 *	@param	string	$moreparam   Add more parameters on sort url links ("" by default)
4290 *	@param  string	$moreattrib  Options of attribute td ("" by defaut, example: 'align="center"')
4291 *	@param  string	$sortfield   Current field used to sort
4292 *	@param  string	$sortorder   Current sort order
4293 *  @param	string	$prefix		 Prefix for css. Use space after prefix to add your own CSS tag.
4294 *  @param	string	$tooltip	 Tooltip
4295 *  @param	string	$forcenowrapcolumntitle		No need for use 'wrapcolumntitle' css style
4296 *	@return	void
4297 */
4298function print_liste_field_titre($name, $file = "", $field = "", $begin = "", $moreparam = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $tooltip = "", $forcenowrapcolumntitle = 0)
4299{
4300	print getTitleFieldOfList($name, 0, $file, $field, $begin, $moreparam, $moreattrib, $sortfield, $sortorder, $prefix, 0, $tooltip, $forcenowrapcolumntitle);
4301}
4302
4303/**
4304 *	Get title line of an array
4305 *
4306 *	@param	string	$name        		Translation key of field to show or complete HTML string to show
4307 *	@param	int		$thead		 		0=To use with standard table format, 1=To use inside <thead><tr>, 2=To use with <div>
4308 *	@param	string	$file        		Url used when we click on sort picto
4309 *	@param	string	$field       		Field to use for new sorting. Empty if this field is not sortable. Example "t.abc" or "t.abc,t.def"
4310 *	@param	string	$begin       		("" by defaut)
4311 *	@param	string	$moreparam   		Add more parameters on sort url links ("" by default)
4312 *	@param  string	$moreattrib  		Add more attributes on th ("" by defaut, example: 'align="center"'). To add more css class, use param $prefix.
4313 *	@param  string	$sortfield   		Current field used to sort (Ex: 'd.datep,d.id')
4314 *	@param  string	$sortorder   		Current sort order (Ex: 'asc,desc')
4315 *  @param	string	$prefix		 		Prefix for css. Use space after prefix to add your own CSS tag, for example 'mycss '.
4316 *  @param	string	$disablesortlink	1=Disable sort link
4317 *  @param	string	$tooltip	 		Tooltip
4318 *  @param	string	$forcenowrapcolumntitle		No need for use 'wrapcolumntitle' css style
4319 *	@return	string
4320 */
4321function getTitleFieldOfList($name, $thead = 0, $file = "", $field = "", $begin = "", $moreparam = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $disablesortlink = 0, $tooltip = '', $forcenowrapcolumntitle = 0)
4322{
4323	global $conf, $langs, $form;
4324	//print "$name, $file, $field, $begin, $options, $moreattrib, $sortfield, $sortorder<br>\n";
4325
4326	if ($moreattrib == 'class="right"') $prefix .= 'right '; // For backward compatibility
4327
4328	$sortorder = strtoupper($sortorder);
4329	$out = '';
4330	$sortimg = '';
4331
4332	$tag = 'th';
4333	if ($thead == 2) $tag = 'div';
4334
4335	$tmpsortfield = explode(',', $sortfield);
4336	$sortfield1 = trim($tmpsortfield[0]); // If $sortfield is 'd.datep,d.id', it becomes 'd.datep'
4337	$tmpfield = explode(',', $field);
4338	$field1 = trim($tmpfield[0]); // If $field is 'd.datep,d.id', it becomes 'd.datep'
4339
4340	if (empty($conf->global->MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE) && empty($forcenowrapcolumntitle)) {
4341		$prefix = 'wrapcolumntitle '.$prefix;
4342	}
4343
4344	//var_dump('field='.$field.' field1='.$field1.' sortfield='.$sortfield.' sortfield1='.$sortfield1);
4345	// If field is used as sort criteria we use a specific css class liste_titre_sel
4346	// Example if (sortfield,field)=("nom","xxx.nom") or (sortfield,field)=("nom","nom")
4347	$liste_titre = 'liste_titre';
4348	if ($field1 && ($sortfield1 == $field1 || $sortfield1 == preg_replace("/^[^\.]+\./", "", $field1))) {
4349		$liste_titre = 'liste_titre_sel';
4350	}
4351	$out .= '<'.$tag.' class="'.$prefix.$liste_titre.'" '.$moreattrib;
4352	//$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)).'"' : '');
4353	$out .= ($name && empty($conf->global->MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE) && empty($forcenowrapcolumntitle) && !dol_textishtml($name)) ? ' title="'.dol_escape_htmltag($langs->trans($name)).'"' : '';
4354	$out .= '>';
4355
4356	if (empty($thead) && $field && empty($disablesortlink))    // If this is a sort field
4357	{
4358		$options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', (is_scalar($moreparam) ? $moreparam : ''));
4359		$options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
4360		$options = preg_replace('/&+/i', '&', $options);
4361		if (!preg_match('/^&/', $options)) $options = '&'.$options;
4362
4363		$sortordertouseinlink = '';
4364		if ($field1 != $sortfield1) // We are on another field than current sorted field
4365		{
4366			if (preg_match('/^DESC/i', $sortorder))
4367			{
4368				$sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
4369			} else // We reverse the var $sortordertouseinlink
4370			{
4371				$sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
4372			}
4373		} else // We are on field that is the first current sorting criteria
4374		{
4375			if (preg_match('/^ASC/i', $sortorder))	// We reverse the var $sortordertouseinlink
4376			{
4377				$sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
4378			} else {
4379				$sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
4380			}
4381		}
4382		$sortordertouseinlink = preg_replace('/,$/', '', $sortordertouseinlink);
4383		$out .= '<a class="reposition" href="'.$file.'?sortfield='.$field.'&sortorder='.$sortordertouseinlink.'&begin='.$begin.$options.'"';
4384		//$out .= (empty($conf->global->MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE) ? ' title="'.dol_escape_htmltag($langs->trans($name)).'"' : '');
4385		$out .= '>';
4386	}
4387
4388	if ($tooltip) {
4389		// You can also use 'TranslationString:keyfortooltiponlick' for a tooltip on click.
4390		$tmptooltip = explode(':', $tooltip);
4391		$out .= $form->textwithpicto($langs->trans($name), $langs->trans($tmptooltip[0]), 1, 'help', '', 0, 3, (empty($tmptooltip[1]) ? '' : 'extra_'.str_replace('.', '_', $field).'_'.$tmptooltip[1]));
4392	}
4393	else $out .= $langs->trans($name);
4394
4395	if (empty($thead) && $field && empty($disablesortlink))    // If this is a sort field
4396	{
4397		$out .= '</a>';
4398	}
4399
4400	if (empty($thead) && $field)    // If this is a sort field
4401	{
4402		$options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', (is_scalar($moreparam) ? $moreparam : ''));
4403		$options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
4404		$options = preg_replace('/&+/i', '&', $options);
4405		if (!preg_match('/^&/', $options)) $options = '&'.$options;
4406
4407		if (!$sortorder || $field1 != $sortfield1)
4408		{
4409			//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",0).'</a>';
4410			//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",0).'</a>';
4411		} else {
4412			if (preg_match('/^DESC/', $sortorder)) {
4413				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",0).'</a>';
4414				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",1).'</a>';
4415				$sortimg .= '<span class="nowrap">'.img_up("Z-A", 0, 'paddingleft').'</span>';
4416			}
4417			if (preg_match('/^ASC/', $sortorder)) {
4418				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",1).'</a>';
4419				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",0).'</a>';
4420				$sortimg .= '<span class="nowrap">'.img_down("A-Z", 0, 'paddingleft').'</span>';
4421			}
4422		}
4423	}
4424
4425	$out .= $sortimg;
4426
4427	$out .= '</'.$tag.'>';
4428
4429	return $out;
4430}
4431
4432/**
4433 *	Show a title.
4434 *
4435 *	@param	string	$title			Title to show
4436 *	@return	string					Title to show
4437 *  @deprecated						Use load_fiche_titre instead
4438 *  @see load_fiche_titre()
4439 */
4440function print_titre($title)
4441{
4442	dol_syslog(__FUNCTION__." is deprecated", LOG_WARNING);
4443
4444	print '<div class="titre">'.$title.'</div>';
4445}
4446
4447/**
4448 *	Show a title with picto
4449 *
4450 *	@param	string	$title				Title to show
4451 *	@param	string	$mesg				Added message to show on right
4452 *	@param	string	$picto				Icon to use before title (should be a 32x32 transparent png file)
4453 *	@param	int		$pictoisfullpath	1=Icon name is a full absolute url of image
4454 * 	@param	int		$id					To force an id on html objects
4455 * 	@return	void
4456 *  @deprecated Use print load_fiche_titre instead
4457 */
4458function print_fiche_titre($title, $mesg = '', $picto = 'generic', $pictoisfullpath = 0, $id = '')
4459{
4460	print load_fiche_titre($title, $mesg, $picto, $pictoisfullpath, $id);
4461}
4462
4463/**
4464 *	Load a title with picto
4465 *
4466 *	@param	string	$titre				Title to show
4467 *	@param	string	$morehtmlright		Added message to show on right
4468 *	@param	string	$picto				Icon to use before title (should be a 32x32 transparent png file)
4469 *	@param	int		$pictoisfullpath	1=Icon name is a full absolute url of image
4470 * 	@param	string	$id					To force an id on html objects
4471 *  @param  string  $morecssontable     More css on table
4472 *	@param	string	$morehtmlcenter		Added message to show on center
4473 * 	@return	string
4474 *  @see print_barre_liste()
4475 */
4476function load_fiche_titre($titre, $morehtmlright = '', $picto = 'generic', $pictoisfullpath = 0, $id = '', $morecssontable = '', $morehtmlcenter = '')
4477{
4478	global $conf;
4479
4480	$return = '';
4481
4482	if ($picto == 'setup') $picto = 'generic';
4483
4484	$return .= "\n";
4485	$return .= '<table '.($id ? 'id="'.$id.'" ' : '').'class="centpercent notopnoleftnoright table-fiche-title'.($morecssontable ? ' '.$morecssontable : '').'">'; // maring bottom must be same than into print_barre_list
4486	$return .= '<tr class="titre">';
4487	if ($picto) $return .= '<td class="nobordernopadding widthpictotitle valignmiddle col-picto">'.img_picto('', $picto, 'class="valignmiddle widthpictotitle pictotitle"', $pictoisfullpath).'</td>';
4488	$return .= '<td class="nobordernopadding valignmiddle col-title">';
4489	$return .= '<div class="titre inline-block">'.$titre.'</div>';
4490	$return .= '</td>';
4491	if (dol_strlen($morehtmlcenter))
4492	{
4493		$return .= '<td class="nobordernopadding center valignmiddle">'.$morehtmlcenter.'</td>';
4494	}
4495	if (dol_strlen($morehtmlright))
4496	{
4497		$return .= '<td class="nobordernopadding titre_right wordbreakimp right valignmiddle">'.$morehtmlright.'</td>';
4498	}
4499	$return .= '</tr></table>'."\n";
4500
4501	return $return;
4502}
4503
4504/**
4505 *	Print a title with navigation controls for pagination
4506 *
4507 *	@param	string	    $titre				Title to show (required)
4508 *	@param	int   	    $page				Numero of page to show in navigation links (required)
4509 *	@param	string	    $file				Url of page (required)
4510 *	@param	string	    $options         	More parameters for links ('' by default, does not include sortfield neither sortorder). Value must be 'urlencoded' before calling function.
4511 *	@param	string    	$sortfield       	Field to sort on ('' by default)
4512 *	@param	string	    $sortorder       	Order to sort ('' by default)
4513 *	@param	string	    $morehtmlcenter     String in the middle ('' by default). We often find here string $massaction comming from $form->selectMassAction()
4514 *	@param	int		    $num				Number of records found by select with limit+1
4515 *	@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.
4516 *	@param	string	    $picto				Icon to use before title (should be a 32x32 transparent png file)
4517 *	@param	int		    $pictoisfullpath	1=Icon name is a full absolute url of image
4518 *  @param	string	    $morehtmlright		More html to show (after arrows)
4519 *  @param  string      $morecss            More css to the table
4520 *  @param  int         $limit              Max number of lines (-1 = use default, 0 = no limit, > 0 = limit).
4521 *  @param  int         $hideselectlimit    Force to hide select limit
4522 *  @param  int         $hidenavigation     Force to hide all navigation tools
4523 *  @param  int			$pagenavastextinput 1=Do not suggest list of pages to navigate but suggest the page number into an input field.
4524 *  @param	string		$morehtmlrightbeforearrow	More html to show (before arrows)
4525 *	@return	void
4526 */
4527function 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 = '')
4528{
4529	global $conf, $langs;
4530
4531	$savlimit = $limit;
4532	$savtotalnboflines = $totalnboflines;
4533	$totalnboflines = abs((int) $totalnboflines);
4534
4535	if ($picto == 'setup') $picto = 'title_setup.png';
4536	if (($conf->browser->name == 'ie') && $picto == 'generic') $picto = 'title.gif';
4537	if ($limit < 0) $limit = $conf->liste_limit;
4538	if ($savlimit != 0 && (($num > $limit) || ($num == -1) || ($limit == 0)))
4539	{
4540		$nextpage = 1;
4541	} else {
4542		$nextpage = 0;
4543	}
4544	//print 'totalnboflines='.$totalnboflines.'-savlimit='.$savlimit.'-limit='.$limit.'-num='.$num.'-nextpage='.$nextpage;
4545
4546	print "\n";
4547	print "<!-- Begin title -->\n";
4548	print '<table class="centpercent notopnoleftnoright table-fiche-title'.($morecss ? ' '.$morecss : '').'"><tr>'; // maring bottom must be same than into load_fiche_tire
4549
4550	// Left
4551
4552	if ($picto && $titre) print '<td class="nobordernopadding widthpictotitle valignmiddle col-picto">'.img_picto('', $picto, 'class="valignmiddle pictotitle widthpictotitle"', $pictoisfullpath).'</td>';
4553	print '<td class="nobordernopadding valignmiddle col-title">';
4554	print '<div class="titre inline-block">'.$titre;
4555	if (!empty($titre) && $savtotalnboflines >= 0 && (string) $savtotalnboflines != '') print '<span class="opacitymedium colorblack paddingleft">('.$totalnboflines.')</span>';
4556	print '</div></td>';
4557
4558	// Center
4559	if ($morehtmlcenter)
4560	{
4561		print '<td class="nobordernopadding center valignmiddle">'.$morehtmlcenter.'</td>';
4562	}
4563
4564	// Right
4565	print '<td class="nobordernopadding valignmiddle right">';
4566	print '<input type="hidden" name="pageplusoneold" value="'.((int) $page + 1).'">';
4567	if ($sortfield) $options .= "&sortfield=".urlencode($sortfield);
4568	if ($sortorder) $options .= "&sortorder=".urlencode($sortorder);
4569	// Show navigation bar
4570	$pagelist = '';
4571	if ($savlimit != 0 && ($page > 0 || $num > $limit))
4572	{
4573		if ($totalnboflines)	// If we know total nb of lines
4574		{
4575			// Define nb of extra page links before and after selected page + ... + first or last
4576			$maxnbofpage = (empty($conf->dol_optimize_smallscreen) ? 4 : 0);
4577
4578			if ($limit > 0) $nbpages = ceil($totalnboflines / $limit);
4579			else $nbpages = 1;
4580			$cpt = ($page - $maxnbofpage);
4581			if ($cpt < 0) { $cpt = 0; }
4582
4583			if ($cpt >= 1)
4584			{
4585				if (empty($pagenavastextinput)) {
4586					$pagelist .= '<li class="pagination"><a href="'.$file.'?page=0'.$options.'">1</a></li>';
4587					if ($cpt > 2) $pagelist .= '<li class="pagination"><span class="inactive">...</span></li>';
4588					elseif ($cpt == 2) $pagelist .= '<li class="pagination"><a href="'.$file.'?page=1'.$options.'">2</a></li>';
4589				}
4590			}
4591
4592			do {
4593				if ($pagenavastextinput) {
4594					if ($cpt == $page)
4595					{
4596						$pagelist .= '<li class="pagination"><input type="text" class="width25 center pageplusone" name="pageplusone" value="'.($page + 1).'"></li>';
4597						$pagelist .= '/';
4598						//if (($cpt + 1) < $nbpages) $pagelist .= '/';
4599					}
4600				} else {
4601					if ($cpt == $page)
4602					{
4603						$pagelist .= '<li class="pagination"><span class="active">'.($page + 1).'</span></li>';
4604					} else {
4605						$pagelist .= '<li class="pagination"><a href="'.$file.'?page='.$cpt.$options.'">'.($cpt + 1).'</a></li>';
4606					}
4607				}
4608				$cpt++;
4609			} while ($cpt < $nbpages && $cpt <= ($page + $maxnbofpage));
4610
4611			if (empty($pagenavastextinput)) {
4612				if ($cpt < $nbpages)
4613				{
4614					if ($cpt < $nbpages - 2) $pagelist .= '<li class="pagination"><span class="inactive">...</span></li>';
4615					elseif ($cpt == $nbpages - 2) $pagelist .= '<li class="pagination"><a href="'.$file.'?page='.($nbpages - 2).$options.'">'.($nbpages - 1).'</a></li>';
4616					$pagelist .= '<li class="pagination"><a href="'.$file.'?page='.($nbpages - 1).$options.'">'.$nbpages.'</a></li>';
4617				}
4618			} else {
4619				//var_dump($page.' '.$cpt.' '.$nbpages);
4620				//if (($page + 1) < $nbpages) {
4621					$pagelist .= '<li class="pagination"><a href="'.$file.'?page='.($nbpages - 1).$options.'">'.$nbpages.'</a></li>';
4622				//}
4623			}
4624		} else {
4625			$pagelist .= '<li class="pagination"><span class="active">'.($page + 1)."</li>";
4626		}
4627	}
4628
4629	if ($savlimit || $morehtmlright || $morehtmlrightbeforearrow) {
4630		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
4631	}
4632
4633	// js to autoselect page field on focus
4634	if ($pagenavastextinput) {
4635		print ajax_autoselect('.pageplusone');
4636	}
4637
4638	print '</td>';
4639
4640	print '</tr></table>'."\n";
4641	print "<!-- End title -->\n\n";
4642}
4643
4644/**
4645 *	Function to show navigation arrows into lists
4646 *
4647 *	@param	int				$page				Number of page
4648 *	@param	string			$file				Page URL (in most cases provided with $_SERVER["PHP_SELF"])
4649 *	@param	string			$options         	Other url parameters to propagate ("" by default, may include sortfield and sortorder)
4650 *	@param	integer			$nextpage	    	Do we show a next page button
4651 *	@param	string			$betweenarrows		HTML content to show between arrows. MUST contains '<li> </li>' tags or '<li><span> </span></li>'.
4652 *  @param	string			$afterarrows		HTML content to show after arrows. Must NOT contains '<li> </li>' tags.
4653 *  @param  int             $limit              Max nb of record to show  (-1 = no combo with limit, 0 = no limit, > 0 = limit)
4654 *	@param	int		        $totalnboflines		Total number of records/lines for all pages (if known)
4655 *  @param  int             $hideselectlimit    Force to hide select limit
4656 *  @param	string			$beforearrows		HTML content to show before arrows. Must NOT contains '<li> </li>' tags.
4657 *	@return	void
4658 */
4659function print_fleche_navigation($page, $file, $options = '', $nextpage = 0, $betweenarrows = '', $afterarrows = '', $limit = -1, $totalnboflines = 0, $hideselectlimit = 0, $beforearrows = '')
4660{
4661	global $conf, $langs;
4662
4663	print '<div class="pagination"><ul>';
4664	if ($beforearrows)
4665	{
4666		print '<li class="paginationbeforearrows">';
4667		print $beforearrows;
4668		print '</li>';
4669	}
4670	if ((int) $limit > 0 && empty($hideselectlimit))
4671	{
4672		$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';
4673		//$pagesizechoices.=',0:'.$langs->trans("All");     // Not yet supported
4674		//$pagesizechoices.=',2:2';
4675		if (!empty($conf->global->MAIN_PAGESIZE_CHOICES)) $pagesizechoices = $conf->global->MAIN_PAGESIZE_CHOICES;
4676
4677		print '<li class="pagination">';
4678		print '<select class="flat selectlimit" name="limit" title="'.dol_escape_htmltag($langs->trans("MaxNbOfRecordPerPage")).'">';
4679		$tmpchoice = explode(',', $pagesizechoices);
4680		$tmpkey = $limit.':'.$limit;
4681		if (!in_array($tmpkey, $tmpchoice)) $tmpchoice[] = $tmpkey;
4682		$tmpkey = $conf->liste_limit.':'.$conf->liste_limit;
4683		if (!in_array($tmpkey, $tmpchoice)) $tmpchoice[] = $tmpkey;
4684		asort($tmpchoice, SORT_NUMERIC);
4685		foreach ($tmpchoice as $val)
4686		{
4687			$selected = '';
4688			$tmp = explode(':', $val);
4689			$key = $tmp[0];
4690			$val = $tmp[1];
4691			if ($key != '' && $val != '')
4692			{
4693				if ((int) $key == (int) $limit)
4694				{
4695					$selected = ' selected="selected"';
4696				}
4697				print '<option name="'.$key.'"'.$selected.'>'.dol_escape_htmltag($val).'</option>'."\n";
4698			}
4699		}
4700		print '</select>';
4701		if ($conf->use_javascript_ajax)
4702		{
4703			print '<!-- JS CODE TO ENABLE select limit to launch submit of page -->
4704            		<script>
4705                	jQuery(document).ready(function () {
4706            	  		jQuery(".selectlimit").change(function() {
4707                            console.log("Change limit. Send submit");
4708                            $(this).parents(\'form:first\').submit();
4709            	  		});
4710                	});
4711            		</script>
4712                ';
4713		}
4714		print '</li>';
4715	}
4716	if ($page > 0)
4717	{
4718		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>';
4719	}
4720	if ($betweenarrows)
4721	{
4722		print '<!--<div class="betweenarrows nowraponall inline-block">-->';
4723		print $betweenarrows;
4724		print '<!--</div>-->';
4725	}
4726	if ($nextpage > 0)
4727	{
4728		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>';
4729	}
4730	if ($afterarrows)
4731	{
4732		print '<li class="paginationafterarrows">';
4733		print $afterarrows;
4734		print '</li>';
4735	}
4736	print '</ul></div>'."\n";
4737}
4738
4739
4740/**
4741 *	Return a string with VAT rate label formated for view output
4742 *	Used into pdf and HTML pages
4743 *
4744 *	@param	string	$rate			Rate value to format ('19.6', '19,6', '19.6%', '19,6%', '19.6 (CODEX)', ...)
4745 *  @param	boolean	$addpercent		Add a percent % sign in output
4746 *	@param	int		$info_bits		Miscellaneous information on vat (0=Default, 1=French NPR vat)
4747 *	@param	int		$usestarfornpr	-1=Never show, 0 or 1=Use '*' for NPR vat rates
4748 *  @return	string					String with formated amounts ('19,6' or '19,6%' or '8.5% (NPR)' or '8.5% *' or '19,6 (CODEX)')
4749 */
4750function vatrate($rate, $addpercent = false, $info_bits = 0, $usestarfornpr = 0)
4751{
4752	$morelabel = '';
4753
4754	if (preg_match('/%/', $rate))
4755	{
4756		$rate = str_replace('%', '', $rate);
4757		$addpercent = true;
4758	}
4759	if (preg_match('/\((.*)\)/', $rate, $reg))
4760	{
4761		$morelabel = ' ('.$reg[1].')';
4762		$rate = preg_replace('/\s*'.preg_quote($morelabel, '/').'/', '', $rate);
4763	}
4764	if (preg_match('/\*/', $rate))
4765	{
4766		$rate = str_replace('*', '', $rate);
4767		$info_bits |= 1;
4768	}
4769
4770	// If rate is '9/9/9' we don't change it.  If rate is '9.000' we apply price()
4771	if (!preg_match('/\//', $rate)) $ret = price($rate, 0, '', 0, 0).($addpercent ? '%' : '');
4772	else {
4773		// TODO Split on / and output with a price2num to have clean numbers without ton of 000.
4774		$ret = $rate.($addpercent ? '%' : '');
4775	}
4776	if (($info_bits & 1) && $usestarfornpr >= 0) $ret .= ' *';
4777	$ret .= $morelabel;
4778	return $ret;
4779}
4780
4781
4782/**
4783 *		Function to format a value into an amount for visual output
4784 *		Function used into PDF and HTML pages
4785 *
4786 *		@param	float		$amount			Amount to format
4787 *		@param	integer		$form			Type of format, HTML or not (not by default)
4788 *		@param	Translate	$outlangs		Object langs for output
4789 *		@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.
4790 *		@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)
4791 *		@param	int			$forcerounding	Force the number of decimal to forcerounding decimal (-1=do not force)
4792 *		@param	string		$currency_code	To add currency symbol (''=add nothing, 'auto'=Use default currency, 'XXX'=add currency symbols for XXX currency)
4793 *		@return	string						Chaine avec montant formate
4794 *
4795 *		@see	price2num()					Revert function of price
4796 */
4797function price($amount, $form = 0, $outlangs = '', $trunc = 1, $rounding = -1, $forcerounding = -1, $currency_code = '')
4798{
4799	global $langs, $conf;
4800
4801	// Clean parameters
4802	if (empty($amount)) $amount = 0; // To have a numeric value if amount not defined or = ''
4803	$amount = (is_numeric($amount) ? $amount : 0); // Check if amount is numeric, for example, an error occured when amount value = o (letter) instead 0 (number)
4804	if ($rounding < 0) $rounding = min($conf->global->MAIN_MAX_DECIMALS_UNIT, $conf->global->MAIN_MAX_DECIMALS_TOT);
4805	$nbdecimal = $rounding;
4806
4807	// Output separators by default (french)
4808	$dec = ','; $thousand = ' ';
4809
4810	// If $outlangs not forced, we use use language
4811	if (!is_object($outlangs)) $outlangs = $langs;
4812
4813	if ($outlangs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal")  $dec = $outlangs->transnoentitiesnoconv("SeparatorDecimal");
4814	if ($outlangs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") $thousand = $outlangs->transnoentitiesnoconv("SeparatorThousand");
4815	if ($thousand == 'None') $thousand = '';
4816	elseif ($thousand == 'Space') $thousand = ' ';
4817	//print "outlangs=".$outlangs->defaultlang." amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
4818
4819	//print "amount=".$amount."-";
4820	$amount = str_replace(',', '.', $amount); // should be useless
4821	//print $amount."-";
4822	$datas = explode('.', $amount);
4823	$decpart = isset($datas[1]) ? $datas[1] : '';
4824	$decpart = preg_replace('/0+$/i', '', $decpart); // Supprime les 0 de fin de partie decimale
4825	//print "decpart=".$decpart."<br>";
4826	$end = '';
4827
4828	// We increase nbdecimal if there is more decimal than asked (to not loose information)
4829	if (dol_strlen($decpart) > $nbdecimal) $nbdecimal = dol_strlen($decpart);
4830	// Si on depasse max
4831	if ($trunc && $nbdecimal > $conf->global->MAIN_MAX_DECIMALS_SHOWN)
4832	{
4833		$nbdecimal = $conf->global->MAIN_MAX_DECIMALS_SHOWN;
4834		if (preg_match('/\.\.\./i', $conf->global->MAIN_MAX_DECIMALS_SHOWN))
4835		{
4836			// Si un affichage est tronque, on montre des ...
4837			$end = '...';
4838		}
4839	}
4840
4841	// If force rounding
4842	if ($forcerounding >= 0) $nbdecimal = $forcerounding;
4843
4844	// Format number
4845	$output = number_format($amount, $nbdecimal, $dec, $thousand);
4846	if ($form)
4847	{
4848		$output = preg_replace('/\s/', '&nbsp;', $output);
4849		$output = preg_replace('/\'/', '&#039;', $output);
4850	}
4851	// Add symbol of currency if requested
4852	$cursymbolbefore = $cursymbolafter = '';
4853	if ($currency_code)
4854	{
4855		if ($currency_code == 'auto') $currency_code = $conf->currency;
4856
4857		$listofcurrenciesbefore = array('AUD', 'CAD', 'CNY', 'COP', 'CLP', 'GBP', 'HKD', 'MXN', 'PEN', 'USD');
4858		$listoflanguagesbefore = array('nl_NL');
4859		if (in_array($currency_code, $listofcurrenciesbefore) || in_array($outlangs->defaultlang, $listoflanguagesbefore))
4860		{
4861			$cursymbolbefore .= $outlangs->getCurrencySymbol($currency_code);
4862		} else {
4863			$tmpcur = $outlangs->getCurrencySymbol($currency_code);
4864			$cursymbolafter .= ($tmpcur == $currency_code ? ' '.$tmpcur : $tmpcur);
4865		}
4866	}
4867	$output = $cursymbolbefore.$output.$end.($cursymbolafter ? ' ' : '').$cursymbolafter;
4868
4869	return $output;
4870}
4871
4872/**
4873 *	Function that return a number with universal decimal format (decimal separator is '.') from an amount typed by a user.
4874 *	Function to use on each input amount before any numeric test or database insert. A better name for this function
4875 *  should be roundtext2num().
4876 *
4877 *	@param	string|float	$amount			Amount to convert/clean or round
4878 *	@param	string			$rounding		''=No rounding
4879 * 											'MU'=Round to Max unit price (MAIN_MAX_DECIMALS_UNIT)
4880 *											'MT'=Round to Max for totals with Tax (MAIN_MAX_DECIMALS_TOT)
4881 *											'MS'=Round to Max for stock quantity (MAIN_MAX_DECIMALS_STOCK)
4882 *      		                            'CU'=Round to Max unit price of foreign currency accuracy
4883 *      		                            'CT'=Round to Max for totals with Tax of foreign currency accuracy
4884 *											Numeric = Nb of digits for rounding
4885 * 	@param	int				$option			Put 1 if you know that content is already universal format number (so no correction on decimal will be done)
4886 * 											Put 2 if you know that number is a user input (so we know we don't have to fix decimal separator).
4887 *	@return	string							Amount with universal numeric format (Example: '99.99999').
4888 *											If conversion fails, it return text unchanged if $rounding = '' or '0' if $rounding is defined.
4889 *											If amount is null or '', it returns '' if $rounding = '' or '0' if $rounding is defined..
4890 *
4891 *	@see    price()							Opposite function of price2num
4892 */
4893function price2num($amount, $rounding = '', $option = 0)
4894{
4895	global $langs, $conf;
4896
4897	// Round PHP function does not allow number like '1,234.56' nor '1.234,56' nor '1 234,56'
4898	// Numbers must be '1234.56'
4899	// Decimal delimiter for PHP and database SQL requests must be '.'
4900	$dec = ','; $thousand = ' ';
4901	if ($langs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal")  $dec = $langs->transnoentitiesnoconv("SeparatorDecimal");
4902	if ($langs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") $thousand = $langs->transnoentitiesnoconv("SeparatorThousand");
4903	if ($thousand == 'None') $thousand = '';
4904	elseif ($thousand == 'Space') $thousand = ' ';
4905	//print "amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
4906
4907	// Convert value to universal number format (no thousand separator, '.' as decimal separator)
4908	if ($option != 1) {	// If not a PHP number or unknown, we change or clean format
4909		//print "\n".'PP'.$amount.' - '.$dec.' - '.$thousand.' - '.intval($amount).'<br>';
4910		if (!is_numeric($amount)) {
4911			$amount = preg_replace('/[a-zA-Z\/\\\*\(\)\<\>\_]/', '', $amount);
4912		}
4913
4914		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
4915			$amount = str_replace($thousand, '', $amount);
4916		}
4917
4918		// Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
4919		// to format defined by LC_NUMERIC after a calculation and we want source format to be like defined by Dolibarr setup.
4920		// So if number was already a good number, it is converted into local Dolibarr setup.
4921		if (is_numeric($amount))
4922		{
4923			// We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
4924			$temps = sprintf("%0.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
4925			$temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
4926			$nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
4927			$amount = number_format($amount, $nbofdec, $dec, $thousand);
4928		}
4929		//print "QQ".$amount."<br>\n";
4930
4931		// Now make replace (the main goal of function)
4932		if ($thousand != ',' && $thousand != '.') {
4933			$amount = str_replace(',', '.', $amount); // To accept 2 notations for french users
4934		}
4935		$amount = str_replace(' ', '', $amount); // To avoid spaces
4936		$amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
4937		$amount = str_replace($dec, '.', $amount);
4938	}
4939	//print ' XX'.$amount.' '.$rounding;
4940
4941	// Now, make a rounding if required
4942	if ($rounding)
4943	{
4944		$nbofdectoround = '';
4945		if ($rounding == 'MU') {
4946			$nbofdectoround = $conf->global->MAIN_MAX_DECIMALS_UNIT;
4947		}
4948		elseif ($rounding == 'MT') {
4949			$nbofdectoround = $conf->global->MAIN_MAX_DECIMALS_TOT;
4950		}
4951		elseif ($rounding == 'MS') {
4952			$nbofdectoround = empty($conf->global->MAIN_MAX_DECIMALS_STOCK) ? 5 : $conf->global->MAIN_MAX_DECIMALS_STOCK;
4953		}
4954		elseif ($rounding == 'CU') {
4955			$nbofdectoround = max($conf->global->MAIN_MAX_DECIMALS_UNIT, 8);	// TODO Use param of currency
4956		}
4957		elseif ($rounding == 'CT') {
4958			$nbofdectoround = max($conf->global->MAIN_MAX_DECIMALS_TOT, 8);		// TODO Use param of currency
4959		}
4960		elseif (is_numeric($rounding))  $nbofdectoround = $rounding;
4961		//print " RR".$amount.' - '.$nbofdectoround.'<br>';
4962		if (dol_strlen($nbofdectoround)) $amount = round(is_string($amount) ? (float) $amount : $amount, $nbofdectoround); // $nbofdectoround can be 0.
4963		else return 'ErrorBadParameterProvidedToFunction';
4964		//print ' SS'.$amount.' - '.$nbofdec.' - '.$dec.' - '.$thousand.' - '.$nbofdectoround.'<br>';
4965
4966		// Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
4967		// to format defined by LC_NUMERIC after a calculation and we want source format to be defined by Dolibarr setup.
4968		if (is_numeric($amount))
4969		{
4970			// We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
4971			$temps = sprintf("%0.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
4972			$temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
4973			$nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
4974			$amount = number_format($amount, min($nbofdec, $nbofdectoround), $dec, $thousand); // Convert amount to format with dolibarr dec and thousand
4975		}
4976		//print "TT".$amount.'<br>';
4977
4978		// Always make replace because each math function (like round) replace
4979		// with local values and we want a number that has a SQL string format x.y
4980		if ($thousand != ',' && $thousand != '.') $amount = str_replace(',', '.', $amount); // To accept 2 notations for french users
4981		$amount = str_replace(' ', '', $amount); // To avoid spaces
4982		$amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
4983		$amount = str_replace($dec, '.', $amount);
4984	}
4985
4986	return $amount;
4987}
4988
4989/**
4990 * Output a dimension with best unit
4991 *
4992 * @param   float       $dimension      Dimension
4993 * @param   int         $unit           Unit scale of dimension (Example: 0=kg, -3=g, -6=mg, 98=ounce, 99=pound, ...)
4994 * @param   string      $type           'weight', 'volume', ...
4995 * @param   Translate   $outputlangs    Translate language object
4996 * @param   int         $round          -1 = non rounding, x = number of decimal
4997 * @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)
4998 * @return  string                      String to show dimensions
4999 */
5000function showDimensionInBestUnit($dimension, $unit, $type, $outputlangs, $round = -1, $forceunitoutput = 'no')
5001{
5002	require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
5003
5004	if (($forceunitoutput == 'no' && $dimension < 1 / 10000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -6))
5005	{
5006		$dimension = $dimension * 1000000;
5007		$unit = $unit - 6;
5008	} elseif (($forceunitoutput == 'no' && $dimension < 1 / 10 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -3))
5009	{
5010		$dimension = $dimension * 1000;
5011		$unit = $unit - 3;
5012	} elseif (($forceunitoutput == 'no' && $dimension > 100000000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 6))
5013	{
5014		$dimension = $dimension / 1000000;
5015		$unit = $unit + 6;
5016	} elseif (($forceunitoutput == 'no' && $dimension > 100000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 3))
5017	{
5018		$dimension = $dimension / 1000;
5019		$unit = $unit + 3;
5020	}
5021	// Special case when we want output unit into pound or ounce
5022	/* TODO
5023	if ($unit < 90 && $type == 'weight' && is_numeric($forceunitoutput) && (($forceunitoutput == 98) || ($forceunitoutput == 99))
5024	{
5025	    $dimension = // convert dimension from standard unit into ounce or pound
5026	    $unit = $forceunitoutput;
5027	}
5028	if ($unit > 90 && $type == 'weight' && is_numeric($forceunitoutput) && $forceunitoutput < 90)
5029	{
5030	    $dimension = // convert dimension from standard unit into ounce or pound
5031	    $unit = $forceunitoutput;
5032	}*/
5033
5034	$ret = price($dimension, 0, $outputlangs, 0, 0, $round).' '.measuringUnitString(0, $type, $unit);
5035
5036	return $ret;
5037}
5038
5039
5040/**
5041 *	Return localtax rate for a particular vat, when selling a product with vat $vatrate, from a $thirdparty_buyer to a $thirdparty_seller
5042 *  Note: This function applies same rules than get_default_tva
5043 *
5044 * 	@param	float		$vatrate		        Vat rate. Can be '8.5' or '8.5 (VATCODEX)' for example
5045 * 	@param  int			$local		         	Local tax to search and return (1 or 2 return only tax rate 1 or tax rate 2)
5046 *  @param  Societe		$thirdparty_buyer    	Object of buying third party
5047 *  @param	Societe		$thirdparty_seller		Object of selling third party ($mysoc if not defined)
5048 *  @param	int			$vatnpr					If vat rate is NPR or not
5049 * 	@return	mixed			   					0 if not found, localtax rate if found
5050 *  @see get_default_tva()
5051 */
5052function get_localtax($vatrate, $local, $thirdparty_buyer = "", $thirdparty_seller = "", $vatnpr = 0)
5053{
5054	global $db, $conf, $mysoc;
5055
5056	if (empty($thirdparty_seller) || !is_object($thirdparty_seller)) $thirdparty_seller = $mysoc;
5057
5058	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);
5059
5060	$vatratecleaned = $vatrate;
5061	$reg = array();
5062	if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) {     // If vat is "xx (yy)"
5063		$vatratecleaned = trim($reg[1]);
5064		$vatratecode = $reg[2];
5065	}
5066
5067	/*if ($thirdparty_buyer->country_code != $thirdparty_seller->country_code)
5068	{
5069		return 0;
5070	}*/
5071
5072	// Some test to guess with no need to make database access
5073	if ($mysoc->country_code == 'ES') { // For spain localtaxes 1 and 2, tax is qualified if buyer use local tax
5074		if ($local == 1) {
5075			if (!$mysoc->localtax1_assuj || (string) $vatratecleaned == "0") return 0;
5076			if ($thirdparty_seller->id == $mysoc->id) {
5077				if (!$thirdparty_buyer->localtax1_assuj) return 0;
5078			} else {
5079				if (!$thirdparty_seller->localtax1_assuj) return 0;
5080			}
5081		}
5082
5083		if ($local == 2) {
5084			//if (! $mysoc->localtax2_assuj || (string) $vatratecleaned == "0") return 0;
5085			if (!$mysoc->localtax2_assuj) return 0; // If main vat is 0, IRPF may be different than 0.
5086			if ($thirdparty_seller->id == $mysoc->id) {
5087				if (!$thirdparty_buyer->localtax2_assuj) return 0;
5088			} else {
5089				if (!$thirdparty_seller->localtax2_assuj) return 0;
5090			}
5091		}
5092	} else {
5093		if ($local == 1 && !$thirdparty_seller->localtax1_assuj) return 0;
5094		if ($local == 2 && !$thirdparty_seller->localtax2_assuj) return 0;
5095	}
5096
5097	// For some country MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY is forced to on.
5098	if (in_array($mysoc->country_code, array('ES'))) {
5099		$conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY = 1;
5100	}
5101
5102	// Search local taxes
5103	if (!empty($conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY))
5104	{
5105		if ($local == 1) {
5106			if ($thirdparty_seller != $mysoc) {
5107				if (!isOnlyOneLocalTax($local))  // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
5108				{
5109					return $thirdparty_seller->localtax1_value;
5110				}
5111			} else { // i am the seller
5112				if (!isOnlyOneLocalTax($local)) { // TODO If seller is me, why not always returning this, even if there is only one locatax vat.
5113					return $conf->global->MAIN_INFO_VALUE_LOCALTAX1;
5114				}
5115			}
5116		}
5117		if ($local == 2) {
5118			if ($thirdparty_seller != $mysoc) {
5119				if (!isOnlyOneLocalTax($local))  // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
5120				// TODO We should also return value defined on thirdparty only if defined
5121				{
5122					return $thirdparty_seller->localtax2_value;
5123				}
5124			} else { // i am the seller
5125				if (in_array($mysoc->country_code, array('ES'))) {
5126					return $thirdparty_buyer->localtax2_value;
5127				} else {
5128					return $conf->global->MAIN_INFO_VALUE_LOCALTAX2;
5129				}
5130			}
5131		}
5132	}
5133
5134	// By default, search value of local tax on line of common tax
5135	$sql = "SELECT t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
5136   	$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
5137   	$sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($thirdparty_seller->country_code)."'";
5138   	$sql .= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
5139   	if (!empty($vatratecode)) $sql .= " AND t.code ='".$db->escape($vatratecode)."'"; // If we have the code, we use it in priority
5140   	else $sql .= " AND t.recuperableonly = '".$db->escape($vatnpr)."'";
5141   	dol_syslog("get_localtax", LOG_DEBUG);
5142   	$resql = $db->query($sql);
5143
5144   	if ($resql)
5145   	{
5146   		$obj = $db->fetch_object($resql);
5147   		if ($obj) {
5148	   		if ($local == 1) return $obj->localtax1;
5149   			elseif ($local == 2) return $obj->localtax2;
5150   		}
5151	}
5152
5153	return 0;
5154}
5155
5156
5157/**
5158 * Return true if LocalTax (1 or 2) is unique.
5159 * Example: If localtax1 is 5 on line with highest common vat rate, return true
5160 * Example: If localtax1 is 5:8:15 on line with highest common vat rate, return false
5161 *
5162 * @param   int 	$local	Local tax to test (1 or 2)
5163 * @return  boolean 		True if LocalTax have multiple values, False if not
5164 */
5165function isOnlyOneLocalTax($local)
5166{
5167	$tax = get_localtax_by_third($local);
5168
5169	$valors = explode(":", $tax);
5170
5171	if (count($valors) > 1) {
5172		return false;
5173	} else {
5174		return true;
5175	}
5176}
5177
5178/**
5179 * Get values of localtaxes (1 or 2) for company country for the common vat with the highest value
5180 *
5181 * @param	int		$local 	LocalTax to get
5182 * @return	number			Values of localtax
5183 */
5184function get_localtax_by_third($local)
5185{
5186	global $db, $mysoc;
5187	$sql = "SELECT t.localtax1, t.localtax2 ";
5188	$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t inner join ".MAIN_DB_PREFIX."c_country as c ON c.rowid=t.fk_pays";
5189	$sql .= " WHERE c.code = '".$db->escape($mysoc->country_code)."' AND t.active = 1 AND t.taux=(";
5190	$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";
5191	$sql .= "  WHERE c.code = '".$db->escape($mysoc->country_code)."' AND tt.active = 1";
5192	$sql .= "  )";
5193
5194	$resql = $db->query($sql);
5195	if ($resql)
5196	{
5197		$obj = $db->fetch_object($resql);
5198		if ($local == 1) return $obj->localtax1;
5199		elseif ($local == 2) return $obj->localtax2;
5200	}
5201
5202	return 0;
5203}
5204
5205
5206/**
5207 *  Get tax (VAT) main information from Id.
5208 *  You can also call getLocalTaxesFromRate() after to get only localtax fields.
5209 *
5210 *  @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.
5211 *  @param	Societe	    $buyer         		Company object
5212 *  @param	Societe	    $seller        		Company object
5213 *  @param  int         $firstparamisid     1 if first param is id into table (use this if you can)
5214 *  @return	array       	  				array('rowid'=> , 'code'=> ...)
5215 *  @see getLocalTaxesFromRate()
5216 */
5217function getTaxesFromId($vatrate, $buyer = null, $seller = null, $firstparamisid = 1)
5218{
5219	global $db, $mysoc;
5220
5221	dol_syslog("getTaxesFromId vat id or rate = ".$vatrate);
5222
5223	// Search local taxes
5224	$sql = "SELECT t.rowid, t.code, t.taux as rate, t.recuperableonly as npr, t.accountancy_code_sell, t.accountancy_code_buy,";
5225	$sql .= " t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type";
5226	$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t";
5227	if ($firstparamisid) {
5228		$sql .= " WHERE t.rowid = ".(int) $vatrate;
5229	} else {
5230		$vatratecleaned = $vatrate;
5231		$vatratecode = '';
5232		$reg = array();
5233		if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg))      // If vat is "xx (yy)"
5234		{
5235			$vatratecleaned = $reg[1];
5236			$vatratecode = $reg[2];
5237		}
5238
5239		$sql .= ", ".MAIN_DB_PREFIX."c_country as c";
5240		/*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 ??
5241		else $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($seller->country_code)."'";*/
5242		$sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($seller->country_code)."'";
5243		$sql .= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
5244		if ($vatratecode) $sql .= " AND t.code = '".$db->escape($vatratecode)."'";
5245	}
5246
5247	$resql = $db->query($sql);
5248	if ($resql) {
5249		$obj = $db->fetch_object($resql);
5250		if ($obj) return array(
5251			'rowid'=>$obj->rowid,
5252			'code'=>$obj->code,
5253			'rate'=>$obj->rate,
5254			'localtax1'=>$obj->localtax1,
5255			'localtax1_type'=>$obj->localtax1_type,
5256			'localtax2'=>$obj->localtax2,
5257			'localtax2_type'=>$obj->localtax2_type,
5258			'npr'=>$obj->npr,
5259			'accountancy_code_sell'=>$obj->accountancy_code_sell,
5260			'accountancy_code_buy'=>$obj->accountancy_code_buy
5261		);
5262		else return array();
5263	} else dol_print_error($db);
5264
5265	return array();
5266}
5267
5268/**
5269 *  Get type and rate of localtaxes for a particular vat rate/country of a thirdparty.
5270 *  This does not take into account the seller setup if subject to vat or not, only country.
5271 *
5272 *  TODO This function is ALSO called to retrieve type for building PDF. Such call of function must be removed.
5273 *  Instead this function must be called when adding a line to get the array of possible values for localtax and type, and then
5274 *  provide the selected value to the function calcul_price_total.
5275 *
5276 *  @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.
5277 *  @param	int		    $local              Number of localtax (1 or 2, or 0 to return 1 & 2)
5278 *  @param	Societe	    $buyer         		Company object
5279 *  @param	Societe	    $seller        		Company object
5280 *  @param  int         $firstparamisid     1 if first param is ID into table instead of Rate+code (use this if you can)
5281 *  @return	array    	    				array(localtax_type1(1-6 or 0 if not found), rate localtax1, localtax_type2, rate localtax2, accountancycodecust, accountancycodesupp)
5282 *  @see getTaxesFromId()
5283 */
5284function getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid = 0)
5285{
5286	global $db, $mysoc;
5287
5288	dol_syslog("getLocalTaxesFromRate vatrate=".$vatrate." local=".$local);
5289
5290	// Search local taxes
5291	$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";
5292	$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t";
5293	if ($firstparamisid) {
5294		$sql .= " WHERE t.rowid = ".(int) $vatrate;
5295	} else {
5296		$vatratecleaned = $vatrate;
5297		$vatratecode = '';
5298		$reg = array();
5299		if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) {     // If vat is "x.x (yy)"
5300			$vatratecleaned = $reg[1];
5301			$vatratecode = $reg[2];
5302		}
5303
5304		$sql .= ", ".MAIN_DB_PREFIX."c_country as c";
5305		if ($mysoc->country_code == 'ES') $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($buyer->country_code)."'"; // local tax in spain use the buyer country ??
5306		else $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape(empty($seller->country_code) ? $mysoc->country_code : $seller->country_code)."'";
5307		$sql .= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
5308		if ($vatratecode) $sql .= " AND t.code = '".$db->escape($vatratecode)."'";
5309	}
5310
5311	$resql = $db->query($sql);
5312	if ($resql) {
5313		$obj = $db->fetch_object($resql);
5314
5315		if ($obj) {
5316			$vateratestring = $obj->rate.($obj->code ? ' ('.$obj->code.')' : '');
5317
5318			if ($local == 1) {
5319				return array($obj->localtax1_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
5320			} elseif ($local == 2) {
5321				return array($obj->localtax2_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
5322			} else {
5323				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);
5324			}
5325		}
5326	}
5327
5328	return array();
5329}
5330
5331/**
5332 *	Return vat rate of a product in a particular selling country or default country vat if product is unknown
5333 *  Function called by get_default_tva
5334 *
5335 *  @param	int			$idprod          	Id of product or 0 if not a predefined product
5336 *  @param  Societe		$thirdparty_seller  Thirdparty with a ->country_code defined (FR, US, IT, ...)
5337 *	@param	int			$idprodfournprice	Id product_fournisseur_price (for "supplier" proposal/order/invoice)
5338 *  @return float|string   				    Vat rate to use with format 5.0 or '5.0 (XXX)'
5339 *  @see get_product_localtax_for_country()
5340 */
5341function get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice = 0)
5342{
5343	global $db, $conf, $mysoc;
5344
5345	require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
5346
5347	$ret = 0;
5348	$found = 0;
5349
5350	if ($idprod > 0)
5351	{
5352		// Load product
5353		$product = new Product($db);
5354		$result = $product->fetch($idprod);
5355
5356		if ($mysoc->country_code == $thirdparty_seller->country_code) // If selling country is ours
5357		{
5358			if ($idprodfournprice > 0)     // We want vat for product for a "supplier" object
5359			{
5360				$product->get_buyprice($idprodfournprice, 0, 0, 0);
5361				$ret = $product->vatrate_supplier;
5362				if ($product->default_vat_code) $ret .= ' ('.$product->default_vat_code.')';
5363			} else {
5364				$ret = $product->tva_tx; // Default vat of product we defined
5365				if ($product->default_vat_code) $ret .= ' ('.$product->default_vat_code.')';
5366			}
5367			$found = 1;
5368		} else {
5369			// TODO Read default product vat according to countrycode and product. Vat for couple countrycode/product is a feature not implemeted yet.
5370			// May be usefull/required if hidden option SERVICE_ARE_ECOMMERCE_200238EC is on
5371		}
5372	}
5373
5374	if (!$found)
5375	{
5376		if (empty($conf->global->MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS))
5377		{
5378			// If vat of product for the country not found or not defined, we return the first higher vat of country.
5379			$sql = "SELECT t.taux as vat_rate, t.code as default_vat_code";
5380			$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
5381			$sql .= " WHERE t.active=1 AND t.fk_pays = c.rowid AND c.code='".$db->escape($thirdparty_seller->country_code)."'";
5382			$sql .= " ORDER BY t.taux DESC, t.code ASC, t.recuperableonly ASC";
5383			$sql .= $db->plimit(1);
5384
5385			$resql = $db->query($sql);
5386			if ($resql)
5387			{
5388				$obj = $db->fetch_object($resql);
5389				if ($obj)
5390				{
5391					$ret = $obj->vat_rate;
5392					if ($obj->default_vat_code) $ret .= ' ('.$obj->default_vat_code.')';
5393				}
5394				$db->free($sql);
5395			} else dol_print_error($db);
5396		} else $ret = $conf->global->MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS; // Forced value if autodetect fails
5397	}
5398
5399	dol_syslog("get_product_vat_for_country: ret=".$ret);
5400	return $ret;
5401}
5402
5403/**
5404 *	Return localtax vat rate of a product in a particular selling country or default country vat if product is unknown
5405 *
5406 *  @param	int		$idprod         		Id of product
5407 *  @param  int		$local          		1 for localtax1, 2 for localtax 2
5408 *  @param  Societe	$thirdparty_seller    	Thirdparty with a ->country_code defined (FR, US, IT, ...)
5409 *  @return int             				<0 if KO, Vat rate if OK
5410 *  @see get_product_vat_for_country()
5411 */
5412function get_product_localtax_for_country($idprod, $local, $thirdparty_seller)
5413{
5414	global $db, $mysoc;
5415
5416	if (!class_exists('Product')) {
5417		require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
5418	}
5419
5420	$ret = 0;
5421	$found = 0;
5422
5423	if ($idprod > 0)
5424	{
5425		// Load product
5426		$product = new Product($db);
5427		$result = $product->fetch($idprod);
5428
5429		if ($mysoc->country_code == $thirdparty_seller->country_code) // If selling country is ours
5430		{
5431			/* Not defined yet, so we don't use this
5432			if ($local==1) $ret=$product->localtax1_tx;
5433			elseif ($local==2) $ret=$product->localtax2_tx;
5434			$found=1;
5435			*/
5436		} else {
5437			// TODO Read default product vat according to countrycode and product
5438		}
5439	}
5440
5441	if (!$found)
5442	{
5443		// If vat of product for the country not found or not defined, we return higher vat of country.
5444		$sql = "SELECT taux as vat_rate, localtax1, localtax2";
5445		$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
5446		$sql .= " WHERE t.active=1 AND t.fk_pays = c.rowid AND c.code='".$db->escape($thirdparty_seller->country_code)."'";
5447		$sql .= " ORDER BY t.taux DESC, t.recuperableonly ASC";
5448		$sql .= $db->plimit(1);
5449
5450		$resql = $db->query($sql);
5451		if ($resql)
5452		{
5453			$obj = $db->fetch_object($resql);
5454			if ($obj)
5455			{
5456				if ($local == 1) $ret = $obj->localtax1;
5457				elseif ($local == 2) $ret = $obj->localtax2;
5458			}
5459		} else dol_print_error($db);
5460	}
5461
5462	dol_syslog("get_product_localtax_for_country: ret=".$ret);
5463	return $ret;
5464}
5465
5466/**
5467 *	Function that return vat rate of a product line (according to seller, buyer and product vat rate)
5468 *   Si vendeur non assujeti a TVA, TVA par defaut=0. Fin de regle.
5469 *	 Si le (pays vendeur = pays acheteur) alors TVA par defaut=TVA du produit vendu. Fin de regle.
5470 *	 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.
5471 *	 Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = particulier ou entreprise sans num TVA intra) alors TVA par defaut=TVA du produit vendu. Fin de regle
5472 *	 Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = entreprise avec num TVA) intra alors TVA par defaut=0. Fin de regle
5473 *	 Sinon TVA proposee par defaut=0. Fin de regle.
5474 *
5475 *	@param	Societe		$thirdparty_seller    	Objet societe vendeuse
5476 *	@param  Societe		$thirdparty_buyer   	Objet societe acheteuse
5477 *	@param  int			$idprod					Id product
5478 *	@param	int			$idprodfournprice		Id product_fournisseur_price (for supplier order/invoice)
5479 *	@return float|string   				      	Vat rate to use with format 5.0 or '5.0 (XXX)', -1 if we can't guess it
5480 *  @see get_default_npr(), get_default_localtax()
5481 */
5482function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
5483{
5484	global $conf;
5485
5486	require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
5487
5488	// Note: possible values for tva_assuj are 0/1 or franchise/reel
5489	$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;
5490
5491	$seller_country_code = $thirdparty_seller->country_code;
5492	$seller_in_cee = isInEEC($thirdparty_seller);
5493
5494	$buyer_country_code = $thirdparty_buyer->country_code;
5495	$buyer_in_cee = isInEEC($thirdparty_buyer);
5496
5497	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 : ''));
5498
5499	// 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)
5500	// we use the buyer VAT.
5501	if (!empty($conf->global->SERVICE_ARE_ECOMMERCE_200238EC))
5502	{
5503		if ($seller_in_cee && $buyer_in_cee && !$thirdparty_buyer->isACompany())
5504		{
5505			//print 'VATRULE 0';
5506			return get_product_vat_for_country($idprod, $thirdparty_buyer, $idprodfournprice);
5507		}
5508	}
5509
5510	// If seller does not use VAT
5511	if (!$seller_use_vat)
5512	{
5513		//print 'VATRULE 1';
5514		return 0;
5515	}
5516
5517	// 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.
5518
5519	// Si le (pays vendeur = pays acheteur) alors la TVA par defaut=TVA du produit vendu. Fin de regle.
5520	if (($seller_country_code == $buyer_country_code)
5521	|| (in_array($seller_country_code, array('FR,MC')) && in_array($buyer_country_code, array('FR', 'MC')))) // Warning ->country_code not always defined
5522	{
5523		//print 'VATRULE 2';
5524		return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
5525	}
5526
5527	// 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.
5528	// Not supported
5529
5530	// Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = entreprise) alors TVA par defaut=0. Fin de regle
5531	// Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = particulier) alors TVA par defaut=TVA du produit vendu. Fin de regle
5532	if (($seller_in_cee && $buyer_in_cee))
5533	{
5534		$isacompany = $thirdparty_buyer->isACompany();
5535		if ($isacompany)
5536		{
5537			if (!empty($conf->global->MAIN_USE_VAT_OF_PRODUCT_FOR_COMPANIES_IN_EEC_WITH_INVALID_VAT_ID) && !isValidVATID($thirdparty_buyer)) {
5538				//print 'VATRULE 6';
5539				return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
5540			}
5541			//print 'VATRULE 3';
5542			return 0;
5543		} else {
5544			//print 'VATRULE 4';
5545			return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
5546		}
5547	}
5548
5549	// Si (vendeur en France et acheteur hors Communaute europeenne et acheteur particulier) alors TVA par defaut=TVA du produit vendu. Fin de regle
5550	if (!empty($conf->global->MAIN_USE_VAT_OF_PRODUCT_FOR_INDIVIDUAL_CUSTOMER_OUT_OF_EEC) && empty($buyer_in_cee) && !$thirdparty_buyer->isACompany()) {
5551		return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
5552	}
5553
5554	// Sinon la TVA proposee par defaut=0. Fin de regle.
5555	// Rem: Cela signifie qu'au moins un des 2 est hors Communaute europeenne et que le pays differe
5556	//print 'VATRULE 5';
5557	return 0;
5558}
5559
5560
5561/**
5562 *	Fonction qui renvoie si tva doit etre tva percue recuperable
5563 *
5564 *	@param	Societe		$thirdparty_seller    	Thirdparty seller
5565 *	@param  Societe		$thirdparty_buyer   	Thirdparty buyer
5566 *  @param  int			$idprod                 Id product
5567 *  @param	int			$idprodfournprice		Id supplier price for product
5568 *	@return float       			        	0 or 1
5569 *  @see get_default_tva(), get_default_localtax()
5570 */
5571function get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
5572{
5573	global $db;
5574
5575	if ($idprodfournprice > 0)
5576	{
5577		if (!class_exists('ProductFournisseur')) {
5578			require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
5579		}
5580		$prodprice = new ProductFournisseur($db);
5581		$prodprice->fetch_product_fournisseur_price($idprodfournprice);
5582		return $prodprice->fourn_tva_npr;
5583	} elseif ($idprod > 0)
5584	{
5585		if (!class_exists('Product')) {
5586			require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
5587		}
5588		$prod = new Product($db);
5589		$prod->fetch($idprod);
5590		return $prod->tva_npr;
5591	}
5592
5593	return 0;
5594}
5595
5596/**
5597 *	Function that return localtax of a product line (according to seller, buyer and product vat rate)
5598 *   Si vendeur non assujeti a TVA, TVA par defaut=0. Fin de regle.
5599 *	 Si le (pays vendeur = pays acheteur) alors TVA par defaut=TVA du produit vendu. Fin de regle.
5600 *	 Sinon TVA proposee par defaut=0. Fin de regle.
5601 *
5602 *	@param	Societe		$thirdparty_seller    	Thirdparty seller
5603 *	@param  Societe		$thirdparty_buyer   	Thirdparty buyer
5604 *  @param	int			$local					Localtax to process (1 or 2)
5605 *	@param  int			$idprod					Id product
5606 *	@return integer        				       	localtax, -1 si ne peut etre determine
5607 *  @see get_default_tva(), get_default_npr()
5608 */
5609function get_default_localtax($thirdparty_seller, $thirdparty_buyer, $local, $idprod = 0)
5610{
5611	global $mysoc;
5612
5613	if (!is_object($thirdparty_seller)) return -1;
5614	if (!is_object($thirdparty_buyer)) return -1;
5615
5616	if ($local == 1) // Localtax 1
5617	{
5618		if ($mysoc->country_code == 'ES')
5619		{
5620			if (is_numeric($thirdparty_buyer->localtax1_assuj) && !$thirdparty_buyer->localtax1_assuj) return 0;
5621		} else {
5622			// Si vendeur non assujeti a Localtax1, localtax1 par default=0
5623			if (is_numeric($thirdparty_seller->localtax1_assuj) && !$thirdparty_seller->localtax1_assuj) return 0;
5624			if (!is_numeric($thirdparty_seller->localtax1_assuj) && $thirdparty_seller->localtax1_assuj == 'localtax1off') return 0;
5625		}
5626	} elseif ($local == 2) //I Localtax 2
5627	{
5628		// Si vendeur non assujeti a Localtax2, localtax2 par default=0
5629		if (is_numeric($thirdparty_seller->localtax2_assuj) && !$thirdparty_seller->localtax2_assuj) return 0;
5630		if (!is_numeric($thirdparty_seller->localtax2_assuj) && $thirdparty_seller->localtax2_assuj == 'localtax2off') return 0;
5631	}
5632
5633	if ($thirdparty_seller->country_code == $thirdparty_buyer->country_code)
5634	{
5635		return get_product_localtax_for_country($idprod, $local, $thirdparty_seller);
5636	}
5637
5638	return 0;
5639}
5640
5641/**
5642 *	Return yes or no in current language
5643 *
5644 *	@param	string|int	$yesno			Value to test (1, 'yes', 'true' or 0, 'no', 'false')
5645 *	@param	integer		$case			1=Yes/No, 0=yes/no, 2=Disabled checkbox, 3=Disabled checkbox + Yes/No
5646 *	@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.
5647 *	@return	string						HTML string
5648 */
5649function yn($yesno, $case = 1, $color = 0)
5650{
5651	global $langs;
5652	$result = 'unknown'; $classname = '';
5653	if ($yesno == 1 || strtolower($yesno) == 'yes' || strtolower($yesno) == 'true') 	// A mettre avant test sur no a cause du == 0
5654	{
5655		$result = $langs->trans('yes');
5656		if ($case == 1 || $case == 3) $result = $langs->trans("Yes");
5657		if ($case == 2) $result = '<input type="checkbox" value="1" checked disabled>';
5658		if ($case == 3) $result = '<input type="checkbox" value="1" checked disabled> '.$result;
5659
5660		$classname = 'ok';
5661	} elseif ($yesno == 0 || strtolower($yesno) == 'no' || strtolower($yesno) == 'false')
5662	{
5663		$result = $langs->trans("no");
5664		if ($case == 1 || $case == 3) $result = $langs->trans("No");
5665		if ($case == 2) $result = '<input type="checkbox" value="0" disabled>';
5666		if ($case == 3) $result = '<input type="checkbox" value="0" disabled> '.$result;
5667
5668		if ($color == 2) $classname = 'ok';
5669		else $classname = 'error';
5670	}
5671	if ($color) return '<font class="'.$classname.'">'.$result.'</font>';
5672	return $result;
5673}
5674
5675/**
5676 *	Return a path to have a the directory according to object where files are stored.
5677 *  New usage:       $conf->module->multidir_output[$object->entity].'/'.get_exdir(0, 0, 0, 1, $object, '').'/'
5678 *         or:       $conf->module->dir_output.'/'.get_exdir(0, 0, 0, 0, $object, '')     if multidir_output not defined.
5679 *  Example out with new usage:       $object is invoice -> 'INYYMM-ABCD'
5680 *  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/"
5681 *
5682 *	@param	string|int	$num            Id of object (deprecated, $object will be used in future)
5683 *	@param  int			$level		    Level of subdirs to return (1, 2 or 3 levels). (deprecated, global option will be used in future)
5684 * 	@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)
5685 *  @param  int			$withoutslash   0=With slash at end (except if '/', we return ''), 1=without slash at end
5686 *  @param	Object		$object			Object to use to get ref to forge the path.
5687 *  @param	string		$modulepart		Type of object ('invoice_supplier, 'donation', 'invoice', ...'). Use '' for autodetect from $object.
5688 *  @return	string						Dir to use ending. Example '' or '1/' or '1/2/'
5689 */
5690function get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart = '')
5691{
5692	global $conf;
5693
5694	if (empty($modulepart) && !empty($object->module)) $modulepart = $object->module;
5695
5696	$path = '';
5697
5698	$arrayforoldpath = array('cheque', 'category', 'holiday', 'supplier_invoice', 'invoice_supplier', 'mailing', 'supplier_payment');
5699	if (!empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO)) $arrayforoldpath[] = 'product';
5700	if (!empty($level) && in_array($modulepart, $arrayforoldpath)) {
5701		// This part should be removed once all code is using "get_exdir" to forge path, with parameter $object and $modulepart provided.
5702		if (empty($alpha)) $num = preg_replace('/([^0-9])/i', '', $num);
5703		else $num = preg_replace('/^.*\-/i', '', $num);
5704		$num = substr("000".$num, -$level);
5705		if ($level == 1) $path = substr($num, 0, 1);
5706		if ($level == 2) $path = substr($num, 1, 1).'/'.substr($num, 0, 1);
5707		if ($level == 3) $path = substr($num, 2, 1).'/'.substr($num, 1, 1).'/'.substr($num, 0, 1);
5708	} else {
5709		// We will enhance here a common way of forging path for document storage.
5710		// In a future, we may distribute directories on several levels depending on setup and object.
5711		// Here, $object->id, $object->ref and $modulepart are required.
5712		//var_dump($modulepart);
5713		$path = dol_sanitizeFileName(empty($object->ref) ? (string) $object->id : $object->ref);
5714	}
5715
5716	if (empty($withoutslash) && !empty($path)) $path .= '/';
5717
5718	return $path;
5719}
5720
5721/**
5722 *	Creation of a directory (this can create recursive subdir)
5723 *
5724 *	@param	string		$dir		Directory to create (Separator must be '/'. Example: '/mydir/mysubdir')
5725 *	@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)
5726 *  @param	string|null	$newmask	Mask for new file (Defaults to $conf->global->MAIN_UMASK or 0755 if unavailable). Example: '0444'
5727 *	@return int         			< 0 if KO, 0 = already exists, > 0 if OK
5728 */
5729function dol_mkdir($dir, $dataroot = '', $newmask = null)
5730{
5731	global $conf;
5732
5733	dol_syslog("functions.lib::dol_mkdir: dir=".$dir, LOG_INFO);
5734
5735	$dir_osencoded = dol_osencode($dir);
5736	if (@is_dir($dir_osencoded)) return 0;
5737
5738	$nberr = 0;
5739	$nbcreated = 0;
5740
5741	$ccdir = '';
5742	if (!empty($dataroot)) {
5743		// Remove data root from loop
5744		$dir = str_replace($dataroot.'/', '', $dir);
5745		$ccdir = $dataroot.'/';
5746	}
5747
5748	$cdir = explode("/", $dir);
5749	$num = count($cdir);
5750	for ($i = 0; $i < $num; $i++)
5751	{
5752		if ($i > 0) $ccdir .= '/'.$cdir[$i];
5753		else $ccdir .= $cdir[$i];
5754		if (preg_match("/^.:$/", $ccdir, $regs)) continue; // Si chemin Windows incomplet, on poursuit par rep suivant
5755
5756		// Attention, le is_dir() peut echouer bien que le rep existe.
5757		// (ex selon config de open_basedir)
5758		if ($ccdir)
5759		{
5760			$ccdir_osencoded = dol_osencode($ccdir);
5761			if (!@is_dir($ccdir_osencoded))
5762			{
5763				dol_syslog("functions.lib::dol_mkdir: Directory '".$ccdir."' does not exists or is outside open_basedir PHP setting.", LOG_DEBUG);
5764
5765				umask(0);
5766				$dirmaskdec = octdec($newmask);
5767				if (empty($newmask)) {
5768					$dirmaskdec = empty($conf->global->MAIN_UMASK) ? octdec('0755') : octdec($conf->global->MAIN_UMASK);
5769				}
5770				$dirmaskdec |= octdec('0111'); // Set x bit required for directories
5771				if (!@mkdir($ccdir_osencoded, $dirmaskdec))
5772				{
5773					// Si le is_dir a renvoye une fausse info, alors on passe ici.
5774					dol_syslog("functions.lib::dol_mkdir: Fails to create directory '".$ccdir."' or directory already exists.", LOG_WARNING);
5775					$nberr++;
5776				} else {
5777					dol_syslog("functions.lib::dol_mkdir: Directory '".$ccdir."' created", LOG_DEBUG);
5778					$nberr = 0; // On remet a zero car si on arrive ici, cela veut dire que les echecs precedents peuvent etre ignore
5779					$nbcreated++;
5780				}
5781			} else {
5782				$nberr = 0; // On remet a zero car si on arrive ici, cela veut dire que les echecs precedents peuvent etre ignores
5783			}
5784		}
5785	}
5786	return ($nberr ? -$nberr : $nbcreated);
5787}
5788
5789
5790/**
5791 *	Return picto saying a field is required
5792 *
5793 *	@return  string		Chaine avec picto obligatoire
5794 */
5795function picto_required()
5796{
5797	return '<span class="fieldrequired">*</span>';
5798}
5799
5800
5801/**
5802 *	Clean a string from all HTML tags and entities.
5803 *  This function differs from strip_tags because:
5804 *  - <br> are replaced with \n if removelinefeed=0 or 1
5805 *  - if entities are found, they are decoded BEFORE the strip
5806 *  - you can decide to convert line feed into a space
5807 *
5808 *	@param	string	$stringtoclean		String to clean
5809 *	@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..."
5810 *  @param  string	$pagecodeto      	Encoding of input/output string
5811 *  @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')
5812 *  @param	integer	$removedoublespaces	Replace double space into one space
5813 *	@return string	    				String cleaned
5814 *
5815 * 	@see	dol_escape_htmltag() strip_tags() dol_string_onlythesehtmltags() dol_string_neverthesehtmltags(), dolStripPhpCode()
5816 */
5817function dol_string_nohtmltag($stringtoclean, $removelinefeed = 1, $pagecodeto = 'UTF-8', $strip_tags = 0, $removedoublespaces = 1)
5818{
5819	if ($removelinefeed == 2) $stringtoclean = preg_replace('/<br[^>]*>(\n|\r)+/ims', '<br>', $stringtoclean);
5820	$temp = preg_replace('/<br[^>]*>/i', "\n", $stringtoclean);
5821
5822	// 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)
5823	$temp = dol_html_entity_decode($temp, ENT_COMPAT | ENT_HTML5, $pagecodeto);
5824
5825	$temp = str_replace('< ', '__ltspace__', $temp);
5826
5827	if ($strip_tags) {
5828		$temp = strip_tags($temp);
5829	} else {
5830		$pattern = "/<[^<>]+>/";
5831		// Example of $temp: <a href="/myurl" title="<u>A title</u>">0000-021</a>
5832		$temp = preg_replace($pattern, "", $temp); // pass 1 - $temp after pass 1: <a href="/myurl" title="A title">0000-021
5833		$temp = preg_replace($pattern, "", $temp); // pass 2 - $temp after pass 2: 0000-021
5834		// Remove '<' into remainging, so non closing html tags like '<abc'. Note: '<123abc' is not a html tag (can be kept), but '<abc123' is (must be removed).
5835		$temp = preg_replace('/<([a-z]+)/i', '\1', $temp);
5836	}
5837
5838	$temp = dol_html_entity_decode($temp, ENT_COMPAT, $pagecodeto);
5839
5840	// Remove also carriage returns
5841	if ($removelinefeed == 1) $temp = str_replace(array("\r\n", "\r", "\n"), " ", $temp);
5842
5843	// And double quotes
5844	if ($removedoublespaces) {
5845		while (strpos($temp, "  ")) {
5846			$temp = str_replace("  ", " ", $temp);
5847		}
5848	}
5849
5850	$temp = str_replace('__ltspace__', '< ', $temp);
5851
5852	return trim($temp);
5853}
5854
5855/**
5856 *	Clean a string to keep only desirable HTML tags.
5857 *  WARNING: This also clean HTML comments (used to obfuscate tag name).
5858 *
5859 *	@param	string	$stringtoclean			String to clean
5860 *  @param	int		$cleanalsosomestyles	Remove absolute/fixed positioning from inline styles
5861 *  @param	int		$removeclassattribute	Remove the class attribute from tags
5862 *  @param	int		$cleanalsojavascript	Remove also occurence of 'javascript:'.
5863 *	@return string	    					String cleaned
5864 *
5865 * 	@see	dol_escape_htmltag() strip_tags() dol_string_nohtmltag() dol_string_neverthesehtmltags()
5866 */
5867function dol_string_onlythesehtmltags($stringtoclean, $cleanalsosomestyles = 1, $removeclassattribute = 1, $cleanalsojavascript = 0)
5868{
5869	$allowed_tags = array(
5870		"html", "head", "meta", "body", "article", "a", "abbr", "b", "blockquote", "br", "cite", "div", "dl", "dd", "dt", "em", "font", "img", "ins", "hr", "i", "li", "link",
5871		"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"
5872	);
5873
5874	$allowed_tags_string = join("><", $allowed_tags);
5875	$allowed_tags_string = '<'.$allowed_tags_string.'>';
5876
5877	$stringtoclean = str_replace('<!DOCTYPE html>', '__!DOCTYPE_HTML__', $stringtoclean);	// Replace DOCTYPE to avoid to have it removed by the strip_tags
5878
5879	$stringtoclean = dol_string_nounprintableascii($stringtoclean, 0);
5880	$stringtoclean = preg_replace('/&colon;/i', ':', $stringtoclean);
5881
5882	$stringtoclean = preg_replace('/<!--[^>]*-->/', '', $stringtoclean);
5883	$stringtoclean = preg_replace('/&#58;|&#0000058|&#x3A/i', '', $stringtoclean); // refused string ':' encoded (no reason to have it encoded) to lock 'javascript:...'
5884	$stringtoclean = preg_replace('/javascript\s*:/i', '', $stringtoclean);
5885
5886	$temp = strip_tags($stringtoclean, $allowed_tags_string);
5887
5888	if ($cleanalsosomestyles) {	// Clean for remaining html tags
5889		$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
5890	}
5891	if ($removeclassattribute) {	// Clean for remaining html tags
5892		$temp = preg_replace('/(<[^>]+)\s+class=((["\']).*?\\3|\\w*)/i', '\\1', $temp);
5893	}
5894
5895	// Remove 'javascript:' that we should not find into a text with
5896	// 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 the GETPOST).
5897	if ($cleanalsojavascript) {
5898		$temp = preg_replace('/javascript\s*:/i', '', $temp);
5899	}
5900
5901	$temp = str_replace('__!DOCTYPE_HTML__', '<!DOCTYPE html>', $temp);	// Restore the DOCTYPE
5902
5903	return $temp;
5904}
5905
5906/**
5907 *	Clean a string from some undesirable HTML tags.
5908 *  Note. Not as secured as dol_string_onlythesehtmltags().
5909 *
5910 *	@param	string	$stringtoclean			String to clean
5911 *  @param	array	$disallowed_tags		Array of tags not allowed
5912 *  @param	string	$cleanalsosomestyles	Clean also some tags
5913 *	@return string	    					String cleaned
5914 *
5915 * 	@see	dol_escape_htmltag() strip_tags() dol_string_nohtmltag() dol_string_onlythesehtmltags()
5916 */
5917function dol_string_neverthesehtmltags($stringtoclean, $disallowed_tags = array('textarea'), $cleanalsosomestyles = 0)
5918{
5919	$temp = $stringtoclean;
5920	foreach ($disallowed_tags as $tagtoremove)
5921	{
5922		$temp = preg_replace('/<\/?'.$tagtoremove.'>/', '', $temp);
5923		$temp = preg_replace('/<\/?'.$tagtoremove.'\s+[^>]*>/', '', $temp);
5924	}
5925
5926	if ($cleanalsosomestyles) {
5927		$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
5928	}
5929
5930	return $temp;
5931}
5932
5933
5934/**
5935 * Return first line of text. Cut will depends if content is HTML or not.
5936 *
5937 * @param 	string	$text		Input text
5938 * @param	int		$nboflines  Nb of lines to get (default is 1 = first line only)
5939 * @param   string  $charset    Charset of $text string (UTF-8 by default)
5940 * @return	string				Output text
5941 * @see dol_nboflines_bis(), dol_string_nohtmltag(), dol_escape_htmltag()
5942 */
5943function dolGetFirstLineOfText($text, $nboflines = 1, $charset = 'UTF-8')
5944{
5945	if ($nboflines == 1)
5946	{
5947		if (dol_textishtml($text))
5948		{
5949			$firstline = preg_replace('/<br[^>]*>.*$/s', '', $text); // The s pattern modifier means the . can match newline characters
5950			$firstline = preg_replace('/<div[^>]*>.*$/s', '', $firstline); // The s pattern modifier means the . can match newline characters
5951		} else {
5952			$firstline = preg_replace('/[\n\r].*/', '', $text);
5953		}
5954		return $firstline.((strlen($firstline) != strlen($text)) ? '...' : '');
5955	} else {
5956		$ishtml = 0;
5957		if (dol_textishtml($text))
5958		{
5959			$text = preg_replace('/\n/', '', $text);
5960			$ishtml = 1;
5961			$repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
5962		} else {
5963			$repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
5964		}
5965
5966		$text = strtr($text, $repTable);
5967		if ($charset == 'UTF-8') { $pattern = '/(<br[^>]*>)/Uu'; } // /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
5968		else $pattern = '/(<br[^>]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
5969		$a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
5970
5971		$firstline = '';
5972		$i = 0;
5973		$nba = count($a); // 2x nb of lines in $a because $a contains also a line for each new line separator
5974		while (($i < $nba) && ($i < ($nboflines * 2)))
5975		{
5976			if ($i % 2 == 0) $firstline .= $a[$i];
5977			elseif (($i < (($nboflines * 2) - 1)) && ($i < ($nba - 1))) $firstline .= ($ishtml ? "<br>\n" : "\n");
5978			$i++;
5979		}
5980		unset($a);
5981		return $firstline.(($i < $nba) ? '...' : '');
5982	}
5983}
5984
5985
5986/**
5987 * Replace CRLF in string with a HTML BR tag.
5988 * 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.
5989 *
5990 * @param	string	$stringtoencode		String to encode
5991 * @param	int     $nl2brmode			0=Adding br before \n, 1=Replacing \n by br
5992 * @param   bool	$forxml             false=Use <br>, true=Use <br />
5993 * @return	string						String encoded
5994 * @see dol_nboflines(), dolGetFirstLineOfText()
5995 */
5996function dol_nl2br($stringtoencode, $nl2brmode = 0, $forxml = false)
5997{
5998	if (!$nl2brmode) {
5999		return nl2br($stringtoencode, $forxml);
6000	} else {
6001		$ret = preg_replace('/(\r\n|\r|\n)/i', ($forxml ? '<br />' : '<br>'), $stringtoencode);
6002		return $ret;
6003	}
6004}
6005
6006
6007/**
6008 *	This function is called to encode a string into a HTML string but differs from htmlentities because
6009 * 	a detection is done before to see if text is already HTML or not. Also, all entities but &,<,>," are converted.
6010 *  This permits to encode special chars to entities with no double encoding for already encoded HTML strings.
6011 * 	This function also remove last EOL or BR if $removelasteolbr=1 (default).
6012 *  For PDF usage, you can show text by 2 ways:
6013 *              - writeHTMLCell -> param must be encoded into HTML.
6014 *              - MultiCell -> param must not be encoded into HTML.
6015 *              Because writeHTMLCell convert also \n into <br>, if function
6016 *              is used to build PDF, nl2brmode must be 1.
6017 *
6018 *	@param	string	$stringtoencode		String to encode
6019 *	@param	int		$nl2brmode			0=Adding br before \n, 1=Replacing \n by br (for use with FPDF writeHTMLCell function for example)
6020 *  @param  string	$pagecodefrom       Pagecode stringtoencode is encoded
6021 *  @param	int		$removelasteolbr	1=Remove last br or lasts \n (default), 0=Do nothing
6022 *  @return	string						String encoded
6023 */
6024function dol_htmlentitiesbr($stringtoencode, $nl2brmode = 0, $pagecodefrom = 'UTF-8', $removelasteolbr = 1)
6025{
6026	$newstring = $stringtoencode;
6027	if (dol_textishtml($stringtoencode))	// Check if text is already HTML or not
6028	{
6029		$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.
6030		if ($removelasteolbr) $newstring = preg_replace('/<br>$/i', '', $newstring); // Remove last <br> (remove only last one)
6031		$newstring = strtr($newstring, array('&'=>'__and__', '<'=>'__lt__', '>'=>'__gt__', '"'=>'__dquot__'));
6032		$newstring = dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom); // Make entity encoding
6033		$newstring = strtr($newstring, array('__and__'=>'&', '__lt__'=>'<', '__gt__'=>'>', '__dquot__'=>'"'));
6034	} else {
6035		if ($removelasteolbr) $newstring = preg_replace('/(\r\n|\r|\n)$/i', '', $newstring); // Remove last \n (may remove several)
6036		$newstring = dol_nl2br(dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom), $nl2brmode);
6037	}
6038	// Other substitutions that htmlentities does not do
6039	//$newstring=str_replace(chr(128),'&euro;',$newstring);	// 128 = 0x80. Not in html entity table.     // Seems useles with TCPDF. Make bug with UTF8 languages
6040	return $newstring;
6041}
6042
6043/**
6044 *	This function is called to decode a HTML string (it decodes entities and br tags)
6045 *
6046 *	@param	string	$stringtodecode		String to decode
6047 *	@param	string	$pagecodeto			Page code for result
6048 *	@return	string						String decoded
6049 */
6050function dol_htmlentitiesbr_decode($stringtodecode, $pagecodeto = 'UTF-8')
6051{
6052	$ret = dol_html_entity_decode($stringtodecode, ENT_COMPAT | ENT_HTML5, $pagecodeto);
6053	$ret = preg_replace('/'."\r\n".'<br(\s[\sa-zA-Z_="]*)?\/?>/i', "<br>", $ret);
6054	$ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>'."\r\n".'/i', "\r\n", $ret);
6055	$ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>'."\n".'/i', "\n", $ret);
6056	$ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>/i', "\n", $ret);
6057	return $ret;
6058}
6059
6060/**
6061 *	This function remove all ending \n and br at end
6062 *
6063 *	@param	string	$stringtodecode		String to decode
6064 *	@return	string						String decoded
6065 */
6066function dol_htmlcleanlastbr($stringtodecode)
6067{
6068	$ret = preg_replace('/(<br>|<br(\s[\sa-zA-Z_="]*)?\/?>|'."\n".'|'."\r".')+$/i', "", $stringtodecode);
6069	return $ret;
6070}
6071
6072/**
6073 * Replace html_entity_decode functions to manage errors
6074 *
6075 * @param   string	$a					Operand a
6076 * @param   string	$b					Operand b (ENT_QUOTES|ENT_HTML5=convert simple, double quotes, colon, e accent, ...)
6077 * @param   string	$c					Operand c
6078 * @param	string	$keepsomeentities	Entities but &, <, >, " are not converted.
6079 * @return  string						String decoded
6080 */
6081function dol_html_entity_decode($a, $b, $c = 'UTF-8', $keepsomeentities = 0)
6082{
6083	$newstring = $a;
6084	if ($keepsomeentities) $newstring = strtr($newstring, array('&amp;'=>'__andamp__', '&lt;'=>'__andlt__', '&gt;'=>'__andgt__', '"'=>'__dquot__'));
6085	$newstring = html_entity_decode($newstring, $b, $c);
6086	if ($keepsomeentities) $newstring = strtr($newstring, array('__andamp__'=>'&amp;', '__andlt__'=>'&lt;', '__andgt__'=>'&gt;', '__dquot__'=>'"'));
6087	return $newstring;
6088}
6089
6090/**
6091 * Replace htmlentities functions.
6092 * Goal of this function is to be sure to have default values of htmlentities that match what we need.
6093 *
6094 * @param   string  $string         The input string to encode
6095 * @param   int     $flags          Flags (see PHP doc above)
6096 * @param   string  $encoding       Encoding page code
6097 * @param   bool    $double_encode  When double_encode is turned off, PHP will not encode existing html entities
6098 * @return  string  $ret            Encoded string
6099 */
6100function dol_htmlentities($string, $flags = null, $encoding = 'UTF-8', $double_encode = false)
6101{
6102	return htmlentities($string, $flags, $encoding, $double_encode);
6103}
6104
6105/**
6106 *	Check if a string is a correct iso string
6107 *	If not, it will we considered not HTML encoded even if it is by FPDF.
6108 *	Example, if string contains euro symbol that has ascii code 128
6109 *
6110 *	@param	string		$s      	String to check
6111 *  @param	string		$clean		Clean if it is not an ISO. Warning, if file is utf8, you will get a bad formated file.
6112 *	@return	int|string  	   		0 if bad iso, 1 if good iso, Or the clean string if $clean is 1
6113 */
6114function dol_string_is_good_iso($s, $clean = 0)
6115{
6116	$len = dol_strlen($s);
6117	$out = '';
6118	$ok = 1;
6119	for ($scursor = 0; $scursor < $len; $scursor++)
6120	{
6121		$ordchar = ord($s[$scursor]);
6122		//print $scursor.'-'.$ordchar.'<br>';
6123		if ($ordchar < 32 && $ordchar != 13 && $ordchar != 10) { $ok = 0; break; } elseif ($ordchar > 126 && $ordchar < 160) { $ok = 0; break; } elseif ($clean) {
6124			$out .= $s[$scursor];
6125		}
6126	}
6127	if ($clean) return $out;
6128	return $ok;
6129}
6130
6131/**
6132 *	Return nb of lines of a clear text
6133 *
6134 *	@param	string	$s			String to check
6135 * 	@param	int     $maxchar	Not yet used
6136 *	@return	int					Number of lines
6137 *  @see	dol_nboflines_bis(), dolGetFirstLineOfText()
6138 */
6139function dol_nboflines($s, $maxchar = 0)
6140{
6141	if ($s == '') return 0;
6142	$arraystring = explode("\n", $s);
6143	$nb = count($arraystring);
6144
6145	return $nb;
6146}
6147
6148
6149/**
6150 *	Return nb of lines of a formated text with \n and <br> (WARNING: string must not have mixed \n and br separators)
6151 *
6152 *	@param	string	$text      		Text
6153 *	@param	int		$maxlinesize  	Largeur de ligne en caracteres (ou 0 si pas de limite - defaut)
6154 * 	@param	string	$charset		Give the charset used to encode the $text variable in memory.
6155 *	@return int						Number of lines
6156 *	@see	dol_nboflines(), dolGetFirstLineOfText()
6157 */
6158function dol_nboflines_bis($text, $maxlinesize = 0, $charset = 'UTF-8')
6159{
6160	$repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
6161	if (dol_textishtml($text)) $repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
6162
6163	$text = strtr($text, $repTable);
6164	if ($charset == 'UTF-8') { $pattern = '/(<br[^>]*>)/Uu'; } // /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
6165	else $pattern = '/(<br[^>]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
6166	$a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
6167
6168	$nblines = (int) floor((count($a) + 1) / 2);
6169	// count possible auto line breaks
6170	if ($maxlinesize)
6171	{
6172		foreach ($a as $line)
6173		{
6174			if (dol_strlen($line) > $maxlinesize)
6175			{
6176				//$line_dec = html_entity_decode(strip_tags($line));
6177				$line_dec = html_entity_decode($line);
6178				if (dol_strlen($line_dec) > $maxlinesize)
6179				{
6180					$line_dec = wordwrap($line_dec, $maxlinesize, '\n', true);
6181					$nblines += substr_count($line_dec, '\n');
6182				}
6183			}
6184		}
6185	}
6186
6187	unset($a);
6188	return $nblines;
6189}
6190
6191/**
6192 *	Return if a text is a html content
6193 *
6194 *	@param	string	$msg		Content to check
6195 *	@param	int		$option		0=Full detection, 1=Fast check
6196 *	@return	boolean				true/false
6197 *	@see	dol_concatdesc()
6198 */
6199function dol_textishtml($msg, $option = 0)
6200{
6201	if ($option == 1)
6202	{
6203		if (preg_match('/<html/i', $msg))				return true;
6204		elseif (preg_match('/<body/i', $msg))			return true;
6205		elseif (preg_match('/<\/textarea/i', $msg))	  return true;
6206		elseif (preg_match('/<br/i', $msg))				return true;
6207		return false;
6208	} else {
6209		if (preg_match('/<html/i', $msg))				return true;
6210		elseif (preg_match('/<body/i', $msg))			return true;
6211		elseif (preg_match('/<\/textarea/i', $msg))	  return true;
6212		elseif (preg_match('/<(b|em|i|u)>/i', $msg))		return true;
6213		elseif (preg_match('/<br\/>/i', $msg))	  return true;
6214		elseif (preg_match('/<(br|div|font|li|p|span|strong|table)>/i', $msg)) 	  return true;
6215		elseif (preg_match('/<(br|div|font|li|p|span|strong|table)\s+[^<>\/]*>/i', $msg)) return true;
6216		elseif (preg_match('/<(br|div|font|li|p|span|strong|table)\s+[^<>\/]*\/>/i', $msg)) return true;
6217		elseif (preg_match('/<img\s+[^<>]*src[^<>]*>/i', $msg)) return true; // must accept <img src="http://example.com/aaa.png" />
6218		elseif (preg_match('/<a\s+[^<>]*href[^<>]*>/i', $msg)) return true; // must accept <a href="http://example.com/aaa.png" />
6219		elseif (preg_match('/<h[0-9]>/i', $msg))			return true;
6220		elseif (preg_match('/&[A-Z0-9]{1,6};/i', $msg))	return true; // Html entities names (http://www.w3schools.com/tags/ref_entities.asp)
6221		elseif (preg_match('/&#[0-9]{2,3};/i', $msg))	return true; // Html entities numbers (http://www.w3schools.com/tags/ref_entities.asp)
6222
6223		return false;
6224	}
6225}
6226
6227/**
6228 *  Concat 2 descriptions with a new line between them (second operand after first one with appropriate new line separator)
6229 *  text1 html + text2 html => text1 + '<br>' + text2
6230 *  text1 html + text2 txt  => text1 + '<br>' + dol_nl2br(text2)
6231 *  text1 txt  + text2 html => dol_nl2br(text1) + '<br>' + text2
6232 *  text1 txt  + text2 txt  => text1 + '\n' + text2
6233 *
6234 *  @param  string  $text1          Text 1
6235 *  @param  string  $text2          Text 2
6236 *  @param  bool    $forxml         true=Use <br /> instead of <br> if we have to add a br tag
6237 *  @param  bool    $invert         invert order of description lines (we often use config MAIN_CHANGE_ORDER_CONCAT_DESCRIPTION in this parameter)
6238 *  @return string                  Text 1 + new line + Text2
6239 *  @see    dol_textishtml()
6240 */
6241function dol_concatdesc($text1, $text2, $forxml = false, $invert = false)
6242{
6243	if (!empty($invert))
6244	{
6245			$tmp = $text1;
6246			$text1 = $text2;
6247			$text2 = $tmp;
6248	}
6249
6250	$ret = '';
6251	$ret .= (!dol_textishtml($text1) && dol_textishtml($text2)) ? dol_nl2br(dol_escape_htmltag($text1, 0, 1, '', 1), 0, $forxml) : $text1;
6252	$ret .= (!empty($text1) && !empty($text2)) ? ((dol_textishtml($text1) || dol_textishtml($text2)) ? ($forxml ? "<br \>\n" : "<br>\n") : "\n") : "";
6253	$ret .= (dol_textishtml($text1) && !dol_textishtml($text2)) ? dol_nl2br(dol_escape_htmltag($text2, 0, 1, '', 1), 0, $forxml) : $text2;
6254	return $ret;
6255}
6256
6257
6258
6259/**
6260 * Return array of possible common substitutions. This includes several families like: 'system', 'mycompany', 'object', 'objectamount', 'date', 'user'
6261 *
6262 * @param	Translate	$outputlangs	Output language
6263 * @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)
6264 * @param   array       $exclude        Array of family keys we want to exclude. For example array('system', 'mycompany', 'object', 'objectamount', 'date', 'user', ...)
6265 * @param   Object      $object         Object for keys on object
6266 * @return	array						Array of substitutions
6267 * @see setSubstitFromObject()
6268 */
6269function getCommonSubstitutionArray($outputlangs, $onlykey = 0, $exclude = null, $object = null)
6270{
6271	global $db, $conf, $mysoc, $user, $extrafields;
6272
6273	$substitutionarray = array();
6274
6275	if (empty($exclude) || !in_array('user', $exclude))
6276	{
6277		// Add SIGNATURE into substitutionarray first, so, when we will make the substitution,
6278		// this will include signature content first and then replace var found into content of signature
6279		$signature = $user->signature;
6280		$substitutionarray = array_merge($substitutionarray, array(
6281			'__USER_SIGNATURE__' => (string) (($signature && empty($conf->global->MAIN_MAIL_DO_NOT_USE_SIGN)) ? ($onlykey == 2 ? dol_trunc(dol_string_nohtmltag($signature), 30) : $signature) : '')
6282		)
6283			);
6284
6285		$substitutionarray = array_merge($substitutionarray, array(
6286		'__USER_ID__' => (string) $user->id,
6287		'__USER_LOGIN__' => (string) $user->login,
6288		'__USER_EMAIL__' => (string) $user->email,
6289		'__USER_LASTNAME__' => (string) $user->lastname,
6290		'__USER_FIRSTNAME__' => (string) $user->firstname,
6291		'__USER_FULLNAME__' => (string) $user->getFullName($outputlangs),
6292		'__USER_SUPERVISOR_ID__' => (string) ($user->fk_user ? $user->fk_user : '0'),
6293		'__USER_REMOTE_IP__' => (string) getUserRemoteIP()
6294		)
6295			);
6296	}
6297	if ((empty($exclude) || !in_array('mycompany', $exclude)) && is_object($mysoc))
6298	{
6299		$substitutionarray = array_merge($substitutionarray, array(
6300			'__MYCOMPANY_NAME__'    => $mysoc->name,
6301			'__MYCOMPANY_EMAIL__'   => $mysoc->email,
6302			'__MYCOMPANY_PROFID1__' => $mysoc->idprof1,
6303			'__MYCOMPANY_PROFID2__' => $mysoc->idprof2,
6304			'__MYCOMPANY_PROFID3__' => $mysoc->idprof3,
6305			'__MYCOMPANY_PROFID4__' => $mysoc->idprof4,
6306			'__MYCOMPANY_PROFID5__' => $mysoc->idprof5,
6307			'__MYCOMPANY_PROFID6__' => $mysoc->idprof6,
6308			'__MYCOMPANY_CAPITAL__' => $mysoc->capital,
6309			'__MYCOMPANY_FULLADDRESS__' => $mysoc->getFullAddress(1, ', '),
6310			'__MYCOMPANY_ADDRESS__' => $mysoc->address,
6311			'__MYCOMPANY_ZIP__'     => $mysoc->zip,
6312			'__MYCOMPANY_TOWN__'    => $mysoc->town,
6313			'__MYCOMPANY_COUNTRY__'    => $mysoc->country,
6314			'__MYCOMPANY_COUNTRY_ID__' => $mysoc->country_id,
6315			'__MYCOMPANY_COUNTRY_CODE__' => $mysoc->country_code,
6316			'__MYCOMPANY_CURRENCY_CODE__' => $conf->currency
6317		));
6318	}
6319
6320	if (($onlykey || is_object($object)) && (empty($exclude) || !in_array('object', $exclude)))
6321	{
6322		if ($onlykey)
6323		{
6324			$substitutionarray['__ID__'] = '__ID__';
6325			$substitutionarray['__REF__'] = '__REF__';
6326			$substitutionarray['__REF_CLIENT__'] = '__REF_CLIENT__';
6327			$substitutionarray['__REF_SUPPLIER__'] = '__REF_SUPPLIER__';
6328			$substitutionarray['__NOTE_PUBLIC__'] = '__NOTE_PUBLIC__';
6329			$substitutionarray['__NOTE_PRIVATE__'] = '__NOTE_PRIVATE__';
6330			$substitutionarray['__EXTRAFIELD_XXX__'] = '__EXTRAFIELD_XXX__';
6331
6332			if (!empty($conf->societe->enabled))	// Most objects are concerned
6333			{
6334				$substitutionarray['__THIRDPARTY_ID__'] = '__THIRDPARTY_ID__';
6335				$substitutionarray['__THIRDPARTY_NAME__'] = '__THIRDPARTY_NAME__';
6336				$substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = '__THIRDPARTY_NAME_ALIAS__';
6337				$substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = '__THIRDPARTY_CODE_CLIENT__';
6338				$substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = '__THIRDPARTY_CODE_FOURNISSEUR__';
6339				$substitutionarray['__THIRDPARTY_EMAIL__'] = '__THIRDPARTY_EMAIL__';
6340				$substitutionarray['__THIRDPARTY_PHONE__'] = '__THIRDPARTY_PHONE__';
6341				$substitutionarray['__THIRDPARTY_FAX__'] = '__THIRDPARTY_FAX__';
6342				$substitutionarray['__THIRDPARTY_ADDRESS__'] = '__THIRDPARTY_ADDRESS__';
6343				$substitutionarray['__THIRDPARTY_ZIP__'] = '__THIRDPARTY_ZIP__';
6344				$substitutionarray['__THIRDPARTY_TOWN__'] = '__THIRDPARTY_TOWN__';
6345				$substitutionarray['__THIRDPARTY_IDPROF1__'] = '__THIRDPARTY_IDPROF1__';
6346				$substitutionarray['__THIRDPARTY_IDPROF2__'] = '__THIRDPARTY_IDPROF2__';
6347				$substitutionarray['__THIRDPARTY_IDPROF3__'] = '__THIRDPARTY_IDPROF3__';
6348				$substitutionarray['__THIRDPARTY_IDPROF4__'] = '__THIRDPARTY_IDPROF4__';
6349				$substitutionarray['__THIRDPARTY_IDPROF5__'] = '__THIRDPARTY_IDPROF5__';
6350				$substitutionarray['__THIRDPARTY_IDPROF6__'] = '__THIRDPARTY_IDPROF6__';
6351				$substitutionarray['__THIRDPARTY_TVAINTRA__'] = '__THIRDPARTY_TVAINTRA__';
6352				$substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = '__THIRDPARTY_NOTE_PUBLIC__';
6353				$substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = '__THIRDPARTY_NOTE_PRIVATE__';
6354			}
6355			if (!empty($conf->adherent->enabled) && (!is_object($object) || $object->element == 'adherent'))
6356			{
6357				$substitutionarray['__MEMBER_ID__'] = '__MEMBER_ID__';
6358				$substitutionarray['__MEMBER_CIVILITY__'] = '__MEMBER_CIVILITY__';
6359				$substitutionarray['__MEMBER_FIRSTNAME__'] = '__MEMBER_FIRSTNAME__';
6360				$substitutionarray['__MEMBER_LASTNAME__'] = '__MEMBER_LASTNAME__';
6361				/*$substitutionarray['__MEMBER_NOTE_PUBLIC__'] = '__MEMBER_NOTE_PUBLIC__';
6362				$substitutionarray['__MEMBER_NOTE_PRIVATE__'] = '__MEMBER_NOTE_PRIVATE__';*/
6363			}
6364			if (!empty($conf->recruitment->enabled) && (!is_object($object) || $object->element == 'candidature'))
6365			{
6366				$substitutionarray['__CANDIDATE_FULLNAME__'] = '__CANDIDATE_FULLNAME__';
6367				$substitutionarray['__CANDIDATE_FIRSTNAME__'] = '__CANDIDATE_FIRSTNAME__';
6368				$substitutionarray['__CANDIDATE_LASTNAME__'] = '__CANDIDATE_LASTNAME__';
6369			}
6370			if (!empty($conf->projet->enabled))		// Most objects
6371			{
6372				$substitutionarray['__PROJECT_ID__'] = '__PROJECT_ID__';
6373				$substitutionarray['__PROJECT_REF__'] = '__PROJECT_REF__';
6374				$substitutionarray['__PROJECT_NAME__'] = '__PROJECT_NAME__';
6375				/*$substitutionarray['__PROJECT_NOTE_PUBLIC__'] = '__PROJECT_NOTE_PUBLIC__';
6376				$substitutionarray['__PROJECT_NOTE_PRIVATE__'] = '__PROJECT_NOTE_PRIVATE__';*/
6377			}
6378			if (!empty($conf->contrat->enabled) && (!is_object($object) || $object->element == 'contract'))
6379			{
6380				$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = 'Highest date planned for a service start';
6381				$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = 'Highest date and hour planned for service start';
6382				$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = 'Lowest data for planned expiration of service';
6383				$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = 'Lowest date and hour for planned expiration of service';
6384			}
6385			$substitutionarray['__ONLINE_PAYMENT_URL__'] = 'UrlToPayOnlineIfApplicable';
6386			$substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = 'TextAndUrlToPayOnlineIfApplicable';
6387			$substitutionarray['__SECUREKEYPAYMENT__'] = 'Security key (if key is not unique per record)';
6388			$substitutionarray['__SECUREKEYPAYMENT_MEMBER__'] = 'Security key for payment on a member subscription (one key per member)';
6389			$substitutionarray['__SECUREKEYPAYMENT_ORDER__'] = 'Security key for payment on an order';
6390			$substitutionarray['__SECUREKEYPAYMENT_INVOICE__'] = 'Security key for payment on an invoice';
6391			$substitutionarray['__SECUREKEYPAYMENT_CONTRACTLINE__'] = 'Security key for payment on a a service';
6392
6393			$substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = 'Direct download url of a proposal';
6394			$substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = 'Direct download url of an order';
6395			$substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = 'Direct download url of an invoice';
6396
6397			if (!empty($conf->expedition->enabled) && (!is_object($object) || $object->element == 'shipping'))
6398			{
6399				$substitutionarray['__SHIPPINGTRACKNUM__'] = 'Shipping tracking number';
6400				$substitutionarray['__SHIPPINGTRACKNUMURL__'] = 'Shipping tracking url';
6401			}
6402			if (!empty($conf->reception->enabled) && (!is_object($object) || $object->element == 'reception'))
6403			{
6404				$substitutionarray['__RECEPTIONTRACKNUM__'] = 'Shippin tracking number of shipment';
6405				$substitutionarray['__RECEPTIONTRACKNUMURL__'] = 'Shipping tracking url';
6406			}
6407		} else {
6408			$substitutionarray['__ID__'] = $object->id;
6409			$substitutionarray['__REF__'] = $object->ref;
6410			$substitutionarray['__REF_CLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
6411			$substitutionarray['__REF_SUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
6412			$substitutionarray['__NOTE_PUBLIC__'] = (isset($object->note_public) ? $object->note_public : null);
6413			$substitutionarray['__NOTE_PRIVATE__'] = (isset($object->note_private) ? $object->note_private : null);
6414
6415			$substitutionarray['__DATE_DELIVERY__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, 'day', 0, $outputlangs) : '');
6416
6417			// For backward compatibility
6418			$substitutionarray['__REFCLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
6419			$substitutionarray['__REFSUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
6420			$substitutionarray['__REFCLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
6421			$substitutionarray['__REFSUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
6422			$substitutionarray['__SUPPLIER_ORDER_DATE_DELIVERY__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, 'day', 0, $outputlangs) : '');
6423			$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 : '')) : '');
6424
6425			if (is_object($object) && ($object->element == 'adherent' || $object->element == 'member') && $object->id > 0)
6426			{
6427				$birthday = (empty($object->birth) ? '' : dol_print_date($object->birth, 'day'));
6428
6429				$substitutionarray['__MEMBER_ID__'] = (isset($object->id) ? $object->id : '');
6430				if (method_exists($object, 'getCivilityLabel')) $substitutionarray['__MEMBER_CIVILITY__'] = $object->getCivilityLabel();
6431				$substitutionarray['__MEMBER_FIRSTNAME__'] = (isset($object->firstname) ? $object->firstname : '');
6432				$substitutionarray['__MEMBER_LASTNAME__'] = (isset($object->lastname) ? $object->lastname : '');
6433				if (method_exists($object, 'getFullName')) $substitutionarray['__MEMBER_FULLNAME__'] = $object->getFullName($outputlangs);
6434				$substitutionarray['__MEMBER_COMPANY__'] = (isset($object->societe) ? $object->societe : '');
6435				$substitutionarray['__MEMBER_ADDRESS__'] = (isset($object->address) ? $object->address : '');
6436				$substitutionarray['__MEMBER_ZIP__'] = (isset($object->zip) ? $object->zip : '');
6437				$substitutionarray['__MEMBER_TOWN__'] = (isset($object->town) ? $object->town : '');
6438				$substitutionarray['__MEMBER_COUNTRY__'] = (isset($object->country) ? $object->country : '');
6439				$substitutionarray['__MEMBER_EMAIL__'] = (isset($object->email) ? $object->email : '');
6440				$substitutionarray['__MEMBER_BIRTH__'] = (isset($birthday) ? $birthday : '');
6441				$substitutionarray['__MEMBER_PHOTO__'] = (isset($object->photo) ? $object->photo : '');
6442				$substitutionarray['__MEMBER_LOGIN__'] = (isset($object->login) ? $object->login : '');
6443				$substitutionarray['__MEMBER_PASSWORD__'] = (isset($object->pass) ? $object->pass : '');
6444				$substitutionarray['__MEMBER_PHONE__'] = (isset($object->phone) ? $object->phone : '');
6445				$substitutionarray['__MEMBER_PHONEPRO__'] = (isset($object->phone_perso) ? $object->phone_perso : '');
6446				$substitutionarray['__MEMBER_PHONEMOBILE__'] = (isset($object->phone_mobile) ? $object->phone_mobile : '');
6447				$substitutionarray['__MEMBER_TYPE__'] = (isset($object->type) ? $object->type : '');
6448				$substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE__']       = dol_print_date($object->first_subscription_date, 'dayrfc');
6449				$substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_START__'] = dol_print_date($object->first_subscription_date_start, 'dayrfc');
6450				$substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_END__']   = dol_print_date($object->first_subscription_date_end, 'dayrfc');
6451				$substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE__']        = dol_print_date($object->last_subscription_date, 'dayrfc');
6452				$substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_START__']  = dol_print_date($object->last_subscription_date_start, 'dayrfc');
6453				$substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_END__']    = dol_print_date($object->last_subscription_date_end, 'dayrfc');
6454			}
6455
6456			if (is_object($object) && $object->element == 'societe') {
6457				$substitutionarray['__THIRDPARTY_ID__'] = (is_object($object) ? $object->id : '');
6458				$substitutionarray['__THIRDPARTY_NAME__'] = (is_object($object) ? $object->name : '');
6459				$substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = (is_object($object) ? $object->name_alias : '');
6460				$substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = (is_object($object) ? $object->code_client : '');
6461				$substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = (is_object($object) ? $object->code_fournisseur : '');
6462				$substitutionarray['__THIRDPARTY_EMAIL__'] = (is_object($object) ? $object->email : '');
6463				$substitutionarray['__THIRDPARTY_PHONE__'] = (is_object($object) ? $object->phone : '');
6464				$substitutionarray['__THIRDPARTY_FAX__'] = (is_object($object) ? $object->fax : '');
6465				$substitutionarray['__THIRDPARTY_ADDRESS__'] = (is_object($object) ? $object->address : '');
6466				$substitutionarray['__THIRDPARTY_ZIP__'] = (is_object($object) ? $object->zip : '');
6467				$substitutionarray['__THIRDPARTY_TOWN__'] = (is_object($object) ? $object->town : '');
6468				$substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = (is_object($object) ? $object->country_id : '');
6469				$substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = (is_object($object) ? $object->country_code : '');
6470				$substitutionarray['__THIRDPARTY_IDPROF1__'] = (is_object($object) ? $object->idprof1 : '');
6471				$substitutionarray['__THIRDPARTY_IDPROF2__'] = (is_object($object) ? $object->idprof2 : '');
6472				$substitutionarray['__THIRDPARTY_IDPROF3__'] = (is_object($object) ? $object->idprof3 : '');
6473				$substitutionarray['__THIRDPARTY_IDPROF4__'] = (is_object($object) ? $object->idprof4 : '');
6474				$substitutionarray['__THIRDPARTY_IDPROF5__'] = (is_object($object) ? $object->idprof5 : '');
6475				$substitutionarray['__THIRDPARTY_IDPROF6__'] = (is_object($object) ? $object->idprof6 : '');
6476				$substitutionarray['__THIRDPARTY_TVAINTRA__'] = (is_object($object) ? $object->tva_intra : '');
6477				$substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = (is_object($object) ? dol_htmlentitiesbr($object->note_public) : '');
6478				$substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = (is_object($object) ? dol_htmlentitiesbr($object->note_private) : '');
6479			} elseif (is_object($object->thirdparty)) {
6480				$substitutionarray['__THIRDPARTY_ID__'] = (is_object($object->thirdparty) ? $object->thirdparty->id : '');
6481				$substitutionarray['__THIRDPARTY_NAME__'] = (is_object($object->thirdparty) ? $object->thirdparty->name : '');
6482				$substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = (is_object($object->thirdparty) ? $object->thirdparty->name_alias : '');
6483				$substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = (is_object($object->thirdparty) ? $object->thirdparty->code_client : '');
6484				$substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = (is_object($object->thirdparty) ? $object->thirdparty->code_fournisseur : '');
6485				$substitutionarray['__THIRDPARTY_EMAIL__'] = (is_object($object->thirdparty) ? $object->thirdparty->email : '');
6486				$substitutionarray['__THIRDPARTY_PHONE__'] = (is_object($object->thirdparty) ? $object->thirdparty->phone : '');
6487				$substitutionarray['__THIRDPARTY_FAX__'] = (is_object($object->thirdparty) ? $object->thirdparty->fax : '');
6488				$substitutionarray['__THIRDPARTY_ADDRESS__'] = (is_object($object->thirdparty) ? $object->thirdparty->address : '');
6489				$substitutionarray['__THIRDPARTY_ZIP__'] = (is_object($object->thirdparty) ? $object->thirdparty->zip : '');
6490				$substitutionarray['__THIRDPARTY_TOWN__'] = (is_object($object->thirdparty) ? $object->thirdparty->town : '');
6491				$substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = (is_object($object->thirdparty) ? $object->thirdparty->country_id : '');
6492				$substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = (is_object($object->thirdparty) ? $object->thirdparty->country_code : '');
6493				$substitutionarray['__THIRDPARTY_IDPROF1__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof1 : '');
6494				$substitutionarray['__THIRDPARTY_IDPROF2__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof2 : '');
6495				$substitutionarray['__THIRDPARTY_IDPROF3__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof3 : '');
6496				$substitutionarray['__THIRDPARTY_IDPROF4__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof4 : '');
6497				$substitutionarray['__THIRDPARTY_IDPROF5__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof5 : '');
6498				$substitutionarray['__THIRDPARTY_IDPROF6__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof6 : '');
6499				$substitutionarray['__THIRDPARTY_TVAINTRA__'] = (is_object($object->thirdparty) ? $object->thirdparty->tva_intra : '');
6500				$substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = (is_object($object->thirdparty) ? dol_htmlentitiesbr($object->thirdparty->note_public) : '');
6501				$substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = (is_object($object->thirdparty) ? dol_htmlentitiesbr($object->thirdparty->note_private) : '');
6502			}
6503
6504			if (is_object($object) && $object->element == 'recruitmentcandidature') {
6505				$substitutionarray['__CANDIDATE_FULLNAME__'] = $object->getFullName($outputlangs);
6506				$substitutionarray['__CANDIDATE_FIRSTNAME__'] = $object->firstname;
6507				$substitutionarray['__CANDIDATE_LASTNAME__'] = $object->lastname;
6508			}
6509
6510			if (is_object($object->project))
6511			{
6512				$substitutionarray['__PROJECT_ID__'] = (is_object($object->project) ? $object->project->id : '');
6513				$substitutionarray['__PROJECT_REF__'] = (is_object($object->project) ? $object->project->ref : '');
6514				$substitutionarray['__PROJECT_NAME__'] = (is_object($object->project) ? $object->project->title : '');
6515			}
6516			if (is_object($object->projet))	// Deprecated, for backward compatibility
6517			{
6518				$substitutionarray['__PROJECT_ID__'] = (is_object($object->projet) ? $object->projet->id : '');
6519				$substitutionarray['__PROJECT_REF__'] = (is_object($object->projet) ? $object->projet->ref : '');
6520				$substitutionarray['__PROJECT_NAME__'] = (is_object($object->projet) ? $object->projet->title : '');
6521			}
6522
6523			if (is_object($object) && $object->element == 'shipping')
6524			{
6525				$substitutionarray['__SHIPPINGTRACKNUM__'] = $object->tracking_number;
6526				$substitutionarray['__SHIPPINGTRACKNUMURL__'] = $object->tracking_url;
6527			}
6528			if (is_object($object) && $object->element == 'reception')
6529			{
6530				$substitutionarray['__RECEPTIONTRACKNUM__'] = $object->tracking_number;
6531				$substitutionarray['__RECEPTIONTRACKNUMURL__'] = $object->tracking_url;
6532			}
6533
6534			if (is_object($object) && $object->element == 'contrat' && $object->id > 0 && is_array($object->lines))
6535			{
6536				$dateplannedstart = '';
6537				$datenextexpiration = '';
6538				foreach ($object->lines as $line)
6539				{
6540					if ($line->date_ouverture_prevue > $dateplannedstart) $dateplannedstart = $line->date_ouverture_prevue;
6541					if ($line->statut == 4 && $line->date_fin_prevue && (!$datenextexpiration || $line->date_fin_prevue < $datenextexpiration)) $datenextexpiration = $line->date_fin_prevue;
6542				}
6543				$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = dol_print_date($dateplannedstart, 'dayrfc');
6544				$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = dol_print_date($dateplannedstart, 'standard');
6545				$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = dol_print_date($datenextexpiration, 'dayrfc');
6546				$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = dol_print_date($datenextexpiration, 'standard');
6547			}
6548
6549			// Create dynamic tags for __EXTRAFIELD_FIELD__
6550			if ($object->table_element && $object->id > 0)
6551			{
6552				if (!is_object($extrafields)) $extrafields = new ExtraFields($db);
6553				$extrafields->fetch_name_optionals_label($object->table_element, true);
6554
6555				if ($object->fetch_optionals() > 0)
6556				{
6557					if (is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label']) > 0)
6558					{
6559						foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) {
6560							$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'__'] = $object->array_options['options_'.$key];
6561							if ($extrafields->attributes[$object->table_element]['type'][$key] == 'date') {
6562								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'__'] = dol_print_date($object->array_options['options_'.$key], 'day');
6563								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_LOCALE__'] = dol_print_date($object->array_options['options_'.$key], 'day', 'tzserver', $outputlangs);
6564								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_RFC__'] = dol_print_date($object->array_options['options_'.$key], 'dayrfc');
6565							} elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'datetime') {
6566								$datetime = $object->array_options['options_'.$key];
6567								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhour') : '');
6568								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_LOCALE__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhour', 'tzserver', $outputlangs) : '');
6569								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_DAY_LOCALE__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'day', 'tzserver', $outputlangs) : '');
6570								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_RFC__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhourrfc') : '');
6571							}
6572						}
6573					}
6574				}
6575			}
6576
6577			// Complete substitution array with the url to make online payment
6578			$paymenturl = '';
6579			if (empty($substitutionarray['__REF__']))
6580			{
6581				$paymenturl = '';
6582			} else {
6583				// Set the online payment url link into __ONLINE_PAYMENT_URL__ key
6584				require_once DOL_DOCUMENT_ROOT.'/core/lib/payments.lib.php';
6585				$outputlangs->loadLangs(array('paypal', 'other'));
6586				$typeforonlinepayment = 'free';
6587				if (is_object($object) && $object->element == 'commande') $typeforonlinepayment = 'order';
6588				if (is_object($object) && $object->element == 'facture')  $typeforonlinepayment = 'invoice';
6589				if (is_object($object) && $object->element == 'member')   $typeforonlinepayment = 'member';
6590				$url = getOnlinePaymentUrl(0, $typeforonlinepayment, $substitutionarray['__REF__']);
6591				$paymenturl = $url;
6592			}
6593
6594			if ($object->id > 0)
6595			{
6596				$substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = ($paymenturl ?str_replace('\n', "\n", $outputlangs->trans("PredefinedMailContentLink", $paymenturl)) : '');
6597				$substitutionarray['__ONLINE_PAYMENT_URL__'] = $paymenturl;
6598
6599				if (!empty($conf->global->PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'propal')
6600				{
6601					$substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = $object->getLastMainDocLink($object->element);
6602				} else $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = '';
6603				if (!empty($conf->global->ORDER_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'commande')
6604				{
6605					$substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = $object->getLastMainDocLink($object->element);
6606				} else $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = '';
6607				if (!empty($conf->global->INVOICE_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'facture')
6608				{
6609					$substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = $object->getLastMainDocLink($object->element);
6610				} else $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = '';
6611
6612				if (is_object($object) && $object->element == 'propal') $substitutionarray['__URL_PROPOSAL__'] = DOL_MAIN_URL_ROOT."/comm/propal/card.php?id=".$object->id;
6613				if (is_object($object) && $object->element == 'commande') $substitutionarray['__URL_ORDER__'] = DOL_MAIN_URL_ROOT."/commande/card.php?id=".$object->id;
6614				if (is_object($object) && $object->element == 'facture') $substitutionarray['__URL_INVOICE__'] = DOL_MAIN_URL_ROOT."/compta/facture/card.php?id=".$object->id;
6615			}
6616
6617			if (is_object($object) && $object->element == 'action')
6618			{
6619				$substitutionarray['__EVENT_LABEL__'] = $object->label;
6620				$substitutionarray['__EVENT_DATE__'] = dol_print_date($object->datep, '%A %d %b %Y');
6621				$substitutionarray['__EVENT_TIME__'] = dol_print_date($object->datep, '%H:%M:%S');
6622			}
6623		}
6624	}
6625	if (empty($exclude) || !in_array('objectamount', $exclude))
6626	{
6627		include_once DOL_DOCUMENT_ROOT.'/core/lib/functionsnumtoword.lib.php';
6628
6629		$substitutionarray['__DATE_YMD__']        = is_object($object) ? (isset($object->date) ? dol_print_date($object->date, 'day', 0, $outputlangs) : null) : '';
6630		$substitutionarray['__DATE_DUE_YMD__']    = is_object($object) ? (isset($object->date_lim_reglement) ? dol_print_date($object->date_lim_reglement, 'day', 0, $outputlangs) : null) : '';
6631
6632		$substitutionarray['__AMOUNT__']          = is_object($object) ? $object->total_ttc : '';
6633		$substitutionarray['__AMOUNT_TEXT__']     = is_object($object) ? dol_convertToWord($object->total_ttc, $outputlangs, '', true) : '';
6634		$substitutionarray['__AMOUNT_TEXTCURRENCY__'] = is_object($object) ? dol_convertToWord($object->total_ttc, $outputlangs, $conf->currency, true) : '';
6635		$substitutionarray['__AMOUNT_EXCL_TAX__'] = is_object($object) ? $object->total_ht : '';
6636		$substitutionarray['__AMOUNT_VAT__']      = is_object($object) ? (isset($object->total_vat) ? $object->total_vat : $object->total_tva) : '';
6637		$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)) : '';
6638		$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)) : '';
6639		if ($onlykey != 2 || $mysoc->useLocalTax(1)) $substitutionarray['__AMOUNT_TAX2__']     = is_object($object) ? $object->total_localtax1 : '';
6640		if ($onlykey != 2 || $mysoc->useLocalTax(2)) $substitutionarray['__AMOUNT_TAX3__']     = is_object($object) ? $object->total_localtax2 : '';
6641
6642		$substitutionarray['__AMOUNT_FORMATED__']          = is_object($object) ? ($object->total_ttc ? price($object->total_ttc, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
6643		$substitutionarray['__AMOUNT_EXCL_TAX_FORMATED__'] = is_object($object) ? ($object->total_ht ? price($object->total_ht, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
6644		$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)) : '';
6645		if ($onlykey != 2 || $mysoc->useLocalTax(1)) $substitutionarray['__AMOUNT_TAX2_FORMATED__']     = is_object($object) ? ($object->total_localtax1 ? price($object->total_localtax1, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
6646		if ($onlykey != 2 || $mysoc->useLocalTax(2)) $substitutionarray['__AMOUNT_TAX3_FORMATED__']     = is_object($object) ? ($object->total_localtax2 ? price($object->total_localtax2, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
6647
6648		$substitutionarray['__AMOUNT_MULTICURRENCY__']          = (is_object($object) && isset($object->multicurrency_total_ttc)) ? $object->multicurrency_total_ttc : '';
6649		$substitutionarray['__AMOUNT_MULTICURRENCY_TEXT__']     = (is_object($object) && isset($object->multicurrency_total_ttc)) ? dol_convertToWord($object->multicurrency_total_ttc, $outputlangs, '', true) : '';
6650		$substitutionarray['__AMOUNT_MULTICURRENCY_TEXTCURRENCY__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? dol_convertToWord($object->multicurrency_total_ttc, $outputlangs, $object->multicurrency_code, true) : '';
6651		// TODO Add other keys for foreign multicurrency
6652
6653		// For backward compatibility
6654		if ($onlykey != 2)
6655		{
6656			$substitutionarray['__TOTAL_TTC__']    = is_object($object) ? $object->total_ttc : '';
6657			$substitutionarray['__TOTAL_HT__']     = is_object($object) ? $object->total_ht : '';
6658			$substitutionarray['__TOTAL_VAT__']    = is_object($object) ? (isset($object->total_vat) ? $object->total_vat : $object->total_tva) : '';
6659		}
6660	}
6661
6662	//var_dump($substitutionarray['__AMOUNT_FORMATED__']);
6663	if (empty($exclude) || !in_array('date', $exclude))
6664	{
6665		include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
6666
6667		$tmp = dol_getdate(dol_now(), true);
6668		$tmp2 = dol_get_prev_day($tmp['mday'], $tmp['mon'], $tmp['year']);
6669		$tmp3 = dol_get_prev_month($tmp['mon'], $tmp['year']);
6670		$tmp4 = dol_get_next_day($tmp['mday'], $tmp['mon'], $tmp['year']);
6671		$tmp5 = dol_get_next_month($tmp['mon'], $tmp['year']);
6672
6673		$daytext = $outputlangs->trans('Day'.$tmp['wday']);
6674
6675		$substitutionarray = array_merge($substitutionarray, array(
6676			'__DAY__' => (string) $tmp['mday'],
6677			'__DAY_TEXT__' => $daytext, // Monday
6678			'__DAY_TEXT_SHORT__' => dol_trunc($daytext, 3, 'right', 'UTF-8', 1), // Mon
6679			'__DAY_TEXT_MIN__' => dol_trunc($daytext, 1, 'right', 'UTF-8', 1), // M
6680			'__MONTH__' => (string) $tmp['mon'],
6681			'__MONTH_TEXT__' => $outputlangs->trans('Month'.sprintf("%02d", $tmp['mon'])),
6682			'__MONTH_TEXT_SHORT__' => $outputlangs->trans('MonthShort'.sprintf("%02d", $tmp['mon'])),
6683			'__MONTH_TEXT_MIN__' => $outputlangs->trans('MonthVeryShort'.sprintf("%02d", $tmp['mon'])),
6684			'__YEAR__' => (string) $tmp['year'],
6685			'__PREVIOUS_DAY__' => (string) $tmp2['day'],
6686			'__PREVIOUS_MONTH__' => (string) $tmp3['month'],
6687			'__PREVIOUS_YEAR__' => (string) ($tmp['year'] - 1),
6688			'__NEXT_DAY__' => (string) $tmp4['day'],
6689			'__NEXT_MONTH__' => (string) $tmp5['month'],
6690			'__NEXT_YEAR__' => (string) ($tmp['year'] + 1),
6691		));
6692	}
6693
6694	if (!empty($conf->multicompany->enabled))
6695	{
6696		$substitutionarray = array_merge($substitutionarray, array('__ENTITY_ID__' => $conf->entity));
6697	}
6698	if (empty($exclude) || !in_array('system', $exclude))
6699	{
6700		$substitutionarray['__DOL_MAIN_URL_ROOT__'] = DOL_MAIN_URL_ROOT;
6701		$substitutionarray['__(AnyTranslationKey)__'] = $outputlangs->trans('TranslationOfKey');
6702		$substitutionarray['__(AnyTranslationKey|langfile)__'] = $outputlangs->trans('TranslationOfKey').' (load also language file before)';
6703		$substitutionarray['__[AnyConstantKey]__'] = $outputlangs->trans('ValueOfConstantKey');
6704	}
6705
6706	return $substitutionarray;
6707}
6708
6709/**
6710 *  Make substitution into a text string, replacing keys with vals from $substitutionarray (oldval=>newval),
6711 *  and texts like __(TranslationKey|langfile)__ and __[ConstantKey]__ are also replaced.
6712 *  Example of usage:
6713 *  $substitutionarray = getCommonSubstitutionArray($langs, 0, null, $thirdparty);
6714 *  complete_substitutions_array($substitutionarray, $langs, $thirdparty);
6715 *  $mesg = make_substitutions($mesg, $substitutionarray, $langs);
6716 *
6717 *  @param	string		$text	      			Source string in which we must do substitution
6718 *  @param  array		$substitutionarray		Array with key->val to substitute. Example: array('__MYKEY__' => 'MyVal', ...)
6719 *  @param	Translate	$outputlangs			Output language
6720 * 	@return string  		    				Output string after substitutions
6721 *  @see	complete_substitutions_array(), getCommonSubstitutionArray()
6722 */
6723function make_substitutions($text, $substitutionarray, $outputlangs = null)
6724{
6725	global $conf, $langs;
6726
6727	if (!is_array($substitutionarray)) return 'ErrorBadParameterSubstitutionArrayWhenCalling_make_substitutions';
6728
6729	if (empty($outputlangs)) $outputlangs = $langs;
6730
6731	// Make substitution for language keys: __(AnyTranslationKey)__ or __(AnyTranslationKey|langfile)__
6732	if (is_object($outputlangs))
6733	{
6734		$reg = array();
6735		while (preg_match('/__\(([^\)]+)\)__/', $text, $reg))
6736		{
6737			$msgishtml = 0;
6738			if (dol_textishtml($text, 1)) $msgishtml = 1;
6739
6740			// If key is __(TranslationKey|langfile)__, then force load of langfile.lang
6741			$tmp = explode('|', $reg[1]);
6742			if (!empty($tmp[1])) $outputlangs->load($tmp[1]);
6743
6744			$text = preg_replace('/__\('.preg_quote($reg[1], '/').'\)__/', $msgishtml ?dol_htmlentitiesbr($outputlangs->transnoentitiesnoconv($reg[1])) : $outputlangs->transnoentitiesnoconv($reg[1]), $text);
6745		}
6746	}
6747
6748	// Make substitution for constant keys.
6749	// Must be after the substitution of translation, so if the text of translation contains a string __[xxx]__, it is also converted.
6750	$reg = array();
6751	while (preg_match('/__\[([^\]]+)\]__/', $text, $reg))
6752	{
6753		$msgishtml = 0;
6754		if (dol_textishtml($text, 1)) $msgishtml = 1;
6755
6756		$keyfound = $reg[1];
6757		if (isASecretKey($keyfound)) $newval = '*****forbidden*****';
6758		else $newval = empty($conf->global->$keyfound) ? '' : $conf->global->$keyfound;
6759		$text = preg_replace('/__\['.preg_quote($keyfound, '/').'\]__/', $msgishtml ?dol_htmlentitiesbr($newval) : $newval, $text);
6760	}
6761
6762	// Make substitition for array $substitutionarray
6763	foreach ($substitutionarray as $key => $value)
6764	{
6765		if (!isset($value)) continue; // If value is null, it same than not having substitution key at all into array, we do not replace.
6766
6767		if ($key == '__USER_SIGNATURE__' && (!empty($conf->global->MAIN_MAIL_DO_NOT_USE_SIGN))) $value = ''; // Protection
6768
6769		$text = str_replace("$key", "$value", $text); // We must keep the " to work when value is 123.5 for example
6770	}
6771
6772	return $text;
6773}
6774
6775/**
6776 *  Complete the $substitutionarray with more entries coming from external module that had set the "substitutions=1" into module_part array.
6777 *  In this case, method completesubstitutionarray provided by module is called.
6778 *
6779 *  @param  array		$substitutionarray		Array substitution old value => new value value
6780 *  @param  Translate	$outputlangs            Output language
6781 *  @param  Object		$object                 Source object
6782 *  @param  mixed		$parameters       		Add more parameters (useful to pass product lines)
6783 *  @param  string      $callfunc               What is the name of the custom function that will be called? (default: completesubstitutionarray)
6784 *  @return	void
6785 *  @see 	make_substitutions()
6786 */
6787function complete_substitutions_array(&$substitutionarray, $outputlangs, $object = null, $parameters = null, $callfunc = "completesubstitutionarray")
6788{
6789	global $conf, $user;
6790
6791	require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6792
6793	// Add a substitution key for each extrafields, using key __EXTRA_XXX__
6794	// TODO Remove this. Already available into the getCommonSubstitutionArray used to build the substitution array.
6795	/*if (is_object($object) && is_array($object->array_options))
6796	{
6797		foreach($object->array_options as $key => $val)
6798		{
6799			$keyshort=preg_replace('/^(options|extra)_/','',$key);
6800			$substitutionarray['__EXTRAFIELD_'.$keyshort.'__']=$val;
6801			// For backward compatibiliy
6802			$substitutionarray['%EXTRA_'.$keyshort.'%']=$val;
6803		}
6804	}*/
6805
6806	// Check if there is external substitution to do, requested by plugins
6807	$dirsubstitutions = array_merge(array(), (array) $conf->modules_parts['substitutions']);
6808
6809	foreach ($dirsubstitutions as $reldir)
6810	{
6811		$dir = dol_buildpath($reldir, 0);
6812
6813		// Check if directory exists
6814		if (!dol_is_dir($dir)) continue;
6815
6816		$substitfiles = dol_dir_list($dir, 'files', 0, 'functions_');
6817		foreach ($substitfiles as $substitfile)
6818		{
6819			$reg = array();
6820			if (preg_match('/functions_(.*)\.lib\.php/i', $substitfile['name'], $reg))
6821			{
6822				$module = $reg[1];
6823
6824				dol_syslog("Library ".$substitfile['name']." found into ".$dir);
6825				// Include the user's functions file
6826				require_once $dir.$substitfile['name'];
6827				// Call the user's function, and only if it is defined
6828				$function_name = $module."_".$callfunc;
6829				if (function_exists($function_name)) {
6830					$function_name($substitutionarray, $outputlangs, $object, $parameters);
6831				}
6832			}
6833		}
6834	}
6835	if (!empty($conf->global->ODT_ENABLE_ALL_TAGS_IN_SUBSTITUTIONS)) {
6836		// to list all tags in odt template
6837		$tags = '';
6838		foreach ($substitutionarray as $key => $value) {
6839			$tags .= '{'.$key.'} => '.$value."\n";
6840		}
6841		$substitutionarray = array_merge($substitutionarray, array('__ALL_TAGS__' => $tags));
6842	}
6843}
6844
6845/**
6846 *    Format output for start and end date
6847 *
6848 *    @param	int	$date_start    Start date
6849 *    @param    int	$date_end      End date
6850 *    @param    string		$format        Output format
6851 *    @param	Translate	$outputlangs   Output language
6852 *    @return	void
6853 */
6854function print_date_range($date_start, $date_end, $format = '', $outputlangs = '')
6855{
6856	print get_date_range($date_start, $date_end, $format, $outputlangs);
6857}
6858
6859/**
6860 *    Format output for start and end date
6861 *
6862 *    @param	int			$date_start    		Start date
6863 *    @param    int			$date_end      		End date
6864 *    @param    string		$format        		Output format
6865 *    @param	Translate	$outputlangs   		Output language
6866 *    @param	integer		$withparenthesis	1=Add parenthesis, 0=non parenthesis
6867 *    @return	string							String
6868 */
6869function get_date_range($date_start, $date_end, $format = '', $outputlangs = '', $withparenthesis = 1)
6870{
6871	global $langs;
6872
6873	$out = '';
6874
6875	if (!is_object($outputlangs)) $outputlangs = $langs;
6876
6877	if ($date_start && $date_end)
6878	{
6879		$out .= ($withparenthesis ? ' (' : '').$outputlangs->transnoentitiesnoconv('DateFromTo', dol_print_date($date_start, $format, false, $outputlangs), dol_print_date($date_end, $format, false, $outputlangs)).($withparenthesis ? ')' : '');
6880	}
6881	if ($date_start && !$date_end)
6882	{
6883		$out .= ($withparenthesis ? ' (' : '').$outputlangs->transnoentitiesnoconv('DateFrom', dol_print_date($date_start, $format, false, $outputlangs)).($withparenthesis ? ')' : '');
6884	}
6885	if (!$date_start && $date_end)
6886	{
6887		$out .= ($withparenthesis ? ' (' : '').$outputlangs->transnoentitiesnoconv('DateUntil', dol_print_date($date_end, $format, false, $outputlangs)).($withparenthesis ? ')' : '');
6888	}
6889
6890	return $out;
6891}
6892
6893/**
6894 * Return firstname and lastname in correct order
6895 *
6896 * @param	string	$firstname		Firstname
6897 * @param	string	$lastname		Lastname
6898 * @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
6899 * @return	string					Firstname + lastname or Lastname + firstname
6900 */
6901function dolGetFirstLastname($firstname, $lastname, $nameorder = -1)
6902{
6903	global $conf;
6904
6905	$ret = '';
6906	// If order not defined, we use the setup
6907	if ($nameorder < 0) $nameorder = (empty($conf->global->MAIN_FIRSTNAME_NAME_POSITION) ? 1 : 0);
6908	if ($nameorder == 1) {
6909		$ret .= $firstname;
6910		if ($firstname && $lastname) $ret .= ' ';
6911		$ret .= $lastname;
6912	} elseif ($nameorder == 2 || $nameorder == 3) {
6913		$ret .= $firstname;
6914		if (empty($ret) && $nameorder == 3) {
6915			$ret .= $lastname;
6916		}
6917	} else {	// 0, 4 or 5
6918		$ret .= $lastname;
6919		if (empty($ret) && $nameorder == 5) {
6920			$ret .= $firstname;
6921		}
6922		if ($nameorder == 0) {
6923			if ($firstname && $lastname) $ret .= ' ';
6924			$ret .= $firstname;
6925		}
6926	}
6927	return $ret;
6928}
6929
6930
6931/**
6932 *	Set event message in dol_events session object. Will be output by calling dol_htmloutput_events.
6933 *  Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function.
6934 *  Note: Prefer to use setEventMessages instead.
6935 *
6936 *	@param	string|string[] $mesgs			Message string or array
6937 *  @param  string          $style      	Which style to use ('mesgs' by default, 'warnings', 'errors')
6938 *  @return	void
6939 *  @see	dol_htmloutput_events()
6940 */
6941function setEventMessage($mesgs, $style = 'mesgs')
6942{
6943	//dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);		This is not deprecated, it is used by setEventMessages function
6944	if (!is_array($mesgs)) {
6945		// If mesgs is a string
6946		if ($mesgs) $_SESSION['dol_events'][$style][] = $mesgs;
6947	} else {
6948		// If mesgs is an array
6949		foreach ($mesgs as $mesg)
6950		{
6951			if ($mesg) $_SESSION['dol_events'][$style][] = $mesg;
6952		}
6953	}
6954}
6955
6956/**
6957 *	Set event messages in dol_events session object. Will be output by calling dol_htmloutput_events.
6958 *  Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function.
6959 *
6960 *	@param	string	$mesg			Message string
6961 *	@param	array	$mesgs			Message array
6962 *  @param  string	$style      	Which style to use ('mesgs' by default, 'warnings', 'errors')
6963 *  @param	string	$messagekey		A key to be used to allow the feature "Never show this message again"
6964 *  @return	void
6965 *  @see	dol_htmloutput_events()
6966 */
6967function setEventMessages($mesg, $mesgs, $style = 'mesgs', $messagekey = '')
6968{
6969	if (empty($mesg) && empty($mesgs))
6970	{
6971		dol_syslog("Try to add a message in stack with empty message", LOG_WARNING);
6972	} else {
6973		if ($messagekey)
6974		{
6975			// Complete message with a js link to set a cookie "DOLHIDEMESSAGE".$messagekey;
6976			// TODO
6977			$mesg .= '';
6978		}
6979		if (empty($messagekey) || empty($_COOKIE["DOLHIDEMESSAGE".$messagekey]))
6980		{
6981			if (!in_array((string) $style, array('mesgs', 'warnings', 'errors'))) dol_print_error('', 'Bad parameter style='.$style.' for setEventMessages');
6982			if (empty($mesgs)) setEventMessage($mesg, $style);
6983			else {
6984				if (!empty($mesg) && !in_array($mesg, $mesgs)) setEventMessage($mesg, $style); // Add message string if not already into array
6985				setEventMessage($mesgs, $style);
6986			}
6987		}
6988	}
6989}
6990
6991/**
6992 *	Print formated messages to output (Used to show messages on html output).
6993 *  Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function, so there is
6994 *  no need to call it explicitely.
6995 *
6996 *  @param	int		$disabledoutputofmessages	Clear all messages stored into session without diplaying them
6997 *  @return	void
6998 *  @see    									dol_htmloutput_mesg()
6999 */
7000function dol_htmloutput_events($disabledoutputofmessages = 0)
7001{
7002	// Show mesgs
7003	if (isset($_SESSION['dol_events']['mesgs'])) {
7004		if (empty($disabledoutputofmessages)) dol_htmloutput_mesg('', $_SESSION['dol_events']['mesgs']);
7005		unset($_SESSION['dol_events']['mesgs']);
7006	}
7007
7008	// Show errors
7009	if (isset($_SESSION['dol_events']['errors'])) {
7010		if (empty($disabledoutputofmessages)) dol_htmloutput_mesg('', $_SESSION['dol_events']['errors'], 'error');
7011		unset($_SESSION['dol_events']['errors']);
7012	}
7013
7014	// Show warnings
7015	if (isset($_SESSION['dol_events']['warnings'])) {
7016		if (empty($disabledoutputofmessages)) dol_htmloutput_mesg('', $_SESSION['dol_events']['warnings'], 'warning');
7017		unset($_SESSION['dol_events']['warnings']);
7018	}
7019}
7020
7021/**
7022 *	Get formated messages to output (Used to show messages on html output).
7023 *  This include also the translation of the message key.
7024 *
7025 *	@param	string		$mesgstring		Message string or message key
7026 *	@param	string[]	$mesgarray      Array of message strings or message keys
7027 *  @param  string		$style          Style of message output ('ok' or 'error')
7028 *  @param  int			$keepembedded   Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
7029 *	@return	string						Return html output
7030 *
7031 *  @see    dol_print_error()
7032 *  @see    dol_htmloutput_errors()
7033 *  @see    setEventMessages()
7034 */
7035function get_htmloutput_mesg($mesgstring = '', $mesgarray = '', $style = 'ok', $keepembedded = 0)
7036{
7037	global $conf, $langs;
7038
7039	$ret = 0; $return = '';
7040	$out = '';
7041	$divstart = $divend = '';
7042
7043	// If inline message with no format, we add it.
7044	if ((empty($conf->use_javascript_ajax) || !empty($conf->global->MAIN_DISABLE_JQUERY_JNOTIFY) || $keepembedded) && !preg_match('/<div class=".*">/i', $out))
7045	{
7046		$divstart = '<div class="'.$style.' clearboth">';
7047		$divend = '</div>';
7048	}
7049
7050	if ((is_array($mesgarray) && count($mesgarray)) || $mesgstring)
7051	{
7052		$langs->load("errors");
7053		$out .= $divstart;
7054		if (is_array($mesgarray) && count($mesgarray))
7055		{
7056			foreach ($mesgarray as $message)
7057			{
7058				$ret++;
7059				$out .= $langs->trans($message);
7060				if ($ret < count($mesgarray)) $out .= "<br>\n";
7061			}
7062		}
7063		if ($mesgstring)
7064		{
7065			$langs->load("errors");
7066			$ret++;
7067			$out .= $langs->trans($mesgstring);
7068		}
7069		$out .= $divend;
7070	}
7071
7072	if ($out)
7073	{
7074		if (!empty($conf->use_javascript_ajax) && empty($conf->global->MAIN_DISABLE_JQUERY_JNOTIFY) && empty($keepembedded))
7075		{
7076			$return = '<script>
7077					$(document).ready(function() {
7078						var block = '.(!empty($conf->global->MAIN_USE_JQUERY_BLOCKUI) ? "true" : "false").'
7079						if (block) {
7080							$.dolEventValid("","'.dol_escape_js($out).'");
7081						} else {
7082							/* jnotify(message, preset of message type, keepmessage) */
7083							$.jnotify("'.dol_escape_js($out).'",
7084							"'.($style == "ok" ? 3000 : $style).'",
7085							'.($style == "ok" ? "false" : "true").',
7086							{ remove: function (){} } );
7087						}
7088					});
7089				</script>';
7090		} else {
7091			$return = $out;
7092		}
7093	}
7094
7095	return $return;
7096}
7097
7098/**
7099 *  Get formated error messages to output (Used to show messages on html output).
7100 *
7101 *  @param  string	$mesgstring         Error message
7102 *  @param  array	$mesgarray          Error messages array
7103 *  @param  int		$keepembedded       Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
7104 *  @return string                		Return html output
7105 *
7106 *  @see    dol_print_error()
7107 *  @see    dol_htmloutput_mesg()
7108 */
7109function get_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0)
7110{
7111	return get_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
7112}
7113
7114/**
7115 *	Print formated messages to output (Used to show messages on html output).
7116 *
7117 *	@param	string		$mesgstring		Message string or message key
7118 *	@param	string[]	$mesgarray      Array of message strings or message keys
7119 *	@param  string      $style          Which style to use ('ok', 'warning', 'error')
7120 *	@param  int         $keepembedded   Set to 1 if message must be kept embedded into its html place (this disable jnotify)
7121 *	@return	void
7122 *
7123 *	@see    dol_print_error()
7124 *	@see    dol_htmloutput_errors()
7125 *	@see    setEventMessages()
7126 */
7127function dol_htmloutput_mesg($mesgstring = '', $mesgarray = array(), $style = 'ok', $keepembedded = 0)
7128{
7129	if (empty($mesgstring) && (!is_array($mesgarray) || count($mesgarray) == 0)) return;
7130
7131	$iserror = 0;
7132	$iswarning = 0;
7133	if (is_array($mesgarray))
7134	{
7135		foreach ($mesgarray as $val)
7136		{
7137			if ($val && preg_match('/class="error"/i', $val)) { $iserror++; break; }
7138			if ($val && preg_match('/class="warning"/i', $val)) { $iswarning++; break; }
7139		}
7140	} elseif ($mesgstring && preg_match('/class="error"/i', $mesgstring)) $iserror++;
7141	elseif ($mesgstring && preg_match('/class="warning"/i', $mesgstring)) $iswarning++;
7142	if ($style == 'error') $iserror++;
7143	if ($style == 'warning') $iswarning++;
7144
7145	if ($iserror || $iswarning)
7146	{
7147		// Remove div from texts
7148		$mesgstring = preg_replace('/<\/div><div class="(error|warning)">/', '<br>', $mesgstring);
7149		$mesgstring = preg_replace('/<div class="(error|warning)">/', '', $mesgstring);
7150		$mesgstring = preg_replace('/<\/div>/', '', $mesgstring);
7151		// Remove div from texts array
7152		if (is_array($mesgarray))
7153		{
7154			$newmesgarray = array();
7155			foreach ($mesgarray as $val)
7156			{
7157				if (is_string($val))
7158				{
7159					$tmpmesgstring = preg_replace('/<\/div><div class="(error|warning)">/', '<br>', $val);
7160					$tmpmesgstring = preg_replace('/<div class="(error|warning)">/', '', $tmpmesgstring);
7161					$tmpmesgstring = preg_replace('/<\/div>/', '', $tmpmesgstring);
7162					$newmesgarray[] = $tmpmesgstring;
7163				} else {
7164					dol_syslog("Error call of dol_htmloutput_mesg with an array with a value that is not a string", LOG_WARNING);
7165				}
7166			}
7167			$mesgarray = $newmesgarray;
7168		}
7169		print get_htmloutput_mesg($mesgstring, $mesgarray, ($iserror ? 'error' : 'warning'), $keepembedded);
7170	} else print get_htmloutput_mesg($mesgstring, $mesgarray, 'ok', $keepembedded);
7171}
7172
7173/**
7174 *  Print formated error messages to output (Used to show messages on html output).
7175 *
7176 *  @param	string	$mesgstring          Error message
7177 *  @param  array	$mesgarray           Error messages array
7178 *  @param  int		$keepembedded        Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
7179 *  @return	void
7180 *
7181 *  @see    dol_print_error()
7182 *  @see    dol_htmloutput_mesg()
7183 */
7184function dol_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0)
7185{
7186	dol_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
7187}
7188
7189/**
7190 * 	Advanced sort array by second index function, which produces ascending (default)
7191 *  or descending output and uses optionally natural case insensitive sorting (which
7192 *  can be optionally case sensitive as well).
7193 *
7194 *  @param      array		$array      		Array to sort (array of array('key1'=>val1,'key2'=>val2,'key3'...) or array of objects)
7195 *  @param      string		$index				Key in array to use for sorting criteria
7196 *  @param      int			$order				Sort order ('asc' or 'desc')
7197 *  @param      int			$natsort			1=use "natural" sort (natsort), 0=use "standard" sort (asort)
7198 *  @param      int			$case_sensitive		1=sort is case sensitive, 0=not case sensitive
7199 *  @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.
7200 *  @return     array							Sorted array
7201 */
7202function dol_sort_array(&$array, $index, $order = 'asc', $natsort = 0, $case_sensitive = 0, $keepindex = 0)
7203{
7204	// Clean parameters
7205	$order = strtolower($order);
7206
7207	if (is_array($array))
7208	{
7209		$sizearray = count($array);
7210		if ($sizearray > 0)
7211		{
7212			$temp = array();
7213			foreach (array_keys($array) as $key)
7214			{
7215				if (is_object($array[$key])) {
7216					$temp[$key] = empty($array[$key]->$index) ? 0 : $array[$key]->$index;
7217				} else {
7218					$temp[$key] = empty($array[$key][$index]) ? 0 : $array[$key][$index];
7219				}
7220			}
7221
7222			if (!$natsort) {
7223				($order == 'asc') ? asort($temp) : arsort($temp);
7224			} else {
7225				($case_sensitive) ? natsort($temp) : natcasesort($temp);
7226				if ($order != 'asc') $temp = array_reverse($temp, true);
7227			}
7228
7229			$sorted = array();
7230
7231			foreach (array_keys($temp) as $key)
7232			{
7233				(is_numeric($key) && empty($keepindex)) ? $sorted[] = $array[$key] : $sorted[$key] = $array[$key];
7234			}
7235
7236			return $sorted;
7237		}
7238	}
7239	return $array;
7240}
7241
7242
7243/**
7244 *      Check if a string is in UTF8
7245 *
7246 *      @param	string	$str        String to check
7247 * 		@return	boolean				True if string is UTF8 or ISO compatible with UTF8, False if not (ISO with special char or Binary)
7248 */
7249function utf8_check($str)
7250{
7251	$str = (string) $str;	// Sometimes string is an int.
7252
7253	// We must use here a binary strlen function (so not dol_strlen)
7254	$strLength = dol_strlen($str);
7255	for ($i = 0; $i < $strLength; $i++) {
7256		if (ord($str[$i]) < 0x80) continue; // 0bbbbbbb
7257		elseif ((ord($str[$i]) & 0xE0) == 0xC0) $n = 1; // 110bbbbb
7258		elseif ((ord($str[$i]) & 0xF0) == 0xE0) $n = 2; // 1110bbbb
7259		elseif ((ord($str[$i]) & 0xF8) == 0xF0) $n = 3; // 11110bbb
7260		elseif ((ord($str[$i]) & 0xFC) == 0xF8) $n = 4; // 111110bb
7261		elseif ((ord($str[$i]) & 0xFE) == 0xFC) $n = 5; // 1111110b
7262		else return false; // Does not match any model
7263		for ($j = 0; $j < $n; $j++) { // n bytes matching 10bbbbbb follow ?
7264			if ((++$i == strlen($str)) || ((ord($str[$i]) & 0xC0) != 0x80))
7265			return false;
7266		}
7267	}
7268	return true;
7269}
7270
7271/**
7272 *      Check if a string is in ASCII
7273 *
7274 *      @param	string	$str        String to check
7275 * 		@return	boolean				True if string is ASCII, False if not (byte value > 0x7F)
7276 */
7277function ascii_check($str)
7278{
7279	if (function_exists('mb_check_encoding')) {
7280		//if (mb_detect_encoding($str, 'ASCII', true) return false;
7281		if (!mb_check_encoding($str, 'ASCII')) return false;
7282	} else {
7283		if (preg_match('/[^\x00-\x7f]/', $str)) return false; // Contains a byte > 7f
7284	}
7285
7286	return true;
7287}
7288
7289
7290/**
7291 *      Return a string encoded into OS filesystem encoding. This function is used to define
7292 * 	    value to pass to filesystem PHP functions.
7293 *
7294 *      @param	string	$str        String to encode (UTF-8)
7295 * 		@return	string				Encoded string (UTF-8, ISO-8859-1)
7296 */
7297function dol_osencode($str)
7298{
7299	global $conf;
7300
7301	$tmp = ini_get("unicode.filesystem_encoding"); // Disponible avec PHP 6.0
7302	if (empty($tmp) && !empty($_SERVER["WINDIR"])) $tmp = 'iso-8859-1'; // By default for windows
7303	if (empty($tmp)) $tmp = 'utf-8'; // By default for other
7304	if (!empty($conf->global->MAIN_FILESYSTEM_ENCODING)) $tmp = $conf->global->MAIN_FILESYSTEM_ENCODING;
7305
7306	if ($tmp == 'iso-8859-1') return utf8_decode($str);
7307	return $str;
7308}
7309
7310
7311/**
7312 *      Return an id or code from a code or id.
7313 *      Store also Code-Id into a cache to speed up next request on same key.
7314 *
7315 * 		@param	DoliDB	$db				Database handler
7316 * 		@param	string	$key			Code or Id to get Id or Code
7317 * 		@param	string	$tablename		Table name without prefix
7318 * 		@param	string	$fieldkey		Field to search the key into
7319 * 		@param	string	$fieldid		Field to get
7320 *      @param  int		$entityfilter	Filter by entity
7321 *      @return int						<0 if KO, Id of code if OK
7322 *      @see $langs->getLabelFromKey
7323 */
7324function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid = 'id', $entityfilter = 0)
7325{
7326	global $cache_codes;
7327
7328	// If key empty
7329	if ($key == '') return '';
7330
7331	// Check in cache
7332	if (isset($cache_codes[$tablename][$key][$fieldid]))	// Can be defined to 0 or ''
7333	{
7334		return $cache_codes[$tablename][$key][$fieldid]; // Found in cache
7335	}
7336
7337	dol_syslog('dol_getIdFromCode (value for field '.$fieldid.' from key '.$key.' not found into cache)', LOG_DEBUG);
7338
7339	$sql = "SELECT ".$fieldid." as valuetoget";
7340	$sql .= " FROM ".MAIN_DB_PREFIX.$tablename;
7341	$sql .= " WHERE ".$fieldkey." = '".$db->escape($key)."'";
7342	if (!empty($entityfilter))
7343		$sql .= " AND entity IN (".getEntity($tablename).")";
7344
7345	$resql = $db->query($sql);
7346	if ($resql)
7347	{
7348		$obj = $db->fetch_object($resql);
7349		if ($obj) $cache_codes[$tablename][$key][$fieldid] = $obj->valuetoget;
7350		else $cache_codes[$tablename][$key][$fieldid] = '';
7351		$db->free($resql);
7352		return $cache_codes[$tablename][$key][$fieldid];
7353	} else {
7354		return -1;
7355	}
7356}
7357
7358/**
7359 * Verify if condition in string is ok or not
7360 *
7361 * @param 	string		$strRights		String with condition to check
7362 * @return 	boolean						True or False. Return True if strRights is ''
7363 */
7364function verifCond($strRights)
7365{
7366	global $user, $conf, $langs;
7367	global $leftmenu;
7368	global $rights; // To export to dol_eval function
7369
7370	//print $strRights."<br>\n";
7371	$rights = true;
7372	if ($strRights != '')
7373	{
7374		$str = 'if(!('.$strRights.')) { $rights = false; }';
7375		dol_eval($str); // The dol_eval must contains all the global $xxx used into a condition
7376	}
7377	return $rights;
7378}
7379
7380/**
7381 * Replace eval function to add more security.
7382 * This function is called by verifCond() or trans() and transnoentitiesnoconv().
7383 *
7384 * @param 	string	$s				String to evaluate
7385 * @param	int		$returnvalue	0=No return (used to execute eval($a=something)). 1=Value of eval is returned (used to eval($something)).
7386 * @param   int     $hideerrors     1=Hide errors
7387 * @return	mixed					Nothing or return result of eval
7388 */
7389function dol_eval($s, $returnvalue = 0, $hideerrors = 1)
7390{
7391	// Only global variables can be changed by eval function and returned to caller
7392	global $db, $langs, $user, $conf, $website, $websitepage;
7393	global $action, $mainmenu, $leftmenu;
7394	global $rights;
7395	global $object;
7396	global $mysoc;
7397
7398	global $obj; // To get $obj used into list when dol_eval is used for computed fields and $obj is not yet $object
7399	global $soc; // For backward compatibility
7400
7401	//print $s."<br>\n";
7402	if ($returnvalue) {
7403		if ($hideerrors) return @eval('return '.$s.';');
7404		else return eval('return '.$s.';');
7405	} else {
7406		if ($hideerrors) @eval($s);
7407		else eval($s);
7408	}
7409}
7410
7411/**
7412 * Return if var element is ok
7413 *
7414 * @param   string      $element    Variable to check
7415 * @return  boolean                 Return true of variable is not empty
7416 */
7417function dol_validElement($element)
7418{
7419	return (trim($element) != '');
7420}
7421
7422/**
7423 * 	Return img flag of country for a language code or country code
7424 *
7425 * 	@param	string	$codelang	Language code ('en_IN', 'fr_CA', ...) or ISO Country code on 2 characters in uppercase ('IN', 'FR')
7426 *  @param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"' or 'class="saturatemedium"')
7427 * 	@return	string				HTML img string with flag.
7428 */
7429function picto_from_langcode($codelang, $moreatt = '')
7430{
7431	if (empty($codelang)) return '';
7432
7433	if ($codelang == 'auto')
7434	{
7435		return '<span class="fa fa-globe"></span>';
7436	}
7437
7438	$langtocountryflag = array(
7439		'ar_AR' => '',
7440		'ca_ES' => 'catalonia',
7441		'da_DA' => 'dk',
7442		'fr_CA' => 'mq',
7443		'sv_SV' => 'se',
7444		'sw_SW' => 'unknown',
7445		'AQ' => 'unknown',
7446		'CW' => 'unknown',
7447		'IM' => 'unknown',
7448		'JE' => 'unknown',
7449		'MF' => 'unknown',
7450		'BL' => 'unknown',
7451		'SX' => 'unknown'
7452	);
7453
7454	if (isset($langtocountryflag[$codelang])) $flagImage = $langtocountryflag[$codelang];
7455	else {
7456		$tmparray = explode('_', $codelang);
7457		$flagImage = empty($tmparray[1]) ? $tmparray[0] : $tmparray[1];
7458	}
7459
7460	return img_picto_common($codelang, 'flags/'.strtolower($flagImage).'.png', $moreatt);
7461}
7462
7463/**
7464 * Return default language from country code.
7465 * Return null if not found.
7466 *
7467 * @param 	string 	$countrycode	Country code like 'US', 'FR', 'CA', ...
7468 * @return	string					Value of locale like 'en_US', 'fr_FR', ...
7469 */
7470function getLanguageCodeFromCountryCode($countrycode)
7471{
7472	global $mysoc;
7473
7474	if (empty($countrycode)) return null;
7475
7476	if (strtoupper($countrycode) == 'MQ') return 'fr_CA';
7477	if (strtoupper($countrycode) == 'SE') return 'sv_SE'; // se_SE is Sami/Sweden, and we want in priority sv_SE for SE country
7478	if (strtoupper($countrycode) == 'CH')
7479	{
7480		if ($mysoc->country_code == 'FR') return 'fr_CH';
7481		if ($mysoc->country_code == 'DE') return 'de_CH';
7482	}
7483
7484	// Locale list taken from:
7485	// http://stackoverflow.com/questions/3191664/
7486	// list-of-all-locales-and-their-short-codes
7487	$locales = array(
7488		'af-ZA',
7489		'am-ET',
7490		'ar-AE',
7491		'ar-BH',
7492		'ar-DZ',
7493		'ar-EG',
7494		'ar-IQ',
7495		'ar-JO',
7496		'ar-KW',
7497		'ar-LB',
7498		'ar-LY',
7499		'ar-MA',
7500		'ar-OM',
7501		'ar-QA',
7502		'ar-SA',
7503		'ar-SY',
7504		'ar-TN',
7505		'ar-YE',
7506		'as-IN',
7507		'ba-RU',
7508		'be-BY',
7509		'bg-BG',
7510		'bn-BD',
7511		'bn-IN',
7512		'bo-CN',
7513		'br-FR',
7514		'ca-ES',
7515		'co-FR',
7516		'cs-CZ',
7517		'cy-GB',
7518		'da-DK',
7519		'de-AT',
7520		'de-CH',
7521		'de-DE',
7522		'de-LI',
7523		'de-LU',
7524		'dv-MV',
7525		'el-GR',
7526		'en-AU',
7527		'en-BZ',
7528		'en-CA',
7529		'en-GB',
7530		'en-IE',
7531		'en-IN',
7532		'en-JM',
7533		'en-MY',
7534		'en-NZ',
7535		'en-PH',
7536		'en-SG',
7537		'en-TT',
7538		'en-US',
7539		'en-ZA',
7540		'en-ZW',
7541		'es-AR',
7542		'es-BO',
7543		'es-CL',
7544		'es-CO',
7545		'es-CR',
7546		'es-DO',
7547		'es-EC',
7548		'es-ES',
7549		'es-GT',
7550		'es-HN',
7551		'es-MX',
7552		'es-NI',
7553		'es-PA',
7554		'es-PE',
7555		'es-PR',
7556		'es-PY',
7557		'es-SV',
7558		'es-US',
7559		'es-UY',
7560		'es-VE',
7561		'et-EE',
7562		'eu-ES',
7563		'fa-IR',
7564		'fi-FI',
7565		'fo-FO',
7566		'fr-BE',
7567		'fr-CA',
7568		'fr-CH',
7569		'fr-FR',
7570		'fr-LU',
7571		'fr-MC',
7572		'fy-NL',
7573		'ga-IE',
7574		'gd-GB',
7575		'gl-ES',
7576		'gu-IN',
7577		'he-IL',
7578		'hi-IN',
7579		'hr-BA',
7580		'hr-HR',
7581		'hu-HU',
7582		'hy-AM',
7583		'id-ID',
7584		'ig-NG',
7585		'ii-CN',
7586		'is-IS',
7587		'it-CH',
7588		'it-IT',
7589		'ja-JP',
7590		'ka-GE',
7591		'kk-KZ',
7592		'kl-GL',
7593		'km-KH',
7594		'kn-IN',
7595		'ko-KR',
7596		'ky-KG',
7597		'lb-LU',
7598		'lo-LA',
7599		'lt-LT',
7600		'lv-LV',
7601		'mi-NZ',
7602		'mk-MK',
7603		'ml-IN',
7604		'mn-MN',
7605		'mr-IN',
7606		'ms-BN',
7607		'ms-MY',
7608		'mt-MT',
7609		'nb-NO',
7610		'ne-NP',
7611		'nl-BE',
7612		'nl-NL',
7613		'nn-NO',
7614		'oc-FR',
7615		'or-IN',
7616		'pa-IN',
7617		'pl-PL',
7618		'ps-AF',
7619		'pt-BR',
7620		'pt-PT',
7621		'rm-CH',
7622		'ro-RO',
7623		'ru-RU',
7624		'rw-RW',
7625		'sa-IN',
7626		'se-FI',
7627		'se-NO',
7628		'se-SE',
7629		'si-LK',
7630		'sk-SK',
7631		'sl-SI',
7632		'sq-AL',
7633		'sv-FI',
7634		'sv-SE',
7635		'sw-KE',
7636		'ta-IN',
7637		'te-IN',
7638		'th-TH',
7639		'tk-TM',
7640		'tn-ZA',
7641		'tr-TR',
7642		'tt-RU',
7643		'ug-CN',
7644		'uk-UA',
7645		'ur-PK',
7646		'vi-VN',
7647		'wo-SN',
7648		'xh-ZA',
7649		'yo-NG',
7650		'zh-CN',
7651		'zh-HK',
7652		'zh-MO',
7653		'zh-SG',
7654		'zh-TW',
7655		'zu-ZA',
7656	);
7657
7658	$buildprimarykeytotest = strtolower($countrycode).'-'.strtoupper($countrycode);
7659	if (in_array($buildprimarykeytotest, $locales)) return strtolower($countrycode).'_'.strtoupper($countrycode);
7660
7661	if (function_exists('locale_get_primary_language') && function_exists('locale_get_region'))    // Need extension php-intl
7662	{
7663		foreach ($locales as $locale)
7664		{
7665			$locale_language = locale_get_primary_language($locale);
7666			$locale_region = locale_get_region($locale);
7667			if (strtoupper($countrycode) == $locale_region)
7668			{
7669				//var_dump($locale.'-'.$locale_language.'-'.$locale_region);
7670				return strtolower($locale_language).'_'.strtoupper($locale_region);
7671			}
7672		}
7673	} else {
7674		dol_syslog("Warning Exention php-intl is not available", LOG_WARNING);
7675	}
7676
7677	return null;
7678}
7679
7680/**
7681 *  Complete or removed entries into a head array (used to build tabs).
7682 *  For example, with value added by external modules. Such values are declared into $conf->modules_parts['tab'].
7683 *  Or by change using hook completeTabsHead
7684 *
7685 *  @param	Conf			$conf           Object conf
7686 *  @param  Translate		$langs          Object langs
7687 *  @param  object|null		$object         Object object
7688 *  @param  array			$head          	Object head
7689 *  @param  int				$h				New position to fill
7690 *  @param  string			$type           Value for object where objectvalue can be
7691 *                              			'thirdparty'       to add a tab in third party view
7692 *		                        	      	'intervention'     to add a tab in intervention view
7693 *     		                    	     	'supplier_order'   to add a tab in supplier order view
7694 *          		            	        'supplier_invoice' to add a tab in supplier invoice view
7695 *                  		    	        'invoice'          to add a tab in customer invoice view
7696 *                          			    'order'            to add a tab in customer order view
7697 *                          				'contract'		   to add a tabl in contract view
7698 *                      			        'product'          to add a tab in product view
7699 *                              			'propal'           to add a tab in propal view
7700 *                              			'user'             to add a tab in user view
7701 *                              			'group'            to add a tab in group view
7702 * 		        	               	     	'member'           to add a tab in fundation member view
7703 *      		                        	'categories_x'	   to add a tab in category view ('x': type of category (0=product, 1=supplier, 2=customer, 3=member)
7704 *      									'ecm'			   to add a tab for another ecm view
7705 *                                          'stock'            to add a tab for warehouse view
7706 *  @param  string		$mode  	        	'add' to complete head, 'remove' to remove entries
7707 *	@return	void
7708 */
7709function complete_head_from_modules($conf, $langs, $object, &$head, &$h, $type, $mode = 'add')
7710{
7711	global $hookmanager;
7712
7713	if (isset($conf->modules_parts['tabs'][$type]) && is_array($conf->modules_parts['tabs'][$type]))
7714	{
7715		foreach ($conf->modules_parts['tabs'][$type] as $value)
7716		{
7717			$values = explode(':', $value);
7718
7719			if ($mode == 'add' && !preg_match('/^\-/', $values[1]))
7720			{
7721				if (count($values) == 6)       // new declaration with permissions:  $value='objecttype:+tabname1:Title1:langfile@mymodule:$user->rights->mymodule->read:/mymodule/mynewtab1.php?id=__ID__'
7722				{
7723					if ($values[0] != $type) continue;
7724
7725					if (verifCond($values[4]))
7726					{
7727						if ($values[3]) $langs->load($values[3]);
7728						if (preg_match('/SUBSTITUTION_([^_]+)/i', $values[2], $reg))
7729						{
7730							$substitutionarray = array();
7731							complete_substitutions_array($substitutionarray, $langs, $object, array('needforkey'=>$values[2]));
7732							$label = make_substitutions($reg[1], $substitutionarray);
7733						} else $label = $langs->trans($values[2]);
7734
7735						$head[$h][0] = dol_buildpath(preg_replace('/__ID__/i', ((is_object($object) && !empty($object->id)) ? $object->id : ''), $values[5]), 1);
7736						$head[$h][1] = $label;
7737						$head[$h][2] = str_replace('+', '', $values[1]);
7738						$h++;
7739					}
7740				} elseif (count($values) == 5)       // deprecated
7741				{
7742					dol_syslog('Passing 5 values in tabs module_parts is deprecated. Please update to 6 with permissions.', LOG_WARNING);
7743
7744					if ($values[0] != $type) continue;
7745					if ($values[3]) $langs->load($values[3]);
7746					if (preg_match('/SUBSTITUTION_([^_]+)/i', $values[2], $reg))
7747					{
7748						$substitutionarray = array();
7749						complete_substitutions_array($substitutionarray, $langs, $object, array('needforkey'=>$values[2]));
7750						$label = make_substitutions($reg[1], $substitutionarray);
7751					} else $label = $langs->trans($values[2]);
7752
7753					$head[$h][0] = dol_buildpath(preg_replace('/__ID__/i', ((is_object($object) && !empty($object->id)) ? $object->id : ''), $values[4]), 1);
7754					$head[$h][1] = $label;
7755					$head[$h][2] = str_replace('+', '', $values[1]);
7756					$h++;
7757				}
7758			} elseif ($mode == 'remove' && preg_match('/^\-/', $values[1]))
7759			{
7760				if ($values[0] != $type) continue;
7761				$tabname = str_replace('-', '', $values[1]);
7762				foreach ($head as $key => $val)
7763				{
7764					$condition = (!empty($values[3]) ? verifCond($values[3]) : 1);
7765					//var_dump($key.' - '.$tabname.' - '.$head[$key][2].' - '.$values[3].' - '.$condition);
7766					if ($head[$key][2] == $tabname && $condition)
7767					{
7768						unset($head[$key]);
7769						break;
7770					}
7771				}
7772			}
7773		}
7774	}
7775
7776	// No need to make a return $head. Var is modified as a reference
7777	if (!empty($hookmanager))
7778	{
7779		$parameters = array('object' => $object, 'mode' => $mode, 'head' => $head);
7780		$reshook = $hookmanager->executeHooks('completeTabsHead', $parameters);
7781		if ($reshook > 0) {		// Hook ask to replace completely the array
7782			$head = $hookmanager->resArray;
7783		} else {				// Hook
7784			$head = array_merge($head, $hookmanager->resArray);
7785		}
7786		$h = count($head);
7787	}
7788}
7789
7790/**
7791 * Print common footer :
7792 * 		conf->global->MAIN_HTML_FOOTER
7793 *      js for switch of menu hider
7794 * 		js for conf->global->MAIN_GOOGLE_AN_ID
7795 * 		js for conf->global->MAIN_SHOW_TUNING_INFO or $_SERVER["MAIN_SHOW_TUNING_INFO"]
7796 * 		js for conf->logbuffer
7797 *
7798 * @param	string	$zone	'private' (for private pages) or 'public' (for public pages)
7799 * @return	void
7800 */
7801function printCommonFooter($zone = 'private')
7802{
7803	global $conf, $hookmanager, $user, $debugbar;
7804	global $action;
7805	global $micro_start_time;
7806
7807	if ($zone == 'private') print "\n".'<!-- Common footer for private page -->'."\n";
7808	else print "\n".'<!-- Common footer for public page -->'."\n";
7809
7810	// A div to store page_y POST parameter so we can read it using javascript
7811	print "\n<!-- A div to store page_y POST parameter -->\n";
7812	print '<div id="page_y" style="display: none;">'.(empty($_POST['page_y']) ? '' : $_POST['page_y']).'</div>'."\n";
7813
7814	$parameters = array();
7815	$reshook = $hookmanager->executeHooks('printCommonFooter', $parameters); // Note that $action and $object may have been modified by some hooks
7816	if (empty($reshook))
7817	{
7818		if (!empty($conf->global->MAIN_HTML_FOOTER)) print $conf->global->MAIN_HTML_FOOTER."\n";
7819
7820		print "\n";
7821		if (!empty($conf->use_javascript_ajax))
7822		{
7823			print '<script>'."\n";
7824			print 'jQuery(document).ready(function() {'."\n";
7825
7826			if ($zone == 'private' && empty($conf->dol_use_jmobile))
7827			{
7828				print "\n";
7829				print '/* JS CODE TO ENABLE to manage handler to switch left menu page (menuhider) */'."\n";
7830				print 'jQuery("li.menuhider").click(function(event) {';
7831				print '  if (!$( "body" ).hasClass( "sidebar-collapse" )){ event.preventDefault(); }'."\n";
7832				print '  console.log("We click on .menuhider");'."\n";
7833				print '  $("body").toggleClass("sidebar-collapse")'."\n";
7834				print '});'."\n";
7835			}
7836
7837			// Management of focus and mandatory for fields
7838			if ($action == 'create' || $action == 'edit' || (empty($action) && (preg_match('/new\.php/', $_SERVER["PHP_SELF"]))))
7839			{
7840				print '/* JS CODE TO ENABLE to manage focus and mandatory form fields */'."\n";
7841				$relativepathstring = $_SERVER["PHP_SELF"];
7842				// Clean $relativepathstring
7843				if (constant('DOL_URL_ROOT')) $relativepathstring = preg_replace('/^'.preg_quote(constant('DOL_URL_ROOT'), '/').'/', '', $relativepathstring);
7844				$relativepathstring = preg_replace('/^\//', '', $relativepathstring);
7845				$relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
7846				//$tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
7847				if (!empty($user->default_values[$relativepathstring]['focus']))
7848				{
7849					foreach ($user->default_values[$relativepathstring]['focus'] as $defkey => $defval)
7850					{
7851						$qualified = 0;
7852						if ($defkey != '_noquery_')
7853						{
7854							$tmpqueryarraytohave = explode('&', $defkey);
7855							$foundintru = 0;
7856							foreach ($tmpqueryarraytohave as $tmpquerytohave)
7857							{
7858								$tmpquerytohaveparam = explode('=', $tmpquerytohave);
7859								//print "console.log('".$tmpquerytohaveparam[0]." ".$tmpquerytohaveparam[1]." ".GETPOST($tmpquerytohaveparam[0])."');";
7860								if (!GETPOSTISSET($tmpquerytohaveparam[0]) || ($tmpquerytohaveparam[1] != GETPOST($tmpquerytohaveparam[0]))) $foundintru = 1;
7861							}
7862							if (!$foundintru) $qualified = 1;
7863							//var_dump($defkey.'-'.$qualified);
7864						} else $qualified = 1;
7865
7866						if ($qualified)
7867						{
7868							foreach ($defval as $paramkey => $paramval)
7869							{
7870								// Set focus on field
7871								print 'jQuery("input[name=\''.$paramkey.'\']").focus();'."\n";
7872								print 'jQuery("textarea[name=\''.$paramkey.'\']").focus();'."\n";
7873								print 'jQuery("select[name=\''.$paramkey.'\']").focus();'."\n"; // Not really usefull, but we keep it in case of.
7874							}
7875						}
7876					}
7877				}
7878				if (!empty($user->default_values[$relativepathstring]['mandatory']))
7879				{
7880					foreach ($user->default_values[$relativepathstring]['mandatory'] as $defkey => $defval)
7881					{
7882						$qualified = 0;
7883						if ($defkey != '_noquery_')
7884						{
7885							$tmpqueryarraytohave = explode('&', $defkey);
7886							$foundintru = 0;
7887							foreach ($tmpqueryarraytohave as $tmpquerytohave)
7888							{
7889								$tmpquerytohaveparam = explode('=', $tmpquerytohave);
7890								//print "console.log('".$tmpquerytohaveparam[0]." ".$tmpquerytohaveparam[1]." ".GETPOST($tmpquerytohaveparam[0])."');";
7891								if (!GETPOSTISSET($tmpquerytohaveparam[0]) || ($tmpquerytohaveparam[1] != GETPOST($tmpquerytohaveparam[0]))) $foundintru = 1;
7892							}
7893							if (!$foundintru) $qualified = 1;
7894							//var_dump($defkey.'-'.$qualified);
7895						} else $qualified = 1;
7896
7897						if ($qualified)
7898						{
7899							foreach ($defval as $paramkey => $paramval)
7900							{
7901								// Add property 'required' on input
7902								print 'jQuery("input[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";
7903								print 'jQuery("textarea[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";
7904								print 'jQuery("select[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n"; // required on a select works only if key is "", this does not happen in Dolibarr
7905							}
7906						}
7907					}
7908				}
7909			}
7910
7911			print '});'."\n";
7912
7913			// End of tuning
7914			if (!empty($_SERVER['MAIN_SHOW_TUNING_INFO']) || !empty($conf->global->MAIN_SHOW_TUNING_INFO))
7915			{
7916				print "\n";
7917				print "/* JS CODE TO ENABLE to add memory info */\n";
7918				print 'window.console && console.log("';
7919				if (!empty($conf->global->MEMCACHED_SERVER)) print 'MEMCACHED_SERVER='.$conf->global->MEMCACHED_SERVER.' - ';
7920				print 'MAIN_OPTIMIZE_SPEED='.(isset($conf->global->MAIN_OPTIMIZE_SPEED) ? $conf->global->MAIN_OPTIMIZE_SPEED : 'off');
7921				if (!empty($micro_start_time))   // Works only if MAIN_SHOW_TUNING_INFO is defined at $_SERVER level. Not in global variable.
7922				{
7923					$micro_end_time = microtime(true);
7924					print ' - Build time: '.ceil(1000 * ($micro_end_time - $micro_start_time)).' ms';
7925				}
7926
7927				if (function_exists("memory_get_usage")) {
7928					print ' - Mem: '.memory_get_usage(); // Do not use true here, it seems it takes the peak amount
7929				}
7930				if (function_exists("memory_get_peak_usage")) {
7931					print ' - Real mem peak: '.memory_get_peak_usage(true);
7932				}
7933				if (function_exists("zend_loader_file_encoded"))
7934				{
7935					print ' - Zend encoded file: '.(zend_loader_file_encoded() ? 'yes' : 'no');
7936				}
7937				print '");'."\n";
7938			}
7939
7940			print "\n".'</script>'."\n";
7941
7942			// Google Analytics
7943			// TODO Add a hook here
7944			if (!empty($conf->google->enabled) && !empty($conf->global->MAIN_GOOGLE_AN_ID))
7945			{
7946				$tmptagarray = explode(',', $conf->global->MAIN_GOOGLE_AN_ID);
7947				foreach ($tmptagarray as $tmptag) {
7948					print "\n";
7949					print "<!-- JS CODE TO ENABLE for google analtics tag -->\n";
7950					print "
7951					<!-- Global site tag (gtag.js) - Google Analytics -->
7952					<script async src=\"https://www.googletagmanager.com/gtag/js?id=".trim($tmptag)."\"></script>
7953					<script>
7954					window.dataLayer = window.dataLayer || [];
7955					function gtag(){dataLayer.push(arguments);}
7956					gtag('js', new Date());
7957
7958					gtag('config', '".trim($tmptag)."');
7959					</script>";
7960					print "\n";
7961				}
7962			}
7963		}
7964
7965		// Add Xdebug coverage of code
7966		if (defined('XDEBUGCOVERAGE'))
7967		{
7968			print_r(xdebug_get_code_coverage());
7969		}
7970
7971		// Add DebugBar data
7972		if (!empty($user->rights->debugbar->read) && is_object($debugbar))
7973		{
7974			$debugbar['time']->stopMeasure('pageaftermaster');
7975			print '<!-- Output debugbar data -->'."\n";
7976			$renderer = $debugbar->getRenderer();
7977			print $debugbar->getRenderer()->render();
7978		} elseif (count($conf->logbuffer))    // If there is some logs in buffer to show
7979		{
7980			print "\n";
7981			print "<!-- Start of log output\n";
7982			//print '<div class="hidden">'."\n";
7983			foreach ($conf->logbuffer as $logline)
7984			{
7985				print $logline."<br>\n";
7986			}
7987			//print '</div>'."\n";
7988			print "End of log output -->\n";
7989		}
7990	}
7991}
7992
7993/**
7994 * Split a string with 2 keys into key array.
7995 * For example: "A=1;B=2;C=2" is exploded into array('A'=>1,'B'=>2,'C'=>3)
7996 *
7997 * @param 	string	$string		String to explode
7998 * @param 	string	$delimiter	Delimiter between each couple of data
7999 * @param 	string	$kv			Delimiter between key and value
8000 * @return	array				Array of data exploded
8001 */
8002function dolExplodeIntoArray($string, $delimiter = ';', $kv = '=')
8003{
8004	if ($a = explode($delimiter, $string))
8005	{
8006		$ka = array();
8007		foreach ($a as $s) { // each part
8008			if ($s) {
8009				if ($pos = strpos($s, $kv)) { // key/value delimiter
8010					$ka[trim(substr($s, 0, $pos))] = trim(substr($s, $pos + strlen($kv)));
8011				} else { // key delimiter not found
8012					$ka[] = trim($s);
8013				}
8014			}
8015		}
8016		return $ka;
8017	}
8018	return array();
8019}
8020
8021
8022/**
8023 * Set focus onto field with selector (similar behaviour of 'autofocus' HTML5 tag)
8024 *
8025 * @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.
8026 * @return	string				HTML code to set focus
8027 */
8028function dol_set_focus($selector)
8029{
8030	print "\n".'<!-- Set focus onto a specific field -->'."\n";
8031	print '<script>jQuery(document).ready(function() { jQuery("'.dol_escape_js($selector).'").focus(); });</script>'."\n";
8032}
8033
8034
8035/**
8036 * Return getmypid() or random PID when function is disabled
8037 * Some web hosts disable this php function for security reasons
8038 * and sometimes we can't redeclare function
8039 *
8040 * @return	int
8041 */
8042function dol_getmypid()
8043{
8044	if (!function_exists('getmypid')) {
8045		return mt_rand(1, 32768);
8046	} else {
8047		return getmypid();
8048	}
8049}
8050
8051
8052/**
8053 * Generate natural SQL search string for a criteria (this criteria can be tested on one or several fields)
8054 *
8055 * @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")
8056 * @param   string 			$value 		The value to look for.
8057 *                          		    If param $mode is 0, can contains several keywords separated with a space or |
8058 *                                      like "keyword1 keyword2" = We want record field like keyword1 AND field like keyword2
8059 *                                      or like "keyword1|keyword2" = We want record field like keyword1 OR field like keyword2
8060 *                             			If param $mode is 1, can contains an operator <, > or = like "<10" or ">=100.5 < 1000"
8061 *                             			If param $mode is 2, can contains a list of int id separated by comma like "1,3,4"
8062 *                             			If param $mode is 3, can contains a list of string separated by comma like "a,b,c"
8063 * @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')
8064 * 										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'
8065 * @param	integer			$nofirstand	1=Do not output the first 'AND'
8066 * @return 	string 			$res 		The statement to append to the SQL query
8067 */
8068function natural_search($fields, $value, $mode = 0, $nofirstand = 0)
8069{
8070	global $db, $langs;
8071
8072	$value = trim($value);
8073
8074	if ($mode == 0)
8075	{
8076		$value = preg_replace('/\*/', '%', $value); // Replace * with %
8077	}
8078	if ($mode == 1)
8079	{
8080		$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
8081	}
8082
8083	$value = preg_replace('/\s*\|\s*/', '|', $value);
8084
8085	$crits = explode(' ', $value);
8086	$res = '';
8087	if (!is_array($fields)) $fields = array($fields);
8088
8089	$j = 0;
8090	foreach ($crits as $crit)
8091	{
8092		$crit = trim($crit);
8093		$i = 0; $i2 = 0;
8094		$newres = '';
8095		foreach ($fields as $field)
8096		{
8097			if ($mode == 1)
8098			{
8099				$operator = '=';
8100				$newcrit = preg_replace('/([<>=]+)/', '', $crit);
8101
8102				$reg = array();
8103				preg_match('/([<>=]+)/', $crit, $reg);
8104				if ($reg[1])
8105				{
8106					$operator = $reg[1];
8107				}
8108				if ($newcrit != '')
8109				{
8110					$numnewcrit = price2num($newcrit);
8111					if (is_numeric($numnewcrit))
8112					{
8113						$newres .= ($i2 > 0 ? ' OR ' : '').$field.' '.$operator.' '.$db->sanitize($numnewcrit); // should be a numeric
8114					} else {
8115						$newres .= ($i2 > 0 ? ' OR ' : '').'1 = 2'; // force false
8116					}
8117					$i2++; // a criteria was added to string
8118				}
8119			} elseif ($mode == 2 || $mode == -2)
8120			{
8121				$crit = preg_replace('/[^0-9,]/', '', $crit); // ID are always integer
8122				$newres .= ($i2 > 0 ? ' OR ' : '').$field." ".($mode == -2 ? 'NOT ' : '');
8123				$newres .= $crit ? "IN (".$db->sanitize($db->escape($crit)).")" : "IN (0)";
8124				if ($mode == -2) $newres .= ' OR '.$field.' IS NULL';
8125				$i2++; // a criteria was added to string
8126			} elseif ($mode == 3 || $mode == -3)
8127			{
8128				$tmparray = explode(',', $crit);
8129				if (count($tmparray))
8130				{
8131					$listofcodes = '';
8132					foreach ($tmparray as $val)
8133					{
8134						$val = trim($val);
8135						if ($val)
8136						{
8137							$listofcodes .= ($listofcodes ? ',' : '');
8138							$listofcodes .= "'".$db->escape($val)."'";
8139						}
8140					}
8141					$newres .= ($i2 > 0 ? ' OR ' : '').$field." ".($mode == -3 ? 'NOT ' : '')."IN (".$db->sanitize($listofcodes, 1).")";
8142					$i2++; // a criteria was added to string
8143				}
8144				if ($mode == -3) $newres .= ' OR '.$field.' IS NULL';
8145			} elseif ($mode == 4)
8146			{
8147				$tmparray = explode(',', $crit);
8148				if (count($tmparray))
8149				{
8150					$listofcodes = '';
8151					foreach ($tmparray as $val)
8152					{
8153						$val = trim($val);
8154						if ($val)
8155						{
8156							$newres .= ($i2 > 0 ? ' OR (' : '(').$field.' LIKE \''.$db->escape($val).',%\'';
8157							$newres .= ' OR '.$field.' = \''.$db->escape($val).'\'';
8158							$newres .= ' OR '.$field.' LIKE \'%,'.$db->escape($val).'\'';
8159							$newres .= ' OR '.$field.' LIKE \'%,'.$db->escape($val).',%\'';
8160							$newres .= ')';
8161							$i2++;
8162						}
8163					}
8164				}
8165			} else // $mode=0
8166			{
8167				$tmpcrits = explode('|', $crit);
8168				$i3 = 0;
8169				foreach ($tmpcrits as $tmpcrit)
8170				{
8171					if ($tmpcrit !== '0' && empty($tmpcrit)) continue;
8172
8173					$newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : '');
8174
8175					if (preg_match('/\.(id|rowid)$/', $field))	// Special case for rowid that is sometimes a ref so used as a search field
8176					{
8177						$newres .= $field." = ".(is_numeric(trim($tmpcrit)) ?trim($tmpcrit) : '0');
8178					} else {
8179						$newres .= $field." LIKE '";
8180
8181						$tmpcrit = trim($tmpcrit);
8182						$tmpcrit2 = $tmpcrit;
8183						$tmpbefore = '%'; $tmpafter = '%';
8184						if (preg_match('/^[\^\$]/', $tmpcrit))
8185						{
8186							$tmpbefore = '';
8187							$tmpcrit2 = preg_replace('/^[\^\$]/', '', $tmpcrit2);
8188						}
8189						if (preg_match('/[\^\$]$/', $tmpcrit))
8190						{
8191							$tmpafter = '';
8192							$tmpcrit2 = preg_replace('/[\^\$]$/', '', $tmpcrit2);
8193						}
8194						$newres .= $tmpbefore;
8195						$newres .= $db->escape($tmpcrit2);
8196						$newres .= $tmpafter;
8197						$newres .= "'";
8198						if ($tmpcrit2 == '')
8199						{
8200							$newres .= ' OR '.$field." IS NULL";
8201						}
8202					}
8203
8204					$i3++;
8205				}
8206				$i2++; // a criteria was added to string
8207			}
8208			$i++;
8209		}
8210		if ($newres) $res = $res.($res ? ' AND ' : '').($i2 > 1 ? '(' : '').$newres.($i2 > 1 ? ')' : '');
8211		$j++;
8212	}
8213	$res = ($nofirstand ? "" : " AND ")."(".$res.")";
8214	//print 'xx'.$res.'yy';
8215	return $res;
8216}
8217
8218/**
8219 * Return string with full Url. The file qualified is the one defined by relative path in $object->last_main_doc
8220 *
8221 * @param   Object	$object				Object
8222 * @return	string						Url string
8223 */
8224function showDirectDownloadLink($object)
8225{
8226	global $conf, $langs;
8227
8228	$out = '';
8229	$url = $object->getLastMainDocLink($object->element);
8230
8231	if ($url)
8232	{
8233		$out .= img_picto('', 'globe').' '.$langs->trans("DirectDownloadLink").'<br>';
8234		$out .= '<input type="text" id="directdownloadlink" class="quatrevingtpercent" value="'.$url.'">';
8235		$out .= ajax_autoselect("directdownloadlink", 0);
8236	}
8237	return $out;
8238}
8239
8240/**
8241 * Return the filename of file to get the thumbs
8242 *
8243 * @param   string  $file           Original filename (full or relative path)
8244 * @param   string  $extName        Extension to differenciate thumb file name ('', '_small', '_mini')
8245 * @param   string  $extImgTarget   Force image extension for thumbs. Use '' to keep same extension than original image (default).
8246 * @return  string                  New file name (full or relative path, including the thumbs/)
8247 */
8248function getImageFileNameForSize($file, $extName, $extImgTarget = '')
8249{
8250	$dirName = dirname($file);
8251	if ($dirName == '.') $dirName = '';
8252
8253	$fileName = preg_replace('/(\.gif|\.jpeg|\.jpg|\.png|\.bmp|\.webp)$/i', '', $file); // We remove extension, whatever is its case
8254	$fileName = basename($fileName);
8255
8256	if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.jpg$/i', $file) ? '.jpg' : '');
8257	if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.jpeg$/i', $file) ? '.jpeg' : '');
8258	if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.gif$/i', $file) ? '.gif' : '');
8259	if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.png$/i', $file) ? '.png' : '');
8260	if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.bmp$/i', $file) ? '.bmp' : '');
8261	if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.webp$/i', $file) ? '.webp' : '');
8262
8263	if (!$extImgTarget) return $file;
8264
8265	$subdir = '';
8266	if ($extName) $subdir = 'thumbs/';
8267
8268	return ($dirName ? $dirName.'/' : '').$subdir.$fileName.$extName.$extImgTarget; // New filename for thumb
8269}
8270
8271
8272/**
8273 * Return URL we can use for advanced preview links
8274 *
8275 * @param   string    $modulepart     propal, facture, facture_fourn, ...
8276 * @param   string    $relativepath   Relative path of docs.
8277 * @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)
8278 * @param	string	  $param		  More param on http links
8279 * @return  string|array              Output string with href link or array with all components of link
8280 */
8281function getAdvancedPreviewUrl($modulepart, $relativepath, $alldata = 0, $param = '')
8282{
8283	global $conf, $langs;
8284
8285	if (empty($conf->use_javascript_ajax)) return '';
8286
8287	$isAllowedForPreview = dolIsAllowedForPreview($relativepath);
8288
8289	if ($alldata == 1)
8290	{
8291		if ($isAllowedForPreview) 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));
8292		else return array();
8293	}
8294
8295	// old behavior, return a string
8296	if ($isAllowedForPreview) 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')).'\')';
8297	else return '';
8298}
8299
8300
8301/**
8302 * Make content of an input box selected when we click into input field.
8303 *
8304 * @param string	$htmlname	Id of html object ('#idvalue' or '.classvalue')
8305 * @param string	$addlink	Add a 'link to' after
8306 * @return string
8307 */
8308function ajax_autoselect($htmlname, $addlink = '')
8309{
8310	global $langs;
8311	$out = '<script>
8312               jQuery(document).ready(function () {
8313				    jQuery("'.((strpos($htmlname, '.') === 0 ? '' : '#').$htmlname).'").click(function() { jQuery(this).select(); } );
8314				});
8315		    </script>';
8316	if ($addlink) $out .= ' <a href="'.$addlink.'" target="_blank">'.$langs->trans("Link").'</a>';
8317	return $out;
8318}
8319
8320/**
8321 *	Return if a file is qualified for preview
8322 *
8323 *	@param	string	$file		Filename we looking for information
8324 *	@return int					1 If allowed, 0 otherwise
8325 *  @see    dol_mimetype(), image_format_supported() from images.lib.php
8326 */
8327function dolIsAllowedForPreview($file)
8328{
8329	global $conf;
8330
8331	// Check .noexe extension in filename
8332	if (preg_match('/\.noexe$/i', $file)) return 0;
8333
8334	// Check mime types
8335	$mime_preview = array('bmp', 'jpeg', 'png', 'gif', 'tiff', 'pdf', 'plain', 'css', 'webp');
8336	if (!empty($conf->global->MAIN_ALLOW_SVG_FILES_AS_IMAGES)) $mime_preview[] = 'svg+xml';
8337	//$mime_preview[]='vnd.oasis.opendocument.presentation';
8338	//$mime_preview[]='archive';
8339	$num_mime = array_search(dol_mimetype($file, '', 1), $mime_preview);
8340	if ($num_mime !== false) return 1;
8341
8342	// By default, not allowed for preview
8343	return 0;
8344}
8345
8346
8347/**
8348 *	Return mime type of a file
8349 *
8350 *	@param	string	$file		Filename we looking for MIME type
8351 *  @param  string	$default    Default mime type if extension not found in known list
8352 * 	@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
8353 *	@return string 		    	Return a mime type family (text/xxx, application/xxx, image/xxx, audio, video, archive)
8354 *  @see    dolIsAllowedForPreview(), image_format_supported() from images.lib.php
8355 */
8356function dol_mimetype($file, $default = 'application/octet-stream', $mode = 0)
8357{
8358	$mime = $default;
8359	$imgmime = 'other.png';
8360	$famime = 'file-o';
8361	$srclang = '';
8362
8363	$tmpfile = preg_replace('/\.noexe$/', '', $file);
8364
8365	// Plain text files
8366	if (preg_match('/\.txt$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $famime = 'file-text-o'; }
8367	if (preg_match('/\.rtx$/i', $tmpfile)) { $mime = 'text/richtext'; $imgmime = 'text.png'; $famime = 'file-text-o'; }
8368	if (preg_match('/\.csv$/i', $tmpfile)) { $mime = 'text/csv'; $imgmime = 'text.png'; $famime = 'file-text-o'; }
8369	if (preg_match('/\.tsv$/i', $tmpfile)) { $mime = 'text/tab-separated-values'; $imgmime = 'text.png'; $famime = 'file-text-o'; }
8370	if (preg_match('/\.(cf|conf|log)$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $famime = 'file-text-o'; }
8371	if (preg_match('/\.ini$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $srclang = 'ini'; $famime = 'file-text-o'; }
8372	if (preg_match('/\.md$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $srclang = 'md'; $famime = 'file-text-o'; }
8373	if (preg_match('/\.css$/i', $tmpfile)) { $mime = 'text/css'; $imgmime = 'css.png'; $srclang = 'css'; $famime = 'file-text-o'; }
8374	if (preg_match('/\.lang$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $srclang = 'lang'; $famime = 'file-text-o'; }
8375	// Certificate files
8376	if (preg_match('/\.(crt|cer|key|pub)$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $famime = 'file-text-o'; }
8377	// XML based (HTML/XML/XAML)
8378	if (preg_match('/\.(html|htm|shtml)$/i', $tmpfile)) { $mime = 'text/html'; $imgmime = 'html.png'; $srclang = 'html'; $famime = 'file-text-o'; }
8379	if (preg_match('/\.(xml|xhtml)$/i', $tmpfile)) { $mime = 'text/xml'; $imgmime = 'other.png'; $srclang = 'xml'; $famime = 'file-text-o'; }
8380	if (preg_match('/\.xaml$/i', $tmpfile)) { $mime = 'text/xml'; $imgmime = 'other.png'; $srclang = 'xaml'; $famime = 'file-text-o'; }
8381	// Languages
8382	if (preg_match('/\.bas$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $srclang = 'bas'; $famime = 'file-code-o'; }
8383	if (preg_match('/\.(c)$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $srclang = 'c'; $famime = 'file-code-o'; }
8384	if (preg_match('/\.(cpp)$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $srclang = 'cpp'; $famime = 'file-code-o'; }
8385	if (preg_match('/\.cs$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $srclang = 'cs'; $famime = 'file-code-o'; }
8386	if (preg_match('/\.(h)$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $srclang = 'h'; $famime = 'file-code-o'; }
8387	if (preg_match('/\.(java|jsp)$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $srclang = 'java'; $famime = 'file-code-o'; }
8388	if (preg_match('/\.php([0-9]{1})?$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'php.png'; $srclang = 'php'; $famime = 'file-code-o'; }
8389	if (preg_match('/\.phtml$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'php.png'; $srclang = 'php'; $famime = 'file-code-o'; }
8390	if (preg_match('/\.(pl|pm)$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'pl.png'; $srclang = 'perl'; $famime = 'file-code-o'; }
8391	if (preg_match('/\.sql$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $srclang = 'sql'; $famime = 'file-code-o'; }
8392	if (preg_match('/\.js$/i', $tmpfile)) { $mime = 'text/x-javascript'; $imgmime = 'jscript.png'; $srclang = 'js'; $famime = 'file-code-o'; }
8393	// Open office
8394	if (preg_match('/\.odp$/i', $tmpfile)) { $mime = 'application/vnd.oasis.opendocument.presentation'; $imgmime = 'ooffice.png'; $famime = 'file-powerpoint-o'; }
8395	if (preg_match('/\.ods$/i', $tmpfile)) { $mime = 'application/vnd.oasis.opendocument.spreadsheet'; $imgmime = 'ooffice.png'; $famime = 'file-excel-o'; }
8396	if (preg_match('/\.odt$/i', $tmpfile)) { $mime = 'application/vnd.oasis.opendocument.text'; $imgmime = 'ooffice.png'; $famime = 'file-word-o'; }
8397	// MS Office
8398	if (preg_match('/\.mdb$/i', $tmpfile)) { $mime = 'application/msaccess'; $imgmime = 'mdb.png'; $famime = 'file-o'; }
8399	if (preg_match('/\.doc(x|m)?$/i', $tmpfile)) { $mime = 'application/msword'; $imgmime = 'doc.png'; $famime = 'file-word-o'; }
8400	if (preg_match('/\.dot(x|m)?$/i', $tmpfile)) { $mime = 'application/msword'; $imgmime = 'doc.png'; $famime = 'file-word-o'; }
8401	if (preg_match('/\.xlt(x)?$/i', $tmpfile)) { $mime = 'application/vnd.ms-excel'; $imgmime = 'xls.png'; $famime = 'file-excel-o'; }
8402	if (preg_match('/\.xla(m)?$/i', $tmpfile)) { $mime = 'application/vnd.ms-excel'; $imgmime = 'xls.png'; $famime = 'file-excel-o'; }
8403	if (preg_match('/\.xls$/i', $tmpfile)) { $mime = 'application/vnd.ms-excel'; $imgmime = 'xls.png'; $famime = 'file-excel-o'; }
8404	if (preg_match('/\.xls(b|m|x)$/i', $tmpfile)) { $mime = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; $imgmime = 'xls.png'; $famime = 'file-excel-o'; }
8405	if (preg_match('/\.pps(m|x)?$/i', $tmpfile)) { $mime = 'application/vnd.ms-powerpoint'; $imgmime = 'ppt.png'; $famime = 'file-powerpoint-o'; }
8406	if (preg_match('/\.ppt(m|x)?$/i', $tmpfile)) { $mime = 'application/x-mspowerpoint'; $imgmime = 'ppt.png'; $famime = 'file-powerpoint-o'; }
8407	// Other
8408	if (preg_match('/\.pdf$/i', $tmpfile)) { $mime = 'application/pdf'; $imgmime = 'pdf.png'; $famime = 'file-pdf-o'; }
8409	// Scripts
8410	if (preg_match('/\.bat$/i', $tmpfile)) { $mime = 'text/x-bat'; $imgmime = 'script.png'; $srclang = 'dos'; $famime = 'file-code-o'; }
8411	if (preg_match('/\.sh$/i', $tmpfile)) { $mime = 'text/x-sh'; $imgmime = 'script.png'; $srclang = 'bash'; $famime = 'file-code-o'; }
8412	if (preg_match('/\.ksh$/i', $tmpfile)) { $mime = 'text/x-ksh'; $imgmime = 'script.png'; $srclang = 'bash'; $famime = 'file-code-o'; }
8413	if (preg_match('/\.bash$/i', $tmpfile)) { $mime = 'text/x-bash'; $imgmime = 'script.png'; $srclang = 'bash'; $famime = 'file-code-o'; }
8414	// Images
8415	if (preg_match('/\.ico$/i', $tmpfile)) { $mime = 'image/x-icon'; $imgmime = 'image.png'; $famime = 'file-image-o'; }
8416	if (preg_match('/\.(jpg|jpeg)$/i', $tmpfile)) { $mime = 'image/jpeg'; $imgmime = 'image.png'; $famime = 'file-image-o'; }
8417	if (preg_match('/\.png$/i', $tmpfile)) { $mime = 'image/png'; $imgmime = 'image.png'; $famime = 'file-image-o'; }
8418	if (preg_match('/\.gif$/i', $tmpfile)) { $mime = 'image/gif'; $imgmime = 'image.png'; $famime = 'file-image-o'; }
8419	if (preg_match('/\.bmp$/i', $tmpfile)) { $mime = 'image/bmp'; $imgmime = 'image.png'; $famime = 'file-image-o'; }
8420	if (preg_match('/\.(tif|tiff)$/i', $tmpfile)) { $mime = 'image/tiff'; $imgmime = 'image.png'; $famime = 'file-image-o'; }
8421	if (preg_match('/\.svg$/i', $tmpfile)) { $mime = 'image/svg+xml'; $imgmime = 'image.png'; $famime = 'file-image-o'; }
8422	if (preg_match('/\.webp$/i', $tmpfile)) { $mime = 'image/webp'; $imgmime = 'image.png'; $famime = 'file-image-o'; }
8423	// Calendar
8424	if (preg_match('/\.vcs$/i', $tmpfile)) { $mime = 'text/calendar'; $imgmime = 'other.png'; $famime = 'file-text-o'; }
8425	if (preg_match('/\.ics$/i', $tmpfile)) { $mime = 'text/calendar'; $imgmime = 'other.png'; $famime = 'file-text-o'; }
8426	// Other
8427	if (preg_match('/\.torrent$/i', $tmpfile)) { $mime = 'application/x-bittorrent'; $imgmime = 'other.png'; $famime = 'file-o'; }
8428	// Audio
8429	if (preg_match('/\.(mp3|ogg|au|wav|wma|mid)$/i', $tmpfile)) { $mime = 'audio'; $imgmime = 'audio.png'; $famime = 'file-audio-o'; }
8430	// Video
8431	if (preg_match('/\.mp4$/i', $tmpfile)) { $mime = 'video/mp4'; $imgmime = 'video.png'; $famime = 'file-video-o'; }
8432	if (preg_match('/\.ogv$/i', $tmpfile)) { $mime = 'video/ogg'; $imgmime = 'video.png'; $famime = 'file-video-o'; }
8433	if (preg_match('/\.webm$/i', $tmpfile)) { $mime = 'video/webm'; $imgmime = 'video.png'; $famime = 'file-video-o'; }
8434	if (preg_match('/\.avi$/i', $tmpfile)) { $mime = 'video/x-msvideo'; $imgmime = 'video.png'; $famime = 'file-video-o'; }
8435	if (preg_match('/\.divx$/i', $tmpfile)) { $mime = 'video/divx'; $imgmime = 'video.png'; $famime = 'file-video-o'; }
8436	if (preg_match('/\.xvid$/i', $tmpfile)) { $mime = 'video/xvid'; $imgmime = 'video.png'; $famime = 'file-video-o'; }
8437	if (preg_match('/\.(wmv|mpg|mpeg)$/i', $tmpfile)) { $mime = 'video'; $imgmime = 'video.png'; $famime = 'file-video-o'; }
8438	// Archive
8439	if (preg_match('/\.(zip|rar|gz|tgz|z|cab|bz2|7z|tar|lzh)$/i', $tmpfile)) { $mime = 'archive'; $imgmime = 'archive.png'; $famime = 'file-archive-o'; }    // application/xxx where zzz is zip, ...
8440	// Exe
8441	if (preg_match('/\.(exe|com)$/i', $tmpfile)) { $mime = 'application/octet-stream'; $imgmime = 'other.png'; $famime = 'file-o'; }
8442	// Lib
8443	if (preg_match('/\.(dll|lib|o|so|a)$/i', $tmpfile)) { $mime = 'library'; $imgmime = 'library.png'; $famime = 'file-o'; }
8444	// Err
8445	if (preg_match('/\.err$/i', $tmpfile)) { $mime = 'error'; $imgmime = 'error.png'; $famime = 'file-text-o'; }
8446
8447	// Return string
8448	if ($mode == 1)
8449	{
8450		$tmp = explode('/', $mime);
8451		return (!empty($tmp[1]) ? $tmp[1] : $tmp[0]);
8452	}
8453	if ($mode == 2)
8454	{
8455		return $imgmime;
8456	}
8457	if ($mode == 3)
8458	{
8459		return $srclang;
8460	}
8461	if ($mode == 4)
8462	{
8463		return $famime;
8464	}
8465	return $mime;
8466}
8467
8468/**
8469 * Return value from dictionary
8470 *
8471 * @param string	$tablename		name of dictionary
8472 * @param string	$field			the value to return
8473 * @param int		$id				id of line
8474 * @param bool		$checkentity	add filter on entity
8475 * @param string	$rowidfield		name of the column rowid
8476 * @return string
8477 */
8478function getDictvalue($tablename, $field, $id, $checkentity = false, $rowidfield = 'rowid')
8479{
8480	global $dictvalues, $db, $langs;
8481
8482	if (!isset($dictvalues[$tablename]))
8483	{
8484		$dictvalues[$tablename] = array();
8485		$sql = 'SELECT * FROM '.$tablename.' WHERE 1 = 1'; // Here select * is allowed as it is generic code and we don't have list of fields
8486		if ($checkentity) $sql .= ' AND entity IN (0,'.getEntity($tablename).')';
8487
8488		$resql = $db->query($sql);
8489		if ($resql)
8490		{
8491			while ($obj = $db->fetch_object($resql))
8492			{
8493				$dictvalues[$tablename][$obj->{$rowidfield}] = $obj;
8494			}
8495		} else {
8496			dol_print_error($db);
8497		}
8498	}
8499
8500	if (!empty($dictvalues[$tablename][$id])) return $dictvalues[$tablename][$id]->{$field}; // Found
8501	else // Not found
8502	{
8503		if ($id > 0) return $id;
8504		return '';
8505	}
8506}
8507
8508/**
8509 *	Return true if the color is light
8510 *
8511 *  @param	string	$stringcolor		String with hex (FFFFFF) or comma RGB ('255,255,255')
8512 *  @return	int							-1 : Error with argument passed |0 : color is dark | 1 : color is light
8513 */
8514function colorIsLight($stringcolor)
8515{
8516	$stringcolor = str_replace('#', '', $stringcolor);
8517	$res = -1;
8518	if (!empty($stringcolor))
8519	{
8520		$res = 0;
8521		$tmp = explode(',', $stringcolor);
8522		if (count($tmp) > 1)   // This is a comma RGB ('255','255','255')
8523		{
8524			$r = $tmp[0];
8525			$g = $tmp[1];
8526			$b = $tmp[2];
8527		} else {
8528			$hexr = $stringcolor[0].$stringcolor[1];
8529			$hexg = $stringcolor[2].$stringcolor[3];
8530			$hexb = $stringcolor[4].$stringcolor[5];
8531			$r = hexdec($hexr);
8532			$g = hexdec($hexg);
8533			$b = hexdec($hexb);
8534		}
8535		$bright = (max($r, $g, $b) + min($r, $g, $b)) / 510.0; // HSL algorithm
8536		if ($bright > 0.6) $res = 1;
8537	}
8538	return $res;
8539}
8540
8541/**
8542 * Function to test if an entry is enabled or not
8543 *
8544 * @param	string		$type_user					0=We test for internal user, 1=We test for external user
8545 * @param	array		$menuentry					Array for feature entry to test
8546 * @param	array		$listofmodulesforexternal	Array with list of modules allowed to external users
8547 * @return	int										0=Hide, 1=Show, 2=Show gray
8548 */
8549function isVisibleToUserType($type_user, &$menuentry, &$listofmodulesforexternal)
8550{
8551	global $conf;
8552
8553	//print 'type_user='.$type_user.' module='.$menuentry['module'].' enabled='.$menuentry['enabled'].' perms='.$menuentry['perms'];
8554	//print 'ok='.in_array($menuentry['module'], $listofmodulesforexternal);
8555	if (empty($menuentry['enabled'])) return 0; // Entry disabled by condition
8556	if ($type_user && $menuentry['module'])
8557	{
8558		$tmploops = explode('|', $menuentry['module']);
8559		$found = 0;
8560		foreach ($tmploops as $tmploop)
8561		{
8562			if (in_array($tmploop, $listofmodulesforexternal)) {
8563				$found++; break;
8564			}
8565		}
8566		if (!$found) return 0; // Entry is for menus all excluded to external users
8567	}
8568	if (!$menuentry['perms'] && $type_user) return 0; // No permissions and user is external
8569	if (!$menuentry['perms'] && !empty($conf->global->MAIN_MENU_HIDE_UNAUTHORIZED))	return 0; // No permissions and option to hide when not allowed, even for internal user, is on
8570	if (!$menuentry['perms']) return 2; // No permissions and user is external
8571	return 1;
8572}
8573
8574/**
8575 * Round to next multiple.
8576 *
8577 * @param 	double		$n		Number to round up
8578 * @param 	integer		$x		Multiple. For example 60 to round up to nearest exact minute for a date with seconds.
8579 * @return 	integer				Value rounded.
8580 */
8581function roundUpToNextMultiple($n, $x = 5)
8582{
8583	return (ceil($n) % $x === 0) ? ceil($n) : round(($n + $x / 2) / $x) * $x;
8584}
8585
8586/**
8587 * Function dolGetBadge
8588 *
8589 * @param   string  $label      label of badge no html : use in alt attribute for accessibility
8590 * @param   string  $html       optional : label of badge with html
8591 * @param   string  $type       type of badge : Primary Secondary Success Danger Warning Info Light Dark status0 status1 status2 status3 status4 status5 status6 status7 status8 status9
8592 * @param   string  $mode       default '' , 'pill', 'dot'
8593 * @param   string  $url        the url for link
8594 * @param   array   $params     various params for future : recommended rather than adding more fuction arguments. array('attr'=>array('title'=>'abc'))
8595 * @return  string              Html badge
8596 */
8597function dolGetBadge($label, $html = '', $type = 'primary', $mode = '', $url = '', $params = array())
8598{
8599	$attr = array(
8600		'class'=>'badge '.(!empty($mode) ? ' badge-'.$mode : '').(!empty($type) ? ' badge-'.$type : '').(empty($params['css']) ? '' : ' '.$params['css'])
8601	);
8602
8603	if (empty($html)) {
8604		$html = $label;
8605	}
8606
8607	if (!empty($url)) {
8608		$attr['href'] = $url;
8609	}
8610
8611	if ($mode === 'dot') {
8612		$attr['class'] .= ' classfortooltip';
8613		$attr['title'] = $html;
8614		$attr['aria-label'] = $label;
8615		$html = '';
8616	}
8617
8618	// Override attr
8619	if (!empty($params['attr']) && is_array($params['attr'])) {
8620		foreach ($params['attr']as $key => $value) {
8621			if ($key == 'class') {
8622				$attr['class'] .= ' '.$value;
8623			} elseif ($key == 'classOverride') {
8624				$attr['class'] = $value;
8625			} else {
8626				$attr[$key] = $value;
8627			}
8628		}
8629	}
8630
8631	// TODO: add hook
8632
8633	// escape all attribute
8634	$attr = array_map('dol_escape_htmltag', $attr);
8635
8636	$TCompiledAttr = array();
8637	foreach ($attr as $key => $value) {
8638		$TCompiledAttr[] = $key.'="'.$value.'"';
8639	}
8640
8641	$compiledAttributes = !empty($TCompiledAttr) ?implode(' ', $TCompiledAttr) : '';
8642
8643	$tag = !empty($url) ? 'a' : 'span';
8644
8645	return '<'.$tag.' '.$compiledAttributes.'>'.$html.'</'.$tag.'>';
8646}
8647
8648
8649/**
8650 * Output the badge of a status.
8651 *
8652 * @param   string  $statusLabel       Label of badge no html : use in alt attribute for accessibility
8653 * @param   string  $statusLabelShort  Short label of badge no html
8654 * @param   string  $html              Optional : label of badge with html
8655 * @param   string  $statusType        status0 status1 status2 status3 status4 status5 status6 status7 status8 status9 : image name or badge name
8656 * @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
8657 * @param   string  $url               The url for link
8658 * @param   array   $params            Various params. Example: array('tooltip'=>'no|...', 'badgeParams'=>...)
8659 * @return  string                     Html status string
8660 */
8661function dolGetStatus($statusLabel = '', $statusLabelShort = '', $html = '', $statusType = 'status0', $displayMode = 0, $url = '', $params = array())
8662{
8663	global $conf;
8664
8665	$return = '';
8666	$dolGetBadgeParams = array();
8667
8668	if (!empty($params['badgeParams'])) {
8669		$dolGetBadgeParams = $params['badgeParams'];
8670	}
8671
8672	// TODO : add a hook
8673	if ($displayMode == 0) {
8674		$return = !empty($html) ? $html : (empty($conf->dol_optimize_smallscreen) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort));
8675	} elseif ($displayMode == 1) {
8676		$return = !empty($html) ? $html : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort);
8677	} // Use status with images (for backward compatibility)
8678	elseif (!empty($conf->global->MAIN_STATUS_USES_IMAGES)) {
8679		$return = '';
8680		$htmlLabel      = (in_array($displayMode, array(1, 2, 5)) ? '<span class="hideonsmartphone">' : '').(!empty($html) ? $html : $statusLabel).(in_array($displayMode, array(1, 2, 5)) ? '</span>' : '');
8681		$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>' : '');
8682
8683		// For small screen, we always use the short label instead of long label.
8684		if (!empty($conf->dol_optimize_smallscreen))
8685		{
8686			if ($displayMode == 0) $displayMode = 1;
8687			elseif ($displayMode == 4) $displayMode = 2;
8688			elseif ($displayMode == 6) $displayMode = 5;
8689		}
8690
8691		// For backward compatibility. Image's filename are still in French, so we use this array to convert
8692		$statusImg = array(
8693			'status0' => 'statut0',
8694			'status1' => 'statut1',
8695			'status2' => 'statut2',
8696			'status3' => 'statut3',
8697			'status4' => 'statut4',
8698			'status5' => 'statut5',
8699			'status6' => 'statut6',
8700			'status7' => 'statut7',
8701			'status8' => 'statut8',
8702			'status9' => 'statut9'
8703		);
8704
8705		if (!empty($statusImg[$statusType])) {
8706			$htmlImg = img_picto($statusLabel, $statusImg[$statusType]);
8707		} else {
8708			$htmlImg = img_picto($statusLabel, $statusType);
8709		}
8710
8711		if ($displayMode === 2) {
8712			$return = $htmlImg.' '.$htmlLabelShort;
8713		} elseif ($displayMode === 3) {
8714			$return = $htmlImg;
8715		} elseif ($displayMode === 4) {
8716			$return = $htmlImg.' '.$htmlLabel;
8717		} elseif ($displayMode === 5) {
8718			$return = $htmlLabelShort.' '.$htmlImg;
8719		} else { // $displayMode >= 6
8720			$return = $htmlLabel.' '.$htmlImg;
8721		}
8722	} // Use new badge
8723	elseif (empty($conf->global->MAIN_STATUS_USES_IMAGES) && !empty($displayMode)) {
8724		$statusLabelShort = (empty($statusLabelShort) ? $statusLabel : $statusLabelShort);
8725
8726		$dolGetBadgeParams['attr']['class'] = 'badge-status';
8727		$dolGetBadgeParams['attr']['title'] = empty($params['tooltip']) ? $statusLabel : ($params['tooltip'] != 'no' ? $params['tooltip'] : '');
8728
8729		if ($displayMode == 3) {
8730			$return = dolGetBadge((empty($conf->dol_optimize_smallscreen) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort)), '', $statusType, 'dot', $url, $dolGetBadgeParams);
8731		} elseif ($displayMode === 5) {
8732			$return = dolGetBadge($statusLabelShort, $html, $statusType, '', $url, $dolGetBadgeParams);
8733		} else {
8734			$return = dolGetBadge((empty($conf->dol_optimize_smallscreen) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort)), $html, $statusType, '', $url, $dolGetBadgeParams);
8735		}
8736	}
8737
8738	return $return;
8739}
8740
8741
8742/**
8743 * Function dolGetButtonAction
8744 *
8745 * @param string    $label      label of button no html : use in alt attribute for accessibility $html is not empty
8746 * @param string    $html       optional : content with html
8747 * @param string    $actionType default, delete, danger
8748 * @param string    $url        the url for link
8749 * @param string    $id         attribute id of button
8750 * @param int       $userRight  user action right
8751 * @param array     $params     various params for future : recommended rather than adding more function arguments
8752 * @return string               html button
8753 */
8754function dolGetButtonAction($label, $html = '', $actionType = 'default', $url = '', $id = '', $userRight = 1, $params = array())
8755{
8756	$class = 'butAction';
8757	if ($actionType == 'danger' || $actionType == 'delete') {
8758		$class = 'butActionDelete';
8759		if (strpos($url, 'token=') === false) $url .= '&token='.newToken();
8760	}
8761
8762	$attr = array(
8763		'class' => $class
8764		,'href' => empty($url) ? '' : $url
8765	);
8766
8767	if (empty($html)) {
8768		$html = $label;
8769	} else {
8770		$attr['aria-label'] = $label;
8771	}
8772
8773	if (empty($userRight)) {
8774		$attr['class'] = 'butActionRefused';
8775		$attr['href'] = '';
8776	}
8777
8778	if (!empty($id)) {
8779		$attr['id'] = $id;
8780	}
8781
8782	// Override attr
8783	if (!empty($params['attr']) && is_array($params['attr'])) {
8784		foreach ($params['attr'] as $key => $value) {
8785			if ($key == 'class') {
8786				$attr['class'] .= ' '.$value;
8787			} elseif ($key == 'classOverride') {
8788				$attr['class'] = $value;
8789			} else {
8790				$attr[$key] = $value;
8791			}
8792		}
8793	}
8794
8795	if (isset($attr['href']) && empty($attr['href'])) {
8796		unset($attr['href']);
8797	}
8798
8799	// TODO : add a hook
8800
8801	// escape all attribute
8802	$attr = array_map('dol_escape_htmltag', $attr);
8803
8804	$TCompiledAttr = array();
8805	foreach ($attr as $key => $value) {
8806		$TCompiledAttr[] = $key.'="'.$value.'"';
8807	}
8808
8809	$compiledAttributes = !empty($TCompiledAttr) ?implode(' ', $TCompiledAttr) : '';
8810
8811	$tag = !empty($attr['href']) ? 'a' : 'span';
8812
8813	return '<div class="inline-block divButAction"><'.$tag.' '.$compiledAttributes.'>'.$html.'</'.$tag.'></div>';
8814}
8815
8816/**
8817 * Function dolGetButtonTitle : this kind of buttons are used in title in list
8818 *
8819 * @param string    $label      label of button
8820 * @param string    $helpText   optional : content for help tooltip
8821 * @param string    $iconClass  class for icon element (Example: 'fa fa-file')
8822 * @param string    $url        the url for link
8823 * @param string    $id         attribute id of button
8824 * @param int       $status     0 no user rights, 1 active, -1 Feature Disabled, -2 disable Other reason use helpText as tooltip
8825 * @param array     $params     various params for future : recommended rather than adding more function arguments
8826 * @return string               html button
8827 */
8828function dolGetButtonTitle($label, $helpText = '', $iconClass = 'fa fa-file', $url = '', $id = '', $status = 1, $params = array())
8829{
8830	global $langs, $conf, $user;
8831
8832	// Actually this conf is used in css too for external module compatibility and smooth transition to this function
8833	if (!empty($conf->global->MAIN_BUTTON_HIDE_UNAUTHORIZED) && (!$user->admin) && $status <= 0) {
8834		return '';
8835	}
8836
8837	$class = 'btnTitle';
8838	if (in_array($iconClass, array('fa fa-plus-circle', 'fa fa-comment-dots'))) $class .= ' btnTitlePlus';
8839	$useclassfortooltip = 1;
8840
8841	if (!empty($params['morecss'])) $class .= ' '.$params['morecss'];
8842
8843	$attr = array(
8844		'class' => $class,
8845		'href' => empty($url) ? '' : $url
8846	);
8847
8848	if (!empty($helpText)) {
8849		$attr['title'] = dol_escape_htmltag($helpText);
8850	} elseif (empty($attr['title']) && $label) {
8851		$attr['title'] = $label;
8852		$useclassfortooltip = 0;
8853	}
8854
8855	if ($status <= 0) {
8856		$attr['class'] .= ' refused';
8857
8858		$attr['href'] = '';
8859
8860		if ($status == -1) { // disable
8861			$attr['title'] = dol_escape_htmltag($langs->transnoentitiesnoconv("FeatureDisabled"));
8862		} elseif ($status == 0) { // Not enough permissions
8863			$attr['title'] = dol_escape_htmltag($langs->transnoentitiesnoconv("NotEnoughPermissions"));
8864		}
8865	}
8866
8867	if (!empty($attr['title']) && $useclassfortooltip) {
8868		$attr['class'] .= ' classfortooltip';
8869	}
8870
8871	if (!empty($id)) {
8872		$attr['id'] = $id;
8873	}
8874
8875	// Override attr
8876	if (!empty($params['attr']) && is_array($params['attr'])) {
8877		foreach ($params['attr'] as $key => $value) {
8878			if ($key == 'class') {
8879				$attr['class'] .= ' '.$value;
8880			} elseif ($key == 'classOverride') {
8881				$attr['class'] = $value;
8882			} else {
8883				$attr[$key] = $value;
8884			}
8885		}
8886	}
8887
8888	if (isset($attr['href']) && empty($attr['href'])) {
8889		unset($attr['href']);
8890	}
8891
8892	// TODO : add a hook
8893
8894	// escape all attribute
8895	$attr = array_map('dol_escape_htmltag', $attr);
8896
8897	$TCompiledAttr = array();
8898	foreach ($attr as $key => $value) {
8899		$TCompiledAttr[] = $key.'="'.$value.'"';
8900	}
8901
8902	$compiledAttributes = (empty($TCompiledAttr) ? '' : implode(' ', $TCompiledAttr));
8903
8904	$tag = (empty($attr['href']) ? 'span' : 'a');
8905
8906	$button = '<'.$tag.' '.$compiledAttributes.'>';
8907	$button .= '<span class="'.$iconClass.' valignmiddle btnTitle-icon"></span>';
8908	if (!empty($params['forcenohideoftext'])) {
8909		$button .= '<span class="valignmiddle text-plus-circle btnTitle-label'.(empty($params['forcenohideoftext']) ? ' hideonsmartphone' : '').'">'.$label.'</span>';
8910	}
8911	$button .= '</'.$tag.'>';
8912
8913	return $button;
8914}
8915
8916/**
8917 * Get an array with properties of an element.
8918 * Called by fetchObjectByElement.
8919 *
8920 * @param   string 	$element_type 	Element type (Value of $object->element). Example: 'action', 'facture', 'project_task' or 'object@mymodule'...
8921 * @return  array					(module, classpath, element, subelement, classfile, classname)
8922 */
8923function getElementProperties($element_type)
8924{
8925	$regs = array();
8926
8927	$classfile = $classname = $classpath = '';
8928
8929	// Parse element/subelement (ex: project_task)
8930	$module = $element_type;
8931	$element = $element_type;
8932	$subelement = $element_type;
8933
8934	// If we ask an resource form external module (instead of default path)
8935	if (preg_match('/^([^@]+)@([^@]+)$/i', $element_type, $regs)) {
8936		$element = $subelement = $regs[1];
8937		$module = $regs[2];
8938	}
8939
8940	//print '<br>1. element : '.$element.' - module : '.$module .'<br>';
8941	if (preg_match('/^([^_]+)_([^_]+)/i', $element, $regs)) {
8942		$module = $element = $regs[1];
8943		$subelement = $regs[2];
8944	}
8945
8946	// For compat
8947	if ($element_type == "action") {
8948		$classpath = 'comm/action/class';
8949		$subelement = 'Actioncomm';
8950		$module = 'agenda';
8951	}
8952
8953	// To work with non standard path
8954	if ($element_type == 'facture' || $element_type == 'invoice') {
8955		$classpath = 'compta/facture/class';
8956		$module = 'facture';
8957		$subelement = 'facture';
8958	}
8959	if ($element_type == 'commande' || $element_type == 'order') {
8960		$classpath = 'commande/class';
8961		$module = 'commande';
8962		$subelement = 'commande';
8963	}
8964	if ($element_type == 'propal') {
8965		$classpath = 'comm/propal/class';
8966	}
8967	if ($element_type == 'supplier_proposal') {
8968		$classpath = 'supplier_proposal/class';
8969	}
8970	if ($element_type == 'shipping') {
8971		$classpath = 'expedition/class';
8972		$subelement = 'expedition';
8973		$module = 'expedition_bon';
8974	}
8975	if ($element_type == 'delivery') {
8976		$classpath = 'delivery/class';
8977		$subelement = 'delivery';
8978		$module = 'delivery_note';
8979	}
8980	if ($element_type == 'contract') {
8981		$classpath = 'contrat/class';
8982		$module = 'contrat';
8983		$subelement = 'contrat';
8984	}
8985	if ($element_type == 'member') {
8986		$classpath = 'adherents/class';
8987		$module = 'adherent';
8988		$subelement = 'adherent';
8989	}
8990	if ($element_type == 'cabinetmed_cons') {
8991		$classpath = 'cabinetmed/class';
8992		$module = 'cabinetmed';
8993		$subelement = 'cabinetmedcons';
8994	}
8995	if ($element_type == 'fichinter') {
8996		$classpath = 'fichinter/class';
8997		$module = 'ficheinter';
8998		$subelement = 'fichinter';
8999	}
9000	if ($element_type == 'dolresource' || $element_type == 'resource') {
9001		$classpath = 'resource/class';
9002		$module = 'resource';
9003		$subelement = 'dolresource';
9004	}
9005	if ($element_type == 'propaldet') {
9006		$classpath = 'comm/propal/class';
9007		$module = 'propal';
9008		$subelement = 'propaleligne';
9009	}
9010	if ($element_type == 'order_supplier') {
9011		$classpath = 'fourn/class';
9012		$module = 'fournisseur';
9013		$subelement = 'commandefournisseur';
9014		$classfile = 'fournisseur.commande';
9015	}
9016	if ($element_type == 'invoice_supplier') {
9017		$classpath = 'fourn/class';
9018		$module = 'fournisseur';
9019		$subelement = 'facturefournisseur';
9020		$classfile = 'fournisseur.facture';
9021	}
9022	if ($element_type == "service") {
9023		$classpath = 'product/class';
9024		$subelement = 'product';
9025	}
9026
9027	if (empty($classfile)) $classfile = strtolower($subelement);
9028	if (empty($classname)) $classname = ucfirst($subelement);
9029	if (empty($classpath)) $classpath = $module.'/class';
9030
9031	$element_properties = array(
9032		'module' => $module,
9033		'classpath' => $classpath,
9034		'element' => $element,
9035		'subelement' => $subelement,
9036		'classfile' => $classfile,
9037		'classname' => $classname
9038	);
9039	return $element_properties;
9040}
9041
9042/**
9043 * Fetch an object from its id and element_type
9044 * Inclusion of classes is automatic
9045 *
9046 * @param	int     	$element_id 	Element id
9047 * @param	string  	$element_type 	Element type
9048 * @param	string     	$element_ref 	Element ref (Use this or element_id but not both)
9049 * @return 	int|object 					object || 0 || -1 if error
9050 */
9051function fetchObjectByElement($element_id, $element_type, $element_ref = '')
9052{
9053	global $conf, $db;
9054
9055	$element_prop = getElementProperties($element_type);
9056	if (is_array($element_prop) && $conf->{$element_prop['module']}->enabled)
9057	{
9058		dol_include_once('/'.$element_prop['classpath'].'/'.$element_prop['classfile'].'.class.php');
9059
9060		$objecttmp = new $element_prop['classname']($db);
9061		$ret = $objecttmp->fetch($element_id, $element_ref);
9062		if ($ret >= 0)
9063		{
9064			return $objecttmp;
9065		}
9066	}
9067	return 0;
9068}
9069
9070/**
9071 * Return if a file can contains executable content
9072 *
9073 * @param   string  $filename       File name to test
9074 * @return  boolean                 True if yes, False if no
9075 */
9076function isAFileWithExecutableContent($filename)
9077{
9078	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))
9079	{
9080		return true;
9081	}
9082
9083	return false;
9084}
9085
9086/**
9087 * Return the value of token currently saved into session with name 'newtoken'.
9088 * This token must be send by any POST as it will be used by next page for comparison with value in session.
9089 *
9090 * @return  string
9091 */
9092function newToken()
9093{
9094	return $_SESSION['newtoken'];
9095}
9096
9097/**
9098 * Return the value of token currently saved into session with name 'token'.
9099 *
9100 * @return  string
9101 */
9102function currentToken()
9103{
9104	return $_SESSION['token'];
9105}
9106
9107/**
9108 * Start a table with headers and a optinal clickable number (don't forget to use "finishSimpleTable()" after the last table row)
9109 *
9110 * @param string	$header		The first left header of the table (automatic translated)
9111 * @param string	$link		(optional) The link to a internal dolibarr page, when click on the number (without the first "/")
9112 * @param string	$arguments	(optional) Additional arguments for the link (e.g. "search_status=0")
9113 * @param integer	$emptyRows	(optional) The count of empty rows after the first header
9114 * @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"
9115 * @return void
9116 *
9117 * @see finishSimpleTable()
9118 */
9119function startSimpleTable($header, $link = "", $arguments = "", $emptyRows = 0, $number = -1)
9120{
9121	global $langs;
9122
9123	print '<div class="div-table-responsive-no-min">';
9124	print '<table class="noborder centpercent">';
9125	print '<tr class="liste_titre">';
9126
9127	print $emptyRows < 1 ? '<th>' : '<th colspan="'.($emptyRows + 1).'">';
9128
9129	print $langs->trans($header);
9130
9131	// extra space between the first header and the number
9132	if ($number > -1) {
9133		print ' ';
9134	}
9135
9136	if (!empty($link)) {
9137		if (!empty($arguments)) {
9138			print '<a href="'.DOL_URL_ROOT.'/'.$link.'?'.$arguments.'">';
9139		} else {
9140			print '<a href="'.DOL_URL_ROOT.'/'.$link.'">';
9141		}
9142	}
9143
9144	if ($number > -1) {
9145		print '<span class="badge">'.$number.'</span>';
9146	}
9147
9148	if (!empty($link)) {
9149		print '</a>';
9150	}
9151
9152	print '</th>';
9153
9154	if ($number < 0 && !empty($link)) {
9155		print '<th class="right">';
9156
9157		if (!empty($arguments)) {
9158			print '<a class="commonlink" href="'.DOL_URL_ROOT.'/'.$link.'?'.$arguments.'">';
9159		} else {
9160			print '<a class="commonlink" href="'.DOL_URL_ROOT.'/'.$link.'">';
9161		}
9162
9163		print $langs->trans("FullList");
9164		print '</a>';
9165		print '</th>';
9166	}
9167
9168	print '</tr>';
9169}
9170
9171/**
9172 * Add the correct HTML close tags for "startSimpleTable(...)" (use after the last table line)
9173 *
9174 * @param 	bool 	$addLineBreak	(optional) Add a extra line break after the complete table (\<br\>)
9175 * @return 	void
9176 *
9177 * @see startSimpleTable()
9178 */
9179function finishSimpleTable($addLineBreak = false)
9180{
9181	print '</table>';
9182	print '</div>';
9183
9184	if ($addLineBreak) {
9185		print '<br>';
9186	}
9187}
9188
9189/**
9190 * Add a summary line to the current open table ("None", "XMoreLines" or "Total xxx")
9191 *
9192 * @param integer	$tableColumnCount		The complete count columns of the table
9193 * @param integer	$num					The count of the rows of the table, when it is zero (0) the "$noneWord" is shown instead
9194 * @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)
9195 * @param integer	$total					(optional)	The total value thaht is shown after when the table has minimum of one entire
9196 * @param string	$noneWord				(optional)	The word that is shown when the table has no entires ($num === 0)
9197 * @param boolean	$extraRightColumn		(optional)	Add a addtional column after the summary word and total number
9198 * @return void
9199 */
9200function addSummaryTableLine($tableColumnCount, $num, $nbofloop = 0, $total = 0, $noneWord = "None", $extraRightColumn = false)
9201{
9202	global $langs;
9203
9204	if ($num === 0) {
9205		print '<tr class="oddeven">';
9206		print '<td colspan="'.$tableColumnCount.'" class="opacitymedium">'.$langs->trans($noneWord).'</td>';
9207		print '</tr>';
9208		return;
9209	}
9210
9211	if ($nbofloop === 0)
9212	{
9213		// don't show a summary line
9214		return;
9215	}
9216
9217	if ($num === 0) {
9218		$colspan = $tableColumnCount;
9219	}
9220	elseif ($num > $nbofloop) {
9221		$colspan = $tableColumnCount;
9222	} else {
9223		$colspan = $tableColumnCount - 1;
9224	}
9225
9226	if ($extraRightColumn) {
9227		$colspan--;
9228	}
9229
9230	print '<tr class="liste_total">';
9231
9232	if ($nbofloop > 0 && $num > $nbofloop) {
9233		print '<td colspan="'.$colspan.'" class="right">'.$langs->trans("XMoreLines", ($num - $nbofloop)).'</td>';
9234	} else {
9235		print '<td colspan="'.$colspan.'" class="right"> '.$langs->trans("Total").'</td>';
9236		print '<td class="right" width="100">'.price($total).'</td>';
9237	}
9238
9239	if ($extraRightColumn) {
9240		print '<td></td>';
9241	}
9242
9243	print '</tr>';
9244}
9245
9246/**
9247 *  Return a file on output using a low memory. It can return very large files with no need of memory.
9248 *  WARNING: This close output buffers.
9249 *
9250 *  @param	string	$fullpath_original_file_osencoded		Full path of file to return.
9251 *  @param	int		$method									-1 automatic, 0=readfile, 1=fread, 2=stream_copy_to_stream
9252 *  @return void
9253 */
9254function readfileLowMemory($fullpath_original_file_osencoded, $method = -1)
9255{
9256	global $conf;
9257
9258	if ($method == -1) {
9259		$method = 0;
9260		if (!empty($conf->global->MAIN_FORCE_READFILE_WITH_FREAD)) $method = 1;
9261		if (!empty($conf->global->MAIN_FORCE_READFILE_WITH_STREAM_COPY)) $method = 2;
9262	}
9263
9264	// Be sure we don't have output buffering enabled to have readfile working correctly
9265	while (ob_get_level()) ob_end_flush();
9266
9267	// Solution 0
9268	if ($method == 0) {
9269		readfile($fullpath_original_file_osencoded);
9270	}
9271	// Solution 1
9272	elseif ($method == 1) {
9273		$handle = fopen($fullpath_original_file_osencoded, "rb");
9274		while (!feof($handle)) {
9275			print fread($handle, 8192);
9276		}
9277		fclose($handle);
9278	}
9279	// Solution 2
9280	elseif ($method == 2) {
9281		$handle1 = fopen($fullpath_original_file_osencoded, "rb");
9282		$handle2 = fopen("php://output", "wb");
9283		stream_copy_to_stream($handle1, $handle2);
9284		fclose($handle1);
9285		fclose($handle2);
9286	}
9287}
9288