Completed
Branch develop (b51004)
by
unknown
35:30
created

functions.lib.php ➔ picto_from_langcode()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 28

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 5
nop 2
dl 0
loc 28
rs 9.1608
c 0
b 0
f 0
1
<?php
2
/* Copyright (C) 2000-2007	Rodolphe Quiedeville			<[email protected]>
3
 * Copyright (C) 2003		Jean-Louis Bergamo			<[email protected]>
4
 * Copyright (C) 2004-2018	Laurent Destailleur			<[email protected]>
5
 * Copyright (C) 2004		Sebastien Di Cintio			<[email protected]>
6
 * Copyright (C) 2004		Benoit Mortier				<[email protected]>
7
 * Copyright (C) 2004		Christophe Combelles			<[email protected]>
8
 * Copyright (C) 2005-2017	Regis Houssin				<[email protected]>
9
 * Copyright (C) 2008		Raphael Bertrand (Resultic)	<[email protected]>
10
 * Copyright (C) 2010-2018	Juanjo Menent				<[email protected]>
11
 * Copyright (C) 2013		Cédric Salvador				<[email protected]>
12
 * Copyright (C) 2013-2017	Alexandre Spangaro			<[email protected]>
13
 * Copyright (C) 2014		Cédric GROSS					<[email protected]>
14
 * Copyright (C) 2014-2015	Marcos García				<[email protected]>
15
 * Copyright (C) 2015		Jean-François Ferry			<[email protected]>
16
 * Copyright (C) 2018       Frédéric France             <[email protected]>
17
 *
18
 * This program is free software; you can redistribute it and/or modify
19
 * it under the terms of the GNU General Public License as published by
20
 * the Free Software Foundation; either version 3 of the License, or
21
 * (at your option) any later version.
22
 *
23
 * This program is distributed in the hope that it will be useful,
24
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26
 * GNU General Public License for more details.
27
 *
28
 * You should have received a copy of the GNU General Public License
29
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
30
 * or see http://www.gnu.org/
31
 */
32
33
/**
34
 *	\file			htdocs/core/lib/functions.lib.php
35
 *	\brief			A set of functions for Dolibarr
36
 *					This file contains all frequently used functions.
37
 */
38
39
include_once DOL_DOCUMENT_ROOT .'/core/lib/json.lib.php';
40
41
42
/**
43
 * Function to return value of a static property when class
44
 * name is dynamically defined (not hard coded).
45
 * This is because $myclass::$myvar works from PHP 5.3.0+ only
46
 *
47
 * @param	string 	$class		Class name
48
 * @param 	string 	$member		Name of property
49
 * @return 	mixed				Return value of static property
50
 * @deprecated Dolibarr now requires 5.3.0+, use $class::$property syntax
51
 * @see https://php.net/manual/language.oop5.static.php
52
 */
53
function getStaticMember($class, $member)
54
{
55
	dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);
56
57
	// This part is deprecated. Uncomment if for php 5.2.*, and comment next isset class::member
58
	/*if (version_compare(phpversion(), '5.3.0', '<'))
59
	{
60
		if (is_object($class)) $class = get_class($class);
61
		$classObj = new ReflectionClass($class);
62
		$result = null;
63
64
		$found=0;
65
		foreach($classObj->getStaticProperties() as $prop => $value)
66
		{
67
			if ($prop == $member)
68
			{
69
				$result = $value;
70
				$found++;
71
				break;
72
			}
73
		}
74
75
		if ($found) return $result;
76
	}*/
77
78
	if (isset($class::$member)) return $class::$member;
79
	dol_print_error('','Try to get a static member "'.$member.'" in class "'.$class.'" that does not exists or is not static.');
80
	return null;
81
}
82
83
84
/**
85
 * Return a DoliDB instance (database handler).
86
 *
87
 * @param   string	$type		Type of database (mysql, pgsql...)
88
 * @param	string	$host		Address of database server
89
 * @param	string	$user		Nom de l'utilisateur autorise
90
 * @param	string	$pass		Mot de passe
91
 * @param	string	$name		Nom de la database
92
 * @param	int		$port		Port of database server
93
 * @return	DoliDB				A DoliDB instance
94
 */
95
function getDoliDBInstance($type, $host, $user, $pass, $name, $port)
96
{
97
	require_once DOL_DOCUMENT_ROOT ."/core/db/".$type.'.class.php';
98
99
	$class='DoliDB'.ucfirst($type);
100
	$dolidb=new $class($type, $host, $user, $pass, $name, $port);
101
	return $dolidb;
102
}
103
104
/**
105
 * 	Get list of entity id to use.
106
 *
107
 * 	@param	string	$element		Current element
108
 *									'societe', 'socpeople', 'actioncomm', 'agenda', 'resource',
109
 *									'product', 'productprice', 'stock',
110
 *									'propal', 'supplier_proposal', 'facture', 'facture_fourn', 'payment_various',
111
 *									'categorie', 'bank_account', 'bank_account', 'adherent', 'user',
112
 *									'commande', 'commande_fournisseur', 'expedition', 'intervention', 'survey',
113
 *									'contract', 'tax', 'expensereport', 'holiday', 'multicurrency', 'project',
114
 *									'email_template', 'event', 'donation'
115
 *									'c_paiement', 'c_payment_term', ...
116
 * 	@param	int		$shared			0=Return id of current entity only,
117
 * 									1=Return id of current entity + shared entities (default)
118
 *  @param	object	$currentobject	Current object if needed
119
 * 	@return	mixed				Entity id(s) to use
120
 */
121
function getEntity($element, $shared=1, $currentobject=null)
122
{
123
	global $conf, $mc;
124
125
	if (is_object($mc))
126
	{
127
		return $mc->getEntity($element, $shared, $currentobject);
128
	}
129
	else
130
	{
131
		$out='';
132
		$addzero = array('user', 'usergroup', 'c_email_templates', 'email_template', 'default_values');
133
		if (in_array($element, $addzero)) $out.= '0,';
134
		$out.= $conf->entity;
135
		return $out;
136
	}
137
}
138
139
/**
140
 * Return information about user browser
141
 *
142
 * Returns array with the following format:
143
 * array(
144
 *  'browsername' => Browser name (firefox|chrome|iceweasel|epiphany|safari|opera|ie|unknown)
145
 *  'browserversion' => Browser version. Empty if unknown
146
 *  'browseros' => Set with mobile OS (android|blackberry|ios|palm|symbian|webos|maemo|windows|unknown)
147
 *  'layout' => (tablet|phone|classic)
148
 *  'phone' => empty if not mobile, (android|blackberry|ios|palm|unknown) if mobile
149
 *  'tablet' => true/false
150
 * )
151
 *
152
 * @param string $user_agent Content of $_SERVER["HTTP_USER_AGENT"] variable
153
 * @return	array Check function documentation
154
 */
155
function getBrowserInfo($user_agent)
156
{
157
	include_once DOL_DOCUMENT_ROOT.'/includes/mobiledetect/mobiledetectlib/Mobile_Detect.php';
158
159
	$name='unknown';
160
	$version='';
161
	$os='unknown';
162
	$phone = '';
163
164
	$detectmobile = new Mobile_Detect(null, $user_agent);
165
	$tablet = $detectmobile->isTablet();
166
167
	if ($detectmobile->isMobile()) {
168
169
		$phone = 'unknown';
170
171
		// If phone/smartphone, we set phone os name.
172
		if ($detectmobile->is('AndroidOS')) {
173
			$os = $phone = 'android';
174
		} elseif ($detectmobile->is('BlackBerryOS')) {
175
			$os = $phone = 'blackberry';
176
		} elseif ($detectmobile->is('iOS')) {
177
			$os = 'ios';
178
			$phone = 'iphone';
179
		} elseif ($detectmobile->is('PalmOS')) {
180
			$os = $phone = 'palm';
181
		} elseif ($detectmobile->is('SymbianOS')) {
182
			$os = 'symbian';
183
		} elseif ($detectmobile->is('webOS')) {
184
			$os = 'webos';
185
		} elseif ($detectmobile->is('MaemoOS')) {
186
			$os = 'maemo';
187
		} elseif ($detectmobile->is('WindowsMobileOS') || $detectmobile->is('WindowsPhoneOS')) {
188
			$os = 'windows';
189
		}
190
	}
191
192
	// OS
193
	if (preg_match('/linux/i', $user_agent))			{ $os='linux'; }
194
	elseif (preg_match('/macintosh/i', $user_agent))	{ $os='macintosh'; }
195
	elseif (preg_match('/windows/i', $user_agent))		{ $os='windows'; }
196
197
	// Name
198
	if (preg_match('/firefox(\/|\s)([\d\.]*)/i', $user_agent, $reg))      { $name='firefox';   $version=$reg[2]; }
199
	elseif (preg_match('/edge(\/|\s)([\d\.]*)/i', $user_agent, $reg))     { $name='edge';      $version=$reg[2]; }
200
	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
201
	elseif (preg_match('/chrome/i', $user_agent, $reg))                   { $name='chrome'; }
202
	elseif (preg_match('/iceweasel/i', $user_agent))                      { $name='iceweasel'; }
203
	elseif (preg_match('/epiphany/i', $user_agent))                       { $name='epiphany';  }
204
	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.
205
	elseif (preg_match('/opera(\/|\s)([\d\.]*)/i', $user_agent, $reg))    { $name='opera';     $version=$reg[2]; }
206
	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
207
	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
208
	elseif (preg_match('/l(i|y)n(x|ks)(\(|\/|\s)*([\d\.]+)/i', $user_agent, $reg)) { $name='lynxlinks'; $version=$reg[4]; }
209
210
	if ($tablet) {
211
		$layout = 'tablet';
212
	} elseif ($phone) {
213
		$layout = 'phone';
214
	} else {
215
		$layout = 'classic';
216
	}
217
218
	return array(
219
		'browsername' => $name,
220
		'browserversion' => $version,
221
		'browseros' => $os,
222
		'layout' => $layout,
223
		'phone' => $phone,
224
		'tablet' => $tablet
225
	);
226
}
227
228
/**
229
 *  Function called at end of web php process
230
 *
231
 *  @return	void
232
 */
233
function dol_shutdown()
234
{
235
	global $conf,$user,$langs,$db;
236
	$disconnectdone=false; $depth=0;
237
	if (is_object($db) && ! empty($db->connected)) { $depth=$db->transaction_opened; $disconnectdone=$db->close(); }
238
	dol_syslog("--- End access to ".$_SERVER["PHP_SELF"].(($disconnectdone && $depth)?' (Warn: db disconnection forced, transaction depth was '.$depth.')':''), (($disconnectdone && $depth)?LOG_WARNING:LOG_INFO));
239
}
240
241
/**
242
 * Return true if we are in a context of submitting a parameter
243
 *
244
 * @param 	string	$paramname		Name or parameter to test
245
 * @return 	boolean					True if we have just submit a POST or GET request with the parameter provided (even if param is empty)
246
 */
247
function GETPOSTISSET($paramname)
248
{
249
	return (isset($_POST[$paramname]) || isset($_GET[$paramname]));
250
}
251
252
/**
253
 *  Return value of a param into GET or POST supervariable.
254
 *  Use the property $user->default_values[path]['creatform'] and/or $user->default_values[path]['filters'] and/or $user->default_values[path]['sortorder']
255
 *  Note: The property $user->default_values is loaded by main.php when loading the user.
256
 *
257
 *  @param  string  $paramname   Name of parameter to found
258
 *  @param  string  $check	     Type of check
259
 *                               ''=no check (deprecated)
260
 *                               'none'=no check (only for param that should have very rich content)
261
 *                               'int'=check it's numeric (integer or float)
262
 *                               'intcomma'=check it's integer+comma ('1,2,3,4...')
263
 *                               'alpha'=check it's text and sign
264
 *                               'aZ'=check it's a-z only
265
 *                               'aZ09'=check it's simple alpha string (recommended for keys)
266
 *                               'array'=check it's array
267
 *                               'san_alpha'=Use filter_var with FILTER_SANITIZE_STRING (do not use this for free text string)
268
 *                               'nohtml', 'alphanohtml'=check there is no html content
269
 *                               'custom'= custom filter specify $filter and $options)
270
 *  @param	int		$method	     Type of method (0 = get then post, 1 = only get, 2 = only post, 3 = post then get, 4 = post then get then cookie)
271
 *  @param  int     $filter      Filter to apply when $check is set to 'custom'. (See http://php.net/manual/en/filter.filters.php for détails)
272
 *  @param  mixed   $options     Options to pass to filter_var when $check is set to 'custom'
273
 *  @param	string	$noreplace	 Force disable of replacement of __xxx__ strings.
274
 *  @return string|string[]      Value found (string or array), or '' if check fails
275
 */
276
function GETPOST($paramname, $check='none', $method=0, $filter=null, $options=null, $noreplace=0)
277
{
278
	global $mysoc,$user,$conf;
279
280
	if (empty($paramname)) return 'BadFirstParameterForGETPOST';
281
	if (empty($check))
282
	{
283
		dol_syslog("Deprecated use of GETPOST, called with 1st param = ".$paramname." and 2nd param is '', when calling page ".$_SERVER["PHP_SELF"], LOG_WARNING);
284
		// Enable this line to know who call the GETPOST with '' $check parameter.
285
		//var_dump(debug_backtrace()[0]);
286
	}
287
288
	if (empty($method)) $out = isset($_GET[$paramname])?$_GET[$paramname]:(isset($_POST[$paramname])?$_POST[$paramname]:'');
289
	elseif ($method==1) $out = isset($_GET[$paramname])?$_GET[$paramname]:'';
290
	elseif ($method==2) $out = isset($_POST[$paramname])?$_POST[$paramname]:'';
291
	elseif ($method==3) $out = isset($_POST[$paramname])?$_POST[$paramname]:(isset($_GET[$paramname])?$_GET[$paramname]:'');
292
	elseif ($method==4) $out = isset($_POST[$paramname])?$_POST[$paramname]:(isset($_GET[$paramname])?$_GET[$paramname]:(isset($_COOKIE[$paramname])?$_COOKIE[$paramname]:''));
293
	else return 'BadThirdParameterForGETPOST';
294
295
	if (empty($method) || $method == 3 || $method == 4)
296
	{
297
		$relativepathstring = $_SERVER["PHP_SELF"];
298
		// Clean $relativepathstring
299
		if (constant('DOL_URL_ROOT')) $relativepathstring = preg_replace('/^'.preg_quote(constant('DOL_URL_ROOT'),'/').'/', '', $relativepathstring);
300
		$relativepathstring = preg_replace('/^\//', '', $relativepathstring);
301
		$relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
302
		//var_dump($relativepathstring);
303
		//var_dump($user->default_values);
304
305
		// Code for search criteria persistence.
306
		// Retrieve values if restore_lastsearch_values
307
		if (! empty($_GET['restore_lastsearch_values']))        // Use $_GET here and not GETPOST
308
		{
309
			if (! empty($_SESSION['lastsearch_values_'.$relativepathstring]))	// If there is saved values
310
			{
311
				$tmp=json_decode($_SESSION['lastsearch_values_'.$relativepathstring], true);
312
				if (is_array($tmp))
313
				{
314
					foreach($tmp as $key => $val)
315
					{
316
						if ($key == $paramname)	// We are on the requested parameter
317
						{
318
							$out=$val;
319
							break;
320
						}
321
					}
322
				}
323
			}
324
			if (! empty($_SESSION['lastsearch_contextpage_'.$relativepathstring]))	// If there is saved contextpage
325
			{
326
				if ($paramname == 'contextpage')
327
				{
328
					$out = $_SESSION['lastsearch_contextpage_'.$relativepathstring];
329
					//var_dump($paramname.' '.$out);
330
				}
331
			}
332
		}
333
		// Else, retreive default values if we are not doing a sort
334
		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
335
		{
336
			if (! empty($_GET['action']) && $_GET['action'] == 'create' && ! isset($_GET[$paramname]) && ! isset($_POST[$paramname]))
337
			{
338
				// Search default value from $object->field
339
				global $object;
340
				if (is_object($object) && isset($object->fields[$paramname]['default']))
341
				{
342
					$out = $object->fields[$paramname]['default'];
343
				}
344
			}
345
			if (! empty($conf->global->MAIN_ENABLE_DEFAULT_VALUES))
346
			{
347
				if (! empty($_GET['action']) && $_GET['action'] == 'create' && ! isset($_GET[$paramname]) && ! isset($_POST[$paramname]))
348
				{
349
					// Now search in setup to overwrite default values
350
					if (! empty($user->default_values))		// $user->default_values defined from menu 'Setup - Default values'
351
					{
352
						if (isset($user->default_values[$relativepathstring]['createform']))
353
						{
354
							foreach($user->default_values[$relativepathstring]['createform'] as $defkey => $defval)
355
							{
356
								$qualified = 0;
357
								if ($defkey != '_noquery_')
358
								{
359
									$tmpqueryarraytohave=explode('&', $defkey);
360
									$tmpqueryarraywehave=explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
361
									$foundintru=0;
362
									foreach($tmpqueryarraytohave as $tmpquerytohave)
363
									{
364
										if (! in_array($tmpquerytohave, $tmpqueryarraywehave)) $foundintru=1;
365
									}
366
									if (! $foundintru) $qualified=1;
367
									//var_dump($defkey.'-'.$qualified);
368
								}
369
								else $qualified = 1;
370
371
								if ($qualified)
372
								{
373
									//var_dump($user->default_values[$relativepathstring][$defkey]['createform']);
374
									if (isset($user->default_values[$relativepathstring]['createform'][$defkey][$paramname]))
375
									{
376
										$out = $user->default_values[$relativepathstring]['createform'][$defkey][$paramname];
377
										break;
378
									}
379
								}
380
							}
381
						}
382
					}
383
				}
384
				// Management of default search_filters and sort order
385
				//elseif (preg_match('/list.php$/', $_SERVER["PHP_SELF"]) && ! empty($paramname) && ! isset($_GET[$paramname]) && ! isset($_POST[$paramname]))
386
				elseif (! empty($paramname) && ! isset($_GET[$paramname]) && ! isset($_POST[$paramname]))
387
				{
388
					if (! empty($user->default_values))		// $user->default_values defined from menu 'Setup - Default values'
389
					{
390
						//var_dump($user->default_values[$relativepathstring]);
391
						if ($paramname == 'sortfield' || $paramname == 'sortorder')			// Sorted on which fields ? ASC or DESC ?
392
						{
393
							if (isset($user->default_values[$relativepathstring]['sortorder']))	// Even if paramname is sortfield, data are stored into ['sortorder...']
394
							{
395
								foreach($user->default_values[$relativepathstring]['sortorder'] as $defkey => $defval)
396
								{
397
									$qualified = 0;
398
									if ($defkey != '_noquery_')
399
									{
400
										$tmpqueryarraytohave=explode('&', $defkey);
401
										$tmpqueryarraywehave=explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
402
										$foundintru=0;
403
										foreach($tmpqueryarraytohave as $tmpquerytohave)
404
										{
405
											if (! in_array($tmpquerytohave, $tmpqueryarraywehave)) $foundintru=1;
406
										}
407
										if (! $foundintru) $qualified=1;
408
										//var_dump($defkey.'-'.$qualified);
409
									}
410
									else $qualified = 1;
411
412
									if ($qualified)
413
									{
414
										$forbidden_chars_to_replace=array(" ","'","/","\\",":","*","?","\"","<",">","|","[","]",";","=");  // we accept _, -, . and ,
415
										foreach($user->default_values[$relativepathstring]['sortorder'][$defkey] as $key => $val)
416
										{
417
											if ($out) $out.=', ';
418
											if ($paramname == 'sortfield')
419
											{
420
												$out.=dol_string_nospecial($key, '', $forbidden_chars_to_replace);
421
											}
422
											if ($paramname == 'sortorder')
423
											{
424
												$out.=dol_string_nospecial($val, '', $forbidden_chars_to_replace);
425
											}
426
										}
427
										//break;	// No break for sortfield and sortorder so we can cumulate fields (is it realy usefull ?)
428
									}
429
								}
430
							}
431
						}
432
						elseif (isset($user->default_values[$relativepathstring]['filters']))
433
						{
434
							foreach($user->default_values[$relativepathstring]['filters'] as $defkey => $defval)	// $defkey is a querystring like 'a=b&c=d', $defval is key of user
435
							{
436
								$qualified = 0;
437
								if ($defkey != '_noquery_')
438
								{
439
									$tmpqueryarraytohave=explode('&', $defkey);
440
									$tmpqueryarraywehave=explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
441
									$foundintru=0;
442
									foreach($tmpqueryarraytohave as $tmpquerytohave)
443
									{
444
										if (! in_array($tmpquerytohave, $tmpqueryarraywehave)) $foundintru=1;
445
									}
446
									if (! $foundintru) $qualified=1;
447
									//var_dump($defkey.'-'.$qualified);
448
								}
449
								else $qualified = 1;
450
451
								if ($qualified)
452
								{
453
									if (isset($_POST['sall']) || isset($_POST['search_all']) || isset($_GET['sall']) || isset($_GET['search_all']))
454
									{
455
										// We made a search from quick search menu, do we still use default filter ?
456
										if (empty($conf->global->MAIN_DISABLE_DEFAULT_FILTER_FOR_QUICK_SEARCH))
457
										{
458
											$forbidden_chars_to_replace=array(" ","'","/","\\",":","*","?","\"","<",">","|","[","]",";","=");  // we accept _, -, . and ,
459
											$out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
460
										}
461
									}
462
									else
463
									{
464
										$forbidden_chars_to_replace=array(" ","'","/","\\",":","*","?","\"","<",">","|","[","]",";","=");  // we accept _, -, . and ,
465
										$out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
466
									}
467
									break;
468
								}
469
							}
470
						}
471
					}
472
				}
473
			}
474
		}
475
	}
476
477
	// Substitution variables for GETPOST (used to get final url with variable parameters or final default value with variable paramaters)
478
	// Example of variables: __DAY__, __MONTH__, __YEAR__, __MYCOMPANY_COUNTRY_ID__, __USER_ID__, ...
479
	// 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.
480
	if (! is_array($out) && empty($_POST[$paramname]) && empty($noreplace))
481
	{
482
		$maxloop=20; $loopnb=0;    // Protection against infinite loop
483
		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.
484
		{
485
				$loopnb++; $newout = '';
486
487
				if ($reg[1] == 'DAY')                { $tmp=dol_getdate(dol_now(), true); $newout = $tmp['mday']; }
488
				elseif ($reg[1] == 'MONTH')          { $tmp=dol_getdate(dol_now(), true); $newout = $tmp['mon'];  }
489
				elseif ($reg[1] == 'YEAR')           { $tmp=dol_getdate(dol_now(), true); $newout = $tmp['year']; }
490
				elseif ($reg[1] == 'PREVIOUS_DAY')   { $tmp=dol_getdate(dol_now(), true); $tmp2=dol_get_prev_day($tmp['mday'], $tmp['mon'], $tmp['year']); $newout = $tmp2['day']; }
491
				elseif ($reg[1] == 'PREVIOUS_MONTH') { $tmp=dol_getdate(dol_now(), true); $tmp2=dol_get_prev_month($tmp['mon'], $tmp['year']); $newout = $tmp2['month']; }
492
				elseif ($reg[1] == 'PREVIOUS_YEAR')  { $tmp=dol_getdate(dol_now(), true); $newout = ($tmp['year'] - 1); }
493
				elseif ($reg[1] == 'NEXT_DAY')       { $tmp=dol_getdate(dol_now(), true); $tmp2=dol_get_next_day($tmp['mday'], $tmp['mon'], $tmp['year']); $newout = $tmp2['day']; }
494
				elseif ($reg[1] == 'NEXT_MONTH')     { $tmp=dol_getdate(dol_now(), true); $tmp2=dol_get_next_month($tmp['mon'], $tmp['year']); $newout = $tmp2['month']; }
495
				elseif ($reg[1] == 'NEXT_YEAR')      { $tmp=dol_getdate(dol_now(), true); $newout = ($tmp['year'] + 1); }
496
				elseif ($reg[1] == 'MYCOMPANY_COUNTRY_ID' || $reg[1] == 'MYCOUNTRY_ID' || $reg[1] == 'MYCOUNTRYID')
497
				{
498
					$newout = $mysoc->country_id;
499
				}
500
				elseif ($reg[1] == 'USER_ID' || $reg[1] == 'USERID')
501
				{
502
					$newout = $user->id;
503
				}
504
				elseif ($reg[1] == 'USER_SUPERVISOR_ID' || $reg[1] == 'SUPERVISOR_ID' || $reg[1] == 'SUPERVISORID')
505
				{
506
					$newout = $user->fk_user;
507
				}
508
				elseif ($reg[1] == 'ENTITY_ID' || $reg[1] == 'ENTITYID')
509
				{
510
					$newout = $conf->entity;
511
				}
512
				else $newout = '';     // Key not found, we replace with empty string
513
				//var_dump('__'.$reg[1].'__ -> '.$newout);
514
				$out = preg_replace('/__'.preg_quote($reg[1],'/').'__/', $newout, $out);
515
		}
516
	}
517
518
	// Check is done after replacement
519
	switch ($check)
520
	{
521
		case 'none':
522
			break;
523
		case 'int':    // Check param is a numeric value (integer but also float or hexadecimal)
524
			if (! is_numeric($out)) { $out=''; }
525
			break;
526
		case 'intcomma':
527
			if (preg_match('/[^0-9,-]+/i',$out)) $out='';
528
			break;
529
		case 'alpha':
530
			if (! is_array($out))
531
			{
532
				$out=trim($out);
533
				// '"' is dangerous because param in url can close the href= or src= and add javascript functions.
534
				// '../' is dangerous because it allows dir transversals
535
				if (preg_match('/"/',$out)) $out='';
536
				else if (preg_match('/\.\.\//',$out)) $out='';
537
			}
538
			break;
539
		case 'san_alpha':
540
			$out=filter_var($out,FILTER_SANITIZE_STRING);
541
			break;
542
		case 'aZ':
543
			if (! is_array($out))
544
			{
545
				$out=trim($out);
546
				if (preg_match('/[^a-z]+/i',$out)) $out='';
547
			}
548
			break;
549
		case 'aZ09':
550
			if (! is_array($out))
551
			{
552
				$out=trim($out);
553
				if (preg_match('/[^a-z0-9_\-\.]+/i',$out)) $out='';
554
			}
555
			break;
556
		case 'aZ09comma':		// great to sanitize sortfield or sortorder params that can be t.abc,t.def_gh
557
			if (! is_array($out))
558
			{
559
				$out=trim($out);
560
				if (preg_match('/[^a-z0-9_\-\.,]+/i',$out)) $out='';
561
			}
562
			break;
563
		case 'array':
564
			if (! is_array($out) || empty($out)) $out=array();
565
			break;
566
		case 'nohtml':		// Recommended for most scalar parameters
567
			$out=dol_string_nohtmltag($out, 0);
568
			break;
569
		case 'alphanohtml':	// Recommended for search parameters
570
			if (! is_array($out))
571
			{
572
				$out=trim($out);
573
				// '"' is dangerous because param in url can close the href= or src= and add javascript functions.
574
				// '../' is dangerous because it allows dir transversals
575
				if (preg_match('/"/',$out)) $out='';
576
				else if (preg_match('/\.\.\//',$out)) $out='';
577
				$out=dol_string_nohtmltag($out);
578
			}
579
			break;
580
		case 'custom':
581
			if (empty($filter)) return 'BadFourthParameterForGETPOST';
582
			$out=filter_var($out, $filter, $options);
583
			break;
584
	}
585
586
	// Code for search criteria persistence.
587
	// Save data into session if key start with 'search_' or is 'smonth', 'syear', 'month', 'year'
588
	if (empty($method) || $method == 3 || $method == 4)
589
	{
590
		if (preg_match('/^search_/', $paramname) || in_array($paramname, array('sortorder','sortfield')))
591
		{
592
			//var_dump($paramname.' - '.$out.' '.$user->default_values[$relativepathstring]['filters'][$paramname]);
593
594
			// We save search key only if $out not empty that means:
595
			// - posted value not empty, or
596
			// - 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).
597
598
			if ($out != '')		// $out = '0' or 'abc', it is a search criteria to keep
599
			{
600
				$user->lastsearch_values_tmp[$relativepathstring][$paramname]=$out;
0 ignored issues
show
Bug introduced by
The variable $relativepathstring does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
601
			}
602
		}
603
	}
604
605
	return $out;
606
}
607
608
609
if (! function_exists('dol_getprefix'))
610
{
611
    /**
612
     *  Return a prefix to use for this Dolibarr instance, for session/cookie names or email id.
613
     *  This prefix is valid in a web context only and is unique for instance and avoid conflict
614
     *  between multi-instances, even when having two instances with one root dir or two instances
615
     *  in virtual servers.
616
     *
617
     *  @param  string  $mode                   '' (prefix for session name) or 'email' (prefix for email id)
618
     *  @return	string                          A calculated prefix
619
     */
620
    function dol_getprefix($mode='')
621
    {
622
		global $conf;
623
624
		// If MAIL_PREFIX_FOR_EMAIL_ID is set and prefix is for email
625
		if ($mode == 'email' && ! empty($conf->global->MAIL_PREFIX_FOR_EMAIL_ID))
626
		{
627
			if ($conf->global->MAIL_PREFIX_FOR_EMAIL_ID != 'SERVER_NAME') return $conf->global->MAIL_PREFIX_FOR_EMAIL_ID;
628
			else if (isset($_SERVER["SERVER_NAME"])) return $_SERVER["SERVER_NAME"];
629
		}
630
631
		if (isset($_SERVER["SERVER_NAME"]) && isset($_SERVER["DOCUMENT_ROOT"]))
632
		{
633
			return dol_hash($_SERVER["SERVER_NAME"].$_SERVER["DOCUMENT_ROOT"].DOL_DOCUMENT_ROOT.DOL_URL_ROOT);
634
			// Use this for a "readable" cookie name
635
			//return dol_sanitizeFileName($_SERVER["SERVER_NAME"].$_SERVER["DOCUMENT_ROOT"].DOL_DOCUMENT_ROOT.DOL_URL_ROOT);
636
		}
637
		else return dol_hash(DOL_DOCUMENT_ROOT.DOL_URL_ROOT);
638
	}
639
}
640
641
/**
642
 *	Make an include_once using default root and alternate root if it fails.
643
 *  To link to a core file, use include(DOL_DOCUMENT_ROOT.'/pathtofile')
644
 *  To link to a module file from a module file, use include './mymodulefile';
645
 *  To link to a module file from a core file, then this function can be used (call by hook / trigger / speciales pages)
646
 *
647
 * 	@param	string	$relpath	Relative path to file (Ie: mydir/myfile, ../myfile, ...)
648
 * 	@param	string	$classname	Class name (deprecated)
649
 *  @return bool                True if load is a success, False if it fails
650
 */
651
function dol_include_once($relpath, $classname='')
652
{
653
	global $conf,$langs,$user,$mysoc;   // Do not remove this. They must be defined for files we include. Other globals var must be retreived with $GLOBALS['var']
654
655
	$fullpath = dol_buildpath($relpath);
656
657
	if (!file_exists($fullpath)) {
658
		dol_syslog('functions::dol_include_once Tried to load unexisting file: '.$relpath, LOG_ERR);
659
		return false;
660
	}
661
662
	if (! empty($classname) && ! class_exists($classname)) {
663
		return include $fullpath;
664
	} else {
665
		return include_once $fullpath;
666
	}
667
}
668
669
670
/**
671
 *	Return path of url or filesystem. Can check into alternate dir or alternate dir + main dir depending on value of $returnemptyifnotfound.
672
 *
673
 * 	@param	string	$path						Relative path to file (if mode=0) or relative url (if mode=1). Ie: mydir/myfile, ../myfile
674
 *  @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)
675
 *  @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)
676
 *  											1:If $type==0 and if file was not found into alternate dir, return empty string
677
 *  											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
678
 *  @return string								Full filesystem path (if path=0), Full url path (if mode=1)
679
 */
680
function dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
681
{
682
	global $conf;
683
684
	$path=preg_replace('/^\//','',$path);
685
686
	if (empty($type))	// For a filesystem path
687
	{
688
		$res = DOL_DOCUMENT_ROOT.'/'.$path;		// Standard default path
689
		foreach ($conf->file->dol_document_root as $key => $dirroot)	// ex: array(["main"]=>"/home/main/htdocs", ["alt0"]=>"/home/dirmod/htdocs", ...)
690
		{
691
			if ($key == 'main')
692
			{
693
				continue;
694
			}
695
			if (file_exists($dirroot.'/'.$path))
696
			{
697
				$res=$dirroot.'/'.$path;
698
				return $res;
699
			}
700
		}
701
		if ($returnemptyifnotfound)								// Not found into alternate dir
702
		{
703
			if ($returnemptyifnotfound == 1 || ! file_exists($res)) return '';
704
		}
705
	}
706
	else				// For an url path
707
	{
708
		// We try to get local path of file on filesystem from url
709
		// Note that trying to know if a file on disk exist by forging path on disk from url
710
		// works only for some web server and some setup. This is bugged when
711
		// using proxy, rewriting, virtual path, etc...
712
		$res='';
713
		if ($type == 1) $res = DOL_URL_ROOT.'/'.$path;			// Standard value
714
		if ($type == 2) $res = DOL_MAIN_URL_ROOT.'/'.$path;		// Standard value
715
		if ($type == 3) $res = DOL_URL_ROOT.'/'.$path;
716
717
		foreach ($conf->file->dol_document_root as $key => $dirroot)	// ex: array(["main"]=>"/home/main/htdocs", ["alt0"]=>"/home/dirmod/htdocs", ...)
718
		{
719
			if ($key == 'main')
720
			{
721
				if ($type == 3)
722
				{
723
					global $dolibarr_main_url_root;
724
725
					// Define $urlwithroot
726
					$urlwithouturlroot=preg_replace('/'.preg_quote(DOL_URL_ROOT,'/').'$/i','',trim($dolibarr_main_url_root));
727
					$urlwithroot=$urlwithouturlroot.DOL_URL_ROOT;		// This is to use external domain name found into config file
728
					//$urlwithroot=DOL_MAIN_URL_ROOT;					// This is to use same domain name than current
729
730
					$res=(preg_match('/^http/i',$conf->file->dol_url_root[$key])?'':$urlwithroot).'/'.$path;     // Test on start with http is for old conf syntax
731
				}
732
				continue;
733
			}
734
			preg_match('/^([^\?]+(\.css\.php|\.css|\.js\.php|\.js|\.png|\.jpg|\.php)?)/i',$path,$regs);    // Take part before '?'
735
			if (! empty($regs[1]))
736
			{
737
				//print $key.'-'.$dirroot.'/'.$path.'-'.$conf->file->dol_url_root[$type].'<br>'."\n";
738
				if (file_exists($dirroot.'/'.$regs[1]))
739
				{
740
					if ($type == 1)
741
					{
742
						$res=(preg_match('/^http/i',$conf->file->dol_url_root[$key])?'':DOL_URL_ROOT).$conf->file->dol_url_root[$key].'/'.$path;
743
					}
744
					if ($type == 2)
745
					{
746
						$res=(preg_match('/^http/i',$conf->file->dol_url_root[$key])?'':DOL_MAIN_URL_ROOT).$conf->file->dol_url_root[$key].'/'.$path;
747
					}
748
					if ($type == 3)
749
					{
750
						global $dolibarr_main_url_root;
751
752
						// Define $urlwithroot
753
						$urlwithouturlroot=preg_replace('/'.preg_quote(DOL_URL_ROOT,'/').'$/i','',trim($dolibarr_main_url_root));
754
						$urlwithroot=$urlwithouturlroot.DOL_URL_ROOT;		// This is to use external domain name found into config file
755
						//$urlwithroot=DOL_MAIN_URL_ROOT;					// This is to use same domain name than current
756
757
						$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
758
					}
759
					break;
760
				}
761
			}
762
		}
763
	}
764
765
	return $res;
766
}
767
768
/**
769
 *	Create a clone of instance of object (new instance with same value for properties)
770
 *  With native = 0: Property that are reference are also new object (true clone). This means $this->db is not valid.
771
 *  With native = 1: Use PHP clone. Property that are reference are same pointer. This means $this->db is still valid.
772
 *
773
 * 	@param	object	$object		Object to clone
774
 *  @param	int		$native		Native method or true method
775
 *	@return object				Object clone
776
 *  @see https://php.net/manual/language.oop5.cloning.php
777
 */
778
function dol_clone($object, $native=0)
779
{
780
	//dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);
781
782
	if (empty($native))
783
	{
784
		$myclone=unserialize(serialize($object));
785
	}
786
	else
787
	{
788
		$myclone = clone $object;     // PHP clone is a shallow copy only, not a real clone, so properties of references will keep references (refer to the same target/variable)
789
	}
790
791
	return $myclone;
792
}
793
794
/**
795
 *	Optimize a size for some browsers (phone, smarphone, ...)
796
 *
797
 * 	@param	int		$size		Size we want
798
 * 	@param	string	$type		Type of optimizing:
799
 * 								'' = function used to define a size for truncation
800
 * 								'width' = function is used to define a width
801
 *	@return int					New size after optimizing
802
 */
803
function dol_size($size,$type='')
804
{
805
	global $conf;
806
	if (empty($conf->dol_optimize_smallscreen)) return $size;
807
	if ($type == 'width' && $size > 250) return 250;
808
	else return 10;
809
}
810
811
812
/**
813
 *	Clean a string to use it as a file name
814
 *
815
 *	@param	string	$str            String to clean
816
 * 	@param	string	$newstr			String to replace bad chars with
817
 *  @param	int	    $unaccent		1=Remove also accent (default), 0 do not remove them
818
 *	@return string          		String cleaned (a-zA-Z_)
819
 *
820
 * 	@see        	dol_string_nospecial, dol_string_unaccent, dol_sanitizePathName
821
 */
822
function dol_sanitizeFileName($str,$newstr='_',$unaccent=1)
823
{
824
	$filesystem_forbidden_chars = array('<','>','/','\\','?','*','|','"','°');
825
	return dol_string_nospecial($unaccent?dol_string_unaccent($str):$str, $newstr, $filesystem_forbidden_chars);
826
}
827
828
/**
829
 *	Clean a string to use it as a path name
830
 *
831
 *	@param	string	$str            String to clean
832
 * 	@param	string	$newstr			String to replace bad chars with
833
 *  @param	int	    $unaccent		1=Remove also accent (default), 0 do not remove them
834
 *	@return string          		String cleaned (a-zA-Z_)
835
 *
836
 * 	@see        	dol_string_nospecial, dol_string_unaccent, dol_sanitizeFileName
837
 */
838
function dol_sanitizePathName($str,$newstr='_',$unaccent=1)
839
{
840
	$filesystem_forbidden_chars = array('<','>','?','*','|','"','°');
841
	return dol_string_nospecial($unaccent?dol_string_unaccent($str):$str, $newstr, $filesystem_forbidden_chars);
842
}
843
844
/**
845
 *	Clean a string from all accent characters to be used as ref, login or by dol_sanitizeFileName
846
 *
847
 *	@param	string	$str			String to clean
848
 *	@return string   	       		Cleaned string
849
 *
850
 * 	@see    		dol_sanitizeFilename, dol_string_nospecial
851
 */
852
function dol_string_unaccent($str)
853
{
854
	if (utf8_check($str))
855
	{
856
		// See http://www.utf8-chartable.de/
857
		$string = rawurlencode($str);
858
		$replacements = array(
859
		'%C3%80' => 'A','%C3%81' => 'A','%C3%82' => 'A','%C3%83' => 'A','%C3%84' => 'A','%C3%85' => 'A',
860
		'%C3%88' => 'E','%C3%89' => 'E','%C3%8A' => 'E','%C3%8B' => 'E',
861
		'%C3%8C' => 'I','%C3%8D' => 'I','%C3%8E' => 'I','%C3%8F' => 'I',
862
		'%C3%92' => 'O','%C3%93' => 'O','%C3%94' => 'O','%C3%95' => 'O','%C3%96' => 'O',
863
		'%C3%99' => 'U','%C3%9A' => 'U','%C3%9B' => 'U','%C3%9C' => 'U',
864
		'%C3%A0' => 'a','%C3%A1' => 'a','%C3%A2' => 'a','%C3%A3' => 'a','%C3%A4' => 'a','%C3%A5' => 'a',
865
		'%C3%A7' => 'c',
866
		'%C3%A8' => 'e','%C3%A9' => 'e','%C3%AA' => 'e','%C3%AB' => 'e',
867
		'%C3%AC' => 'i','%C3%AD' => 'i','%C3%AE' => 'i','%C3%AF' => 'i',
868
		'%C3%B1' => 'n',
869
		'%C3%B2' => 'o','%C3%B3' => 'o','%C3%B4' => 'o','%C3%B5' => 'o','%C3%B6' => 'o',
870
		'%C3%B9' => 'u','%C3%BA' => 'u','%C3%BB' => 'u','%C3%BC' => 'u',
871
		'%C3%BF' => 'y'
872
		);
873
		$string=strtr($string, $replacements);
874
		return rawurldecode($string);
875
	}
876
	else
877
	{
878
		// See http://www.ascii-code.com/
879
		$string = strtr(
880
			$str,
881
			"\xC0\xC1\xC2\xC3\xC4\xC5\xC7
882
			\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1
883
			\xD2\xD3\xD4\xD5\xD8\xD9\xDA\xDB\xDD
884
			\xE0\xE1\xE2\xE3\xE4\xE5\xE7\xE8\xE9\xEA\xEB
885
			\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF8
886
			\xF9\xFA\xFB\xFC\xFD\xFF",
887
			"AAAAAAC
888
			EEEEIIIIDN
889
			OOOOOUUUY
890
			aaaaaaceeee
891
			iiiidnooooo
892
			uuuuyy"
893
		);
894
		$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"));
895
		return $string;
896
	}
897
}
898
899
/**
900
 *	Clean a string from all punctuation characters to use it as a ref or login.
901
 *  This is a more complete function than dol_sanitizeFileName.
902
 *
903
 *	@param	string	$str            	String to clean
904
 * 	@param	string	$newstr				String to replace forbidden chars with
905
 *  @param  array	$badcharstoreplace  List of forbidden characters
906
 * 	@return string          			Cleaned string
907
 *
908
 * 	@see    		dol_sanitizeFilename, dol_string_unaccent
909
 */
910
function dol_string_nospecial($str,$newstr='_',$badcharstoreplace='')
911
{
912
	$forbidden_chars_to_replace=array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ",", ";", "=", '°');  // more complete than dol_sanitizeFileName
913
	$forbidden_chars_to_remove=array();
914
	if (is_array($badcharstoreplace)) $forbidden_chars_to_replace=$badcharstoreplace;
915
	//$forbidden_chars_to_remove=array("(",")");
916
917
	return str_replace($forbidden_chars_to_replace,$newstr,str_replace($forbidden_chars_to_remove,"",$str));
918
}
919
920
921
/**
922
 * Encode string for xml usage
923
 *
924
 * @param 	string	$string		String to encode
925
 * @return	string				String encoded
926
 */
927
function dolEscapeXML($string)
928
{
929
	return strtr($string, array('\''=>'&apos;','"'=>'&quot;','&'=>'&amp;','<'=>'&lt;','>'=>'&gt;'));
930
}
931
932
/**
933
 *  Returns text escaped for inclusion into javascript code
934
 *
935
 *  @param      string		$stringtoescape		String to escape
936
 *  @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 \
937
 *  @param		int		$noescapebackslashn	0=Escape also \n. 1=Do not escape \n.
938
 *  @return     string     		 				Escaped string. Both ' and " are escaped into ' if they are escaped.
939
 */
940
function dol_escape_js($stringtoescape, $mode=0, $noescapebackslashn=0)
941
{
942
	// escape quotes and backslashes, newlines, etc.
943
	$substitjs=array("&#039;"=>"\\'","\r"=>'\\r');
944
	//$substitjs['</']='<\/';	// We removed this. Should be useless.
945
	if (empty($noescapebackslashn)) { $substitjs["\n"]='\\n'; $substitjs['\\']='\\\\'; }
946
	if (empty($mode)) { $substitjs["'"]="\\'"; $substitjs['"']="\\'"; }
947
	else if ($mode == 1) $substitjs["'"]="\\'";
948
	else if ($mode == 2) { $substitjs['"']='\\"'; }
949
	else if ($mode == 3) { $substitjs["'"]="\\'"; $substitjs['"']="\\\""; }
950
	return strtr($stringtoescape, $substitjs);
951
}
952
953
954
/**
955
 *  Returns text escaped for inclusion in HTML alt or title tags, or into values of HTML input fields.
956
 *
957
 *  @param      string		$stringtoescape		String to escape
958
 *  @param		int			$keepb				1=Preserve b tags (otherwise, remove them)
959
 *  @param      int         $keepn              1=Preserve \r\n strings (otherwise, replace them with escaped value)
960
 *  @return     string     				 		Escaped string
961
 *  @see		dol_string_nohtmltag, dol_string_nospecial, dol_string_unaccent
962
 */
963
function dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0)
964
{
965
	// escape quotes and backslashes, newlines, etc.
966
	$tmp=html_entity_decode($stringtoescape, ENT_COMPAT, 'UTF-8');		// TODO Use htmlspecialchars_decode instead, that make only required change for html tags
967
	if (! $keepb) $tmp=strtr($tmp, array("<b>"=>'','</b>'=>''));
968
	if (! $keepn) $tmp=strtr($tmp, array("\r"=>'\\r',"\n"=>'\\n'));
969
	return htmlentities($tmp, ENT_COMPAT, 'UTF-8');						// TODO Use htmlspecialchars instead, that make only required change for html tags
970
}
971
972
973
/**
974
 * Convert a string to lower. Never use strtolower because it does not works with UTF8 strings.
975
 *
976
 * @param 	string		$utf8_string		String to encode
977
 * @return 	string							String converted
978
 */
979
function dol_strtolower($utf8_string)
980
{
981
	return mb_strtolower($utf8_string, "UTF-8");
982
}
983
984
/**
985
 * Convert a string to upper. Never use strtolower because it does not works with UTF8 strings.
986
 *
987
 * @param 	string		$utf8_string		String to encode
988
 * @return 	string							String converted
989
 */
990
function dol_strtoupper($utf8_string)
991
{
992
	return mb_strtoupper($utf8_string, "UTF-8");
993
}
994
995
996
/**
997
 *	Write log message into outputs. Possible outputs can be:
998
 *	SYSLOG_HANDLERS = ["mod_syslog_file"]  		file name is then defined by SYSLOG_FILE
999
 *	SYSLOG_HANDLERS = ["mod_syslog_syslog"]  	facility is then defined by SYSLOG_FACILITY
1000
 *  Warning, syslog functions are bugged on Windows, generating memory protection faults. To solve
1001
 *  this, use logging to files instead of syslog (see setup of module).
1002
 *  Note: If constant 'SYSLOG_FILE_NO_ERROR' defined, we never output any error message when writing to log fails.
1003
 *  Note: You can get log message into html sources by adding parameter &logtohtml=1 (constant MAIN_LOGTOHTML must be set)
1004
 *  This function works only if syslog module is enabled.
1005
 * 	This must not use any call to other function calling dol_syslog (avoid infinite loop).
1006
 *
1007
 * 	@param  string		$message				Line to log. ''=Show nothing
1008
 *  @param  int			$level					Log level
1009
 *												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
1010
 *												On Linux   LOG_ERR=3, LOG_WARNING=4, LOG_INFO=6, LOG_DEBUG=7
1011
 *  @param	int			$ident					1=Increase ident of 1, -1=Decrease ident of 1
1012
 *  @param	string		$suffixinfilename		When output is a file, append this suffix into default log filename.
1013
 *  @param	string		$restricttologhandler	Output log only for this log handler
1014
 *  @return	void
1015
 */
1016
function dol_syslog($message, $level = LOG_INFO, $ident = 0, $suffixinfilename='', $restricttologhandler='')
1017
{
1018
	global $conf, $user;
1019
1020
	// If syslog module enabled
1021
	if (empty($conf->syslog->enabled)) return;
1022
1023
	if ($ident < 0)
1024
	{
1025
		foreach ($conf->loghandlers as $loghandlerinstance)
1026
		{
1027
			$loghandlerinstance->setIdent($ident);
1028
		}
1029
	}
1030
1031
	if (! empty($message))
1032
	{
1033
		// Test log level
1034
		$logLevels = array(LOG_EMERG, LOG_ALERT, LOG_CRIT, LOG_ERR, LOG_WARNING, LOG_NOTICE, LOG_INFO, LOG_DEBUG);
1035
		if (!in_array($level, $logLevels, true))
1036
		{
1037
			throw new Exception('Incorrect log level');
1038
		}
1039
		if ($level > $conf->global->SYSLOG_LEVEL) return;
1040
1041
		$message = preg_replace('/password=\'[^\']*\'/', 'password=\'hidden\'', $message);	// protection to avoid to have value of password in log
1042
1043
		// If adding log inside HTML page is required
1044
		if (! empty($_REQUEST['logtohtml']) && (! empty($conf->global->MAIN_ENABLE_LOG_TO_HTML) || ! empty($conf->global->MAIN_LOGTOHTML)))   // MAIN_LOGTOHTML kept for backward compatibility
1045
		{
1046
			$conf->logbuffer[] = dol_print_date(time(),"%Y-%m-%d %H:%M:%S")." ".$message;
1047
		}
1048
1049
		//TODO: Remove this. MAIN_ENABLE_LOG_INLINE_HTML should be deprecated and use a log handler dedicated to HTML output
1050
		// If html log tag enabled and url parameter log defined, we show output log on HTML comments
1051
		if (! empty($conf->global->MAIN_ENABLE_LOG_INLINE_HTML) && ! empty($_GET["log"]))
1052
		{
1053
			print "\n\n<!-- Log start\n";
1054
			print $message."\n";
1055
			print "Log end -->\n";
1056
		}
1057
1058
		$data = array(
1059
			'message' => $message,
1060
			'script' => (isset($_SERVER['PHP_SELF'])? basename($_SERVER['PHP_SELF'],'.php') : false),
1061
			'level' => $level,
1062
			'user' => ((is_object($user) && $user->id) ? $user->login : false),
1063
			'ip' => false
1064
		);
1065
1066
		// This is when server run behind a reverse proxy
1067
		if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) $data['ip'] = $_SERVER['HTTP_X_FORWARDED_FOR'].(empty($_SERVER["REMOTE_ADDR"])?'':'->'.$_SERVER['REMOTE_ADDR']);
1068
		// This is when server run normally on a server
1069
		else if (! empty($_SERVER["REMOTE_ADDR"])) $data['ip'] = $_SERVER['REMOTE_ADDR'];
1070
		// This is when PHP session is ran inside a web server but not inside a client request (example: init code of apache)
1071
		else if (! empty($_SERVER['SERVER_ADDR'])) $data['ip'] = $_SERVER['SERVER_ADDR'];
1072
		// 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).
1073
		else if (! empty($_SERVER['COMPUTERNAME'])) $data['ip'] = $_SERVER['COMPUTERNAME'].(empty($_SERVER['USERNAME'])?'':'@'.$_SERVER['USERNAME']);
1074
		// 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).
1075
		else if (! empty($_SERVER['LOGNAME'])) $data['ip'] = '???@'.$_SERVER['LOGNAME'];
1076
		// Loop on each log handler and send output
1077
		foreach ($conf->loghandlers as $loghandlerinstance)
1078
		{
1079
			if ($restricttologhandler && $loghandlerinstance->code != $restricttologhandler) continue;
1080
			$loghandlerinstance->export($data,$suffixinfilename);
1081
		}
1082
		unset($data);
1083
	}
1084
1085
	if ($ident > 0)
1086
	{
1087
		foreach ($conf->loghandlers as $loghandlerinstance)
1088
		{
1089
			$loghandlerinstance->setIdent($ident);
1090
		}
1091
	}
1092
}
1093
1094
1095
/**
1096
 *	Show tab header of a card
1097
 *
1098
 *	@param	array	$links				Array of tabs. Currently initialized by calling a function xxx_admin_prepare_head
1099
 *	@param	string	$active     		Active tab name (document', 'info', 'ldap', ....)
1100
 *	@param  string	$title      		Title
1101
 *	@param  int		$notab				-1 or 0=Add tab header, 1=no tab header. If you set this to 1, using dol_fiche_end() to close tab is not required.
1102
 * 	@param	string	$picto				Add a picto on tab title
1103
 *	@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.
1104
 *  @param	string	$morehtmlright		Add more html content on right of tabs title
1105
 *  @param	string	$morecss			More Css
1106
 * 	@return	void
1107
 */
1108
function dol_fiche_head($links=array(), $active='0', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='')
1109
{
1110
	print dol_get_fiche_head($links, $active, $title, $notab, $picto, $pictoisfullpath, $morehtmlright, $morecss);
1111
}
1112
1113
/**
1114
 *  Show tab header of a card
1115
 *
1116
 *	@param	array	$links				Array of tabs
1117
 *	@param	string	$active     		Active tab name
1118
 *	@param  string	$title      		Title
1119
 *	@param  int		$notab				-1 or 0=Add tab header, 1=no tab header. If you set this to 1, using dol_fiche_end() to close tab is not required.
1120
 * 	@param	string	$picto				Add a picto on tab title
1121
 *	@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.
1122
 *  @param	string	$morehtmlright		Add more html content on right of tabs title
1123
 *  @param	string	$morecss			More Css
1124
 * 	@return	string
1125
 */
1126
function dol_get_fiche_head($links=array(), $active='', $title='', $notab=0, $picto='', $pictoisfullpath=0, $morehtmlright='', $morecss='')
1127
{
1128
	global $conf, $langs, $hookmanager;
1129
1130
	$out="\n".'<div class="tabs" data-role="controlgroup" data-type="horizontal">'."\n";
1131
1132
	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.
1133
1134
	// Show title
1135
	$showtitle=1;
1136
	if (! empty($conf->dol_optimize_smallscreen)) $showtitle=0;
1137
	if (! empty($title) && $showtitle)
1138
	{
1139
		$limittitle=30;
1140
		$out.='<a class="tabTitle">';
1141
		if ($picto) $out.=img_picto($title,($pictoisfullpath?'':'object_').$picto,'',$pictoisfullpath).' ';
1142
		$out.='<span class="tabTitleText">'.dol_trunc($title,$limittitle).'</span>';
1143
		$out.='</a>';
1144
	}
1145
1146
	// Define max of key (max may be higher than sizeof because of hole due to module disabling some tabs).
1147
	$maxkey=-1;
1148
	if (is_array($links) && ! empty($links))
1149
	{
1150
		$keys=array_keys($links);
1151
		if (count($keys)) $maxkey=max($keys);
1152
	}
1153
1154
	if (! empty($conf->dol_optimize_smallscreen)) $conf->global->MAIN_MAXTABS_IN_CARD=2;
1155
1156
	// Show tabs
1157
	$bactive=false;
1158
	// if =0 we don't use the feature
1159
	$limittoshow=(empty($conf->global->MAIN_MAXTABS_IN_CARD)?99:$conf->global->MAIN_MAXTABS_IN_CARD);
1160
	$displaytab=0;
1161
	$nbintab=0;
1162
	$popuptab=0; $outmore='';
1163
	for ($i = 0 ; $i <= $maxkey ; $i++)
1164
	{
1165
		if ((is_numeric($active) && $i == $active) || (! empty($links[$i][2]) && ! is_numeric($active) && $active == $links[$i][2]))
1166
		{
1167
			// If active tab is already present
1168
			if ($i >= $limittoshow) $limittoshow--;
1169
		}
1170
	}
1171
1172
	for ($i = 0 ; $i <= $maxkey ; $i++)
1173
	{
1174
		if ((is_numeric($active) && $i == $active) || (! empty($links[$i][2]) && ! is_numeric($active) && $active == $links[$i][2]))
1175
		{
1176
			$isactive=true;
1177
			$bactive=true;
1178
		}
1179
		else
1180
		{
1181
			$isactive=false;
1182
		}
1183
1184
		if ($i < $limittoshow || $isactive)
1185
		{
1186
			$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]).' -->';
1187
			if (isset($links[$i][2]) && $links[$i][2] == 'image')
1188
			{
1189
				if (!empty($links[$i][0]))
1190
				{
1191
					$out.='<a class="tabimage'.($morecss?' '.$morecss:'').'" href="'.$links[$i][0].'">'.$links[$i][1].'</a>'."\n";
1192
				}
1193
				else
1194
				{
1195
					$out.='<span class="tabspan">'.$links[$i][1].'</span>'."\n";
1196
				}
1197
			}
1198
			else if (! empty($links[$i][1]))
1199
			{
1200
				//print "x $i $active ".$links[$i][2]." z";
1201
				if ($isactive)
1202
				{
1203
					$out.='<a'.(! empty($links[$i][2])?' id="'.$links[$i][2].'"':'').' class="tabactive tab inline-block'.($morecss?' '.$morecss:'').'" href="'.$links[$i][0].'">';
1204
					$out.=$links[$i][1];
1205
					$out.='</a>'."\n";
1206
				}
1207
				else
1208
				{
1209
					$out.='<a'.(! empty($links[$i][2])?' id="'.$links[$i][2].'"':'').' class="tabunactive tab inline-block'.($morecss?' '.$morecss:'').'" href="'.$links[$i][0].'">';
1210
					$out.=$links[$i][1];
1211
					$out.='</a>'."\n";
1212
				}
1213
			}
1214
			$out.='</div>';
1215
		}
1216
		else
1217
		{
1218
			// The popup with the other tabs
1219
			if (! $popuptab)
1220
			{
1221
				$popuptab=1;
1222
				$outmore.='<div class="popuptabset wordwrap">';	// The css used to hide/show popup
1223
			}
1224
			$outmore.='<div class="popuptab wordwrap" style="display:inherit;">';
1225
			if (isset($links[$i][2]) && $links[$i][2] == 'image')
1226
			{
1227
				if (!empty($links[$i][0]))
1228
					$outmore.='<a class="tabimage'.($morecss?' '.$morecss:'').'" href="'.$links[$i][0].'">'.$links[$i][1].'</a>'."\n";
1229
				else
1230
					$outmore.='<span class="tabspan">'.$links[$i][1].'</span>'."\n";
1231
			}
1232
			else if (! empty($links[$i][1]))
1233
			{
1234
				$outmore.='<a'.(! empty($links[$i][2])?' id="'.$links[$i][2].'"':'').' class="wordwrap inline-block'.($morecss?' '.$morecss:'').'" href="'.$links[$i][0].'">';
1235
				$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.
1236
				$outmore.='</a>'."\n";
1237
			}
1238
			$outmore.='</div>';
1239
1240
			$nbintab++;
1241
		}
1242
		$displaytab=$i;
1243
	}
1244
	if ($popuptab) $outmore.='</div>';
1245
1246
	if ($displaytab > $limittoshow)
1247
	{
1248
		$left=($langs->trans("DIRECTION") == 'rtl'?'right':'left');
1249
		$right=($langs->trans("DIRECTION") == 'rtl'?'left':'right');
1250
1251
		$tabsname=str_replace("@", "", $picto);
1252
		$out.='<div id="moretabs'.$tabsname.'" class="inline-block tabsElem">';
1253
		$out.='<a href="#" class="tab moretab inline-block tabunactive reposition">'.$langs->trans("More").'... ('.$nbintab.')</a>';
1254
		$out.='<div id="moretabsList'.$tabsname.'" style="position: absolute; '.$left.': -999em; text-align: '.$left.'; margin:0px; padding:2px">';
1255
		$out.=$outmore;
1256
		$out.='</div>';
1257
		$out.='<div></div>';
1258
		$out.="</div>\n";
1259
1260
		$out.="<script>";
1261
		$out.="$('#moretabs".$tabsname."').mouseenter( function() { console.log('mouseenter ".$left."'); $('#moretabsList".$tabsname."').css('".$left."','auto');});";
1262
		$out.="$('#moretabs".$tabsname."').mouseleave( function() { console.log('mouseleave ".$left."'); $('#moretabsList".$tabsname."').css('".$left."','-999em');});";
1263
		$out.="</script>";
1264
	}
1265
1266
	$out.="</div>\n";
1267
1268
	if (! $notab || $notab == -1) $out.="\n".'<div class="tabBar'.($notab == -1 ? '' : ' tabBarWithBottom').'">'."\n";
1269
1270
	$parameters=array('tabname' => $active, 'out' => $out);
1271
	$reshook=$hookmanager->executeHooks('printTabsHead',$parameters);	// This hook usage is called just before output the head of tabs. Take also a look at "completeTabsHead"
1272
	if ($reshook > 0)
1273
	{
1274
		$out = $hookmanager->resPrint;
1275
	}
1276
1277
	return $out;
1278
}
1279
1280
/**
1281
 *  Show tab footer of a card
1282
 *
1283
 *  @param	int		$notab       -1 or 0=Add tab footer, 1=no tab footer
1284
 *  @return	void
1285
 */
1286
function dol_fiche_end($notab=0)
1287
{
1288
	print dol_get_fiche_end($notab);
1289
}
1290
1291
/**
1292
 *	Return tab footer of a card
1293
 *
1294
 *	@param  int		$notab		-1 or 0=Add tab footer, 1=no tab footer
1295
 *  @return	string
1296
 */
1297
function dol_get_fiche_end($notab=0)
1298
{
1299
	if (! $notab || $notab == -1) return "\n</div>\n";
1300
	else return '';
1301
}
1302
1303
/**
1304
 *  Show tab footer of a card.
1305
 *  Note: $object->next_prev_filter can be set to restrict select to find next or previous record by $form->showrefnav.
1306
 *
1307
 *  @param	Object	$object			Object to show
1308
 *  @param	string	$paramid   		Name of parameter to use to name the id into the URL next/previous link
1309
 *  @param	string	$morehtml  		More html content to output just before the nav bar
1310
 *  @param	int		$shownav	  	Show Condition (navigation is shown if value is 1)
1311
 *  @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.
1312
 *  @param	string	$fieldref   	Nom du champ objet ref (object->ref) a utiliser pour select next et previous
1313
 *  @param	string	$morehtmlref  	More html to show after ref
1314
 *  @param	string	$moreparam  	More param to add in nav link url.
1315
 *	@param	int		$nodbprefix		Do not include DB prefix to forge table name
1316
 *	@param	string	$morehtmlleft	More html code to show before ref
1317
 *	@param	string	$morehtmlstatus	More html code to show under navigation arrows
1318
 *  @param  int     $onlybanner     Put this to 1, if the card will contains only a banner (this add css 'arearefnobottom' on div)
1319
 *	@param	string	$morehtmlright	More html code to show before navigation arrows
1320
 *  @return	void
1321
 */
1322
function dol_banner_tab($object, $paramid, $morehtml='', $shownav=1, $fieldid='rowid', $fieldref='ref', $morehtmlref='', $moreparam='', $nodbprefix=0, $morehtmlleft='', $morehtmlstatus='', $onlybanner=0, $morehtmlright='')
1323
{
1324
	global $conf, $form, $user, $langs;
1325
1326
	$error = 0;
1327
1328
	$maxvisiblephotos=1;
1329
	$showimage=1;
1330
	$entity=(empty($object->entity)?$conf->entity:$object->entity);
1331
	$showbarcode=empty($conf->barcode->enabled)?0:($object->barcode?1:0);
1332
	if (! empty($conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->barcode->lire_advance)) $showbarcode=0;
1333
	$modulepart='unknown';
1334
1335
	if ($object->element == 'societe')         $modulepart='societe';
1336
	if ($object->element == 'contact')         $modulepart='contact';
1337
	if ($object->element == 'member')          $modulepart='memberphoto';
1338
	if ($object->element == 'user')            $modulepart='userphoto';
1339
	if ($object->element == 'product')         $modulepart='product';
1340
1341
	if (class_exists("Imagick"))
1342
	{
1343
		if ($object->element == 'propal')            $modulepart='propal';
1344
		if ($object->element == 'commande')          $modulepart='commande';
1345
		if ($object->element == 'facture')           $modulepart='facture';
1346
		if ($object->element == 'fichinter')         $modulepart='ficheinter';
1347
		if ($object->element == 'contrat')           $modulepart='contract';
1348
		if ($object->element == 'supplier_proposal') $modulepart='supplier_proposal';
1349
		if ($object->element == 'order_supplier')    $modulepart='supplier_order';
1350
		if ($object->element == 'invoice_supplier')  $modulepart='supplier_invoice';
1351
		if ($object->element == 'expensereport')     $modulepart='expensereport';
1352
	}
1353
1354
	if ($object->element == 'product')
1355
	{
1356
		$width=80; $cssclass='photoref';
1357
		$showimage=$object->is_photo_available($conf->product->multidir_output[$entity]);
1358
		$maxvisiblephotos=(isset($conf->global->PRODUCT_MAX_VISIBLE_PHOTO)?$conf->global->PRODUCT_MAX_VISIBLE_PHOTO:5);
1359
		if ($conf->browser->layout == 'phone') $maxvisiblephotos=1;
1360
		if ($showimage) $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>';
1361
		else
1362
		{
1363
			if (!empty($conf->global->PRODUCT_NODISPLAYIFNOPHOTO)) {
1364
				$nophoto='';
1365
				$morehtmlleft.='<div class="floatleft inline-block valignmiddle divphotoref"></div>';
1366
			}
1367
			//elseif ($conf->browser->layout != 'phone') {    // Show no photo link
1368
				$nophoto='/public/theme/common/nophoto.png';
1369
				$morehtmlleft.='<div class="floatleft inline-block valignmiddle divphotoref"><img class="photo'.$modulepart.($cssclass?' '.$cssclass:'').'" alt="No photo" border="0"'.($width?' width="'.$width.'"':'').' src="'.DOL_URL_ROOT.$nophoto.'"></div>';
1370
			//}
1371
		}
1372
	}
1373
	elseif ($object->element == 'ticket')
1374
	{
1375
		$width=80; $cssclass='photoref';
1376
		$showimage=$object->is_photo_available($conf->ticket->multidir_output[$entity].'/'.$object->track_id);
1377
		$maxvisiblephotos=(isset($conf->global->TICKETSUP_MAX_VISIBLE_PHOTO)?$conf->global->TICKETSUP_MAX_VISIBLE_PHOTO:2);
1378
		if ($conf->browser->layout == 'phone') $maxvisiblephotos=1;
1379
		if ($showimage) $morehtmlleft.='<div class="floatleft inline-block valignmiddle divphotoref">'.$object->show_photos('ticket', $conf->ticket->multidir_output[$entity],'small',$maxvisiblephotos,0,0,0,$width,0).'</div>';
1380
		else
1381
		{
1382
			if (!empty($conf->global->TICKETSUP_NODISPLAYIFNOPHOTO)) {
1383
				$nophoto='';
1384
				$morehtmlleft.='<div class="floatleft inline-block valignmiddle divphotoref"></div>';
1385
			}
1386
			//elseif ($conf->browser->layout != 'phone') {    // Show no photo link
1387
			$nophoto='/public/theme/common/nophoto.png';
1388
			$morehtmlleft.='<div class="floatleft inline-block valignmiddle divphotoref"><img class="photo'.$modulepart.($cssclass?' '.$cssclass:'').'" alt="No photo" border="0"'.($width?' width="'.$width.'"':'').' src="'.DOL_URL_ROOT.$nophoto.'"></div>';
1389
			//}
1390
		}
1391
	}
1392
	else
1393
	{
1394
		if ($showimage)
1395
		{
1396
			if ($modulepart != 'unknown')
1397
			{
1398
				$phototoshow='';
1399
				// Check if a preview file is available
1400
				if (in_array($modulepart, array('propal', 'commande', 'facture', 'ficheinter', 'contract', 'supplier_order', 'supplier_proposal', 'supplier_invoice', 'expensereport')) && class_exists("Imagick"))
1401
				{
1402
					$objectref = dol_sanitizeFileName($object->ref);
1403
					$dir_output = (empty($conf->$modulepart->multidir_output[$entity]) ? $conf->$modulepart->dir_output : $conf->$modulepart->multidir_output[$entity]) . "/";
1404
					if (in_array($modulepart, array('invoice_supplier', 'supplier_invoice')))
1405
					{
1406
						$subdir = get_exdir($object->id, 2, 0, 1, $object, $modulepart);
1407
						$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
1408
					}
1409
					else
1410
					{
1411
						$subdir = get_exdir($object->id, 0, 0, 1, $object, $modulepart);
1412
					}
1413
					if (empty($subdir)) $subdir = 'errorgettingsubdirofobject';	// Protection to avoid to return empty path
1414
1415
					$filepath = $dir_output . $subdir . "/";
1416
1417
					$file = $filepath . $objectref . ".pdf";
1418
					$relativepath = $subdir.'/'.$objectref.'.pdf';
1419
1420
					// Define path to preview pdf file (preview precompiled "file.ext" are "file.ext_preview.png")
1421
					$fileimage = $file.'_preview.png';              // If PDF has 1 page
1422
					$fileimagebis = $file.'_preview-0.png';         // If PDF has more than one page
1423
					$relativepathimage = $relativepath.'_preview.png';
1424
1425
					// Si fichier PDF existe
1426
					if (file_exists($file))
1427
					{
1428
						$encfile = urlencode($file);
1429
						// Conversion du PDF en image png si fichier png non existant
1430
						if ( (! file_exists($fileimage) || (filemtime($fileimage) < filemtime($file)))
1431
						  && (! file_exists($fileimagebis) || (filemtime($fileimagebis) < filemtime($file)))
1432
						   )
1433
						{
1434
							if (empty($conf->global->MAIN_DISABLE_PDF_THUMBS))		// If you experienc trouble with pdf thumb generation and imagick, you can disable here.
1435
							{
1436
								$ret = dol_convert_file($file, 'png', $fileimage);
1437
								if ($ret < 0) $error++;
1438
							}
1439
						}
1440
1441
						$heightforphotref=70;
1442
						if (! empty($conf->dol_optimize_smallscreen)) $heightforphotref=60;
1443
						// Si fichier png PDF d'1 page trouve
1444
						if (file_exists($fileimage))
1445
						{
1446
							$phototoshow = '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
1447
							$phototoshow.= '<img height="'.$heightforphotref.'" class="photo photowithmargin photowithborder" src="'.DOL_URL_ROOT . '/viewimage.php?modulepart=apercu'.$modulepart.'&amp;file='.urlencode($relativepathimage).'">';
1448
							$phototoshow.= '</div></div>';
1449
						}
1450
						// Si fichier png PDF de plus d'1 page trouve
1451
						elseif (file_exists($fileimagebis))
1452
						{
1453
							$preview = preg_replace('/\.png/','',$relativepathimage) . "-0.png";
1454
							$phototoshow = '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
1455
							$phototoshow.= '<img height="'.$heightforphotref.'" class="photo photowithmargin photowithborder" src="'.DOL_URL_ROOT . '/viewimage.php?modulepart=apercu'.$modulepart.'&amp;file='.urlencode($preview).'"><p>';
1456
							$phototoshow.= '</div></div>';
1457
						}
1458
					}
1459
				}
1460
				else if (! $phototoshow)
1461
				{
1462
					$phototoshow = $form->showphoto($modulepart,$object,0,0,0,'photoref','small',1,0,$maxvisiblephotos);
1463
				}
1464
1465
				if ($phototoshow)
1466
				{
1467
					$morehtmlleft.='<div class="floatleft inline-block valignmiddle divphotoref">';
1468
					$morehtmlleft.=$phototoshow;
1469
					$morehtmlleft.='</div>';
1470
				}
1471
			}
1472
1473
			if (! $phototoshow)      // Show No photo link (picto of pbject)
0 ignored issues
show
Bug introduced by
The variable $phototoshow does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1474
			{
1475
				$morehtmlleft.='<div class="floatleft inline-block valignmiddle divphotoref">';
1476
				if ($object->element == 'action')
1477
				{
1478
					$width=80;
1479
					$cssclass='photorefcenter';
1480
					$nophoto=img_picto('', 'title_agenda', '', false, 1);
1481
				}
1482
				else
1483
				{
1484
					$width=14; $cssclass='photorefcenter';
1485
					$picto = $object->picto;
1486
					if ($object->element == 'project' && ! $object->public) $picto = 'project'; // instead of projectpub
1487
					$nophoto=img_picto('', 'object_'.$picto, '', false, 1);
1488
				}
1489
				$morehtmlleft.='<!-- No photo to show -->';
1490
				$morehtmlleft.='<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref"><img class="photo'.$modulepart.($cssclass?' '.$cssclass:'').'" alt="No photo" border="0"'.($width?' width="'.$width.'"':'').' src="'.$nophoto.'"></div></div>';
1491
1492
				$morehtmlleft.='</div>';
1493
			}
1494
		}
1495
	}
1496
1497
	if ($showbarcode) $morehtmlleft.='<div class="floatleft inline-block valignmiddle divphotoref">'.$form->showbarcode($object).'</div>';
1498
1499
	if ($object->element == 'societe')
1500
	{
1501
		if (! empty($conf->use_javascript_ajax) && $user->rights->societe->creer && ! empty($conf->global->MAIN_DIRECT_STATUS_UPDATE))
1502
		{
1503
		   	$morehtmlstatus.=ajax_object_onoff($object, 'status', 'status', 'InActivity', 'ActivityCeased');
1504
		}
1505
		else {
1506
			$morehtmlstatus.=$object->getLibStatut(6);
1507
		}
1508
	}
1509
	elseif ($object->element == 'product')
1510
	{
1511
		//$morehtmlstatus.=$langs->trans("Status").' ('.$langs->trans("Sell").') ';
1512
		if (! empty($conf->use_javascript_ajax) && $user->rights->produit->creer && ! empty($conf->global->MAIN_DIRECT_STATUS_UPDATE)) {
1513
			$morehtmlstatus.=ajax_object_onoff($object, 'status', 'tosell', 'ProductStatusOnSell', 'ProductStatusNotOnSell');
1514
		} else {
1515
			$morehtmlstatus.='<span class="statusrefsell">'.$object->getLibStatut(5,0).'</span>';
1516
		}
1517
		$morehtmlstatus.=' &nbsp; ';
1518
		//$morehtmlstatus.=$langs->trans("Status").' ('.$langs->trans("Buy").') ';
1519
		if (! empty($conf->use_javascript_ajax) && $user->rights->produit->creer && ! empty($conf->global->MAIN_DIRECT_STATUS_UPDATE)) {
1520
			$morehtmlstatus.=ajax_object_onoff($object, 'status_buy', 'tobuy', 'ProductStatusOnBuy', 'ProductStatusNotOnBuy');
1521
		} else {
1522
			$morehtmlstatus.='<span class="statusrefbuy">'.$object->getLibStatut(5,1).'</span>';
1523
		}
1524
	}
1525
	elseif (in_array($object->element, array('facture', 'invoice', 'invoice_supplier', 'chargesociales', 'loan')))
1526
	{
1527
		$tmptxt=$object->getLibStatut(6, $object->totalpaye);
1528
		if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3) || $conf->browser->layout=='phone') $tmptxt=$object->getLibStatut(5, $object->totalpaye);
1529
		$morehtmlstatus.=$tmptxt;
1530
	}
1531
	elseif ($object->element == 'contrat' || $object->element == 'contract')
1532
	{
1533
		if ($object->statut == 0) $morehtmlstatus.=$object->getLibStatut(5);
1534
		else $morehtmlstatus.=$object->getLibStatut(4);
1535
	}
1536
	elseif ($object->element == 'facturerec')
1537
	{
1538
		if ($object->frequency == 0) $morehtmlstatus.=$object->getLibStatut(2);
1539
		else $morehtmlstatus.=$object->getLibStatut(5);
1540
	}
1541
	elseif ($object->element == 'project_task')
1542
	{
1543
		$object->fk_statut = 1;
1544
		if ($object->progress > 0) $object->fk_statut = 2;
1545
		if ($object->progress >= 100) $object->fk_statut = 3;
1546
		$tmptxt=$object->getLibStatut(5);
1547
		$morehtmlstatus.=$tmptxt;		// No status on task
1548
	}
1549
	else { // Generic case
1550
		$tmptxt=$object->getLibStatut(6);
1551
		if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3) || $conf->browser->layout=='phone') $tmptxt=$object->getLibStatut(5);
1552
		$morehtmlstatus.=$tmptxt;
1553
	}
1554
1555
	// Add if object was dispatched "into accountancy"
1556
	if (! empty($conf->accounting->enabled) && in_array($object->element, array('bank', 'facture', 'invoice', 'invoice_supplier', 'expensereport')))
1557
	{
1558
		if (method_exists($object, 'getVentilExportCompta'))
1559
		{
1560
			$accounted = $object->getVentilExportCompta();
1561
			$langs->load("accountancy");
1562
			$morehtmlstatus.='</div><div class="statusref statusrefbis">'.($accounted > 0 ? $langs->trans("Accounted") : $langs->trans("NotYetAccounted"));
1563
		}
1564
	}
1565
1566
	// Add alias for thirdparty
1567
	if (! empty($object->name_alias)) $morehtmlref.='<div class="refidno">'.$object->name_alias.'</div>';
1568
1569
	// Add label
1570
	if ($object->element == 'product' || $object->element == 'bank_account' || $object->element == 'project_task')
1571
	{
1572
		if (! empty($object->label)) $morehtmlref.='<div class="refidno">'.$object->label.'</div>';
1573
	}
1574
1575
	if (method_exists($object, 'getBannerAddress') && $object->element != 'product' && $object->element != 'bookmark' && $object->element != 'ecm_directories' && $object->element != 'ecm_files')
1576
	{
1577
		$morehtmlref.='<div class="refidno">';
1578
		$morehtmlref.=$object->getBannerAddress('refaddress',$object);
1579
		$morehtmlref.='</div>';
1580
	}
1581
	if (! empty($conf->global->MAIN_SHOW_TECHNICAL_ID) && in_array($object->element, array('societe', 'contact', 'member', 'product')))
1582
	{
1583
		$morehtmlref.='<div style="clear: both;"></div><div class="refidno">';
1584
		$morehtmlref.=$langs->trans("TechnicalID").': '.$object->id;
1585
		$morehtmlref.='</div>';
1586
	}
1587
1588
	print '<div class="'.($onlybanner?'arearefnobottom ':'arearef ').'heightref valignmiddle" width="100%">';
1589
	print $form->showrefnav($object, $paramid, $morehtml, $shownav, $fieldid, $fieldref, $morehtmlref, $moreparam, $nodbprefix, $morehtmlleft, $morehtmlstatus, $morehtmlright);
1590
	print '</div>';
1591
	print '<div class="underrefbanner clearboth"></div>';
1592
}
1593
1594
/**
1595
 * Show a string with the label tag dedicated to the HTML edit field.
1596
 *
1597
 * @param	string	$langkey		Translation key
1598
 * @param 	string	$fieldkey		Key of the html select field the text refers to
1599
 * @param	int		$fieldrequired	1=Field is mandatory
1600
 * @return string
1601
 * @deprecated Form::editfieldkey
1602
 */
1603
function fieldLabel($langkey, $fieldkey, $fieldrequired=0)
1604
{
1605
	global $conf, $langs;
1606
	$ret='';
1607
	if ($fieldrequired) $ret.='<span class="fieldrequired">';
1608
	if (($conf->dol_use_jmobile != 4)) $ret.='<label for="'.$fieldkey.'">';
1609
	$ret.=$langs->trans($langkey);
1610
	if (($conf->dol_use_jmobile != 4)) $ret.='</label>';
1611
	if ($fieldrequired) $ret.='</span>';
1612
	return $ret;
1613
}
1614
1615
/**
1616
 * Return string to add class property on html element with pair/impair.
1617
 *
1618
 * @param	string	$var			0 or 1
1619
 * @param	string	$moreclass		More class to add
1620
 * @return	string					String to add class onto HTML element
1621
 */
1622
function dol_bc($var,$moreclass='')
1623
{
1624
	global $bc;
1625
	$ret=' '.$bc[$var];
1626
	if ($moreclass) $ret=preg_replace('/class=\"/','class="'.$moreclass.' ',$ret);
1627
	return $ret;
1628
}
1629
1630
/**
1631
 *      Return a formated address (part address/zip/town/state) according to country rules
1632
 *
1633
 *      @param  Object		$object			A company or contact object
1634
 * 	    @param	int			$withcountry		1=Add country into address string
1635
 *      @param	string		$sep				Separator to use to build string
1636
 *      @param	Translate	$outputlangs		Object lang that contains language for text translation.
1637
 *      @param	int		$mode		0=Standard output, 1=Remove address
1638
 *      @return string						Formated string
1639
 *      @see dol_print_address
1640
 */
1641
function dol_format_address($object, $withcountry=0, $sep="\n", $outputlangs='', $mode=0)
1642
{
1643
	global $conf,$langs;
1644
1645
	$ret='';
1646
	$countriesusingstate=array('AU','CA','US','IN','GB','ES','UK','TR');    // See also MAIN_FORCE_STATE_INTO_ADDRESS
1647
1648
	// Address
1649
	if (empty($mode)) {
1650
		$ret .= $object->address;
1651
	}
1652
	// Zip/Town/State
1653
	if (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
1654
	{
1655
		$ret .= ($ret ? $sep : '' ).$object->town;
1656
		if ($object->state)
1657
		{
1658
			$ret.=($ret?", ":'').$object->state;
1659
		}
1660
		if ($object->zip) $ret .= ($ret?", ":'').$object->zip;
1661
	}
1662
	else if (in_array($object->country_code,array('GB','UK'))) // UK: title firstname name \n address lines \n town state \n zip \n country
1663
	{
1664
		$ret .= ($ret ? $sep : '' ).$object->town;
1665
		if ($object->state)
1666
		{
1667
			$ret.=($ret?", ":'').$object->state;
1668
		}
1669
		if ($object->zip) $ret .= ($ret ? $sep : '' ).$object->zip;
1670
	}
1671
	else if (in_array($object->country_code,array('ES','TR'))) // ES: title firstname name \n address lines \n zip town \n state \n country
1672
	{
1673
		$ret .= ($ret ? $sep : '' ).$object->zip;
1674
		$ret .= ($object->town?(($object->zip?' ':'').$object->town):'');
1675
		if ($object->state)
1676
		{
1677
			$ret.="\n".$object->state;
1678
		}
1679
	}
1680
	else if (in_array($object->country_code,array('IT'))) // IT: tile firstname name\n address lines \n zip (Code Departement) \n country
1681
	{
1682
		$ret .= ($ret ? $sep : '' ).$object->zip;
1683
		$ret .= ($object->town?(($object->zip?' ':'').$object->town):'');
1684
		$ret .= ($object->departement_id?(' ('.($object->departement_id).')'):'');
1685
	}
1686
	else                                        		// Other: title firstname name \n address lines \n zip town \n country
1687
	{
1688
		$ret .= $object->zip ? (($ret ? $sep : '' ).$object->zip) : '';
1689
		$ret .= ($object->town?(($object->zip?' ':($ret ? $sep : '' )).$object->town):'');
1690
		if ($object->state && in_array($object->country_code,$countriesusingstate))
1691
		{
1692
			$ret.=($ret?", ":'').$object->state;
1693
		}
1694
	}
1695
	if (! is_object($outputlangs)) $outputlangs=$langs;
1696
	if ($withcountry)
1697
	{
1698
		$langs->load("dict");
1699
		$ret.=($object->country_code?($ret?$sep:'').$outputlangs->convToOutputCharset($outputlangs->transnoentitiesnoconv("Country".$object->country_code)):'');
1700
	}
1701
1702
	return $ret;
1703
}
1704
1705
1706
1707
/**
1708
 *	Format a string.
1709
 *
1710
 *	@param	string	$fmt		Format of strftime function (http://php.net/manual/fr/function.strftime.php)
1711
 *  @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)
1712
 *  @param	int		$is_gmt		See comment of timestamp parameter
1713
 *	@return	string				A formatted string
1714
 */
1715
function dol_strftime($fmt, $ts=false, $is_gmt=false)
1716
{
1717
	if ((abs($ts) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range
1718
		return ($is_gmt)? @gmstrftime($fmt,$ts): @strftime($fmt,$ts);
1719
	}
1720
	else return 'Error date into a not supported range';
1721
}
1722
1723
/**
1724
 *	Output date in a string format according to outputlangs (or langs if not defined).
1725
 * 	Return charset is always UTF-8, except if encodetoouput is defined. In this case charset is output charset
1726
 *
1727
 *	@param	int			$time			GM Timestamps date
1728
 *	@param	string		$format      	Output date format (tag of strftime function)
1729
 *										"%d %b %Y",
1730
 *										"%d/%m/%Y %H:%M",
1731
 *										"%d/%m/%Y %H:%M:%S",
1732
 *                                      "%B"=Long text of month, "%A"=Long text of day, "%b"=Short text of month, "%a"=Short text of day
1733
 *										"day", "daytext", "dayhour", "dayhourldap", "dayhourtext", "dayrfc", "dayhourrfc", "...reduceformat"
1734
 * 	@param	string		$tzoutput		true or 'gmt' => string is for Greenwich location
1735
 * 										false or 'tzserver' => output string is for local PHP server TZ usage
1736
 * 										'tzuser' => output string is for user TZ (current browser TZ with current dst) => In a future, we should have same behaviour than 'tzuserrel'
1737
 *                                      'tzuserrel' => output string is for user TZ (current browser TZ with dst or not, depending on date position) (TODO not implemented yet)
1738
 *	@param	Translate	$outputlangs	Object lang that contains language for text translation.
1739
 *  @param  boolean		$encodetooutput false=no convert into output pagecode
1740
 * 	@return string      				Formated date or '' if time is null
1741
 *
1742
 *  @see        dol_mktime, dol_stringtotime, dol_getdate
1743
 */
1744
function dol_print_date($time,$format='',$tzoutput='tzserver',$outputlangs='',$encodetooutput=false)
1745
{
1746
	global $conf,$langs;
1747
1748
	// Clean parameters
1749
	$to_gmt=false;
1750
	$offsettz=$offsetdst=0;
1751
	if ($tzoutput)
1752
	{
1753
		$to_gmt=true;	// For backward compatibility
1754
		if (is_string($tzoutput))
1755
		{
1756
			if ($tzoutput == 'tzserver')
1757
			{
1758
				$to_gmt=false;
1759
				$offsettzstring=@date_default_timezone_get();		// Example 'Europe/Berlin' or 'Indian/Reunion'
1760
				$offsettz=0;
1761
				$offsetdst=0;
1762
			}
1763
			elseif ($tzoutput == 'tzuser' || $tzoutput == 'tzuserrel')
1764
			{
1765
				$to_gmt=true;
1766
				$offsettzstring=(empty($_SESSION['dol_tz_string'])?'UTC':$_SESSION['dol_tz_string']);	// Example 'Europe/Berlin' or 'Indian/Reunion'
1767
				$offsettz=(empty($_SESSION['dol_tz'])?0:$_SESSION['dol_tz'])*60*60;		// Will not be used anymore
1768
				$offsetdst=(empty($_SESSION['dol_dst'])?0:$_SESSION['dol_dst'])*60*60;	// Will not be used anymore
1769
			}
1770
		}
1771
	}
1772
	if (! is_object($outputlangs)) $outputlangs=$langs;
1773
	if (! $format) $format='daytextshort';
1774
	$reduceformat=(! empty($conf->dol_optimize_smallscreen) && in_array($format,array('day','dayhour')))?1:0;
1775
	$formatwithoutreduce = preg_replace('/reduceformat/','',$format);
1776
	if ($formatwithoutreduce != $format) { $format = $formatwithoutreduce; $reduceformat=1; }  // so format 'dayreduceformat' is processed like day
1777
1778
	// Change predefined format into computer format. If found translation in lang file we use it, otherwise we use default.
1779
	// TODO Add format daysmallyear and dayhoursmallyear
1780
	if ($format == 'day')				$format=($outputlangs->trans("FormatDateShort")!="FormatDateShort"?$outputlangs->trans("FormatDateShort"):$conf->format_date_short);
1781
	else if ($format == 'hour')			$format=($outputlangs->trans("FormatHourShort")!="FormatHourShort"?$outputlangs->trans("FormatHourShort"):$conf->format_hour_short);
1782
	else if ($format == 'hourduration')	$format=($outputlangs->trans("FormatHourShortDuration")!="FormatHourShortDuration"?$outputlangs->trans("FormatHourShortDuration"):$conf->format_hour_short_duration);
1783
	else if ($format == 'daytext')			 $format=($outputlangs->trans("FormatDateText")!="FormatDateText"?$outputlangs->trans("FormatDateText"):$conf->format_date_text);
1784
	else if ($format == 'daytextshort')	$format=($outputlangs->trans("FormatDateTextShort")!="FormatDateTextShort"?$outputlangs->trans("FormatDateTextShort"):$conf->format_date_text_short);
1785
	else if ($format == 'dayhour')			 $format=($outputlangs->trans("FormatDateHourShort")!="FormatDateHourShort"?$outputlangs->trans("FormatDateHourShort"):$conf->format_date_hour_short);
1786
	else if ($format == 'dayhoursec')		 $format=($outputlangs->trans("FormatDateHourSecShort")!="FormatDateHourSecShort"?$outputlangs->trans("FormatDateHourSecShort"):$conf->format_date_hour_sec_short);
1787
	else if ($format == 'dayhourtext')		 $format=($outputlangs->trans("FormatDateHourText")!="FormatDateHourText"?$outputlangs->trans("FormatDateHourText"):$conf->format_date_hour_text);
1788
	else if ($format == 'dayhourtextshort') $format=($outputlangs->trans("FormatDateHourTextShort")!="FormatDateHourTextShort"?$outputlangs->trans("FormatDateHourTextShort"):$conf->format_date_hour_text_short);
1789
	// Format not sensitive to language
1790
	else if ($format == 'dayhourlog')		 $format='%Y%m%d%H%M%S';
1791
	else if ($format == 'dayhourldap')		 $format='%Y%m%d%H%M%SZ';
1792
	else if ($format == 'dayhourxcard')	$format='%Y%m%dT%H%M%SZ';
1793
	else if ($format == 'dayxcard')	 	$format='%Y%m%d';
1794
	else if ($format == 'dayrfc')			 $format='%Y-%m-%d';             // DATE_RFC3339
1795
	else if ($format == 'dayhourrfc')		 $format='%Y-%m-%dT%H:%M:%SZ';   // DATETIME RFC3339
1796
	else if ($format == 'standard')		$format='%Y-%m-%d %H:%M:%S';
1797
1798
	if ($reduceformat)
1799
	{
1800
		$format=str_replace('%Y','%y',$format);
1801
		$format=str_replace('yyyy','yy',$format);
1802
	}
1803
1804
	// If date undefined or "", we return ""
1805
	if (dol_strlen($time) == 0) return '';		// $time=0 allowed (it means 01/01/1970 00:00:00)
1806
1807
	// Clean format
1808
	if (preg_match('/%b/i',$format))		// There is some text to translate
1809
	{
1810
		// We inhibate translation to text made by strftime functions. We will use trans instead later.
1811
		$format=str_replace('%b','__b__',$format);
1812
		$format=str_replace('%B','__B__',$format);
1813
	}
1814
	if (preg_match('/%a/i',$format))		// There is some text to translate
1815
	{
1816
		// We inhibate translation to text made by strftime functions. We will use trans instead later.
1817
		$format=str_replace('%a','__a__',$format);
1818
		$format=str_replace('%A','__A__',$format);
1819
	}
1820
1821
	// Analyze date
1822
	if (preg_match('/^([0-9]+)\-([0-9]+)\-([0-9]+) ?([0-9]+)?:?([0-9]+)?:?([0-9]+)?/i',$time,$reg)
1823
	|| 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
1824
	{
1825
		// TODO Remove this.
1826
		// This part of code should not be used.
1827
		dol_syslog("Functions.lib::dol_print_date function call with deprecated value of time in page ".$_SERVER["PHP_SELF"], LOG_ERR);
1828
		// Date has format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS' or 'YYYYMMDDHHMMSS'
1829
		$syear	= (! empty($reg[1]) ? $reg[1] : '');
1830
		$smonth	= (! empty($reg[2]) ? $reg[2] : '');
1831
		$sday	= (! empty($reg[3]) ? $reg[3] : '');
1832
		$shour	= (! empty($reg[4]) ? $reg[4] : '');
1833
		$smin	= (! empty($reg[5]) ? $reg[5] : '');
1834
		$ssec	= (! empty($reg[6]) ? $reg[6] : '');
1835
1836
		$time=dol_mktime($shour,$smin,$ssec,$smonth,$sday,$syear,true);
1837
		$ret=adodb_strftime($format, $time+$offsettz+$offsetdst, $to_gmt);
1838
	}
1839
	else
1840
	{
1841
		// Date is a timestamps
1842
		if ($time < 100000000000)	// Protection against bad date values
1843
		{
1844
			$timetouse = $time+$offsettz+$offsetdst;	// TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
1845
1846
			$ret=adodb_strftime($format, $timetouse, $to_gmt);
1847
		}
1848
		else $ret='Bad value '.$time.' for date';
1849
	}
1850
1851
	if (preg_match('/__b__/i',$format))
1852
	{
1853
		$timetouse = $time+$offsettz+$offsetdst;	// TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
1854
1855
		// Here ret is string in PHP setup language (strftime was used). Now we convert to $outputlangs.
1856
		$month=adodb_strftime('%m', $timetouse);
1857
		$month=sprintf("%02d", $month);	// $month may be return with format '06' on some installation and '6' on other, so we force it to '06'.
1858
		if ($encodetooutput)
1859
		{
1860
			$monthtext=$outputlangs->transnoentities('Month'.$month);
1861
			$monthtextshort=$outputlangs->transnoentities('MonthShort'.$month);
1862
		}
1863
		else
1864
		{
1865
			$monthtext=$outputlangs->transnoentitiesnoconv('Month'.$month);
1866
			$monthtextshort=$outputlangs->transnoentitiesnoconv('MonthShort'.$month);
1867
		}
1868
		//print 'monthtext='.$monthtext.' monthtextshort='.$monthtextshort;
1869
		$ret=str_replace('__b__',$monthtextshort,$ret);
1870
		$ret=str_replace('__B__',$monthtext,$ret);
1871
		//print 'x'.$outputlangs->charset_output.'-'.$ret.'x';
1872
		//return $ret;
1873
	}
1874
	if (preg_match('/__a__/i',$format))
1875
	{
1876
		$timetouse = $time+$offsettz+$offsetdst;	// TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
1877
1878
		$w=adodb_strftime('%w', $timetouse);						// TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
1879
		$dayweek=$outputlangs->transnoentitiesnoconv('Day'.$w);
1880
		$ret=str_replace('__A__',$dayweek,$ret);
1881
		$ret=str_replace('__a__',dol_substr($dayweek,0,3),$ret);
1882
	}
1883
1884
	return $ret;
1885
}
1886
1887
1888
/**
1889
 *	Return an array with locale date info.
1890
 *  PHP getdate is restricted to the years 1901-2038 on Unix and 1970-2038 on Windows
1891
 *  WARNING: This function always use PHP server timezone to return locale informations !!!
1892
 *  Usage must be avoid.
1893
 *  FIXME: Replace this with PHP date function and a parameter $gm
1894
 *
1895
 *	@param	int			$timestamp      Timestamp
1896
 *	@param	boolean		$fast           Fast mode
1897
 *	@return	array						Array of informations
1898
 *										If no fast mode:
1899
 *										'seconds' => $secs,
1900
 *										'minutes' => $min,
1901
 *										'hours' => $hour,
1902
 *										'mday' => $day,
1903
 *										'wday' => $dow,		0=sunday, 6=saturday
1904
 *										'mon' => $month,
1905
 *										'year' => $year,
1906
 *										'yday' => floor($secsInYear/$_day_power),
1907
 *										'weekday' => gmdate('l',$_day_power*(3+$dow)),
1908
 *										'month' => gmdate('F',mktime(0,0,0,$month,2,1971)),
1909
 *										If fast mode:
1910
 *										'seconds' => $secs,
1911
 *										'minutes' => $min,
1912
 *										'hours' => $hour,
1913
 *										'mday' => $day,
1914
 *										'mon' => $month,
1915
 *										'year' => $year,
1916
 *										'yday' => floor($secsInYear/$_day_power),
1917
 *										'leap' => $leaf,
1918
 *										'ndays' => $ndays
1919
 * 	@see 								dol_print_date, dol_stringtotime, dol_mktime
1920
 */
1921
function dol_getdate($timestamp,$fast=false)
1922
{
1923
	global $conf;
1924
1925
	$usealternatemethod=false;
1926
	if ($timestamp <= 0) $usealternatemethod=true;				// <= 1970
1927
	if ($timestamp >= 2145913200) $usealternatemethod=true;		// >= 2038
1928
1929
	if ($usealternatemethod)
1930
	{
1931
		$arrayinfo=adodb_getdate($timestamp,$fast);
1932
	}
1933
	else
1934
	{
1935
		$arrayinfo=getdate($timestamp);
1936
	}
1937
1938
	return $arrayinfo;
1939
}
1940
1941
/**
1942
 *	Return a timestamp date built from detailed informations (by default a local PHP server timestamp)
1943
 * 	Replace function mktime not available under Windows if year < 1970
1944
 *	PHP mktime is restricted to the years 1901-2038 on Unix and 1970-2038 on Windows
1945
 *
1946
 * 	@param	int			$hour			Hour	(can be -1 for undefined)
1947
 *	@param	int			$minute			Minute	(can be -1 for undefined)
1948
 *	@param	int			$second			Second	(can be -1 for undefined)
1949
 *	@param	int			$month			Month (1 to 12)
1950
 *	@param	int			$day			Day (1 to 31)
1951
 *	@param	int			$year			Year
1952
 *	@param	mixed		$gm				True or 1 or 'gmt'=Input informations are GMT values
1953
 *										False or 0 or 'server' = local to server TZ
1954
 *										'user' = local to user TZ
1955
 *										'tz,TimeZone' = use specified timezone
1956
 *	@param	int			$check			0=No check on parameters (Can use day 32, etc...)
1957
 *	@return	int|string					Date as a timestamp, '' or false if error
1958
 * 	@see 								dol_print_date, dol_stringtotime, dol_getdate
1959
 */
1960
function dol_mktime($hour,$minute,$second,$month,$day,$year,$gm=false,$check=1)
1961
{
1962
	global $conf;
1963
	//print "- ".$hour.",".$minute.",".$second.",".$month.",".$day.",".$year.",".$_SERVER["WINDIR"]." -";
1964
1965
	// Clean parameters
1966
	if ($hour   == -1 || empty($hour)) $hour=0;
1967
	if ($minute == -1 || empty($minute)) $minute=0;
1968
	if ($second == -1 || empty($second)) $second=0;
1969
1970
	// Check parameters
1971
	if ($check)
1972
	{
1973
		if (! $month || ! $day)  return '';
1974
		if ($day   > 31) return '';
1975
		if ($month > 12) return '';
1976
		if ($hour  < 0 || $hour   > 24) return '';
1977
		if ($minute< 0 || $minute > 60) return '';
1978
		if ($second< 0 || $second > 60) return '';
1979
	}
1980
1981
	if (method_exists('DateTime','getTimestamp'))
1982
	{
1983
		if (empty($gm) || $gm === 'server')
1984
		{
1985
			$default_timezone=@date_default_timezone_get();		// Example 'Europe/Berlin'
1986
			$localtz = new DateTimeZone($default_timezone);
1987
		}
1988
		else if ($gm === 'user')
1989
		{
1990
			// We use dol_tz_string first because it is more reliable.
1991
			$default_timezone=(empty($_SESSION["dol_tz_string"])?@date_default_timezone_get():$_SESSION["dol_tz_string"]);		// Example 'Europe/Berlin'
1992
			try {
1993
				$localtz = new DateTimeZone($default_timezone);
1994
			}
1995
			catch(Exception $e)
1996
			{
1997
				dol_syslog("Warning dol_tz_string contains an invalid value ".$_SESSION["dol_tz_string"], LOG_WARNING);
1998
				$default_timezone=@date_default_timezone_get();
1999
			}
2000
		}
2001
		else if (strrpos($gm, "tz,") !== false)
2002
		{
2003
			$timezone=str_replace("tz,", "", $gm);  // Example 'tz,Europe/Berlin'
2004
			try
2005
			{
2006
				$localtz = new DateTimeZone($timezone);
2007
			}
2008
			catch(Exception $e)
2009
			{
2010
				dol_syslog("Warning passed timezone contains an invalid value ".$timezone, LOG_WARNING);
2011
			}
2012
		}
2013
2014
		if (empty($localtz)) {
2015
			$localtz = new DateTimeZone('UTC');
2016
		}
2017
		//var_dump($localtz);
2018
		//var_dump($year.'-'.$month.'-'.$day.'-'.$hour.'-'.$minute);
2019
		$dt = new DateTime(null,$localtz);
2020
		$dt->setDate($year,$month,$day);
2021
		$dt->setTime((int) $hour, (int) $minute, (int) $second);
2022
		$date=$dt->getTimestamp();	// should include daylight saving time
2023
		//var_dump($date);
2024
		return $date;
2025
	}
2026
	else
2027
	{
2028
		dol_print_error('','PHP version must be 5.4+');
2029
		return '';
2030
	}
2031
}
2032
2033
2034
/**
2035
 *	Return date for now. In most cases, we use this function without parameters (that means GMT time).
2036
 *
2037
 * 	@param	string		$mode	'gmt' => we return GMT timestamp,
2038
 * 								'tzserver' => we add the PHP server timezone
2039
 *  							'tzref' => we add the company timezone
2040
 * 								'tzuser' => we add the user timezone
2041
 *	@return int   $date	Timestamp
2042
 */
2043
function dol_now($mode='gmt')
2044
{
2045
	$ret=0;
2046
2047
	// Note that gmmktime and mktime return same value (GMT) when used without parameters
2048
	//if ($mode == 'gmt') $ret=gmmktime(); // Strict Standards: gmmktime(): You should be using the time() function instead
2049
	if ($mode == 'gmt') $ret=time();	// Time for now at greenwich.
2050
	else if ($mode == 'tzserver')		// Time for now with PHP server timezone added
2051
	{
2052
		require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
2053
		$tzsecond=getServerTimeZoneInt('now');    // Contains tz+dayling saving time
2054
		$ret=(int) (dol_now('gmt')+($tzsecond*3600));
2055
	}
2056
	/*else if ($mode == 'tzref')				// Time for now with parent company timezone is added
2057
	{
2058
		require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
2059
		$tzsecond=getParentCompanyTimeZoneInt();    // Contains tz+dayling saving time
2060
		$ret=dol_now('gmt')+($tzsecond*3600);
2061
	}*/
2062
	else if ($mode == 'tzuser')				// Time for now with user timezone added
2063
	{
2064
		//print 'time: '.time().'-'.mktime().'-'.gmmktime();
2065
		$offsettz=(empty($_SESSION['dol_tz'])?0:$_SESSION['dol_tz'])*60*60;
2066
		$offsetdst=(empty($_SESSION['dol_dst'])?0:$_SESSION['dol_dst'])*60*60;
2067
		$ret=(int) (dol_now('gmt')+($offsettz+$offsetdst));
2068
	}
2069
2070
	return $ret;
2071
}
2072
2073
2074
/**
2075
 * Return string with formated size
2076
 *
2077
 * @param	int		$size		Size to print
2078
 * @param	int		$shortvalue	Tell if we want long value to use another unit (Ex: 1.5Kb instead of 1500b)
2079
 * @param	int		$shortunit	Use short label of size unit (for example 'b' instead of 'bytes')
2080
 * @return	string				Link
2081
 */
2082
function dol_print_size($size,$shortvalue=0,$shortunit=0)
2083
{
2084
	global $conf,$langs;
2085
	$level=1024;
2086
2087
	if (! empty($conf->dol_optimize_smallscreen)) $shortunit=1;
2088
2089
	// Set value text
2090
	if (empty($shortvalue) || $size < ($level*10))
2091
	{
2092
		$ret=$size;
2093
		$textunitshort=$langs->trans("b");
2094
		$textunitlong=$langs->trans("Bytes");
2095
	}
2096
	else
2097
	{
2098
		$ret=round($size/$level,0);
2099
		$textunitshort=$langs->trans("Kb");
2100
		$textunitlong=$langs->trans("KiloBytes");
2101
	}
2102
	// Use long or short text unit
2103
	if (empty($shortunit)) { $ret.=' '.$textunitlong; }
2104
	else { $ret.=' '.$textunitshort; }
2105
2106
	return $ret;
2107
}
2108
2109
/**
2110
 * Show Url link
2111
 *
2112
 * @param	string		$url		Url to show
2113
 * @param	string		$target		Target for link
2114
 * @param	int			$max		Max number of characters to show
2115
 * @param	int			$withpicto	With picto
2116
 * @return	string					HTML Link
2117
 */
2118
function dol_print_url($url,$target='_blank',$max=32,$withpicto=0)
2119
{
2120
	global $langs;
2121
2122
	if (empty($url)) return '';
2123
2124
	$link='<a href="';
2125
	if (! preg_match('/^http/i',$url)) $link.='http://';
2126
	$link.=$url;
2127
	$link.='"';
2128
	if ($target) $link.=' target="'.$target.'"';
2129
	$link.='>';
2130
	if (! preg_match('/^http/i',$url)) $link.='http://';
2131
	$link.=dol_trunc($url,$max);
2132
	$link.='</a>';
2133
	return '<div class="nospan float" style="margin-right: 10px">'.($withpicto?img_picto($langs->trans("Url"), 'object_globe.png').' ':'').$link.'</div>';
2134
}
2135
2136
/**
2137
 * Show EMail link
2138
 *
2139
 * @param	string		$email			EMail to show (only email, without 'Name of recipient' before)
2140
 * @param 	int			$cid 			Id of contact if known
2141
 * @param 	int			$socid 			Id of third party if known
2142
 * @param 	int			$addlink		0=no link, 1=email has a html email link (+ link to create action if constant AGENDA_ADDACTIONFOREMAIL is on)
2143
 * @param	int			$max			Max number of characters to show
2144
 * @param	int			$showinvalid	Show warning if syntax email is wrong
2145
 * @param	int			$withpicto		Show picto
2146
 * @return	string						HTML Link
2147
 */
2148
function dol_print_email($email,$cid=0,$socid=0,$addlink=0,$max=64,$showinvalid=1,$withpicto=0)
2149
{
2150
	global $conf,$user,$langs,$hookmanager;
2151
2152
	$newemail=$email;
2153
2154
	if (empty($email)) return '&nbsp;';
2155
2156
	if (! empty($addlink))
2157
	{
2158
		$newemail='<a style="text-overflow: ellipsis;" href="';
2159
		if (! preg_match('/^mailto:/i',$email)) $newemail.='mailto:';
2160
		$newemail.=$email;
2161
		$newemail.='">';
2162
		$newemail.=dol_trunc($email,$max);
2163
		$newemail.='</a>';
2164
		if ($showinvalid && ! isValidEmail($email))
2165
		{
2166
			$langs->load("errors");
2167
			$newemail.=img_warning($langs->trans("ErrorBadEMail",$email));
2168
		}
2169
2170
		if (($cid || $socid) && ! empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create)
2171
		{
2172
			$type='AC_EMAIL'; $link='';
2173
			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>';
2174
			if ($link) $newemail='<div>'.$newemail.' '.$link.'</div>';
2175
		}
2176
	}
2177
	else
2178
	{
2179
		if ($showinvalid && ! isValidEmail($email))
2180
		{
2181
			$langs->load("errors");
2182
			$newemail.=img_warning($langs->trans("ErrorBadEMail",$email));
2183
		}
2184
	}
2185
2186
	$rep = '<div class="nospan float" style="margin-right: 10px">'.($withpicto?img_picto($langs->trans("EMail"), 'object_email.png').' ':'').$newemail.'</div>';
2187
	if ($hookmanager) {
2188
		$parameters = array('cid' => $cid, 'socid' => $socid,'addlink' => $addlink, 'picto' => $withpicto);
2189
		$reshook = $hookmanager->executeHooks('printEmail', $parameters, $email);
2190
		$rep.=$hookmanager->resPrint;
2191
	}
2192
2193
	return $rep;
2194
}
2195
2196
/**
2197
 * Show social network link
2198
 *
2199
 * @param	string		$value			Skype to show (only skype, without 'Name of recipient' before)
2200
 * @param	int 		$cid 			Id of contact if known
2201
 * @param	int 		$socid 			Id of third party if known
2202
 * @param	string 		$type			'skype','facebook',...
2203
 * @return	string						HTML Link
2204
 */
2205
function dol_print_socialnetworks($value,$cid,$socid,$type)
2206
{
2207
	global $conf,$user,$langs;
2208
2209
	$newskype=$value;
2210
2211
	if (empty($value)) return '&nbsp;';
2212
2213
	if (! empty($type))
2214
	{
2215
		$newskype ='<div class="divsocialnetwork inline-block valignmiddle">';
2216
		$newskype.=img_picto($langs->trans(strtoupper($type)), $type.'.png', '', false, 0, 0, '', 'paddingright');
2217
		$newskype.=$value;
2218
		if ($type == 'skype')
2219
		{
2220
			$newskype.= '&nbsp;';
2221
			$newskype.='<a href="skype:';
2222
			$newskype.=$value;
2223
			$newskype.='?call" alt="'.$langs->trans("Call").'&nbsp;'.$value.'" title="'.$langs->trans("Call").'&nbsp;'.$value.'">';
2224
			$newskype.='<img src="'.DOL_URL_ROOT.'/theme/common/skype_callbutton.png" border="0">';
2225
			$newskype.='</a><a href="skype:';
2226
			$newskype.=$value;
2227
			$newskype.='?chat" alt="'.$langs->trans("Chat").'&nbsp;'.$value.'" title="'.$langs->trans("Chat").'&nbsp;'.$value.'">';
2228
			$newskype.='<img class="paddingleft" src="'.DOL_URL_ROOT.'/theme/common/skype_chatbutton.png" border="0">';
2229
			$newskype.='</a>';
2230
		}
2231
		if (($cid || $socid) && ! empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create && $type=='skype')
2232
		{
2233
			$addlink='AC_SKYPE'; $link='';
2234
			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>';
2235
			$newskype.=($link?' '.$link:'');
2236
		}
2237
		$newskype.='</div>';
2238
	}
2239
	else
2240
	{
2241
		$langs->load("errors");
2242
		$newskype.=img_warning($langs->trans("ErrorBadSocialNetworkValue",$value));
2243
	}
2244
	return $newskype;
2245
}
2246
2247
/**
2248
 * 	Format phone numbers according to country
2249
 *
2250
 * 	@param  string  $phone          Phone number to format
2251
 * 	@param  string  $countrycode    Country code to use for formatting
2252
 * 	@param 	int		$cid 		    Id of contact if known
2253
 * 	@param 	int		$socid          Id of third party if known
2254
 * 	@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)
2255
 * 	@param 	string	$separ 		    Separation between numbers for a better visibility example : xx.xx.xx.xx.xx
2256
 *  @param	string  $withpicto      Show picto
2257
 *  @param	string	$titlealt	    Text to show on alt
2258
 *  @param  int     $adddivfloat    Add div float around phone.
2259
 * 	@return string 				    Formated phone number
2260
 */
2261
function dol_print_phone($phone,$countrycode='',$cid=0,$socid=0,$addlink='',$separ="&nbsp;",$withpicto='',$titlealt='',$adddivfloat=0)
2262
{
2263
	global $conf, $user, $langs, $mysoc, $hookmanager;
2264
2265
	// Clean phone parameter
2266
	$phone = preg_replace("/[\s.-]/","",trim($phone));
2267
	if (empty($phone)) { return ''; }
2268
	if (empty($countrycode)) $countrycode=$mysoc->country_code;
2269
2270
	// Short format for small screens
2271
	if ($conf->dol_optimize_smallscreen) $separ='';
2272
2273
	$newphone=$phone;
2274
	if (strtoupper($countrycode) == "FR")
2275
	{
2276
		// France
2277
		if (dol_strlen($phone) == 10) {
2278
			$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);
2279
		}
2280
		elseif (dol_strlen($phone) == 7)
2281
		{
2282
			$newphone=substr($newphone,0,3).$separ.substr($newphone,3,2).$separ.substr($newphone,5,2);
2283
		}
2284
		elseif (dol_strlen($phone) == 9)
2285
		{
2286
			$newphone=substr($newphone,0,2).$separ.substr($newphone,2,3).$separ.substr($newphone,5,2).$separ.substr($newphone,7,2);
2287
		}
2288
		elseif (dol_strlen($phone) == 11)
2289
		{
2290
			$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);
2291
		}
2292
		elseif (dol_strlen($phone) == 12)
2293
		{
2294
			$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);
2295
		}
2296
	}
2297
2298
	elseif (strtoupper($countrycode) == "CA")
2299
	{
2300
		if (dol_strlen($phone) == 10) {
2301
			$newphone=($separ!=''?'(':'').substr($newphone,0,3).($separ!=''?')':'').$separ.substr($newphone,3,3).($separ!=''?'-':'').substr($newphone,6,4);
2302
		}
2303
	}
2304
	elseif (strtoupper($countrycode) == "PT" )
2305
	{//Portugal
2306
		if (dol_strlen($phone) == 13)
2307
		{//ex: +351_ABC_DEF_GHI
2308
			$newphone= substr($newphone,0,4).$separ.substr($newphone,4,3).$separ.substr($newphone,7,3).$separ.substr($newphone,10,3);
2309
		}
2310
	}
2311
	elseif (strtoupper($countrycode) == "SR" )
2312
	{//Suriname
2313
		if (dol_strlen($phone) == 10)
2314
		{//ex: +597_ABC_DEF
2315
			$newphone= substr($newphone,0,4).$separ.substr($newphone,4,3).$separ.substr($newphone,7,3);
2316
		}
2317
		elseif (dol_strlen($phone) == 11)
2318
		{//ex: +597_ABC_DEFG
2319
			$newphone= substr($newphone,0,4).$separ.substr($newphone,4,3).$separ.substr($newphone,7,4);
2320
		}
2321
	}
2322
	elseif (strtoupper($countrycode) == "DE" )
2323
	{//Allemagne
2324
		if (dol_strlen($phone) == 14)
2325
		{//ex:  +49_ABCD_EFGH_IJK
2326
			$newphone= substr($newphone,0,3).$separ.substr($newphone,3,4).$separ.substr($newphone,7,4).$separ.substr($newphone,11,3);
2327
		}
2328
		elseif (dol_strlen($phone) == 13)
2329
		{//ex: +49_ABC_DEFG_HIJ
2330
			$newphone= substr($newphone,0,3).$separ.substr($newphone,3,3).$separ.substr($newphone,6,4).$separ.substr($newphone,10,3);
2331
		}
2332
	}
2333
	elseif (strtoupper($countrycode) == "ES")
2334
	{//Espagne
2335
		if (dol_strlen($phone) == 12)
2336
		{//ex:  +34_ABC_DEF_GHI
2337
			$newphone= substr($newphone,0,3).$separ.substr($newphone,3,3).$separ.substr($newphone,6,3).$separ.substr($newphone,9,3);
2338
		}
2339
	}
2340
	elseif (strtoupper($countrycode) == "BF")
2341
	{// Burkina Faso
2342
		if (dol_strlen($phone) == 12)
2343
		{//ex :  +22 A BC_DE_FG_HI
2344
			$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);
2345
		}
2346
	}
2347
	elseif (strtoupper($countrycode) == "RO")
2348
	{// Roumanie
2349
		if (dol_strlen($phone) == 12)
2350
		{//ex :  +40 AB_CDE_FG_HI
2351
			$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);
2352
		}
2353
	}
2354
	elseif (strtoupper($countrycode) == "TR")
2355
	{//Turquie
2356
		if (dol_strlen($phone) == 13)
2357
		{//ex :  +90 ABC_DEF_GHIJ
2358
			$newphone= substr($newphone,0,3).$separ.substr($newphone,3,3).$separ.substr($newphone,6,3).$separ.substr($newphone,9,4);
2359
		}
2360
	}
2361
	elseif (strtoupper($countrycode) == "US")
2362
	{//Etat-Unis
2363
		if (dol_strlen($phone) == 12)
2364
		{//ex: +1 ABC_DEF_GHIJ
2365
			$newphone= substr($newphone,0,2).$separ.substr($newphone,2,3).$separ.substr($newphone,5,3).$separ.substr($newphone,8,4);
2366
		}
2367
	}
2368
	elseif (strtoupper($countrycode) == "MX")
2369
	{//Mexique
2370
		if (dol_strlen($phone) == 12)
2371
		{//ex: +52 ABCD_EFG_HI
2372
			$newphone = substr($newphone,0,3).$separ.substr($newphone,3,4).$separ.substr($newphone,7,3).$separ.substr($newphone,10,2);
2373
		}
2374
		elseif (dol_strlen($phone) == 11)
2375
		{//ex: +52 AB_CD_EF_GH
2376
			$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);
2377
		}
2378
		elseif (dol_strlen($phone) == 13)
2379
		{//ex: +52 ABC_DEF_GHIJ
2380
			$newphone = substr($newphone,0,3).$separ.substr($newphone,3,3).$separ.substr($newphone,6,3).$separ.substr($newphone,9,4);
2381
		}
2382
	}
2383
	elseif (strtoupper($countrycode) == "ML")
2384
	{//Mali
2385
		if(dol_strlen($phone) == 12)
2386
		{//ex: +223 AB_CD_EF_GH
2387
			$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);
2388
		}
2389
	}
2390
	elseif (strtoupper($countrycode) == "TH")
2391
	{//Thaïlande
2392
		if(dol_strlen($phone) == 11)
2393
		{//ex: +66_ABC_DE_FGH
2394
			$newphone = substr($newphone,0,3).$separ.substr($newphone,3,3).$separ.substr($newphone,6,2).$separ.substr($newphone,8,3);
2395
		}
2396
		elseif(dol_strlen($phone) == 12)
2397
		{//ex: +66_A_BCD_EF_GHI
2398
			$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);
2399
		}
2400
		}
2401
	elseif (strtoupper($countrycode) == "MU")
2402
	{//Maurice
2403
		if(dol_strlen($phone) == 11)
2404
		{//ex: +230_ABC_DE_FG
2405
			$newphone = substr($newphone,0,4).$separ.substr($newphone,4,3).$separ.substr($newphone,7,2).$separ.substr($newphone,9,2);
2406
		}
2407
		elseif(dol_strlen($phone) == 12)
2408
		{//ex: +230_ABCD_EF_GH
2409
			$newphone = substr($newphone,0,4).$separ.substr($newphone,4,4).$separ.substr($newphone,8,2).$separ.substr($newphone,10,2);
2410
		}
2411
	}
2412
	elseif (strtoupper($countrycode) == "ZA")
2413
	{//Afrique du sud
2414
		if(dol_strlen($phone) == 12)
2415
		{//ex: +27_AB_CDE_FG_HI
2416
			$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);
2417
		}
2418
	}
2419
	elseif (strtoupper($countrycode) == "SY")
2420
	{//Syrie
2421
		if(dol_strlen($phone) == 12)
2422
		{//ex: +963_AB_CD_EF_GH
2423
			$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);
2424
		}
2425
		elseif(dol_strlen($phone) == 13)
2426
		{//ex: +963_AB_CD_EF_GHI
2427
			$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);
2428
		}
2429
	}
2430
	elseif (strtoupper($countrycode) == "AE")
2431
	{//Emirats Arabes Unis
2432
		if(dol_strlen($phone) == 12)
2433
		{//ex: +971_ABC_DEF_GH
2434
			$newphone = substr($newphone,0,4).$separ.substr($newphone,4,3).$separ.substr($newphone,7,3).$separ.substr($newphone,10,2);
2435
		}
2436
		elseif(dol_strlen($phone) == 13)
2437
		{//ex: +971_ABC_DEF_GHI
2438
			$newphone = substr($newphone,0,4).$separ.substr($newphone,4,3).$separ.substr($newphone,7,3).$separ.substr($newphone,10,3);
2439
		}
2440
		elseif(dol_strlen($phone) == 14)
2441
		{//ex: +971_ABC_DEF_GHIK
2442
			$newphone = substr($newphone,0,4).$separ.substr($newphone,4,3).$separ.substr($newphone,7,3).$separ.substr($newphone,10,4);
2443
		}
2444
	}
2445
	elseif (strtoupper($countrycode) == "DZ")
2446
	{//Algérie
2447
		if(dol_strlen($phone) == 13)
2448
		{//ex: +213_ABC_DEF_GHI
2449
			$newphone = substr($newphone,0,4).$separ.substr($newphone,4,3).$separ.substr($newphone,7,3).$separ.substr($newphone,10,3);
2450
		}
2451
	}
2452
	elseif (strtoupper($countrycode) == "BE")
2453
	{//Belgique
2454
		if(dol_strlen($phone) == 11)
2455
		{//ex: +32_ABC_DE_FGH
2456
			$newphone = substr($newphone,0,3).$separ.substr($newphone,3,3).$separ.substr($newphone,6,2).$separ.substr($newphone,8,3);
2457
		}
2458
		elseif(dol_strlen($phone) == 12)
2459
		{//ex: +32_ABC_DEF_GHI
2460
			$newphone = substr($newphone,0,3).$separ.substr($newphone,3,3).$separ.substr($newphone,6,3).$separ.substr($newphone,9,3);
2461
		}
2462
	}
2463
	elseif (strtoupper($countrycode) == "PF")
2464
	{//Polynésie française
2465
		if(dol_strlen($phone) == 12)
2466
		{//ex: +689_AB_CD_EF_GH
2467
			$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);
2468
		}
2469
	}
2470
	elseif (strtoupper($countrycode) == "CO")
2471
	{//Colombie
2472
		if(dol_strlen($phone) == 13)
2473
		{//ex: +57_ABC_DEF_GH_IJ
2474
			$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);
2475
		}
2476
	}
2477
	elseif (strtoupper($countrycode) == "JO")
2478
	{//Jordanie
2479
		if(dol_strlen($phone) == 12)
2480
		{//ex: +962_A_BCD_EF_GH
2481
			$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);
2482
		}
2483
	}
2484
	elseif (strtoupper($countrycode) == "MG")
2485
	{//Madagascar
2486
		if(dol_strlen($phone) == 13)
2487
		{//ex: +261_AB_CD_EF_GHI
2488
			$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);
2489
		}
2490
	}
2491
	elseif (strtoupper($countrycode) == "GB")
2492
	{//Royaume uni
2493
		if(dol_strlen($phone) == 13)
2494
		{//ex: +44_ABCD_EFG_HIJ
2495
			$newphone = substr($newphone,0,3).$separ.substr($newphone,3,4).$separ.substr($newphone,7,3).$separ.substr($newphone,10,3);
2496
		}
2497
	}
2498
	elseif (strtoupper($countrycode) == "CH")
2499
	{//Suisse
2500
		if(dol_strlen($phone) == 12)
2501
		{//ex: +41_AB_CDE_FG_HI
2502
			$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);
2503
		}
2504
		elseif(dol_strlen($phone) == 15)
2505
		{// +41_AB_CDE_FGH_IJKL
2506
			$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);
2507
		}
2508
	}
2509
	elseif (strtoupper($countrycode) == "TN")
2510
	{//Tunisie
2511
		if(dol_strlen($phone) == 12)
2512
		{//ex: +216_AB_CDE_FGH
2513
			$newphone = substr($newphone,0,4).$separ.substr($newphone,4,2).$separ.substr($newphone,6,3).$separ.substr($newphone,9,3);
2514
		}
2515
	}
2516
	elseif (strtoupper($countrycode) == "GF")
2517
	{//Guyane francaise
2518
		if(dol_strlen($phone) == 13)
2519
		{//ex: +594_ABC_DE_FG_HI  (ABC=594 de nouveau)
2520
			$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);
2521
		}
2522
	}
2523
	elseif (strtoupper($countrycode) == "GP")
2524
	{//Guadeloupe
2525
		if(dol_strlen($phone) == 13)
2526
		{//ex: +590_ABC_DE_FG_HI  (ABC=590 de nouveau)
2527
			$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);
2528
		}
2529
	}
2530
	elseif (strtoupper($countrycode) == "MQ")
2531
	{//Martinique
2532
		if(dol_strlen($phone) == 13)
2533
		{//ex: +596_ABC_DE_FG_HI  (ABC=596 de nouveau)
2534
			$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);
2535
		}
2536
	}
2537
	elseif (strtoupper($countrycode) == "IT")
2538
	{//Italie
2539
		if(dol_strlen($phone) == 12)
2540
		{//ex: +39_ABC_DEF_GHI
2541
			$newphone = substr($newphone,0,3).$separ.substr($newphone,3,3).$separ.substr($newphone,6,3).$separ.substr($newphone,9,3);
2542
		}
2543
		elseif(dol_strlen($phone) == 13)
2544
		{//ex: +39_ABC_DEF_GH_IJ
2545
			$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);
2546
		}
2547
	}
2548
	elseif(strtoupper($countrycode) == "AU")
2549
	{//Australie
2550
		 if(dol_strlen($phone) == 12)
2551
		{//ex: +61_A_BCDE_FGHI
2552
			$newphone = substr($newphone,0,3).$separ.substr($newphone,3,1).$separ.substr($newphone,4,4).$separ.substr($newphone,8,4);
2553
		}
2554
	}
2555
	if (! empty($addlink))	// Link on phone number (+ link to add action if conf->global->AGENDA_ADDACTIONFORPHONE set)
2556
	{
2557
		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
2558
		{
2559
			$newphone ='<a href="tel:'.$phone.'"';
2560
			$newphone.='>'.$phone.'</a>';
2561
		}
2562
		else if (! empty($conf->clicktodial->enabled) && $addlink == 'AC_TEL')		// If click to dial, we use click to dial url
2563
		{
2564
			if (empty($user->clicktodial_loaded)) $user->fetch_clicktodial();
2565
2566
			// Define urlmask
2567
			$urlmask='ErrorClickToDialModuleNotConfigured';
2568
			if (! empty($conf->global->CLICKTODIAL_URL)) $urlmask=$conf->global->CLICKTODIAL_URL;
2569
			if (! empty($user->clicktodial_url)) $urlmask=$user->clicktodial_url;
2570
2571
			$clicktodial_poste=(! empty($user->clicktodial_poste)?urlencode($user->clicktodial_poste):'');
2572
			$clicktodial_login=(! empty($user->clicktodial_login)?urlencode($user->clicktodial_login):'');
2573
			$clicktodial_password=(! empty($user->clicktodial_password)?urlencode($user->clicktodial_password):'');
2574
			// This line is for backward compatibility
2575
			$url = sprintf($urlmask, urlencode($phone), $clicktodial_poste, $clicktodial_login, $clicktodial_password);
2576
			// Thoose lines are for substitution
2577
			$substitarray=array('__PHONEFROM__'=>$clicktodial_poste,
2578
								'__PHONETO__'=>urlencode($phone),
2579
								'__LOGIN__'=>$clicktodial_login,
2580
								'__PASS__'=>$clicktodial_password);
2581
			$url = make_substitutions($url, $substitarray);
2582
			$newphonesav=$newphone;
2583
			$newphone ='<a href="'.$url.'"';
2584
			if (! empty($conf->global->CLICKTODIAL_FORCENEWTARGET)) $newphone.=' target="_blank"';
2585
			$newphone.='>'.$newphonesav.'</a>';
2586
		}
2587
2588
		//if (($cid || $socid) && ! empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create)
2589
		if (! empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create)
2590
		{
2591
			$type='AC_TEL'; $link='';
2592
			if ($addlink == 'AC_FAX') $type='AC_FAX';
2593
			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>';
2594
			if ($link) $newphone='<div>'.$newphone.' '.$link.'</div>';
2595
		}
2596
	}
2597
2598
	if (empty($titlealt))
2599
	{
2600
		$titlealt=($withpicto=='fax'?$langs->trans("Fax"):$langs->trans("Phone"));
2601
	}
2602
	$rep='';
2603
2604
	if ($hookmanager) {
2605
		$parameters = array('countrycode' => $countrycode, 'cid' => $cid, 'socid' => $socid,'titlealt' => $titlealt, 'picto' => $withpicto);
2606
		$reshook = $hookmanager->executeHooks('printPhone', $parameters, $phone);
2607
		$rep.=$hookmanager->resPrint;
2608
	}
2609
	if (empty($reshook))
2610
	{
2611
		$picto = '';
2612
		if($withpicto){
2613
			if($withpicto=='fax'){
2614
				$picto = 'phoning_fax';
2615
			}elseif($withpicto=='phone'){
2616
				$picto = 'phoning';
2617
			}elseif($withpicto=='mobile'){
2618
				$picto = 'phoning_mobile';
2619
			}else{
2620
				$picto = '';
2621
			}
2622
		}
2623
		if ($adddivfloat) $rep.='<div class="nospan float" style="margin-right: 10px">';
2624
		else $rep.='<span style="margin-right: 10px;">';
2625
		$rep.=($withpicto?img_picto($titlealt, 'object_'.$picto.'.png').' ':'').$newphone;
2626
		if ($adddivfloat) $rep.='</div>';
2627
		else $rep.='</span>';
2628
	  }
2629
2630
	return $rep;
2631
}
2632
2633
/**
2634
 * 	Return an IP formated to be shown on screen
2635
 *
2636
 * 	@param	string	$ip			IP
2637
 * 	@param	int		$mode		0=return IP + country/flag, 1=return only country/flag, 2=return only IP
2638
 * 	@return string 				Formated IP, with country if GeoIP module is enabled
2639
 */
2640
function dol_print_ip($ip,$mode=0)
2641
{
2642
	global $conf,$langs;
2643
2644
	$ret='';
2645
2646
	if (empty($mode)) $ret.=$ip;
2647
2648
	if ($mode != 2)
2649
	{
2650
		$countrycode=dolGetCountryCodeFromIp($ip);
2651
		if ($countrycode)	// If success, countrycode is us, fr, ...
2652
		{
2653
			if (file_exists(DOL_DOCUMENT_ROOT.'/theme/common/flags/'.$countrycode.'.png'))
2654
			{
2655
				$ret.=' '.img_picto($countrycode.' '.$langs->trans("AccordingToGeoIPDatabase"),DOL_URL_ROOT.'/theme/common/flags/'.$countrycode.'.png','',1);
2656
			}
2657
			else $ret.=' ('.$countrycode.')';
2658
		}
2659
	}
2660
2661
	return $ret;
2662
}
2663
2664
/**
2665
 * 	Return a country code from IP. Empty string if not found.
2666
 *
2667
 * 	@param	string	$ip			IP
2668
 * 	@return string 				Country code ('us', 'fr', ...)
2669
 */
2670
function dolGetCountryCodeFromIp($ip)
2671
{
2672
	global $conf;
2673
2674
	$countrycode='';
2675
2676
	if (! empty($conf->geoipmaxmind->enabled))
2677
	{
2678
		$datafile=$conf->global->GEOIPMAXMIND_COUNTRY_DATAFILE;
2679
		//$ip='24.24.24.24';
2680
		//$datafile='E:\Mes Sites\Web\Admin1\awstats\maxmind\GeoIP.dat';    Note that this must be downloaded datafile (not same than datafile provided with ubuntu packages)
2681
2682
		include_once DOL_DOCUMENT_ROOT.'/core/class/dolgeoip.class.php';
2683
		$geoip=new DolGeoIP('country',$datafile);
2684
		//print 'ip='.$ip.' databaseType='.$geoip->gi->databaseType." GEOIP_CITY_EDITION_REV1=".GEOIP_CITY_EDITION_REV1."\n";
2685
		//print "geoip_country_id_by_addr=".geoip_country_id_by_addr($geoip->gi,$ip)."\n";
2686
		$countrycode=$geoip->getCountryCodeFromIP($ip);
2687
	}
2688
2689
	return $countrycode;
2690
}
2691
2692
2693
/**
2694
 *  Return country code for current user.
2695
 *  If software is used inside a local network, detection may fails (we need a public ip)
2696
 *
2697
 *  @return     string      Country code (fr, es, it, us, ...)
2698
 */
2699
function dol_user_country()
2700
{
2701
	global $conf,$langs,$user;
2702
2703
	//$ret=$user->xxx;
2704
	$ret='';
2705
	if (! empty($conf->geoipmaxmind->enabled))
2706
	{
2707
		$ip=$_SERVER["REMOTE_ADDR"];
2708
		$datafile=$conf->global->GEOIPMAXMIND_COUNTRY_DATAFILE;
2709
		//$ip='24.24.24.24';
2710
		//$datafile='E:\Mes Sites\Web\Admin1\awstats\maxmind\GeoIP.dat';
2711
		include_once DOL_DOCUMENT_ROOT.'/core/class/dolgeoip.class.php';
2712
		$geoip=new DolGeoIP('country',$datafile);
2713
		$countrycode=$geoip->getCountryCodeFromIP($ip);
2714
		$ret=$countrycode;
2715
	}
2716
	return $ret;
2717
}
2718
2719
/**
2720
 *  Format address string
2721
 *
2722
 *  @param	string	$address    Address
2723
 *  @param  int		$htmlid     Html ID (for example 'gmap')
2724
 *  @param  int		$mode       thirdparty|contact|member|other
2725
 *  @param  int		$id         Id of object
2726
 *  @param	int		$noprint	No output. Result is the function return
2727
 *  @param  string  $charfornl  Char to use instead of nl2br. '' means we use a standad nl2br.
2728
 *  @return string|void			Nothing if noprint is 0, formatted address if noprint is 1
2729
 *  @see dol_format_address
2730
 */
2731
function dol_print_address($address, $htmlid, $mode, $id, $noprint=0, $charfornl='')
2732
{
2733
	global $conf, $user, $langs, $hookmanager;
2734
2735
	$out = '';
2736
2737
	if ($address)
2738
	{
2739
		if ($hookmanager) {
2740
			$parameters = array('element' => $mode, 'id' => $id);
2741
			$reshook = $hookmanager->executeHooks('printAddress', $parameters, $address);
2742
			$out.=$hookmanager->resPrint;
2743
		}
2744
		if (empty($reshook))
2745
		{
2746
			if (empty($charfornl)) $out.=nl2br($address);
2747
			else $out.=preg_replace('/[\r\n]+/', $charfornl, $address);
2748
2749
			$showgmap=$showomap=0;
2750
2751
			// TODO Add a hook here
2752
			if (($mode=='thirdparty' || $mode =='societe') && ! empty($conf->google->enabled) && ! empty($conf->global->GOOGLE_ENABLE_GMAPS)) $showgmap=1;
2753
			if ($mode=='contact' && ! empty($conf->google->enabled) && ! empty($conf->global->GOOGLE_ENABLE_GMAPS_CONTACTS)) $showgmap=1;
2754
			if ($mode=='member' && ! empty($conf->google->enabled) && ! empty($conf->global->GOOGLE_ENABLE_GMAPS_MEMBERS)) $showgmap=1;
2755
			if (($mode=='thirdparty' || $mode =='societe') && ! empty($conf->openstreetmap->enabled) && ! empty($conf->global->OPENSTREETMAP_ENABLE_MAPS)) $showomap=1;
2756
			if ($mode=='contact' && ! empty($conf->openstreetmap->enabled) && ! empty($conf->global->OPENSTREETMAP_ENABLE_MAPS_CONTACTS)) $showomap=1;
2757
			if ($mode=='member' && ! empty($conf->openstreetmap->enabled) && ! empty($conf->global->OPENSTREETMAP_ENABLE_MAPS_MEMBERS)) $showomap=1;
2758
2759
			if ($showgmap)
2760
			{
2761
				$url=dol_buildpath('/google/gmaps.php?mode='.$mode.'&id='.$id,1);
2762
				$out.=' <a href="'.$url.'" target="_gmaps"><img id="'.$htmlid.'" class="valigntextbottom" src="'.DOL_URL_ROOT.'/theme/common/gmap.png"></a>';
2763
			}
2764
			if ($showomap)
2765
			{
2766
				$url=dol_buildpath('/openstreetmap/maps.php?mode='.$mode.'&id='.$id,1);
2767
				$out.=' <a href="'.$url.'" target="_gmaps"><img id="'.$htmlid.'_openstreetmap" class="valigntextbottom" src="'.DOL_URL_ROOT.'/theme/common/gmap.png"></a>';
2768
			}
2769
		}
2770
	}
2771
	if ($noprint) return $out;
2772
	else print $out;
2773
}
2774
2775
2776
/**
2777
 *	Return true if email syntax is ok
2778
 *
2779
 *	@param	    string		$address    			email (Ex: "[email protected]", "John Do <[email protected]>")
2780
 *  @param		int			$acceptsupervisorkey	If 1, the special string '__SUPERVISOREMAIL__' is also accepted as valid
2781
 *	@return     boolean     						true if email syntax is OK, false if KO or empty string
2782
 */
2783
function isValidEmail($address, $acceptsupervisorkey=0)
2784
{
2785
	if ($acceptsupervisorkey && $address == '__SUPERVISOREMAIL__') return true;
2786
	if (filter_var($address, FILTER_VALIDATE_EMAIL)) return true;
2787
2788
	return false;
2789
}
2790
2791
/**
2792
 *	Return if the domain name has a valid MX record.
2793
 *  WARNING: This need function idn_to_ascii, checkdnsrr and getmxrr
2794
 *
2795
 *	@param	    string		$domain	    			Domain name (Ex: "yahoo.com", "yhaoo.com", "dolibarr.fr")
2796
 *	@return     int     							-1 if error (function not available), 0=Not valid, 1=Valid
2797
 */
2798
function isValidMXRecord($domain)
2799
{
2800
	if (function_exists('idn_to_ascii') && function_exists('checkdnsrr'))
2801
	{
2802
		if (! checkdnsrr(idn_to_ascii($domain), 'MX'))
2803
		{
2804
			return 0;
2805
		}
2806
		if (function_exists('getmxrr'))
2807
		{
2808
			$mxhosts=array();
2809
			$weight=array();
2810
			getmxrr(idn_to_ascii($domain), $mxhosts, $weight);
2811
			if (count($mxhosts) > 1) return 1;
2812
			if (count($mxhosts) == 1 && ! empty($mxhosts[0])) return 1;
2813
2814
			return 0;
2815
		}
2816
	}
2817
	return -1;
2818
}
2819
2820
/**
2821
 *  Return true if phone number syntax is ok
2822
 *  TODO Decide what to do with this
2823
 *
2824
 *  @param	string		$phone		phone (Ex: "0601010101")
2825
 *  @return boolean     			true if phone syntax is OK, false if KO or empty string
2826
 */
2827
function isValidPhone($phone)
2828
{
2829
	return true;
2830
}
2831
2832
2833
/**
2834
 * Make a strlen call. Works even if mbstring module not enabled
2835
 *
2836
 * @param   string		$string				String to calculate length
2837
 * @param   string		$stringencoding		Encoding of string
2838
 * @return  int								Length of string
2839
 */
2840
function dol_strlen($string, $stringencoding='UTF-8')
2841
{
2842
	if (function_exists('mb_strlen')) return mb_strlen($string,$stringencoding);
2843
	else return strlen($string);
2844
}
2845
2846
/**
2847
 * Make a substring. Works even if mbstring module is not enabled for better compatibility.
2848
 *
2849
 * @param	string	$string				String to scan
2850
 * @param	string	$start				Start position
2851
 * @param	int		$length				Length (in nb of characters or nb of bytes depending on trunconbytes param)
2852
 * @param   string	$stringencoding		Page code used for input string encoding
2853
 * @param	int		$trunconbytes		1=Length is max of bytes instead of max of characters
2854
 * @return  string						substring
2855
 */
2856
function dol_substr($string, $start, $length, $stringencoding='', $trunconbytes=0)
2857
{
2858
	global $langs;
2859
2860
	if (empty($stringencoding)) $stringencoding=$langs->charset_output;
2861
2862
	$ret='';
2863
	if (empty($trunconbytes))
2864
	{
2865
		if (function_exists('mb_substr'))
2866
		{
2867
			$ret=mb_substr($string, $start, $length, $stringencoding);
2868
		}
2869
		else
2870
		{
2871
			$ret=substr($string, $start, $length);
2872
		}
2873
	}
2874
	else
2875
	{
2876
		if (function_exists('mb_strcut'))
2877
		{
2878
			$ret=mb_strcut($string, $start, $length, $stringencoding);
2879
		}
2880
		else
2881
		{
2882
			$ret=substr($string, $start, $length);
2883
		}
2884
	}
2885
	return $ret;
2886
}
2887
2888
2889
/**
2890
 *  Show a javascript graph.
2891
 *  Do not use this function anymore. Use DolGraph class instead.
2892
 *
2893
 *  @param		string	$htmlid			Html id name
2894
 *  @param		int		$width			Width in pixel
2895
 *  @param		int		$height			Height in pixel
2896
 *  @param		array	$data			Data array
2897
 *  @param		int		$showlegend		1 to show legend, 0 otherwise
2898
 *  @param		string	$type			Type of graph ('pie', 'barline')
2899
 *  @param		int		$showpercent	Show percent (with type='pie' only)
2900
 *  @param		string	$url			Param to add an url to click values
2901
 *  @param		int		$combineother	0=No combine, 0.05=Combine if lower than 5%
2902
 *  @param      int     $shownographyet Show graph to say there is not enough data
2903
 *  @return		void
2904
 *  @deprecated
2905
 *  @see DolGraph
2906
 */
2907
function dol_print_graph($htmlid,$width,$height,$data,$showlegend=0,$type='pie',$showpercent=0,$url='',$combineother=0.05,$shownographyet=0)
2908
{
2909
	dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);
2910
2911
	global $conf,$langs;
2912
	global $theme_datacolor;    // To have var kept when function is called several times
2913
2914
	if ($shownographyet)
2915
	{
2916
		print '<div class="nographyet" style="width:'.$width.'px;height:'.$height.'px;"></div>';
2917
		print '<div class="nographyettext">'.$langs->trans("NotEnoughDataYet").'</div>';
2918
		return;
2919
	}
2920
2921
	if (empty($conf->use_javascript_ajax)) return;
2922
	$jsgraphlib='flot';
2923
	$datacolor=array();
2924
2925
	// Load colors of theme into $datacolor array
2926
	$color_file = DOL_DOCUMENT_ROOT."/theme/".$conf->theme."/graph-color.php";
2927
	if (is_readable($color_file))
2928
	{
2929
		include_once $color_file;
2930
		if (isset($theme_datacolor))
2931
		{
2932
			$datacolor=array();
2933
			foreach($theme_datacolor as $val)
2934
			{
2935
				$datacolor[]="#".sprintf("%02x",$val[0]).sprintf("%02x",$val[1]).sprintf("%02x",$val[2]);
2936
			}
2937
		}
2938
	}
2939
	print '<div id="'.$htmlid.'" style="width:'.$width.'px;height:'.$height.'px;"></div>';
2940
2941
	// We use Flot js lib
2942
	if ($jsgraphlib == 'flot')
2943
	{
2944
		if ($type == 'pie')
2945
		{
2946
			// data is   array('series'=>array(serie1,serie2,...),
2947
			//                 'seriestype'=>array('bar','line',...),
2948
			//                 'seriescolor'=>array(0=>'#999999',1=>'#999999',...)
2949
			//                 'xlabel'=>array(0=>labelx1,1=>labelx2,...));
2950
			// serieX is array('label'=>'label', data=>val)
2951
			print '
2952
			<script type="text/javascript">
2953
			$(function () {
2954
				var data = '.json_encode($data['series']).';
2955
2956
				function plotWithOptions() {
2957
					$.plot($("#'.$htmlid.'"), data,
2958
					{
2959
						series: {
2960
							pie: {
2961
								show: true,
2962
								radius: 0.8,';
2963
			if ($combineother)
2964
			{
2965
				print '
2966
								combine: {
2967
								 	threshold: '.$combineother.'
2968
								},';
2969
			}
2970
			print '
2971
								label: {
2972
									show: true,
2973
									radius: 0.9,
2974
									formatter: function(label, series) {
2975
										var percent=Math.round(series.percent);
2976
										var number=series.data[0][1];
2977
										return \'';
2978
										print '<div style="font-size:8pt;text-align:center;padding:2px;color:black;">';
2979
										if ($url) print '<a style="color: #FFFFFF;" border="0" href="'.$url.'">';
2980
										print '\'+'.($showlegend?'number':'label+\' \'+number');
2981
										if (! empty($showpercent)) print '+\'<br/>\'+percent+\'%\'';
2982
										print '+\'';
2983
										if ($url) print '</a>';
2984
										print '</div>\';
2985
									},
2986
									background: {
2987
										opacity: 0.0,
2988
										color: \'#000000\'
2989
									},
2990
								}
2991
							}
2992
						},
2993
						zoom: {
2994
							interactive: true
2995
						},
2996
						pan: {
2997
							interactive: true
2998
						},';
2999
						if (count($datacolor))
3000
						{
3001
							print 'colors: '.(! empty($data['seriescolor']) ? json_encode($data['seriescolor']) : json_encode($datacolor)).',';
3002
						}
3003
						print 'legend: {show: '.($showlegend?'true':'false').', position: \'ne\' }
3004
					});
3005
				}
3006
				plotWithOptions();
3007
			});
3008
			</script>';
3009
		}
3010
		else if ($type == 'barline')
3011
		{
3012
			// data is   array('series'=>array(serie1,serie2,...),
3013
			//                 'seriestype'=>array('bar','line',...),
3014
			//                 'seriescolor'=>array(0=>'#999999',1=>'#999999',...)
3015
			//                 'xlabel'=>array(0=>labelx1,1=>labelx2,...));
3016
			// serieX is array('label'=>'label', data=>array(0=>y1,1=>y2,...)) with same nb of value than into xlabel
3017
			print '
3018
			<script type="text/javascript">
3019
			$(function () {
3020
				var data = [';
3021
				$i=0; $outputserie=0;
3022
				foreach($data['series'] as $serie)
3023
				{
3024
					if ($data['seriestype'][$i]=='line') { $i++; continue; };
3025
					if ($outputserie > 0) print ',';
3026
					print '{ bars: { stack: 0, show: true, barWidth: 0.9, align: \'center\' }, label: \''.dol_escape_js($serie['label']).'\', data: '.json_encode($serie['data']).'}'."\n";
3027
					$outputserie++; $i++;
3028
				}
3029
				if ($outputserie) print ', ';
3030
				//print '];
3031
				//var datalines = [';
3032
				$i=0; $outputserie=0;
3033
				foreach($data['series'] as $serie)
3034
				{
3035
					if (empty($data['seriestype'][$i]) || $data['seriestype'][$i]=='bar') { $i++; continue; };
3036
					if ($outputserie > 0) print ',';
3037
					print '{ lines: { show: true }, label: \''.dol_escape_js($serie['label']).'\', data: '.json_encode($serie['data']).'}'."\n";
3038
					$outputserie++; $i++;
3039
				}
3040
				print '];
3041
				var dataticks = '.json_encode($data['xlabel']).'
3042
3043
				function plotWithOptions() {
3044
					$.plot(jQuery("#'.$htmlid.'"), data,
3045
					{
3046
						series: {
3047
							stack: 0
3048
						},
3049
						zoom: {
3050
							interactive: true
3051
						},
3052
						pan: {
3053
							interactive: true
3054
						},';
3055
						if (count($datacolor))
3056
						{
3057
							print 'colors: '.json_encode($datacolor).',';
3058
						}
3059
						print 'legend: {show: '.($showlegend?'true':'false').'},
3060
						xaxis: {ticks: dataticks}
3061
					});
3062
				}
3063
				plotWithOptions();
3064
			});
3065
			</script>';
3066
		}
3067
		else print 'BadValueForParameterType';
3068
	}
3069
}
3070
3071
/**
3072
 *	Truncate a string to a particular length adding '...' if string larger than length.
3073
 * 	If length = max length+1, we do no truncate to avoid having just 1 char replaced with '...'.
3074
 *  MAIN_DISABLE_TRUNC=1 can disable all truncings
3075
 *
3076
 *	@param	string	$string				String to truncate
3077
 *	@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 ...)
3078
 *	@param	string	$trunc				Where to trunc: right, left, middle (size must be a 2 power), wrap
3079
 * 	@param	string	$stringencoding		Tell what is source string encoding
3080
 *  @param	int		$nodot				Truncation do not add ... after truncation. So it's an exact truncation.
3081
 *  @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)
3082
 *	@return string						Truncated string. WARNING: length is never higher than $size if $nodot is set, but can be 3 chars higher otherwise.
3083
 */
3084
function dol_trunc($string,$size=40,$trunc='right',$stringencoding='UTF-8',$nodot=0, $display=0)
3085
{
3086
	global $conf;
3087
3088
	if ($size==0 || ! empty($conf->global->MAIN_DISABLE_TRUNC)) return $string;
3089
3090
	if (empty($stringencoding)) $stringencoding='UTF-8';
3091
	// reduce for small screen
3092
	if ($conf->dol_optimize_smallscreen==1 && $display==1) $size = round($size/3);
3093
3094
	// We go always here
3095
	if ($trunc == 'right')
3096
	{
3097
		$newstring=dol_textishtml($string)?dol_string_nohtmltag($string,1):$string;
3098
		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 ...
3099
		return dol_substr($newstring,0,$size,$stringencoding).($nodot?'':'...');
3100
		else
3101
		//return 'u'.$size.'-'.$newstring.'-'.dol_strlen($newstring,$stringencoding).'-'.$string;
3102
		return $string;
3103
	}
3104
	elseif ($trunc == 'middle')
3105
	{
3106
		$newstring=dol_textishtml($string)?dol_string_nohtmltag($string,1):$string;
3107
		if (dol_strlen($newstring,$stringencoding) > 2 && dol_strlen($newstring,$stringencoding) > ($size+1))
3108
		{
3109
			$size1=round($size/2);
3110
			$size2=round($size/2);
3111
			return dol_substr($newstring,0,$size1,$stringencoding).'...'.dol_substr($newstring,dol_strlen($newstring,$stringencoding) - $size2,$size2,$stringencoding);
3112
		}
3113
		else
3114
		return $string;
3115
	}
3116
	elseif ($trunc == 'left')
3117
	{
3118
		$newstring=dol_textishtml($string)?dol_string_nohtmltag($string,1):$string;
3119
		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 ...
3120
		return '...'.dol_substr($newstring,dol_strlen($newstring,$stringencoding) - $size,$size,$stringencoding);
3121
		else
3122
		return $string;
3123
	}
3124
	elseif ($trunc == 'wrap')
3125
	{
3126
		$newstring=dol_textishtml($string)?dol_string_nohtmltag($string,1):$string;
3127
		if (dol_strlen($newstring,$stringencoding) > ($size+1))
3128
		return dol_substr($newstring,0,$size,$stringencoding)."\n".dol_trunc(dol_substr($newstring,$size,dol_strlen($newstring,$stringencoding)-$size,$stringencoding),$size,$trunc);
3129
		else
3130
		return $string;
3131
	}
3132
	else return 'BadParam3CallingDolTrunc';
3133
}
3134
3135
/**
3136
 *	Show picto whatever it's its name (generic function)
3137
 *
3138
 *	@param      string		$titlealt         	Text on title tag for tooltip. Not used if param notitle is set to 1.
3139
 *	@param      string		$picto       		Name of image file to show ('filenew', ...)
3140
 *												If no extension provided, we use '.png'. Image must be stored into theme/xxx/img directory.
3141
 *                                  			Example: picto.png                  if picto.png is stored into htdocs/theme/mytheme/img
3142
 *                                  			Example: picto.png@mymodule         if picto.png is stored into htdocs/mymodule/img
3143
 *                                  			Example: /mydir/mysubdir/picto.png  if picto.png is stored into htdocs/mydir/mysubdir (pictoisfullpath must be set to 1)
3144
 *	@param		string		$moreatt			Add more attribute on img tag (For example 'style="float: right"')
3145
 *	@param		boolean|int	$pictoisfullpath	If true or 1, image path is a full path
3146
 *	@param		int			$srconly			Return only content of the src attribute of img.
3147
 *  @param		int			$notitle			1=Disable tag title. Use it if you add js tooltip, to avoid duplicate tooltip.
3148
 *  @param		string		$alt				Force alt for bind people
3149
 *  @param		string		$morecss			Add more class css on img tag (For example 'myclascss'). Work only if $moreatt is empty.
3150
 *  @return     string       				    Return img tag
3151
 *  @see        #img_object, #img_picto_common
3152
 */
3153
function img_picto($titlealt, $picto, $moreatt = '', $pictoisfullpath = false, $srconly=0, $notitle=0, $alt='', $morecss='')
3154
{
3155
	global $conf, $langs;
3156
3157
	// We forge fullpathpicto for image to $path/img/$picto. By default, we take DOL_URL_ROOT/theme/$conf->theme/img/$picto
3158
	$url = DOL_URL_ROOT;
3159
	$theme = $conf->theme;
3160
	$path = 'theme/'.$theme;
3161
3162
	// Define fullpathpicto to use into src
3163
	if ($pictoisfullpath) {
3164
		// Clean parameters
3165
		if (! preg_match('/(\.png|\.gif|\.svg)$/i',$picto)) {
3166
			$picto .= '.png';
3167
		}
3168
		$fullpathpicto = $picto;
3169
	}
3170
	else {
3171
		$pictowithoutext = preg_replace('/(\.png|\.gif|\.svg)$/', '', $picto);
3172
3173
		//if (in_array($picto, array('switch_off', 'switch_on', 'off', 'on')))
3174
		if (empty($srconly) && in_array($pictowithoutext, array(
3175
				'bank', 'close_title', 'delete', 'edit', 'ellipsis-h', 'filter', 'grip', 'grip_title', 'list', 'listlight', 'off', 'on', 'play', 'playdisabled', 'printer', 'resize',
3176
				'note','switch_off', 'switch_on', 'unlink', 'uparrow', '1downarrow', '1uparrow',
3177
				'skype','twitter','facebook'
3178
			)
3179
		)) {
3180
			$fakey = $pictowithoutext;
3181
			$facolor = ''; $fasize = '';
3182
			$marginleftonlyshort = 2;
3183
			if ($pictowithoutext == 'switch_off') {
3184
				$fakey = 'fa-toggle-off';
3185
				$facolor = '#999';
3186
				$fasize = '2em';
3187
			}
3188
			elseif ($pictowithoutext == 'switch_on') {
3189
				$fakey = 'fa-toggle-on';
3190
				$facolor = '#227722';
3191
				$fasize = '2em';
3192
			}
3193
			elseif ($pictowithoutext == 'off') {
3194
				$fakey = 'fa-square-o';
3195
				$fasize = '1.3em';
3196
			}
3197
			elseif ($pictowithoutext == 'on') {
3198
				$fakey = 'fa-check-square-o';
3199
				$fasize = '1.3em';
3200
			}
3201
			elseif ($pictowithoutext == 'bank') {
3202
				$fakey = 'fa-bank';
3203
				$facolor = '#444';
3204
			}
3205
			elseif ($pictowithoutext == 'close_title') {
3206
				$fakey = 'fa-window-close';
3207
			}
3208
			elseif ($pictowithoutext == 'delete') {
3209
				$fakey = 'fa-trash';
3210
				$facolor = '#444';
3211
			}
3212
			elseif ($pictowithoutext == 'edit') {
3213
				$fakey = 'fa-pencil';
3214
				$facolor = '#444';
3215
			}
3216
			elseif ($pictowithoutext == 'filter') {
3217
				$fakey = 'fa-'.$pictowithoutext;
3218
			}
3219
			elseif ($pictowithoutext == 'grip_title' || $pictowithoutext == 'grip') {
3220
				$fakey = 'fa-arrows';
3221
			}
3222
			elseif ($pictowithoutext == 'listlight') {
3223
				$fakey = 'fa-download';
3224
				$facolor = '#999';
3225
				$marginleftonlyshort=1;
3226
			}
3227
			elseif ($pictowithoutext == 'printer') {
3228
				$fakey = 'fa-print';
3229
				$fasize = '1.2em';
3230
				$facolor = '#444';
3231
			}
3232
			elseif ($pictowithoutext == 'resize') {
3233
				$fakey = 'fa-crop';
3234
				$facolor = '#444';
3235
			}
3236
			elseif ($pictowithoutext == 'note') {
3237
				$fakey = 'fa-sticky-note-o';
3238
				$facolor = '#999';
3239
				$marginleftonlyshort=1;
3240
			}
3241
			elseif ($pictowithoutext == 'uparrow') {
3242
				$fakey = 'fa-mail-forward';
3243
				$facolor = '#555';
3244
			}
3245
			elseif ($pictowithoutext == '1uparrow') {
3246
				$fakey = 'fa-caret-up';
3247
				$marginleftonlyshort = 1;
3248
			}
3249
			elseif ($pictowithoutext == '1downarrow') {
3250
				$fakey = 'fa-caret-down';
3251
				$marginleftonlyshort = 1;
3252
			}
3253
			elseif ($pictowithoutext == 'unlink')     {
3254
				$fakey = 'fa-chain-broken';
3255
				$facolor = '#555';
3256
			}
3257
			elseif ($pictowithoutext == 'playdisabled') {
3258
				$fakey = 'fa-play';
3259
				$facolor = '#ccc';
3260
			} elseif ($pictowithoutext == 'play') {
3261
				$fakey = 'fa-play';
3262
				$facolor = '#444';
3263
			}
3264
			else {
3265
				$fakey = 'fa-'.$pictowithoutext;
3266
				$facolor = '#444';
3267
				$marginleftonlyshort=0;
3268
			}
3269
3270
			if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
3271
				$morecss.= ($morecss?' ':'').$reg[1];
3272
			}
3273
			$enabledisablehtml = '<span class="fa '.$fakey.' '.($marginleftonlyshort?($marginleftonlyshort==1?'marginleftonlyshort':'marginleftonly'):'').' valignmiddle'.($morecss?' '.$morecss:'').'" style="'.($fasize?('font-size: '.$fasize.';'):'').($facolor?(' color: '.$facolor.';'):'').'" alt="'.dol_escape_htmltag($titlealt).'"'.(($notitle || empty($title))?'':' title="'.dol_escape_htmltag($title).'"').($moreatt?' '.$moreatt:'').'>';
0 ignored issues
show
Bug introduced by
The variable $title seems to never exist, and therefore empty should always return true. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
3274
			if (! empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
3275
				$enabledisablehtml.= $titlealt;
3276
			}
3277
			$enabledisablehtml.= '</span>';
3278
3279
			return $enabledisablehtml;
3280
		}
3281
3282
		if (! empty($conf->global->MAIN_OVERWRITE_THEME_PATH)) {
3283
			$path = $conf->global->MAIN_OVERWRITE_THEME_PATH.'/theme/'.$theme;	// If the theme does not have the same name as the module
3284
		}
3285
		else if (! empty($conf->global->MAIN_OVERWRITE_THEME_RES)) {
3286
			$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
3287
		}
3288
		else if (! empty($conf->modules_parts['theme']) && array_key_exists($theme, $conf->modules_parts['theme'])) {
3289
			$path = $theme.'/theme/'.$theme;	// If the theme have the same name as the module
3290
		}
3291
3292
		// If we ask an image into $url/$mymodule/img (instead of default path)
3293
		if (preg_match('/^([^@]+)@([^@]+)$/i',$picto,$regs)) {
3294
			$picto = $regs[1];
3295
			$path = $regs[2];	// $path is $mymodule
3296
		}
3297
3298
		// Clean parameters
3299
		if (! preg_match('/(\.png|\.gif|\.svg)$/i',$picto)) {
3300
			$picto .= '.png';
3301
		}
3302
		// If alt path are defined, define url where img file is, according to physical path
3303
		// ex: array(["main"]=>"/home/maindir/htdocs", ["alt0"]=>"/home/moddir0/htdocs", ...)
3304
		foreach ($conf->file->dol_document_root as $type => $dirroot) {
3305
			if ($type == 'main') {
3306
				continue;
3307
			}
3308
			// This need a lot of time, that's why enabling alternative dir like "custom" dir is not recommanded
3309
			if (file_exists($dirroot.'/'.$path.'/img/'.$picto)) {
3310
				$url = DOL_URL_ROOT.$conf->file->dol_url_root[$type];
3311
				break;
3312
			}
3313
		}
3314
3315
		// $url is '' or '/custom', $path is current theme or
3316
		$fullpathpicto = $url.'/'.$path.'/img/'.$picto;
3317
	}
3318
3319
	if ($srconly) {
3320
		return $fullpathpicto;
3321
	}
3322
		// tag title is used for tooltip on <a>, tag alt can be used with very simple text on image for bind people
3323
    return '<img src="'.$fullpathpicto.'" alt="'.dol_escape_htmltag($alt).'"'.(($notitle || empty($titlealt))?'':' title="'.dol_escape_htmltag($titlealt).'"').($moreatt?' '.$moreatt:' class="inline-block'.($morecss?' '.$morecss:'').'"').'>';	// Alt is used for accessibility, title for popup
3324
}
3325
3326
/**
3327
 *	Show a picto called object_picto (generic function)
3328
 *
3329
 *	@param	string	$titlealt			Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3330
 *	@param	string	$picto				Name of image to show object_picto (example: user, group, action, bill, contract, propal, product, ...)
3331
 *										For external modules use imagename@mymodule to search into directory "img" of module.
3332
 *	@param	string	$moreatt			Add more attribute on img tag (ie: class="datecallink")
3333
 *	@param	int		$pictoisfullpath	If 1, image path is a full path
3334
 *	@param	int		$srconly			Return only content of the src attribute of img.
3335
 *  @param	int		$notitle			1=Disable tag title. Use it if you add js tooltip, to avoid duplicate tooltip.
3336
 *	@return	string						Return img tag
3337
 *	@see	#img_picto, #img_picto_common
3338
 */
3339
function img_object($titlealt, $picto, $moreatt = '', $pictoisfullpath = false, $srconly=0, $notitle=0)
3340
{
3341
	return img_picto($titlealt, 'object_'.$picto, $moreatt, $pictoisfullpath, $srconly, $notitle);
3342
}
3343
3344
/**
3345
 *	Show weather picto
3346
 *
3347
 *	@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.
3348
 *	@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.
3349
 *	@param		string		$moreatt			Add more attribute on img tag
3350
 *	@param		int			$pictoisfullpath	If 1, image path is a full path
3351
 *	@return     string      					Return img tag
3352
 *  @see        #img_object, #img_picto
3353
 */
3354
function img_weather($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0)
3355
{
3356
	global $conf;
3357
3358
	if (! preg_match('/(\.png|\.gif)$/i', $picto)) $picto .= '.png';
3359
3360
	$path = DOL_URL_ROOT.'/theme/'.$conf->theme.'/img/weather/'.$picto;
3361
3362
	return img_picto($titlealt, $path, $moreatt, 1);
3363
}
3364
3365
/**
3366
 *	Show picto (generic function)
3367
 *
3368
 *	@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.
3369
 *	@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.
3370
 *	@param		string		$moreatt			Add more attribute on img tag
3371
 *	@param		int			$pictoisfullpath	If 1, image path is a full path
3372
 *	@return     string      					Return img tag
3373
 *  @see        #img_object, #img_picto
3374
 */
3375
function img_picto_common($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0)
3376
{
3377
	global $conf;
3378
3379
	if (! preg_match('/(\.png|\.gif)$/i', $picto)) $picto .= '.png';
3380
3381
	if ($pictoisfullpath) $path = $picto;
3382
	else
3383
	{
3384
		$path = DOL_URL_ROOT.'/theme/common/'.$picto;
3385
3386
		if (! empty($conf->global->MAIN_MODULE_CAN_OVERWRITE_COMMONICONS))
3387
		{
3388
			$themepath = DOL_DOCUMENT_ROOT.'/theme/'.$conf->theme.'/img/'.$picto;
3389
3390
			if (file_exists($themepath)) $path = $themepath;
3391
		}
3392
	}
3393
3394
	return img_picto($titlealt, $path, $moreatt, 1);
3395
}
3396
3397
/**
3398
 *	Show logo action
3399
 *
3400
 *	@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.
3401
 *	@param  string		$numaction   	Action id or code to show
3402
 *	@return string      				Return an img tag
3403
 */
3404
function img_action($titlealt, $numaction)
3405
{
3406
	global $conf, $langs;
3407
3408
	if (empty($titlealt) || $titlealt == 'default')
3409
	{
3410
		if ($numaction == '-1' || $numaction == 'ST_NO')			{ $numaction = -1; $titlealt = $langs->transnoentitiesnoconv('ChangeDoNotContact'); }
3411
		elseif ($numaction ==  '0' || $numaction == 'ST_NEVER') 	{ $numaction = 0; $titlealt = $langs->transnoentitiesnoconv('ChangeNeverContacted'); }
3412
		elseif ($numaction ==  '1' || $numaction == 'ST_TODO')  	{ $numaction = 1; $titlealt = $langs->transnoentitiesnoconv('ChangeToContact'); }
3413
		elseif ($numaction ==  '2' || $numaction == 'ST_PEND')  	{ $numaction = 2; $titlealt = $langs->transnoentitiesnoconv('ChangeContactInProcess'); }
3414
		elseif ($numaction ==  '3' || $numaction == 'ST_DONE')  	{ $numaction = 3; $titlealt = $langs->transnoentitiesnoconv('ChangeContactDone'); }
3415
		else { $titlealt = $langs->transnoentitiesnoconv('ChangeStatus '.$numaction); $numaction = 0; }
3416
	}
3417
	if (! is_numeric($numaction)) $numaction=0;
3418
3419
	return img_picto($titlealt, 'stcomm'.$numaction.'.png');
3420
}
3421
3422
/**
3423
 *  Show pdf logo
3424
 *
3425
 *  @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.
3426
 *  @param  int		    $size       Taille de l'icone : 3 = 16x16px , 2 = 14x14px
3427
 *  @return string      			Retourne tag img
3428
 */
3429
function img_pdf($titlealt = 'default', $size = 3)
3430
{
3431
	global $conf, $langs;
3432
3433
	if ($titlealt == 'default') $titlealt = $langs->trans('Show');
3434
3435
	return img_picto($titlealt, 'pdf'.$size.'.png');
3436
}
3437
3438
/**
3439
 *	Show logo +
3440
 *
3441
 *	@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.
3442
 *	@param  string	$other      Add more attributes on img
3443
 *	@return string      		Return tag img
3444
 */
3445
function img_edit_add($titlealt = 'default', $other = '')
3446
{
3447
	global $conf, $langs;
3448
3449
	if ($titlealt == 'default') $titlealt = $langs->trans('Add');
3450
3451
	return img_picto($titlealt, 'edit_add.png', $other);
3452
}
3453
/**
3454
 *	Show logo -
3455
 *
3456
 *	@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.
3457
 *	@param  string	$other      Add more attributes on img
3458
 *	@return string      		Return tag img
3459
 */
3460
function img_edit_remove($titlealt = 'default', $other='')
3461
{
3462
	global $conf, $langs;
3463
3464
	if ($titlealt == 'default') $titlealt = $langs->trans('Remove');
3465
3466
	return img_picto($titlealt, 'edit_remove.png', $other);
3467
}
3468
3469
/**
3470
 *	Show logo editer/modifier fiche
3471
 *
3472
 *	@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.
3473
 *	@param  integer	$float      Si il faut y mettre le style "float: right"
3474
 *	@param  string	$other		Add more attributes on img
3475
 *	@return string      		Return tag img
3476
 */
3477
function img_edit($titlealt = 'default', $float = 0, $other = 'class="pictoedit"')
3478
{
3479
	global $conf, $langs;
3480
3481
	if ($titlealt == 'default') $titlealt = $langs->trans('Modify');
3482
3483
	return img_picto($titlealt, 'edit.png', ($float ? 'style="float: '.($langs->tab_translate["DIRECTION"] == 'rtl'?'left':'right').'"' : "") . ($other?' '.$other:''));
3484
}
3485
3486
/**
3487
 *	Show logo view card
3488
 *
3489
 *	@param	string	$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3490
 *	@param  integer	$float      Si il faut y mettre le style "float: right"
3491
 *	@param  string	$other		Add more attributes on img
3492
 *	@return string      		Return tag img
3493
 */
3494
function img_view($titlealt = 'default', $float = 0, $other = '')
3495
{
3496
	global $conf, $langs;
3497
3498
	if ($titlealt == 'default') $titlealt = $langs->trans('View');
3499
3500
	$moreatt = ($float ? 'style="float: right" ' : '').$other;
3501
3502
	return img_picto($titlealt, 'view.png', $moreatt);
3503
}
3504
3505
/**
3506
 *  Show delete logo
3507
 *
3508
 *  @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.
3509
 *	@param  string	$other      Add more attributes on img
3510
 *  @return string      		Retourne tag img
3511
 */
3512
function img_delete($titlealt = 'default', $other = 'class="pictodelete"')
3513
{
3514
	global $conf, $langs;
3515
3516
	if ($titlealt == 'default') $titlealt = $langs->trans('Delete');
3517
3518
	return img_picto($titlealt, 'delete.png', $other);
3519
	//return '<span class="fa fa-trash fa-2x fa-fw" style="font-size: 1.7em;" title="'.$titlealt.'"></span>';
3520
}
3521
3522
/**
3523
 *  Show printer logo
3524
 *
3525
 *  @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.
3526
 *  @param  string  $other      Add more attributes on img
3527
 *  @return string              Retourne tag img
3528
 */
3529
function img_printer($titlealt = "default", $other='')
3530
{
3531
	global $conf,$langs;
3532
	if ($titlealt=="default") $titlealt=$langs->trans("Print");
3533
	return img_picto($titlealt,'printer.png',$other);
3534
}
3535
3536
/**
3537
 *  Show split logo
3538
 *
3539
 *  @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.
3540
 *	@param  string	$other      Add more attributes on img
3541
 *  @return string      		Retourne tag img
3542
 */
3543
function img_split($titlealt = 'default', $other = 'class="pictosplit"')
3544
{
3545
	global $conf, $langs;
3546
3547
	if ($titlealt == 'default') $titlealt = $langs->trans('Split');
3548
3549
	return img_picto($titlealt, 'split.png', $other);
3550
}
3551
3552
/**
3553
 *	Show help logo with cursor "?"
3554
 *
3555
 * 	@param	int              	$usehelpcursor		1=Use help cursor, 2=Use click pointer cursor, 0=No specific cursor
3556
 * 	@param	int|string	        $usealttitle		Text to use as alt title
3557
 * 	@return string            	           			Return tag img
3558
 */
3559
function img_help($usehelpcursor = 1, $usealttitle = 1)
3560
{
3561
	global $conf, $langs;
3562
3563
	if ($usealttitle)
3564
	{
3565
		if (is_string($usealttitle)) $usealttitle = dol_escape_htmltag($usealttitle);
3566
		else $usealttitle = $langs->trans('Info');
3567
	}
3568
3569
	return img_picto($usealttitle, 'info.png', 'style="vertical-align: middle;'.($usehelpcursor == 1 ? ' cursor: help': ($usehelpcursor == 2 ? ' cursor: pointer':'')).'"');
3570
}
3571
3572
/**
3573
 *	Show info logo
3574
 *
3575
 *	@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.
3576
 *	@return string      		Return img tag
3577
 */
3578
function img_info($titlealt = 'default')
3579
{
3580
	global $conf, $langs;
3581
3582
	if ($titlealt == 'default') $titlealt = $langs->trans('Informations');
3583
3584
	return img_picto($titlealt, 'info.png', 'style="vertical-align: middle;"');
3585
}
3586
3587
/**
3588
 *	Show warning logo
3589
 *
3590
 *	@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.
3591
 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"'). If 1, add float: right. Can't be "class" attribute.
3592
 *	@return string      		Return img tag
3593
 */
3594
function img_warning($titlealt = 'default', $moreatt = '')
3595
{
3596
	global $conf, $langs;
3597
3598
	if ($titlealt == 'default') $titlealt = $langs->trans('Warning');
3599
3600
	//return '<div class="imglatecoin">'.img_picto($titlealt, 'warning_white.png', 'class="pictowarning valignmiddle"'.($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' '.$moreatt): '')).'</div>';
3601
	return img_picto($titlealt, 'warning.png', 'class="pictowarning valignmiddle"'.($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' '.$moreatt): ''));
3602
}
3603
3604
/**
3605
 *  Show error logo
3606
 *
3607
 *	@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.
3608
 *	@return string      		Return img tag
3609
 */
3610
function img_error($titlealt = 'default')
3611
{
3612
	global $conf, $langs;
3613
3614
	if ($titlealt == 'default') $titlealt = $langs->trans('Error');
3615
3616
	return img_picto($titlealt, 'error.png', 'class="valigntextbottom"');
3617
}
3618
3619
/**
3620
 *	Show next logo
3621
 *
3622
 *	@param	string	$titlealt   Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3623
*	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
3624
 *	@return string      		Return img tag
3625
 */
3626
function img_next($titlealt = 'default', $moreatt='')
3627
{
3628
	global $conf, $langs;
3629
3630
	if ($titlealt == 'default') $titlealt = $langs->trans('Next');
3631
3632
	//return img_picto($titlealt, 'next.png', $moreatt);
3633
	return '<span class="fa fa-chevron-right paddingright paddingleft" title="'.dol_escape_htmltag($titlealt).'"></span>';
3634
}
3635
3636
/**
3637
 *	Show previous logo
3638
 *
3639
 *	@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.
3640
 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
3641
 *	@return string      		Return img tag
3642
 */
3643
function img_previous($titlealt = 'default', $moreatt='')
3644
{
3645
	global $conf, $langs;
3646
3647
	if ($titlealt == 'default') $titlealt = $langs->trans('Previous');
3648
3649
	//return img_picto($titlealt, 'previous.png', $moreatt);
3650
	return '<span class="fa fa-chevron-left paddingright paddingleft" title="'.dol_escape_htmltag($titlealt).'"></span>';
3651
}
3652
3653
/**
3654
 *	Show down arrow logo
3655
 *
3656
 *	@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.
3657
 *	@param  int		$selected   Selected
3658
 *  @param	string	$moreclass	Add more CSS classes
3659
 *	@return string      		Return img tag
3660
 */
3661
function img_down($titlealt = 'default', $selected = 0, $moreclass='')
3662
{
3663
	global $conf, $langs;
3664
3665
	if ($titlealt == 'default') $titlealt = $langs->trans('Down');
3666
3667
	return img_picto($titlealt, ($selected ? '1downarrow_selected.png' : '1downarrow.png'), 'class="imgdown'.($moreclass?" ".$moreclass:"").'"');
3668
}
3669
3670
/**
3671
 *	Show top arrow logo
3672
 *
3673
 *	@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.
3674
 *	@param  int		$selected	Selected
3675
 *  @param	string	$moreclass	Add more CSS classes
3676
 *	@return string      		Return img tag
3677
 */
3678
function img_up($titlealt = 'default', $selected = 0, $moreclass='')
3679
{
3680
	global $conf, $langs;
3681
3682
	if ($titlealt == 'default') $titlealt = $langs->trans('Up');
3683
3684
	return img_picto($titlealt, ($selected ? '1uparrow_selected.png' : '1uparrow.png'), 'class="imgup'.($moreclass?" ".$moreclass:"").'"');
3685
}
3686
3687
/**
3688
 *	Show left arrow logo
3689
 *
3690
 *	@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.
3691
 *	@param  int		$selected	Selected
3692
 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
3693
 *	@return string      		Return img tag
3694
 */
3695
function img_left($titlealt = 'default', $selected = 0, $moreatt='')
3696
{
3697
	global $conf, $langs;
3698
3699
	if ($titlealt == 'default') $titlealt = $langs->trans('Left');
3700
3701
	return img_picto($titlealt, ($selected ? '1leftarrow_selected.png' : '1leftarrow.png'), $moreatt);
3702
}
3703
3704
/**
3705
 *	Show right arrow logo
3706
 *
3707
 *	@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.
3708
 *	@param  int		$selected	Selected
3709
 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
3710
 *	@return string      		Return img tag
3711
 */
3712
function img_right($titlealt = 'default', $selected = 0, $moreatt='')
3713
{
3714
	global $conf, $langs;
3715
3716
	if ($titlealt == 'default') $titlealt = $langs->trans('Right');
3717
3718
	return img_picto($titlealt, ($selected ? '1rightarrow_selected.png' : '1rightarrow.png'), $moreatt);
3719
}
3720
3721
/**
3722
 *	Show tick logo if allowed
3723
 *
3724
 *	@param	string	$allow		Allow
3725
 *	@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.
3726
 *	@return string      		Return img tag
3727
 */
3728
function img_allow($allow, $titlealt = 'default')
3729
{
3730
	global $conf, $langs;
3731
3732
	if ($titlealt == 'default') $titlealt = $langs->trans('Active');
3733
3734
	if ($allow == 1) return img_picto($titlealt, 'tick.png');
3735
3736
	return '-';
3737
}
3738
3739
/**
3740
 *	Return image of a credit card according to its brand name
3741
 *
3742
 *	@param	string	$brand		Brand name of credit card
3743
 *	@return string     			Return img tag
3744
 */
3745
function img_credit_card($brand)
3746
{
3747
	if ($brand == 'Visa') {$brand='cc-visa';}
3748
	elseif ($brand == 'MasterCard') {$brand='cc-mastercard';}
3749
	elseif ($brand == 'American Express') {$brand='cc-amex';}
3750
	elseif ($brand == 'Discover') {$brand='cc-discover';}
3751
	elseif ($brand == 'JCB') {$brand='cc-jcb';}
3752
	elseif ($brand == 'Diners Club') {$brand='cc-diners-club';}
3753
	elseif (! in_array($brand, array('cc-visa','cc-mastercard','cc-amex','cc-discover','cc-jcb','cc-diners-club'))) {$brand='credit-card';}
3754
3755
	return '<span class="fa fa-'.$brand.' fa-2x fa-fw"></span>';
3756
}
3757
3758
/**
3759
 *	Show MIME img of a file
3760
 *
3761
 *	@param	string	$file		Filename
3762
 * 	@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.
3763
 *  @param	string	$morecss	More css
3764
 *	@return string     			Return img tag
3765
 */
3766
function img_mime($file, $titlealt = '', $morecss='')
3767
{
3768
	require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
3769
3770
	$mimetype = dol_mimetype($file, '', 1);
3771
	$mimeimg = dol_mimetype($file, '', 2);
3772
	$mimefa = dol_mimetype($file, '', 4);
3773
3774
	if (empty($titlealt)) $titlealt = 'Mime type: '.$mimetype;
3775
3776
	//return img_picto_common($titlealt, 'mime/'.$mimeimg, 'class="'.$morecss.'"');
3777
	return '<i class="fa fa-'.$mimefa.' paddingright"></i>';
3778
}
3779
3780
3781
/**
3782
 *	Show phone logo.
3783
 *  Use img_picto instead.
3784
 *
3785
 *	@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.
3786
 *	@param  int		$option		Option
3787
 *	@return string      		Return img tag
3788
 *  @deprecated
3789
 *  @see img_picto
3790
 */
3791
function img_phone($titlealt = 'default', $option = 0)
3792
{
3793
	dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);
3794
3795
	global $conf,$langs;
3796
3797
	if ($titlealt == 'default') $titlealt = $langs->trans('Call');
3798
3799
	if ($option == 1) $img = 'call';
3800
	else $img = 'call_out';
3801
3802
	return img_picto($titlealt, $img);
3803
}
3804
3805
/**
3806
 *  Show search logo
3807
 *
3808
 *  @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.
3809
 *	@param  string	$other      Add more attributes on img
3810
 *  @return string      		Retourne tag img
3811
 */
3812
function img_search($titlealt = 'default', $other = '')
3813
{
3814
	global $conf, $langs;
3815
3816
	if ($titlealt == 'default') $titlealt = $langs->trans('Search');
3817
3818
	$img = img_picto($titlealt, 'search.png', $other, false, 1);
3819
3820
	$input = '<input type="image" class="liste_titre" name="button_search" src="'.$img.'" ';
3821
	$input.= 'value="'.dol_escape_htmltag($titlealt).'" title="'.dol_escape_htmltag($titlealt).'" >';
3822
3823
	return $input;
3824
}
3825
3826
/**
3827
 *  Show search logo
3828
 *
3829
 *  @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.
3830
 *	@param  string	$other      Add more attributes on img
3831
 *  @return string      		Retourne tag img
3832
 */
3833
function img_searchclear($titlealt = 'default', $other = '')
3834
{
3835
	global $conf, $langs;
3836
3837
	if ($titlealt == 'default') $titlealt = $langs->trans('Search');
3838
3839
	$img = img_picto($titlealt, 'searchclear.png', $other, false, 1);
3840
3841
	$input = '<input type="image" class="liste_titre" name="button_removefilter" src="'.$img.'" ';
3842
	$input.= 'value="'.dol_escape_htmltag($titlealt).'" title="'.dol_escape_htmltag($titlealt).'" >';
3843
3844
	return $input;
3845
}
3846
3847
/**
3848
 *	Show information for admin users or standard users
3849
 *
3850
 *	@param	string	$text			Text info
3851
 *	@param  integer	$infoonimgalt	Info is shown only on alt of star picto, otherwise it is show on output after the star picto
3852
 *	@param	int		$nodiv			No div
3853
 *  @param  string  $admin          '1'=Info for admin users. '0'=Info for standard users (change only the look), 'xxx'=Other
3854
 *  @param	string	$morecss		More CSS
3855
 *	@return	string					String with info text
3856
 */
3857
function info_admin($text, $infoonimgalt = 0, $nodiv=0, $admin='1', $morecss='')
3858
{
3859
	global $conf, $langs;
3860
3861
	if ($infoonimgalt)
3862
	{
3863
		return img_picto($text, 'info', 'class="hideonsmartphone'.($morecss?' '.$morecss:'').'"');
3864
	}
3865
3866
	return ($nodiv?'':'<div class="'.(empty($admin)?'':($admin=='1'?'info':$admin)).' hideonsmartphone'.($morecss?' '.$morecss:'').'">').'<span class="fa fa-info-circle" title="'.dol_escape_htmltag($admin?$langs->trans('InfoAdmin'):$langs->trans('Note')).'"></span> '.$text.($nodiv?'':'</div>');
3867
}
3868
3869
3870
/**
3871
 *	Affiche message erreur system avec toutes les informations pour faciliter le diagnostic et la remontee des bugs.
3872
 *	On doit appeler cette fonction quand une erreur technique bloquante est rencontree.
3873
 *	Toutefois, il faut essayer de ne l'appeler qu'au sein de pages php, les classes devant
3874
 *	renvoyer leur erreur par l'intermediaire de leur propriete "error".
3875
 *
3876
 *	@param	 	DoliDB	$db      	Database handler
3877
 *	@param  	mixed	$error		String or array of errors strings to show
3878
 *  @param		array	$errors		Array of errors
3879
 *	@return 	void
3880
 *  @see    	dol_htmloutput_errors
3881
 */
3882
function dol_print_error($db='',$error='',$errors=null)
3883
{
3884
	global $conf,$langs,$argv;
3885
	global $dolibarr_main_prod;
3886
3887
	$out = '';
3888
	$syslog = '';
3889
3890
	// Si erreur intervenue avant chargement langue
3891
	if (! $langs)
3892
	{
3893
		require_once DOL_DOCUMENT_ROOT .'/core/class/translate.class.php';
3894
		$langs = new Translate('', $conf);
3895
		$langs->load("main");
3896
	}
3897
	// Load translation files required by the page
3898
    $langs->loadLangs(array('main', 'errors'));
3899
3900
	if ($_SERVER['DOCUMENT_ROOT'])    // Mode web
3901
	{
3902
		$out.=$langs->trans("DolibarrHasDetectedError").".<br>\n";
3903
		if (! empty($conf->global->MAIN_FEATURES_LEVEL)) $out.="You use an experimental or develop level of features, so please do NOT report any bugs, except if problem is confirmed moving option MAIN_FEATURES_LEVEL back to 0.<br>\n";
3904
		$out.=$langs->trans("InformationToHelpDiagnose").":<br>\n";
3905
3906
		$out.="<b>".$langs->trans("Date").":</b> ".dol_print_date(time(),'dayhourlog')."<br>\n";
3907
		$out.="<b>".$langs->trans("Dolibarr").":</b> ".DOL_VERSION."<br>\n";
3908
		if (isset($conf->global->MAIN_FEATURES_LEVEL)) $out.="<b>".$langs->trans("LevelOfFeature").":</b> ".$conf->global->MAIN_FEATURES_LEVEL."<br>\n";
3909
		if (function_exists("phpversion"))
3910
		{
3911
			$out.="<b>".$langs->trans("PHP").":</b> ".phpversion()."<br>\n";
3912
		}
3913
		$out.="<b>".$langs->trans("Server").":</b> ".$_SERVER["SERVER_SOFTWARE"]."<br>\n";
3914
		if (function_exists("php_uname"))
3915
		{
3916
			$out.="<b>".$langs->trans("OS").":</b> ".php_uname()."<br>\n";
3917
		}
3918
		$out.="<b>".$langs->trans("UserAgent").":</b> ".$_SERVER["HTTP_USER_AGENT"]."<br>\n";
3919
		$out.="<br>\n";
3920
		$out.="<b>".$langs->trans("RequestedUrl").":</b> ".dol_htmlentities($_SERVER["REQUEST_URI"],ENT_COMPAT,'UTF-8')."<br>\n";
3921
		$out.="<b>".$langs->trans("Referer").":</b> ".(isset($_SERVER["HTTP_REFERER"])?dol_htmlentities($_SERVER["HTTP_REFERER"],ENT_COMPAT,'UTF-8'):'')."<br>\n";
3922
		$out.="<b>".$langs->trans("MenuManager").":</b> ".(isset($conf->standard_menu)?$conf->standard_menu:'')."<br>\n";
3923
		$out.="<br>\n";
3924
		$syslog.="url=".dol_escape_htmltag($_SERVER["REQUEST_URI"]);
3925
		$syslog.=", query_string=".dol_escape_htmltag($_SERVER["QUERY_STRING"]);
3926
	}
3927
	else                              // Mode CLI
3928
	{
3929
		$out.='> '.$langs->transnoentities("ErrorInternalErrorDetected").":\n".$argv[0]."\n";
3930
		$syslog.="pid=".dol_getmypid();
3931
	}
3932
3933
	if (is_object($db))
3934
	{
3935
		if ($_SERVER['DOCUMENT_ROOT'])  // Mode web
3936
		{
3937
			$out.="<b>".$langs->trans("DatabaseTypeManager").":</b> ".$db->type."<br>\n";
3938
			$out.="<b>".$langs->trans("RequestLastAccessInError").":</b> ".($db->lastqueryerror()?dol_escape_htmltag($db->lastqueryerror()):$langs->trans("ErrorNoRequestInError"))."<br>\n";
3939
			$out.="<b>".$langs->trans("ReturnCodeLastAccessInError").":</b> ".($db->lasterrno()?dol_escape_htmltag($db->lasterrno()):$langs->trans("ErrorNoRequestInError"))."<br>\n";
3940
			$out.="<b>".$langs->trans("InformationLastAccessInError").":</b> ".($db->lasterror()?dol_escape_htmltag($db->lasterror()):$langs->trans("ErrorNoRequestInError"))."<br>\n";
3941
			$out.="<br>\n";
3942
		}
3943
		else                            // Mode CLI
3944
		{
3945
			// No dol_escape_htmltag for output, we are in CLI mode
3946
			$out.='> '.$langs->transnoentities("DatabaseTypeManager").":\n".$db->type."\n";
3947
			$out.='> '.$langs->transnoentities("RequestLastAccessInError").":\n".($db->lastqueryerror()?$db->lastqueryerror():$langs->transnoentities("ErrorNoRequestInError"))."\n";
3948
			$out.='> '.$langs->transnoentities("ReturnCodeLastAccessInError").":\n".($db->lasterrno()?$db->lasterrno():$langs->transnoentities("ErrorNoRequestInError"))."\n";
3949
			$out.='> '.$langs->transnoentities("InformationLastAccessInError").":\n".($db->lasterror()?$db->lasterror():$langs->transnoentities("ErrorNoRequestInError"))."\n";
3950
		}
3951
		$syslog.=", sql=".$db->lastquery();
3952
		$syslog.=", db_error=".$db->lasterror();
3953
	}
3954
3955
	if ($error || $errors)
3956
	{
3957
		$langs->load("errors");
3958
3959
		// Merge all into $errors array
3960
		if (is_array($error) && is_array($errors)) $errors=array_merge($error,$errors);
3961
		elseif (is_array($error)) $errors=$error;
3962
		elseif (is_array($errors)) $errors=array_merge(array($error),$errors);
3963
		else $errors=array_merge(array($error));
3964
3965
		foreach($errors as $msg)
3966
		{
3967
			if (empty($msg)) continue;
3968
			if ($_SERVER['DOCUMENT_ROOT'])  // Mode web
3969
			{
3970
				$out.="<b>".$langs->trans("Message").":</b> ".dol_escape_htmltag($msg)."<br>\n" ;
3971
			}
3972
			else                        // Mode CLI
3973
			{
3974
				$out.='> '.$langs->transnoentities("Message").":\n".$msg."\n" ;
3975
			}
3976
			$syslog.=", msg=".$msg;
3977
		}
3978
	}
3979
	if (empty($dolibarr_main_prod) && $_SERVER['DOCUMENT_ROOT'] && function_exists('xdebug_print_function_stack') && function_exists('xdebug_call_file'))
3980
	{
3981
		xdebug_print_function_stack();
3982
		$out.='<b>XDebug informations:</b>'."<br>\n";
3983
		$out.='File: '.xdebug_call_file()."<br>\n";
3984
		$out.='Line: '.xdebug_call_line()."<br>\n";
3985
		$out.='Function: '.xdebug_call_function()."<br>\n";
3986
		$out.="<br>\n";
3987
	}
3988
3989
	if (empty($dolibarr_main_prod)) print $out;
3990
	else
3991
	{
3992
		print $langs->trans("DolibarrHasDetectedError").'. ';
3993
		print $langs->trans("YouCanSetOptionDolibarrMainProdToZero");
3994
		define("MAIN_CORE_ERROR", 1);
3995
	}
3996
	//else print 'Sorry, an error occured but the parameter $dolibarr_main_prod is defined in conf file so no message is reported to your browser. Please read the log file for error message.';
3997
	dol_syslog("Error ".$syslog, LOG_ERR);
3998
}
3999
4000
/**
4001
 * Show a public email and error code to contact if technical error
4002
 *
4003
 * @param	string	$prefixcode		Prefix of public error code
4004
 * @param   string  $errormessage   Complete error message
4005
 * @param	array	$errormessages	Array of error messages
4006
 * @param	string	$morecss		More css
4007
 * @param	string	$email			Email
4008
 * @return	void
4009
 */
4010
function dol_print_error_email($prefixcode, $errormessage='', $errormessages=array(), $morecss='error', $email='')
4011
{
4012
	global $langs,$conf;
4013
4014
	if (empty($email)) $email=$conf->global->MAIN_INFO_SOCIETE_MAIL;
4015
4016
	$langs->load("errors");
4017
	$now=dol_now();
4018
4019
	print '<br><div class="center login_main_message"><div class="'.$morecss.'">';
4020
	print $langs->trans("ErrorContactEMail", $email, $prefixcode.dol_print_date($now,'%Y%m%d'));
4021
	if ($errormessage) print '<br><br>'.$errormessage;
4022
	if (is_array($errormessages) && count($errormessages))
4023
	{
4024
		foreach($errormessages as $mesgtoshow)
4025
		{
4026
			print '<br><br>'.$mesgtoshow;
4027
		}
4028
	}
4029
	print '</div></div>';
4030
}
4031
4032
/**
4033
 *	Show title line of an array
4034
 *
4035
 *	@param	string	$name        Label of field
4036
 *	@param	string	$file        Url used when we click on sort picto
4037
 *	@param	string	$field       Field to use for new sorting
4038
 *	@param	string	$begin       ("" by defaut)
4039
 *	@param	string	$moreparam   Add more parameters on sort url links ("" by default)
4040
 *	@param  string	$moreattrib  Options of attribute td ("" by defaut, example: 'align="center"')
4041
 *	@param  string	$sortfield   Current field used to sort
4042
 *	@param  string	$sortorder   Current sort order
4043
 *  @param	string	$prefix		 Prefix for css. Use space after prefix to add your own CSS tag.
4044
 *  @param	string	$tooltip	 Tooltip
4045
 *	@return	void
4046
 */
4047
function print_liste_field_titre($name, $file="", $field="", $begin="", $moreparam="", $moreattrib="", $sortfield="", $sortorder="", $prefix="", $tooltip="")
4048
{
4049
	print getTitleFieldOfList($name, 0, $file, $field, $begin, $moreparam, $moreattrib, $sortfield, $sortorder, $prefix, 0, $tooltip);
4050
}
4051
4052
/**
4053
 *	Get title line of an array
4054
 *
4055
 *	@param	string	$name        		Translation key of field
4056
 *	@param	int		$thead		 		0=To use with standard table format, 1=To use inside <thead><tr>, 2=To use with <div>
4057
 *	@param	string	$file        		Url used when we click on sort picto
4058
 *	@param	string	$field       		Field to use for new sorting. Empty if this field is not sortable. Example "t.abc" or "t.abc,t.def"
4059
 *	@param	string	$begin       		("" by defaut)
4060
 *	@param	string	$moreparam   		Add more parameters on sort url links ("" by default)
4061
 *	@param  string	$moreattrib  		Add more attributes on th ("" by defaut, example: 'align="center"'). To add more css class, use param $prefix.
4062
 *	@param  string	$sortfield   		Current field used to sort (Ex: 'd.datep,d.id')
4063
 *	@param  string	$sortorder   		Current sort order (Ex: 'asc,desc')
4064
 *  @param	string	$prefix		 		Prefix for css. Use space after prefix to add your own CSS tag, for example 'mycss '.
4065
 *  @param	string	$disablesortlink	1=Disable sort link
4066
 *  @param	string	$tooltip	 		Tooltip
4067
 *	@return	string
4068
 */
4069
function getTitleFieldOfList($name, $thead=0, $file="", $field="", $begin="", $moreparam="", $moreattrib="", $sortfield="", $sortorder="", $prefix="", $disablesortlink=0, $tooltip='')
4070
{
4071
	global $conf, $langs, $form;
4072
	//print "$name, $file, $field, $begin, $options, $moreattrib, $sortfield, $sortorder<br>\n";
4073
4074
	$sortorder=strtoupper($sortorder);
4075
	$out='';
4076
	$sortimg='';
4077
4078
	$tag='th';
4079
	if ($thead==2) $tag='div';
4080
4081
	$tmpsortfield=explode(',',$sortfield);
4082
	$sortfield1=trim($tmpsortfield[0]);    // If $sortfield is 'd.datep,d.id', it becomes 'd.datep'
4083
	$tmpfield=explode(',',$field);
4084
	$field1=trim($tmpfield[0]);            // If $field is 'd.datep,d.id', it becomes 'd.datep'
4085
4086
	//var_dump('field='.$field.' field1='.$field1.' sortfield='.$sortfield.' sortfield1='.$sortfield1);
4087
	// If field is used as sort criteria we use a specific css class liste_titre_sel
4088
	// Example if (sortfield,field)=("nom","xxx.nom") or (sortfield,field)=("nom","nom")
4089
	if ($field1 && ($sortfield1 == $field1 || $sortfield1 == preg_replace("/^[^\.]+\./","",$field1))) $out.= '<'.$tag.' class="'.$prefix.'liste_titre_sel" '. $moreattrib.'>';
4090
	else $out.= '<'.$tag.' class="'.$prefix.'liste_titre" '. $moreattrib.'>';
4091
4092
	if (empty($thead) && $field && empty($disablesortlink))    // If this is a sort field
4093
	{
4094
		$options=preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i','',$moreparam);
4095
		$options=preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i','',$options);
4096
		$options=preg_replace('/&+/i','&',$options);
4097
		if (! preg_match('/^&/',$options)) $options='&'.$options;
4098
4099
		$sortordertouseinlink='';
4100
		if ($field1 != $sortfield1) // We are on another field than current sorted field
4101
		{
4102
			if (preg_match('/^DESC/i', $sortorder))
4103
			{
4104
				$sortordertouseinlink.=str_repeat('desc,', count(explode(',',$field)));
4105
			}
4106
			else		// We reverse the var $sortordertouseinlink
4107
			{
4108
				$sortordertouseinlink.=str_repeat('asc,', count(explode(',',$field)));
4109
			}
4110
		}
4111
		else                        // We are on field that is the first current sorting criteria
4112
		{
4113
			if (preg_match('/^ASC/i', $sortorder))	// We reverse the var $sortordertouseinlink
4114
			{
4115
				$sortordertouseinlink.=str_repeat('desc,', count(explode(',',$field)));
4116
			}
4117
			else
4118
			{
4119
				$sortordertouseinlink.=str_repeat('asc,', count(explode(',',$field)));
4120
			}
4121
		}
4122
		$sortordertouseinlink=preg_replace('/,$/', '', $sortordertouseinlink);
4123
		$out.= '<a class="reposition" href="'.$file.'?sortfield='.$field.'&sortorder='.$sortordertouseinlink.'&begin='.$begin.$options.'">';
4124
	}
4125
4126
	if ($tooltip) $out.=$form->textwithpicto($langs->trans($name), $langs->trans($tooltip));
4127
	else $out.=$langs->trans($name);
4128
4129
	if (empty($thead) && $field && empty($disablesortlink))    // If this is a sort field
4130
	{
4131
		$out.='</a>';
4132
	}
4133
4134
	if (empty($thead) && $field)    // If this is a sort field
4135
	{
4136
		$options=preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i','',$moreparam);
4137
		$options=preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i','',$options);
4138
		$options=preg_replace('/&+/i','&',$options);
4139
		if (! preg_match('/^&/',$options)) $options='&'.$options;
4140
4141
		if (! $sortorder || $field1 != $sortfield1)
4142
		{
4143
			//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",0).'</a>';
4144
			//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",0).'</a>';
4145
		}
4146
		else
4147
		{
4148
			if (preg_match('/^DESC/', $sortorder)) {
4149
				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",0).'</a>';
4150
				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",1).'</a>';
4151
				$sortimg.= '<span class="nowrap">'.img_up("Z-A",0).'</span>';
4152
			}
4153
			if (preg_match('/^ASC/', $sortorder)) {
4154
				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",1).'</a>';
4155
				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",0).'</a>';
4156
				$sortimg.= '<span class="nowrap">'.img_down("A-Z",0).'</span>';
4157
			}
4158
		}
4159
	}
4160
4161
	$out.=$sortimg;
4162
4163
	$out.='</'.$tag.'>';
4164
4165
	return $out;
4166
}
4167
4168
/**
4169
 *	Show a title.
4170
 *
4171
 *	@param	string	$title			Title to show
4172
 *	@return	string					Title to show
4173
 *  @deprecated						Use load_fiche_titre instead
4174
 *  @see load_fiche_titre
4175
 */
4176
function print_titre($title)
4177
{
4178
	dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);
4179
4180
	print '<div class="titre">'.$title.'</div>';
4181
}
4182
4183
/**
4184
 *	Show a title with picto
4185
 *
4186
 *	@param	string	$title				Title to show
4187
 *	@param	string	$mesg				Added message to show on right
4188
 *	@param	string	$picto				Icon to use before title (should be a 32x32 transparent png file)
4189
 *	@param	int		$pictoisfullpath	1=Icon name is a full absolute url of image
4190
 * 	@param	int		$id					To force an id on html objects
4191
 * 	@return	void
4192
 *  @deprecated Use print load_fiche_titre instead
4193
 */
4194
function print_fiche_titre($title, $mesg='', $picto='title_generic.png', $pictoisfullpath=0, $id='')
4195
{
4196
	print load_fiche_titre($title, $mesg, $picto, $pictoisfullpath, $id);
4197
}
4198
4199
/**
4200
 *	Load a title with picto
4201
 *
4202
 *	@param	string	$titre				Title to show
4203
 *	@param	string	$morehtmlright		Added message to show on right
4204
 *	@param	string	$picto				Icon to use before title (should be a 32x32 transparent png file)
4205
 *	@param	int		$pictoisfullpath	1=Icon name is a full absolute url of image
4206
 * 	@param	string	$id					To force an id on html objects
4207
 *  @param  string  $morecssontable     More css on table
4208
 *	@param	string	$morehtmlcenter		Added message to show on center
4209
 * 	@return	string
4210
 *  @see print_barre_liste
4211
 */
4212
function load_fiche_titre($titre, $morehtmlright='', $picto='title_generic.png', $pictoisfullpath=0, $id='', $morecssontable='', $morehtmlcenter='')
4213
{
4214
	global $conf;
4215
4216
	$return='';
4217
4218
	if ($picto == 'setup') $picto='title_generic.png';
4219
4220
	$return.= "\n";
4221
	$return.= '<table '.($id?'id="'.$id.'" ':'').'summary="" class="centpercent notopnoleftnoright'.($morecssontable?' '.$morecssontable:'').'" style="margin-bottom: 2px;"><tr>';
4222
	if ($picto) $return.= '<td class="nobordernopadding widthpictotitle opacityhigh" valign="middle">'.img_picto('',$picto, 'class="valignmiddle widthpictotitle pictotitle"', $pictoisfullpath).'</td>';
4223
	$return.= '<td class="nobordernopadding" valign="middle">';
4224
	$return.= '<div class="titre">'.$titre.'</div>';
4225
	$return.= '</td>';
4226
	if (dol_strlen($morehtmlcenter))
4227
	{
4228
		$return.= '<td class="nobordernopadding" align="center" valign="middle">'.$morehtmlcenter.'</td>';
4229
	}
4230
	if (dol_strlen($morehtmlright))
4231
	{
4232
		$return.= '<td class="nobordernopadding titre_right wordbreak" align="right" valign="middle">'.$morehtmlright.'</td>';
4233
	}
4234
	$return.= '</tr></table>'."\n";
4235
4236
	return $return;
4237
}
4238
4239
/**
4240
 *	Print a title with navigation controls for pagination
4241
 *
4242
 *	@param	string	    $titre				Title to show (required)
4243
 *	@param	int   	    $page				Numero of page to show in navigation links (required)
4244
 *	@param	string	    $file				Url of page (required)
4245
 *	@param	string	    $options         	More parameters for links ('' by default, does not include sortfield neither sortorder). Value must be 'urlencoded' before calling function.
4246
 *	@param	string    	$sortfield       	Field to sort on ('' by default)
4247
 *	@param	string	    $sortorder       	Order to sort ('' by default)
4248
 *	@param	string	    $morehtmlcenter     String in the middle ('' by default). We often find here string $massaction comming from $form->selectMassAction()
4249
 *	@param	int		    $num				Number of records found by select with limit+1
4250
 *	@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.
4251
 *	@param	string	    $picto				Icon to use before title (should be a 32x32 transparent png file)
4252
 *	@param	int		    $pictoisfullpath	1=Icon name is a full absolute url of image
4253
 *  @param	string	    $morehtmlright			More html to show
4254
 *  @param  string      $morecss            More css to the table
4255
 *  @param  int         $limit              Max number of lines (-1 = use default, 0 = no limit, > 0 = limit).
4256
 *  @param  int         $hideselectlimit    Force to hide select limit
4257
 *  @param  int         $hidenavigation     Force to hide all navigation tools
4258
 *	@return	void
4259
 */
4260
function print_barre_liste($titre, $page, $file, $options='', $sortfield='', $sortorder='', $morehtmlcenter='', $num=-1, $totalnboflines='', $picto='title_generic.png', $pictoisfullpath=0, $morehtmlright='', $morecss='', $limit=-1, $hideselectlimit=0, $hidenavigation=0)
4261
{
4262
	global $conf,$langs;
4263
4264
	$savlimit = $limit;
4265
	$savtotalnboflines = $totalnboflines;
4266
	$totalnboflines=abs($totalnboflines);
4267
4268
	if ($picto == 'setup') $picto='title_setup.png';
4269
	if (($conf->browser->name == 'ie') && $picto=='title_generic.png') $picto='title.gif';
4270
	if ($limit < 0) $limit = $conf->liste_limit;
4271
	if ($savlimit != 0 && (($num > $limit) || ($num == -1) || ($limit == 0)))
4272
	{
4273
		$nextpage = 1;
4274
	}
4275
	else
4276
	{
4277
		$nextpage = 0;
4278
	}
4279
	//print 'totalnboflines='.$totalnboflines.'-savlimit='.$savlimit.'-limit='.$limit.'-num='.$num.'-nextpage='.$nextpage;
4280
4281
	print "\n";
4282
	print "<!-- Begin title '".$titre."' -->\n";
4283
	print '<table width="100%" border="0" class="notopnoleftnoright'.($morecss?' '.$morecss:'').'" style="margin-bottom: 6px;"><tr>';
4284
4285
	// Left
4286
	//if ($picto && $titre) print '<td class="nobordernopadding hideonsmartphone" width="40" align="left" valign="middle">'.img_picto('', $picto, 'id="pictotitle"', $pictoisfullpath).'</td>';
4287
	print '<td class="nobordernopadding valignmiddle">';
4288
	if ($picto && $titre) print img_picto('', $picto, 'class="hideonsmartphone valignmiddle opacityhigh pictotitle widthpictotitle"', $pictoisfullpath);
4289
	print '<div class="titre inline-block">'.$titre;
4290
	if (!empty($titre) && $savtotalnboflines >= 0 && (string) $savtotalnboflines != '') print ' ('.$totalnboflines.')';
4291
	print '</div></td>';
4292
4293
	// Center
4294
	if ($morehtmlcenter)
4295
	{
4296
		print '<td class="nobordernopadding center valignmiddle">'.$morehtmlcenter.'</td>';
4297
	}
4298
4299
	// Right
4300
	print '<td class="nobordernopadding valignmiddle" align="right">';
4301
	if ($sortfield) $options .= "&sortfield=".urlencode($sortfield);
4302
	if ($sortorder) $options .= "&sortorder=".urlencode($sortorder);
4303
	// Show navigation bar
4304
	$pagelist = '';
4305
	if ($savlimit != 0 && ($page > 0 || $num > $limit))
4306
	{
4307
		if ($totalnboflines)	// If we know total nb of lines
4308
		{
4309
			// Define nb of extra page links before and after selected page + ... + first or last
4310
			$maxnbofpage=(empty($conf->dol_optimize_smallscreen) ? 4 : 1);
4311
4312
			if ($limit > 0) $nbpages=ceil($totalnboflines/$limit);
4313
			else $nbpages=1;
4314
			$cpt=($page-$maxnbofpage);
4315
			if ($cpt < 0) { $cpt=0; }
4316
4317
			if ($cpt>=1)
4318
			{
4319
				$pagelist.= '<li'.(($conf->dol_use_jmobile != 4)?' class="pagination"':'').'><a href="'.$file.'?page=0'.$options.'">1</a></li>';
4320
				if ($cpt > 2) $pagelist.='<li'.(($conf->dol_use_jmobile != 4)?' class="pagination"':'').'><span '.(($conf->dol_use_jmobile != 4)?'class="inactive"':'').'>...</span></li>';
4321
				else if ($cpt == 2) $pagelist.='<li'.(($conf->dol_use_jmobile != 4)?' class="pagination"':'').'><a href="'.$file.'?page=1'.$options.'">2</a></li>';
4322
			}
4323
4324
			do
4325
			{
4326
				if ($cpt==$page)
4327
				{
4328
					$pagelist.= '<li'.(($conf->dol_use_jmobile != 4)?' class="pagination"':'').'><span '.(($conf->dol_use_jmobile != 4)?'class="active"':'').'>'.($page+1).'</span></li>';
4329
				}
4330
				else
4331
				{
4332
					$pagelist.= '<li'.(($conf->dol_use_jmobile != 4)?' class="pagination"':'').'><a href="'.$file.'?page='.$cpt.$options.'">'.($cpt+1).'</a></li>';
4333
				}
4334
				$cpt++;
4335
			}
4336
			while ($cpt < $nbpages && $cpt<=$page+$maxnbofpage);
4337
4338
			if ($cpt<$nbpages)
4339
			{
4340
				if ($cpt<$nbpages-2) $pagelist.= '<li'.(($conf->dol_use_jmobile != 4)?' class="pagination"':'').'><span '.(($conf->dol_use_jmobile != 4)?'class="inactive"':'').'>...</span></li>';
4341
				else if ($cpt == $nbpages-2) $pagelist.= '<li'.(($conf->dol_use_jmobile != 4)?' class="pagination"':'').'><a href="'.$file.'?page='.($nbpages-2).$options.'">'.($nbpages - 1).'</a></li>';
4342
				$pagelist.= '<li'.(($conf->dol_use_jmobile != 4)?' class="pagination"':'').'><a href="'.$file.'?page='.($nbpages-1).$options.'">'.$nbpages.'</a></li>';
4343
			}
4344
		}
4345
		else
4346
		{
4347
			$pagelist.= '<li'.(($conf->dol_use_jmobile != 4)?' class="pagination"':'').'><span '.(($conf->dol_use_jmobile != 4)?'class="active"':'').'>'.($page+1)."</li>";
4348
		}
4349
	}
4350
4351
	print_fleche_navigation($page, $file, $options, $nextpage, $pagelist, $morehtmlright, $savlimit, $totalnboflines, $hideselectlimit);		// output the div and ul for previous/last completed with page numbers into $pagelist
4352
4353
	print '</td>';
4354
4355
	print '</tr></table>'."\n";
4356
	print "<!-- End title -->\n\n";
4357
}
4358
4359
/**
4360
 *	Function to show navigation arrows into lists
4361
 *
4362
 *	@param	int				$page				Number of page
4363
 *	@param	string			$file				Page URL (in most cases provided with $_SERVER["PHP_SELF"])
4364
 *	@param	string			$options         	Other url paramaters to propagate ("" by default, may include sortfield and sortorder)
4365
 *	@param	integer			$nextpage	    	Do we show a next page button
4366
 *	@param	string			$betweenarrows		HTML content to show between arrows. MUST contains '<li> </li>' tags or '<li><span> </span></li>'.
4367
 *  @param	string			$afterarrows		HTML content to show after arrows. Must NOT contains '<li> </li>' tags.
4368
 *  @param  int             $limit              Max nb of record to show  (-1 = no combo with limit, 0 = no limit, > 0 = limit)
4369
 *	@param	int		        $totalnboflines		Total number of records/lines for all pages (if known)
4370
 *  @param  int             $hideselectlimit    Force to hide select limit
4371
 *	@return	void
4372
 */
4373
function print_fleche_navigation($page, $file, $options='', $nextpage=0, $betweenarrows='', $afterarrows='', $limit=-1, $totalnboflines=0, $hideselectlimit=0)
4374
{
4375
	global $conf, $langs;
4376
4377
	print '<div class="pagination"><ul>';
4378
	if ((int) $limit >= 0 && empty($hideselectlimit))
4379
	{
4380
		$pagesizechoices='10:10,15:15,20:20,30:30,40:40,50:50,100:100,250:250,500:500,1000:1000,5000:5000';
4381
		//$pagesizechoices.=',0:'.$langs->trans("All");     // Not yet supported
4382
		//$pagesizechoices.=',2:2';
4383
		if (! empty($conf->global->MAIN_PAGESIZE_CHOICES)) $pagesizechoices=$conf->global->MAIN_PAGESIZE_CHOICES;
4384
4385
		print '<li class="pagination">';
4386
		print '<select class="flat selectlimit" name="limit" title="'.dol_escape_htmltag($langs->trans("MaxNbOfRecordPerPage")).'">';
4387
		$tmpchoice=explode(',',$pagesizechoices);
4388
		$tmpkey=$limit.':'.$limit;
4389
		if (! in_array($tmpkey, $tmpchoice)) $tmpchoice[]=$tmpkey;
4390
		$tmpkey=$conf->liste_limit.':'.$conf->liste_limit;
4391
		if (! in_array($tmpkey, $tmpchoice)) $tmpchoice[]=$tmpkey;
4392
		asort($tmpchoice, SORT_NUMERIC);
4393
		$found=false;
4394
		foreach($tmpchoice as $val)
4395
		{
4396
			$selected='';
4397
			$tmp=explode(':',$val);
4398
			$key=$tmp[0];
4399
			$val=$tmp[1];
4400
			if ($key != '' && $val != '')
4401
			{
4402
				if ((int) $key == (int) $limit)
4403
				{
4404
					$selected = ' selected="selected"';
4405
					$found = true;
4406
				}
4407
				print '<option name="'.$key.'"'.$selected.'>'.dol_escape_htmltag($val).'</option>'."\n";
4408
			}
4409
		}
4410
		print '</select>';
4411
		if ($conf->use_javascript_ajax)
4412
		{
4413
			print '<!-- JS CODE TO ENABLE select limit to launch submit of page -->
4414
            		<script type="text/javascript">
4415
                	jQuery(document).ready(function () {
4416
            	  		jQuery(".selectlimit").change(function() {
4417
                            console.log("Change limit. Send submit");
4418
                            $(this).parents(\'form:first\').submit();
4419
            	  		});
4420
                	});
4421
            		</script>
4422
                ';
4423
		}
4424
		print '</li>';
4425
	}
4426
	if ($page > 0)
4427
	{
4428
		print '<li class="pagination"><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>';
4429
	}
4430
	if ($betweenarrows)
4431
	{
4432
		print $betweenarrows;
4433
	}
4434
	if ($nextpage > 0)
4435
	{
4436
		print '<li class="pagination"><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>';
4437
	}
4438
	if ($afterarrows)
4439
	{
4440
		print '<li class="paginationafterarrows">';
4441
		print $afterarrows;
4442
		print '</li>';
4443
	}
4444
	print '</ul></div>'."\n";
4445
}
4446
4447
4448
/**
4449
 *	Return a string with VAT rate label formated for view output
4450
 *	Used into pdf and HTML pages
4451
 *
4452
 *	@param	string	$rate			Rate value to format ('19.6', '19,6', '19.6%', '19,6%', '19.6 (CODEX)', ...)
4453
 *  @param	boolean	$addpercent		Add a percent % sign in output
4454
 *	@param	int		$info_bits		Miscellaneous information on vat (0=Default, 1=French NPR vat)
4455
 *	@param	int		$usestarfornpr	-1=Never show, 0 or 1=Use '*' for NPR vat rates
4456
 *  @return	string					String with formated amounts ('19,6' or '19,6%' or '8.5% (NPR)' or '8.5% *' or '19,6 (CODEX)')
4457
 */
4458
function vatrate($rate, $addpercent=false, $info_bits=0, $usestarfornpr=0)
4459
{
4460
	$morelabel='';
4461
4462
	if (preg_match('/%/',$rate))
4463
	{
4464
		$rate=str_replace('%','',$rate);
4465
		$addpercent=true;
4466
	}
4467
	if (preg_match('/\((.*)\)/',$rate,$reg))
4468
	{
4469
		$morelabel=' ('.$reg[1].')';
4470
		$rate=preg_replace('/\s*'.preg_quote($morelabel,'/').'/','',$rate);
4471
	}
4472
	if (preg_match('/\*/',$rate))
4473
	{
4474
		$rate=str_replace('*','',$rate);
4475
		$info_bits |= 1;
4476
	}
4477
4478
	// If rate is '9/9/9' we don't change it.  If rate is '9.000' we apply price()
4479
	if (! preg_match('/\//', $rate)) $ret=price($rate,0,'',0,0).($addpercent?'%':'');
4480
	else
4481
	{
4482
		// TODO Split on / and output with a price2num to have clean numbers without ton of 000.
4483
		$ret=$rate.($addpercent?'%':'');
4484
	}
4485
	if (($info_bits & 1) && $usestarfornpr >= 0) $ret.=' *';
4486
	$ret.=$morelabel;
4487
	return $ret;
4488
}
4489
4490
4491
/**
4492
 *		Function to format a value into an amount for visual output
4493
 *		Function used into PDF and HTML pages
4494
 *
4495
 *		@param	float		$amount			Amount to format
4496
 *		@param	integer		$form			Type of format, HTML or not (not by default)
4497
 *		@param	Translate	$outlangs		Object langs for output
4498
 *		@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.
4499
 *		@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)
4500
 *		@param	int			$forcerounding	Force the number of decimal to forcerounding decimal (-1=do not force)
4501
 *		@param	string		$currency_code	To add currency symbol (''=add nothing, 'auto'=Use default currency, 'XXX'=add currency symbols for XXX currency)
4502
 *		@return	string						Chaine avec montant formate
4503
 *
4504
 *		@see	price2num					Revert function of price
4505
 */
4506
function price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
4507
{
4508
	global $langs,$conf;
4509
4510
	// Clean parameters
4511
	if (empty($amount)) $amount=0;	// To have a numeric value if amount not defined or = ''
4512
	$amount = (is_numeric($amount)?$amount:0); // Check if amount is numeric, for example, an error occured when amount value = o (letter) instead 0 (number)
4513
	if ($rounding < 0) $rounding=min($conf->global->MAIN_MAX_DECIMALS_UNIT,$conf->global->MAIN_MAX_DECIMALS_TOT);
4514
	$nbdecimal=$rounding;
4515
4516
	// Output separators by default (french)
4517
	$dec=','; $thousand=' ';
4518
4519
	// If $outlangs not forced, we use use language
4520
	if (! is_object($outlangs)) $outlangs=$langs;
4521
4522
	if ($outlangs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal")  $dec=$outlangs->transnoentitiesnoconv("SeparatorDecimal");
4523
	if ($outlangs->transnoentitiesnoconv("SeparatorThousand")!= "SeparatorThousand") $thousand=$outlangs->transnoentitiesnoconv("SeparatorThousand");
4524
	if ($thousand == 'None') $thousand='';
4525
	else if ($thousand == 'Space') $thousand=' ';
4526
	//print "outlangs=".$outlangs->defaultlang." amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
4527
4528
	//print "amount=".$amount."-";
4529
	$amount = str_replace(',','.',$amount);	// should be useless
4530
	//print $amount."-";
4531
	$datas = explode('.',$amount);
4532
	$decpart = isset($datas[1])?$datas[1]:'';
4533
	$decpart = preg_replace('/0+$/i','',$decpart);	// Supprime les 0 de fin de partie decimale
4534
	//print "decpart=".$decpart."<br>";
4535
	$end='';
4536
4537
	// We increase nbdecimal if there is more decimal than asked (to not loose information)
4538
	if (dol_strlen($decpart) > $nbdecimal) $nbdecimal=dol_strlen($decpart);
4539
	// Si on depasse max
4540
	if ($trunc && $nbdecimal > $conf->global->MAIN_MAX_DECIMALS_SHOWN)
4541
	{
4542
		$nbdecimal=$conf->global->MAIN_MAX_DECIMALS_SHOWN;
4543
		if (preg_match('/\.\.\./i',$conf->global->MAIN_MAX_DECIMALS_SHOWN))
4544
		{
4545
			// Si un affichage est tronque, on montre des ...
4546
			$end='...';
4547
		}
4548
	}
4549
4550
	// If force rounding
4551
	if ($forcerounding >= 0) $nbdecimal = $forcerounding;
4552
4553
	// Format number
4554
	$output=number_format($amount, $nbdecimal, $dec, $thousand);
4555
	if ($form)
4556
	{
4557
		$output=preg_replace('/\s/','&nbsp;',$output);
4558
		$output=preg_replace('/\'/','&#039;',$output);
4559
	}
4560
	// Add symbol of currency if requested
4561
	$cursymbolbefore=$cursymbolafter='';
4562
	if ($currency_code)
4563
	{
4564
		if ($currency_code == 'auto') $currency_code=$conf->currency;
4565
4566
		$listofcurrenciesbefore=array('USD','GBP','AUD','MXN','PEN','CNY');
4567
		if (in_array($currency_code,$listofcurrenciesbefore)) $cursymbolbefore.=$outlangs->getCurrencySymbol($currency_code);
4568
		else
4569
		{
4570
			$tmpcur=$outlangs->getCurrencySymbol($currency_code);
4571
			$cursymbolafter.=($tmpcur == $currency_code ? ' '.$tmpcur : $tmpcur);
4572
		}
4573
	}
4574
	$output=$cursymbolbefore.$output.$end.($cursymbolafter?' ':'').$cursymbolafter;
4575
4576
	return $output;
4577
}
4578
4579
/**
4580
 *	Function that return a number with universal decimal format (decimal separator is '.') from an amount typed by a user.
4581
 *	Function to use on each input amount before any numeric test or database insert
4582
 *
4583
 *	@param	float	$amount			Amount to convert/clean
4584
 *	@param	string	$rounding		''=No rounding
4585
 * 									'MU'=Round to Max unit price (MAIN_MAX_DECIMALS_UNIT)
4586
 *									'MT'=Round to Max for totals with Tax (MAIN_MAX_DECIMALS_TOT)
4587
 *									'MS'=Round to Max for stock quantity (MAIN_MAX_DECIMALS_STOCK)
4588
 *									Numeric = Nb of digits for rounding
4589
 * 	@param	int		$alreadysqlnb	Put 1 if you know that content is already universal format number
4590
 *	@return	string					Amount with universal numeric format (Example: '99.99999') or unchanged text if conversion fails. If amount is null or '', it returns ''.
4591
 *
4592
 *	@see    price					Opposite function of price2num
4593
 */
4594
function price2num($amount,$rounding='',$alreadysqlnb=0)
4595
{
4596
	global $langs,$conf;
4597
4598
	// Round PHP function does not allow number like '1,234.56' nor '1.234,56' nor '1 234,56'
4599
	// Numbers must be '1234.56'
4600
	// Decimal delimiter for PHP and database SQL requests must be '.'
4601
	$dec=','; $thousand=' ';
4602
	if ($langs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal")  $dec=$langs->transnoentitiesnoconv("SeparatorDecimal");
4603
	if ($langs->transnoentitiesnoconv("SeparatorThousand")!= "SeparatorThousand") $thousand=$langs->transnoentitiesnoconv("SeparatorThousand");
4604
	if ($thousand == 'None') $thousand='';
4605
	elseif ($thousand == 'Space') $thousand=' ';
4606
	//print "amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
4607
4608
	// Convert value to universal number format (no thousand separator, '.' as decimal separator)
4609
	if ($alreadysqlnb != 1)	// If not a PHP number or unknown, we change format
4610
	{
4611
		//print 'PP'.$amount.' - '.$dec.' - '.$thousand.' - '.intval($amount).'<br>';
4612
4613
		// Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
4614
		// to format defined by LC_NUMERIC after a calculation and we want source format to be like defined by Dolibarr setup.
4615
		if (is_numeric($amount))
4616
		{
4617
			// We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
4618
			$temps=sprintf("%0.10F",$amount-intval($amount));	// temps=0.0000000000 or 0.0000200000 or 9999.1000000000
4619
			$temps=preg_replace('/([\.1-9])0+$/','\\1',$temps); // temps=0. or 0.00002 or 9999.1
4620
			$nbofdec=max(0,dol_strlen($temps)-2);	// -2 to remove "0."
4621
			$amount=number_format($amount,$nbofdec,$dec,$thousand);
4622
		}
4623
		//print "QQ".$amount.'<br>';
4624
4625
		// Now make replace (the main goal of function)
4626
		if ($thousand != ',' && $thousand != '.') $amount=str_replace(',','.',$amount);	// To accept 2 notations for french users
4627
		$amount=str_replace(' ','',$amount);		// To avoid spaces
4628
		$amount=str_replace($thousand,'',$amount);	// Replace of thousand before replace of dec to avoid pb if thousand is .
4629
		$amount=str_replace($dec,'.',$amount);
4630
	}
4631
4632
	// Now, make a rounding if required
4633
	if ($rounding)
4634
	{
4635
		$nbofdectoround='';
4636
		if ($rounding == 'MU')     $nbofdectoround=$conf->global->MAIN_MAX_DECIMALS_UNIT;
4637
		elseif ($rounding == 'MT') $nbofdectoround=$conf->global->MAIN_MAX_DECIMALS_TOT;
4638
		elseif ($rounding == 'MS') $nbofdectoround=empty($conf->global->MAIN_MAX_DECIMALS_STOCK)?5:$conf->global->MAIN_MAX_DECIMALS_STOCK;
4639
		elseif (is_numeric($rounding))  $nbofdectoround=$rounding;
4640
		//print "RR".$amount.' - '.$nbofdectoround.'<br>';
4641
		if (dol_strlen($nbofdectoround)) $amount = round($amount,$nbofdectoround);	// $nbofdectoround can be 0.
4642
		else return 'ErrorBadParameterProvidedToFunction';
4643
		//print 'SS'.$amount.' - '.$nbofdec.' - '.$dec.' - '.$thousand.' - '.$nbofdectoround.'<br>';
4644
4645
		// Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
4646
		// to format defined by LC_NUMERIC after a calculation and we want source format to be defined by Dolibarr setup.
4647
		if (is_numeric($amount))
4648
		{
4649
			// We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
4650
			$temps=sprintf("%0.10F",$amount-intval($amount));	// temps=0.0000000000 or 0.0000200000 or 9999.1000000000
4651
			$temps=preg_replace('/([\.1-9])0+$/','\\1',$temps); // temps=0. or 0.00002 or 9999.1
4652
			$nbofdec=max(0,dol_strlen($temps)-2);	// -2 to remove "0."
4653
			$amount=number_format($amount,min($nbofdec,$nbofdectoround),$dec,$thousand);		// Convert amount to format with dolibarr dec and thousand
4654
		}
4655
		//print "TT".$amount.'<br>';
4656
4657
		// Always make replace because each math function (like round) replace
4658
		// with local values and we want a number that has a SQL string format x.y
4659
		if ($thousand != ',' && $thousand != '.') $amount=str_replace(',','.',$amount);	// To accept 2 notations for french users
4660
		$amount=str_replace(' ','',$amount);		// To avoid spaces
4661
		$amount=str_replace($thousand,'',$amount);	// Replace of thousand before replace of dec to avoid pb if thousand is .
4662
		$amount=str_replace($dec,'.',$amount);
4663
	}
4664
4665
	return $amount;
4666
}
4667
4668
4669
/**
4670
 * Output a dimension with best unit
4671
 *
4672
 * @param   float       $dimension      Dimension
4673
 * @param   int         $unit           Unit of dimension (0, -3, ...)
4674
 * @param   string      $type           'weight', 'volume', ...
4675
 * @param   Translate   $outputlangs    Translate language object
4676
 * @param   int         $round          -1 = non rounding, x = number of decimal
4677
 * @param   string      $forceunitoutput    'no' or numeric (-3, -6, ...) compared to $unit
4678
 * @return  string                      String to show dimensions
4679
 */
4680
function showDimensionInBestUnit($dimension, $unit, $type, $outputlangs, $round=-1, $forceunitoutput='no')
4681
{
4682
	require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
4683
4684
	if (($forceunitoutput == 'no' && $dimension < 1/10000) || (is_numeric($forceunitoutput) && $forceunitoutput == -6))
4685
	{
4686
		$dimension = $dimension * 1000000;
4687
		$unit = $unit - 6;
4688
	}
4689
	elseif (($forceunitoutput == 'no' && $dimension < 1/10) || (is_numeric($forceunitoutput) && $forceunitoutput == -3))
4690
	{
4691
		$dimension = $dimension * 1000;
4692
		$unit = $unit - 3;
4693
	}
4694
	elseif (($forceunitoutput == 'no' && $dimension > 100000000) || (is_numeric($forceunitoutput) && $forceunitoutput == 6))
4695
	{
4696
		$dimension = $dimension / 1000000;
4697
		$unit = $unit + 6;
4698
	}
4699
	elseif (($forceunitoutput == 'no' && $dimension > 100000) || (is_numeric($forceunitoutput) && $forceunitoutput == 3))
4700
	{
4701
		$dimension = $dimension / 1000;
4702
		$unit = $unit + 3;
4703
	}
4704
4705
	$ret=price($dimension, 0, $outputlangs, 0, 0, $round).' '.measuring_units_string($unit, $type);
4706
4707
	return $ret;
4708
}
4709
4710
4711
/**
4712
 *	Return localtax rate for a particular vat, when selling a product with vat $vatrate, from a $thirdparty_buyer to a $thirdparty_seller
4713
 *  Note: This function applies same rules than get_default_tva
4714
 *
4715
 * 	@param	float		$vatrate		        Vat rate. Can be '8.5' or '8.5 (VATCODEX)' for example
4716
 * 	@param  int			$local		         	Local tax to search and return (1 or 2 return only tax rate 1 or tax rate 2)
4717
 *  @param  Societe		$thirdparty_buyer    	Object of buying third party
4718
 *  @param	Societe		$thirdparty_seller		Object of selling third party ($mysoc if not defined)
4719
 *  @param	int			$vatnpr					If vat rate is NPR or not
4720
 * 	@return	mixed			   					0 if not found, localtax rate if found
4721
 *  @see get_default_tva
4722
 */
4723
function get_localtax($vatrate, $local, $thirdparty_buyer="", $thirdparty_seller="", $vatnpr=0)
4724
{
4725
	global $db, $conf, $mysoc;
4726
4727
	if (empty($thirdparty_seller) || ! is_object($thirdparty_seller)) $thirdparty_seller=$mysoc;
4728
4729
	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);
4730
4731
	$vatratecleaned = $vatrate;
4732
	if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg))      // If vat is "xx (yy)"
4733
	{
4734
		$vatratecleaned = trim($reg[1]);
4735
		$vatratecode = $reg[2];
4736
	}
4737
4738
	/*if ($thirdparty_buyer->country_code != $thirdparty_seller->country_code)
4739
	{
4740
		return 0;
4741
	}*/
4742
4743
	// Some test to guess with no need to make database access
4744
	if ($mysoc->country_code == 'ES') // For spain localtaxes 1 and 2, tax is qualified if buyer use local tax
4745
	{
4746
		if ($local == 1)
4747
		{
4748
			if (! $mysoc->localtax1_assuj || (string) $vatratecleaned == "0") return 0;
4749
			if ($thirdparty_seller->id == $mysoc->id)
4750
			{
4751
				if (! $thirdparty_buyer->localtax1_assuj) return 0;
4752
			}
4753
			else
4754
			{
4755
				if (! $thirdparty_seller->localtax1_assuj) return 0;
4756
			}
4757
		}
4758
4759
		if ($local == 2)
4760
		{
4761
			//if (! $mysoc->localtax2_assuj || (string) $vatratecleaned == "0") return 0;
4762
			if (! $mysoc->localtax2_assuj) return 0;		// If main vat is 0, IRPF may be different than 0.
4763
			if ($thirdparty_seller->id == $mysoc->id)
4764
			{
4765
				if (! $thirdparty_buyer->localtax2_assuj) return 0;
4766
			}
4767
			else
4768
			{
4769
				if (! $thirdparty_seller->localtax2_assuj) return 0;
4770
			}
4771
		}
4772
	}
4773
	else
4774
	{
4775
		if ($local == 1 && ! $thirdparty_seller->localtax1_assuj) return 0;
4776
		if ($local == 2 && ! $thirdparty_seller->localtax2_assuj) return 0;
4777
	}
4778
4779
	// For some country MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY is forced to on.
4780
	if (in_array($mysoc->country_code, array('ES')))
4781
	{
4782
		$conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY = 1;
4783
	}
4784
4785
	// Search local taxes
4786
	if (! empty($conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY))
4787
	{
4788
		if ($local==1)
4789
		{
4790
			if ($thirdparty_seller != $mysoc)
4791
			{
4792
				if (!isOnlyOneLocalTax($local))  // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
4793
				{
4794
					return $thirdparty_seller->localtax1_value;
4795
				}
4796
			}
4797
			else  // i am the seller
4798
			{
4799
				if (!isOnlyOneLocalTax($local))  // TODO If seller is me, why not always returning this, even if there is only one locatax vat.
4800
				{
4801
					return $conf->global->MAIN_INFO_VALUE_LOCALTAX1;
4802
				}
4803
			}
4804
		}
4805
		if ($local==2)
4806
		{
4807
			if ($thirdparty_seller != $mysoc)
4808
			{
4809
				if (!isOnlyOneLocalTax($local))  // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
4810
				// TODO We should also return value defined on thirdparty only if defined
4811
				{
4812
					return $thirdparty_seller->localtax2_value;
4813
				}
4814
			}
4815
			else  // i am the seller
4816
			{
4817
				if (in_array($mysoc->country_code, array('ES')))
4818
				{
4819
					return $thirdparty_buyer->localtax2_value;
4820
				}
4821
				else
4822
				{
4823
					return $conf->global->MAIN_INFO_VALUE_LOCALTAX2;
4824
				}
4825
			}
4826
		}
4827
	}
4828
4829
	// By default, search value of local tax on line of common tax
4830
	$sql  = "SELECT t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
4831
   	$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
4832
   	$sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$thirdparty_seller->country_code."'";
4833
   	$sql .= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
4834
   	if ($vatratecode) $sql.= " AND t.code ='".$vatratecode."'";		// If we have the code, we use it in priority
0 ignored issues
show
Bug introduced by
The variable $vatratecode does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
4835
   	else $sql.= " AND t.recuperableonly ='".$vatnpr."'";
4836
   	dol_syslog("get_localtax", LOG_DEBUG);
4837
   	$resql=$db->query($sql);
4838
4839
   	if ($resql)
4840
   	{
4841
   		$obj = $db->fetch_object($resql);
4842
   		if ($local==1) return $obj->localtax1;
4843
   		elseif ($local==2) return $obj->localtax2;
4844
	}
4845
4846
	return 0;
4847
}
4848
4849
4850
/**
4851
 * Return true if LocalTax (1 or 2) is unique.
4852
 * Example: If localtax1 is 5 on line with highest common vat rate, return true
4853
 * Example: If localtax1 is 5:8:15 on line with highest common vat rate, return false
4854
 *
4855
 * @param   int 	$local	Local tax to test (1 or 2)
4856
 * @return  boolean 		True if LocalTax have multiple values, False if not
4857
 */
4858
function isOnlyOneLocalTax($local)
4859
{
4860
	$tax=get_localtax_by_third($local);
4861
4862
	$valors=explode(":", $tax);
4863
4864
	if (count($valors)>1)
4865
	{
4866
		return false;
4867
	}
4868
	else
4869
	{
4870
		return true;
4871
	}
4872
}
4873
4874
/**
4875
 * Get values of localtaxes (1 or 2) for company country for the common vat with the highest value
4876
 *
4877
 * @param	int		$local 	LocalTax to get
4878
 * @return	number			Values of localtax
4879
 */
4880
function get_localtax_by_third($local)
4881
{
4882
	global $db, $mysoc;
4883
	$sql ="SELECT t.localtax1, t.localtax2 ";
4884
	$sql.=" FROM ".MAIN_DB_PREFIX."c_tva as t inner join ".MAIN_DB_PREFIX."c_country as c ON c.rowid=t.fk_pays";
4885
	$sql.=" WHERE c.code = '".$mysoc->country_code."' AND t.active = 1 AND t.taux=(";
4886
	$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";
4887
	$sql.="  WHERE c.code = '".$mysoc->country_code."' AND tt.active = 1";
4888
	$sql.="  )";
4889
4890
	$resql=$db->query($sql);
4891
	if ($resql)
4892
	{
4893
		$obj = $db->fetch_object($resql);
4894
		if ($local==1) return $obj->localtax1;
4895
		elseif ($local==2) return $obj->localtax2;
4896
	}
4897
4898
	return 0;
4899
}
4900
4901
4902
/**
4903
 *  Get vat main information from Id.
4904
 *  You can call getLocalTaxesFromRate after to get other fields.
4905
 *
4906
 *  @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.
4907
 *  @param	Societe	    $buyer         		Company object
4908
 *  @param	Societe	    $seller        		Company object
4909
 *  @param  int         $firstparamisid     1 if first param is id into table (use this if you can)
4910
 *  @return	array       	  				array('rowid'=> , 'code'=> ...)
4911
 *  @see getLocalTaxesFromRate
4912
 */
4913
function getTaxesFromId($vatrate, $buyer=null, $seller=null, $firstparamisid=1)
4914
{
4915
	global $db, $mysoc;
4916
4917
	dol_syslog("getTaxesFromId vat id or rate = ".$vatrate);
4918
4919
	// Search local taxes
4920
	$sql = "SELECT t.rowid, t.code, t.taux as rate, t.recuperableonly as npr, t.accountancy_code_sell, t.accountancy_code_buy";
4921
	$sql.= " FROM ".MAIN_DB_PREFIX."c_tva as t";
4922
	if ($firstparamisid) $sql.= " WHERE t.rowid = ".(int) $vatrate;
4923
	else
4924
	{
4925
		$vatratecleaned = $vatrate;
4926
		$vatratecode = '';
4927
		if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg))      // If vat is "xx (yy)"
4928
		{
4929
			$vatratecleaned = $reg[1];
4930
			$vatratecode = $reg[2];
4931
		}
4932
4933
		$sql.=", ".MAIN_DB_PREFIX."c_country as c";
4934
		/*if ($mysoc->country_code == 'ES') $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$buyer->country_code."'";    // vat in spain use the buyer country ??
4935
		else $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$seller->country_code."'";*/
4936
		$sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$seller->country_code."'";
4937
		$sql.= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
4938
		if ($vatratecode) $sql.= " AND t.code = '".$vatratecode."'";
4939
	}
4940
4941
	$resql=$db->query($sql);
4942
	if ($resql)
4943
	{
4944
		$obj = $db->fetch_object($resql);
4945
		if ($obj) return array('rowid'=>$obj->rowid, 'code'=>$obj->code, 'rate'=>$obj->rate, 'npr'=>$obj->npr, 'accountancy_code_sell'=>$obj->accountancy_code_sell, 'accountancy_code_buy'=>$obj->accountancy_code_buy);
4946
		else return array();
4947
	}
4948
	else dol_print_error($db);
4949
4950
	return array();
4951
}
4952
4953
/**
4954
 *  Get type and rate of localtaxes for a particular vat rate/country of a thirdparty.
4955
 *  This does not take into account the seller setup if subject to vat or not, only country.
4956
 *  TODO
4957
 *  This function is ALSO called to retrieve type for building PDF. Such call of function must be removed.
4958
 *  Instead this function must be called when adding a line to get the array of localtax and type, and then
4959
 *  provide it to the function calcul_price_total.
4960
 *
4961
 *  @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.
4962
 *  @param	int		    $local              Number of localtax (1 or 2, or 0 to return 1 & 2)
4963
 *  @param	Societe	    $buyer         		Company object
4964
 *  @param	Societe	    $seller        		Company object
4965
 *  @param  int         $firstparamisid     1 if first param is ID into table instead of Rate+code (use this if you can)
4966
 *  @return	array    	    				array(localtax_type1(1-6/0 if not found), rate localtax1, localtax_type2, rate localtax2, accountancycodecust, accountancycodesupp)
4967
 *  @see getTaxesFromId
4968
 */
4969
function getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid=0)
4970
{
4971
	global $db, $mysoc;
4972
4973
	dol_syslog("getLocalTaxesFromRate vatrate=".$vatrate." local=".$local);
4974
4975
	// Search local taxes
4976
	$sql  = "SELECT t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type, t.accountancy_code_sell, t.accountancy_code_buy";
4977
	$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t";
4978
	if ($firstparamisid) $sql.= " WHERE t.rowid = ".(int) $vatrate;
4979
	else
4980
	{
4981
		$vatratecleaned = $vatrate;
4982
		$vatratecode = '';
4983
		if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg))      // If vat is "xx (yy)"
4984
		{
4985
			$vatratecleaned = $reg[1];
4986
			$vatratecode = $reg[2];
4987
		}
4988
4989
		$sql.=", ".MAIN_DB_PREFIX."c_country as c";
4990
		if ($mysoc->country_code == 'ES') $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$buyer->country_code."'";    // local tax in spain use the buyer country ??
4991
		else $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$seller->country_code."'";
4992
		$sql.= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
4993
		if ($vatratecode) $sql.= " AND t.code = '".$vatratecode."'";
4994
	}
4995
4996
	$resql=$db->query($sql);
4997
	if ($resql)
4998
	{
4999
		$obj = $db->fetch_object($resql);
5000
		if ($local == 1)
5001
		{
5002
			return array($obj->localtax1_type, get_localtax($vatrate, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
5003
		}
5004
		elseif ($local == 2)
5005
		{
5006
			return array($obj->localtax2_type, get_localtax($vatrate, $local, $buyer, $seller),$obj->accountancy_code_sell, $obj->accountancy_code_buy);
5007
		}
5008
		else
5009
		{
5010
			return array($obj->localtax1_type, get_localtax($vatrate, 1, $buyer, $seller), $obj->localtax2_type, get_localtax($vatrate, 2, $buyer, $seller), $obj->accountancy_code_sell,$obj->accountancy_code_buy);
5011
		}
5012
	}
5013
5014
	return 0;
5015
}
5016
5017
/**
5018
 *	Return vat rate of a product in a particular selling country or default country vat if product is unknown
5019
 *  Function called by get_default_tva
5020
 *
5021
 *  @param	int			$idprod          	Id of product or 0 if not a predefined product
5022
 *  @param  Societe		$thirdparty_seller  Thirdparty with a ->country_code defined (FR, US, IT, ...)
5023
 *	@param	int			$idprodfournprice	Id product_fournisseur_price (for "supplier" proposal/order/invoice)
5024
 *  @return float|string   				    Vat rate to use with format 5.0 or '5.0 (XXX)'
5025
 *  @see get_product_localtax_for_country
5026
 */
5027
function get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice=0)
5028
{
5029
	global $db,$conf,$mysoc;
5030
5031
	require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
5032
5033
	$ret=0;
5034
	$found=0;
5035
5036
	if ($idprod > 0)
5037
	{
5038
		// Load product
5039
		$product=new Product($db);
5040
		$result=$product->fetch($idprod);
5041
5042
		if ($mysoc->country_code == $thirdparty_seller->country_code) // If selling country is ours
5043
		{
5044
			if ($idprodfournprice > 0)     // We want vat for product for a "supplier" object
5045
			{
5046
				$product->get_buyprice($idprodfournprice,0,0,0);
5047
				$ret=$product->vatrate_supplier;
5048
				if ($product->default_vat_code) $ret.=' ('.$product->default_vat_code.')';
5049
			}
5050
			else
5051
			{
5052
				$ret=$product->tva_tx;    // Default vat of product we defined
5053
				if ($product->default_vat_code) $ret.=' ('.$product->default_vat_code.')';
5054
			}
5055
			$found=1;
5056
		}
5057
		else
5058
		{
5059
			// TODO Read default product vat according to countrycode and product. Vat for couple countrycode/product is a feature not implemeted yet.
5060
			// May be usefull/required if hidden option SERVICE_ARE_ECOMMERCE_200238EC is on
5061
		}
5062
	}
5063
5064
	if (! $found)
5065
	{
5066
		if (empty($conf->global->MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS))
5067
		{
5068
			// If vat of product for the country not found or not defined, we return the first higher vat of country.
5069
			$sql = "SELECT t.taux as vat_rate, t.code as default_vat_code";
5070
			$sql.= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
5071
			$sql.= " WHERE t.active=1 AND t.fk_pays = c.rowid AND c.code='".$thirdparty_seller->country_code."'";
5072
			$sql.= " ORDER BY t.taux DESC, t.code ASC, t.recuperableonly ASC";
5073
			$sql.= $db->plimit(1);
5074
5075
			$resql=$db->query($sql);
5076
			if ($resql)
5077
			{
5078
				$obj=$db->fetch_object($resql);
5079
				if ($obj)
5080
				{
5081
					$ret=$obj->vat_rate;
5082
					if ($obj->default_vat_code) $ret.=' ('.$obj->default_vat_code.')';
5083
				}
5084
				$db->free($sql);
5085
			}
5086
			else dol_print_error($db);
5087
		}
5088
		else $ret=$conf->global->MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS;    // Forced value if autodetect fails
5089
	}
5090
5091
	dol_syslog("get_product_vat_for_country: ret=".$ret);
5092
	return $ret;
5093
}
5094
5095
/**
5096
 *	Return localtax vat rate of a product in a particular selling country or default country vat if product is unknown
5097
 *
5098
 *  @param	int		$idprod         		Id of product
5099
 *  @param  int		$local          		1 for localtax1, 2 for localtax 2
5100
 *  @param  Societe	$thirdparty_seller    	Thirdparty with a ->country_code defined (FR, US, IT, ...)
5101
 *  @return int             				<0 if KO, Vat rate if OK
5102
 *  @see get_product_vat_for_country
5103
 */
5104
function get_product_localtax_for_country($idprod, $local, $thirdparty_seller)
5105
{
5106
	global $db,$mysoc;
5107
5108
	if (! class_exists('Product')) {
5109
		require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
5110
	}
5111
5112
	$ret=0;
5113
	$found=0;
5114
5115
	if ($idprod > 0)
5116
	{
5117
		// Load product
5118
		$product=new Product($db);
5119
		$result=$product->fetch($idprod);
5120
5121
		if ($mysoc->country_code == $thirdparty_seller->country_code) // If selling country is ours
5122
		{
5123
			/* Not defined yet, so we don't use this
5124
			if ($local==1) $ret=$product->localtax1_tx;
5125
			elseif ($local==2) $ret=$product->localtax2_tx;
5126
			$found=1;
5127
			*/
5128
		}
5129
		else
5130
		{
5131
			// TODO Read default product vat according to countrycode and product
5132
		}
5133
	}
5134
5135
	if (! $found)
5136
	{
5137
		// If vat of product for the country not found or not defined, we return higher vat of country.
5138
		$sql = "SELECT taux as vat_rate, localtax1, localtax2";
5139
		$sql.= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
5140
		$sql.= " WHERE t.active=1 AND t.fk_pays = c.rowid AND c.code='".$thirdparty_seller->country_code."'";
5141
		$sql.= " ORDER BY t.taux DESC, t.recuperableonly ASC";
5142
		$sql.= $db->plimit(1);
5143
5144
		$resql=$db->query($sql);
5145
		if ($resql)
5146
		{
5147
			$obj=$db->fetch_object($resql);
5148
			if ($obj)
5149
			{
5150
				if ($local==1) $ret=$obj->localtax1;
5151
				elseif ($local==2) $ret=$obj->localtax2;
5152
			}
5153
		}
5154
		else dol_print_error($db);
5155
	}
5156
5157
	dol_syslog("get_product_localtax_for_country: ret=".$ret);
5158
	return $ret;
5159
}
5160
5161
/**
5162
 *	Function that return vat rate of a product line (according to seller, buyer and product vat rate)
5163
 *   Si vendeur non assujeti a TVA, TVA par defaut=0. Fin de regle.
5164
 *	 Si le (pays vendeur = pays acheteur) alors TVA par defaut=TVA du produit vendu. Fin de regle.
5165
 *	 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.
5166
 *	 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
5167
 *	 Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = entreprise avec num TVA) intra alors TVA par defaut=0. Fin de regle
5168
 *	 Sinon TVA proposee par defaut=0. Fin de regle.
5169
 *
5170
 *	@param	Societe		$thirdparty_seller    	Objet societe vendeuse
5171
 *	@param  Societe		$thirdparty_buyer   	Objet societe acheteuse
5172
 *	@param  int			$idprod					Id product
5173
 *	@param	int			$idprodfournprice		Id product_fournisseur_price (for supplier order/invoice)
5174
 *	@return float|string   				      	Vat rate to use with format 5.0 or '5.0 (XXX)', -1 if we can't guess it
5175
 *  @see get_default_npr, get_default_localtax
5176
 */
5177
function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
5178
{
5179
	global $conf;
5180
5181
	require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
5182
5183
	// Note: possible values for tva_assuj are 0/1 or franchise/reel
5184
	$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;
5185
5186
	$seller_country_code = $thirdparty_seller->country_code;
5187
	$seller_in_cee = isInEEC($thirdparty_seller);
5188
5189
	$buyer_country_code = $thirdparty_buyer->country_code;
5190
	$buyer_in_cee = isInEEC($thirdparty_buyer);
5191
5192
	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:''));
5193
5194
	// 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)
5195
	// we use the buyer VAT.
5196
	if (! empty($conf->global->SERVICE_ARE_ECOMMERCE_200238EC))
5197
	{
5198
		if ($seller_in_cee && $buyer_in_cee && ! $thirdparty_buyer->isACompany())
5199
		{
5200
			//print 'VATRULE 0';
5201
			return get_product_vat_for_country($idprod,$thirdparty_buyer,$idprodfournprice);
5202
		}
5203
	}
5204
5205
	// If seller does not use VAT
5206
	if (! $seller_use_vat)
5207
	{
5208
		//print 'VATRULE 1';
5209
		return 0;
5210
	}
5211
5212
	// 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.
5213
5214
	// Si le (pays vendeur = pays acheteur) alors la TVA par defaut=TVA du produit vendu. Fin de regle.
5215
	if (($seller_country_code == $buyer_country_code)
5216
	|| (in_array($seller_country_code,array('FR,MC')) && in_array($buyer_country_code,array('FR','MC')))) // Warning ->country_code not always defined
5217
	{
5218
		//print 'VATRULE 2';
5219
		return get_product_vat_for_country($idprod,$thirdparty_seller,$idprodfournprice);
5220
	}
5221
5222
	// 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.
5223
	// Not supported
5224
5225
	// Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = entreprise) alors TVA par defaut=0. Fin de regle
5226
	// Si (vendeur et acheteur dans Communaute europeenne) et (acheteur = particulier) alors TVA par defaut=TVA du produit vendu. Fin de regle
5227
	if (($seller_in_cee && $buyer_in_cee))
5228
	{
5229
		$isacompany=$thirdparty_buyer->isACompany();
5230
		if ($isacompany)
5231
		{
5232
			//print 'VATRULE 3';
5233
			return 0;
5234
		}
5235
		else
5236
		{
5237
			//print 'VATRULE 4';
5238
			return get_product_vat_for_country($idprod,$thirdparty_seller,$idprodfournprice);
5239
		}
5240
	}
5241
5242
	// Si (vendeur en France et acheteur hors Communaute europeenne et acheteur particulier) alors TVA par defaut=TVA du produit vendu. Fin de regle
5243
	if (! empty($conf->global->MAIN_USE_VAT_OF_PRODUCT_FOR_INDIVIDUAL_CUSTOMER_OUT_OF_EEC) && empty($buyer_in_cee) && !$thirdparty_buyer->isACompany()) {
5244
		return get_product_vat_for_country($idprod,$thirdparty_seller,$idprodfournprice);
5245
	}
5246
5247
	// Sinon la TVA proposee par defaut=0. Fin de regle.
5248
	// Rem: Cela signifie qu'au moins un des 2 est hors Communaute europeenne et que le pays differe
5249
	//print 'VATRULE 5';
5250
	return 0;
5251
}
5252
5253
5254
/**
5255
 *	Fonction qui renvoie si tva doit etre tva percue recuperable
5256
 *
5257
 *	@param	Societe		$thirdparty_seller    	Thirdparty seller
5258
 *	@param  Societe		$thirdparty_buyer   	Thirdparty buyer
5259
 *  @param  int			$idprod                 Id product
5260
 *  @param	int			$idprodfournprice		Id supplier price for product
5261
 *	@return float       			        	0 or 1
5262
 *  @see get_default_tva, get_default_localtax
5263
 */
5264
function get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
5265
{
5266
	global $db;
5267
5268
	if ($idprodfournprice > 0)
5269
	{
5270
		if (! class_exists('ProductFournisseur'))
5271
			require_once DOL_DOCUMENT_ROOT . '/fourn/class/fournisseur.product.class.php';
5272
		$prodprice = new ProductFournisseur($db);
5273
		$prodprice->fetch_product_fournisseur_price($idprodfournprice);
5274
		return $prodprice->fourn_tva_npr;
5275
	}
5276
	elseif ($idprod > 0)
5277
	{
5278
		if (! class_exists('Product'))
5279
			require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
5280
		$prod = new Product($db);
5281
		$prod->fetch($idprod);
5282
		return $prod->tva_npr;
5283
	}
5284
5285
	return 0;
5286
}
5287
5288
/**
5289
 *	Function that return localtax of a product line (according to seller, buyer and product vat rate)
5290
 *   Si vendeur non assujeti a TVA, TVA par defaut=0. Fin de regle.
5291
 *	 Si le (pays vendeur = pays acheteur) alors TVA par defaut=TVA du produit vendu. Fin de regle.
5292
 *	 Sinon TVA proposee par defaut=0. Fin de regle.
5293
 *
5294
 *	@param	Societe		$thirdparty_seller    	Thirdparty seller
5295
 *	@param  Societe		$thirdparty_buyer   	Thirdparty buyer
5296
 *  @param	int			$local					Localtax to process (1 or 2)
5297
 *	@param  int			$idprod					Id product
5298
 *	@return integer        				       	localtax, -1 si ne peut etre determine
5299
 *  @see get_default_tva, get_default_npr
5300
 */
5301
function get_default_localtax($thirdparty_seller, $thirdparty_buyer, $local, $idprod=0)
5302
{
5303
	global $mysoc;
5304
5305
	if (!is_object($thirdparty_seller)) return -1;
5306
	if (!is_object($thirdparty_buyer)) return -1;
5307
5308
	if ($local==1) // Localtax 1
5309
	{
5310
		if ($mysoc->country_code == 'ES')
5311
		{
5312
			if (is_numeric($thirdparty_buyer->localtax1_assuj) && ! $thirdparty_buyer->localtax1_assuj) return 0;
5313
		}
5314
		else
5315
		{
5316
			// Si vendeur non assujeti a Localtax1, localtax1 par default=0
5317
			if (is_numeric($thirdparty_seller->localtax1_assuj) && ! $thirdparty_seller->localtax1_assuj) return 0;
5318
			if (! is_numeric($thirdparty_seller->localtax1_assuj) && $thirdparty_seller->localtax1_assuj=='localtax1off') return 0;
5319
		}
5320
	}
5321
	elseif ($local==2) //I Localtax 2
5322
	{
5323
		// Si vendeur non assujeti a Localtax2, localtax2 par default=0
5324
		if (is_numeric($thirdparty_seller->localtax2_assuj) && ! $thirdparty_seller->localtax2_assuj) return 0;
5325
		if (! is_numeric($thirdparty_seller->localtax2_assuj) && $thirdparty_seller->localtax2_assuj=='localtax2off') return 0;
5326
	}
5327
5328
	if ($thirdparty_seller->country_code == $thirdparty_buyer->country_code)
5329
	{
5330
		return get_product_localtax_for_country($idprod, $local, $thirdparty_seller);
5331
	}
5332
5333
	return 0;
5334
}
5335
5336
/**
5337
 *	Return yes or no in current language
5338
 *
5339
 *	@param	string	$yesno			Value to test (1, 'yes', 'true' or 0, 'no', 'false')
5340
 *	@param	integer	$case			1=Yes/No, 0=yes/no, 2=Disabled checkbox, 3=Disabled checkbox + Yes/No
5341
 *	@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.
5342
 *	@return	string					HTML string
5343
 */
5344
function yn($yesno, $case=1, $color=0)
5345
{
5346
	global $langs;
5347
	$result='unknown'; $classname='';
5348
	if ($yesno == 1 || strtolower($yesno) == 'yes' || strtolower($yesno) == 'true') 	// A mettre avant test sur no a cause du == 0
5349
	{
5350
		$result=$langs->trans('yes');
5351
		if ($case == 1 || $case == 3) $result=$langs->trans("Yes");
5352
		if ($case == 2) $result='<input type="checkbox" value="1" checked disabled>';
5353
		if ($case == 3) $result='<input type="checkbox" value="1" checked disabled> '.$result;
5354
5355
		$classname='ok';
5356
	}
5357
	elseif ($yesno == 0 || strtolower($yesno) == 'no' || strtolower($yesno) == 'false')
5358
	{
5359
		$result=$langs->trans("no");
5360
		if ($case == 1 || $case == 3) $result=$langs->trans("No");
5361
		if ($case == 2) $result='<input type="checkbox" value="0" disabled>';
5362
		if ($case == 3) $result='<input type="checkbox" value="0" disabled> '.$result;
5363
5364
		if ($color == 2) $classname='ok';
5365
		else $classname='error';
5366
	}
5367
	if ($color) return '<font class="'.$classname.'">'.$result.'</font>';
5368
	return $result;
5369
}
5370
5371
5372
/**
5373
 *	Return a path to have a the directory according to object where files are stored.
5374
 *  New usage:       $conf->module->multidir_output[$object->entity].'/'.get_exdir(0, 0, 0, 1, $object, $modulepart)
5375
 *         or:       $conf->module->dir_output.'/'.get_exdir(0, 0, 0, 1, $object, $modulepart)     if multidir_output not defined.
5376
 *  Example our with new usage:       $object is invoice -> 'INYYMM-ABCD'
5377
 *  Example our with old usage:       '015' with level 3->"0/1/5/", '015' with level 1->"5/", 'ABC-1' with level 3 ->"0/0/1/"
5378
 *
5379
 *	@param	string	$num            Id of object (deprecated, $object will be used in future)
5380
 *	@param  int		$level		    Level of subdirs to return (1, 2 or 3 levels). (deprecated, global option will be used in future)
5381
 * 	@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)
5382
 *  @param  int		$withoutslash   0=With slash at end (except if '/', we return ''), 1=without slash at end
5383
 *  @param	Object	$object			Object
5384
 *  @param	string	$modulepart		Type of object ('invoice_supplier, 'donation', 'invoice', ...')
5385
 *  @return	string					Dir to use ending. Example '' or '1/' or '1/2/'
5386
 */
5387
function get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart)
5388
{
5389
	global $conf;
5390
5391
	$path = '';
5392
5393
	$arrayforoldpath=array('cheque','user','category','holiday','supplier_invoice','invoice_supplier','mailing','supplier_payment');
5394
	if (! empty($conf->global->PRODUCT_USE_OLD_PATH_FOR_PHOTO)) $arrayforoldpath[]='product';
5395
	if (! empty($level) && in_array($modulepart, $arrayforoldpath))
5396
	{
5397
		// This part should be removed once all code is using "get_exdir" to forge path, with all parameters provided.
5398
		if (empty($alpha)) $num = preg_replace('/([^0-9])/i','',$num);
5399
		else $num = preg_replace('/^.*\-/i','',$num);
5400
		$num = substr("000".$num, -$level);
5401
		if ($level == 1) $path = substr($num,0,1);
5402
		if ($level == 2) $path = substr($num,1,1).'/'.substr($num,0,1);
5403
		if ($level == 3) $path = substr($num,2,1).'/'.substr($num,1,1).'/'.substr($num,0,1);
5404
	}
5405
	else
5406
	{
5407
		// TODO
5408
		// We will enhance here a common way of forging path for document storage
5409
		// Here, object->id, object->ref and modulepart are required.
5410
		//var_dump($modulepart);
5411
		if (in_array($modulepart, array('thirdparty','contact','member','propal','proposal','commande','order','facture','invoice',
5412
			'supplier_order','supplier_proposal','shipment','contract','expensereport')))
5413
		{
5414
			$path=($object->ref?$object->ref:$object->id);
5415
		}
5416
	}
5417
5418
	if (empty($withoutslash) && ! empty($path)) $path.='/';
5419
5420
	return $path;
5421
}
5422
5423
/**
5424
 *	Creation of a directory (this can create recursive subdir)
5425
 *
5426
 *	@param	string	$dir		Directory to create (Separator must be '/'. Example: '/mydir/mysubdir')
5427
 *	@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)
5428
 *  @param	int		$newmask	Mask for new file (Defaults to $conf->global->MAIN_UMASK or 0755 if unavailable). Example: '0444'
5429
 *	@return int         		< 0 if KO, 0 = already exists, > 0 if OK
5430
 */
5431
function dol_mkdir($dir, $dataroot='', $newmask=null)
5432
{
5433
	global $conf;
5434
5435
	dol_syslog("functions.lib::dol_mkdir: dir=".$dir,LOG_INFO);
5436
5437
	$dir_osencoded=dol_osencode($dir);
5438
	if (@is_dir($dir_osencoded)) return 0;
5439
5440
	$nberr=0;
5441
	$nbcreated=0;
5442
5443
	$ccdir='';
5444
	if (! empty($dataroot)) {
5445
		// Remove data root from loop
5446
		$dir = str_replace($dataroot.'/', '', $dir);
5447
		$ccdir = $dataroot.'/';
5448
	}
5449
5450
	$cdir = explode("/", $dir);
5451
	$num=count($cdir);
5452
	for ($i = 0; $i < $num; $i++)
5453
	{
5454
		if ($i > 0) $ccdir .= '/'.$cdir[$i];
5455
		else $ccdir .= $cdir[$i];
5456
		if (preg_match("/^.:$/",$ccdir,$regs)) continue;	// Si chemin Windows incomplet, on poursuit par rep suivant
5457
5458
		// Attention, le is_dir() peut echouer bien que le rep existe.
5459
		// (ex selon config de open_basedir)
5460
		if ($ccdir)
5461
		{
5462
			$ccdir_osencoded=dol_osencode($ccdir);
5463
			if (! @is_dir($ccdir_osencoded))
5464
			{
5465
				dol_syslog("functions.lib::dol_mkdir: Directory '".$ccdir."' does not exists or is outside open_basedir PHP setting.",LOG_DEBUG);
5466
5467
				umask(0);
5468
				$dirmaskdec=octdec($newmask);
5469
				if (empty($newmask)) {
5470
					$dirmaskdec = empty( $conf->global->MAIN_UMASK ) ? octdec( '0755' ) : octdec( $conf->global->MAIN_UMASK );
5471
				}
5472
				$dirmaskdec |= octdec('0111');  // Set x bit required for directories
5473
				if (! @mkdir($ccdir_osencoded, $dirmaskdec))
5474
				{
5475
					// Si le is_dir a renvoye une fausse info, alors on passe ici.
5476
					dol_syslog("functions.lib::dol_mkdir: Fails to create directory '".$ccdir."' or directory already exists.",LOG_WARNING);
5477
					$nberr++;
5478
				}
5479
				else
5480
				{
5481
					dol_syslog("functions.lib::dol_mkdir: Directory '".$ccdir."' created",LOG_DEBUG);
5482
					$nberr=0;	// On remet a zero car si on arrive ici, cela veut dire que les echecs precedents peuvent etre ignore
5483
					$nbcreated++;
5484
				}
5485
			}
5486
			else
5487
			{
5488
				$nberr=0;	// On remet a zero car si on arrive ici, cela veut dire que les echecs precedents peuvent etre ignores
5489
			}
5490
		}
5491
	}
5492
	return ($nberr ? -$nberr : $nbcreated);
5493
}
5494
5495
5496
/**
5497
 *	Return picto saying a field is required
5498
 *
5499
 *	@return  string		Chaine avec picto obligatoire
5500
 */
5501
function picto_required()
5502
{
5503
	return '<span class="fieldrequired">*</span>';
5504
}
5505
5506
5507
/**
5508
 *	Clean a string from all HTML tags and entities.
5509
 *  This function differs from strip_tags because:
5510
 *  - <br> are replaced with \n if removelinefeed=0 or 1
5511
 *  - if entities are found, they are decoded BEFORE the strip
5512
 *  - you can decide to convert line feed into a space
5513
 *
5514
 *	@param	string	$stringtoclean		String to clean
5515
 *	@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..."
5516
 *  @param  string	$pagecodeto      	Encoding of input/output string
5517
 *  @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)
5518
 *	@return string	    				String cleaned
5519
 *
5520
 * 	@see	dol_escape_htmltag strip_tags dol_string_onlythesehtmltags dol_string_neverthesehtmltags
5521
 */
5522
function dol_string_nohtmltag($stringtoclean, $removelinefeed=1, $pagecodeto='UTF-8', $strip_tags=0)
5523
{
5524
	if ($removelinefeed == 2) $stringtoclean = preg_replace('/<br[^>]*>\n+/ims', '<br>', $stringtoclean);
5525
	$temp = preg_replace('/<br[^>]*>/i', "\n", $stringtoclean);
5526
5527
	if ($strip_tags) {
5528
		$temp = strip_tags($temp);
5529
	} else {
5530
		$pattern = "/<[^<>]+>/";
5531
		// Exemple of $temp: <a href="/myurl" title="<u>A title</u>">0000-021</a>
5532
		$temp = preg_replace($pattern,"",$temp);    // pass 1
5533
		// $temp after pass 1: <a href="/myurl" title="A title">0000-021
5534
		$temp = preg_replace($pattern,"",$temp);    // pass 2
5535
		// $temp after pass 2: 0000-021
5536
	}
5537
5538
	$temp = dol_html_entity_decode($temp,ENT_COMPAT,$pagecodeto);
5539
5540
	// Supprime aussi les retours
5541
	if ($removelinefeed == 1) $temp=str_replace(array("\r\n","\r","\n")," ",$temp);
5542
5543
	// et les espaces doubles
5544
	while (strpos($temp,"  "))
5545
	{
5546
		$temp = str_replace("  "," ",$temp);
5547
	}
5548
5549
	return trim($temp);
5550
}
5551
5552
/**
5553
 *	Clean a string to keep only desirable HTML tags.
5554
 *
5555
 *	@param	string	$stringtoclean		String to clean
5556
 *	@return string	    				String cleaned
5557
 *
5558
 * 	@see	dol_escape_htmltag strip_tags dol_string_nohtmltag dol_string_neverthesehtmltags
5559
 */
5560
function dol_string_onlythesehtmltags($stringtoclean)
5561
{
5562
	$allowed_tags = array(
5563
		"html", "head", "meta", "body", "article", "a", "b", "br", "div", "em", "font", "img", "ins", "hr", "i", "li", "link",
5564
		"ol", "p", "s", "section", "span", "strong", "title",
5565
		"table", "tr", "th", "td", "u", "ul"
5566
	);
5567
5568
	$allowed_tags_string = join("><", $allowed_tags);
5569
	$allowed_tags_string = preg_replace('/^>/','',$allowed_tags_string);
5570
	$allowed_tags_string = preg_replace('/<$/','',$allowed_tags_string);
5571
5572
	$temp = strip_tags($stringtoclean, $allowed_tags_string);
5573
5574
	return $temp;
5575
}
5576
5577
/**
5578
 *	Clean a string from some undesirable HTML tags.
5579
 *
5580
 *	@param	string	$stringtoclean		String to clean
5581
 *  @param	array	$disallowed_tags	Array of tags not allowed
5582
 *	@return string	    				String cleaned
5583
 *
5584
 * 	@see	dol_escape_htmltag strip_tags dol_string_nohtmltag dol_string_onlythesehtmltags
5585
 */
5586
function dol_string_neverthesehtmltags($stringtoclean, $disallowed_tags=array('textarea'))
5587
{
5588
	$temp = $stringtoclean;
5589
	foreach($disallowed_tags as $tagtoremove)
5590
	{
5591
		$temp = preg_replace('/<\/?'.$tagtoremove.'>/', '', $temp);
5592
		$temp = preg_replace('/<\/?'.$tagtoremove.'\s+[^>]*>/', '', $temp);
5593
	}
5594
	return $temp;
5595
}
5596
5597
5598
/**
5599
 * Return first line of text. Cut will depends if content is HTML or not.
5600
 *
5601
 * @param 	string	$text		Input text
5602
 * @param	int		$nboflines  Nb of lines to get (default is 1 = first line only)
5603
 * @return	string				Output text
5604
 * @see dol_nboflines_bis, dol_string_nohtmltag, dol_escape_htmltag
5605
 */
5606
function dolGetFirstLineOfText($text, $nboflines=1)
5607
{
5608
	if ($nboflines == 1)
5609
	{
5610
		if (dol_textishtml($text))
5611
		{
5612
			$firstline=preg_replace('/<br[^>]*>.*$/s','',$text);		// The s pattern modifier means the . can match newline characters
5613
			$firstline=preg_replace('/<div[^>]*>.*$/s','',$firstline);	// The s pattern modifier means the . can match newline characters
5614
		}
5615
		else
5616
		{
5617
			$firstline=preg_replace('/[\n\r].*/','',$text);
5618
		}
5619
		return $firstline.((strlen($firstline) != strlen($text))?'...':'');
5620
	}
5621
	else
5622
	{
5623
		$ishtml=0;
5624
		if (dol_textishtml($text))
5625
		{
5626
			$text=preg_replace('/\n/','',$text);
5627
			$ishtml=1;
5628
			$repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
5629
		}
5630
		else
5631
		{
5632
			$repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
5633
		}
5634
5635
		$text = strtr($text, $repTable);
5636
		if ($charset == 'UTF-8') { $pattern = '/(<br[^>]*>)/Uu'; }	// /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
0 ignored issues
show
Bug introduced by
The variable $charset does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
5637
		else $pattern = '/(<br[^>]*>)/U';							// /U is to have UNGREEDY regex to limit to one html tag.
5638
		$a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
5639
5640
		$firstline='';
5641
		$i=0;
5642
		$nba = count($a);	// 2x nb of lines in $a because $a contains also a line for each new line separator
5643
		while (($i < $nba) && ($i < ($nboflines * 2)))
5644
		{
5645
			if ($i % 2 == 0) $firstline .= $a[$i];
5646
			elseif (($i < (($nboflines * 2) - 1)) && ($i < ($nba - 1))) $firstline .= ($ishtml?"<br>\n":"\n");
5647
			$i++;
5648
		}
5649
		unset($a);
5650
		return $firstline.(($i < $nba)?'...':'');
5651
	}
5652
}
5653
5654
5655
/**
5656
 * Replace CRLF in string with a HTML BR tag
5657
 *
5658
 * @param	string	$stringtoencode		String to encode
5659
 * @param	int     $nl2brmode			0=Adding br before \n, 1=Replacing \n by br
5660
 * @param   bool	$forxml             false=Use <br>, true=Use <br />
5661
 * @return	string						String encoded
5662
 * @see dol_nboflines, dolGetFirstLineOfText
5663
 */
5664
function dol_nl2br($stringtoencode,$nl2brmode=0,$forxml=false)
5665
{
5666
	if (!$nl2brmode) {
5667
		return nl2br($stringtoencode, $forxml);
5668
	} else {
5669
		$ret=preg_replace('/(\r\n|\r|\n)/i', ($forxml?'<br />':'<br>'), $stringtoencode);
5670
		return $ret;
5671
	}
5672
}
5673
5674
5675
/**
5676
 *	This function is called to encode a string into a HTML string but differs from htmlentities because
5677
 * 	a detection is done before to see if text is already HTML or not. Also, all entities but &,<,> are converted.
5678
 *  This permits to encode special chars to entities with no double encoding for already encoded HTML strings.
5679
 * 	This function also remove last EOL or BR if $removelasteolbr=1 (default).
5680
 *  For PDF usage, you can show text by 2 ways:
5681
 *              - writeHTMLCell -> param must be encoded into HTML.
5682
 *              - MultiCell -> param must not be encoded into HTML.
5683
 *              Because writeHTMLCell convert also \n into <br>, if function
5684
 *              is used to build PDF, nl2brmode must be 1.
5685
 *
5686
 *	@param	string	$stringtoencode		String to encode
5687
 *	@param	int		$nl2brmode			0=Adding br before \n, 1=Replacing \n by br (for use with FPDF writeHTMLCell function for example)
5688
 *  @param  string	$pagecodefrom       Pagecode stringtoencode is encoded
5689
 *  @param	int		$removelasteolbr	1=Remove last br or lasts \n (default), 0=Do nothing
5690
 *  @return	string						String encoded
5691
 */
5692
function dol_htmlentitiesbr($stringtoencode, $nl2brmode=0, $pagecodefrom='UTF-8', $removelasteolbr=1)
5693
{
5694
	$newstring=$stringtoencode;
5695
	if (dol_textishtml($stringtoencode))	// Check if text is already HTML or not
5696
	{
5697
		$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.
5698
		if ($removelasteolbr) $newstring=preg_replace('/<br>$/i','',$newstring);	// Remove last <br> (remove only last one)
5699
		$newstring=strtr($newstring,array('&'=>'__and__','<'=>'__lt__','>'=>'__gt__','"'=>'__dquot__'));
5700
		$newstring=dol_htmlentities($newstring,ENT_COMPAT,$pagecodefrom);	// Make entity encoding
5701
		$newstring=strtr($newstring,array('__and__'=>'&','__lt__'=>'<','__gt__'=>'>','__dquot__'=>'"'));
5702
	}
5703
	else
5704
	{
5705
		if ($removelasteolbr) $newstring=preg_replace('/(\r\n|\r|\n)$/i','',$newstring);	// Remove last \n (may remove several)
5706
		$newstring=dol_nl2br(dol_htmlentities($newstring,ENT_COMPAT,$pagecodefrom),$nl2brmode);
5707
	}
5708
	// Other substitutions that htmlentities does not do
5709
	//$newstring=str_replace(chr(128),'&euro;',$newstring);	// 128 = 0x80. Not in html entity table.     // Seems useles with TCPDF. Make bug with UTF8 languages
5710
	return $newstring;
5711
}
5712
5713
/**
5714
 *	This function is called to decode a HTML string (it decodes entities and br tags)
5715
 *
5716
 *	@param	string	$stringtodecode		String to decode
5717
 *	@param	string	$pagecodeto			Page code for result
5718
 *	@return	string						String decoded
5719
 */
5720
function dol_htmlentitiesbr_decode($stringtodecode,$pagecodeto='UTF-8')
5721
{
5722
	$ret=dol_html_entity_decode($stringtodecode,ENT_COMPAT,$pagecodeto);
5723
	$ret=preg_replace('/'."\r\n".'<br(\s[\sa-zA-Z_="]*)?\/?>/i',"<br>",$ret);
5724
	$ret=preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>'."\r\n".'/i',"\r\n",$ret);
5725
	$ret=preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>'."\n".'/i',"\n",$ret);
5726
	$ret=preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>/i',"\n",$ret);
5727
	return $ret;
5728
}
5729
5730
/**
5731
 *	This function remove all ending \n and br at end
5732
 *
5733
 *	@param	string	$stringtodecode		String to decode
5734
 *	@return	string						String decoded
5735
 */
5736
function dol_htmlcleanlastbr($stringtodecode)
5737
{
5738
	$ret=preg_replace('/(<br>|<br(\s[\sa-zA-Z_="]*)?\/?>|'."\n".'|'."\r".')+$/i',"",$stringtodecode);
5739
	return $ret;
5740
}
5741
5742
/**
5743
 * Replace html_entity_decode functions to manage errors
5744
 *
5745
 * @param   string	$a		Operand a
5746
 * @param   string	$b		Operand b (ENT_QUOTES=convert simple and double quotes)
5747
 * @param   string	$c		Operand c
5748
 * @return  string			String decoded
5749
 */
5750
function dol_html_entity_decode($a,$b,$c='UTF-8')
5751
{
5752
	return html_entity_decode($a,$b,$c);
5753
}
5754
5755
/**
5756
 * Replace htmlentities functions.
5757
 * Goal of this function is to be sure to have default values of htmlentities that match what we need.
5758
 *
5759
 * @param   string  $string         The input string to encode
5760
 * @param   int     $flags          Flags (see PHP doc above)
5761
 * @param   string  $encoding       Encoding page code
5762
 * @param   bool    $double_encode  When double_encode is turned off, PHP will not encode existing html entities
5763
 * @return  string  $ret            Encoded string
5764
 */
5765
function dol_htmlentities($string, $flags=null, $encoding='UTF-8', $double_encode=false)
5766
{
5767
	return htmlentities($string, $flags, $encoding, $double_encode);
5768
}
5769
5770
/**
5771
 *	Check if a string is a correct iso string
5772
 *	If not, it will we considered not HTML encoded even if it is by FPDF.
5773
 *	Example, if string contains euro symbol that has ascii code 128
5774
 *
5775
 *	@param	string	$s      String to check
5776
 *	@return	int     		0 if bad iso, 1 if good iso
5777
 */
5778
function dol_string_is_good_iso($s)
5779
{
5780
	$len=dol_strlen($s);
5781
	$ok=1;
5782
	for($scursor=0;$scursor<$len;$scursor++)
5783
	{
5784
		$ordchar=ord($s{$scursor});
5785
		//print $scursor.'-'.$ordchar.'<br>';
5786
		if ($ordchar < 32 && $ordchar != 13 && $ordchar != 10) { $ok=0; break; }
5787
		if ($ordchar > 126 && $ordchar < 160) { $ok=0; break; }
5788
	}
5789
	return $ok;
5790
}
5791
5792
5793
/**
5794
 *	Return nb of lines of a clear text
5795
 *
5796
 *	@param	string	$s			String to check
5797
 * 	@param	int     $maxchar	Not yet used
5798
 *	@return	int					Number of lines
5799
 *  @see	dol_nboflines_bis, dolGetFirstLineOfText
5800
 */
5801
function dol_nboflines($s,$maxchar=0)
5802
{
5803
	if ($s == '') return 0;
5804
	$arraystring=explode("\n",$s);
5805
	$nb=count($arraystring);
5806
5807
	return $nb;
5808
}
5809
5810
5811
/**
5812
 *	Return nb of lines of a formated text with \n and <br> (WARNING: string must not have mixed \n and br separators)
5813
 *
5814
 *	@param	string	$text      		Text
5815
 *	@param	int		$maxlinesize  	Largeur de ligne en caracteres (ou 0 si pas de limite - defaut)
5816
 * 	@param	string	$charset		Give the charset used to encode the $text variable in memory.
5817
 *	@return int						Number of lines
5818
 *	@see	dol_nboflines, dolGetFirstLineOfText
5819
 */
5820
function dol_nboflines_bis($text,$maxlinesize=0,$charset='UTF-8')
5821
{
5822
	$repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
5823
	if (dol_textishtml($text)) $repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
5824
5825
	$text = strtr($text, $repTable);
5826
	if ($charset == 'UTF-8') { $pattern = '/(<br[^>]*>)/Uu'; }	// /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
5827
	else $pattern = '/(<br[^>]*>)/U';							// /U is to have UNGREEDY regex to limit to one html tag.
5828
	$a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
5829
5830
	$nblines = (int) floor((count($a)+1)/2);
5831
	// count possible auto line breaks
5832
	if($maxlinesize)
5833
	{
5834
		foreach ($a as $line)
5835
		{
5836
			if (dol_strlen($line)>$maxlinesize)
5837
			{
5838
				//$line_dec = html_entity_decode(strip_tags($line));
5839
				$line_dec = html_entity_decode($line);
5840
				if(dol_strlen($line_dec)>$maxlinesize)
5841
				{
5842
					$line_dec=wordwrap($line_dec,$maxlinesize,'\n',true);
5843
					$nblines+=substr_count($line_dec,'\n');
5844
				}
5845
			}
5846
		}
5847
	}
5848
5849
	unset($a);
5850
	return $nblines;
5851
}
5852
5853
/**
5854
 *	 Same function than microtime in PHP 5 but compatible with PHP4
5855
 *
5856
 * @return		float		Time (millisecondes) with microsecondes in decimal part
5857
 * @deprecated Dolibarr does not support PHP4, you should use native function
5858
 * @see microtime()
5859
 */
5860
function dol_microtime_float()
5861
{
5862
	dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);
5863
5864
	return microtime(true);
5865
}
5866
5867
/**
5868
 *	Return if a text is a html content
5869
 *
5870
 *	@param	string	$msg		Content to check
5871
 *	@param	int		$option		0=Full detection, 1=Fast check
5872
 *	@return	boolean				true/false
5873
 *	@see	dol_concatdesc
5874
 */
5875
function dol_textishtml($msg,$option=0)
5876
{
5877
	if ($option == 1)
5878
	{
5879
		if (preg_match('/<html/i',$msg))				return true;
5880
		elseif (preg_match('/<body/i',$msg))			return true;
5881
		elseif (preg_match('/<br/i',$msg))				return true;
5882
		return false;
5883
	}
5884
	else
5885
	{
5886
		if (preg_match('/<html/i',$msg))				return true;
5887
		elseif (preg_match('/<body/i',$msg))			return true;
5888
		elseif (preg_match('/<(b|em|i|u)>/i',$msg))		return true;
5889
		elseif (preg_match('/<br\/>/i',$msg))	  return true;
5890
		elseif (preg_match('/<(br|div|font|li|p|span|strong|table)>/i',$msg)) 	  return true;
5891
		elseif (preg_match('/<(br|div|font|li|p|span|strong|table)\s+[^<>\/]*>/i',$msg)) return true;
5892
		elseif (preg_match('/<(br|div|font|li|p|span|strong|table)\s+[^<>\/]*\/>/i',$msg)) return true;
5893
		elseif (preg_match('/<img\s+[^<>]*src[^<>]*>/i',$msg)) return true;	// must accept <img src="http://example.com/aaa.png" />
5894
		elseif (preg_match('/<a\s+[^<>]*href[^<>]*>/i',$msg)) return true;	// must accept <a href="http://example.com/aaa.png" />
5895
		elseif (preg_match('/<h[0-9]>/i',$msg))			return true;
5896
		elseif (preg_match('/&[A-Z0-9]{1,6};/i',$msg))	return true;    // Html entities names (http://www.w3schools.com/tags/ref_entities.asp)
5897
		elseif (preg_match('/&#[0-9]{2,3};/i',$msg))	return true;    // Html entities numbers (http://www.w3schools.com/tags/ref_entities.asp)
5898
5899
		return false;
5900
	}
5901
}
5902
5903
/**
5904
 *  Concat 2 descriptions with a new line between them (second operand after first one with appropriate new line separator)
5905
 *  text1 html + text2 html => text1 + '<br>' + text2
5906
 *  text1 html + text2 txt  => text1 + '<br>' + dol_nl2br(text2)
5907
 *  text1 txt  + text2 html => dol_nl2br(text1) + '<br>' + text2
5908
 *  text1 txt  + text2 txt  => text1 + '\n' + text2
5909
 *
5910
 *  @param	string	$text1		Text 1
5911
 *  @param	string	$text2		Text 2
5912
 *  @param  bool	$forxml     false=Use <br>, true=Use <br />
5913
 *  @return	string				Text 1 + new line + Text2
5914
 *  @see    dol_textishtml
5915
 */
5916
function dol_concatdesc($text1,$text2,$forxml=false)
5917
{
5918
	$ret='';
5919
	$ret.= (! dol_textishtml($text1) && dol_textishtml($text2))?dol_nl2br($text1, 0, $forxml):$text1;
5920
	$ret.= (! empty($text1) && ! empty($text2)) ? ((dol_textishtml($text1) || dol_textishtml($text2))?($forxml?"<br \>\n":"<br>\n") : "\n") : "";
5921
	$ret.= (dol_textishtml($text1) && ! dol_textishtml($text2))?dol_nl2br($text2, 0, $forxml):$text2;
5922
	return $ret;
5923
}
5924
5925
5926
/**
5927
 * Return array of possible common substitutions. This includes several families like: 'system', 'mycompany', 'object', 'objectamount', 'date', 'user'
5928
 *
5929
 * @param	Translate	$outputlangs	Output language
5930
 * @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)
5931
 * @param   array       $exclude        Array of family keys we want to exclude. For example array('system', 'mycompany', 'object', 'objectamount', 'date', 'user', ...)
5932
 * @param   Object      $object         Object for keys on object
5933
 * @return	array						Array of substitutions
5934
 * @see setSubstitFromObject
5935
 */
5936
function getCommonSubstitutionArray($outputlangs, $onlykey=0, $exclude=null, $object=null)
5937
{
5938
	global $db, $conf, $mysoc, $user, $extrafields;
5939
5940
	$substitutionarray=array();
5941
5942
	if (empty($exclude) || ! in_array('user', $exclude))
5943
	{
5944
		// Add SIGNATURE into substitutionarray first, so, when we will make the substitution,
5945
		// this will include signature content first and then replace var found into content of signature
5946
		$signature = $user->signature;
5947
		$substitutionarray=array_merge($substitutionarray, array(
5948
		'__USER_SIGNATURE__' => (string) (($signature && empty($conf->global->MAIN_MAIL_DO_NOT_USE_SIGN)) ? ($onlykey == 2 ? dol_trunc(dol_string_nohtmltag($signature), 30) : $signature) : '')
5949
		)
5950
			);
5951
		// For backward compatibility
5952
		if ($onlykey != 2)
5953
		{
5954
			$substitutionarray['__SIGNATURE__'] = (string) (($signature && empty($conf->global->MAIN_MAIL_DO_NOT_USE_SIGN)) ? ($onlykey == 2 ? dol_trunc(dol_string_nohtmltag($signature), 30) : $signature) : '');
5955
		}
5956
5957
		$substitutionarray=array_merge($substitutionarray, array(
5958
		'__USER_ID__' => (string) $user->id,
5959
		'__USER_LOGIN__' => (string) $user->login,
5960
		'__USER_LASTNAME__' => (string) $user->lastname,
5961
		'__USER_FIRSTNAME__' => (string) $user->firstname,
5962
		'__USER_FULLNAME__' => (string) $user->getFullName($outputlangs),
5963
		'__USER_SUPERVISOR_ID__' => (string) ($user->fk_user ? $user->fk_user : '0'),
5964
		'__USER_REMOTE_IP__' => (string) $_SERVER['REMOTE_ADDR']
5965
		)
5966
			);
5967
	}
5968
	if ((empty($exclude) || ! in_array('mycompany', $exclude)) && is_object($mysoc))
5969
	{
5970
		$substitutionarray=array_merge($substitutionarray, array(
5971
			'__MYCOMPANY_NAME__'    => $mysoc->name,
5972
			'__MYCOMPANY_EMAIL__'   => $mysoc->email,
5973
			'__MYCOMPANY_PROFID1__' => $mysoc->idprof1,
5974
			'__MYCOMPANY_PROFID2__' => $mysoc->idprof2,
5975
			'__MYCOMPANY_PROFID3__' => $mysoc->idprof3,
5976
			'__MYCOMPANY_PROFID4__' => $mysoc->idprof4,
5977
			'__MYCOMPANY_PROFID5__' => $mysoc->idprof5,
5978
			'__MYCOMPANY_PROFID6__' => $mysoc->idprof6,
5979
			'__MYCOMPANY_CAPITAL__' => $mysoc->capital,
5980
			'__MYCOMPANY_FULLADDRESS__' => $mysoc->getFullAddress(1, ', '),
5981
			'__MYCOMPANY_ADDRESS__' => $mysoc->address,
5982
			'__MYCOMPANY_ZIP__'     => $mysoc->zip,
5983
			'__MYCOMPANY_TOWN__'    => $mysoc->town,
5984
			'__MYCOMPANY_COUNTRY__'    => $mysoc->country,
5985
			'__MYCOMPANY_COUNTRY_ID__' => $mysoc->country_id,
5986
			'__MYCOMPANY_CURRENCY_CODE__' => $conf->currency
5987
		));
5988
	}
5989
5990
	if (($onlykey || is_object($object)) && (empty($exclude) || ! in_array('object', $exclude)))
5991
	{
5992
		if ($onlykey)
5993
		{
5994
			$substitutionarray['__ID__'] = '__ID__';
5995
			$substitutionarray['__REF__'] = '__REF__';
5996
			$substitutionarray['__REFCLIENT__'] = '__REFCLIENT__';
5997
			$substitutionarray['__REFSUPPLIER__'] = '__REFSUPPLIER__';
5998
			$substitutionarray['__EXTRAFIELD_XXX__'] = '__EXTRAFIELD_XXX__';
5999
6000
			$substitutionarray['__THIRDPARTY_ID__'] = '__THIRDPARTY_ID__';
6001
			$substitutionarray['__THIRDPARTY_NAME__'] = '__THIRDPARTY_NAME__';
6002
			$substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = '__THIRDPARTY_NAME_ALIAS__';
6003
			$substitutionarray['__THIRDPARTY_EMAIL__'] = '__THIRDPARTY_EMAIL__';
6004
6005
			if (is_object($object) && $object->element == 'member')
6006
			{
6007
				$substitutionarray['__MEMBER_ID__'] = '__MEMBER_ID__';
6008
				$substitutionarray['__MEMBER_CIVILITY__'] = '__MEMBER_CIVILITY__';
6009
				$substitutionarray['__MEMBER_FIRSTNAME__'] = '__MEMBER_FIRSTNAME__';
6010
				$substitutionarray['__MEMBER_LASTNAME__'] = '__MEMBER_LASTNAME__';
6011
			}
6012
			$substitutionarray['__PROJECT_ID__'] = '__PROJECT_ID__';
6013
			$substitutionarray['__PROJECT_REF__'] = '__PROJECT_REF__';
6014
			$substitutionarray['__PROJECT_NAME__'] = '__PROJECT_NAME__';
6015
6016
			$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = 'Highest date planned for a service start';
6017
			$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = 'Highest date and hour planned for service start';
6018
			$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = 'Lowest data for planned expiration of service';
6019
			$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = 'Lowest date and hour for planned expiration of service';
6020
6021
			$substitutionarray['__ONLINE_PAYMENT_URL__'] = 'UrlToPayOnlineIfApplicable';
6022
			$substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = 'TextAndUrlToPayOnlineIfApplicable';
6023
			$substitutionarray['__SECUREKEYPAYMENT__'] = 'Security key (if key is not unique per record)';
6024
			$substitutionarray['__SECUREKEYPAYMENT_MEMBER__'] = 'Security key for payment on a member subscription (one key per member)';
6025
			$substitutionarray['__SECUREKEYPAYMENT_ORDER__'] = 'Security key for payment on an order';
6026
			$substitutionarray['__SECUREKEYPAYMENT_INVOICE__'] = 'Security key for payment on an invoice';
6027
			$substitutionarray['__SECUREKEYPAYMENT_CONTRACTLINE__'] = 'Security key for payment on a a service';
6028
6029
			if (is_object($object) && $object->element == 'shipping')
6030
			{
6031
				$substitutionarray['__SHIPPINGTRACKNUM__']='Shipping tacking number';
6032
				$substitutionarray['__SHIPPINGTRACKNUMURL__']='Shipping tracking url';
6033
			}
6034
		}
6035
		else
6036
		{
6037
			$substitutionarray['__ID__'] = $object->id;
6038
			$substitutionarray['__REF__'] = $object->ref;
6039
			$substitutionarray['__REFCLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : ''));
6040
			$substitutionarray['__REFSUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : '');
6041
6042
			// TODO Use this ?
6043
			$msgishtml = 0;
6044
6045
			$birthday = dol_print_date($object->birth,'day');
6046
6047
			$substitutionarray['__MEMBER_ID__']=$object->id;
6048
			if (method_exists($object, 'getCivilityLabel')) $substitutionarray['__MEMBER_CIVILITY__'] = $object->getCivilityLabel();
6049
			$substitutionarray['__MEMBER_FIRSTNAME__']=$msgishtml?dol_htmlentitiesbr($object->firstname):$object->firstname;
6050
			$substitutionarray['__MEMBER_LASTNAME__']=$msgishtml?dol_htmlentitiesbr($object->lastname):$object->lastname;
6051
			if (method_exists($object, 'getFullName')) $substitutionarray['__MEMBER_FULLNAME__']=$msgishtml?dol_htmlentitiesbr($object->getFullName($outputlangs)):$object->getFullName($outputlangs);
6052
			$substitutionarray['__MEMBER_COMPANY__']=$msgishtml?dol_htmlentitiesbr($object->societe):$object->societe;
6053
			$substitutionarray['__MEMBER_ADDRESS__']=$msgishtml?dol_htmlentitiesbr($object->address):$object->address;
6054
			$substitutionarray['__MEMBER_ZIP__']=$msgishtml?dol_htmlentitiesbr($object->zip):$object->zip;
6055
			$substitutionarray['__MEMBER_TOWN__']=$msgishtml?dol_htmlentitiesbr($object->town):$object->town;
6056
			$substitutionarray['__MEMBER_COUNTRY__']=$msgishtml?dol_htmlentitiesbr($object->country):$object->country;
6057
			$substitutionarray['__MEMBER_EMAIL__']=$msgishtml?dol_htmlentitiesbr($object->email):$object->email;
6058
			$substitutionarray['__MEMBER_BIRTH__']=$msgishtml?dol_htmlentitiesbr($birthday):$birthday;
6059
			$substitutionarray['__MEMBER_PHOTO__']=$msgishtml?dol_htmlentitiesbr($object->photo):$object->photo;
6060
			$substitutionarray['__MEMBER_LOGIN__']=$msgishtml?dol_htmlentitiesbr($object->login):$object->login;
6061
			$substitutionarray['__MEMBER_PASSWORD__']=$msgishtml?dol_htmlentitiesbr($object->pass):$object->pass;
6062
			$substitutionarray['__MEMBER_PHONE__']=$msgishtml?dol_htmlentitiesbr($object->phone):$object->phone;
6063
			$substitutionarray['__MEMBER_PHONEPRO__']=$msgishtml?dol_htmlentitiesbr($object->phone_perso):$object->phone_perso;
6064
			$substitutionarray['__MEMBER_PHONEMOBILE__']=$msgishtml?dol_htmlentitiesbr($object->phone_mobile):$object->phone_mobile;
6065
			$substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE__']       = dol_print_date($object->first_subscription_date, 'dayrfc');
6066
			$substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_START__'] = dol_print_date($object->first_subscription_date_start, 'dayrfc');
6067
			$substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_END__']   = dol_print_date($object->first_subscription_date_end, 'dayrfc');
6068
			$substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE__']        = dol_print_date($object->last_subscription_date, 'dayrfc');
6069
			$substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_START__']  = dol_print_date($object->last_subscription_date_start, 'dayrfc');
6070
			$substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_END__']    = dol_print_date($object->last_subscription_date_end, 'dayrfc');
6071
6072
			if (is_object($object) && $object->element == 'societe')
6073
			{
6074
				$substitutionarray['__THIRDPARTY_ID__'] = (is_object($object)?$object->id:'');
6075
				$substitutionarray['__THIRDPARTY_NAME__'] = (is_object($object)?$object->name:'');
6076
				$substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = (is_object($object)?$object->name_alias:'');
6077
				$substitutionarray['__THIRDPARTY_EMAIL__'] = (is_object($object)?$object->email:'');
6078
			}
6079
			elseif (is_object($object->thirdparty) && $object->thirdparty->id > 0)
6080
			{
6081
				$substitutionarray['__THIRDPARTY_ID__'] = (is_object($object->thirdparty)?$object->thirdparty->id:'');
6082
				$substitutionarray['__THIRDPARTY_NAME__'] = (is_object($object->thirdparty)?$object->thirdparty->name:'');
6083
				$substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = (is_object($object->thirdparty)?$object->thirdparty->name_alias:'');
6084
				$substitutionarray['__THIRDPARTY_EMAIL__'] = (is_object($object->thirdparty)?$object->thirdparty->email:'');
6085
			}
6086
6087
			if (is_object($object->projet) && $object->projet->id > 0)
6088
			{
6089
				$substitutionarray['__PROJECT_ID__'] = (is_object($object->projet)?$object->projet->id:'');
6090
				$substitutionarray['__PROJECT_REF__'] = (is_object($object->projet)?$object->projet->ref:'');
6091
				$substitutionarray['__PROJECT_NAME__'] = (is_object($object->projet)?$object->projet->title:'');
6092
			}
6093
6094
			if (is_object($object) && $object->element == 'shipping')
6095
			{
6096
				$substitutionarray['__SHIPPINGTRACKNUM__']=$object->tracking_number;
6097
				$substitutionarray['__SHIPPINGTRACKNUMURL__']=$object->tracking_url;
6098
			}
6099
6100
			if (is_object($object) && $object->element == 'contrat' && is_array($object->lines))
6101
			{
6102
				$dateplannedstart='';
6103
				$datenextexpiration='';
6104
				foreach($object->lines as $line)
6105
				{
6106
					if ($line->date_ouverture_prevue > $dateplannedstart) $dateplannedstart = $line->date_ouverture_prevue;
6107
					if ($line->statut == 4 && $line->date_fin_prevue && (! $datenextexpiration || $line->date_fin_prevue < $datenextexpiration)) $datenextexpiration = $line->date_fin_prevue;
6108
				}
6109
				$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = dol_print_date($dateplannedstart, 'dayrfc');
6110
				$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = dol_print_date($dateplannedstart, 'standard');
6111
				$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = dol_print_date($datenextexpiration, 'dayrfc');
6112
				$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = dol_print_date($datenextexpiration, 'standard');
6113
			}
6114
6115
			// Create dynamic tags for __EXTRAFIELD_FIELD__
6116
			if ($object->table_element && $object->id > 0)
6117
			{
6118
				if (! is_object($extrafields)) $extrafields = new ExtraFields($db);
6119
				$extrafields->fetch_name_optionals_label($object->table_element, true);
6120
6121
				if ($object->fetch_optionals() > 0)
6122
				{
6123
					if (is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label']) > 0)
6124
					{
6125
						foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) {
6126
							$substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = $object->array_options['options_' . $key];
6127
						}
6128
					}
6129
				}
6130
			}
6131
6132
			// Complete substitution array with the url to make online payment
6133
			$paymenturl='';
6134
			if (empty($substitutionarray['__REF__']))
6135
			{
6136
				$paymenturl='';
6137
			}
6138
			else
6139
			{
6140
				// Set the online payment url link into __ONLINE_PAYMENT_URL__ key
6141
				require_once DOL_DOCUMENT_ROOT.'/core/lib/payments.lib.php';
6142
				$outputlangs->loadLangs(array('paypal','other'));
6143
				$typeforonlinepayment='free';
6144
				if (is_object($object) && $object->element == 'commande') $typeforonlinepayment='order';
6145
				if (is_object($object) && $object->element == 'facture')  $typeforonlinepayment='invoice';
6146
				if (is_object($object) && $object->element == 'member')   $typeforonlinepayment='member';
6147
				$url=getOnlinePaymentUrl(0, $typeforonlinepayment, $substitutionarray['__REF__']);
6148
				$paymenturl=$url;
6149
			}
6150
6151
			$substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__']=($paymenturl?str_replace('\n', "\n", $outputlangs->trans("PredefinedMailContentLink", $paymenturl)):'');
6152
			$substitutionarray['__ONLINE_PAYMENT_URL__']=$paymenturl;
6153
		}
6154
	}
6155
	if (empty($exclude) || ! in_array('objectamount', $exclude))
6156
	{
6157
		$substitutionarray['__DATE_YMD__']        = is_object($object)?(isset($object->date) ? dol_print_date($object->date, 'day', 0, $outputlangs) : '') : '';
6158
		$substitutionarray['__DATE_DUE_YMD__']    = is_object($object)?(isset($object->date_lim_reglement)? dol_print_date($object->date_lim_reglement, 'day', 0, $outputlangs) : '') : '';
6159
6160
		$substitutionarray['__AMOUNT__']          = is_object($object)?$object->total_ttc:'';
6161
		$substitutionarray['__AMOUNT_EXCL_TAX__'] = is_object($object)?$object->total_ht:'';
6162
		$substitutionarray['__AMOUNT_VAT__']      = is_object($object)?($object->total_vat?$object->total_vat:$object->total_tva):'';
6163
		if ($onlykey != 2 || $mysoc->useLocalTax(1)) $substitutionarray['__AMOUNT_TAX2__']     = is_object($object)?$object->total_localtax1:'';
6164
		if ($onlykey != 2 || $mysoc->useLocalTax(2)) $substitutionarray['__AMOUNT_TAX3__']     = is_object($object)?$object->total_localtax2:'';
6165
6166
		$substitutionarray['__AMOUNT_FORMATED__']          = is_object($object)?price($object->total_ttc, 0, $outputlangs, 0, 0, -1, $conf->currency):'';
6167
		$substitutionarray['__AMOUNT_EXCL_TAX_FORMATED__'] = is_object($object)?price($object->total_ht, 0, $outputlangs, 0, 0, -1, $conf->currency):'';
6168
		$substitutionarray['__AMOUNT_VAT_FORMATED__']      = is_object($object)?($object->total_vat?price($object->total_vat, 0, $outputlangs, 0, 0, -1, $conf->currency):price($object->total_tva, 0, $outputlangs, 0, 0, -1, $conf->currency)):'';
6169
		if ($onlykey != 2 || $mysoc->useLocalTax(1)) $substitutionarray['__AMOUNT_TAX2_FORMATED__']     = is_object($object)?price($object->total_localtax1, 0, $outputlangs, 0, 0, -1, $conf->currency):'';
6170
		if ($onlykey != 2 || $mysoc->useLocalTax(2)) $substitutionarray['__AMOUNT_TAX3_FORMATED__']     = is_object($object)?price($object->total_localtax2, 0, $outputlangs, 0, 0, -1, $conf->currency):'';
6171
6172
		// TODO Add keys for foreign multicurrency
6173
6174
		// For backward compatibility
6175
		if ($onlykey != 2)
6176
		{
6177
			$substitutionarray['__TOTAL_TTC__']    = is_object($object)?$object->total_ttc:'';
6178
			$substitutionarray['__TOTAL_HT__']     = is_object($object)?$object->total_ht:'';
6179
			$substitutionarray['__TOTAL_VAT__']    = is_object($object)?($object->total_vat?$object->total_vat:$object->total_tva):'';
6180
		}
6181
	}
6182
6183
	//var_dump($substitutionarray['__AMOUNT_FORMATED__']);
6184
	if (empty($exclude) || ! in_array('date', $exclude))
6185
	{
6186
		include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
6187
6188
		$tmp=dol_getdate(dol_now(), true);
6189
		$tmp2=dol_get_prev_day($tmp['mday'], $tmp['mon'], $tmp['year']);
6190
		$tmp3=dol_get_prev_month($tmp['mon'], $tmp['year']);
6191
		$tmp4=dol_get_next_day($tmp['mday'], $tmp['mon'], $tmp['year']);
6192
		$tmp5=dol_get_next_month($tmp['mon'], $tmp['year']);
6193
6194
		$substitutionarray=array_merge($substitutionarray, array(
6195
			'__DAY__' => (string) $tmp['mday'],
6196
			'__DAY_TEXT__' => $outputlangs->trans('Day'.$tmp['wday']),					// Monday
6197
			'__DAY_TEXT_SHORT__' => $outputlangs->trans($tmp['weekday'].'Min'),			// Mon
6198
			'__DAY_TEXT_MIN__' => $outputlangs->trans('Short'.$tmp['weekday']),			// M
6199
			'__MONTH__' => (string) $tmp['mon'],
6200
			'__MONTH_TEXT__' => $outputlangs->trans('Month'.sprintf("%02d", $tmp['mon'])),
6201
			'__MONTH_TEXT_SHORT__' => $outputlangs->trans('MonthShort'.sprintf("%02d", $tmp['mon'])),
6202
			'__MONTH_TEXT_MIN__' => $outputlangs->trans('MonthVeryShort'.sprintf("%02d", $tmp['mon'])),
6203
			'__YEAR__' => (string) $tmp['year'],
6204
			'__PREVIOUS_DAY__' => (string) $tmp2['day'],
6205
			'__PREVIOUS_MONTH__' => (string) $tmp3['month'],
6206
			'__PREVIOUS_YEAR__' => (string) ($tmp['year'] - 1),
6207
			'__NEXT_DAY__' => (string) $tmp4['day'],
6208
			'__NEXT_MONTH__' => (string) $tmp5['month'],
6209
			'__NEXT_YEAR__' => (string) ($tmp['year'] + 1),
6210
		));
6211
	}
6212
6213
	if (empty($exclude) || ! in_array('system', $exclude))
6214
	{
6215
		$substitutionarray['__(AnyTranslationKey)__']=$outputlangs->trans('TranslationOfKey');
6216
		$substitutionarray['__[AnyConstantKey]__']=$outputlangs->trans('ValueOfConstant');
6217
		$substitutionarray['__DOL_MAIN_URL_ROOT__']=DOL_MAIN_URL_ROOT;
6218
	}
6219
	if (! empty($conf->multicompany->enabled))
6220
	{
6221
		$substitutionarray=array_merge($substitutionarray, array('__ENTITY_ID__' => $conf->entity));
6222
	}
6223
6224
	return $substitutionarray;
6225
}
6226
6227
/**
6228
 *  Make substitution into a text string, replacing keys with vals from $substitutionarray (oldval=>newval),
6229
 *  and texts like __(TranslationKey|langfile)__ and __[ConstantKey]__ are also replaced.
6230
 *  Example of usage:
6231
 *  $substitutionarray = getCommonSubstitutionArray($langs, 0, null, $thirdparty);
6232
 *  complete_substitutions_array($substitutionarray, $langs, $thirdparty);
6233
 *  $mesg = make_substitutions($mesg, $substitutionarray, $langs);
6234
 *
6235
 *  @param	string		$text	      			Source string in which we must do substitution
6236
 *  @param  array		$substitutionarray		Array with key->val to substitute. Example: array('__MYKEY__' => 'MyVal', ...)
6237
 *  @param	Translate	$outputlangs			Output language
6238
 * 	@return string  		    				Output string after substitutions
6239
 *  @see	complete_substitutions_array, getCommonSubstitutionArray
6240
 */
6241
function make_substitutions($text, $substitutionarray, $outputlangs=null)
6242
{
6243
	global $conf, $langs;
6244
6245
	if (! is_array($substitutionarray)) return 'ErrorBadParameterSubstitutionArrayWhenCalling_make_substitutions';
6246
6247
	if (empty($outputlangs)) $outputlangs=$langs;
6248
6249
	// Make substitution for language keys
6250
	if (is_object($outputlangs))
6251
	{
6252
		while (preg_match('/__\(([^\)]+)\)__/', $text, $reg))
6253
		{
6254
			$msgishtml = 0;
6255
			if (dol_textishtml($text,1)) $msgishtml = 1;
6256
6257
			// If key is __(TranslationKey|langfile)__, then force load of langfile.lang
6258
			$tmp=explode('|',$reg[1]);
6259
			if (! empty($tmp[1])) $outputlangs->load($tmp[1]);
6260
6261
			$text = preg_replace('/__\('.preg_quote($reg[1], '/').'\)__/', $msgishtml?dol_htmlentitiesbr($outputlangs->transnoentitiesnoconv($reg[1])):$outputlangs->transnoentitiesnoconv($reg[1]), $text);
6262
		}
6263
	}
6264
6265
	// Make substitution for constant keys. Must be after the substitution of translation, so if text of translation contains a constant,
6266
	// it is also converted.
6267
	while (preg_match('/__\[([^\]]+)\]__/', $text, $reg))
6268
	{
6269
		$msgishtml = 0;
6270
		if (dol_textishtml($text,1)) $msgishtml = 1;
6271
6272
		$keyfound = $reg[1];
6273
		if (preg_match('/(_pass|password|secret|_key|key$)/i', $keyfound)) $newval = '*****forbidden*****';
6274
		else $newval=empty($conf->global->$keyfound)?'':$conf->global->$keyfound;
6275
		$text = preg_replace('/__\['.preg_quote($keyfound, '/').'\]__/', $msgishtml?dol_htmlentitiesbr($newval):$newval, $text);
6276
	}
6277
6278
	// Make substitition for array $substitutionarray
6279
	foreach ($substitutionarray as $key => $value)
6280
	{
6281
		if ($key == '__SIGNATURE__' && (! empty($conf->global->MAIN_MAIL_DO_NOT_USE_SIGN))) $value='';		// Protection
6282
		if ($key == '__USER_SIGNATURE__' && (! empty($conf->global->MAIN_MAIL_DO_NOT_USE_SIGN))) $value='';	// Protection
6283
6284
		$text=str_replace("$key","$value",$text);	// We must keep the " to work when value is 123.5 for example
6285
	}
6286
6287
	return $text;
6288
}
6289
6290
/**
6291
 *  Complete the $substitutionarray with more entries coming from external module that had set the "substitutions=1" into module_part array.
6292
 *  In this case, method completesubstitutionarray provided by module is called.
6293
 *
6294
 *  @param  array		$substitutionarray		Array substitution old value => new value value
6295
 *  @param  Translate	$outputlangs            Output language
6296
 *  @param  Object		$object                 Source object
6297
 *  @param  mixed		$parameters       		Add more parameters (useful to pass product lines)
6298
 *  @param  string      $callfunc               What is the name of the custom function that will be called? (default: completesubstitutionarray)
6299
 *  @return	void
6300
 *  @see 	make_substitutions
6301
 */
6302
function complete_substitutions_array(&$substitutionarray, $outputlangs, $object=null, $parameters=null, $callfunc="completesubstitutionarray")
6303
{
6304
	global $conf,$user;
6305
6306
	require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6307
6308
	// Add a substitution key for each extrafields, using key __EXTRA_XXX__
6309
	// TODO Remove this. Already available into the getCommonSubstitutionArray used to build the substitution array.
6310
	/*if (is_object($object) && is_array($object->array_options))
6311
	{
6312
		foreach($object->array_options as $key => $val)
6313
		{
6314
			$keyshort=preg_replace('/^(options|extra)_/','',$key);
6315
			$substitutionarray['__EXTRAFIELD_'.$keyshort.'__']=$val;
6316
			// For backward compatibiliy
6317
			$substitutionarray['%EXTRA_'.$keyshort.'%']=$val;
6318
		}
6319
	}*/
6320
6321
	// Check if there is external substitution to do, requested by plugins
6322
	$dirsubstitutions=array_merge(array(),(array) $conf->modules_parts['substitutions']);
6323
6324
	foreach($dirsubstitutions as $reldir)
6325
	{
6326
		$dir=dol_buildpath($reldir,0);
6327
6328
		// Check if directory exists
6329
		if (! dol_is_dir($dir)) continue;
6330
6331
		$substitfiles=dol_dir_list($dir,'files',0,'functions_');
6332
		foreach($substitfiles as $substitfile)
6333
		{
6334
			if (preg_match('/functions_(.*)\.lib\.php/i',$substitfile['name'],$reg))
6335
			{
6336
				$module=$reg[1];
6337
6338
				dol_syslog("Library ".$substitfile['name']." found into ".$dir);
6339
				// Include the user's functions file
6340
				require_once $dir.$substitfile['name'];
6341
				// Call the user's function, and only if it is defined
6342
				$function_name=$module."_".$callfunc;
6343
				if (function_exists($function_name)) $function_name($substitutionarray,$outputlangs,$object,$parameters);
6344
			}
6345
		}
6346
	}
6347
}
6348
6349
/**
6350
 *    Format output for start and end date
6351
 *
6352
 *    @param	int	$date_start    Start date
6353
 *    @param    int	$date_end      End date
6354
 *    @param    string		$format        Output format
6355
 *    @param	Translate	$outputlangs   Output language
6356
 *    @return	void
6357
 */
6358
function print_date_range($date_start,$date_end,$format = '',$outputlangs='')
6359
{
6360
	print get_date_range($date_start,$date_end,$format,$outputlangs);
6361
}
6362
6363
/**
6364
 *    Format output for start and end date
6365
 *
6366
 *    @param	int			$date_start    		Start date
6367
 *    @param    int			$date_end      		End date
6368
 *    @param    string		$format        		Output format
6369
 *    @param	Translate	$outputlangs   		Output language
6370
 *    @param	integer		$withparenthesis	1=Add parenthesis, 0=non parenthesis
6371
 *    @return	string							String
6372
 */
6373
function get_date_range($date_start,$date_end,$format = '',$outputlangs='', $withparenthesis=1)
6374
{
6375
	global $langs;
6376
6377
	$out='';
6378
6379
	if (! is_object($outputlangs)) $outputlangs=$langs;
6380
6381
	if ($date_start && $date_end)
6382
	{
6383
		$out.= ($withparenthesis?' (':'').$outputlangs->transnoentitiesnoconv('DateFromTo',dol_print_date($date_start, $format, false, $outputlangs),dol_print_date($date_end, $format, false, $outputlangs)).($withparenthesis?')':'');
6384
	}
6385
	if ($date_start && ! $date_end)
6386
	{
6387
		$out.= ($withparenthesis?' (':'').$outputlangs->transnoentitiesnoconv('DateFrom',dol_print_date($date_start, $format, false, $outputlangs)).($withparenthesis?')':'');
6388
	}
6389
	if (! $date_start && $date_end)
6390
	{
6391
		$out.= ($withparenthesis?' (':'').$outputlangs->transnoentitiesnoconv('DateUntil',dol_print_date($date_end, $format, false, $outputlangs)).($withparenthesis?')':'');
6392
	}
6393
6394
	return $out;
6395
}
6396
6397
/**
6398
 * Return firstname and lastname in correct order
6399
 *
6400
 * @param	string	$firstname		Firstname
6401
 * @param	string	$lastname		Lastname
6402
 * @param	int		$nameorder		-1=Auto, 0=Lastname+Firstname, 1=Firstname+Lastname, 2=Firstname
6403
 * @return	string					Firstname + lastname or Lastname + firstname
6404
 */
6405
function dolGetFirstLastname($firstname,$lastname,$nameorder=-1)
6406
{
6407
	global $conf;
6408
6409
	$ret='';
6410
	// If order not defined, we use the setup
6411
	if ($nameorder < 0) $nameorder=(empty($conf->global->MAIN_FIRSTNAME_NAME_POSITION)?1:0);
6412
	if ($nameorder && ((string) $nameorder != '2'))
6413
	{
6414
		$ret.=$firstname;
6415
		if ($firstname && $lastname) $ret.=' ';
6416
		$ret.=$lastname;
6417
	}
6418
	else if ($nameorder == 2)
6419
	{
6420
	   $ret.=$firstname;
6421
	}
6422
	else
6423
	{
6424
		$ret.=$lastname;
6425
		if ($firstname && $lastname) $ret.=' ';
6426
		$ret.=$firstname;
6427
	}
6428
	return $ret;
6429
}
6430
6431
6432
/**
6433
 *	Set event message in dol_events session object. Will be output by calling dol_htmloutput_events.
6434
 *  Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function.
6435
 *  Note: Prefer to use setEventMessages instead.
6436
 *
6437
 *	@param	mixed	$mesgs			Message string or array
6438
 *  @param  string	$style      	Which style to use ('mesgs' by default, 'warnings', 'errors')
6439
 *  @return	void
6440
 *  @see	dol_htmloutput_events
6441
 */
6442
function setEventMessage($mesgs, $style='mesgs')
6443
{
6444
	//dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);		This is not deprecated, it is used by setEventMessages function
6445
	if (! is_array($mesgs))		// If mesgs is a string
6446
	{
6447
		if ($mesgs) $_SESSION['dol_events'][$style][] = $mesgs;
6448
	}
6449
	else						// If mesgs is an array
6450
	{
6451
		foreach($mesgs as $mesg)
6452
		{
6453
			if ($mesg) $_SESSION['dol_events'][$style][] = $mesg;
6454
		}
6455
	}
6456
}
6457
6458
/**
6459
 *	Set event messages in dol_events session object. Will be output by calling dol_htmloutput_events.
6460
 *  Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function.
6461
 *
6462
 *	@param	string	$mesg			Message string
6463
 *	@param	array	$mesgs			Message array
6464
 *  @param  string	$style      	Which style to use ('mesgs' by default, 'warnings', 'errors')
6465
 *  @return	void
6466
 *  @see	dol_htmloutput_events
6467
 */
6468
function setEventMessages($mesg, $mesgs, $style='mesgs')
6469
{
6470
	if (empty($mesg) && empty($mesgs))
6471
	{
6472
		dol_syslog("Try to add a message in stack with empty message", LOG_WARNING);
6473
	}
6474
	else
6475
	{
6476
		if (! in_array((string) $style, array('mesgs','warnings','errors'))) dol_print_error('','Bad parameter style='.$style.' for setEventMessages');
6477
		if (empty($mesgs)) setEventMessage($mesg, $style);
6478
		else
6479
		{
6480
			if (! empty($mesg) && ! in_array($mesg, $mesgs)) setEventMessage($mesg, $style);	// Add message string if not already into array
6481
			setEventMessage($mesgs, $style);
6482
		}
6483
	}
6484
}
6485
6486
/**
6487
 *	Print formated messages to output (Used to show messages on html output).
6488
 *  Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function, so there is
6489
 *  no need to call it explicitely.
6490
 *
6491
 *  @param	int		$disabledoutputofmessages	Clear all messages stored into session without diplaying them
6492
 *  @return	void
6493
 *  @see    									dol_htmloutput_mesg
6494
 */
6495
function dol_htmloutput_events($disabledoutputofmessages=0)
6496
{
6497
	// Show mesgs
6498
	if (isset($_SESSION['dol_events']['mesgs'])) {
6499
		if (empty($disabledoutputofmessages)) dol_htmloutput_mesg('', $_SESSION['dol_events']['mesgs']);
6500
		unset($_SESSION['dol_events']['mesgs']);
6501
	}
6502
6503
	// Show errors
6504
	if (isset($_SESSION['dol_events']['errors'])) {
6505
		if (empty($disabledoutputofmessages)) dol_htmloutput_mesg('', $_SESSION['dol_events']['errors'], 'error');
6506
		unset($_SESSION['dol_events']['errors']);
6507
	}
6508
6509
	// Show warnings
6510
	if (isset($_SESSION['dol_events']['warnings'])) {
6511
		if (empty($disabledoutputofmessages)) dol_htmloutput_mesg('', $_SESSION['dol_events']['warnings'], 'warning');
6512
		unset($_SESSION['dol_events']['warnings']);
6513
	}
6514
}
6515
6516
/**
6517
 *	Get formated messages to output (Used to show messages on html output).
6518
 *  This include also the translation of the message key.
6519
 *
6520
 *	@param	string		$mesgstring		Message string or message key
6521
 *	@param	string[]	$mesgarray      Array of message strings or message keys
6522
 *  @param  string		$style          Style of message output ('ok' or 'error')
6523
 *  @param  int			$keepembedded   Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
6524
 *	@return	string						Return html output
6525
 *
6526
 *  @see    dol_print_error
6527
 *  @see    dol_htmloutput_errors
6528
 *  @see    setEventMessages
6529
 */
6530
function get_htmloutput_mesg($mesgstring='',$mesgarray='', $style='ok', $keepembedded=0)
6531
{
6532
	global $conf, $langs;
6533
6534
	$ret=0; $return='';
6535
	$out='';
6536
	$divstart=$divend='';
6537
6538
	// If inline message with no format, we add it.
6539
	if ((empty($conf->use_javascript_ajax) || ! empty($conf->global->MAIN_DISABLE_JQUERY_JNOTIFY) || $keepembedded) && ! preg_match('/<div class=".*">/i',$out))
6540
	{
6541
		$divstart='<div class="'.$style.' clearboth">';
6542
		$divend='</div>';
6543
	}
6544
6545
	if ((is_array($mesgarray) && count($mesgarray)) || $mesgstring)
6546
	{
6547
		$langs->load("errors");
6548
		$out.=$divstart;
6549
		if (is_array($mesgarray) && count($mesgarray))
6550
		{
6551
			foreach($mesgarray as $message)
6552
			{
6553
				$ret++;
6554
				$out.= $langs->trans($message);
6555
				if ($ret < count($mesgarray)) $out.= "<br>\n";
6556
			}
6557
		}
6558
		if ($mesgstring)
6559
		{
6560
			$langs->load("errors");
6561
			$ret++;
6562
			$out.= $langs->trans($mesgstring);
6563
		}
6564
		$out.=$divend;
6565
	}
6566
6567
	if ($out)
6568
	{
6569
		if (! empty($conf->use_javascript_ajax) && empty($conf->global->MAIN_DISABLE_JQUERY_JNOTIFY) && empty($keepembedded))
6570
		{
6571
			$return = '<script type="text/javascript">
6572
					$(document).ready(function() {
6573
						var block = '.(! empty($conf->global->MAIN_USE_JQUERY_BLOCKUI)?"true":"false").'
6574
						if (block) {
6575
							$.dolEventValid("","'.dol_escape_js($out).'");
6576
						} else {
6577
							/* jnotify(message, preset of message type, keepmessage) */
6578
							$.jnotify("'.dol_escape_js($out).'",
6579
							"'.($style=="ok" ? 3000 : $style).'",
6580
							'.($style=="ok" ? "false" : "true").',
6581
							{ remove: function (){} } );
6582
						}
6583
					});
6584
				</script>';
6585
		}
6586
		else
6587
		{
6588
			$return = $out;
6589
		}
6590
	}
6591
6592
	return $return;
6593
}
6594
6595
/**
6596
 *  Get formated error messages to output (Used to show messages on html output).
6597
 *
6598
 *  @param  string	$mesgstring         Error message
6599
 *  @param  array	$mesgarray          Error messages array
6600
 *  @param  int		$keepembedded       Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
6601
 *  @return string                		Return html output
6602
 *
6603
 *  @see    dol_print_error
6604
 *  @see    dol_htmloutput_mesg
6605
 */
6606
function get_htmloutput_errors($mesgstring='', $mesgarray=array(), $keepembedded=0)
6607
{
6608
	return get_htmloutput_mesg($mesgstring, $mesgarray,'error',$keepembedded);
6609
}
6610
6611
/**
6612
 *	Print formated messages to output (Used to show messages on html output).
6613
 *
6614
 *	@param	string		$mesgstring		Message string or message key
6615
 *	@param	string[]	$mesgarray      Array of message strings or message keys
6616
 *	@param  string      $style          Which style to use ('ok', 'warning', 'error')
6617
 *	@param  int         $keepembedded   Set to 1 if message must be kept embedded into its html place (this disable jnotify)
6618
 *	@return	void
6619
 *
6620
 *	@see    dol_print_error
6621
 *	@see    dol_htmloutput_errors
6622
 *	@see    setEventMessages
6623
 */
6624
function dol_htmloutput_mesg($mesgstring = '',$mesgarray = array(), $style = 'ok', $keepembedded=0)
6625
{
6626
	if (empty($mesgstring) && (! is_array($mesgarray) || count($mesgarray) == 0)) return;
6627
6628
	$iserror=0;
6629
	$iswarning=0;
6630
	if (is_array($mesgarray))
6631
	{
6632
		foreach($mesgarray as $val)
6633
		{
6634
			if ($val && preg_match('/class="error"/i',$val)) { $iserror++; break; }
6635
			if ($val && preg_match('/class="warning"/i',$val)) { $iswarning++; break; }
6636
		}
6637
	}
6638
	else if ($mesgstring && preg_match('/class="error"/i',$mesgstring)) $iserror++;
6639
	else if ($mesgstring && preg_match('/class="warning"/i',$mesgstring)) $iswarning++;
6640
	if ($style=='error') $iserror++;
6641
	if ($style=='warning') $iswarning++;
6642
6643
	if ($iserror || $iswarning)
6644
	{
6645
		// Remove div from texts
6646
		$mesgstring=preg_replace('/<\/div><div class="(error|warning)">/','<br>',$mesgstring);
6647
		$mesgstring=preg_replace('/<div class="(error|warning)">/','',$mesgstring);
6648
		$mesgstring=preg_replace('/<\/div>/','',$mesgstring);
6649
		// Remove div from texts array
6650
		if (is_array($mesgarray))
6651
		{
6652
			$newmesgarray=array();
6653
			foreach($mesgarray as $val)
6654
			{
6655
				$tmpmesgstring=preg_replace('/<\/div><div class="(error|warning)">/','<br>',$val);
6656
				$tmpmesgstring=preg_replace('/<div class="(error|warning)">/','',$tmpmesgstring);
6657
				$tmpmesgstring=preg_replace('/<\/div>/','',$tmpmesgstring);
6658
				$newmesgarray[]=$tmpmesgstring;
6659
			}
6660
			$mesgarray=$newmesgarray;
6661
		}
6662
		print get_htmloutput_mesg($mesgstring,$mesgarray,($iserror?'error':'warning'),$keepembedded);
6663
	}
6664
	else print get_htmloutput_mesg($mesgstring,$mesgarray,'ok',$keepembedded);
6665
}
6666
6667
/**
6668
 *  Print formated error messages to output (Used to show messages on html output).
6669
 *
6670
 *  @param	string	$mesgstring          Error message
6671
 *  @param  array	$mesgarray           Error messages array
6672
 *  @param  int		$keepembedded        Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
6673
 *  @return	void
6674
 *
6675
 *  @see    dol_print_error
6676
 *  @see    dol_htmloutput_mesg
6677
 */
6678
function dol_htmloutput_errors($mesgstring='', $mesgarray=array(), $keepembedded=0)
6679
{
6680
	dol_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
6681
}
6682
6683
/**
6684
 * 	Advanced sort array by second index function, which produces ascending (default)
6685
 *  or descending output and uses optionally natural case insensitive sorting (which
6686
 *  can be optionally case sensitive as well).
6687
 *
6688
 *  @param      array		$array      		Array to sort (array of array('key','otherkey1','otherkey2'...))
6689
 *  @param      string		$index				Key in array to use for sorting criteria
6690
 *  @param      int			$order				Sort order ('asc' or 'desc')
6691
 *  @param      int			$natsort			1=use "natural" sort (natsort), 0=use "standard" sort (asort)
6692
 *  @param      int			$case_sensitive		1=sort is case sensitive, 0=not case sensitive
6693
 *  @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.
6694
 *  @return     array							Sorted array
6695
 */
6696
function dol_sort_array(&$array, $index, $order='asc', $natsort=0, $case_sensitive=0, $keepindex=0)
6697
{
6698
	// Clean parameters
6699
	$order=strtolower($order);
6700
6701
	if (is_array($array))
6702
	{
6703
		$sizearray=count($array);
6704
		if ($sizearray>0)
6705
		{
6706
			$temp = array();
6707
			foreach(array_keys($array) as $key) $temp[$key]=$array[$key][$index];
6708
6709
			if (!$natsort) ($order=='asc') ? asort($temp) : arsort($temp);
6710
			else
6711
			{
6712
				($case_sensitive) ? natsort($temp) : natcasesort($temp);
6713
				if($order!='asc') $temp=array_reverse($temp, true);
6714
			}
6715
6716
			$sorted = array();
6717
6718
			foreach(array_keys($temp) as $key)
6719
			{
6720
				(is_numeric($key) && empty($keepindex)) ? $sorted[]=$array[$key] : $sorted[$key]=$array[$key];
6721
			}
6722
6723
			return $sorted;
6724
		}
6725
	}
6726
	return $array;
6727
}
6728
6729
6730
/**
6731
 *      Check if a string is in UTF8
6732
 *
6733
 *      @param	string	$str        String to check
6734
 * 		@return	boolean				True if string is UTF8 or ISO compatible with UTF8, False if not (ISO with special char or Binary)
6735
 */
6736
function utf8_check($str)
6737
{
6738
	// We must use here a binary strlen function (so not dol_strlen)
6739
	$strLength = dol_strlen($str);
6740
	for ($i=0; $i<$strLength; $i++)
6741
	{
6742
		if (ord($str[$i]) < 0x80) continue; // 0bbbbbbb
6743
		elseif ((ord($str[$i]) & 0xE0) == 0xC0) $n=1; // 110bbbbb
6744
		elseif ((ord($str[$i]) & 0xF0) == 0xE0) $n=2; // 1110bbbb
6745
		elseif ((ord($str[$i]) & 0xF8) == 0xF0) $n=3; // 11110bbb
6746
		elseif ((ord($str[$i]) & 0xFC) == 0xF8) $n=4; // 111110bb
6747
		elseif ((ord($str[$i]) & 0xFE) == 0xFC) $n=5; // 1111110b
6748
		else return false; // Does not match any model
6749
		for ($j=0; $j<$n; $j++) { // n bytes matching 10bbbbbb follow ?
6750
			if ((++$i == strlen($str)) || ((ord($str[$i]) & 0xC0) != 0x80))
6751
			return false;
6752
		}
6753
	}
6754
	return true;
6755
}
6756
6757
6758
/**
6759
 *      Return a string encoded into OS filesystem encoding. This function is used to define
6760
 * 	    value to pass to filesystem PHP functions.
6761
 *
6762
 *      @param	string	$str        String to encode (UTF-8)
6763
 * 		@return	string				Encoded string (UTF-8, ISO-8859-1)
6764
 */
6765
function dol_osencode($str)
6766
{
6767
	global $conf;
6768
6769
	$tmp=ini_get("unicode.filesystem_encoding");						// Disponible avec PHP 6.0
6770
	if (empty($tmp) && ! empty($_SERVER["WINDIR"])) $tmp='iso-8859-1';	// By default for windows
6771
	if (empty($tmp)) $tmp='utf-8';										// By default for other
6772
	if (! empty($conf->global->MAIN_FILESYSTEM_ENCODING)) $tmp=$conf->global->MAIN_FILESYSTEM_ENCODING;
6773
6774
	if ($tmp == 'iso-8859-1') return utf8_decode($str);
6775
	return $str;
6776
}
6777
6778
6779
/**
6780
 *      Return an id or code from a code or id.
6781
 *      Store also Code-Id into a cache to speed up next request on same key.
6782
 *
6783
 * 		@param	DoliDB	$db				Database handler
6784
 * 		@param	string	$key			Code or Id to get Id or Code
6785
 * 		@param	string	$tablename		Table name without prefix
6786
 * 		@param	string	$fieldkey		Field to search the key into
6787
 * 		@param	string	$fieldid		Field to get
6788
 *      @param  int		$entityfilter	Filter by entity
6789
 *      @return int						<0 if KO, Id of code if OK
6790
 *      @see $langs->getLabelFromKey
6791
 */
6792
function dol_getIdFromCode($db, $key, $tablename, $fieldkey='code', $fieldid='id', $entityfilter=0)
6793
{
6794
	global $cache_codes;
6795
6796
	// If key empty
6797
	if ($key == '') return '';
6798
6799
	// Check in cache
6800
	if (isset($cache_codes[$tablename][$key][$fieldid]))	// Can be defined to 0 or ''
6801
	{
6802
		return $cache_codes[$tablename][$key][$fieldid];   // Found in cache
6803
	}
6804
6805
	dol_syslog('dol_getIdFromCode (value not found into cache)', LOG_DEBUG);
6806
6807
	$sql = "SELECT ".$fieldid." as valuetoget";
6808
	$sql.= " FROM ".MAIN_DB_PREFIX.$tablename;
6809
	$sql.= " WHERE ".$fieldkey." = '".$db->escape($key)."'";
6810
	if (! empty($entityfilter))
6811
		$sql.= " AND entity IN (" . getEntity($tablename) . ")";
6812
6813
	$resql = $db->query($sql);
6814
	if ($resql)
6815
	{
6816
		$obj = $db->fetch_object($resql);
6817
		if ($obj) $cache_codes[$tablename][$key][$fieldid]=$obj->valuetoget;
6818
		else $cache_codes[$tablename][$key][$fieldid]='';
6819
		$db->free($resql);
6820
		return $cache_codes[$tablename][$key][$fieldid];
6821
	}
6822
	else
6823
	{
6824
		return -1;
6825
	}
6826
}
6827
6828
/**
6829
 * Verify if condition in string is ok or not
6830
 *
6831
 * @param 	string		$strRights		String with condition to check
6832
 * @return 	boolean						True or False. Return True if strRights is ''
6833
 */
6834
function verifCond($strRights)
6835
{
6836
	global $user,$conf,$langs;
6837
	global $leftmenu;
6838
	global $rights;    // To export to dol_eval function
6839
6840
	//print $strRights."<br>\n";
6841
	$rights = true;
6842
	if ($strRights != '')
6843
	{
6844
		$str = 'if(!(' . $strRights . ')) { $rights = false; }';
6845
		dol_eval($str);		// The dol_eval must contains all the global $xxx used into a condition
6846
	}
6847
	return $rights;
6848
}
6849
6850
/**
6851
 * Replace eval function to add more security.
6852
 * This function is called by verifCond() or trans() and transnoentitiesnoconv().
6853
 *
6854
 * @param 	string	$s				String to evaluate
6855
 * @param	int		$returnvalue	0=No return (used to execute eval($a=something)). 1=Value of eval is returned (used to eval($something)).
6856
 * @param   int     $hideerrors     1=Hide errors
6857
 * @return	mixed					Nothing or return of eval
6858
 */
6859
function dol_eval($s, $returnvalue=0, $hideerrors=1)
6860
{
6861
	// Only global variables can be changed by eval function and returned to caller
6862
	global $db, $langs, $user, $conf, $website, $websitepage;
6863
	global $action, $mainmenu, $leftmenu;
6864
	global $rights;
6865
	global $object;
6866
	global $mysoc;
6867
6868
	global $obj;       // To get $obj used into list when dol_eval is used for computed fields and $obj is not yet $object
6869
	global $soc;       // For backward compatibility
6870
6871
	//print $s."<br>\n";
6872
	if ($returnvalue)
6873
	{
6874
		if ($hideerrors) return @eval('return '.$s.';');
6875
		else return eval('return '.$s.';');
6876
	}
6877
	else
6878
	{
6879
		if ($hideerrors) @eval($s);
6880
		else eval($s);
6881
	}
6882
}
6883
6884
/**
6885
 * Return if var element is ok
6886
 *
6887
 * @param   string      $element    Variable to check
6888
 * @return  boolean                 Return true of variable is not empty
6889
 */
6890
function dol_validElement($element)
6891
{
6892
	return (trim($element) != '');
6893
}
6894
6895
/**
6896
 * 	Return img flag of country for a language code or country code
6897
 *
6898
 * 	@param	string	$codelang	Language code (en_IN, fr_CA...) or Country code (IN, FR)
6899
 *  @param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
6900
 * 	@return	string				HTML img string with flag.
6901
 */
6902
function picto_from_langcode($codelang, $moreatt = '')
6903
{
6904
	global $langs;
6905
6906
	if (empty($codelang)) return '';
6907
6908
	if ($codelang == 'auto')
6909
	{
6910
		return '<span class="fa fa-globe"></span>';
6911
	}
6912
6913
	$langtocountryflag = array(
6914
		'ar_AR' => '',
6915
		'ca_ES' => 'catalonia',
6916
		'da_DA' => 'dk',
6917
		'fr_CA' => 'mq',
6918
		'sv_SV' => 'se'
6919
	);
6920
6921
	if (isset($langtocountryflag[$codelang])) $flagImage = $langtocountryflag[$codelang];
6922
	else
6923
	{
6924
		$tmparray = explode('_', $codelang);
6925
		$flagImage = empty($tmparray[1]) ? $tmparray[0] : $tmparray[1];
6926
	}
6927
6928
	return img_picto_common($codelang, 'flags/'.strtolower($flagImage).'.png', $moreatt);
6929
}
6930
6931
/**
6932
 *  Complete or removed entries into a head array (used to build tabs).
6933
 *  For example, with value added by external modules. Such values are declared into $conf->modules_parts['tab'].
6934
 *  Or by change using hook completeTabsHead
6935
 *
6936
 *  @param	Conf			$conf           Object conf
6937
 *  @param  Translate		$langs          Object langs
6938
 *  @param  object|null		$object         Object object
6939
 *  @param  array			$head          	Object head
6940
 *  @param  int				$h				New position to fill
6941
 *  @param  string			$type           Value for object where objectvalue can be
6942
 *                              			'thirdparty'       to add a tab in third party view
6943
 *		                        	      	'intervention'     to add a tab in intervention view
6944
 *     		                    	     	'supplier_order'   to add a tab in supplier order view
6945
 *          		            	        'supplier_invoice' to add a tab in supplier invoice view
6946
 *                  		    	        'invoice'          to add a tab in customer invoice view
6947
 *                          			    'order'            to add a tab in customer order view
6948
 *                          				'contract'		   to add a tabl in contract view
6949
 *                      			        'product'          to add a tab in product view
6950
 *                              			'propal'           to add a tab in propal view
6951
 *                              			'user'             to add a tab in user view
6952
 *                              			'group'            to add a tab in group view
6953
 * 		        	               	     	'member'           to add a tab in fundation member view
6954
 *      		                        	'categories_x'	   to add a tab in category view ('x': type of category (0=product, 1=supplier, 2=customer, 3=member)
6955
 *      									'ecm'			   to add a tab for another ecm view
6956
 *                                          'stock'            to add a tab for warehouse view
6957
 *  @param  string		$mode  	        	'add' to complete head, 'remove' to remove entries
6958
 *	@return	void
6959
 */
6960
function complete_head_from_modules($conf,$langs,$object,&$head,&$h,$type,$mode='add')
6961
{
6962
	global $hookmanager;
6963
6964
	if (isset($conf->modules_parts['tabs'][$type]) && is_array($conf->modules_parts['tabs'][$type]))
6965
	{
6966
		foreach ($conf->modules_parts['tabs'][$type] as $value)
6967
		{
6968
			$values=explode(':',$value);
6969
6970
			if ($mode == 'add' && ! preg_match('/^\-/',$values[1]))
6971
			{
6972
				if (count($values) == 6)       // new declaration with permissions:  $value='objecttype:+tabname1:Title1:langfile@mymodule:$user->rights->mymodule->read:/mymodule/mynewtab1.php?id=__ID__'
6973
				{
6974
					if ($values[0] != $type) continue;
6975
6976
					if (verifCond($values[4]))
6977
					{
6978
						if ($values[3]) $langs->load($values[3]);
6979
						if (preg_match('/SUBSTITUTION_([^_]+)/i',$values[2],$reg))
6980
						{
6981
							$substitutionarray=array();
6982
							complete_substitutions_array($substitutionarray,$langs,$object,array('needforkey'=>$values[2]));
6983
							$label=make_substitutions($reg[1], $substitutionarray);
6984
						}
6985
						else $label=$langs->trans($values[2]);
6986
6987
						$head[$h][0] = dol_buildpath(preg_replace('/__ID__/i', ((is_object($object) && ! empty($object->id))?$object->id:''), $values[5]), 1);
6988
						$head[$h][1] = $label;
6989
						$head[$h][2] = str_replace('+','',$values[1]);
6990
						$h++;
6991
					}
6992
				}
6993
				else if (count($values) == 5)       // deprecated
6994
				{
6995
					dol_syslog('Passing 5 values in tabs module_parts is deprecated. Please update to 6 with permissions.', LOG_WARNING);
6996
6997
					if ($values[0] != $type) continue;
6998
					if ($values[3]) $langs->load($values[3]);
6999
					if (preg_match('/SUBSTITUTION_([^_]+)/i',$values[2],$reg))
7000
					{
7001
						$substitutionarray=array();
7002
						complete_substitutions_array($substitutionarray,$langs,$object,array('needforkey'=>$values[2]));
7003
						$label=make_substitutions($reg[1], $substitutionarray);
7004
					}
7005
					else $label=$langs->trans($values[2]);
7006
7007
					$head[$h][0] = dol_buildpath(preg_replace('/__ID__/i', ((is_object($object) && ! empty($object->id))?$object->id:''), $values[4]), 1);
7008
					$head[$h][1] = $label;
7009
					$head[$h][2] = str_replace('+','',$values[1]);
7010
					$h++;
7011
				}
7012
			}
7013
			else if ($mode == 'remove' && preg_match('/^\-/',$values[1]))
7014
			{
7015
				if ($values[0] != $type) continue;
7016
				$tabname=str_replace('-','',$values[1]);
7017
				foreach($head as $key => $val)
7018
				{
7019
					$condition = (! empty($values[3]) ? verifCond($values[3]) : 1);
7020
					//var_dump($key.' - '.$tabname.' - '.$head[$key][2].' - '.$values[3].' - '.$condition);
7021
					if ($head[$key][2]==$tabname && $condition)
7022
					{
7023
						unset($head[$key]);
7024
						break;
7025
					}
7026
				}
7027
			}
7028
		}
7029
	}
7030
7031
	// No need to make a return $head. Var is modified as a reference
7032
	if (! empty($hookmanager))
7033
	{
7034
		$parameters=array('object' => $object, 'mode' => $mode, 'head' => $head);
7035
		$reshook=$hookmanager->executeHooks('completeTabsHead', $parameters);
7036
		if ($reshook > 0)
7037
		{
7038
			$head = $hookmanager->resArray;
7039
            $h = count($head);
7040
		}
7041
	}
7042
}
7043
7044
/**
7045
 * Print common footer :
7046
 * 		conf->global->MAIN_HTML_FOOTER
7047
 *      js for switch of menu hider
7048
 * 		js for conf->global->MAIN_GOOGLE_AN_ID
7049
 * 		js for conf->global->MAIN_SHOW_TUNING_INFO or $_SERVER["MAIN_SHOW_TUNING_INFO"]
7050
 * 		js for conf->logbuffer
7051
 *
7052
 * @param	string	$zone	'private' (for private pages) or 'public' (for public pages)
7053
 * @return	void
7054
 */
7055
function printCommonFooter($zone='private')
7056
{
7057
	global $conf, $hookmanager, $user;
7058
	global $action;
7059
	global $micro_start_time;
7060
7061
	if ($zone == 'private') print "\n".'<!-- Common footer for private page -->'."\n";
7062
	else print "\n".'<!-- Common footer for public page -->'."\n";
7063
7064
	// A div to store page_y POST parameter so we can read it using javascript
7065
	print "\n<!-- A div to store page_y POST paramater -->\n";
7066
	print '<div id="page_y" style="display: none;">'.$_POST['page_y'].'</div>'."\n";
7067
7068
	$parameters=array();
7069
	$reshook=$hookmanager->executeHooks('printCommonFooter',$parameters);    // Note that $action and $object may have been modified by some hooks
7070
	if (empty($reshook))
7071
	{
7072
		if (! empty($conf->global->MAIN_HTML_FOOTER)) print $conf->global->MAIN_HTML_FOOTER."\n";
7073
7074
		print "\n";
7075
		if (! empty($conf->use_javascript_ajax))
7076
		{
7077
			print '<script type="text/javascript" language="javascript">'."\n";
7078
			print 'jQuery(document).ready(function() {'."\n";
7079
7080
			if ($zone == 'private' && empty($conf->dol_use_jmobile))
7081
			{
7082
				print "\n";
7083
				print '/* JS CODE TO ENABLE to enable handler to switch left menu page (menuhider) */'."\n";
7084
				print 'jQuery(".menuhider").click(function() {';
7085
				print '  console.log("We click on .menuhider");'."\n";
7086
				//print "  $('.side-nav').animate({width:'toggle'},200);\n";     // OK with eldy theme but not with md
7087
				print "  $('.side-nav').toggle()\n";
7088
				print "  $('.login_block').toggle()\n";
7089
				print '});'."\n";
7090
			}
7091
7092
			// Management of focus and mandatory for fields
7093
			if ($action == 'create' || $action == 'edit')
7094
			{
7095
				print '/* Code js to manage focus and mandatory form fields */'."\n";
7096
				$relativepathstring = $_SERVER["PHP_SELF"];
7097
				// Clean $relativepathstring
7098
				if (constant('DOL_URL_ROOT')) $relativepathstring = preg_replace('/^'.preg_quote(constant('DOL_URL_ROOT'),'/').'/', '', $relativepathstring);
7099
				$relativepathstring = preg_replace('/^\//', '', $relativepathstring);
7100
				$relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
7101
				$tmpqueryarraywehave=explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
7102
				if (!empty($user->default_values[$relativepathstring]['focus']))
7103
				{
7104
					foreach($user->default_values[$relativepathstring]['focus'] as $defkey => $defval)
7105
					{
7106
						$qualified = 0;
7107
						if ($defkey != '_noquery_')
7108
						{
7109
							$tmpqueryarraytohave=explode('&', $defkey);
7110
							$foundintru=0;
7111
							foreach($tmpqueryarraytohave as $tmpquerytohave)
7112
							{
7113
								if (! in_array($tmpquerytohave, $tmpqueryarraywehave)) $foundintru=1;
7114
							}
7115
							if (! $foundintru) $qualified=1;
7116
							//var_dump($defkey.'-'.$qualified);
7117
						}
7118
						else $qualified = 1;
7119
7120
						if ($qualified)
7121
						{
7122
							foreach($defval as $paramkey => $paramval)
7123
							{
7124
								// Add property 'required' on input
7125
								print 'jQuery("input[name=\''.$paramkey.'\']").focus();'."\n";
7126
							}
7127
						}
7128
					}
7129
				}
7130
				if (!empty($user->default_values[$relativepathstring]['mandatory']))
7131
				{
7132
					foreach($user->default_values[$relativepathstring]['mandatory'] as $defkey => $defval)
7133
					{
7134
						$qualified = 0;
7135
						if ($defkey != '_noquery_')
7136
						{
7137
							$tmpqueryarraytohave=explode('&', $defkey);
7138
							$foundintru=0;
7139
							foreach($tmpqueryarraytohave as $tmpquerytohave)
7140
							{
7141
								if (! in_array($tmpquerytohave, $tmpqueryarraywehave)) $foundintru=1;
7142
							}
7143
							if (! $foundintru) $qualified=1;
7144
							//var_dump($defkey.'-'.$qualified);
7145
						}
7146
						else $qualified = 1;
7147
7148
						if ($qualified)
7149
						{
7150
							foreach($defval as $paramkey => $paramval)
7151
							{
7152
								// Add property 'required' on input
7153
								print 'jQuery("input[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";
7154
								print 'jQuery("select[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";		// required on a select works only if key is "", this does not happen in Dolibarr
7155
							}
7156
						}
7157
					}
7158
				}
7159
			}
7160
7161
			print '});'."\n";
7162
7163
			// Google Analytics
7164
			// TODO Add a hook here
7165
			if (! empty($conf->google->enabled) && ! empty($conf->global->MAIN_GOOGLE_AN_ID))
7166
			{
7167
				if (($conf->dol_use_jmobile != 4))
7168
				{
7169
					print "\n";
7170
					print "/* JS CODE TO ENABLE for google analtics tag */\n";
7171
					print '  var _gaq = _gaq || [];'."\n";
7172
					print '  _gaq.push([\'_setAccount\', \''.$conf->global->MAIN_GOOGLE_AN_ID.'\']);'."\n";
7173
					print '  _gaq.push([\'_trackPageview\']);'."\n";
7174
					print ''."\n";
7175
					print '  (function() {'."\n";
7176
					print '    var ga = document.createElement(\'script\'); ga.type = \'text/javascript\'; ga.async = true;'."\n";
7177
					print '    ga.src = (\'https:\' == document.location.protocol ? \'https://ssl\' : \'http://www\') + \'.google-analytics.com/ga.js\';'."\n";
7178
					print '    var s = document.getElementsByTagName(\'script\')[0]; s.parentNode.insertBefore(ga, s);'."\n";
7179
					print '  })();'."\n";
7180
				}
7181
			}
7182
7183
			// End of tuning
7184
			if (! empty($_SERVER['MAIN_SHOW_TUNING_INFO']) || ! empty($conf->global->MAIN_SHOW_TUNING_INFO))
7185
			{
7186
				print "\n";
7187
				print "/* JS CODE TO ENABLE to add memory info */\n";
7188
				print 'window.console && console.log("';
7189
				if (! empty($conf->global->MEMCACHED_SERVER)) print 'MEMCACHED_SERVER='.$conf->global->MEMCACHED_SERVER.' - ';
7190
				print 'MAIN_OPTIMIZE_SPEED='.(isset($conf->global->MAIN_OPTIMIZE_SPEED)?$conf->global->MAIN_OPTIMIZE_SPEED:'off');
7191
				if (! empty($micro_start_time))   // Works only if MAIN_SHOW_TUNING_INFO is defined at $_SERVER level. Not in global variable.
7192
				{
7193
					$micro_end_time = microtime(true);
7194
					print ' - Build time: '.ceil(1000*($micro_end_time-$micro_start_time)).' ms';
7195
				}
7196
				if (function_exists("memory_get_usage"))
7197
				{
7198
					print ' - Mem: '.memory_get_usage();
7199
				}
7200
				if (function_exists("xdebug_memory_usage"))
7201
				{
7202
					print ' - XDebug time: '.ceil(1000*xdebug_time_index()).' ms';
7203
					print ' - XDebug mem: '.xdebug_memory_usage();
7204
					print ' - XDebug mem peak: '.xdebug_peak_memory_usage();
7205
				}
7206
				if (function_exists("zend_loader_file_encoded"))
7207
				{
7208
					print ' - Zend encoded file: '.(zend_loader_file_encoded()?'yes':'no');
7209
				}
7210
				print '");'."\n";
7211
			}
7212
7213
			print "\n".'</script>'."\n";
7214
		}
7215
7216
		// Add Xdebug coverage of code
7217
		if (defined('XDEBUGCOVERAGE'))
7218
		{
7219
			print_r(xdebug_get_code_coverage());
7220
		}
7221
7222
		// If there is some logs in buffer to show
7223
		if (count($conf->logbuffer))
7224
		{
7225
			print "\n";
7226
			print "<!-- Start of log output\n";
7227
			//print '<div class="hidden">'."\n";
7228
			foreach($conf->logbuffer as $logline)
7229
			{
7230
				print $logline."<br>\n";
7231
			}
7232
			//print '</div>'."\n";
7233
			print "End of log output -->\n";
7234
		}
7235
	}
7236
}
7237
7238
/**
7239
 * Split a string with 2 keys into key array.
7240
 * For example: "A=1;B=2;C=2" is exploded into array('A'=>1,'B'=>2,'C'=>3)
7241
 *
7242
 * @param 	string	$string		String to explode
7243
 * @param 	string	$delimiter	Delimiter between each couple of data
7244
 * @param 	string	$kv			Delimiter between key and value
7245
 * @return	array				Array of data exploded
7246
 */
7247
function dolExplodeIntoArray($string, $delimiter = ';', $kv = '=')
7248
{
7249
	if ($a = explode($delimiter, $string))
7250
	{
7251
		$ka = array();
7252
		foreach ($a as $s) { // each part
7253
			if ($s) {
7254
				if ($pos = strpos($s, $kv)) { // key/value delimiter
7255
					$ka[trim(substr($s, 0, $pos))] = trim(substr($s, $pos + strlen($kv)));
7256
				} else { // key delimiter not found
7257
					$ka[] = trim($s);
7258
				}
7259
			}
7260
		}
7261
		return $ka;
7262
	}
7263
	return array();
7264
}
7265
7266
7267
/**
7268
 * Set focus onto field with selector (similar behaviour of 'autofocus' HTML5 tag)
7269
 *
7270
 * @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.
7271
 * @return	string				HTML code to set focus
7272
 */
7273
function dol_set_focus($selector)
7274
{
7275
	print "\n".'<!-- Set focus onto a specific field -->'."\n";
7276
	print '<script type="text/javascript" language="javascript">jQuery(document).ready(function() { jQuery("'.dol_escape_js($selector).'").focus(); });</script>'."\n";
7277
}
7278
7279
7280
/**
7281
 * Return getmypid() or random PID when function is disabled
7282
 * Some web hosts disable this php function for security reasons
7283
 * and sometimes we can't redeclare function
7284
 *
7285
 * @return	int
7286
 */
7287
function dol_getmypid()
7288
{
7289
	if (! function_exists('getmypid')) {
7290
		return mt_rand(1,32768);
7291
	} else {
7292
		return getmypid();
7293
	}
7294
}
7295
7296
7297
/**
7298
 * Generate natural SQL search string for a criteria (this criteria can be tested on one or several fields)
7299
 *
7300
 * @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")
7301
 * @param   string 			$value 		The value to look for.
7302
 *                          		    If param $mode is 0, can contains several keywords separated with a space or |
7303
 *                                      like "keyword1 keyword2" = We want record field like keyword1 AND field like keyword2
7304
 *                                      or like "keyword1|keyword2" = We want record field like keyword1 OR field like keyword2
7305
 *                             			If param $mode is 1, can contains an operator <, > or = like "<10" or ">=100.5 < 1000"
7306
 *                             			If param $mode is 2, can contains a list of int id separated by comma like "1,3,4"
7307
 *                             			If param $mode is 3, can contains a list of string separated by comma like "a,b,c"
7308
 * @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')
7309
 * 										3=value is list of string separated with comma (Example 'text 1,text 2'), 4=value is a list of ID separated with comma (Example '1,3,4') for search into a multiselect string ('1,2')
7310
 * @param	integer			$nofirstand	1=Do not output the first 'AND'
7311
 * @return 	string 			$res 		The statement to append to the SQL query
7312
 */
7313
function natural_search($fields, $value, $mode=0, $nofirstand=0)
7314
{
7315
	global $db,$langs;
7316
7317
	$value=trim($value);
7318
7319
	if ($mode == 0)
7320
	{
7321
		$value=preg_replace('/\*/','%',$value);	// Replace * with %
7322
	}
7323
	if ($mode == 1)
7324
	{
7325
		$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
7326
	}
7327
7328
	$value = preg_replace('/\s*\|\s*/','|', $value);
7329
7330
	$crits = explode(' ', $value);
7331
	$res = '';
7332
	if (! is_array($fields)) $fields = array($fields);
7333
7334
	$nboffields = count($fields);
7335
	$end2 = count($crits);
7336
	$j = 0;
7337
	foreach ($crits as $crit)
7338
	{
7339
		$i = 0; $i2 = 0;
7340
		$newres = '';
7341
		foreach ($fields as $field)
7342
		{
7343
			if ($mode == 1)
7344
			{
7345
				$operator='=';
7346
				$newcrit = preg_replace('/([<>=]+)/','',trim($crit));
7347
7348
				preg_match('/([<>=]+)/',trim($crit), $reg);
7349
				if ($reg[1])
7350
				{
7351
					$operator = $reg[1];
7352
				}
7353
				if ($newcrit != '')
7354
				{
7355
					$numnewcrit = price2num($newcrit);
7356
					if (is_numeric($numnewcrit))
7357
					{
7358
						$newres .= ($i2 > 0 ? ' OR ' : '') . $field . ' '.$operator.' '.$numnewcrit;
7359
					}
7360
					else
7361
					{
7362
						$newres .= ($i2 > 0 ? ' OR ' : '') . '1 = 2';	// force false
7363
					}
7364
					$i2++;	// a criteria was added to string
7365
				}
7366
			}
7367
			else if ($mode == 2)
7368
			{
7369
				$newres .= ($i2 > 0 ? ' OR ' : '') . $field . " IN (" . $db->escape(trim($crit)) . ")";
7370
				$i2++;	// a criteria was added to string
7371
			}
7372
			else if ($mode == 3)
7373
			{
7374
				$tmparray=explode(',',trim($crit));
7375
				if (count($tmparray))
7376
				{
7377
					$listofcodes='';
7378
					foreach($tmparray as $val)
7379
					{
7380
						if ($val)
7381
						{
7382
							$listofcodes.=($listofcodes?',':'');
7383
							$listofcodes.="'".$db->escape(trim($val))."'";
7384
						}
7385
					}
7386
					$newres .= ($i2 > 0 ? ' OR ' : '') . $field . " IN (" . $listofcodes . ")";
7387
					$i2++;	// a criteria was added to string
7388
				}
7389
			}
7390
			else if ($mode == 4)
7391
			{
7392
			    $tmparray=explode(',',trim($crit));
7393
			    if (count($tmparray))
7394
			    {
7395
			        $listofcodes='';
7396
			        foreach($tmparray as $val)
7397
			        {
7398
			            if ($val)
7399
			            {
7400
			                $newres .= ($i2 > 0 ? ' OR (' : '(') . $field . ' LIKE \'' . $db->escape(trim($val)) . ',%\'';
7401
			                $newres .= ' OR '. $field . ' = \'' . $db->escape(trim($val)) . '\'';
7402
			                $newres .= ' OR '. $field . ' LIKE \'%,' . $db->escape(trim($val)) . '\'';
7403
			                $newres .= ' OR '. $field . ' LIKE \'%,' . $db->escape(trim($val)) . ',%\'';
7404
			                $newres .= ')';
7405
			                $i2++;
7406
			            }
7407
			        }
7408
			    }
7409
			}
7410
			else    // $mode=0
7411
			{
7412
				$textcrit = '';
7413
				$tmpcrits = explode('|',$crit);
7414
				$i3 = 0;
7415
				foreach($tmpcrits as $tmpcrit)
7416
				{
7417
					if(empty($tmpcrit)) continue;
7418
7419
					$newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : '');
7420
7421
					if (preg_match('/\.(id|rowid)$/', $field))	// Special case for rowid that is sometimes a ref so used as a search field
7422
					{
7423
						$newres .= $field . " = " . (is_numeric(trim($tmpcrit))?trim($tmpcrit):'0');
7424
					}
7425
					else
7426
					{
7427
						$newres .= $field . " LIKE '";
7428
7429
						$tmpcrit=trim($tmpcrit);
7430
						$tmpcrit2=$tmpcrit;
7431
						$tmpbefore='%'; $tmpafter='%';
7432
						if (preg_match('/^[\^\$]/', $tmpcrit))
7433
						{
7434
							$tmpbefore='';
7435
							$tmpcrit2 = preg_replace('/^[\^\$]/', '', $tmpcrit2);
7436
						}
7437
						if (preg_match('/[\^\$]$/', $tmpcrit))
7438
						{
7439
							$tmpafter='';
7440
							$tmpcrit2 = preg_replace('/[\^\$]$/', '', $tmpcrit2);
7441
						}
7442
						$newres .= $tmpbefore;
7443
						$newres .= $db->escape($tmpcrit2);
7444
						$newres .= $tmpafter;
7445
						$newres .= "'";
7446
						if ($tmpcrit2 == '')
7447
						{
7448
							$newres .= ' OR ' . $field . " IS NULL";
7449
						}
7450
					}
7451
7452
					$i3++;
7453
				}
7454
				$i2++;	// a criteria was added to string
7455
			}
7456
			$i++;
7457
		}
7458
		if ($newres) $res = $res . ($res ? ' AND ' : '') . ($i2 > 1 ? '(' : '') .$newres . ($i2 > 1 ? ')' : '');
7459
		$j++;
7460
	}
7461
	$res = ($nofirstand?"":" AND ")."(" . $res . ")";
7462
	//print 'xx'.$res.'yy';
7463
	return $res;
7464
}
7465
7466
/**
7467
 * Return string with full Url. The file qualified is the one defined by relative path in $object->last_main_doc
7468
 *
7469
 * @param   Object	$object				Object
7470
 * @return	string						Url string
7471
 */
7472
function showDirectDownloadLink($object)
7473
{
7474
	global $conf, $langs;
7475
7476
	$out='';
7477
	$url = $object->getLastMainDocLink($object->element);
7478
7479
	if ($url)
7480
	{
7481
		$out.= img_picto('','object_globe.png').' '.$langs->trans("DirectDownloadLink").'<br>';
7482
		$out.= '<input type="text" id="directdownloadlink" class="quatrevingtpercent" value="'.$url.'">';
7483
		$out.= ajax_autoselect("directdownloadlink", 0);
7484
	}
7485
	return $out;
7486
}
7487
7488
/**
7489
 * Return the filename of file to get the thumbs
7490
 *
7491
 * @param   string  $file           Original filename (full or relative path)
7492
 * @param   string  $extName        Extension to differenciate thumb file name ('', '_small', '_mini')
7493
 * @param   string  $extImgTarget   Force image extension for thumbs. Use '' to keep same extension than original image (default).
7494
 * @return  string                  New file name (full or relative path, including the thumbs/)
7495
 */
7496
function getImageFileNameForSize($file, $extName, $extImgTarget='')
7497
{
7498
	$dirName = dirname($file);
7499
	if ($dirName == '.') $dirName='';
7500
7501
	$fileName = preg_replace('/(\.gif|\.jpeg|\.jpg|\.png|\.bmp)$/i','',$file);	// We remove extension, whatever is its case
7502
	$fileName = basename($fileName);
7503
7504
	if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.jpg$/i',$file)?'.jpg':'');
7505
	if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.jpeg$/i',$file)?'.jpeg':'');
7506
	if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.gif$/i',$file)?'.gif':'');
7507
	if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.png$/i',$file)?'.png':'');
7508
	if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.bmp$/i',$file)?'.bmp':'');
7509
7510
	if (! $extImgTarget) return $file;
7511
7512
	$subdir='';
7513
	if ($extName) $subdir = 'thumbs/';
7514
7515
	return ($dirName?$dirName.'/':'').$subdir.$fileName.$extName.$extImgTarget; // New filename for thumb
7516
}
7517
7518
7519
/**
7520
 * Return URL we can use for advanced preview links
7521
 *
7522
 * @param   string    $modulepart     propal, facture, facture_fourn, ...
7523
 * @param   string    $relativepath   Relative path of docs.
7524
 * @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)
7525
 * @param	string	  $param		  More param on http links
7526
 * @return  string|array              Output string with href link or array with all components of link
7527
 */
7528
function getAdvancedPreviewUrl($modulepart, $relativepath, $alldata=0, $param='')
7529
{
7530
	global $conf, $langs;
7531
7532
	if (empty($conf->use_javascript_ajax)) return '';
7533
7534
	$mime_preview = array('bmp', 'jpeg', 'png', 'gif', 'tiff', 'pdf', 'plain', 'css', 'svg+xml');
7535
	//$mime_preview[]='vnd.oasis.opendocument.presentation';
7536
	//$mime_preview[]='archive';
7537
	$num_mime = array_search(dol_mimetype($relativepath, '', 1), $mime_preview);
7538
7539
	if ($alldata == 1)
7540
	{
7541
		if ($num_mime !== false) 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), );
7542
		else return array();
7543
	}
7544
7545
	// old behavior
7546
	if ($num_mime !== false) 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')).'\')';
7547
	else return '';
7548
}
7549
7550
7551
/**
7552
 * Make content of an input box selected when we click into input field.
7553
 *
7554
 * @param string	$htmlname	Id of html object
7555
 * @param string	$addlink	Add a 'link to' after
7556
 * @return string
7557
 */
7558
function ajax_autoselect($htmlname, $addlink='')
7559
{
7560
	global $langs;
7561
	$out = '<script type="text/javascript">
7562
               jQuery(document).ready(function () {
7563
				    jQuery("#'.$htmlname.'").click(function() { jQuery(this).select(); } );
7564
				});
7565
		    </script>';
7566
	if ($addlink) $out.=' <a href="'.$addlink.'" target="_blank">'.$langs->trans("Link").'</a>';
7567
	return $out;
7568
}
7569
7570
7571
/**
7572
 *	Return mime type of a file
7573
 *
7574
 *	@param	string	$file		Filename we looking for MIME type
7575
 *  @param  string	$default    Default mime type if extension not found in known list
7576
 * 	@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
7577
 *	@return string 		    	Return a mime type family (text/xxx, application/xxx, image/xxx, audio, video, archive)
7578
 *  @see    image_format_supported (images.lib.php)
7579
 */
7580
function dol_mimetype($file, $default='application/octet-stream', $mode=0)
7581
{
7582
	$mime=$default;
7583
	$imgmime='other.png';
7584
	$famime='file-o';
7585
	$srclang='';
7586
7587
	$tmpfile=preg_replace('/\.noexe$/','',$file);
7588
7589
	// Text files
7590
	if (preg_match('/\.txt$/i',$tmpfile))         			   { $mime='text/plain';    $imgmime='text.png'; $famime='file-text-o'; }
7591
	if (preg_match('/\.rtx$/i',$tmpfile))                      { $mime='text/richtext'; $imgmime='text.png'; $famime='file-text-o'; }
7592
	if (preg_match('/\.csv$/i',$tmpfile))					   { $mime='text/csv';      $imgmime='text.png'; $famime='file-text-o'; }
7593
	if (preg_match('/\.tsv$/i',$tmpfile))					   { $mime='text/tab-separated-values'; $imgmime='text.png'; $famime='file-text-o'; }
7594
	if (preg_match('/\.(cf|conf|log)$/i',$tmpfile))            { $mime='text/plain';    $imgmime='text.png'; $famime='file-text-o'; }
7595
	if (preg_match('/\.ini$/i',$tmpfile))                      { $mime='text/plain';    $imgmime='text.png'; $srclang='ini'; $famime='file-text-o'; }
7596
	if (preg_match('/\.css$/i',$tmpfile))                      { $mime='text/css';      $imgmime='css.png'; $srclang='css'; $famime='file-text-o'; }
7597
	// Certificate files
7598
	if (preg_match('/\.(crt|cer|key|pub)$/i',$tmpfile))        { $mime='text/plain';    $imgmime='text.png'; $famime='file-text-o'; }
7599
	// HTML/XML
7600
	if (preg_match('/\.(html|htm|shtml)$/i',$tmpfile))         { $mime='text/html';     $imgmime='html.png'; $srclang='html'; $famime='file-text-o'; }
7601
	if (preg_match('/\.(xml|xhtml)$/i',$tmpfile))              { $mime='text/xml';      $imgmime='other.png'; $srclang='xml'; $famime='file-text-o'; }
7602
	// Languages
7603
	if (preg_match('/\.bas$/i',$tmpfile))                      { $mime='text/plain'; $imgmime='text.png'; $srclang='bas';  $famime='file-code-o'; }
7604
	if (preg_match('/\.(c)$/i',$tmpfile))                      { $mime='text/plain'; $imgmime='text.png'; $srclang='c';  $famime='file-code-o'; }
7605
	if (preg_match('/\.(cpp)$/i',$tmpfile))                    { $mime='text/plain'; $imgmime='text.png'; $srclang='cpp';  $famime='file-code-o'; }
7606
	if (preg_match('/\.(h)$/i',$tmpfile))                      { $mime='text/plain'; $imgmime='text.png'; $srclang='h';  $famime='file-code-o'; }
7607
	if (preg_match('/\.(java|jsp)$/i',$tmpfile))               { $mime='text/plain'; $imgmime='text.png'; $srclang='java';  $famime='file-code-o'; }
7608
	if (preg_match('/\.php([0-9]{1})?$/i',$tmpfile))           { $mime='text/plain'; $imgmime='php.png'; $srclang='php';  $famime='file-code-o'; }
7609
	if (preg_match('/\.phtml$/i',$tmpfile))                    { $mime='text/plain'; $imgmime='php.png'; $srclang='php';  $famime='file-code-o'; }
7610
	if (preg_match('/\.(pl|pm)$/i',$tmpfile))                  { $mime='text/plain'; $imgmime='pl.png'; $srclang='perl';  $famime='file-code-o'; }
7611
	if (preg_match('/\.sql$/i',$tmpfile))                      { $mime='text/plain'; $imgmime='text.png'; $srclang='sql';  $famime='file-code-o'; }
7612
	if (preg_match('/\.js$/i',$tmpfile))                       { $mime='text/x-javascript'; $imgmime='jscript.png'; $srclang='js';  $famime='file-code-o'; }
7613
	// Open office
7614
	if (preg_match('/\.odp$/i',$tmpfile))                      { $mime='application/vnd.oasis.opendocument.presentation'; $imgmime='ooffice.png'; $famime='file-powerpoint-o'; }
7615
	if (preg_match('/\.ods$/i',$tmpfile))                      { $mime='application/vnd.oasis.opendocument.spreadsheet';  $imgmime='ooffice.png'; $famime='file-excel-o'; }
7616
	if (preg_match('/\.odt$/i',$tmpfile))                      { $mime='application/vnd.oasis.opendocument.text';         $imgmime='ooffice.png'; $famime='file-word-o'; }
7617
	// MS Office
7618
	if (preg_match('/\.mdb$/i',$tmpfile))					   { $mime='application/msaccess'; $imgmime='mdb.png'; $famime='file-o'; }
7619
	if (preg_match('/\.doc(x|m)?$/i',$tmpfile))				   { $mime='application/msword'; $imgmime='doc.png'; $famime='file-word-o'; }
7620
	if (preg_match('/\.dot(x|m)?$/i',$tmpfile))				   { $mime='application/msword'; $imgmime='doc.png'; $famime='file-word-o'; }
7621
	if (preg_match('/\.xlt(x)?$/i',$tmpfile))				   { $mime='application/vnd.ms-excel'; $imgmime='xls.png'; $famime='file-excel-o'; }
7622
	if (preg_match('/\.xla(m)?$/i',$tmpfile))				   { $mime='application/vnd.ms-excel'; $imgmime='xls.png'; $famime='file-excel-o'; }
7623
	if (preg_match('/\.xls$/i',$tmpfile))			           { $mime='application/vnd.ms-excel'; $imgmime='xls.png'; $famime='file-excel-o'; }
7624
	if (preg_match('/\.xls(b|m|x)$/i',$tmpfile))			   { $mime='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; $imgmime='xls.png'; $famime='file-excel-o'; }
7625
	if (preg_match('/\.pps(m|x)?$/i',$tmpfile))				   { $mime='application/vnd.ms-powerpoint'; $imgmime='ppt.png'; $famime='file-powerpoint-o'; }
7626
	if (preg_match('/\.ppt(m|x)?$/i',$tmpfile))				   { $mime='application/x-mspowerpoint'; $imgmime='ppt.png'; $famime='file-powerpoint-o'; }
7627
	// Other
7628
	if (preg_match('/\.pdf$/i',$tmpfile))                      { $mime='application/pdf'; $imgmime='pdf.png'; $famime='file-pdf-o'; }
7629
	// Scripts
7630
	if (preg_match('/\.bat$/i',$tmpfile))                      { $mime='text/x-bat';  $imgmime='script.png'; $srclang='dos'; $famime='file-code-o'; }
7631
	if (preg_match('/\.sh$/i',$tmpfile))                       { $mime='text/x-sh';   $imgmime='script.png'; $srclang='bash'; $famime='file-code-o'; }
7632
	if (preg_match('/\.ksh$/i',$tmpfile))                      { $mime='text/x-ksh';  $imgmime='script.png'; $srclang='bash'; $famime='file-code-o'; }
7633
	if (preg_match('/\.bash$/i',$tmpfile))                     { $mime='text/x-bash'; $imgmime='script.png'; $srclang='bash'; $famime='file-code-o'; }
7634
	// Images
7635
	if (preg_match('/\.ico$/i',$tmpfile))                      { $mime='image/x-icon'; $imgmime='image.png'; $famime='file-image-o'; }
7636
	if (preg_match('/\.(jpg|jpeg)$/i',$tmpfile))			   { $mime='image/jpeg';   $imgmime='image.png'; $famime='file-image-o'; }
7637
	if (preg_match('/\.png$/i',$tmpfile))					   { $mime='image/png';    $imgmime='image.png'; $famime='file-image-o'; }
7638
	if (preg_match('/\.gif$/i',$tmpfile))					   { $mime='image/gif';    $imgmime='image.png'; $famime='file-image-o'; }
7639
	if (preg_match('/\.bmp$/i',$tmpfile))					   { $mime='image/bmp';    $imgmime='image.png'; $famime='file-image-o'; }
7640
	if (preg_match('/\.(tif|tiff)$/i',$tmpfile))			   { $mime='image/tiff';   $imgmime='image.png'; $famime='file-image-o'; }
7641
	if (preg_match('/\.svg$/i',$tmpfile))					   { $mime='image/svg+xml';$imgmime='image.png'; $famime='file-image-o'; }
7642
	// Calendar
7643
	if (preg_match('/\.vcs$/i',$tmpfile))					   { $mime='text/calendar'; $imgmime='other.png'; $famime='file-text-o'; }
7644
	if (preg_match('/\.ics$/i',$tmpfile))					   { $mime='text/calendar'; $imgmime='other.png'; $famime='file-text-o'; }
7645
	// Other
7646
	if (preg_match('/\.torrent$/i',$tmpfile))				   { $mime='application/x-bittorrent'; $imgmime='other.png'; $famime='file-o'; }
7647
	// Audio
7648
	if (preg_match('/\.(mp3|ogg|au|wav|wma|mid)$/i',$tmpfile)) { $mime='audio'; $imgmime='audio.png'; $famime='file-audio-o'; }
7649
	// Video
7650
	if (preg_match('/\.ogv$/i',$tmpfile))                      { $mime='video/ogg';       $imgmime='video.png'; $famime='file-video-o'; }
7651
	if (preg_match('/\.webm$/i',$tmpfile))                     { $mime='video/webm';      $imgmime='video.png'; $famime='file-video-o'; }
7652
	if (preg_match('/\.avi$/i',$tmpfile))                      { $mime='video/x-msvideo'; $imgmime='video.png'; $famime='file-video-o'; }
7653
	if (preg_match('/\.divx$/i',$tmpfile))                     { $mime='video/divx';      $imgmime='video.png'; $famime='file-video-o'; }
7654
	if (preg_match('/\.xvid$/i',$tmpfile))                     { $mime='video/xvid';      $imgmime='video.png'; $famime='file-video-o'; }
7655
	if (preg_match('/\.(wmv|mpg|mpeg)$/i',$tmpfile))           { $mime='video';           $imgmime='video.png'; $famime='file-video-o'; }
7656
	// Archive
7657
	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, ...
7658
	// Exe
7659
	if (preg_match('/\.(exe|com)$/i',$tmpfile))                { $mime='application/octet-stream'; $imgmime='other.png'; $famime='file-o'; }
7660
	// Lib
7661
	if (preg_match('/\.(dll|lib|o|so|a)$/i',$tmpfile))         { $mime='library'; $imgmime='library.png'; $famime='file-o'; }
7662
	// Err
7663
	if (preg_match('/\.err$/i',$tmpfile))                      { $mime='error'; $imgmime='error.png'; $famime='file-text-o'; }
7664
7665
	// Return string
7666
	if ($mode == 1)
7667
	{
7668
		$tmp=explode('/',$mime);
7669
		return (! empty($tmp[1])?$tmp[1]:$tmp[0]);
7670
	}
7671
	if ($mode == 2)
7672
	{
7673
		return $imgmime;
7674
	}
7675
	if ($mode == 3)
7676
	{
7677
		return $srclang;
7678
	}
7679
	if ($mode == 4)
7680
	{
7681
		return $famime;
7682
	}
7683
	return $mime;
7684
}
7685
7686
/**
7687
 * Return value from dictionary
7688
 *
7689
 * @param string	$tablename		name of dictionary
7690
 * @param string	$field			the value to return
7691
 * @param int		$id				id of line
7692
 * @param bool		$checkentity	add filter on entity
7693
 * @param string	$rowidfield		name of the column rowid
7694
 * @return string
7695
 */
7696
function getDictvalue($tablename, $field, $id, $checkentity=false, $rowidfield='rowid')
7697
{
7698
	global $dictvalues,$db,$langs;
7699
7700
	if (!isset($dictvalues[$tablename]))
7701
	{
7702
		$dictvalues[$tablename] = array();
7703
		$sql = 'SELECT * FROM '.$tablename.' WHERE 1';
7704
		if ($checkentity) $sql.= ' AND entity IN (0,'.getEntity($tablename).')';
7705
7706
		$resql = $db->query($sql);
7707
		if ($resql)
7708
		{
7709
			while ($obj = $db->fetch_object($resql))
7710
			{
7711
				$dictvalues[$tablename][$obj->{$rowidfield}] = $obj;
7712
			}
7713
		}
7714
		else
7715
		{
7716
			dol_print_error($db);
7717
		}
7718
	}
7719
7720
	if (!empty($dictvalues[$tablename][$id])) return $dictvalues[$tablename][$id]->{$field}; // Found
7721
	else // Not found
7722
	{
7723
		if ($id > 0) return $id;
7724
		return '';
7725
	}
7726
}
7727
7728
/**
7729
 *	Return true if the color is light
7730
 *
7731
 *  @param	string	$stringcolor		String with hex (FFFFFF) or comma RGB ('255,255,255')
7732
 *  @return	int							-1 : Error with argument passed |0 : color is dark | 1 : color is light
7733
 */
7734
function colorIsLight($stringcolor)
7735
{
7736
	$res = -1;
7737
	if (!empty($stringcolor))
7738
	{
7739
		$res = 0;
7740
		$tmp=explode(',', $stringcolor);
7741
		if (count($tmp) > 1)   // This is a comma RGB ('255','255','255')
7742
		{
7743
			$r = $tmp[0];
7744
			$g = $tmp[1];
7745
			$b = $tmp[2];
7746
		}
7747
		else
7748
		{
7749
			$hexr=$stringcolor[0].$stringcolor[1];
7750
			$hexg=$stringcolor[2].$stringcolor[3];
7751
			$hexb=$stringcolor[4].$stringcolor[5];
7752
			$r = hexdec($hexr);
7753
			$g = hexdec($hexg);
7754
			$b = hexdec($hexb);
7755
		}
7756
		$bright = (max($r, $g, $b) + min($r, $g, $b)) / 510.0;    // HSL algorithm
7757
		if ($bright > 0.6) $res = 1;
7758
	}
7759
	return $res;
7760
}
7761
7762
/**
7763
 * Function to test if an entry is enabled or not
7764
 *
7765
 * @param	string		$type_user					0=We test for internal user, 1=We test for external user
7766
 * @param	array		$menuentry					Array for feature entry to test
7767
 * @param	array		$listofmodulesforexternal	Array with list of modules allowed to external users
7768
 * @return	int										0=Hide, 1=Show, 2=Show gray
7769
 */
7770
function isVisibleToUserType($type_user, &$menuentry, &$listofmodulesforexternal)
7771
{
7772
	global $conf;
7773
7774
	//print 'type_user='.$type_user.' module='.$menuentry['module'].' enabled='.$menuentry['enabled'].' perms='.$menuentry['perms'];
7775
	//print 'ok='.in_array($menuentry['module'], $listofmodulesforexternal);
7776
	if (empty($menuentry['enabled'])) return 0;	// Entry disabled by condition
7777
	if ($type_user && $menuentry['module'])
7778
	{
7779
		$tmploops=explode('|',$menuentry['module']);
7780
		$found=0;
7781
		foreach($tmploops as $tmploop)
7782
		{
7783
			if (in_array($tmploop, $listofmodulesforexternal)) {
7784
				$found++; break;
7785
			}
7786
		}
7787
		if (! $found) return 0;	// Entry is for menus all excluded to external users
7788
	}
7789
	if (! $menuentry['perms'] && $type_user) return 0; 											// No permissions and user is external
7790
	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
7791
	if (! $menuentry['perms']) return 2;															// No permissions and user is external
7792
	return 1;
7793
}
7794
7795
/**
7796
 * Round to next multiple.
7797
 *
7798
 * @param 	double		$n		Number to round up
7799
 * @param 	integer		$x		Multiple. For example 60 to round up to nearest exact minute for a date with seconds.
7800
 * @return 	integer				Value rounded.
7801
 */
7802
function roundUpToNextMultiple($n, $x=5)
7803
{
7804
	return (ceil($n)%$x === 0) ? ceil($n) : round(($n+$x/2)/$x)*$x;
7805
}
7806