Passed
Branch develop (07a336)
by
unknown
39:19
created

dol_now()   B

Complexity

Conditions 8
Paths 14

Size

Total Lines 29
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 13
nc 14
nop 1
dl 0
loc 29
rs 8.4444
c 0
b 0
f 0
1
<?php
2
/* Copyright (C) 2000-2007	Rodolphe Quiedeville		<[email protected]>
3
 * Copyright (C) 2003		Jean-Louis Bergamo			<[email protected]>
4
 * Copyright (C) 2004-2018	Laurent Destailleur			<[email protected]>
5
 * Copyright (C) 2004		Sebastien Di Cintio			<[email protected]>
6
 * Copyright (C) 2004		Benoit Mortier				<[email protected]>
7
 * Copyright (C) 2004		Christophe Combelles		<[email protected]>
8
 * Copyright (C) 2005-2019	Regis Houssin				<[email protected]>
9
 * Copyright (C) 2008		Raphael Bertrand (Resultic)	<[email protected]>
10
 * Copyright (C) 2010-2018	Juanjo Menent				<[email protected]>
11
 * Copyright (C) 2013		Cédric Salvador				<[email protected]>
12
 * Copyright (C) 2013-2017	Alexandre Spangaro			<[email protected]>
13
 * Copyright (C) 2014		Cédric GROSS				<[email protected]>
14
 * Copyright (C) 2014-2015	Marcos García				<[email protected]>
15
 * Copyright (C) 2015		Jean-François Ferry			<[email protected]>
16
 * Copyright (C) 2018-2020  Frédéric France             <[email protected]>
17
 * Copyright (C) 2019       Thibault Foucart            <[email protected]>
18
 * Copyright (C) 2020       Open-Dsi         			<[email protected]>
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
41
include_once DOL_DOCUMENT_ROOT.'/core/lib/json.lib.php';
42
43
44
/**
45
 * Return a DoliDB instance (database handler).
46
 *
47
 * @param   string	$type		Type of database (mysql, pgsql...)
48
 * @param	string	$host		Address of database server
49
 * @param	string	$user		Authorized username
50
 * @param	string	$pass		Password
51
 * @param	string	$name		Name of database
52
 * @param	int		$port		Port of database server
53
 * @return	DoliDB				A DoliDB instance
54
 */
55
function getDoliDBInstance($type, $host, $user, $pass, $name, $port)
56
{
57
	require_once DOL_DOCUMENT_ROOT."/core/db/".$type.'.class.php';
58
59
	$class = 'DoliDB'.ucfirst($type);
60
	$dolidb = new $class($type, $host, $user, $pass, $name, $port);
61
	return $dolidb;
62
}
63
64
/**
65
 * 	Get list of entity id to use.
66
 *
67
 * 	@param	string	$element		Current element
68
 *									'societe', 'socpeople', 'actioncomm', 'agenda', 'resource',
69
 *									'product', 'productprice', 'stock', 'bom', 'mo',
70
 *									'propal', 'supplier_proposal', 'invoice', 'supplier_invoice', 'payment_various',
71
 *									'categorie', 'bank_account', 'bank_account', 'adherent', 'user',
72
 *									'commande', 'supplier_order', 'expedition', 'intervention', 'survey',
73
 *									'contract', 'tax', 'expensereport', 'holiday', 'multicurrency', 'project',
74
 *									'email_template', 'event', 'donation'
75
 *									'c_paiement', 'c_payment_term', ...
76
 * 	@param	int		$shared			0=Return id of current entity only,
77
 * 									1=Return id of current entity + shared entities (default)
78
 *  @param	object	$currentobject	Current object if needed
79
 * 	@return	mixed					Entity id(s) to use ( eg. entity IN ('.getEntity(elementname).')' )
80
 */
81
function getEntity($element, $shared = 1, $currentobject = null)
82
{
83
	global $conf, $mc;
84
85
	// fix different element names (France to English)
86
	switch ($element) {
87
		case 'contrat':			$element = 'contract'; break; // "/contrat/class/contrat.class.php"
88
		case 'order_supplier':	$element = 'supplier_order'; break; // "/fourn/class/fournisseur.commande.class.php"
89
	}
90
91
	if (is_object($mc))
92
	{
93
		return $mc->getEntity($element, $shared, $currentobject);
94
	} else {
95
		$out = '';
96
		$addzero = array('user', 'usergroup', 'c_email_templates', 'email_template', 'default_values');
97
		if (in_array($element, $addzero)) $out .= '0,';
98
		$out .= ((int) $conf->entity);
99
		return $out;
100
	}
101
}
102
103
/**
104
 * 	Set entity id to use when to create an object
105
 *
106
 * 	@param	object	$currentobject	Current object
107
 * 	@return	mixed					Entity id to use ( eg. entity = '.setEntity($object) )
108
 */
109
function setEntity($currentobject)
110
{
111
	global $conf, $mc;
112
113
	if (is_object($mc) && method_exists($mc, 'setEntity'))
114
	{
115
		return $mc->setEntity($currentobject);
116
	} else {
117
		return ((is_object($currentobject) && $currentobject->id > 0 && $currentobject->entity > 0) ? $currentobject->entity : $conf->entity);
118
	}
119
}
120
121
/**
122
 * 	Return if string has a name dedicated to store a secret
123
 *
124
 * 	@param	string	$keyname	Name of key to test
125
 * 	@return	boolean				True if key is used to store a secret
126
 */
127
function isASecretKey($keyname)
128
{
129
	return preg_match('/(_pass|password|_pw|_key|securekey|serverkey|secret\d?|p12key|exportkey|_PW_[a-z]+|token)$/i', $keyname);
130
}
131
132
/**
133
 * Return information about user browser
134
 *
135
 * Returns array with the following format:
136
 * array(
137
 *  'browsername' => Browser name (firefox|chrome|iceweasel|epiphany|safari|opera|ie|unknown)
138
 *  'browserversion' => Browser version. Empty if unknown
139
 *  'browseros' => Set with mobile OS (android|blackberry|ios|palm|symbian|webos|maemo|windows|unknown)
140
 *  'layout' => (tablet|phone|classic)
141
 *  'phone' => empty if not mobile, (android|blackberry|ios|palm|unknown) if mobile
142
 *  'tablet' => true/false
143
 * )
144
 *
145
 * @param string $user_agent Content of $_SERVER["HTTP_USER_AGENT"] variable
146
 * @return	array Check function documentation
147
 */
148
function getBrowserInfo($user_agent)
149
{
150
	include_once DOL_DOCUMENT_ROOT.'/includes/mobiledetect/mobiledetectlib/Mobile_Detect.php';
151
152
	$name = 'unknown';
153
	$version = '';
154
	$os = 'unknown';
155
	$phone = '';
156
157
	$user_agent = substr($user_agent, 0, 512);	// Avoid to process too large user agent
158
159
	$detectmobile = new Mobile_Detect(null, $user_agent);
160
	$tablet = $detectmobile->isTablet();
161
162
	if ($detectmobile->isMobile()) {
163
		$phone = 'unknown';
164
165
		// If phone/smartphone, we set phone os name.
166
		if ($detectmobile->is('AndroidOS')) {
167
			$os = $phone = 'android';
168
		} elseif ($detectmobile->is('BlackBerryOS')) {
169
			$os = $phone = 'blackberry';
170
		} elseif ($detectmobile->is('iOS')) {
171
			$os = 'ios';
172
			$phone = 'iphone';
173
		} elseif ($detectmobile->is('PalmOS')) {
174
			$os = $phone = 'palm';
175
		} elseif ($detectmobile->is('SymbianOS')) {
176
			$os = 'symbian';
177
		} elseif ($detectmobile->is('webOS')) {
178
			$os = 'webos';
179
		} elseif ($detectmobile->is('MaemoOS')) {
180
			$os = 'maemo';
181
		} elseif ($detectmobile->is('WindowsMobileOS') || $detectmobile->is('WindowsPhoneOS')) {
182
			$os = 'windows';
183
		}
184
	}
185
186
	// OS
187
	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'; }
188
189
	// Name
190
	$reg = array();
191
	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
192
	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.
193
	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
194
	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
195
	elseif (preg_match('/l(i|y)n(x|ks)(\(|\/|\s)*([\d\.]+)/i', $user_agent, $reg)) { $name = 'lynxlinks'; $version = $reg[4]; }
196
197
	if ($tablet) {
198
		$layout = 'tablet';
199
	} elseif ($phone) {
200
		$layout = 'phone';
201
	} else {
202
		$layout = 'classic';
203
	}
204
205
	return array(
206
		'browsername' => $name,
207
		'browserversion' => $version,
208
		'browseros' => $os,
209
		'layout' => $layout,
210
		'phone' => $phone,
211
		'tablet' => $tablet
212
	);
213
}
214
215
/**
216
 *  Function called at end of web php process
217
 *
218
 *  @return	void
219
 */
220
function dol_shutdown()
221
{
222
	global $conf, $user, $langs, $db;
223
	$disconnectdone = false; $depth = 0;
224
	if (is_object($db) && !empty($db->connected)) { $depth = $db->transaction_opened; $disconnectdone = $db->close(); }
225
	dol_syslog("--- End access to ".$_SERVER["PHP_SELF"].(($disconnectdone && $depth) ? ' (Warn: db disconnection forced, transaction depth was '.$depth.')' : ''), (($disconnectdone && $depth) ?LOG_WARNING:LOG_INFO));
226
}
227
228
/**
229
 * Return true if we are in a context of submitting the parameter $paramname
230
 *
231
 * @param 	string	$paramname		Name or parameter to test
232
 * @return 	boolean					True if we have just submit a POST or GET request with the parameter provided (even if param is empty)
233
 */
234
function GETPOSTISSET($paramname)
235
{
236
	$isset = false;
237
238
	$relativepathstring = $_SERVER["PHP_SELF"];
239
	// Clean $relativepathstring
240
	if (constant('DOL_URL_ROOT')) $relativepathstring = preg_replace('/^'.preg_quote(constant('DOL_URL_ROOT'), '/').'/', '', $relativepathstring);
241
	$relativepathstring = preg_replace('/^\//', '', $relativepathstring);
242
	$relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
243
	//var_dump($relativepathstring);
244
	//var_dump($user->default_values);
245
246
	// Code for search criteria persistence.
247
	// Retrieve values if restore_lastsearch_values
248
	if (!empty($_GET['restore_lastsearch_values']))        // Use $_GET here and not GETPOST
249
	{
250
		if (!empty($_SESSION['lastsearch_values_'.$relativepathstring]))	// If there is saved values
251
		{
252
			$tmp = json_decode($_SESSION['lastsearch_values_'.$relativepathstring], true);
253
			if (is_array($tmp))
254
			{
255
				foreach ($tmp as $key => $val)
256
				{
257
					if ($key == $paramname)	// We are on the requested parameter
258
					{
259
						$isset = true;
260
						break;
261
					}
262
				}
263
			}
264
		}
265
		// If there is saved contextpage, page or limit
266
		if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_'.$relativepathstring]))
267
		{
268
			$isset = true;
269
		} elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_'.$relativepathstring]))
270
		{
271
			$isset = true;
272
		} elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_'.$relativepathstring]))
273
		{
274
			$isset = true;
275
		}
276
	} else {
277
		$isset = (isset($_POST[$paramname]) || isset($_GET[$paramname])); // We must keep $_POST and $_GET here
278
	}
279
280
	return $isset;
281
}
282
283
/**
284
 *  Return value of a param into GET or POST supervariable.
285
 *  Use the property $user->default_values[path]['creatform'] and/or $user->default_values[path]['filters'] and/or $user->default_values[path]['sortorder']
286
 *  Note: The property $user->default_values is loaded by main.php when loading the user.
287
 *
288
 *  @param  string  $paramname   Name of parameter to found
289
 *  @param  string  $check	     Type of check
290
 *                               ''=no check (deprecated)
291
 *                               'none'=no check (only for param that should have very rich content)
292
 *                               'array', 'array:restricthtml' or 'array:aZ09' to check it's an array
293
 *                               'int'=check it's numeric (integer or float)
294
 *                               'intcomma'=check it's integer+comma ('1,2,3,4...')
295
 *                               'alpha'=Same than alphanohtml since v13
296
 *                               'alphanohtml'=check there is no html content and no " and no ../
297
 *                               'aZ'=check it's a-z only
298
 *                               'aZ09'=check it's simple alpha string (recommended for keys)
299
 *                               'san_alpha'=Use filter_var with FILTER_SANITIZE_STRING (do not use this for free text string)
300
 *                               'nohtml'=check there is no html content and no " and no ../
301
 *                               'restricthtml'=check html content is restricted to some tags only
302
 *                               'custom'= custom filter specify $filter and $options)
303
 *  @param	int		$method	     Type of method (0 = get then post, 1 = only get, 2 = only post, 3 = post then get)
304
 *  @param  int     $filter      Filter to apply when $check is set to 'custom'. (See http://php.net/manual/en/filter.filters.php for détails)
305
 *  @param  mixed   $options     Options to pass to filter_var when $check is set to 'custom'
306
 *  @param	string	$noreplace	 Force disable of replacement of __xxx__ strings.
307
 *  @return string|array         Value found (string or array), or '' if check fails
308
 */
309
function GETPOST($paramname, $check = 'alphanohtml', $method = 0, $filter = null, $options = null, $noreplace = 0)
310
{
311
	global $mysoc, $user, $conf;
312
313
	if (empty($paramname)) return 'BadFirstParameterForGETPOST';
314
	if (empty($check))
315
	{
316
		dol_syslog("Deprecated use of GETPOST, called with 1st param = ".$paramname." and 2nd param is '', when calling page ".$_SERVER["PHP_SELF"], LOG_WARNING);
317
		// Enable this line to know who call the GETPOST with '' $check parameter.
318
		//var_dump(debug_backtrace()[0]);
319
	}
320
321
	if (empty($method)) $out = isset($_GET[$paramname]) ? $_GET[$paramname] : (isset($_POST[$paramname]) ? $_POST[$paramname] : '');
322
	elseif ($method == 1) $out = isset($_GET[$paramname]) ? $_GET[$paramname] : '';
323
	elseif ($method == 2) $out = isset($_POST[$paramname]) ? $_POST[$paramname] : '';
324
	elseif ($method == 3) $out = isset($_POST[$paramname]) ? $_POST[$paramname] : (isset($_GET[$paramname]) ? $_GET[$paramname] : '');
325
	else return 'BadThirdParameterForGETPOST';
326
327
	if (empty($method) || $method == 3 || $method == 4)
328
	{
329
		$relativepathstring = $_SERVER["PHP_SELF"];
330
		// Clean $relativepathstring
331
		if (constant('DOL_URL_ROOT')) $relativepathstring = preg_replace('/^'.preg_quote(constant('DOL_URL_ROOT'), '/').'/', '', $relativepathstring);
332
		$relativepathstring = preg_replace('/^\//', '', $relativepathstring);
333
		$relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
334
		//var_dump($relativepathstring);
335
		//var_dump($user->default_values);
336
337
		// Code for search criteria persistence.
338
		// Retrieve values if restore_lastsearch_values
339
		if (!empty($_GET['restore_lastsearch_values']))        // Use $_GET here and not GETPOST
340
		{
341
			if (!empty($_SESSION['lastsearch_values_'.$relativepathstring]))	// If there is saved values
342
			{
343
				$tmp = json_decode($_SESSION['lastsearch_values_'.$relativepathstring], true);
344
				if (is_array($tmp))
345
				{
346
					foreach ($tmp as $key => $val)
347
					{
348
						if ($key == $paramname)	// We are on the requested parameter
349
						{
350
							$out = $val;
351
							break;
352
						}
353
					}
354
				}
355
			}
356
			// If there is saved contextpage, page or limit
357
			if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_'.$relativepathstring]))
358
			{
359
				$out = $_SESSION['lastsearch_contextpage_'.$relativepathstring];
360
			} elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_'.$relativepathstring]))
361
			{
362
				$out = $_SESSION['lastsearch_page_'.$relativepathstring];
363
			} elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_'.$relativepathstring]))
364
			{
365
				$out = $_SESSION['lastsearch_limit_'.$relativepathstring];
366
			}
367
		} // Else, retrieve default values if we are not doing a sort
368
		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
369
		{
370
			if (!empty($_GET['action']) && $_GET['action'] == 'create' && !isset($_GET[$paramname]) && !isset($_POST[$paramname]))
371
			{
372
				// Search default value from $object->field
373
				global $object;
374
				if (is_object($object) && isset($object->fields[$paramname]['default']))
375
				{
376
					$out = $object->fields[$paramname]['default'];
377
				}
378
			}
379
			if (!empty($conf->global->MAIN_ENABLE_DEFAULT_VALUES))
380
			{
381
				if (!empty($_GET['action']) && (preg_match('/^create/', $_GET['action']) || preg_match('/^presend/', $_GET['action'])) && !isset($_GET[$paramname]) && !isset($_POST[$paramname]))
382
				{
383
					// Now search in setup to overwrite default values
384
					if (!empty($user->default_values))		// $user->default_values defined from menu 'Setup - Default values'
385
					{
386
						if (isset($user->default_values[$relativepathstring]['createform']))
387
						{
388
							foreach ($user->default_values[$relativepathstring]['createform'] as $defkey => $defval)
389
							{
390
								$qualified = 0;
391
								if ($defkey != '_noquery_')
392
								{
393
									$tmpqueryarraytohave = explode('&', $defkey);
394
									$tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
395
									$foundintru = 0;
396
									foreach ($tmpqueryarraytohave as $tmpquerytohave)
397
									{
398
										if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) $foundintru = 1;
399
									}
400
									if (!$foundintru) $qualified = 1;
401
									//var_dump($defkey.'-'.$qualified);
402
								} else $qualified = 1;
403
404
								if ($qualified)
405
								{
406
									if (isset($user->default_values[$relativepathstring]['createform'][$defkey][$paramname]))
407
									{
408
										$out = $user->default_values[$relativepathstring]['createform'][$defkey][$paramname];
409
										break;
410
									}
411
								}
412
							}
413
						}
414
					}
415
				} // Management of default search_filters and sort order
416
				elseif (!empty($paramname) && !isset($_GET[$paramname]) && !isset($_POST[$paramname]))
417
				{
418
					if (!empty($user->default_values))		// $user->default_values defined from menu 'Setup - Default values'
419
					{
420
						//var_dump($user->default_values[$relativepathstring]);
421
						if ($paramname == 'sortfield' || $paramname == 'sortorder')			// Sorted on which fields ? ASC or DESC ?
422
						{
423
							if (isset($user->default_values[$relativepathstring]['sortorder']))	// Even if paramname is sortfield, data are stored into ['sortorder...']
424
							{
425
								foreach ($user->default_values[$relativepathstring]['sortorder'] as $defkey => $defval)
426
								{
427
									$qualified = 0;
428
									if ($defkey != '_noquery_')
429
									{
430
										$tmpqueryarraytohave = explode('&', $defkey);
431
										$tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
432
										$foundintru = 0;
433
										foreach ($tmpqueryarraytohave as $tmpquerytohave)
434
										{
435
											if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) $foundintru = 1;
436
										}
437
										if (!$foundintru) $qualified = 1;
438
										//var_dump($defkey.'-'.$qualified);
439
									} else $qualified = 1;
440
441
									if ($qualified)
442
									{
443
										$forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
444
										foreach ($user->default_values[$relativepathstring]['sortorder'][$defkey] as $key => $val)
445
										{
446
											if ($out) $out .= ', ';
447
											if ($paramname == 'sortfield')
448
											{
449
												$out .= dol_string_nospecial($key, '', $forbidden_chars_to_replace);
450
											}
451
											if ($paramname == 'sortorder')
452
											{
453
												$out .= dol_string_nospecial($val, '', $forbidden_chars_to_replace);
454
											}
455
										}
456
										//break;	// No break for sortfield and sortorder so we can cumulate fields (is it realy usefull ?)
457
									}
458
								}
459
							}
460
						} elseif (isset($user->default_values[$relativepathstring]['filters']))
461
						{
462
							foreach ($user->default_values[$relativepathstring]['filters'] as $defkey => $defval)	// $defkey is a querystring like 'a=b&c=d', $defval is key of user
463
							{
464
								$qualified = 0;
465
								if ($defkey != '_noquery_')
466
								{
467
									$tmpqueryarraytohave = explode('&', $defkey);
468
									$tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
469
									$foundintru = 0;
470
									foreach ($tmpqueryarraytohave as $tmpquerytohave)
471
									{
472
										if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) $foundintru = 1;
473
									}
474
									if (!$foundintru) $qualified = 1;
475
									//var_dump($defkey.'-'.$qualified);
476
								} else $qualified = 1;
477
478
								if ($qualified)
479
								{
480
									// We must keep $_POST and $_GET here
481
									if (isset($_POST['sall']) || isset($_POST['search_all']) || isset($_GET['sall']) || isset($_GET['search_all']))
482
									{
483
										// We made a search from quick search menu, do we still use default filter ?
484
										if (empty($conf->global->MAIN_DISABLE_DEFAULT_FILTER_FOR_QUICK_SEARCH))
485
										{
486
											$forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
487
											$out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
488
										}
489
									} else {
490
										$forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
491
										$out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
492
									}
493
									break;
494
								}
495
							}
496
						}
497
					}
498
				}
499
			}
500
		}
501
	}
502
503
	// Substitution variables for GETPOST (used to get final url with variable parameters or final default value with variable parameters)
504
	// Example of variables: __DAY__, __MONTH__, __YEAR__, __MYCOMPANY_COUNTRY_ID__, __USER_ID__, ...
505
	// 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.
506
	if (!is_array($out) && empty($_POST[$paramname]) && empty($noreplace))
507
	{
508
		$reg = array();
509
		$maxloop = 20; $loopnb = 0; // Protection against infinite loop
510
		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.
511
		{
512
			$loopnb++; $newout = '';
513
514
			if ($reg[1] == 'DAY') {
515
				$tmp = dol_getdate(dol_now(), true);
516
				$newout = $tmp['mday'];
517
			} elseif ($reg[1] == 'MONTH') {
518
				$tmp = dol_getdate(dol_now(), true);
519
				$newout = $tmp['mon'];
520
			} elseif ($reg[1] == 'YEAR') {
521
				$tmp = dol_getdate(dol_now(), true);
522
				$newout = $tmp['year'];
523
			} elseif ($reg[1] == 'PREVIOUS_DAY') {
524
				$tmp = dol_getdate(dol_now(), true);
525
				$tmp2 = dol_get_prev_day($tmp['mday'], $tmp['mon'], $tmp['year']);
526
				$newout = $tmp2['day'];
527
			} elseif ($reg[1] == 'PREVIOUS_MONTH') {
528
				$tmp = dol_getdate(dol_now(), true);
529
				$tmp2 = dol_get_prev_month($tmp['mon'], $tmp['year']);
530
				$newout = $tmp2['month'];
531
			} elseif ($reg[1] == 'PREVIOUS_YEAR') {
532
				$tmp = dol_getdate(dol_now(), true);
533
				$newout = ($tmp['year'] - 1);
534
			} elseif ($reg[1] == 'NEXT_DAY') {
535
				$tmp = dol_getdate(dol_now(), true);
536
				$tmp2 = dol_get_next_day($tmp['mday'], $tmp['mon'], $tmp['year']);
537
				$newout = $tmp2['day'];
538
			} elseif ($reg[1] == 'NEXT_MONTH') {
539
				$tmp = dol_getdate(dol_now(), true);
540
				$tmp2 = dol_get_next_month($tmp['mon'], $tmp['year']);
541
				$newout = $tmp2['month'];
542
			} elseif ($reg[1] == 'NEXT_YEAR') {
543
				$tmp = dol_getdate(dol_now(), true);
544
				$newout = ($tmp['year'] + 1);
545
			} elseif ($reg[1] == 'MYCOMPANY_COUNTRY_ID' || $reg[1] == 'MYCOUNTRY_ID' || $reg[1] == 'MYCOUNTRYID') {
546
				$newout = $mysoc->country_id;
547
			} elseif ($reg[1] == 'USER_ID' || $reg[1] == 'USERID') {
548
				$newout = $user->id;
549
			} elseif ($reg[1] == 'USER_SUPERVISOR_ID' || $reg[1] == 'SUPERVISOR_ID' || $reg[1] == 'SUPERVISORID') {
550
				$newout = $user->fk_user;
551
			} elseif ($reg[1] == 'ENTITY_ID' || $reg[1] == 'ENTITYID') {
552
				$newout = $conf->entity;
553
			} else {
554
				$newout = ''; // Key not found, we replace with empty string
555
			}
556
			//var_dump('__'.$reg[1].'__ -> '.$newout);
557
			$out = preg_replace('/__'.preg_quote($reg[1], '/').'__/', $newout, $out);
558
		}
559
	}
560
561
	// Check rule
562
	if (preg_match('/^array/', $check)) {	// If 'array' or 'array:restricthtml' or 'array:aZ09'
563
		if (!is_array($out) || empty($out)) {
564
			$out = array();
565
		} else {
566
			$tmparray = explode(':', $check);
567
			if (!empty($tmparray[1])) {
568
				$tmpcheck = $tmparray[1];
569
			} else {
570
				$tmpcheck = 'alphanohtml';
571
			}
572
			foreach ($out as $outkey => $outval) {
573
				$out[$outkey] = checkVal($outval, $tmpcheck, $filter, $options);
574
			}
575
		}
576
	}
577
	else {
578
		$out = checkVal($out, $check, $filter, $options);
579
	}
580
581
	// Sanitizing for special parameters. There is no reason to allow the backtopage parameter to contains an external URL.
582
	if ($paramname == 'backtopage') {
583
		$out = str_replace('\\', '/', $out);
584
		$out = preg_replace(array('/^\/\/+/', '/^[a-z]*:/i'), '', $out);
585
	}
586
587
	// Code for search criteria persistence.
588
	// Save data into session if key start with 'search_' or is 'smonth', 'syear', 'month', 'year'
589
	if (empty($method) || $method == 3 || $method == 4)
590
	{
591
		if (preg_match('/^search_/', $paramname) || in_array($paramname, array('sortorder', 'sortfield')))
592
		{
593
			//var_dump($paramname.' - '.$out.' '.$user->default_values[$relativepathstring]['filters'][$paramname]);
594
595
			// We save search key only if $out not empty that means:
596
			// - posted value not empty, or
597
			// - 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).
598
599
			if ($out != '')		// $out = '0' or 'abc', it is a search criteria to keep
600
			{
601
				$user->lastsearch_values_tmp[$relativepathstring][$paramname] = $out;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $relativepathstring does not seem to be defined for all execution paths leading up to this point.
Loading history...
602
			}
603
		}
604
	}
605
606
	return $out;
607
}
608
609
/**
610
 *  Return value of a param into GET or POST supervariable.
611
 *  Use the property $user->default_values[path]['creatform'] and/or $user->default_values[path]['filters'] and/or $user->default_values[path]['sortorder']
612
 *  Note: The property $user->default_values is loaded by main.php when loading the user.
613
 *
614
 *  @param  string  $paramname   Name of parameter to found
615
 *  @param	int		$method	     Type of method (0 = get then post, 1 = only get, 2 = only post, 3 = post then get)
616
 *  @param  int     $filter      Filter to apply when $check is set to 'custom'. (See http://php.net/manual/en/filter.filters.php for détails)
617
 *  @param  mixed   $options     Options to pass to filter_var when $check is set to 'custom'
618
 *  @param	string	$noreplace   Force disable of replacement of __xxx__ strings.
619
 *  @return int                  Value found (int)
620
 */
621
function GETPOSTINT($paramname, $method = 0, $filter = null, $options = null, $noreplace = 0)
622
{
623
	return (int) GETPOST($paramname, 'int', $method, $filter, $options, $noreplace);
624
}
625
626
/**
627
 *  Return a value after checking on a rule.
628
 *
629
 *  @param  string  $out	     Value to get/check
630
 *  @param  string  $check	     Type of check
631
 *  @param  int     $filter      Filter to apply when $check is set to 'custom'. (See http://php.net/manual/en/filter.filters.php for détails)
632
 *  @param  mixed   $options     Options to pass to filter_var when $check is set to 'custom'
633
 *  @return string|array         Value found (string or array), or '' if check fails
634
 */
635
function checkVal($out = '', $check = 'alphanohtml', $filter = null, $options = null)
636
{
637
	// Check is done after replacement
638
	switch ($check)
639
	{
640
		case 'none':
641
			break;
642
		case 'int':    // Check param is a numeric value (integer but also float or hexadecimal)
643
			if (!is_numeric($out)) { $out = ''; }
644
			break;
645
		case 'intcomma':
646
			if (preg_match('/[^0-9,-]+/i', $out)) $out = '';
647
			break;
648
		case 'san_alpha':
649
			$out = filter_var($out, FILTER_SANITIZE_STRING);
650
			break;
651
		case 'email':
652
			$out = filter_var($out, FILTER_SANITIZE_EMAIL);
653
			break;
654
		case 'aZ':
655
			if (!is_array($out))
1 ignored issue
show
introduced by
The condition is_array($out) is always false.
Loading history...
656
			{
657
				$out = trim($out);
658
				if (preg_match('/[^a-z]+/i', $out)) $out = '';
659
			}
660
			break;
661
		case 'aZ09':
662
			if (!is_array($out))
1 ignored issue
show
introduced by
The condition is_array($out) is always false.
Loading history...
663
			{
664
				$out = trim($out);
665
				if (preg_match('/[^a-z0-9_\-\.]+/i', $out)) $out = '';
666
			}
667
			break;
668
		case 'aZ09comma':		// great to sanitize sortfield or sortorder params that can be t.abc,t.def_gh
669
			if (!is_array($out))
1 ignored issue
show
introduced by
The condition is_array($out) is always false.
Loading history...
670
			{
671
				$out = trim($out);
672
				if (preg_match('/[^a-z0-9_\-\.,]+/i', $out)) $out = '';
673
			}
674
			break;
675
		case 'nohtml':
676
			$out = dol_string_nohtmltag($out, 0);
677
			break;
678
		case 'alpha':		// No html and no " and no ../
679
		case 'alphanohtml':	// Recommended for most scalar parameters and search parameters
680
			if (!is_array($out)) {
1 ignored issue
show
introduced by
The condition is_array($out) is always false.
Loading history...
681
				// '"' is dangerous because param in url can close the href= or src= and add javascript functions.
682
				// '../' is dangerous because it allows dir transversals
683
				$out = str_replace(array('&quot;', '"', '../'), '', trim($out));
684
				// keep lines feed
685
				$out = dol_string_nohtmltag($out, 0);
686
			}
687
			break;
688
		case 'restricthtml':		// Recommended for most html textarea
689
			$out = dol_string_onlythesehtmltags($out, 0, 1, 1);
690
			break;
691
		case 'custom':
692
			if (empty($filter)) return 'BadFourthParameterForGETPOST';
693
			$out = filter_var($out, $filter, $options);
694
			break;
695
	}
696
697
	return $out;
698
}
699
700
701
702
if (!function_exists('dol_getprefix'))
703
{
704
	/**
705
	 *  Return a prefix to use for this Dolibarr instance, for session/cookie names or email id.
706
	 *  The prefix is unique for instance and avoid conflict between multi-instances, even when having two instances with same root dir
707
	 *  or two instances in same virtual servers.
708
	 *
709
	 *  @param  string  $mode                   '' (prefix for session name) or 'email' (prefix for email id)
710
	 *  @return	string                          A calculated prefix
711
	 */
712
	function dol_getprefix($mode = '')
713
	{
714
		// If prefix is for email (we need to have $conf alreayd loaded for this case)
715
		if ($mode == 'email')
716
		{
717
			global $conf;
718
719
			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)
720
			{
721
				if ($conf->global->MAIL_PREFIX_FOR_EMAIL_ID != 'SERVER_NAME') return $conf->global->MAIL_PREFIX_FOR_EMAIL_ID;
722
				elseif (isset($_SERVER["SERVER_NAME"])) return $_SERVER["SERVER_NAME"];
723
			}
724
725
			// The recommended value (may be not defined for old versions)
726
			if (!empty($conf->file->instance_unique_id)) return $conf->file->instance_unique_id;
727
728
			// For backward compatibility
729
			return dol_hash(DOL_DOCUMENT_ROOT.DOL_URL_ROOT, '3');
730
		}
731
732
		// If prefix is for session (no need to have $conf loaded)
733
		global $dolibarr_main_instance_unique_id, $dolibarr_main_cookie_cryptkey;	// This is loaded by filefunc.inc.php
734
		$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
735
736
		// The recommended value (may be not defined for old versions)
737
		if (!empty($tmp_instance_unique_id)) {
738
			return $tmp_instance_unique_id;
739
		}
740
741
		// For backward compatibility
742
		if (isset($_SERVER["SERVER_NAME"]) && isset($_SERVER["DOCUMENT_ROOT"])) {
743
			return dol_hash($_SERVER["SERVER_NAME"].$_SERVER["DOCUMENT_ROOT"].DOL_DOCUMENT_ROOT.DOL_URL_ROOT, '3');
744
		}
745
746
		return dol_hash(DOL_DOCUMENT_ROOT.DOL_URL_ROOT, '3');
747
	}
748
}
749
750
/**
751
 *	Make an include_once using default root and alternate root if it fails.
752
 *  To link to a core file, use include(DOL_DOCUMENT_ROOT.'/pathtofile')
753
 *  To link to a module file from a module file, use include './mymodulefile';
754
 *  To link to a module file from a core file, then this function can be used (call by hook / trigger / speciales pages)
755
 *
756
 * 	@param	string	$relpath	Relative path to file (Ie: mydir/myfile, ../myfile, ...)
757
 * 	@param	string	$classname	Class name (deprecated)
758
 *  @return bool                True if load is a success, False if it fails
759
 */
760
function dol_include_once($relpath, $classname = '')
761
{
762
	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']
763
764
	$fullpath = dol_buildpath($relpath);
765
766
	if (!file_exists($fullpath)) {
767
		dol_syslog('functions::dol_include_once Tried to load unexisting file: '.$relpath, LOG_WARNING);
768
		return false;
769
	}
770
771
	if (!empty($classname) && !class_exists($classname)) {
772
		return include $fullpath;
773
	} else {
774
		return include_once $fullpath;
775
	}
776
}
777
778
779
/**
780
 *	Return path of url or filesystem. Can check into alternate dir or alternate dir + main dir depending on value of $returnemptyifnotfound.
781
 *
782
 * 	@param	string	$path						Relative path to file (if mode=0) or relative url (if mode=1). Ie: mydir/myfile, ../myfile
783
 *  @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)
784
 *  @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)
785
 *  											1:If $type==0 and if file was not found into alternate dir, return empty string
786
 *  											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
787
 *  @return string								Full filesystem path (if path=0) or '' if file not found, Full url path (if mode=1)
788
 */
789
function dol_buildpath($path, $type = 0, $returnemptyifnotfound = 0)
790
{
791
	global $conf;
792
793
	$path = preg_replace('/^\//', '', $path);
794
795
	if (empty($type))	// For a filesystem path
796
	{
797
		$res = DOL_DOCUMENT_ROOT.'/'.$path; // Standard default path
798
		if (is_array($conf->file->dol_document_root))
799
		{
800
			foreach ($conf->file->dol_document_root as $key => $dirroot)	// ex: array("main"=>"/home/main/htdocs", "alt0"=>"/home/dirmod/htdocs", ...)
801
			{
802
				if ($key == 'main')
803
				{
804
					continue;
805
				}
806
				if (file_exists($dirroot.'/'.$path))
807
				{
808
					$res = $dirroot.'/'.$path;
809
					return $res;
810
				}
811
			}
812
		}
813
		if ($returnemptyifnotfound)								// Not found into alternate dir
814
		{
815
			if ($returnemptyifnotfound == 1 || !file_exists($res)) return '';
816
		}
817
	} else // For an url path
818
	{
819
		// We try to get local path of file on filesystem from url
820
		// Note that trying to know if a file on disk exist by forging path on disk from url
821
		// works only for some web server and some setup. This is bugged when
822
		// using proxy, rewriting, virtual path, etc...
823
		$res = '';
824
		if ($type == 1) $res = DOL_URL_ROOT.'/'.$path; // Standard value
825
		if ($type == 2) $res = DOL_MAIN_URL_ROOT.'/'.$path; // Standard value
826
		if ($type == 3) $res = DOL_URL_ROOT.'/'.$path;
827
828
		foreach ($conf->file->dol_document_root as $key => $dirroot)	// ex: array(["main"]=>"/home/main/htdocs", ["alt0"]=>"/home/dirmod/htdocs", ...)
829
		{
830
			if ($key == 'main')
831
			{
832
				if ($type == 3)
833
				{
834
					global $dolibarr_main_url_root;
835
836
					// Define $urlwithroot
837
					$urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
838
					$urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file
839
					//$urlwithroot=DOL_MAIN_URL_ROOT;					// This is to use same domain name than current
840
841
					$res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : $urlwithroot).'/'.$path; // Test on start with http is for old conf syntax
842
				}
843
				continue;
844
			}
845
			preg_match('/^([^\?]+(\.css\.php|\.css|\.js\.php|\.js|\.png|\.jpg|\.php)?)/i', $path, $regs); // Take part before '?'
846
			if (!empty($regs[1]))
847
			{
848
				//print $key.'-'.$dirroot.'/'.$path.'-'.$conf->file->dol_url_root[$type].'<br>'."\n";
849
				if (file_exists($dirroot.'/'.$regs[1]))
850
				{
851
					if ($type == 1)
852
					{
853
						$res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_URL_ROOT).$conf->file->dol_url_root[$key].'/'.$path;
854
					}
855
					if ($type == 2)
856
					{
857
						$res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_MAIN_URL_ROOT).$conf->file->dol_url_root[$key].'/'.$path;
858
					}
859
					if ($type == 3)
860
					{
861
						global $dolibarr_main_url_root;
862
863
						// Define $urlwithroot
864
						$urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
865
						$urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file
866
						//$urlwithroot=DOL_MAIN_URL_ROOT;					// This is to use same domain name than current
867
868
						$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
869
					}
870
					break;
871
				}
872
			}
873
		}
874
	}
875
876
	return $res;
877
}
878
879
/**
880
 *	Create a clone of instance of object (new instance with same value for properties)
881
 *  With native = 0: Property that are reference are also new object (full isolation clone). This means $this->db of new object is not valid.
882
 *  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.
883
 *
884
 * 	@param	object	$object		Object to clone
885
 *  @param	int		$native		0=Full isolation method, 1=Native PHP method
886
 *	@return object				Clone object
887
 *  @see https://php.net/manual/language.oop5.cloning.php
888
 */
889
function dol_clone($object, $native = 0)
890
{
891
	if (empty($native))
892
	{
893
		$myclone = unserialize(serialize($object));
894
	} else {
895
		$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)
896
	}
897
898
	return $myclone;
899
}
900
901
/**
902
 *	Optimize a size for some browsers (phone, smarphone, ...)
903
 *
904
 * 	@param	int		$size		Size we want
905
 * 	@param	string	$type		Type of optimizing:
906
 * 								'' = function used to define a size for truncation
907
 * 								'width' = function is used to define a width
908
 *	@return int					New size after optimizing
909
 */
910
function dol_size($size, $type = '')
911
{
912
	global $conf;
913
	if (empty($conf->dol_optimize_smallscreen)) return $size;
914
	if ($type == 'width' && $size > 250) return 250;
915
	else return 10;
916
}
917
918
919
/**
920
 *	Clean a string to use it as a file name
921
 *
922
 *	@param	string	$str            String to clean
923
 * 	@param	string	$newstr			String to replace bad chars with
924
 *  @param	int	    $unaccent		1=Remove also accent (default), 0 do not remove them
925
 *	@return string          		String cleaned (a-zA-Z_)
926
 *
927
 * 	@see        	dol_string_nospecial(), dol_string_unaccent(), dol_sanitizePathName()
928
 */
929
function dol_sanitizeFileName($str, $newstr = '_', $unaccent = 1)
930
{
931
	// List of special chars for filenames in windows are defined on page https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
932
	// Char '>' '<' '|' '$' and ';' are special chars for shells.
933
	// Char '/' and '\' are file delimiters.
934
	// -- 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
935
	$filesystem_forbidden_chars = array('<', '>', '/', '\\', '?', '*', '|', '"', ':', '°', '$', ';', '--');
936
	return dol_string_nospecial($unaccent ? dol_string_unaccent($str) : $str, $newstr, $filesystem_forbidden_chars);
937
}
938
939
/**
940
 *	Clean a string to use it as a path name
941
 *
942
 *	@param	string	$str            String to clean
943
 * 	@param	string	$newstr			String to replace bad chars with
944
 *  @param	int	    $unaccent		1=Remove also accent (default), 0 do not remove them
945
 *	@return string          		String cleaned (a-zA-Z_)
946
 *
947
 * 	@see        	dol_string_nospecial(), dol_string_unaccent(), dol_sanitizeFileName()
948
 */
949
function dol_sanitizePathName($str, $newstr = '_', $unaccent = 1)
950
{
951
	$filesystem_forbidden_chars = array('<', '>', '?', '*', '|', '"', '°');
952
	return dol_string_nospecial($unaccent ? dol_string_unaccent($str) : $str, $newstr, $filesystem_forbidden_chars);
953
}
954
955
/**
956
 *	Clean a string from all accent characters to be used as ref, login or by dol_sanitizeFileName
957
 *
958
 *	@param	string	$str			String to clean
959
 *	@return string   	       		Cleaned string
960
 *
961
 * 	@see    		dol_sanitizeFilename(), dol_string_nospecial()
962
 */
963
function dol_string_unaccent($str)
964
{
965
	if (utf8_check($str))
966
	{
967
		// See http://www.utf8-chartable.de/
968
		$string = rawurlencode($str);
969
		$replacements = array(
970
		'%C3%80' => 'A', '%C3%81' => 'A', '%C3%82' => 'A', '%C3%83' => 'A', '%C3%84' => 'A', '%C3%85' => 'A',
971
		'%C3%88' => 'E', '%C3%89' => 'E', '%C3%8A' => 'E', '%C3%8B' => 'E',
972
		'%C3%8C' => 'I', '%C3%8D' => 'I', '%C3%8E' => 'I', '%C3%8F' => 'I',
973
		'%C3%92' => 'O', '%C3%93' => 'O', '%C3%94' => 'O', '%C3%95' => 'O', '%C3%96' => 'O',
974
		'%C3%99' => 'U', '%C3%9A' => 'U', '%C3%9B' => 'U', '%C3%9C' => 'U',
975
		'%C3%A0' => 'a', '%C3%A1' => 'a', '%C3%A2' => 'a', '%C3%A3' => 'a', '%C3%A4' => 'a', '%C3%A5' => 'a',
976
		'%C3%A7' => 'c',
977
		'%C3%A8' => 'e', '%C3%A9' => 'e', '%C3%AA' => 'e', '%C3%AB' => 'e',
978
		'%C3%AC' => 'i', '%C3%AD' => 'i', '%C3%AE' => 'i', '%C3%AF' => 'i',
979
		'%C3%B1' => 'n',
980
		'%C3%B2' => 'o', '%C3%B3' => 'o', '%C3%B4' => 'o', '%C3%B5' => 'o', '%C3%B6' => 'o',
981
		'%C3%B9' => 'u', '%C3%BA' => 'u', '%C3%BB' => 'u', '%C3%BC' => 'u',
982
		'%C3%BF' => 'y'
983
		);
984
		$string = strtr($string, $replacements);
985
		return rawurldecode($string);
986
	} else {
987
		// See http://www.ascii-code.com/
988
		$string = strtr(
989
			$str,
990
			"\xC0\xC1\xC2\xC3\xC4\xC5\xC7
991
			\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1
992
			\xD2\xD3\xD4\xD5\xD8\xD9\xDA\xDB\xDD
993
			\xE0\xE1\xE2\xE3\xE4\xE5\xE7\xE8\xE9\xEA\xEB
994
			\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF8
995
			\xF9\xFA\xFB\xFC\xFD\xFF",
996
			"AAAAAAC
997
			EEEEIIIIDN
998
			OOOOOUUUY
999
			aaaaaaceeee
1000
			iiiidnooooo
1001
			uuuuyy"
1002
		);
1003
		$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"));
1004
		return $string;
1005
	}
1006
}
1007
1008
/**
1009
 *	Clean a string from all punctuation characters to use it as a ref or login.
1010
 *  This is a more complete function than dol_sanitizeFileName.
1011
 *
1012
 *	@param	string	$str            	String to clean
1013
 * 	@param	string	$newstr				String to replace forbidden chars with
1014
 *  @param  array	$badcharstoreplace  List of forbidden characters
1015
 * 	@return string          			Cleaned string
1016
 *
1017
 * 	@see    		dol_sanitizeFilename(), dol_string_unaccent(), dol_string_nounprintableascii()
1018
 */
1019
function dol_string_nospecial($str, $newstr = '_', $badcharstoreplace = '')
1020
{
1021
	$forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ",", ";", "=", '°'); // more complete than dol_sanitizeFileName
1022
	$forbidden_chars_to_remove = array();
1023
	if (is_array($badcharstoreplace)) $forbidden_chars_to_replace = $badcharstoreplace;
1024
	//$forbidden_chars_to_remove=array("(",")");
1025
1026
	return str_replace($forbidden_chars_to_replace, $newstr, str_replace($forbidden_chars_to_remove, "", $str));
1027
}
1028
1029
1030
/**
1031
 *	Clean a string from all non printable ASCII chars (0x00-0x1F and 0x7F). It can also removes also Tab-CR-LF. UTF8 chars remains.
1032
 *  This can be used to sanitize a string and view its real content. Some hacks try to obfuscate attacks by inserting non printable chars.
1033
 *  Note, for information: UTF8 on 1 byte are: \x00-\7F
1034
 *                                 2 bytes are: byte 1 \xc0-\xdf, byte 2 = \x80-\xbf
1035
 *                                 3 bytes are: byte 1 \xe0-\xef, byte 2 = \x80-\xbf, byte 3 = \x80-\xbf
1036
 *                                 4 bytes are: byte 1 \xf0-\xf7, byte 2 = \x80-\xbf, byte 3 = \x80-\xbf, byte 4 = \x80-\xbf
1037
 *	@param	string	$str            	String to clean
1038
 *  @param	int		$removetabcrlf		Remove also CR-LF
1039
 * 	@return string          			Cleaned string
1040
 *
1041
 * 	@see    		dol_sanitizeFilename(), dol_string_unaccent(), dol_string_nospecial()
1042
 */
1043
function dol_string_nounprintableascii($str, $removetabcrlf = 1)
1044
{
1045
	if ($removetabcrlf) {
1046
		return preg_replace('/[\x00-\x1F\x7F]/u', '', $str); // /u operator makes UTF8 valid characters being ignored so are not included into the replace
1047
	} else {
1048
		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
1049
	}
1050
}
1051
1052
1053
/**
1054
 *  Returns text escaped for inclusion into javascript code
1055
 *
1056
 *  @param      string		$stringtoescape		String to escape
1057
 *  @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 \
1058
 *  @param		int		$noescapebackslashn	0=Escape also \n. 1=Do not escape \n.
1059
 *  @return     string     		 				Escaped string. Both ' and " are escaped into ' if they are escaped.
1060
 */
1061
function dol_escape_js($stringtoescape, $mode = 0, $noescapebackslashn = 0)
1062
{
1063
	// escape quotes and backslashes, newlines, etc.
1064
	$substitjs = array("&#039;"=>"\\'", "\r"=>'\\r');
1065
	//$substitjs['</']='<\/';	// We removed this. Should be useless.
1066
	if (empty($noescapebackslashn)) { $substitjs["\n"] = '\\n'; $substitjs['\\'] = '\\\\'; }
1067
	if (empty($mode)) { $substitjs["'"] = "\\'"; $substitjs['"'] = "\\'"; } elseif ($mode == 1) $substitjs["'"] = "\\'";
1068
	elseif ($mode == 2) { $substitjs['"'] = '\\"'; } elseif ($mode == 3) { $substitjs["'"] = "\\'"; $substitjs['"'] = "\\\""; }
1069
	return strtr($stringtoescape, $substitjs);
1070
}
1071
1072
/**
1073
 *  Returns text escaped for inclusion into javascript code
1074
 *
1075
 *  @param      string		$stringtoescape		String to escape
1076
 *  @return     string     		 				Escaped string for json content.
1077
 */
1078
function dol_escape_json($stringtoescape)
1079
{
1080
	return str_replace('"', '\"', $stringtoescape);
1081
}
1082
1083
/**
1084
 *  Returns text escaped for inclusion in HTML alt or title tags, or into values of HTML input fields.
1085
 *
1086
 *  @param      string		$stringtoescape			String to escape
1087
 *  @param		int			$keepb					1=Keep b tags and escape them, 0=remove them
1088
 *  @param      int         $keepn              	1=Preserve \r\n strings (otherwise, replace them with escaped value). Set to 1 when escaping for a <textarea>.
1089
 *  @param		string		$keepmoretags			'' or 'common' or list of tags
1090
 *  @param		int			$escapeonlyhtmltags		1=Escape only html tags, not the special chars like accents.
1091
 *  @return     string     				 			Escaped string
1092
 *  @see		dol_string_nohtmltag(), dol_string_nospecial(), dol_string_unaccent()
1093
 */
1094
function dol_escape_htmltag($stringtoescape, $keepb = 0, $keepn = 0, $keepmoretags = '', $escapeonlyhtmltags = 0)
1095
{
1096
	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';
1097
	// TODO Implement $keepmoretags
1098
1099
	// escape quotes and backslashes, newlines, etc.
1100
	if ($escapeonlyhtmltags) {
1101
		$tmp = htmlspecialchars_decode($stringtoescape, ENT_COMPAT);
1102
	} else {
1103
		$tmp = html_entity_decode($stringtoescape, ENT_COMPAT, 'UTF-8');
1104
	}
1105
	if (!$keepb) $tmp = strtr($tmp, array("<b>"=>'', '</b>'=>''));
1106
	if (!$keepn) $tmp = strtr($tmp, array("\r"=>'\\r', "\n"=>'\\n'));
1107
	if ($escapeonlyhtmltags) {
1108
		return htmlspecialchars($tmp, ENT_COMPAT, 'UTF-8');
1109
	} else {
1110
		return htmlentities($tmp, ENT_COMPAT, 'UTF-8');
1111
	}
1112
}
1113
1114
/**
1115
 * Convert a string to lower. Never use strtolower because it does not works with UTF8 strings.
1116
 *
1117
 * @param 	string		$string		        String to encode
1118
 * @param   string      $encoding           Character set encoding
1119
 * @return 	string							String converted
1120
 */
1121
function dol_strtolower($string, $encoding = "UTF-8")
1122
{
1123
	if (function_exists('mb_strtolower')) {
1124
		return mb_strtolower($string, $encoding);
1125
	} else {
1126
		return strtolower($string);
1127
	}
1128
}
1129
1130
/**
1131
 * Convert a string to upper. Never use strtolower because it does not works with UTF8 strings.
1132
 *
1133
 * @param 	string		$string		        String to encode
1134
 * @param   string      $encoding           Character set encoding
1135
 * @return 	string							String converted
1136
 */
1137
function dol_strtoupper($string, $encoding = "UTF-8")
1138
{
1139
	if (function_exists('mb_strtoupper')) {
1140
		return mb_strtoupper($string, $encoding);
1141
	} else {
1142
		return strtoupper($string);
1143
	}
1144
}
1145
1146
/**
1147
 * Convert first character of the first word of a string to upper. Never use ucfirst because it does not works with UTF8 strings.
1148
 *
1149
 * @param   string      $string         String to encode
1150
 * @param   string      $encoding       Character set encodign
1151
 * @return  string                      String converted
1152
 */
1153
function dol_ucfirst($string, $encoding = "UTF-8")
1154
{
1155
	if (function_exists('mb_substr')) {
1156
		return mb_strtoupper(mb_substr($string, 0, 1, $encoding), $encoding).mb_substr($string, 1, null, $encoding);
1157
	} else {
1158
		return ucfirst($string);
1159
	}
1160
}
1161
1162
/**
1163
 * Convert first character of all the words of a string to upper. Never use ucfirst because it does not works with UTF8 strings.
1164
 *
1165
 * @param   string      $string         String to encode
1166
 * @param   string      $encoding       Character set encodign
1167
 * @return  string                      String converted
1168
 */
1169
function dol_ucwords($string, $encoding = "UTF-8")
1170
{
1171
	if (function_exists('mb_convert_case')) {
1172
		return mb_convert_case($string, MB_CASE_TITLE, $encoding);
1173
	} else {
1174
		return ucwords($string);
1175
	}
1176
}
1177
1178
/**
1179
 *	Write log message into outputs. Possible outputs can be:
1180
 *	SYSLOG_HANDLERS = ["mod_syslog_file"]  		file name is then defined by SYSLOG_FILE
1181
 *	SYSLOG_HANDLERS = ["mod_syslog_syslog"]  	facility is then defined by SYSLOG_FACILITY
1182
 *  Warning, syslog functions are bugged on Windows, generating memory protection faults. To solve
1183
 *  this, use logging to files instead of syslog (see setup of module).
1184
 *  Note: If constant 'SYSLOG_FILE_NO_ERROR' defined, we never output any error message when writing to log fails.
1185
 *  Note: You can get log message into html sources by adding parameter &logtohtml=1 (constant MAIN_LOGTOHTML must be set)
1186
 *  This function works only if syslog module is enabled.
1187
 * 	This must not use any call to other function calling dol_syslog (avoid infinite loop).
1188
 *
1189
 * 	@param  string		$message				Line to log. ''=Show nothing
1190
 *  @param  int			$level					Log level
1191
 *												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
1192
 *												On Linux   LOG_ERR=3, LOG_WARNING=4, LOG_INFO=6, LOG_DEBUG=7
1193
 *  @param	int			$ident					1=Increase ident of 1, -1=Decrease ident of 1
1194
 *  @param	string		$suffixinfilename		When output is a file, append this suffix into default log filename.
1195
 *  @param	string		$restricttologhandler	Force output of log only to this log handler
1196
 *  @param	array|null	$logcontext				If defined, an array with extra informations (can be used by some log handlers)
1197
 *  @return	void
1198
 */
1199
function dol_syslog($message, $level = LOG_INFO, $ident = 0, $suffixinfilename = '', $restricttologhandler = '', $logcontext = null)
1200
{
1201
	global $conf, $user, $debugbar;
1202
1203
	// If syslog module enabled
1204
	if (empty($conf->syslog->enabled)) return;
1205
1206
	// Check if we are into execution of code of a website
1207
	if (defined('USEEXTERNALSERVER') && !defined('USEDOLIBARRSERVER') && !defined('USEDOLIBARREDITOR')) {
1208
		global $website, $websitekey;
1209
		if (is_object($website) && !empty($website->ref)) $suffixinfilename .= '_website_'.$website->ref;
1210
		elseif (!empty($websitekey)) $suffixinfilename .= '_website_'.$websitekey;
1211
	}
1212
1213
	if ($ident < 0)
1214
	{
1215
		foreach ($conf->loghandlers as $loghandlerinstance)
1216
		{
1217
			$loghandlerinstance->setIdent($ident);
1218
		}
1219
	}
1220
1221
	if (!empty($message))
1222
	{
1223
		// Test log level
1224
		$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');
1225
		if (!array_key_exists($level, $logLevels))
1226
		{
1227
			throw new Exception('Incorrect log level');
1228
		}
1229
		if ($level > $conf->global->SYSLOG_LEVEL) return;
1230
1231
		$message = preg_replace('/password=\'[^\']*\'/', 'password=\'hidden\'', $message); // protection to avoid to have value of password in log
1232
1233
		// If adding log inside HTML page is required
1234
		if ((!empty($_REQUEST['logtohtml']) && !empty($conf->global->MAIN_ENABLE_LOG_TO_HTML))
1235
			|| (!empty($user->rights->debugbar->read) && is_object($debugbar)))
1236
		{
1237
			$conf->logbuffer[] = dol_print_date(time(), "%Y-%m-%d %H:%M:%S")." ".$logLevels[$level]." ".$message;
1238
		}
1239
1240
		//TODO: Remove this. MAIN_ENABLE_LOG_INLINE_HTML should be deprecated and use a log handler dedicated to HTML output
1241
		// If html log tag enabled and url parameter log defined, we show output log on HTML comments
1242
		if (!empty($conf->global->MAIN_ENABLE_LOG_INLINE_HTML) && !empty($_GET["log"]))
1243
		{
1244
			print "\n\n<!-- Log start\n";
1245
			print $message."\n";
1246
			print "Log end -->\n";
1247
		}
1248
1249
		$data = array(
1250
			'message' => $message,
1251
			'script' => (isset($_SERVER['PHP_SELF']) ? basename($_SERVER['PHP_SELF'], '.php') : false),
1252
			'level' => $level,
1253
			'user' => ((is_object($user) && $user->id) ? $user->login : false),
1254
			'ip' => false
1255
		);
1256
1257
		$remoteip = getUserRemoteIP(); // Get ip when page run on a web server
1258
		if (!empty($remoteip)) {
1259
			$data['ip'] = $remoteip;
1260
			// This is when server run behind a reverse proxy
1261
			if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR'] != $remoteip) $data['ip'] = $_SERVER['HTTP_X_FORWARDED_FOR'].' -> '.$data['ip'];
1262
			elseif (!empty($_SERVER['HTTP_CLIENT_IP']) && $_SERVER['HTTP_CLIENT_IP'] != $remoteip) $data['ip'] = $_SERVER['HTTP_CLIENT_IP'].' -> '.$data['ip'];
1263
		} // This is when PHP session is ran inside a web server but not inside a client request (example: init code of apache)
1264
		elseif (!empty($_SERVER['SERVER_ADDR'])) $data['ip'] = $_SERVER['SERVER_ADDR'];
1265
		// 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).
1266
		elseif (!empty($_SERVER['COMPUTERNAME'])) $data['ip'] = $_SERVER['COMPUTERNAME'].(empty($_SERVER['USERNAME']) ? '' : '@'.$_SERVER['USERNAME']);
1267
		// 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).
1268
		elseif (!empty($_SERVER['LOGNAME'])) $data['ip'] = '???@'.$_SERVER['LOGNAME'];
1269
		// Loop on each log handler and send output
1270
		foreach ($conf->loghandlers as $loghandlerinstance)
1271
		{
1272
			if ($restricttologhandler && $loghandlerinstance->code != $restricttologhandler) continue;
1273
			$loghandlerinstance->export($data, $suffixinfilename);
1274
		}
1275
		unset($data);
1276
	}
1277
1278
	if ($ident > 0)
1279
	{
1280
		foreach ($conf->loghandlers as $loghandlerinstance)
1281
		{
1282
			$loghandlerinstance->setIdent($ident);
1283
		}
1284
	}
1285
}
1286
1287
/**
1288
 *	Return HTML code to output a button to open a dialog popup box.
1289
 *  Such buttons must be included inside a HTML form.
1290
 *
1291
 *	@param	string	$name				A name for the html component
1292
 *	@param	string	$label 	    		Label of button
1293
 *	@param  string	$buttonstring  		button string
1294
 *	@param  string	$url				Url to open
1295
 *  @param	string	$disabled			Disabled text
1296
 * 	@return	string						HTML component with button
1297
 */
1298
function dolButtonToOpenUrlInDialogPopup($name, $label, $buttonstring, $url, $disabled = '')
1299
{
1300
	if (strpos($url, '?') > 0) {
1301
		$url .= '&dol_hide_topmenu=1&dol_hide_leftmenu=1&dol_openinpopup=1';
1302
	} else {
1303
		$url .= '?dol_hide_menuinpopup=1&dol_hide_leftmenu=1&dol_openinpopup=1';
1304
	}
1305
1306
	//print '<input type="submit" class="button bordertransp"'.$disabled.' value="'.dol_escape_htmltag($langs->trans("MediaFiles")).'" name="file_manager">';
1307
	$out = '<a class="button bordertransp button_'.$name.'"'.$disabled.' title="'.dol_escape_htmltag($label).'">'.$buttonstring.'</a>';
1308
	$out .= '<script language="javascript">
1309
				 jQuery(document).ready(function () {
1310
					 jQuery(".button_'.$name.'").click(function () {
1311
						 var $dialog = $(\'<div></div>\').html(\'<iframe class="iframedialog" style="border: 0px;" src="'.DOL_URL_ROOT.$url.'" width="100%" height="98%"></iframe>\')
1312
						 .dialog({
1313
						 	autoOpen: false,
1314
						 	modal: true,
1315
						 	height: (window.innerHeight - 150),
1316
						 	width: \'80%\',
1317
						 	title: "'.dol_escape_js($label).'"
1318
						 });
1319
						 $dialog.dialog(\'open\');
1320
					 });
1321
				 });
1322
				 </script>';
1323
	return $out;
1324
}
1325
1326
/**
1327
 *	Show tab header of a card
1328
 *
1329
 *	@param	array	$links				Array of tabs. Currently initialized by calling a function xxx_admin_prepare_head
1330
 *	@param	string	$active     		Active tab name (document', 'info', 'ldap', ....)
1331
 *	@param  string	$title      		Title
1332
 *	@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)
1333
 * 	@param	string	$picto				Add a picto on tab title
1334
 *	@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.
1335
 *  @param	string	$morehtmlright		Add more html content on right of tabs title
1336
 *  @param	string	$morecss			More Css
1337
 *  @param	int		$limittoshow		Limit number of tabs to show. Use 0 to use automatic default value.
1338
 *  @param	string	$moretabssuffix		A suffix to use when you have several dol_get_fiche_head() in same page
1339
 * 	@return	void
1340
 *  @deprecated Use print dol_get_fiche_head() instead
1341
 */
1342
function dol_fiche_head($links = array(), $active = '0', $title = '', $notab = 0, $picto = '', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limittoshow = 0, $moretabssuffix = '')
1343
{
1344
	print dol_get_fiche_head($links, $active, $title, $notab, $picto, $pictoisfullpath, $morehtmlright, $morecss, $limittoshow, $moretabssuffix);
1345
}
1346
1347
/**
1348
 *  Show tabs of a record
1349
 *
1350
 *	@param	array	$links				Array of tabs
1351
 *	@param	string	$active     		Active tab name
1352
 *	@param  string	$title      		Title
1353
 *	@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)
1354
 * 	@param	string	$picto				Add a picto on tab title
1355
 *	@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.
1356
 *  @param	string	$morehtmlright		Add more html content on right of tabs title
1357
 *  @param	string	$morecss			More Css
1358
 *  @param	int		$limittoshow		Limit number of tabs to show. Use 0 to use automatic default value.
1359
 *  @param	string	$moretabssuffix		A suffix to use when you have several dol_get_fiche_head() in same page
1360
 * 	@return	string
1361
 */
1362
function dol_get_fiche_head($links = array(), $active = '', $title = '', $notab = 0, $picto = '', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limittoshow = 0, $moretabssuffix = '')
1363
{
1364
	global $conf, $langs, $hookmanager;
1365
1366
	// Show title
1367
	$showtitle = 1;
1368
	if (!empty($conf->dol_optimize_smallscreen)) $showtitle = 0;
1369
1370
	$out = "\n".'<!-- dol_fiche_head - dol_get_fiche_head -->';
1371
1372
	if ((!empty($title) && $showtitle) || $morehtmlright || !empty($links)) {
1373
		$out .= '<div class="tabs'.($picto ? '' : ' nopaddingleft').'" data-role="controlgroup" data-type="horizontal">'."\n";
1374
	}
1375
1376
	// Show right part
1377
	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.
1378
1379
	// Show title
1380
	if (!empty($title) && $showtitle && empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER))
1381
	{
1382
		$limittitle = 30;
1383
		$out .= '<a class="tabTitle">';
1384
		if ($picto) $out .= img_picto($title, ($pictoisfullpath ? '' : 'object_').$picto, '', $pictoisfullpath, 0, 0, '', 'imgTabTitle').' ';
1385
		$out .= '<span class="tabTitleText">'.dol_trunc($title, $limittitle).'</span>';
1386
		$out .= '</a>';
1387
	}
1388
1389
	// Show tabs
1390
1391
	// Define max of key (max may be higher than sizeof because of hole due to module disabling some tabs).
1392
	$maxkey = -1;
1393
	if (is_array($links) && !empty($links))
1394
	{
1395
		$keys = array_keys($links);
1396
		if (count($keys)) $maxkey = max($keys);
1397
	}
1398
1399
	// Show tabs
1400
	// if =0 we don't use the feature
1401
	if (empty($limittoshow)) {
1402
		$limittoshow = (empty($conf->global->MAIN_MAXTABS_IN_CARD) ? 99 : $conf->global->MAIN_MAXTABS_IN_CARD);
1403
	}
1404
	if (!empty($conf->dol_optimize_smallscreen)) $limittoshow = 2;
1405
1406
	$displaytab = 0;
1407
	$nbintab = 0;
1408
	$popuptab = 0;
1409
	$outmore = '';
1410
	for ($i = 0; $i <= $maxkey; $i++)
1411
	{
1412
		if ((is_numeric($active) && $i == $active) || (!empty($links[$i][2]) && !is_numeric($active) && $active == $links[$i][2])) {
1413
			// If active tab is already present
1414
			if ($i >= $limittoshow) $limittoshow--;
1415
		}
1416
	}
1417
1418
	for ($i = 0; $i <= $maxkey; $i++)
1419
	{
1420
		if ((is_numeric($active) && $i == $active) || (!empty($links[$i][2]) && !is_numeric($active) && $active == $links[$i][2])) {
1421
			$isactive = true;
1422
		} else {
1423
			$isactive = false;
1424
		}
1425
1426
		if ($i < $limittoshow || $isactive)
1427
		{
1428
			$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]).' -->';
1429
			if (isset($links[$i][2]) && $links[$i][2] == 'image')
1430
			{
1431
				if (!empty($links[$i][0]))
1432
				{
1433
					$out .= '<a class="tabimage'.($morecss ? ' '.$morecss : '').'" href="'.$links[$i][0].'">'.$links[$i][1].'</a>'."\n";
1434
				} else {
1435
					$out .= '<span class="tabspan">'.$links[$i][1].'</span>'."\n";
1436
				}
1437
			} elseif (!empty($links[$i][1]))
1438
			{
1439
				//print "x $i $active ".$links[$i][2]." z";
1440
				if ($isactive)
1441
				{
1442
					$out .= '<a'.(!empty($links[$i][2]) ? ' id="'.$links[$i][2].'"' : '').' class="tabactive tab inline-block'.($morecss ? ' '.$morecss : '').'" href="'.$links[$i][0].'">';
1443
					$out .= $links[$i][1];
1444
					$out .= '</a>'."\n";
1445
				} else {
1446
					$out .= '<a'.(!empty($links[$i][2]) ? ' id="'.$links[$i][2].'"' : '').' class="tabunactive tab inline-block'.($morecss ? ' '.$morecss : '').'" href="'.$links[$i][0].'">';
1447
					$out .= $links[$i][1];
1448
					$out .= '</a>'."\n";
1449
				}
1450
			}
1451
			$out .= '</div>';
1452
		} else {
1453
			// The popup with the other tabs
1454
			if (!$popuptab)
1455
			{
1456
				$popuptab = 1;
1457
				$outmore .= '<div class="popuptabset wordwrap">'; // The css used to hide/show popup
1458
			}
1459
			$outmore .= '<div class="popuptab wordwrap" style="display:inherit;">';
1460
			if (isset($links[$i][2]) && $links[$i][2] == 'image')
1461
			{
1462
				if (!empty($links[$i][0]))
1463
					$outmore .= '<a class="tabimage'.($morecss ? ' '.$morecss : '').'" href="'.$links[$i][0].'">'.$links[$i][1].'</a>'."\n";
1464
				else $outmore .= '<span class="tabspan">'.$links[$i][1].'</span>'."\n";
1465
			} elseif (!empty($links[$i][1]))
1466
			{
1467
				$outmore .= '<a'.(!empty($links[$i][2]) ? ' id="'.$links[$i][2].'"' : '').' class="wordwrap inline-block'.($morecss ? ' '.$morecss : '').'" href="'.$links[$i][0].'">';
1468
				$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.
1469
				$outmore .= '</a>'."\n";
1470
			}
1471
			$outmore .= '</div>';
1472
1473
			$nbintab++;
1474
		}
1475
		$displaytab = $i;
1476
	}
1477
	if ($popuptab) $outmore .= '</div>';
1478
1479
	if ($popuptab)	// If there is some tabs not shown
1480
	{
1481
		$left = ($langs->trans("DIRECTION") == 'rtl' ? 'right' : 'left');
1482
		$right = ($langs->trans("DIRECTION") == 'rtl' ? 'left' : 'right');
1483
		$widthofpopup = 200;
1484
1485
		$tabsname = $moretabssuffix;
1486
		if (empty($tabsname)) { $tabsname = str_replace("@", "", $picto); }
1487
		$out .= '<div id="moretabs'.$tabsname.'" class="inline-block tabsElem">';
1488
		$out .= '<a href="#" class="tab moretab inline-block tabunactive">'.$langs->trans("More").'... ('.$nbintab.')</a>'; // Do not use "reposition" class in the "More".
1489
		$out .= '<div id="moretabsList'.$tabsname.'" style="width: '.$widthofpopup.'px; position: absolute; '.$left.': -999em; text-align: '.$left.'; margin:0px; padding:2px; z-index:10;">';
1490
		$out .= $outmore;
1491
		$out .= '</div>';
1492
		$out .= '<div></div>';
1493
		$out .= "</div>\n";
1494
1495
		$out .= "<script>";
1496
		$out .= "$('#moretabs".$tabsname."').mouseenter( function() {
1497
			var x = this.offsetLeft, y = this.offsetTop;
1498
			console.log('mouseenter ".$left." x='+x+' y='+y+' window.innerWidth='+window.innerWidth);
1499
			if ((window.innerWidth - x) < ".($widthofpopup + 10).") {
1500
				$('#moretabsList".$tabsname."').css('".$right."','8px');
1501
			}
1502
			$('#moretabsList".$tabsname."').css('".$left."','auto');
1503
			});
1504
		";
1505
		$out .= "$('#moretabs".$tabsname."').mouseleave( function() { console.log('mouseleave ".$left."'); $('#moretabsList".$tabsname."').css('".$left."','-999em');});";
1506
		$out .= "</script>";
1507
	}
1508
1509
	if ((!empty($title) && $showtitle) || $morehtmlright || !empty($links)) {
1510
		$out .= "</div>\n";
1511
	}
1512
1513
	if (!$notab || $notab == -1 || $notab == -2) $out .= "\n".'<div class="tabBar'.($notab == -1 ? '' : ($notab == -2 ? ' tabBarNoTop' : ' tabBarWithBottom')).'">'."\n";
1514
1515
	$parameters = array('tabname' => $active, 'out' => $out);
1516
	$reshook = $hookmanager->executeHooks('printTabsHead', $parameters); // This hook usage is called just before output the head of tabs. Take also a look at "completeTabsHead"
1517
	if ($reshook > 0)
1518
	{
1519
		$out = $hookmanager->resPrint;
1520
	}
1521
1522
	return $out;
1523
}
1524
1525
/**
1526
 *  Show tab footer of a card
1527
 *
1528
 *  @param	int		$notab       -1 or 0=Add tab footer, 1=no tab footer
1529
 *  @return	void
1530
 *  @deprecated Use print dol_get_fiche_end() instead
1531
 */
1532
function dol_fiche_end($notab = 0)
1533
{
1534
	print dol_get_fiche_end($notab);
1535
}
1536
1537
/**
1538
 *	Return tab footer of a card
1539
 *
1540
 *	@param  int		$notab		-1 or 0=Add tab footer, 1=no tab footer
1541
 *  @return	string
1542
 */
1543
function dol_get_fiche_end($notab = 0)
1544
{
1545
	if (!$notab || $notab == -1) return "\n</div>\n";
1546
	else return '';
1547
}
1548
1549
/**
1550
 *  Show tab footer of a card.
1551
 *  Note: $object->next_prev_filter can be set to restrict select to find next or previous record by $form->showrefnav.
1552
 *
1553
 *  @param	Object	$object			Object to show
1554
 *  @param	string	$paramid   		Name of parameter to use to name the id into the URL next/previous link
1555
 *  @param	string	$morehtml  		More html content to output just before the nav bar
1556
 *  @param	int		$shownav	  	Show Condition (navigation is shown if value is 1)
1557
 *  @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.
1558
 *  @param	string	$fieldref   	Nom du champ objet ref (object->ref) a utiliser pour select next et previous
1559
 *  @param	string	$morehtmlref  	More html to show after ref
1560
 *  @param	string	$moreparam  	More param to add in nav link url.
1561
 *	@param	int		$nodbprefix		Do not include DB prefix to forge table name
1562
 *	@param	string	$morehtmlleft	More html code to show before ref
1563
 *	@param	string	$morehtmlstatus	More html code to show under navigation arrows
1564
 *  @param  int     $onlybanner     Put this to 1, if the card will contains only a banner (this add css 'arearefnobottom' on div)
1565
 *	@param	string	$morehtmlright	More html code to show before navigation arrows
1566
 *  @return	void
1567
 */
1568
function dol_banner_tab($object, $paramid, $morehtml = '', $shownav = 1, $fieldid = 'rowid', $fieldref = 'ref', $morehtmlref = '', $moreparam = '', $nodbprefix = 0, $morehtmlleft = '', $morehtmlstatus = '', $onlybanner = 0, $morehtmlright = '')
1569
{
1570
	global $conf, $form, $user, $langs;
1571
1572
	$error = 0;
1573
1574
	$maxvisiblephotos = 1;
1575
	$showimage = 1;
1576
	$entity = (empty($object->entity) ? $conf->entity : $object->entity);
1577
	$showbarcode = empty($conf->barcode->enabled) ? 0 : ($object->barcode ? 1 : 0);
1578
	if (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->barcode->lire_advance)) $showbarcode = 0;
1579
	$modulepart = 'unknown';
1580
1581
	if ($object->element == 'societe')         $modulepart = 'societe';
1582
	if ($object->element == 'contact')         $modulepart = 'contact';
1583
	if ($object->element == 'member')          $modulepart = 'memberphoto';
1584
	if ($object->element == 'user')            $modulepart = 'userphoto';
1585
	if ($object->element == 'product')         $modulepart = 'product';
1586
	if ($object->element == 'ticket')          $modulepart = 'ticket';
1587
1588
	if (class_exists("Imagick"))
1589
	{
1590
		if ($object->element == 'propal')            $modulepart = 'propal';
1591
		if ($object->element == 'commande')          $modulepart = 'commande';
1592
		if ($object->element == 'facture')           $modulepart = 'facture';
1593
		if ($object->element == 'fichinter')         $modulepart = 'ficheinter';
1594
		if ($object->element == 'contrat')           $modulepart = 'contract';
1595
		if ($object->element == 'supplier_proposal') $modulepart = 'supplier_proposal';
1596
		if ($object->element == 'order_supplier')    $modulepart = 'supplier_order';
1597
		if ($object->element == 'invoice_supplier')  $modulepart = 'supplier_invoice';
1598
		if ($object->element == 'expensereport')     $modulepart = 'expensereport';
1599
	}
1600
1601
	if ($object->element == 'product')
1602
	{
1603
		$width = 80; $cssclass = 'photoref';
1604
		$showimage = $object->is_photo_available($conf->product->multidir_output[$entity]);
1605
		$maxvisiblephotos = (isset($conf->global->PRODUCT_MAX_VISIBLE_PHOTO) ? $conf->global->PRODUCT_MAX_VISIBLE_PHOTO : 5);
1606
		if ($conf->browser->layout == 'phone') $maxvisiblephotos = 1;
1607
		if ($showimage) {
1608
			$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>';
1609
		} else {
1610
			if (!empty($conf->global->PRODUCT_NODISPLAYIFNOPHOTO)) {
1611
				$nophoto = '';
1612
				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
1613
			} else {    // Show no photo link
1614
				$nophoto = '/public/theme/common/nophoto.png';
1615
				$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>';
1616
			}
1617
		}
1618
	} elseif ($object->element == 'ticket') {
1619
		$width = 80; $cssclass = 'photoref';
1620
		$showimage = $object->is_photo_available($conf->ticket->multidir_output[$entity].'/'.$object->ref);
1621
		$maxvisiblephotos = (isset($conf->global->TICKET_MAX_VISIBLE_PHOTO) ? $conf->global->TICKET_MAX_VISIBLE_PHOTO : 2);
1622
		if ($conf->browser->layout == 'phone') $maxvisiblephotos = 1;
1623
1624
		if ($showimage)
1625
		{
1626
			$showphoto = $object->show_photos('ticket', $conf->ticket->multidir_output[$entity], 'small', $maxvisiblephotos, 0, 0, 0, $width, 0);
1627
			if ($object->nbphoto > 0)
1628
			{
1629
				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">'.$showphoto.'</div>';
1630
			} else {
1631
				$showimage = 0;
1632
			}
1633
		}
1634
		if (!$showimage)
1635
		{
1636
			if (!empty($conf->global->TICKET_NODISPLAYIFNOPHOTO)) {
1637
				$nophoto = '';
1638
				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
1639
			} else {    // Show no photo link
1640
				$nophoto = img_picto('No photo', 'object_ticket');
1641
				$morehtmlleft .= '<!-- No photo to show -->';
1642
				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
1643
				$morehtmlleft .= $nophoto;
1644
				$morehtmlleft .= '</div></div>';
1645
			}
1646
		}
1647
	} else {
1648
		if ($showimage)
1649
		{
1650
			if ($modulepart != 'unknown')
1651
			{
1652
				$phototoshow = '';
1653
				// Check if a preview file is available
1654
				if (in_array($modulepart, array('propal', 'commande', 'facture', 'ficheinter', 'contract', 'supplier_order', 'supplier_proposal', 'supplier_invoice', 'expensereport')) && class_exists("Imagick"))
1655
				{
1656
					$objectref = dol_sanitizeFileName($object->ref);
1657
					$dir_output = (empty($conf->$modulepart->multidir_output[$entity]) ? $conf->$modulepart->dir_output : $conf->$modulepart->multidir_output[$entity])."/";
1658
					if (in_array($modulepart, array('invoice_supplier', 'supplier_invoice')))
1659
					{
1660
						$subdir = get_exdir($object->id, 2, 0, 1, $object, $modulepart);
1661
						$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
1662
					} else {
1663
						$subdir = get_exdir($object->id, 0, 0, 1, $object, $modulepart);
1664
					}
1665
					if (empty($subdir)) $subdir = 'errorgettingsubdirofobject'; // Protection to avoid to return empty path
1666
1667
					$filepath = $dir_output.$subdir."/";
1668
1669
					$filepdf = $filepath.$objectref.".pdf";
1670
					$relativepath = $subdir.'/'.$objectref.'.pdf';
1671
1672
					// Define path to preview pdf file (preview precompiled "file.ext" are "file.ext_preview.png")
1673
					$fileimage = $filepdf.'_preview.png';
1674
					$relativepathimage = $relativepath.'_preview.png';
1675
1676
					$pdfexists = file_exists($filepdf);
1677
1678
					// If PDF file exists
1679
					if ($pdfexists)
1680
					{
1681
						// Conversion du PDF en image png si fichier png non existant
1682
						if (!file_exists($fileimage) || (filemtime($fileimage) < filemtime($filepdf)))
1683
						{
1684
							if (empty($conf->global->MAIN_DISABLE_PDF_THUMBS))		// If you experience trouble with pdf thumb generation and imagick, you can disable here.
1685
							{
1686
								include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1687
								$ret = dol_convert_file($filepdf, 'png', $fileimage, '0'); // Convert first page of PDF into a file _preview.png
1688
								if ($ret < 0) $error++;
1689
							}
1690
						}
1691
					}
1692
1693
					if ($pdfexists && !$error)
1694
					{
1695
						$heightforphotref = 80;
1696
						if (!empty($conf->dol_optimize_smallscreen)) $heightforphotref = 60;
1697
						// If the preview file is found
1698
						if (file_exists($fileimage))
1699
						{
1700
							$phototoshow = '<div class="photoref">';
1701
							$phototoshow .= '<img height="'.$heightforphotref.'" class="photo photowithmargin photowithborder" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart=apercu'.$modulepart.'&amp;file='.urlencode($relativepathimage).'">';
1702
							$phototoshow .= '</div>';
1703
						}
1704
					}
1705
				} elseif (!$phototoshow) { // example if modulepart = 'photo'
1706
					$phototoshow .= $form->showphoto($modulepart, $object, 0, 0, 0, 'photoref', 'small', 1, 0, $maxvisiblephotos);
1707
				}
1708
1709
				if ($phototoshow) {
1710
					$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">';
1711
					$morehtmlleft .= $phototoshow;
1712
					$morehtmlleft .= '</div>';
1713
				}
1714
			}
1715
1716
			if (!$phototoshow)      // Show No photo link (picto of object)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $phototoshow does not seem to be defined for all execution paths leading up to this point.
Loading history...
1717
			{
1718
				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">';
1719
				if ($object->element == 'action')
1720
				{
1721
					$width = 80;
1722
					$cssclass = 'photorefcenter';
1723
					$nophoto = img_picto('No photo', 'title_agenda');
1724
				} else {
1725
					$width = 14; $cssclass = 'photorefcenter';
1726
					$picto = $object->picto;
1727
					if ($object->element == 'project' && !$object->public) $picto = 'project'; // instead of projectpub
1728
					$nophoto = img_picto('No photo', 'object_'.$picto);
1729
				}
1730
				$morehtmlleft .= '<!-- No photo to show -->';
1731
				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
1732
				$morehtmlleft .= $nophoto;
1733
				$morehtmlleft .= '</div></div>';
1734
1735
				$morehtmlleft .= '</div>';
1736
			}
1737
		}
1738
	}
1739
1740
	if ($showbarcode) $morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">'.$form->showbarcode($object).'</div>';
1741
1742
	if ($object->element == 'societe')
1743
	{
1744
		if (!empty($conf->use_javascript_ajax) && $user->rights->societe->creer && !empty($conf->global->MAIN_DIRECT_STATUS_UPDATE))
1745
		{
1746
		   	$morehtmlstatus .= ajax_object_onoff($object, 'status', 'status', 'InActivity', 'ActivityCeased');
1747
		} else {
1748
			$morehtmlstatus .= $object->getLibStatut(6);
1749
		}
1750
	} elseif ($object->element == 'product')
1751
	{
1752
		//$morehtmlstatus.=$langs->trans("Status").' ('.$langs->trans("Sell").') ';
1753
		if (!empty($conf->use_javascript_ajax) && $user->rights->produit->creer && !empty($conf->global->MAIN_DIRECT_STATUS_UPDATE)) {
1754
			$morehtmlstatus .= ajax_object_onoff($object, 'status', 'tosell', 'ProductStatusOnSell', 'ProductStatusNotOnSell');
1755
		} else {
1756
			$morehtmlstatus .= '<span class="statusrefsell">'.$object->getLibStatut(6, 0).'</span>';
1757
		}
1758
		$morehtmlstatus .= ' &nbsp; ';
1759
		//$morehtmlstatus.=$langs->trans("Status").' ('.$langs->trans("Buy").') ';
1760
		if (!empty($conf->use_javascript_ajax) && $user->rights->produit->creer && !empty($conf->global->MAIN_DIRECT_STATUS_UPDATE)) {
1761
			$morehtmlstatus .= ajax_object_onoff($object, 'status_buy', 'tobuy', 'ProductStatusOnBuy', 'ProductStatusNotOnBuy');
1762
		} else {
1763
			$morehtmlstatus .= '<span class="statusrefbuy">'.$object->getLibStatut(6, 1).'</span>';
1764
		}
1765
	} elseif (in_array($object->element, array('facture', 'invoice', 'invoice_supplier', 'chargesociales', 'loan'))) {
1766
		$tmptxt = $object->getLibStatut(6, $object->totalpaye);
1767
		if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) $tmptxt = $object->getLibStatut(5, $object->totalpaye);
1768
		$morehtmlstatus .= $tmptxt;
1769
	} elseif ($object->element == 'contrat' || $object->element == 'contract') {
1770
		if ($object->statut == 0) $morehtmlstatus .= $object->getLibStatut(5);
1771
		else $morehtmlstatus .= $object->getLibStatut(4);
1772
	} elseif ($object->element == 'facturerec') {
1773
		if ($object->frequency == 0) $morehtmlstatus .= $object->getLibStatut(2);
1774
		else $morehtmlstatus .= $object->getLibStatut(5);
1775
	} elseif ($object->element == 'project_task') {
1776
		$object->fk_statut = 1;
1777
		if ($object->progress > 0) $object->fk_statut = 2;
1778
		if ($object->progress >= 100) $object->fk_statut = 3;
1779
		$tmptxt = $object->getLibStatut(5);
1780
		$morehtmlstatus .= $tmptxt; // No status on task
1781
	} else { // Generic case
1782
		$tmptxt = $object->getLibStatut(6);
1783
		if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) $tmptxt = $object->getLibStatut(5);
1784
		$morehtmlstatus .= $tmptxt;
1785
	}
1786
1787
	// Add if object was dispatched "into accountancy"
1788
	if (!empty($conf->accounting->enabled) && in_array($object->element, array('bank', 'paiementcharge', 'facture', 'invoice', 'invoice_supplier', 'expensereport', 'payment_various')))
1789
	{
1790
		// Note: For 'chargesociales', 'salaries'... this is the payments that are dispatched (so element = 'bank')
1791
		if (method_exists($object, 'getVentilExportCompta'))
1792
		{
1793
			$accounted = $object->getVentilExportCompta();
1794
			$langs->load("accountancy");
1795
			$morehtmlstatus .= '</div><div class="statusref statusrefbis"><span class="opacitymedium">'.($accounted > 0 ? $langs->trans("Accounted") : $langs->trans("NotYetAccounted")).'</span>';
1796
		}
1797
	}
1798
1799
	// Add alias for thirdparty
1800
	if (!empty($object->name_alias)) $morehtmlref .= '<div class="refidno">'.$object->name_alias.'</div>';
1801
1802
	// Add label
1803
	if (in_array($object->element, array('product', 'bank_account', 'project_task')))
1804
	{
1805
		if (!empty($object->label)) $morehtmlref .= '<div class="refidno">'.$object->label.'</div>';
1806
	}
1807
1808
	if (method_exists($object, 'getBannerAddress') && !in_array($object->element, array('product', 'bookmark', 'ecm_directories', 'ecm_files')))
1809
	{
1810
		$moreaddress = $object->getBannerAddress('refaddress', $object);
1811
		if ($moreaddress) {
1812
			$morehtmlref .= '<div class="refidno">';
1813
			$morehtmlref .= $moreaddress;
1814
			$morehtmlref .= '</div>';
1815
		}
1816
	}
1817
	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))
1818
	{
1819
		$morehtmlref .= '<div style="clear: both;"></div>';
1820
		$morehtmlref .= '<div class="refidno">';
1821
		$morehtmlref .= $langs->trans("TechnicalID").': '.$object->id;
1822
		$morehtmlref .= '</div>';
1823
	}
1824
1825
	print '<div class="'.($onlybanner ? 'arearefnobottom ' : 'arearef ').'heightref valignmiddle centpercent">';
1826
	print $form->showrefnav($object, $paramid, $morehtml, $shownav, $fieldid, $fieldref, $morehtmlref, $moreparam, $nodbprefix, $morehtmlleft, $morehtmlstatus, $morehtmlright);
1827
	print '</div>';
1828
	print '<div class="underrefbanner clearboth"></div>';
1829
}
1830
1831
/**
1832
 * Show a string with the label tag dedicated to the HTML edit field.
1833
 *
1834
 * @param	string	$langkey		Translation key
1835
 * @param 	string	$fieldkey		Key of the html select field the text refers to
1836
 * @param	int		$fieldrequired	1=Field is mandatory
1837
 * @return string
1838
 * @deprecated Form::editfieldkey
1839
 */
1840
function fieldLabel($langkey, $fieldkey, $fieldrequired = 0)
1841
{
1842
	global $langs;
1843
	$ret = '';
1844
	if ($fieldrequired) $ret .= '<span class="fieldrequired">';
1845
	$ret .= '<label for="'.$fieldkey.'">';
1846
	$ret .= $langs->trans($langkey);
1847
	$ret .= '</label>';
1848
	if ($fieldrequired) $ret .= '</span>';
1849
	return $ret;
1850
}
1851
1852
/**
1853
 * Return string to add class property on html element with pair/impair.
1854
 *
1855
 * @param	string	$var			0 or 1
1856
 * @param	string	$moreclass		More class to add
1857
 * @return	string					String to add class onto HTML element
1858
 */
1859
function dol_bc($var, $moreclass = '')
1860
{
1861
	global $bc;
1862
	$ret = ' '.$bc[$var];
1863
	if ($moreclass) $ret = preg_replace('/class=\"/', 'class="'.$moreclass.' ', $ret);
1864
	return $ret;
1865
}
1866
1867
/**
1868
 *      Return a formated address (part address/zip/town/state) according to country rules.
1869
 *      See https://en.wikipedia.org/wiki/Address
1870
 *
1871
 *      @param  Object		$object			A company or contact object
1872
 * 	    @param	int			$withcountry	1=Add country into address string
1873
 *      @param	string		$sep			Separator to use to build string
1874
 *      @param	Translate	$outputlangs	Object lang that contains language for text translation.
1875
 *      @param	int			$mode			0=Standard output, 1=Remove address
1876
 *  	@param	string		$extralangcode	User extralanguage $langcode as values for address, town
1877
 *      @return string						Formated string
1878
 *      @see dol_print_address()
1879
 */
1880
function dol_format_address($object, $withcountry = 0, $sep = "\n", $outputlangs = '', $mode = 0, $extralangcode = '')
1881
{
1882
	global $conf, $langs;
1883
1884
	$ret = '';
1885
	$countriesusingstate = array('AU', 'CA', 'US', 'IN', 'GB', 'ES', 'UK', 'TR'); // See also MAIN_FORCE_STATE_INTO_ADDRESS
1886
1887
	// See format of addresses on https://en.wikipedia.org/wiki/Address
1888
	// Address
1889
	if (empty($mode)) {
1890
		$ret .= ($extralangcode ? $object->array_languages['address'][$extralangcode] : $object->address);
1891
	}
1892
	// Zip/Town/State
1893
	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
1894
		$town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : $object->town);
1895
		$ret .= ($ret ? $sep : '').$town;
1896
		if (!empty($object->state))	{
1897
			$ret .= ($ret ? ", " : '').$object->state;
1898
		}
1899
		if ($object->zip) $ret .= ($ret ? ", " : '').$object->zip;
1900
	} 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
1901
		$town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : $object->town);
1902
		$ret .= ($ret ? $sep : '').$town;
1903
		if (!empty($object->state)) {
1904
			$ret .= ($ret ? ", " : '').$object->state;
1905
		}
1906
		if ($object->zip) $ret .= ($ret ? $sep : '').$object->zip;
1907
	} 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
1908
		$ret .= ($ret ? $sep : '').$object->zip;
1909
		$town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : $object->town);
1910
		$ret .= ($town ? (($object->zip ? ' ' : '').$town) : '');
1911
		if (!empty($object->state)) {
1912
			$ret .= "\n".$object->state;
1913
		}
1914
	} 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
1915
		$ret .= ($ret ? $sep : '').$object->zip;
1916
		$town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : $object->town);
1917
		$ret .= ($town ? (($object->zip ? ' ' : '').$town) : '');
1918
		$ret .= (empty($object->state_code) ? '' : (' '.$object->state_code));
1919
	} else { // Other: title firstname name \n address lines \n zip town \n country
1920
		$town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : $object->town);
1921
		$ret .= $object->zip ? (($ret ? $sep : '').$object->zip) : '';
1922
		$ret .= ($town ? (($object->zip ? ' ' : ($ret ? $sep : '')).$town) : '');
1923
		if (!empty($object->state) && in_array($object->country_code, $countriesusingstate)) {
1924
			$ret .= ($ret ? ", " : '').$object->state;
1925
		}
1926
	}
1927
	if (!is_object($outputlangs)) $outputlangs = $langs;
1928
	if ($withcountry) {
1929
		$langs->load("dict");
1930
		$ret .= (empty($object->country_code) ? '' : ($ret ? $sep : '').$outputlangs->convToOutputCharset($outputlangs->transnoentitiesnoconv("Country".$object->country_code)));
1931
	}
1932
1933
	return $ret;
1934
}
1935
1936
1937
1938
/**
1939
 *	Format a string.
1940
 *
1941
 *	@param	string	$fmt		Format of strftime function (http://php.net/manual/fr/function.strftime.php)
1942
 *  @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)
1943
 *  @param	int		$is_gmt		See comment of timestamp parameter
1944
 *	@return	string				A formatted string
1945
 */
1946
function dol_strftime($fmt, $ts = false, $is_gmt = false)
1947
{
1948
	if ((abs($ts) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range
1949
		return ($is_gmt) ? @gmstrftime($fmt, $ts) : @strftime($fmt, $ts);
1950
	} else return 'Error date into a not supported range';
1951
}
1952
1953
/**
1954
 *	Output date in a string format according to outputlangs (or langs if not defined).
1955
 * 	Return charset is always UTF-8, except if encodetoouput is defined. In this case charset is output charset
1956
 *
1957
 *	@param	int			$time			GM Timestamps date
1958
 *	@param	string		$format      	Output date format (tag of strftime function)
1959
 *										"%d %b %Y",
1960
 *										"%d/%m/%Y %H:%M",
1961
 *										"%d/%m/%Y %H:%M:%S",
1962
 *                                      "%B"=Long text of month, "%A"=Long text of day, "%b"=Short text of month, "%a"=Short text of day
1963
 *										"day", "daytext", "dayhour", "dayhourldap", "dayhourtext", "dayrfc", "dayhourrfc", "...reduceformat"
1964
 * 	@param	string		$tzoutput		true or 'gmt' => string is for Greenwich location
1965
 * 										false or 'tzserver' => output string is for local PHP server TZ usage
1966
 * 										'tzuser' => output string is for user TZ (current browser TZ with current dst) => In a future, we should have same behaviour than 'tzuserrel'
1967
 *                                      'tzuserrel' => output string is for user TZ (current browser TZ with dst or not, depending on date position) (TODO not implemented yet)
1968
 *	@param	Translate	$outputlangs	Object lang that contains language for text translation.
1969
 *  @param  boolean		$encodetooutput false=no convert into output pagecode
1970
 * 	@return string      				Formated date or '' if time is null
1971
 *
1972
 *  @see        dol_mktime(), dol_stringtotime(), dol_getdate()
1973
 */
1974
function dol_print_date($time, $format = '', $tzoutput = 'auto', $outputlangs = '', $encodetooutput = false)
1975
{
1976
	global $conf, $langs;
1977
1978
	if ($tzoutput == 'auto') {
1979
		$tzoutput = $conf->tzuserinputkey;
1980
	}
1981
1982
	// Clean parameters
1983
	$to_gmt = false;
1984
	$offsettz = $offsetdst = 0;
1985
	if ($tzoutput)
1986
	{
1987
		$to_gmt = true; // For backward compatibility
1988
		if (is_string($tzoutput))
1989
		{
1990
			if ($tzoutput == 'tzserver')
1991
			{
1992
				$to_gmt = false;
1993
				$offsettzstring = @date_default_timezone_get(); // Example 'Europe/Berlin' or 'Indian/Reunion'
1994
				$offsettz = 0;
1995
				$offsetdst = 0;
1996
			} elseif ($tzoutput == 'tzuser' || $tzoutput == 'tzuserrel')
1997
			{
1998
				$to_gmt = true;
1999
				$offsettzstring = (empty($_SESSION['dol_tz_string']) ? 'UTC' : $_SESSION['dol_tz_string']); // Example 'Europe/Berlin' or 'Indian/Reunion'
2000
				$offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60; // Will not be used anymore
2001
				$offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60; // Will not be used anymore
2002
			}
2003
		}
2004
	}
2005
	if (!is_object($outputlangs)) $outputlangs = $langs;
2006
	if (!$format) $format = 'daytextshort';
2007
	$reduceformat = (!empty($conf->dol_optimize_smallscreen) && in_array($format, array('day', 'dayhour'))) ? 1 : 0;
2008
	$formatwithoutreduce = preg_replace('/reduceformat/', '', $format);
2009
	if ($formatwithoutreduce != $format) { $format = $formatwithoutreduce; $reduceformat = 1; }  // so format 'dayreduceformat' is processed like day
2010
2011
	// Change predefined format into computer format. If found translation in lang file we use it, otherwise we use default.
2012
	// TODO Add format daysmallyear and dayhoursmallyear
2013
	if ($format == 'day')				$format = ($outputlangs->trans("FormatDateShort") != "FormatDateShort" ? $outputlangs->trans("FormatDateShort") : $conf->format_date_short);
2014
	elseif ($format == 'hour')			$format = ($outputlangs->trans("FormatHourShort") != "FormatHourShort" ? $outputlangs->trans("FormatHourShort") : $conf->format_hour_short);
2015
	elseif ($format == 'hourduration')	$format = ($outputlangs->trans("FormatHourShortDuration") != "FormatHourShortDuration" ? $outputlangs->trans("FormatHourShortDuration") : $conf->format_hour_short_duration);
2016
	elseif ($format == 'daytext')			 $format = ($outputlangs->trans("FormatDateText") != "FormatDateText" ? $outputlangs->trans("FormatDateText") : $conf->format_date_text);
2017
	elseif ($format == 'daytextshort')	$format = ($outputlangs->trans("FormatDateTextShort") != "FormatDateTextShort" ? $outputlangs->trans("FormatDateTextShort") : $conf->format_date_text_short);
2018
	elseif ($format == 'dayhour')			 $format = ($outputlangs->trans("FormatDateHourShort") != "FormatDateHourShort" ? $outputlangs->trans("FormatDateHourShort") : $conf->format_date_hour_short);
2019
	elseif ($format == 'dayhoursec')		 $format = ($outputlangs->trans("FormatDateHourSecShort") != "FormatDateHourSecShort" ? $outputlangs->trans("FormatDateHourSecShort") : $conf->format_date_hour_sec_short);
2020
	elseif ($format == 'dayhourtext')		 $format = ($outputlangs->trans("FormatDateHourText") != "FormatDateHourText" ? $outputlangs->trans("FormatDateHourText") : $conf->format_date_hour_text);
2021
	elseif ($format == 'dayhourtextshort') $format = ($outputlangs->trans("FormatDateHourTextShort") != "FormatDateHourTextShort" ? $outputlangs->trans("FormatDateHourTextShort") : $conf->format_date_hour_text_short);
2022
	// Format not sensitive to language
2023
	elseif ($format == 'dayhourlog')		 $format = '%Y%m%d%H%M%S';
2024
	elseif ($format == 'dayhourldap')		 $format = '%Y%m%d%H%M%SZ';
2025
	elseif ($format == 'dayhourxcard')	$format = '%Y%m%dT%H%M%SZ';
2026
	elseif ($format == 'dayxcard')	 	$format = '%Y%m%d';
2027
	elseif ($format == 'dayrfc')			 $format = '%Y-%m-%d'; // DATE_RFC3339
2028
	elseif ($format == 'dayhourrfc')		 $format = '%Y-%m-%dT%H:%M:%SZ'; // DATETIME RFC3339
2029
	elseif ($format == 'standard')		$format = '%Y-%m-%d %H:%M:%S';
2030
2031
	if ($reduceformat)
2032
	{
2033
		$format = str_replace('%Y', '%y', $format);
2034
		$format = str_replace('yyyy', 'yy', $format);
2035
	}
2036
2037
	// If date undefined or "", we return ""
2038
	if (dol_strlen($time) == 0) return ''; // $time=0 allowed (it means 01/01/1970 00:00:00)
2039
2040
	// Clean format
2041
	if (preg_match('/%b/i', $format))		// There is some text to translate
2042
	{
2043
		// We inhibate translation to text made by strftime functions. We will use trans instead later.
2044
		$format = str_replace('%b', '__b__', $format);
2045
		$format = str_replace('%B', '__B__', $format);
2046
	}
2047
	if (preg_match('/%a/i', $format))		// There is some text to translate
2048
	{
2049
		// We inhibate translation to text made by strftime functions. We will use trans instead later.
2050
		$format = str_replace('%a', '__a__', $format);
2051
		$format = str_replace('%A', '__A__', $format);
2052
	}
2053
2054
2055
	// Analyze date
2056
	$reg = array();
2057
	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
2058
	{
2059
		dol_print_error("Functions.lib::dol_print_date function called with a bad value from page ".$_SERVER["PHP_SELF"]);
2060
		return '';
2061
	} 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
2062
	{
2063
		// This part of code should not be used.
2064
		dol_syslog("Functions.lib::dol_print_date function called with a bad value from page ".$_SERVER["PHP_SELF"], LOG_WARNING);
2065
		//if (function_exists('debug_print_backtrace')) debug_print_backtrace();
2066
		// Date has format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'
2067
		$syear	= (!empty($reg[1]) ? $reg[1] : '');
2068
		$smonth = (!empty($reg[2]) ? $reg[2] : '');
2069
		$sday	= (!empty($reg[3]) ? $reg[3] : '');
2070
		$shour	= (!empty($reg[4]) ? $reg[4] : '');
2071
		$smin	= (!empty($reg[5]) ? $reg[5] : '');
2072
		$ssec	= (!empty($reg[6]) ? $reg[6] : '');
2073
2074
		$time = dol_mktime($shour, $smin, $ssec, $smonth, $sday, $syear, true);
2075
		$ret = adodb_strftime($format, $time + $offsettz + $offsetdst, $to_gmt);
2076
	} else {
2077
		// Date is a timestamps
2078
		if ($time < 100000000000)	// Protection against bad date values
2079
		{
2080
			$timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
2081
2082
			$ret = adodb_strftime($format, $timetouse, $to_gmt);
2083
		} else $ret = 'Bad value '.$time.' for date';
2084
	}
2085
2086
	if (preg_match('/__b__/i', $format))
2087
	{
2088
		$timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
2089
2090
		// Here ret is string in PHP setup language (strftime was used). Now we convert to $outputlangs.
2091
		$month = adodb_strftime('%m', $timetouse);
2092
		$month = sprintf("%02d", $month); // $month may be return with format '06' on some installation and '6' on other, so we force it to '06'.
2093
		if ($encodetooutput)
2094
		{
2095
			$monthtext = $outputlangs->transnoentities('Month'.$month);
2096
			$monthtextshort = $outputlangs->transnoentities('MonthShort'.$month);
2097
		} else {
2098
			$monthtext = $outputlangs->transnoentitiesnoconv('Month'.$month);
2099
			$monthtextshort = $outputlangs->transnoentitiesnoconv('MonthShort'.$month);
2100
		}
2101
		//print 'monthtext='.$monthtext.' monthtextshort='.$monthtextshort;
2102
		$ret = str_replace('__b__', $monthtextshort, $ret);
2103
		$ret = str_replace('__B__', $monthtext, $ret);
2104
		//print 'x'.$outputlangs->charset_output.'-'.$ret.'x';
2105
		//return $ret;
2106
	}
2107
	if (preg_match('/__a__/i', $format))
2108
	{
2109
		$timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
2110
2111
		$w = adodb_strftime('%w', $timetouse); // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
2112
		$dayweek = $outputlangs->transnoentitiesnoconv('Day'.$w);
2113
		$ret = str_replace('__A__', $dayweek, $ret);
2114
		$ret = str_replace('__a__', dol_substr($dayweek, 0, 3), $ret);
2115
	}
2116
2117
	return $ret;
2118
}
2119
2120
2121
/**
2122
 *  Return an array with locale date info.
2123
 *  WARNING: This function use PHP server timezone by default to return locale informations.
2124
 *  Be aware to add the third parameter to "UTC" if you need to work on UTC.
2125
 *
2126
 *	@param	int			$timestamp      Timestamp
2127
 *	@param	boolean		$fast           Fast mode. deprecated.
2128
 *  @param	string		$forcetimezone	'' to use the PHP server timezone. Or use a form like 'Europe/Paris' or '+0200' to force timezone.
2129
 *	@return	array						Array of informations
2130
 *										'seconds' => $secs,
2131
 *										'minutes' => $min,
2132
 *										'hours' => $hour,
2133
 *										'mday' => $day,
2134
 *										'wday' => $dow,		0=sunday, 6=saturday
2135
 *										'mon' => $month,
2136
 *										'year' => $year,
2137
 *										'yday' => floor($secsInYear/$_day_power)
2138
 *										'0' => original timestamp
2139
 * 	@see 								dol_print_date(), dol_stringtotime(), dol_mktime()
2140
 */
2141
function dol_getdate($timestamp, $fast = false, $forcetimezone = '')
2142
{
2143
	global $conf;
2144
2145
	if (empty($conf->global->MAIN_USE_OLD_FUNCTIONS_FOR_GETDATE)) {
2146
		//$datetimeobj = new DateTime('@'.$timestamp);
2147
		$datetimeobj = new DateTime();
2148
		$datetimeobj->setTimestamp($timestamp); // Use local PHP server timezone
2149
		if ($forcetimezone) $datetimeobj->setTimezone(new DateTimeZone($forcetimezone)); //  (add timezone relative to the date entered)
2150
		$arrayinfo = array(
2151
			'year'=>((int) date_format($datetimeobj, 'Y')),
2152
			'mon'=>((int) date_format($datetimeobj, 'm')),
2153
			'mday'=>((int) date_format($datetimeobj, 'd')),
2154
			'wday'=>((int) date_format($datetimeobj, 'w')),
2155
			'yday'=>((int) date_format($datetimeobj, 'z')),
2156
			'hours'=>((int) date_format($datetimeobj, 'H')),
2157
			'minutes'=>((int) date_format($datetimeobj, 'i')),
2158
			'seconds'=>((int) date_format($datetimeobj, 's')),
2159
			'0'=>$timestamp
2160
		);
2161
	} else {
2162
		// PHP getdate is restricted to the years 1901-2038 on Unix and 1970-2038 on Windows
2163
		$usealternatemethod = false;
2164
		if ($timestamp <= 0) $usealternatemethod = true; // <= 1970
2165
		if ($timestamp >= 2145913200) $usealternatemethod = true; // >= 2038
2166
2167
		if ($usealternatemethod)
2168
		{
2169
			$arrayinfo = adodb_getdate($timestamp, $fast);
2170
		} else {
2171
			$arrayinfo = getdate($timestamp);
2172
		}
2173
	}
2174
2175
	return $arrayinfo;
2176
}
2177
2178
/**
2179
 *	Return a timestamp date built from detailed informations (by default a local PHP server timestamp)
2180
 * 	Replace function mktime not available under Windows if year < 1970
2181
 *	PHP mktime is restricted to the years 1901-2038 on Unix and 1970-2038 on Windows
2182
 *
2183
 * 	@param	int			$hour			Hour	(can be -1 for undefined)
2184
 *	@param	int			$minute			Minute	(can be -1 for undefined)
2185
 *	@param	int			$second			Second	(can be -1 for undefined)
2186
 *	@param	int			$month			Month (1 to 12)
2187
 *	@param	int			$day			Day (1 to 31)
2188
 *	@param	int			$year			Year
2189
 *	@param	mixed		$gm				True or 1 or 'gmt'=Input informations are GMT values
2190
 *										False or 0 or 'tzserver' = local to server TZ
2191
 *										'auto'
2192
 *										'tzuser' = local to user TZ taking dst into account at the current date. Not yet implemented.
2193
 *										'tzuserrel' = local to user TZ taking dst into account at the given date. Use this one to convert date input from user.
2194
 *										'tz,TimeZone' = use specified timezone
2195
 *	@param	int			$check			0=No check on parameters (Can use day 32, etc...)
2196
 *	@return	int|string					Date as a timestamp, '' or false if error
2197
 * 	@see 								dol_print_date(), dol_stringtotime(), dol_getdate()
2198
 */
2199
function dol_mktime($hour, $minute, $second, $month, $day, $year, $gm = 'auto', $check = 1)
2200
{
2201
	global $conf;
2202
	//print "- ".$hour.",".$minute.",".$second.",".$month.",".$day.",".$year.",".$_SERVER["WINDIR"]." -";
2203
2204
	if ($gm == 'auto') {
2205
		$gm = $conf->tzuserinputkey;
2206
	}
2207
2208
	// Clean parameters
2209
	if ($hour == -1 || empty($hour)) $hour = 0;
2210
	if ($minute == -1 || empty($minute)) $minute = 0;
2211
	if ($second == -1 || empty($second)) $second = 0;
2212
2213
	// Check parameters
2214
	if ($check)
2215
	{
2216
		if (!$month || !$day)  return '';
2217
		if ($day > 31) return '';
2218
		if ($month > 12) return '';
2219
		if ($hour < 0 || $hour > 24) return '';
2220
		if ($minute < 0 || $minute > 60) return '';
2221
		if ($second < 0 || $second > 60) return '';
2222
	}
2223
2224
	if (empty($gm) || ($gm === 'server' || $gm === 'tzserver'))
2225
	{
2226
		$default_timezone = @date_default_timezone_get(); // Example 'Europe/Berlin'
2227
		$localtz = new DateTimeZone($default_timezone);
2228
	} elseif ($gm === 'user' || $gm === 'tzuser' || $gm === 'tzuserrel')
2229
	{
2230
		// We use dol_tz_string first because it is more reliable.
2231
		$default_timezone = (empty($_SESSION["dol_tz_string"]) ? @date_default_timezone_get() : $_SESSION["dol_tz_string"]); // Example 'Europe/Berlin'
2232
		try {
2233
			$localtz = new DateTimeZone($default_timezone);
2234
		} catch (Exception $e)
2235
		{
2236
			dol_syslog("Warning dol_tz_string contains an invalid value ".$_SESSION["dol_tz_string"], LOG_WARNING);
2237
			$default_timezone = @date_default_timezone_get();
2238
		}
2239
	} elseif (strrpos($gm, "tz,") !== false)
2240
	{
2241
		$timezone = str_replace("tz,", "", $gm); // Example 'tz,Europe/Berlin'
2242
		try {
2243
			$localtz = new DateTimeZone($timezone);
2244
		} catch (Exception $e)
2245
		{
2246
			dol_syslog("Warning passed timezone contains an invalid value ".$timezone, LOG_WARNING);
2247
		}
2248
	}
2249
2250
	if (empty($localtz)) {
2251
		$localtz = new DateTimeZone('UTC');
2252
	}
2253
	//var_dump($localtz);
2254
	//var_dump($year.'-'.$month.'-'.$day.'-'.$hour.'-'.$minute);
2255
	$dt = new DateTime(null, $localtz);
2256
	$dt->setDate((int) $year, (int) $month, (int) $day);
2257
	$dt->setTime((int) $hour, (int) $minute, (int) $second);
2258
	$date = $dt->getTimestamp(); // should include daylight saving time
2259
	//var_dump($date);
2260
	return $date;
2261
}
2262
2263
2264
/**
2265
 *  Return date for now. In most cases, we use this function without parameters (that means GMT time).
2266
 *
2267
 *  @param	string		$mode	'auto'
2268
 *  							'gmt' => we return GMT timestamp,
2269
 * 								'tzserver' => we add the PHP server timezone
2270
 *  							'tzref' => we add the company timezone. Not implemented.
2271
 * 								'tzuser' or 'tzuserrel' => we add the user timezone
2272
 *	@return int   $date	Timestamp
2273
 */
2274
function dol_now($mode = 'auto')
2275
{
2276
	$ret = 0;
2277
2278
	if ($mode == 'auto') {
2279
		$mode = 'tzserver';
2280
	}
2281
2282
	if ($mode == 'gmt') $ret = time(); // Time for now at greenwich.
2283
	elseif ($mode == 'tzserver')		// Time for now with PHP server timezone added
2284
	{
2285
		require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
2286
		$tzsecond = getServerTimeZoneInt('now'); // Contains tz+dayling saving time
2287
		$ret = (int) (dol_now('gmt') + ($tzsecond * 3600));
2288
	} /*elseif ($mode == 'tzref')				// Time for now with parent company timezone is added
2289
	{
2290
		require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
2291
		$tzsecond=getParentCompanyTimeZoneInt();    // Contains tz+dayling saving time
2292
		$ret=dol_now('gmt')+($tzsecond*3600);
2293
	}*/
2294
	elseif ($mode == 'tzuser' || $mode == 'tzuserrel')				// Time for now with user timezone added
2295
	{
2296
		//print 'time: '.time();
2297
		$offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60;
2298
		$offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60;
2299
		$ret = (int) (dol_now('gmt') + ($offsettz + $offsetdst));
2300
	}
2301
2302
	return $ret;
2303
}
2304
2305
2306
/**
2307
 * Return string with formated size
2308
 *
2309
 * @param	int		$size		Size to print
2310
 * @param	int		$shortvalue	Tell if we want long value to use another unit (Ex: 1.5Kb instead of 1500b)
2311
 * @param	int		$shortunit	Use short label of size unit (for example 'b' instead of 'bytes')
2312
 * @return	string				Link
2313
 */
2314
function dol_print_size($size, $shortvalue = 0, $shortunit = 0)
2315
{
2316
	global $conf, $langs;
2317
	$level = 1024;
2318
2319
	if (!empty($conf->dol_optimize_smallscreen)) $shortunit = 1;
2320
2321
	// Set value text
2322
	if (empty($shortvalue) || $size < ($level * 10))
2323
	{
2324
		$ret = $size;
2325
		$textunitshort = $langs->trans("b");
2326
		$textunitlong = $langs->trans("Bytes");
2327
	} else {
2328
		$ret = round($size / $level, 0);
2329
		$textunitshort = $langs->trans("Kb");
2330
		$textunitlong = $langs->trans("KiloBytes");
2331
	}
2332
	// Use long or short text unit
2333
	if (empty($shortunit)) { $ret .= ' '.$textunitlong; } else { $ret .= ' '.$textunitshort; }
2334
2335
	return $ret;
2336
}
2337
2338
/**
2339
 * Show Url link
2340
 *
2341
 * @param	string		$url		Url to show
2342
 * @param	string		$target		Target for link
2343
 * @param	int			$max		Max number of characters to show
2344
 * @param	int			$withpicto	With picto
2345
 * @return	string					HTML Link
2346
 */
2347
function dol_print_url($url, $target = '_blank', $max = 32, $withpicto = 0)
2348
{
2349
	global $langs;
2350
2351
	if (empty($url)) return '';
2352
2353
	$link = '<a href="';
2354
	if (!preg_match('/^http/i', $url)) $link .= 'http://';
2355
	$link .= $url;
2356
	$link .= '"';
2357
	if ($target) $link .= ' target="'.$target.'"';
2358
	$link .= '>';
2359
	if (!preg_match('/^http/i', $url)) $link .= 'http://';
2360
	$link .= dol_trunc($url, $max);
2361
	$link .= '</a>';
2362
	return '<div class="nospan float" style="margin-right: 10px">'.($withpicto ?img_picto($langs->trans("Url"), 'globe').' ' : '').$link.'</div>';
2363
}
2364
2365
/**
2366
 * Show EMail link formatted for HTML output.
2367
 *
2368
 * @param	string		$email			EMail to show (only email, without 'Name of recipient' before)
2369
 * @param 	int			$cid 			Id of contact if known
2370
 * @param 	int			$socid 			Id of third party if known
2371
 * @param 	int			$addlink		0=no link, 1=email has a html email link (+ link to create action if constant AGENDA_ADDACTIONFOREMAIL is on)
2372
 * @param	int			$max			Max number of characters to show
2373
 * @param	int			$showinvalid	1=Show warning if syntax email is wrong
2374
 * @param	int			$withpicto		Show picto
2375
 * @return	string						HTML Link
2376
 */
2377
function dol_print_email($email, $cid = 0, $socid = 0, $addlink = 0, $max = 64, $showinvalid = 1, $withpicto = 0)
2378
{
2379
	global $conf, $user, $langs, $hookmanager;
2380
2381
	$newemail = dol_escape_htmltag($email);
2382
2383
	if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER) && $withpicto) $withpicto = 0;
2384
2385
	if (empty($email)) return '&nbsp;';
2386
2387
	if (!empty($addlink))
2388
	{
2389
		$newemail = '<a style="text-overflow: ellipsis;" href="';
2390
		if (!preg_match('/^mailto:/i', $email)) $newemail .= 'mailto:';
2391
		$newemail .= $email;
2392
		$newemail .= '">';
2393
		$newemail .= dol_trunc($email, $max);
2394
		$newemail .= '</a>';
2395
		if ($showinvalid && !isValidEmail($email))
2396
		{
2397
			$langs->load("errors");
2398
			$newemail .= img_warning($langs->trans("ErrorBadEMail", $email));
2399
		}
2400
2401
		if (($cid || $socid) && !empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create)
2402
		{
2403
			$type = 'AC_EMAIL'; $link = '';
2404
			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>';
2405
			if ($link) $newemail = '<div>'.$newemail.' '.$link.'</div>';
2406
		}
2407
	} else {
2408
		if ($showinvalid && !isValidEmail($email))
2409
		{
2410
			$langs->load("errors");
2411
			$newemail .= img_warning($langs->trans("ErrorBadEMail", $email));
2412
		}
2413
	}
2414
2415
	//$rep = '<div class="nospan" style="margin-right: 10px">';
2416
	$rep = ($withpicto ? img_picto($langs->trans("EMail").' : '.$email, 'object_email.png').' ' : '').$newemail;
2417
	//$rep .= '</div>';
2418
	if ($hookmanager) {
2419
		$parameters = array('cid' => $cid, 'socid' => $socid, 'addlink' => $addlink, 'picto' => $withpicto);
2420
		$reshook = $hookmanager->executeHooks('printEmail', $parameters, $email);
2421
		if ($reshook > 0) {
2422
			$rep = '';
2423
		}
2424
		$rep .= $hookmanager->resPrint;
2425
	}
2426
2427
	return $rep;
2428
}
2429
2430
/**
2431
 * Get array of social network dictionary
2432
 *
2433
 * @return  array       Array of Social Networks Dictionary
2434
 */
2435
function getArrayOfSocialNetworks()
2436
{
2437
	global $conf, $db;
2438
	$sql = "SELECT rowid, code, label, url, icon, active FROM ".MAIN_DB_PREFIX."c_socialnetworks";
2439
	$sql .= " WHERE entity=".$conf->entity;
2440
	$socialnetworks = array();
2441
	$resql = $db->query($sql);
2442
	if ($resql) {
2443
		while ($obj = $db->fetch_object($resql)) {
2444
			$socialnetworks[$obj->code] = array(
2445
				'rowid' => $obj->rowid,
2446
				'label' => $obj->label,
2447
				'url' => $obj->url,
2448
				'icon' => $obj->icon,
2449
				'active' => $obj->active,
2450
			);
2451
		}
2452
	}
2453
	return $socialnetworks;
2454
}
2455
2456
/**
2457
 * Show social network link
2458
 *
2459
 * @param	string		$value				Skype to show (only skype, without 'Name of recipient' before)
2460
 * @param	int 		$cid 				Id of contact if known
2461
 * @param	int 		$socid 				Id of third party if known
2462
 * @param	string 		$type				'skype','facebook',...
2463
 * @param	array		$dictsocialnetworks socialnetworks availables
2464
 * @return	string							HTML Link
2465
 */
2466
function dol_print_socialnetworks($value, $cid, $socid, $type, $dictsocialnetworks = array())
2467
{
2468
	global $conf, $user, $langs;
2469
2470
	$htmllink = $value;
2471
2472
	if (empty($value)) return '&nbsp;';
2473
2474
	if (!empty($type)) {
2475
		$htmllink = '<div class="divsocialnetwork inline-block valignmiddle">';
2476
		// TODO use dictionary definition for picto $dictsocialnetworks[$type]['icon']
2477
		$htmllink .= img_picto($langs->trans(dol_ucfirst($type)), $type.'.png', '', false, 0, 0, '', 'paddingright', 0);
2478
		if ($type == 'skype') {
2479
			$htmllink .= $value;
2480
			$htmllink .= '&nbsp;';
2481
			$htmllink .= '<a href="skype:';
2482
			$htmllink .= $value;
2483
			$htmllink .= '?call" alt="'.$langs->trans("Call").'&nbsp;'.$value.'" title="'.$langs->trans("Call").'&nbsp;'.$value.'">';
2484
			$htmllink .= '<img src="'.DOL_URL_ROOT.'/theme/common/skype_callbutton.png" border="0">';
2485
			$htmllink .= '</a><a href="skype:';
2486
			$htmllink .= $value;
2487
			$htmllink .= '?chat" alt="'.$langs->trans("Chat").'&nbsp;'.$value.'" title="'.$langs->trans("Chat").'&nbsp;'.$value.'">';
2488
			$htmllink .= '<img class="paddingleft" src="'.DOL_URL_ROOT.'/theme/common/skype_chatbutton.png" border="0">';
2489
			$htmllink .= '</a>';
2490
			if (($cid || $socid) && !empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create) {
2491
				$addlink = 'AC_SKYPE';
2492
				$link = '';
2493
				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>';
2494
				$htmllink .= ($link ? ' '.$link : '');
2495
			}
2496
		} else {
2497
			if (!empty($dictsocialnetworks[$type]['url'])) {
2498
				$link = str_replace('{socialid}', $value, $dictsocialnetworks[$type]['url']);
2499
				$htmllink .= '&nbsp;<a href="'.$link.'" target="_blank">'.$value.'</a>';
2500
			} else {
2501
				$htmllink .= $value;
2502
			}
2503
		}
2504
		$htmllink .= '</div>';
2505
	} else {
2506
		$langs->load("errors");
2507
		$htmllink .= img_warning($langs->trans("ErrorBadSocialNetworkValue", $value));
2508
	}
2509
	return $htmllink;
2510
}
2511
2512
/**
2513
 * 	Format phone numbers according to country
2514
 *
2515
 * 	@param  string  $phone          Phone number to format
2516
 * 	@param  string  $countrycode    Country code to use for formatting
2517
 * 	@param 	int		$cid 		    Id of contact if known
2518
 * 	@param 	int		$socid          Id of third party if known
2519
 * 	@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)
2520
 * 	@param 	string	$separ 		    Separation between numbers for a better visibility example : xx.xx.xx.xx.xx
2521
 *  @param	string  $withpicto      Show picto
2522
 *  @param	string	$titlealt	    Text to show on alt
2523
 *  @param  int     $adddivfloat    Add div float around phone.
2524
 * 	@return string 				    Formated phone number
2525
 */
2526
function dol_print_phone($phone, $countrycode = '', $cid = 0, $socid = 0, $addlink = '', $separ = "&nbsp;", $withpicto = '', $titlealt = '', $adddivfloat = 0)
2527
{
2528
	global $conf, $user, $langs, $mysoc, $hookmanager;
2529
2530
	// Clean phone parameter
2531
	$phone = preg_replace("/[\s.-]/", "", trim($phone));
2532
	if (empty($phone)) { return ''; }
2533
	if (!empty($conf->global->MAIN_PHONE_SEPAR)) $separ = $conf->global->MAIN_PHONE_SEPAR;
2534
	if (empty($countrycode)) $countrycode = $mysoc->country_code;
2535
2536
	// Short format for small screens
2537
	if ($conf->dol_optimize_smallscreen) $separ = '';
2538
2539
	$newphone = $phone;
2540
	if (strtoupper($countrycode) == "FR")
2541
	{
2542
		// France
2543
		if (dol_strlen($phone) == 10) {
2544
			$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);
2545
		} elseif (dol_strlen($phone) == 7)
2546
		{
2547
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 2).$separ.substr($newphone, 5, 2);
2548
		} elseif (dol_strlen($phone) == 9)
2549
		{
2550
			$newphone = substr($newphone, 0, 2).$separ.substr($newphone, 2, 3).$separ.substr($newphone, 5, 2).$separ.substr($newphone, 7, 2);
2551
		} elseif (dol_strlen($phone) == 11)
2552
		{
2553
			$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);
2554
		} elseif (dol_strlen($phone) == 12)
2555
		{
2556
			$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);
2557
		}
2558
	} elseif (strtoupper($countrycode) == "CA")
2559
	{
2560
		if (dol_strlen($phone) == 10) {
2561
			$newphone = ($separ != '' ? '(' : '').substr($newphone, 0, 3).($separ != '' ? ')' : '').$separ.substr($newphone, 3, 3).($separ != '' ? '-' : '').substr($newphone, 6, 4);
2562
		}
2563
	} elseif (strtoupper($countrycode) == "PT")
2564
	{//Portugal
2565
		if (dol_strlen($phone) == 13)
2566
		{//ex: +351_ABC_DEF_GHI
2567
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
2568
		}
2569
	} elseif (strtoupper($countrycode) == "SR")
2570
	{//Suriname
2571
		if (dol_strlen($phone) == 10)
2572
		{//ex: +597_ABC_DEF
2573
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3);
2574
		} elseif (dol_strlen($phone) == 11)
2575
		{//ex: +597_ABC_DEFG
2576
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 4);
2577
		}
2578
	} elseif (strtoupper($countrycode) == "DE")
2579
	{//Allemagne
2580
		if (dol_strlen($phone) == 14)
2581
		{//ex:  +49_ABCD_EFGH_IJK
2582
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 4).$separ.substr($newphone, 7, 4).$separ.substr($newphone, 11, 3);
2583
		} elseif (dol_strlen($phone) == 13)
2584
		{//ex: +49_ABC_DEFG_HIJ
2585
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 4).$separ.substr($newphone, 10, 3);
2586
		}
2587
	} elseif (strtoupper($countrycode) == "ES")
2588
	{//Espagne
2589
		if (dol_strlen($phone) == 12)
2590
		{//ex:  +34_ABC_DEF_GHI
2591
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
2592
		}
2593
	} elseif (strtoupper($countrycode) == "BF")
2594
	{// Burkina Faso
2595
		if (dol_strlen($phone) == 12)
2596
		{//ex :  +22 A BC_DE_FG_HI
2597
			$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);
2598
		}
2599
	} elseif (strtoupper($countrycode) == "RO")
2600
	{// Roumanie
2601
		if (dol_strlen($phone) == 12)
2602
		{//ex :  +40 AB_CDE_FG_HI
2603
			$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);
2604
		}
2605
	} elseif (strtoupper($countrycode) == "TR")
2606
	{//Turquie
2607
		if (dol_strlen($phone) == 13)
2608
		{//ex :  +90 ABC_DEF_GHIJ
2609
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 4);
2610
		}
2611
	} elseif (strtoupper($countrycode) == "US")
2612
	{//Etat-Unis
2613
		if (dol_strlen($phone) == 12)
2614
		{//ex: +1 ABC_DEF_GHIJ
2615
			$newphone = substr($newphone, 0, 2).$separ.substr($newphone, 2, 3).$separ.substr($newphone, 5, 3).$separ.substr($newphone, 8, 4);
2616
		}
2617
	} elseif (strtoupper($countrycode) == "MX")
2618
	{//Mexique
2619
		if (dol_strlen($phone) == 12)
2620
		{//ex: +52 ABCD_EFG_HI
2621
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 4).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 2);
2622
		} elseif (dol_strlen($phone) == 11)
2623
		{//ex: +52 AB_CD_EF_GH
2624
			$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);
2625
		} elseif (dol_strlen($phone) == 13)
2626
		{//ex: +52 ABC_DEF_GHIJ
2627
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 4);
2628
		}
2629
	} elseif (strtoupper($countrycode) == "ML")
2630
	{//Mali
2631
		if (dol_strlen($phone) == 12)
2632
		{//ex: +223 AB_CD_EF_GH
2633
			$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);
2634
		}
2635
	} elseif (strtoupper($countrycode) == "TH")
2636
	{//Thaïlande
2637
		if (dol_strlen($phone) == 11)
2638
		{//ex: +66_ABC_DE_FGH
2639
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 2).$separ.substr($newphone, 8, 3);
2640
		} elseif (dol_strlen($phone) == 12)
2641
		{//ex: +66_A_BCD_EF_GHI
2642
			$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);
2643
		}
2644
	} elseif (strtoupper($countrycode) == "MU")
2645
	{
2646
		//Maurice
2647
		if (dol_strlen($phone) == 11)
2648
		{//ex: +230_ABC_DE_FG
2649
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 2).$separ.substr($newphone, 9, 2);
2650
		} elseif (dol_strlen($phone) == 12)
2651
		{//ex: +230_ABCD_EF_GH
2652
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 4).$separ.substr($newphone, 8, 2).$separ.substr($newphone, 10, 2);
2653
		}
2654
	} elseif (strtoupper($countrycode) == "ZA")
2655
	{//Afrique du sud
2656
		if (dol_strlen($phone) == 12)
2657
		{//ex: +27_AB_CDE_FG_HI
2658
			$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);
2659
		}
2660
	} elseif (strtoupper($countrycode) == "SY")
2661
	{//Syrie
2662
		if (dol_strlen($phone) == 12)
2663
		{//ex: +963_AB_CD_EF_GH
2664
			$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);
2665
		} elseif (dol_strlen($phone) == 13)
2666
		{//ex: +963_AB_CD_EF_GHI
2667
			$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);
2668
		}
2669
	} elseif (strtoupper($countrycode) == "AE")
2670
	{//Emirats Arabes Unis
2671
		if (dol_strlen($phone) == 12)
2672
		{//ex: +971_ABC_DEF_GH
2673
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 2);
2674
		} elseif (dol_strlen($phone) == 13)
2675
		{//ex: +971_ABC_DEF_GHI
2676
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
2677
		} elseif (dol_strlen($phone) == 14)
2678
		{//ex: +971_ABC_DEF_GHIK
2679
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 4);
2680
		}
2681
	} elseif (strtoupper($countrycode) == "DZ")
2682
	{//Algérie
2683
		if (dol_strlen($phone) == 13)
2684
		{//ex: +213_ABC_DEF_GHI
2685
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
2686
		}
2687
	} elseif (strtoupper($countrycode) == "BE")
2688
	{//Belgique
2689
		if (dol_strlen($phone) == 11)
2690
		{//ex: +32_ABC_DE_FGH
2691
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 2).$separ.substr($newphone, 8, 3);
2692
		} elseif (dol_strlen($phone) == 12)
2693
		{//ex: +32_ABC_DEF_GHI
2694
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
2695
		}
2696
	} elseif (strtoupper($countrycode) == "PF")
2697
	{//Polynésie française
2698
		if (dol_strlen($phone) == 12)
2699
		{//ex: +689_AB_CD_EF_GH
2700
			$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);
2701
		}
2702
	} elseif (strtoupper($countrycode) == "CO")
2703
	{//Colombie
2704
		if (dol_strlen($phone) == 13)
2705
		{//ex: +57_ABC_DEF_GH_IJ
2706
			$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);
2707
		}
2708
	} elseif (strtoupper($countrycode) == "JO")
2709
	{//Jordanie
2710
		if (dol_strlen($phone) == 12)
2711
		{//ex: +962_A_BCD_EF_GH
2712
			$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);
2713
		}
2714
	} elseif (strtoupper($countrycode) == "JM")
2715
	{//Jamaïque
2716
		if (dol_strlen($newphone) == 12)
2717
		{//ex: +1867_ABC_DEFG
2718
			$newphone = substr($newphone, 0, 5).$separ.substr($newphone, 5, 3).$separ.substr($newphone, 8, 4);
2719
		}
2720
	} elseif (strtoupper($countrycode) == "MG")
2721
	{//Madagascar
2722
		if (dol_strlen($phone) == 13)
2723
		{//ex: +261_AB_CD_EF_GHI
2724
			$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);
2725
		}
2726
	} elseif (strtoupper($countrycode) == "GB")
2727
	{//Royaume uni
2728
		if (dol_strlen($phone) == 13)
2729
		{//ex: +44_ABCD_EFG_HIJ
2730
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 4).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
2731
		}
2732
	} elseif (strtoupper($countrycode) == "CH")
2733
	{//Suisse
2734
		if (dol_strlen($phone) == 12)
2735
		{//ex: +41_AB_CDE_FG_HI
2736
			$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);
2737
		} elseif (dol_strlen($phone) == 15)
2738
		{// +41_AB_CDE_FGH_IJKL
2739
			$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);
2740
		}
2741
	} elseif (strtoupper($countrycode) == "TN")
2742
	{//Tunisie
2743
		if (dol_strlen($phone) == 12)
2744
		{//ex: +216_AB_CDE_FGH
2745
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 2).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
2746
		}
2747
	} elseif (strtoupper($countrycode) == "GF")
2748
	{//Guyane francaise
2749
		if (dol_strlen($phone) == 13)
2750
		{//ex: +594_ABC_DE_FG_HI  (ABC=594 de nouveau)
2751
			$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);
2752
		}
2753
	} elseif (strtoupper($countrycode) == "GP")
2754
	{//Guadeloupe
2755
		if (dol_strlen($phone) == 13)
2756
		{//ex: +590_ABC_DE_FG_HI  (ABC=590 de nouveau)
2757
			$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);
2758
		}
2759
	} elseif (strtoupper($countrycode) == "MQ")
2760
	{//Martinique
2761
		if (dol_strlen($phone) == 13)
2762
		{//ex: +596_ABC_DE_FG_HI  (ABC=596 de nouveau)
2763
			$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);
2764
		}
2765
	} elseif (strtoupper($countrycode) == "IT")
2766
	{//Italie
2767
		if (dol_strlen($phone) == 12)
2768
		{//ex: +39_ABC_DEF_GHI
2769
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
2770
		} elseif (dol_strlen($phone) == 13)
2771
		{//ex: +39_ABC_DEF_GH_IJ
2772
			$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);
2773
		}
2774
	} elseif (strtoupper($countrycode) == "AU")
2775
	{
2776
		//Australie
2777
		if (dol_strlen($phone) == 12)
2778
		{
2779
			//ex: +61_A_BCDE_FGHI
2780
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 1).$separ.substr($newphone, 4, 4).$separ.substr($newphone, 8, 4);
2781
		}
2782
	}
2783
	if (!empty($addlink))	// Link on phone number (+ link to add action if conf->global->AGENDA_ADDACTIONFORPHONE set)
2784
	{
2785
		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
2786
		{
2787
			$newphoneform = $newphone;
2788
			$newphone = '<a href="tel:'.$phone.'"';
2789
			$newphone .= '>'.$newphoneform.'</a>';
2790
		} elseif (!empty($conf->clicktodial->enabled) && $addlink == 'AC_TEL')		// If click to dial, we use click to dial url
2791
		{
2792
			if (empty($user->clicktodial_loaded)) $user->fetch_clicktodial();
2793
2794
			// Define urlmask
2795
			$urlmask = 'ErrorClickToDialModuleNotConfigured';
2796
			if (!empty($conf->global->CLICKTODIAL_URL)) $urlmask = $conf->global->CLICKTODIAL_URL;
2797
			if (!empty($user->clicktodial_url)) $urlmask = $user->clicktodial_url;
2798
2799
			$clicktodial_poste = (!empty($user->clicktodial_poste) ?urlencode($user->clicktodial_poste) : '');
2800
			$clicktodial_login = (!empty($user->clicktodial_login) ?urlencode($user->clicktodial_login) : '');
2801
			$clicktodial_password = (!empty($user->clicktodial_password) ?urlencode($user->clicktodial_password) : '');
2802
			// This line is for backward compatibility
2803
			$url = sprintf($urlmask, urlencode($phone), $clicktodial_poste, $clicktodial_login, $clicktodial_password);
2804
			// Thoose lines are for substitution
2805
			$substitarray = array('__PHONEFROM__'=>$clicktodial_poste,
2806
								'__PHONETO__'=>urlencode($phone),
2807
								'__LOGIN__'=>$clicktodial_login,
2808
								'__PASS__'=>$clicktodial_password);
2809
			$url = make_substitutions($url, $substitarray);
2810
			$newphonesav = $newphone;
2811
			$newphone = '<a href="'.$url.'"';
2812
			if (!empty($conf->global->CLICKTODIAL_FORCENEWTARGET)) $newphone .= ' target="_blank"';
2813
			$newphone .= '>'.$newphonesav.'</a>';
2814
		}
2815
2816
		//if (($cid || $socid) && ! empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create)
2817
		if (!empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create)
2818
		{
2819
			$type = 'AC_TEL'; $link = '';
2820
			if ($addlink == 'AC_FAX') $type = 'AC_FAX';
2821
			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>';
2822
			if ($link) $newphone = '<div>'.$newphone.' '.$link.'</div>';
2823
		}
2824
	}
2825
2826
	if (empty($titlealt))
2827
	{
2828
		$titlealt = ($withpicto == 'fax' ? $langs->trans("Fax") : $langs->trans("Phone"));
2829
	}
2830
	$rep = '';
2831
2832
	if ($hookmanager) {
2833
		$parameters = array('countrycode' => $countrycode, 'cid' => $cid, 'socid' => $socid, 'titlealt' => $titlealt, 'picto' => $withpicto);
2834
		$reshook = $hookmanager->executeHooks('printPhone', $parameters, $phone);
2835
		$rep .= $hookmanager->resPrint;
2836
	}
2837
	if (empty($reshook))
2838
	{
2839
		$picto = '';
2840
		if ($withpicto) {
2841
			if ($withpicto == 'fax') {
2842
				$picto = 'phoning_fax';
2843
			} elseif ($withpicto == 'phone') {
2844
				$picto = 'phoning';
2845
			} elseif ($withpicto == 'mobile') {
2846
				$picto = 'phoning_mobile';
2847
			} else {
2848
				$picto = '';
2849
			}
2850
		}
2851
		if ($adddivfloat) $rep .= '<div class="nospan float" style="margin-right: 10px">';
2852
		else $rep .= '<span style="margin-right: 10px;">';
2853
		$rep .= ($withpicto ?img_picto($titlealt, 'object_'.$picto.'.png').' ' : '').$newphone;
2854
		if ($adddivfloat) $rep .= '</div>';
2855
		else $rep .= '</span>';
2856
	}
2857
2858
	return $rep;
2859
}
2860
2861
/**
2862
 * 	Return an IP formated to be shown on screen
2863
 *
2864
 * 	@param	string	$ip			IP
2865
 * 	@param	int		$mode		0=return IP + country/flag, 1=return only country/flag, 2=return only IP
2866
 * 	@return string 				Formated IP, with country if GeoIP module is enabled
2867
 */
2868
function dol_print_ip($ip, $mode = 0)
2869
{
2870
	global $conf, $langs;
2871
2872
	$ret = '';
2873
2874
	if (empty($mode)) $ret .= $ip;
2875
2876
	if ($mode != 2)
2877
	{
2878
		$countrycode = dolGetCountryCodeFromIp($ip);
2879
		if ($countrycode)	// If success, countrycode is us, fr, ...
2880
		{
2881
			if (file_exists(DOL_DOCUMENT_ROOT.'/theme/common/flags/'.$countrycode.'.png'))
2882
			{
2883
				$ret .= ' '.img_picto($countrycode.' '.$langs->trans("AccordingToGeoIPDatabase"), DOL_URL_ROOT.'/theme/common/flags/'.$countrycode.'.png', '', 1);
2884
			} else $ret .= ' ('.$countrycode.')';
2885
		} else {
2886
			// Nothing
2887
		}
2888
	}
2889
2890
	return $ret;
2891
}
2892
2893
/**
2894
 * Return the IP of remote user.
2895
 * Take HTTP_X_FORWARDED_FOR (defined when using proxy)
2896
 * Then HTTP_CLIENT_IP if defined (rare)
2897
 * Then REMOTE_ADDR (no way to be modified by user but may be wrong if user is using a proxy)
2898
 *
2899
 * @return	string		Ip of remote user.
2900
 */
2901
function getUserRemoteIP()
2902
{
2903
	if (empty($_SERVER['HTTP_X_FORWARDED_FOR']) || preg_match('/[^0-9\.\:,\[\]]/', $_SERVER['HTTP_X_FORWARDED_FOR'])) {
2904
		if (empty($_SERVER['HTTP_CLIENT_IP']) || preg_match('/[^0-9\.\:,\[\]]/', $_SERVER['HTTP_CLIENT_IP'])) {
2905
			if (empty($_SERVER["HTTP_CF_CONNECTING_IP"])) {
2906
				$ip = (empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR']);	// value may have been forged by client
2907
			} else {
2908
				$ip = $_SERVER["HTTP_CF_CONNECTING_IP"];	// value here may have been forged by client
2909
			}
2910
		} else {
2911
			$ip = $_SERVER['HTTP_CLIENT_IP']; // value is clean here but may have been forged by proxy
2912
		}
2913
	} else {
2914
		$ip = $_SERVER['HTTP_X_FORWARDED_FOR']; // value is clean here but may have been forged by proxy
2915
	}
2916
	return $ip;
2917
}
2918
2919
/**
2920
 * Return if we are using a HTTPS connexion
2921
 * Check HTTPS (no way to be modified by user but may be empty or wrong if user is using a proxy)
2922
 * Take HTTP_X_FORWARDED_PROTO (defined when using proxy)
2923
 * Then HTTP_X_FORWARDED_SSL
2924
 *
2925
 * @return	boolean		True if user is using HTTPS
2926
 */
2927
function isHTTPS()
2928
{
2929
	$isSecure = false;
2930
	if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
2931
		$isSecure = true;
2932
	}
2933
	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') {
2934
		$isSecure = true;
2935
	}
2936
	return $isSecure;
2937
}
2938
2939
/**
2940
 * 	Return a country code from IP. Empty string if not found.
2941
 *
2942
 * 	@param	string	$ip			IP
2943
 * 	@return string 				Country code ('us', 'fr', ...)
2944
 */
2945
function dolGetCountryCodeFromIp($ip)
2946
{
2947
	global $conf;
2948
2949
	$countrycode = '';
2950
2951
	if (!empty($conf->geoipmaxmind->enabled))
2952
	{
2953
		$datafile = $conf->global->GEOIPMAXMIND_COUNTRY_DATAFILE;
2954
		//$ip='24.24.24.24';
2955
		//$datafile='/usr/share/GeoIP/GeoIP.dat';    Note that this must be downloaded datafile (not same than datafile provided with ubuntu packages)
2956
		include_once DOL_DOCUMENT_ROOT.'/core/class/dolgeoip.class.php';
2957
		$geoip = new DolGeoIP('country', $datafile);
2958
		//print 'ip='.$ip.' databaseType='.$geoip->gi->databaseType." GEOIP_CITY_EDITION_REV1=".GEOIP_CITY_EDITION_REV1."\n";
2959
		$countrycode = $geoip->getCountryCodeFromIP($ip);
2960
	}
2961
2962
	return $countrycode;
2963
}
2964
2965
2966
/**
2967
 *  Return country code for current user.
2968
 *  If software is used inside a local network, detection may fails (we need a public ip)
2969
 *
2970
 *  @return     string      Country code (fr, es, it, us, ...)
2971
 */
2972
function dol_user_country()
2973
{
2974
	global $conf, $langs, $user;
2975
2976
	//$ret=$user->xxx;
2977
	$ret = '';
2978
	if (!empty($conf->geoipmaxmind->enabled))
2979
	{
2980
		$ip = getUserRemoteIP();
2981
		$datafile = $conf->global->GEOIPMAXMIND_COUNTRY_DATAFILE;
2982
		//$ip='24.24.24.24';
2983
		//$datafile='E:\Mes Sites\Web\Admin1\awstats\maxmind\GeoIP.dat';
2984
		include_once DOL_DOCUMENT_ROOT.'/core/class/dolgeoip.class.php';
2985
		$geoip = new DolGeoIP('country', $datafile);
2986
		$countrycode = $geoip->getCountryCodeFromIP($ip);
2987
		$ret = $countrycode;
2988
	}
2989
	return $ret;
2990
}
2991
2992
/**
2993
 *  Format address string
2994
 *
2995
 *  @param	string	$address    Address string, already formatted with dol_format_address()
2996
 *  @param  int		$htmlid     Html ID (for example 'gmap')
2997
 *  @param  int		$element    'thirdparty'|'contact'|'member'|'other'
2998
 *  @param  int		$id         Id of object
2999
 *  @param	int		$noprint	No output. Result is the function return
3000
 *  @param  string  $charfornl  Char to use instead of nl2br. '' means we use a standad nl2br.
3001
 *  @return string|void			Nothing if noprint is 0, formatted address if noprint is 1
3002
 *  @see dol_format_address()
3003
 */
3004
function dol_print_address($address, $htmlid, $element, $id, $noprint = 0, $charfornl = '')
3005
{
3006
	global $conf, $user, $langs, $hookmanager;
3007
3008
	$out = '';
3009
3010
	if ($address)
3011
	{
3012
		if ($hookmanager) {
3013
			$parameters = array('element' => $element, 'id' => $id);
3014
			$reshook = $hookmanager->executeHooks('printAddress', $parameters, $address);
3015
			$out .= $hookmanager->resPrint;
3016
		}
3017
		if (empty($reshook))
3018
		{
3019
			if (empty($charfornl)) $out .= nl2br($address);
3020
			else $out .= preg_replace('/[\r\n]+/', $charfornl, $address);
3021
3022
			// TODO Remove this block, we can add this using the hook now
3023
			$showgmap = $showomap = 0;
3024
			if (($element == 'thirdparty' || $element == 'societe') && !empty($conf->google->enabled) && !empty($conf->global->GOOGLE_ENABLE_GMAPS)) $showgmap = 1;
3025
			if ($element == 'contact' && !empty($conf->google->enabled) && !empty($conf->global->GOOGLE_ENABLE_GMAPS_CONTACTS)) $showgmap = 1;
3026
			if ($element == 'member' && !empty($conf->google->enabled) && !empty($conf->global->GOOGLE_ENABLE_GMAPS_MEMBERS)) $showgmap = 1;
3027
			if (($element == 'thirdparty' || $element == 'societe') && !empty($conf->openstreetmap->enabled) && !empty($conf->global->OPENSTREETMAP_ENABLE_MAPS)) $showomap = 1;
3028
			if ($element == 'contact' && !empty($conf->openstreetmap->enabled) && !empty($conf->global->OPENSTREETMAP_ENABLE_MAPS_CONTACTS)) $showomap = 1;
3029
			if ($element == 'member' && !empty($conf->openstreetmap->enabled) && !empty($conf->global->OPENSTREETMAP_ENABLE_MAPS_MEMBERS)) $showomap = 1;
3030
			if ($showgmap)
3031
			{
3032
				$url = dol_buildpath('/google/gmaps.php?mode='.$element.'&id='.$id, 1);
3033
				$out .= ' <a href="'.$url.'" target="_gmaps"><img id="'.$htmlid.'" class="valigntextbottom" src="'.DOL_URL_ROOT.'/theme/common/gmap.png"></a>';
3034
			}
3035
			if ($showomap)
3036
			{
3037
				$url = dol_buildpath('/openstreetmap/maps.php?mode='.$element.'&id='.$id, 1);
3038
				$out .= ' <a href="'.$url.'" target="_gmaps"><img id="'.$htmlid.'_openstreetmap" class="valigntextbottom" src="'.DOL_URL_ROOT.'/theme/common/gmap.png"></a>';
3039
			}
3040
		}
3041
	}
3042
	if ($noprint) return $out;
3043
	else print $out;
3044
}
3045
3046
3047
/**
3048
 *	Return true if email syntax is ok.
3049
 *
3050
 *	@param	    string		$address    			email (Ex: "[email protected]". Long form "John Do <[email protected]>" will be false)
3051
 *  @param		int			$acceptsupervisorkey	If 1, the special string '__SUPERVISOREMAIL__' is also accepted as valid
3052
 *	@return     boolean     						true if email syntax is OK, false if KO or empty string
3053
 *  @see isValidMXRecord()
3054
 */
3055
function isValidEmail($address, $acceptsupervisorkey = 0)
3056
{
3057
	if ($acceptsupervisorkey && $address == '__SUPERVISOREMAIL__') return true;
3058
	if (filter_var($address, FILTER_VALIDATE_EMAIL)) return true;
3059
3060
	return false;
3061
}
3062
3063
/**
3064
 *	Return if the domain name has a valid MX record.
3065
 *  WARNING: This need function idn_to_ascii, checkdnsrr and getmxrr
3066
 *
3067
 *	@param	    string		$domain	    			Domain name (Ex: "yahoo.com", "yhaoo.com", "dolibarr.fr")
3068
 *	@return     int     							-1 if error (function not available), 0=Not valid, 1=Valid
3069
 *  @see isValidEmail()
3070
 */
3071
function isValidMXRecord($domain)
3072
{
3073
	if (function_exists('idn_to_ascii') && function_exists('checkdnsrr'))
3074
	{
3075
		if (!checkdnsrr(idn_to_ascii($domain), 'MX'))
3076
		{
3077
			return 0;
3078
		}
3079
		if (function_exists('getmxrr'))
3080
		{
3081
			$mxhosts = array();
3082
			$weight = array();
3083
			getmxrr(idn_to_ascii($domain), $mxhosts, $weight);
3084
			if (count($mxhosts) > 1) return 1;
3085
			if (count($mxhosts) == 1 && !empty($mxhosts[0])) return 1;
3086
3087
			return 0;
3088
		}
3089
	}
3090
	return -1;
3091
}
3092
3093
/**
3094
 *  Return true if phone number syntax is ok
3095
 *  TODO Decide what to do with this
3096
 *
3097
 *  @param	string		$phone		phone (Ex: "0601010101")
3098
 *  @return boolean     			true if phone syntax is OK, false if KO or empty string
3099
 */
3100
function isValidPhone($phone)
3101
{
3102
	return true;
3103
}
3104
3105
3106
/**
3107
 * Make a strlen call. Works even if mbstring module not enabled
3108
 *
3109
 * @param   string		$string				String to calculate length
3110
 * @param   string		$stringencoding		Encoding of string
3111
 * @return  int								Length of string
3112
 */
3113
function dol_strlen($string, $stringencoding = 'UTF-8')
3114
{
3115
	if (function_exists('mb_strlen')) return mb_strlen($string, $stringencoding);
3116
	else return strlen($string);
3117
}
3118
3119
/**
3120
 * Make a substring. Works even if mbstring module is not enabled for better compatibility.
3121
 *
3122
 * @param	string	$string				String to scan
3123
 * @param	string	$start				Start position
3124
 * @param	int		$length				Length (in nb of characters or nb of bytes depending on trunconbytes param)
3125
 * @param   string	$stringencoding		Page code used for input string encoding
3126
 * @param	int		$trunconbytes		1=Length is max of bytes instead of max of characters
3127
 * @return  string						substring
3128
 */
3129
function dol_substr($string, $start, $length, $stringencoding = '', $trunconbytes = 0)
3130
{
3131
	global $langs;
3132
3133
	if (empty($stringencoding)) $stringencoding = $langs->charset_output;
3134
3135
	$ret = '';
3136
	if (empty($trunconbytes))
3137
	{
3138
		if (function_exists('mb_substr'))
3139
		{
3140
			$ret = mb_substr($string, $start, $length, $stringencoding);
3141
		} else {
3142
			$ret = substr($string, $start, $length);
3143
		}
3144
	} else {
3145
		if (function_exists('mb_strcut'))
3146
		{
3147
			$ret = mb_strcut($string, $start, $length, $stringencoding);
3148
		} else {
3149
			$ret = substr($string, $start, $length);
3150
		}
3151
	}
3152
	return $ret;
3153
}
3154
3155
3156
/**
3157
 *	Truncate a string to a particular length adding '...' if string larger than length.
3158
 * 	If length = max length+1, we do no truncate to avoid having just 1 char replaced with '...'.
3159
 *  MAIN_DISABLE_TRUNC=1 can disable all truncings
3160
 *
3161
 *	@param	string	$string				String to truncate
3162
 *	@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 ...)
3163
 *	@param	string	$trunc				Where to trunc: 'right', 'left', 'middle' (size must be a 2 power), 'wrap'
3164
 * 	@param	string	$stringencoding		Tell what is source string encoding
3165
 *  @param	int		$nodot				Truncation do not add ... after truncation. So it's an exact truncation.
3166
 *  @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)
3167
 *	@return string						Truncated string. WARNING: length is never higher than $size if $nodot is set, but can be 3 chars higher otherwise.
3168
 */
3169
function dol_trunc($string, $size = 40, $trunc = 'right', $stringencoding = 'UTF-8', $nodot = 0, $display = 0)
3170
{
3171
	global $conf;
3172
3173
	if ($size == 0 || !empty($conf->global->MAIN_DISABLE_TRUNC)) return $string;
3174
3175
	if (empty($stringencoding)) $stringencoding = 'UTF-8';
3176
	// reduce for small screen
3177
	if ($conf->dol_optimize_smallscreen == 1 && $display == 1) $size = round($size / 3);
3178
3179
	// We go always here
3180
	if ($trunc == 'right')
3181
	{
3182
		$newstring = dol_textishtml($string) ?dol_string_nohtmltag($string, 1) : $string;
3183
		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 ...
3184
		return dol_substr($newstring, 0, $size, $stringencoding).($nodot ? '' : '...');
3185
		else //return 'u'.$size.'-'.$newstring.'-'.dol_strlen($newstring,$stringencoding).'-'.$string;
3186
		return $string;
3187
	} elseif ($trunc == 'middle')
3188
	{
3189
		$newstring = dol_textishtml($string) ?dol_string_nohtmltag($string, 1) : $string;
3190
		if (dol_strlen($newstring, $stringencoding) > 2 && dol_strlen($newstring, $stringencoding) > ($size + 1))
3191
		{
3192
			$size1 = round($size / 2);
3193
			$size2 = round($size / 2);
3194
			return dol_substr($newstring, 0, $size1, $stringencoding).'...'.dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size2, $size2, $stringencoding);
3195
		} else return $string;
3196
	} elseif ($trunc == 'left')
3197
	{
3198
		$newstring = dol_textishtml($string) ?dol_string_nohtmltag($string, 1) : $string;
3199
		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 ...
3200
		return '...'.dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size, $size, $stringencoding);
3201
		else return $string;
3202
	} elseif ($trunc == 'wrap')
3203
	{
3204
		$newstring = dol_textishtml($string) ?dol_string_nohtmltag($string, 1) : $string;
3205
		if (dol_strlen($newstring, $stringencoding) > ($size + 1))
3206
		return dol_substr($newstring, 0, $size, $stringencoding)."\n".dol_trunc(dol_substr($newstring, $size, dol_strlen($newstring, $stringencoding) - $size, $stringencoding), $size, $trunc);
3207
		else return $string;
3208
	} else return 'BadParam3CallingDolTrunc';
3209
}
3210
3211
/**
3212
 *	Show picto whatever it's its name (generic function)
3213
 *
3214
 *	@param      string		$titlealt         		Text on title tag for tooltip. Not used if param notitle is set to 1.
3215
 *	@param      string		$picto       			Name of image file to show ('filenew', ...)
3216
 *													If no extension provided, we use '.png'. Image must be stored into theme/xxx/img directory.
3217
 *                                  				Example: picto.png                  if picto.png is stored into htdocs/theme/mytheme/img
3218
 *                                  				Example: picto.png@mymodule         if picto.png is stored into htdocs/mymodule/img
3219
 *                                  				Example: /mydir/mysubdir/picto.png  if picto.png is stored into htdocs/mydir/mysubdir (pictoisfullpath must be set to 1)
3220
 *	@param		string		$moreatt				Add more attribute on img tag (For example 'style="float: right"')
3221
 *	@param		boolean|int	$pictoisfullpath		If true or 1, image path is a full path
3222
 *	@param		int			$srconly				Return only content of the src attribute of img.
3223
 *  @param		int			$notitle				1=Disable tag title. Use it if you add js tooltip, to avoid duplicate tooltip.
3224
 *  @param		string		$alt					Force alt for bind people
3225
 *  @param		string		$morecss				Add more class css on img tag (For example 'myclascss').
3226
 *  @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.
3227
 *  @return     string       				    	Return img tag
3228
 *  @see        img_object(), img_picto_common()
3229
 */
3230
function img_picto($titlealt, $picto, $moreatt = '', $pictoisfullpath = false, $srconly = 0, $notitle = 0, $alt = '', $morecss = '', $marginleftonlyshort = 2)
3231
{
3232
	global $conf, $langs;
3233
3234
	// We forge fullpathpicto for image to $path/img/$picto. By default, we take DOL_URL_ROOT/theme/$conf->theme/img/$picto
3235
	$url = DOL_URL_ROOT;
3236
	$theme = isset($conf->theme) ? $conf->theme : null;
3237
	$path = 'theme/'.$theme;
3238
	// Define fullpathpicto to use into src
3239
	if ($pictoisfullpath) {
3240
		// Clean parameters
3241
		if (!preg_match('/(\.png|\.gif|\.svg)$/i', $picto)) {
3242
			$picto .= '.png';
3243
		}
3244
		$fullpathpicto = $picto;
3245
		$reg = array();
3246
		if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
3247
			$morecss .= ($morecss ? ' ' : '').$reg[1];
3248
			$moreatt = str_replace('class="'.$reg[1].'"', '', $moreatt);
3249
		}
3250
	} else {
3251
		$pictowithouttext = preg_replace('/(\.png|\.gif|\.svg)$/', '', $picto);
3252
		if (empty($srconly) && in_array($pictowithouttext, array(
3253
				'1downarrow', '1uparrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected',
3254
				'accountancy', 'account', 'accountline', 'action', 'add', 'address', 'bank_account', 'barcode', 'bank', 'bill', 'billa', 'billr', 'billd', 'bookmark', 'bom', 'building',
3255
				'cash-register', 'category', 'check', 'clock', 'close_title', 'company', 'contact', 'contract', 'cron', 'cubes',
3256
				'delete', 'dolly', 'dollyrevert', 'donation', 'download', 'edit', 'ellipsis-h', 'email', 'eraser', 'external-link-alt', 'external-link-square-alt',
3257
				'filter', 'file-code', 'file-export', 'file-import', 'file-upload', 'folder', 'folder-open', 'globe', 'globe-americas', 'grip', 'grip_title', 'group',
3258
				'help', 'holiday',
3259
				'intervention', 'label', 'language', 'link', 'list', 'listlight', 'lot',
3260
				'map-marker-alt', 'member', 'money-bill-alt', 'mrp', 'note', 'next',
3261
				'object_accounting', 'object_account', 'object_accountline', 'object_action', 'object_barcode', 'object_bill', 'object_billa', 'object_billd', 'object_bom',
3262
				'object_category', 'object_conversation', 'object_bookmark', 'object_bug', 'object_dolly', 'object_dollyrevert', 'object_generic', 'object_folder',
3263
				'object_list-alt', 'object_calendar', 'object_calendarweek', 'object_calendarmonth', 'object_calendarday', 'object_calendarperuser',
3264
				'object_cash-register', 'object_company', 'object_contact', 'object_contract', 'object_donation', 'object_dynamicprice',
3265
				'object_globe', 'object_holiday', 'object_hrm', 'object_invoice', 'object_intervention', 'object_label',
3266
				'object_margin', 'object_money-bill-alt', 'object_multicurrency', 'object_order', 'object_payment',
3267
				'object_lot', 'object_mrp', 'object_other',
3268
				'object_payment', 'object_pdf', 'object_product', 'object_propal',
3269
				'object_paragraph', 'object_poll', 'object_printer', 'object_project', 'object_projectpub', 'object_propal', 'object_resource', 'object_rss', 'object_projecttask',
3270
				'object_recruitmentjobposition', 'object_recruitmentcandidature',
3271
				'object_shipment', 'object_share-alt', 'object_supplier_invoice', 'object_supplier_invoicea', 'object_supplier_invoiced', 'object_supplier_order', 'object_supplier_proposal', 'object_service', 'object_stock',
3272
				'object_technic', 'object_ticket', 'object_trip', 'object_user', 'object_group', 'object_member',
3273
				'object_phoning', 'object_phoning_mobile', 'object_phoning_fax', 'object_email', 'object_website',
3274
				'off', 'on', 'order',
3275
				'paiment', 'play', 'pdf', 'playdisabled', 'previous', 'poll', 'printer', 'product', 'propal', 'projecttask', 'stock', 'resize', 'service', 'stats', 'trip',
3276
				'setup', 'share-alt', 'sign-out', 'split', 'stripe-s', 'switch_off', 'switch_on', 'tools', 'unlink', 'uparrow', 'user', 'vcard', 'wrench',
3277
				'jabber', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'youtube', 'google-plus-g', 'whatsapp',
3278
				'chevron-left', 'chevron-right', 'chevron-down', 'chevron-top', 'commercial', 'companies',
3279
				'generic', 'home', 'hrm', 'members', 'products', 'invoicing',
3280
				'payment', 'pencil-ruler', 'preview', 'project', 'projectpub', 'refresh', 'supplier_invoice', 'ticket',
3281
				'error', 'warning',
3282
				'recruitmentcandidature', 'recruitmentjobposition', 'resource',
3283
				'supplier_proposal', 'supplier_order', 'supplier_invoice',
3284
				'title_setup', 'title_accountancy', 'title_bank', 'title_hrm', 'title_agenda'
3285
			)
3286
		)) {
3287
			$pictowithouttext = str_replace('object_', '', $pictowithouttext);
3288
3289
			$fakey = $pictowithouttext;
3290
			$facolor = ''; $fasize = '';
3291
			$fa = 'fas';
3292
			if (in_array($pictowithouttext, array('clock', 'generic', 'minus-square', 'object_generic', 'pdf', 'plus-square', 'note', 'off', 'on', 'object_bookmark', 'bookmark', 'vcard'))) {
3293
				$fa = 'far';
3294
			}
3295
			if (in_array($pictowithouttext, array('black-tie', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'stripe-s', 'youtube', 'google-plus-g', 'whatsapp'))) {
3296
				$fa = 'fab';
3297
			}
3298
3299
			$arrayconvpictotofa = array(
3300
				'account'=>'university', 'accountline'=>'receipt', 'accountancy'=>'money-check-alt', 'action'=>'calendar-alt', 'add'=>'plus-circle', 'address'=> 'address-book',
3301
				'bank_account'=>'university', 'bill'=>'file-invoice-dollar', 'billa'=>'file-excel', 'supplier_invoicea'=>'file-excel', 'billd'=>'file-medical', 'supplier_invoiced'=>'file-medical', 'bom'=>'cubes',
3302
				'company'=>'building', 'contact'=>'address-book', 'contract'=>'suitcase', 'conversation'=>'comments', 'donation'=>'file-alt', 'dynamicprice'=>'hand-holding-usd',
3303
				'setup'=>'cog', 'companies'=>'building', 'products'=>'cube', 'commercial'=>'suitcase', 'invoicing'=>'coins',
3304
				'accounting'=>'chart-line', 'category'=>'tag', 'dollyrevert'=>'dolly',
3305
				'hrm'=>'user-tie', 'margin'=>'calculator', 'members'=>'users', 'ticket'=>'ticket-alt', 'globe'=>'external-link-alt', 'lot'=>'barcode',
3306
				'email'=>'at',
3307
				'edit'=>'pencil-alt', 'grip_title'=>'arrows-alt', 'grip'=>'arrows-alt', 'help'=>'question-circle',
3308
				'generic'=>'file', 'holiday'=>'umbrella-beach', 'label'=>'layer-group',
3309
				'member'=>'users', 'mrp'=>'cubes', 'next'=>'arrow-alt-circle-right',
3310
				'trip'=>'wallet', 'group'=>'users',
3311
				'sign-out'=>'sign-out-alt',
3312
				'switch_off'=>'toggle-off', 'switch_on'=>'toggle-on', 'check'=>'check', 'bookmark'=>'star', 'bookmark'=>'star',
3313
				'bank'=>'university', 'close_title'=>'times', 'delete'=>'trash', 'edit'=>'pencil-alt', 'filter'=>'filter',
3314
				'list-alt'=>'list-alt', 'calendar'=>'calendar-alt', 'calendarweek'=>'calendar-week', 'calendarmonth'=>'calendar-alt', 'calendarday'=>'calendar-day', 'calendarperuser'=>'table',
3315
				'intervention'=>'ambulance', 'invoice'=>'file-invoice-dollar', 'multicurrency'=>'dollar-sign', 'order'=>'file-invoice',
3316
				'error'=>'exclamation-triangle', 'warning'=>'exclamation-triangle',
3317
				'other'=>'square',
3318
				'playdisabled'=>'play', 'pdf'=>'file-pdf',  'poll'=>'check-double', 'preview'=>'binoculars', 'project'=>'sitemap', 'projectpub'=>'sitemap', 'projecttask'=>'tasks', 'propal'=>'file-signature',
3319
				'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',
3320
				'recruitmentjobposition'=>'id-card-alt', 'recruitmentcandidature'=>'id-badge',
3321
				'resize'=>'crop', 'supplier_order'=>'dol-order_supplier', 'supplier_proposal'=>'file-signature',
3322
				'refresh'=>'redo', 'resource'=>'laptop-house',
3323
				'shipment'=>'dolly', 'stock'=>'box-open', 'stats' => 'chart-bar', 'split'=>'code-branch', 'supplier_invoice'=>'file-invoice-dollar', 'technic'=>'cogs', 'ticket'=>'ticket-alt',
3324
				'title_setup'=>'tools', 'title_accountancy'=>'money-check-alt', 'title_bank'=>'university', 'title_hrm'=>'umbrella-beach',
3325
				'title_agenda'=>'calendar-alt',
3326
				'uparrow'=>'mail-forward', 'vcard'=>'address-card',
3327
				'jabber'=>'comment-o',
3328
				'website'=>'globe-americas'
3329
			);
3330
			if ($pictowithouttext == 'off') {
3331
				$fakey = 'fa-square';
3332
				$fasize = '1.3em';
3333
			} elseif ($pictowithouttext == 'on') {
3334
				$fakey = 'fa-check-square';
3335
				$fasize = '1.3em';
3336
			} elseif ($pictowithouttext == 'listlight') {
3337
				$fakey = 'fa-download';
3338
				$marginleftonlyshort = 1;
3339
			} elseif ($pictowithouttext == 'printer') {
3340
				$fakey = 'fa-print';
3341
				$fasize = '1.2em';
3342
			} elseif ($pictowithouttext == 'note') {
3343
				$fakey = 'fa-sticky-note';
3344
				$marginleftonlyshort = 1;
3345
			} elseif (in_array($pictowithouttext, array('1uparrow', '1downarrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected'))) {
3346
				$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');
3347
				$fakey = 'fa-'.$convertarray[$pictowithouttext];
3348
				if (preg_match('/selected/', $pictowithouttext)) $facolor = '#888';
3349
				$marginleftonlyshort = 1;
3350
			} elseif (!empty($arrayconvpictotofa[$pictowithouttext])) {
3351
				$fakey = 'fa-'.$arrayconvpictotofa[$pictowithouttext];
3352
			} else {
3353
				$fakey = 'fa-'.$pictowithouttext;
3354
			}
3355
3356
			// Define $marginleftonlyshort
3357
			$arrayconvpictotomarginleftonly = array(
3358
				'bank', 'check', 'delete', 'generic', 'grip', 'grip_title', 'jabber',
3359
				'grip_title', 'grip', 'listlight', 'note', 'on', 'off', 'playdisabled', 'printer', 'resize', 'sign-out', 'stats', 'switch_on', 'switch_off',
3360
				'uparrow', '1uparrow', '1downarrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected'
3361
			);
3362
			if (!isset($arrayconvpictotomarginleftonly[$pictowithouttext])) {
3363
				$marginleftonlyshort = 0;
3364
			}
3365
3366
			// Add CSS
3367
			$arrayconvpictotomorcess = array(
3368
				'action'=>'infobox-action', 'account'=>'infobox-bank_account', 'accountline'=>'infobox-bank_account', 'accountancy'=>'infobox-bank_account',
3369
				'bank_account'=>'bg-infobox-bank_account',
3370
				'bill'=>'infobox-commande', 'billa'=>'infobox-commande', 'billd'=>'infobox-commande',
3371
				'cash-register'=>'infobox-bank_account', 'contract'=>'infobox-contrat', 'check'=>'font-status4', 'conversation'=>'infobox-contrat',
3372
				'donation'=>'infobox-commande', 'dollyrevert'=>'flip', 'ecm'=>'infobox-action',
3373
				'hrm'=>'infobox-adherent', 'group'=>'infobox-adherent', 'intervention'=>'infobox-contrat',
3374
				'multicurrency'=>'infobox-bank_account',
3375
				'members'=>'infobox-adherent', 'member'=>'infobox-adherent', 'money-bill-alt'=>'infobox-bank_account',
3376
				'order'=>'infobox-commande',
3377
				'user'=>'infobox-adherent', 'users'=>'infobox-adherent',
3378
				'error'=>'pictoerror', 'warning'=>'pictowarning', 'switch_on'=>'font-status4',
3379
				'holiday'=>'infobox-holiday', 'invoice'=>'infobox-commande',
3380
				'payment'=>'infobox-bank_account', 'poll'=>'infobox-adherent', 'project'=>'infobox-project', 'projecttask'=>'infobox-project', 'propal'=>'infobox-propal',
3381
				'recruitmentjobposition'=>'infobox-adherent', 'recruitmentcandidature'=>'infobox-adherent',
3382
				'resource'=>'infobox-action',
3383
				'supplier_invoice'=>'infobox-order_supplier', 'supplier_invoicea'=>'infobox-order_supplier', 'supplier_invoiced'=>'infobox-order_supplier',
3384
				'supplier_order'=>'infobox-order_supplier', 'supplier_proposal'=>'infobox-supplier_proposal',
3385
				'ticket'=>'infobox-contrat', 'title_accountancy'=>'infobox-bank_account', 'title_hrm'=>'infobox-holiday', 'trip'=>'infobox-expensereport', 'title_agenda'=>'infobox-action',
3386
				//'title_setup'=>'infobox-action', 'tools'=>'infobox-action',
3387
				'list-alt'=>'imgforviewmode', 'calendar'=>'imgforviewmode', 'calendarweek'=>'imgforviewmode', 'calendarmonth'=>'imgforviewmode', 'calendarday'=>'imgforviewmode', 'calendarperuser'=>'imgforviewmode'
3388
			);
3389
			if (!empty($arrayconvpictotomorcess[$pictowithouttext])) {
3390
				$morecss .= ($morecss ? ' ' : '').$arrayconvpictotomorcess[$pictowithouttext];
3391
			}
3392
3393
			// Define $color
3394
			$arrayconvpictotocolor = array(
3395
				'address'=>'#6c6aa8', 'building'=>'#6c6aa8', 'bom'=>'#a69944',
3396
				'companies'=>'#6c6aa8', 'company'=>'#6c6aa8', 'contact'=>'#6c6aa8', 'dynamicprice'=>'#a69944',
3397
				'edit'=>'#444', 'note'=>'#999', 'error'=>'', 'help'=>'#bbb', 'listlight'=>'#999',
3398
				'dolly'=>'#a69944', 'dollyrevert'=>'#a69944', 'lot'=>'#a69944',
3399
				'map-marker-alt'=>'#aaa', 'mrp'=>'#a69944', 'product'=>'#a69944', 'service'=>'#a69944', 'stock'=>'#a69944',
3400
				'other'=>'#ddd',
3401
				'playdisabled'=>'#ccc', 'printer'=>'#444', 'projectpub'=>'#986c6a', 'resize'=>'#444', 'rss'=>'#cba',
3402
				'shipment'=>'#a69944', 'stats'=>'#444', 'switch_off'=>'#999', 'uparrow'=>'#555', 'globe-americas'=>'#aaa',
3403
				'website'=>'#304'
3404
			);
3405
			if (isset($arrayconvpictotocolor[$pictowithouttext])) {
3406
				$facolor = $arrayconvpictotocolor[$pictowithouttext];
3407
			}
3408
3409
			// This snippet only needed since function img_edit accepts only one additional parameter: no separate one for css only.
3410
			// class/style need to be extracted to avoid duplicate class/style validation errors when $moreatt is added to the end of the attributes.
3411
			$morestyle = '';
3412
			$reg = array();
3413
			if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
3414
				$morecss .= ($morecss ? ' ' : '').$reg[1];
3415
				$moreatt = str_replace('class="'.$reg[1].'"', '', $moreatt);
3416
			}
3417
			if (preg_match('/style="([^"]+)"/', $moreatt, $reg)) {
3418
				$morestyle = $reg[1];
3419
				$moreatt = str_replace('style="'.$reg[1].'"', '', $moreatt);
3420
			}
3421
			$moreatt = trim($moreatt);
3422
3423
			$enabledisablehtml = '<span class="'.$fa.' '.$fakey.($marginleftonlyshort ? ($marginleftonlyshort == 1 ? ' marginleftonlyshort' : ' marginleftonly') : '');
3424
			$enabledisablehtml .= ($morecss ? ' '.$morecss : '').'" style="'.($fasize ? ('font-size: '.$fasize.';') : '').($facolor ? (' color: '.$facolor.';') : '').($morestyle ? ' '.$morestyle : '').'"'.(($notitle || empty($titlealt)) ? '' : ' title="'.dol_escape_htmltag($titlealt).'"').($moreatt ? ' '.$moreatt : '').'>';
3425
			/*if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
3426
				$enabledisablehtml .= $titlealt;
3427
			}*/
3428
			$enabledisablehtml .= '</span>';
3429
3430
			return $enabledisablehtml;
3431
		}
3432
3433
		if (!empty($conf->global->MAIN_OVERWRITE_THEME_PATH)) {
3434
			$path = $conf->global->MAIN_OVERWRITE_THEME_PATH.'/theme/'.$theme; // If the theme does not have the same name as the module
3435
		} elseif (!empty($conf->global->MAIN_OVERWRITE_THEME_RES)) {
3436
			$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
3437
		} elseif (!empty($conf->modules_parts['theme']) && array_key_exists($theme, $conf->modules_parts['theme'])) {
3438
			$path = $theme.'/theme/'.$theme; // If the theme have the same name as the module
3439
		}
3440
3441
		// If we ask an image into $url/$mymodule/img (instead of default path)
3442
		$regs = array();
3443
		if (preg_match('/^([^@]+)@([^@]+)$/i', $picto, $regs)) {
3444
			$picto = $regs[1];
3445
			$path = $regs[2]; // $path is $mymodule
3446
		}
3447
3448
		// Clean parameters
3449
		if (!preg_match('/(\.png|\.gif|\.svg)$/i', $picto)) {
3450
			$picto .= '.png';
3451
		}
3452
		// If alt path are defined, define url where img file is, according to physical path
3453
		// ex: array(["main"]=>"/home/maindir/htdocs", ["alt0"]=>"/home/moddir0/htdocs", ...)
3454
		foreach ($conf->file->dol_document_root as $type => $dirroot) {
3455
			if ($type == 'main') {
3456
				continue;
3457
			}
3458
			// This need a lot of time, that's why enabling alternative dir like "custom" dir is not recommanded
3459
			if (file_exists($dirroot.'/'.$path.'/img/'.$picto)) {
3460
				$url = DOL_URL_ROOT.$conf->file->dol_url_root[$type];
3461
				break;
3462
			}
3463
		}
3464
3465
		// $url is '' or '/custom', $path is current theme or
3466
		$fullpathpicto = $url.'/'.$path.'/img/'.$picto;
3467
	}
3468
3469
	if ($srconly) {
3470
		return $fullpathpicto;
3471
	}
3472
		// tag title is used for tooltip on <a>, tag alt can be used with very simple text on image for blind people
3473
	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
3474
}
3475
3476
/**
3477
 *	Show a picto called object_picto (generic function)
3478
 *
3479
 *	@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.
3480
 *	@param	string	$picto				Name of image to show object_picto (example: user, group, action, bill, contract, propal, product, ...)
3481
 *										For external modules use imagename@mymodule to search into directory "img" of module.
3482
 *	@param	string	$moreatt			Add more attribute on img tag (ie: class="datecallink")
3483
 *	@param	int		$pictoisfullpath	If 1, image path is a full path
3484
 *	@param	int		$srconly			Return only content of the src attribute of img.
3485
 *  @param	int		$notitle			1=Disable tag title. Use it if you add js tooltip, to avoid duplicate tooltip.
3486
 *	@return	string						Return img tag
3487
 *	@see	img_picto(), img_picto_common()
3488
 */
3489
function img_object($titlealt, $picto, $moreatt = '', $pictoisfullpath = false, $srconly = 0, $notitle = 0)
3490
{
3491
	if (strpos($picto, '^') === 0) return img_picto($titlealt, str_replace('^', '', $picto), $moreatt, $pictoisfullpath, $srconly, $notitle);
3492
	else return img_picto($titlealt, 'object_'.$picto, $moreatt, $pictoisfullpath, $srconly, $notitle);
3493
}
3494
3495
/**
3496
 *	Show weather picto
3497
 *
3498
 *	@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.
3499
 *	@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).
3500
 *	@param		string		$moreatt			Add more attribute on img tag
3501
 *	@param		int			$pictoisfullpath	If 1, image path is a full path
3502
 *  @param      string      $morecss            More CSS
3503
 *	@return     string      					Return img tag
3504
 *  @see        img_object(), img_picto()
3505
 */
3506
function img_weather($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $morecss = '')
3507
{
3508
	global $conf;
3509
3510
	if (is_numeric($picto)) {
3511
		//$leveltopicto = array(0=>'weather-clear.png', 1=>'weather-few-clouds.png', 2=>'weather-clouds.png', 3=>'weather-many-clouds.png', 4=>'weather-storm.png');
3512
		//$picto = $leveltopicto[$picto];
3513
		return '<i class="fa fa-weather-level'.$picto.'"></i>';
3514
	} elseif (!preg_match('/(\.png|\.gif)$/i', $picto)) {
3515
		$picto .= '.png';
3516
	}
3517
3518
	$path = DOL_URL_ROOT.'/theme/'.$conf->theme.'/img/weather/'.$picto;
3519
3520
	return img_picto($titlealt, $path, $moreatt, 1, 0, 0, '', $morecss);
3521
}
3522
3523
/**
3524
 *	Show picto (generic function)
3525
 *
3526
 *	@param      string		$titlealt         	Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3527
 *	@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.
3528
 *	@param		string		$moreatt			Add more attribute on img tag
3529
 *	@param		int			$pictoisfullpath	If 1, image path is a full path
3530
 *	@return     string      					Return img tag
3531
 *  @see        img_object(), img_picto()
3532
 */
3533
function img_picto_common($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0)
3534
{
3535
	global $conf;
3536
3537
	if (!preg_match('/(\.png|\.gif)$/i', $picto)) $picto .= '.png';
3538
3539
	if ($pictoisfullpath) $path = $picto;
3540
	else {
3541
		$path = DOL_URL_ROOT.'/theme/common/'.$picto;
3542
3543
		if (!empty($conf->global->MAIN_MODULE_CAN_OVERWRITE_COMMONICONS))
3544
		{
3545
			$themepath = DOL_DOCUMENT_ROOT.'/theme/'.$conf->theme.'/img/'.$picto;
3546
3547
			if (file_exists($themepath)) $path = $themepath;
3548
		}
3549
	}
3550
3551
	return img_picto($titlealt, $path, $moreatt, 1);
3552
}
3553
3554
/**
3555
 *	Show logo action
3556
 *
3557
 *	@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.
3558
 *	@param  string		$numaction   	Action id or code to show
3559
 *	@param 	string		$picto      	Name of image file to show ('filenew', ...)
3560
 *                                      If no extension provided, we use '.png'. Image must be stored into theme/xxx/img directory.
3561
 *                                      Example: picto.png                  if picto.png is stored into htdocs/theme/mytheme/img
3562
 *                                      Example: picto.png@mymodule         if picto.png is stored into htdocs/mymodule/img
3563
 *                                      Example: /mydir/mysubdir/picto.png  if picto.png is stored into htdocs/mydir/mysubdir (pictoisfullpath must be set to 1)
3564
 *	@return string      				Return an img tag
3565
 */
3566
function img_action($titlealt, $numaction, $picto = '')
3567
{
3568
	global $langs;
3569
3570
	if (empty($titlealt) || $titlealt == 'default')
3571
	{
3572
		if ($numaction == '-1' || $numaction == 'ST_NO') {
3573
			$numaction = -1;
3574
			$titlealt = $langs->transnoentitiesnoconv('ChangeDoNotContact');
3575
		} elseif ($numaction == '0' || $numaction == 'ST_NEVER') {
3576
			$numaction = 0;
3577
			$titlealt = $langs->transnoentitiesnoconv('ChangeNeverContacted');
3578
		} elseif ($numaction == '1' || $numaction == 'ST_TODO') {
3579
			$numaction = 1;
3580
			$titlealt = $langs->transnoentitiesnoconv('ChangeToContact');
3581
		} elseif ($numaction == '2' || $numaction == 'ST_PEND') {
3582
			$numaction = 2;
3583
			$titlealt = $langs->transnoentitiesnoconv('ChangeContactInProcess');
3584
		} elseif ($numaction == '3' || $numaction == 'ST_DONE') {
3585
			$numaction = 3;
3586
			$titlealt = $langs->transnoentitiesnoconv('ChangeContactDone');
3587
		} else {
3588
			$titlealt = $langs->transnoentitiesnoconv('ChangeStatus '.$numaction);
3589
			$numaction = 0;
3590
		}
3591
	}
3592
	if (!is_numeric($numaction)) $numaction = 0;
3593
3594
	return img_picto($titlealt, !empty($picto) ? $picto : 'stcomm'.$numaction.'.png');
3595
}
3596
3597
/**
3598
 *  Show pdf logo
3599
 *
3600
 *  @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.
3601
 *  @param  int		    $size       Taille de l'icone : 3 = 16x16px , 2 = 14x14px
3602
 *  @return string      			Retourne tag img
3603
 */
3604
function img_pdf($titlealt = 'default', $size = 3)
3605
{
3606
	global $langs;
3607
3608
	if ($titlealt == 'default') $titlealt = $langs->trans('Show');
3609
3610
	return img_picto($titlealt, 'pdf'.$size.'.png');
3611
}
3612
3613
/**
3614
 *	Show logo +
3615
 *
3616
 *	@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.
3617
 *	@param  string	$other      Add more attributes on img
3618
 *	@return string      		Return tag img
3619
 */
3620
function img_edit_add($titlealt = 'default', $other = '')
3621
{
3622
	global $langs;
3623
3624
	if ($titlealt == 'default') $titlealt = $langs->trans('Add');
3625
3626
	return img_picto($titlealt, 'edit_add.png', $other);
3627
}
3628
/**
3629
 *	Show logo -
3630
 *
3631
 *	@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.
3632
 *	@param  string	$other      Add more attributes on img
3633
 *	@return string      		Return tag img
3634
 */
3635
function img_edit_remove($titlealt = 'default', $other = '')
3636
{
3637
	global $langs;
3638
3639
	if ($titlealt == 'default') $titlealt = $langs->trans('Remove');
3640
3641
	return img_picto($titlealt, 'edit_remove.png', $other);
3642
}
3643
3644
/**
3645
 *	Show logo editer/modifier fiche
3646
 *
3647
 *	@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.
3648
 *	@param  integer	$float      If you have to put the style "float: right"
3649
 *	@param  string	$other		Add more attributes on img
3650
 *	@return string      		Return tag img
3651
 */
3652
function img_edit($titlealt = 'default', $float = 0, $other = '')
3653
{
3654
	global $langs;
3655
3656
	if ($titlealt == 'default') $titlealt = $langs->trans('Modify');
3657
3658
	return img_picto($titlealt, 'edit.png', ($float ? 'style="float: '.($langs->tab_translate["DIRECTION"] == 'rtl' ? 'left' : 'right').'"' : "").($other ? ' '.$other : ''));
3659
}
3660
3661
/**
3662
 *	Show logo view card
3663
 *
3664
 *	@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.
3665
 *	@param  integer	$float      If you have to put the style "float: right"
3666
 *	@param  string	$other		Add more attributes on img
3667
 *	@return string      		Return tag img
3668
 */
3669
function img_view($titlealt = 'default', $float = 0, $other = '')
3670
{
3671
	global $langs;
3672
3673
	if ($titlealt == 'default') $titlealt = $langs->trans('View');
3674
3675
	$moreatt = ($float ? 'style="float: right" ' : '').$other;
3676
3677
	return img_picto($titlealt, 'view.png', $moreatt);
3678
}
3679
3680
/**
3681
 *  Show delete logo
3682
 *
3683
 *  @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.
3684
 *	@param  string	$other      Add more attributes on img
3685
 *  @param	string	$morecss	More CSS
3686
 *  @return string      		Retourne tag img
3687
 */
3688
function img_delete($titlealt = 'default', $other = 'class="pictodelete"', $morecss = '')
3689
{
3690
	global $langs;
3691
3692
	if ($titlealt == 'default') $titlealt = $langs->trans('Delete');
3693
3694
	return img_picto($titlealt, 'delete.png', $other, false, 0, 0, '', $morecss);
3695
}
3696
3697
/**
3698
 *  Show printer logo
3699
 *
3700
 *  @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.
3701
 *  @param  string  $other      Add more attributes on img
3702
 *  @return string              Retourne tag img
3703
 */
3704
function img_printer($titlealt = "default", $other = '')
3705
{
3706
	global $langs;
3707
	if ($titlealt == "default") $titlealt = $langs->trans("Print");
3708
	return img_picto($titlealt, 'printer.png', $other);
3709
}
3710
3711
/**
3712
 *  Show split logo
3713
 *
3714
 *  @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.
3715
 *	@param  string	$other      Add more attributes on img
3716
 *  @return string      		Retourne tag img
3717
 */
3718
function img_split($titlealt = 'default', $other = 'class="pictosplit"')
3719
{
3720
	global $langs;
3721
3722
	if ($titlealt == 'default') $titlealt = $langs->trans('Split');
3723
3724
	return img_picto($titlealt, 'split.png', $other);
3725
}
3726
3727
/**
3728
 *	Show help logo with cursor "?"
3729
 *
3730
 * 	@param	int              	$usehelpcursor		1=Use help cursor, 2=Use click pointer cursor, 0=No specific cursor
3731
 * 	@param	int|string	        $usealttitle		Text to use as alt title
3732
 * 	@return string            	           			Return tag img
3733
 */
3734
function img_help($usehelpcursor = 1, $usealttitle = 1)
3735
{
3736
	global $langs;
3737
3738
	if ($usealttitle)
3739
	{
3740
		if (is_string($usealttitle)) $usealttitle = dol_escape_htmltag($usealttitle);
3741
		else $usealttitle = $langs->trans('Info');
3742
	}
3743
3744
	return img_picto($usealttitle, 'info.png', 'style="vertical-align: middle;'.($usehelpcursor == 1 ? ' cursor: help' : ($usehelpcursor == 2 ? ' cursor: pointer' : '')).'"');
3745
}
3746
3747
/**
3748
 *	Show info logo
3749
 *
3750
 *	@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.
3751
 *	@return string      		Return img tag
3752
 */
3753
function img_info($titlealt = 'default')
3754
{
3755
	global $langs;
3756
3757
	if ($titlealt == 'default') $titlealt = $langs->trans('Informations');
3758
3759
	return img_picto($titlealt, 'info.png', 'style="vertical-align: middle;"');
3760
}
3761
3762
/**
3763
 *	Show warning logo
3764
 *
3765
 *	@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.
3766
 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"'). If 1, add float: right. Can't be "class" attribute.
3767
 *  @param	string  $morecss	Add more CSS
3768
 *	@return string      		Return img tag
3769
 */
3770
function img_warning($titlealt = 'default', $moreatt = '', $morecss = 'pictowarning')
3771
{
3772
	global $langs;
3773
3774
	if ($titlealt == 'default') $titlealt = $langs->trans('Warning');
3775
3776
	//return '<div class="imglatecoin">'.img_picto($titlealt, 'warning_white.png', 'class="pictowarning valignmiddle"'.($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' '.$moreatt): '')).'</div>';
3777
	return img_picto($titlealt, 'warning.png', 'class="'.$morecss.'"'.($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' '.$moreatt) : ''));
3778
}
3779
3780
/**
3781
 *  Show error logo
3782
 *
3783
 *	@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.
3784
 *	@return string      		Return img tag
3785
 */
3786
function img_error($titlealt = 'default')
3787
{
3788
	global $langs;
3789
3790
	if ($titlealt == 'default') $titlealt = $langs->trans('Error');
3791
3792
	return img_picto($titlealt, 'error.png');
3793
}
3794
3795
/**
3796
 *	Show next logo
3797
 *
3798
 *	@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.
3799
*	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
3800
 *	@return string      		Return img tag
3801
 */
3802
function img_next($titlealt = 'default', $moreatt = '')
3803
{
3804
	global $langs;
3805
3806
	if ($titlealt == 'default') $titlealt = $langs->trans('Next');
3807
3808
	//return img_picto($titlealt, 'next.png', $moreatt);
3809
	return '<span class="fa fa-chevron-right paddingright paddingleft" title="'.dol_escape_htmltag($titlealt).'"></span>';
3810
}
3811
3812
/**
3813
 *	Show previous logo
3814
 *
3815
 *	@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.
3816
 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
3817
 *	@return string      		Return img tag
3818
 */
3819
function img_previous($titlealt = 'default', $moreatt = '')
3820
{
3821
	global $langs;
3822
3823
	if ($titlealt == 'default') $titlealt = $langs->trans('Previous');
3824
3825
	//return img_picto($titlealt, 'previous.png', $moreatt);
3826
	return '<span class="fa fa-chevron-left paddingright paddingleft" title="'.dol_escape_htmltag($titlealt).'"></span>';
3827
}
3828
3829
/**
3830
 *	Show down arrow logo
3831
 *
3832
 *	@param	string	$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3833
 *	@param  int		$selected   Selected
3834
 *  @param	string	$moreclass	Add more CSS classes
3835
 *	@return string      		Return img tag
3836
 */
3837
function img_down($titlealt = 'default', $selected = 0, $moreclass = '')
3838
{
3839
	global $langs;
3840
3841
	if ($titlealt == 'default') $titlealt = $langs->trans('Down');
3842
3843
	return img_picto($titlealt, ($selected ? '1downarrow_selected.png' : '1downarrow.png'), 'class="imgdown'.($moreclass ? " ".$moreclass : "").'"');
3844
}
3845
3846
/**
3847
 *	Show top arrow logo
3848
 *
3849
 *	@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.
3850
 *	@param  int		$selected	Selected
3851
 *  @param	string	$moreclass	Add more CSS classes
3852
 *	@return string      		Return img tag
3853
 */
3854
function img_up($titlealt = 'default', $selected = 0, $moreclass = '')
3855
{
3856
	global $langs;
3857
3858
	if ($titlealt == 'default') $titlealt = $langs->trans('Up');
3859
3860
	return img_picto($titlealt, ($selected ? '1uparrow_selected.png' : '1uparrow.png'), 'class="imgup'.($moreclass ? " ".$moreclass : "").'"');
3861
}
3862
3863
/**
3864
 *	Show left arrow logo
3865
 *
3866
 *	@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.
3867
 *	@param  int		$selected	Selected
3868
 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
3869
 *	@return string      		Return img tag
3870
 */
3871
function img_left($titlealt = 'default', $selected = 0, $moreatt = '')
3872
{
3873
	global $langs;
3874
3875
	if ($titlealt == 'default') {
3876
		$titlealt = $langs->trans('Left');
3877
	}
3878
3879
	return img_picto($titlealt, ($selected ? '1leftarrow_selected.png' : '1leftarrow.png'), $moreatt);
3880
}
3881
3882
/**
3883
 *	Show right arrow logo
3884
 *
3885
 *	@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.
3886
 *	@param  int		$selected	Selected
3887
 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
3888
 *	@return string      		Return img tag
3889
 */
3890
function img_right($titlealt = 'default', $selected = 0, $moreatt = '')
3891
{
3892
	global $langs;
3893
3894
	if ($titlealt == 'default') $titlealt = $langs->trans('Right');
3895
3896
	return img_picto($titlealt, ($selected ? '1rightarrow_selected.png' : '1rightarrow.png'), $moreatt);
3897
}
3898
3899
/**
3900
 *	Show tick logo if allowed
3901
 *
3902
 *	@param	string	$allow		Allow
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
 *	@return string      		Return img tag
3905
 */
3906
function img_allow($allow, $titlealt = 'default')
3907
{
3908
	global $langs;
3909
3910
	if ($titlealt == 'default') $titlealt = $langs->trans('Active');
3911
3912
	if ($allow == 1) return img_picto($titlealt, 'tick.png');
3913
3914
	return '-';
3915
}
3916
3917
/**
3918
 *	Return image of a credit card according to its brand name
3919
 *
3920
 *	@param  string	$brand		Brand name of credit card
3921
 *  @param  string	$morecss	More CSS
3922
 *	@return string     			Return img tag
3923
 */
3924
function img_credit_card($brand, $morecss = null)
3925
{
3926
	if (is_null($morecss)) $morecss = 'fa-2x';
3927
3928
	if ($brand == 'visa' || $brand == 'Visa') {
3929
		$brand = 'cc-visa';
3930
	} elseif ($brand == 'mastercard' || $brand == 'MasterCard') {
3931
		$brand = 'cc-mastercard';
3932
	} elseif ($brand == 'amex' || $brand == 'American Express') {
3933
		$brand = 'cc-amex';
3934
	} elseif ($brand == 'discover' || $brand == 'Discover') {
3935
		$brand = 'cc-discover';
3936
	} elseif ($brand == 'jcb' || $brand == 'JCB') {
3937
		$brand = 'cc-jcb';
3938
	} elseif ($brand == 'diners' || $brand == 'Diners club') {
3939
		$brand = 'cc-diners-club';
3940
	} elseif (!in_array($brand, array('cc-visa', 'cc-mastercard', 'cc-amex', 'cc-discover', 'cc-jcb', 'cc-diners-club'))) {
3941
		$brand = 'credit-card';
3942
	}
3943
3944
	return '<span class="fa fa-'.$brand.' fa-fw'.($morecss ? ' '.$morecss : '').'"></span>';
3945
}
3946
3947
/**
3948
 *	Show MIME img of a file
3949
 *
3950
 *	@param	string	$file		Filename
3951
 * 	@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.
3952
 *  @param	string	$morecss	More css
3953
 *	@return string     			Return img tag
3954
 */
3955
function img_mime($file, $titlealt = '', $morecss = '')
3956
{
3957
	require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3958
3959
	$mimetype = dol_mimetype($file, '', 1);
3960
	$mimeimg = dol_mimetype($file, '', 2);
3961
	$mimefa = dol_mimetype($file, '', 4);
3962
3963
	if (empty($titlealt)) $titlealt = 'Mime type: '.$mimetype;
3964
3965
	//return img_picto_common($titlealt, 'mime/'.$mimeimg, 'class="'.$morecss.'"');
3966
	return '<i class="fa fa-'.$mimefa.' paddingright"'.($titlealt ? ' title="'.$titlealt.'"' : '').'></i>';
3967
}
3968
3969
3970
/**
3971
 *  Show search logo
3972
 *
3973
 *  @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.
3974
 *	@param  string	$other      Add more attributes on img
3975
 *  @return string      		Retourne tag img
3976
 */
3977
function img_search($titlealt = 'default', $other = '')
3978
{
3979
	global $conf, $langs;
3980
3981
	if ($titlealt == 'default') $titlealt = $langs->trans('Search');
3982
3983
	$img = img_picto($titlealt, 'search.png', $other, false, 1);
3984
3985
	$input = '<input type="image" class="liste_titre" name="button_search" src="'.$img.'" ';
3986
	$input .= 'value="'.dol_escape_htmltag($titlealt).'" title="'.dol_escape_htmltag($titlealt).'" >';
3987
3988
	return $input;
3989
}
3990
3991
/**
3992
 *  Show search logo
3993
 *
3994
 *  @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.
3995
 *	@param  string	$other      Add more attributes on img
3996
 *  @return string      		Retourne tag img
3997
 */
3998
function img_searchclear($titlealt = 'default', $other = '')
3999
{
4000
	global $conf, $langs;
4001
4002
	if ($titlealt == 'default') $titlealt = $langs->trans('Search');
4003
4004
	$img = img_picto($titlealt, 'searchclear.png', $other, false, 1);
4005
4006
	$input = '<input type="image" class="liste_titre" name="button_removefilter" src="'.$img.'" ';
4007
	$input .= 'value="'.dol_escape_htmltag($titlealt).'" title="'.dol_escape_htmltag($titlealt).'" >';
4008
4009
	return $input;
4010
}
4011
4012
/**
4013
 *	Show information for admin users or standard users
4014
 *
4015
 *	@param	string	$text				Text info
4016
 *	@param  integer	$infoonimgalt		Info is shown only on alt of star picto, otherwise it is show on output after the star picto
4017
 *	@param	int		$nodiv				No div
4018
 *  @param  string  $admin      	    '1'=Info for admin users. '0'=Info for standard users (change only the look), 'error','xxx'=Other
4019
 *  @param	string	$morecss			More CSS ('', 'warning', 'error')
4020
 *  @param	string	$textfordropdown	Show a text to click to dropdown the info box.
4021
 *	@return	string						String with info text
4022
 */
4023
function info_admin($text, $infoonimgalt = 0, $nodiv = 0, $admin = '1', $morecss = '', $textfordropdown = '')
4024
{
4025
	global $conf, $langs;
4026
4027
	if ($infoonimgalt)
4028
	{
4029
		$result = img_picto($text, 'info', 'class="hideonsmartphone'.($morecss ? ' '.$morecss : '').'"');
4030
	} else {
4031
		if (empty($conf->use_javascript_ajax)) $textfordropdown = '';
4032
4033
		$class = (empty($admin) ? 'undefined' : ($admin == '1' ? 'info' : $admin));
4034
		$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>');
4035
4036
		if ($textfordropdown) {
4037
			$tmpresult .= '<span class="'.$class.'text opacitymedium cursorpointer">'.$langs->trans($textfordropdown).' '.img_picto($langs->trans($textfordropdown), '1downarrow').'</span>';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $tmpresult does not exist. Did you maybe mean $result?
Loading history...
4038
			$tmpresult .= '<script type="text/javascript" language="javascript">
4039
				jQuery(document).ready(function() {
4040
					jQuery(".'.$class.'text").click(function() {
4041
						console.log("toggle text");
4042
						jQuery(".'.$class.'").toggle();
4043
					});
4044
				});
4045
				</script>';
4046
4047
			$result = $tmpresult.$result;
4048
		}
4049
	}
4050
4051
	return $result;
4052
}
4053
4054
4055
/**
4056
 *  Displays error message system with all the information to facilitate the diagnosis and the escalation of the bugs.
4057
 *  This function must be called when a blocking technical error is encountered.
4058
 *  However, one must try to call it only within php pages, classes must return their error through their property "error".
4059
 *
4060
 *	@param	 	DoliDB          $db      	Database handler
4061
 *	@param  	string|string[] $error		String or array of errors strings to show
4062
 *  @param		array           $errors		Array of errors
4063
 *	@return 	void
4064
 *  @see    	dol_htmloutput_errors()
4065
 */
4066
function dol_print_error($db = '', $error = '', $errors = null)
4067
{
4068
	global $conf, $langs, $argv;
4069
	global $dolibarr_main_prod;
4070
4071
	$out = '';
4072
	$syslog = '';
4073
4074
	// If error occurs before the $lang object was loaded
4075
	if (!$langs)
4076
	{
4077
		require_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
4078
		$langs = new Translate('', $conf);
4079
		$langs->load("main");
4080
	}
4081
4082
	// Load translation files required by the error messages
4083
	$langs->loadLangs(array('main', 'errors'));
4084
4085
	if ($_SERVER['DOCUMENT_ROOT'])    // Mode web
4086
	{
4087
		$out .= $langs->trans("DolibarrHasDetectedError").".<br>\n";
4088
		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";
4089
		$out .= $langs->trans("InformationToHelpDiagnose").":<br>\n";
4090
4091
		$out .= "<b>".$langs->trans("Date").":</b> ".dol_print_date(time(), 'dayhourlog')."<br>\n";
4092
		$out .= "<b>".$langs->trans("Dolibarr").":</b> ".DOL_VERSION." - https://www.dolibarr.org<br>\n";
4093
		if (isset($conf->global->MAIN_FEATURES_LEVEL)) $out .= "<b>".$langs->trans("LevelOfFeature").":</b> ".$conf->global->MAIN_FEATURES_LEVEL."<br>\n";
4094
		if (function_exists("phpversion"))
4095
		{
4096
			$out .= "<b>".$langs->trans("PHP").":</b> ".phpversion()."<br>\n";
4097
		}
4098
		$out .= "<b>".$langs->trans("Server").":</b> ".dol_htmlentities($_SERVER["SERVER_SOFTWARE"])."<br>\n";
4099
		if (function_exists("php_uname"))
4100
		{
4101
			$out .= "<b>".$langs->trans("OS").":</b> ".php_uname()."<br>\n";
4102
		}
4103
		$out .= "<b>".$langs->trans("UserAgent").":</b> ".dol_htmlentities($_SERVER["HTTP_USER_AGENT"], ENT_COMPAT, 'UTF-8')."<br>\n";
4104
		$out .= "<br>\n";
4105
		$out .= "<b>".$langs->trans("RequestedUrl").":</b> ".dol_htmlentities($_SERVER["REQUEST_URI"], ENT_COMPAT, 'UTF-8')."<br>\n";
4106
		$out .= "<b>".$langs->trans("Referer").":</b> ".(isset($_SERVER["HTTP_REFERER"]) ? dol_htmlentities($_SERVER["HTTP_REFERER"], ENT_COMPAT, 'UTF-8') : '')."<br>\n";
4107
		$out .= "<b>".$langs->trans("MenuManager").":</b> ".(isset($conf->standard_menu) ? dol_htmlentities($conf->standard_menu) : '')."<br>\n";
4108
		$out .= "<br>\n";
4109
		$syslog .= "url=".dol_escape_htmltag($_SERVER["REQUEST_URI"]);
4110
		$syslog .= ", query_string=".dol_escape_htmltag($_SERVER["QUERY_STRING"]);
4111
	} else // Mode CLI
4112
	{
4113
		$out .= '> '.$langs->transnoentities("ErrorInternalErrorDetected").":\n".$argv[0]."\n";
4114
		$syslog .= "pid=".dol_getmypid();
4115
	}
4116
4117
	if (!empty($conf->modules))
4118
	{
4119
		$out .= "<b>".$langs->trans("Modules").":</b> ".join(', ', $conf->modules)."<br>\n";
4120
	}
4121
4122
	if (is_object($db))
4123
	{
4124
		if ($_SERVER['DOCUMENT_ROOT'])  // Mode web
4125
		{
4126
			$out .= "<b>".$langs->trans("DatabaseTypeManager").":</b> ".$db->type."<br>\n";
4127
			$out .= "<b>".$langs->trans("RequestLastAccessInError").":</b> ".($db->lastqueryerror() ? dol_escape_htmltag($db->lastqueryerror()) : $langs->trans("ErrorNoRequestInError"))."<br>\n";
4128
			$out .= "<b>".$langs->trans("ReturnCodeLastAccessInError").":</b> ".($db->lasterrno() ? dol_escape_htmltag($db->lasterrno()) : $langs->trans("ErrorNoRequestInError"))."<br>\n";
4129
			$out .= "<b>".$langs->trans("InformationLastAccessInError").":</b> ".($db->lasterror() ? dol_escape_htmltag($db->lasterror()) : $langs->trans("ErrorNoRequestInError"))."<br>\n";
4130
			$out .= "<br>\n";
4131
		} else // Mode CLI
4132
		{
4133
			// No dol_escape_htmltag for output, we are in CLI mode
4134
			$out .= '> '.$langs->transnoentities("DatabaseTypeManager").":\n".$db->type."\n";
4135
			$out .= '> '.$langs->transnoentities("RequestLastAccessInError").":\n".($db->lastqueryerror() ? $db->lastqueryerror() : $langs->transnoentities("ErrorNoRequestInError"))."\n";
4136
			$out .= '> '.$langs->transnoentities("ReturnCodeLastAccessInError").":\n".($db->lasterrno() ? $db->lasterrno() : $langs->transnoentities("ErrorNoRequestInError"))."\n";
4137
			$out .= '> '.$langs->transnoentities("InformationLastAccessInError").":\n".($db->lasterror() ? $db->lasterror() : $langs->transnoentities("ErrorNoRequestInError"))."\n";
4138
		}
4139
		$syslog .= ", sql=".$db->lastquery();
4140
		$syslog .= ", db_error=".$db->lasterror();
4141
	}
4142
4143
	if ($error || $errors)
4144
	{
4145
		$langs->load("errors");
4146
4147
		// Merge all into $errors array
4148
		if (is_array($error) && is_array($errors)) $errors = array_merge($error, $errors);
4149
		elseif (is_array($error)) $errors = $error;
4150
		elseif (is_array($errors)) $errors = array_merge(array($error), $errors);
4151
		else $errors = array_merge(array($error));
4152
4153
		foreach ($errors as $msg)
4154
		{
4155
			if (empty($msg)) continue;
4156
			if ($_SERVER['DOCUMENT_ROOT'])  // Mode web
4157
			{
4158
				$out .= "<b>".$langs->trans("Message").":</b> ".dol_escape_htmltag($msg)."<br>\n";
4159
			} else // Mode CLI
4160
			{
4161
				$out .= '> '.$langs->transnoentities("Message").":\n".$msg."\n";
4162
			}
4163
			$syslog .= ", msg=".$msg;
4164
		}
4165
	}
4166
	if (empty($dolibarr_main_prod) && $_SERVER['DOCUMENT_ROOT'] && function_exists('xdebug_print_function_stack') && function_exists('xdebug_call_file'))
4167
	{
4168
		xdebug_print_function_stack();
4169
		$out .= '<b>XDebug informations:</b>'."<br>\n";
4170
		$out .= 'File: '.xdebug_call_file()."<br>\n";
4171
		$out .= 'Line: '.xdebug_call_line()."<br>\n";
4172
		$out .= 'Function: '.xdebug_call_function()."<br>\n";
4173
		$out .= "<br>\n";
4174
	}
4175
4176
	// Return a http error code if possible
4177
	if (!headers_sent()) {
4178
		http_response_code(500);
4179
	}
4180
4181
	if (empty($dolibarr_main_prod)) {
4182
		print $out;
4183
	} else {
4184
		if (empty($langs->defaultlang)) $langs->setDefaultLang();
4185
		$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.
4186
		// This should not happen, except if there is a bug somewhere. Enabled and check log in such case.
4187
		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";
4188
		print $langs->trans("DolibarrHasDetectedError").'. ';
4189
		print $langs->trans("YouCanSetOptionDolibarrMainProdToZero");
4190
		define("MAIN_CORE_ERROR", 1);
4191
	}
4192
4193
	dol_syslog("Error ".$syslog, LOG_ERR);
4194
}
4195
4196
/**
4197
 * Show a public email and error code to contact if technical error
4198
 *
4199
 * @param	string	$prefixcode		Prefix of public error code
4200
 * @param   string  $errormessage   Complete error message
4201
 * @param	array	$errormessages	Array of error messages
4202
 * @param	string	$morecss		More css
4203
 * @param	string	$email			Email
4204
 * @return	void
4205
 */
4206
function dol_print_error_email($prefixcode, $errormessage = '', $errormessages = array(), $morecss = 'error', $email = '')
4207
{
4208
	global $langs, $conf;
4209
4210
	if (empty($email)) $email = $conf->global->MAIN_INFO_SOCIETE_MAIL;
4211
4212
	$langs->load("errors");
4213
	$now = dol_now();
4214
4215
	print '<br><div class="center login_main_message"><div class="'.$morecss.'">';
4216
	print $langs->trans("ErrorContactEMail", $email, $prefixcode.dol_print_date($now, '%Y%m%d%H%M%S'));
4217
	if ($errormessage) print '<br><br>'.$errormessage;
4218
	if (is_array($errormessages) && count($errormessages))
4219
	{
4220
		foreach ($errormessages as $mesgtoshow)
4221
		{
4222
			print '<br><br>'.$mesgtoshow;
4223
		}
4224
	}
4225
	print '</div></div>';
4226
}
4227
4228
/**
4229
 *	Show title line of an array
4230
 *
4231
 *	@param	string	$name        Label of field
4232
 *	@param	string	$file        Url used when we click on sort picto
4233
 *	@param	string	$field       Field to use for new sorting
4234
 *	@param	string	$begin       ("" by defaut)
4235
 *	@param	string	$moreparam   Add more parameters on sort url links ("" by default)
4236
 *	@param  string	$moreattrib  Options of attribute td ("" by defaut, example: 'align="center"')
4237
 *	@param  string	$sortfield   Current field used to sort
4238
 *	@param  string	$sortorder   Current sort order
4239
 *  @param	string	$prefix		 Prefix for css. Use space after prefix to add your own CSS tag.
4240
 *  @param	string	$tooltip	 Tooltip
4241
 *  @param	string	$forcenowrapcolumntitle		No need for use 'wrapcolumntitle' css style
4242
 *	@return	void
4243
 */
4244
function print_liste_field_titre($name, $file = "", $field = "", $begin = "", $moreparam = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $tooltip = "", $forcenowrapcolumntitle = 0)
4245
{
4246
	print getTitleFieldOfList($name, 0, $file, $field, $begin, $moreparam, $moreattrib, $sortfield, $sortorder, $prefix, 0, $tooltip, $forcenowrapcolumntitle);
4247
}
4248
4249
/**
4250
 *	Get title line of an array
4251
 *
4252
 *	@param	string	$name        		Translation key of field to show or complete HTML string to show
4253
 *	@param	int		$thead		 		0=To use with standard table format, 1=To use inside <thead><tr>, 2=To use with <div>
4254
 *	@param	string	$file        		Url used when we click on sort picto
4255
 *	@param	string	$field       		Field to use for new sorting. Empty if this field is not sortable. Example "t.abc" or "t.abc,t.def"
4256
 *	@param	string	$begin       		("" by defaut)
4257
 *	@param	string	$moreparam   		Add more parameters on sort url links ("" by default)
4258
 *	@param  string	$moreattrib  		Add more attributes on th ("" by defaut, example: 'align="center"'). To add more css class, use param $prefix.
4259
 *	@param  string	$sortfield   		Current field used to sort (Ex: 'd.datep,d.id')
4260
 *	@param  string	$sortorder   		Current sort order (Ex: 'asc,desc')
4261
 *  @param	string	$prefix		 		Prefix for css. Use space after prefix to add your own CSS tag, for example 'mycss '.
4262
 *  @param	string	$disablesortlink	1=Disable sort link
4263
 *  @param	string	$tooltip	 		Tooltip
4264
 *  @param	string	$forcenowrapcolumntitle		No need for use 'wrapcolumntitle' css style
4265
 *	@return	string
4266
 */
4267
function getTitleFieldOfList($name, $thead = 0, $file = "", $field = "", $begin = "", $moreparam = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $disablesortlink = 0, $tooltip = '', $forcenowrapcolumntitle = 0)
4268
{
4269
	global $conf, $langs, $form;
4270
	//print "$name, $file, $field, $begin, $options, $moreattrib, $sortfield, $sortorder<br>\n";
4271
4272
	if ($moreattrib == 'class="right"') $prefix .= 'right '; // For backward compatibility
4273
4274
	$sortorder = strtoupper($sortorder);
4275
	$out = '';
4276
	$sortimg = '';
4277
4278
	$tag = 'th';
4279
	if ($thead == 2) $tag = 'div';
4280
4281
	$tmpsortfield = explode(',', $sortfield);
4282
	$sortfield1 = trim($tmpsortfield[0]); // If $sortfield is 'd.datep,d.id', it becomes 'd.datep'
4283
	$tmpfield = explode(',', $field);
4284
	$field1 = trim($tmpfield[0]); // If $field is 'd.datep,d.id', it becomes 'd.datep'
4285
4286
	if (empty($conf->global->MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE) && empty($forcenowrapcolumntitle)) {
4287
		$prefix = 'wrapcolumntitle '.$prefix;
4288
	}
4289
4290
	//var_dump('field='.$field.' field1='.$field1.' sortfield='.$sortfield.' sortfield1='.$sortfield1);
4291
	// If field is used as sort criteria we use a specific css class liste_titre_sel
4292
	// Example if (sortfield,field)=("nom","xxx.nom") or (sortfield,field)=("nom","nom")
4293
	$liste_titre = 'liste_titre';
4294
	if ($field1 && ($sortfield1 == $field1 || $sortfield1 == preg_replace("/^[^\.]+\./", "", $field1))) {
4295
		$liste_titre = 'liste_titre_sel';
4296
	}
4297
	$out .= '<'.$tag.' class="'.$prefix.$liste_titre.'" '.$moreattrib;
4298
	//$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)).'"' : '');
4299
	$out .= ($name && empty($conf->global->MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE) && empty($forcenowrapcolumntitle) && !dol_textishtml($name)) ? ' title="'.dol_escape_htmltag($langs->trans($name)).'"' : '';
4300
	$out .= '>';
4301
4302
	if (empty($thead) && $field && empty($disablesortlink))    // If this is a sort field
4303
	{
4304
		$options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', (is_scalar($moreparam) ? $moreparam : ''));
4305
		$options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
4306
		$options = preg_replace('/&+/i', '&', $options);
4307
		if (!preg_match('/^&/', $options)) $options = '&'.$options;
4308
4309
		$sortordertouseinlink = '';
4310
		if ($field1 != $sortfield1) // We are on another field than current sorted field
4311
		{
4312
			if (preg_match('/^DESC/i', $sortorder))
4313
			{
4314
				$sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
4315
			} else // We reverse the var $sortordertouseinlink
4316
			{
4317
				$sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
4318
			}
4319
		} else // We are on field that is the first current sorting criteria
4320
		{
4321
			if (preg_match('/^ASC/i', $sortorder))	// We reverse the var $sortordertouseinlink
4322
			{
4323
				$sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
4324
			} else {
4325
				$sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
4326
			}
4327
		}
4328
		$sortordertouseinlink = preg_replace('/,$/', '', $sortordertouseinlink);
4329
		$out .= '<a class="reposition" href="'.$file.'?sortfield='.$field.'&sortorder='.$sortordertouseinlink.'&begin='.$begin.$options.'"';
4330
		//$out .= (empty($conf->global->MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE) ? ' title="'.dol_escape_htmltag($langs->trans($name)).'"' : '');
4331
		$out .= '>';
4332
	}
4333
4334
	if ($tooltip) {
4335
		// You can also use 'TranslationString:keyfortooltiponlick' for a tooltip on click.
4336
		$tmptooltip = explode(':', $tooltip);
4337
		$out .= $form->textwithpicto($langs->trans($name), $langs->trans($tmptooltip[0]), 1, 'help', '', 0, 3, (empty($tmptooltip[1]) ? '' : 'extra_'.str_replace('.', '_', $field).'_'.$tmptooltip[1]));
4338
	}
4339
	else $out .= $langs->trans($name);
4340
4341
	if (empty($thead) && $field && empty($disablesortlink))    // If this is a sort field
4342
	{
4343
		$out .= '</a>';
4344
	}
4345
4346
	if (empty($thead) && $field)    // If this is a sort field
4347
	{
4348
		$options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', (is_scalar($moreparam) ? $moreparam : ''));
4349
		$options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
4350
		$options = preg_replace('/&+/i', '&', $options);
4351
		if (!preg_match('/^&/', $options)) $options = '&'.$options;
4352
4353
		if (!$sortorder || $field1 != $sortfield1)
4354
		{
4355
			//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",0).'</a>';
4356
			//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",0).'</a>';
4357
		} else {
4358
			if (preg_match('/^DESC/', $sortorder)) {
4359
				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",0).'</a>';
4360
				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",1).'</a>';
4361
				$sortimg .= '<span class="nowrap">'.img_up("Z-A", 0, 'paddingleft').'</span>';
4362
			}
4363
			if (preg_match('/^ASC/', $sortorder)) {
4364
				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",1).'</a>';
4365
				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",0).'</a>';
4366
				$sortimg .= '<span class="nowrap">'.img_down("A-Z", 0, 'paddingleft').'</span>';
4367
			}
4368
		}
4369
	}
4370
4371
	$out .= $sortimg;
4372
4373
	$out .= '</'.$tag.'>';
4374
4375
	return $out;
4376
}
4377
4378
/**
4379
 *	Show a title.
4380
 *
4381
 *	@param	string	$title			Title to show
4382
 *	@return	string					Title to show
4383
 *  @deprecated						Use load_fiche_titre instead
4384
 *  @see load_fiche_titre()
4385
 */
4386
function print_titre($title)
4387
{
4388
	dol_syslog(__FUNCTION__." is deprecated", LOG_WARNING);
4389
4390
	print '<div class="titre">'.$title.'</div>';
4391
}
4392
4393
/**
4394
 *	Show a title with picto
4395
 *
4396
 *	@param	string	$title				Title to show
4397
 *	@param	string	$mesg				Added message to show on right
4398
 *	@param	string	$picto				Icon to use before title (should be a 32x32 transparent png file)
4399
 *	@param	int		$pictoisfullpath	1=Icon name is a full absolute url of image
4400
 * 	@param	int		$id					To force an id on html objects
4401
 * 	@return	void
4402
 *  @deprecated Use print load_fiche_titre instead
4403
 */
4404
function print_fiche_titre($title, $mesg = '', $picto = 'generic', $pictoisfullpath = 0, $id = '')
4405
{
4406
	print load_fiche_titre($title, $mesg, $picto, $pictoisfullpath, $id);
4407
}
4408
4409
/**
4410
 *	Load a title with picto
4411
 *
4412
 *	@param	string	$titre				Title to show
4413
 *	@param	string	$morehtmlright		Added message to show on right
4414
 *	@param	string	$picto				Icon to use before title (should be a 32x32 transparent png file)
4415
 *	@param	int		$pictoisfullpath	1=Icon name is a full absolute url of image
4416
 * 	@param	string	$id					To force an id on html objects
4417
 *  @param  string  $morecssontable     More css on table
4418
 *	@param	string	$morehtmlcenter		Added message to show on center
4419
 * 	@return	string
4420
 *  @see print_barre_liste()
4421
 */
4422
function load_fiche_titre($titre, $morehtmlright = '', $picto = 'generic', $pictoisfullpath = 0, $id = '', $morecssontable = '', $morehtmlcenter = '')
4423
{
4424
	global $conf;
4425
4426
	$return = '';
4427
4428
	if ($picto == 'setup') $picto = 'generic';
4429
4430
	$return .= "\n";
4431
	$return .= '<table '.($id ? 'id="'.$id.'" ' : '').'class="centpercent notopnoleftnoright table-fiche-title'.($morecssontable ? ' '.$morecssontable : '').'">'; // maring bottom must be same than into print_barre_list
4432
	$return .= '<tr class="titre">';
4433
	if ($picto) $return .= '<td class="nobordernopadding widthpictotitle valignmiddle col-picto">'.img_picto('', $picto, 'class="valignmiddle widthpictotitle pictotitle"', $pictoisfullpath).'</td>';
4434
	$return .= '<td class="nobordernopadding valignmiddle col-title">';
4435
	$return .= '<div class="titre inline-block">'.$titre.'</div>';
4436
	$return .= '</td>';
4437
	if (dol_strlen($morehtmlcenter))
4438
	{
4439
		$return .= '<td class="nobordernopadding center valignmiddle">'.$morehtmlcenter.'</td>';
4440
	}
4441
	if (dol_strlen($morehtmlright))
4442
	{
4443
		$return .= '<td class="nobordernopadding titre_right wordbreakimp right valignmiddle">'.$morehtmlright.'</td>';
4444
	}
4445
	$return .= '</tr></table>'."\n";
4446
4447
	return $return;
4448
}
4449
4450
/**
4451
 *	Print a title with navigation controls for pagination
4452
 *
4453
 *	@param	string	    $titre				Title to show (required)
4454
 *	@param	int   	    $page				Numero of page to show in navigation links (required)
4455
 *	@param	string	    $file				Url of page (required)
4456
 *	@param	string	    $options         	More parameters for links ('' by default, does not include sortfield neither sortorder). Value must be 'urlencoded' before calling function.
4457
 *	@param	string    	$sortfield       	Field to sort on ('' by default)
4458
 *	@param	string	    $sortorder       	Order to sort ('' by default)
4459
 *	@param	string	    $morehtmlcenter     String in the middle ('' by default). We often find here string $massaction comming from $form->selectMassAction()
4460
 *	@param	int		    $num				Number of records found by select with limit+1
4461
 *	@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.
4462
 *	@param	string	    $picto				Icon to use before title (should be a 32x32 transparent png file)
4463
 *	@param	int		    $pictoisfullpath	1=Icon name is a full absolute url of image
4464
 *  @param	string	    $morehtmlright		More html to show (after arrows)
4465
 *  @param  string      $morecss            More css to the table
4466
 *  @param  int         $limit              Max number of lines (-1 = use default, 0 = no limit, > 0 = limit).
4467
 *  @param  int         $hideselectlimit    Force to hide select limit
4468
 *  @param  int         $hidenavigation     Force to hide all navigation tools
4469
 *  @param  int			$pagenavastextinput 1=Do not suggest list of pages to navigate but suggest the page number into an input field.
4470
 *  @param	string		$morehtmlrightbeforearrow	More html to show (before arrows)
4471
 *	@return	void
4472
 */
4473
function 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 = '')
4474
{
4475
	global $conf, $langs;
4476
4477
	$savlimit = $limit;
4478
	$savtotalnboflines = $totalnboflines;
4479
	$totalnboflines = abs((int) $totalnboflines);
4480
4481
	if ($picto == 'setup') $picto = 'title_setup.png';
4482
	if (($conf->browser->name == 'ie') && $picto == 'generic') $picto = 'title.gif';
4483
	if ($limit < 0) $limit = $conf->liste_limit;
4484
	if ($savlimit != 0 && (($num > $limit) || ($num == -1) || ($limit == 0)))
4485
	{
4486
		$nextpage = 1;
4487
	} else {
4488
		$nextpage = 0;
4489
	}
4490
	//print 'totalnboflines='.$totalnboflines.'-savlimit='.$savlimit.'-limit='.$limit.'-num='.$num.'-nextpage='.$nextpage;
4491
4492
	print "\n";
4493
	print "<!-- Begin title '".$titre."' -->\n";
4494
	print '<table class="centpercent notopnoleftnoright table-fiche-title'.($morecss ? ' '.$morecss : '').'"><tr>'; // maring bottom must be same than into load_fiche_tire
4495
4496
	// Left
4497
4498
	if ($picto && $titre) print '<td class="nobordernopadding widthpictotitle valignmiddle col-picto">'.img_picto('', $picto, 'class="valignmiddle pictotitle widthpictotitle"', $pictoisfullpath).'</td>';
4499
	print '<td class="nobordernopadding valignmiddle col-title">';
4500
	print '<div class="titre inline-block">'.$titre;
4501
	if (!empty($titre) && $savtotalnboflines >= 0 && (string) $savtotalnboflines != '') print '<span class="opacitymedium colorblack paddingleft">('.$totalnboflines.')</span>';
4502
	print '</div></td>';
4503
4504
	// Center
4505
	if ($morehtmlcenter)
4506
	{
4507
		print '<td class="nobordernopadding center valignmiddle">'.$morehtmlcenter.'</td>';
4508
	}
4509
4510
	// Right
4511
	print '<td class="nobordernopadding valignmiddle right">';
4512
	print '<input type="hidden" name="pageplusoneold" value="'.((int) $page + 1).'">';
4513
	if ($sortfield) $options .= "&sortfield=".urlencode($sortfield);
4514
	if ($sortorder) $options .= "&sortorder=".urlencode($sortorder);
4515
	// Show navigation bar
4516
	$pagelist = '';
4517
	if ($savlimit != 0 && ($page > 0 || $num > $limit))
4518
	{
4519
		if ($totalnboflines)	// If we know total nb of lines
4520
		{
4521
			// Define nb of extra page links before and after selected page + ... + first or last
4522
			$maxnbofpage = (empty($conf->dol_optimize_smallscreen) ? 4 : 0);
4523
4524
			if ($limit > 0) $nbpages = ceil($totalnboflines / $limit);
4525
			else $nbpages = 1;
4526
			$cpt = ($page - $maxnbofpage);
4527
			if ($cpt < 0) { $cpt = 0; }
4528
4529
			if ($cpt >= 1)
4530
			{
4531
				if (empty($pagenavastextinput)) {
4532
					$pagelist .= '<li class="pagination"><a href="'.$file.'?page=0'.$options.'">1</a></li>';
4533
					if ($cpt > 2) $pagelist .= '<li class="pagination"><span class="inactive">...</span></li>';
4534
					elseif ($cpt == 2) $pagelist .= '<li class="pagination"><a href="'.$file.'?page=1'.$options.'">2</a></li>';
4535
				}
4536
			}
4537
4538
			do {
4539
				if ($pagenavastextinput) {
4540
					if ($cpt == $page)
4541
					{
4542
						$pagelist .= '<li class="pagination"><input type="text" class="width25 center pageplusone" name="pageplusone" value="'.($page + 1).'"></li>';
4543
						$pagelist .= '/';
4544
						//if (($cpt + 1) < $nbpages) $pagelist .= '/';
4545
					}
4546
				} else {
4547
					if ($cpt == $page)
4548
					{
4549
						$pagelist .= '<li class="pagination"><span class="active">'.($page + 1).'</span></li>';
4550
					} else {
4551
						$pagelist .= '<li class="pagination"><a href="'.$file.'?page='.$cpt.$options.'">'.($cpt + 1).'</a></li>';
4552
					}
4553
				}
4554
				$cpt++;
4555
			} while ($cpt < $nbpages && $cpt <= ($page + $maxnbofpage));
4556
4557
			if (empty($pagenavastextinput)) {
4558
				if ($cpt < $nbpages)
4559
				{
4560
					if ($cpt < $nbpages - 2) $pagelist .= '<li class="pagination"><span class="inactive">...</span></li>';
4561
					elseif ($cpt == $nbpages - 2) $pagelist .= '<li class="pagination"><a href="'.$file.'?page='.($nbpages - 2).$options.'">'.($nbpages - 1).'</a></li>';
4562
					$pagelist .= '<li class="pagination"><a href="'.$file.'?page='.($nbpages - 1).$options.'">'.$nbpages.'</a></li>';
4563
				}
4564
			} else {
4565
				//var_dump($page.' '.$cpt.' '.$nbpages);
4566
				//if (($page + 1) < $nbpages) {
4567
					$pagelist .= '<li class="pagination"><a href="'.$file.'?page='.($nbpages - 1).$options.'">'.$nbpages.'</a></li>';
4568
				//}
4569
			}
4570
		} else {
4571
			$pagelist .= '<li class="pagination"><span class="active">'.($page + 1)."</li>";
4572
		}
4573
	}
4574
4575
	if ($savlimit || $morehtmlright || $morehtmlrightbeforearrow) {
4576
		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
4577
	}
4578
4579
	// js to autoselect page field on focus
4580
	if ($pagenavastextinput) {
4581
		print ajax_autoselect('.pageplusone');
4582
	}
4583
4584
	print '</td>';
4585
4586
	print '</tr></table>'."\n";
4587
	print "<!-- End title -->\n\n";
4588
}
4589
4590
/**
4591
 *	Function to show navigation arrows into lists
4592
 *
4593
 *	@param	int				$page				Number of page
4594
 *	@param	string			$file				Page URL (in most cases provided with $_SERVER["PHP_SELF"])
4595
 *	@param	string			$options         	Other url parameters to propagate ("" by default, may include sortfield and sortorder)
4596
 *	@param	integer			$nextpage	    	Do we show a next page button
4597
 *	@param	string			$betweenarrows		HTML content to show between arrows. MUST contains '<li> </li>' tags or '<li><span> </span></li>'.
4598
 *  @param	string			$afterarrows		HTML content to show after arrows. Must NOT contains '<li> </li>' tags.
4599
 *  @param  int             $limit              Max nb of record to show  (-1 = no combo with limit, 0 = no limit, > 0 = limit)
4600
 *	@param	int		        $totalnboflines		Total number of records/lines for all pages (if known)
4601
 *  @param  int             $hideselectlimit    Force to hide select limit
4602
 *  @param	string			$beforearrows		HTML content to show before arrows. Must NOT contains '<li> </li>' tags.
4603
 *	@return	void
4604
 */
4605
function print_fleche_navigation($page, $file, $options = '', $nextpage = 0, $betweenarrows = '', $afterarrows = '', $limit = -1, $totalnboflines = 0, $hideselectlimit = 0, $beforearrows = '')
4606
{
4607
	global $conf, $langs;
4608
4609
	print '<div class="pagination"><ul>';
4610
	if ($beforearrows)
4611
	{
4612
		print '<li class="paginationbeforearrows">';
4613
		print $beforearrows;
4614
		print '</li>';
4615
	}
4616
	if ((int) $limit > 0 && empty($hideselectlimit))
4617
	{
4618
		$pagesizechoices = '10:10,15:15,20:20,30:30,40:40,50:50,100:100,250:250,500:500,1000:1000,5000:5000';
4619
		//$pagesizechoices.=',0:'.$langs->trans("All");     // Not yet supported
4620
		//$pagesizechoices.=',2:2';
4621
		if (!empty($conf->global->MAIN_PAGESIZE_CHOICES)) $pagesizechoices = $conf->global->MAIN_PAGESIZE_CHOICES;
4622
4623
		print '<li class="pagination">';
4624
		print '<select class="flat selectlimit" name="limit" title="'.dol_escape_htmltag($langs->trans("MaxNbOfRecordPerPage")).'">';
4625
		$tmpchoice = explode(',', $pagesizechoices);
4626
		$tmpkey = $limit.':'.$limit;
4627
		if (!in_array($tmpkey, $tmpchoice)) $tmpchoice[] = $tmpkey;
4628
		$tmpkey = $conf->liste_limit.':'.$conf->liste_limit;
4629
		if (!in_array($tmpkey, $tmpchoice)) $tmpchoice[] = $tmpkey;
4630
		asort($tmpchoice, SORT_NUMERIC);
4631
		foreach ($tmpchoice as $val)
4632
		{
4633
			$selected = '';
4634
			$tmp = explode(':', $val);
4635
			$key = $tmp[0];
4636
			$val = $tmp[1];
4637
			if ($key != '' && $val != '')
4638
			{
4639
				if ((int) $key == (int) $limit)
4640
				{
4641
					$selected = ' selected="selected"';
4642
				}
4643
				print '<option name="'.$key.'"'.$selected.'>'.dol_escape_htmltag($val).'</option>'."\n";
4644
			}
4645
		}
4646
		print '</select>';
4647
		if ($conf->use_javascript_ajax)
4648
		{
4649
			print '<!-- JS CODE TO ENABLE select limit to launch submit of page -->
4650
            		<script>
4651
                	jQuery(document).ready(function () {
4652
            	  		jQuery(".selectlimit").change(function() {
4653
                            console.log("Change limit. Send submit");
4654
                            $(this).parents(\'form:first\').submit();
4655
            	  		});
4656
                	});
4657
            		</script>
4658
                ';
4659
		}
4660
		print '</li>';
4661
	}
4662
	if ($page > 0)
4663
	{
4664
		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>';
4665
	}
4666
	if ($betweenarrows)
4667
	{
4668
		print '<!--<div class="betweenarrows nowraponall inline-block">-->';
4669
		print $betweenarrows;
4670
		print '<!--</div>-->';
4671
	}
4672
	if ($nextpage > 0)
4673
	{
4674
		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>';
4675
	}
4676
	if ($afterarrows)
4677
	{
4678
		print '<li class="paginationafterarrows">';
4679
		print $afterarrows;
4680
		print '</li>';
4681
	}
4682
	print '</ul></div>'."\n";
4683
}
4684
4685
4686
/**
4687
 *	Return a string with VAT rate label formated for view output
4688
 *	Used into pdf and HTML pages
4689
 *
4690
 *	@param	string	$rate			Rate value to format ('19.6', '19,6', '19.6%', '19,6%', '19.6 (CODEX)', ...)
4691
 *  @param	boolean	$addpercent		Add a percent % sign in output
4692
 *	@param	int		$info_bits		Miscellaneous information on vat (0=Default, 1=French NPR vat)
4693
 *	@param	int		$usestarfornpr	-1=Never show, 0 or 1=Use '*' for NPR vat rates
4694
 *  @return	string					String with formated amounts ('19,6' or '19,6%' or '8.5% (NPR)' or '8.5% *' or '19,6 (CODEX)')
4695
 */
4696
function vatrate($rate, $addpercent = false, $info_bits = 0, $usestarfornpr = 0)
4697
{
4698
	$morelabel = '';
4699
4700
	if (preg_match('/%/', $rate))
4701
	{
4702
		$rate = str_replace('%', '', $rate);
4703
		$addpercent = true;
4704
	}
4705
	if (preg_match('/\((.*)\)/', $rate, $reg))
4706
	{
4707
		$morelabel = ' ('.$reg[1].')';
4708
		$rate = preg_replace('/\s*'.preg_quote($morelabel, '/').'/', '', $rate);
4709
	}
4710
	if (preg_match('/\*/', $rate))
4711
	{
4712
		$rate = str_replace('*', '', $rate);
4713
		$info_bits |= 1;
4714
	}
4715
4716
	// If rate is '9/9/9' we don't change it.  If rate is '9.000' we apply price()
4717
	if (!preg_match('/\//', $rate)) $ret = price($rate, 0, '', 0, 0).($addpercent ? '%' : '');
4718
	else {
4719
		// TODO Split on / and output with a price2num to have clean numbers without ton of 000.
4720
		$ret = $rate.($addpercent ? '%' : '');
4721
	}
4722
	if (($info_bits & 1) && $usestarfornpr >= 0) $ret .= ' *';
4723
	$ret .= $morelabel;
4724
	return $ret;
4725
}
4726
4727
4728
/**
4729
 *		Function to format a value into an amount for visual output
4730
 *		Function used into PDF and HTML pages
4731
 *
4732
 *		@param	float		$amount			Amount to format
4733
 *		@param	integer		$form			Type of format, HTML or not (not by default)
4734
 *		@param	Translate	$outlangs		Object langs for output
4735
 *		@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.
4736
 *		@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)
4737
 *		@param	int			$forcerounding	Force the number of decimal to forcerounding decimal (-1=do not force)
4738
 *		@param	string		$currency_code	To add currency symbol (''=add nothing, 'auto'=Use default currency, 'XXX'=add currency symbols for XXX currency)
4739
 *		@return	string						Chaine avec montant formate
4740
 *
4741
 *		@see	price2num()					Revert function of price
4742
 */
4743
function price($amount, $form = 0, $outlangs = '', $trunc = 1, $rounding = -1, $forcerounding = -1, $currency_code = '')
4744
{
4745
	global $langs, $conf;
4746
4747
	// Clean parameters
4748
	if (empty($amount)) $amount = 0; // To have a numeric value if amount not defined or = ''
4749
	$amount = (is_numeric($amount) ? $amount : 0); // Check if amount is numeric, for example, an error occured when amount value = o (letter) instead 0 (number)
4750
	if ($rounding < 0) $rounding = min($conf->global->MAIN_MAX_DECIMALS_UNIT, $conf->global->MAIN_MAX_DECIMALS_TOT);
4751
	$nbdecimal = $rounding;
4752
4753
	// Output separators by default (french)
4754
	$dec = ','; $thousand = ' ';
4755
4756
	// If $outlangs not forced, we use use language
4757
	if (!is_object($outlangs)) $outlangs = $langs;
4758
4759
	if ($outlangs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal")  $dec = $outlangs->transnoentitiesnoconv("SeparatorDecimal");
4760
	if ($outlangs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") $thousand = $outlangs->transnoentitiesnoconv("SeparatorThousand");
4761
	if ($thousand == 'None') $thousand = '';
4762
	elseif ($thousand == 'Space') $thousand = ' ';
4763
	//print "outlangs=".$outlangs->defaultlang." amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
4764
4765
	//print "amount=".$amount."-";
4766
	$amount = str_replace(',', '.', $amount); // should be useless
4767
	//print $amount."-";
4768
	$datas = explode('.', $amount);
4769
	$decpart = isset($datas[1]) ? $datas[1] : '';
4770
	$decpart = preg_replace('/0+$/i', '', $decpart); // Supprime les 0 de fin de partie decimale
4771
	//print "decpart=".$decpart."<br>";
4772
	$end = '';
4773
4774
	// We increase nbdecimal if there is more decimal than asked (to not loose information)
4775
	if (dol_strlen($decpart) > $nbdecimal) $nbdecimal = dol_strlen($decpart);
4776
	// Si on depasse max
4777
	if ($trunc && $nbdecimal > $conf->global->MAIN_MAX_DECIMALS_SHOWN)
4778
	{
4779
		$nbdecimal = $conf->global->MAIN_MAX_DECIMALS_SHOWN;
4780
		if (preg_match('/\.\.\./i', $conf->global->MAIN_MAX_DECIMALS_SHOWN))
4781
		{
4782
			// Si un affichage est tronque, on montre des ...
4783
			$end = '...';
4784
		}
4785
	}
4786
4787
	// If force rounding
4788
	if ($forcerounding >= 0) $nbdecimal = $forcerounding;
4789
4790
	// Format number
4791
	$output = number_format($amount, $nbdecimal, $dec, $thousand);
4792
	if ($form)
4793
	{
4794
		$output = preg_replace('/\s/', '&nbsp;', $output);
4795
		$output = preg_replace('/\'/', '&#039;', $output);
4796
	}
4797
	// Add symbol of currency if requested
4798
	$cursymbolbefore = $cursymbolafter = '';
4799
	if ($currency_code)
4800
	{
4801
		if ($currency_code == 'auto') $currency_code = $conf->currency;
4802
4803
		$listofcurrenciesbefore = array('AUD', 'CAD', 'CNY', 'COP', 'CLP', 'GBP', 'HKD', 'MXN', 'PEN', 'USD');
4804
		$listoflanguagesbefore = array('nl_NL');
4805
		if (in_array($currency_code, $listofcurrenciesbefore) || in_array($outlangs->defaultlang, $listoflanguagesbefore))
4806
		{
4807
			$cursymbolbefore .= $outlangs->getCurrencySymbol($currency_code);
4808
		} else {
4809
			$tmpcur = $outlangs->getCurrencySymbol($currency_code);
4810
			$cursymbolafter .= ($tmpcur == $currency_code ? ' '.$tmpcur : $tmpcur);
4811
		}
4812
	}
4813
	$output = $cursymbolbefore.$output.$end.($cursymbolafter ? ' ' : '').$cursymbolafter;
4814
4815
	return $output;
4816
}
4817
4818
/**
4819
 *	Function that return a number with universal decimal format (decimal separator is '.') from an amount typed by a user.
4820
 *	Function to use on each input amount before any numeric test or database insert. A better name for this function
4821
 *  should be roundtext2num().
4822
 *
4823
 *	@param	string|float	$amount			Amount to convert/clean or round
4824
 *	@param	string			$rounding		''=No rounding
4825
 * 											'MU'=Round to Max unit price (MAIN_MAX_DECIMALS_UNIT)
4826
 *											'MT'=Round to Max for totals with Tax (MAIN_MAX_DECIMALS_TOT)
4827
 *											'MS'=Round to Max for stock quantity (MAIN_MAX_DECIMALS_STOCK)
4828
 *      		                            'CR'=Foreign currency accurancy
4829
 *											Numeric = Nb of digits for rounding
4830
 * 	@param	int				$option			Put 1 if you know that content is already universal format number (so no correction on decimal will be done)
4831
 * 											Put 2 if you know that number is a user input (so we know we don't have to fix decimal separator).
4832
 *	@return	string							Amount with universal numeric format (Example: '99.99999').
4833
 *											If conversion fails, it return text unchanged if $rounding = '' or '0' if $rounding is defined.
4834
 *											If amount is null or '', it returns '' if $rounding = '' or '0' if $rounding is defined..
4835
 *
4836
 *	@see    price()							Opposite function of price2num
4837
 */
4838
function price2num($amount, $rounding = '', $option = 0)
4839
{
4840
	global $langs, $conf;
4841
4842
	// Round PHP function does not allow number like '1,234.56' nor '1.234,56' nor '1 234,56'
4843
	// Numbers must be '1234.56'
4844
	// Decimal delimiter for PHP and database SQL requests must be '.'
4845
	$dec = ','; $thousand = ' ';
4846
	if ($langs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal")  $dec = $langs->transnoentitiesnoconv("SeparatorDecimal");
4847
	if ($langs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") $thousand = $langs->transnoentitiesnoconv("SeparatorThousand");
4848
	if ($thousand == 'None') $thousand = '';
4849
	elseif ($thousand == 'Space') $thousand = ' ';
4850
	//print "amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
4851
4852
	// Convert value to universal number format (no thousand separator, '.' as decimal separator)
4853
	if ($option != 1) {	// If not a PHP number or unknown, we change or clean format
4854
		//print 'PP'.$amount.' - '.$dec.' - '.$thousand.' - '.intval($amount).'<br>';
4855
		if (!is_numeric($amount)) {
4856
			$amount = preg_replace('/[a-zA-Z\/\\\*\(\)\<\>]/', '', $amount);
4857
		}
4858
4859
		if ($option == 2 && $thousand == '.' && preg_match('/\.(\d\d\d)$/', (string) $amount)) {	// It means the . is used as a thousand separator and string come frominput data, so 1.123 is 1123
4860
			$amount = str_replace($thousand, '', $amount);
4861
		}
4862
4863
		// Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
4864
		// to format defined by LC_NUMERIC after a calculation and we want source format to be like defined by Dolibarr setup.
4865
		// So if number was already a good number, it is converted into local Dolibarr setup.
4866
		if (is_numeric($amount))
4867
		{
4868
			// We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
4869
			$temps = sprintf("%0.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
4870
			$temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
4871
			$nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
4872
			$amount = number_format($amount, $nbofdec, $dec, $thousand);
4873
		}
4874
		//print "QQ".$amount."<br>\n";
4875
4876
		// Now make replace (the main goal of function)
4877
		if ($thousand != ',' && $thousand != '.') {
4878
			$amount = str_replace(',', '.', $amount); // To accept 2 notations for french users
4879
		}
4880
		$amount = str_replace(' ', '', $amount); // To avoid spaces
4881
		$amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
4882
		$amount = str_replace($dec, '.', $amount);
4883
	}
4884
4885
	// Now, make a rounding if required
4886
	if ($rounding)
4887
	{
4888
		$nbofdectoround = '';
4889
		if ($rounding == 'MU') {
4890
			$nbofdectoround = $conf->global->MAIN_MAX_DECIMALS_UNIT;
4891
		}
4892
		elseif ($rounding == 'MT') {
4893
			$nbofdectoround = $conf->global->MAIN_MAX_DECIMALS_TOT;
4894
		}
4895
		elseif ($rounding == 'MS') {
4896
			$nbofdectoround = empty($conf->global->MAIN_MAX_DECIMALS_STOCK) ? 5 : $conf->global->MAIN_MAX_DECIMALS_STOCK;
4897
		}
4898
		elseif ($rounding == 'CR') {
4899
			$nbofdectoround = max($conf->global->MAIN_MAX_DECIMALS_TOT, 8);
4900
		}
4901
		elseif (is_numeric($rounding))  $nbofdectoround = $rounding;
4902
		//print "RR".$amount.' - '.$nbofdectoround.'<br>';
4903
		if (dol_strlen($nbofdectoround)) $amount = round(is_string($amount) ? (float) $amount : $amount, $nbofdectoround); // $nbofdectoround can be 0.
4904
		else return 'ErrorBadParameterProvidedToFunction';
4905
		//print 'SS'.$amount.' - '.$nbofdec.' - '.$dec.' - '.$thousand.' - '.$nbofdectoround.'<br>';
4906
4907
		// Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
4908
		// to format defined by LC_NUMERIC after a calculation and we want source format to be defined by Dolibarr setup.
4909
		if (is_numeric($amount))
4910
		{
4911
			// We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
4912
			$temps = sprintf("%0.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
4913
			$temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
4914
			$nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
4915
			$amount = number_format($amount, min($nbofdec, $nbofdectoround), $dec, $thousand); // Convert amount to format with dolibarr dec and thousand
4916
		}
4917
		//print "TT".$amount.'<br>';
4918
4919
		// Always make replace because each math function (like round) replace
4920
		// with local values and we want a number that has a SQL string format x.y
4921
		if ($thousand != ',' && $thousand != '.') $amount = str_replace(',', '.', $amount); // To accept 2 notations for french users
4922
		$amount = str_replace(' ', '', $amount); // To avoid spaces
4923
		$amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
4924
		$amount = str_replace($dec, '.', $amount);
4925
	}
4926
4927
	return $amount;
4928
}
4929
4930
/**
4931
 * Output a dimension with best unit
4932
 *
4933
 * @param   float       $dimension      Dimension
4934
 * @param   int         $unit           Unit scale of dimension (Example: 0=kg, -3=g, -6=mg, 98=ounce, 99=pound, ...)
4935
 * @param   string      $type           'weight', 'volume', ...
4936
 * @param   Translate   $outputlangs    Translate language object
4937
 * @param   int         $round          -1 = non rounding, x = number of decimal
4938
 * @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)
4939
 * @return  string                      String to show dimensions
4940
 */
4941
function showDimensionInBestUnit($dimension, $unit, $type, $outputlangs, $round = -1, $forceunitoutput = 'no')
4942
{
4943
	require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
4944
4945
	if (($forceunitoutput == 'no' && $dimension < 1 / 10000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -6))
4946
	{
4947
		$dimension = $dimension * 1000000;
4948
		$unit = $unit - 6;
4949
	} elseif (($forceunitoutput == 'no' && $dimension < 1 / 10 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -3))
4950
	{
4951
		$dimension = $dimension * 1000;
4952
		$unit = $unit - 3;
4953
	} elseif (($forceunitoutput == 'no' && $dimension > 100000000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 6))
4954
	{
4955
		$dimension = $dimension / 1000000;
4956
		$unit = $unit + 6;
4957
	} elseif (($forceunitoutput == 'no' && $dimension > 100000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 3))
4958
	{
4959
		$dimension = $dimension / 1000;
4960
		$unit = $unit + 3;
4961
	}
4962
	// Special case when we want output unit into pound or ounce
4963
	/* TODO
4964
	if ($unit < 90 && $type == 'weight' && is_numeric($forceunitoutput) && (($forceunitoutput == 98) || ($forceunitoutput == 99))
4965
	{
4966
	    $dimension = // convert dimension from standard unit into ounce or pound
4967
	    $unit = $forceunitoutput;
4968
	}
4969
	if ($unit > 90 && $type == 'weight' && is_numeric($forceunitoutput) && $forceunitoutput < 90)
4970
	{
4971
	    $dimension = // convert dimension from standard unit into ounce or pound
4972
	    $unit = $forceunitoutput;
4973
	}*/
4974
4975
	$ret = price($dimension, 0, $outputlangs, 0, 0, $round).' '.measuringUnitString(0, $type, $unit);
4976
4977
	return $ret;
4978
}
4979
4980
4981
/**
4982
 *	Return localtax rate for a particular vat, when selling a product with vat $vatrate, from a $thirdparty_buyer to a $thirdparty_seller
4983
 *  Note: This function applies same rules than get_default_tva
4984
 *
4985
 * 	@param	float		$vatrate		        Vat rate. Can be '8.5' or '8.5 (VATCODEX)' for example
4986
 * 	@param  int			$local		         	Local tax to search and return (1 or 2 return only tax rate 1 or tax rate 2)
4987
 *  @param  Societe		$thirdparty_buyer    	Object of buying third party
4988
 *  @param	Societe		$thirdparty_seller		Object of selling third party ($mysoc if not defined)
4989
 *  @param	int			$vatnpr					If vat rate is NPR or not
4990
 * 	@return	mixed			   					0 if not found, localtax rate if found
4991
 *  @see get_default_tva()
4992
 */
4993
function get_localtax($vatrate, $local, $thirdparty_buyer = "", $thirdparty_seller = "", $vatnpr = 0)
4994
{
4995
	global $db, $conf, $mysoc;
4996
4997
	if (empty($thirdparty_seller) || !is_object($thirdparty_seller)) $thirdparty_seller = $mysoc;
4998
4999
	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);
5000
5001
	$vatratecleaned = $vatrate;
5002
	$reg = array();
5003
	if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) {     // If vat is "xx (yy)"
5004
		$vatratecleaned = trim($reg[1]);
5005
		$vatratecode = $reg[2];
5006
	}
5007
5008
	/*if ($thirdparty_buyer->country_code != $thirdparty_seller->country_code)
5009
	{
5010
		return 0;
5011
	}*/
5012
5013
	// Some test to guess with no need to make database access
5014
	if ($mysoc->country_code == 'ES') { // For spain localtaxes 1 and 2, tax is qualified if buyer use local tax
5015
		if ($local == 1) {
5016
			if (!$mysoc->localtax1_assuj || (string) $vatratecleaned == "0") return 0;
5017
			if ($thirdparty_seller->id == $mysoc->id) {
5018
				if (!$thirdparty_buyer->localtax1_assuj) return 0;
5019
			} else {
5020
				if (!$thirdparty_seller->localtax1_assuj) return 0;
5021
			}
5022
		}
5023
5024
		if ($local == 2) {
5025
			//if (! $mysoc->localtax2_assuj || (string) $vatratecleaned == "0") return 0;
5026
			if (!$mysoc->localtax2_assuj) return 0; // If main vat is 0, IRPF may be different than 0.
5027
			if ($thirdparty_seller->id == $mysoc->id) {
5028
				if (!$thirdparty_buyer->localtax2_assuj) return 0;
5029
			} else {
5030
				if (!$thirdparty_seller->localtax2_assuj) return 0;
5031
			}
5032
		}
5033
	} else {
5034
		if ($local == 1 && !$thirdparty_seller->localtax1_assuj) return 0;
5035
		if ($local == 2 && !$thirdparty_seller->localtax2_assuj) return 0;
5036
	}
5037
5038
	// For some country MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY is forced to on.
5039
	if (in_array($mysoc->country_code, array('ES'))) {
5040
		$conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY = 1;
5041
	}
5042
5043
	// Search local taxes
5044
	if (!empty($conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY))
5045
	{
5046
		if ($local == 1) {
5047
			if ($thirdparty_seller != $mysoc) {
5048
				if (!isOnlyOneLocalTax($local))  // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
5049
				{
5050
					return $thirdparty_seller->localtax1_value;
5051
				}
5052
			} else { // i am the seller
5053
				if (!isOnlyOneLocalTax($local)) { // TODO If seller is me, why not always returning this, even if there is only one locatax vat.
5054
					return $conf->global->MAIN_INFO_VALUE_LOCALTAX1;
5055
				}
5056
			}
5057
		}
5058
		if ($local == 2) {
5059
			if ($thirdparty_seller != $mysoc) {
5060
				if (!isOnlyOneLocalTax($local))  // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
5061
				// TODO We should also return value defined on thirdparty only if defined
5062
				{
5063
					return $thirdparty_seller->localtax2_value;
5064
				}
5065
			} else { // i am the seller
5066
				if (in_array($mysoc->country_code, array('ES'))) {
5067
					return $thirdparty_buyer->localtax2_value;
5068
				} else {
5069
					return $conf->global->MAIN_INFO_VALUE_LOCALTAX2;
5070
				}
5071
			}
5072
		}
5073
	}
5074
5075
	// By default, search value of local tax on line of common tax
5076
	$sql = "SELECT t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
5077
   	$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
5078
   	$sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($thirdparty_seller->country_code)."'";
5079
   	$sql .= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
5080
   	if (!empty($vatratecode)) $sql .= " AND t.code ='".$db->escape($vatratecode)."'"; // If we have the code, we use it in priority
5081
   	else $sql .= " AND t.recuperableonly = '".$db->escape($vatnpr)."'";
5082
   	dol_syslog("get_localtax", LOG_DEBUG);
5083
   	$resql = $db->query($sql);
5084
5085
   	if ($resql)
5086
   	{
5087
   		$obj = $db->fetch_object($resql);
5088
   		if ($obj) {
5089
	   		if ($local == 1) return $obj->localtax1;
5090
   			elseif ($local == 2) return $obj->localtax2;
5091
   		}
5092
	}
5093
5094
	return 0;
5095
}
5096
5097
5098
/**
5099
 * Return true if LocalTax (1 or 2) is unique.
5100
 * Example: If localtax1 is 5 on line with highest common vat rate, return true
5101
 * Example: If localtax1 is 5:8:15 on line with highest common vat rate, return false
5102
 *
5103
 * @param   int 	$local	Local tax to test (1 or 2)
5104
 * @return  boolean 		True if LocalTax have multiple values, False if not
5105
 */
5106
function isOnlyOneLocalTax($local)
5107
{
5108
	$tax = get_localtax_by_third($local);
5109
5110
	$valors = explode(":", $tax);
5111
5112
	if (count($valors) > 1) {
5113
		return false;
5114
	} else {
5115
		return true;
5116
	}
5117
}
5118
5119
/**
5120
 * Get values of localtaxes (1 or 2) for company country for the common vat with the highest value
5121
 *
5122
 * @param	int		$local 	LocalTax to get
5123
 * @return	number			Values of localtax
5124
 */
5125
function get_localtax_by_third($local)
5126
{
5127
	global $db, $mysoc;
5128
	$sql = "SELECT t.localtax1, t.localtax2 ";
5129
	$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t inner join ".MAIN_DB_PREFIX."c_country as c ON c.rowid=t.fk_pays";
5130
	$sql .= " WHERE c.code = '".$db->escape($mysoc->country_code)."' AND t.active = 1 AND t.taux=(";
5131
	$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";
5132
	$sql .= "  WHERE c.code = '".$db->escape($mysoc->country_code)."' AND tt.active = 1";
5133
	$sql .= "  )";
5134
5135
	$resql = $db->query($sql);
5136
	if ($resql)
5137
	{
5138
		$obj = $db->fetch_object($resql);
5139
		if ($local == 1) return $obj->localtax1;
5140
		elseif ($local == 2) return $obj->localtax2;
5141
	}
5142
5143
	return 0;
5144
}
5145
5146
5147
/**
5148
 *  Get tax (VAT) main information from Id.
5149
 *  You can also call getLocalTaxesFromRate() after to get only localtax fields.
5150
 *
5151
 *  @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.
5152
 *  @param	Societe	    $buyer         		Company object
5153
 *  @param	Societe	    $seller        		Company object
5154
 *  @param  int         $firstparamisid     1 if first param is id into table (use this if you can)
5155
 *  @return	array       	  				array('rowid'=> , 'code'=> ...)
5156
 *  @see getLocalTaxesFromRate()
5157
 */
5158
function getTaxesFromId($vatrate, $buyer = null, $seller = null, $firstparamisid = 1)
5159
{
5160
	global $db, $mysoc;
5161
5162
	dol_syslog("getTaxesFromId vat id or rate = ".$vatrate);
5163
5164
	// Search local taxes
5165
	$sql = "SELECT t.rowid, t.code, t.taux as rate, t.recuperableonly as npr, t.accountancy_code_sell, t.accountancy_code_buy,";
5166
	$sql .= " t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type";
5167
	$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t";
5168
	if ($firstparamisid) {
5169
		$sql .= " WHERE t.rowid = ".(int) $vatrate;
5170
	} else {
5171
		$vatratecleaned = $vatrate;
5172
		$vatratecode = '';
5173
		$reg = array();
5174
		if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg))      // If vat is "xx (yy)"
5175
		{
5176
			$vatratecleaned = $reg[1];
5177
			$vatratecode = $reg[2];
5178
		}
5179
5180
		$sql .= ", ".MAIN_DB_PREFIX."c_country as c";
5181
		/*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 ??
5182
		else $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($seller->country_code)."'";*/
5183
		$sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($seller->country_code)."'";
5184
		$sql .= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
5185
		if ($vatratecode) $sql .= " AND t.code = '".$db->escape($vatratecode)."'";
5186
	}
5187
5188
	$resql = $db->query($sql);
5189
	if ($resql) {
5190
		$obj = $db->fetch_object($resql);
5191
		if ($obj) return array(
5192
			'rowid'=>$obj->rowid,
5193
			'code'=>$obj->code,
5194
			'rate'=>$obj->rate,
5195
			'localtax1'=>$obj->localtax1,
5196
			'localtax1_type'=>$obj->localtax1_type,
5197
			'localtax2'=>$obj->localtax2,
5198
			'localtax2_type'=>$obj->localtax2_type,
5199
			'npr'=>$obj->npr,
5200
			'accountancy_code_sell'=>$obj->accountancy_code_sell,
5201
			'accountancy_code_buy'=>$obj->accountancy_code_buy
5202
		);
5203
		else return array();
5204
	} else dol_print_error($db);
5205
5206
	return array();
5207
}
5208
5209
/**
5210
 *  Get type and rate of localtaxes for a particular vat rate/country of a thirdparty.
5211
 *  This does not take into account the seller setup if subject to vat or not, only country.
5212
 *
5213
 *  TODO This function is ALSO called to retrieve type for building PDF. Such call of function must be removed.
5214
 *  Instead this function must be called when adding a line to get the array of possible values for localtax and type, and then
5215
 *  provide the selected value to the function calcul_price_total.
5216
 *
5217
 *  @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.
5218
 *  @param	int		    $local              Number of localtax (1 or 2, or 0 to return 1 & 2)
5219
 *  @param	Societe	    $buyer         		Company object
5220
 *  @param	Societe	    $seller        		Company object
5221
 *  @param  int         $firstparamisid     1 if first param is ID into table instead of Rate+code (use this if you can)
5222
 *  @return	array    	    				array(localtax_type1(1-6 or 0 if not found), rate localtax1, localtax_type2, rate localtax2, accountancycodecust, accountancycodesupp)
5223
 *  @see getTaxesFromId()
5224
 */
5225
function getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid = 0)
5226
{
5227
	global $db, $mysoc;
5228
5229
	dol_syslog("getLocalTaxesFromRate vatrate=".$vatrate." local=".$local);
5230
5231
	// Search local taxes
5232
	$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";
5233
	$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t";
5234
	if ($firstparamisid) {
5235
		$sql .= " WHERE t.rowid = ".(int) $vatrate;
5236
	} else {
5237
		$vatratecleaned = $vatrate;
5238
		$vatratecode = '';
5239
		$reg = array();
5240
		if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) {     // If vat is "x.x (yy)"
5241
			$vatratecleaned = $reg[1];
5242
			$vatratecode = $reg[2];
5243
		}
5244
5245
		$sql .= ", ".MAIN_DB_PREFIX."c_country as c";
5246
		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 ??
5247
		else $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape(empty($seller->country_code) ? $mysoc->country_code : $seller->country_code)."'";
5248
		$sql .= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
5249
		if ($vatratecode) $sql .= " AND t.code = '".$db->escape($vatratecode)."'";
5250
	}
5251
5252
	$resql = $db->query($sql);
5253
	if ($resql) {
5254
		$obj = $db->fetch_object($resql);
5255
5256
		if ($obj) {
5257
			$vateratestring = $obj->rate.($obj->code ? ' ('.$obj->code.')' : '');
5258
5259
			if ($local == 1) {
5260
				return array($obj->localtax1_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
5261
			} elseif ($local == 2) {
5262
				return array($obj->localtax2_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
5263
			} else {
5264
				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);
5265
			}
5266
		}
5267
	}
5268
5269
	return array();
5270
}
5271
5272
/**
5273
 *	Return vat rate of a product in a particular selling country or default country vat if product is unknown
5274
 *  Function called by get_default_tva
5275
 *
5276
 *  @param	int			$idprod          	Id of product or 0 if not a predefined product
5277
 *  @param  Societe		$thirdparty_seller  Thirdparty with a ->country_code defined (FR, US, IT, ...)
5278
 *	@param	int			$idprodfournprice	Id product_fournisseur_price (for "supplier" proposal/order/invoice)
5279
 *  @return float|string   				    Vat rate to use with format 5.0 or '5.0 (XXX)'
5280
 *  @see get_product_localtax_for_country()
5281
 */
5282
function get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice = 0)
5283
{
5284
	global $db, $conf, $mysoc;
5285
5286
	require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
5287
5288
	$ret = 0;
5289
	$found = 0;
5290
5291
	if ($idprod > 0)
5292
	{
5293
		// Load product
5294
		$product = new Product($db);
5295
		$result = $product->fetch($idprod);
5296
5297
		if ($mysoc->country_code == $thirdparty_seller->country_code) // If selling country is ours
5298
		{
5299
			if ($idprodfournprice > 0)     // We want vat for product for a "supplier" object
5300
			{
5301
				$product->get_buyprice($idprodfournprice, 0, 0, 0);
5302
				$ret = $product->vatrate_supplier;
5303
				if ($product->default_vat_code) $ret .= ' ('.$product->default_vat_code.')';
5304
			} else {
5305
				$ret = $product->tva_tx; // Default vat of product we defined
5306
				if ($product->default_vat_code) $ret .= ' ('.$product->default_vat_code.')';
5307
			}
5308
			$found = 1;
5309
		} else {
5310
			// TODO Read default product vat according to countrycode and product. Vat for couple countrycode/product is a feature not implemeted yet.
5311
			// May be usefull/required if hidden option SERVICE_ARE_ECOMMERCE_200238EC is on
5312
		}
5313
	}
5314
5315
	if (!$found)
5316
	{
5317
		if (empty($conf->global->MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS))
5318
		{
5319
			// If vat of product for the country not found or not defined, we return the first higher vat of country.
5320
			$sql = "SELECT t.taux as vat_rate, t.code as default_vat_code";
5321
			$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
5322
			$sql .= " WHERE t.active=1 AND t.fk_pays = c.rowid AND c.code='".$db->escape($thirdparty_seller->country_code)."'";
5323
			$sql .= " ORDER BY t.taux DESC, t.code ASC, t.recuperableonly ASC";
5324
			$sql .= $db->plimit(1);
5325
5326
			$resql = $db->query($sql);
5327
			if ($resql)
5328
			{
5329
				$obj = $db->fetch_object($resql);
5330
				if ($obj)
5331
				{
5332
					$ret = $obj->vat_rate;
5333
					if ($obj->default_vat_code) $ret .= ' ('.$obj->default_vat_code.')';
5334
				}
5335
				$db->free($sql);
5336
			} else dol_print_error($db);
5337
		} else $ret = $conf->global->MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS; // Forced value if autodetect fails
5338
	}
5339
5340
	dol_syslog("get_product_vat_for_country: ret=".$ret);
5341
	return $ret;
5342
}
5343
5344
/**
5345
 *	Return localtax vat rate of a product in a particular selling country or default country vat if product is unknown
5346
 *
5347
 *  @param	int		$idprod         		Id of product
5348
 *  @param  int		$local          		1 for localtax1, 2 for localtax 2
5349
 *  @param  Societe	$thirdparty_seller    	Thirdparty with a ->country_code defined (FR, US, IT, ...)
5350
 *  @return int             				<0 if KO, Vat rate if OK
5351
 *  @see get_product_vat_for_country()
5352
 */
5353
function get_product_localtax_for_country($idprod, $local, $thirdparty_seller)
5354
{
5355
	global $db, $mysoc;
5356
5357
	if (!class_exists('Product')) {
5358
		require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
5359
	}
5360
5361
	$ret = 0;
5362
	$found = 0;
5363
5364
	if ($idprod > 0)
5365
	{
5366
		// Load product
5367
		$product = new Product($db);
5368
		$result = $product->fetch($idprod);
5369
5370
		if ($mysoc->country_code == $thirdparty_seller->country_code) // If selling country is ours
5371
		{
5372
			/* Not defined yet, so we don't use this
5373
			if ($local==1) $ret=$product->localtax1_tx;
5374
			elseif ($local==2) $ret=$product->localtax2_tx;
5375
			$found=1;
5376
			*/
5377
		} else {
5378
			// TODO Read default product vat according to countrycode and product
5379
		}
5380
	}
5381
5382
	if (!$found)
5383
	{
5384
		// If vat of product for the country not found or not defined, we return higher vat of country.
5385
		$sql = "SELECT taux as vat_rate, localtax1, localtax2";
5386
		$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
5387
		$sql .= " WHERE t.active=1 AND t.fk_pays = c.rowid AND c.code='".$db->escape($thirdparty_seller->country_code)."'";
5388
		$sql .= " ORDER BY t.taux DESC, t.recuperableonly ASC";
5389
		$sql .= $db->plimit(1);
5390
5391
		$resql = $db->query($sql);
5392
		if ($resql)
5393
		{
5394
			$obj = $db->fetch_object($resql);
5395
			if ($obj)
5396
			{
5397
				if ($local == 1) $ret = $obj->localtax1;
5398
				elseif ($local == 2) $ret = $obj->localtax2;
5399
			}
5400
		} else dol_print_error($db);
5401
	}
5402
5403
	dol_syslog("get_product_localtax_for_country: ret=".$ret);
5404
	return $ret;
5405
}
5406
5407
/**
5408
 *	Function that return vat rate of a product line (according to seller, buyer and product vat rate)
5409
 *   Si vendeur non assujeti a TVA, TVA par defaut=0. Fin de regle.
5410
 *	 Si le (pays vendeur = pays acheteur) alors TVA par defaut=TVA du produit vendu. Fin de regle.
5411
 *	 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.
5412
 *	 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
5413
 *	 Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = entreprise avec num TVA) intra alors TVA par defaut=0. Fin de regle
5414
 *	 Sinon TVA proposee par defaut=0. Fin de regle.
5415
 *
5416
 *	@param	Societe		$thirdparty_seller    	Objet societe vendeuse
5417
 *	@param  Societe		$thirdparty_buyer   	Objet societe acheteuse
5418
 *	@param  int			$idprod					Id product
5419
 *	@param	int			$idprodfournprice		Id product_fournisseur_price (for supplier order/invoice)
5420
 *	@return float|string   				      	Vat rate to use with format 5.0 or '5.0 (XXX)', -1 if we can't guess it
5421
 *  @see get_default_npr(), get_default_localtax()
5422
 */
5423
function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
5424
{
5425
	global $conf;
5426
5427
	require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
5428
5429
	// Note: possible values for tva_assuj are 0/1 or franchise/reel
5430
	$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;
5431
5432
	$seller_country_code = $thirdparty_seller->country_code;
5433
	$seller_in_cee = isInEEC($thirdparty_seller);
5434
5435
	$buyer_country_code = $thirdparty_buyer->country_code;
5436
	$buyer_in_cee = isInEEC($thirdparty_buyer);
5437
5438
	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 : ''));
5439
5440
	// 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)
5441
	// we use the buyer VAT.
5442
	if (!empty($conf->global->SERVICE_ARE_ECOMMERCE_200238EC))
5443
	{
5444
		if ($seller_in_cee && $buyer_in_cee && !$thirdparty_buyer->isACompany())
5445
		{
5446
			//print 'VATRULE 0';
5447
			return get_product_vat_for_country($idprod, $thirdparty_buyer, $idprodfournprice);
5448
		}
5449
	}
5450
5451
	// If seller does not use VAT
5452
	if (!$seller_use_vat)
5453
	{
5454
		//print 'VATRULE 1';
5455
		return 0;
5456
	}
5457
5458
	// 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.
5459
5460
	// Si le (pays vendeur = pays acheteur) alors la TVA par defaut=TVA du produit vendu. Fin de regle.
5461
	if (($seller_country_code == $buyer_country_code)
5462
	|| (in_array($seller_country_code, array('FR,MC')) && in_array($buyer_country_code, array('FR', 'MC')))) // Warning ->country_code not always defined
5463
	{
5464
		//print 'VATRULE 2';
5465
		return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
5466
	}
5467
5468
	// 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.
5469
	// Not supported
5470
5471
	// Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = entreprise) alors TVA par defaut=0. Fin de regle
5472
	// Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = particulier) alors TVA par defaut=TVA du produit vendu. Fin de regle
5473
	if (($seller_in_cee && $buyer_in_cee))
5474
	{
5475
		$isacompany = $thirdparty_buyer->isACompany();
5476
		if ($isacompany)
5477
		{
5478
			//print 'VATRULE 3';
5479
			return 0;
5480
		} else {
5481
			//print 'VATRULE 4';
5482
			return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
5483
		}
5484
	}
5485
5486
	// Si (vendeur en France et acheteur hors Communaute europeenne et acheteur particulier) alors TVA par defaut=TVA du produit vendu. Fin de regle
5487
	if (!empty($conf->global->MAIN_USE_VAT_OF_PRODUCT_FOR_INDIVIDUAL_CUSTOMER_OUT_OF_EEC) && empty($buyer_in_cee) && !$thirdparty_buyer->isACompany()) {
5488
		return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
5489
	}
5490
5491
	// Sinon la TVA proposee par defaut=0. Fin de regle.
5492
	// Rem: Cela signifie qu'au moins un des 2 est hors Communaute europeenne et que le pays differe
5493
	//print 'VATRULE 5';
5494
	return 0;
5495
}
5496
5497
5498
/**
5499
 *	Fonction qui renvoie si tva doit etre tva percue recuperable
5500
 *
5501
 *	@param	Societe		$thirdparty_seller    	Thirdparty seller
5502
 *	@param  Societe		$thirdparty_buyer   	Thirdparty buyer
5503
 *  @param  int			$idprod                 Id product
5504
 *  @param	int			$idprodfournprice		Id supplier price for product
5505
 *	@return float       			        	0 or 1
5506
 *  @see get_default_tva(), get_default_localtax()
5507
 */
5508
function get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
5509
{
5510
	global $db;
5511
5512
	if ($idprodfournprice > 0)
5513
	{
5514
		if (!class_exists('ProductFournisseur')) {
5515
			require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
5516
		}
5517
		$prodprice = new ProductFournisseur($db);
5518
		$prodprice->fetch_product_fournisseur_price($idprodfournprice);
5519
		return $prodprice->fourn_tva_npr;
5520
	} elseif ($idprod > 0)
5521
	{
5522
		if (!class_exists('Product')) {
5523
			require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
5524
		}
5525
		$prod = new Product($db);
5526
		$prod->fetch($idprod);
5527
		return $prod->tva_npr;
5528
	}
5529
5530
	return 0;
5531
}
5532
5533
/**
5534
 *	Function that return localtax of a product line (according to seller, buyer and product vat rate)
5535
 *   Si vendeur non assujeti a TVA, TVA par defaut=0. Fin de regle.
5536
 *	 Si le (pays vendeur = pays acheteur) alors TVA par defaut=TVA du produit vendu. Fin de regle.
5537
 *	 Sinon TVA proposee par defaut=0. Fin de regle.
5538
 *
5539
 *	@param	Societe		$thirdparty_seller    	Thirdparty seller
5540
 *	@param  Societe		$thirdparty_buyer   	Thirdparty buyer
5541
 *  @param	int			$local					Localtax to process (1 or 2)
5542
 *	@param  int			$idprod					Id product
5543
 *	@return integer        				       	localtax, -1 si ne peut etre determine
5544
 *  @see get_default_tva(), get_default_npr()
5545
 */
5546
function get_default_localtax($thirdparty_seller, $thirdparty_buyer, $local, $idprod = 0)
5547
{
5548
	global $mysoc;
5549
5550
	if (!is_object($thirdparty_seller)) return -1;
5551
	if (!is_object($thirdparty_buyer)) return -1;
5552
5553
	if ($local == 1) // Localtax 1
5554
	{
5555
		if ($mysoc->country_code == 'ES')
5556
		{
5557
			if (is_numeric($thirdparty_buyer->localtax1_assuj) && !$thirdparty_buyer->localtax1_assuj) return 0;
5558
		} else {
5559
			// Si vendeur non assujeti a Localtax1, localtax1 par default=0
5560
			if (is_numeric($thirdparty_seller->localtax1_assuj) && !$thirdparty_seller->localtax1_assuj) return 0;
5561
			if (!is_numeric($thirdparty_seller->localtax1_assuj) && $thirdparty_seller->localtax1_assuj == 'localtax1off') return 0;
5562
		}
5563
	} elseif ($local == 2) //I Localtax 2
5564
	{
5565
		// Si vendeur non assujeti a Localtax2, localtax2 par default=0
5566
		if (is_numeric($thirdparty_seller->localtax2_assuj) && !$thirdparty_seller->localtax2_assuj) return 0;
5567
		if (!is_numeric($thirdparty_seller->localtax2_assuj) && $thirdparty_seller->localtax2_assuj == 'localtax2off') return 0;
5568
	}
5569
5570
	if ($thirdparty_seller->country_code == $thirdparty_buyer->country_code)
5571
	{
5572
		return get_product_localtax_for_country($idprod, $local, $thirdparty_seller);
5573
	}
5574
5575
	return 0;
5576
}
5577
5578
/**
5579
 *	Return yes or no in current language
5580
 *
5581
 *	@param	string|int	$yesno			Value to test (1, 'yes', 'true' or 0, 'no', 'false')
5582
 *	@param	integer		$case			1=Yes/No, 0=yes/no, 2=Disabled checkbox, 3=Disabled checkbox + Yes/No
5583
 *	@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.
5584
 *	@return	string						HTML string
5585
 */
5586
function yn($yesno, $case = 1, $color = 0)
5587
{
5588
	global $langs;
5589
	$result = 'unknown'; $classname = '';
5590
	if ($yesno == 1 || strtolower($yesno) == 'yes' || strtolower($yesno) == 'true') 	// A mettre avant test sur no a cause du == 0
5591
	{
5592
		$result = $langs->trans('yes');
5593
		if ($case == 1 || $case == 3) $result = $langs->trans("Yes");
5594
		if ($case == 2) $result = '<input type="checkbox" value="1" checked disabled>';
5595
		if ($case == 3) $result = '<input type="checkbox" value="1" checked disabled> '.$result;
5596
5597
		$classname = 'ok';
5598
	} elseif ($yesno == 0 || strtolower($yesno) == 'no' || strtolower($yesno) == 'false')
5599
	{
5600
		$result = $langs->trans("no");
5601
		if ($case == 1 || $case == 3) $result = $langs->trans("No");
5602
		if ($case == 2) $result = '<input type="checkbox" value="0" disabled>';
5603
		if ($case == 3) $result = '<input type="checkbox" value="0" disabled> '.$result;
5604
5605
		if ($color == 2) $classname = 'ok';
5606
		else $classname = 'error';
5607
	}
5608
	if ($color) return '<font class="'.$classname.'">'.$result.'</font>';
5609
	return $result;
5610
}
5611
5612
/**
5613
 *	Return a path to have a the directory according to object where files are stored.
5614
 *  New usage:       $conf->module->multidir_output[$object->entity].'/'.get_exdir(0, 0, 0, 1, $object, '').'/'
5615
 *         or:       $conf->module->dir_output.'/'.get_exdir(0, 0, 0, 0, $object, '')     if multidir_output not defined.
5616
 *  Example out with new usage:       $object is invoice -> 'INYYMM-ABCD'
5617
 *  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/"
5618
 *
5619
 *	@param	string|int	$num            Id of object (deprecated, $object will be used in future)
5620
 *	@param  int			$level		    Level of subdirs to return (1, 2 or 3 levels). (deprecated, global option will be used in future)
5621
 * 	@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)
5622
 *  @param  int			$withoutslash   0=With slash at end (except if '/', we return ''), 1=without slash at end
5623
 *  @param	Object		$object			Object to use to get ref to forge the path.
5624
 *  @param	string		$modulepart		Type of object ('invoice_supplier, 'donation', 'invoice', ...'). Use '' for autodetect from $object.
5625
 *  @return	string						Dir to use ending. Example '' or '1/' or '1/2/'
5626
 */
5627
function get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart = '')
5628
{
5629
	global $conf;
5630
5631
	if (empty($modulepart) && !empty($object->module)) $modulepart = $object->module;
5632
5633
	$path = '';
5634
5635
	$arrayforoldpath = array('cheque', 'category', 'holiday', 'supplier_invoice', 'invoice_supplier', 'mailing', 'supplier_payment');
5636
	if (!empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO)) $arrayforoldpath[] = 'product';
5637
	if (!empty($level) && in_array($modulepart, $arrayforoldpath)) {
5638
		// This part should be removed once all code is using "get_exdir" to forge path, with all parameters provided.
5639
		if (empty($alpha)) $num = preg_replace('/([^0-9])/i', '', $num);
5640
		else $num = preg_replace('/^.*\-/i', '', $num);
5641
		$num = substr("000".$num, -$level);
5642
		if ($level == 1) $path = substr($num, 0, 1);
5643
		if ($level == 2) $path = substr($num, 1, 1).'/'.substr($num, 0, 1);
5644
		if ($level == 3) $path = substr($num, 2, 1).'/'.substr($num, 1, 1).'/'.substr($num, 0, 1);
5645
	} else {
5646
		// We will enhance here a common way of forging path for document storage.
5647
		// In a future, we may distribute directories on several levels depending on setup and object.
5648
		// Here, $object->id, $object->ref and $modulepart are required.
5649
		//var_dump($modulepart);
5650
		$path = dol_sanitizeFileName(empty($object->ref) ? (string) $object->id : $object->ref);
5651
	}
5652
5653
	if (empty($withoutslash) && !empty($path)) $path .= '/';
5654
5655
	return $path;
5656
}
5657
5658
/**
5659
 *	Creation of a directory (this can create recursive subdir)
5660
 *
5661
 *	@param	string		$dir		Directory to create (Separator must be '/'. Example: '/mydir/mysubdir')
5662
 *	@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)
5663
 *  @param	string|null	$newmask	Mask for new file (Defaults to $conf->global->MAIN_UMASK or 0755 if unavailable). Example: '0444'
5664
 *	@return int         			< 0 if KO, 0 = already exists, > 0 if OK
5665
 */
5666
function dol_mkdir($dir, $dataroot = '', $newmask = null)
5667
{
5668
	global $conf;
5669
5670
	dol_syslog("functions.lib::dol_mkdir: dir=".$dir, LOG_INFO);
5671
5672
	$dir_osencoded = dol_osencode($dir);
5673
	if (@is_dir($dir_osencoded)) return 0;
5674
5675
	$nberr = 0;
5676
	$nbcreated = 0;
5677
5678
	$ccdir = '';
5679
	if (!empty($dataroot)) {
5680
		// Remove data root from loop
5681
		$dir = str_replace($dataroot.'/', '', $dir);
5682
		$ccdir = $dataroot.'/';
5683
	}
5684
5685
	$cdir = explode("/", $dir);
5686
	$num = count($cdir);
5687
	for ($i = 0; $i < $num; $i++)
5688
	{
5689
		if ($i > 0) $ccdir .= '/'.$cdir[$i];
5690
		else $ccdir .= $cdir[$i];
5691
		if (preg_match("/^.:$/", $ccdir, $regs)) continue; // Si chemin Windows incomplet, on poursuit par rep suivant
5692
5693
		// Attention, le is_dir() peut echouer bien que le rep existe.
5694
		// (ex selon config de open_basedir)
5695
		if ($ccdir)
5696
		{
5697
			$ccdir_osencoded = dol_osencode($ccdir);
5698
			if (!@is_dir($ccdir_osencoded))
5699
			{
5700
				dol_syslog("functions.lib::dol_mkdir: Directory '".$ccdir."' does not exists or is outside open_basedir PHP setting.", LOG_DEBUG);
5701
5702
				umask(0);
5703
				$dirmaskdec = octdec($newmask);
5704
				if (empty($newmask)) {
5705
					$dirmaskdec = empty($conf->global->MAIN_UMASK) ? octdec('0755') : octdec($conf->global->MAIN_UMASK);
5706
				}
5707
				$dirmaskdec |= octdec('0111'); // Set x bit required for directories
5708
				if (!@mkdir($ccdir_osencoded, $dirmaskdec))
5709
				{
5710
					// Si le is_dir a renvoye une fausse info, alors on passe ici.
5711
					dol_syslog("functions.lib::dol_mkdir: Fails to create directory '".$ccdir."' or directory already exists.", LOG_WARNING);
5712
					$nberr++;
5713
				} else {
5714
					dol_syslog("functions.lib::dol_mkdir: Directory '".$ccdir."' created", LOG_DEBUG);
5715
					$nberr = 0; // On remet a zero car si on arrive ici, cela veut dire que les echecs precedents peuvent etre ignore
5716
					$nbcreated++;
5717
				}
5718
			} else {
5719
				$nberr = 0; // On remet a zero car si on arrive ici, cela veut dire que les echecs precedents peuvent etre ignores
5720
			}
5721
		}
5722
	}
5723
	return ($nberr ? -$nberr : $nbcreated);
5724
}
5725
5726
5727
/**
5728
 *	Return picto saying a field is required
5729
 *
5730
 *	@return  string		Chaine avec picto obligatoire
5731
 */
5732
function picto_required()
5733
{
5734
	return '<span class="fieldrequired">*</span>';
5735
}
5736
5737
5738
/**
5739
 *	Clean a string from all HTML tags and entities.
5740
 *  This function differs from strip_tags because:
5741
 *  - <br> are replaced with \n if removelinefeed=0 or 1
5742
 *  - if entities are found, they are decoded BEFORE the strip
5743
 *  - you can decide to convert line feed into a space
5744
 *
5745
 *	@param	string	$stringtoclean		String to clean
5746
 *	@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..."
5747
 *  @param  string	$pagecodeto      	Encoding of input/output string
5748
 *  @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)
5749
 *  @param	integer	$removedoublespaces	Replace double space into one space
5750
 *	@return string	    				String cleaned
5751
 *
5752
 * 	@see	dol_escape_htmltag() strip_tags() dol_string_onlythesehtmltags() dol_string_neverthesehtmltags(), dolStripPhpCode()
5753
 */
5754
function dol_string_nohtmltag($stringtoclean, $removelinefeed = 1, $pagecodeto = 'UTF-8', $strip_tags = 0, $removedoublespaces = 1)
5755
{
5756
	if ($removelinefeed == 2) $stringtoclean = preg_replace('/<br[^>]*>(\n|\r)+/ims', '<br>', $stringtoclean);
5757
	$temp = preg_replace('/<br[^>]*>/i', "\n", $stringtoclean);
5758
5759
	// 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)
5760
	$temp = dol_html_entity_decode($temp, ENT_COMPAT | ENT_HTML5, $pagecodeto);
5761
5762
	$temp = str_replace('< ', '__ltspace__', $temp);
5763
5764
	if ($strip_tags) {
5765
		$temp = strip_tags($temp);
5766
	} else {
5767
		$pattern = "/<[^<>]+>/";
5768
		// Example of $temp: <a href="/myurl" title="<u>A title</u>">0000-021</a>
5769
		$temp = preg_replace($pattern, "", $temp); // pass 1
5770
		// $temp after pass 1: <a href="/myurl" title="A title">0000-021
5771
		$temp = preg_replace($pattern, "", $temp); // pass 2
5772
		// $temp after pass 2: 0000-021
5773
	}
5774
5775
	$temp = dol_html_entity_decode($temp, ENT_COMPAT, $pagecodeto);
5776
5777
	// Remove also carriage returns
5778
	if ($removelinefeed == 1) $temp = str_replace(array("\r\n", "\r", "\n"), " ", $temp);
5779
5780
	// And double quotes
5781
	if ($removedoublespaces) {
5782
		while (strpos($temp, "  ")) {
5783
			$temp = str_replace("  ", " ", $temp);
5784
		}
5785
	}
5786
5787
	$temp = str_replace('__ltspace__', '< ', $temp);
5788
5789
	return trim($temp);
5790
}
5791
5792
/**
5793
 *	Clean a string to keep only desirable HTML tags.
5794
 *  WARNING: This also clean HTML comments (used to obfuscate tag name).
5795
 *
5796
 *	@param	string	$stringtoclean			String to clean
5797
 *  @param	int		$cleanalsosomestyles	Remove absolute/fixed positioning from inline styles
5798
 *  @param	int		$removeclassattribute	Remove the class attribute from tags
5799
 *  @param	int		$cleanalsojavascript	Remove also occurence of 'javascript:'.
5800
 *	@return string	    					String cleaned
5801
 *
5802
 * 	@see	dol_escape_htmltag() strip_tags() dol_string_nohtmltag() dol_string_neverthesehtmltags()
5803
 */
5804
function dol_string_onlythesehtmltags($stringtoclean, $cleanalsosomestyles = 1, $removeclassattribute = 1, $cleanalsojavascript = 0)
5805
{
5806
	$allowed_tags = array(
5807
		"html", "head", "meta", "body", "article", "a", "abbr", "b", "blockquote", "br", "cite", "div", "dl", "dd", "dt", "em", "font", "img", "ins", "hr", "i", "li", "link",
5808
		"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"
5809
	);
5810
5811
	$allowed_tags_string = join("><", $allowed_tags);
5812
	$allowed_tags_string = '<'.$allowed_tags_string.'>';
5813
5814
	$stringtoclean = dol_string_nounprintableascii($stringtoclean, 0);
5815
	$stringtoclean = preg_replace('/&colon;/i', ':', $stringtoclean);
5816
5817
	$stringtoclean = preg_replace('/<!--[^>]*-->/', '', $stringtoclean);
5818
	$stringtoclean = preg_replace('/&#58;|&#0000058|&#x3A/i', '', $stringtoclean); // refused string ':' encoded (no reason to have it encoded) to lock 'javascript:...'
5819
	$stringtoclean = preg_replace('/javascript\s*:/i', '', $stringtoclean);
5820
5821
	$temp = strip_tags($stringtoclean, $allowed_tags_string);
5822
5823
	if ($cleanalsosomestyles) {	// Clean for remaining html tags
5824
		$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
5825
	}
5826
	if ($removeclassattribute) {	// Clean for remaining html tags
5827
		$temp = preg_replace('/(<[^>]+)\s+class=((["\']).*?\\3|\\w*)/i', '\\1', $temp);
5828
	}
5829
5830
	// Remove 'javascript:' that we should not find into a text with
5831
	// 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).
5832
	if ($cleanalsojavascript) {
5833
		$temp = preg_replace('/javascript\s*:/i', '', $temp);
5834
	}
5835
5836
	return $temp;
5837
}
5838
5839
/**
5840
 *	Clean a string from some undesirable HTML tags.
5841
 *  Note. Not as secured as dol_string_onlythesehtmltags().
5842
 *
5843
 *	@param	string	$stringtoclean			String to clean
5844
 *  @param	array	$disallowed_tags		Array of tags not allowed
5845
 *  @param	string	$cleanalsosomestyles	Clean also some tags
5846
 *	@return string	    					String cleaned
5847
 *
5848
 * 	@see	dol_escape_htmltag() strip_tags() dol_string_nohtmltag() dol_string_onlythesehtmltags()
5849
 */
5850
function dol_string_neverthesehtmltags($stringtoclean, $disallowed_tags = array('textarea'), $cleanalsosomestyles = 0)
5851
{
5852
	$temp = $stringtoclean;
5853
	foreach ($disallowed_tags as $tagtoremove)
5854
	{
5855
		$temp = preg_replace('/<\/?'.$tagtoremove.'>/', '', $temp);
5856
		$temp = preg_replace('/<\/?'.$tagtoremove.'\s+[^>]*>/', '', $temp);
5857
	}
5858
5859
	if ($cleanalsosomestyles) {
5860
		$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
5861
	}
5862
5863
	return $temp;
5864
}
5865
5866
5867
/**
5868
 * Return first line of text. Cut will depends if content is HTML or not.
5869
 *
5870
 * @param 	string	$text		Input text
5871
 * @param	int		$nboflines  Nb of lines to get (default is 1 = first line only)
5872
 * @param   string  $charset    Charset of $text string (UTF-8 by default)
5873
 * @return	string				Output text
5874
 * @see dol_nboflines_bis(), dol_string_nohtmltag(), dol_escape_htmltag()
5875
 */
5876
function dolGetFirstLineOfText($text, $nboflines = 1, $charset = 'UTF-8')
5877
{
5878
	if ($nboflines == 1)
5879
	{
5880
		if (dol_textishtml($text))
5881
		{
5882
			$firstline = preg_replace('/<br[^>]*>.*$/s', '', $text); // The s pattern modifier means the . can match newline characters
5883
			$firstline = preg_replace('/<div[^>]*>.*$/s', '', $firstline); // The s pattern modifier means the . can match newline characters
5884
		} else {
5885
			$firstline = preg_replace('/[\n\r].*/', '', $text);
5886
		}
5887
		return $firstline.((strlen($firstline) != strlen($text)) ? '...' : '');
5888
	} else {
5889
		$ishtml = 0;
5890
		if (dol_textishtml($text))
5891
		{
5892
			$text = preg_replace('/\n/', '', $text);
5893
			$ishtml = 1;
5894
			$repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
5895
		} else {
5896
			$repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
5897
		}
5898
5899
		$text = strtr($text, $repTable);
5900
		if ($charset == 'UTF-8') { $pattern = '/(<br[^>]*>)/Uu'; } // /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
5901
		else $pattern = '/(<br[^>]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
5902
		$a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
5903
5904
		$firstline = '';
5905
		$i = 0;
5906
		$nba = count($a); // 2x nb of lines in $a because $a contains also a line for each new line separator
5907
		while (($i < $nba) && ($i < ($nboflines * 2)))
5908
		{
5909
			if ($i % 2 == 0) $firstline .= $a[$i];
5910
			elseif (($i < (($nboflines * 2) - 1)) && ($i < ($nba - 1))) $firstline .= ($ishtml ? "<br>\n" : "\n");
5911
			$i++;
5912
		}
5913
		unset($a);
5914
		return $firstline.(($i < $nba) ? '...' : '');
5915
	}
5916
}
5917
5918
5919
/**
5920
 * Replace CRLF in string with a HTML BR tag.
5921
 * 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.
5922
 *
5923
 * @param	string	$stringtoencode		String to encode
5924
 * @param	int     $nl2brmode			0=Adding br before \n, 1=Replacing \n by br
5925
 * @param   bool	$forxml             false=Use <br>, true=Use <br />
5926
 * @return	string						String encoded
5927
 * @see dol_nboflines(), dolGetFirstLineOfText()
5928
 */
5929
function dol_nl2br($stringtoencode, $nl2brmode = 0, $forxml = false)
5930
{
5931
	if (!$nl2brmode) {
5932
		return nl2br($stringtoencode, $forxml);
5933
	} else {
5934
		$ret = preg_replace('/(\r\n|\r|\n)/i', ($forxml ? '<br />' : '<br>'), $stringtoencode);
5935
		return $ret;
5936
	}
5937
}
5938
5939
5940
/**
5941
 *	This function is called to encode a string into a HTML string but differs from htmlentities because
5942
 * 	a detection is done before to see if text is already HTML or not. Also, all entities but &,<,>," are converted.
5943
 *  This permits to encode special chars to entities with no double encoding for already encoded HTML strings.
5944
 * 	This function also remove last EOL or BR if $removelasteolbr=1 (default).
5945
 *  For PDF usage, you can show text by 2 ways:
5946
 *              - writeHTMLCell -> param must be encoded into HTML.
5947
 *              - MultiCell -> param must not be encoded into HTML.
5948
 *              Because writeHTMLCell convert also \n into <br>, if function
5949
 *              is used to build PDF, nl2brmode must be 1.
5950
 *
5951
 *	@param	string	$stringtoencode		String to encode
5952
 *	@param	int		$nl2brmode			0=Adding br before \n, 1=Replacing \n by br (for use with FPDF writeHTMLCell function for example)
5953
 *  @param  string	$pagecodefrom       Pagecode stringtoencode is encoded
5954
 *  @param	int		$removelasteolbr	1=Remove last br or lasts \n (default), 0=Do nothing
5955
 *  @return	string						String encoded
5956
 */
5957
function dol_htmlentitiesbr($stringtoencode, $nl2brmode = 0, $pagecodefrom = 'UTF-8', $removelasteolbr = 1)
5958
{
5959
	$newstring = $stringtoencode;
5960
	if (dol_textishtml($stringtoencode))	// Check if text is already HTML or not
5961
	{
5962
		$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.
5963
		if ($removelasteolbr) $newstring = preg_replace('/<br>$/i', '', $newstring); // Remove last <br> (remove only last one)
5964
		$newstring = strtr($newstring, array('&'=>'__and__', '<'=>'__lt__', '>'=>'__gt__', '"'=>'__dquot__'));
5965
		$newstring = dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom); // Make entity encoding
5966
		$newstring = strtr($newstring, array('__and__'=>'&', '__lt__'=>'<', '__gt__'=>'>', '__dquot__'=>'"'));
5967
	} else {
5968
		if ($removelasteolbr) $newstring = preg_replace('/(\r\n|\r|\n)$/i', '', $newstring); // Remove last \n (may remove several)
5969
		$newstring = dol_nl2br(dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom), $nl2brmode);
5970
	}
5971
	// Other substitutions that htmlentities does not do
5972
	//$newstring=str_replace(chr(128),'&euro;',$newstring);	// 128 = 0x80. Not in html entity table.     // Seems useles with TCPDF. Make bug with UTF8 languages
5973
	return $newstring;
5974
}
5975
5976
/**
5977
 *	This function is called to decode a HTML string (it decodes entities and br tags)
5978
 *
5979
 *	@param	string	$stringtodecode		String to decode
5980
 *	@param	string	$pagecodeto			Page code for result
5981
 *	@return	string						String decoded
5982
 */
5983
function dol_htmlentitiesbr_decode($stringtodecode, $pagecodeto = 'UTF-8')
5984
{
5985
	$ret = dol_html_entity_decode($stringtodecode, ENT_COMPAT | ENT_HTML5, $pagecodeto);
5986
	$ret = preg_replace('/'."\r\n".'<br(\s[\sa-zA-Z_="]*)?\/?>/i', "<br>", $ret);
5987
	$ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>'."\r\n".'/i', "\r\n", $ret);
5988
	$ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>'."\n".'/i', "\n", $ret);
5989
	$ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>/i', "\n", $ret);
5990
	return $ret;
5991
}
5992
5993
/**
5994
 *	This function remove all ending \n and br at end
5995
 *
5996
 *	@param	string	$stringtodecode		String to decode
5997
 *	@return	string						String decoded
5998
 */
5999
function dol_htmlcleanlastbr($stringtodecode)
6000
{
6001
	$ret = preg_replace('/(<br>|<br(\s[\sa-zA-Z_="]*)?\/?>|'."\n".'|'."\r".')+$/i', "", $stringtodecode);
6002
	return $ret;
6003
}
6004
6005
/**
6006
 * Replace html_entity_decode functions to manage errors
6007
 *
6008
 * @param   string	$a					Operand a
6009
 * @param   string	$b					Operand b (ENT_QUOTES|ENT_HTML5=convert simple, double quotes, colon, e accent, ...)
6010
 * @param   string	$c					Operand c
6011
 * @param	string	$keepsomeentities	Entities but &, <, >, " are not converted.
6012
 * @return  string						String decoded
6013
 */
6014
function dol_html_entity_decode($a, $b, $c = 'UTF-8', $keepsomeentities = 0)
6015
{
6016
	$newstring = $a;
6017
	if ($keepsomeentities) $newstring = strtr($newstring, array('&amp;'=>'__andamp__', '&lt;'=>'__andlt__', '&gt;'=>'__andgt__', '"'=>'__dquot__'));
6018
	$newstring = html_entity_decode($newstring, $b, $c);
6019
	if ($keepsomeentities) $newstring = strtr($newstring, array('__andamp__'=>'&amp;', '__andlt__'=>'&lt;', '__andgt__'=>'&gt;', '__dquot__'=>'"'));
6020
	return $newstring;
6021
}
6022
6023
/**
6024
 * Replace htmlentities functions.
6025
 * Goal of this function is to be sure to have default values of htmlentities that match what we need.
6026
 *
6027
 * @param   string  $string         The input string to encode
6028
 * @param   int     $flags          Flags (see PHP doc above)
6029
 * @param   string  $encoding       Encoding page code
6030
 * @param   bool    $double_encode  When double_encode is turned off, PHP will not encode existing html entities
6031
 * @return  string  $ret            Encoded string
6032
 */
6033
function dol_htmlentities($string, $flags = null, $encoding = 'UTF-8', $double_encode = false)
6034
{
6035
	return htmlentities($string, $flags, $encoding, $double_encode);
6036
}
6037
6038
/**
6039
 *	Check if a string is a correct iso string
6040
 *	If not, it will we considered not HTML encoded even if it is by FPDF.
6041
 *	Example, if string contains euro symbol that has ascii code 128
6042
 *
6043
 *	@param	string		$s      	String to check
6044
 *  @param	string		$clean		Clean if it is not an ISO. Warning, if file is utf8, you will get a bad formated file.
6045
 *	@return	int|string  	   		0 if bad iso, 1 if good iso, Or the clean string if $clean is 1
6046
 */
6047
function dol_string_is_good_iso($s, $clean = 0)
6048
{
6049
	$len = dol_strlen($s);
6050
	$out = '';
6051
	$ok = 1;
6052
	for ($scursor = 0; $scursor < $len; $scursor++)
6053
	{
6054
		$ordchar = ord($s[$scursor]);
6055
		//print $scursor.'-'.$ordchar.'<br>';
6056
		if ($ordchar < 32 && $ordchar != 13 && $ordchar != 10) { $ok = 0; break; } elseif ($ordchar > 126 && $ordchar < 160) { $ok = 0; break; } elseif ($clean) {
6057
			$out .= $s[$scursor];
6058
		}
6059
	}
6060
	if ($clean) return $out;
6061
	return $ok;
6062
}
6063
6064
/**
6065
 *	Return nb of lines of a clear text
6066
 *
6067
 *	@param	string	$s			String to check
6068
 * 	@param	int     $maxchar	Not yet used
6069
 *	@return	int					Number of lines
6070
 *  @see	dol_nboflines_bis(), dolGetFirstLineOfText()
6071
 */
6072
function dol_nboflines($s, $maxchar = 0)
6073
{
6074
	if ($s == '') return 0;
6075
	$arraystring = explode("\n", $s);
6076
	$nb = count($arraystring);
6077
6078
	return $nb;
6079
}
6080
6081
6082
/**
6083
 *	Return nb of lines of a formated text with \n and <br> (WARNING: string must not have mixed \n and br separators)
6084
 *
6085
 *	@param	string	$text      		Text
6086
 *	@param	int		$maxlinesize  	Largeur de ligne en caracteres (ou 0 si pas de limite - defaut)
6087
 * 	@param	string	$charset		Give the charset used to encode the $text variable in memory.
6088
 *	@return int						Number of lines
6089
 *	@see	dol_nboflines(), dolGetFirstLineOfText()
6090
 */
6091
function dol_nboflines_bis($text, $maxlinesize = 0, $charset = 'UTF-8')
6092
{
6093
	$repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
6094
	if (dol_textishtml($text)) $repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
6095
6096
	$text = strtr($text, $repTable);
6097
	if ($charset == 'UTF-8') { $pattern = '/(<br[^>]*>)/Uu'; } // /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
6098
	else $pattern = '/(<br[^>]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
6099
	$a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
6100
6101
	$nblines = (int) floor((count($a) + 1) / 2);
6102
	// count possible auto line breaks
6103
	if ($maxlinesize)
6104
	{
6105
		foreach ($a as $line)
6106
		{
6107
			if (dol_strlen($line) > $maxlinesize)
6108
			{
6109
				//$line_dec = html_entity_decode(strip_tags($line));
6110
				$line_dec = html_entity_decode($line);
6111
				if (dol_strlen($line_dec) > $maxlinesize)
6112
				{
6113
					$line_dec = wordwrap($line_dec, $maxlinesize, '\n', true);
6114
					$nblines += substr_count($line_dec, '\n');
6115
				}
6116
			}
6117
		}
6118
	}
6119
6120
	unset($a);
6121
	return $nblines;
6122
}
6123
6124
/**
6125
 *	Return if a text is a html content
6126
 *
6127
 *	@param	string	$msg		Content to check
6128
 *	@param	int		$option		0=Full detection, 1=Fast check
6129
 *	@return	boolean				true/false
6130
 *	@see	dol_concatdesc()
6131
 */
6132
function dol_textishtml($msg, $option = 0)
6133
{
6134
	if ($option == 1)
6135
	{
6136
		if (preg_match('/<html/i', $msg))				return true;
6137
		elseif (preg_match('/<body/i', $msg))			return true;
6138
		elseif (preg_match('/<\/textarea/i', $msg))	  return true;
6139
		elseif (preg_match('/<br/i', $msg))				return true;
6140
		return false;
6141
	} else {
6142
		if (preg_match('/<html/i', $msg))				return true;
6143
		elseif (preg_match('/<body/i', $msg))			return true;
6144
		elseif (preg_match('/<\/textarea/i', $msg))	  return true;
6145
		elseif (preg_match('/<(b|em|i|u)>/i', $msg))		return true;
6146
		elseif (preg_match('/<br\/>/i', $msg))	  return true;
6147
		elseif (preg_match('/<(br|div|font|li|p|span|strong|table)>/i', $msg)) 	  return true;
6148
		elseif (preg_match('/<(br|div|font|li|p|span|strong|table)\s+[^<>\/]*>/i', $msg)) return true;
6149
		elseif (preg_match('/<(br|div|font|li|p|span|strong|table)\s+[^<>\/]*\/>/i', $msg)) return true;
6150
		elseif (preg_match('/<img\s+[^<>]*src[^<>]*>/i', $msg)) return true; // must accept <img src="http://example.com/aaa.png" />
6151
		elseif (preg_match('/<a\s+[^<>]*href[^<>]*>/i', $msg)) return true; // must accept <a href="http://example.com/aaa.png" />
6152
		elseif (preg_match('/<h[0-9]>/i', $msg))			return true;
6153
		elseif (preg_match('/&[A-Z0-9]{1,6};/i', $msg))	return true; // Html entities names (http://www.w3schools.com/tags/ref_entities.asp)
6154
		elseif (preg_match('/&#[0-9]{2,3};/i', $msg))	return true; // Html entities numbers (http://www.w3schools.com/tags/ref_entities.asp)
6155
6156
		return false;
6157
	}
6158
}
6159
6160
/**
6161
 *  Concat 2 descriptions with a new line between them (second operand after first one with appropriate new line separator)
6162
 *  text1 html + text2 html => text1 + '<br>' + text2
6163
 *  text1 html + text2 txt  => text1 + '<br>' + dol_nl2br(text2)
6164
 *  text1 txt  + text2 html => dol_nl2br(text1) + '<br>' + text2
6165
 *  text1 txt  + text2 txt  => text1 + '\n' + text2
6166
 *
6167
 *  @param  string  $text1          Text 1
6168
 *  @param  string  $text2          Text 2
6169
 *  @param  bool    $forxml         true=Use <br /> instead of <br> if we have to add a br tag
6170
 *  @param  bool    $invert         invert order of description lines (we often use config MAIN_CHANGE_ORDER_CONCAT_DESCRIPTION in this parameter)
6171
 *  @return string                  Text 1 + new line + Text2
6172
 *  @see    dol_textishtml()
6173
 */
6174
function dol_concatdesc($text1, $text2, $forxml = false, $invert = false)
6175
{
6176
	if (!empty($invert))
6177
	{
6178
			$tmp = $text1;
6179
			$text1 = $text2;
6180
			$text2 = $tmp;
6181
	}
6182
6183
	$ret = '';
6184
	$ret .= (!dol_textishtml($text1) && dol_textishtml($text2)) ? dol_nl2br(dol_escape_htmltag($text1, 0, 1, '', 1), 0, $forxml) : $text1;
6185
	$ret .= (!empty($text1) && !empty($text2)) ? ((dol_textishtml($text1) || dol_textishtml($text2)) ? ($forxml ? "<br \>\n" : "<br>\n") : "\n") : "";
6186
	$ret .= (dol_textishtml($text1) && !dol_textishtml($text2)) ? dol_nl2br(dol_escape_htmltag($text2, 0, 1, '', 1), 0, $forxml) : $text2;
6187
	return $ret;
6188
}
6189
6190
6191
6192
/**
6193
 * Return array of possible common substitutions. This includes several families like: 'system', 'mycompany', 'object', 'objectamount', 'date', 'user'
6194
 *
6195
 * @param	Translate	$outputlangs	Output language
6196
 * @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)
6197
 * @param   array       $exclude        Array of family keys we want to exclude. For example array('system', 'mycompany', 'object', 'objectamount', 'date', 'user', ...)
6198
 * @param   Object      $object         Object for keys on object
6199
 * @return	array						Array of substitutions
6200
 * @see setSubstitFromObject()
6201
 */
6202
function getCommonSubstitutionArray($outputlangs, $onlykey = 0, $exclude = null, $object = null)
6203
{
6204
	global $db, $conf, $mysoc, $user, $extrafields;
6205
6206
	$substitutionarray = array();
6207
6208
	if (empty($exclude) || !in_array('user', $exclude))
6209
	{
6210
		// Add SIGNATURE into substitutionarray first, so, when we will make the substitution,
6211
		// this will include signature content first and then replace var found into content of signature
6212
		$signature = $user->signature;
6213
		$substitutionarray = array_merge($substitutionarray, array(
6214
			'__USER_SIGNATURE__' => (string) (($signature && empty($conf->global->MAIN_MAIL_DO_NOT_USE_SIGN)) ? ($onlykey == 2 ? dol_trunc(dol_string_nohtmltag($signature), 30) : $signature) : '')
6215
		)
6216
			);
6217
6218
		$substitutionarray = array_merge($substitutionarray, array(
6219
		'__USER_ID__' => (string) $user->id,
6220
		'__USER_LOGIN__' => (string) $user->login,
6221
		'__USER_EMAIL__' => (string) $user->email,
6222
		'__USER_LASTNAME__' => (string) $user->lastname,
6223
		'__USER_FIRSTNAME__' => (string) $user->firstname,
6224
		'__USER_FULLNAME__' => (string) $user->getFullName($outputlangs),
6225
		'__USER_SUPERVISOR_ID__' => (string) ($user->fk_user ? $user->fk_user : '0'),
6226
		'__USER_REMOTE_IP__' => (string) getUserRemoteIP()
6227
		)
6228
			);
6229
	}
6230
	if ((empty($exclude) || !in_array('mycompany', $exclude)) && is_object($mysoc))
6231
	{
6232
		$substitutionarray = array_merge($substitutionarray, array(
6233
			'__MYCOMPANY_NAME__'    => $mysoc->name,
6234
			'__MYCOMPANY_EMAIL__'   => $mysoc->email,
6235
			'__MYCOMPANY_PROFID1__' => $mysoc->idprof1,
6236
			'__MYCOMPANY_PROFID2__' => $mysoc->idprof2,
6237
			'__MYCOMPANY_PROFID3__' => $mysoc->idprof3,
6238
			'__MYCOMPANY_PROFID4__' => $mysoc->idprof4,
6239
			'__MYCOMPANY_PROFID5__' => $mysoc->idprof5,
6240
			'__MYCOMPANY_PROFID6__' => $mysoc->idprof6,
6241
			'__MYCOMPANY_CAPITAL__' => $mysoc->capital,
6242
			'__MYCOMPANY_FULLADDRESS__' => $mysoc->getFullAddress(1, ', '),
6243
			'__MYCOMPANY_ADDRESS__' => $mysoc->address,
6244
			'__MYCOMPANY_ZIP__'     => $mysoc->zip,
6245
			'__MYCOMPANY_TOWN__'    => $mysoc->town,
6246
			'__MYCOMPANY_COUNTRY__'    => $mysoc->country,
6247
			'__MYCOMPANY_COUNTRY_ID__' => $mysoc->country_id,
6248
			'__MYCOMPANY_COUNTRY_CODE__' => $mysoc->country_code,
6249
			'__MYCOMPANY_CURRENCY_CODE__' => $conf->currency
6250
		));
6251
	}
6252
6253
	if (($onlykey || is_object($object)) && (empty($exclude) || !in_array('object', $exclude)))
6254
	{
6255
		if ($onlykey)
6256
		{
6257
			$substitutionarray['__ID__'] = '__ID__';
6258
			$substitutionarray['__REF__'] = '__REF__';
6259
			$substitutionarray['__REF_CLIENT__'] = '__REF_CLIENT__';
6260
			$substitutionarray['__REF_SUPPLIER__'] = '__REF_SUPPLIER__';
6261
			$substitutionarray['__NOTE_PUBLIC__'] = '__NOTE_PUBLIC__';
6262
			$substitutionarray['__NOTE_PRIVATE__'] = '__NOTE_PRIVATE__';
6263
			$substitutionarray['__EXTRAFIELD_XXX__'] = '__EXTRAFIELD_XXX__';
6264
6265
			if (!empty($conf->societe->enabled))	// Most objects are concerned
6266
			{
6267
				$substitutionarray['__THIRDPARTY_ID__'] = '__THIRDPARTY_ID__';
6268
				$substitutionarray['__THIRDPARTY_NAME__'] = '__THIRDPARTY_NAME__';
6269
				$substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = '__THIRDPARTY_NAME_ALIAS__';
6270
				$substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = '__THIRDPARTY_CODE_CLIENT__';
6271
				$substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = '__THIRDPARTY_CODE_FOURNISSEUR__';
6272
				$substitutionarray['__THIRDPARTY_EMAIL__'] = '__THIRDPARTY_EMAIL__';
6273
				$substitutionarray['__THIRDPARTY_PHONE__'] = '__THIRDPARTY_PHONE__';
6274
				$substitutionarray['__THIRDPARTY_FAX__'] = '__THIRDPARTY_FAX__';
6275
				$substitutionarray['__THIRDPARTY_ADDRESS__'] = '__THIRDPARTY_ADDRESS__';
6276
				$substitutionarray['__THIRDPARTY_ZIP__'] = '__THIRDPARTY_ZIP__';
6277
				$substitutionarray['__THIRDPARTY_TOWN__'] = '__THIRDPARTY_TOWN__';
6278
				$substitutionarray['__THIRDPARTY_IDPROF1__'] = '__THIRDPARTY_IDPROF1__';
6279
				$substitutionarray['__THIRDPARTY_IDPROF2__'] = '__THIRDPARTY_IDPROF2__';
6280
				$substitutionarray['__THIRDPARTY_IDPROF3__'] = '__THIRDPARTY_IDPROF3__';
6281
				$substitutionarray['__THIRDPARTY_IDPROF4__'] = '__THIRDPARTY_IDPROF4__';
6282
				$substitutionarray['__THIRDPARTY_IDPROF5__'] = '__THIRDPARTY_IDPROF5__';
6283
				$substitutionarray['__THIRDPARTY_IDPROF6__'] = '__THIRDPARTY_IDPROF6__';
6284
				$substitutionarray['__THIRDPARTY_TVAINTRA__'] = '__THIRDPARTY_TVAINTRA__';
6285
				$substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = '__THIRDPARTY_NOTE_PUBLIC__';
6286
				$substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = '__THIRDPARTY_NOTE_PRIVATE__';
6287
			}
6288
			if (!empty($conf->adherent->enabled) && (!is_object($object) || $object->element == 'adherent'))
6289
			{
6290
				$substitutionarray['__MEMBER_ID__'] = '__MEMBER_ID__';
6291
				$substitutionarray['__MEMBER_CIVILITY__'] = '__MEMBER_CIVILITY__';
6292
				$substitutionarray['__MEMBER_FIRSTNAME__'] = '__MEMBER_FIRSTNAME__';
6293
				$substitutionarray['__MEMBER_LASTNAME__'] = '__MEMBER_LASTNAME__';
6294
				/*$substitutionarray['__MEMBER_NOTE_PUBLIC__'] = '__MEMBER_NOTE_PUBLIC__';
6295
				$substitutionarray['__MEMBER_NOTE_PRIVATE__'] = '__MEMBER_NOTE_PRIVATE__';*/
6296
			}
6297
			if (!empty($conf->recruitment->enabled) && (!is_object($object) || $object->element == 'candidature'))
6298
			{
6299
				$substitutionarray['__CANDIDATE_FULLNAME__'] = '__CANDIDATE_FULLNAME__';
6300
				$substitutionarray['__CANDIDATE_FIRSTNAME__'] = '__CANDIDATE_FIRSTNAME__';
6301
				$substitutionarray['__CANDIDATE_LASTNAME__'] = '__CANDIDATE_LASTNAME__';
6302
			}
6303
			if (!empty($conf->projet->enabled))		// Most objects
6304
			{
6305
				$substitutionarray['__PROJECT_ID__'] = '__PROJECT_ID__';
6306
				$substitutionarray['__PROJECT_REF__'] = '__PROJECT_REF__';
6307
				$substitutionarray['__PROJECT_NAME__'] = '__PROJECT_NAME__';
6308
				/*$substitutionarray['__PROJECT_NOTE_PUBLIC__'] = '__PROJECT_NOTE_PUBLIC__';
6309
				$substitutionarray['__PROJECT_NOTE_PRIVATE__'] = '__PROJECT_NOTE_PRIVATE__';*/
6310
			}
6311
			if (!empty($conf->contrat->enabled) && (!is_object($object) || $object->element == 'contract'))
6312
			{
6313
				$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = 'Highest date planned for a service start';
6314
				$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = 'Highest date and hour planned for service start';
6315
				$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = 'Lowest data for planned expiration of service';
6316
				$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = 'Lowest date and hour for planned expiration of service';
6317
			}
6318
			$substitutionarray['__ONLINE_PAYMENT_URL__'] = 'UrlToPayOnlineIfApplicable';
6319
			$substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = 'TextAndUrlToPayOnlineIfApplicable';
6320
			$substitutionarray['__SECUREKEYPAYMENT__'] = 'Security key (if key is not unique per record)';
6321
			$substitutionarray['__SECUREKEYPAYMENT_MEMBER__'] = 'Security key for payment on a member subscription (one key per member)';
6322
			$substitutionarray['__SECUREKEYPAYMENT_ORDER__'] = 'Security key for payment on an order';
6323
			$substitutionarray['__SECUREKEYPAYMENT_INVOICE__'] = 'Security key for payment on an invoice';
6324
			$substitutionarray['__SECUREKEYPAYMENT_CONTRACTLINE__'] = 'Security key for payment on a a service';
6325
6326
			$substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = 'Direct download url of a proposal';
6327
			$substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = 'Direct download url of an order';
6328
			$substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = 'Direct download url of an invoice';
6329
6330
			if (!empty($conf->expedition->enabled) && (!is_object($object) || $object->element == 'shipping'))
6331
			{
6332
				$substitutionarray['__SHIPPINGTRACKNUM__'] = 'Shipping tracking number';
6333
				$substitutionarray['__SHIPPINGTRACKNUMURL__'] = 'Shipping tracking url';
6334
			}
6335
			if (!empty($conf->reception->enabled) && (!is_object($object) || $object->element == 'reception'))
6336
			{
6337
				$substitutionarray['__RECEPTIONTRACKNUM__'] = 'Shippin tracking number of shipment';
6338
				$substitutionarray['__RECEPTIONTRACKNUMURL__'] = 'Shipping tracking url';
6339
			}
6340
		} else {
6341
			$substitutionarray['__ID__'] = $object->id;
6342
			$substitutionarray['__REF__'] = $object->ref;
6343
			$substitutionarray['__REF_CLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
6344
			$substitutionarray['__REF_SUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
6345
			$substitutionarray['__NOTE_PUBLIC__'] = (isset($object->note_public) ? $object->note_public : null);
6346
			$substitutionarray['__NOTE_PRIVATE__'] = (isset($object->note_private) ? $object->note_private : null);
6347
6348
			$substitutionarray['__DATE_DELIVERY__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, 'day', 0, $outputlangs) : '');
6349
6350
			// For backward compatibility
6351
			$substitutionarray['__REFCLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
6352
			$substitutionarray['__REFSUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
6353
			$substitutionarray['__REFCLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
6354
			$substitutionarray['__REFSUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
6355
			$substitutionarray['__SUPPLIER_ORDER_DATE_DELIVERY__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, 'day', 0, $outputlangs) : '');
6356
			$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 : '')) : '');
6357
6358
			if (is_object($object) && ($object->element == 'adherent' || $object->element == 'member') && $object->id > 0)
6359
			{
6360
				$birthday = (empty($object->birth) ? '' : dol_print_date($object->birth, 'day'));
6361
6362
				$substitutionarray['__MEMBER_ID__'] = (isset($object->id) ? $object->id : '');
6363
				if (method_exists($object, 'getCivilityLabel')) $substitutionarray['__MEMBER_CIVILITY__'] = $object->getCivilityLabel();
6364
				$substitutionarray['__MEMBER_FIRSTNAME__'] = (isset($object->firstname) ? $object->firstname : '');
6365
				$substitutionarray['__MEMBER_LASTNAME__'] = (isset($object->lastname) ? $object->lastname : '');
6366
				if (method_exists($object, 'getFullName')) $substitutionarray['__MEMBER_FULLNAME__'] = $object->getFullName($outputlangs);
6367
				$substitutionarray['__MEMBER_COMPANY__'] = (isset($object->societe) ? $object->societe : '');
6368
				$substitutionarray['__MEMBER_ADDRESS__'] = (isset($object->address) ? $object->address : '');
6369
				$substitutionarray['__MEMBER_ZIP__'] = (isset($object->zip) ? $object->zip : '');
6370
				$substitutionarray['__MEMBER_TOWN__'] = (isset($object->town) ? $object->town : '');
6371
				$substitutionarray['__MEMBER_COUNTRY__'] = (isset($object->country) ? $object->country : '');
6372
				$substitutionarray['__MEMBER_EMAIL__'] = (isset($object->email) ? $object->email : '');
6373
				$substitutionarray['__MEMBER_BIRTH__'] = (isset($birthday) ? $birthday : '');
6374
				$substitutionarray['__MEMBER_PHOTO__'] = (isset($object->photo) ? $object->photo : '');
6375
				$substitutionarray['__MEMBER_LOGIN__'] = (isset($object->login) ? $object->login : '');
6376
				$substitutionarray['__MEMBER_PASSWORD__'] = (isset($object->pass) ? $object->pass : '');
6377
				$substitutionarray['__MEMBER_PHONE__'] = (isset($object->phone) ? $object->phone : '');
6378
				$substitutionarray['__MEMBER_PHONEPRO__'] = (isset($object->phone_perso) ? $object->phone_perso : '');
6379
				$substitutionarray['__MEMBER_PHONEMOBILE__'] = (isset($object->phone_mobile) ? $object->phone_mobile : '');
6380
				$substitutionarray['__MEMBER_TYPE__'] = (isset($object->type) ? $object->type : '');
6381
				$substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE__']       = dol_print_date($object->first_subscription_date, 'dayrfc');
6382
				$substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_START__'] = dol_print_date($object->first_subscription_date_start, 'dayrfc');
6383
				$substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_END__']   = dol_print_date($object->first_subscription_date_end, 'dayrfc');
6384
				$substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE__']        = dol_print_date($object->last_subscription_date, 'dayrfc');
6385
				$substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_START__']  = dol_print_date($object->last_subscription_date_start, 'dayrfc');
6386
				$substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_END__']    = dol_print_date($object->last_subscription_date_end, 'dayrfc');
6387
			}
6388
6389
			if (is_object($object) && $object->element == 'societe') {
6390
				$substitutionarray['__THIRDPARTY_ID__'] = (is_object($object) ? $object->id : '');
6391
				$substitutionarray['__THIRDPARTY_NAME__'] = (is_object($object) ? $object->name : '');
6392
				$substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = (is_object($object) ? $object->name_alias : '');
6393
				$substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = (is_object($object) ? $object->code_client : '');
6394
				$substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = (is_object($object) ? $object->code_fournisseur : '');
6395
				$substitutionarray['__THIRDPARTY_EMAIL__'] = (is_object($object) ? $object->email : '');
6396
				$substitutionarray['__THIRDPARTY_PHONE__'] = (is_object($object) ? $object->phone : '');
6397
				$substitutionarray['__THIRDPARTY_FAX__'] = (is_object($object) ? $object->fax : '');
6398
				$substitutionarray['__THIRDPARTY_ADDRESS__'] = (is_object($object) ? $object->address : '');
6399
				$substitutionarray['__THIRDPARTY_ZIP__'] = (is_object($object) ? $object->zip : '');
6400
				$substitutionarray['__THIRDPARTY_TOWN__'] = (is_object($object) ? $object->town : '');
6401
				$substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = (is_object($object) ? $object->country_id : '');
6402
				$substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = (is_object($object) ? $object->country_code : '');
6403
				$substitutionarray['__THIRDPARTY_IDPROF1__'] = (is_object($object) ? $object->idprof1 : '');
6404
				$substitutionarray['__THIRDPARTY_IDPROF2__'] = (is_object($object) ? $object->idprof2 : '');
6405
				$substitutionarray['__THIRDPARTY_IDPROF3__'] = (is_object($object) ? $object->idprof3 : '');
6406
				$substitutionarray['__THIRDPARTY_IDPROF4__'] = (is_object($object) ? $object->idprof4 : '');
6407
				$substitutionarray['__THIRDPARTY_IDPROF5__'] = (is_object($object) ? $object->idprof5 : '');
6408
				$substitutionarray['__THIRDPARTY_IDPROF6__'] = (is_object($object) ? $object->idprof6 : '');
6409
				$substitutionarray['__THIRDPARTY_TVAINTRA__'] = (is_object($object) ? $object->tva_intra : '');
6410
				$substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = (is_object($object) ? dol_htmlentitiesbr($object->note_public) : '');
6411
				$substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = (is_object($object) ? dol_htmlentitiesbr($object->note_private) : '');
6412
			} elseif (is_object($object->thirdparty)) {
6413
				$substitutionarray['__THIRDPARTY_ID__'] = (is_object($object->thirdparty) ? $object->thirdparty->id : '');
6414
				$substitutionarray['__THIRDPARTY_NAME__'] = (is_object($object->thirdparty) ? $object->thirdparty->name : '');
6415
				$substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = (is_object($object->thirdparty) ? $object->thirdparty->name_alias : '');
6416
				$substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = (is_object($object->thirdparty) ? $object->thirdparty->code_client : '');
6417
				$substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = (is_object($object->thirdparty) ? $object->thirdparty->code_fournisseur : '');
6418
				$substitutionarray['__THIRDPARTY_EMAIL__'] = (is_object($object->thirdparty) ? $object->thirdparty->email : '');
6419
				$substitutionarray['__THIRDPARTY_PHONE__'] = (is_object($object->thirdparty) ? $object->thirdparty->phone : '');
6420
				$substitutionarray['__THIRDPARTY_FAX__'] = (is_object($object->thirdparty) ? $object->thirdparty->fax : '');
6421
				$substitutionarray['__THIRDPARTY_ADDRESS__'] = (is_object($object->thirdparty) ? $object->thirdparty->address : '');
6422
				$substitutionarray['__THIRDPARTY_ZIP__'] = (is_object($object->thirdparty) ? $object->thirdparty->zip : '');
6423
				$substitutionarray['__THIRDPARTY_TOWN__'] = (is_object($object->thirdparty) ? $object->thirdparty->town : '');
6424
				$substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = (is_object($object->thirdparty) ? $object->thirdparty->country_id : '');
6425
				$substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = (is_object($object->thirdparty) ? $object->thirdparty->country_code : '');
6426
				$substitutionarray['__THIRDPARTY_IDPROF1__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof1 : '');
6427
				$substitutionarray['__THIRDPARTY_IDPROF2__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof2 : '');
6428
				$substitutionarray['__THIRDPARTY_IDPROF3__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof3 : '');
6429
				$substitutionarray['__THIRDPARTY_IDPROF4__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof4 : '');
6430
				$substitutionarray['__THIRDPARTY_IDPROF5__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof5 : '');
6431
				$substitutionarray['__THIRDPARTY_IDPROF6__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof6 : '');
6432
				$substitutionarray['__THIRDPARTY_TVAINTRA__'] = (is_object($object->thirdparty) ? $object->thirdparty->tva_intra : '');
6433
				$substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = (is_object($object->thirdparty) ? dol_htmlentitiesbr($object->thirdparty->note_public) : '');
6434
				$substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = (is_object($object->thirdparty) ? dol_htmlentitiesbr($object->thirdparty->note_private) : '');
6435
			}
6436
6437
			if (is_object($object) && $object->element == 'recruitmentcandidature') {
6438
				$substitutionarray['__CANDIDATE_FULLNAME__'] = $object->getFullName($outputlangs);
6439
				$substitutionarray['__CANDIDATE_FIRSTNAME__'] = $object->firstname;
6440
				$substitutionarray['__CANDIDATE_LASTNAME__'] = $object->lastname;
6441
			}
6442
6443
			if (is_object($object->project))
6444
			{
6445
				$substitutionarray['__PROJECT_ID__'] = (is_object($object->project) ? $object->project->id : '');
6446
				$substitutionarray['__PROJECT_REF__'] = (is_object($object->project) ? $object->project->ref : '');
6447
				$substitutionarray['__PROJECT_NAME__'] = (is_object($object->project) ? $object->project->title : '');
6448
			}
6449
			if (is_object($object->projet))	// Deprecated, for backward compatibility
6450
			{
6451
				$substitutionarray['__PROJECT_ID__'] = (is_object($object->projet) ? $object->projet->id : '');
6452
				$substitutionarray['__PROJECT_REF__'] = (is_object($object->projet) ? $object->projet->ref : '');
6453
				$substitutionarray['__PROJECT_NAME__'] = (is_object($object->projet) ? $object->projet->title : '');
6454
			}
6455
6456
			if (is_object($object) && $object->element == 'shipping')
6457
			{
6458
				$substitutionarray['__SHIPPINGTRACKNUM__'] = $object->tracking_number;
6459
				$substitutionarray['__SHIPPINGTRACKNUMURL__'] = $object->tracking_url;
6460
			}
6461
			if (is_object($object) && $object->element == 'reception')
6462
			{
6463
				$substitutionarray['__RECEPTIONTRACKNUM__'] = $object->tracking_number;
6464
				$substitutionarray['__RECEPTIONTRACKNUMURL__'] = $object->tracking_url;
6465
			}
6466
6467
			if (is_object($object) && $object->element == 'contrat' && $object->id > 0 && is_array($object->lines))
6468
			{
6469
				$dateplannedstart = '';
6470
				$datenextexpiration = '';
6471
				foreach ($object->lines as $line)
6472
				{
6473
					if ($line->date_ouverture_prevue > $dateplannedstart) $dateplannedstart = $line->date_ouverture_prevue;
6474
					if ($line->statut == 4 && $line->date_fin_prevue && (!$datenextexpiration || $line->date_fin_prevue < $datenextexpiration)) $datenextexpiration = $line->date_fin_prevue;
6475
				}
6476
				$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = dol_print_date($dateplannedstart, 'dayrfc');
6477
				$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = dol_print_date($dateplannedstart, 'standard');
6478
				$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = dol_print_date($datenextexpiration, 'dayrfc');
6479
				$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = dol_print_date($datenextexpiration, 'standard');
6480
			}
6481
6482
			// Create dynamic tags for __EXTRAFIELD_FIELD__
6483
			if ($object->table_element && $object->id > 0)
6484
			{
6485
				if (!is_object($extrafields)) $extrafields = new ExtraFields($db);
6486
				$extrafields->fetch_name_optionals_label($object->table_element, true);
6487
6488
				if ($object->fetch_optionals() > 0)
0 ignored issues
show
Bug introduced by
The method fetch_optionals() does not exist on null. ( Ignorable by Annotation )

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

6488
				if ($object->/** @scrutinizer ignore-call */ fetch_optionals() > 0)

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

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

Loading history...
6489
				{
6490
					if (is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label']) > 0)
6491
					{
6492
						foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) {
6493
							$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'__'] = $object->array_options['options_'.$key];
6494
							if ($extrafields->attributes[$object->table_element]['type'][$key] == 'date') {
6495
								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'__'] = dol_print_date($object->array_options['options_'.$key], 'day');
6496
								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_LOCALE__'] = dol_print_date($object->array_options['options_'.$key], 'day', 'tzserver', $outputlangs);
6497
								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_RFC__'] = dol_print_date($object->array_options['options_'.$key], 'dayrfc');
6498
							} elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'datetime') {
6499
								$datetime = $object->array_options['options_'.$key];
6500
								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhour') : '');
6501
								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_LOCALE__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhour', 'tzserver', $outputlangs) : '');
6502
								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_DAY_LOCALE__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'day', 'tzserver', $outputlangs) : '');
6503
								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_RFC__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhourrfc') : '');
6504
							}
6505
						}
6506
					}
6507
				}
6508
			}
6509
6510
			// Complete substitution array with the url to make online payment
6511
			$paymenturl = '';
6512
			if (empty($substitutionarray['__REF__']))
6513
			{
6514
				$paymenturl = '';
6515
			} else {
6516
				// Set the online payment url link into __ONLINE_PAYMENT_URL__ key
6517
				require_once DOL_DOCUMENT_ROOT.'/core/lib/payments.lib.php';
6518
				$outputlangs->loadLangs(array('paypal', 'other'));
6519
				$typeforonlinepayment = 'free';
6520
				if (is_object($object) && $object->element == 'commande') $typeforonlinepayment = 'order';
6521
				if (is_object($object) && $object->element == 'facture')  $typeforonlinepayment = 'invoice';
6522
				if (is_object($object) && $object->element == 'member')   $typeforonlinepayment = 'member';
6523
				$url = getOnlinePaymentUrl(0, $typeforonlinepayment, $substitutionarray['__REF__']);
6524
				$paymenturl = $url;
6525
			}
6526
6527
			if ($object->id > 0)
6528
			{
6529
				$substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = ($paymenturl ?str_replace('\n', "\n", $outputlangs->trans("PredefinedMailContentLink", $paymenturl)) : '');
6530
				$substitutionarray['__ONLINE_PAYMENT_URL__'] = $paymenturl;
6531
6532
				if (!empty($conf->global->PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'propal')
6533
				{
6534
					$substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = $object->getLastMainDocLink($object->element);
6535
				} else $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = '';
6536
				if (!empty($conf->global->ORDER_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'commande')
6537
				{
6538
					$substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = $object->getLastMainDocLink($object->element);
6539
				} else $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = '';
6540
				if (!empty($conf->global->INVOICE_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'facture')
6541
				{
6542
					$substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = $object->getLastMainDocLink($object->element);
6543
				} else $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = '';
6544
6545
				if (is_object($object) && $object->element == 'propal') $substitutionarray['__URL_PROPOSAL__'] = DOL_MAIN_URL_ROOT."/comm/propal/card.php?id=".$object->id;
6546
				if (is_object($object) && $object->element == 'commande') $substitutionarray['__URL_ORDER__'] = DOL_MAIN_URL_ROOT."/commande/card.php?id=".$object->id;
6547
				if (is_object($object) && $object->element == 'facture') $substitutionarray['__URL_INVOICE__'] = DOL_MAIN_URL_ROOT."/compta/facture/card.php?id=".$object->id;
6548
			}
6549
6550
			if (is_object($object) && $object->element == 'action')
6551
			{
6552
				$substitutionarray['__EVENT_LABEL__'] = $object->label;
6553
				$substitutionarray['__EVENT_DATE__'] = dol_print_date($object->datep, '%A %d %b %Y');
6554
				$substitutionarray['__EVENT_TIME__'] = dol_print_date($object->datep, '%H:%M:%S');
6555
			}
6556
		}
6557
	}
6558
	if (empty($exclude) || !in_array('objectamount', $exclude))
6559
	{
6560
		include_once DOL_DOCUMENT_ROOT.'/core/lib/functionsnumtoword.lib.php';
6561
6562
		$substitutionarray['__DATE_YMD__']        = is_object($object) ? (isset($object->date) ? dol_print_date($object->date, 'day', 0, $outputlangs) : null) : '';
6563
		$substitutionarray['__DATE_DUE_YMD__']    = is_object($object) ? (isset($object->date_lim_reglement) ? dol_print_date($object->date_lim_reglement, 'day', 0, $outputlangs) : null) : '';
6564
6565
		$substitutionarray['__AMOUNT__']          = is_object($object) ? $object->total_ttc : '';
6566
		$substitutionarray['__AMOUNT_TEXT__']     = is_object($object) ? dol_convertToWord($object->total_ttc, $outputlangs, '', true) : '';
6567
		$substitutionarray['__AMOUNT_TEXTCURRENCY__'] = is_object($object) ? dol_convertToWord($object->total_ttc, $outputlangs, $conf->currency, true) : '';
6568
		$substitutionarray['__AMOUNT_EXCL_TAX__'] = is_object($object) ? $object->total_ht : '';
6569
		$substitutionarray['__AMOUNT_VAT__']      = is_object($object) ? (isset($object->total_vat) ? $object->total_vat : $object->total_tva) : '';
6570
		$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)) : '';
6571
		$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)) : '';
6572
		if ($onlykey != 2 || $mysoc->useLocalTax(1)) $substitutionarray['__AMOUNT_TAX2__']     = is_object($object) ? $object->total_localtax1 : '';
6573
		if ($onlykey != 2 || $mysoc->useLocalTax(2)) $substitutionarray['__AMOUNT_TAX3__']     = is_object($object) ? $object->total_localtax2 : '';
6574
6575
		$substitutionarray['__AMOUNT_FORMATED__']          = is_object($object) ? ($object->total_ttc ? price($object->total_ttc, 0, $outputlangs, 0, 0, -1, $conf->currency) : null) : '';
6576
		$substitutionarray['__AMOUNT_EXCL_TAX_FORMATED__'] = is_object($object) ? ($object->total_ht ? price($object->total_ht, 0, $outputlangs, 0, 0, -1, $conf->currency) : null) : '';
6577
		$substitutionarray['__AMOUNT_VAT_FORMATED__']      = is_object($object) ? (isset($object->total_vat) ? price($object->total_vat, 0, $outputlangs, 0, 0, -1, $conf->currency) : ($object->total_tva ? price($object->total_tva, 0, $outputlangs, 0, 0, -1, $conf->currency) : null)) : '';
6578
		if ($onlykey != 2 || $mysoc->useLocalTax(1)) $substitutionarray['__AMOUNT_TAX2_FORMATED__']     = is_object($object) ? ($object->total_localtax1 ? price($object->total_localtax1, 0, $outputlangs, 0, 0, -1, $conf->currency) : null) : '';
6579
		if ($onlykey != 2 || $mysoc->useLocalTax(2)) $substitutionarray['__AMOUNT_TAX3_FORMATED__']     = is_object($object) ? ($object->total_localtax2 ? price($object->total_localtax2, 0, $outputlangs, 0, 0, -1, $conf->currency) : null) : '';
6580
6581
		$substitutionarray['__AMOUNT_MULTICURRENCY__']          = (is_object($object) && isset($object->multicurrency_total_ttc)) ? $object->multicurrency_total_ttc : '';
6582
		$substitutionarray['__AMOUNT_MULTICURRENCY_TEXT__']     = (is_object($object) && isset($object->multicurrency_total_ttc)) ? dol_convertToWord($object->multicurrency_total_ttc, $outputlangs, '', true) : '';
6583
		$substitutionarray['__AMOUNT_MULTICURRENCY_TEXTCURRENCY__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? dol_convertToWord($object->multicurrency_total_ttc, $outputlangs, $object->multicurrency_code, true) : '';
6584
		// TODO Add other keys for foreign multicurrency
6585
6586
		// For backward compatibility
6587
		if ($onlykey != 2)
6588
		{
6589
			$substitutionarray['__TOTAL_TTC__']    = is_object($object) ? $object->total_ttc : '';
6590
			$substitutionarray['__TOTAL_HT__']     = is_object($object) ? $object->total_ht : '';
6591
			$substitutionarray['__TOTAL_VAT__']    = is_object($object) ? (isset($object->total_vat) ? $object->total_vat : $object->total_tva) : '';
6592
		}
6593
	}
6594
6595
	//var_dump($substitutionarray['__AMOUNT_FORMATED__']);
6596
	if (empty($exclude) || !in_array('date', $exclude))
6597
	{
6598
		include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
6599
6600
		$tmp = dol_getdate(dol_now(), true);
6601
		$tmp2 = dol_get_prev_day($tmp['mday'], $tmp['mon'], $tmp['year']);
6602
		$tmp3 = dol_get_prev_month($tmp['mon'], $tmp['year']);
6603
		$tmp4 = dol_get_next_day($tmp['mday'], $tmp['mon'], $tmp['year']);
6604
		$tmp5 = dol_get_next_month($tmp['mon'], $tmp['year']);
6605
6606
		$daytext = $outputlangs->trans('Day'.$tmp['wday']);
6607
6608
		$substitutionarray = array_merge($substitutionarray, array(
6609
			'__DAY__' => (string) $tmp['mday'],
6610
			'__DAY_TEXT__' => $daytext, // Monday
6611
			'__DAY_TEXT_SHORT__' => dol_trunc($daytext, 3, 'right', 'UTF-8', 1), // Mon
6612
			'__DAY_TEXT_MIN__' => dol_trunc($daytext, 1, 'right', 'UTF-8', 1), // M
6613
			'__MONTH__' => (string) $tmp['mon'],
6614
			'__MONTH_TEXT__' => $outputlangs->trans('Month'.sprintf("%02d", $tmp['mon'])),
6615
			'__MONTH_TEXT_SHORT__' => $outputlangs->trans('MonthShort'.sprintf("%02d", $tmp['mon'])),
6616
			'__MONTH_TEXT_MIN__' => $outputlangs->trans('MonthVeryShort'.sprintf("%02d", $tmp['mon'])),
6617
			'__YEAR__' => (string) $tmp['year'],
6618
			'__PREVIOUS_DAY__' => (string) $tmp2['day'],
6619
			'__PREVIOUS_MONTH__' => (string) $tmp3['month'],
6620
			'__PREVIOUS_YEAR__' => (string) ($tmp['year'] - 1),
6621
			'__NEXT_DAY__' => (string) $tmp4['day'],
6622
			'__NEXT_MONTH__' => (string) $tmp5['month'],
6623
			'__NEXT_YEAR__' => (string) ($tmp['year'] + 1),
6624
		));
6625
	}
6626
6627
	if (!empty($conf->multicompany->enabled))
6628
	{
6629
		$substitutionarray = array_merge($substitutionarray, array('__ENTITY_ID__' => $conf->entity));
6630
	}
6631
	if (empty($exclude) || !in_array('system', $exclude))
6632
	{
6633
		$substitutionarray['__DOL_MAIN_URL_ROOT__'] = DOL_MAIN_URL_ROOT;
6634
		$substitutionarray['__(AnyTranslationKey)__'] = $outputlangs->trans('TranslationOfKey');
6635
		$substitutionarray['__(AnyTranslationKey|langfile)__'] = $outputlangs->trans('TranslationOfKey').' (load also language file before)';
6636
		$substitutionarray['__[AnyConstantKey]__'] = $outputlangs->trans('ValueOfConstantKey');
6637
	}
6638
6639
	return $substitutionarray;
6640
}
6641
6642
/**
6643
 *  Make substitution into a text string, replacing keys with vals from $substitutionarray (oldval=>newval),
6644
 *  and texts like __(TranslationKey|langfile)__ and __[ConstantKey]__ are also replaced.
6645
 *  Example of usage:
6646
 *  $substitutionarray = getCommonSubstitutionArray($langs, 0, null, $thirdparty);
6647
 *  complete_substitutions_array($substitutionarray, $langs, $thirdparty);
6648
 *  $mesg = make_substitutions($mesg, $substitutionarray, $langs);
6649
 *
6650
 *  @param	string		$text	      			Source string in which we must do substitution
6651
 *  @param  array		$substitutionarray		Array with key->val to substitute. Example: array('__MYKEY__' => 'MyVal', ...)
6652
 *  @param	Translate	$outputlangs			Output language
6653
 * 	@return string  		    				Output string after substitutions
6654
 *  @see	complete_substitutions_array(), getCommonSubstitutionArray()
6655
 */
6656
function make_substitutions($text, $substitutionarray, $outputlangs = null)
6657
{
6658
	global $conf, $langs;
6659
6660
	if (!is_array($substitutionarray)) return 'ErrorBadParameterSubstitutionArrayWhenCalling_make_substitutions';
6661
6662
	if (empty($outputlangs)) $outputlangs = $langs;
6663
6664
	// Make substitution for language keys: __(AnyTranslationKey)__ or __(AnyTranslationKey|langfile)__
6665
	if (is_object($outputlangs))
6666
	{
6667
		$reg = array();
6668
		while (preg_match('/__\(([^\)]+)\)__/', $text, $reg))
6669
		{
6670
			$msgishtml = 0;
6671
			if (dol_textishtml($text, 1)) $msgishtml = 1;
6672
6673
			// If key is __(TranslationKey|langfile)__, then force load of langfile.lang
6674
			$tmp = explode('|', $reg[1]);
6675
			if (!empty($tmp[1])) $outputlangs->load($tmp[1]);
6676
6677
			$text = preg_replace('/__\('.preg_quote($reg[1], '/').'\)__/', $msgishtml ?dol_htmlentitiesbr($outputlangs->transnoentitiesnoconv($reg[1])) : $outputlangs->transnoentitiesnoconv($reg[1]), $text);
6678
		}
6679
	}
6680
6681
	// Make substitution for constant keys.
6682
	// Must be after the substitution of translation, so if the text of translation contains a string __[xxx]__, it is also converted.
6683
	$reg = array();
6684
	while (preg_match('/__\[([^\]]+)\]__/', $text, $reg))
6685
	{
6686
		$msgishtml = 0;
6687
		if (dol_textishtml($text, 1)) $msgishtml = 1;
6688
6689
		$keyfound = $reg[1];
6690
		if (isASecretKey($keyfound)) $newval = '*****forbidden*****';
6691
		else $newval = empty($conf->global->$keyfound) ? '' : $conf->global->$keyfound;
6692
		$text = preg_replace('/__\['.preg_quote($keyfound, '/').'\]__/', $msgishtml ?dol_htmlentitiesbr($newval) : $newval, $text);
6693
	}
6694
6695
	// Make substitition for array $substitutionarray
6696
	foreach ($substitutionarray as $key => $value)
6697
	{
6698
		if (!isset($value)) continue; // If value is null, it same than not having substitution key at all into array, we do not replace.
6699
6700
		if ($key == '__USER_SIGNATURE__' && (!empty($conf->global->MAIN_MAIL_DO_NOT_USE_SIGN))) $value = ''; // Protection
6701
6702
		$text = str_replace("$key", "$value", $text); // We must keep the " to work when value is 123.5 for example
6703
	}
6704
6705
	return $text;
6706
}
6707
6708
/**
6709
 *  Complete the $substitutionarray with more entries coming from external module that had set the "substitutions=1" into module_part array.
6710
 *  In this case, method completesubstitutionarray provided by module is called.
6711
 *
6712
 *  @param  array		$substitutionarray		Array substitution old value => new value value
6713
 *  @param  Translate	$outputlangs            Output language
6714
 *  @param  Object		$object                 Source object
6715
 *  @param  mixed		$parameters       		Add more parameters (useful to pass product lines)
6716
 *  @param  string      $callfunc               What is the name of the custom function that will be called? (default: completesubstitutionarray)
6717
 *  @return	void
6718
 *  @see 	make_substitutions()
6719
 */
6720
function complete_substitutions_array(&$substitutionarray, $outputlangs, $object = null, $parameters = null, $callfunc = "completesubstitutionarray")
6721
{
6722
	global $conf, $user;
6723
6724
	require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6725
6726
	// Add a substitution key for each extrafields, using key __EXTRA_XXX__
6727
	// TODO Remove this. Already available into the getCommonSubstitutionArray used to build the substitution array.
6728
	/*if (is_object($object) && is_array($object->array_options))
6729
	{
6730
		foreach($object->array_options as $key => $val)
6731
		{
6732
			$keyshort=preg_replace('/^(options|extra)_/','',$key);
6733
			$substitutionarray['__EXTRAFIELD_'.$keyshort.'__']=$val;
6734
			// For backward compatibiliy
6735
			$substitutionarray['%EXTRA_'.$keyshort.'%']=$val;
6736
		}
6737
	}*/
6738
6739
	// Check if there is external substitution to do, requested by plugins
6740
	$dirsubstitutions = array_merge(array(), (array) $conf->modules_parts['substitutions']);
6741
6742
	foreach ($dirsubstitutions as $reldir)
6743
	{
6744
		$dir = dol_buildpath($reldir, 0);
6745
6746
		// Check if directory exists
6747
		if (!dol_is_dir($dir)) continue;
6748
6749
		$substitfiles = dol_dir_list($dir, 'files', 0, 'functions_');
6750
		foreach ($substitfiles as $substitfile)
6751
		{
6752
			$reg = array();
6753
			if (preg_match('/functions_(.*)\.lib\.php/i', $substitfile['name'], $reg))
6754
			{
6755
				$module = $reg[1];
6756
6757
				dol_syslog("Library ".$substitfile['name']." found into ".$dir);
6758
				// Include the user's functions file
6759
				require_once $dir.$substitfile['name'];
6760
				// Call the user's function, and only if it is defined
6761
				$function_name = $module."_".$callfunc;
6762
				if (function_exists($function_name)) {
6763
					$function_name($substitutionarray, $outputlangs, $object, $parameters);
6764
				}
6765
			}
6766
		}
6767
	}
6768
	if (!empty($conf->global->ODT_ENABLE_ALL_TAGS_IN_SUBSTITUTIONS)) {
6769
		// to list all tags in odt template
6770
		$tags = '';
6771
		foreach ($substitutionarray as $key => $value) {
6772
			$tags .= '{'.$key.'} => '.$value."\n";
6773
		}
6774
		$substitutionarray = array_merge($substitutionarray, array('__ALL_TAGS__' => $tags));
6775
	}
6776
}
6777
6778
/**
6779
 *    Format output for start and end date
6780
 *
6781
 *    @param	int	$date_start    Start date
6782
 *    @param    int	$date_end      End date
6783
 *    @param    string		$format        Output format
6784
 *    @param	Translate	$outputlangs   Output language
6785
 *    @return	void
6786
 */
6787
function print_date_range($date_start, $date_end, $format = '', $outputlangs = '')
6788
{
6789
	print get_date_range($date_start, $date_end, $format, $outputlangs);
6790
}
6791
6792
/**
6793
 *    Format output for start and end date
6794
 *
6795
 *    @param	int			$date_start    		Start date
6796
 *    @param    int			$date_end      		End date
6797
 *    @param    string		$format        		Output format
6798
 *    @param	Translate	$outputlangs   		Output language
6799
 *    @param	integer		$withparenthesis	1=Add parenthesis, 0=non parenthesis
6800
 *    @return	string							String
6801
 */
6802
function get_date_range($date_start, $date_end, $format = '', $outputlangs = '', $withparenthesis = 1)
6803
{
6804
	global $langs;
6805
6806
	$out = '';
6807
6808
	if (!is_object($outputlangs)) $outputlangs = $langs;
6809
6810
	if ($date_start && $date_end)
6811
	{
6812
		$out .= ($withparenthesis ? ' (' : '').$outputlangs->transnoentitiesnoconv('DateFromTo', dol_print_date($date_start, $format, false, $outputlangs), dol_print_date($date_end, $format, false, $outputlangs)).($withparenthesis ? ')' : '');
6813
	}
6814
	if ($date_start && !$date_end)
6815
	{
6816
		$out .= ($withparenthesis ? ' (' : '').$outputlangs->transnoentitiesnoconv('DateFrom', dol_print_date($date_start, $format, false, $outputlangs)).($withparenthesis ? ')' : '');
6817
	}
6818
	if (!$date_start && $date_end)
6819
	{
6820
		$out .= ($withparenthesis ? ' (' : '').$outputlangs->transnoentitiesnoconv('DateUntil', dol_print_date($date_end, $format, false, $outputlangs)).($withparenthesis ? ')' : '');
6821
	}
6822
6823
	return $out;
6824
}
6825
6826
/**
6827
 * Return firstname and lastname in correct order
6828
 *
6829
 * @param	string	$firstname		Firstname
6830
 * @param	string	$lastname		Lastname
6831
 * @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
6832
 * @return	string					Firstname + lastname or Lastname + firstname
6833
 */
6834
function dolGetFirstLastname($firstname, $lastname, $nameorder = -1)
6835
{
6836
	global $conf;
6837
6838
	$ret = '';
6839
	// If order not defined, we use the setup
6840
	if ($nameorder < 0) $nameorder = (empty($conf->global->MAIN_FIRSTNAME_NAME_POSITION) ? 1 : 0);
6841
	if ($nameorder == 1) {
6842
		$ret .= $firstname;
6843
		if ($firstname && $lastname) $ret .= ' ';
6844
		$ret .= $lastname;
6845
	} elseif ($nameorder == 2 || $nameorder == 3) {
6846
		$ret .= $firstname;
6847
		if (empty($ret) && $nameorder == 3) {
6848
			$ret .= $lastname;
6849
		}
6850
	} else {	// 0, 4 or 5
6851
		$ret .= $lastname;
6852
		if (empty($ret) && $nameorder == 5) {
6853
			$ret .= $firstname;
6854
		}
6855
		if ($nameorder == 0) {
6856
			if ($firstname && $lastname) $ret .= ' ';
6857
			$ret .= $firstname;
6858
		}
6859
	}
6860
	return $ret;
6861
}
6862
6863
6864
/**
6865
 *	Set event message in dol_events session object. Will be output by calling dol_htmloutput_events.
6866
 *  Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function.
6867
 *  Note: Prefer to use setEventMessages instead.
6868
 *
6869
 *	@param	string|string[] $mesgs			Message string or array
6870
 *  @param  string          $style      	Which style to use ('mesgs' by default, 'warnings', 'errors')
6871
 *  @return	void
6872
 *  @see	dol_htmloutput_events()
6873
 */
6874
function setEventMessage($mesgs, $style = 'mesgs')
6875
{
6876
	//dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);		This is not deprecated, it is used by setEventMessages function
6877
	if (!is_array($mesgs)) {
6878
		// If mesgs is a string
6879
		if ($mesgs) $_SESSION['dol_events'][$style][] = $mesgs;
6880
	} else {
6881
		// If mesgs is an array
6882
		foreach ($mesgs as $mesg)
6883
		{
6884
			if ($mesg) $_SESSION['dol_events'][$style][] = $mesg;
6885
		}
6886
	}
6887
}
6888
6889
/**
6890
 *	Set event messages in dol_events session object. Will be output by calling dol_htmloutput_events.
6891
 *  Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function.
6892
 *
6893
 *	@param	string	$mesg			Message string
6894
 *	@param	array	$mesgs			Message array
6895
 *  @param  string	$style      	Which style to use ('mesgs' by default, 'warnings', 'errors')
6896
 *  @param	string	$messagekey		A key to be used to allow the feature "Never show this message again"
6897
 *  @return	void
6898
 *  @see	dol_htmloutput_events()
6899
 */
6900
function setEventMessages($mesg, $mesgs, $style = 'mesgs', $messagekey = '')
6901
{
6902
	if (empty($mesg) && empty($mesgs))
6903
	{
6904
		dol_syslog("Try to add a message in stack with empty message", LOG_WARNING);
6905
	} else {
6906
		if ($messagekey)
6907
		{
6908
			// Complete message with a js link to set a cookie "DOLHIDEMESSAGE".$messagekey;
6909
			// TODO
6910
			$mesg .= '';
6911
		}
6912
		if (empty($messagekey) || empty($_COOKIE["DOLHIDEMESSAGE".$messagekey]))
6913
		{
6914
			if (!in_array((string) $style, array('mesgs', 'warnings', 'errors'))) dol_print_error('', 'Bad parameter style='.$style.' for setEventMessages');
6915
			if (empty($mesgs)) setEventMessage($mesg, $style);
6916
			else {
6917
				if (!empty($mesg) && !in_array($mesg, $mesgs)) setEventMessage($mesg, $style); // Add message string if not already into array
6918
				setEventMessage($mesgs, $style);
6919
			}
6920
		}
6921
	}
6922
}
6923
6924
/**
6925
 *	Print formated messages to output (Used to show messages on html output).
6926
 *  Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function, so there is
6927
 *  no need to call it explicitely.
6928
 *
6929
 *  @param	int		$disabledoutputofmessages	Clear all messages stored into session without diplaying them
6930
 *  @return	void
6931
 *  @see    									dol_htmloutput_mesg()
6932
 */
6933
function dol_htmloutput_events($disabledoutputofmessages = 0)
6934
{
6935
	// Show mesgs
6936
	if (isset($_SESSION['dol_events']['mesgs'])) {
6937
		if (empty($disabledoutputofmessages)) dol_htmloutput_mesg('', $_SESSION['dol_events']['mesgs']);
6938
		unset($_SESSION['dol_events']['mesgs']);
6939
	}
6940
6941
	// Show errors
6942
	if (isset($_SESSION['dol_events']['errors'])) {
6943
		if (empty($disabledoutputofmessages)) dol_htmloutput_mesg('', $_SESSION['dol_events']['errors'], 'error');
6944
		unset($_SESSION['dol_events']['errors']);
6945
	}
6946
6947
	// Show warnings
6948
	if (isset($_SESSION['dol_events']['warnings'])) {
6949
		if (empty($disabledoutputofmessages)) dol_htmloutput_mesg('', $_SESSION['dol_events']['warnings'], 'warning');
6950
		unset($_SESSION['dol_events']['warnings']);
6951
	}
6952
}
6953
6954
/**
6955
 *	Get formated messages to output (Used to show messages on html output).
6956
 *  This include also the translation of the message key.
6957
 *
6958
 *	@param	string		$mesgstring		Message string or message key
6959
 *	@param	string[]	$mesgarray      Array of message strings or message keys
6960
 *  @param  string		$style          Style of message output ('ok' or 'error')
6961
 *  @param  int			$keepembedded   Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
6962
 *	@return	string						Return html output
6963
 *
6964
 *  @see    dol_print_error()
6965
 *  @see    dol_htmloutput_errors()
6966
 *  @see    setEventMessages()
6967
 */
6968
function get_htmloutput_mesg($mesgstring = '', $mesgarray = '', $style = 'ok', $keepembedded = 0)
6969
{
6970
	global $conf, $langs;
6971
6972
	$ret = 0; $return = '';
6973
	$out = '';
6974
	$divstart = $divend = '';
6975
6976
	// If inline message with no format, we add it.
6977
	if ((empty($conf->use_javascript_ajax) || !empty($conf->global->MAIN_DISABLE_JQUERY_JNOTIFY) || $keepembedded) && !preg_match('/<div class=".*">/i', $out))
6978
	{
6979
		$divstart = '<div class="'.$style.' clearboth">';
6980
		$divend = '</div>';
6981
	}
6982
6983
	if ((is_array($mesgarray) && count($mesgarray)) || $mesgstring)
6984
	{
6985
		$langs->load("errors");
6986
		$out .= $divstart;
6987
		if (is_array($mesgarray) && count($mesgarray))
6988
		{
6989
			foreach ($mesgarray as $message)
6990
			{
6991
				$ret++;
6992
				$out .= $langs->trans($message);
6993
				if ($ret < count($mesgarray)) $out .= "<br>\n";
6994
			}
6995
		}
6996
		if ($mesgstring)
6997
		{
6998
			$langs->load("errors");
6999
			$ret++;
7000
			$out .= $langs->trans($mesgstring);
7001
		}
7002
		$out .= $divend;
7003
	}
7004
7005
	if ($out)
7006
	{
7007
		if (!empty($conf->use_javascript_ajax) && empty($conf->global->MAIN_DISABLE_JQUERY_JNOTIFY) && empty($keepembedded))
7008
		{
7009
			$return = '<script>
7010
					$(document).ready(function() {
7011
						var block = '.(!empty($conf->global->MAIN_USE_JQUERY_BLOCKUI) ? "true" : "false").'
7012
						if (block) {
7013
							$.dolEventValid("","'.dol_escape_js($out).'");
7014
						} else {
7015
							/* jnotify(message, preset of message type, keepmessage) */
7016
							$.jnotify("'.dol_escape_js($out).'",
7017
							"'.($style == "ok" ? 3000 : $style).'",
7018
							'.($style == "ok" ? "false" : "true").',
7019
							{ remove: function (){} } );
7020
						}
7021
					});
7022
				</script>';
7023
		} else {
7024
			$return = $out;
7025
		}
7026
	}
7027
7028
	return $return;
7029
}
7030
7031
/**
7032
 *  Get formated error messages to output (Used to show messages on html output).
7033
 *
7034
 *  @param  string	$mesgstring         Error message
7035
 *  @param  array	$mesgarray          Error messages array
7036
 *  @param  int		$keepembedded       Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
7037
 *  @return string                		Return html output
7038
 *
7039
 *  @see    dol_print_error()
7040
 *  @see    dol_htmloutput_mesg()
7041
 */
7042
function get_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0)
7043
{
7044
	return get_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
7045
}
7046
7047
/**
7048
 *	Print formated messages to output (Used to show messages on html output).
7049
 *
7050
 *	@param	string		$mesgstring		Message string or message key
7051
 *	@param	string[]	$mesgarray      Array of message strings or message keys
7052
 *	@param  string      $style          Which style to use ('ok', 'warning', 'error')
7053
 *	@param  int         $keepembedded   Set to 1 if message must be kept embedded into its html place (this disable jnotify)
7054
 *	@return	void
7055
 *
7056
 *	@see    dol_print_error()
7057
 *	@see    dol_htmloutput_errors()
7058
 *	@see    setEventMessages()
7059
 */
7060
function dol_htmloutput_mesg($mesgstring = '', $mesgarray = array(), $style = 'ok', $keepembedded = 0)
7061
{
7062
	if (empty($mesgstring) && (!is_array($mesgarray) || count($mesgarray) == 0)) return;
7063
7064
	$iserror = 0;
7065
	$iswarning = 0;
7066
	if (is_array($mesgarray))
7067
	{
7068
		foreach ($mesgarray as $val)
7069
		{
7070
			if ($val && preg_match('/class="error"/i', $val)) { $iserror++; break; }
7071
			if ($val && preg_match('/class="warning"/i', $val)) { $iswarning++; break; }
7072
		}
7073
	} elseif ($mesgstring && preg_match('/class="error"/i', $mesgstring)) $iserror++;
7074
	elseif ($mesgstring && preg_match('/class="warning"/i', $mesgstring)) $iswarning++;
7075
	if ($style == 'error') $iserror++;
7076
	if ($style == 'warning') $iswarning++;
7077
7078
	if ($iserror || $iswarning)
7079
	{
7080
		// Remove div from texts
7081
		$mesgstring = preg_replace('/<\/div><div class="(error|warning)">/', '<br>', $mesgstring);
7082
		$mesgstring = preg_replace('/<div class="(error|warning)">/', '', $mesgstring);
7083
		$mesgstring = preg_replace('/<\/div>/', '', $mesgstring);
7084
		// Remove div from texts array
7085
		if (is_array($mesgarray))
7086
		{
7087
			$newmesgarray = array();
7088
			foreach ($mesgarray as $val)
7089
			{
7090
				if (is_string($val))
7091
				{
7092
					$tmpmesgstring = preg_replace('/<\/div><div class="(error|warning)">/', '<br>', $val);
7093
					$tmpmesgstring = preg_replace('/<div class="(error|warning)">/', '', $tmpmesgstring);
7094
					$tmpmesgstring = preg_replace('/<\/div>/', '', $tmpmesgstring);
7095
					$newmesgarray[] = $tmpmesgstring;
7096
				} else {
7097
					dol_syslog("Error call of dol_htmloutput_mesg with an array with a value that is not a string", LOG_WARNING);
7098
				}
7099
			}
7100
			$mesgarray = $newmesgarray;
7101
		}
7102
		print get_htmloutput_mesg($mesgstring, $mesgarray, ($iserror ? 'error' : 'warning'), $keepembedded);
7103
	} else print get_htmloutput_mesg($mesgstring, $mesgarray, 'ok', $keepembedded);
7104
}
7105
7106
/**
7107
 *  Print formated error messages to output (Used to show messages on html output).
7108
 *
7109
 *  @param	string	$mesgstring          Error message
7110
 *  @param  array	$mesgarray           Error messages array
7111
 *  @param  int		$keepembedded        Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
7112
 *  @return	void
7113
 *
7114
 *  @see    dol_print_error()
7115
 *  @see    dol_htmloutput_mesg()
7116
 */
7117
function dol_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0)
7118
{
7119
	dol_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
7120
}
7121
7122
/**
7123
 * 	Advanced sort array by second index function, which produces ascending (default)
7124
 *  or descending output and uses optionally natural case insensitive sorting (which
7125
 *  can be optionally case sensitive as well).
7126
 *
7127
 *  @param      array		$array      		Array to sort (array of array('key1'=>val1,'key2'=>val2,'key3'...) or array of objects)
7128
 *  @param      string		$index				Key in array to use for sorting criteria
7129
 *  @param      int			$order				Sort order ('asc' or 'desc')
7130
 *  @param      int			$natsort			1=use "natural" sort (natsort), 0=use "standard" sort (asort)
7131
 *  @param      int			$case_sensitive		1=sort is case sensitive, 0=not case sensitive
7132
 *  @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.
7133
 *  @return     array							Sorted array
7134
 */
7135
function dol_sort_array(&$array, $index, $order = 'asc', $natsort = 0, $case_sensitive = 0, $keepindex = 0)
7136
{
7137
	// Clean parameters
7138
	$order = strtolower($order);
7139
7140
	if (is_array($array))
7141
	{
7142
		$sizearray = count($array);
7143
		if ($sizearray > 0)
7144
		{
7145
			$temp = array();
7146
			foreach (array_keys($array) as $key)
7147
			{
7148
				if (is_object($array[$key])) {
7149
					$temp[$key] = empty($array[$key]->$index) ? 0 : $array[$key]->$index;
7150
				} else {
7151
					$temp[$key] = empty($array[$key][$index]) ? 0 : $array[$key][$index];
7152
				}
7153
			}
7154
7155
			if (!$natsort) {
7156
				($order == 'asc') ? asort($temp) : arsort($temp);
7157
			} else {
7158
				($case_sensitive) ? natsort($temp) : natcasesort($temp);
7159
				if ($order != 'asc') $temp = array_reverse($temp, true);
7160
			}
7161
7162
			$sorted = array();
7163
7164
			foreach (array_keys($temp) as $key)
7165
			{
7166
				(is_numeric($key) && empty($keepindex)) ? $sorted[] = $array[$key] : $sorted[$key] = $array[$key];
7167
			}
7168
7169
			return $sorted;
7170
		}
7171
	}
7172
	return $array;
7173
}
7174
7175
7176
/**
7177
 *      Check if a string is in UTF8
7178
 *
7179
 *      @param	string	$str        String to check
7180
 * 		@return	boolean				True if string is UTF8 or ISO compatible with UTF8, False if not (ISO with special char or Binary)
7181
 */
7182
function utf8_check($str)
7183
{
7184
	$str = (string) $str;	// Sometimes string is an int.
7185
7186
	// We must use here a binary strlen function (so not dol_strlen)
7187
	$strLength = dol_strlen($str);
7188
	for ($i = 0; $i < $strLength; $i++) {
7189
		if (ord($str[$i]) < 0x80) continue; // 0bbbbbbb
7190
		elseif ((ord($str[$i]) & 0xE0) == 0xC0) $n = 1; // 110bbbbb
7191
		elseif ((ord($str[$i]) & 0xF0) == 0xE0) $n = 2; // 1110bbbb
7192
		elseif ((ord($str[$i]) & 0xF8) == 0xF0) $n = 3; // 11110bbb
7193
		elseif ((ord($str[$i]) & 0xFC) == 0xF8) $n = 4; // 111110bb
7194
		elseif ((ord($str[$i]) & 0xFE) == 0xFC) $n = 5; // 1111110b
7195
		else return false; // Does not match any model
7196
		for ($j = 0; $j < $n; $j++) { // n bytes matching 10bbbbbb follow ?
7197
			if ((++$i == strlen($str)) || ((ord($str[$i]) & 0xC0) != 0x80))
7198
			return false;
7199
		}
7200
	}
7201
	return true;
7202
}
7203
7204
/**
7205
 *      Check if a string is in ASCII
7206
 *
7207
 *      @param	string	$str        String to check
7208
 * 		@return	boolean				True if string is ASCII, False if not (byte value > 0x7F)
7209
 */
7210
function ascii_check($str)
7211
{
7212
	if (function_exists('mb_check_encoding')) {
7213
		//if (mb_detect_encoding($str, 'ASCII', true) return false;
7214
		if (!mb_check_encoding($str, 'ASCII')) return false;
7215
	} else {
7216
		if (preg_match('/[^\x00-\x7f]/', $str)) return false; // Contains a byte > 7f
7217
	}
7218
7219
	return true;
7220
}
7221
7222
7223
/**
7224
 *      Return a string encoded into OS filesystem encoding. This function is used to define
7225
 * 	    value to pass to filesystem PHP functions.
7226
 *
7227
 *      @param	string	$str        String to encode (UTF-8)
7228
 * 		@return	string				Encoded string (UTF-8, ISO-8859-1)
7229
 */
7230
function dol_osencode($str)
7231
{
7232
	global $conf;
7233
7234
	$tmp = ini_get("unicode.filesystem_encoding"); // Disponible avec PHP 6.0
7235
	if (empty($tmp) && !empty($_SERVER["WINDIR"])) $tmp = 'iso-8859-1'; // By default for windows
7236
	if (empty($tmp)) $tmp = 'utf-8'; // By default for other
7237
	if (!empty($conf->global->MAIN_FILESYSTEM_ENCODING)) $tmp = $conf->global->MAIN_FILESYSTEM_ENCODING;
7238
7239
	if ($tmp == 'iso-8859-1') return utf8_decode($str);
7240
	return $str;
7241
}
7242
7243
7244
/**
7245
 *      Return an id or code from a code or id.
7246
 *      Store also Code-Id into a cache to speed up next request on same key.
7247
 *
7248
 * 		@param	DoliDB	$db				Database handler
7249
 * 		@param	string	$key			Code or Id to get Id or Code
7250
 * 		@param	string	$tablename		Table name without prefix
7251
 * 		@param	string	$fieldkey		Field to search the key into
7252
 * 		@param	string	$fieldid		Field to get
7253
 *      @param  int		$entityfilter	Filter by entity
7254
 *      @return int						<0 if KO, Id of code if OK
7255
 *      @see $langs->getLabelFromKey
7256
 */
7257
function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid = 'id', $entityfilter = 0)
7258
{
7259
	global $cache_codes;
7260
7261
	// If key empty
7262
	if ($key == '') return '';
7263
7264
	// Check in cache
7265
	if (isset($cache_codes[$tablename][$key][$fieldid]))	// Can be defined to 0 or ''
7266
	{
7267
		return $cache_codes[$tablename][$key][$fieldid]; // Found in cache
7268
	}
7269
7270
	dol_syslog('dol_getIdFromCode (value for field '.$fieldid.' from key '.$key.' not found into cache)', LOG_DEBUG);
7271
7272
	$sql = "SELECT ".$fieldid." as valuetoget";
7273
	$sql .= " FROM ".MAIN_DB_PREFIX.$tablename;
7274
	$sql .= " WHERE ".$fieldkey." = '".$db->escape($key)."'";
7275
	if (!empty($entityfilter))
7276
		$sql .= " AND entity IN (".getEntity($tablename).")";
7277
7278
	$resql = $db->query($sql);
7279
	if ($resql)
7280
	{
7281
		$obj = $db->fetch_object($resql);
7282
		if ($obj) $cache_codes[$tablename][$key][$fieldid] = $obj->valuetoget;
7283
		else $cache_codes[$tablename][$key][$fieldid] = '';
7284
		$db->free($resql);
7285
		return $cache_codes[$tablename][$key][$fieldid];
7286
	} else {
7287
		return -1;
7288
	}
7289
}
7290
7291
/**
7292
 * Verify if condition in string is ok or not
7293
 *
7294
 * @param 	string		$strRights		String with condition to check
7295
 * @return 	boolean						True or False. Return True if strRights is ''
7296
 */
7297
function verifCond($strRights)
7298
{
7299
	global $user, $conf, $langs;
7300
	global $leftmenu;
7301
	global $rights; // To export to dol_eval function
7302
7303
	//print $strRights."<br>\n";
7304
	$rights = true;
7305
	if ($strRights != '')
7306
	{
7307
		$str = 'if(!('.$strRights.')) { $rights = false; }';
7308
		dol_eval($str); // The dol_eval must contains all the global $xxx used into a condition
7309
	}
7310
	return $rights;
7311
}
7312
7313
/**
7314
 * Replace eval function to add more security.
7315
 * This function is called by verifCond() or trans() and transnoentitiesnoconv().
7316
 *
7317
 * @param 	string	$s				String to evaluate
7318
 * @param	int		$returnvalue	0=No return (used to execute eval($a=something)). 1=Value of eval is returned (used to eval($something)).
7319
 * @param   int     $hideerrors     1=Hide errors
7320
 * @return	mixed					Nothing or return result of eval
7321
 */
7322
function dol_eval($s, $returnvalue = 0, $hideerrors = 1)
7323
{
7324
	// Only global variables can be changed by eval function and returned to caller
7325
	global $db, $langs, $user, $conf, $website, $websitepage;
7326
	global $action, $mainmenu, $leftmenu;
7327
	global $rights;
7328
	global $object;
7329
	global $mysoc;
7330
7331
	global $obj; // To get $obj used into list when dol_eval is used for computed fields and $obj is not yet $object
7332
	global $soc; // For backward compatibility
7333
7334
	//print $s."<br>\n";
7335
	if ($returnvalue) {
7336
		if ($hideerrors) return @eval('return '.$s.';');
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
7337
		else return eval('return '.$s.';');
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
7338
	} else {
7339
		if ($hideerrors) @eval($s);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
7340
		else eval($s);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
7341
	}
7342
}
7343
7344
/**
7345
 * Return if var element is ok
7346
 *
7347
 * @param   string      $element    Variable to check
7348
 * @return  boolean                 Return true of variable is not empty
7349
 */
7350
function dol_validElement($element)
7351
{
7352
	return (trim($element) != '');
7353
}
7354
7355
/**
7356
 * 	Return img flag of country for a language code or country code
7357
 *
7358
 * 	@param	string	$codelang	Language code ('en_IN', 'fr_CA', ...) or ISO Country code on 2 characters in uppercase ('IN', 'FR')
7359
 *  @param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"' or 'class="saturatemedium"')
7360
 * 	@return	string				HTML img string with flag.
7361
 */
7362
function picto_from_langcode($codelang, $moreatt = '')
7363
{
7364
	if (empty($codelang)) return '';
7365
7366
	if ($codelang == 'auto')
7367
	{
7368
		return '<span class="fa fa-globe"></span>';
7369
	}
7370
7371
	$langtocountryflag = array(
7372
		'ar_AR' => '',
7373
		'ca_ES' => 'catalonia',
7374
		'da_DA' => 'dk',
7375
		'fr_CA' => 'mq',
7376
		'sv_SV' => 'se',
7377
		'AQ' => 'unknown',
7378
		'CW' => 'unknown',
7379
		'IM' => 'unknown',
7380
		'JE' => 'unknown',
7381
		'MF' => 'unknown',
7382
		'BL' => 'unknown',
7383
		'SX' => 'unknown'
7384
	);
7385
7386
	if (isset($langtocountryflag[$codelang])) $flagImage = $langtocountryflag[$codelang];
7387
	else {
7388
		$tmparray = explode('_', $codelang);
7389
		$flagImage = empty($tmparray[1]) ? $tmparray[0] : $tmparray[1];
7390
	}
7391
7392
	return img_picto_common($codelang, 'flags/'.strtolower($flagImage).'.png', $moreatt);
7393
}
7394
7395
/**
7396
 * Return default language from country code.
7397
 * Return null if not found.
7398
 *
7399
 * @param 	string 	$countrycode	Country code like 'US', 'FR', 'CA', ...
7400
 * @return	string					Value of locale like 'en_US', 'fr_FR', ...
7401
 */
7402
function getLanguageCodeFromCountryCode($countrycode)
7403
{
7404
	global $mysoc;
7405
7406
	if (empty($countrycode)) return null;
7407
7408
	if (strtoupper($countrycode) == 'MQ') return 'fr_CA';
7409
	if (strtoupper($countrycode) == 'SE') return 'sv_SE'; // se_SE is Sami/Sweden, and we want in priority sv_SE for SE country
7410
	if (strtoupper($countrycode) == 'CH')
7411
	{
7412
		if ($mysoc->country_code == 'FR') return 'fr_CH';
7413
		if ($mysoc->country_code == 'DE') return 'de_CH';
7414
	}
7415
7416
	// Locale list taken from:
7417
	// http://stackoverflow.com/questions/3191664/
7418
	// list-of-all-locales-and-their-short-codes
7419
	$locales = array(
7420
		'af-ZA',
7421
		'am-ET',
7422
		'ar-AE',
7423
		'ar-BH',
7424
		'ar-DZ',
7425
		'ar-EG',
7426
		'ar-IQ',
7427
		'ar-JO',
7428
		'ar-KW',
7429
		'ar-LB',
7430
		'ar-LY',
7431
		'ar-MA',
7432
		'ar-OM',
7433
		'ar-QA',
7434
		'ar-SA',
7435
		'ar-SY',
7436
		'ar-TN',
7437
		'ar-YE',
7438
		'as-IN',
7439
		'ba-RU',
7440
		'be-BY',
7441
		'bg-BG',
7442
		'bn-BD',
7443
		'bn-IN',
7444
		'bo-CN',
7445
		'br-FR',
7446
		'ca-ES',
7447
		'co-FR',
7448
		'cs-CZ',
7449
		'cy-GB',
7450
		'da-DK',
7451
		'de-AT',
7452
		'de-CH',
7453
		'de-DE',
7454
		'de-LI',
7455
		'de-LU',
7456
		'dv-MV',
7457
		'el-GR',
7458
		'en-AU',
7459
		'en-BZ',
7460
		'en-CA',
7461
		'en-GB',
7462
		'en-IE',
7463
		'en-IN',
7464
		'en-JM',
7465
		'en-MY',
7466
		'en-NZ',
7467
		'en-PH',
7468
		'en-SG',
7469
		'en-TT',
7470
		'en-US',
7471
		'en-ZA',
7472
		'en-ZW',
7473
		'es-AR',
7474
		'es-BO',
7475
		'es-CL',
7476
		'es-CO',
7477
		'es-CR',
7478
		'es-DO',
7479
		'es-EC',
7480
		'es-ES',
7481
		'es-GT',
7482
		'es-HN',
7483
		'es-MX',
7484
		'es-NI',
7485
		'es-PA',
7486
		'es-PE',
7487
		'es-PR',
7488
		'es-PY',
7489
		'es-SV',
7490
		'es-US',
7491
		'es-UY',
7492
		'es-VE',
7493
		'et-EE',
7494
		'eu-ES',
7495
		'fa-IR',
7496
		'fi-FI',
7497
		'fo-FO',
7498
		'fr-BE',
7499
		'fr-CA',
7500
		'fr-CH',
7501
		'fr-FR',
7502
		'fr-LU',
7503
		'fr-MC',
7504
		'fy-NL',
7505
		'ga-IE',
7506
		'gd-GB',
7507
		'gl-ES',
7508
		'gu-IN',
7509
		'he-IL',
7510
		'hi-IN',
7511
		'hr-BA',
7512
		'hr-HR',
7513
		'hu-HU',
7514
		'hy-AM',
7515
		'id-ID',
7516
		'ig-NG',
7517
		'ii-CN',
7518
		'is-IS',
7519
		'it-CH',
7520
		'it-IT',
7521
		'ja-JP',
7522
		'ka-GE',
7523
		'kk-KZ',
7524
		'kl-GL',
7525
		'km-KH',
7526
		'kn-IN',
7527
		'ko-KR',
7528
		'ky-KG',
7529
		'lb-LU',
7530
		'lo-LA',
7531
		'lt-LT',
7532
		'lv-LV',
7533
		'mi-NZ',
7534
		'mk-MK',
7535
		'ml-IN',
7536
		'mn-MN',
7537
		'mr-IN',
7538
		'ms-BN',
7539
		'ms-MY',
7540
		'mt-MT',
7541
		'nb-NO',
7542
		'ne-NP',
7543
		'nl-BE',
7544
		'nl-NL',
7545
		'nn-NO',
7546
		'oc-FR',
7547
		'or-IN',
7548
		'pa-IN',
7549
		'pl-PL',
7550
		'ps-AF',
7551
		'pt-BR',
7552
		'pt-PT',
7553
		'rm-CH',
7554
		'ro-RO',
7555
		'ru-RU',
7556
		'rw-RW',
7557
		'sa-IN',
7558
		'se-FI',
7559
		'se-NO',
7560
		'se-SE',
7561
		'si-LK',
7562
		'sk-SK',
7563
		'sl-SI',
7564
		'sq-AL',
7565
		'sv-FI',
7566
		'sv-SE',
7567
		'sw-KE',
7568
		'ta-IN',
7569
		'te-IN',
7570
		'th-TH',
7571
		'tk-TM',
7572
		'tn-ZA',
7573
		'tr-TR',
7574
		'tt-RU',
7575
		'ug-CN',
7576
		'uk-UA',
7577
		'ur-PK',
7578
		'vi-VN',
7579
		'wo-SN',
7580
		'xh-ZA',
7581
		'yo-NG',
7582
		'zh-CN',
7583
		'zh-HK',
7584
		'zh-MO',
7585
		'zh-SG',
7586
		'zh-TW',
7587
		'zu-ZA',
7588
	);
7589
7590
	$buildprimarykeytotest = strtolower($countrycode).'-'.strtoupper($countrycode);
7591
	if (in_array($buildprimarykeytotest, $locales)) return strtolower($countrycode).'_'.strtoupper($countrycode);
7592
7593
	if (function_exists('locale_get_primary_language') && function_exists('locale_get_region'))    // Need extension php-intl
7594
	{
7595
		foreach ($locales as $locale)
7596
		{
7597
			$locale_language = locale_get_primary_language($locale);
7598
			$locale_region = locale_get_region($locale);
7599
			if (strtoupper($countrycode) == $locale_region)
7600
			{
7601
				//var_dump($locale.'-'.$locale_language.'-'.$locale_region);
7602
				return strtolower($locale_language).'_'.strtoupper($locale_region);
7603
			}
7604
		}
7605
	} else {
7606
		dol_syslog("Warning Exention php-intl is not available", LOG_WARNING);
7607
	}
7608
7609
	return null;
7610
}
7611
7612
/**
7613
 *  Complete or removed entries into a head array (used to build tabs).
7614
 *  For example, with value added by external modules. Such values are declared into $conf->modules_parts['tab'].
7615
 *  Or by change using hook completeTabsHead
7616
 *
7617
 *  @param	Conf			$conf           Object conf
7618
 *  @param  Translate		$langs          Object langs
7619
 *  @param  object|null		$object         Object object
7620
 *  @param  array			$head          	Object head
7621
 *  @param  int				$h				New position to fill
7622
 *  @param  string			$type           Value for object where objectvalue can be
7623
 *                              			'thirdparty'       to add a tab in third party view
7624
 *		                        	      	'intervention'     to add a tab in intervention view
7625
 *     		                    	     	'supplier_order'   to add a tab in supplier order view
7626
 *          		            	        'supplier_invoice' to add a tab in supplier invoice view
7627
 *                  		    	        'invoice'          to add a tab in customer invoice view
7628
 *                          			    'order'            to add a tab in customer order view
7629
 *                          				'contract'		   to add a tabl in contract view
7630
 *                      			        'product'          to add a tab in product view
7631
 *                              			'propal'           to add a tab in propal view
7632
 *                              			'user'             to add a tab in user view
7633
 *                              			'group'            to add a tab in group view
7634
 * 		        	               	     	'member'           to add a tab in fundation member view
7635
 *      		                        	'categories_x'	   to add a tab in category view ('x': type of category (0=product, 1=supplier, 2=customer, 3=member)
7636
 *      									'ecm'			   to add a tab for another ecm view
7637
 *                                          'stock'            to add a tab for warehouse view
7638
 *  @param  string		$mode  	        	'add' to complete head, 'remove' to remove entries
7639
 *	@return	void
7640
 */
7641
function complete_head_from_modules($conf, $langs, $object, &$head, &$h, $type, $mode = 'add')
7642
{
7643
	global $hookmanager;
7644
7645
	if (isset($conf->modules_parts['tabs'][$type]) && is_array($conf->modules_parts['tabs'][$type]))
7646
	{
7647
		foreach ($conf->modules_parts['tabs'][$type] as $value)
7648
		{
7649
			$values = explode(':', $value);
7650
7651
			if ($mode == 'add' && !preg_match('/^\-/', $values[1]))
7652
			{
7653
				if (count($values) == 6)       // new declaration with permissions:  $value='objecttype:+tabname1:Title1:langfile@mymodule:$user->rights->mymodule->read:/mymodule/mynewtab1.php?id=__ID__'
7654
				{
7655
					if ($values[0] != $type) continue;
7656
7657
					if (verifCond($values[4]))
7658
					{
7659
						if ($values[3]) $langs->load($values[3]);
7660
						if (preg_match('/SUBSTITUTION_([^_]+)/i', $values[2], $reg))
7661
						{
7662
							$substitutionarray = array();
7663
							complete_substitutions_array($substitutionarray, $langs, $object, array('needforkey'=>$values[2]));
7664
							$label = make_substitutions($reg[1], $substitutionarray);
7665
						} else $label = $langs->trans($values[2]);
7666
7667
						$head[$h][0] = dol_buildpath(preg_replace('/__ID__/i', ((is_object($object) && !empty($object->id)) ? $object->id : ''), $values[5]), 1);
7668
						$head[$h][1] = $label;
7669
						$head[$h][2] = str_replace('+', '', $values[1]);
7670
						$h++;
7671
					}
7672
				} elseif (count($values) == 5)       // deprecated
7673
				{
7674
					dol_syslog('Passing 5 values in tabs module_parts is deprecated. Please update to 6 with permissions.', LOG_WARNING);
7675
7676
					if ($values[0] != $type) continue;
7677
					if ($values[3]) $langs->load($values[3]);
7678
					if (preg_match('/SUBSTITUTION_([^_]+)/i', $values[2], $reg))
7679
					{
7680
						$substitutionarray = array();
7681
						complete_substitutions_array($substitutionarray, $langs, $object, array('needforkey'=>$values[2]));
7682
						$label = make_substitutions($reg[1], $substitutionarray);
7683
					} else $label = $langs->trans($values[2]);
7684
7685
					$head[$h][0] = dol_buildpath(preg_replace('/__ID__/i', ((is_object($object) && !empty($object->id)) ? $object->id : ''), $values[4]), 1);
7686
					$head[$h][1] = $label;
7687
					$head[$h][2] = str_replace('+', '', $values[1]);
7688
					$h++;
7689
				}
7690
			} elseif ($mode == 'remove' && preg_match('/^\-/', $values[1]))
7691
			{
7692
				if ($values[0] != $type) continue;
7693
				$tabname = str_replace('-', '', $values[1]);
7694
				foreach ($head as $key => $val)
7695
				{
7696
					$condition = (!empty($values[3]) ? verifCond($values[3]) : 1);
7697
					//var_dump($key.' - '.$tabname.' - '.$head[$key][2].' - '.$values[3].' - '.$condition);
7698
					if ($head[$key][2] == $tabname && $condition)
7699
					{
7700
						unset($head[$key]);
7701
						break;
7702
					}
7703
				}
7704
			}
7705
		}
7706
	}
7707
7708
	// No need to make a return $head. Var is modified as a reference
7709
	if (!empty($hookmanager))
7710
	{
7711
		$parameters = array('object' => $object, 'mode' => $mode, 'head' => $head);
7712
		$reshook = $hookmanager->executeHooks('completeTabsHead', $parameters);
7713
		if ($reshook > 0)
7714
		{
7715
			$head = $hookmanager->resArray;
7716
			$h = count($head);
7717
		}
7718
	}
7719
}
7720
7721
/**
7722
 * Print common footer :
7723
 * 		conf->global->MAIN_HTML_FOOTER
7724
 *      js for switch of menu hider
7725
 * 		js for conf->global->MAIN_GOOGLE_AN_ID
7726
 * 		js for conf->global->MAIN_SHOW_TUNING_INFO or $_SERVER["MAIN_SHOW_TUNING_INFO"]
7727
 * 		js for conf->logbuffer
7728
 *
7729
 * @param	string	$zone	'private' (for private pages) or 'public' (for public pages)
7730
 * @return	void
7731
 */
7732
function printCommonFooter($zone = 'private')
7733
{
7734
	global $conf, $hookmanager, $user, $debugbar;
7735
	global $action;
7736
	global $micro_start_time;
7737
7738
	if ($zone == 'private') print "\n".'<!-- Common footer for private page -->'."\n";
7739
	else print "\n".'<!-- Common footer for public page -->'."\n";
7740
7741
	// A div to store page_y POST parameter so we can read it using javascript
7742
	print "\n<!-- A div to store page_y POST parameter -->\n";
7743
	print '<div id="page_y" style="display: none;">'.(empty($_POST['page_y']) ? '' : $_POST['page_y']).'</div>'."\n";
7744
7745
	$parameters = array();
7746
	$reshook = $hookmanager->executeHooks('printCommonFooter', $parameters); // Note that $action and $object may have been modified by some hooks
7747
	if (empty($reshook))
7748
	{
7749
		if (!empty($conf->global->MAIN_HTML_FOOTER)) print $conf->global->MAIN_HTML_FOOTER."\n";
7750
7751
		print "\n";
7752
		if (!empty($conf->use_javascript_ajax))
7753
		{
7754
			print '<script>'."\n";
7755
			print 'jQuery(document).ready(function() {'."\n";
7756
7757
			if ($zone == 'private' && empty($conf->dol_use_jmobile))
7758
			{
7759
				print "\n";
7760
				print '/* JS CODE TO ENABLE to manage handler to switch left menu page (menuhider) */'."\n";
7761
				print 'jQuery("li.menuhider").click(function(event) {';
7762
				print '  if (!$( "body" ).hasClass( "sidebar-collapse" )){ event.preventDefault(); }'."\n";
7763
				print '  console.log("We click on .menuhider");'."\n";
7764
				print '  $("body").toggleClass("sidebar-collapse")'."\n";
7765
				print '});'."\n";
7766
			}
7767
7768
			// Management of focus and mandatory for fields
7769
			if ($action == 'create' || $action == 'edit' || (empty($action) && (preg_match('/new\.php/', $_SERVER["PHP_SELF"]))))
7770
			{
7771
				print '/* JS CODE TO ENABLE to manage focus and mandatory form fields */'."\n";
7772
				$relativepathstring = $_SERVER["PHP_SELF"];
7773
				// Clean $relativepathstring
7774
				if (constant('DOL_URL_ROOT')) $relativepathstring = preg_replace('/^'.preg_quote(constant('DOL_URL_ROOT'), '/').'/', '', $relativepathstring);
7775
				$relativepathstring = preg_replace('/^\//', '', $relativepathstring);
7776
				$relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
7777
				//$tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
7778
				if (!empty($user->default_values[$relativepathstring]['focus']))
7779
				{
7780
					foreach ($user->default_values[$relativepathstring]['focus'] as $defkey => $defval)
7781
					{
7782
						$qualified = 0;
7783
						if ($defkey != '_noquery_')
7784
						{
7785
							$tmpqueryarraytohave = explode('&', $defkey);
7786
							$foundintru = 0;
7787
							foreach ($tmpqueryarraytohave as $tmpquerytohave)
7788
							{
7789
								$tmpquerytohaveparam = explode('=', $tmpquerytohave);
7790
								//print "console.log('".$tmpquerytohaveparam[0]." ".$tmpquerytohaveparam[1]." ".GETPOST($tmpquerytohaveparam[0])."');";
7791
								if (!GETPOSTISSET($tmpquerytohaveparam[0]) || ($tmpquerytohaveparam[1] != GETPOST($tmpquerytohaveparam[0]))) $foundintru = 1;
7792
							}
7793
							if (!$foundintru) $qualified = 1;
7794
							//var_dump($defkey.'-'.$qualified);
7795
						} else $qualified = 1;
7796
7797
						if ($qualified)
7798
						{
7799
							foreach ($defval as $paramkey => $paramval)
7800
							{
7801
								// Set focus on field
7802
								print 'jQuery("input[name=\''.$paramkey.'\']").focus();'."\n";
7803
								print 'jQuery("textarea[name=\''.$paramkey.'\']").focus();'."\n";
7804
								print 'jQuery("select[name=\''.$paramkey.'\']").focus();'."\n"; // Not really usefull, but we keep it in case of.
7805
							}
7806
						}
7807
					}
7808
				}
7809
				if (!empty($user->default_values[$relativepathstring]['mandatory']))
7810
				{
7811
					foreach ($user->default_values[$relativepathstring]['mandatory'] as $defkey => $defval)
7812
					{
7813
						$qualified = 0;
7814
						if ($defkey != '_noquery_')
7815
						{
7816
							$tmpqueryarraytohave = explode('&', $defkey);
7817
							$foundintru = 0;
7818
							foreach ($tmpqueryarraytohave as $tmpquerytohave)
7819
							{
7820
								$tmpquerytohaveparam = explode('=', $tmpquerytohave);
7821
								//print "console.log('".$tmpquerytohaveparam[0]." ".$tmpquerytohaveparam[1]." ".GETPOST($tmpquerytohaveparam[0])."');";
7822
								if (!GETPOSTISSET($tmpquerytohaveparam[0]) || ($tmpquerytohaveparam[1] != GETPOST($tmpquerytohaveparam[0]))) $foundintru = 1;
7823
							}
7824
							if (!$foundintru) $qualified = 1;
7825
							//var_dump($defkey.'-'.$qualified);
7826
						} else $qualified = 1;
7827
7828
						if ($qualified)
7829
						{
7830
							foreach ($defval as $paramkey => $paramval)
7831
							{
7832
								// Add property 'required' on input
7833
								print 'jQuery("input[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";
7834
								print 'jQuery("textarea[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";
7835
								print 'jQuery("select[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n"; // required on a select works only if key is "", this does not happen in Dolibarr
7836
							}
7837
						}
7838
					}
7839
				}
7840
			}
7841
7842
			print '});'."\n";
7843
7844
			// End of tuning
7845
			if (!empty($_SERVER['MAIN_SHOW_TUNING_INFO']) || !empty($conf->global->MAIN_SHOW_TUNING_INFO))
7846
			{
7847
				print "\n";
7848
				print "/* JS CODE TO ENABLE to add memory info */\n";
7849
				print 'window.console && console.log("';
7850
				if (!empty($conf->global->MEMCACHED_SERVER)) print 'MEMCACHED_SERVER='.$conf->global->MEMCACHED_SERVER.' - ';
7851
				print 'MAIN_OPTIMIZE_SPEED='.(isset($conf->global->MAIN_OPTIMIZE_SPEED) ? $conf->global->MAIN_OPTIMIZE_SPEED : 'off');
7852
				if (!empty($micro_start_time))   // Works only if MAIN_SHOW_TUNING_INFO is defined at $_SERVER level. Not in global variable.
7853
				{
7854
					$micro_end_time = microtime(true);
7855
					print ' - Build time: '.ceil(1000 * ($micro_end_time - $micro_start_time)).' ms';
7856
				}
7857
7858
				if (function_exists("memory_get_usage")) {
7859
					print ' - Mem: '.memory_get_usage(); // Do not use true here, it seems it takes the peak amount
7860
				}
7861
				if (function_exists("memory_get_peak_usage")) {
7862
					print ' - Real mem peak: '.memory_get_peak_usage(true);
7863
				}
7864
				if (function_exists("zend_loader_file_encoded"))
7865
				{
7866
					print ' - Zend encoded file: '.(zend_loader_file_encoded() ? 'yes' : 'no');
7867
				}
7868
				print '");'."\n";
7869
			}
7870
7871
			print "\n".'</script>'."\n";
7872
7873
			// Google Analytics
7874
			// TODO Add a hook here
7875
			if (!empty($conf->google->enabled) && !empty($conf->global->MAIN_GOOGLE_AN_ID))
7876
			{
7877
				$tmptagarray = explode(',', $conf->global->MAIN_GOOGLE_AN_ID);
7878
				foreach ($tmptagarray as $tmptag) {
7879
					print "\n";
7880
					print "<!-- JS CODE TO ENABLE for google analtics tag -->\n";
7881
					print "
7882
					<!-- Global site tag (gtag.js) - Google Analytics -->
7883
					<script async src=\"https://www.googletagmanager.com/gtag/js?id=".trim($tmptag)."\"></script>
7884
					<script>
7885
					window.dataLayer = window.dataLayer || [];
7886
					function gtag(){dataLayer.push(arguments);}
7887
					gtag('js', new Date());
7888
7889
					gtag('config', '".trim($tmptag)."');
7890
					</script>";
7891
					print "\n";
7892
				}
7893
			}
7894
		}
7895
7896
		// Add Xdebug coverage of code
7897
		if (defined('XDEBUGCOVERAGE'))
7898
		{
7899
			print_r(xdebug_get_code_coverage());
7900
		}
7901
7902
		// Add DebugBar data
7903
		if (!empty($user->rights->debugbar->read) && is_object($debugbar))
7904
		{
7905
			$debugbar['time']->stopMeasure('pageaftermaster');
7906
			print '<!-- Output debugbar data -->'."\n";
7907
			$renderer = $debugbar->getRenderer();
7908
			print $debugbar->getRenderer()->render();
7909
		} elseif (count($conf->logbuffer))    // If there is some logs in buffer to show
7910
		{
7911
			print "\n";
7912
			print "<!-- Start of log output\n";
7913
			//print '<div class="hidden">'."\n";
7914
			foreach ($conf->logbuffer as $logline)
7915
			{
7916
				print $logline."<br>\n";
7917
			}
7918
			//print '</div>'."\n";
7919
			print "End of log output -->\n";
7920
		}
7921
	}
7922
}
7923
7924
/**
7925
 * Split a string with 2 keys into key array.
7926
 * For example: "A=1;B=2;C=2" is exploded into array('A'=>1,'B'=>2,'C'=>3)
7927
 *
7928
 * @param 	string	$string		String to explode
7929
 * @param 	string	$delimiter	Delimiter between each couple of data
7930
 * @param 	string	$kv			Delimiter between key and value
7931
 * @return	array				Array of data exploded
7932
 */
7933
function dolExplodeIntoArray($string, $delimiter = ';', $kv = '=')
7934
{
7935
	if ($a = explode($delimiter, $string))
7936
	{
7937
		$ka = array();
7938
		foreach ($a as $s) { // each part
7939
			if ($s) {
7940
				if ($pos = strpos($s, $kv)) { // key/value delimiter
7941
					$ka[trim(substr($s, 0, $pos))] = trim(substr($s, $pos + strlen($kv)));
7942
				} else { // key delimiter not found
7943
					$ka[] = trim($s);
7944
				}
7945
			}
7946
		}
7947
		return $ka;
7948
	}
7949
	return array();
7950
}
7951
7952
7953
/**
7954
 * Set focus onto field with selector (similar behaviour of 'autofocus' HTML5 tag)
7955
 *
7956
 * @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.
7957
 * @return	string				HTML code to set focus
7958
 */
7959
function dol_set_focus($selector)
7960
{
7961
	print "\n".'<!-- Set focus onto a specific field -->'."\n";
7962
	print '<script>jQuery(document).ready(function() { jQuery("'.dol_escape_js($selector).'").focus(); });</script>'."\n";
7963
}
7964
7965
7966
/**
7967
 * Return getmypid() or random PID when function is disabled
7968
 * Some web hosts disable this php function for security reasons
7969
 * and sometimes we can't redeclare function
7970
 *
7971
 * @return	int
7972
 */
7973
function dol_getmypid()
7974
{
7975
	if (!function_exists('getmypid')) {
7976
		return mt_rand(1, 32768);
7977
	} else {
7978
		return getmypid();
7979
	}
7980
}
7981
7982
7983
/**
7984
 * Generate natural SQL search string for a criteria (this criteria can be tested on one or several fields)
7985
 *
7986
 * @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")
7987
 * @param   string 			$value 		The value to look for.
7988
 *                          		    If param $mode is 0, can contains several keywords separated with a space or |
7989
 *                                      like "keyword1 keyword2" = We want record field like keyword1 AND field like keyword2
7990
 *                                      or like "keyword1|keyword2" = We want record field like keyword1 OR field like keyword2
7991
 *                             			If param $mode is 1, can contains an operator <, > or = like "<10" or ">=100.5 < 1000"
7992
 *                             			If param $mode is 2, can contains a list of int id separated by comma like "1,3,4"
7993
 *                             			If param $mode is 3, can contains a list of string separated by comma like "a,b,c"
7994
 * @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')
7995
 * 										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'
7996
 * @param	integer			$nofirstand	1=Do not output the first 'AND'
7997
 * @return 	string 			$res 		The statement to append to the SQL query
7998
 */
7999
function natural_search($fields, $value, $mode = 0, $nofirstand = 0)
8000
{
8001
	global $db, $langs;
8002
8003
	$value = trim($value);
8004
8005
	if ($mode == 0)
8006
	{
8007
		$value = preg_replace('/\*/', '%', $value); // Replace * with %
8008
	}
8009
	if ($mode == 1)
8010
	{
8011
		$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
8012
	}
8013
8014
	$value = preg_replace('/\s*\|\s*/', '|', $value);
8015
8016
	$crits = explode(' ', $value);
8017
	$res = '';
8018
	if (!is_array($fields)) $fields = array($fields);
8019
8020
	$j = 0;
8021
	foreach ($crits as $crit)
8022
	{
8023
		$crit = trim($crit);
8024
		$i = 0; $i2 = 0;
8025
		$newres = '';
8026
		foreach ($fields as $field)
8027
		{
8028
			if ($mode == 1)
8029
			{
8030
				$operator = '=';
8031
				$newcrit = preg_replace('/([<>=]+)/', '', $crit);
8032
8033
				$reg = array();
8034
				preg_match('/([<>=]+)/', $crit, $reg);
8035
				if ($reg[1])
8036
				{
8037
					$operator = $reg[1];
8038
				}
8039
				if ($newcrit != '')
8040
				{
8041
					$numnewcrit = price2num($newcrit);
8042
					if (is_numeric($numnewcrit))
8043
					{
8044
						$newres .= ($i2 > 0 ? ' OR ' : '').$field.' '.$operator.' '.$db->sanitize($numnewcrit); // should be a numeric
8045
					} else {
8046
						$newres .= ($i2 > 0 ? ' OR ' : '').'1 = 2'; // force false
8047
					}
8048
					$i2++; // a criteria was added to string
8049
				}
8050
			} elseif ($mode == 2 || $mode == -2)
8051
			{
8052
				$crit = preg_replace('/[^0-9,]/', '', $crit); // ID are always integer
8053
				$newres .= ($i2 > 0 ? ' OR ' : '').$field." ".($mode == -2 ? 'NOT ' : '');
8054
				$newres .= $crit ? "IN (".$db->sanitize($db->escape($crit)).")" : "IN (0)";
8055
				if ($mode == -2) $newres .= ' OR '.$field.' IS NULL';
8056
				$i2++; // a criteria was added to string
8057
			} elseif ($mode == 3 || $mode == -3)
8058
			{
8059
				$tmparray = explode(',', $crit);
8060
				if (count($tmparray))
8061
				{
8062
					$listofcodes = '';
8063
					foreach ($tmparray as $val)
8064
					{
8065
						$val = trim($val);
8066
						if ($val)
8067
						{
8068
							$listofcodes .= ($listofcodes ? ',' : '');
8069
							$listofcodes .= "'".$db->escape($val)."'";
8070
						}
8071
					}
8072
					$newres .= ($i2 > 0 ? ' OR ' : '').$field." ".($mode == -3 ? 'NOT ' : '')."IN (".$db->sanitize($listofcodes, 1).")";
8073
					$i2++; // a criteria was added to string
8074
				}
8075
				if ($mode == -3) $newres .= ' OR '.$field.' IS NULL';
8076
			} elseif ($mode == 4)
8077
			{
8078
				$tmparray = explode(',', $crit);
8079
				if (count($tmparray))
8080
				{
8081
					$listofcodes = '';
8082
					foreach ($tmparray as $val)
8083
					{
8084
						$val = trim($val);
8085
						if ($val)
8086
						{
8087
							$newres .= ($i2 > 0 ? ' OR (' : '(').$field.' LIKE \''.$db->escape($val).',%\'';
8088
							$newres .= ' OR '.$field.' = \''.$db->escape($val).'\'';
8089
							$newres .= ' OR '.$field.' LIKE \'%,'.$db->escape($val).'\'';
8090
							$newres .= ' OR '.$field.' LIKE \'%,'.$db->escape($val).',%\'';
8091
							$newres .= ')';
8092
							$i2++;
8093
						}
8094
					}
8095
				}
8096
			} else // $mode=0
8097
			{
8098
				$tmpcrits = explode('|', $crit);
8099
				$i3 = 0;
8100
				foreach ($tmpcrits as $tmpcrit)
8101
				{
8102
					if ($tmpcrit !== '0' && empty($tmpcrit)) continue;
8103
8104
					$newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : '');
8105
8106
					if (preg_match('/\.(id|rowid)$/', $field))	// Special case for rowid that is sometimes a ref so used as a search field
8107
					{
8108
						$newres .= $field." = ".(is_numeric(trim($tmpcrit)) ?trim($tmpcrit) : '0');
8109
					} else {
8110
						$newres .= $field." LIKE '";
8111
8112
						$tmpcrit = trim($tmpcrit);
8113
						$tmpcrit2 = $tmpcrit;
8114
						$tmpbefore = '%'; $tmpafter = '%';
8115
						if (preg_match('/^[\^\$]/', $tmpcrit))
8116
						{
8117
							$tmpbefore = '';
8118
							$tmpcrit2 = preg_replace('/^[\^\$]/', '', $tmpcrit2);
8119
						}
8120
						if (preg_match('/[\^\$]$/', $tmpcrit))
8121
						{
8122
							$tmpafter = '';
8123
							$tmpcrit2 = preg_replace('/[\^\$]$/', '', $tmpcrit2);
8124
						}
8125
						$newres .= $tmpbefore;
8126
						$newres .= $db->escape($tmpcrit2);
8127
						$newres .= $tmpafter;
8128
						$newres .= "'";
8129
						if ($tmpcrit2 == '')
8130
						{
8131
							$newres .= ' OR '.$field." IS NULL";
8132
						}
8133
					}
8134
8135
					$i3++;
8136
				}
8137
				$i2++; // a criteria was added to string
8138
			}
8139
			$i++;
8140
		}
8141
		if ($newres) $res = $res.($res ? ' AND ' : '').($i2 > 1 ? '(' : '').$newres.($i2 > 1 ? ')' : '');
8142
		$j++;
8143
	}
8144
	$res = ($nofirstand ? "" : " AND ")."(".$res.")";
8145
	//print 'xx'.$res.'yy';
8146
	return $res;
8147
}
8148
8149
/**
8150
 * Return string with full Url. The file qualified is the one defined by relative path in $object->last_main_doc
8151
 *
8152
 * @param   Object	$object				Object
8153
 * @return	string						Url string
8154
 */
8155
function showDirectDownloadLink($object)
8156
{
8157
	global $conf, $langs;
8158
8159
	$out = '';
8160
	$url = $object->getLastMainDocLink($object->element);
8161
8162
	if ($url)
8163
	{
8164
		$out .= img_picto('', 'globe').' '.$langs->trans("DirectDownloadLink").'<br>';
8165
		$out .= '<input type="text" id="directdownloadlink" class="quatrevingtpercent" value="'.$url.'">';
8166
		$out .= ajax_autoselect("directdownloadlink", 0);
8167
	}
8168
	return $out;
8169
}
8170
8171
/**
8172
 * Return the filename of file to get the thumbs
8173
 *
8174
 * @param   string  $file           Original filename (full or relative path)
8175
 * @param   string  $extName        Extension to differenciate thumb file name ('', '_small', '_mini')
8176
 * @param   string  $extImgTarget   Force image extension for thumbs. Use '' to keep same extension than original image (default).
8177
 * @return  string                  New file name (full or relative path, including the thumbs/)
8178
 */
8179
function getImageFileNameForSize($file, $extName, $extImgTarget = '')
8180
{
8181
	$dirName = dirname($file);
8182
	if ($dirName == '.') $dirName = '';
8183
8184
	$fileName = preg_replace('/(\.gif|\.jpeg|\.jpg|\.png|\.bmp|\.webp)$/i', '', $file); // We remove extension, whatever is its case
8185
	$fileName = basename($fileName);
8186
8187
	if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.jpg$/i', $file) ? '.jpg' : '');
8188
	if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.jpeg$/i', $file) ? '.jpeg' : '');
8189
	if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.gif$/i', $file) ? '.gif' : '');
8190
	if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.png$/i', $file) ? '.png' : '');
8191
	if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.bmp$/i', $file) ? '.bmp' : '');
8192
	if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.webp$/i', $file) ? '.webp' : '');
8193
8194
	if (!$extImgTarget) return $file;
8195
8196
	$subdir = '';
8197
	if ($extName) $subdir = 'thumbs/';
8198
8199
	return ($dirName ? $dirName.'/' : '').$subdir.$fileName.$extName.$extImgTarget; // New filename for thumb
8200
}
8201
8202
8203
/**
8204
 * Return URL we can use for advanced preview links
8205
 *
8206
 * @param   string    $modulepart     propal, facture, facture_fourn, ...
8207
 * @param   string    $relativepath   Relative path of docs.
8208
 * @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)
8209
 * @param	string	  $param		  More param on http links
8210
 * @return  string|array              Output string with href link or array with all components of link
8211
 */
8212
function getAdvancedPreviewUrl($modulepart, $relativepath, $alldata = 0, $param = '')
8213
{
8214
	global $conf, $langs;
8215
8216
	if (empty($conf->use_javascript_ajax)) return '';
8217
8218
	$isAllowedForPreview = dolIsAllowedForPreview($relativepath);
8219
8220
	if ($alldata == 1)
8221
	{
8222
		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));
8223
		else return array();
8224
	}
8225
8226
	// old behavior, return a string
8227
	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')).'\')';
8228
	else return '';
8229
}
8230
8231
8232
/**
8233
 * Make content of an input box selected when we click into input field.
8234
 *
8235
 * @param string	$htmlname	Id of html object ('#idvalue' or '.classvalue')
8236
 * @param string	$addlink	Add a 'link to' after
8237
 * @return string
8238
 */
8239
function ajax_autoselect($htmlname, $addlink = '')
8240
{
8241
	global $langs;
8242
	$out = '<script>
8243
               jQuery(document).ready(function () {
8244
				    jQuery("'.((strpos($htmlname, '.') === 0 ? '' : '#').$htmlname).'").click(function() { jQuery(this).select(); } );
8245
				});
8246
		    </script>';
8247
	if ($addlink) $out .= ' <a href="'.$addlink.'" target="_blank">'.$langs->trans("Link").'</a>';
8248
	return $out;
8249
}
8250
8251
/**
8252
 *	Return if a file is qualified for preview
8253
 *
8254
 *	@param	string	$file		Filename we looking for information
8255
 *	@return int					1 If allowed, 0 otherwise
8256
 *  @see    dol_mimetype(), image_format_supported() from images.lib.php
8257
 */
8258
function dolIsAllowedForPreview($file)
8259
{
8260
	global $conf;
8261
8262
	// Check .noexe extension in filename
8263
	if (preg_match('/\.noexe$/i', $file)) return 0;
8264
8265
	// Check mime types
8266
	$mime_preview = array('bmp', 'jpeg', 'png', 'gif', 'tiff', 'pdf', 'plain', 'css', 'webp');
8267
	if (!empty($conf->global->MAIN_ALLOW_SVG_FILES_AS_IMAGES)) $mime_preview[] = 'svg+xml';
8268
	//$mime_preview[]='vnd.oasis.opendocument.presentation';
8269
	//$mime_preview[]='archive';
8270
	$num_mime = array_search(dol_mimetype($file, '', 1), $mime_preview);
8271
	if ($num_mime !== false) return 1;
8272
8273
	// By default, not allowed for preview
8274
	return 0;
8275
}
8276
8277
8278
/**
8279
 *	Return mime type of a file
8280
 *
8281
 *	@param	string	$file		Filename we looking for MIME type
8282
 *  @param  string	$default    Default mime type if extension not found in known list
8283
 * 	@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
8284
 *	@return string 		    	Return a mime type family (text/xxx, application/xxx, image/xxx, audio, video, archive)
8285
 *  @see    dolIsAllowedForPreview(), image_format_supported() from images.lib.php
8286
 */
8287
function dol_mimetype($file, $default = 'application/octet-stream', $mode = 0)
8288
{
8289
	$mime = $default;
8290
	$imgmime = 'other.png';
8291
	$famime = 'file-o';
8292
	$srclang = '';
8293
8294
	$tmpfile = preg_replace('/\.noexe$/', '', $file);
8295
8296
	// Plain text files
8297
	if (preg_match('/\.txt$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $famime = 'file-text-o'; }
8298
	if (preg_match('/\.rtx$/i', $tmpfile)) { $mime = 'text/richtext'; $imgmime = 'text.png'; $famime = 'file-text-o'; }
8299
	if (preg_match('/\.csv$/i', $tmpfile)) { $mime = 'text/csv'; $imgmime = 'text.png'; $famime = 'file-text-o'; }
8300
	if (preg_match('/\.tsv$/i', $tmpfile)) { $mime = 'text/tab-separated-values'; $imgmime = 'text.png'; $famime = 'file-text-o'; }
8301
	if (preg_match('/\.(cf|conf|log)$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $famime = 'file-text-o'; }
8302
	if (preg_match('/\.ini$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $srclang = 'ini'; $famime = 'file-text-o'; }
8303
	if (preg_match('/\.md$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $srclang = 'md'; $famime = 'file-text-o'; }
8304
	if (preg_match('/\.css$/i', $tmpfile)) { $mime = 'text/css'; $imgmime = 'css.png'; $srclang = 'css'; $famime = 'file-text-o'; }
8305
	if (preg_match('/\.lang$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $srclang = 'lang'; $famime = 'file-text-o'; }
8306
	// Certificate files
8307
	if (preg_match('/\.(crt|cer|key|pub)$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $famime = 'file-text-o'; }
8308
	// XML based (HTML/XML/XAML)
8309
	if (preg_match('/\.(html|htm|shtml)$/i', $tmpfile)) { $mime = 'text/html'; $imgmime = 'html.png'; $srclang = 'html'; $famime = 'file-text-o'; }
8310
	if (preg_match('/\.(xml|xhtml)$/i', $tmpfile)) { $mime = 'text/xml'; $imgmime = 'other.png'; $srclang = 'xml'; $famime = 'file-text-o'; }
8311
	if (preg_match('/\.xaml$/i', $tmpfile)) { $mime = 'text/xml'; $imgmime = 'other.png'; $srclang = 'xaml'; $famime = 'file-text-o'; }
8312
	// Languages
8313
	if (preg_match('/\.bas$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $srclang = 'bas'; $famime = 'file-code-o'; }
8314
	if (preg_match('/\.(c)$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $srclang = 'c'; $famime = 'file-code-o'; }
8315
	if (preg_match('/\.(cpp)$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $srclang = 'cpp'; $famime = 'file-code-o'; }
8316
	if (preg_match('/\.cs$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $srclang = 'cs'; $famime = 'file-code-o'; }
8317
	if (preg_match('/\.(h)$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $srclang = 'h'; $famime = 'file-code-o'; }
8318
	if (preg_match('/\.(java|jsp)$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $srclang = 'java'; $famime = 'file-code-o'; }
8319
	if (preg_match('/\.php([0-9]{1})?$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'php.png'; $srclang = 'php'; $famime = 'file-code-o'; }
8320
	if (preg_match('/\.phtml$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'php.png'; $srclang = 'php'; $famime = 'file-code-o'; }
8321
	if (preg_match('/\.(pl|pm)$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'pl.png'; $srclang = 'perl'; $famime = 'file-code-o'; }
8322
	if (preg_match('/\.sql$/i', $tmpfile)) { $mime = 'text/plain'; $imgmime = 'text.png'; $srclang = 'sql'; $famime = 'file-code-o'; }
8323
	if (preg_match('/\.js$/i', $tmpfile)) { $mime = 'text/x-javascript'; $imgmime = 'jscript.png'; $srclang = 'js'; $famime = 'file-code-o'; }
8324
	// Open office
8325
	if (preg_match('/\.odp$/i', $tmpfile)) { $mime = 'application/vnd.oasis.opendocument.presentation'; $imgmime = 'ooffice.png'; $famime = 'file-powerpoint-o'; }
8326
	if (preg_match('/\.ods$/i', $tmpfile)) { $mime = 'application/vnd.oasis.opendocument.spreadsheet'; $imgmime = 'ooffice.png'; $famime = 'file-excel-o'; }
8327
	if (preg_match('/\.odt$/i', $tmpfile)) { $mime = 'application/vnd.oasis.opendocument.text'; $imgmime = 'ooffice.png'; $famime = 'file-word-o'; }
8328
	// MS Office
8329
	if (preg_match('/\.mdb$/i', $tmpfile)) { $mime = 'application/msaccess'; $imgmime = 'mdb.png'; $famime = 'file-o'; }
8330
	if (preg_match('/\.doc(x|m)?$/i', $tmpfile)) { $mime = 'application/msword'; $imgmime = 'doc.png'; $famime = 'file-word-o'; }
8331
	if (preg_match('/\.dot(x|m)?$/i', $tmpfile)) { $mime = 'application/msword'; $imgmime = 'doc.png'; $famime = 'file-word-o'; }
8332
	if (preg_match('/\.xlt(x)?$/i', $tmpfile)) { $mime = 'application/vnd.ms-excel'; $imgmime = 'xls.png'; $famime = 'file-excel-o'; }
8333
	if (preg_match('/\.xla(m)?$/i', $tmpfile)) { $mime = 'application/vnd.ms-excel'; $imgmime = 'xls.png'; $famime = 'file-excel-o'; }
8334
	if (preg_match('/\.xls$/i', $tmpfile)) { $mime = 'application/vnd.ms-excel'; $imgmime = 'xls.png'; $famime = 'file-excel-o'; }
8335
	if (preg_match('/\.xls(b|m|x)$/i', $tmpfile)) { $mime = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; $imgmime = 'xls.png'; $famime = 'file-excel-o'; }
8336
	if (preg_match('/\.pps(m|x)?$/i', $tmpfile)) { $mime = 'application/vnd.ms-powerpoint'; $imgmime = 'ppt.png'; $famime = 'file-powerpoint-o'; }
8337
	if (preg_match('/\.ppt(m|x)?$/i', $tmpfile)) { $mime = 'application/x-mspowerpoint'; $imgmime = 'ppt.png'; $famime = 'file-powerpoint-o'; }
8338
	// Other
8339
	if (preg_match('/\.pdf$/i', $tmpfile)) { $mime = 'application/pdf'; $imgmime = 'pdf.png'; $famime = 'file-pdf-o'; }
8340
	// Scripts
8341
	if (preg_match('/\.bat$/i', $tmpfile)) { $mime = 'text/x-bat'; $imgmime = 'script.png'; $srclang = 'dos'; $famime = 'file-code-o'; }
8342
	if (preg_match('/\.sh$/i', $tmpfile)) { $mime = 'text/x-sh'; $imgmime = 'script.png'; $srclang = 'bash'; $famime = 'file-code-o'; }
8343
	if (preg_match('/\.ksh$/i', $tmpfile)) { $mime = 'text/x-ksh'; $imgmime = 'script.png'; $srclang = 'bash'; $famime = 'file-code-o'; }
8344
	if (preg_match('/\.bash$/i', $tmpfile)) { $mime = 'text/x-bash'; $imgmime = 'script.png'; $srclang = 'bash'; $famime = 'file-code-o'; }
8345
	// Images
8346
	if (preg_match('/\.ico$/i', $tmpfile)) { $mime = 'image/x-icon'; $imgmime = 'image.png'; $famime = 'file-image-o'; }
8347
	if (preg_match('/\.(jpg|jpeg)$/i', $tmpfile)) { $mime = 'image/jpeg'; $imgmime = 'image.png'; $famime = 'file-image-o'; }
8348
	if (preg_match('/\.png$/i', $tmpfile)) { $mime = 'image/png'; $imgmime = 'image.png'; $famime = 'file-image-o'; }
8349
	if (preg_match('/\.gif$/i', $tmpfile)) { $mime = 'image/gif'; $imgmime = 'image.png'; $famime = 'file-image-o'; }
8350
	if (preg_match('/\.bmp$/i', $tmpfile)) { $mime = 'image/bmp'; $imgmime = 'image.png'; $famime = 'file-image-o'; }
8351
	if (preg_match('/\.(tif|tiff)$/i', $tmpfile)) { $mime = 'image/tiff'; $imgmime = 'image.png'; $famime = 'file-image-o'; }
8352
	if (preg_match('/\.svg$/i', $tmpfile)) { $mime = 'image/svg+xml'; $imgmime = 'image.png'; $famime = 'file-image-o'; }
8353
	if (preg_match('/\.webp$/i', $tmpfile)) { $mime = 'image/webp'; $imgmime = 'image.png'; $famime = 'file-image-o'; }
8354
	// Calendar
8355
	if (preg_match('/\.vcs$/i', $tmpfile)) { $mime = 'text/calendar'; $imgmime = 'other.png'; $famime = 'file-text-o'; }
8356
	if (preg_match('/\.ics$/i', $tmpfile)) { $mime = 'text/calendar'; $imgmime = 'other.png'; $famime = 'file-text-o'; }
8357
	// Other
8358
	if (preg_match('/\.torrent$/i', $tmpfile)) { $mime = 'application/x-bittorrent'; $imgmime = 'other.png'; $famime = 'file-o'; }
8359
	// Audio
8360
	if (preg_match('/\.(mp3|ogg|au|wav|wma|mid)$/i', $tmpfile)) { $mime = 'audio'; $imgmime = 'audio.png'; $famime = 'file-audio-o'; }
8361
	// Video
8362
	if (preg_match('/\.ogv$/i', $tmpfile)) { $mime = 'video/ogg'; $imgmime = 'video.png'; $famime = 'file-video-o'; }
8363
	if (preg_match('/\.webm$/i', $tmpfile)) { $mime = 'video/webm'; $imgmime = 'video.png'; $famime = 'file-video-o'; }
8364
	if (preg_match('/\.avi$/i', $tmpfile)) { $mime = 'video/x-msvideo'; $imgmime = 'video.png'; $famime = 'file-video-o'; }
8365
	if (preg_match('/\.divx$/i', $tmpfile)) { $mime = 'video/divx'; $imgmime = 'video.png'; $famime = 'file-video-o'; }
8366
	if (preg_match('/\.xvid$/i', $tmpfile)) { $mime = 'video/xvid'; $imgmime = 'video.png'; $famime = 'file-video-o'; }
8367
	if (preg_match('/\.(wmv|mpg|mpeg)$/i', $tmpfile)) { $mime = 'video'; $imgmime = 'video.png'; $famime = 'file-video-o'; }
8368
	// Archive
8369
	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, ...
8370
	// Exe
8371
	if (preg_match('/\.(exe|com)$/i', $tmpfile)) { $mime = 'application/octet-stream'; $imgmime = 'other.png'; $famime = 'file-o'; }
8372
	// Lib
8373
	if (preg_match('/\.(dll|lib|o|so|a)$/i', $tmpfile)) { $mime = 'library'; $imgmime = 'library.png'; $famime = 'file-o'; }
8374
	// Err
8375
	if (preg_match('/\.err$/i', $tmpfile)) { $mime = 'error'; $imgmime = 'error.png'; $famime = 'file-text-o'; }
8376
8377
	// Return string
8378
	if ($mode == 1)
8379
	{
8380
		$tmp = explode('/', $mime);
8381
		return (!empty($tmp[1]) ? $tmp[1] : $tmp[0]);
8382
	}
8383
	if ($mode == 2)
8384
	{
8385
		return $imgmime;
8386
	}
8387
	if ($mode == 3)
8388
	{
8389
		return $srclang;
8390
	}
8391
	if ($mode == 4)
8392
	{
8393
		return $famime;
8394
	}
8395
	return $mime;
8396
}
8397
8398
/**
8399
 * Return value from dictionary
8400
 *
8401
 * @param string	$tablename		name of dictionary
8402
 * @param string	$field			the value to return
8403
 * @param int		$id				id of line
8404
 * @param bool		$checkentity	add filter on entity
8405
 * @param string	$rowidfield		name of the column rowid
8406
 * @return string
8407
 */
8408
function getDictvalue($tablename, $field, $id, $checkentity = false, $rowidfield = 'rowid')
8409
{
8410
	global $dictvalues, $db, $langs;
8411
8412
	if (!isset($dictvalues[$tablename]))
8413
	{
8414
		$dictvalues[$tablename] = array();
8415
		$sql = 'SELECT * FROM '.$tablename.' WHERE 1'; // Here select * is allowed as it is generic code and we don't have list of fields
8416
		if ($checkentity) $sql .= ' AND entity IN (0,'.getEntity($tablename).')';
8417
8418
		$resql = $db->query($sql);
8419
		if ($resql)
8420
		{
8421
			while ($obj = $db->fetch_object($resql))
8422
			{
8423
				$dictvalues[$tablename][$obj->{$rowidfield}] = $obj;
8424
			}
8425
		} else {
8426
			dol_print_error($db);
8427
		}
8428
	}
8429
8430
	if (!empty($dictvalues[$tablename][$id])) return $dictvalues[$tablename][$id]->{$field}; // Found
8431
	else // Not found
8432
	{
8433
		if ($id > 0) return $id;
8434
		return '';
8435
	}
8436
}
8437
8438
/**
8439
 *	Return true if the color is light
8440
 *
8441
 *  @param	string	$stringcolor		String with hex (FFFFFF) or comma RGB ('255,255,255')
8442
 *  @return	int							-1 : Error with argument passed |0 : color is dark | 1 : color is light
8443
 */
8444
function colorIsLight($stringcolor)
8445
{
8446
	$stringcolor = str_replace('#', '', $stringcolor);
8447
	$res = -1;
8448
	if (!empty($stringcolor))
8449
	{
8450
		$res = 0;
8451
		$tmp = explode(',', $stringcolor);
8452
		if (count($tmp) > 1)   // This is a comma RGB ('255','255','255')
8453
		{
8454
			$r = $tmp[0];
8455
			$g = $tmp[1];
8456
			$b = $tmp[2];
8457
		} else {
8458
			$hexr = $stringcolor[0].$stringcolor[1];
8459
			$hexg = $stringcolor[2].$stringcolor[3];
8460
			$hexb = $stringcolor[4].$stringcolor[5];
8461
			$r = hexdec($hexr);
8462
			$g = hexdec($hexg);
8463
			$b = hexdec($hexb);
8464
		}
8465
		$bright = (max($r, $g, $b) + min($r, $g, $b)) / 510.0; // HSL algorithm
8466
		if ($bright > 0.6) $res = 1;
8467
	}
8468
	return $res;
8469
}
8470
8471
/**
8472
 * Function to test if an entry is enabled or not
8473
 *
8474
 * @param	string		$type_user					0=We test for internal user, 1=We test for external user
8475
 * @param	array		$menuentry					Array for feature entry to test
8476
 * @param	array		$listofmodulesforexternal	Array with list of modules allowed to external users
8477
 * @return	int										0=Hide, 1=Show, 2=Show gray
8478
 */
8479
function isVisibleToUserType($type_user, &$menuentry, &$listofmodulesforexternal)
8480
{
8481
	global $conf;
8482
8483
	//print 'type_user='.$type_user.' module='.$menuentry['module'].' enabled='.$menuentry['enabled'].' perms='.$menuentry['perms'];
8484
	//print 'ok='.in_array($menuentry['module'], $listofmodulesforexternal);
8485
	if (empty($menuentry['enabled'])) return 0; // Entry disabled by condition
8486
	if ($type_user && $menuentry['module'])
8487
	{
8488
		$tmploops = explode('|', $menuentry['module']);
8489
		$found = 0;
8490
		foreach ($tmploops as $tmploop)
8491
		{
8492
			if (in_array($tmploop, $listofmodulesforexternal)) {
8493
				$found++; break;
8494
			}
8495
		}
8496
		if (!$found) return 0; // Entry is for menus all excluded to external users
8497
	}
8498
	if (!$menuentry['perms'] && $type_user) return 0; // No permissions and user is external
8499
	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
8500
	if (!$menuentry['perms']) return 2; // No permissions and user is external
8501
	return 1;
8502
}
8503
8504
/**
8505
 * Round to next multiple.
8506
 *
8507
 * @param 	double		$n		Number to round up
8508
 * @param 	integer		$x		Multiple. For example 60 to round up to nearest exact minute for a date with seconds.
8509
 * @return 	integer				Value rounded.
8510
 */
8511
function roundUpToNextMultiple($n, $x = 5)
8512
{
8513
	return (ceil($n) % $x === 0) ? ceil($n) : round(($n + $x / 2) / $x) * $x;
8514
}
8515
8516
/**
8517
 * Function dolGetBadge
8518
 *
8519
 * @param   string  $label      label of badge no html : use in alt attribute for accessibility
8520
 * @param   string  $html       optional : label of badge with html
8521
 * @param   string  $type       type of badge : Primary Secondary Success Danger Warning Info Light Dark status0 status1 status2 status3 status4 status5 status6 status7 status8 status9
8522
 * @param   string  $mode       default '' , 'pill', 'dot'
8523
 * @param   string  $url        the url for link
8524
 * @param   array   $params     various params for future : recommended rather than adding more fuction arguments. array('attr'=>array('title'=>'abc'))
8525
 * @return  string              Html badge
8526
 */
8527
function dolGetBadge($label, $html = '', $type = 'primary', $mode = '', $url = '', $params = array())
8528
{
8529
	$attr = array(
8530
		'class'=>'badge '.(!empty($mode) ? ' badge-'.$mode : '').(!empty($type) ? ' badge-'.$type : '').(empty($params['css']) ? '' : ' '.$params['css'])
8531
	);
8532
8533
	if (empty($html)) {
8534
		$html = $label;
8535
	}
8536
8537
	if (!empty($url)) {
8538
		$attr['href'] = $url;
8539
	}
8540
8541
	if ($mode === 'dot') {
8542
		$attr['class'] .= ' classfortooltip';
8543
		$attr['title'] = $html;
8544
		$attr['aria-label'] = $label;
8545
		$html = '';
8546
	}
8547
8548
	// Override attr
8549
	if (!empty($params['attr']) && is_array($params['attr'])) {
8550
		foreach ($params['attr']as $key => $value) {
8551
			if ($key == 'class') {
8552
				$attr['class'] .= ' '.$value;
8553
			} elseif ($key == 'classOverride') {
8554
				$attr['class'] = $value;
8555
			} else {
8556
				$attr[$key] = $value;
8557
			}
8558
		}
8559
	}
8560
8561
	// TODO: add hook
8562
8563
	// escape all attribute
8564
	$attr = array_map('dol_escape_htmltag', $attr);
8565
8566
	$TCompiledAttr = array();
8567
	foreach ($attr as $key => $value) {
8568
		$TCompiledAttr[] = $key.'="'.$value.'"';
8569
	}
8570
8571
	$compiledAttributes = !empty($TCompiledAttr) ?implode(' ', $TCompiledAttr) : '';
8572
8573
	$tag = !empty($url) ? 'a' : 'span';
8574
8575
	return '<'.$tag.' '.$compiledAttributes.'>'.$html.'</'.$tag.'>';
8576
}
8577
8578
8579
/**
8580
 * Output the badge of a status.
8581
 *
8582
 * @param   string  $statusLabel       Label of badge no html : use in alt attribute for accessibility
8583
 * @param   string  $statusLabelShort  Short label of badge no html
8584
 * @param   string  $html              Optional : label of badge with html
8585
 * @param   string  $statusType        status0 status1 status2 status3 status4 status5 status6 status7 status8 status9 : image name or badge name
8586
 * @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
8587
 * @param   string  $url               The url for link
8588
 * @param   array   $params            Various params. Example: array('tooltip'=>'no|...', 'badgeParams'=>...)
8589
 * @return  string                     Html status string
8590
 */
8591
function dolGetStatus($statusLabel = '', $statusLabelShort = '', $html = '', $statusType = 'status0', $displayMode = 0, $url = '', $params = array())
8592
{
8593
	global $conf;
8594
8595
	$return = '';
8596
	$dolGetBadgeParams = array();
8597
8598
	if (!empty($params['badgeParams'])) {
8599
		$dolGetBadgeParams = $params['badgeParams'];
8600
	}
8601
8602
	// TODO : add a hook
8603
	if ($displayMode == 0) {
8604
		$return = !empty($html) ? $html : (empty($conf->dol_optimize_smallscreen) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort));
8605
	} elseif ($displayMode == 1) {
8606
		$return = !empty($html) ? $html : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort);
8607
	} // Use status with images (for backward compatibility)
8608
	elseif (!empty($conf->global->MAIN_STATUS_USES_IMAGES)) {
8609
		$return = '';
8610
		$htmlLabel      = (in_array($displayMode, array(1, 2, 5)) ? '<span class="hideonsmartphone">' : '').(!empty($html) ? $html : $statusLabel).(in_array($displayMode, array(1, 2, 5)) ? '</span>' : '');
8611
		$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>' : '');
8612
8613
		// For small screen, we always use the short label instead of long label.
8614
		if (!empty($conf->dol_optimize_smallscreen))
8615
		{
8616
			if ($displayMode == 0) $displayMode = 1;
8617
			elseif ($displayMode == 4) $displayMode = 2;
8618
			elseif ($displayMode == 6) $displayMode = 5;
8619
		}
8620
8621
		// For backward compatibility. Image's filename are still in French, so we use this array to convert
8622
		$statusImg = array(
8623
			'status0' => 'statut0',
8624
			'status1' => 'statut1',
8625
			'status2' => 'statut2',
8626
			'status3' => 'statut3',
8627
			'status4' => 'statut4',
8628
			'status5' => 'statut5',
8629
			'status6' => 'statut6',
8630
			'status7' => 'statut7',
8631
			'status8' => 'statut8',
8632
			'status9' => 'statut9'
8633
		);
8634
8635
		if (!empty($statusImg[$statusType])) {
8636
			$htmlImg = img_picto($statusLabel, $statusImg[$statusType]);
8637
		} else {
8638
			$htmlImg = img_picto($statusLabel, $statusType);
8639
		}
8640
8641
		if ($displayMode === 2) {
8642
			$return = $htmlImg.' '.$htmlLabelShort;
8643
		} elseif ($displayMode === 3) {
8644
			$return = $htmlImg;
8645
		} elseif ($displayMode === 4) {
8646
			$return = $htmlImg.' '.$htmlLabel;
8647
		} elseif ($displayMode === 5) {
8648
			$return = $htmlLabelShort.' '.$htmlImg;
8649
		} else { // $displayMode >= 6
8650
			$return = $htmlLabel.' '.$htmlImg;
8651
		}
8652
	} // Use new badge
8653
	elseif (empty($conf->global->MAIN_STATUS_USES_IMAGES) && !empty($displayMode)) {
8654
		$statusLabelShort = (empty($statusLabelShort) ? $statusLabel : $statusLabelShort);
8655
8656
		$dolGetBadgeParams['attr']['class'] = 'badge-status';
8657
		$dolGetBadgeParams['attr']['title'] = empty($params['tooltip']) ? $statusLabel : ($params['tooltip'] != 'no' ? $params['tooltip'] : '');
8658
8659
		if ($displayMode == 3) {
8660
			$return = dolGetBadge((empty($conf->dol_optimize_smallscreen) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort)), '', $statusType, 'dot', $url, $dolGetBadgeParams);
8661
		} elseif ($displayMode === 5) {
8662
			$return = dolGetBadge($statusLabelShort, $html, $statusType, '', $url, $dolGetBadgeParams);
8663
		} else {
8664
			$return = dolGetBadge((empty($conf->dol_optimize_smallscreen) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort)), $html, $statusType, '', $url, $dolGetBadgeParams);
8665
		}
8666
	}
8667
8668
	return $return;
8669
}
8670
8671
8672
/**
8673
 * Function dolGetButtonAction
8674
 *
8675
 * @param string    $label      label of button no html : use in alt attribute for accessibility $html is not empty
8676
 * @param string    $html       optional : content with html
8677
 * @param string    $actionType default, delete, danger
8678
 * @param string    $url        the url for link
8679
 * @param string    $id         attribute id of button
8680
 * @param int       $userRight  user action right
8681
 * @param array     $params     various params for future : recommended rather than adding more function arguments
8682
 * @return string               html button
8683
 */
8684
function dolGetButtonAction($label, $html = '', $actionType = 'default', $url = '', $id = '', $userRight = 1, $params = array())
8685
{
8686
	$class = 'butAction';
8687
	if ($actionType == 'danger' || $actionType == 'delete') {
8688
		$class = 'butActionDelete';
8689
	}
8690
8691
	$attr = array(
8692
		'class' => $class
8693
		,'href' => empty($url) ? '' : $url
8694
	);
8695
8696
	if (empty($html)) {
8697
		$html = $label;
8698
	} else {
8699
		$attr['aria-label'] = $label;
8700
	}
8701
8702
	if (empty($userRight)) {
8703
		$attr['class'] = 'butActionRefused';
8704
		$attr['href'] = '';
8705
	}
8706
8707
	if (!empty($id)) {
8708
		$attr['id'] = $id;
8709
	}
8710
8711
	// Override attr
8712
	if (!empty($params['attr']) && is_array($params['attr'])) {
8713
		foreach ($params['attr'] as $key => $value) {
8714
			if ($key == 'class') {
8715
				$attr['class'] .= ' '.$value;
8716
			} elseif ($key == 'classOverride') {
8717
				$attr['class'] = $value;
8718
			} else {
8719
				$attr[$key] = $value;
8720
			}
8721
		}
8722
	}
8723
8724
	if (isset($attr['href']) && empty($attr['href'])) {
8725
		unset($attr['href']);
8726
	}
8727
8728
	// TODO : add a hook
8729
8730
	// escape all attribute
8731
	$attr = array_map('dol_escape_htmltag', $attr);
8732
8733
	$TCompiledAttr = array();
8734
	foreach ($attr as $key => $value) {
8735
		$TCompiledAttr[] = $key.'="'.$value.'"';
8736
	}
8737
8738
	$compiledAttributes = !empty($TCompiledAttr) ?implode(' ', $TCompiledAttr) : '';
8739
8740
	$tag = !empty($attr['href']) ? 'a' : 'span';
8741
8742
	return '<div class="inline-block divButAction"><'.$tag.' '.$compiledAttributes.'>'.$html.'</'.$tag.'></div>';
8743
}
8744
8745
/**
8746
 * Function dolGetButtonTitle : this kind of buttons are used in title in list
8747
 *
8748
 * @param string    $label      label of button
8749
 * @param string    $helpText   optional : content for help tooltip
8750
 * @param string    $iconClass  class for icon element (Example: 'fa fa-file')
8751
 * @param string    $url        the url for link
8752
 * @param string    $id         attribute id of button
8753
 * @param int       $status     0 no user rights, 1 active, -1 Feature Disabled, -2 disable Other reason use helpText as tooltip
8754
 * @param array     $params     various params for future : recommended rather than adding more function arguments
8755
 * @return string               html button
8756
 */
8757
function dolGetButtonTitle($label, $helpText = '', $iconClass = 'fa fa-file', $url = '', $id = '', $status = 1, $params = array())
8758
{
8759
	global $langs, $conf, $user;
8760
8761
	// Actually this conf is used in css too for external module compatibility and smooth transition to this function
8762
	if (!empty($conf->global->MAIN_BUTTON_HIDE_UNAUTHORIZED) && (!$user->admin) && $status <= 0) {
8763
		return '';
8764
	}
8765
8766
	$class = 'btnTitle';
8767
	if (in_array($iconClass, array('fa fa-plus-circle', 'fa fa-comment-dots'))) $class .= ' btnTitlePlus';
8768
	$useclassfortooltip = 1;
8769
8770
	if (!empty($params['morecss'])) $class .= ' '.$params['morecss'];
8771
8772
	$attr = array(
8773
		'class' => $class,
8774
		'href' => empty($url) ? '' : $url
8775
	);
8776
8777
	if (!empty($helpText)) {
8778
		$attr['title'] = dol_escape_htmltag($helpText);
8779
	} elseif (empty($attr['title']) && $label) {
8780
		$attr['title'] = $label;
8781
		$useclassfortooltip = 0;
8782
	}
8783
8784
	if ($status <= 0) {
8785
		$attr['class'] .= ' refused';
8786
8787
		$attr['href'] = '';
8788
8789
		if ($status == -1) { // disable
8790
			$attr['title'] = dol_escape_htmltag($langs->transnoentitiesnoconv("FeatureDisabled"));
8791
		} elseif ($status == 0) { // Not enough permissions
8792
			$attr['title'] = dol_escape_htmltag($langs->transnoentitiesnoconv("NotEnoughPermissions"));
8793
		}
8794
	}
8795
8796
	if (!empty($attr['title']) && $useclassfortooltip) {
8797
		$attr['class'] .= ' classfortooltip';
8798
	}
8799
8800
	if (!empty($id)) {
8801
		$attr['id'] = $id;
8802
	}
8803
8804
	// Override attr
8805
	if (!empty($params['attr']) && is_array($params['attr'])) {
8806
		foreach ($params['attr'] as $key => $value) {
8807
			if ($key == 'class') {
8808
				$attr['class'] .= ' '.$value;
8809
			} elseif ($key == 'classOverride') {
8810
				$attr['class'] = $value;
8811
			} else {
8812
				$attr[$key] = $value;
8813
			}
8814
		}
8815
	}
8816
8817
	if (isset($attr['href']) && empty($attr['href'])) {
8818
		unset($attr['href']);
8819
	}
8820
8821
	// TODO : add a hook
8822
8823
	// escape all attribute
8824
	$attr = array_map('dol_escape_htmltag', $attr);
8825
8826
	$TCompiledAttr = array();
8827
	foreach ($attr as $key => $value) {
8828
		$TCompiledAttr[] = $key.'="'.$value.'"';
8829
	}
8830
8831
	$compiledAttributes = (empty($TCompiledAttr) ? '' : implode(' ', $TCompiledAttr));
8832
8833
	$tag = (empty($attr['href']) ? 'span' : 'a');
8834
8835
	$button = '<'.$tag.' '.$compiledAttributes.'>';
8836
	$button .= '<span class="'.$iconClass.' valignmiddle btnTitle-icon"></span>';
8837
	if (!empty($params['forcenohideoftext'])) {
8838
		$button .= '<span class="valignmiddle text-plus-circle btnTitle-label'.(empty($params['forcenohideoftext']) ? ' hideonsmartphone' : '').'">'.$label.'</span>';
8839
	}
8840
	$button .= '</'.$tag.'>';
8841
8842
	return $button;
8843
}
8844
8845
/**
8846
 * Get an array with properties of an element.
8847
 * Called by fetchObjectByElement.
8848
 *
8849
 * @param   string 	$element_type 	Element type (Value of $object->element). Example: 'action', 'facture', 'project_task' or 'object@mymodule'...
8850
 * @return  array					(module, classpath, element, subelement, classfile, classname)
8851
 */
8852
function getElementProperties($element_type)
8853
{
8854
	$regs = array();
8855
8856
	$classfile = $classname = $classpath = '';
8857
8858
	// Parse element/subelement (ex: project_task)
8859
	$module = $element_type;
8860
	$element = $element_type;
8861
	$subelement = $element_type;
8862
8863
	// If we ask an resource form external module (instead of default path)
8864
	if (preg_match('/^([^@]+)@([^@]+)$/i', $element_type, $regs)) {
8865
		$element = $subelement = $regs[1];
8866
		$module = $regs[2];
8867
	}
8868
8869
	//print '<br>1. element : '.$element.' - module : '.$module .'<br>';
8870
	if (preg_match('/^([^_]+)_([^_]+)/i', $element, $regs)) {
8871
		$module = $element = $regs[1];
8872
		$subelement = $regs[2];
8873
	}
8874
8875
	// For compat
8876
	if ($element_type == "action") {
8877
		$classpath = 'comm/action/class';
8878
		$subelement = 'Actioncomm';
8879
		$module = 'agenda';
8880
	}
8881
8882
	// To work with non standard path
8883
	if ($element_type == 'facture' || $element_type == 'invoice') {
8884
		$classpath = 'compta/facture/class';
8885
		$module = 'facture';
8886
		$subelement = 'facture';
8887
	}
8888
	if ($element_type == 'commande' || $element_type == 'order') {
8889
		$classpath = 'commande/class';
8890
		$module = 'commande';
8891
		$subelement = 'commande';
8892
	}
8893
	if ($element_type == 'propal') {
8894
		$classpath = 'comm/propal/class';
8895
	}
8896
	if ($element_type == 'supplier_proposal') {
8897
		$classpath = 'supplier_proposal/class';
8898
	}
8899
	if ($element_type == 'shipping') {
8900
		$classpath = 'expedition/class';
8901
		$subelement = 'expedition';
8902
		$module = 'expedition_bon';
8903
	}
8904
	if ($element_type == 'delivery') {
8905
		$classpath = 'delivery/class';
8906
		$subelement = 'delivery';
8907
		$module = 'delivery_note';
8908
	}
8909
	if ($element_type == 'contract') {
8910
		$classpath = 'contrat/class';
8911
		$module = 'contrat';
8912
		$subelement = 'contrat';
8913
	}
8914
	if ($element_type == 'member') {
8915
		$classpath = 'adherents/class';
8916
		$module = 'adherent';
8917
		$subelement = 'adherent';
8918
	}
8919
	if ($element_type == 'cabinetmed_cons') {
8920
		$classpath = 'cabinetmed/class';
8921
		$module = 'cabinetmed';
8922
		$subelement = 'cabinetmedcons';
8923
	}
8924
	if ($element_type == 'fichinter') {
8925
		$classpath = 'fichinter/class';
8926
		$module = 'ficheinter';
8927
		$subelement = 'fichinter';
8928
	}
8929
	if ($element_type == 'dolresource' || $element_type == 'resource') {
8930
		$classpath = 'resource/class';
8931
		$module = 'resource';
8932
		$subelement = 'dolresource';
8933
	}
8934
	if ($element_type == 'propaldet') {
8935
		$classpath = 'comm/propal/class';
8936
		$module = 'propal';
8937
		$subelement = 'propaleligne';
8938
	}
8939
	if ($element_type == 'order_supplier') {
8940
		$classpath = 'fourn/class';
8941
		$module = 'fournisseur';
8942
		$subelement = 'commandefournisseur';
8943
		$classfile = 'fournisseur.commande';
8944
	}
8945
	if ($element_type == 'invoice_supplier') {
8946
		$classpath = 'fourn/class';
8947
		$module = 'fournisseur';
8948
		$subelement = 'facturefournisseur';
8949
		$classfile = 'fournisseur.facture';
8950
	}
8951
	if ($element_type == "service") {
8952
		$classpath = 'product/class';
8953
		$subelement = 'product';
8954
	}
8955
8956
	if (empty($classfile)) $classfile = strtolower($subelement);
8957
	if (empty($classname)) $classname = ucfirst($subelement);
8958
	if (empty($classpath)) $classpath = $module.'/class';
8959
8960
	$element_properties = array(
8961
		'module' => $module,
8962
		'classpath' => $classpath,
8963
		'element' => $element,
8964
		'subelement' => $subelement,
8965
		'classfile' => $classfile,
8966
		'classname' => $classname
8967
	);
8968
	return $element_properties;
8969
}
8970
8971
/**
8972
 * Fetch an object from its id and element_type
8973
 * Inclusion of classes is automatic
8974
 *
8975
 * @param	int     	$element_id 	Element id
8976
 * @param	string  	$element_type 	Element type
8977
 * @param	string     	$element_ref 	Element ref (Use this or element_id but not both)
8978
 * @return 	int|object 					object || 0 || -1 if error
8979
 */
8980
function fetchObjectByElement($element_id, $element_type, $element_ref = '')
8981
{
8982
	global $conf, $db;
8983
8984
	$element_prop = getElementProperties($element_type);
8985
	if (is_array($element_prop) && $conf->{$element_prop['module']}->enabled)
8986
	{
8987
		dol_include_once('/'.$element_prop['classpath'].'/'.$element_prop['classfile'].'.class.php');
8988
8989
		$objecttmp = new $element_prop['classname']($db);
8990
		$ret = $objecttmp->fetch($element_id, $element_ref);
8991
		if ($ret >= 0)
8992
		{
8993
			return $objecttmp;
8994
		}
8995
	}
8996
	return 0;
8997
}
8998
8999
/**
9000
 * Return if a file can contains executable content
9001
 *
9002
 * @param   string  $filename       File name to test
9003
 * @return  boolean                 True if yes, False if no
9004
 */
9005
function isAFileWithExecutableContent($filename)
9006
{
9007
	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))
9008
	{
9009
		return true;
9010
	}
9011
9012
	return false;
9013
}
9014
9015
/**
9016
 * Return the value of token currently saved into session with name 'newtoken'.
9017
 * This token must be send by any POST as it will be used by next page for comparison with value in session.
9018
 *
9019
 * @return  string
9020
 */
9021
function newToken()
9022
{
9023
	return $_SESSION['newtoken'];
9024
}
9025
9026
/**
9027
 * Return the value of token currently saved into session with name 'token'.
9028
 *
9029
 * @return  string
9030
 */
9031
function currentToken()
9032
{
9033
	return $_SESSION['token'];
9034
}
9035
9036
/**
9037
 * Start a table with headers and a optinal clickable number (don't forget to use "finishSimpleTable()" after the last table row)
9038
 *
9039
 * @param string	$header		The first left header of the table (automatic translated)
9040
 * @param string	$link		(optional) The link to a internal dolibarr page, when click on the number (without the first "/")
9041
 * @param string	$arguments	(optional) Additional arguments for the link (e.g. "search_status=0")
9042
 * @param integer	$emptyRows	(optional) The count of empty rows after the first header
9043
 * @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"
9044
 * @return void
9045
 *
9046
 * @see finishSimpleTable()
9047
 */
9048
function startSimpleTable($header, $link = "", $arguments = "", $emptyRows = 0, $number = -1)
9049
{
9050
	global $langs;
9051
9052
	print '<div class="div-table-responsive-no-min">';
9053
	print '<table class="noborder centpercent">';
9054
	print '<tr class="liste_titre">';
9055
9056
	print $emptyRows < 1 ? '<th>' : '<th colspan="'.($emptyRows + 1).'">';
9057
9058
	print $langs->trans($header);
9059
9060
	// extra space between the first header and the number
9061
	if ($number > -1) {
9062
		print ' ';
9063
	}
9064
9065
	if (!empty($link)) {
9066
		if (!empty($arguments)) {
9067
			print '<a href="'.DOL_URL_ROOT.'/'.$link.'?'.$arguments.'">';
9068
		} else {
9069
			print '<a href="'.DOL_URL_ROOT.'/'.$link.'">';
9070
		}
9071
	}
9072
9073
	if ($number > -1) {
9074
		print '<span class="badge">'.$number.'</span>';
9075
	}
9076
9077
	if (!empty($link)) {
9078
		print '</a>';
9079
	}
9080
9081
	print '</th>';
9082
9083
	if ($number < 0 && !empty($link)) {
9084
		print '<th class="right">';
9085
9086
		if (!empty($arguments)) {
9087
			print '<a class="commonlink" href="'.DOL_URL_ROOT.'/'.$link.'?'.$arguments.'">';
9088
		} else {
9089
			print '<a class="commonlink" href="'.DOL_URL_ROOT.'/'.$link.'">';
9090
		}
9091
9092
		print $langs->trans("FullList");
9093
		print '</a>';
9094
		print '</th>';
9095
	}
9096
9097
	print '</tr>';
9098
}
9099
9100
/**
9101
 * Add the correct HTML close tags for "startSimpleTable(...)" (use after the last table line)
9102
 *
9103
 * @param 	bool 	$addLineBreak	(optional) Add a extra line break after the complete table (\<br\>)
9104
 * @return 	void
9105
 *
9106
 * @see startSimpleTable()
9107
 */
9108
function finishSimpleTable($addLineBreak = false)
9109
{
9110
	print '</table>';
9111
	print '</div>';
9112
9113
	if ($addLineBreak) {
9114
		print '<br>';
9115
	}
9116
}
9117
9118
/**
9119
 * Add a summary line to the current open table ("None", "XMoreLines" or "Total xxx")
9120
 *
9121
 * @param integer	$tableColumnCount		The complete count columns of the table
9122
 * @param integer	$num					The count of the rows of the table, when it is zero (0) the "$noneWord" is shown instead
9123
 * @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)
9124
 * @param integer	$total					(optional)	The total value thaht is shown after when the table has minimum of one entire
9125
 * @param string	$noneWord				(optional)	The word that is shown when the table has no entires ($num === 0)
9126
 * @param boolean	$extraRightColumn		(optional)	Add a addtional column after the summary word and total number
9127
 * @return void
9128
 */
9129
function addSummaryTableLine($tableColumnCount, $num, $nbofloop = 0, $total = 0, $noneWord = "None", $extraRightColumn = false)
9130
{
9131
	global $langs;
9132
9133
	if ($num === 0) {
9134
		print '<tr class="oddeven">';
9135
		print '<td colspan="'.$tableColumnCount.'" class="opacitymedium">'.$langs->trans($noneWord).'</td>';
9136
		print '</tr>';
9137
		return;
9138
	}
9139
9140
	if ($nbofloop === 0)
9141
	{
9142
		// don't show a summary line
9143
		return;
9144
	}
9145
9146
	if ($num === 0) {
9147
		$colspan = $tableColumnCount;
9148
	}
9149
	elseif ($num > $nbofloop) {
9150
		$colspan = $tableColumnCount;
9151
	} else {
9152
		$colspan = $tableColumnCount - 1;
9153
	}
9154
9155
	if ($extraRightColumn) {
9156
		$colspan--;
9157
	}
9158
9159
	print '<tr class="liste_total">';
9160
9161
	if ($nbofloop > 0 && $num > $nbofloop) {
9162
		print '<td colspan="'.$colspan.'" class="right">'.$langs->trans("XMoreLines", ($num - $nbofloop)).'</td>';
9163
	} else {
9164
		print '<td colspan="'.$colspan.'" class="right"> '.$langs->trans("Total").'</td>';
9165
		print '<td class="right" width="100">'.price($total).'</td>';
9166
	}
9167
9168
	if ($extraRightColumn) {
9169
		print '<td></td>';
9170
	}
9171
9172
	print '</tr>';
9173
}
9174
9175
/**
9176
 *  Return a file on output using a low memory. It can return very large files with no need of memory.
9177
 *  WARNING: This close output buffers.
9178
 *
9179
 *  @param	string	$fullpath_original_file_osencoded		Full path of file to return.
9180
 *  @param	int		$method									-1 automatic, 0=readfile, 1=fread, 2=stream_copy_to_stream
9181
 *  @return void
9182
 */
9183
function readfileLowMemory($fullpath_original_file_osencoded, $method = -1)
9184
{
9185
	global $conf;
9186
9187
	if ($method == -1) {
9188
		$method = 0;
9189
		if (!empty($conf->global->MAIN_FORCE_READFILE_WITH_FREAD)) $method = 1;
9190
		if (!empty($conf->global->MAIN_FORCE_READFILE_WITH_STREAM_COPY)) $method = 2;
9191
	}
9192
9193
	// Be sure we don't have output buffering enabled to have readfile working correctly
9194
	while (ob_get_level()) ob_end_flush();
9195
9196
	// Solution 0
9197
	if ($method == 0) {
9198
		readfile($fullpath_original_file_osencoded);
9199
	}
9200
	// Solution 1
9201
	elseif ($method == 1) {
9202
		$handle = fopen($fullpath_original_file_osencoded, "rb");
9203
		while (!feof($handle)) {
9204
			print fread($handle, 8192);
9205
		}
9206
		fclose($handle);
9207
	}
9208
	// Solution 2
9209
	elseif ($method == 2) {
9210
		$handle1 = fopen($fullpath_original_file_osencoded, "rb");
9211
		$handle2 = fopen("php://output", "wb");
9212
		stream_copy_to_stream($handle1, $handle2);
9213
		fclose($handle1);
9214
		fclose($handle2);
9215
	}
9216
}
9217