Passed
Branch develop (5cbde9)
by
unknown
26:38
created

dol_syslog()   F

Complexity

Conditions 28
Paths 1547

Size

Total Lines 75
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 28
eloc 36
c 0
b 0
f 0
nc 1547
nop 5
dl 0
loc 75
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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

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