Test Failed
Push — master ( d56fde...b8c830 )
by Alxarafe
37:54
created

dol_getIdFromCode()   A

Complexity

Conditions 6
Paths 8

Size

Total Lines 33
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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

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