Passed
Branch develop (30d2c8)
by
unknown
26:49
created

getIsHTTPS()   B

Complexity

Conditions 7
Paths 3

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 6
c 0
b 0
f 0
nc 3
nop 0
dl 0
loc 10
rs 8.8333
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 = 'tzserver', $outputlangs = '', $encodetooutput = false)
1975
{
1976
	global $conf, $langs;
1977
1978
	// Clean parameters
1979
	$to_gmt = false;
1980
	$offsettz = $offsetdst = 0;
1981
	if ($tzoutput)
1982
	{
1983
		$to_gmt = true; // For backward compatibility
1984
		if (is_string($tzoutput))
1985
		{
1986
			if ($tzoutput == 'tzserver')
1987
			{
1988
				$to_gmt = false;
1989
				$offsettzstring = @date_default_timezone_get(); // Example 'Europe/Berlin' or 'Indian/Reunion'
1990
				$offsettz = 0;
1991
				$offsetdst = 0;
1992
			} elseif ($tzoutput == 'tzuser' || $tzoutput == 'tzuserrel')
1993
			{
1994
				$to_gmt = true;
1995
				$offsettzstring = (empty($_SESSION['dol_tz_string']) ? 'UTC' : $_SESSION['dol_tz_string']); // Example 'Europe/Berlin' or 'Indian/Reunion'
1996
				$offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60; // Will not be used anymore
1997
				$offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60; // Will not be used anymore
1998
			}
1999
		}
2000
	}
2001
	if (!is_object($outputlangs)) $outputlangs = $langs;
2002
	if (!$format) $format = 'daytextshort';
2003
	$reduceformat = (!empty($conf->dol_optimize_smallscreen) && in_array($format, array('day', 'dayhour'))) ? 1 : 0;
2004
	$formatwithoutreduce = preg_replace('/reduceformat/', '', $format);
2005
	if ($formatwithoutreduce != $format) { $format = $formatwithoutreduce; $reduceformat = 1; }  // so format 'dayreduceformat' is processed like day
2006
2007
	// Change predefined format into computer format. If found translation in lang file we use it, otherwise we use default.
2008
	// TODO Add format daysmallyear and dayhoursmallyear
2009
	if ($format == 'day')				$format = ($outputlangs->trans("FormatDateShort") != "FormatDateShort" ? $outputlangs->trans("FormatDateShort") : $conf->format_date_short);
2010
	elseif ($format == 'hour')			$format = ($outputlangs->trans("FormatHourShort") != "FormatHourShort" ? $outputlangs->trans("FormatHourShort") : $conf->format_hour_short);
2011
	elseif ($format == 'hourduration')	$format = ($outputlangs->trans("FormatHourShortDuration") != "FormatHourShortDuration" ? $outputlangs->trans("FormatHourShortDuration") : $conf->format_hour_short_duration);
2012
	elseif ($format == 'daytext')			 $format = ($outputlangs->trans("FormatDateText") != "FormatDateText" ? $outputlangs->trans("FormatDateText") : $conf->format_date_text);
2013
	elseif ($format == 'daytextshort')	$format = ($outputlangs->trans("FormatDateTextShort") != "FormatDateTextShort" ? $outputlangs->trans("FormatDateTextShort") : $conf->format_date_text_short);
2014
	elseif ($format == 'dayhour')			 $format = ($outputlangs->trans("FormatDateHourShort") != "FormatDateHourShort" ? $outputlangs->trans("FormatDateHourShort") : $conf->format_date_hour_short);
2015
	elseif ($format == 'dayhoursec')		 $format = ($outputlangs->trans("FormatDateHourSecShort") != "FormatDateHourSecShort" ? $outputlangs->trans("FormatDateHourSecShort") : $conf->format_date_hour_sec_short);
2016
	elseif ($format == 'dayhourtext')		 $format = ($outputlangs->trans("FormatDateHourText") != "FormatDateHourText" ? $outputlangs->trans("FormatDateHourText") : $conf->format_date_hour_text);
2017
	elseif ($format == 'dayhourtextshort') $format = ($outputlangs->trans("FormatDateHourTextShort") != "FormatDateHourTextShort" ? $outputlangs->trans("FormatDateHourTextShort") : $conf->format_date_hour_text_short);
2018
	// Format not sensitive to language
2019
	elseif ($format == 'dayhourlog')		 $format = '%Y%m%d%H%M%S';
2020
	elseif ($format == 'dayhourldap')		 $format = '%Y%m%d%H%M%SZ';
2021
	elseif ($format == 'dayhourxcard')	$format = '%Y%m%dT%H%M%SZ';
2022
	elseif ($format == 'dayxcard')	 	$format = '%Y%m%d';
2023
	elseif ($format == 'dayrfc')			 $format = '%Y-%m-%d'; // DATE_RFC3339
2024
	elseif ($format == 'dayhourrfc')		 $format = '%Y-%m-%dT%H:%M:%SZ'; // DATETIME RFC3339
2025
	elseif ($format == 'standard')		$format = '%Y-%m-%d %H:%M:%S';
2026
2027
	if ($reduceformat)
2028
	{
2029
		$format = str_replace('%Y', '%y', $format);
2030
		$format = str_replace('yyyy', 'yy', $format);
2031
	}
2032
2033
	// If date undefined or "", we return ""
2034
	if (dol_strlen($time) == 0) return ''; // $time=0 allowed (it means 01/01/1970 00:00:00)
2035
2036
	// Clean format
2037
	if (preg_match('/%b/i', $format))		// There is some text to translate
2038
	{
2039
		// We inhibate translation to text made by strftime functions. We will use trans instead later.
2040
		$format = str_replace('%b', '__b__', $format);
2041
		$format = str_replace('%B', '__B__', $format);
2042
	}
2043
	if (preg_match('/%a/i', $format))		// There is some text to translate
2044
	{
2045
		// We inhibate translation to text made by strftime functions. We will use trans instead later.
2046
		$format = str_replace('%a', '__a__', $format);
2047
		$format = str_replace('%A', '__A__', $format);
2048
	}
2049
2050
2051
	// Analyze date
2052
	$reg = array();
2053
	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
2054
	{
2055
		dol_print_error("Functions.lib::dol_print_date function called with a bad value from page ".$_SERVER["PHP_SELF"]);
2056
		return '';
2057
	} 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
2058
	{
2059
		// This part of code should not be used.
2060
		dol_syslog("Functions.lib::dol_print_date function called with a bad value from page ".$_SERVER["PHP_SELF"], LOG_WARNING);
2061
		//if (function_exists('debug_print_backtrace')) debug_print_backtrace();
2062
		// Date has format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'
2063
		$syear	= (!empty($reg[1]) ? $reg[1] : '');
2064
		$smonth = (!empty($reg[2]) ? $reg[2] : '');
2065
		$sday	= (!empty($reg[3]) ? $reg[3] : '');
2066
		$shour	= (!empty($reg[4]) ? $reg[4] : '');
2067
		$smin	= (!empty($reg[5]) ? $reg[5] : '');
2068
		$ssec	= (!empty($reg[6]) ? $reg[6] : '');
2069
2070
		$time = dol_mktime($shour, $smin, $ssec, $smonth, $sday, $syear, true);
2071
		$ret = adodb_strftime($format, $time + $offsettz + $offsetdst, $to_gmt);
2072
	} else {
2073
		// Date is a timestamps
2074
		if ($time < 100000000000)	// Protection against bad date values
2075
		{
2076
			$timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
2077
2078
			$ret = adodb_strftime($format, $timetouse, $to_gmt);
2079
		} else $ret = 'Bad value '.$time.' for date';
2080
	}
2081
2082
	if (preg_match('/__b__/i', $format))
2083
	{
2084
		$timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
2085
2086
		// Here ret is string in PHP setup language (strftime was used). Now we convert to $outputlangs.
2087
		$month = adodb_strftime('%m', $timetouse);
2088
		$month = sprintf("%02d", $month); // $month may be return with format '06' on some installation and '6' on other, so we force it to '06'.
2089
		if ($encodetooutput)
2090
		{
2091
			$monthtext = $outputlangs->transnoentities('Month'.$month);
2092
			$monthtextshort = $outputlangs->transnoentities('MonthShort'.$month);
2093
		} else {
2094
			$monthtext = $outputlangs->transnoentitiesnoconv('Month'.$month);
2095
			$monthtextshort = $outputlangs->transnoentitiesnoconv('MonthShort'.$month);
2096
		}
2097
		//print 'monthtext='.$monthtext.' monthtextshort='.$monthtextshort;
2098
		$ret = str_replace('__b__', $monthtextshort, $ret);
2099
		$ret = str_replace('__B__', $monthtext, $ret);
2100
		//print 'x'.$outputlangs->charset_output.'-'.$ret.'x';
2101
		//return $ret;
2102
	}
2103
	if (preg_match('/__a__/i', $format))
2104
	{
2105
		$timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
2106
2107
		$w = adodb_strftime('%w', $timetouse); // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
2108
		$dayweek = $outputlangs->transnoentitiesnoconv('Day'.$w);
2109
		$ret = str_replace('__A__', $dayweek, $ret);
2110
		$ret = str_replace('__a__', dol_substr($dayweek, 0, 3), $ret);
2111
	}
2112
2113
	return $ret;
2114
}
2115
2116
2117
/**
2118
 *  Return an array with locale date info.
2119
 *  WARNING: This function use PHP server timezone by default to return locale informations.
2120
 *  Be aware to add the third parameter to "UTC" if you need to work on UTC.
2121
 *
2122
 *	@param	int			$timestamp      Timestamp
2123
 *	@param	boolean		$fast           Fast mode. deprecated.
2124
 *  @param	string		$forcetimezone	'' to use the PHP server timezone. Or use a form like 'Europe/Paris' or '+0200' to force timezone.
2125
 *	@return	array						Array of informations
2126
 *										'seconds' => $secs,
2127
 *										'minutes' => $min,
2128
 *										'hours' => $hour,
2129
 *										'mday' => $day,
2130
 *										'wday' => $dow,		0=sunday, 6=saturday
2131
 *										'mon' => $month,
2132
 *										'year' => $year,
2133
 *										'yday' => floor($secsInYear/$_day_power)
2134
 *										'0' => original timestamp
2135
 * 	@see 								dol_print_date(), dol_stringtotime(), dol_mktime()
2136
 */
2137
function dol_getdate($timestamp, $fast = false, $forcetimezone = '')
2138
{
2139
	global $conf;
2140
2141
	if (empty($conf->global->MAIN_USE_OLD_FUNCTIONS_FOR_GETDATE)) {
2142
		//$datetimeobj = new DateTime('@'.$timestamp);
2143
		$datetimeobj = new DateTime();
2144
		$datetimeobj->setTimestamp($timestamp); // Use local PHP server timezone
2145
		if ($forcetimezone) $datetimeobj->setTimezone(new DateTimeZone($forcetimezone)); //  (add timezone relative to the date entered)
2146
		$arrayinfo = array(
2147
			'year'=>((int) date_format($datetimeobj, 'Y')),
2148
			'mon'=>((int) date_format($datetimeobj, 'm')),
2149
			'mday'=>((int) date_format($datetimeobj, 'd')),
2150
			'wday'=>((int) date_format($datetimeobj, 'w')),
2151
			'yday'=>((int) date_format($datetimeobj, 'z')),
2152
			'hours'=>((int) date_format($datetimeobj, 'H')),
2153
			'minutes'=>((int) date_format($datetimeobj, 'i')),
2154
			'seconds'=>((int) date_format($datetimeobj, 's')),
2155
			'0'=>$timestamp
2156
		);
2157
	} else {
2158
		// PHP getdate is restricted to the years 1901-2038 on Unix and 1970-2038 on Windows
2159
		$usealternatemethod = false;
2160
		if ($timestamp <= 0) $usealternatemethod = true; // <= 1970
2161
		if ($timestamp >= 2145913200) $usealternatemethod = true; // >= 2038
2162
2163
		if ($usealternatemethod)
2164
		{
2165
			$arrayinfo = adodb_getdate($timestamp, $fast);
2166
		} else {
2167
			$arrayinfo = getdate($timestamp);
2168
		}
2169
	}
2170
2171
	return $arrayinfo;
2172
}
2173
2174
/**
2175
 *	Return a timestamp date built from detailed informations (by default a local PHP server timestamp)
2176
 * 	Replace function mktime not available under Windows if year < 1970
2177
 *	PHP mktime is restricted to the years 1901-2038 on Unix and 1970-2038 on Windows
2178
 *
2179
 * 	@param	int			$hour			Hour	(can be -1 for undefined)
2180
 *	@param	int			$minute			Minute	(can be -1 for undefined)
2181
 *	@param	int			$second			Second	(can be -1 for undefined)
2182
 *	@param	int			$month			Month (1 to 12)
2183
 *	@param	int			$day			Day (1 to 31)
2184
 *	@param	int			$year			Year
2185
 *	@param	mixed		$gm				True or 1 or 'gmt'=Input informations are GMT values
2186
 *										False or 0 or 'server' = local to server TZ
2187
 *										'user' = local to user TZ
2188
 *										'tz,TimeZone' = use specified timezone
2189
 *	@param	int			$check			0=No check on parameters (Can use day 32, etc...)
2190
 *	@return	int|string					Date as a timestamp, '' or false if error
2191
 * 	@see 								dol_print_date(), dol_stringtotime(), dol_getdate()
2192
 */
2193
function dol_mktime($hour, $minute, $second, $month, $day, $year, $gm = false, $check = 1)
2194
{
2195
	global $conf;
2196
	//print "- ".$hour.",".$minute.",".$second.",".$month.",".$day.",".$year.",".$_SERVER["WINDIR"]." -";
2197
2198
	// Clean parameters
2199
	if ($hour == -1 || empty($hour)) $hour = 0;
2200
	if ($minute == -1 || empty($minute)) $minute = 0;
2201
	if ($second == -1 || empty($second)) $second = 0;
2202
2203
	// Check parameters
2204
	if ($check)
2205
	{
2206
		if (!$month || !$day)  return '';
2207
		if ($day > 31) return '';
2208
		if ($month > 12) return '';
2209
		if ($hour < 0 || $hour > 24) return '';
2210
		if ($minute < 0 || $minute > 60) return '';
2211
		if ($second < 0 || $second > 60) return '';
2212
	}
2213
2214
	if (empty($gm) || $gm === 'server')
2215
	{
2216
		$default_timezone = @date_default_timezone_get(); // Example 'Europe/Berlin'
2217
		$localtz = new DateTimeZone($default_timezone);
2218
	} elseif ($gm === 'user')
2219
	{
2220
		// We use dol_tz_string first because it is more reliable.
2221
		$default_timezone = (empty($_SESSION["dol_tz_string"]) ? @date_default_timezone_get() : $_SESSION["dol_tz_string"]); // Example 'Europe/Berlin'
2222
		try {
2223
			$localtz = new DateTimeZone($default_timezone);
2224
		} catch (Exception $e)
2225
		{
2226
			dol_syslog("Warning dol_tz_string contains an invalid value ".$_SESSION["dol_tz_string"], LOG_WARNING);
2227
			$default_timezone = @date_default_timezone_get();
2228
		}
2229
	} elseif (strrpos($gm, "tz,") !== false)
2230
	{
2231
		$timezone = str_replace("tz,", "", $gm); // Example 'tz,Europe/Berlin'
2232
		try {
2233
			$localtz = new DateTimeZone($timezone);
2234
		} catch (Exception $e)
2235
		{
2236
			dol_syslog("Warning passed timezone contains an invalid value ".$timezone, LOG_WARNING);
2237
		}
2238
	}
2239
2240
	if (empty($localtz)) {
2241
		$localtz = new DateTimeZone('UTC');
2242
	}
2243
	//var_dump($localtz);
2244
	//var_dump($year.'-'.$month.'-'.$day.'-'.$hour.'-'.$minute);
2245
	$dt = new DateTime(null, $localtz);
2246
	$dt->setDate((int) $year, (int) $month, (int) $day);
2247
	$dt->setTime((int) $hour, (int) $minute, (int) $second);
2248
	$date = $dt->getTimestamp(); // should include daylight saving time
2249
	//var_dump($date);
2250
	return $date;
2251
}
2252
2253
2254
/**
2255
 *  Return date for now. In most cases, we use this function without parameters (that means GMT time).
2256
 *
2257
 *  @param	string		$mode	'gmt' => we return GMT timestamp,
2258
 * 								'tzserver' => we add the PHP server timezone
2259
 *  							'tzref' => we add the company timezone
2260
 * 								'tzuser' => we add the user timezone
2261
 *	@return int   $date	Timestamp
2262
 */
2263
function dol_now($mode = 'gmt')
2264
{
2265
	$ret = 0;
2266
2267
	if ($mode == 'gmt') $ret = time(); // Time for now at greenwich.
2268
	elseif ($mode == 'tzserver')		// Time for now with PHP server timezone added
2269
	{
2270
		require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
2271
		$tzsecond = getServerTimeZoneInt('now'); // Contains tz+dayling saving time
2272
		$ret = (int) (dol_now('gmt') + ($tzsecond * 3600));
2273
	} /*elseif ($mode == 'tzref')				// Time for now with parent company timezone is added
2274
	{
2275
		require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
2276
		$tzsecond=getParentCompanyTimeZoneInt();    // Contains tz+dayling saving time
2277
		$ret=dol_now('gmt')+($tzsecond*3600);
2278
	}*/
2279
	elseif ($mode == 'tzuser')				// Time for now with user timezone added
2280
	{
2281
		//print 'time: '.time();
2282
		$offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60;
2283
		$offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60;
2284
		$ret = (int) (dol_now('gmt') + ($offsettz + $offsetdst));
2285
	}
2286
2287
	return $ret;
2288
}
2289
2290
2291
/**
2292
 * Return string with formated size
2293
 *
2294
 * @param	int		$size		Size to print
2295
 * @param	int		$shortvalue	Tell if we want long value to use another unit (Ex: 1.5Kb instead of 1500b)
2296
 * @param	int		$shortunit	Use short label of size unit (for example 'b' instead of 'bytes')
2297
 * @return	string				Link
2298
 */
2299
function dol_print_size($size, $shortvalue = 0, $shortunit = 0)
2300
{
2301
	global $conf, $langs;
2302
	$level = 1024;
2303
2304
	if (!empty($conf->dol_optimize_smallscreen)) $shortunit = 1;
2305
2306
	// Set value text
2307
	if (empty($shortvalue) || $size < ($level * 10))
2308
	{
2309
		$ret = $size;
2310
		$textunitshort = $langs->trans("b");
2311
		$textunitlong = $langs->trans("Bytes");
2312
	} else {
2313
		$ret = round($size / $level, 0);
2314
		$textunitshort = $langs->trans("Kb");
2315
		$textunitlong = $langs->trans("KiloBytes");
2316
	}
2317
	// Use long or short text unit
2318
	if (empty($shortunit)) { $ret .= ' '.$textunitlong; } else { $ret .= ' '.$textunitshort; }
2319
2320
	return $ret;
2321
}
2322
2323
/**
2324
 * Show Url link
2325
 *
2326
 * @param	string		$url		Url to show
2327
 * @param	string		$target		Target for link
2328
 * @param	int			$max		Max number of characters to show
2329
 * @param	int			$withpicto	With picto
2330
 * @return	string					HTML Link
2331
 */
2332
function dol_print_url($url, $target = '_blank', $max = 32, $withpicto = 0)
2333
{
2334
	global $langs;
2335
2336
	if (empty($url)) return '';
2337
2338
	$link = '<a href="';
2339
	if (!preg_match('/^http/i', $url)) $link .= 'http://';
2340
	$link .= $url;
2341
	$link .= '"';
2342
	if ($target) $link .= ' target="'.$target.'"';
2343
	$link .= '>';
2344
	if (!preg_match('/^http/i', $url)) $link .= 'http://';
2345
	$link .= dol_trunc($url, $max);
2346
	$link .= '</a>';
2347
	return '<div class="nospan float" style="margin-right: 10px">'.($withpicto ?img_picto($langs->trans("Url"), 'globe').' ' : '').$link.'</div>';
2348
}
2349
2350
/**
2351
 * Show EMail link formatted for HTML output.
2352
 *
2353
 * @param	string		$email			EMail to show (only email, without 'Name of recipient' before)
2354
 * @param 	int			$cid 			Id of contact if known
2355
 * @param 	int			$socid 			Id of third party if known
2356
 * @param 	int			$addlink		0=no link, 1=email has a html email link (+ link to create action if constant AGENDA_ADDACTIONFOREMAIL is on)
2357
 * @param	int			$max			Max number of characters to show
2358
 * @param	int			$showinvalid	1=Show warning if syntax email is wrong
2359
 * @param	int			$withpicto		Show picto
2360
 * @return	string						HTML Link
2361
 */
2362
function dol_print_email($email, $cid = 0, $socid = 0, $addlink = 0, $max = 64, $showinvalid = 1, $withpicto = 0)
2363
{
2364
	global $conf, $user, $langs, $hookmanager;
2365
2366
	$newemail = dol_escape_htmltag($email);
2367
2368
	if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER) && $withpicto) $withpicto = 0;
2369
2370
	if (empty($email)) return '&nbsp;';
2371
2372
	if (!empty($addlink))
2373
	{
2374
		$newemail = '<a style="text-overflow: ellipsis;" href="';
2375
		if (!preg_match('/^mailto:/i', $email)) $newemail .= 'mailto:';
2376
		$newemail .= $email;
2377
		$newemail .= '">';
2378
		$newemail .= dol_trunc($email, $max);
2379
		$newemail .= '</a>';
2380
		if ($showinvalid && !isValidEmail($email))
2381
		{
2382
			$langs->load("errors");
2383
			$newemail .= img_warning($langs->trans("ErrorBadEMail", $email));
2384
		}
2385
2386
		if (($cid || $socid) && !empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create)
2387
		{
2388
			$type = 'AC_EMAIL'; $link = '';
2389
			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>';
2390
			if ($link) $newemail = '<div>'.$newemail.' '.$link.'</div>';
2391
		}
2392
	} else {
2393
		if ($showinvalid && !isValidEmail($email))
2394
		{
2395
			$langs->load("errors");
2396
			$newemail .= img_warning($langs->trans("ErrorBadEMail", $email));
2397
		}
2398
	}
2399
2400
	//$rep = '<div class="nospan" style="margin-right: 10px">';
2401
	$rep = ($withpicto ? img_picto($langs->trans("EMail").' : '.$email, 'object_email.png').' ' : '').$newemail;
2402
	//$rep .= '</div>';
2403
	if ($hookmanager) {
2404
		$parameters = array('cid' => $cid, 'socid' => $socid, 'addlink' => $addlink, 'picto' => $withpicto);
2405
		$reshook = $hookmanager->executeHooks('printEmail', $parameters, $email);
2406
		if ($reshook > 0) {
2407
			$rep = '';
2408
		}
2409
		$rep .= $hookmanager->resPrint;
2410
	}
2411
2412
	return $rep;
2413
}
2414
2415
/**
2416
 * Get array of social network dictionary
2417
 *
2418
 * @return  array       Array of Social Networks Dictionary
2419
 */
2420
function getArrayOfSocialNetworks()
2421
{
2422
	global $conf, $db;
2423
	$sql = "SELECT rowid, code, label, url, icon, active FROM ".MAIN_DB_PREFIX."c_socialnetworks";
2424
	$sql .= " WHERE entity=".$conf->entity;
2425
	$socialnetworks = array();
2426
	$resql = $db->query($sql);
2427
	if ($resql) {
2428
		while ($obj = $db->fetch_object($resql)) {
2429
			$socialnetworks[$obj->code] = array(
2430
				'rowid' => $obj->rowid,
2431
				'label' => $obj->label,
2432
				'url' => $obj->url,
2433
				'icon' => $obj->icon,
2434
				'active' => $obj->active,
2435
			);
2436
		}
2437
	}
2438
	return $socialnetworks;
2439
}
2440
2441
/**
2442
 * Show social network link
2443
 *
2444
 * @param	string		$value				Skype to show (only skype, without 'Name of recipient' before)
2445
 * @param	int 		$cid 				Id of contact if known
2446
 * @param	int 		$socid 				Id of third party if known
2447
 * @param	string 		$type				'skype','facebook',...
2448
 * @param	array		$dictsocialnetworks socialnetworks availables
2449
 * @return	string							HTML Link
2450
 */
2451
function dol_print_socialnetworks($value, $cid, $socid, $type, $dictsocialnetworks = array())
2452
{
2453
	global $conf, $user, $langs;
2454
2455
	$htmllink = $value;
2456
2457
	if (empty($value)) return '&nbsp;';
2458
2459
	if (!empty($type)) {
2460
		$htmllink = '<div class="divsocialnetwork inline-block valignmiddle">';
2461
		// TODO use dictionary definition for picto $dictsocialnetworks[$type]['icon']
2462
		$htmllink .= img_picto($langs->trans(dol_ucfirst($type)), $type.'.png', '', false, 0, 0, '', 'paddingright', 0);
2463
		if ($type == 'skype') {
2464
			$htmllink .= $value;
2465
			$htmllink .= '&nbsp;';
2466
			$htmllink .= '<a href="skype:';
2467
			$htmllink .= $value;
2468
			$htmllink .= '?call" alt="'.$langs->trans("Call").'&nbsp;'.$value.'" title="'.$langs->trans("Call").'&nbsp;'.$value.'">';
2469
			$htmllink .= '<img src="'.DOL_URL_ROOT.'/theme/common/skype_callbutton.png" border="0">';
2470
			$htmllink .= '</a><a href="skype:';
2471
			$htmllink .= $value;
2472
			$htmllink .= '?chat" alt="'.$langs->trans("Chat").'&nbsp;'.$value.'" title="'.$langs->trans("Chat").'&nbsp;'.$value.'">';
2473
			$htmllink .= '<img class="paddingleft" src="'.DOL_URL_ROOT.'/theme/common/skype_chatbutton.png" border="0">';
2474
			$htmllink .= '</a>';
2475
			if (($cid || $socid) && !empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create) {
2476
				$addlink = 'AC_SKYPE';
2477
				$link = '';
2478
				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>';
2479
				$htmllink .= ($link ? ' '.$link : '');
2480
			}
2481
		} else {
2482
			if (!empty($dictsocialnetworks[$type]['url'])) {
2483
				$link = str_replace('{socialid}', $value, $dictsocialnetworks[$type]['url']);
2484
				$htmllink .= '&nbsp;<a href="'.$link.'" target="_blank">'.$value.'</a>';
2485
			} else {
2486
				$htmllink .= $value;
2487
			}
2488
		}
2489
		$htmllink .= '</div>';
2490
	} else {
2491
		$langs->load("errors");
2492
		$htmllink .= img_warning($langs->trans("ErrorBadSocialNetworkValue", $value));
2493
	}
2494
	return $htmllink;
2495
}
2496
2497
/**
2498
 * 	Format phone numbers according to country
2499
 *
2500
 * 	@param  string  $phone          Phone number to format
2501
 * 	@param  string  $countrycode    Country code to use for formatting
2502
 * 	@param 	int		$cid 		    Id of contact if known
2503
 * 	@param 	int		$socid          Id of third party if known
2504
 * 	@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)
2505
 * 	@param 	string	$separ 		    Separation between numbers for a better visibility example : xx.xx.xx.xx.xx
2506
 *  @param	string  $withpicto      Show picto
2507
 *  @param	string	$titlealt	    Text to show on alt
2508
 *  @param  int     $adddivfloat    Add div float around phone.
2509
 * 	@return string 				    Formated phone number
2510
 */
2511
function dol_print_phone($phone, $countrycode = '', $cid = 0, $socid = 0, $addlink = '', $separ = "&nbsp;", $withpicto = '', $titlealt = '', $adddivfloat = 0)
2512
{
2513
	global $conf, $user, $langs, $mysoc, $hookmanager;
2514
2515
	// Clean phone parameter
2516
	$phone = preg_replace("/[\s.-]/", "", trim($phone));
2517
	if (empty($phone)) { return ''; }
2518
	if (!empty($conf->global->MAIN_PHONE_SEPAR)) $separ = $conf->global->MAIN_PHONE_SEPAR;
2519
	if (empty($countrycode)) $countrycode = $mysoc->country_code;
2520
2521
	// Short format for small screens
2522
	if ($conf->dol_optimize_smallscreen) $separ = '';
2523
2524
	$newphone = $phone;
2525
	if (strtoupper($countrycode) == "FR")
2526
	{
2527
		// France
2528
		if (dol_strlen($phone) == 10) {
2529
			$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);
2530
		} elseif (dol_strlen($phone) == 7)
2531
		{
2532
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 2).$separ.substr($newphone, 5, 2);
2533
		} elseif (dol_strlen($phone) == 9)
2534
		{
2535
			$newphone = substr($newphone, 0, 2).$separ.substr($newphone, 2, 3).$separ.substr($newphone, 5, 2).$separ.substr($newphone, 7, 2);
2536
		} elseif (dol_strlen($phone) == 11)
2537
		{
2538
			$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);
2539
		} elseif (dol_strlen($phone) == 12)
2540
		{
2541
			$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);
2542
		}
2543
	} elseif (strtoupper($countrycode) == "CA")
2544
	{
2545
		if (dol_strlen($phone) == 10) {
2546
			$newphone = ($separ != '' ? '(' : '').substr($newphone, 0, 3).($separ != '' ? ')' : '').$separ.substr($newphone, 3, 3).($separ != '' ? '-' : '').substr($newphone, 6, 4);
2547
		}
2548
	} elseif (strtoupper($countrycode) == "PT")
2549
	{//Portugal
2550
		if (dol_strlen($phone) == 13)
2551
		{//ex: +351_ABC_DEF_GHI
2552
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
2553
		}
2554
	} elseif (strtoupper($countrycode) == "SR")
2555
	{//Suriname
2556
		if (dol_strlen($phone) == 10)
2557
		{//ex: +597_ABC_DEF
2558
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3);
2559
		} elseif (dol_strlen($phone) == 11)
2560
		{//ex: +597_ABC_DEFG
2561
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 4);
2562
		}
2563
	} elseif (strtoupper($countrycode) == "DE")
2564
	{//Allemagne
2565
		if (dol_strlen($phone) == 14)
2566
		{//ex:  +49_ABCD_EFGH_IJK
2567
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 4).$separ.substr($newphone, 7, 4).$separ.substr($newphone, 11, 3);
2568
		} elseif (dol_strlen($phone) == 13)
2569
		{//ex: +49_ABC_DEFG_HIJ
2570
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 4).$separ.substr($newphone, 10, 3);
2571
		}
2572
	} elseif (strtoupper($countrycode) == "ES")
2573
	{//Espagne
2574
		if (dol_strlen($phone) == 12)
2575
		{//ex:  +34_ABC_DEF_GHI
2576
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
2577
		}
2578
	} elseif (strtoupper($countrycode) == "BF")
2579
	{// Burkina Faso
2580
		if (dol_strlen($phone) == 12)
2581
		{//ex :  +22 A BC_DE_FG_HI
2582
			$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);
2583
		}
2584
	} elseif (strtoupper($countrycode) == "RO")
2585
	{// Roumanie
2586
		if (dol_strlen($phone) == 12)
2587
		{//ex :  +40 AB_CDE_FG_HI
2588
			$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);
2589
		}
2590
	} elseif (strtoupper($countrycode) == "TR")
2591
	{//Turquie
2592
		if (dol_strlen($phone) == 13)
2593
		{//ex :  +90 ABC_DEF_GHIJ
2594
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 4);
2595
		}
2596
	} elseif (strtoupper($countrycode) == "US")
2597
	{//Etat-Unis
2598
		if (dol_strlen($phone) == 12)
2599
		{//ex: +1 ABC_DEF_GHIJ
2600
			$newphone = substr($newphone, 0, 2).$separ.substr($newphone, 2, 3).$separ.substr($newphone, 5, 3).$separ.substr($newphone, 8, 4);
2601
		}
2602
	} elseif (strtoupper($countrycode) == "MX")
2603
	{//Mexique
2604
		if (dol_strlen($phone) == 12)
2605
		{//ex: +52 ABCD_EFG_HI
2606
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 4).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 2);
2607
		} elseif (dol_strlen($phone) == 11)
2608
		{//ex: +52 AB_CD_EF_GH
2609
			$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);
2610
		} elseif (dol_strlen($phone) == 13)
2611
		{//ex: +52 ABC_DEF_GHIJ
2612
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 4);
2613
		}
2614
	} elseif (strtoupper($countrycode) == "ML")
2615
	{//Mali
2616
		if (dol_strlen($phone) == 12)
2617
		{//ex: +223 AB_CD_EF_GH
2618
			$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);
2619
		}
2620
	} elseif (strtoupper($countrycode) == "TH")
2621
	{//Thaïlande
2622
		if (dol_strlen($phone) == 11)
2623
		{//ex: +66_ABC_DE_FGH
2624
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 2).$separ.substr($newphone, 8, 3);
2625
		} elseif (dol_strlen($phone) == 12)
2626
		{//ex: +66_A_BCD_EF_GHI
2627
			$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);
2628
		}
2629
	} elseif (strtoupper($countrycode) == "MU")
2630
	{
2631
		//Maurice
2632
		if (dol_strlen($phone) == 11)
2633
		{//ex: +230_ABC_DE_FG
2634
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 2).$separ.substr($newphone, 9, 2);
2635
		} elseif (dol_strlen($phone) == 12)
2636
		{//ex: +230_ABCD_EF_GH
2637
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 4).$separ.substr($newphone, 8, 2).$separ.substr($newphone, 10, 2);
2638
		}
2639
	} elseif (strtoupper($countrycode) == "ZA")
2640
	{//Afrique du sud
2641
		if (dol_strlen($phone) == 12)
2642
		{//ex: +27_AB_CDE_FG_HI
2643
			$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);
2644
		}
2645
	} elseif (strtoupper($countrycode) == "SY")
2646
	{//Syrie
2647
		if (dol_strlen($phone) == 12)
2648
		{//ex: +963_AB_CD_EF_GH
2649
			$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);
2650
		} elseif (dol_strlen($phone) == 13)
2651
		{//ex: +963_AB_CD_EF_GHI
2652
			$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);
2653
		}
2654
	} elseif (strtoupper($countrycode) == "AE")
2655
	{//Emirats Arabes Unis
2656
		if (dol_strlen($phone) == 12)
2657
		{//ex: +971_ABC_DEF_GH
2658
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 2);
2659
		} elseif (dol_strlen($phone) == 13)
2660
		{//ex: +971_ABC_DEF_GHI
2661
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
2662
		} elseif (dol_strlen($phone) == 14)
2663
		{//ex: +971_ABC_DEF_GHIK
2664
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 4);
2665
		}
2666
	} elseif (strtoupper($countrycode) == "DZ")
2667
	{//Algérie
2668
		if (dol_strlen($phone) == 13)
2669
		{//ex: +213_ABC_DEF_GHI
2670
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
2671
		}
2672
	} elseif (strtoupper($countrycode) == "BE")
2673
	{//Belgique
2674
		if (dol_strlen($phone) == 11)
2675
		{//ex: +32_ABC_DE_FGH
2676
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 2).$separ.substr($newphone, 8, 3);
2677
		} elseif (dol_strlen($phone) == 12)
2678
		{//ex: +32_ABC_DEF_GHI
2679
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
2680
		}
2681
	} elseif (strtoupper($countrycode) == "PF")
2682
	{//Polynésie française
2683
		if (dol_strlen($phone) == 12)
2684
		{//ex: +689_AB_CD_EF_GH
2685
			$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);
2686
		}
2687
	} elseif (strtoupper($countrycode) == "CO")
2688
	{//Colombie
2689
		if (dol_strlen($phone) == 13)
2690
		{//ex: +57_ABC_DEF_GH_IJ
2691
			$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);
2692
		}
2693
	} elseif (strtoupper($countrycode) == "JO")
2694
	{//Jordanie
2695
		if (dol_strlen($phone) == 12)
2696
		{//ex: +962_A_BCD_EF_GH
2697
			$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);
2698
		}
2699
	} elseif (strtoupper($countrycode) == "JM")
2700
	{//Jamaïque
2701
		if (dol_strlen($newphone) == 12)
2702
		{//ex: +1867_ABC_DEFG
2703
			$newphone = substr($newphone, 0, 5).$separ.substr($newphone, 5, 3).$separ.substr($newphone, 8, 4);
2704
		}
2705
	} elseif (strtoupper($countrycode) == "MG")
2706
	{//Madagascar
2707
		if (dol_strlen($phone) == 13)
2708
		{//ex: +261_AB_CD_EF_GHI
2709
			$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);
2710
		}
2711
	} elseif (strtoupper($countrycode) == "GB")
2712
	{//Royaume uni
2713
		if (dol_strlen($phone) == 13)
2714
		{//ex: +44_ABCD_EFG_HIJ
2715
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 4).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
2716
		}
2717
	} elseif (strtoupper($countrycode) == "CH")
2718
	{//Suisse
2719
		if (dol_strlen($phone) == 12)
2720
		{//ex: +41_AB_CDE_FG_HI
2721
			$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);
2722
		} elseif (dol_strlen($phone) == 15)
2723
		{// +41_AB_CDE_FGH_IJKL
2724
			$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);
2725
		}
2726
	} elseif (strtoupper($countrycode) == "TN")
2727
	{//Tunisie
2728
		if (dol_strlen($phone) == 12)
2729
		{//ex: +216_AB_CDE_FGH
2730
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 2).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
2731
		}
2732
	} elseif (strtoupper($countrycode) == "GF")
2733
	{//Guyane francaise
2734
		if (dol_strlen($phone) == 13)
2735
		{//ex: +594_ABC_DE_FG_HI  (ABC=594 de nouveau)
2736
			$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);
2737
		}
2738
	} elseif (strtoupper($countrycode) == "GP")
2739
	{//Guadeloupe
2740
		if (dol_strlen($phone) == 13)
2741
		{//ex: +590_ABC_DE_FG_HI  (ABC=590 de nouveau)
2742
			$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);
2743
		}
2744
	} elseif (strtoupper($countrycode) == "MQ")
2745
	{//Martinique
2746
		if (dol_strlen($phone) == 13)
2747
		{//ex: +596_ABC_DE_FG_HI  (ABC=596 de nouveau)
2748
			$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);
2749
		}
2750
	} elseif (strtoupper($countrycode) == "IT")
2751
	{//Italie
2752
		if (dol_strlen($phone) == 12)
2753
		{//ex: +39_ABC_DEF_GHI
2754
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
2755
		} elseif (dol_strlen($phone) == 13)
2756
		{//ex: +39_ABC_DEF_GH_IJ
2757
			$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);
2758
		}
2759
	} elseif (strtoupper($countrycode) == "AU")
2760
	{
2761
		//Australie
2762
		if (dol_strlen($phone) == 12)
2763
		{
2764
			//ex: +61_A_BCDE_FGHI
2765
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 1).$separ.substr($newphone, 4, 4).$separ.substr($newphone, 8, 4);
2766
		}
2767
	}
2768
	if (!empty($addlink))	// Link on phone number (+ link to add action if conf->global->AGENDA_ADDACTIONFORPHONE set)
2769
	{
2770
		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
2771
		{
2772
			$newphoneform = $newphone;
2773
			$newphone = '<a href="tel:'.$phone.'"';
2774
			$newphone .= '>'.$newphoneform.'</a>';
2775
		} elseif (!empty($conf->clicktodial->enabled) && $addlink == 'AC_TEL')		// If click to dial, we use click to dial url
2776
		{
2777
			if (empty($user->clicktodial_loaded)) $user->fetch_clicktodial();
2778
2779
			// Define urlmask
2780
			$urlmask = 'ErrorClickToDialModuleNotConfigured';
2781
			if (!empty($conf->global->CLICKTODIAL_URL)) $urlmask = $conf->global->CLICKTODIAL_URL;
2782
			if (!empty($user->clicktodial_url)) $urlmask = $user->clicktodial_url;
2783
2784
			$clicktodial_poste = (!empty($user->clicktodial_poste) ?urlencode($user->clicktodial_poste) : '');
2785
			$clicktodial_login = (!empty($user->clicktodial_login) ?urlencode($user->clicktodial_login) : '');
2786
			$clicktodial_password = (!empty($user->clicktodial_password) ?urlencode($user->clicktodial_password) : '');
2787
			// This line is for backward compatibility
2788
			$url = sprintf($urlmask, urlencode($phone), $clicktodial_poste, $clicktodial_login, $clicktodial_password);
2789
			// Thoose lines are for substitution
2790
			$substitarray = array('__PHONEFROM__'=>$clicktodial_poste,
2791
								'__PHONETO__'=>urlencode($phone),
2792
								'__LOGIN__'=>$clicktodial_login,
2793
								'__PASS__'=>$clicktodial_password);
2794
			$url = make_substitutions($url, $substitarray);
2795
			$newphonesav = $newphone;
2796
			$newphone = '<a href="'.$url.'"';
2797
			if (!empty($conf->global->CLICKTODIAL_FORCENEWTARGET)) $newphone .= ' target="_blank"';
2798
			$newphone .= '>'.$newphonesav.'</a>';
2799
		}
2800
2801
		//if (($cid || $socid) && ! empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create)
2802
		if (!empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create)
2803
		{
2804
			$type = 'AC_TEL'; $link = '';
2805
			if ($addlink == 'AC_FAX') $type = 'AC_FAX';
2806
			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>';
2807
			if ($link) $newphone = '<div>'.$newphone.' '.$link.'</div>';
2808
		}
2809
	}
2810
2811
	if (empty($titlealt))
2812
	{
2813
		$titlealt = ($withpicto == 'fax' ? $langs->trans("Fax") : $langs->trans("Phone"));
2814
	}
2815
	$rep = '';
2816
2817
	if ($hookmanager) {
2818
		$parameters = array('countrycode' => $countrycode, 'cid' => $cid, 'socid' => $socid, 'titlealt' => $titlealt, 'picto' => $withpicto);
2819
		$reshook = $hookmanager->executeHooks('printPhone', $parameters, $phone);
2820
		$rep .= $hookmanager->resPrint;
2821
	}
2822
	if (empty($reshook))
2823
	{
2824
		$picto = '';
2825
		if ($withpicto) {
2826
			if ($withpicto == 'fax') {
2827
				$picto = 'phoning_fax';
2828
			} elseif ($withpicto == 'phone') {
2829
				$picto = 'phoning';
2830
			} elseif ($withpicto == 'mobile') {
2831
				$picto = 'phoning_mobile';
2832
			} else {
2833
				$picto = '';
2834
			}
2835
		}
2836
		if ($adddivfloat) $rep .= '<div class="nospan float" style="margin-right: 10px">';
2837
		else $rep .= '<span style="margin-right: 10px;">';
2838
		$rep .= ($withpicto ?img_picto($titlealt, 'object_'.$picto.'.png').' ' : '').$newphone;
2839
		if ($adddivfloat) $rep .= '</div>';
2840
		else $rep .= '</span>';
2841
	}
2842
2843
	return $rep;
2844
}
2845
2846
/**
2847
 * 	Return an IP formated to be shown on screen
2848
 *
2849
 * 	@param	string	$ip			IP
2850
 * 	@param	int		$mode		0=return IP + country/flag, 1=return only country/flag, 2=return only IP
2851
 * 	@return string 				Formated IP, with country if GeoIP module is enabled
2852
 */
2853
function dol_print_ip($ip, $mode = 0)
2854
{
2855
	global $conf, $langs;
2856
2857
	$ret = '';
2858
2859
	if (empty($mode)) $ret .= $ip;
2860
2861
	if ($mode != 2)
2862
	{
2863
		$countrycode = dolGetCountryCodeFromIp($ip);
2864
		if ($countrycode)	// If success, countrycode is us, fr, ...
2865
		{
2866
			if (file_exists(DOL_DOCUMENT_ROOT.'/theme/common/flags/'.$countrycode.'.png'))
2867
			{
2868
				$ret .= ' '.img_picto($countrycode.' '.$langs->trans("AccordingToGeoIPDatabase"), DOL_URL_ROOT.'/theme/common/flags/'.$countrycode.'.png', '', 1);
2869
			} else $ret .= ' ('.$countrycode.')';
2870
		} else {
2871
			// Nothing
2872
		}
2873
	}
2874
2875
	return $ret;
2876
}
2877
2878
/**
2879
 * Return the IP of remote user.
2880
 * Take HTTP_X_FORWARDED_FOR (defined when using proxy)
2881
 * Then HTTP_CLIENT_IP if defined (rare)
2882
 * Then REMOTE_ADDR (no way to be modified by user but may be wrong if user is using a proxy)
2883
 *
2884
 * @return	string		Ip of remote user.
2885
 */
2886
function getUserRemoteIP()
2887
{
2888
	if (empty($_SERVER['HTTP_X_FORWARDED_FOR']) || preg_match('/[^0-9\.\:,\[\]]/', $_SERVER['HTTP_X_FORWARDED_FOR'])) {
2889
		if (empty($_SERVER['HTTP_CLIENT_IP']) || preg_match('/[^0-9\.\:,\[\]]/', $_SERVER['HTTP_CLIENT_IP'])) {
2890
			if (empty($_SERVER["HTTP_CF_CONNECTING_IP"])) {
2891
				$ip = (empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR']);	// value may have been forged by client
2892
			} else {
2893
				$ip = $_SERVER["HTTP_CF_CONNECTING_IP"];	// value here may have been forged by client
2894
			}
2895
		} else {
2896
			$ip = $_SERVER['HTTP_CLIENT_IP']; // value is clean here but may have been forged by proxy
2897
		}
2898
	} else {
2899
		$ip = $_SERVER['HTTP_X_FORWARDED_FOR']; // value is clean here but may have been forged by proxy
2900
	}
2901
	return $ip;
2902
}
2903
2904
/**
2905
 * Return if we are using a HTTPS connexion
2906
 * Check HTTPS (no way to be modified by user but may be empty or wrong if user is using a proxy)
2907
 * Take HTTP_X_FORWARDED_PROTO (defined when using proxy)
2908
 * Then HTTP_X_FORWARDED_SSL
2909
 *
2910
 * @return	boolean		True if user is using HTTPS
2911
 */
2912
function getIsHTTPS()
2913
{
2914
	$isSecure = false;
2915
	if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
2916
		$isSecure = true;
2917
	}
2918
	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') {
2919
		$isSecure = true;
2920
	}
2921
	return $isSecure;
2922
}
2923
2924
/**
2925
 * 	Return a country code from IP. Empty string if not found.
2926
 *
2927
 * 	@param	string	$ip			IP
2928
 * 	@return string 				Country code ('us', 'fr', ...)
2929
 */
2930
function dolGetCountryCodeFromIp($ip)
2931
{
2932
	global $conf;
2933
2934
	$countrycode = '';
2935
2936
	if (!empty($conf->geoipmaxmind->enabled))
2937
	{
2938
		$datafile = $conf->global->GEOIPMAXMIND_COUNTRY_DATAFILE;
2939
		//$ip='24.24.24.24';
2940
		//$datafile='/usr/share/GeoIP/GeoIP.dat';    Note that this must be downloaded datafile (not same than datafile provided with ubuntu packages)
2941
		include_once DOL_DOCUMENT_ROOT.'/core/class/dolgeoip.class.php';
2942
		$geoip = new DolGeoIP('country', $datafile);
2943
		//print 'ip='.$ip.' databaseType='.$geoip->gi->databaseType." GEOIP_CITY_EDITION_REV1=".GEOIP_CITY_EDITION_REV1."\n";
2944
		$countrycode = $geoip->getCountryCodeFromIP($ip);
2945
	}
2946
2947
	return $countrycode;
2948
}
2949
2950
2951
/**
2952
 *  Return country code for current user.
2953
 *  If software is used inside a local network, detection may fails (we need a public ip)
2954
 *
2955
 *  @return     string      Country code (fr, es, it, us, ...)
2956
 */
2957
function dol_user_country()
2958
{
2959
	global $conf, $langs, $user;
2960
2961
	//$ret=$user->xxx;
2962
	$ret = '';
2963
	if (!empty($conf->geoipmaxmind->enabled))
2964
	{
2965
		$ip = getUserRemoteIP();
2966
		$datafile = $conf->global->GEOIPMAXMIND_COUNTRY_DATAFILE;
2967
		//$ip='24.24.24.24';
2968
		//$datafile='E:\Mes Sites\Web\Admin1\awstats\maxmind\GeoIP.dat';
2969
		include_once DOL_DOCUMENT_ROOT.'/core/class/dolgeoip.class.php';
2970
		$geoip = new DolGeoIP('country', $datafile);
2971
		$countrycode = $geoip->getCountryCodeFromIP($ip);
2972
		$ret = $countrycode;
2973
	}
2974
	return $ret;
2975
}
2976
2977
/**
2978
 *  Format address string
2979
 *
2980
 *  @param	string	$address    Address string, already formatted with dol_format_address()
2981
 *  @param  int		$htmlid     Html ID (for example 'gmap')
2982
 *  @param  int		$element    'thirdparty'|'contact'|'member'|'other'
2983
 *  @param  int		$id         Id of object
2984
 *  @param	int		$noprint	No output. Result is the function return
2985
 *  @param  string  $charfornl  Char to use instead of nl2br. '' means we use a standad nl2br.
2986
 *  @return string|void			Nothing if noprint is 0, formatted address if noprint is 1
2987
 *  @see dol_format_address()
2988
 */
2989
function dol_print_address($address, $htmlid, $element, $id, $noprint = 0, $charfornl = '')
2990
{
2991
	global $conf, $user, $langs, $hookmanager;
2992
2993
	$out = '';
2994
2995
	if ($address)
2996
	{
2997
		if ($hookmanager) {
2998
			$parameters = array('element' => $element, 'id' => $id);
2999
			$reshook = $hookmanager->executeHooks('printAddress', $parameters, $address);
3000
			$out .= $hookmanager->resPrint;
3001
		}
3002
		if (empty($reshook))
3003
		{
3004
			if (empty($charfornl)) $out .= nl2br($address);
3005
			else $out .= preg_replace('/[\r\n]+/', $charfornl, $address);
3006
3007
			// TODO Remove this block, we can add this using the hook now
3008
			$showgmap = $showomap = 0;
3009
			if (($element == 'thirdparty' || $element == 'societe') && !empty($conf->google->enabled) && !empty($conf->global->GOOGLE_ENABLE_GMAPS)) $showgmap = 1;
3010
			if ($element == 'contact' && !empty($conf->google->enabled) && !empty($conf->global->GOOGLE_ENABLE_GMAPS_CONTACTS)) $showgmap = 1;
3011
			if ($element == 'member' && !empty($conf->google->enabled) && !empty($conf->global->GOOGLE_ENABLE_GMAPS_MEMBERS)) $showgmap = 1;
3012
			if (($element == 'thirdparty' || $element == 'societe') && !empty($conf->openstreetmap->enabled) && !empty($conf->global->OPENSTREETMAP_ENABLE_MAPS)) $showomap = 1;
3013
			if ($element == 'contact' && !empty($conf->openstreetmap->enabled) && !empty($conf->global->OPENSTREETMAP_ENABLE_MAPS_CONTACTS)) $showomap = 1;
3014
			if ($element == 'member' && !empty($conf->openstreetmap->enabled) && !empty($conf->global->OPENSTREETMAP_ENABLE_MAPS_MEMBERS)) $showomap = 1;
3015
			if ($showgmap)
3016
			{
3017
				$url = dol_buildpath('/google/gmaps.php?mode='.$element.'&id='.$id, 1);
3018
				$out .= ' <a href="'.$url.'" target="_gmaps"><img id="'.$htmlid.'" class="valigntextbottom" src="'.DOL_URL_ROOT.'/theme/common/gmap.png"></a>';
3019
			}
3020
			if ($showomap)
3021
			{
3022
				$url = dol_buildpath('/openstreetmap/maps.php?mode='.$element.'&id='.$id, 1);
3023
				$out .= ' <a href="'.$url.'" target="_gmaps"><img id="'.$htmlid.'_openstreetmap" class="valigntextbottom" src="'.DOL_URL_ROOT.'/theme/common/gmap.png"></a>';
3024
			}
3025
		}
3026
	}
3027
	if ($noprint) return $out;
3028
	else print $out;
3029
}
3030
3031
3032
/**
3033
 *	Return true if email syntax is ok.
3034
 *
3035
 *	@param	    string		$address    			email (Ex: "[email protected]", "John Do <[email protected]>")
3036
 *  @param		int			$acceptsupervisorkey	If 1, the special string '__SUPERVISOREMAIL__' is also accepted as valid
3037
 *	@return     boolean     						true if email syntax is OK, false if KO or empty string
3038
 *  @see isValidMXRecord()
3039
 */
3040
function isValidEmail($address, $acceptsupervisorkey = 0)
3041
{
3042
	if ($acceptsupervisorkey && $address == '__SUPERVISOREMAIL__') return true;
3043
	if (filter_var($address, FILTER_VALIDATE_EMAIL)) return true;
3044
3045
	return false;
3046
}
3047
3048
/**
3049
 *	Return if the domain name has a valid MX record.
3050
 *  WARNING: This need function idn_to_ascii, checkdnsrr and getmxrr
3051
 *
3052
 *	@param	    string		$domain	    			Domain name (Ex: "yahoo.com", "yhaoo.com", "dolibarr.fr")
3053
 *	@return     int     							-1 if error (function not available), 0=Not valid, 1=Valid
3054
 *  @see isValidEmail()
3055
 */
3056
function isValidMXRecord($domain)
3057
{
3058
	if (function_exists('idn_to_ascii') && function_exists('checkdnsrr'))
3059
	{
3060
		if (!checkdnsrr(idn_to_ascii($domain), 'MX'))
3061
		{
3062
			return 0;
3063
		}
3064
		if (function_exists('getmxrr'))
3065
		{
3066
			$mxhosts = array();
3067
			$weight = array();
3068
			getmxrr(idn_to_ascii($domain), $mxhosts, $weight);
3069
			if (count($mxhosts) > 1) return 1;
3070
			if (count($mxhosts) == 1 && !empty($mxhosts[0])) return 1;
3071
3072
			return 0;
3073
		}
3074
	}
3075
	return -1;
3076
}
3077
3078
/**
3079
 *  Return true if phone number syntax is ok
3080
 *  TODO Decide what to do with this
3081
 *
3082
 *  @param	string		$phone		phone (Ex: "0601010101")
3083
 *  @return boolean     			true if phone syntax is OK, false if KO or empty string
3084
 */
3085
function isValidPhone($phone)
3086
{
3087
	return true;
3088
}
3089
3090
3091
/**
3092
 * Make a strlen call. Works even if mbstring module not enabled
3093
 *
3094
 * @param   string		$string				String to calculate length
3095
 * @param   string		$stringencoding		Encoding of string
3096
 * @return  int								Length of string
3097
 */
3098
function dol_strlen($string, $stringencoding = 'UTF-8')
3099
{
3100
	if (function_exists('mb_strlen')) return mb_strlen($string, $stringencoding);
3101
	else return strlen($string);
3102
}
3103
3104
/**
3105
 * Make a substring. Works even if mbstring module is not enabled for better compatibility.
3106
 *
3107
 * @param	string	$string				String to scan
3108
 * @param	string	$start				Start position
3109
 * @param	int		$length				Length (in nb of characters or nb of bytes depending on trunconbytes param)
3110
 * @param   string	$stringencoding		Page code used for input string encoding
3111
 * @param	int		$trunconbytes		1=Length is max of bytes instead of max of characters
3112
 * @return  string						substring
3113
 */
3114
function dol_substr($string, $start, $length, $stringencoding = '', $trunconbytes = 0)
3115
{
3116
	global $langs;
3117
3118
	if (empty($stringencoding)) $stringencoding = $langs->charset_output;
3119
3120
	$ret = '';
3121
	if (empty($trunconbytes))
3122
	{
3123
		if (function_exists('mb_substr'))
3124
		{
3125
			$ret = mb_substr($string, $start, $length, $stringencoding);
3126
		} else {
3127
			$ret = substr($string, $start, $length);
3128
		}
3129
	} else {
3130
		if (function_exists('mb_strcut'))
3131
		{
3132
			$ret = mb_strcut($string, $start, $length, $stringencoding);
3133
		} else {
3134
			$ret = substr($string, $start, $length);
3135
		}
3136
	}
3137
	return $ret;
3138
}
3139
3140
3141
/**
3142
 *	Truncate a string to a particular length adding '...' if string larger than length.
3143
 * 	If length = max length+1, we do no truncate to avoid having just 1 char replaced with '...'.
3144
 *  MAIN_DISABLE_TRUNC=1 can disable all truncings
3145
 *
3146
 *	@param	string	$string				String to truncate
3147
 *	@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 ...)
3148
 *	@param	string	$trunc				Where to trunc: 'right', 'left', 'middle' (size must be a 2 power), 'wrap'
3149
 * 	@param	string	$stringencoding		Tell what is source string encoding
3150
 *  @param	int		$nodot				Truncation do not add ... after truncation. So it's an exact truncation.
3151
 *  @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)
3152
 *	@return string						Truncated string. WARNING: length is never higher than $size if $nodot is set, but can be 3 chars higher otherwise.
3153
 */
3154
function dol_trunc($string, $size = 40, $trunc = 'right', $stringencoding = 'UTF-8', $nodot = 0, $display = 0)
3155
{
3156
	global $conf;
3157
3158
	if ($size == 0 || !empty($conf->global->MAIN_DISABLE_TRUNC)) return $string;
3159
3160
	if (empty($stringencoding)) $stringencoding = 'UTF-8';
3161
	// reduce for small screen
3162
	if ($conf->dol_optimize_smallscreen == 1 && $display == 1) $size = round($size / 3);
3163
3164
	// We go always here
3165
	if ($trunc == 'right')
3166
	{
3167
		$newstring = dol_textishtml($string) ?dol_string_nohtmltag($string, 1) : $string;
3168
		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 ...
3169
		return dol_substr($newstring, 0, $size, $stringencoding).($nodot ? '' : '...');
3170
		else //return 'u'.$size.'-'.$newstring.'-'.dol_strlen($newstring,$stringencoding).'-'.$string;
3171
		return $string;
3172
	} elseif ($trunc == 'middle')
3173
	{
3174
		$newstring = dol_textishtml($string) ?dol_string_nohtmltag($string, 1) : $string;
3175
		if (dol_strlen($newstring, $stringencoding) > 2 && dol_strlen($newstring, $stringencoding) > ($size + 1))
3176
		{
3177
			$size1 = round($size / 2);
3178
			$size2 = round($size / 2);
3179
			return dol_substr($newstring, 0, $size1, $stringencoding).'...'.dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size2, $size2, $stringencoding);
3180
		} else return $string;
3181
	} elseif ($trunc == 'left')
3182
	{
3183
		$newstring = dol_textishtml($string) ?dol_string_nohtmltag($string, 1) : $string;
3184
		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 ...
3185
		return '...'.dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size, $size, $stringencoding);
3186
		else return $string;
3187
	} elseif ($trunc == 'wrap')
3188
	{
3189
		$newstring = dol_textishtml($string) ?dol_string_nohtmltag($string, 1) : $string;
3190
		if (dol_strlen($newstring, $stringencoding) > ($size + 1))
3191
		return dol_substr($newstring, 0, $size, $stringencoding)."\n".dol_trunc(dol_substr($newstring, $size, dol_strlen($newstring, $stringencoding) - $size, $stringencoding), $size, $trunc);
3192
		else return $string;
3193
	} else return 'BadParam3CallingDolTrunc';
3194
}
3195
3196
/**
3197
 *	Show picto whatever it's its name (generic function)
3198
 *
3199
 *	@param      string		$titlealt         		Text on title tag for tooltip. Not used if param notitle is set to 1.
3200
 *	@param      string		$picto       			Name of image file to show ('filenew', ...)
3201
 *													If no extension provided, we use '.png'. Image must be stored into theme/xxx/img directory.
3202
 *                                  				Example: picto.png                  if picto.png is stored into htdocs/theme/mytheme/img
3203
 *                                  				Example: picto.png@mymodule         if picto.png is stored into htdocs/mymodule/img
3204
 *                                  				Example: /mydir/mysubdir/picto.png  if picto.png is stored into htdocs/mydir/mysubdir (pictoisfullpath must be set to 1)
3205
 *	@param		string		$moreatt				Add more attribute on img tag (For example 'style="float: right"')
3206
 *	@param		boolean|int	$pictoisfullpath		If true or 1, image path is a full path
3207
 *	@param		int			$srconly				Return only content of the src attribute of img.
3208
 *  @param		int			$notitle				1=Disable tag title. Use it if you add js tooltip, to avoid duplicate tooltip.
3209
 *  @param		string		$alt					Force alt for bind people
3210
 *  @param		string		$morecss				Add more class css on img tag (For example 'myclascss').
3211
 *  @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.
3212
 *  @return     string       				    	Return img tag
3213
 *  @see        img_object(), img_picto_common()
3214
 */
3215
function img_picto($titlealt, $picto, $moreatt = '', $pictoisfullpath = false, $srconly = 0, $notitle = 0, $alt = '', $morecss = '', $marginleftonlyshort = 2)
3216
{
3217
	global $conf, $langs;
3218
3219
	// We forge fullpathpicto for image to $path/img/$picto. By default, we take DOL_URL_ROOT/theme/$conf->theme/img/$picto
3220
	$url = DOL_URL_ROOT;
3221
	$theme = isset($conf->theme) ? $conf->theme : null;
3222
	$path = 'theme/'.$theme;
3223
	// Define fullpathpicto to use into src
3224
	if ($pictoisfullpath) {
3225
		// Clean parameters
3226
		if (!preg_match('/(\.png|\.gif|\.svg)$/i', $picto)) {
3227
			$picto .= '.png';
3228
		}
3229
		$fullpathpicto = $picto;
3230
		$reg = array();
3231
		if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
3232
			$morecss .= ($morecss ? ' ' : '').$reg[1];
3233
			$moreatt = str_replace('class="'.$reg[1].'"', '', $moreatt);
3234
		}
3235
	} else {
3236
		$pictowithouttext = preg_replace('/(\.png|\.gif|\.svg)$/', '', $picto);
3237
		if (empty($srconly) && in_array($pictowithouttext, array(
3238
				'1downarrow', '1uparrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected',
3239
				'accountancy', 'account', 'accountline', 'action', 'add', 'address', 'bank_account', 'barcode', 'bank', 'bill', 'billa', 'billr', 'billd', 'bookmark', 'bom', 'building',
3240
				'cash-register', 'category', 'check', 'clock', 'close_title', 'company', 'contact', 'contract', 'cron', 'cubes',
3241
				'delete', 'dolly', 'dollyrevert', 'donation', 'download', 'edit', 'ellipsis-h', 'email', 'eraser', 'external-link-alt', 'external-link-square-alt',
3242
				'filter', 'file-code', 'file-export', 'file-import', 'file-upload', 'folder', 'folder-open', 'globe', 'globe-americas', 'grip', 'grip_title', 'group',
3243
				'help', 'holiday',
3244
				'intervention', 'label', 'language', 'link', 'list', 'listlight', 'lot',
3245
				'map-marker-alt', 'member', 'money-bill-alt', 'mrp', 'note', 'next',
3246
				'object_accounting', 'object_account', 'object_accountline', 'object_action', 'object_barcode', 'object_bill', 'object_billa', 'object_billd', 'object_bom',
3247
				'object_category', 'object_conversation', 'object_bookmark', 'object_bug', 'object_dolly', 'object_dollyrevert', 'object_generic', 'object_folder',
3248
				'object_list-alt', 'object_calendar', 'object_calendarweek', 'object_calendarmonth', 'object_calendarday', 'object_calendarperuser',
3249
				'object_cash-register', 'object_company', 'object_contact', 'object_contract', 'object_donation', 'object_dynamicprice',
3250
				'object_globe', 'object_holiday', 'object_hrm', 'object_invoice', 'object_intervention', 'object_label',
3251
				'object_margin', 'object_money-bill-alt', 'object_multicurrency', 'object_order', 'object_payment',
3252
				'object_lot', 'object_mrp', 'object_other',
3253
				'object_payment', 'object_pdf', 'object_product', 'object_propal',
3254
				'object_paragraph', 'object_poll', 'object_printer', 'object_project', 'object_projectpub', 'object_propal', 'object_resource', 'object_rss', 'object_projecttask',
3255
				'object_recruitmentjobposition', 'object_recruitmentcandidature',
3256
				'object_shipment', 'object_share-alt', 'object_supplier_invoice', 'object_supplier_invoicea', 'object_supplier_invoiced', 'object_supplier_order', 'object_supplier_proposal', 'object_service', 'object_stock',
3257
				'object_technic', 'object_ticket', 'object_trip', 'object_user', 'object_group', 'object_member',
3258
				'object_phoning', 'object_phoning_mobile', 'object_phoning_fax', 'object_email', 'object_website',
3259
				'off', 'on', 'order',
3260
				'paiment', 'play', 'pdf', 'playdisabled', 'previous', 'poll', 'printer', 'product', 'propal', 'projecttask', 'stock', 'resize', 'service', 'stats', 'trip',
3261
				'setup', 'share-alt', 'sign-out', 'split', 'stripe-s', 'switch_off', 'switch_on', 'tools', 'unlink', 'uparrow', 'user', 'vcard', 'wrench',
3262
				'jabber', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'youtube', 'google-plus-g', 'whatsapp',
3263
				'chevron-left', 'chevron-right', 'chevron-down', 'chevron-top', 'commercial', 'companies',
3264
				'generic', 'home', 'hrm', 'members', 'products', 'invoicing',
3265
				'payment', 'pencil-ruler', 'preview', 'project', 'projectpub', 'refresh', 'supplier_invoice', 'ticket',
3266
				'error', 'warning',
3267
				'recruitmentcandidature', 'recruitmentjobposition', 'resource',
3268
				'supplier_proposal', 'supplier_order', 'supplier_invoice',
3269
				'title_setup', 'title_accountancy', 'title_bank', 'title_hrm', 'title_agenda'
3270
			)
3271
		)) {
3272
			$pictowithouttext = str_replace('object_', '', $pictowithouttext);
3273
3274
			$fakey = $pictowithouttext;
3275
			$facolor = ''; $fasize = '';
3276
			$fa = 'fas';
3277
			if (in_array($pictowithouttext, array('clock', 'generic', 'minus-square', 'object_generic', 'pdf', 'plus-square', 'note', 'off', 'on', 'object_bookmark', 'bookmark', 'vcard'))) {
3278
				$fa = 'far';
3279
			}
3280
			if (in_array($pictowithouttext, array('black-tie', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'stripe-s', 'youtube', 'google-plus-g', 'whatsapp'))) {
3281
				$fa = 'fab';
3282
			}
3283
3284
			$arrayconvpictotofa = array(
3285
				'account'=>'university', 'accountline'=>'receipt', 'accountancy'=>'money-check-alt', 'action'=>'calendar-alt', 'add'=>'plus-circle', 'address'=> 'address-book',
3286
				'bank_account'=>'university', 'bill'=>'file-invoice-dollar', 'billa'=>'file-excel', 'supplier_invoicea'=>'file-excel', 'billd'=>'file-medical', 'supplier_invoiced'=>'file-medical', 'bom'=>'cubes',
3287
				'company'=>'building', 'contact'=>'address-book', 'contract'=>'suitcase', 'conversation'=>'comments', 'donation'=>'file-alt', 'dynamicprice'=>'hand-holding-usd',
3288
				'setup'=>'cog', 'companies'=>'building', 'products'=>'cube', 'commercial'=>'suitcase', 'invoicing'=>'coins',
3289
				'accounting'=>'chart-line', 'category'=>'tag', 'dollyrevert'=>'dolly',
3290
				'hrm'=>'user-tie', 'margin'=>'calculator', 'members'=>'users', 'ticket'=>'ticket-alt', 'globe'=>'external-link-alt', 'lot'=>'barcode',
3291
				'email'=>'at',
3292
				'edit'=>'pencil-alt', 'grip_title'=>'arrows-alt', 'grip'=>'arrows-alt', 'help'=>'question-circle',
3293
				'generic'=>'file', 'holiday'=>'umbrella-beach', 'label'=>'layer-group',
3294
				'member'=>'users', 'mrp'=>'cubes', 'next'=>'arrow-alt-circle-right',
3295
				'trip'=>'wallet', 'group'=>'users',
3296
				'sign-out'=>'sign-out-alt',
3297
				'switch_off'=>'toggle-off', 'switch_on'=>'toggle-on', 'check'=>'check', 'bookmark'=>'star', 'bookmark'=>'star',
3298
				'bank'=>'university', 'close_title'=>'times', 'delete'=>'trash', 'edit'=>'pencil-alt', 'filter'=>'filter',
3299
				'list-alt'=>'list-alt', 'calendar'=>'calendar-alt', 'calendarweek'=>'calendar-week', 'calendarmonth'=>'calendar-alt', 'calendarday'=>'calendar-day', 'calendarperuser'=>'table',
3300
				'intervention'=>'ambulance', 'invoice'=>'file-invoice-dollar', 'multicurrency'=>'dollar-sign', 'order'=>'file-invoice',
3301
				'error'=>'exclamation-triangle', 'warning'=>'exclamation-triangle',
3302
				'other'=>'square',
3303
				'playdisabled'=>'play', 'pdf'=>'file-pdf',  'poll'=>'check-double', 'preview'=>'binoculars', 'project'=>'sitemap', 'projectpub'=>'sitemap', 'projecttask'=>'tasks', 'propal'=>'file-signature',
3304
				'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',
3305
				'recruitmentjobposition'=>'id-card-alt', 'recruitmentcandidature'=>'id-badge',
3306
				'resize'=>'crop', 'supplier_order'=>'dol-order_supplier', 'supplier_proposal'=>'file-signature',
3307
				'refresh'=>'redo', 'resource'=>'laptop-house',
3308
				'shipment'=>'dolly', 'stock'=>'box-open', 'stats' => 'chart-bar', 'split'=>'code-branch', 'supplier_invoice'=>'file-invoice-dollar', 'technic'=>'cogs', 'ticket'=>'ticket-alt',
3309
				'title_setup'=>'tools', 'title_accountancy'=>'money-check-alt', 'title_bank'=>'university', 'title_hrm'=>'umbrella-beach',
3310
				'title_agenda'=>'calendar-alt',
3311
				'uparrow'=>'mail-forward', 'vcard'=>'address-card',
3312
				'jabber'=>'comment-o',
3313
				'website'=>'globe-americas'
3314
			);
3315
			if ($pictowithouttext == 'off') {
3316
				$fakey = 'fa-square';
3317
				$fasize = '1.3em';
3318
			} elseif ($pictowithouttext == 'on') {
3319
				$fakey = 'fa-check-square';
3320
				$fasize = '1.3em';
3321
			} elseif ($pictowithouttext == 'listlight') {
3322
				$fakey = 'fa-download';
3323
				$marginleftonlyshort = 1;
3324
			} elseif ($pictowithouttext == 'printer') {
3325
				$fakey = 'fa-print';
3326
				$fasize = '1.2em';
3327
			} elseif ($pictowithouttext == 'note') {
3328
				$fakey = 'fa-sticky-note';
3329
				$marginleftonlyshort = 1;
3330
			} elseif (in_array($pictowithouttext, array('1uparrow', '1downarrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected'))) {
3331
				$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');
3332
				$fakey = 'fa-'.$convertarray[$pictowithouttext];
3333
				if (preg_match('/selected/', $pictowithouttext)) $facolor = '#888';
3334
				$marginleftonlyshort = 1;
3335
			} elseif (!empty($arrayconvpictotofa[$pictowithouttext])) {
3336
				$fakey = 'fa-'.$arrayconvpictotofa[$pictowithouttext];
3337
			} else {
3338
				$fakey = 'fa-'.$pictowithouttext;
3339
			}
3340
3341
			// Define $marginleftonlyshort
3342
			$arrayconvpictotomarginleftonly = array(
3343
				'bank', 'check', 'delete', 'generic', 'grip', 'grip_title', 'jabber',
3344
				'grip_title', 'grip', 'listlight', 'note', 'on', 'off', 'playdisabled', 'printer', 'resize', 'sign-out', 'stats', 'switch_on', 'switch_off',
3345
				'uparrow', '1uparrow', '1downarrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected'
3346
			);
3347
			if (!isset($arrayconvpictotomarginleftonly[$pictowithouttext])) {
3348
				$marginleftonlyshort = 0;
3349
			}
3350
3351
			// Add CSS
3352
			$arrayconvpictotomorcess = array(
3353
				'action'=>'infobox-action', 'account'=>'infobox-bank_account', 'accountline'=>'infobox-bank_account', 'accountancy'=>'infobox-bank_account',
3354
				'bank_account'=>'bg-infobox-bank_account',
3355
				'bill'=>'infobox-commande', 'billa'=>'infobox-commande', 'billd'=>'infobox-commande',
3356
				'cash-register'=>'infobox-bank_account', 'contract'=>'infobox-contrat', 'check'=>'font-status4', 'conversation'=>'infobox-contrat',
3357
				'donation'=>'infobox-commande', 'dollyrevert'=>'flip', 'ecm'=>'infobox-action',
3358
				'hrm'=>'infobox-adherent', 'group'=>'infobox-adherent', 'intervention'=>'infobox-contrat',
3359
				'multicurrency'=>'infobox-bank_account',
3360
				'members'=>'infobox-adherent', 'member'=>'infobox-adherent', 'money-bill-alt'=>'infobox-bank_account',
3361
				'order'=>'infobox-commande',
3362
				'user'=>'infobox-adherent', 'users'=>'infobox-adherent',
3363
				'error'=>'pictoerror', 'warning'=>'pictowarning', 'switch_on'=>'font-status4',
3364
				'holiday'=>'infobox-holiday', 'invoice'=>'infobox-commande',
3365
				'payment'=>'infobox-bank_account', 'poll'=>'infobox-adherent', 'project'=>'infobox-project', 'projecttask'=>'infobox-project', 'propal'=>'infobox-propal',
3366
				'recruitmentjobposition'=>'infobox-adherent', 'recruitmentcandidature'=>'infobox-adherent',
3367
				'resource'=>'infobox-action',
3368
				'supplier_invoice'=>'infobox-order_supplier', 'supplier_invoicea'=>'infobox-order_supplier', 'supplier_invoiced'=>'infobox-order_supplier',
3369
				'supplier_order'=>'infobox-order_supplier', 'supplier_proposal'=>'infobox-supplier_proposal',
3370
				'ticket'=>'infobox-contrat', 'title_accountancy'=>'infobox-bank_account', 'title_hrm'=>'infobox-holiday', 'trip'=>'infobox-expensereport', 'title_agenda'=>'infobox-action',
3371
				//'title_setup'=>'infobox-action', 'tools'=>'infobox-action',
3372
				'list-alt'=>'imgforviewmode', 'calendar'=>'imgforviewmode', 'calendarweek'=>'imgforviewmode', 'calendarmonth'=>'imgforviewmode', 'calendarday'=>'imgforviewmode', 'calendarperuser'=>'imgforviewmode'
3373
			);
3374
			if (!empty($arrayconvpictotomorcess[$pictowithouttext])) {
3375
				$morecss .= ($morecss ? ' ' : '').$arrayconvpictotomorcess[$pictowithouttext];
3376
			}
3377
3378
			// Define $color
3379
			$arrayconvpictotocolor = array(
3380
				'address'=>'#6c6aa8', 'building'=>'#6c6aa8', 'bom'=>'#a69944',
3381
				'companies'=>'#6c6aa8', 'company'=>'#6c6aa8', 'contact'=>'#6c6aa8', 'dynamicprice'=>'#a69944',
3382
				'edit'=>'#444', 'note'=>'#999', 'error'=>'', 'help'=>'#bbb', 'listlight'=>'#999',
3383
				'dolly'=>'#a69944', 'dollyrevert'=>'#a69944', 'lot'=>'#a69944',
3384
				'map-marker-alt'=>'#aaa', 'mrp'=>'#a69944', 'product'=>'#a69944', 'service'=>'#a69944', 'stock'=>'#a69944',
3385
				'other'=>'#ddd',
3386
				'playdisabled'=>'#ccc', 'printer'=>'#444', 'projectpub'=>'#986c6a', 'resize'=>'#444', 'rss'=>'#cba',
3387
				'shipment'=>'#a69944', 'stats'=>'#444', 'switch_off'=>'#999', 'uparrow'=>'#555', 'globe-americas'=>'#aaa',
3388
				'website'=>'#304'
3389
			);
3390
			if (isset($arrayconvpictotocolor[$pictowithouttext])) {
3391
				$facolor = $arrayconvpictotocolor[$pictowithouttext];
3392
			}
3393
3394
			// This snippet only needed since function img_edit accepts only one additional parameter: no separate one for css only.
3395
			// class/style need to be extracted to avoid duplicate class/style validation errors when $moreatt is added to the end of the attributes.
3396
			$morestyle = '';
3397
			$reg = array();
3398
			if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
3399
				$morecss .= ($morecss ? ' ' : '').$reg[1];
3400
				$moreatt = str_replace('class="'.$reg[1].'"', '', $moreatt);
3401
			}
3402
			if (preg_match('/style="([^"]+)"/', $moreatt, $reg)) {
3403
				$morestyle = $reg[1];
3404
				$moreatt = str_replace('style="'.$reg[1].'"', '', $moreatt);
3405
			}
3406
			$moreatt = trim($moreatt);
3407
3408
			$enabledisablehtml = '<span class="'.$fa.' '.$fakey.($marginleftonlyshort ? ($marginleftonlyshort == 1 ? ' marginleftonlyshort' : ' marginleftonly') : '');
3409
			$enabledisablehtml .= ($morecss ? ' '.$morecss : '').'" style="'.($fasize ? ('font-size: '.$fasize.';') : '').($facolor ? (' color: '.$facolor.';') : '').($morestyle ? ' '.$morestyle : '').'"'.(($notitle || empty($titlealt)) ? '' : ' title="'.dol_escape_htmltag($titlealt).'"').($moreatt ? ' '.$moreatt : '').'>';
3410
			/*if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
3411
				$enabledisablehtml .= $titlealt;
3412
			}*/
3413
			$enabledisablehtml .= '</span>';
3414
3415
			return $enabledisablehtml;
3416
		}
3417
3418
		if (!empty($conf->global->MAIN_OVERWRITE_THEME_PATH)) {
3419
			$path = $conf->global->MAIN_OVERWRITE_THEME_PATH.'/theme/'.$theme; // If the theme does not have the same name as the module
3420
		} elseif (!empty($conf->global->MAIN_OVERWRITE_THEME_RES)) {
3421
			$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
3422
		} elseif (!empty($conf->modules_parts['theme']) && array_key_exists($theme, $conf->modules_parts['theme'])) {
3423
			$path = $theme.'/theme/'.$theme; // If the theme have the same name as the module
3424
		}
3425
3426
		// If we ask an image into $url/$mymodule/img (instead of default path)
3427
		$regs = array();
3428
		if (preg_match('/^([^@]+)@([^@]+)$/i', $picto, $regs)) {
3429
			$picto = $regs[1];
3430
			$path = $regs[2]; // $path is $mymodule
3431
		}
3432
3433
		// Clean parameters
3434
		if (!preg_match('/(\.png|\.gif|\.svg)$/i', $picto)) {
3435
			$picto .= '.png';
3436
		}
3437
		// If alt path are defined, define url where img file is, according to physical path
3438
		// ex: array(["main"]=>"/home/maindir/htdocs", ["alt0"]=>"/home/moddir0/htdocs", ...)
3439
		foreach ($conf->file->dol_document_root as $type => $dirroot) {
3440
			if ($type == 'main') {
3441
				continue;
3442
			}
3443
			// This need a lot of time, that's why enabling alternative dir like "custom" dir is not recommanded
3444
			if (file_exists($dirroot.'/'.$path.'/img/'.$picto)) {
3445
				$url = DOL_URL_ROOT.$conf->file->dol_url_root[$type];
3446
				break;
3447
			}
3448
		}
3449
3450
		// $url is '' or '/custom', $path is current theme or
3451
		$fullpathpicto = $url.'/'.$path.'/img/'.$picto;
3452
	}
3453
3454
	if ($srconly) {
3455
		return $fullpathpicto;
3456
	}
3457
		// tag title is used for tooltip on <a>, tag alt can be used with very simple text on image for blind people
3458
	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
3459
}
3460
3461
/**
3462
 *	Show a picto called object_picto (generic function)
3463
 *
3464
 *	@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.
3465
 *	@param	string	$picto				Name of image to show object_picto (example: user, group, action, bill, contract, propal, product, ...)
3466
 *										For external modules use imagename@mymodule to search into directory "img" of module.
3467
 *	@param	string	$moreatt			Add more attribute on img tag (ie: class="datecallink")
3468
 *	@param	int		$pictoisfullpath	If 1, image path is a full path
3469
 *	@param	int		$srconly			Return only content of the src attribute of img.
3470
 *  @param	int		$notitle			1=Disable tag title. Use it if you add js tooltip, to avoid duplicate tooltip.
3471
 *	@return	string						Return img tag
3472
 *	@see	img_picto(), img_picto_common()
3473
 */
3474
function img_object($titlealt, $picto, $moreatt = '', $pictoisfullpath = false, $srconly = 0, $notitle = 0)
3475
{
3476
	if (strpos($picto, '^') === 0) return img_picto($titlealt, str_replace('^', '', $picto), $moreatt, $pictoisfullpath, $srconly, $notitle);
3477
	else return img_picto($titlealt, 'object_'.$picto, $moreatt, $pictoisfullpath, $srconly, $notitle);
3478
}
3479
3480
/**
3481
 *	Show weather picto
3482
 *
3483
 *	@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.
3484
 *	@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).
3485
 *	@param		string		$moreatt			Add more attribute on img tag
3486
 *	@param		int			$pictoisfullpath	If 1, image path is a full path
3487
 *  @param      string      $morecss            More CSS
3488
 *	@return     string      					Return img tag
3489
 *  @see        img_object(), img_picto()
3490
 */
3491
function img_weather($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $morecss = '')
3492
{
3493
	global $conf;
3494
3495
	if (is_numeric($picto)) {
3496
		//$leveltopicto = array(0=>'weather-clear.png', 1=>'weather-few-clouds.png', 2=>'weather-clouds.png', 3=>'weather-many-clouds.png', 4=>'weather-storm.png');
3497
		//$picto = $leveltopicto[$picto];
3498
		return '<i class="fa fa-weather-level'.$picto.'"></i>';
3499
	} elseif (!preg_match('/(\.png|\.gif)$/i', $picto)) {
3500
		$picto .= '.png';
3501
	}
3502
3503
	$path = DOL_URL_ROOT.'/theme/'.$conf->theme.'/img/weather/'.$picto;
3504
3505
	return img_picto($titlealt, $path, $moreatt, 1, 0, 0, '', $morecss);
3506
}
3507
3508
/**
3509
 *	Show picto (generic function)
3510
 *
3511
 *	@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.
3512
 *	@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.
3513
 *	@param		string		$moreatt			Add more attribute on img tag
3514
 *	@param		int			$pictoisfullpath	If 1, image path is a full path
3515
 *	@return     string      					Return img tag
3516
 *  @see        img_object(), img_picto()
3517
 */
3518
function img_picto_common($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0)
3519
{
3520
	global $conf;
3521
3522
	if (!preg_match('/(\.png|\.gif)$/i', $picto)) $picto .= '.png';
3523
3524
	if ($pictoisfullpath) $path = $picto;
3525
	else {
3526
		$path = DOL_URL_ROOT.'/theme/common/'.$picto;
3527
3528
		if (!empty($conf->global->MAIN_MODULE_CAN_OVERWRITE_COMMONICONS))
3529
		{
3530
			$themepath = DOL_DOCUMENT_ROOT.'/theme/'.$conf->theme.'/img/'.$picto;
3531
3532
			if (file_exists($themepath)) $path = $themepath;
3533
		}
3534
	}
3535
3536
	return img_picto($titlealt, $path, $moreatt, 1);
3537
}
3538
3539
/**
3540
 *	Show logo action
3541
 *
3542
 *	@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.
3543
 *	@param  string		$numaction   	Action id or code to show
3544
 *	@param 	string		$picto      	Name of image file to show ('filenew', ...)
3545
 *                                      If no extension provided, we use '.png'. Image must be stored into theme/xxx/img directory.
3546
 *                                      Example: picto.png                  if picto.png is stored into htdocs/theme/mytheme/img
3547
 *                                      Example: picto.png@mymodule         if picto.png is stored into htdocs/mymodule/img
3548
 *                                      Example: /mydir/mysubdir/picto.png  if picto.png is stored into htdocs/mydir/mysubdir (pictoisfullpath must be set to 1)
3549
 *	@return string      				Return an img tag
3550
 */
3551
function img_action($titlealt, $numaction, $picto = '')
3552
{
3553
	global $langs;
3554
3555
	if (empty($titlealt) || $titlealt == 'default')
3556
	{
3557
		if ($numaction == '-1' || $numaction == 'ST_NO') {
3558
			$numaction = -1;
3559
			$titlealt = $langs->transnoentitiesnoconv('ChangeDoNotContact');
3560
		} elseif ($numaction == '0' || $numaction == 'ST_NEVER') {
3561
			$numaction = 0;
3562
			$titlealt = $langs->transnoentitiesnoconv('ChangeNeverContacted');
3563
		} elseif ($numaction == '1' || $numaction == 'ST_TODO') {
3564
			$numaction = 1;
3565
			$titlealt = $langs->transnoentitiesnoconv('ChangeToContact');
3566
		} elseif ($numaction == '2' || $numaction == 'ST_PEND') {
3567
			$numaction = 2;
3568
			$titlealt = $langs->transnoentitiesnoconv('ChangeContactInProcess');
3569
		} elseif ($numaction == '3' || $numaction == 'ST_DONE') {
3570
			$numaction = 3;
3571
			$titlealt = $langs->transnoentitiesnoconv('ChangeContactDone');
3572
		} else {
3573
			$titlealt = $langs->transnoentitiesnoconv('ChangeStatus '.$numaction);
3574
			$numaction = 0;
3575
		}
3576
	}
3577
	if (!is_numeric($numaction)) $numaction = 0;
3578
3579
	return img_picto($titlealt, !empty($picto) ? $picto : 'stcomm'.$numaction.'.png');
3580
}
3581
3582
/**
3583
 *  Show pdf logo
3584
 *
3585
 *  @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.
3586
 *  @param  int		    $size       Taille de l'icone : 3 = 16x16px , 2 = 14x14px
3587
 *  @return string      			Retourne tag img
3588
 */
3589
function img_pdf($titlealt = 'default', $size = 3)
3590
{
3591
	global $langs;
3592
3593
	if ($titlealt == 'default') $titlealt = $langs->trans('Show');
3594
3595
	return img_picto($titlealt, 'pdf'.$size.'.png');
3596
}
3597
3598
/**
3599
 *	Show logo +
3600
 *
3601
 *	@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.
3602
 *	@param  string	$other      Add more attributes on img
3603
 *	@return string      		Return tag img
3604
 */
3605
function img_edit_add($titlealt = 'default', $other = '')
3606
{
3607
	global $langs;
3608
3609
	if ($titlealt == 'default') $titlealt = $langs->trans('Add');
3610
3611
	return img_picto($titlealt, 'edit_add.png', $other);
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_remove($titlealt = 'default', $other = '')
3621
{
3622
	global $langs;
3623
3624
	if ($titlealt == 'default') $titlealt = $langs->trans('Remove');
3625
3626
	return img_picto($titlealt, 'edit_remove.png', $other);
3627
}
3628
3629
/**
3630
 *	Show logo editer/modifier fiche
3631
 *
3632
 *	@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.
3633
 *	@param  integer	$float      If you have to put the style "float: right"
3634
 *	@param  string	$other		Add more attributes on img
3635
 *	@return string      		Return tag img
3636
 */
3637
function img_edit($titlealt = 'default', $float = 0, $other = '')
3638
{
3639
	global $langs;
3640
3641
	if ($titlealt == 'default') $titlealt = $langs->trans('Modify');
3642
3643
	return img_picto($titlealt, 'edit.png', ($float ? 'style="float: '.($langs->tab_translate["DIRECTION"] == 'rtl' ? 'left' : 'right').'"' : "").($other ? ' '.$other : ''));
3644
}
3645
3646
/**
3647
 *	Show logo view card
3648
 *
3649
 *	@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.
3650
 *	@param  integer	$float      If you have to put the style "float: right"
3651
 *	@param  string	$other		Add more attributes on img
3652
 *	@return string      		Return tag img
3653
 */
3654
function img_view($titlealt = 'default', $float = 0, $other = '')
3655
{
3656
	global $langs;
3657
3658
	if ($titlealt == 'default') $titlealt = $langs->trans('View');
3659
3660
	$moreatt = ($float ? 'style="float: right" ' : '').$other;
3661
3662
	return img_picto($titlealt, 'view.png', $moreatt);
3663
}
3664
3665
/**
3666
 *  Show delete logo
3667
 *
3668
 *  @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.
3669
 *	@param  string	$other      Add more attributes on img
3670
 *  @param	string	$morecss	More CSS
3671
 *  @return string      		Retourne tag img
3672
 */
3673
function img_delete($titlealt = 'default', $other = 'class="pictodelete"', $morecss = '')
3674
{
3675
	global $langs;
3676
3677
	if ($titlealt == 'default') $titlealt = $langs->trans('Delete');
3678
3679
	return img_picto($titlealt, 'delete.png', $other, false, 0, 0, '', $morecss);
3680
}
3681
3682
/**
3683
 *  Show printer logo
3684
 *
3685
 *  @param  string  $titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3686
 *  @param  string  $other      Add more attributes on img
3687
 *  @return string              Retourne tag img
3688
 */
3689
function img_printer($titlealt = "default", $other = '')
3690
{
3691
	global $langs;
3692
	if ($titlealt == "default") $titlealt = $langs->trans("Print");
3693
	return img_picto($titlealt, 'printer.png', $other);
3694
}
3695
3696
/**
3697
 *  Show split logo
3698
 *
3699
 *  @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.
3700
 *	@param  string	$other      Add more attributes on img
3701
 *  @return string      		Retourne tag img
3702
 */
3703
function img_split($titlealt = 'default', $other = 'class="pictosplit"')
3704
{
3705
	global $langs;
3706
3707
	if ($titlealt == 'default') $titlealt = $langs->trans('Split');
3708
3709
	return img_picto($titlealt, 'split.png', $other);
3710
}
3711
3712
/**
3713
 *	Show help logo with cursor "?"
3714
 *
3715
 * 	@param	int              	$usehelpcursor		1=Use help cursor, 2=Use click pointer cursor, 0=No specific cursor
3716
 * 	@param	int|string	        $usealttitle		Text to use as alt title
3717
 * 	@return string            	           			Return tag img
3718
 */
3719
function img_help($usehelpcursor = 1, $usealttitle = 1)
3720
{
3721
	global $langs;
3722
3723
	if ($usealttitle)
3724
	{
3725
		if (is_string($usealttitle)) $usealttitle = dol_escape_htmltag($usealttitle);
3726
		else $usealttitle = $langs->trans('Info');
3727
	}
3728
3729
	return img_picto($usealttitle, 'info.png', 'style="vertical-align: middle;'.($usehelpcursor == 1 ? ' cursor: help' : ($usehelpcursor == 2 ? ' cursor: pointer' : '')).'"');
3730
}
3731
3732
/**
3733
 *	Show info logo
3734
 *
3735
 *	@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.
3736
 *	@return string      		Return img tag
3737
 */
3738
function img_info($titlealt = 'default')
3739
{
3740
	global $langs;
3741
3742
	if ($titlealt == 'default') $titlealt = $langs->trans('Informations');
3743
3744
	return img_picto($titlealt, 'info.png', 'style="vertical-align: middle;"');
3745
}
3746
3747
/**
3748
 *	Show warning 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
 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"'). If 1, add float: right. Can't be "class" attribute.
3752
 *  @param	string  $morecss	Add more CSS
3753
 *	@return string      		Return img tag
3754
 */
3755
function img_warning($titlealt = 'default', $moreatt = '', $morecss = 'pictowarning')
3756
{
3757
	global $langs;
3758
3759
	if ($titlealt == 'default') $titlealt = $langs->trans('Warning');
3760
3761
	//return '<div class="imglatecoin">'.img_picto($titlealt, 'warning_white.png', 'class="pictowarning valignmiddle"'.($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' '.$moreatt): '')).'</div>';
3762
	return img_picto($titlealt, 'warning.png', 'class="'.$morecss.'"'.($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' '.$moreatt) : ''));
3763
}
3764
3765
/**
3766
 *  Show error logo
3767
 *
3768
 *	@param	string	$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3769
 *	@return string      		Return img tag
3770
 */
3771
function img_error($titlealt = 'default')
3772
{
3773
	global $langs;
3774
3775
	if ($titlealt == 'default') $titlealt = $langs->trans('Error');
3776
3777
	return img_picto($titlealt, 'error.png');
3778
}
3779
3780
/**
3781
 *	Show next 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
*	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
3785
 *	@return string      		Return img tag
3786
 */
3787
function img_next($titlealt = 'default', $moreatt = '')
3788
{
3789
	global $langs;
3790
3791
	if ($titlealt == 'default') $titlealt = $langs->trans('Next');
3792
3793
	//return img_picto($titlealt, 'next.png', $moreatt);
3794
	return '<span class="fa fa-chevron-right paddingright paddingleft" title="'.dol_escape_htmltag($titlealt).'"></span>';
3795
}
3796
3797
/**
3798
 *	Show previous logo
3799
 *
3800
 *	@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.
3801
 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
3802
 *	@return string      		Return img tag
3803
 */
3804
function img_previous($titlealt = 'default', $moreatt = '')
3805
{
3806
	global $langs;
3807
3808
	if ($titlealt == 'default') $titlealt = $langs->trans('Previous');
3809
3810
	//return img_picto($titlealt, 'previous.png', $moreatt);
3811
	return '<span class="fa fa-chevron-left paddingright paddingleft" title="'.dol_escape_htmltag($titlealt).'"></span>';
3812
}
3813
3814
/**
3815
 *	Show down arrow logo
3816
 *
3817
 *	@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.
3818
 *	@param  int		$selected   Selected
3819
 *  @param	string	$moreclass	Add more CSS classes
3820
 *	@return string      		Return img tag
3821
 */
3822
function img_down($titlealt = 'default', $selected = 0, $moreclass = '')
3823
{
3824
	global $langs;
3825
3826
	if ($titlealt == 'default') $titlealt = $langs->trans('Down');
3827
3828
	return img_picto($titlealt, ($selected ? '1downarrow_selected.png' : '1downarrow.png'), 'class="imgdown'.($moreclass ? " ".$moreclass : "").'"');
3829
}
3830
3831
/**
3832
 *	Show top arrow logo
3833
 *
3834
 *	@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.
3835
 *	@param  int		$selected	Selected
3836
 *  @param	string	$moreclass	Add more CSS classes
3837
 *	@return string      		Return img tag
3838
 */
3839
function img_up($titlealt = 'default', $selected = 0, $moreclass = '')
3840
{
3841
	global $langs;
3842
3843
	if ($titlealt == 'default') $titlealt = $langs->trans('Up');
3844
3845
	return img_picto($titlealt, ($selected ? '1uparrow_selected.png' : '1uparrow.png'), 'class="imgup'.($moreclass ? " ".$moreclass : "").'"');
3846
}
3847
3848
/**
3849
 *	Show left arrow logo
3850
 *
3851
 *	@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.
3852
 *	@param  int		$selected	Selected
3853
 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
3854
 *	@return string      		Return img tag
3855
 */
3856
function img_left($titlealt = 'default', $selected = 0, $moreatt = '')
3857
{
3858
	global $langs;
3859
3860
	if ($titlealt == 'default') {
3861
		$titlealt = $langs->trans('Left');
3862
	}
3863
3864
	return img_picto($titlealt, ($selected ? '1leftarrow_selected.png' : '1leftarrow.png'), $moreatt);
3865
}
3866
3867
/**
3868
 *	Show right arrow logo
3869
 *
3870
 *	@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.
3871
 *	@param  int		$selected	Selected
3872
 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
3873
 *	@return string      		Return img tag
3874
 */
3875
function img_right($titlealt = 'default', $selected = 0, $moreatt = '')
3876
{
3877
	global $langs;
3878
3879
	if ($titlealt == 'default') $titlealt = $langs->trans('Right');
3880
3881
	return img_picto($titlealt, ($selected ? '1rightarrow_selected.png' : '1rightarrow.png'), $moreatt);
3882
}
3883
3884
/**
3885
 *	Show tick logo if allowed
3886
 *
3887
 *	@param	string	$allow		Allow
3888
 *	@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.
3889
 *	@return string      		Return img tag
3890
 */
3891
function img_allow($allow, $titlealt = 'default')
3892
{
3893
	global $langs;
3894
3895
	if ($titlealt == 'default') $titlealt = $langs->trans('Active');
3896
3897
	if ($allow == 1) return img_picto($titlealt, 'tick.png');
3898
3899
	return '-';
3900
}
3901
3902
/**
3903
 *	Return image of a credit card according to its brand name
3904
 *
3905
 *	@param  string	$brand		Brand name of credit card
3906
 *  @param  string	$morecss	More CSS
3907
 *	@return string     			Return img tag
3908
 */
3909
function img_credit_card($brand, $morecss = null)
3910
{
3911
	if (is_null($morecss)) $morecss = 'fa-2x';
3912
3913
	if ($brand == 'visa' || $brand == 'Visa') {
3914
		$brand = 'cc-visa';
3915
	} elseif ($brand == 'mastercard' || $brand == 'MasterCard') {
3916
		$brand = 'cc-mastercard';
3917
	} elseif ($brand == 'amex' || $brand == 'American Express') {
3918
		$brand = 'cc-amex';
3919
	} elseif ($brand == 'discover' || $brand == 'Discover') {
3920
		$brand = 'cc-discover';
3921
	} elseif ($brand == 'jcb' || $brand == 'JCB') {
3922
		$brand = 'cc-jcb';
3923
	} elseif ($brand == 'diners' || $brand == 'Diners club') {
3924
		$brand = 'cc-diners-club';
3925
	} elseif (!in_array($brand, array('cc-visa', 'cc-mastercard', 'cc-amex', 'cc-discover', 'cc-jcb', 'cc-diners-club'))) {
3926
		$brand = 'credit-card';
3927
	}
3928
3929
	return '<span class="fa fa-'.$brand.' fa-fw'.($morecss ? ' '.$morecss : '').'"></span>';
3930
}
3931
3932
/**
3933
 *	Show MIME img of a file
3934
 *
3935
 *	@param	string	$file		Filename
3936
 * 	@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.
3937
 *  @param	string	$morecss	More css
3938
 *	@return string     			Return img tag
3939
 */
3940
function img_mime($file, $titlealt = '', $morecss = '')
3941
{
3942
	require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3943
3944
	$mimetype = dol_mimetype($file, '', 1);
3945
	$mimeimg = dol_mimetype($file, '', 2);
3946
	$mimefa = dol_mimetype($file, '', 4);
3947
3948
	if (empty($titlealt)) $titlealt = 'Mime type: '.$mimetype;
3949
3950
	//return img_picto_common($titlealt, 'mime/'.$mimeimg, 'class="'.$morecss.'"');
3951
	return '<i class="fa fa-'.$mimefa.' paddingright"'.($titlealt ? ' title="'.$titlealt.'"' : '').'></i>';
3952
}
3953
3954
3955
/**
3956
 *  Show search logo
3957
 *
3958
 *  @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.
3959
 *	@param  string	$other      Add more attributes on img
3960
 *  @return string      		Retourne tag img
3961
 */
3962
function img_search($titlealt = 'default', $other = '')
3963
{
3964
	global $conf, $langs;
3965
3966
	if ($titlealt == 'default') $titlealt = $langs->trans('Search');
3967
3968
	$img = img_picto($titlealt, 'search.png', $other, false, 1);
3969
3970
	$input = '<input type="image" class="liste_titre" name="button_search" src="'.$img.'" ';
3971
	$input .= 'value="'.dol_escape_htmltag($titlealt).'" title="'.dol_escape_htmltag($titlealt).'" >';
3972
3973
	return $input;
3974
}
3975
3976
/**
3977
 *  Show search logo
3978
 *
3979
 *  @param	string	$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3980
 *	@param  string	$other      Add more attributes on img
3981
 *  @return string      		Retourne tag img
3982
 */
3983
function img_searchclear($titlealt = 'default', $other = '')
3984
{
3985
	global $conf, $langs;
3986
3987
	if ($titlealt == 'default') $titlealt = $langs->trans('Search');
3988
3989
	$img = img_picto($titlealt, 'searchclear.png', $other, false, 1);
3990
3991
	$input = '<input type="image" class="liste_titre" name="button_removefilter" src="'.$img.'" ';
3992
	$input .= 'value="'.dol_escape_htmltag($titlealt).'" title="'.dol_escape_htmltag($titlealt).'" >';
3993
3994
	return $input;
3995
}
3996
3997
/**
3998
 *	Show information for admin users or standard users
3999
 *
4000
 *	@param	string	$text				Text info
4001
 *	@param  integer	$infoonimgalt		Info is shown only on alt of star picto, otherwise it is show on output after the star picto
4002
 *	@param	int		$nodiv				No div
4003
 *  @param  string  $admin      	    '1'=Info for admin users. '0'=Info for standard users (change only the look), 'error','xxx'=Other
4004
 *  @param	string	$morecss			More CSS ('', 'warning', 'error')
4005
 *  @param	string	$textfordropdown	Show a text to click to dropdown the info box.
4006
 *	@return	string						String with info text
4007
 */
4008
function info_admin($text, $infoonimgalt = 0, $nodiv = 0, $admin = '1', $morecss = '', $textfordropdown = '')
4009
{
4010
	global $conf, $langs;
4011
4012
	if ($infoonimgalt)
4013
	{
4014
		$result = img_picto($text, 'info', 'class="hideonsmartphone'.($morecss ? ' '.$morecss : '').'"');
4015
	} else {
4016
		if (empty($conf->use_javascript_ajax)) $textfordropdown = '';
4017
4018
		$class = (empty($admin) ? 'undefined' : ($admin == '1' ? 'info' : $admin));
4019
		$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>');
4020
4021
		if ($textfordropdown) {
4022
			$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...
4023
			$tmpresult .= '<script type="text/javascript" language="javascript">
4024
				jQuery(document).ready(function() {
4025
					jQuery(".'.$class.'text").click(function() {
4026
						console.log("toggle text");
4027
						jQuery(".'.$class.'").toggle();
4028
					});
4029
				});
4030
				</script>';
4031
4032
			$result = $tmpresult.$result;
4033
		}
4034
	}
4035
4036
	return $result;
4037
}
4038
4039
4040
/**
4041
 *  Displays error message system with all the information to facilitate the diagnosis and the escalation of the bugs.
4042
 *  This function must be called when a blocking technical error is encountered.
4043
 *  However, one must try to call it only within php pages, classes must return their error through their property "error".
4044
 *
4045
 *	@param	 	DoliDB          $db      	Database handler
4046
 *	@param  	string|string[] $error		String or array of errors strings to show
4047
 *  @param		array           $errors		Array of errors
4048
 *	@return 	void
4049
 *  @see    	dol_htmloutput_errors()
4050
 */
4051
function dol_print_error($db = '', $error = '', $errors = null)
4052
{
4053
	global $conf, $langs, $argv;
4054
	global $dolibarr_main_prod;
4055
4056
	$out = '';
4057
	$syslog = '';
4058
4059
	// If error occurs before the $lang object was loaded
4060
	if (!$langs)
4061
	{
4062
		require_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
4063
		$langs = new Translate('', $conf);
4064
		$langs->load("main");
4065
	}
4066
4067
	// Load translation files required by the error messages
4068
	$langs->loadLangs(array('main', 'errors'));
4069
4070
	if ($_SERVER['DOCUMENT_ROOT'])    // Mode web
4071
	{
4072
		$out .= $langs->trans("DolibarrHasDetectedError").".<br>\n";
4073
		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";
4074
		$out .= $langs->trans("InformationToHelpDiagnose").":<br>\n";
4075
4076
		$out .= "<b>".$langs->trans("Date").":</b> ".dol_print_date(time(), 'dayhourlog')."<br>\n";
4077
		$out .= "<b>".$langs->trans("Dolibarr").":</b> ".DOL_VERSION." - https://www.dolibarr.org<br>\n";
4078
		if (isset($conf->global->MAIN_FEATURES_LEVEL)) $out .= "<b>".$langs->trans("LevelOfFeature").":</b> ".$conf->global->MAIN_FEATURES_LEVEL."<br>\n";
4079
		if (function_exists("phpversion"))
4080
		{
4081
			$out .= "<b>".$langs->trans("PHP").":</b> ".phpversion()."<br>\n";
4082
		}
4083
		$out .= "<b>".$langs->trans("Server").":</b> ".dol_htmlentities($_SERVER["SERVER_SOFTWARE"])."<br>\n";
4084
		if (function_exists("php_uname"))
4085
		{
4086
			$out .= "<b>".$langs->trans("OS").":</b> ".php_uname()."<br>\n";
4087
		}
4088
		$out .= "<b>".$langs->trans("UserAgent").":</b> ".dol_htmlentities($_SERVER["HTTP_USER_AGENT"], ENT_COMPAT, 'UTF-8')."<br>\n";
4089
		$out .= "<br>\n";
4090
		$out .= "<b>".$langs->trans("RequestedUrl").":</b> ".dol_htmlentities($_SERVER["REQUEST_URI"], ENT_COMPAT, 'UTF-8')."<br>\n";
4091
		$out .= "<b>".$langs->trans("Referer").":</b> ".(isset($_SERVER["HTTP_REFERER"]) ? dol_htmlentities($_SERVER["HTTP_REFERER"], ENT_COMPAT, 'UTF-8') : '')."<br>\n";
4092
		$out .= "<b>".$langs->trans("MenuManager").":</b> ".(isset($conf->standard_menu) ? dol_htmlentities($conf->standard_menu) : '')."<br>\n";
4093
		$out .= "<br>\n";
4094
		$syslog .= "url=".dol_escape_htmltag($_SERVER["REQUEST_URI"]);
4095
		$syslog .= ", query_string=".dol_escape_htmltag($_SERVER["QUERY_STRING"]);
4096
	} else // Mode CLI
4097
	{
4098
		$out .= '> '.$langs->transnoentities("ErrorInternalErrorDetected").":\n".$argv[0]."\n";
4099
		$syslog .= "pid=".dol_getmypid();
4100
	}
4101
4102
	if (!empty($conf->modules))
4103
	{
4104
		$out .= "<b>".$langs->trans("Modules").":</b> ".join(', ', $conf->modules)."<br>\n";
4105
	}
4106
4107
	if (is_object($db))
4108
	{
4109
		if ($_SERVER['DOCUMENT_ROOT'])  // Mode web
4110
		{
4111
			$out .= "<b>".$langs->trans("DatabaseTypeManager").":</b> ".$db->type."<br>\n";
4112
			$out .= "<b>".$langs->trans("RequestLastAccessInError").":</b> ".($db->lastqueryerror() ? dol_escape_htmltag($db->lastqueryerror()) : $langs->trans("ErrorNoRequestInError"))."<br>\n";
4113
			$out .= "<b>".$langs->trans("ReturnCodeLastAccessInError").":</b> ".($db->lasterrno() ? dol_escape_htmltag($db->lasterrno()) : $langs->trans("ErrorNoRequestInError"))."<br>\n";
4114
			$out .= "<b>".$langs->trans("InformationLastAccessInError").":</b> ".($db->lasterror() ? dol_escape_htmltag($db->lasterror()) : $langs->trans("ErrorNoRequestInError"))."<br>\n";
4115
			$out .= "<br>\n";
4116
		} else // Mode CLI
4117
		{
4118
			// No dol_escape_htmltag for output, we are in CLI mode
4119
			$out .= '> '.$langs->transnoentities("DatabaseTypeManager").":\n".$db->type."\n";
4120
			$out .= '> '.$langs->transnoentities("RequestLastAccessInError").":\n".($db->lastqueryerror() ? $db->lastqueryerror() : $langs->transnoentities("ErrorNoRequestInError"))."\n";
4121
			$out .= '> '.$langs->transnoentities("ReturnCodeLastAccessInError").":\n".($db->lasterrno() ? $db->lasterrno() : $langs->transnoentities("ErrorNoRequestInError"))."\n";
4122
			$out .= '> '.$langs->transnoentities("InformationLastAccessInError").":\n".($db->lasterror() ? $db->lasterror() : $langs->transnoentities("ErrorNoRequestInError"))."\n";
4123
		}
4124
		$syslog .= ", sql=".$db->lastquery();
4125
		$syslog .= ", db_error=".$db->lasterror();
4126
	}
4127
4128
	if ($error || $errors)
4129
	{
4130
		$langs->load("errors");
4131
4132
		// Merge all into $errors array
4133
		if (is_array($error) && is_array($errors)) $errors = array_merge($error, $errors);
4134
		elseif (is_array($error)) $errors = $error;
4135
		elseif (is_array($errors)) $errors = array_merge(array($error), $errors);
4136
		else $errors = array_merge(array($error));
4137
4138
		foreach ($errors as $msg)
4139
		{
4140
			if (empty($msg)) continue;
4141
			if ($_SERVER['DOCUMENT_ROOT'])  // Mode web
4142
			{
4143
				$out .= "<b>".$langs->trans("Message").":</b> ".dol_escape_htmltag($msg)."<br>\n";
4144
			} else // Mode CLI
4145
			{
4146
				$out .= '> '.$langs->transnoentities("Message").":\n".$msg."\n";
4147
			}
4148
			$syslog .= ", msg=".$msg;
4149
		}
4150
	}
4151
	if (empty($dolibarr_main_prod) && $_SERVER['DOCUMENT_ROOT'] && function_exists('xdebug_print_function_stack') && function_exists('xdebug_call_file'))
4152
	{
4153
		xdebug_print_function_stack();
4154
		$out .= '<b>XDebug informations:</b>'."<br>\n";
4155
		$out .= 'File: '.xdebug_call_file()."<br>\n";
4156
		$out .= 'Line: '.xdebug_call_line()."<br>\n";
4157
		$out .= 'Function: '.xdebug_call_function()."<br>\n";
4158
		$out .= "<br>\n";
4159
	}
4160
4161
	// Return a http error code if possible
4162
	if (!headers_sent()) {
4163
		http_response_code(500);
4164
	}
4165
4166
	if (empty($dolibarr_main_prod)) {
4167
		print $out;
4168
	} else {
4169
		if (empty($langs->defaultlang)) $langs->setDefaultLang();
4170
		$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.
4171
		// This should not happen, except if there is a bug somewhere. Enabled and check log in such case.
4172
		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";
4173
		print $langs->trans("DolibarrHasDetectedError").'. ';
4174
		print $langs->trans("YouCanSetOptionDolibarrMainProdToZero");
4175
		define("MAIN_CORE_ERROR", 1);
4176
	}
4177
4178
	dol_syslog("Error ".$syslog, LOG_ERR);
4179
}
4180
4181
/**
4182
 * Show a public email and error code to contact if technical error
4183
 *
4184
 * @param	string	$prefixcode		Prefix of public error code
4185
 * @param   string  $errormessage   Complete error message
4186
 * @param	array	$errormessages	Array of error messages
4187
 * @param	string	$morecss		More css
4188
 * @param	string	$email			Email
4189
 * @return	void
4190
 */
4191
function dol_print_error_email($prefixcode, $errormessage = '', $errormessages = array(), $morecss = 'error', $email = '')
4192
{
4193
	global $langs, $conf;
4194
4195
	if (empty($email)) $email = $conf->global->MAIN_INFO_SOCIETE_MAIL;
4196
4197
	$langs->load("errors");
4198
	$now = dol_now();
4199
4200
	print '<br><div class="center login_main_message"><div class="'.$morecss.'">';
4201
	print $langs->trans("ErrorContactEMail", $email, $prefixcode.dol_print_date($now, '%Y%m%d%H%M%S'));
4202
	if ($errormessage) print '<br><br>'.$errormessage;
4203
	if (is_array($errormessages) && count($errormessages))
4204
	{
4205
		foreach ($errormessages as $mesgtoshow)
4206
		{
4207
			print '<br><br>'.$mesgtoshow;
4208
		}
4209
	}
4210
	print '</div></div>';
4211
}
4212
4213
/**
4214
 *	Show title line of an array
4215
 *
4216
 *	@param	string	$name        Label of field
4217
 *	@param	string	$file        Url used when we click on sort picto
4218
 *	@param	string	$field       Field to use for new sorting
4219
 *	@param	string	$begin       ("" by defaut)
4220
 *	@param	string	$moreparam   Add more parameters on sort url links ("" by default)
4221
 *	@param  string	$moreattrib  Options of attribute td ("" by defaut, example: 'align="center"')
4222
 *	@param  string	$sortfield   Current field used to sort
4223
 *	@param  string	$sortorder   Current sort order
4224
 *  @param	string	$prefix		 Prefix for css. Use space after prefix to add your own CSS tag.
4225
 *  @param	string	$tooltip	 Tooltip
4226
 *  @param	string	$forcenowrapcolumntitle		No need for use 'wrapcolumntitle' css style
4227
 *	@return	void
4228
 */
4229
function print_liste_field_titre($name, $file = "", $field = "", $begin = "", $moreparam = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $tooltip = "", $forcenowrapcolumntitle = 0)
4230
{
4231
	print getTitleFieldOfList($name, 0, $file, $field, $begin, $moreparam, $moreattrib, $sortfield, $sortorder, $prefix, 0, $tooltip, $forcenowrapcolumntitle);
4232
}
4233
4234
/**
4235
 *	Get title line of an array
4236
 *
4237
 *	@param	string	$name        		Translation key of field to show or complete HTML string to show
4238
 *	@param	int		$thead		 		0=To use with standard table format, 1=To use inside <thead><tr>, 2=To use with <div>
4239
 *	@param	string	$file        		Url used when we click on sort picto
4240
 *	@param	string	$field       		Field to use for new sorting. Empty if this field is not sortable. Example "t.abc" or "t.abc,t.def"
4241
 *	@param	string	$begin       		("" by defaut)
4242
 *	@param	string	$moreparam   		Add more parameters on sort url links ("" by default)
4243
 *	@param  string	$moreattrib  		Add more attributes on th ("" by defaut, example: 'align="center"'). To add more css class, use param $prefix.
4244
 *	@param  string	$sortfield   		Current field used to sort (Ex: 'd.datep,d.id')
4245
 *	@param  string	$sortorder   		Current sort order (Ex: 'asc,desc')
4246
 *  @param	string	$prefix		 		Prefix for css. Use space after prefix to add your own CSS tag, for example 'mycss '.
4247
 *  @param	string	$disablesortlink	1=Disable sort link
4248
 *  @param	string	$tooltip	 		Tooltip
4249
 *  @param	string	$forcenowrapcolumntitle		No need for use 'wrapcolumntitle' css style
4250
 *	@return	string
4251
 */
4252
function getTitleFieldOfList($name, $thead = 0, $file = "", $field = "", $begin = "", $moreparam = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $disablesortlink = 0, $tooltip = '', $forcenowrapcolumntitle = 0)
4253
{
4254
	global $conf, $langs, $form;
4255
	//print "$name, $file, $field, $begin, $options, $moreattrib, $sortfield, $sortorder<br>\n";
4256
4257
	if ($moreattrib == 'class="right"') $prefix .= 'right '; // For backward compatibility
4258
4259
	$sortorder = strtoupper($sortorder);
4260
	$out = '';
4261
	$sortimg = '';
4262
4263
	$tag = 'th';
4264
	if ($thead == 2) $tag = 'div';
4265
4266
	$tmpsortfield = explode(',', $sortfield);
4267
	$sortfield1 = trim($tmpsortfield[0]); // If $sortfield is 'd.datep,d.id', it becomes 'd.datep'
4268
	$tmpfield = explode(',', $field);
4269
	$field1 = trim($tmpfield[0]); // If $field is 'd.datep,d.id', it becomes 'd.datep'
4270
4271
	if (empty($conf->global->MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE) && empty($forcenowrapcolumntitle)) {
4272
		$prefix = 'wrapcolumntitle '.$prefix;
4273
	}
4274
4275
	//var_dump('field='.$field.' field1='.$field1.' sortfield='.$sortfield.' sortfield1='.$sortfield1);
4276
	// If field is used as sort criteria we use a specific css class liste_titre_sel
4277
	// Example if (sortfield,field)=("nom","xxx.nom") or (sortfield,field)=("nom","nom")
4278
	$liste_titre = 'liste_titre';
4279
	if ($field1 && ($sortfield1 == $field1 || $sortfield1 == preg_replace("/^[^\.]+\./", "", $field1))) {
4280
		$liste_titre = 'liste_titre_sel';
4281
	}
4282
	$out .= '<'.$tag.' class="'.$prefix.$liste_titre.'" '.$moreattrib;
4283
	//$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)).'"' : '');
4284
	$out .= ($name && empty($conf->global->MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE) && empty($forcenowrapcolumntitle) && !dol_textishtml($name)) ? ' title="'.dol_escape_htmltag($langs->trans($name)).'"' : '';
4285
	$out .= '>';
4286
4287
	if (empty($thead) && $field && empty($disablesortlink))    // If this is a sort field
4288
	{
4289
		$options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', (is_scalar($moreparam) ? $moreparam : ''));
4290
		$options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
4291
		$options = preg_replace('/&+/i', '&', $options);
4292
		if (!preg_match('/^&/', $options)) $options = '&'.$options;
4293
4294
		$sortordertouseinlink = '';
4295
		if ($field1 != $sortfield1) // We are on another field than current sorted field
4296
		{
4297
			if (preg_match('/^DESC/i', $sortorder))
4298
			{
4299
				$sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
4300
			} else // We reverse the var $sortordertouseinlink
4301
			{
4302
				$sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
4303
			}
4304
		} else // We are on field that is the first current sorting criteria
4305
		{
4306
			if (preg_match('/^ASC/i', $sortorder))	// We reverse the var $sortordertouseinlink
4307
			{
4308
				$sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
4309
			} else {
4310
				$sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
4311
			}
4312
		}
4313
		$sortordertouseinlink = preg_replace('/,$/', '', $sortordertouseinlink);
4314
		$out .= '<a class="reposition" href="'.$file.'?sortfield='.$field.'&sortorder='.$sortordertouseinlink.'&begin='.$begin.$options.'"';
4315
		//$out .= (empty($conf->global->MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE) ? ' title="'.dol_escape_htmltag($langs->trans($name)).'"' : '');
4316
		$out .= '>';
4317
	}
4318
4319
	if ($tooltip) {
4320
		// You can also use 'TranslationString:keyfortooltiponlick' for a tooltip on click.
4321
		$tmptooltip = explode(':', $tooltip);
4322
		$out .= $form->textwithpicto($langs->trans($name), $langs->trans($tmptooltip[0]), 1, 'help', '', 0, 3, (empty($tmptooltip[1]) ? '' : 'extra_'.str_replace('.', '_', $field).'_'.$tmptooltip[1]));
4323
	}
4324
	else $out .= $langs->trans($name);
4325
4326
	if (empty($thead) && $field && empty($disablesortlink))    // If this is a sort field
4327
	{
4328
		$out .= '</a>';
4329
	}
4330
4331
	if (empty($thead) && $field)    // If this is a sort field
4332
	{
4333
		$options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', (is_scalar($moreparam) ? $moreparam : ''));
4334
		$options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
4335
		$options = preg_replace('/&+/i', '&', $options);
4336
		if (!preg_match('/^&/', $options)) $options = '&'.$options;
4337
4338
		if (!$sortorder || $field1 != $sortfield1)
4339
		{
4340
			//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",0).'</a>';
4341
			//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",0).'</a>';
4342
		} else {
4343
			if (preg_match('/^DESC/', $sortorder)) {
4344
				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",0).'</a>';
4345
				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",1).'</a>';
4346
				$sortimg .= '<span class="nowrap">'.img_up("Z-A", 0, 'paddingleft').'</span>';
4347
			}
4348
			if (preg_match('/^ASC/', $sortorder)) {
4349
				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",1).'</a>';
4350
				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",0).'</a>';
4351
				$sortimg .= '<span class="nowrap">'.img_down("A-Z", 0, 'paddingleft').'</span>';
4352
			}
4353
		}
4354
	}
4355
4356
	$out .= $sortimg;
4357
4358
	$out .= '</'.$tag.'>';
4359
4360
	return $out;
4361
}
4362
4363
/**
4364
 *	Show a title.
4365
 *
4366
 *	@param	string	$title			Title to show
4367
 *	@return	string					Title to show
4368
 *  @deprecated						Use load_fiche_titre instead
4369
 *  @see load_fiche_titre()
4370
 */
4371
function print_titre($title)
4372
{
4373
	dol_syslog(__FUNCTION__." is deprecated", LOG_WARNING);
4374
4375
	print '<div class="titre">'.$title.'</div>';
4376
}
4377
4378
/**
4379
 *	Show a title with picto
4380
 *
4381
 *	@param	string	$title				Title to show
4382
 *	@param	string	$mesg				Added message to show on right
4383
 *	@param	string	$picto				Icon to use before title (should be a 32x32 transparent png file)
4384
 *	@param	int		$pictoisfullpath	1=Icon name is a full absolute url of image
4385
 * 	@param	int		$id					To force an id on html objects
4386
 * 	@return	void
4387
 *  @deprecated Use print load_fiche_titre instead
4388
 */
4389
function print_fiche_titre($title, $mesg = '', $picto = 'generic', $pictoisfullpath = 0, $id = '')
4390
{
4391
	print load_fiche_titre($title, $mesg, $picto, $pictoisfullpath, $id);
4392
}
4393
4394
/**
4395
 *	Load a title with picto
4396
 *
4397
 *	@param	string	$titre				Title to show
4398
 *	@param	string	$morehtmlright		Added message to show on right
4399
 *	@param	string	$picto				Icon to use before title (should be a 32x32 transparent png file)
4400
 *	@param	int		$pictoisfullpath	1=Icon name is a full absolute url of image
4401
 * 	@param	string	$id					To force an id on html objects
4402
 *  @param  string  $morecssontable     More css on table
4403
 *	@param	string	$morehtmlcenter		Added message to show on center
4404
 * 	@return	string
4405
 *  @see print_barre_liste()
4406
 */
4407
function load_fiche_titre($titre, $morehtmlright = '', $picto = 'generic', $pictoisfullpath = 0, $id = '', $morecssontable = '', $morehtmlcenter = '')
4408
{
4409
	global $conf;
4410
4411
	$return = '';
4412
4413
	if ($picto == 'setup') $picto = 'generic';
4414
4415
	$return .= "\n";
4416
	$return .= '<table '.($id ? 'id="'.$id.'" ' : '').'class="centpercent notopnoleftnoright table-fiche-title'.($morecssontable ? ' '.$morecssontable : '').'">'; // maring bottom must be same than into print_barre_list
4417
	$return .= '<tr class="titre">';
4418
	if ($picto) $return .= '<td class="nobordernopadding widthpictotitle valignmiddle col-picto">'.img_picto('', $picto, 'class="valignmiddle widthpictotitle pictotitle"', $pictoisfullpath).'</td>';
4419
	$return .= '<td class="nobordernopadding valignmiddle col-title">';
4420
	$return .= '<div class="titre inline-block">'.$titre.'</div>';
4421
	$return .= '</td>';
4422
	if (dol_strlen($morehtmlcenter))
4423
	{
4424
		$return .= '<td class="nobordernopadding center valignmiddle">'.$morehtmlcenter.'</td>';
4425
	}
4426
	if (dol_strlen($morehtmlright))
4427
	{
4428
		$return .= '<td class="nobordernopadding titre_right wordbreakimp right valignmiddle">'.$morehtmlright.'</td>';
4429
	}
4430
	$return .= '</tr></table>'."\n";
4431
4432
	return $return;
4433
}
4434
4435
/**
4436
 *	Print a title with navigation controls for pagination
4437
 *
4438
 *	@param	string	    $titre				Title to show (required)
4439
 *	@param	int   	    $page				Numero of page to show in navigation links (required)
4440
 *	@param	string	    $file				Url of page (required)
4441
 *	@param	string	    $options         	More parameters for links ('' by default, does not include sortfield neither sortorder). Value must be 'urlencoded' before calling function.
4442
 *	@param	string    	$sortfield       	Field to sort on ('' by default)
4443
 *	@param	string	    $sortorder       	Order to sort ('' by default)
4444
 *	@param	string	    $morehtmlcenter     String in the middle ('' by default). We often find here string $massaction comming from $form->selectMassAction()
4445
 *	@param	int		    $num				Number of records found by select with limit+1
4446
 *	@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.
4447
 *	@param	string	    $picto				Icon to use before title (should be a 32x32 transparent png file)
4448
 *	@param	int		    $pictoisfullpath	1=Icon name is a full absolute url of image
4449
 *  @param	string	    $morehtmlright		More html to show (after arrows)
4450
 *  @param  string      $morecss            More css to the table
4451
 *  @param  int         $limit              Max number of lines (-1 = use default, 0 = no limit, > 0 = limit).
4452
 *  @param  int         $hideselectlimit    Force to hide select limit
4453
 *  @param  int         $hidenavigation     Force to hide all navigation tools
4454
 *  @param  int			$pagenavastextinput 1=Do not suggest list of pages to navigate but suggest the page number into an input field.
4455
 *  @param	string		$morehtmlrightbeforearrow	More html to show (before arrows)
4456
 *	@return	void
4457
 */
4458
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 = '')
4459
{
4460
	global $conf, $langs;
4461
4462
	$savlimit = $limit;
4463
	$savtotalnboflines = $totalnboflines;
4464
	$totalnboflines = abs((int) $totalnboflines);
4465
4466
	if ($picto == 'setup') $picto = 'title_setup.png';
4467
	if (($conf->browser->name == 'ie') && $picto == 'generic') $picto = 'title.gif';
4468
	if ($limit < 0) $limit = $conf->liste_limit;
4469
	if ($savlimit != 0 && (($num > $limit) || ($num == -1) || ($limit == 0)))
4470
	{
4471
		$nextpage = 1;
4472
	} else {
4473
		$nextpage = 0;
4474
	}
4475
	//print 'totalnboflines='.$totalnboflines.'-savlimit='.$savlimit.'-limit='.$limit.'-num='.$num.'-nextpage='.$nextpage;
4476
4477
	print "\n";
4478
	print "<!-- Begin title '".$titre."' -->\n";
4479
	print '<table class="centpercent notopnoleftnoright table-fiche-title'.($morecss ? ' '.$morecss : '').'"><tr>'; // maring bottom must be same than into load_fiche_tire
4480
4481
	// Left
4482
4483
	if ($picto && $titre) print '<td class="nobordernopadding widthpictotitle valignmiddle col-picto">'.img_picto('', $picto, 'class="valignmiddle pictotitle widthpictotitle"', $pictoisfullpath).'</td>';
4484
	print '<td class="nobordernopadding valignmiddle col-title">';
4485
	print '<div class="titre inline-block">'.$titre;
4486
	if (!empty($titre) && $savtotalnboflines >= 0 && (string) $savtotalnboflines != '') print '<span class="opacitymedium colorblack paddingleft">('.$totalnboflines.')</span>';
4487
	print '</div></td>';
4488
4489
	// Center
4490
	if ($morehtmlcenter)
4491
	{
4492
		print '<td class="nobordernopadding center valignmiddle">'.$morehtmlcenter.'</td>';
4493
	}
4494
4495
	// Right
4496
	print '<td class="nobordernopadding valignmiddle right">';
4497
	print '<input type="hidden" name="pageplusoneold" value="'.((int) $page + 1).'">';
4498
	if ($sortfield) $options .= "&sortfield=".urlencode($sortfield);
4499
	if ($sortorder) $options .= "&sortorder=".urlencode($sortorder);
4500
	// Show navigation bar
4501
	$pagelist = '';
4502
	if ($savlimit != 0 && ($page > 0 || $num > $limit))
4503
	{
4504
		if ($totalnboflines)	// If we know total nb of lines
4505
		{
4506
			// Define nb of extra page links before and after selected page + ... + first or last
4507
			$maxnbofpage = (empty($conf->dol_optimize_smallscreen) ? 4 : 0);
4508
4509
			if ($limit > 0) $nbpages = ceil($totalnboflines / $limit);
4510
			else $nbpages = 1;
4511
			$cpt = ($page - $maxnbofpage);
4512
			if ($cpt < 0) { $cpt = 0; }
4513
4514
			if ($cpt >= 1)
4515
			{
4516
				if (empty($pagenavastextinput)) {
4517
					$pagelist .= '<li class="pagination"><a href="'.$file.'?page=0'.$options.'">1</a></li>';
4518
					if ($cpt > 2) $pagelist .= '<li class="pagination"><span class="inactive">...</span></li>';
4519
					elseif ($cpt == 2) $pagelist .= '<li class="pagination"><a href="'.$file.'?page=1'.$options.'">2</a></li>';
4520
				}
4521
			}
4522
4523
			do {
4524
				if ($pagenavastextinput) {
4525
					if ($cpt == $page)
4526
					{
4527
						$pagelist .= '<li class="pagination"><input type="text" class="width25 center pageplusone" name="pageplusone" value="'.($page + 1).'"></li>';
4528
						$pagelist .= '/';
4529
						//if (($cpt + 1) < $nbpages) $pagelist .= '/';
4530
					}
4531
				} else {
4532
					if ($cpt == $page)
4533
					{
4534
						$pagelist .= '<li class="pagination"><span class="active">'.($page + 1).'</span></li>';
4535
					} else {
4536
						$pagelist .= '<li class="pagination"><a href="'.$file.'?page='.$cpt.$options.'">'.($cpt + 1).'</a></li>';
4537
					}
4538
				}
4539
				$cpt++;
4540
			} while ($cpt < $nbpages && $cpt <= ($page + $maxnbofpage));
4541
4542
			if (empty($pagenavastextinput)) {
4543
				if ($cpt < $nbpages)
4544
				{
4545
					if ($cpt < $nbpages - 2) $pagelist .= '<li class="pagination"><span class="inactive">...</span></li>';
4546
					elseif ($cpt == $nbpages - 2) $pagelist .= '<li class="pagination"><a href="'.$file.'?page='.($nbpages - 2).$options.'">'.($nbpages - 1).'</a></li>';
4547
					$pagelist .= '<li class="pagination"><a href="'.$file.'?page='.($nbpages - 1).$options.'">'.$nbpages.'</a></li>';
4548
				}
4549
			} else {
4550
				//var_dump($page.' '.$cpt.' '.$nbpages);
4551
				//if (($page + 1) < $nbpages) {
4552
					$pagelist .= '<li class="pagination"><a href="'.$file.'?page='.($nbpages - 1).$options.'">'.$nbpages.'</a></li>';
4553
				//}
4554
			}
4555
		} else {
4556
			$pagelist .= '<li class="pagination"><span class="active">'.($page + 1)."</li>";
4557
		}
4558
	}
4559
4560
	if ($savlimit || $morehtmlright || $morehtmlrightbeforearrow) {
4561
		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
4562
	}
4563
4564
	// js to autoselect page field on focus
4565
	if ($pagenavastextinput) {
4566
		print ajax_autoselect('.pageplusone');
4567
	}
4568
4569
	print '</td>';
4570
4571
	print '</tr></table>'."\n";
4572
	print "<!-- End title -->\n\n";
4573
}
4574
4575
/**
4576
 *	Function to show navigation arrows into lists
4577
 *
4578
 *	@param	int				$page				Number of page
4579
 *	@param	string			$file				Page URL (in most cases provided with $_SERVER["PHP_SELF"])
4580
 *	@param	string			$options         	Other url parameters to propagate ("" by default, may include sortfield and sortorder)
4581
 *	@param	integer			$nextpage	    	Do we show a next page button
4582
 *	@param	string			$betweenarrows		HTML content to show between arrows. MUST contains '<li> </li>' tags or '<li><span> </span></li>'.
4583
 *  @param	string			$afterarrows		HTML content to show after arrows. Must NOT contains '<li> </li>' tags.
4584
 *  @param  int             $limit              Max nb of record to show  (-1 = no combo with limit, 0 = no limit, > 0 = limit)
4585
 *	@param	int		        $totalnboflines		Total number of records/lines for all pages (if known)
4586
 *  @param  int             $hideselectlimit    Force to hide select limit
4587
 *  @param	string			$beforearrows		HTML content to show before arrows. Must NOT contains '<li> </li>' tags.
4588
 *	@return	void
4589
 */
4590
function print_fleche_navigation($page, $file, $options = '', $nextpage = 0, $betweenarrows = '', $afterarrows = '', $limit = -1, $totalnboflines = 0, $hideselectlimit = 0, $beforearrows = '')
4591
{
4592
	global $conf, $langs;
4593
4594
	print '<div class="pagination"><ul>';
4595
	if ($beforearrows)
4596
	{
4597
		print '<li class="paginationbeforearrows">';
4598
		print $beforearrows;
4599
		print '</li>';
4600
	}
4601
	if ((int) $limit > 0 && empty($hideselectlimit))
4602
	{
4603
		$pagesizechoices = '10:10,15:15,20:20,30:30,40:40,50:50,100:100,250:250,500:500,1000:1000,5000:5000';
4604
		//$pagesizechoices.=',0:'.$langs->trans("All");     // Not yet supported
4605
		//$pagesizechoices.=',2:2';
4606
		if (!empty($conf->global->MAIN_PAGESIZE_CHOICES)) $pagesizechoices = $conf->global->MAIN_PAGESIZE_CHOICES;
4607
4608
		print '<li class="pagination">';
4609
		print '<select class="flat selectlimit" name="limit" title="'.dol_escape_htmltag($langs->trans("MaxNbOfRecordPerPage")).'">';
4610
		$tmpchoice = explode(',', $pagesizechoices);
4611
		$tmpkey = $limit.':'.$limit;
4612
		if (!in_array($tmpkey, $tmpchoice)) $tmpchoice[] = $tmpkey;
4613
		$tmpkey = $conf->liste_limit.':'.$conf->liste_limit;
4614
		if (!in_array($tmpkey, $tmpchoice)) $tmpchoice[] = $tmpkey;
4615
		asort($tmpchoice, SORT_NUMERIC);
4616
		foreach ($tmpchoice as $val)
4617
		{
4618
			$selected = '';
4619
			$tmp = explode(':', $val);
4620
			$key = $tmp[0];
4621
			$val = $tmp[1];
4622
			if ($key != '' && $val != '')
4623
			{
4624
				if ((int) $key == (int) $limit)
4625
				{
4626
					$selected = ' selected="selected"';
4627
				}
4628
				print '<option name="'.$key.'"'.$selected.'>'.dol_escape_htmltag($val).'</option>'."\n";
4629
			}
4630
		}
4631
		print '</select>';
4632
		if ($conf->use_javascript_ajax)
4633
		{
4634
			print '<!-- JS CODE TO ENABLE select limit to launch submit of page -->
4635
            		<script>
4636
                	jQuery(document).ready(function () {
4637
            	  		jQuery(".selectlimit").change(function() {
4638
                            console.log("Change limit. Send submit");
4639
                            $(this).parents(\'form:first\').submit();
4640
            	  		});
4641
                	});
4642
            		</script>
4643
                ';
4644
		}
4645
		print '</li>';
4646
	}
4647
	if ($page > 0)
4648
	{
4649
		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>';
4650
	}
4651
	if ($betweenarrows)
4652
	{
4653
		print '<!--<div class="betweenarrows nowraponall inline-block">-->';
4654
		print $betweenarrows;
4655
		print '<!--</div>-->';
4656
	}
4657
	if ($nextpage > 0)
4658
	{
4659
		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>';
4660
	}
4661
	if ($afterarrows)
4662
	{
4663
		print '<li class="paginationafterarrows">';
4664
		print $afterarrows;
4665
		print '</li>';
4666
	}
4667
	print '</ul></div>'."\n";
4668
}
4669
4670
4671
/**
4672
 *	Return a string with VAT rate label formated for view output
4673
 *	Used into pdf and HTML pages
4674
 *
4675
 *	@param	string	$rate			Rate value to format ('19.6', '19,6', '19.6%', '19,6%', '19.6 (CODEX)', ...)
4676
 *  @param	boolean	$addpercent		Add a percent % sign in output
4677
 *	@param	int		$info_bits		Miscellaneous information on vat (0=Default, 1=French NPR vat)
4678
 *	@param	int		$usestarfornpr	-1=Never show, 0 or 1=Use '*' for NPR vat rates
4679
 *  @return	string					String with formated amounts ('19,6' or '19,6%' or '8.5% (NPR)' or '8.5% *' or '19,6 (CODEX)')
4680
 */
4681
function vatrate($rate, $addpercent = false, $info_bits = 0, $usestarfornpr = 0)
4682
{
4683
	$morelabel = '';
4684
4685
	if (preg_match('/%/', $rate))
4686
	{
4687
		$rate = str_replace('%', '', $rate);
4688
		$addpercent = true;
4689
	}
4690
	if (preg_match('/\((.*)\)/', $rate, $reg))
4691
	{
4692
		$morelabel = ' ('.$reg[1].')';
4693
		$rate = preg_replace('/\s*'.preg_quote($morelabel, '/').'/', '', $rate);
4694
	}
4695
	if (preg_match('/\*/', $rate))
4696
	{
4697
		$rate = str_replace('*', '', $rate);
4698
		$info_bits |= 1;
4699
	}
4700
4701
	// If rate is '9/9/9' we don't change it.  If rate is '9.000' we apply price()
4702
	if (!preg_match('/\//', $rate)) $ret = price($rate, 0, '', 0, 0).($addpercent ? '%' : '');
4703
	else {
4704
		// TODO Split on / and output with a price2num to have clean numbers without ton of 000.
4705
		$ret = $rate.($addpercent ? '%' : '');
4706
	}
4707
	if (($info_bits & 1) && $usestarfornpr >= 0) $ret .= ' *';
4708
	$ret .= $morelabel;
4709
	return $ret;
4710
}
4711
4712
4713
/**
4714
 *		Function to format a value into an amount for visual output
4715
 *		Function used into PDF and HTML pages
4716
 *
4717
 *		@param	float		$amount			Amount to format
4718
 *		@param	integer		$form			Type of format, HTML or not (not by default)
4719
 *		@param	Translate	$outlangs		Object langs for output
4720
 *		@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.
4721
 *		@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)
4722
 *		@param	int			$forcerounding	Force the number of decimal to forcerounding decimal (-1=do not force)
4723
 *		@param	string		$currency_code	To add currency symbol (''=add nothing, 'auto'=Use default currency, 'XXX'=add currency symbols for XXX currency)
4724
 *		@return	string						Chaine avec montant formate
4725
 *
4726
 *		@see	price2num()					Revert function of price
4727
 */
4728
function price($amount, $form = 0, $outlangs = '', $trunc = 1, $rounding = -1, $forcerounding = -1, $currency_code = '')
4729
{
4730
	global $langs, $conf;
4731
4732
	// Clean parameters
4733
	if (empty($amount)) $amount = 0; // To have a numeric value if amount not defined or = ''
4734
	$amount = (is_numeric($amount) ? $amount : 0); // Check if amount is numeric, for example, an error occured when amount value = o (letter) instead 0 (number)
4735
	if ($rounding < 0) $rounding = min($conf->global->MAIN_MAX_DECIMALS_UNIT, $conf->global->MAIN_MAX_DECIMALS_TOT);
4736
	$nbdecimal = $rounding;
4737
4738
	// Output separators by default (french)
4739
	$dec = ','; $thousand = ' ';
4740
4741
	// If $outlangs not forced, we use use language
4742
	if (!is_object($outlangs)) $outlangs = $langs;
4743
4744
	if ($outlangs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal")  $dec = $outlangs->transnoentitiesnoconv("SeparatorDecimal");
4745
	if ($outlangs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") $thousand = $outlangs->transnoentitiesnoconv("SeparatorThousand");
4746
	if ($thousand == 'None') $thousand = '';
4747
	elseif ($thousand == 'Space') $thousand = ' ';
4748
	//print "outlangs=".$outlangs->defaultlang." amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
4749
4750
	//print "amount=".$amount."-";
4751
	$amount = str_replace(',', '.', $amount); // should be useless
4752
	//print $amount."-";
4753
	$datas = explode('.', $amount);
4754
	$decpart = isset($datas[1]) ? $datas[1] : '';
4755
	$decpart = preg_replace('/0+$/i', '', $decpart); // Supprime les 0 de fin de partie decimale
4756
	//print "decpart=".$decpart."<br>";
4757
	$end = '';
4758
4759
	// We increase nbdecimal if there is more decimal than asked (to not loose information)
4760
	if (dol_strlen($decpart) > $nbdecimal) $nbdecimal = dol_strlen($decpart);
4761
	// Si on depasse max
4762
	if ($trunc && $nbdecimal > $conf->global->MAIN_MAX_DECIMALS_SHOWN)
4763
	{
4764
		$nbdecimal = $conf->global->MAIN_MAX_DECIMALS_SHOWN;
4765
		if (preg_match('/\.\.\./i', $conf->global->MAIN_MAX_DECIMALS_SHOWN))
4766
		{
4767
			// Si un affichage est tronque, on montre des ...
4768
			$end = '...';
4769
		}
4770
	}
4771
4772
	// If force rounding
4773
	if ($forcerounding >= 0) $nbdecimal = $forcerounding;
4774
4775
	// Format number
4776
	$output = number_format($amount, $nbdecimal, $dec, $thousand);
4777
	if ($form)
4778
	{
4779
		$output = preg_replace('/\s/', '&nbsp;', $output);
4780
		$output = preg_replace('/\'/', '&#039;', $output);
4781
	}
4782
	// Add symbol of currency if requested
4783
	$cursymbolbefore = $cursymbolafter = '';
4784
	if ($currency_code)
4785
	{
4786
		if ($currency_code == 'auto') $currency_code = $conf->currency;
4787
4788
		$listofcurrenciesbefore = array('AUD', 'CAD', 'CNY', 'COP', 'CLP', 'GBP', 'HKD', 'MXN', 'PEN', 'USD');
4789
		$listoflanguagesbefore = array('nl_NL');
4790
		if (in_array($currency_code, $listofcurrenciesbefore) || in_array($outlangs->defaultlang, $listoflanguagesbefore))
4791
		{
4792
			$cursymbolbefore .= $outlangs->getCurrencySymbol($currency_code);
4793
		} else {
4794
			$tmpcur = $outlangs->getCurrencySymbol($currency_code);
4795
			$cursymbolafter .= ($tmpcur == $currency_code ? ' '.$tmpcur : $tmpcur);
4796
		}
4797
	}
4798
	$output = $cursymbolbefore.$output.$end.($cursymbolafter ? ' ' : '').$cursymbolafter;
4799
4800
	return $output;
4801
}
4802
4803
/**
4804
 *	Function that return a number with universal decimal format (decimal separator is '.') from an amount typed by a user.
4805
 *	Function to use on each input amount before any numeric test or database insert. A better name for this function
4806
 *  should be roundtext2num().
4807
 *
4808
 *	@param	string|float	$amount			Amount to convert/clean or round
4809
 *	@param	string			$rounding		''=No rounding
4810
 * 											'MU'=Round to Max unit price (MAIN_MAX_DECIMALS_UNIT)
4811
 *											'MT'=Round to Max for totals with Tax (MAIN_MAX_DECIMALS_TOT)
4812
 *											'MS'=Round to Max for stock quantity (MAIN_MAX_DECIMALS_STOCK)
4813
 *      		                            'CR'=Currency rate
4814
 *											Numeric = Nb of digits for rounding
4815
 * 	@param	int				$option			Put 1 if you know that content is already universal format number (so no correction on decimal will be done)
4816
 * 											Put 2 if you know that number is a user input (so we know we don't have to fix decimal separator).
4817
 *	@return	string							Amount with universal numeric format (Example: '99.99999').
4818
 *											If conversion fails, it return text unchanged if $rounding = '' or '0' if $rounding is defined.
4819
 *											If amount is null or '', it returns '' if $rounding = '' or '0' if $rounding is defined..
4820
 *
4821
 *	@see    price()							Opposite function of price2num
4822
 */
4823
function price2num($amount, $rounding = '', $option = 0)
4824
{
4825
	global $langs, $conf;
4826
4827
	// Round PHP function does not allow number like '1,234.56' nor '1.234,56' nor '1 234,56'
4828
	// Numbers must be '1234.56'
4829
	// Decimal delimiter for PHP and database SQL requests must be '.'
4830
	$dec = ','; $thousand = ' ';
4831
	if ($langs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal")  $dec = $langs->transnoentitiesnoconv("SeparatorDecimal");
4832
	if ($langs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") $thousand = $langs->transnoentitiesnoconv("SeparatorThousand");
4833
	if ($thousand == 'None') $thousand = '';
4834
	elseif ($thousand == 'Space') $thousand = ' ';
4835
	//print "amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
4836
4837
	// Convert value to universal number format (no thousand separator, '.' as decimal separator)
4838
	if ($option != 1) {	// If not a PHP number or unknown, we change or clean format
4839
		//print 'PP'.$amount.' - '.$dec.' - '.$thousand.' - '.intval($amount).'<br>';
4840
		if (!is_numeric($amount)) {
4841
			$amount = preg_replace('/[a-zA-Z\/\\\*\(\)\<\>]/', '', $amount);
4842
		}
4843
4844
		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
4845
			$amount = str_replace($thousand, '', $amount);
4846
		}
4847
4848
		// Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
4849
		// to format defined by LC_NUMERIC after a calculation and we want source format to be like defined by Dolibarr setup.
4850
		// So if number was already a good number, it is converted into local Dolibarr setup.
4851
		if (is_numeric($amount))
4852
		{
4853
			// We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
4854
			$temps = sprintf("%0.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
4855
			$temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
4856
			$nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
4857
			$amount = number_format($amount, $nbofdec, $dec, $thousand);
4858
		}
4859
		//print "QQ".$amount."<br>\n";
4860
4861
		// Now make replace (the main goal of function)
4862
		if ($thousand != ',' && $thousand != '.') {
4863
			$amount = str_replace(',', '.', $amount); // To accept 2 notations for french users
4864
		}
4865
		$amount = str_replace(' ', '', $amount); // To avoid spaces
4866
		$amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
4867
		$amount = str_replace($dec, '.', $amount);
4868
	}
4869
4870
	// Now, make a rounding if required
4871
	if ($rounding)
4872
	{
4873
		$nbofdectoround = '';
4874
		if ($rounding == 'MU')     $nbofdectoround = $conf->global->MAIN_MAX_DECIMALS_UNIT;
4875
		elseif ($rounding == 'MT') $nbofdectoround = $conf->global->MAIN_MAX_DECIMALS_TOT;
4876
		elseif ($rounding == 'MS') $nbofdectoround = empty($conf->global->MAIN_MAX_DECIMALS_STOCK) ? 5 : $conf->global->MAIN_MAX_DECIMALS_STOCK;
4877
		elseif ($rounding == 'CR') $nbofdectoround = 8;
4878
		elseif (is_numeric($rounding))  $nbofdectoround = $rounding;
4879
		//print "RR".$amount.' - '.$nbofdectoround.'<br>';
4880
		if (dol_strlen($nbofdectoround)) $amount = round(is_string($amount) ? (float) $amount : $amount, $nbofdectoround); // $nbofdectoround can be 0.
4881
		else return 'ErrorBadParameterProvidedToFunction';
4882
		//print 'SS'.$amount.' - '.$nbofdec.' - '.$dec.' - '.$thousand.' - '.$nbofdectoround.'<br>';
4883
4884
		// Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
4885
		// to format defined by LC_NUMERIC after a calculation and we want source format to be defined by Dolibarr setup.
4886
		if (is_numeric($amount))
4887
		{
4888
			// We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
4889
			$temps = sprintf("%0.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
4890
			$temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
4891
			$nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
4892
			$amount = number_format($amount, min($nbofdec, $nbofdectoround), $dec, $thousand); // Convert amount to format with dolibarr dec and thousand
4893
		}
4894
		//print "TT".$amount.'<br>';
4895
4896
		// Always make replace because each math function (like round) replace
4897
		// with local values and we want a number that has a SQL string format x.y
4898
		if ($thousand != ',' && $thousand != '.') $amount = str_replace(',', '.', $amount); // To accept 2 notations for french users
4899
		$amount = str_replace(' ', '', $amount); // To avoid spaces
4900
		$amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
4901
		$amount = str_replace($dec, '.', $amount);
4902
	}
4903
4904
	return $amount;
4905
}
4906
4907
/**
4908
 * Output a dimension with best unit
4909
 *
4910
 * @param   float       $dimension      Dimension
4911
 * @param   int         $unit           Unit scale of dimension (Example: 0=kg, -3=g, -6=mg, 98=ounce, 99=pound, ...)
4912
 * @param   string      $type           'weight', 'volume', ...
4913
 * @param   Translate   $outputlangs    Translate language object
4914
 * @param   int         $round          -1 = non rounding, x = number of decimal
4915
 * @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)
4916
 * @return  string                      String to show dimensions
4917
 */
4918
function showDimensionInBestUnit($dimension, $unit, $type, $outputlangs, $round = -1, $forceunitoutput = 'no')
4919
{
4920
	require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
4921
4922
	if (($forceunitoutput == 'no' && $dimension < 1 / 10000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -6))
4923
	{
4924
		$dimension = $dimension * 1000000;
4925
		$unit = $unit - 6;
4926
	} elseif (($forceunitoutput == 'no' && $dimension < 1 / 10 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -3))
4927
	{
4928
		$dimension = $dimension * 1000;
4929
		$unit = $unit - 3;
4930
	} elseif (($forceunitoutput == 'no' && $dimension > 100000000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 6))
4931
	{
4932
		$dimension = $dimension / 1000000;
4933
		$unit = $unit + 6;
4934
	} elseif (($forceunitoutput == 'no' && $dimension > 100000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 3))
4935
	{
4936
		$dimension = $dimension / 1000;
4937
		$unit = $unit + 3;
4938
	}
4939
	// Special case when we want output unit into pound or ounce
4940
	/* TODO
4941
	if ($unit < 90 && $type == 'weight' && is_numeric($forceunitoutput) && (($forceunitoutput == 98) || ($forceunitoutput == 99))
4942
	{
4943
	    $dimension = // convert dimension from standard unit into ounce or pound
4944
	    $unit = $forceunitoutput;
4945
	}
4946
	if ($unit > 90 && $type == 'weight' && is_numeric($forceunitoutput) && $forceunitoutput < 90)
4947
	{
4948
	    $dimension = // convert dimension from standard unit into ounce or pound
4949
	    $unit = $forceunitoutput;
4950
	}*/
4951
4952
	$ret = price($dimension, 0, $outputlangs, 0, 0, $round).' '.measuringUnitString(0, $type, $unit);
4953
4954
	return $ret;
4955
}
4956
4957
4958
/**
4959
 *	Return localtax rate for a particular vat, when selling a product with vat $vatrate, from a $thirdparty_buyer to a $thirdparty_seller
4960
 *  Note: This function applies same rules than get_default_tva
4961
 *
4962
 * 	@param	float		$vatrate		        Vat rate. Can be '8.5' or '8.5 (VATCODEX)' for example
4963
 * 	@param  int			$local		         	Local tax to search and return (1 or 2 return only tax rate 1 or tax rate 2)
4964
 *  @param  Societe		$thirdparty_buyer    	Object of buying third party
4965
 *  @param	Societe		$thirdparty_seller		Object of selling third party ($mysoc if not defined)
4966
 *  @param	int			$vatnpr					If vat rate is NPR or not
4967
 * 	@return	mixed			   					0 if not found, localtax rate if found
4968
 *  @see get_default_tva()
4969
 */
4970
function get_localtax($vatrate, $local, $thirdparty_buyer = "", $thirdparty_seller = "", $vatnpr = 0)
4971
{
4972
	global $db, $conf, $mysoc;
4973
4974
	if (empty($thirdparty_seller) || !is_object($thirdparty_seller)) $thirdparty_seller = $mysoc;
4975
4976
	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);
4977
4978
	$vatratecleaned = $vatrate;
4979
	$reg = array();
4980
	if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) {     // If vat is "xx (yy)"
4981
		$vatratecleaned = trim($reg[1]);
4982
		$vatratecode = $reg[2];
4983
	}
4984
4985
	/*if ($thirdparty_buyer->country_code != $thirdparty_seller->country_code)
4986
	{
4987
		return 0;
4988
	}*/
4989
4990
	// Some test to guess with no need to make database access
4991
	if ($mysoc->country_code == 'ES') { // For spain localtaxes 1 and 2, tax is qualified if buyer use local tax
4992
		if ($local == 1) {
4993
			if (!$mysoc->localtax1_assuj || (string) $vatratecleaned == "0") return 0;
4994
			if ($thirdparty_seller->id == $mysoc->id) {
4995
				if (!$thirdparty_buyer->localtax1_assuj) return 0;
4996
			} else {
4997
				if (!$thirdparty_seller->localtax1_assuj) return 0;
4998
			}
4999
		}
5000
5001
		if ($local == 2) {
5002
			//if (! $mysoc->localtax2_assuj || (string) $vatratecleaned == "0") return 0;
5003
			if (!$mysoc->localtax2_assuj) return 0; // If main vat is 0, IRPF may be different than 0.
5004
			if ($thirdparty_seller->id == $mysoc->id) {
5005
				if (!$thirdparty_buyer->localtax2_assuj) return 0;
5006
			} else {
5007
				if (!$thirdparty_seller->localtax2_assuj) return 0;
5008
			}
5009
		}
5010
	} else {
5011
		if ($local == 1 && !$thirdparty_seller->localtax1_assuj) return 0;
5012
		if ($local == 2 && !$thirdparty_seller->localtax2_assuj) return 0;
5013
	}
5014
5015
	// For some country MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY is forced to on.
5016
	if (in_array($mysoc->country_code, array('ES'))) {
5017
		$conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY = 1;
5018
	}
5019
5020
	// Search local taxes
5021
	if (!empty($conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY))
5022
	{
5023
		if ($local == 1) {
5024
			if ($thirdparty_seller != $mysoc) {
5025
				if (!isOnlyOneLocalTax($local))  // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
5026
				{
5027
					return $thirdparty_seller->localtax1_value;
5028
				}
5029
			} else { // i am the seller
5030
				if (!isOnlyOneLocalTax($local)) { // TODO If seller is me, why not always returning this, even if there is only one locatax vat.
5031
					return $conf->global->MAIN_INFO_VALUE_LOCALTAX1;
5032
				}
5033
			}
5034
		}
5035
		if ($local == 2) {
5036
			if ($thirdparty_seller != $mysoc) {
5037
				if (!isOnlyOneLocalTax($local))  // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
5038
				// TODO We should also return value defined on thirdparty only if defined
5039
				{
5040
					return $thirdparty_seller->localtax2_value;
5041
				}
5042
			} else { // i am the seller
5043
				if (in_array($mysoc->country_code, array('ES'))) {
5044
					return $thirdparty_buyer->localtax2_value;
5045
				} else {
5046
					return $conf->global->MAIN_INFO_VALUE_LOCALTAX2;
5047
				}
5048
			}
5049
		}
5050
	}
5051
5052
	// By default, search value of local tax on line of common tax
5053
	$sql = "SELECT t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
5054
   	$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
5055
   	$sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($thirdparty_seller->country_code)."'";
5056
   	$sql .= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
5057
   	if (!empty($vatratecode)) $sql .= " AND t.code ='".$db->escape($vatratecode)."'"; // If we have the code, we use it in priority
5058
   	else $sql .= " AND t.recuperableonly = '".$db->escape($vatnpr)."'";
5059
   	dol_syslog("get_localtax", LOG_DEBUG);
5060
   	$resql = $db->query($sql);
5061
5062
   	if ($resql)
5063
   	{
5064
   		$obj = $db->fetch_object($resql);
5065
   		if ($obj) {
5066
	   		if ($local == 1) return $obj->localtax1;
5067
   			elseif ($local == 2) return $obj->localtax2;
5068
   		}
5069
	}
5070
5071
	return 0;
5072
}
5073
5074
5075
/**
5076
 * Return true if LocalTax (1 or 2) is unique.
5077
 * Example: If localtax1 is 5 on line with highest common vat rate, return true
5078
 * Example: If localtax1 is 5:8:15 on line with highest common vat rate, return false
5079
 *
5080
 * @param   int 	$local	Local tax to test (1 or 2)
5081
 * @return  boolean 		True if LocalTax have multiple values, False if not
5082
 */
5083
function isOnlyOneLocalTax($local)
5084
{
5085
	$tax = get_localtax_by_third($local);
5086
5087
	$valors = explode(":", $tax);
5088
5089
	if (count($valors) > 1) {
5090
		return false;
5091
	} else {
5092
		return true;
5093
	}
5094
}
5095
5096
/**
5097
 * Get values of localtaxes (1 or 2) for company country for the common vat with the highest value
5098
 *
5099
 * @param	int		$local 	LocalTax to get
5100
 * @return	number			Values of localtax
5101
 */
5102
function get_localtax_by_third($local)
5103
{
5104
	global $db, $mysoc;
5105
	$sql = "SELECT t.localtax1, t.localtax2 ";
5106
	$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t inner join ".MAIN_DB_PREFIX."c_country as c ON c.rowid=t.fk_pays";
5107
	$sql .= " WHERE c.code = '".$db->escape($mysoc->country_code)."' AND t.active = 1 AND t.taux=(";
5108
	$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";
5109
	$sql .= "  WHERE c.code = '".$db->escape($mysoc->country_code)."' AND tt.active = 1";
5110
	$sql .= "  )";
5111
5112
	$resql = $db->query($sql);
5113
	if ($resql)
5114
	{
5115
		$obj = $db->fetch_object($resql);
5116
		if ($local == 1) return $obj->localtax1;
5117
		elseif ($local == 2) return $obj->localtax2;
5118
	}
5119
5120
	return 0;
5121
}
5122
5123
5124
/**
5125
 *  Get tax (VAT) main information from Id.
5126
 *  You can also call getLocalTaxesFromRate() after to get only localtax fields.
5127
 *
5128
 *  @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.
5129
 *  @param	Societe	    $buyer         		Company object
5130
 *  @param	Societe	    $seller        		Company object
5131
 *  @param  int         $firstparamisid     1 if first param is id into table (use this if you can)
5132
 *  @return	array       	  				array('rowid'=> , 'code'=> ...)
5133
 *  @see getLocalTaxesFromRate()
5134
 */
5135
function getTaxesFromId($vatrate, $buyer = null, $seller = null, $firstparamisid = 1)
5136
{
5137
	global $db, $mysoc;
5138
5139
	dol_syslog("getTaxesFromId vat id or rate = ".$vatrate);
5140
5141
	// Search local taxes
5142
	$sql = "SELECT t.rowid, t.code, t.taux as rate, t.recuperableonly as npr, t.accountancy_code_sell, t.accountancy_code_buy,";
5143
	$sql .= " t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type";
5144
	$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t";
5145
	if ($firstparamisid) {
5146
		$sql .= " WHERE t.rowid = ".(int) $vatrate;
5147
	} else {
5148
		$vatratecleaned = $vatrate;
5149
		$vatratecode = '';
5150
		$reg = array();
5151
		if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg))      // If vat is "xx (yy)"
5152
		{
5153
			$vatratecleaned = $reg[1];
5154
			$vatratecode = $reg[2];
5155
		}
5156
5157
		$sql .= ", ".MAIN_DB_PREFIX."c_country as c";
5158
		/*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 ??
5159
		else $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($seller->country_code)."'";*/
5160
		$sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($seller->country_code)."'";
5161
		$sql .= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
5162
		if ($vatratecode) $sql .= " AND t.code = '".$db->escape($vatratecode)."'";
5163
	}
5164
5165
	$resql = $db->query($sql);
5166
	if ($resql) {
5167
		$obj = $db->fetch_object($resql);
5168
		if ($obj) return array(
5169
			'rowid'=>$obj->rowid,
5170
			'code'=>$obj->code,
5171
			'rate'=>$obj->rate,
5172
			'localtax1'=>$obj->localtax1,
5173
			'localtax1_type'=>$obj->localtax1_type,
5174
			'localtax2'=>$obj->localtax2,
5175
			'localtax2_type'=>$obj->localtax2_type,
5176
			'npr'=>$obj->npr,
5177
			'accountancy_code_sell'=>$obj->accountancy_code_sell,
5178
			'accountancy_code_buy'=>$obj->accountancy_code_buy
5179
		);
5180
		else return array();
5181
	} else dol_print_error($db);
5182
5183
	return array();
5184
}
5185
5186
/**
5187
 *  Get type and rate of localtaxes for a particular vat rate/country of a thirdparty.
5188
 *  This does not take into account the seller setup if subject to vat or not, only country.
5189
 *
5190
 *  TODO This function is ALSO called to retrieve type for building PDF. Such call of function must be removed.
5191
 *  Instead this function must be called when adding a line to get the array of possible values for localtax and type, and then
5192
 *  provide the selected value to the function calcul_price_total.
5193
 *
5194
 *  @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.
5195
 *  @param	int		    $local              Number of localtax (1 or 2, or 0 to return 1 & 2)
5196
 *  @param	Societe	    $buyer         		Company object
5197
 *  @param	Societe	    $seller        		Company object
5198
 *  @param  int         $firstparamisid     1 if first param is ID into table instead of Rate+code (use this if you can)
5199
 *  @return	array    	    				array(localtax_type1(1-6 or 0 if not found), rate localtax1, localtax_type2, rate localtax2, accountancycodecust, accountancycodesupp)
5200
 *  @see getTaxesFromId()
5201
 */
5202
function getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid = 0)
5203
{
5204
	global $db, $mysoc;
5205
5206
	dol_syslog("getLocalTaxesFromRate vatrate=".$vatrate." local=".$local);
5207
5208
	// Search local taxes
5209
	$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";
5210
	$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t";
5211
	if ($firstparamisid) {
5212
		$sql .= " WHERE t.rowid = ".(int) $vatrate;
5213
	} else {
5214
		$vatratecleaned = $vatrate;
5215
		$vatratecode = '';
5216
		$reg = array();
5217
		if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) {     // If vat is "x.x (yy)"
5218
			$vatratecleaned = $reg[1];
5219
			$vatratecode = $reg[2];
5220
		}
5221
5222
		$sql .= ", ".MAIN_DB_PREFIX."c_country as c";
5223
		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 ??
5224
		else $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape(empty($seller->country_code) ? $mysoc->country_code : $seller->country_code)."'";
5225
		$sql .= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
5226
		if ($vatratecode) $sql .= " AND t.code = '".$db->escape($vatratecode)."'";
5227
	}
5228
5229
	$resql = $db->query($sql);
5230
	if ($resql) {
5231
		$obj = $db->fetch_object($resql);
5232
5233
		if ($obj) {
5234
			$vateratestring = $obj->rate.($obj->code ? ' ('.$obj->code.')' : '');
5235
5236
			if ($local == 1) {
5237
				return array($obj->localtax1_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
5238
			} elseif ($local == 2) {
5239
				return array($obj->localtax2_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
5240
			} else {
5241
				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);
5242
			}
5243
		}
5244
	}
5245
5246
	return array();
5247
}
5248
5249
/**
5250
 *	Return vat rate of a product in a particular selling country or default country vat if product is unknown
5251
 *  Function called by get_default_tva
5252
 *
5253
 *  @param	int			$idprod          	Id of product or 0 if not a predefined product
5254
 *  @param  Societe		$thirdparty_seller  Thirdparty with a ->country_code defined (FR, US, IT, ...)
5255
 *	@param	int			$idprodfournprice	Id product_fournisseur_price (for "supplier" proposal/order/invoice)
5256
 *  @return float|string   				    Vat rate to use with format 5.0 or '5.0 (XXX)'
5257
 *  @see get_product_localtax_for_country()
5258
 */
5259
function get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice = 0)
5260
{
5261
	global $db, $conf, $mysoc;
5262
5263
	require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
5264
5265
	$ret = 0;
5266
	$found = 0;
5267
5268
	if ($idprod > 0)
5269
	{
5270
		// Load product
5271
		$product = new Product($db);
5272
		$result = $product->fetch($idprod);
5273
5274
		if ($mysoc->country_code == $thirdparty_seller->country_code) // If selling country is ours
5275
		{
5276
			if ($idprodfournprice > 0)     // We want vat for product for a "supplier" object
5277
			{
5278
				$product->get_buyprice($idprodfournprice, 0, 0, 0);
5279
				$ret = $product->vatrate_supplier;
5280
				if ($product->default_vat_code) $ret .= ' ('.$product->default_vat_code.')';
5281
			} else {
5282
				$ret = $product->tva_tx; // Default vat of product we defined
5283
				if ($product->default_vat_code) $ret .= ' ('.$product->default_vat_code.')';
5284
			}
5285
			$found = 1;
5286
		} else {
5287
			// TODO Read default product vat according to countrycode and product. Vat for couple countrycode/product is a feature not implemeted yet.
5288
			// May be usefull/required if hidden option SERVICE_ARE_ECOMMERCE_200238EC is on
5289
		}
5290
	}
5291
5292
	if (!$found)
5293
	{
5294
		if (empty($conf->global->MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS))
5295
		{
5296
			// If vat of product for the country not found or not defined, we return the first higher vat of country.
5297
			$sql = "SELECT t.taux as vat_rate, t.code as default_vat_code";
5298
			$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
5299
			$sql .= " WHERE t.active=1 AND t.fk_pays = c.rowid AND c.code='".$db->escape($thirdparty_seller->country_code)."'";
5300
			$sql .= " ORDER BY t.taux DESC, t.code ASC, t.recuperableonly ASC";
5301
			$sql .= $db->plimit(1);
5302
5303
			$resql = $db->query($sql);
5304
			if ($resql)
5305
			{
5306
				$obj = $db->fetch_object($resql);
5307
				if ($obj)
5308
				{
5309
					$ret = $obj->vat_rate;
5310
					if ($obj->default_vat_code) $ret .= ' ('.$obj->default_vat_code.')';
5311
				}
5312
				$db->free($sql);
5313
			} else dol_print_error($db);
5314
		} else $ret = $conf->global->MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS; // Forced value if autodetect fails
5315
	}
5316
5317
	dol_syslog("get_product_vat_for_country: ret=".$ret);
5318
	return $ret;
5319
}
5320
5321
/**
5322
 *	Return localtax vat rate of a product in a particular selling country or default country vat if product is unknown
5323
 *
5324
 *  @param	int		$idprod         		Id of product
5325
 *  @param  int		$local          		1 for localtax1, 2 for localtax 2
5326
 *  @param  Societe	$thirdparty_seller    	Thirdparty with a ->country_code defined (FR, US, IT, ...)
5327
 *  @return int             				<0 if KO, Vat rate if OK
5328
 *  @see get_product_vat_for_country()
5329
 */
5330
function get_product_localtax_for_country($idprod, $local, $thirdparty_seller)
5331
{
5332
	global $db, $mysoc;
5333
5334
	if (!class_exists('Product')) {
5335
		require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
5336
	}
5337
5338
	$ret = 0;
5339
	$found = 0;
5340
5341
	if ($idprod > 0)
5342
	{
5343
		// Load product
5344
		$product = new Product($db);
5345
		$result = $product->fetch($idprod);
5346
5347
		if ($mysoc->country_code == $thirdparty_seller->country_code) // If selling country is ours
5348
		{
5349
			/* Not defined yet, so we don't use this
5350
			if ($local==1) $ret=$product->localtax1_tx;
5351
			elseif ($local==2) $ret=$product->localtax2_tx;
5352
			$found=1;
5353
			*/
5354
		} else {
5355
			// TODO Read default product vat according to countrycode and product
5356
		}
5357
	}
5358
5359
	if (!$found)
5360
	{
5361
		// If vat of product for the country not found or not defined, we return higher vat of country.
5362
		$sql = "SELECT taux as vat_rate, localtax1, localtax2";
5363
		$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
5364
		$sql .= " WHERE t.active=1 AND t.fk_pays = c.rowid AND c.code='".$db->escape($thirdparty_seller->country_code)."'";
5365
		$sql .= " ORDER BY t.taux DESC, t.recuperableonly ASC";
5366
		$sql .= $db->plimit(1);
5367
5368
		$resql = $db->query($sql);
5369
		if ($resql)
5370
		{
5371
			$obj = $db->fetch_object($resql);
5372
			if ($obj)
5373
			{
5374
				if ($local == 1) $ret = $obj->localtax1;
5375
				elseif ($local == 2) $ret = $obj->localtax2;
5376
			}
5377
		} else dol_print_error($db);
5378
	}
5379
5380
	dol_syslog("get_product_localtax_for_country: ret=".$ret);
5381
	return $ret;
5382
}
5383
5384
/**
5385
 *	Function that return vat rate of a product line (according to seller, buyer and product vat rate)
5386
 *   Si vendeur non assujeti a TVA, TVA par defaut=0. Fin de regle.
5387
 *	 Si le (pays vendeur = pays acheteur) alors TVA par defaut=TVA du produit vendu. Fin de regle.
5388
 *	 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.
5389
 *	 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
5390
 *	 Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = entreprise avec num TVA) intra alors TVA par defaut=0. Fin de regle
5391
 *	 Sinon TVA proposee par defaut=0. Fin de regle.
5392
 *
5393
 *	@param	Societe		$thirdparty_seller    	Objet societe vendeuse
5394
 *	@param  Societe		$thirdparty_buyer   	Objet societe acheteuse
5395
 *	@param  int			$idprod					Id product
5396
 *	@param	int			$idprodfournprice		Id product_fournisseur_price (for supplier order/invoice)
5397
 *	@return float|string   				      	Vat rate to use with format 5.0 or '5.0 (XXX)', -1 if we can't guess it
5398
 *  @see get_default_npr(), get_default_localtax()
5399
 */
5400
function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
5401
{
5402
	global $conf;
5403
5404
	require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
5405
5406
	// Note: possible values for tva_assuj are 0/1 or franchise/reel
5407
	$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;
5408
5409
	$seller_country_code = $thirdparty_seller->country_code;
5410
	$seller_in_cee = isInEEC($thirdparty_seller);
5411
5412
	$buyer_country_code = $thirdparty_buyer->country_code;
5413
	$buyer_in_cee = isInEEC($thirdparty_buyer);
5414
5415
	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 : ''));
5416
5417
	// 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)
5418
	// we use the buyer VAT.
5419
	if (!empty($conf->global->SERVICE_ARE_ECOMMERCE_200238EC))
5420
	{
5421
		if ($seller_in_cee && $buyer_in_cee && !$thirdparty_buyer->isACompany())
5422
		{
5423
			//print 'VATRULE 0';
5424
			return get_product_vat_for_country($idprod, $thirdparty_buyer, $idprodfournprice);
5425
		}
5426
	}
5427
5428
	// If seller does not use VAT
5429
	if (!$seller_use_vat)
5430
	{
5431
		//print 'VATRULE 1';
5432
		return 0;
5433
	}
5434
5435
	// 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.
5436
5437
	// Si le (pays vendeur = pays acheteur) alors la TVA par defaut=TVA du produit vendu. Fin de regle.
5438
	if (($seller_country_code == $buyer_country_code)
5439
	|| (in_array($seller_country_code, array('FR,MC')) && in_array($buyer_country_code, array('FR', 'MC')))) // Warning ->country_code not always defined
5440
	{
5441
		//print 'VATRULE 2';
5442
		return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
5443
	}
5444
5445
	// 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.
5446
	// Not supported
5447
5448
	// Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = entreprise) alors TVA par defaut=0. Fin de regle
5449
	// Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = particulier) alors TVA par defaut=TVA du produit vendu. Fin de regle
5450
	if (($seller_in_cee && $buyer_in_cee))
5451
	{
5452
		$isacompany = $thirdparty_buyer->isACompany();
5453
		if ($isacompany)
5454
		{
5455
			//print 'VATRULE 3';
5456
			return 0;
5457
		} else {
5458
			//print 'VATRULE 4';
5459
			return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
5460
		}
5461
	}
5462
5463
	// Si (vendeur en France et acheteur hors Communaute europeenne et acheteur particulier) alors TVA par defaut=TVA du produit vendu. Fin de regle
5464
	if (!empty($conf->global->MAIN_USE_VAT_OF_PRODUCT_FOR_INDIVIDUAL_CUSTOMER_OUT_OF_EEC) && empty($buyer_in_cee) && !$thirdparty_buyer->isACompany()) {
5465
		return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
5466
	}
5467
5468
	// Sinon la TVA proposee par defaut=0. Fin de regle.
5469
	// Rem: Cela signifie qu'au moins un des 2 est hors Communaute europeenne et que le pays differe
5470
	//print 'VATRULE 5';
5471
	return 0;
5472
}
5473
5474
5475
/**
5476
 *	Fonction qui renvoie si tva doit etre tva percue recuperable
5477
 *
5478
 *	@param	Societe		$thirdparty_seller    	Thirdparty seller
5479
 *	@param  Societe		$thirdparty_buyer   	Thirdparty buyer
5480
 *  @param  int			$idprod                 Id product
5481
 *  @param	int			$idprodfournprice		Id supplier price for product
5482
 *	@return float       			        	0 or 1
5483
 *  @see get_default_tva(), get_default_localtax()
5484
 */
5485
function get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
5486
{
5487
	global $db;
5488
5489
	if ($idprodfournprice > 0)
5490
	{
5491
		if (!class_exists('ProductFournisseur')) {
5492
			require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
5493
		}
5494
		$prodprice = new ProductFournisseur($db);
5495
		$prodprice->fetch_product_fournisseur_price($idprodfournprice);
5496
		return $prodprice->fourn_tva_npr;
5497
	} elseif ($idprod > 0)
5498
	{
5499
		if (!class_exists('Product')) {
5500
			require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
5501
		}
5502
		$prod = new Product($db);
5503
		$prod->fetch($idprod);
5504
		return $prod->tva_npr;
5505
	}
5506
5507
	return 0;
5508
}
5509
5510
/**
5511
 *	Function that return localtax of a product line (according to seller, buyer and product vat rate)
5512
 *   Si vendeur non assujeti a TVA, TVA par defaut=0. Fin de regle.
5513
 *	 Si le (pays vendeur = pays acheteur) alors TVA par defaut=TVA du produit vendu. Fin de regle.
5514
 *	 Sinon TVA proposee par defaut=0. Fin de regle.
5515
 *
5516
 *	@param	Societe		$thirdparty_seller    	Thirdparty seller
5517
 *	@param  Societe		$thirdparty_buyer   	Thirdparty buyer
5518
 *  @param	int			$local					Localtax to process (1 or 2)
5519
 *	@param  int			$idprod					Id product
5520
 *	@return integer        				       	localtax, -1 si ne peut etre determine
5521
 *  @see get_default_tva(), get_default_npr()
5522
 */
5523
function get_default_localtax($thirdparty_seller, $thirdparty_buyer, $local, $idprod = 0)
5524
{
5525
	global $mysoc;
5526
5527
	if (!is_object($thirdparty_seller)) return -1;
5528
	if (!is_object($thirdparty_buyer)) return -1;
5529
5530
	if ($local == 1) // Localtax 1
5531
	{
5532
		if ($mysoc->country_code == 'ES')
5533
		{
5534
			if (is_numeric($thirdparty_buyer->localtax1_assuj) && !$thirdparty_buyer->localtax1_assuj) return 0;
5535
		} else {
5536
			// Si vendeur non assujeti a Localtax1, localtax1 par default=0
5537
			if (is_numeric($thirdparty_seller->localtax1_assuj) && !$thirdparty_seller->localtax1_assuj) return 0;
5538
			if (!is_numeric($thirdparty_seller->localtax1_assuj) && $thirdparty_seller->localtax1_assuj == 'localtax1off') return 0;
5539
		}
5540
	} elseif ($local == 2) //I Localtax 2
5541
	{
5542
		// Si vendeur non assujeti a Localtax2, localtax2 par default=0
5543
		if (is_numeric($thirdparty_seller->localtax2_assuj) && !$thirdparty_seller->localtax2_assuj) return 0;
5544
		if (!is_numeric($thirdparty_seller->localtax2_assuj) && $thirdparty_seller->localtax2_assuj == 'localtax2off') return 0;
5545
	}
5546
5547
	if ($thirdparty_seller->country_code == $thirdparty_buyer->country_code)
5548
	{
5549
		return get_product_localtax_for_country($idprod, $local, $thirdparty_seller);
5550
	}
5551
5552
	return 0;
5553
}
5554
5555
/**
5556
 *	Return yes or no in current language
5557
 *
5558
 *	@param	string|int	$yesno			Value to test (1, 'yes', 'true' or 0, 'no', 'false')
5559
 *	@param	integer		$case			1=Yes/No, 0=yes/no, 2=Disabled checkbox, 3=Disabled checkbox + Yes/No
5560
 *	@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.
5561
 *	@return	string						HTML string
5562
 */
5563
function yn($yesno, $case = 1, $color = 0)
5564
{
5565
	global $langs;
5566
	$result = 'unknown'; $classname = '';
5567
	if ($yesno == 1 || strtolower($yesno) == 'yes' || strtolower($yesno) == 'true') 	// A mettre avant test sur no a cause du == 0
5568
	{
5569
		$result = $langs->trans('yes');
5570
		if ($case == 1 || $case == 3) $result = $langs->trans("Yes");
5571
		if ($case == 2) $result = '<input type="checkbox" value="1" checked disabled>';
5572
		if ($case == 3) $result = '<input type="checkbox" value="1" checked disabled> '.$result;
5573
5574
		$classname = 'ok';
5575
	} elseif ($yesno == 0 || strtolower($yesno) == 'no' || strtolower($yesno) == 'false')
5576
	{
5577
		$result = $langs->trans("no");
5578
		if ($case == 1 || $case == 3) $result = $langs->trans("No");
5579
		if ($case == 2) $result = '<input type="checkbox" value="0" disabled>';
5580
		if ($case == 3) $result = '<input type="checkbox" value="0" disabled> '.$result;
5581
5582
		if ($color == 2) $classname = 'ok';
5583
		else $classname = 'error';
5584
	}
5585
	if ($color) return '<font class="'.$classname.'">'.$result.'</font>';
5586
	return $result;
5587
}
5588
5589
/**
5590
 *	Return a path to have a the directory according to object where files are stored.
5591
 *  New usage:       $conf->module->multidir_output[$object->entity].'/'.get_exdir(0, 0, 0, 1, $object, '').'/'
5592
 *         or:       $conf->module->dir_output.'/'.get_exdir(0, 0, 0, 0, $object, '')     if multidir_output not defined.
5593
 *  Example out with new usage:       $object is invoice -> 'INYYMM-ABCD'
5594
 *  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/"
5595
 *
5596
 *	@param	string|int	$num            Id of object (deprecated, $object will be used in future)
5597
 *	@param  int			$level		    Level of subdirs to return (1, 2 or 3 levels). (deprecated, global option will be used in future)
5598
 * 	@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)
5599
 *  @param  int			$withoutslash   0=With slash at end (except if '/', we return ''), 1=without slash at end
5600
 *  @param	Object		$object			Object to use to get ref to forge the path.
5601
 *  @param	string		$modulepart		Type of object ('invoice_supplier, 'donation', 'invoice', ...'). Use '' for autodetect from $object.
5602
 *  @return	string						Dir to use ending. Example '' or '1/' or '1/2/'
5603
 */
5604
function get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart = '')
5605
{
5606
	global $conf;
5607
5608
	if (empty($modulepart) && !empty($object->module)) $modulepart = $object->module;
5609
5610
	$path = '';
5611
5612
	$arrayforoldpath = array('cheque', 'category', 'holiday', 'supplier_invoice', 'invoice_supplier', 'mailing', 'supplier_payment');
5613
	if (!empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO)) $arrayforoldpath[] = 'product';
5614
	if (!empty($level) && in_array($modulepart, $arrayforoldpath)) {
5615
		// This part should be removed once all code is using "get_exdir" to forge path, with all parameters provided.
5616
		if (empty($alpha)) $num = preg_replace('/([^0-9])/i', '', $num);
5617
		else $num = preg_replace('/^.*\-/i', '', $num);
5618
		$num = substr("000".$num, -$level);
5619
		if ($level == 1) $path = substr($num, 0, 1);
5620
		if ($level == 2) $path = substr($num, 1, 1).'/'.substr($num, 0, 1);
5621
		if ($level == 3) $path = substr($num, 2, 1).'/'.substr($num, 1, 1).'/'.substr($num, 0, 1);
5622
	} else {
5623
		// We will enhance here a common way of forging path for document storage.
5624
		// In a future, we may distribute directories on several levels depending on setup and object.
5625
		// Here, $object->id, $object->ref and $modulepart are required.
5626
		//var_dump($modulepart);
5627
		$path = dol_sanitizeFileName(empty($object->ref) ? (string) $object->id : $object->ref);
5628
	}
5629
5630
	if (empty($withoutslash) && !empty($path)) $path .= '/';
5631
5632
	return $path;
5633
}
5634
5635
/**
5636
 *	Creation of a directory (this can create recursive subdir)
5637
 *
5638
 *	@param	string		$dir		Directory to create (Separator must be '/'. Example: '/mydir/mysubdir')
5639
 *	@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)
5640
 *  @param	string|null	$newmask	Mask for new file (Defaults to $conf->global->MAIN_UMASK or 0755 if unavailable). Example: '0444'
5641
 *	@return int         			< 0 if KO, 0 = already exists, > 0 if OK
5642
 */
5643
function dol_mkdir($dir, $dataroot = '', $newmask = null)
5644
{
5645
	global $conf;
5646
5647
	dol_syslog("functions.lib::dol_mkdir: dir=".$dir, LOG_INFO);
5648
5649
	$dir_osencoded = dol_osencode($dir);
5650
	if (@is_dir($dir_osencoded)) return 0;
5651
5652
	$nberr = 0;
5653
	$nbcreated = 0;
5654
5655
	$ccdir = '';
5656
	if (!empty($dataroot)) {
5657
		// Remove data root from loop
5658
		$dir = str_replace($dataroot.'/', '', $dir);
5659
		$ccdir = $dataroot.'/';
5660
	}
5661
5662
	$cdir = explode("/", $dir);
5663
	$num = count($cdir);
5664
	for ($i = 0; $i < $num; $i++)
5665
	{
5666
		if ($i > 0) $ccdir .= '/'.$cdir[$i];
5667
		else $ccdir .= $cdir[$i];
5668
		if (preg_match("/^.:$/", $ccdir, $regs)) continue; // Si chemin Windows incomplet, on poursuit par rep suivant
5669
5670
		// Attention, le is_dir() peut echouer bien que le rep existe.
5671
		// (ex selon config de open_basedir)
5672
		if ($ccdir)
5673
		{
5674
			$ccdir_osencoded = dol_osencode($ccdir);
5675
			if (!@is_dir($ccdir_osencoded))
5676
			{
5677
				dol_syslog("functions.lib::dol_mkdir: Directory '".$ccdir."' does not exists or is outside open_basedir PHP setting.", LOG_DEBUG);
5678
5679
				umask(0);
5680
				$dirmaskdec = octdec($newmask);
5681
				if (empty($newmask)) {
5682
					$dirmaskdec = empty($conf->global->MAIN_UMASK) ? octdec('0755') : octdec($conf->global->MAIN_UMASK);
5683
				}
5684
				$dirmaskdec |= octdec('0111'); // Set x bit required for directories
5685
				if (!@mkdir($ccdir_osencoded, $dirmaskdec))
5686
				{
5687
					// Si le is_dir a renvoye une fausse info, alors on passe ici.
5688
					dol_syslog("functions.lib::dol_mkdir: Fails to create directory '".$ccdir."' or directory already exists.", LOG_WARNING);
5689
					$nberr++;
5690
				} else {
5691
					dol_syslog("functions.lib::dol_mkdir: Directory '".$ccdir."' created", LOG_DEBUG);
5692
					$nberr = 0; // On remet a zero car si on arrive ici, cela veut dire que les echecs precedents peuvent etre ignore
5693
					$nbcreated++;
5694
				}
5695
			} else {
5696
				$nberr = 0; // On remet a zero car si on arrive ici, cela veut dire que les echecs precedents peuvent etre ignores
5697
			}
5698
		}
5699
	}
5700
	return ($nberr ? -$nberr : $nbcreated);
5701
}
5702
5703
5704
/**
5705
 *	Return picto saying a field is required
5706
 *
5707
 *	@return  string		Chaine avec picto obligatoire
5708
 */
5709
function picto_required()
5710
{
5711
	return '<span class="fieldrequired">*</span>';
5712
}
5713
5714
5715
/**
5716
 *	Clean a string from all HTML tags and entities.
5717
 *  This function differs from strip_tags because:
5718
 *  - <br> are replaced with \n if removelinefeed=0 or 1
5719
 *  - if entities are found, they are decoded BEFORE the strip
5720
 *  - you can decide to convert line feed into a space
5721
 *
5722
 *	@param	string	$stringtoclean		String to clean
5723
 *	@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..."
5724
 *  @param  string	$pagecodeto      	Encoding of input/output string
5725
 *  @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)
5726
 *  @param	integer	$removedoublespaces	Replace double space into one space
5727
 *	@return string	    				String cleaned
5728
 *
5729
 * 	@see	dol_escape_htmltag() strip_tags() dol_string_onlythesehtmltags() dol_string_neverthesehtmltags(), dolStripPhpCode()
5730
 */
5731
function dol_string_nohtmltag($stringtoclean, $removelinefeed = 1, $pagecodeto = 'UTF-8', $strip_tags = 0, $removedoublespaces = 1)
5732
{
5733
	if ($removelinefeed == 2) $stringtoclean = preg_replace('/<br[^>]*>(\n|\r)+/ims', '<br>', $stringtoclean);
5734
	$temp = preg_replace('/<br[^>]*>/i', "\n", $stringtoclean);
5735
5736
	// 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)
5737
	$temp = dol_html_entity_decode($temp, ENT_COMPAT | ENT_HTML5, $pagecodeto);
5738
5739
	$temp = str_replace('< ', '__ltspace__', $temp);
5740
5741
	if ($strip_tags) {
5742
		$temp = strip_tags($temp);
5743
	} else {
5744
		$pattern = "/<[^<>]+>/";
5745
		// Example of $temp: <a href="/myurl" title="<u>A title</u>">0000-021</a>
5746
		$temp = preg_replace($pattern, "", $temp); // pass 1
5747
		// $temp after pass 1: <a href="/myurl" title="A title">0000-021
5748
		$temp = preg_replace($pattern, "", $temp); // pass 2
5749
		// $temp after pass 2: 0000-021
5750
	}
5751
5752
	$temp = dol_html_entity_decode($temp, ENT_COMPAT, $pagecodeto);
5753
5754
	// Remove also carriage returns
5755
	if ($removelinefeed == 1) $temp = str_replace(array("\r\n", "\r", "\n"), " ", $temp);
5756
5757
	// And double quotes
5758
	if ($removedoublespaces) {
5759
		while (strpos($temp, "  ")) {
5760
			$temp = str_replace("  ", " ", $temp);
5761
		}
5762
	}
5763
5764
	$temp = str_replace('__ltspace__', '< ', $temp);
5765
5766
	return trim($temp);
5767
}
5768
5769
/**
5770
 *	Clean a string to keep only desirable HTML tags.
5771
 *  WARNING: This also clean HTML comments (used to obfuscate tag name).
5772
 *
5773
 *	@param	string	$stringtoclean			String to clean
5774
 *  @param	int		$cleanalsosomestyles	Remove absolute/fixed positioning from inline styles
5775
 *  @param	int		$removeclassattribute	Remove the class attribute from tags
5776
 *  @param	int		$cleanalsojavascript	Remove also occurence of 'javascript:'.
5777
 *	@return string	    					String cleaned
5778
 *
5779
 * 	@see	dol_escape_htmltag() strip_tags() dol_string_nohtmltag() dol_string_neverthesehtmltags()
5780
 */
5781
function dol_string_onlythesehtmltags($stringtoclean, $cleanalsosomestyles = 1, $removeclassattribute = 1, $cleanalsojavascript = 0)
5782
{
5783
	$allowed_tags = array(
5784
		"html", "head", "meta", "body", "article", "a", "abbr", "b", "blockquote", "br", "cite", "div", "dl", "dd", "dt", "em", "font", "img", "ins", "hr", "i", "li", "link",
5785
		"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"
5786
	);
5787
5788
	$allowed_tags_string = join("><", $allowed_tags);
5789
	$allowed_tags_string = '<'.$allowed_tags_string.'>';
5790
5791
	$stringtoclean = dol_string_nounprintableascii($stringtoclean, 0);
5792
	$stringtoclean = preg_replace('/&colon;/i', ':', $stringtoclean);
5793
5794
	$stringtoclean = preg_replace('/<!--[^>]*-->/', '', $stringtoclean);
5795
	$stringtoclean = preg_replace('/&#58;|&#0000058|&#x3A/i', '', $stringtoclean); // refused string ':' encoded (no reason to have it encoded) to lock 'javascript:...'
5796
	$stringtoclean = preg_replace('/javascript\s*:/i', '', $stringtoclean);
5797
5798
	$temp = strip_tags($stringtoclean, $allowed_tags_string);
5799
5800
	if ($cleanalsosomestyles) {	// Clean for remaining html tags
5801
		$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
5802
	}
5803
	if ($removeclassattribute) {	// Clean for remaining html tags
5804
		$temp = preg_replace('/(<[^>]+)\s+class=((["\']).*?\\3|\\w*)/i', '\\1', $temp);
5805
	}
5806
5807
	// Remove 'javascript:' that we should not find into a text with
5808
	// 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).
5809
	if ($cleanalsojavascript) {
5810
		$temp = preg_replace('/javascript\s*:/i', '', $temp);
5811
	}
5812
5813
	return $temp;
5814
}
5815
5816
/**
5817
 *	Clean a string from some undesirable HTML tags.
5818
 *  Note. Not as secured as dol_string_onlythesehtmltags().
5819
 *
5820
 *	@param	string	$stringtoclean			String to clean
5821
 *  @param	array	$disallowed_tags		Array of tags not allowed
5822
 *  @param	string	$cleanalsosomestyles	Clean also some tags
5823
 *	@return string	    					String cleaned
5824
 *
5825
 * 	@see	dol_escape_htmltag() strip_tags() dol_string_nohtmltag() dol_string_onlythesehtmltags()
5826
 */
5827
function dol_string_neverthesehtmltags($stringtoclean, $disallowed_tags = array('textarea'), $cleanalsosomestyles = 0)
5828
{
5829
	$temp = $stringtoclean;
5830
	foreach ($disallowed_tags as $tagtoremove)
5831
	{
5832
		$temp = preg_replace('/<\/?'.$tagtoremove.'>/', '', $temp);
5833
		$temp = preg_replace('/<\/?'.$tagtoremove.'\s+[^>]*>/', '', $temp);
5834
	}
5835
5836
	if ($cleanalsosomestyles) {
5837
		$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
5838
	}
5839
5840
	return $temp;
5841
}
5842
5843
5844
/**
5845
 * Return first line of text. Cut will depends if content is HTML or not.
5846
 *
5847
 * @param 	string	$text		Input text
5848
 * @param	int		$nboflines  Nb of lines to get (default is 1 = first line only)
5849
 * @param   string  $charset    Charset of $text string (UTF-8 by default)
5850
 * @return	string				Output text
5851
 * @see dol_nboflines_bis(), dol_string_nohtmltag(), dol_escape_htmltag()
5852
 */
5853
function dolGetFirstLineOfText($text, $nboflines = 1, $charset = 'UTF-8')
5854
{
5855
	if ($nboflines == 1)
5856
	{
5857
		if (dol_textishtml($text))
5858
		{
5859
			$firstline = preg_replace('/<br[^>]*>.*$/s', '', $text); // The s pattern modifier means the . can match newline characters
5860
			$firstline = preg_replace('/<div[^>]*>.*$/s', '', $firstline); // The s pattern modifier means the . can match newline characters
5861
		} else {
5862
			$firstline = preg_replace('/[\n\r].*/', '', $text);
5863
		}
5864
		return $firstline.((strlen($firstline) != strlen($text)) ? '...' : '');
5865
	} else {
5866
		$ishtml = 0;
5867
		if (dol_textishtml($text))
5868
		{
5869
			$text = preg_replace('/\n/', '', $text);
5870
			$ishtml = 1;
5871
			$repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
5872
		} else {
5873
			$repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
5874
		}
5875
5876
		$text = strtr($text, $repTable);
5877
		if ($charset == 'UTF-8') { $pattern = '/(<br[^>]*>)/Uu'; } // /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
5878
		else $pattern = '/(<br[^>]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
5879
		$a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
5880
5881
		$firstline = '';
5882
		$i = 0;
5883
		$nba = count($a); // 2x nb of lines in $a because $a contains also a line for each new line separator
5884
		while (($i < $nba) && ($i < ($nboflines * 2)))
5885
		{
5886
			if ($i % 2 == 0) $firstline .= $a[$i];
5887
			elseif (($i < (($nboflines * 2) - 1)) && ($i < ($nba - 1))) $firstline .= ($ishtml ? "<br>\n" : "\n");
5888
			$i++;
5889
		}
5890
		unset($a);
5891
		return $firstline.(($i < $nba) ? '...' : '');
5892
	}
5893
}
5894
5895
5896
/**
5897
 * Replace CRLF in string with a HTML BR tag.
5898
 * 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.
5899
 *
5900
 * @param	string	$stringtoencode		String to encode
5901
 * @param	int     $nl2brmode			0=Adding br before \n, 1=Replacing \n by br
5902
 * @param   bool	$forxml             false=Use <br>, true=Use <br />
5903
 * @return	string						String encoded
5904
 * @see dol_nboflines(), dolGetFirstLineOfText()
5905
 */
5906
function dol_nl2br($stringtoencode, $nl2brmode = 0, $forxml = false)
5907
{
5908
	if (!$nl2brmode) {
5909
		return nl2br($stringtoencode, $forxml);
5910
	} else {
5911
		$ret = preg_replace('/(\r\n|\r|\n)/i', ($forxml ? '<br />' : '<br>'), $stringtoencode);
5912
		return $ret;
5913
	}
5914
}
5915
5916
5917
/**
5918
 *	This function is called to encode a string into a HTML string but differs from htmlentities because
5919
 * 	a detection is done before to see if text is already HTML or not. Also, all entities but &,<,>," are converted.
5920
 *  This permits to encode special chars to entities with no double encoding for already encoded HTML strings.
5921
 * 	This function also remove last EOL or BR if $removelasteolbr=1 (default).
5922
 *  For PDF usage, you can show text by 2 ways:
5923
 *              - writeHTMLCell -> param must be encoded into HTML.
5924
 *              - MultiCell -> param must not be encoded into HTML.
5925
 *              Because writeHTMLCell convert also \n into <br>, if function
5926
 *              is used to build PDF, nl2brmode must be 1.
5927
 *
5928
 *	@param	string	$stringtoencode		String to encode
5929
 *	@param	int		$nl2brmode			0=Adding br before \n, 1=Replacing \n by br (for use with FPDF writeHTMLCell function for example)
5930
 *  @param  string	$pagecodefrom       Pagecode stringtoencode is encoded
5931
 *  @param	int		$removelasteolbr	1=Remove last br or lasts \n (default), 0=Do nothing
5932
 *  @return	string						String encoded
5933
 */
5934
function dol_htmlentitiesbr($stringtoencode, $nl2brmode = 0, $pagecodefrom = 'UTF-8', $removelasteolbr = 1)
5935
{
5936
	$newstring = $stringtoencode;
5937
	if (dol_textishtml($stringtoencode))	// Check if text is already HTML or not
5938
	{
5939
		$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.
5940
		if ($removelasteolbr) $newstring = preg_replace('/<br>$/i', '', $newstring); // Remove last <br> (remove only last one)
5941
		$newstring = strtr($newstring, array('&'=>'__and__', '<'=>'__lt__', '>'=>'__gt__', '"'=>'__dquot__'));
5942
		$newstring = dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom); // Make entity encoding
5943
		$newstring = strtr($newstring, array('__and__'=>'&', '__lt__'=>'<', '__gt__'=>'>', '__dquot__'=>'"'));
5944
	} else {
5945
		if ($removelasteolbr) $newstring = preg_replace('/(\r\n|\r|\n)$/i', '', $newstring); // Remove last \n (may remove several)
5946
		$newstring = dol_nl2br(dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom), $nl2brmode);
5947
	}
5948
	// Other substitutions that htmlentities does not do
5949
	//$newstring=str_replace(chr(128),'&euro;',$newstring);	// 128 = 0x80. Not in html entity table.     // Seems useles with TCPDF. Make bug with UTF8 languages
5950
	return $newstring;
5951
}
5952
5953
/**
5954
 *	This function is called to decode a HTML string (it decodes entities and br tags)
5955
 *
5956
 *	@param	string	$stringtodecode		String to decode
5957
 *	@param	string	$pagecodeto			Page code for result
5958
 *	@return	string						String decoded
5959
 */
5960
function dol_htmlentitiesbr_decode($stringtodecode, $pagecodeto = 'UTF-8')
5961
{
5962
	$ret = dol_html_entity_decode($stringtodecode, ENT_COMPAT | ENT_HTML5, $pagecodeto);
5963
	$ret = preg_replace('/'."\r\n".'<br(\s[\sa-zA-Z_="]*)?\/?>/i', "<br>", $ret);
5964
	$ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>'."\r\n".'/i', "\r\n", $ret);
5965
	$ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>'."\n".'/i', "\n", $ret);
5966
	$ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>/i', "\n", $ret);
5967
	return $ret;
5968
}
5969
5970
/**
5971
 *	This function remove all ending \n and br at end
5972
 *
5973
 *	@param	string	$stringtodecode		String to decode
5974
 *	@return	string						String decoded
5975
 */
5976
function dol_htmlcleanlastbr($stringtodecode)
5977
{
5978
	$ret = preg_replace('/(<br>|<br(\s[\sa-zA-Z_="]*)?\/?>|'."\n".'|'."\r".')+$/i', "", $stringtodecode);
5979
	return $ret;
5980
}
5981
5982
/**
5983
 * Replace html_entity_decode functions to manage errors
5984
 *
5985
 * @param   string	$a					Operand a
5986
 * @param   string	$b					Operand b (ENT_QUOTES|ENT_HTML5=convert simple, double quotes, colon, e accent, ...)
5987
 * @param   string	$c					Operand c
5988
 * @param	string	$keepsomeentities	Entities but &, <, >, " are not converted.
5989
 * @return  string						String decoded
5990
 */
5991
function dol_html_entity_decode($a, $b, $c = 'UTF-8', $keepsomeentities = 0)
5992
{
5993
	$newstring = $a;
5994
	if ($keepsomeentities) $newstring = strtr($newstring, array('&amp;'=>'__andamp__', '&lt;'=>'__andlt__', '&gt;'=>'__andgt__', '"'=>'__dquot__'));
5995
	$newstring = html_entity_decode($newstring, $b, $c);
5996
	if ($keepsomeentities) $newstring = strtr($newstring, array('__andamp__'=>'&amp;', '__andlt__'=>'&lt;', '__andgt__'=>'&gt;', '__dquot__'=>'"'));
5997
	return $newstring;
5998
}
5999
6000
/**
6001
 * Replace htmlentities functions.
6002
 * Goal of this function is to be sure to have default values of htmlentities that match what we need.
6003
 *
6004
 * @param   string  $string         The input string to encode
6005
 * @param   int     $flags          Flags (see PHP doc above)
6006
 * @param   string  $encoding       Encoding page code
6007
 * @param   bool    $double_encode  When double_encode is turned off, PHP will not encode existing html entities
6008
 * @return  string  $ret            Encoded string
6009
 */
6010
function dol_htmlentities($string, $flags = null, $encoding = 'UTF-8', $double_encode = false)
6011
{
6012
	return htmlentities($string, $flags, $encoding, $double_encode);
6013
}
6014
6015
/**
6016
 *	Check if a string is a correct iso string
6017
 *	If not, it will we considered not HTML encoded even if it is by FPDF.
6018
 *	Example, if string contains euro symbol that has ascii code 128
6019
 *
6020
 *	@param	string		$s      	String to check
6021
 *  @param	string		$clean		Clean if it is not an ISO. Warning, if file is utf8, you will get a bad formated file.
6022
 *	@return	int|string  	   		0 if bad iso, 1 if good iso, Or the clean string if $clean is 1
6023
 */
6024
function dol_string_is_good_iso($s, $clean = 0)
6025
{
6026
	$len = dol_strlen($s);
6027
	$out = '';
6028
	$ok = 1;
6029
	for ($scursor = 0; $scursor < $len; $scursor++)
6030
	{
6031
		$ordchar = ord($s[$scursor]);
6032
		//print $scursor.'-'.$ordchar.'<br>';
6033
		if ($ordchar < 32 && $ordchar != 13 && $ordchar != 10) { $ok = 0; break; } elseif ($ordchar > 126 && $ordchar < 160) { $ok = 0; break; } elseif ($clean) {
6034
			$out .= $s[$scursor];
6035
		}
6036
	}
6037
	if ($clean) return $out;
6038
	return $ok;
6039
}
6040
6041
/**
6042
 *	Return nb of lines of a clear text
6043
 *
6044
 *	@param	string	$s			String to check
6045
 * 	@param	int     $maxchar	Not yet used
6046
 *	@return	int					Number of lines
6047
 *  @see	dol_nboflines_bis(), dolGetFirstLineOfText()
6048
 */
6049
function dol_nboflines($s, $maxchar = 0)
6050
{
6051
	if ($s == '') return 0;
6052
	$arraystring = explode("\n", $s);
6053
	$nb = count($arraystring);
6054
6055
	return $nb;
6056
}
6057
6058
6059
/**
6060
 *	Return nb of lines of a formated text with \n and <br> (WARNING: string must not have mixed \n and br separators)
6061
 *
6062
 *	@param	string	$text      		Text
6063
 *	@param	int		$maxlinesize  	Largeur de ligne en caracteres (ou 0 si pas de limite - defaut)
6064
 * 	@param	string	$charset		Give the charset used to encode the $text variable in memory.
6065
 *	@return int						Number of lines
6066
 *	@see	dol_nboflines(), dolGetFirstLineOfText()
6067
 */
6068
function dol_nboflines_bis($text, $maxlinesize = 0, $charset = 'UTF-8')
6069
{
6070
	$repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
6071
	if (dol_textishtml($text)) $repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
6072
6073
	$text = strtr($text, $repTable);
6074
	if ($charset == 'UTF-8') { $pattern = '/(<br[^>]*>)/Uu'; } // /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
6075
	else $pattern = '/(<br[^>]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
6076
	$a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
6077
6078
	$nblines = (int) floor((count($a) + 1) / 2);
6079
	// count possible auto line breaks
6080
	if ($maxlinesize)
6081
	{
6082
		foreach ($a as $line)
6083
		{
6084
			if (dol_strlen($line) > $maxlinesize)
6085
			{
6086
				//$line_dec = html_entity_decode(strip_tags($line));
6087
				$line_dec = html_entity_decode($line);
6088
				if (dol_strlen($line_dec) > $maxlinesize)
6089
				{
6090
					$line_dec = wordwrap($line_dec, $maxlinesize, '\n', true);
6091
					$nblines += substr_count($line_dec, '\n');
6092
				}
6093
			}
6094
		}
6095
	}
6096
6097
	unset($a);
6098
	return $nblines;
6099
}
6100
6101
/**
6102
 *	Return if a text is a html content
6103
 *
6104
 *	@param	string	$msg		Content to check
6105
 *	@param	int		$option		0=Full detection, 1=Fast check
6106
 *	@return	boolean				true/false
6107
 *	@see	dol_concatdesc()
6108
 */
6109
function dol_textishtml($msg, $option = 0)
6110
{
6111
	if ($option == 1)
6112
	{
6113
		if (preg_match('/<html/i', $msg))				return true;
6114
		elseif (preg_match('/<body/i', $msg))			return true;
6115
		elseif (preg_match('/<\/textarea/i', $msg))	  return true;
6116
		elseif (preg_match('/<br/i', $msg))				return true;
6117
		return false;
6118
	} else {
6119
		if (preg_match('/<html/i', $msg))				return true;
6120
		elseif (preg_match('/<body/i', $msg))			return true;
6121
		elseif (preg_match('/<\/textarea/i', $msg))	  return true;
6122
		elseif (preg_match('/<(b|em|i|u)>/i', $msg))		return true;
6123
		elseif (preg_match('/<br\/>/i', $msg))	  return true;
6124
		elseif (preg_match('/<(br|div|font|li|p|span|strong|table)>/i', $msg)) 	  return true;
6125
		elseif (preg_match('/<(br|div|font|li|p|span|strong|table)\s+[^<>\/]*>/i', $msg)) return true;
6126
		elseif (preg_match('/<(br|div|font|li|p|span|strong|table)\s+[^<>\/]*\/>/i', $msg)) return true;
6127
		elseif (preg_match('/<img\s+[^<>]*src[^<>]*>/i', $msg)) return true; // must accept <img src="http://example.com/aaa.png" />
6128
		elseif (preg_match('/<a\s+[^<>]*href[^<>]*>/i', $msg)) return true; // must accept <a href="http://example.com/aaa.png" />
6129
		elseif (preg_match('/<h[0-9]>/i', $msg))			return true;
6130
		elseif (preg_match('/&[A-Z0-9]{1,6};/i', $msg))	return true; // Html entities names (http://www.w3schools.com/tags/ref_entities.asp)
6131
		elseif (preg_match('/&#[0-9]{2,3};/i', $msg))	return true; // Html entities numbers (http://www.w3schools.com/tags/ref_entities.asp)
6132
6133
		return false;
6134
	}
6135
}
6136
6137
/**
6138
 *  Concat 2 descriptions with a new line between them (second operand after first one with appropriate new line separator)
6139
 *  text1 html + text2 html => text1 + '<br>' + text2
6140
 *  text1 html + text2 txt  => text1 + '<br>' + dol_nl2br(text2)
6141
 *  text1 txt  + text2 html => dol_nl2br(text1) + '<br>' + text2
6142
 *  text1 txt  + text2 txt  => text1 + '\n' + text2
6143
 *
6144
 *  @param  string  $text1          Text 1
6145
 *  @param  string  $text2          Text 2
6146
 *  @param  bool    $forxml         true=Use <br /> instead of <br> if we have to add a br tag
6147
 *  @param  bool    $invert         invert order of description lines (we often use config MAIN_CHANGE_ORDER_CONCAT_DESCRIPTION in this parameter)
6148
 *  @return string                  Text 1 + new line + Text2
6149
 *  @see    dol_textishtml()
6150
 */
6151
function dol_concatdesc($text1, $text2, $forxml = false, $invert = false)
6152
{
6153
	if (!empty($invert))
6154
	{
6155
			$tmp = $text1;
6156
			$text1 = $text2;
6157
			$text2 = $tmp;
6158
	}
6159
6160
	$ret = '';
6161
	$ret .= (!dol_textishtml($text1) && dol_textishtml($text2)) ? dol_nl2br(dol_escape_htmltag($text1, 0, 1, '', 1), 0, $forxml) : $text1;
6162
	$ret .= (!empty($text1) && !empty($text2)) ? ((dol_textishtml($text1) || dol_textishtml($text2)) ? ($forxml ? "<br \>\n" : "<br>\n") : "\n") : "";
6163
	$ret .= (dol_textishtml($text1) && !dol_textishtml($text2)) ? dol_nl2br(dol_escape_htmltag($text2, 0, 1, '', 1), 0, $forxml) : $text2;
6164
	return $ret;
6165
}
6166
6167
6168
6169
/**
6170
 * Return array of possible common substitutions. This includes several families like: 'system', 'mycompany', 'object', 'objectamount', 'date', 'user'
6171
 *
6172
 * @param	Translate	$outputlangs	Output language
6173
 * @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)
6174
 * @param   array       $exclude        Array of family keys we want to exclude. For example array('system', 'mycompany', 'object', 'objectamount', 'date', 'user', ...)
6175
 * @param   Object      $object         Object for keys on object
6176
 * @return	array						Array of substitutions
6177
 * @see setSubstitFromObject()
6178
 */
6179
function getCommonSubstitutionArray($outputlangs, $onlykey = 0, $exclude = null, $object = null)
6180
{
6181
	global $db, $conf, $mysoc, $user, $extrafields;
6182
6183
	$substitutionarray = array();
6184
6185
	if (empty($exclude) || !in_array('user', $exclude))
6186
	{
6187
		// Add SIGNATURE into substitutionarray first, so, when we will make the substitution,
6188
		// this will include signature content first and then replace var found into content of signature
6189
		$signature = $user->signature;
6190
		$substitutionarray = array_merge($substitutionarray, array(
6191
			'__USER_SIGNATURE__' => (string) (($signature && empty($conf->global->MAIN_MAIL_DO_NOT_USE_SIGN)) ? ($onlykey == 2 ? dol_trunc(dol_string_nohtmltag($signature), 30) : $signature) : '')
6192
		)
6193
			);
6194
6195
		$substitutionarray = array_merge($substitutionarray, array(
6196
		'__USER_ID__' => (string) $user->id,
6197
		'__USER_LOGIN__' => (string) $user->login,
6198
		'__USER_EMAIL__' => (string) $user->email,
6199
		'__USER_LASTNAME__' => (string) $user->lastname,
6200
		'__USER_FIRSTNAME__' => (string) $user->firstname,
6201
		'__USER_FULLNAME__' => (string) $user->getFullName($outputlangs),
6202
		'__USER_SUPERVISOR_ID__' => (string) ($user->fk_user ? $user->fk_user : '0'),
6203
		'__USER_REMOTE_IP__' => (string) getUserRemoteIP()
6204
		)
6205
			);
6206
	}
6207
	if ((empty($exclude) || !in_array('mycompany', $exclude)) && is_object($mysoc))
6208
	{
6209
		$substitutionarray = array_merge($substitutionarray, array(
6210
			'__MYCOMPANY_NAME__'    => $mysoc->name,
6211
			'__MYCOMPANY_EMAIL__'   => $mysoc->email,
6212
			'__MYCOMPANY_PROFID1__' => $mysoc->idprof1,
6213
			'__MYCOMPANY_PROFID2__' => $mysoc->idprof2,
6214
			'__MYCOMPANY_PROFID3__' => $mysoc->idprof3,
6215
			'__MYCOMPANY_PROFID4__' => $mysoc->idprof4,
6216
			'__MYCOMPANY_PROFID5__' => $mysoc->idprof5,
6217
			'__MYCOMPANY_PROFID6__' => $mysoc->idprof6,
6218
			'__MYCOMPANY_CAPITAL__' => $mysoc->capital,
6219
			'__MYCOMPANY_FULLADDRESS__' => $mysoc->getFullAddress(1, ', '),
6220
			'__MYCOMPANY_ADDRESS__' => $mysoc->address,
6221
			'__MYCOMPANY_ZIP__'     => $mysoc->zip,
6222
			'__MYCOMPANY_TOWN__'    => $mysoc->town,
6223
			'__MYCOMPANY_COUNTRY__'    => $mysoc->country,
6224
			'__MYCOMPANY_COUNTRY_ID__' => $mysoc->country_id,
6225
			'__MYCOMPANY_COUNTRY_CODE__' => $mysoc->country_code,
6226
			'__MYCOMPANY_CURRENCY_CODE__' => $conf->currency
6227
		));
6228
	}
6229
6230
	if (($onlykey || is_object($object)) && (empty($exclude) || !in_array('object', $exclude)))
6231
	{
6232
		if ($onlykey)
6233
		{
6234
			$substitutionarray['__ID__'] = '__ID__';
6235
			$substitutionarray['__REF__'] = '__REF__';
6236
			$substitutionarray['__REF_CLIENT__'] = '__REF_CLIENT__';
6237
			$substitutionarray['__REF_SUPPLIER__'] = '__REF_SUPPLIER__';
6238
			$substitutionarray['__NOTE_PUBLIC__'] = '__NOTE_PUBLIC__';
6239
			$substitutionarray['__NOTE_PRIVATE__'] = '__NOTE_PRIVATE__';
6240
			$substitutionarray['__EXTRAFIELD_XXX__'] = '__EXTRAFIELD_XXX__';
6241
6242
			if (!empty($conf->societe->enabled))	// Most objects are concerned
6243
			{
6244
				$substitutionarray['__THIRDPARTY_ID__'] = '__THIRDPARTY_ID__';
6245
				$substitutionarray['__THIRDPARTY_NAME__'] = '__THIRDPARTY_NAME__';
6246
				$substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = '__THIRDPARTY_NAME_ALIAS__';
6247
				$substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = '__THIRDPARTY_CODE_CLIENT__';
6248
				$substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = '__THIRDPARTY_CODE_FOURNISSEUR__';
6249
				$substitutionarray['__THIRDPARTY_EMAIL__'] = '__THIRDPARTY_EMAIL__';
6250
				$substitutionarray['__THIRDPARTY_PHONE__'] = '__THIRDPARTY_PHONE__';
6251
				$substitutionarray['__THIRDPARTY_FAX__'] = '__THIRDPARTY_FAX__';
6252
				$substitutionarray['__THIRDPARTY_ADDRESS__'] = '__THIRDPARTY_ADDRESS__';
6253
				$substitutionarray['__THIRDPARTY_ZIP__'] = '__THIRDPARTY_ZIP__';
6254
				$substitutionarray['__THIRDPARTY_TOWN__'] = '__THIRDPARTY_TOWN__';
6255
				$substitutionarray['__THIRDPARTY_IDPROF1__'] = '__THIRDPARTY_IDPROF1__';
6256
				$substitutionarray['__THIRDPARTY_IDPROF2__'] = '__THIRDPARTY_IDPROF2__';
6257
				$substitutionarray['__THIRDPARTY_IDPROF3__'] = '__THIRDPARTY_IDPROF3__';
6258
				$substitutionarray['__THIRDPARTY_IDPROF4__'] = '__THIRDPARTY_IDPROF4__';
6259
				$substitutionarray['__THIRDPARTY_IDPROF5__'] = '__THIRDPARTY_IDPROF5__';
6260
				$substitutionarray['__THIRDPARTY_IDPROF6__'] = '__THIRDPARTY_IDPROF6__';
6261
				$substitutionarray['__THIRDPARTY_TVAINTRA__'] = '__THIRDPARTY_TVAINTRA__';
6262
				$substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = '__THIRDPARTY_NOTE_PUBLIC__';
6263
				$substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = '__THIRDPARTY_NOTE_PRIVATE__';
6264
			}
6265
			if (!empty($conf->adherent->enabled) && (!is_object($object) || $object->element == 'adherent'))
6266
			{
6267
				$substitutionarray['__MEMBER_ID__'] = '__MEMBER_ID__';
6268
				$substitutionarray['__MEMBER_CIVILITY__'] = '__MEMBER_CIVILITY__';
6269
				$substitutionarray['__MEMBER_FIRSTNAME__'] = '__MEMBER_FIRSTNAME__';
6270
				$substitutionarray['__MEMBER_LASTNAME__'] = '__MEMBER_LASTNAME__';
6271
				/*$substitutionarray['__MEMBER_NOTE_PUBLIC__'] = '__MEMBER_NOTE_PUBLIC__';
6272
				$substitutionarray['__MEMBER_NOTE_PRIVATE__'] = '__MEMBER_NOTE_PRIVATE__';*/
6273
			}
6274
			if (!empty($conf->recruitment->enabled) && (!is_object($object) || $object->element == 'candidature'))
6275
			{
6276
				$substitutionarray['__CANDIDATE_FULLNAME__'] = '__CANDIDATE_FULLNAME__';
6277
				$substitutionarray['__CANDIDATE_FIRSTNAME__'] = '__CANDIDATE_FIRSTNAME__';
6278
				$substitutionarray['__CANDIDATE_LASTNAME__'] = '__CANDIDATE_LASTNAME__';
6279
			}
6280
			if (!empty($conf->projet->enabled))		// Most objects
6281
			{
6282
				$substitutionarray['__PROJECT_ID__'] = '__PROJECT_ID__';
6283
				$substitutionarray['__PROJECT_REF__'] = '__PROJECT_REF__';
6284
				$substitutionarray['__PROJECT_NAME__'] = '__PROJECT_NAME__';
6285
				/*$substitutionarray['__PROJECT_NOTE_PUBLIC__'] = '__PROJECT_NOTE_PUBLIC__';
6286
				$substitutionarray['__PROJECT_NOTE_PRIVATE__'] = '__PROJECT_NOTE_PRIVATE__';*/
6287
			}
6288
			if (!empty($conf->contrat->enabled) && (!is_object($object) || $object->element == 'contract'))
6289
			{
6290
				$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = 'Highest date planned for a service start';
6291
				$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = 'Highest date and hour planned for service start';
6292
				$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = 'Lowest data for planned expiration of service';
6293
				$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = 'Lowest date and hour for planned expiration of service';
6294
			}
6295
			$substitutionarray['__ONLINE_PAYMENT_URL__'] = 'UrlToPayOnlineIfApplicable';
6296
			$substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = 'TextAndUrlToPayOnlineIfApplicable';
6297
			$substitutionarray['__SECUREKEYPAYMENT__'] = 'Security key (if key is not unique per record)';
6298
			$substitutionarray['__SECUREKEYPAYMENT_MEMBER__'] = 'Security key for payment on a member subscription (one key per member)';
6299
			$substitutionarray['__SECUREKEYPAYMENT_ORDER__'] = 'Security key for payment on an order';
6300
			$substitutionarray['__SECUREKEYPAYMENT_INVOICE__'] = 'Security key for payment on an invoice';
6301
			$substitutionarray['__SECUREKEYPAYMENT_CONTRACTLINE__'] = 'Security key for payment on a a service';
6302
6303
			$substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = 'Direct download url of a proposal';
6304
			$substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = 'Direct download url of an order';
6305
			$substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = 'Direct download url of an invoice';
6306
6307
			if (!empty($conf->expedition->enabled) && (!is_object($object) || $object->element == 'shipping'))
6308
			{
6309
				$substitutionarray['__SHIPPINGTRACKNUM__'] = 'Shipping tracking number';
6310
				$substitutionarray['__SHIPPINGTRACKNUMURL__'] = 'Shipping tracking url';
6311
			}
6312
			if (!empty($conf->reception->enabled) && (!is_object($object) || $object->element == 'reception'))
6313
			{
6314
				$substitutionarray['__RECEPTIONTRACKNUM__'] = 'Shippin tracking number of shipment';
6315
				$substitutionarray['__RECEPTIONTRACKNUMURL__'] = 'Shipping tracking url';
6316
			}
6317
		} else {
6318
			$substitutionarray['__ID__'] = $object->id;
6319
			$substitutionarray['__REF__'] = $object->ref;
6320
			$substitutionarray['__REF_CLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
6321
			$substitutionarray['__REF_SUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
6322
			$substitutionarray['__NOTE_PUBLIC__'] = (isset($object->note_public) ? $object->note_public : null);
6323
			$substitutionarray['__NOTE_PRIVATE__'] = (isset($object->note_private) ? $object->note_private : null);
6324
6325
			$substitutionarray['__DATE_DELIVERY__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, 'day', 0, $outputlangs) : '');
6326
6327
			// For backward compatibility
6328
			$substitutionarray['__REFCLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
6329
			$substitutionarray['__REFSUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
6330
			$substitutionarray['__REFCLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
6331
			$substitutionarray['__REFSUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
6332
			$substitutionarray['__SUPPLIER_ORDER_DATE_DELIVERY__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, 'day', 0, $outputlangs) : '');
6333
			$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 : '')) : '');
6334
6335
			if (is_object($object) && ($object->element == 'adherent' || $object->element == 'member') && $object->id > 0)
6336
			{
6337
				$birthday = (empty($object->birth) ? '' : dol_print_date($object->birth, 'day'));
6338
6339
				$substitutionarray['__MEMBER_ID__'] = (isset($object->id) ? $object->id : '');
6340
				if (method_exists($object, 'getCivilityLabel')) $substitutionarray['__MEMBER_CIVILITY__'] = $object->getCivilityLabel();
6341
				$substitutionarray['__MEMBER_FIRSTNAME__'] = (isset($object->firstname) ? $object->firstname : '');
6342
				$substitutionarray['__MEMBER_LASTNAME__'] = (isset($object->lastname) ? $object->lastname : '');
6343
				if (method_exists($object, 'getFullName')) $substitutionarray['__MEMBER_FULLNAME__'] = $object->getFullName($outputlangs);
6344
				$substitutionarray['__MEMBER_COMPANY__'] = (isset($object->societe) ? $object->societe : '');
6345
				$substitutionarray['__MEMBER_ADDRESS__'] = (isset($object->address) ? $object->address : '');
6346
				$substitutionarray['__MEMBER_ZIP__'] = (isset($object->zip) ? $object->zip : '');
6347
				$substitutionarray['__MEMBER_TOWN__'] = (isset($object->town) ? $object->town : '');
6348
				$substitutionarray['__MEMBER_COUNTRY__'] = (isset($object->country) ? $object->country : '');
6349
				$substitutionarray['__MEMBER_EMAIL__'] = (isset($object->email) ? $object->email : '');
6350
				$substitutionarray['__MEMBER_BIRTH__'] = (isset($birthday) ? $birthday : '');
6351
				$substitutionarray['__MEMBER_PHOTO__'] = (isset($object->photo) ? $object->photo : '');
6352
				$substitutionarray['__MEMBER_LOGIN__'] = (isset($object->login) ? $object->login : '');
6353
				$substitutionarray['__MEMBER_PASSWORD__'] = (isset($object->pass) ? $object->pass : '');
6354
				$substitutionarray['__MEMBER_PHONE__'] = (isset($object->phone) ? $object->phone : '');
6355
				$substitutionarray['__MEMBER_PHONEPRO__'] = (isset($object->phone_perso) ? $object->phone_perso : '');
6356
				$substitutionarray['__MEMBER_PHONEMOBILE__'] = (isset($object->phone_mobile) ? $object->phone_mobile : '');
6357
				$substitutionarray['__MEMBER_TYPE__'] = (isset($object->type) ? $object->type : '');
6358
				$substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE__']       = dol_print_date($object->first_subscription_date, 'dayrfc');
6359
				$substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_START__'] = dol_print_date($object->first_subscription_date_start, 'dayrfc');
6360
				$substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_END__']   = dol_print_date($object->first_subscription_date_end, 'dayrfc');
6361
				$substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE__']        = dol_print_date($object->last_subscription_date, 'dayrfc');
6362
				$substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_START__']  = dol_print_date($object->last_subscription_date_start, 'dayrfc');
6363
				$substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_END__']    = dol_print_date($object->last_subscription_date_end, 'dayrfc');
6364
			}
6365
6366
			if (is_object($object) && $object->element == 'societe') {
6367
				$substitutionarray['__THIRDPARTY_ID__'] = (is_object($object) ? $object->id : '');
6368
				$substitutionarray['__THIRDPARTY_NAME__'] = (is_object($object) ? $object->name : '');
6369
				$substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = (is_object($object) ? $object->name_alias : '');
6370
				$substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = (is_object($object) ? $object->code_client : '');
6371
				$substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = (is_object($object) ? $object->code_fournisseur : '');
6372
				$substitutionarray['__THIRDPARTY_EMAIL__'] = (is_object($object) ? $object->email : '');
6373
				$substitutionarray['__THIRDPARTY_PHONE__'] = (is_object($object) ? $object->phone : '');
6374
				$substitutionarray['__THIRDPARTY_FAX__'] = (is_object($object) ? $object->fax : '');
6375
				$substitutionarray['__THIRDPARTY_ADDRESS__'] = (is_object($object) ? $object->address : '');
6376
				$substitutionarray['__THIRDPARTY_ZIP__'] = (is_object($object) ? $object->zip : '');
6377
				$substitutionarray['__THIRDPARTY_TOWN__'] = (is_object($object) ? $object->town : '');
6378
				$substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = (is_object($object) ? $object->country_id : '');
6379
				$substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = (is_object($object) ? $object->country_code : '');
6380
				$substitutionarray['__THIRDPARTY_IDPROF1__'] = (is_object($object) ? $object->idprof1 : '');
6381
				$substitutionarray['__THIRDPARTY_IDPROF2__'] = (is_object($object) ? $object->idprof2 : '');
6382
				$substitutionarray['__THIRDPARTY_IDPROF3__'] = (is_object($object) ? $object->idprof3 : '');
6383
				$substitutionarray['__THIRDPARTY_IDPROF4__'] = (is_object($object) ? $object->idprof4 : '');
6384
				$substitutionarray['__THIRDPARTY_IDPROF5__'] = (is_object($object) ? $object->idprof5 : '');
6385
				$substitutionarray['__THIRDPARTY_IDPROF6__'] = (is_object($object) ? $object->idprof6 : '');
6386
				$substitutionarray['__THIRDPARTY_TVAINTRA__'] = (is_object($object) ? $object->tva_intra : '');
6387
				$substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = (is_object($object) ? dol_htmlentitiesbr($object->note_public) : '');
6388
				$substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = (is_object($object) ? dol_htmlentitiesbr($object->note_private) : '');
6389
			} elseif (is_object($object->thirdparty)) {
6390
				$substitutionarray['__THIRDPARTY_ID__'] = (is_object($object->thirdparty) ? $object->thirdparty->id : '');
6391
				$substitutionarray['__THIRDPARTY_NAME__'] = (is_object($object->thirdparty) ? $object->thirdparty->name : '');
6392
				$substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = (is_object($object->thirdparty) ? $object->thirdparty->name_alias : '');
6393
				$substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = (is_object($object->thirdparty) ? $object->thirdparty->code_client : '');
6394
				$substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = (is_object($object->thirdparty) ? $object->thirdparty->code_fournisseur : '');
6395
				$substitutionarray['__THIRDPARTY_EMAIL__'] = (is_object($object->thirdparty) ? $object->thirdparty->email : '');
6396
				$substitutionarray['__THIRDPARTY_PHONE__'] = (is_object($object->thirdparty) ? $object->thirdparty->phone : '');
6397
				$substitutionarray['__THIRDPARTY_FAX__'] = (is_object($object->thirdparty) ? $object->thirdparty->fax : '');
6398
				$substitutionarray['__THIRDPARTY_ADDRESS__'] = (is_object($object->thirdparty) ? $object->thirdparty->address : '');
6399
				$substitutionarray['__THIRDPARTY_ZIP__'] = (is_object($object->thirdparty) ? $object->thirdparty->zip : '');
6400
				$substitutionarray['__THIRDPARTY_TOWN__'] = (is_object($object->thirdparty) ? $object->thirdparty->town : '');
6401
				$substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = (is_object($object->thirdparty) ? $object->thirdparty->country_id : '');
6402
				$substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = (is_object($object->thirdparty) ? $object->thirdparty->country_code : '');
6403
				$substitutionarray['__THIRDPARTY_IDPROF1__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof1 : '');
6404
				$substitutionarray['__THIRDPARTY_IDPROF2__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof2 : '');
6405
				$substitutionarray['__THIRDPARTY_IDPROF3__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof3 : '');
6406
				$substitutionarray['__THIRDPARTY_IDPROF4__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof4 : '');
6407
				$substitutionarray['__THIRDPARTY_IDPROF5__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof5 : '');
6408
				$substitutionarray['__THIRDPARTY_IDPROF6__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof6 : '');
6409
				$substitutionarray['__THIRDPARTY_TVAINTRA__'] = (is_object($object->thirdparty) ? $object->thirdparty->tva_intra : '');
6410
				$substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = (is_object($object->thirdparty) ? dol_htmlentitiesbr($object->thirdparty->note_public) : '');
6411
				$substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = (is_object($object->thirdparty) ? dol_htmlentitiesbr($object->thirdparty->note_private) : '');
6412
			}
6413
6414
			if (is_object($object) && $object->element == 'recruitmentcandidature') {
6415
				$substitutionarray['__CANDIDATE_FULLNAME__'] = $object->getFullName($outputlangs);
6416
				$substitutionarray['__CANDIDATE_FIRSTNAME__'] = $object->firstname;
6417
				$substitutionarray['__CANDIDATE_LASTNAME__'] = $object->lastname;
6418
			}
6419
6420
			if (is_object($object->project))
6421
			{
6422
				$substitutionarray['__PROJECT_ID__'] = (is_object($object->project) ? $object->project->id : '');
6423
				$substitutionarray['__PROJECT_REF__'] = (is_object($object->project) ? $object->project->ref : '');
6424
				$substitutionarray['__PROJECT_NAME__'] = (is_object($object->project) ? $object->project->title : '');
6425
			}
6426
			if (is_object($object->projet))	// Deprecated, for backward compatibility
6427
			{
6428
				$substitutionarray['__PROJECT_ID__'] = (is_object($object->projet) ? $object->projet->id : '');
6429
				$substitutionarray['__PROJECT_REF__'] = (is_object($object->projet) ? $object->projet->ref : '');
6430
				$substitutionarray['__PROJECT_NAME__'] = (is_object($object->projet) ? $object->projet->title : '');
6431
			}
6432
6433
			if (is_object($object) && $object->element == 'shipping')
6434
			{
6435
				$substitutionarray['__SHIPPINGTRACKNUM__'] = $object->tracking_number;
6436
				$substitutionarray['__SHIPPINGTRACKNUMURL__'] = $object->tracking_url;
6437
			}
6438
			if (is_object($object) && $object->element == 'reception')
6439
			{
6440
				$substitutionarray['__RECEPTIONTRACKNUM__'] = $object->tracking_number;
6441
				$substitutionarray['__RECEPTIONTRACKNUMURL__'] = $object->tracking_url;
6442
			}
6443
6444
			if (is_object($object) && $object->element == 'contrat' && $object->id > 0 && is_array($object->lines))
6445
			{
6446
				$dateplannedstart = '';
6447
				$datenextexpiration = '';
6448
				foreach ($object->lines as $line)
6449
				{
6450
					if ($line->date_ouverture_prevue > $dateplannedstart) $dateplannedstart = $line->date_ouverture_prevue;
6451
					if ($line->statut == 4 && $line->date_fin_prevue && (!$datenextexpiration || $line->date_fin_prevue < $datenextexpiration)) $datenextexpiration = $line->date_fin_prevue;
6452
				}
6453
				$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = dol_print_date($dateplannedstart, 'dayrfc');
6454
				$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = dol_print_date($dateplannedstart, 'standard');
6455
				$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = dol_print_date($datenextexpiration, 'dayrfc');
6456
				$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = dol_print_date($datenextexpiration, 'standard');
6457
			}
6458
6459
			// Create dynamic tags for __EXTRAFIELD_FIELD__
6460
			if ($object->table_element && $object->id > 0)
6461
			{
6462
				if (!is_object($extrafields)) $extrafields = new ExtraFields($db);
6463
				$extrafields->fetch_name_optionals_label($object->table_element, true);
6464
6465
				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

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