Passed
Branch develop (4a815d)
by
unknown
26:55
created

dol_microtime_float()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

Loading history...
6034
				{
6035
					if (is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label']) > 0)
6036
					{
6037
						foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) {
6038
							$substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = $object->array_options['options_' . $key];
6039
						}
6040
					}
6041
				}
6042
			}
6043
6044
			// Complete substitution array with the url to make online payment
6045
			$paymenturl='';
6046
			if (empty($substitutionarray['__REF__']))
6047
			{
6048
				$paymenturl='';
6049
			}
6050
			else
6051
			{
6052
				// Set the online payment url link into __ONLINE_PAYMENT_URL__ key
6053
				require_once DOL_DOCUMENT_ROOT.'/core/lib/payments.lib.php';
6054
				$outputlangs->loadLangs(array('paypal','other'));
6055
				$typeforonlinepayment='free';
6056
				if (is_object($object) && $object->element == 'commande') $typeforonlinepayment='order';
6057
				if (is_object($object) && $object->element == 'facture')  $typeforonlinepayment='invoice';
6058
				if (is_object($object) && $object->element == 'member')   $typeforonlinepayment='member';
6059
				$url=getOnlinePaymentUrl(0, $typeforonlinepayment, $substitutionarray['__REF__']);
6060
				$paymenturl=$url;
6061
			}
6062
6063
			if ($object->id > 0)
6064
			{
6065
				$substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__']=($paymenturl?str_replace('\n', "\n", $outputlangs->trans("PredefinedMailContentLink", $paymenturl)):'');
6066
				$substitutionarray['__ONLINE_PAYMENT_URL__']=$paymenturl;
6067
6068
				if (! empty($conf->global->PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'propal')
6069
				{
6070
					$substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = $object->getLastMainDocLink($object->element);
6071
				}
6072
				else $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = '';
6073
				if (! empty($conf->global->ORDER_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'commande')
6074
				{
6075
					$substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = $object->getLastMainDocLink($object->element);
6076
				}
6077
				else $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = '';
6078
				if (! empty($conf->global->INVOICE_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'facture')
6079
				{
6080
					$substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = $object->getLastMainDocLink($object->element);
6081
				}
6082
				else $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = '';
6083
			}
6084
		}
6085
	}
6086
	if (empty($exclude) || ! in_array('objectamount', $exclude))
6087
	{
6088
		$substitutionarray['__DATE_YMD__']        = is_object($object)?(isset($object->date) ? dol_print_date($object->date, 'day', 0, $outputlangs) : null) : '';
6089
		$substitutionarray['__DATE_DUE_YMD__']    = is_object($object)?(isset($object->date_lim_reglement) ? dol_print_date($object->date_lim_reglement, 'day', 0, $outputlangs) : null) : '';
6090
6091
		$substitutionarray['__AMOUNT__']          = is_object($object)?$object->total_ttc:'';
6092
		$substitutionarray['__AMOUNT_EXCL_TAX__'] = is_object($object)?$object->total_ht:'';
6093
		$substitutionarray['__AMOUNT_VAT__']      = is_object($object)?($object->total_vat?$object->total_vat:$object->total_tva):'';
6094
		if ($onlykey != 2 || $mysoc->useLocalTax(1)) $substitutionarray['__AMOUNT_TAX2__']     = is_object($object)?$object->total_localtax1:'';
6095
		if ($onlykey != 2 || $mysoc->useLocalTax(2)) $substitutionarray['__AMOUNT_TAX3__']     = is_object($object)?$object->total_localtax2:'';
6096
6097
		$substitutionarray['__AMOUNT_FORMATED__']          = is_object($object)?($object->total_ttc ? price($object->total_ttc, 0, $outputlangs, 0, 0, -1, $conf->currency) : null):'';
6098
		$substitutionarray['__AMOUNT_EXCL_TAX_FORMATED__'] = is_object($object)?($object->total_ht ? price($object->total_ht, 0, $outputlangs, 0, 0, -1, $conf->currency) : null):'';
6099
		$substitutionarray['__AMOUNT_VAT_FORMATED__']      = is_object($object)?($object->total_vat ? price($object->total_vat, 0, $outputlangs, 0, 0, -1, $conf->currency): ($object->total_tva ? price($object->total_tva, 0, $outputlangs, 0, 0, -1, $conf->currency) : null)):'';
6100
		if ($onlykey != 2 || $mysoc->useLocalTax(1)) $substitutionarray['__AMOUNT_TAX2_FORMATED__']     = is_object($object)? ($object->total_localtax1 ? price($object->total_localtax1, 0, $outputlangs, 0, 0, -1, $conf->currency) : null):'';
6101
		if ($onlykey != 2 || $mysoc->useLocalTax(2)) $substitutionarray['__AMOUNT_TAX3_FORMATED__']     = is_object($object)? ($object->total_localtax2 ? price($object->total_localtax2, 0, $outputlangs, 0, 0, -1, $conf->currency) : null):'';
6102
6103
		// TODO Add keys for foreign multicurrency
6104
6105
		// For backward compatibility
6106
		if ($onlykey != 2)
6107
		{
6108
			$substitutionarray['__TOTAL_TTC__']    = is_object($object)?$object->total_ttc:'';
6109
			$substitutionarray['__TOTAL_HT__']     = is_object($object)?$object->total_ht:'';
6110
			$substitutionarray['__TOTAL_VAT__']    = is_object($object)?($object->total_vat?$object->total_vat:$object->total_tva):'';
6111
		}
6112
	}
6113
6114
	//var_dump($substitutionarray['__AMOUNT_FORMATED__']);
6115
	if (empty($exclude) || ! in_array('date', $exclude))
6116
	{
6117
		include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
6118
6119
		$tmp=dol_getdate(dol_now(), true);
6120
		$tmp2=dol_get_prev_day($tmp['mday'], $tmp['mon'], $tmp['year']);
6121
		$tmp3=dol_get_prev_month($tmp['mon'], $tmp['year']);
6122
		$tmp4=dol_get_next_day($tmp['mday'], $tmp['mon'], $tmp['year']);
6123
		$tmp5=dol_get_next_month($tmp['mon'], $tmp['year']);
6124
6125
        $substitutionarray=array_merge($substitutionarray, array(
6126
			'__DAY__' => (string) $tmp['mday'],
6127
			'__DAY_TEXT__' => $outputlangs->trans('Day'.$tmp['wday']),					// Monday
6128
			'__DAY_TEXT_SHORT__' => $outputlangs->trans($tmp['weekday'].'Min'),			// Mon
6129
			'__DAY_TEXT_MIN__' => $outputlangs->trans('Short'.$tmp['weekday']),			// M
6130
			'__MONTH__' => (string) $tmp['mon'],
6131
			'__MONTH_TEXT__' => $outputlangs->trans('Month'.sprintf("%02d", $tmp['mon'])),
6132
			'__MONTH_TEXT_SHORT__' => $outputlangs->trans('MonthShort'.sprintf("%02d", $tmp['mon'])),
6133
			'__MONTH_TEXT_MIN__' => $outputlangs->trans('MonthVeryShort'.sprintf("%02d", $tmp['mon'])),
6134
			'__YEAR__' => (string) $tmp['year'],
6135
			'__PREVIOUS_DAY__' => (string) $tmp2['day'],
6136
			'__PREVIOUS_MONTH__' => (string) $tmp3['month'],
6137
			'__PREVIOUS_YEAR__' => (string) ($tmp['year'] - 1),
6138
			'__NEXT_DAY__' => (string) $tmp4['day'],
6139
			'__NEXT_MONTH__' => (string) $tmp5['month'],
6140
			'__NEXT_YEAR__' => (string) ($tmp['year'] + 1),
6141
		));
6142
	}
6143
6144
	if (! empty($conf->multicompany->enabled))
6145
	{
6146
		$substitutionarray=array_merge($substitutionarray, array('__ENTITY_ID__' => $conf->entity));
6147
	}
6148
	if (empty($exclude) || ! in_array('system', $exclude))
6149
	{
6150
		$substitutionarray['__DOL_MAIN_URL_ROOT__']=DOL_MAIN_URL_ROOT;
6151
		$substitutionarray['__(AnyTranslationKey)__']=$outputlangs->trans('TranslationOfKey');
6152
		$substitutionarray['__(AnyTranslationKey|langfile)__']=$outputlangs->trans('TranslationOfKey').' (load also language file before)';
6153
		$substitutionarray['__[AnyConstantKey]__']=$outputlangs->trans('ValueOfConstantKey');
6154
	}
6155
6156
	return $substitutionarray;
6157
}
6158
6159
/**
6160
 *  Make substitution into a text string, replacing keys with vals from $substitutionarray (oldval=>newval),
6161
 *  and texts like __(TranslationKey|langfile)__ and __[ConstantKey]__ are also replaced.
6162
 *  Example of usage:
6163
 *  $substitutionarray = getCommonSubstitutionArray($langs, 0, null, $thirdparty);
6164
 *  complete_substitutions_array($substitutionarray, $langs, $thirdparty);
6165
 *  $mesg = make_substitutions($mesg, $substitutionarray, $langs);
6166
 *
6167
 *  @param	string		$text	      			Source string in which we must do substitution
6168
 *  @param  array		$substitutionarray		Array with key->val to substitute. Example: array('__MYKEY__' => 'MyVal', ...)
6169
 *  @param	Translate	$outputlangs			Output language
6170
 * 	@return string  		    				Output string after substitutions
6171
 *  @see	complete_substitutions_array(), getCommonSubstitutionArray()
6172
 */
6173
function make_substitutions($text, $substitutionarray, $outputlangs = null)
6174
{
6175
	global $conf, $langs;
6176
6177
	if (! is_array($substitutionarray)) return 'ErrorBadParameterSubstitutionArrayWhenCalling_make_substitutions';
1 ignored issue
show
introduced by
The condition is_array($substitutionarray) is always true.
Loading history...
6178
6179
	if (empty($outputlangs)) $outputlangs=$langs;
6180
6181
	// Make substitution for language keys: __(AnyTranslationKey)__ or __(AnyTranslationKey|langfile)__
6182
	if (is_object($outputlangs))
6183
	{
6184
		while (preg_match('/__\(([^\)]+)\)__/', $text, $reg))
6185
		{
6186
			$msgishtml = 0;
6187
			if (dol_textishtml($text, 1)) $msgishtml = 1;
6188
6189
			// If key is __(TranslationKey|langfile)__, then force load of langfile.lang
6190
			$tmp=explode('|', $reg[1]);
6191
			if (! empty($tmp[1])) $outputlangs->load($tmp[1]);
6192
6193
			$text = preg_replace('/__\('.preg_quote($reg[1], '/').'\)__/', $msgishtml?dol_htmlentitiesbr($outputlangs->transnoentitiesnoconv($reg[1])):$outputlangs->transnoentitiesnoconv($reg[1]), $text);
6194
		}
6195
	}
6196
6197
	// Make substitution for constant keys.
6198
	// Must be after the substitution of translation, so if the text of translation contains a string __[xxx]__, it is also converted.
6199
	while (preg_match('/__\[([^\]]+)\]__/', $text, $reg))
6200
	{
6201
		$msgishtml = 0;
6202
		if (dol_textishtml($text, 1)) $msgishtml = 1;
6203
6204
		$keyfound = $reg[1];
6205
		if (preg_match('/(_pass|password|secret|_key|key$)/i', $keyfound)) $newval = '*****forbidden*****';
6206
		else $newval=empty($conf->global->$keyfound)?'':$conf->global->$keyfound;
6207
		$text = preg_replace('/__\['.preg_quote($keyfound, '/').'\]__/', $msgishtml?dol_htmlentitiesbr($newval):$newval, $text);
6208
	}
6209
6210
	// Make substitition for array $substitutionarray
6211
	foreach ($substitutionarray as $key => $value)
6212
	{
6213
		if (! isset($value)) continue;	// If value is null, it same than not having substitution key at all into array, we do not replace.
6214
6215
		if ($key == '__SIGNATURE__' && (! empty($conf->global->MAIN_MAIL_DO_NOT_USE_SIGN))) $value='';		// Protection
6216
		if ($key == '__USER_SIGNATURE__' && (! empty($conf->global->MAIN_MAIL_DO_NOT_USE_SIGN))) $value='';	// Protection
6217
6218
		$text=str_replace("$key", "$value", $text);	// We must keep the " to work when value is 123.5 for example
6219
	}
6220
6221
	return $text;
6222
}
6223
6224
/**
6225
 *  Complete the $substitutionarray with more entries coming from external module that had set the "substitutions=1" into module_part array.
6226
 *  In this case, method completesubstitutionarray provided by module is called.
6227
 *
6228
 *  @param  array		$substitutionarray		Array substitution old value => new value value
6229
 *  @param  Translate	$outputlangs            Output language
6230
 *  @param  Object		$object                 Source object
6231
 *  @param  mixed		$parameters       		Add more parameters (useful to pass product lines)
6232
 *  @param  string      $callfunc               What is the name of the custom function that will be called? (default: completesubstitutionarray)
6233
 *  @return	void
6234
 *  @see 	make_substitutions()
6235
 */
6236
function complete_substitutions_array(&$substitutionarray, $outputlangs, $object = null, $parameters = null, $callfunc = "completesubstitutionarray")
6237
{
6238
	global $conf,$user;
6239
6240
	require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
6241
6242
	// Add a substitution key for each extrafields, using key __EXTRA_XXX__
6243
	// TODO Remove this. Already available into the getCommonSubstitutionArray used to build the substitution array.
6244
	/*if (is_object($object) && is_array($object->array_options))
6245
	{
6246
		foreach($object->array_options as $key => $val)
6247
		{
6248
			$keyshort=preg_replace('/^(options|extra)_/','',$key);
6249
			$substitutionarray['__EXTRAFIELD_'.$keyshort.'__']=$val;
6250
			// For backward compatibiliy
6251
			$substitutionarray['%EXTRA_'.$keyshort.'%']=$val;
6252
		}
6253
	}*/
6254
6255
	// Check if there is external substitution to do, requested by plugins
6256
	$dirsubstitutions=array_merge(array(), (array) $conf->modules_parts['substitutions']);
6257
6258
	foreach($dirsubstitutions as $reldir)
6259
	{
6260
		$dir=dol_buildpath($reldir, 0);
6261
6262
		// Check if directory exists
6263
		if (! dol_is_dir($dir)) continue;
6264
6265
		$substitfiles=dol_dir_list($dir, 'files', 0, 'functions_');
6266
		foreach($substitfiles as $substitfile)
6267
		{
6268
			if (preg_match('/functions_(.*)\.lib\.php/i', $substitfile['name'], $reg))
6269
			{
6270
				$module=$reg[1];
6271
6272
				dol_syslog("Library ".$substitfile['name']." found into ".$dir);
6273
				// Include the user's functions file
6274
				require_once $dir.$substitfile['name'];
6275
				// Call the user's function, and only if it is defined
6276
				$function_name=$module."_".$callfunc;
6277
				if (function_exists($function_name)) $function_name($substitutionarray, $outputlangs, $object, $parameters);
6278
			}
6279
		}
6280
	}
6281
}
6282
6283
/**
6284
 *    Format output for start and end date
6285
 *
6286
 *    @param	int	$date_start    Start date
6287
 *    @param    int	$date_end      End date
6288
 *    @param    string		$format        Output format
6289
 *    @param	Translate	$outputlangs   Output language
6290
 *    @return	void
6291
 */
6292
function print_date_range($date_start, $date_end, $format = '', $outputlangs = '')
6293
{
6294
	print get_date_range($date_start, $date_end, $format, $outputlangs);
6295
}
6296
6297
/**
6298
 *    Format output for start and end date
6299
 *
6300
 *    @param	int			$date_start    		Start date
6301
 *    @param    int			$date_end      		End date
6302
 *    @param    string		$format        		Output format
6303
 *    @param	Translate	$outputlangs   		Output language
6304
 *    @param	integer		$withparenthesis	1=Add parenthesis, 0=non parenthesis
6305
 *    @return	string							String
6306
 */
6307
function get_date_range($date_start, $date_end, $format = '', $outputlangs = '', $withparenthesis = 1)
6308
{
6309
	global $langs;
6310
6311
	$out='';
6312
6313
	if (! is_object($outputlangs)) $outputlangs=$langs;
6314
6315
	if ($date_start && $date_end)
6316
	{
6317
		$out.= ($withparenthesis?' (':'').$outputlangs->transnoentitiesnoconv('DateFromTo', dol_print_date($date_start, $format, false, $outputlangs), dol_print_date($date_end, $format, false, $outputlangs)).($withparenthesis?')':'');
6318
	}
6319
	if ($date_start && ! $date_end)
6320
	{
6321
		$out.= ($withparenthesis?' (':'').$outputlangs->transnoentitiesnoconv('DateFrom', dol_print_date($date_start, $format, false, $outputlangs)).($withparenthesis?')':'');
6322
	}
6323
	if (! $date_start && $date_end)
6324
	{
6325
		$out.= ($withparenthesis?' (':'').$outputlangs->transnoentitiesnoconv('DateUntil', dol_print_date($date_end, $format, false, $outputlangs)).($withparenthesis?')':'');
6326
	}
6327
6328
	return $out;
6329
}
6330
6331
/**
6332
 * Return firstname and lastname in correct order
6333
 *
6334
 * @param	string	$firstname		Firstname
6335
 * @param	string	$lastname		Lastname
6336
 * @param	int		$nameorder		-1=Auto, 0=Lastname+Firstname, 1=Firstname+Lastname, 2=Firstname, 3=Firstname if defined else lastname
6337
 * @return	string					Firstname + lastname or Lastname + firstname
6338
 */
6339
function dolGetFirstLastname($firstname, $lastname, $nameorder = -1)
6340
{
6341
	global $conf;
6342
6343
	$ret='';
6344
	// If order not defined, we use the setup
6345
	if ($nameorder < 0) $nameorder=(empty($conf->global->MAIN_FIRSTNAME_NAME_POSITION)?1:0);
6346
	if ($nameorder && $nameorder != 2 && $nameorder != 3)
6347
	{
6348
		$ret.=$firstname;
6349
		if ($firstname && $lastname) $ret.=' ';
6350
		$ret.=$lastname;
6351
	}
6352
	elseif ($nameorder == 2 || $nameorder == 3)
6353
	{
6354
	   $ret.=$firstname;
6355
	   if (empty($ret) && $nameorder == 3)
6356
	   {
6357
	   		$ret.=$lastname;
6358
	   }
6359
	}
6360
	else
6361
	{
6362
		$ret.=$lastname;
6363
		if ($firstname && $lastname) $ret.=' ';
6364
		$ret.=$firstname;
6365
	}
6366
	return $ret;
6367
}
6368
6369
6370
/**
6371
 *	Set event message in dol_events session object. Will be output by calling dol_htmloutput_events.
6372
 *  Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function.
6373
 *  Note: Prefer to use setEventMessages instead.
6374
 *
6375
 *	@param	mixed	$mesgs			Message string or array
6376
 *  @param  string	$style      	Which style to use ('mesgs' by default, 'warnings', 'errors')
6377
 *  @return	void
6378
 *  @see	dol_htmloutput_events()
6379
 */
6380
function setEventMessage($mesgs, $style = 'mesgs')
6381
{
6382
	//dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);		This is not deprecated, it is used by setEventMessages function
6383
	if (! is_array($mesgs))		// If mesgs is a string
6384
	{
6385
		if ($mesgs) $_SESSION['dol_events'][$style][] = $mesgs;
6386
	}
6387
	else						// If mesgs is an array
6388
	{
6389
		foreach($mesgs as $mesg)
6390
		{
6391
			if ($mesg) $_SESSION['dol_events'][$style][] = $mesg;
6392
		}
6393
	}
6394
}
6395
6396
/**
6397
 *	Set event messages in dol_events session object. Will be output by calling dol_htmloutput_events.
6398
 *  Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function.
6399
 *
6400
 *	@param	string	$mesg			Message string
6401
 *	@param	array	$mesgs			Message array
6402
 *  @param  string	$style      	Which style to use ('mesgs' by default, 'warnings', 'errors')
6403
 *  @param	string	$messagekey		A key to be used to allow the feature "Never show this message again"
6404
 *  @return	void
6405
 *  @see	dol_htmloutput_events()
6406
 */
6407
function setEventMessages($mesg, $mesgs, $style = 'mesgs', $messagekey = '')
6408
{
6409
	if (empty($mesg) && empty($mesgs))
6410
	{
6411
		dol_syslog("Try to add a message in stack with empty message", LOG_WARNING);
6412
	}
6413
	else
6414
	{
6415
		if ($messagekey)
6416
		{
6417
			// Complete message with a js link to set a cookie "DOLHIDEMESSAGE".$messagekey;
6418
			// TODO
6419
			$mesg.='';
6420
		}
6421
		if (empty($messagekey) || empty($_COOKIE["DOLHIDEMESSAGE".$messagekey]))
6422
		{
6423
			if (! in_array((string) $style, array('mesgs','warnings','errors'))) dol_print_error('', 'Bad parameter style='.$style.' for setEventMessages');
6424
			if (empty($mesgs)) setEventMessage($mesg, $style);
6425
			else
6426
			{
6427
				if (! empty($mesg) && ! in_array($mesg, $mesgs)) setEventMessage($mesg, $style);	// Add message string if not already into array
6428
				setEventMessage($mesgs, $style);
6429
			}
6430
		}
6431
	}
6432
}
6433
6434
/**
6435
 *	Print formated messages to output (Used to show messages on html output).
6436
 *  Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function, so there is
6437
 *  no need to call it explicitely.
6438
 *
6439
 *  @param	int		$disabledoutputofmessages	Clear all messages stored into session without diplaying them
6440
 *  @return	void
6441
 *  @see    									dol_htmloutput_mesg()
6442
 */
6443
function dol_htmloutput_events($disabledoutputofmessages = 0)
6444
{
6445
	// Show mesgs
6446
	if (isset($_SESSION['dol_events']['mesgs'])) {
6447
		if (empty($disabledoutputofmessages)) dol_htmloutput_mesg('', $_SESSION['dol_events']['mesgs']);
6448
		unset($_SESSION['dol_events']['mesgs']);
6449
	}
6450
6451
	// Show errors
6452
	if (isset($_SESSION['dol_events']['errors'])) {
6453
		if (empty($disabledoutputofmessages)) dol_htmloutput_mesg('', $_SESSION['dol_events']['errors'], 'error');
6454
		unset($_SESSION['dol_events']['errors']);
6455
	}
6456
6457
	// Show warnings
6458
	if (isset($_SESSION['dol_events']['warnings'])) {
6459
		if (empty($disabledoutputofmessages)) dol_htmloutput_mesg('', $_SESSION['dol_events']['warnings'], 'warning');
6460
		unset($_SESSION['dol_events']['warnings']);
6461
	}
6462
}
6463
6464
/**
6465
 *	Get formated messages to output (Used to show messages on html output).
6466
 *  This include also the translation of the message key.
6467
 *
6468
 *	@param	string		$mesgstring		Message string or message key
6469
 *	@param	string[]	$mesgarray      Array of message strings or message keys
6470
 *  @param  string		$style          Style of message output ('ok' or 'error')
6471
 *  @param  int			$keepembedded   Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
6472
 *	@return	string						Return html output
6473
 *
6474
 *  @see    dol_print_error()
6475
 *  @see    dol_htmloutput_errors()
6476
 *  @see    setEventMessages()
6477
 */
6478
function get_htmloutput_mesg($mesgstring = '', $mesgarray = '', $style = 'ok', $keepembedded = 0)
6479
{
6480
	global $conf, $langs;
6481
6482
	$ret=0; $return='';
6483
	$out='';
6484
	$divstart=$divend='';
6485
6486
	// If inline message with no format, we add it.
6487
	if ((empty($conf->use_javascript_ajax) || ! empty($conf->global->MAIN_DISABLE_JQUERY_JNOTIFY) || $keepembedded) && ! preg_match('/<div class=".*">/i', $out))
6488
	{
6489
		$divstart='<div class="'.$style.' clearboth">';
6490
		$divend='</div>';
6491
	}
6492
6493
	if ((is_array($mesgarray) && count($mesgarray)) || $mesgstring)
6494
	{
6495
		$langs->load("errors");
6496
		$out.=$divstart;
6497
		if (is_array($mesgarray) && count($mesgarray))
6498
		{
6499
			foreach($mesgarray as $message)
6500
			{
6501
				$ret++;
6502
				$out.= $langs->trans($message);
6503
				if ($ret < count($mesgarray)) $out.= "<br>\n";
6504
			}
6505
		}
6506
		if ($mesgstring)
6507
		{
6508
			$langs->load("errors");
6509
			$ret++;
6510
			$out.= $langs->trans($mesgstring);
6511
		}
6512
		$out.=$divend;
6513
	}
6514
6515
	if ($out)
6516
	{
6517
		if (! empty($conf->use_javascript_ajax) && empty($conf->global->MAIN_DISABLE_JQUERY_JNOTIFY) && empty($keepembedded))
6518
		{
6519
			$return = '<script>
6520
					$(document).ready(function() {
6521
						var block = '.(! empty($conf->global->MAIN_USE_JQUERY_BLOCKUI)?"true":"false").'
6522
						if (block) {
6523
							$.dolEventValid("","'.dol_escape_js($out).'");
6524
						} else {
6525
							/* jnotify(message, preset of message type, keepmessage) */
6526
							$.jnotify("'.dol_escape_js($out).'",
6527
							"'.($style=="ok" ? 3000 : $style).'",
6528
							'.($style=="ok" ? "false" : "true").',
6529
							{ remove: function (){} } );
6530
						}
6531
					});
6532
				</script>';
6533
		}
6534
		else
6535
		{
6536
			$return = $out;
6537
		}
6538
	}
6539
6540
	return $return;
6541
}
6542
6543
/**
6544
 *  Get formated error messages to output (Used to show messages on html output).
6545
 *
6546
 *  @param  string	$mesgstring         Error message
6547
 *  @param  array	$mesgarray          Error messages array
6548
 *  @param  int		$keepembedded       Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
6549
 *  @return string                		Return html output
6550
 *
6551
 *  @see    dol_print_error()
6552
 *  @see    dol_htmloutput_mesg()
6553
 */
6554
function get_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0)
6555
{
6556
	return get_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
6557
}
6558
6559
/**
6560
 *	Print formated messages to output (Used to show messages on html output).
6561
 *
6562
 *	@param	string		$mesgstring		Message string or message key
6563
 *	@param	string[]	$mesgarray      Array of message strings or message keys
6564
 *	@param  string      $style          Which style to use ('ok', 'warning', 'error')
6565
 *	@param  int         $keepembedded   Set to 1 if message must be kept embedded into its html place (this disable jnotify)
6566
 *	@return	void
6567
 *
6568
 *	@see    dol_print_error()
6569
 *	@see    dol_htmloutput_errors()
6570
 *	@see    setEventMessages()
6571
 */
6572
function dol_htmloutput_mesg($mesgstring = '', $mesgarray = array(), $style = 'ok', $keepembedded = 0)
6573
{
6574
	if (empty($mesgstring) && (! is_array($mesgarray) || count($mesgarray) == 0)) return;
1 ignored issue
show
introduced by
The condition is_array($mesgarray) is always true.
Loading history...
6575
6576
	$iserror=0;
6577
	$iswarning=0;
6578
	if (is_array($mesgarray))
1 ignored issue
show
introduced by
The condition is_array($mesgarray) is always true.
Loading history...
6579
	{
6580
		foreach($mesgarray as $val)
6581
		{
6582
			if ($val && preg_match('/class="error"/i', $val)) { $iserror++; break; }
6583
			if ($val && preg_match('/class="warning"/i', $val)) { $iswarning++; break; }
6584
		}
6585
	}
6586
	elseif ($mesgstring && preg_match('/class="error"/i', $mesgstring)) $iserror++;
6587
	elseif ($mesgstring && preg_match('/class="warning"/i', $mesgstring)) $iswarning++;
6588
	if ($style=='error') $iserror++;
6589
	if ($style=='warning') $iswarning++;
6590
6591
	if ($iserror || $iswarning)
6592
	{
6593
		// Remove div from texts
6594
		$mesgstring=preg_replace('/<\/div><div class="(error|warning)">/', '<br>', $mesgstring);
6595
		$mesgstring=preg_replace('/<div class="(error|warning)">/', '', $mesgstring);
6596
		$mesgstring=preg_replace('/<\/div>/', '', $mesgstring);
6597
		// Remove div from texts array
6598
		if (is_array($mesgarray))
1 ignored issue
show
introduced by
The condition is_array($mesgarray) is always true.
Loading history...
6599
		{
6600
			$newmesgarray=array();
6601
			foreach($mesgarray as $val)
6602
			{
6603
				if (is_string($val))
6604
				{
6605
					$tmpmesgstring=preg_replace('/<\/div><div class="(error|warning)">/', '<br>', $val);
6606
					$tmpmesgstring=preg_replace('/<div class="(error|warning)">/', '', $tmpmesgstring);
6607
					$tmpmesgstring=preg_replace('/<\/div>/', '', $tmpmesgstring);
6608
					$newmesgarray[]=$tmpmesgstring;
6609
				}
6610
				else
6611
				{
6612
					dol_syslog("Error call of dol_htmloutput_mesg with an array with a value that is not a string", LOG_WARNING);
6613
				}
6614
			}
6615
			$mesgarray=$newmesgarray;
6616
		}
6617
		print get_htmloutput_mesg($mesgstring, $mesgarray, ($iserror?'error':'warning'), $keepembedded);
6618
	}
6619
	else print get_htmloutput_mesg($mesgstring, $mesgarray, 'ok', $keepembedded);
6620
}
6621
6622
/**
6623
 *  Print formated error messages to output (Used to show messages on html output).
6624
 *
6625
 *  @param	string	$mesgstring          Error message
6626
 *  @param  array	$mesgarray           Error messages array
6627
 *  @param  int		$keepembedded        Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
6628
 *  @return	void
6629
 *
6630
 *  @see    dol_print_error()
6631
 *  @see    dol_htmloutput_mesg()
6632
 */
6633
function dol_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0)
6634
{
6635
	dol_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
6636
}
6637
6638
/**
6639
 * 	Advanced sort array by second index function, which produces ascending (default)
6640
 *  or descending output and uses optionally natural case insensitive sorting (which
6641
 *  can be optionally case sensitive as well).
6642
 *
6643
 *  @param      array		$array      		Array to sort (array of array('key1'=>val1,'key2'=>val2,'key3'...) or array of objects)
6644
 *  @param      string		$index				Key in array to use for sorting criteria
6645
 *  @param      int			$order				Sort order ('asc' or 'desc')
6646
 *  @param      int			$natsort			1=use "natural" sort (natsort), 0=use "standard" sort (asort)
6647
 *  @param      int			$case_sensitive		1=sort is case sensitive, 0=not case sensitive
6648
 *  @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.
6649
 *  @return     array							Sorted array
6650
 */
6651
function dol_sort_array(&$array, $index, $order = 'asc', $natsort = 0, $case_sensitive = 0, $keepindex = 0)
6652
{
6653
	// Clean parameters
6654
	$order=strtolower($order);
6655
6656
	if (is_array($array))
1 ignored issue
show
introduced by
The condition is_array($array) is always true.
Loading history...
6657
	{
6658
		$sizearray=count($array);
6659
		if ($sizearray>0)
6660
		{
6661
			$temp = array();
6662
			foreach(array_keys($array) as $key)
6663
			{
6664
				if (is_object($array[$key]))
6665
				{
6666
					$temp[$key]=$array[$key]->$index;
6667
				}
6668
				else
6669
				{
6670
					$temp[$key]=$array[$key][$index];
6671
				}
6672
			}
6673
6674
            if (! $natsort) {
6675
                ($order=='asc') ? asort($temp) : arsort($temp);
6676
            } else {
6677
                ($case_sensitive) ? natsort($temp) : natcasesort($temp);
6678
                if($order!='asc') $temp=array_reverse($temp, true);
6679
            }
6680
6681
			$sorted = array();
6682
6683
			foreach(array_keys($temp) as $key)
6684
			{
6685
				(is_numeric($key) && empty($keepindex)) ? $sorted[]=$array[$key] : $sorted[$key]=$array[$key];
6686
			}
6687
6688
			return $sorted;
6689
		}
6690
	}
6691
	return $array;
6692
}
6693
6694
6695
/**
6696
 *      Check if a string is in UTF8
6697
 *
6698
 *      @param	string	$str        String to check
6699
 * 		@return	boolean				True if string is UTF8 or ISO compatible with UTF8, False if not (ISO with special char or Binary)
6700
 */
6701
function utf8_check($str)
6702
{
6703
	// We must use here a binary strlen function (so not dol_strlen)
6704
	$strLength = dol_strlen($str);
6705
	for ($i=0; $i<$strLength; $i++)
6706
	{
6707
		if (ord($str[$i]) < 0x80) continue; // 0bbbbbbb
6708
		elseif ((ord($str[$i]) & 0xE0) == 0xC0) $n=1; // 110bbbbb
6709
		elseif ((ord($str[$i]) & 0xF0) == 0xE0) $n=2; // 1110bbbb
6710
		elseif ((ord($str[$i]) & 0xF8) == 0xF0) $n=3; // 11110bbb
6711
		elseif ((ord($str[$i]) & 0xFC) == 0xF8) $n=4; // 111110bb
6712
		elseif ((ord($str[$i]) & 0xFE) == 0xFC) $n=5; // 1111110b
6713
		else return false; // Does not match any model
6714
		for ($j=0; $j<$n; $j++) { // n bytes matching 10bbbbbb follow ?
6715
			if ((++$i == strlen($str)) || ((ord($str[$i]) & 0xC0) != 0x80))
6716
			return false;
6717
		}
6718
	}
6719
	return true;
6720
}
6721
6722
6723
/**
6724
 *      Return a string encoded into OS filesystem encoding. This function is used to define
6725
 * 	    value to pass to filesystem PHP functions.
6726
 *
6727
 *      @param	string	$str        String to encode (UTF-8)
6728
 * 		@return	string				Encoded string (UTF-8, ISO-8859-1)
6729
 */
6730
function dol_osencode($str)
6731
{
6732
	global $conf;
6733
6734
	$tmp=ini_get("unicode.filesystem_encoding");						// Disponible avec PHP 6.0
6735
	if (empty($tmp) && ! empty($_SERVER["WINDIR"])) $tmp='iso-8859-1';	// By default for windows
6736
	if (empty($tmp)) $tmp='utf-8';										// By default for other
6737
	if (! empty($conf->global->MAIN_FILESYSTEM_ENCODING)) $tmp=$conf->global->MAIN_FILESYSTEM_ENCODING;
6738
6739
	if ($tmp == 'iso-8859-1') return utf8_decode($str);
6740
	return $str;
6741
}
6742
6743
6744
/**
6745
 *      Return an id or code from a code or id.
6746
 *      Store also Code-Id into a cache to speed up next request on same key.
6747
 *
6748
 * 		@param	DoliDB	$db				Database handler
6749
 * 		@param	string	$key			Code or Id to get Id or Code
6750
 * 		@param	string	$tablename		Table name without prefix
6751
 * 		@param	string	$fieldkey		Field to search the key into
6752
 * 		@param	string	$fieldid		Field to get
6753
 *      @param  int		$entityfilter	Filter by entity
6754
 *      @return int						<0 if KO, Id of code if OK
6755
 *      @see $langs->getLabelFromKey
6756
 */
6757
function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid = 'id', $entityfilter = 0)
6758
{
6759
	global $cache_codes;
6760
6761
	// If key empty
6762
	if ($key == '') return '';
6763
6764
	// Check in cache
6765
	if (isset($cache_codes[$tablename][$key][$fieldid]))	// Can be defined to 0 or ''
6766
	{
6767
		return $cache_codes[$tablename][$key][$fieldid];   // Found in cache
6768
	}
6769
6770
	dol_syslog('dol_getIdFromCode (value for field '.$fieldid.' from key '.$key.' not found into cache)', LOG_DEBUG);
6771
6772
	$sql = "SELECT ".$fieldid." as valuetoget";
6773
	$sql.= " FROM ".MAIN_DB_PREFIX.$tablename;
6774
	$sql.= " WHERE ".$fieldkey." = '".$db->escape($key)."'";
6775
	if (! empty($entityfilter))
6776
		$sql.= " AND entity IN (" . getEntity($tablename) . ")";
6777
6778
	$resql = $db->query($sql);
6779
	if ($resql)
6780
	{
6781
		$obj = $db->fetch_object($resql);
6782
		if ($obj) $cache_codes[$tablename][$key][$fieldid]=$obj->valuetoget;
6783
		else $cache_codes[$tablename][$key][$fieldid]='';
6784
		$db->free($resql);
6785
		return $cache_codes[$tablename][$key][$fieldid];
6786
	}
6787
	else
6788
	{
6789
		return -1;
6790
	}
6791
}
6792
6793
/**
6794
 * Verify if condition in string is ok or not
6795
 *
6796
 * @param 	string		$strRights		String with condition to check
6797
 * @return 	boolean						True or False. Return True if strRights is ''
6798
 */
6799
function verifCond($strRights)
6800
{
6801
	global $user,$conf,$langs;
6802
	global $leftmenu;
6803
	global $rights;    // To export to dol_eval function
6804
6805
	//print $strRights."<br>\n";
6806
	$rights = true;
6807
	if ($strRights != '')
6808
	{
6809
		$str = 'if(!(' . $strRights . ')) { $rights = false; }';
6810
		dol_eval($str);		// The dol_eval must contains all the global $xxx used into a condition
6811
	}
6812
	return $rights;
6813
}
6814
6815
/**
6816
 * Replace eval function to add more security.
6817
 * This function is called by verifCond() or trans() and transnoentitiesnoconv().
6818
 *
6819
 * @param 	string	$s				String to evaluate
6820
 * @param	int		$returnvalue	0=No return (used to execute eval($a=something)). 1=Value of eval is returned (used to eval($something)).
6821
 * @param   int     $hideerrors     1=Hide errors
6822
 * @return	mixed					Nothing or return of eval
6823
 */
6824
function dol_eval($s, $returnvalue = 0, $hideerrors = 1)
6825
{
6826
	// Only global variables can be changed by eval function and returned to caller
6827
	global $db, $langs, $user, $conf, $website, $websitepage;
6828
	global $action, $mainmenu, $leftmenu;
6829
	global $rights;
6830
	global $object;
6831
	global $mysoc;
6832
6833
	global $obj;       // To get $obj used into list when dol_eval is used for computed fields and $obj is not yet $object
6834
	global $soc;       // For backward compatibility
6835
6836
	//print $s."<br>\n";
6837
	if ($returnvalue)
6838
	{
6839
		if ($hideerrors) return @eval('return '.$s.';');
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6840
		else return eval('return '.$s.';');
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6841
	}
6842
	else
6843
	{
6844
		if ($hideerrors) @eval($s);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6845
		else eval($s);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
6846
	}
6847
}
6848
6849
/**
6850
 * Return if var element is ok
6851
 *
6852
 * @param   string      $element    Variable to check
6853
 * @return  boolean                 Return true of variable is not empty
6854
 */
6855
function dol_validElement($element)
6856
{
6857
	return (trim($element) != '');
6858
}
6859
6860
/**
6861
 * 	Return img flag of country for a language code or country code
6862
 *
6863
 * 	@param	string	$codelang	Language code (en_IN, fr_CA...) or Country code (IN, FR)
6864
 *  @param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
6865
 * 	@return	string				HTML img string with flag.
6866
 */
6867
function picto_from_langcode($codelang, $moreatt = '')
6868
{
6869
	global $langs;
6870
6871
	if (empty($codelang)) return '';
6872
6873
	if ($codelang == 'auto')
6874
	{
6875
		return '<span class="fa fa-globe"></span>';
6876
	}
6877
6878
	$langtocountryflag = array(
6879
		'ar_AR' => '',
6880
		'ca_ES' => 'catalonia',
6881
		'da_DA' => 'dk',
6882
		'fr_CA' => 'mq',
6883
		'sv_SV' => 'se'
6884
	);
6885
6886
	if (isset($langtocountryflag[$codelang])) $flagImage = $langtocountryflag[$codelang];
6887
	else
6888
	{
6889
		$tmparray = explode('_', $codelang);
6890
		$flagImage = empty($tmparray[1]) ? $tmparray[0] : $tmparray[1];
6891
	}
6892
6893
	return img_picto_common($codelang, 'flags/'.strtolower($flagImage).'.png', $moreatt);
6894
}
6895
6896
/**
6897
 * Return default language from country code.
6898
 * Return null if not found.
6899
 *
6900
 * @param 	string 	$countrycode	Country code like 'US', 'FR', 'CA', ...
6901
 * @return	string					Value of locale like 'en_US', 'fr_FR', ...
6902
 */
6903
function getLanguageCodeFromCountryCode($countrycode)
6904
{
6905
	global $mysoc;
6906
6907
	if (empty($countrycode)) return null;
6908
6909
	if (strtoupper($countrycode) == 'MQ') return 'fr_CA';
6910
	if (strtoupper($countrycode) == 'SE') return 'sv_SE';	// se_SE is Sami/Sweden, and we want in priority sv_SE for SE country
6911
	if (strtoupper($countrycode) == 'CH')
6912
	{
6913
		if ($mysoc->country_code == 'FR') return 'fr_CH';
6914
		if ($mysoc->country_code == 'DE') return 'de_CH';
6915
	}
6916
6917
	// Locale list taken from:
6918
	// http://stackoverflow.com/questions/3191664/
6919
	// list-of-all-locales-and-their-short-codes
6920
	$locales = array(
6921
		'af-ZA',
6922
		'am-ET',
6923
		'ar-AE',
6924
		'ar-BH',
6925
		'ar-DZ',
6926
		'ar-EG',
6927
		'ar-IQ',
6928
		'ar-JO',
6929
		'ar-KW',
6930
		'ar-LB',
6931
		'ar-LY',
6932
		'ar-MA',
6933
		'ar-OM',
6934
		'ar-QA',
6935
		'ar-SA',
6936
		'ar-SY',
6937
		'ar-TN',
6938
		'ar-YE',
6939
		'as-IN',
6940
		'ba-RU',
6941
		'be-BY',
6942
		'bg-BG',
6943
		'bn-BD',
6944
		'bn-IN',
6945
		'bo-CN',
6946
		'br-FR',
6947
		'ca-ES',
6948
		'co-FR',
6949
		'cs-CZ',
6950
		'cy-GB',
6951
		'da-DK',
6952
		'de-AT',
6953
		'de-CH',
6954
		'de-DE',
6955
		'de-LI',
6956
		'de-LU',
6957
		'dv-MV',
6958
		'el-GR',
6959
		'en-AU',
6960
		'en-BZ',
6961
		'en-CA',
6962
		'en-GB',
6963
		'en-IE',
6964
		'en-IN',
6965
		'en-JM',
6966
		'en-MY',
6967
		'en-NZ',
6968
		'en-PH',
6969
		'en-SG',
6970
		'en-TT',
6971
		'en-US',
6972
		'en-ZA',
6973
		'en-ZW',
6974
		'es-AR',
6975
		'es-BO',
6976
		'es-CL',
6977
		'es-CO',
6978
		'es-CR',
6979
		'es-DO',
6980
		'es-EC',
6981
		'es-ES',
6982
		'es-GT',
6983
		'es-HN',
6984
		'es-MX',
6985
		'es-NI',
6986
		'es-PA',
6987
		'es-PE',
6988
		'es-PR',
6989
		'es-PY',
6990
		'es-SV',
6991
		'es-US',
6992
		'es-UY',
6993
		'es-VE',
6994
		'et-EE',
6995
		'eu-ES',
6996
		'fa-IR',
6997
		'fi-FI',
6998
		'fo-FO',
6999
		'fr-BE',
7000
		'fr-CA',
7001
		'fr-CH',
7002
		'fr-FR',
7003
		'fr-LU',
7004
		'fr-MC',
7005
		'fy-NL',
7006
		'ga-IE',
7007
		'gd-GB',
7008
		'gl-ES',
7009
		'gu-IN',
7010
		'he-IL',
7011
		'hi-IN',
7012
		'hr-BA',
7013
		'hr-HR',
7014
		'hu-HU',
7015
		'hy-AM',
7016
		'id-ID',
7017
		'ig-NG',
7018
		'ii-CN',
7019
		'is-IS',
7020
		'it-CH',
7021
		'it-IT',
7022
		'ja-JP',
7023
		'ka-GE',
7024
		'kk-KZ',
7025
		'kl-GL',
7026
		'km-KH',
7027
		'kn-IN',
7028
		'ko-KR',
7029
		'ky-KG',
7030
		'lb-LU',
7031
		'lo-LA',
7032
		'lt-LT',
7033
		'lv-LV',
7034
		'mi-NZ',
7035
		'mk-MK',
7036
		'ml-IN',
7037
		'mn-MN',
7038
		'mr-IN',
7039
		'ms-BN',
7040
		'ms-MY',
7041
		'mt-MT',
7042
		'nb-NO',
7043
		'ne-NP',
7044
		'nl-BE',
7045
		'nl-NL',
7046
		'nn-NO',
7047
		'oc-FR',
7048
		'or-IN',
7049
		'pa-IN',
7050
		'pl-PL',
7051
		'ps-AF',
7052
		'pt-BR',
7053
		'pt-PT',
7054
		'rm-CH',
7055
		'ro-RO',
7056
		'ru-RU',
7057
		'rw-RW',
7058
		'sa-IN',
7059
		'se-FI',
7060
		'se-NO',
7061
		'se-SE',
7062
		'si-LK',
7063
		'sk-SK',
7064
		'sl-SI',
7065
		'sq-AL',
7066
		'sv-FI',
7067
		'sv-SE',
7068
		'sw-KE',
7069
		'ta-IN',
7070
		'te-IN',
7071
		'th-TH',
7072
		'tk-TM',
7073
		'tn-ZA',
7074
		'tr-TR',
7075
		'tt-RU',
7076
		'ug-CN',
7077
		'uk-UA',
7078
		'ur-PK',
7079
		'vi-VN',
7080
		'wo-SN',
7081
		'xh-ZA',
7082
		'yo-NG',
7083
		'zh-CN',
7084
		'zh-HK',
7085
		'zh-MO',
7086
		'zh-SG',
7087
		'zh-TW',
7088
		'zu-ZA',
7089
	);
7090
7091
	$buildprimarykeytotest = strtolower($countrycode).'-'.strtoupper($countrycode);
7092
	if (in_array($buildprimarykeytotest, $locales)) return strtolower($countrycode).'_'.strtoupper($countrycode);
7093
7094
	if (function_exists('locale_get_primary_language') && function_exists('locale_get_region'))    // Need extension php-intl
7095
	{
7096
	    foreach ($locales as $locale)
7097
    	{
7098
    		$locale_language = locale_get_primary_language($locale);
7099
    		$locale_region = locale_get_region($locale);
7100
    		if (strtoupper($countrycode) == $locale_region)
7101
    		{
7102
    			//var_dump($locale.'-'.$locale_language.'-'.$locale_region);
7103
    			return strtolower($locale_language).'_'.strtoupper($locale_region);
7104
    		}
7105
    	}
7106
	}
7107
	else
7108
	{
7109
        dol_syslog("Warning Exention php-intl is not available", LOG_WARNING);
7110
	}
7111
7112
	return null;
7113
}
7114
7115
/**
7116
 *  Complete or removed entries into a head array (used to build tabs).
7117
 *  For example, with value added by external modules. Such values are declared into $conf->modules_parts['tab'].
7118
 *  Or by change using hook completeTabsHead
7119
 *
7120
 *  @param	Conf			$conf           Object conf
7121
 *  @param  Translate		$langs          Object langs
7122
 *  @param  object|null		$object         Object object
7123
 *  @param  array			$head          	Object head
7124
 *  @param  int				$h				New position to fill
7125
 *  @param  string			$type           Value for object where objectvalue can be
7126
 *                              			'thirdparty'       to add a tab in third party view
7127
 *		                        	      	'intervention'     to add a tab in intervention view
7128
 *     		                    	     	'supplier_order'   to add a tab in supplier order view
7129
 *          		            	        'supplier_invoice' to add a tab in supplier invoice view
7130
 *                  		    	        'invoice'          to add a tab in customer invoice view
7131
 *                          			    'order'            to add a tab in customer order view
7132
 *                          				'contract'		   to add a tabl in contract view
7133
 *                      			        'product'          to add a tab in product view
7134
 *                              			'propal'           to add a tab in propal view
7135
 *                              			'user'             to add a tab in user view
7136
 *                              			'group'            to add a tab in group view
7137
 * 		        	               	     	'member'           to add a tab in fundation member view
7138
 *      		                        	'categories_x'	   to add a tab in category view ('x': type of category (0=product, 1=supplier, 2=customer, 3=member)
7139
 *      									'ecm'			   to add a tab for another ecm view
7140
 *                                          'stock'            to add a tab for warehouse view
7141
 *  @param  string		$mode  	        	'add' to complete head, 'remove' to remove entries
7142
 *	@return	void
7143
 */
7144
function complete_head_from_modules($conf, $langs, $object, &$head, &$h, $type, $mode = 'add')
7145
{
7146
	global $hookmanager;
7147
7148
	if (isset($conf->modules_parts['tabs'][$type]) && is_array($conf->modules_parts['tabs'][$type]))
7149
	{
7150
		foreach ($conf->modules_parts['tabs'][$type] as $value)
7151
		{
7152
			$values=explode(':', $value);
7153
7154
			if ($mode == 'add' && ! preg_match('/^\-/', $values[1]))
7155
			{
7156
				if (count($values) == 6)       // new declaration with permissions:  $value='objecttype:+tabname1:Title1:langfile@mymodule:$user->rights->mymodule->read:/mymodule/mynewtab1.php?id=__ID__'
7157
				{
7158
					if ($values[0] != $type) continue;
7159
7160
					if (verifCond($values[4]))
7161
					{
7162
						if ($values[3]) $langs->load($values[3]);
7163
						if (preg_match('/SUBSTITUTION_([^_]+)/i', $values[2], $reg))
7164
						{
7165
							$substitutionarray=array();
7166
							complete_substitutions_array($substitutionarray, $langs, $object, array('needforkey'=>$values[2]));
7167
							$label=make_substitutions($reg[1], $substitutionarray);
7168
						}
7169
						else $label=$langs->trans($values[2]);
7170
7171
						$head[$h][0] = dol_buildpath(preg_replace('/__ID__/i', ((is_object($object) && ! empty($object->id))?$object->id:''), $values[5]), 1);
7172
						$head[$h][1] = $label;
7173
						$head[$h][2] = str_replace('+', '', $values[1]);
7174
						$h++;
7175
					}
7176
				}
7177
				elseif (count($values) == 5)       // deprecated
7178
				{
7179
					dol_syslog('Passing 5 values in tabs module_parts is deprecated. Please update to 6 with permissions.', LOG_WARNING);
7180
7181
					if ($values[0] != $type) continue;
7182
					if ($values[3]) $langs->load($values[3]);
7183
					if (preg_match('/SUBSTITUTION_([^_]+)/i', $values[2], $reg))
7184
					{
7185
						$substitutionarray=array();
7186
						complete_substitutions_array($substitutionarray, $langs, $object, array('needforkey'=>$values[2]));
7187
						$label=make_substitutions($reg[1], $substitutionarray);
7188
					}
7189
					else $label=$langs->trans($values[2]);
7190
7191
					$head[$h][0] = dol_buildpath(preg_replace('/__ID__/i', ((is_object($object) && ! empty($object->id))?$object->id:''), $values[4]), 1);
7192
					$head[$h][1] = $label;
7193
					$head[$h][2] = str_replace('+', '', $values[1]);
7194
					$h++;
7195
				}
7196
			}
7197
			elseif ($mode == 'remove' && preg_match('/^\-/', $values[1]))
7198
			{
7199
				if ($values[0] != $type) continue;
7200
				$tabname=str_replace('-', '', $values[1]);
7201
				foreach($head as $key => $val)
7202
				{
7203
					$condition = (! empty($values[3]) ? verifCond($values[3]) : 1);
7204
					//var_dump($key.' - '.$tabname.' - '.$head[$key][2].' - '.$values[3].' - '.$condition);
7205
					if ($head[$key][2]==$tabname && $condition)
7206
					{
7207
						unset($head[$key]);
7208
						break;
7209
					}
7210
				}
7211
			}
7212
		}
7213
	}
7214
7215
	// No need to make a return $head. Var is modified as a reference
7216
	if (! empty($hookmanager))
7217
	{
7218
		$parameters=array('object' => $object, 'mode' => $mode, 'head' => $head);
7219
		$reshook=$hookmanager->executeHooks('completeTabsHead', $parameters);
7220
		if ($reshook > 0)
7221
		{
7222
			$head = $hookmanager->resArray;
7223
            $h = count($head);
7224
		}
7225
	}
7226
}
7227
7228
/**
7229
 * Print common footer :
7230
 * 		conf->global->MAIN_HTML_FOOTER
7231
 *      js for switch of menu hider
7232
 * 		js for conf->global->MAIN_GOOGLE_AN_ID
7233
 * 		js for conf->global->MAIN_SHOW_TUNING_INFO or $_SERVER["MAIN_SHOW_TUNING_INFO"]
7234
 * 		js for conf->logbuffer
7235
 *
7236
 * @param	string	$zone	'private' (for private pages) or 'public' (for public pages)
7237
 * @return	void
7238
 */
7239
function printCommonFooter($zone = 'private')
7240
{
7241
	global $conf, $hookmanager, $user, $debugbar;
7242
	global $action;
7243
	global $micro_start_time;
7244
7245
	if ($zone == 'private') print "\n".'<!-- Common footer for private page -->'."\n";
7246
	else print "\n".'<!-- Common footer for public page -->'."\n";
7247
7248
	// A div to store page_y POST parameter so we can read it using javascript
7249
	print "\n<!-- A div to store page_y POST parameter -->\n";
7250
	print '<div id="page_y" style="display: none;">'.$_POST['page_y'].'</div>'."\n";
7251
7252
	$parameters=array();
7253
	$reshook=$hookmanager->executeHooks('printCommonFooter', $parameters);    // Note that $action and $object may have been modified by some hooks
7254
	if (empty($reshook))
7255
	{
7256
		if (! empty($conf->global->MAIN_HTML_FOOTER)) print $conf->global->MAIN_HTML_FOOTER."\n";
7257
7258
		print "\n";
7259
		if (! empty($conf->use_javascript_ajax))
7260
		{
7261
			print '<script>'."\n";
7262
			print 'jQuery(document).ready(function() {'."\n";
7263
7264
			if ($zone == 'private' && empty($conf->dol_use_jmobile))
7265
			{
7266
				print "\n";
7267
				print '/* JS CODE TO ENABLE to manage handler to switch left menu page (menuhider) */'."\n";
7268
				print 'jQuery(".menuhider").click(function(event) {';
7269
				print '  if (!$( "body" ).hasClass( "sidebar-collapse" )){ event.preventDefault(); }'."\n";
7270
				print '  console.log("We click on .menuhider");'."\n";
7271
				print '  $("body").toggleClass("sidebar-collapse")'."\n";
7272
				print '});'."\n";
7273
			}
7274
7275
			// Management of focus and mandatory for fields
7276
			if ($action == 'create' || $action == 'edit' || (empty($action) && (preg_match('/new\.php/', $_SERVER["PHP_SELF"]))))
7277
			{
7278
				print '/* JS CODE TO ENABLE to manage focus and mandatory form fields */'."\n";
7279
				$relativepathstring = $_SERVER["PHP_SELF"];
7280
				// Clean $relativepathstring
7281
				if (constant('DOL_URL_ROOT')) $relativepathstring = preg_replace('/^'.preg_quote(constant('DOL_URL_ROOT'), '/').'/', '', $relativepathstring);
7282
				$relativepathstring = preg_replace('/^\//', '', $relativepathstring);
7283
				$relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
7284
				$tmpqueryarraywehave=explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
7285
				if (!empty($user->default_values[$relativepathstring]['focus']))
7286
				{
7287
					foreach($user->default_values[$relativepathstring]['focus'] as $defkey => $defval)
7288
					{
7289
						$qualified = 0;
7290
						if ($defkey != '_noquery_')
7291
						{
7292
							$tmpqueryarraytohave=explode('&', $defkey);
7293
							$foundintru=0;
7294
							foreach($tmpqueryarraytohave as $tmpquerytohave)
7295
							{
7296
								if (! in_array($tmpquerytohave, $tmpqueryarraywehave)) $foundintru=1;
7297
							}
7298
							if (! $foundintru) $qualified=1;
7299
							//var_dump($defkey.'-'.$qualified);
7300
						}
7301
						else $qualified = 1;
7302
7303
						if ($qualified)
7304
						{
7305
							foreach($defval as $paramkey => $paramval)
7306
							{
7307
								// Set focus on field
7308
								print 'jQuery("input[name=\''.$paramkey.'\']").focus();'."\n";
7309
								print 'jQuery("textarea[name=\''.$paramkey.'\']").focus();'."\n";
7310
								print 'jQuery("select[name=\''.$paramkey.'\']").focus();'."\n";		// Not really usefull, but we keep it in case of.
7311
							}
7312
						}
7313
					}
7314
				}
7315
				if (!empty($user->default_values[$relativepathstring]['mandatory']))
7316
				{
7317
					foreach($user->default_values[$relativepathstring]['mandatory'] as $defkey => $defval)
7318
					{
7319
						$qualified = 0;
7320
						if ($defkey != '_noquery_')
7321
						{
7322
							$tmpqueryarraytohave=explode('&', $defkey);
7323
							$foundintru=0;
7324
							foreach($tmpqueryarraytohave as $tmpquerytohave)
7325
							{
7326
								if (! in_array($tmpquerytohave, $tmpqueryarraywehave)) $foundintru=1;
7327
							}
7328
							if (! $foundintru) $qualified=1;
7329
							//var_dump($defkey.'-'.$qualified);
7330
						}
7331
						else $qualified = 1;
7332
7333
						if ($qualified)
7334
						{
7335
							foreach($defval as $paramkey => $paramval)
7336
							{
7337
								// Add property 'required' on input
7338
								print 'jQuery("input[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";
7339
								print 'jQuery("textarea[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";
7340
								print 'jQuery("select[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";		// required on a select works only if key is "", this does not happen in Dolibarr
7341
							}
7342
						}
7343
					}
7344
				}
7345
			}
7346
7347
			print '});'."\n";
7348
7349
			// Google Analytics
7350
			// TODO Add a hook here
7351
			if (! empty($conf->google->enabled) && ! empty($conf->global->MAIN_GOOGLE_AN_ID))
7352
			{
7353
				print "\n";
7354
				print "/* JS CODE TO ENABLE for google analtics tag */\n";
7355
				print '  var _gaq = _gaq || [];'."\n";
7356
				print '  _gaq.push([\'_setAccount\', \''.$conf->global->MAIN_GOOGLE_AN_ID.'\']);'."\n";
7357
				print '  _gaq.push([\'_trackPageview\']);'."\n";
7358
				print ''."\n";
7359
				print '  (function() {'."\n";
7360
				print '    var ga = document.createElement(\'script\'); ga.type = \'text/javascript\'; ga.async = true;'."\n";
7361
				print '    ga.src = (\'https:\' == document.location.protocol ? \'https://ssl\' : \'http://www\') + \'.google-analytics.com/ga.js\';'."\n";
7362
				print '    var s = document.getElementsByTagName(\'script\')[0]; s.parentNode.insertBefore(ga, s);'."\n";
7363
				print '  })();'."\n";
7364
			}
7365
7366
			// End of tuning
7367
			if (! empty($_SERVER['MAIN_SHOW_TUNING_INFO']) || ! empty($conf->global->MAIN_SHOW_TUNING_INFO))
7368
			{
7369
				print "\n";
7370
				print "/* JS CODE TO ENABLE to add memory info */\n";
7371
				print 'window.console && console.log("';
7372
				if (! empty($conf->global->MEMCACHED_SERVER)) print 'MEMCACHED_SERVER='.$conf->global->MEMCACHED_SERVER.' - ';
7373
				print 'MAIN_OPTIMIZE_SPEED='.(isset($conf->global->MAIN_OPTIMIZE_SPEED)?$conf->global->MAIN_OPTIMIZE_SPEED:'off');
7374
				if (! empty($micro_start_time))   // Works only if MAIN_SHOW_TUNING_INFO is defined at $_SERVER level. Not in global variable.
7375
				{
7376
					$micro_end_time = microtime(true);
7377
					print ' - Build time: '.ceil(1000*($micro_end_time-$micro_start_time)).' ms';
7378
				}
7379
				if (function_exists("memory_get_usage"))
7380
				{
7381
					print ' - Mem: '.memory_get_usage();
7382
				}
7383
				if (function_exists("xdebug_memory_usage"))
7384
				{
7385
					print ' - XDebug time: '.ceil(1000*xdebug_time_index()).' ms';
7386
					print ' - XDebug mem: '.xdebug_memory_usage();
7387
					print ' - XDebug mem peak: '.xdebug_peak_memory_usage();
7388
				}
7389
				if (function_exists("zend_loader_file_encoded"))
7390
				{
7391
					print ' - Zend encoded file: '.(zend_loader_file_encoded()?'yes':'no');
7392
				}
7393
				print '");'."\n";
7394
			}
7395
7396
			print "\n".'</script>'."\n";
7397
		}
7398
7399
		// Add Xdebug coverage of code
7400
		if (defined('XDEBUGCOVERAGE'))
7401
		{
7402
		    print_r(xdebug_get_code_coverage());
7403
		}
7404
7405
		// Add DebugBar data
7406
	    if (! empty($user->rights->debugbar->read) && is_object($debugbar))
7407
	    {
7408
	        $debugbar['time']->stopMeasure('pageaftermaster');
7409
	        print '<!-- Output debugbar data -->'."\n";
7410
		    print $debugbar->getRenderer()->render();
7411
		}
7412
		elseif (count($conf->logbuffer))    // If there is some logs in buffer to show
7413
		{
7414
			print "\n";
7415
			print "<!-- Start of log output\n";
7416
			//print '<div class="hidden">'."\n";
7417
			foreach($conf->logbuffer as $logline)
7418
			{
7419
				print $logline."<br>\n";
7420
			}
7421
			//print '</div>'."\n";
7422
			print "End of log output -->\n";
7423
		}
7424
	}
7425
}
7426
7427
/**
7428
 * Split a string with 2 keys into key array.
7429
 * For example: "A=1;B=2;C=2" is exploded into array('A'=>1,'B'=>2,'C'=>3)
7430
 *
7431
 * @param 	string	$string		String to explode
7432
 * @param 	string	$delimiter	Delimiter between each couple of data
7433
 * @param 	string	$kv			Delimiter between key and value
7434
 * @return	array				Array of data exploded
7435
 */
7436
function dolExplodeIntoArray($string, $delimiter = ';', $kv = '=')
7437
{
7438
	if ($a = explode($delimiter, $string))
7439
	{
7440
		$ka = array();
7441
		foreach ($a as $s) { // each part
7442
			if ($s) {
7443
				if ($pos = strpos($s, $kv)) { // key/value delimiter
7444
					$ka[trim(substr($s, 0, $pos))] = trim(substr($s, $pos + strlen($kv)));
7445
				} else { // key delimiter not found
7446
					$ka[] = trim($s);
7447
				}
7448
			}
7449
		}
7450
		return $ka;
7451
	}
7452
	return array();
7453
}
7454
7455
7456
/**
7457
 * Set focus onto field with selector (similar behaviour of 'autofocus' HTML5 tag)
7458
 *
7459
 * @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.
7460
 * @return	string				HTML code to set focus
7461
 */
7462
function dol_set_focus($selector)
7463
{
7464
	print "\n".'<!-- Set focus onto a specific field -->'."\n";
7465
	print '<script>jQuery(document).ready(function() { jQuery("'.dol_escape_js($selector).'").focus(); });</script>'."\n";
7466
}
7467
7468
7469
/**
7470
 * Return getmypid() or random PID when function is disabled
7471
 * Some web hosts disable this php function for security reasons
7472
 * and sometimes we can't redeclare function
7473
 *
7474
 * @return	int
7475
 */
7476
function dol_getmypid()
7477
{
7478
	if (! function_exists('getmypid')) {
7479
		return mt_rand(1, 32768);
7480
	} else {
7481
		return getmypid();
7482
	}
7483
}
7484
7485
7486
/**
7487
 * Generate natural SQL search string for a criteria (this criteria can be tested on one or several fields)
7488
 *
7489
 * @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")
7490
 * @param   string 			$value 		The value to look for.
7491
 *                          		    If param $mode is 0, can contains several keywords separated with a space or |
7492
 *                                      like "keyword1 keyword2" = We want record field like keyword1 AND field like keyword2
7493
 *                                      or like "keyword1|keyword2" = We want record field like keyword1 OR field like keyword2
7494
 *                             			If param $mode is 1, can contains an operator <, > or = like "<10" or ">=100.5 < 1000"
7495
 *                             			If param $mode is 2, can contains a list of int id separated by comma like "1,3,4"
7496
 *                             			If param $mode is 3, can contains a list of string separated by comma like "a,b,c"
7497
 * @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')
7498
 * 										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')
7499
 * @param	integer			$nofirstand	1=Do not output the first 'AND'
7500
 * @return 	string 			$res 		The statement to append to the SQL query
7501
 */
7502
function natural_search($fields, $value, $mode = 0, $nofirstand = 0)
7503
{
7504
	global $db,$langs;
7505
7506
	$value=trim($value);
7507
7508
	if ($mode == 0)
7509
	{
7510
		$value=preg_replace('/\*/', '%', $value);	// Replace * with %
7511
	}
7512
	if ($mode == 1)
7513
	{
7514
		$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
7515
	}
7516
7517
	$value = preg_replace('/\s*\|\s*/', '|', $value);
7518
7519
	$crits = explode(' ', $value);
7520
	$res = '';
7521
	if (! is_array($fields)) $fields = array($fields);
7522
7523
	$nboffields = count($fields);
7524
	$end2 = count($crits);
7525
	$j = 0;
7526
	foreach ($crits as $crit)
7527
	{
7528
		$i = 0; $i2 = 0;
7529
		$newres = '';
7530
		foreach ($fields as $field)
7531
		{
7532
			if ($mode == 1)
7533
			{
7534
				$operator='=';
7535
				$newcrit = preg_replace('/([<>=]+)/', '', trim($crit));
7536
7537
				preg_match('/([<>=]+)/', trim($crit), $reg);
7538
				if ($reg[1])
7539
				{
7540
					$operator = $reg[1];
7541
				}
7542
				if ($newcrit != '')
7543
				{
7544
					$numnewcrit = price2num($newcrit);
7545
					if (is_numeric($numnewcrit))
7546
					{
7547
						$newres .= ($i2 > 0 ? ' OR ' : '') . $field . ' '.$operator.' '.$numnewcrit;
7548
					}
7549
					else
7550
					{
7551
						$newres .= ($i2 > 0 ? ' OR ' : '') . '1 = 2';	// force false
7552
					}
7553
					$i2++;	// a criteria was added to string
7554
				}
7555
			}
7556
			elseif ($mode == 2 || $mode == -2)
7557
			{
7558
				$newres .= ($i2 > 0 ? ' OR ' : '') . $field . " ".($mode == -2 ? 'NOT ' : '')."IN (" . $db->escape(trim($crit)) . ")";
7559
				if ($mode == -2) $newres .= ' OR '.$field.' IS NULL';
7560
				$i2++;	// a criteria was added to string
7561
			}
7562
			elseif ($mode == 3 || $mode == -3)
7563
			{
7564
				$tmparray=explode(',', trim($crit));
7565
				if (count($tmparray))
7566
				{
7567
					$listofcodes='';
7568
					foreach($tmparray as $val)
7569
					{
7570
						if ($val)
7571
						{
7572
							$listofcodes.=($listofcodes?',':'');
7573
							$listofcodes.="'".$db->escape(trim($val))."'";
7574
						}
7575
					}
7576
					$newres .= ($i2 > 0 ? ' OR ' : '') . $field . " ".($mode == -3 ? 'NOT ' : '')."IN (" . $listofcodes . ")";
7577
					$i2++;	// a criteria was added to string
7578
				}
7579
				if ($mode == -3) $newres .= ' OR '.$field.' IS NULL';
7580
			}
7581
			elseif ($mode == 4)
7582
			{
7583
			    $tmparray=explode(',', trim($crit));
7584
			    if (count($tmparray))
7585
			    {
7586
			        $listofcodes='';
7587
			        foreach($tmparray as $val)
7588
			        {
7589
			            if ($val)
7590
			            {
7591
			                $newres .= ($i2 > 0 ? ' OR (' : '(') . $field . ' LIKE \'' . $db->escape(trim($val)) . ',%\'';
7592
			                $newres .= ' OR '. $field . ' = \'' . $db->escape(trim($val)) . '\'';
7593
			                $newres .= ' OR '. $field . ' LIKE \'%,' . $db->escape(trim($val)) . '\'';
7594
			                $newres .= ' OR '. $field . ' LIKE \'%,' . $db->escape(trim($val)) . ',%\'';
7595
			                $newres .= ')';
7596
			                $i2++;
7597
			            }
7598
			        }
7599
			    }
7600
			}
7601
			else    // $mode=0
7602
			{
7603
				$textcrit = '';
7604
				$tmpcrits = explode('|', $crit);
7605
				$i3 = 0;
7606
				foreach($tmpcrits as $tmpcrit)
7607
				{
7608
					if(empty($tmpcrit)) continue;
7609
7610
					$newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : '');
7611
7612
					if (preg_match('/\.(id|rowid)$/', $field))	// Special case for rowid that is sometimes a ref so used as a search field
7613
					{
7614
						$newres .= $field . " = " . (is_numeric(trim($tmpcrit))?trim($tmpcrit):'0');
7615
					}
7616
					else
7617
					{
7618
						$newres .= $field . " LIKE '";
7619
7620
						$tmpcrit=trim($tmpcrit);
7621
						$tmpcrit2=$tmpcrit;
7622
						$tmpbefore='%'; $tmpafter='%';
7623
						if (preg_match('/^[\^\$]/', $tmpcrit))
7624
						{
7625
							$tmpbefore='';
7626
							$tmpcrit2 = preg_replace('/^[\^\$]/', '', $tmpcrit2);
7627
						}
7628
						if (preg_match('/[\^\$]$/', $tmpcrit))
7629
						{
7630
							$tmpafter='';
7631
							$tmpcrit2 = preg_replace('/[\^\$]$/', '', $tmpcrit2);
7632
						}
7633
						$newres .= $tmpbefore;
7634
						$newres .= $db->escape($tmpcrit2);
7635
						$newres .= $tmpafter;
7636
						$newres .= "'";
7637
						if ($tmpcrit2 == '')
7638
						{
7639
							$newres .= ' OR ' . $field . " IS NULL";
7640
						}
7641
					}
7642
7643
					$i3++;
7644
				}
7645
				$i2++;	// a criteria was added to string
7646
			}
7647
			$i++;
7648
		}
7649
		if ($newres) $res = $res . ($res ? ' AND ' : '') . ($i2 > 1 ? '(' : '') .$newres . ($i2 > 1 ? ')' : '');
7650
		$j++;
7651
	}
7652
	$res = ($nofirstand?"":" AND ")."(" . $res . ")";
7653
	//print 'xx'.$res.'yy';
7654
	return $res;
7655
}
7656
7657
/**
7658
 * Return string with full Url. The file qualified is the one defined by relative path in $object->last_main_doc
7659
 *
7660
 * @param   Object	$object				Object
7661
 * @return	string						Url string
7662
 */
7663
function showDirectDownloadLink($object)
7664
{
7665
	global $conf, $langs;
7666
7667
	$out='';
7668
	$url = $object->getLastMainDocLink($object->element);
7669
7670
	if ($url)
7671
	{
7672
		$out.= img_picto('', 'object_globe.png').' '.$langs->trans("DirectDownloadLink").'<br>';
7673
		$out.= '<input type="text" id="directdownloadlink" class="quatrevingtpercent" value="'.$url.'">';
7674
		$out.= ajax_autoselect("directdownloadlink", 0);
7675
	}
7676
	return $out;
7677
}
7678
7679
/**
7680
 * Return the filename of file to get the thumbs
7681
 *
7682
 * @param   string  $file           Original filename (full or relative path)
7683
 * @param   string  $extName        Extension to differenciate thumb file name ('', '_small', '_mini')
7684
 * @param   string  $extImgTarget   Force image extension for thumbs. Use '' to keep same extension than original image (default).
7685
 * @return  string                  New file name (full or relative path, including the thumbs/)
7686
 */
7687
function getImageFileNameForSize($file, $extName, $extImgTarget = '')
7688
{
7689
	$dirName = dirname($file);
7690
	if ($dirName == '.') $dirName='';
7691
7692
	$fileName = preg_replace('/(\.gif|\.jpeg|\.jpg|\.png|\.bmp)$/i', '', $file);	// We remove extension, whatever is its case
7693
	$fileName = basename($fileName);
7694
7695
	if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.jpg$/i', $file)?'.jpg':'');
7696
	if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.jpeg$/i', $file)?'.jpeg':'');
7697
	if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.gif$/i', $file)?'.gif':'');
7698
	if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.png$/i', $file)?'.png':'');
7699
	if (empty($extImgTarget)) $extImgTarget = (preg_match('/\.bmp$/i', $file)?'.bmp':'');
7700
7701
	if (! $extImgTarget) return $file;
7702
7703
	$subdir='';
7704
	if ($extName) $subdir = 'thumbs/';
7705
7706
	return ($dirName?$dirName.'/':'').$subdir.$fileName.$extName.$extImgTarget; // New filename for thumb
7707
}
7708
7709
7710
/**
7711
 * Return URL we can use for advanced preview links
7712
 *
7713
 * @param   string    $modulepart     propal, facture, facture_fourn, ...
7714
 * @param   string    $relativepath   Relative path of docs.
7715
 * @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)
7716
 * @param	string	  $param		  More param on http links
7717
 * @return  string|array              Output string with href link or array with all components of link
7718
 */
7719
function getAdvancedPreviewUrl($modulepart, $relativepath, $alldata = 0, $param = '')
7720
{
7721
	global $conf, $langs;
7722
7723
	if (empty($conf->use_javascript_ajax)) return '';
7724
7725
	$mime_preview = array('bmp', 'jpeg', 'png', 'gif', 'tiff', 'pdf', 'plain', 'css', 'svg+xml');
7726
	//$mime_preview[]='vnd.oasis.opendocument.presentation';
7727
	//$mime_preview[]='archive';
7728
	$num_mime = array_search(dol_mimetype($relativepath, '', 1), $mime_preview);
7729
7730
	if ($alldata == 1)
7731
	{
7732
		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), );
7733
		else return array();
7734
	}
7735
7736
	// old behavior
7737
	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')).'\')';
7738
	else return '';
7739
}
7740
7741
7742
/**
7743
 * Make content of an input box selected when we click into input field.
7744
 *
7745
 * @param string	$htmlname	Id of html object
7746
 * @param string	$addlink	Add a 'link to' after
7747
 * @return string
7748
 */
7749
function ajax_autoselect($htmlname, $addlink = '')
7750
{
7751
	global $langs;
7752
	$out = '<script>
7753
               jQuery(document).ready(function () {
7754
				    jQuery("#'.$htmlname.'").click(function() { jQuery(this).select(); } );
7755
				});
7756
		    </script>';
7757
	if ($addlink) $out.=' <a href="'.$addlink.'" target="_blank">'.$langs->trans("Link").'</a>';
7758
	return $out;
7759
}
7760
7761
7762
/**
7763
 *	Return mime type of a file
7764
 *
7765
 *	@param	string	$file		Filename we looking for MIME type
7766
 *  @param  string	$default    Default mime type if extension not found in known list
7767
 * 	@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
7768
 *	@return string 		    	Return a mime type family (text/xxx, application/xxx, image/xxx, audio, video, archive)
7769
 *  @see    image_format_supported() from images.lib.php
7770
 */
7771
function dol_mimetype($file, $default = 'application/octet-stream', $mode = 0)
7772
{
7773
	$mime=$default;
7774
	$imgmime='other.png';
7775
	$famime='file-o';
7776
	$srclang='';
7777
7778
	$tmpfile=preg_replace('/\.noexe$/', '', $file);
7779
7780
	// Text files
7781
	if (preg_match('/\.txt$/i', $tmpfile))         			   { $mime='text/plain';    $imgmime='text.png'; $famime='file-text-o'; }
7782
	if (preg_match('/\.rtx$/i', $tmpfile))                      { $mime='text/richtext'; $imgmime='text.png'; $famime='file-text-o'; }
7783
	if (preg_match('/\.csv$/i', $tmpfile))					   { $mime='text/csv';      $imgmime='text.png'; $famime='file-text-o'; }
7784
	if (preg_match('/\.tsv$/i', $tmpfile))					   { $mime='text/tab-separated-values'; $imgmime='text.png'; $famime='file-text-o'; }
7785
	if (preg_match('/\.(cf|conf|log)$/i', $tmpfile))            { $mime='text/plain';    $imgmime='text.png'; $famime='file-text-o'; }
7786
	if (preg_match('/\.ini$/i', $tmpfile))                      { $mime='text/plain';    $imgmime='text.png'; $srclang='ini'; $famime='file-text-o'; }
7787
	if (preg_match('/\.css$/i', $tmpfile))                      { $mime='text/css';      $imgmime='css.png'; $srclang='css'; $famime='file-text-o'; }
7788
	// Certificate files
7789
	if (preg_match('/\.(crt|cer|key|pub)$/i', $tmpfile))        { $mime='text/plain';    $imgmime='text.png'; $famime='file-text-o'; }
7790
	// HTML/XML
7791
	if (preg_match('/\.(html|htm|shtml)$/i', $tmpfile))         { $mime='text/html';     $imgmime='html.png'; $srclang='html'; $famime='file-text-o'; }
7792
	if (preg_match('/\.(xml|xhtml)$/i', $tmpfile))              { $mime='text/xml';      $imgmime='other.png'; $srclang='xml'; $famime='file-text-o'; }
7793
	// Languages
7794
	if (preg_match('/\.bas$/i', $tmpfile))                      { $mime='text/plain'; $imgmime='text.png'; $srclang='bas';  $famime='file-code-o'; }
7795
	if (preg_match('/\.(c)$/i', $tmpfile))                      { $mime='text/plain'; $imgmime='text.png'; $srclang='c';  $famime='file-code-o'; }
7796
	if (preg_match('/\.(cpp)$/i', $tmpfile))                    { $mime='text/plain'; $imgmime='text.png'; $srclang='cpp';  $famime='file-code-o'; }
7797
	if (preg_match('/\.(h)$/i', $tmpfile))                      { $mime='text/plain'; $imgmime='text.png'; $srclang='h';  $famime='file-code-o'; }
7798
	if (preg_match('/\.(java|jsp)$/i', $tmpfile))               { $mime='text/plain'; $imgmime='text.png'; $srclang='java';  $famime='file-code-o'; }
7799
	if (preg_match('/\.php([0-9]{1})?$/i', $tmpfile))           { $mime='text/plain'; $imgmime='php.png'; $srclang='php';  $famime='file-code-o'; }
7800
	if (preg_match('/\.phtml$/i', $tmpfile))                    { $mime='text/plain'; $imgmime='php.png'; $srclang='php';  $famime='file-code-o'; }
7801
	if (preg_match('/\.(pl|pm)$/i', $tmpfile))                  { $mime='text/plain'; $imgmime='pl.png'; $srclang='perl';  $famime='file-code-o'; }
7802
	if (preg_match('/\.sql$/i', $tmpfile))                      { $mime='text/plain'; $imgmime='text.png'; $srclang='sql';  $famime='file-code-o'; }
7803
	if (preg_match('/\.js$/i', $tmpfile))                       { $mime='text/x-javascript'; $imgmime='jscript.png'; $srclang='js';  $famime='file-code-o'; }
7804
	// Open office
7805
	if (preg_match('/\.odp$/i', $tmpfile))                      { $mime='application/vnd.oasis.opendocument.presentation'; $imgmime='ooffice.png'; $famime='file-powerpoint-o'; }
7806
	if (preg_match('/\.ods$/i', $tmpfile))                      { $mime='application/vnd.oasis.opendocument.spreadsheet';  $imgmime='ooffice.png'; $famime='file-excel-o'; }
7807
	if (preg_match('/\.odt$/i', $tmpfile))                      { $mime='application/vnd.oasis.opendocument.text';         $imgmime='ooffice.png'; $famime='file-word-o'; }
7808
	// MS Office
7809
	if (preg_match('/\.mdb$/i', $tmpfile))					   { $mime='application/msaccess'; $imgmime='mdb.png'; $famime='file-o'; }
7810
	if (preg_match('/\.doc(x|m)?$/i', $tmpfile))				   { $mime='application/msword'; $imgmime='doc.png'; $famime='file-word-o'; }
7811
	if (preg_match('/\.dot(x|m)?$/i', $tmpfile))				   { $mime='application/msword'; $imgmime='doc.png'; $famime='file-word-o'; }
7812
	if (preg_match('/\.xlt(x)?$/i', $tmpfile))				   { $mime='application/vnd.ms-excel'; $imgmime='xls.png'; $famime='file-excel-o'; }
7813
	if (preg_match('/\.xla(m)?$/i', $tmpfile))				   { $mime='application/vnd.ms-excel'; $imgmime='xls.png'; $famime='file-excel-o'; }
7814
	if (preg_match('/\.xls$/i', $tmpfile))			           { $mime='application/vnd.ms-excel'; $imgmime='xls.png'; $famime='file-excel-o'; }
7815
	if (preg_match('/\.xls(b|m|x)$/i', $tmpfile))			   { $mime='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; $imgmime='xls.png'; $famime='file-excel-o'; }
7816
	if (preg_match('/\.pps(m|x)?$/i', $tmpfile))				   { $mime='application/vnd.ms-powerpoint'; $imgmime='ppt.png'; $famime='file-powerpoint-o'; }
7817
	if (preg_match('/\.ppt(m|x)?$/i', $tmpfile))				   { $mime='application/x-mspowerpoint'; $imgmime='ppt.png'; $famime='file-powerpoint-o'; }
7818
	// Other
7819
	if (preg_match('/\.pdf$/i', $tmpfile))                      { $mime='application/pdf'; $imgmime='pdf.png'; $famime='file-pdf-o'; }
7820
	// Scripts
7821
	if (preg_match('/\.bat$/i', $tmpfile))                      { $mime='text/x-bat';  $imgmime='script.png'; $srclang='dos'; $famime='file-code-o'; }
7822
	if (preg_match('/\.sh$/i', $tmpfile))                       { $mime='text/x-sh';   $imgmime='script.png'; $srclang='bash'; $famime='file-code-o'; }
7823
	if (preg_match('/\.ksh$/i', $tmpfile))                      { $mime='text/x-ksh';  $imgmime='script.png'; $srclang='bash'; $famime='file-code-o'; }
7824
	if (preg_match('/\.bash$/i', $tmpfile))                     { $mime='text/x-bash'; $imgmime='script.png'; $srclang='bash'; $famime='file-code-o'; }
7825
	// Images
7826
	if (preg_match('/\.ico$/i', $tmpfile))                      { $mime='image/x-icon'; $imgmime='image.png'; $famime='file-image-o'; }
7827
	if (preg_match('/\.(jpg|jpeg)$/i', $tmpfile))			   { $mime='image/jpeg';   $imgmime='image.png'; $famime='file-image-o'; }
7828
	if (preg_match('/\.png$/i', $tmpfile))					   { $mime='image/png';    $imgmime='image.png'; $famime='file-image-o'; }
7829
	if (preg_match('/\.gif$/i', $tmpfile))					   { $mime='image/gif';    $imgmime='image.png'; $famime='file-image-o'; }
7830
	if (preg_match('/\.bmp$/i', $tmpfile))					   { $mime='image/bmp';    $imgmime='image.png'; $famime='file-image-o'; }
7831
	if (preg_match('/\.(tif|tiff)$/i', $tmpfile))			   { $mime='image/tiff';   $imgmime='image.png'; $famime='file-image-o'; }
7832
	if (preg_match('/\.svg$/i', $tmpfile))					   { $mime='image/svg+xml';$imgmime='image.png'; $famime='file-image-o'; }
7833
	// Calendar
7834
	if (preg_match('/\.vcs$/i', $tmpfile))					   { $mime='text/calendar'; $imgmime='other.png'; $famime='file-text-o'; }
7835
	if (preg_match('/\.ics$/i', $tmpfile))					   { $mime='text/calendar'; $imgmime='other.png'; $famime='file-text-o'; }
7836
	// Other
7837
	if (preg_match('/\.torrent$/i', $tmpfile))				   { $mime='application/x-bittorrent'; $imgmime='other.png'; $famime='file-o'; }
7838
	// Audio
7839
	if (preg_match('/\.(mp3|ogg|au|wav|wma|mid)$/i', $tmpfile)) { $mime='audio'; $imgmime='audio.png'; $famime='file-audio-o'; }
7840
	// Video
7841
	if (preg_match('/\.ogv$/i', $tmpfile))                      { $mime='video/ogg';       $imgmime='video.png'; $famime='file-video-o'; }
7842
	if (preg_match('/\.webm$/i', $tmpfile))                     { $mime='video/webm';      $imgmime='video.png'; $famime='file-video-o'; }
7843
	if (preg_match('/\.avi$/i', $tmpfile))                      { $mime='video/x-msvideo'; $imgmime='video.png'; $famime='file-video-o'; }
7844
	if (preg_match('/\.divx$/i', $tmpfile))                     { $mime='video/divx';      $imgmime='video.png'; $famime='file-video-o'; }
7845
	if (preg_match('/\.xvid$/i', $tmpfile))                     { $mime='video/xvid';      $imgmime='video.png'; $famime='file-video-o'; }
7846
	if (preg_match('/\.(wmv|mpg|mpeg)$/i', $tmpfile))           { $mime='video';           $imgmime='video.png'; $famime='file-video-o'; }
7847
	// Archive
7848
	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, ...
7849
	// Exe
7850
	if (preg_match('/\.(exe|com)$/i', $tmpfile))                { $mime='application/octet-stream'; $imgmime='other.png'; $famime='file-o'; }
7851
	// Lib
7852
	if (preg_match('/\.(dll|lib|o|so|a)$/i', $tmpfile))         { $mime='library'; $imgmime='library.png'; $famime='file-o'; }
7853
	// Err
7854
	if (preg_match('/\.err$/i', $tmpfile))                      { $mime='error'; $imgmime='error.png'; $famime='file-text-o'; }
7855
7856
	// Return string
7857
	if ($mode == 1)
7858
	{
7859
		$tmp=explode('/', $mime);
7860
		return (! empty($tmp[1])?$tmp[1]:$tmp[0]);
7861
	}
7862
	if ($mode == 2)
7863
	{
7864
		return $imgmime;
7865
	}
7866
	if ($mode == 3)
7867
	{
7868
		return $srclang;
7869
	}
7870
	if ($mode == 4)
7871
	{
7872
		return $famime;
7873
	}
7874
	return $mime;
7875
}
7876
7877
/**
7878
 * Return value from dictionary
7879
 *
7880
 * @param string	$tablename		name of dictionary
7881
 * @param string	$field			the value to return
7882
 * @param int		$id				id of line
7883
 * @param bool		$checkentity	add filter on entity
7884
 * @param string	$rowidfield		name of the column rowid
7885
 * @return string
7886
 */
7887
function getDictvalue($tablename, $field, $id, $checkentity = false, $rowidfield = 'rowid')
7888
{
7889
	global $dictvalues,$db,$langs;
7890
7891
	if (!isset($dictvalues[$tablename]))
7892
	{
7893
		$dictvalues[$tablename] = array();
7894
		$sql = 'SELECT * FROM '.$tablename.' WHERE 1';
7895
		if ($checkentity) $sql.= ' AND entity IN (0,'.getEntity($tablename).')';
7896
7897
		$resql = $db->query($sql);
7898
		if ($resql)
7899
		{
7900
			while ($obj = $db->fetch_object($resql))
7901
			{
7902
				$dictvalues[$tablename][$obj->{$rowidfield}] = $obj;
7903
			}
7904
		}
7905
		else
7906
		{
7907
			dol_print_error($db);
7908
		}
7909
	}
7910
7911
	if (!empty($dictvalues[$tablename][$id])) return $dictvalues[$tablename][$id]->{$field}; // Found
7912
	else // Not found
7913
	{
7914
		if ($id > 0) return $id;
7915
		return '';
7916
	}
7917
}
7918
7919
/**
7920
 *	Return true if the color is light
7921
 *
7922
 *  @param	string	$stringcolor		String with hex (FFFFFF) or comma RGB ('255,255,255')
7923
 *  @return	int							-1 : Error with argument passed |0 : color is dark | 1 : color is light
7924
 */
7925
function colorIsLight($stringcolor)
7926
{
7927
    $stringcolor = str_replace('#', '', $stringcolor);
7928
	$res = -1;
7929
	if (!empty($stringcolor))
7930
	{
7931
		$res = 0;
7932
		$tmp=explode(',', $stringcolor);
7933
		if (count($tmp) > 1)   // This is a comma RGB ('255','255','255')
7934
		{
7935
			$r = $tmp[0];
7936
			$g = $tmp[1];
7937
			$b = $tmp[2];
7938
		}
7939
		else
7940
		{
7941
			$hexr=$stringcolor[0].$stringcolor[1];
7942
			$hexg=$stringcolor[2].$stringcolor[3];
7943
			$hexb=$stringcolor[4].$stringcolor[5];
7944
			$r = hexdec($hexr);
7945
			$g = hexdec($hexg);
7946
			$b = hexdec($hexb);
7947
		}
7948
		$bright = (max($r, $g, $b) + min($r, $g, $b)) / 510.0;    // HSL algorithm
7949
		if ($bright > 0.6) $res = 1;
7950
	}
7951
	return $res;
7952
}
7953
7954
/**
7955
 * Function to test if an entry is enabled or not
7956
 *
7957
 * @param	string		$type_user					0=We test for internal user, 1=We test for external user
7958
 * @param	array		$menuentry					Array for feature entry to test
7959
 * @param	array		$listofmodulesforexternal	Array with list of modules allowed to external users
7960
 * @return	int										0=Hide, 1=Show, 2=Show gray
7961
 */
7962
function isVisibleToUserType($type_user, &$menuentry, &$listofmodulesforexternal)
7963
{
7964
	global $conf;
7965
7966
	//print 'type_user='.$type_user.' module='.$menuentry['module'].' enabled='.$menuentry['enabled'].' perms='.$menuentry['perms'];
7967
	//print 'ok='.in_array($menuentry['module'], $listofmodulesforexternal);
7968
	if (empty($menuentry['enabled'])) return 0;	// Entry disabled by condition
7969
	if ($type_user && $menuentry['module'])
7970
	{
7971
		$tmploops=explode('|', $menuentry['module']);
7972
		$found=0;
7973
		foreach($tmploops as $tmploop)
7974
		{
7975
			if (in_array($tmploop, $listofmodulesforexternal)) {
7976
				$found++; break;
7977
			}
7978
		}
7979
		if (! $found) return 0;	// Entry is for menus all excluded to external users
7980
	}
7981
	if (! $menuentry['perms'] && $type_user) return 0; 											// No permissions and user is external
7982
	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
7983
	if (! $menuentry['perms']) return 2;															// No permissions and user is external
7984
	return 1;
7985
}
7986
7987
/**
7988
 * Round to next multiple.
7989
 *
7990
 * @param 	double		$n		Number to round up
7991
 * @param 	integer		$x		Multiple. For example 60 to round up to nearest exact minute for a date with seconds.
7992
 * @return 	integer				Value rounded.
7993
 */
7994
function roundUpToNextMultiple($n, $x = 5)
7995
{
7996
	return (ceil($n)%$x === 0) ? ceil($n) : round(($n+$x/2)/$x)*$x;
7997
}
7998
7999
/**
8000
 * Function dolGetBadge
8001
 *
8002
 * @param   string  $label      label of badge no html : use in alt attribute for accessibility
8003
 * @param   string  $html       optional : label of badge with html
8004
 * @param   string  $type       type of badge : Primary Secondary Success Danger Warning Info Light Dark status0 status1 status2 status3 status4 status5 status6 status7 status8 status9
8005
 * @param   string  $mode       default '' , pill, dot
8006
 * @param   string  $url        the url for link
8007
 * @param   array   $params     various params for future : recommended rather than adding more fuction arguments
8008
 * @return  string              Html badge
8009
 */
8010
function dolGetBadge($label, $html = '', $type = 'primary', $mode = '', $url = '', $params = array())
8011
{
8012
    $attr=array(
8013
        'class'=>'badge'.(!empty($mode)?' badge-'.$mode:'').(!empty($type)?' badge-'.$type:'')
8014
    );
8015
8016
    if(empty($html)){
8017
        $html = $label;
8018
    }
8019
8020
    if(!empty($url)){
8021
        $attr['href'] = $url;
8022
    }
8023
8024
    if($mode==='dot')
8025
    {
8026
        $attr['class'].= ' classfortooltip';
8027
        $attr['title'] = $html;
8028
        $attr['aria-label'] = $label;
8029
        $html='';
8030
    }
8031
8032
    // Override attr
8033
    if(!empty($params['attr']) && is_array($params['attr'])){
8034
        foreach($params['attr']as $key => $value){
8035
            $attr[$key] = $value;
8036
        }
8037
    }
8038
8039
    // TODO: add hook
8040
8041
    // escape all attribute
8042
    $attr = array_map('dol_escape_htmltag', $attr);
8043
8044
    $TCompiledAttr = array();
8045
    foreach($attr as $key => $value){
8046
        $TCompiledAttr[] = $key.'="'.$value.'"';
8047
    }
8048
8049
    $compiledAttributes = !empty($TCompiledAttr)?implode(' ', $TCompiledAttr):'';
8050
8051
    $tag = !empty($url)?'a':'span';
8052
8053
    return '<'.$tag.' '.$compiledAttributes.'>'.$html.'</'.$tag.'>';
8054
}
8055
8056
8057
/**
8058
 * Function dolGetStatus
8059
 *
8060
 * @param   string  $statusLabel       Label of badge no html : use in alt attribute for accessibility
8061
 * @param   string  $statusLabelShort  Short label of badge no html
8062
 * @param   string  $html              Optional : label of badge with html
8063
 * @param   string  $statusType        status0 status1 status2 status3 status4 status5 status6 status7 status8 status9 : image name or badge name
8064
 * @param   int	    $displayMode       0=Long label, 1=Short label, 2=Picto + Short label, 3=Picto, 4=Picto + Long label, 5=Short label + Picto, 6=Long label + Picto
8065
 * @param   string  $url               The url for link
8066
 * @param   array   $params            Various params for future : recommended rather than adding more function arguments
8067
 * @return  string                     Html status string
8068
 */
8069
function dolGetStatus($statusLabel = '', $statusLabelShort = '', $html = '', $statusType = 'status0', $displayMode = 0, $url = '', $params = array())
8070
{
8071
    global $conf;
8072
8073
    $return = '';
8074
8075
    // image's filename are still in French
8076
    $statusImg=array(
8077
        'status0' => 'statut0'
8078
        ,'status1' => 'statut1'
8079
        ,'status2' => 'statut2'
8080
        ,'status3' => 'statut3'
8081
        ,'status4' => 'statut4'
8082
        ,'status5' => 'statut5'
8083
        ,'status6' => 'statut6'
8084
        ,'status7' => 'statut7'
8085
        ,'status8' => 'statut8'
8086
        ,'status9' => 'statut9'
8087
    );
8088
8089
    // TODO : add a hook
8090
8091
    if ($displayMode == 0) {
8092
        $return = !empty($html)?$html:$statusLabel;
8093
    }
8094
    elseif ($displayMode == 1) {
8095
        $return = !empty($html)?$html:(!empty($statusLabelShort)?$statusLabelShort:$statusLabel);
8096
    }
8097
    // use status with images
8098
    elseif (empty($conf->global->MAIN_STATUS_USES_CSS)){
8099
        $return = '';
8100
        $htmlLabel      = (in_array($displayMode, array(1,2,5))?'<span class="hideonsmartphone">':'').(!empty($html)?$html:$statusLabel).(in_array($displayMode, array(1,2,5))?'</span>':'');
8101
        $htmlLabelShort = (in_array($displayMode, array(1,2,5))?'<span class="hideonsmartphone">':'').(!empty($html)?$html:(!empty($statusLabelShort)?$statusLabelShort:$statusLabel)).(in_array($displayMode, array(1,2,5))?'</span>':'');
8102
8103
        if(!empty($statusImg[$statusType])){
8104
            $htmlImg = img_picto($statusLabel, $statusImg[$statusType]);
8105
        }else{
8106
            $htmlImg = img_picto($statusLabel, $statusType);
8107
        }
8108
8109
        if ($displayMode === 2) {
8110
            $return =  $htmlImg .' '. $htmlLabelShort;
8111
        }
8112
        elseif ($displayMode === 3) {
8113
            $return = $htmlImg;
8114
        }
8115
        elseif ($displayMode === 4) {
8116
            $return =  $htmlImg .' '. $htmlLabel;
8117
        }
8118
        elseif ($displayMode === 5) {
8119
            $return = $htmlLabelShort .' '. $htmlImg;
8120
        }
8121
        else { // $displayMode >= 6
8122
            $return = $htmlLabel .' '. $htmlImg;
8123
        }
8124
    }
8125
    // Use new badge
8126
    elseif (!empty($conf->global->MAIN_STATUS_USES_CSS) && !empty($displayMode)) {
8127
        $statusLabelShort = !empty($statusLabelShort)?$statusLabelShort:$statusLabel;
8128
8129
        if ($displayMode == 3) {
8130
            $return = dolGetBadge($statusLabel, '', $statusType, 'dot');
8131
        }
8132
        elseif ($displayMode === 5) {
8133
            $return = dolGetBadge($statusLabelShort, $html, $statusType);
8134
        }
8135
        else {
8136
            $return = dolGetBadge($statusLabel, $html, $statusType);
8137
        }
8138
    }
8139
8140
    return $return;
8141
}
8142
8143
8144
/**
8145
 * Function dolGetButtonAction
8146
 *
8147
 * @param string    $label      label of button no html : use in alt attribute for accessibility $html is not empty
8148
 * @param string    $html       optional : content with html
8149
 * @param string    $actionType default, delete, danger
8150
 * @param string    $url        the url for link
8151
 * @param string    $id         attribute id of button
8152
 * @param int       $userRight  user action right
8153
 * @param array     $params     various params for future : recommended rather than adding more function arguments
8154
 * @return string               html button
8155
 */
8156
function dolGetButtonAction($label, $html = '', $actionType = 'default', $url = '', $id = '', $userRight = 1, $params = array())
8157
{
8158
    $class = 'butAction' ;
8159
    if($actionType == 'danger' || $actionType == 'delete'){
8160
        $class = 'butActionDelete' ;
8161
    }
8162
8163
    $attr=array(
8164
        'class' => $class
8165
        ,'href' => empty($url)?'':$url
8166
    );
8167
8168
    if(empty($html)){
8169
        $html = $label;
8170
    }else{
8171
        $attr['aria-label'] = $label;
8172
    }
8173
8174
    if(empty($userRight)){
8175
        $attr['class'] = 'butActionRefused';
8176
        $attr['href'] = '';
8177
    }
8178
8179
    if(!empty($id)){
8180
        $attr['id'] = $id;
8181
    }
8182
8183
    // Override attr
8184
    if(!empty($params['attr']) && is_array($params['attr'])){
8185
        foreach($params['attr'] as $key => $value){
8186
            if($key == 'class'){
8187
                $attr['class'].= ' '.$value;
8188
            }
8189
            elseif($key == 'classOverride'){
8190
                $attr['class'] = $value;
8191
            }
8192
            else{
8193
                $attr[$key] = $value;
8194
            }
8195
        }
8196
    }
8197
8198
    if(isset($attr['href']) && empty($attr['href'])){
8199
        unset($attr['href']);
8200
    }
8201
8202
    // TODO : add a hook
8203
8204
    // escape all attribute
8205
    $attr = array_map('dol_escape_htmltag', $attr);
8206
8207
    $TCompiledAttr = array();
8208
    foreach($attr as $key => $value){
8209
        $TCompiledAttr[] = $key.'="'.$value.'"';
8210
    }
8211
8212
    $compiledAttributes = !empty($TCompiledAttr)?implode(' ', $TCompiledAttr):'';
8213
8214
    $tag = !empty($attr['href'])?'a':'span';
8215
8216
    return '<div class="inline-block divButAction"><'.$tag.' '.$compiledAttributes.'>'.$html.'</'.$tag.'></div>';
8217
}
8218
8219
/**
8220
 * Function dolGetButtonTitle : this kind of buttons are used in title in list
8221
 *
8222
 * @param string    $label      label of button
8223
 * @param string    $helpText   optional : content for help tooltip
8224
 * @param string    $iconClass  class for icon element
8225
 * @param string    $url        the url for link
8226
 * @param string    $id         attribute id of button
8227
 * @param int       $status     0 no user rights, 1 active, -1 Feature Disabled, -2 disable Other reason use helpText as tooltip
8228
 * @param array     $params     various params for future : recommended rather than adding more function arguments
8229
 * @return string               html button
8230
 */
8231
function dolGetButtonTitle($label, $helpText = '', $iconClass = 'fa fa-file', $url = '', $id = '', $status = 1, $params = array())
8232
{
8233
    global $langs, $conf, $user;
8234
8235
    // Actually this conf is used in css too for external module compatibility and smooth transition to this function
8236
    if (! empty($conf->global->MAIN_BUTTON_HIDE_UNAUTHORIZED) && (! $user->admin) && $status <= 0) {
8237
        return '';
8238
    }
8239
8240
    $class = 'btnTitle' ;
8241
8242
    // hidden conf keep during button transition TODO: remove this block
8243
    if(empty($conf->global->MAIN_USE_NEW_TITLE_BUTTON)){
8244
        $class = 'butActionNew';
8245
    }
8246
8247
    $attr=array(
8248
        'class' => $class
8249
        ,'href' => empty($url)?'':$url
8250
    );
8251
8252
    if(!empty($helpText)){
8253
        $attr['title'] = dol_escape_htmltag($helpText);
8254
    }
8255
8256
    if($status <= 0){
8257
        $attr['class'] .= ' refused';
8258
8259
        // hidden conf keep during button transition TODO: remove this block
8260
        if(empty($conf->global->MAIN_USE_NEW_TITLE_BUTTON)){
8261
            $attr['class'] = 'butActionNewRefused';
8262
        }
8263
8264
        $attr['href'] = '';
8265
8266
        if($status == -1){ // disable
8267
            $attr['title'] = dol_escape_htmltag($langs->transnoentitiesnoconv("FeatureDisabled"));
8268
        }
8269
        elseif($status == 0){ // Not enough permissions
8270
            $attr['title'] = dol_escape_htmltag($langs->transnoentitiesnoconv("NotEnoughPermissions"));
8271
        }
8272
    }
8273
8274
    if(!empty($attr['title'])){
8275
        $attr['class'] .= ' classfortooltip';
8276
    }
8277
8278
    if(empty($id)){
8279
        $attr['id'] = $id;
8280
    }
8281
8282
    // Override attr
8283
    if(!empty($params['attr']) && is_array($params['attr'])){
8284
        foreach($params['attr'] as $key => $value){
8285
            if($key == 'class'){
8286
                $attr['class'].= ' '.$value;
8287
            }
8288
            elseif($key == 'classOverride'){
8289
                $attr['class'] = $value;
8290
            }
8291
            else{
8292
                $attr[$key] = $value;
8293
            }
8294
        }
8295
    }
8296
8297
    if(isset($attr['href']) && empty($attr['href'])){
8298
        unset($attr['href']);
8299
    }
8300
8301
    // TODO : add a hook
8302
8303
    // escape all attribute
8304
    $attr = array_map('dol_escape_htmltag', $attr);
8305
8306
    $TCompiledAttr = array();
8307
    foreach($attr as $key => $value){
8308
        $TCompiledAttr[] = $key.'="'.$value.'"';
8309
    }
8310
8311
    $compiledAttributes = !empty($TCompiledAttr)?implode(' ', $TCompiledAttr):'';
8312
8313
    $tag = !empty($attr['href'])?'a':'span';
8314
8315
8316
    $button ='<'.$tag.' '.$compiledAttributes.' >';
8317
    $button.= '<span class="'.$iconClass.' valignmiddle btnTitle-icon"></span>';
8318
    $button.= '<span class="valignmiddle text-plus-circle btnTitle-label">'.$label.'</span>';
8319
    $button.= '</'.$tag.'>';
8320
8321
    // hidden conf keep during button transition TODO: remove this block
8322
    if(empty($conf->global->MAIN_USE_NEW_TITLE_BUTTON)){
8323
        $button='<'.$tag.' '.$compiledAttributes.' ><span class="text-plus-circle">'.$label.'</span>';
8324
        $button.= '<span class="'.$iconClass.' valignmiddle"></span>';
8325
        $button.= '</'.$tag.'>';
8326
    }
8327
8328
    return $button;
8329
}
8330
8331
/**
8332
 * Return if a file can contains executable content
8333
 *
8334
 * @param   string  $filename       File NamedRange
8335
 * @return  boolean                 True if yes, False if no
8336
 */
8337
function isAFileWithExecutableContent($filename)
8338
{
8339
    if (preg_match('/\.(htm|html|js|php|phtml|pl|py|cgi|ksh|sh|bash|bat|cmd|wpk|exe|dmg)$/i', $filename))
8340
    {
8341
        return true;
8342
    }
8343
    return false;
8344
}
8345