Passed
Branch develop (89ad1c)
by
unknown
111:31
created

dol_htmlwithnojs()   C

Complexity

Conditions 13
Paths 85

Size

Total Lines 82
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 13
eloc 36
nc 85
nop 3
dl 0
loc 82
rs 6.6166
c 1
b 0
f 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-2022	Laurent Destailleur			<[email protected]>
5
 * Copyright (C) 2004		Sebastien Di Cintio			<[email protected]>
6
 * Copyright (C) 2004		Benoit Mortier				<[email protected]>
7
 * Copyright (C) 2004		Christophe Combelles		<[email protected]>
8
 * Copyright (C) 2005-2019	Regis Houssin				<[email protected]>
9
 * Copyright (C) 2008		Raphael Bertrand (Resultic)	<[email protected]>
10
 * Copyright (C) 2010-2018	Juanjo Menent				<[email protected]>
11
 * Copyright (C) 2013		Cédric Salvador				<[email protected]>
12
 * Copyright (C) 2013-2021	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-2022  Frédéric France             <[email protected]>
17
 * Copyright (C) 2019-2022  Thibault Foucart            <[email protected]>
18
 * Copyright (C) 2020       Open-Dsi         			<[email protected]>
19
 * Copyright (C) 2021       Gauthier VERDOL         	<[email protected]>
20
 * Copyright (C) 2022       Anthony Berton	         	<[email protected]>
21
 * Copyright (C) 2022       Ferran Marcet           	<[email protected]>
22
 * Copyright (C) 2022       Charlene Benke           	<[email protected]>
23
 *
24
 * This program is free software; you can redistribute it and/or modify
25
 * it under the terms of the GNU General Public License as published by
26
 * the Free Software Foundation; either version 3 of the License, or
27
 * (at your option) any later version.
28
 *
29
 * This program is distributed in the hope that it will be useful,
30
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
31
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
32
 * GNU General Public License for more details.
33
 *
34
 * You should have received a copy of the GNU General Public License
35
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
36
 * or see https://www.gnu.org/
37
 */
38
39
/**
40
 *	\file			htdocs/core/lib/functions.lib.php
41
 *	\brief			A set of functions for Dolibarr
42
 *					This file contains all frequently used functions.
43
 */
44
45
include_once DOL_DOCUMENT_ROOT.'/core/lib/json.lib.php';
46
47
48
if (!function_exists('utf8_encode')) {
49
	/**
50
	 * Implement utf8_encode for PHP that does not support it.
51
	 *
52
	 * @param	mixed	$elements		PHP Object to json encode
53
	 * @return 	string					Json encoded string
54
	 */
55
	function utf8_encode($elements)
56
	{
57
		return mb_convert_encoding($elements, 'UTF-8', 'ISO-8859-1');
58
	}
59
}
60
61
if (!function_exists('utf8_decode')) {
62
	/**
63
	 * Implement utf8_decode for PHP that does not support it.
64
	 *
65
	 * @param	mixed	$elements		PHP Object to json encode
66
	 * @return 	string					Json encoded string
67
	 */
68
	function utf8_decode($elements)
69
	{
70
		return mb_convert_encoding($elements, 'ISO-8859-1', 'UTF-8');
71
	}
72
}
73
74
75
/**
76
 * Return dolibarr global constant string value
77
 *
78
 * @param string $key 		key to return value, return '' if not set
79
 * @param string $default 	value to return
80
 * @return string
81
 */
82
function getDolGlobalString($key, $default = '')
83
{
84
	global $conf;
85
	// return $conf->global->$key ?? $default;
86
	return (string) (empty($conf->global->$key) ? $default : $conf->global->$key);
87
}
88
89
/**
90
 * Return dolibarr global constant int value
91
 *
92
 * @param string 	$key 		key to return value, return 0 if not set
93
 * @param int 		$default 	value to return
94
 * @return int
95
 */
96
function getDolGlobalInt($key, $default = 0)
97
{
98
	global $conf;
99
	// return $conf->global->$key ?? $default;
100
	return (int) (empty($conf->global->$key) ? $default : $conf->global->$key);
101
}
102
103
/**
104
 * Return Dolibarr user constant string value
105
 *
106
 * @param string $key 		key to return value, return '' if not set
107
 * @param string $default 	value to return
108
 * @param User   $tmpuser   To get another user than current user
109
 * @return string
110
 */
111
function getDolUserString($key, $default = '', $tmpuser = null)
112
{
113
	if (empty($tmpuser)) {
114
		global $user;
115
		$tmpuser = $user;
116
	}
117
118
	// return $conf->global->$key ?? $default;
119
	return (string) (empty($tmpuser->conf->$key) ? $default : $tmpuser->conf->$key);
120
}
121
122
/**
123
 * Return Dolibarr user constant int value
124
 *
125
 * @param string 	$key 			key to return value, return 0 if not set
126
 * @param int 		$default 		value to return
127
 * @param User   	$tmpuser   		To get another user than current user
128
 * @return int
129
 */
130
function getDolUserInt($key, $default = 0, $tmpuser = null)
131
{
132
	if (empty($tmpuser)) {
133
		global $user;
134
		$tmpuser = $user;
135
	}
136
137
	// return $conf->global->$key ?? $default;
138
	return (int) (empty($tmpuser->conf->$key) ? $default : $tmpuser->conf->$key);
139
}
140
141
/**
142
 * Is Dolibarr module enabled
143
 *
144
 * @param string $module module name to check
145
 * @return int
146
 */
147
function isModEnabled($module)
148
{
149
	global $conf;
150
	return !empty($conf->$module->enabled);
151
}
152
153
/**
154
 * Return a DoliDB instance (database handler).
155
 *
156
 * @param   string	$type		Type of database (mysql, pgsql...)
157
 * @param	string	$host		Address of database server
158
 * @param	string	$user		Authorized username
159
 * @param	string	$pass		Password
160
 * @param	string	$name		Name of database
161
 * @param	int		$port		Port of database server
162
 * @return	DoliDB				A DoliDB instance
163
 */
164
function getDoliDBInstance($type, $host, $user, $pass, $name, $port)
165
{
166
	require_once DOL_DOCUMENT_ROOT."/core/db/".$type.'.class.php';
167
168
	$class = 'DoliDB'.ucfirst($type);
169
	$dolidb = new $class($type, $host, $user, $pass, $name, $port);
170
	return $dolidb;
171
}
172
173
/**
174
 * 	Get list of entity id to use.
175
 *
176
 * 	@param	string	$element		Current element
177
 *									'societe', 'socpeople', 'actioncomm', 'agenda', 'resource',
178
 *									'product', 'productprice', 'stock', 'bom', 'mo',
179
 *									'propal', 'supplier_proposal', 'invoice', 'supplier_invoice', 'payment_various',
180
 *									'categorie', 'bank_account', 'bank_account', 'adherent', 'user',
181
 *									'commande', 'supplier_order', 'expedition', 'intervention', 'survey',
182
 *									'contract', 'tax', 'expensereport', 'holiday', 'multicurrency', 'project',
183
 *									'email_template', 'event', 'donation'
184
 *									'c_paiement', 'c_payment_term', ...
185
 * 	@param	int		$shared			0=Return id of current entity only,
186
 * 									1=Return id of current entity + shared entities (default)
187
 *  @param	object	$currentobject	Current object if needed
188
 * 	@return	mixed					Entity id(s) to use ( eg. entity IN ('.getEntity(elementname).')' )
189
 */
190
function getEntity($element, $shared = 1, $currentobject = null)
191
{
192
	global $conf, $mc, $hookmanager, $object, $action, $db;
193
194
	if (! is_object($hookmanager)) {
195
		$hookmanager = new HookManager($db);
196
	}
197
198
	// fix different element names (France to English)
199
	switch ($element) {
200
		case 'contrat':
201
			$element = 'contract';
202
			break; // "/contrat/class/contrat.class.php"
203
		case 'order_supplier':
204
			$element = 'supplier_order';
205
			break; // "/fourn/class/fournisseur.commande.class.php"
206
		case 'invoice_supplier':
207
			$element = 'supplier_invoice';
208
			break; // "/fourn/class/fournisseur.facture.class.php"
209
	}
210
211
	if (is_object($mc)) {
212
		$out = $mc->getEntity($element, $shared, $currentobject);
213
	} else {
214
		$out = '';
215
		$addzero = array('user', 'usergroup', 'c_email_templates', 'email_template', 'default_values');
216
		if (in_array($element, $addzero)) {
217
			$out .= '0,';
218
		}
219
		$out .= ((int) $conf->entity);
220
	}
221
222
	// Manipulate entities to query on the fly
223
	$parameters = array(
224
		'element' => $element,
225
		'shared' => $shared,
226
		'object' => $object,
227
		'currentobject' => $currentobject,
228
		'out' => $out
229
	);
230
	$reshook = $hookmanager->executeHooks('hookGetEntity', $parameters, $currentobject, $action); // Note that $action and $object may have been modified by some hooks
231
232
	if (is_numeric($reshook)) {
233
		if ($reshook == 0 && !empty($hookmanager->resPrint)) {
234
			$out .= ','.$hookmanager->resPrint; // add
235
		} elseif ($reshook == 1) {
236
			$out = $hookmanager->resPrint; // replace
237
		}
238
	}
239
240
	return $out;
241
}
242
243
/**
244
 * 	Set entity id to use when to create an object
245
 *
246
 * 	@param	object	$currentobject	Current object
247
 * 	@return	mixed					Entity id to use ( eg. entity = '.setEntity($object) )
248
 */
249
function setEntity($currentobject)
250
{
251
	global $conf, $mc;
252
253
	if (is_object($mc) && method_exists($mc, 'setEntity')) {
254
		return $mc->setEntity($currentobject);
255
	} else {
256
		return ((is_object($currentobject) && $currentobject->id > 0 && $currentobject->entity > 0) ? $currentobject->entity : $conf->entity);
257
	}
258
}
259
260
/**
261
 * 	Return if string has a name dedicated to store a secret
262
 *
263
 * 	@param	string	$keyname	Name of key to test
264
 * 	@return	boolean				True if key is used to store a secret
265
 */
266
function isASecretKey($keyname)
267
{
268
	return preg_match('/(_pass|password|_pw|_key|securekey|serverkey|secret\d?|p12key|exportkey|_PW_[a-z]+|token)$/i', $keyname);
269
}
270
271
272
/**
273
 * Return a numeric value into an Excel like column number. So 0 return 'A', 1 returns 'B'..., 26 return 'AA'
274
 *
275
 * @param	int|string		$n		Numeric value
276
 * @return 	string					Column in Excel format
277
 */
278
function num2Alpha($n)
279
{
280
	for ($r = ""; $n >= 0; $n = intval($n / 26) - 1)
281
		$r = chr($n % 26 + 0x41) . $r;
282
		return $r;
283
}
284
285
286
/**
287
 * Return information about user browser
288
 *
289
 * Returns array with the following format:
290
 * array(
291
 *  'browsername' => Browser name (firefox|chrome|iceweasel|epiphany|safari|opera|ie|unknown)
292
 *  'browserversion' => Browser version. Empty if unknown
293
 *  'browseros' => Set with mobile OS (android|blackberry|ios|palm|symbian|webos|maemo|windows|unknown)
294
 *  'layout' => (tablet|phone|classic)
295
 *  'phone' => empty if not mobile, (android|blackberry|ios|palm|unknown) if mobile
296
 *  'tablet' => true/false
297
 * )
298
 *
299
 * @param string $user_agent Content of $_SERVER["HTTP_USER_AGENT"] variable
300
 * @return	array Check function documentation
301
 */
302
function getBrowserInfo($user_agent)
303
{
304
	include_once DOL_DOCUMENT_ROOT.'/includes/mobiledetect/mobiledetectlib/Mobile_Detect.php';
305
306
	$name = 'unknown';
307
	$version = '';
308
	$os = 'unknown';
309
	$phone = '';
310
311
	$user_agent = substr($user_agent, 0, 512);	// Avoid to process too large user agent
312
313
	$detectmobile = new Mobile_Detect(null, $user_agent);
0 ignored issues
show
Bug introduced by
The type Mobile_Detect was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
314
	$tablet = $detectmobile->isTablet();
315
316
	if ($detectmobile->isMobile()) {
317
		$phone = 'unknown';
318
319
		// If phone/smartphone, we set phone os name.
320
		if ($detectmobile->is('AndroidOS')) {
321
			$os = $phone = 'android';
322
		} elseif ($detectmobile->is('BlackBerryOS')) {
323
			$os = $phone = 'blackberry';
324
		} elseif ($detectmobile->is('iOS')) {
325
			$os = 'ios';
326
			$phone = 'iphone';
327
		} elseif ($detectmobile->is('PalmOS')) {
328
			$os = $phone = 'palm';
329
		} elseif ($detectmobile->is('SymbianOS')) {
330
			$os = 'symbian';
331
		} elseif ($detectmobile->is('webOS')) {
332
			$os = 'webos';
333
		} elseif ($detectmobile->is('MaemoOS')) {
334
			$os = 'maemo';
335
		} elseif ($detectmobile->is('WindowsMobileOS') || $detectmobile->is('WindowsPhoneOS')) {
336
			$os = 'windows';
337
		}
338
	}
339
340
	// OS
341
	if (preg_match('/linux/i', $user_agent)) {
342
		$os = 'linux';
343
	} elseif (preg_match('/macintosh/i', $user_agent)) {
344
		$os = 'macintosh';
345
	} elseif (preg_match('/windows/i', $user_agent)) {
346
		$os = 'windows';
347
	}
348
349
	// Name
350
	$reg = array();
351
	if (preg_match('/firefox(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
352
		$name = 'firefox';
353
		$version = empty($reg[2]) ? '' : $reg[2];
354
	} elseif (preg_match('/edge(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
355
		$name = 'edge';
356
		$version = empty($reg[2]) ? '' : $reg[2];
357
	} elseif (preg_match('/chrome(\/|\s)([\d\.]+)/i', $user_agent, $reg)) {
358
		$name = 'chrome';
359
		$version = empty($reg[2]) ? '' : $reg[2];
360
	} elseif (preg_match('/chrome/i', $user_agent, $reg)) {
361
		// we can have 'chrome (Mozilla...) chrome x.y' in one string
362
		$name = 'chrome';
363
	} elseif (preg_match('/iceweasel/i', $user_agent)) {
364
		$name = 'iceweasel';
365
	} elseif (preg_match('/epiphany/i', $user_agent)) {
366
		$name = 'epiphany';
367
	} elseif (preg_match('/safari(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
368
		$name = 'safari';
369
		$version = empty($reg[2]) ? '' : $reg[2];
370
	} elseif (preg_match('/opera(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
371
		// Safari is often present in string for mobile but its not.
372
		$name = 'opera';
373
		$version = empty($reg[2]) ? '' : $reg[2];
374
	} elseif (preg_match('/(MSIE\s([0-9]+\.[0-9]))|.*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) {
375
		$name = 'ie';
376
		$version = end($reg);
377
	} elseif (preg_match('/(Windows NT\s([0-9]+\.[0-9])).*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) {
378
		// MS products at end
379
		$name = 'ie';
380
		$version = end($reg);
381
	} elseif (preg_match('/l[iy]n(x|ks)(\(|\/|\s)*([\d\.]+)/i', $user_agent, $reg)) {
382
		// MS products at end
383
		$name = 'lynxlinks';
384
		$version = empty($reg[3]) ? '' : $reg[3];
385
	}
386
387
	if ($tablet) {
388
		$layout = 'tablet';
389
	} elseif ($phone) {
390
		$layout = 'phone';
391
	} else {
392
		$layout = 'classic';
393
	}
394
395
	return array(
396
		'browsername' => $name,
397
		'browserversion' => $version,
398
		'browseros' => $os,
399
		'layout' => $layout,
400
		'phone' => $phone,
401
		'tablet' => $tablet
402
	);
403
}
404
405
/**
406
 *  Function called at end of web php process
407
 *
408
 *  @return	void
409
 */
410
function dol_shutdown()
411
{
412
	global $conf, $user, $langs, $db;
413
	$disconnectdone = false;
414
	$depth = 0;
415
	if (is_object($db) && !empty($db->connected)) {
416
		$depth = $db->transaction_opened;
417
		$disconnectdone = $db->close();
418
	}
419
	dol_syslog("--- End access to ".$_SERVER["PHP_SELF"].(($disconnectdone && $depth) ? ' (Warn: db disconnection forced, transaction depth was '.$depth.')' : ''), (($disconnectdone && $depth) ? LOG_WARNING : LOG_INFO));
420
}
421
422
/**
423
 * Return true if we are in a context of submitting the parameter $paramname from a POST of a form.
424
 * Warning:
425
 * For action=add, use:     $var = GETPOST('var');		// No GETPOSTISSET, so GETPOST always called and default value is retreived if not a form POST, and value of form is retreived if it is a form POST.
426
 * For action=update, use:  $var = GETPOSTISSET('var') ? GETPOST('var') : $object->var;
427
 *
428
 * @param 	string	$paramname		Name or parameter to test
429
 * @return 	boolean					True if we have just submit a POST or GET request with the parameter provided (even if param is empty)
430
 */
431
function GETPOSTISSET($paramname)
432
{
433
	$isset = false;
434
435
	$relativepathstring = $_SERVER["PHP_SELF"];
436
	// Clean $relativepathstring
437
	if (constant('DOL_URL_ROOT')) {
438
		$relativepathstring = preg_replace('/^'.preg_quote(constant('DOL_URL_ROOT'), '/').'/', '', $relativepathstring);
439
	}
440
	$relativepathstring = preg_replace('/^\//', '', $relativepathstring);
441
	$relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
442
	//var_dump($relativepathstring);
443
	//var_dump($user->default_values);
444
445
	// Code for search criteria persistence.
446
	// Retrieve values if restore_lastsearch_values
447
	if (!empty($_GET['restore_lastsearch_values'])) {        // Use $_GET here and not GETPOST
448
		if (!empty($_SESSION['lastsearch_values_'.$relativepathstring])) {	// If there is saved values
449
			$tmp = json_decode($_SESSION['lastsearch_values_'.$relativepathstring], true);
450
			if (is_array($tmp)) {
451
				foreach ($tmp as $key => $val) {
452
					if ($key == $paramname) {	// We are on the requested parameter
453
						$isset = true;
454
						break;
455
					}
456
				}
457
			}
458
		}
459
		// If there is saved contextpage, limit, page or mode
460
		if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_'.$relativepathstring])) {
461
			$isset = true;
462
		} elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_'.$relativepathstring])) {
463
			$isset = true;
464
		} elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_'.$relativepathstring])) {
465
			$isset = true;
466
		} elseif ($paramname == 'mode' && !empty($_SESSION['lastsearch_mode_'.$relativepathstring])) {
467
			$isset = true;
468
		}
469
	} else {
470
		$isset = (isset($_POST[$paramname]) || isset($_GET[$paramname])); // We must keep $_POST and $_GET here
471
	}
472
473
	return $isset;
474
}
475
476
/**
477
 * Return true if the parameter $paramname is submit from a POST OR GET as an array.
478
 * Can be used before GETPOST to know if the $check param of GETPOST need to check an array or a string
479
 *
480
 * @param 	string	$paramname		Name or parameter to test
481
 * @param	int		$method			Type of method (0 = get then post, 1 = only get, 2 = only post, 3 = post then get)
482
 * @return 	bool 					True if we have just submit a POST or GET request with the parameter provided (even if param is empty)
483
 */
484
function GETPOSTISARRAY($paramname, $method = 0)
485
{
486
	// for $method test need return the same $val as GETPOST
487
	if (empty($method)) {
488
		$val = isset($_GET[$paramname]) ? $_GET[$paramname] : (isset($_POST[$paramname]) ? $_POST[$paramname] : '');
489
	} elseif ($method == 1) {
490
		$val = isset($_GET[$paramname]) ? $_GET[$paramname] : '';
491
	} elseif ($method == 2) {
492
		$val = isset($_POST[$paramname]) ? $_POST[$paramname] : '';
493
	} elseif ($method == 3) {
494
		$val = isset($_POST[$paramname]) ? $_POST[$paramname] : (isset($_GET[$paramname]) ? $_GET[$paramname] : '');
495
	} else {
496
		$val = 'BadFirstParameterForGETPOST';
497
	}
498
499
	return is_array($val);
500
}
501
502
/**
503
 *  Return value of a param into GET or POST supervariable.
504
 *  Use the property $user->default_values[path]['createform'] and/or $user->default_values[path]['filters'] and/or $user->default_values[path]['sortorder']
505
 *  Note: The property $user->default_values is loaded by main.php when loading the user.
506
 *
507
 *  @param  string  $paramname   Name of parameter to found
508
 *  @param  string  $check	     Type of check
509
 *                               ''=no check (deprecated)
510
 *                               'none'=no check (only for param that should have very rich content like passwords)
511
 *                               'array', 'array:restricthtml' or 'array:aZ09' to check it's an array
512
 *                               'int'=check it's numeric (integer or float)
513
 *                               'intcomma'=check it's integer+comma ('1,2,3,4...')
514
 *                               'alpha'=Same than alphanohtml since v13
515
 *                               'alphawithlgt'=alpha with lgt
516
 *                               'alphanohtml'=check there is no html content and no " and no ../
517
 *                               'aZ'=check it's a-z only
518
 *                               'aZ09'=check it's simple alpha string (recommended for keys)
519
 *                               'aZ09comma'=check it's a string for a sortfield or sortorder
520
 *                               'san_alpha'=Use filter_var with FILTER_SANITIZE_STRING (do not use this for free text string)
521
 *                               'nohtml'=check there is no html content
522
 *                               'restricthtml'=check html content is restricted to some tags only
523
 *                               'custom'= custom filter specify $filter and $options)
524
 *  @param	int		$method	     Type of method (0 = get then post, 1 = only get, 2 = only post, 3 = post then get)
525
 *  @param  int     $filter      Filter to apply when $check is set to 'custom'. (See http://php.net/manual/en/filter.filters.php for détails)
526
 *  @param  mixed   $options     Options to pass to filter_var when $check is set to 'custom'
527
 *  @param	string	$noreplace	 Force disable of replacement of __xxx__ strings.
528
 *  @return string|array         Value found (string or array), or '' if check fails
529
 */
530
function GETPOST($paramname, $check = 'alphanohtml', $method = 0, $filter = null, $options = null, $noreplace = 0)
531
{
532
	global $mysoc, $user, $conf;
533
534
	if (empty($paramname)) {
535
		return 'BadFirstParameterForGETPOST';
536
	}
537
	if (empty($check)) {
538
		dol_syslog("Deprecated use of GETPOST, called with 1st param = ".$paramname." and 2nd param is '', when calling page ".$_SERVER["PHP_SELF"], LOG_WARNING);
539
		// Enable this line to know who call the GETPOST with '' $check parameter.
540
		//var_dump(debug_backtrace()[0]);
541
	}
542
543
	if (empty($method)) {
544
		$out = isset($_GET[$paramname]) ? $_GET[$paramname] : (isset($_POST[$paramname]) ? $_POST[$paramname] : '');
545
	} elseif ($method == 1) {
546
		$out = isset($_GET[$paramname]) ? $_GET[$paramname] : '';
547
	} elseif ($method == 2) {
548
		$out = isset($_POST[$paramname]) ? $_POST[$paramname] : '';
549
	} elseif ($method == 3) {
550
		$out = isset($_POST[$paramname]) ? $_POST[$paramname] : (isset($_GET[$paramname]) ? $_GET[$paramname] : '');
551
	} else {
552
		return 'BadThirdParameterForGETPOST';
553
	}
554
555
	if (empty($method) || $method == 3 || $method == 4) {
556
		$relativepathstring = $_SERVER["PHP_SELF"];
557
		// Clean $relativepathstring
558
		if (constant('DOL_URL_ROOT')) {
559
			$relativepathstring = preg_replace('/^'.preg_quote(constant('DOL_URL_ROOT'), '/').'/', '', $relativepathstring);
560
		}
561
		$relativepathstring = preg_replace('/^\//', '', $relativepathstring);
562
		$relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
563
		//var_dump($relativepathstring);
564
		//var_dump($user->default_values);
565
566
		// Code for search criteria persistence.
567
		// Retrieve values if restore_lastsearch_values
568
		if (!empty($_GET['restore_lastsearch_values'])) {        // Use $_GET here and not GETPOST
569
			if (!empty($_SESSION['lastsearch_values_'.$relativepathstring])) {	// If there is saved values
570
				$tmp = json_decode($_SESSION['lastsearch_values_'.$relativepathstring], true);
571
				if (is_array($tmp)) {
572
					foreach ($tmp as $key => $val) {
573
						if ($key == $paramname) {	// We are on the requested parameter
574
							$out = $val;
575
							break;
576
						}
577
					}
578
				}
579
			}
580
			// If there is saved contextpage, page or limit
581
			if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_'.$relativepathstring])) {
582
				$out = $_SESSION['lastsearch_contextpage_'.$relativepathstring];
583
			} elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_'.$relativepathstring])) {
584
				$out = $_SESSION['lastsearch_limit_'.$relativepathstring];
585
			} elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_'.$relativepathstring])) {
586
				$out = $_SESSION['lastsearch_page_'.$relativepathstring];
587
			} elseif ($paramname == 'mode' && !empty($_SESSION['lastsearch_mode_'.$relativepathstring])) {
588
				$out = $_SESSION['lastsearch_mode_'.$relativepathstring];
589
			}
590
		} elseif (!isset($_GET['sortfield'])) {
591
			// Else, retrieve default values if we are not doing a sort
592
			// 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
593
			if (!empty($_GET['action']) && $_GET['action'] == 'create' && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
594
				// Search default value from $object->field
595
				global $object;
596
				if (is_object($object) && isset($object->fields[$paramname]['default'])) {
597
					$out = $object->fields[$paramname]['default'];
598
				}
599
			}
600
			if (!empty($conf->global->MAIN_ENABLE_DEFAULT_VALUES)) {
601
				if (!empty($_GET['action']) && (preg_match('/^create/', $_GET['action']) || preg_match('/^presend/', $_GET['action'])) && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
602
					// Now search in setup to overwrite default values
603
					if (!empty($user->default_values)) {		// $user->default_values defined from menu 'Setup - Default values'
604
						if (isset($user->default_values[$relativepathstring]['createform'])) {
605
							foreach ($user->default_values[$relativepathstring]['createform'] as $defkey => $defval) {
606
								$qualified = 0;
607
								if ($defkey != '_noquery_') {
608
									$tmpqueryarraytohave = explode('&', $defkey);
609
									$tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
610
									$foundintru = 0;
611
									foreach ($tmpqueryarraytohave as $tmpquerytohave) {
612
										if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
613
											$foundintru = 1;
614
										}
615
									}
616
									if (!$foundintru) {
617
										$qualified = 1;
618
									}
619
									//var_dump($defkey.'-'.$qualified);
620
								} else {
621
									$qualified = 1;
622
								}
623
624
								if ($qualified) {
625
									if (isset($user->default_values[$relativepathstring]['createform'][$defkey][$paramname])) {
626
										$out = $user->default_values[$relativepathstring]['createform'][$defkey][$paramname];
627
										break;
628
									}
629
								}
630
							}
631
						}
632
					}
633
				} elseif (!empty($paramname) && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
634
					// Management of default search_filters and sort order
635
					if (!empty($user->default_values)) {
636
						// $user->default_values defined from menu 'Setup - Default values'
637
						//var_dump($user->default_values[$relativepathstring]);
638
						if ($paramname == 'sortfield' || $paramname == 'sortorder') {
639
							// Sorted on which fields ? ASC or DESC ?
640
							if (isset($user->default_values[$relativepathstring]['sortorder'])) {
641
								// Even if paramname is sortfield, data are stored into ['sortorder...']
642
								foreach ($user->default_values[$relativepathstring]['sortorder'] as $defkey => $defval) {
643
									$qualified = 0;
644
									if ($defkey != '_noquery_') {
645
										$tmpqueryarraytohave = explode('&', $defkey);
646
										$tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
647
										$foundintru = 0;
648
										foreach ($tmpqueryarraytohave as $tmpquerytohave) {
649
											if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
650
												$foundintru = 1;
651
											}
652
										}
653
										if (!$foundintru) {
654
											$qualified = 1;
655
										}
656
										//var_dump($defkey.'-'.$qualified);
657
									} else {
658
										$qualified = 1;
659
									}
660
661
									if ($qualified) {
662
										$forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
663
										foreach ($user->default_values[$relativepathstring]['sortorder'][$defkey] as $key => $val) {
664
											if ($out) {
665
												$out .= ', ';
666
											}
667
											if ($paramname == 'sortfield') {
668
												$out .= dol_string_nospecial($key, '', $forbidden_chars_to_replace);
669
											}
670
											if ($paramname == 'sortorder') {
671
												$out .= dol_string_nospecial($val, '', $forbidden_chars_to_replace);
672
											}
673
										}
674
										//break;	// No break for sortfield and sortorder so we can cumulate fields (is it realy usefull ?)
675
									}
676
								}
677
							}
678
						} elseif (isset($user->default_values[$relativepathstring]['filters'])) {
679
							foreach ($user->default_values[$relativepathstring]['filters'] as $defkey => $defval) {	// $defkey is a querystring like 'a=b&c=d', $defval is key of user
680
								if (!empty($_GET['disabledefaultvalues'])) {	// If set of default values has been disabled by a request parameter
681
									continue;
682
								}
683
								$qualified = 0;
684
								if ($defkey != '_noquery_') {
685
									$tmpqueryarraytohave = explode('&', $defkey);
686
									$tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
687
									$foundintru = 0;
688
									foreach ($tmpqueryarraytohave as $tmpquerytohave) {
689
										if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
690
											$foundintru = 1;
691
										}
692
									}
693
									if (!$foundintru) {
694
										$qualified = 1;
695
									}
696
									//var_dump($defkey.'-'.$qualified);
697
								} else {
698
									$qualified = 1;
699
								}
700
701
								if ($qualified) {
702
									// We must keep $_POST and $_GET here
703
									if (isset($_POST['sall']) || isset($_POST['search_all']) || isset($_GET['sall']) || isset($_GET['search_all'])) {
704
										// We made a search from quick search menu, do we still use default filter ?
705
										if (empty($conf->global->MAIN_DISABLE_DEFAULT_FILTER_FOR_QUICK_SEARCH)) {
706
											$forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
707
											$out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
708
										}
709
									} else {
710
										$forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
711
										$out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
712
									}
713
									break;
714
								}
715
							}
716
						}
717
					}
718
				}
719
			}
720
		}
721
	}
722
723
	// Substitution variables for GETPOST (used to get final url with variable parameters or final default value with variable parameters)
724
	// Example of variables: __DAY__, __MONTH__, __YEAR__, __MYCOMPANY_COUNTRY_ID__, __USER_ID__, ...
725
	// 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.
726
	if (!is_array($out) && empty($_POST[$paramname]) && empty($noreplace)) {
727
		$reg = array();
728
		$maxloop = 20;
729
		$loopnb = 0; // Protection against infinite loop
730
		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.
731
			$loopnb++;
732
			$newout = '';
733
734
			if ($reg[1] == 'DAY') {
735
				$tmp = dol_getdate(dol_now(), true);
736
				$newout = $tmp['mday'];
737
			} elseif ($reg[1] == 'MONTH') {
738
				$tmp = dol_getdate(dol_now(), true);
739
				$newout = $tmp['mon'];
740
			} elseif ($reg[1] == 'YEAR') {
741
				$tmp = dol_getdate(dol_now(), true);
742
				$newout = $tmp['year'];
743
			} elseif ($reg[1] == 'PREVIOUS_DAY') {
744
				$tmp = dol_getdate(dol_now(), true);
745
				$tmp2 = dol_get_prev_day($tmp['mday'], $tmp['mon'], $tmp['year']);
746
				$newout = $tmp2['day'];
747
			} elseif ($reg[1] == 'PREVIOUS_MONTH') {
748
				$tmp = dol_getdate(dol_now(), true);
749
				$tmp2 = dol_get_prev_month($tmp['mon'], $tmp['year']);
750
				$newout = $tmp2['month'];
751
			} elseif ($reg[1] == 'PREVIOUS_YEAR') {
752
				$tmp = dol_getdate(dol_now(), true);
753
				$newout = ($tmp['year'] - 1);
754
			} elseif ($reg[1] == 'NEXT_DAY') {
755
				$tmp = dol_getdate(dol_now(), true);
756
				$tmp2 = dol_get_next_day($tmp['mday'], $tmp['mon'], $tmp['year']);
757
				$newout = $tmp2['day'];
758
			} elseif ($reg[1] == 'NEXT_MONTH') {
759
				$tmp = dol_getdate(dol_now(), true);
760
				$tmp2 = dol_get_next_month($tmp['mon'], $tmp['year']);
761
				$newout = $tmp2['month'];
762
			} elseif ($reg[1] == 'NEXT_YEAR') {
763
				$tmp = dol_getdate(dol_now(), true);
764
				$newout = ($tmp['year'] + 1);
765
			} elseif ($reg[1] == 'MYCOMPANY_COUNTRY_ID' || $reg[1] == 'MYCOUNTRY_ID' || $reg[1] == 'MYCOUNTRYID') {
766
				$newout = $mysoc->country_id;
767
			} elseif ($reg[1] == 'USER_ID' || $reg[1] == 'USERID') {
768
				$newout = $user->id;
769
			} elseif ($reg[1] == 'USER_SUPERVISOR_ID' || $reg[1] == 'SUPERVISOR_ID' || $reg[1] == 'SUPERVISORID') {
770
				$newout = $user->fk_user;
771
			} elseif ($reg[1] == 'ENTITY_ID' || $reg[1] == 'ENTITYID') {
772
				$newout = $conf->entity;
773
			} else {
774
				$newout = ''; // Key not found, we replace with empty string
775
			}
776
			//var_dump('__'.$reg[1].'__ -> '.$newout);
777
			$out = preg_replace('/__'.preg_quote($reg[1], '/').'__/', $newout, $out);
778
		}
779
	}
780
781
	// Check rule
782
	if (preg_match('/^array/', $check)) {	// If 'array' or 'array:restricthtml' or 'array:aZ09' or 'array:intcomma'
783
		if (!is_array($out) || empty($out)) {
784
			$out = array();
785
		} else {
786
			$tmparray = explode(':', $check);
787
			if (!empty($tmparray[1])) {
788
				$tmpcheck = $tmparray[1];
789
			} else {
790
				$tmpcheck = 'alphanohtml';
791
			}
792
			foreach ($out as $outkey => $outval) {
793
				$out[$outkey] = sanitizeVal($outval, $tmpcheck, $filter, $options);
794
			}
795
		}
796
	} else {
797
		$out = sanitizeVal($out, $check, $filter, $options);
798
	}
799
800
	// Sanitizing for special parameters.
801
	// Note: There is no reason to allow the backtopage, backtolist or backtourl parameter to contains an external URL. Only relative URLs are allowed.
802
	if ($paramname == 'backtopage' || $paramname == 'backtolist' || $paramname == 'backtourl') {
803
		$out = str_replace('\\', '/', $out);								// Can be before the loop because only 1 char is replaced. No risk to get it after other replacements.
804
		$out = str_replace(array(':', ';', '@', "\t", ' '), '', $out);		// Can be before the loop because only 1 char is replaced. No risk to retreive it after other replacements.
805
		do {
806
			$oldstringtoclean = $out;
807
			$out = str_ireplace(array('javascript', 'vbscript', '&colon', '&#'), '', $out);
808
			$out = preg_replace(array('/^[^\?]*%/'), '', $out);				// We remove any % chars before the ?. Example in url: '/product/stock/card.php?action=create&backtopage=%2Fdolibarr_dev%2Fhtdocs%2Fpro%25duct%2Fcard.php%3Fid%3Dabc'
809
			$out = preg_replace(array('/^[a-z]*\/\s*\/+/i'), '', $out);		// We remove schema*// to remove external URL
810
		} while ($oldstringtoclean != $out);
811
	}
812
813
	// Code for search criteria persistence.
814
	// Save data into session if key start with 'search_' or is 'smonth', 'syear', 'month', 'year'
815
	if (empty($method) || $method == 3 || $method == 4) {
816
		if (preg_match('/^search_/', $paramname) || in_array($paramname, array('sortorder', 'sortfield'))) {
817
			//var_dump($paramname.' - '.$out.' '.$user->default_values[$relativepathstring]['filters'][$paramname]);
818
819
			// We save search key only if $out not empty that means:
820
			// - posted value not empty, or
821
			// - 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).
822
823
			if ($out != '' && isset($user)) {// $out = '0' or 'abc', it is a search criteria to keep
824
				$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...
825
			}
826
		}
827
	}
828
829
	return $out;
830
}
831
832
/**
833
 *  Return value of a param into GET or POST supervariable.
834
 *  Use the property $user->default_values[path]['creatform'] and/or $user->default_values[path]['filters'] and/or $user->default_values[path]['sortorder']
835
 *  Note: The property $user->default_values is loaded by main.php when loading the user.
836
 *
837
 *  @param  string  $paramname   Name of parameter to found
838
 *  @param	int		$method	     Type of method (0 = get then post, 1 = only get, 2 = only post, 3 = post then get)
839
 *  @return int                  Value found (int)
840
 */
841
function GETPOSTINT($paramname, $method = 0)
842
{
843
	return (int) GETPOST($paramname, 'int', $method, null, null, 0);
844
}
845
846
847
/**
848
 *  Return a sanitized or empty value after checking value against a rule.
849
 *
850
 *  @deprecated
851
 *  @param  string|array  	$out	     Value to check/clear.
852
 *  @param  string  		$check	     Type of check/sanitizing
853
 *  @param  int     		$filter      Filter to apply when $check is set to 'custom'. (See http://php.net/manual/en/filter.filters.php for détails)
854
 *  @param  mixed   		$options     Options to pass to filter_var when $check is set to 'custom'
855
 *  @return string|array    		     Value sanitized (string or array). It may be '' if format check fails.
856
 */
857
function checkVal($out = '', $check = 'alphanohtml', $filter = null, $options = null)
858
{
859
	return sanitizeVal($out, $check, $filter, $options);
860
}
861
862
/**
863
 *  Return a sanitized or empty value after checking value against a rule.
864
 *
865
 *  @param  string|array  	$out	     Value to check/clear.
866
 *  @param  string  		$check	     Type of check/sanitizing
867
 *  @param  int     		$filter      Filter to apply when $check is set to 'custom'. (See http://php.net/manual/en/filter.filters.php for détails)
868
 *  @param  mixed   		$options     Options to pass to filter_var when $check is set to 'custom'
869
 *  @return string|array    		     Value sanitized (string or array). It may be '' if format check fails.
870
 */
871
function sanitizeVal($out = '', $check = 'alphanohtml', $filter = null, $options = null)
872
{
873
	global $conf;
874
875
	// TODO : use class "Validate" to perform tests (and add missing tests) if needed for factorize
876
	// Check is done after replacement
877
	switch ($check) {
878
		case 'none':
879
			break;
880
		case 'int':    // Check param is a numeric value (integer but also float or hexadecimal)
881
			if (!is_numeric($out)) {
882
				$out = '';
883
			}
884
			break;
885
		case 'intcomma':
886
			if (preg_match('/[^0-9,-]+/i', $out)) {
887
				$out = '';
888
			}
889
			break;
890
		case 'san_alpha':
891
			$out = filter_var($out, FILTER_SANITIZE_STRING);
892
			break;
893
		case 'email':
894
			$out = filter_var($out, FILTER_SANITIZE_EMAIL);
895
			break;
896
		case 'aZ':
897
			if (!is_array($out)) {
898
				$out = trim($out);
899
				if (preg_match('/[^a-z]+/i', $out)) {
900
					$out = '';
901
				}
902
			}
903
			break;
904
		case 'aZ09':
905
			if (!is_array($out)) {
906
				$out = trim($out);
907
				if (preg_match('/[^a-z0-9_\-\.]+/i', $out)) {
908
					$out = '';
909
				}
910
			}
911
			break;
912
		case 'aZ09comma':		// great to sanitize sortfield or sortorder params that can be t.abc,t.def_gh
913
			if (!is_array($out)) {
914
				$out = trim($out);
915
				if (preg_match('/[^a-z0-9_\-\.,]+/i', $out)) {
916
					$out = '';
917
				}
918
			}
919
			break;
920
		case 'nohtml':		// No html
921
			$out = dol_string_nohtmltag($out, 0);
922
			break;
923
		case 'alpha':		// No html and no ../ and "
924
		case 'alphanohtml':	// Recommended for most scalar parameters and search parameters
925
			if (!is_array($out)) {
926
				$out = trim($out);
927
				do {
928
					$oldstringtoclean = $out;
929
					// Remove html tags
930
					$out = dol_string_nohtmltag($out, 0);
931
					// Remove also other dangerous string sequences
932
					// '"' is dangerous because param in url can close the href= or src= and add javascript functions.
933
					// '../' or '..\' is dangerous because it allows dir transversals
934
					// Note &#38, '&#0000038', '&#x26'... is a simple char like '&' alone but there is no reason to accept such way to encode input data.
935
					$out = str_ireplace(array('&#38', '&#0000038', '&#x26', '&quot', '&#34', '&#0000034', '&#x22', '"', '&#47', '&#0000047', '&#92', '&#0000092', '&#x2F', '../', '..\\'), '', $out);
936
				} while ($oldstringtoclean != $out);
937
				// keep lines feed
938
			}
939
			break;
940
		case 'alphawithlgt':		// No " and no ../ but we keep balanced < > tags with no special chars inside. Can be used for email string like "Name <email>". Less secured than 'alphanohtml'
941
			if (!is_array($out)) {
942
				$out = trim($out);
943
				do {
944
					$oldstringtoclean = $out;
945
					// Remove html tags
946
					$out = dol_html_entity_decode($out, ENT_COMPAT | ENT_HTML5, 'UTF-8');
947
					// '"' is dangerous because param in url can close the href= or src= and add javascript functions.
948
					// '../' or '..\' is dangerous because it allows dir transversals
949
					// Note &#38, '&#0000038', '&#x26'... is a simple char like '&' alone but there is no reason to accept such way to encode input data.
950
					$out = str_ireplace(array('&#38', '&#0000038', '&#x26', '&quot', '&#34', '&#0000034', '&#x22', '"', '&#47', '&#0000047', '&#92', '&#0000092', '&#x2F', '../', '..\\'), '', $out);
951
				} while ($oldstringtoclean != $out);
952
			}
953
			break;
954
		case 'restricthtml':		// Recommended for most html textarea
955
		case 'restricthtmlnolink':
956
		case 'restricthtmlallowunvalid':
957
			$out = dol_htmlwithnojs($out, 1, $check);
958
			break;
959
		case 'custom':
960
			if (!empty($out)) {
961
				if (empty($filter)) {
962
					return 'BadParameterForGETPOST - Param 3 of sanitizeVal()';
963
				}
964
				/*if (empty($options)) {
965
					return 'BadParameterForGETPOST - Param 4 of sanitizeVal()';
966
				}*/
967
				$out = filter_var($out, $filter, $options);
968
			}
969
			break;
970
	}
971
972
	return $out;
973
}
974
975
976
if (!function_exists('dol_getprefix')) {
977
	/**
978
	 *  Return a prefix to use for this Dolibarr instance, for session/cookie names or email id.
979
	 *  The prefix is unique for instance and avoid conflict between multi-instances, even when having two instances with same root dir
980
	 *  or two instances in same virtual servers.
981
	 *  This function must not use dol_hash (that is used for password hash) and need to have all context $conf loaded.
982
	 *
983
	 *  @param  string  $mode                   '' (prefix for session name) or 'email' (prefix for email id)
984
	 *  @return	string                          A calculated prefix
985
	 */
986
	function dol_getprefix($mode = '')
987
	{
988
		// If prefix is for email (we need to have $conf already loaded for this case)
989
		if ($mode == 'email') {
990
			global $conf;
991
992
			if (!empty($conf->global->MAIL_PREFIX_FOR_EMAIL_ID)) {	// If MAIL_PREFIX_FOR_EMAIL_ID is set
993
				if ($conf->global->MAIL_PREFIX_FOR_EMAIL_ID != 'SERVER_NAME') {
994
					return $conf->global->MAIL_PREFIX_FOR_EMAIL_ID;
995
				} elseif (isset($_SERVER["SERVER_NAME"])) {	// If MAIL_PREFIX_FOR_EMAIL_ID is set to 'SERVER_NAME'
996
					return $_SERVER["SERVER_NAME"];
997
				}
998
			}
999
1000
			// The recommended value if MAIL_PREFIX_FOR_EMAIL_ID is not defined (may be not defined for old versions)
1001
			if (!empty($conf->file->instance_unique_id)) {
1002
				return sha1('dolibarr'.$conf->file->instance_unique_id);
1003
			}
1004
1005
			// For backward compatibility when instance_unique_id is not set
1006
			return sha1(DOL_DOCUMENT_ROOT.DOL_URL_ROOT);
1007
		}
1008
1009
		// If prefix is for session (no need to have $conf loaded)
1010
		global $dolibarr_main_instance_unique_id, $dolibarr_main_cookie_cryptkey;	// This is loaded by filefunc.inc.php
1011
		$tmp_instance_unique_id = empty($dolibarr_main_instance_unique_id) ? (empty($dolibarr_main_cookie_cryptkey) ? '' : $dolibarr_main_cookie_cryptkey) : $dolibarr_main_instance_unique_id; // Unique id of instance
1012
1013
		// The recommended value (may be not defined for old versions)
1014
		if (!empty($tmp_instance_unique_id)) {
1015
			return sha1('dolibarr'.$tmp_instance_unique_id);
1016
		}
1017
1018
		// For backward compatibility when instance_unique_id is not set
1019
		if (isset($_SERVER["SERVER_NAME"]) && isset($_SERVER["DOCUMENT_ROOT"])) {
1020
			return sha1($_SERVER["SERVER_NAME"].$_SERVER["DOCUMENT_ROOT"].DOL_DOCUMENT_ROOT.DOL_URL_ROOT);
1021
		} else {
1022
			return sha1(DOL_DOCUMENT_ROOT.DOL_URL_ROOT);
1023
		}
1024
	}
1025
}
1026
1027
/**
1028
 *	Make an include_once using default root and alternate root if it fails.
1029
 *  To link to a core file, use include(DOL_DOCUMENT_ROOT.'/pathtofile')
1030
 *  To link to a module file from a module file, use include './mymodulefile';
1031
 *  To link to a module file from a core file, then this function can be used (call by hook / trigger / speciales pages)
1032
 *
1033
 * 	@param	string	$relpath	Relative path to file (Ie: mydir/myfile, ../myfile, ...)
1034
 * 	@param	string	$classname	Class name (deprecated)
1035
 *  @return bool                True if load is a success, False if it fails
1036
 */
1037
function dol_include_once($relpath, $classname = '')
1038
{
1039
	global $conf, $langs, $user, $mysoc; // Do not remove this. They must be defined for files we include. Other globals var must be retrieved with $GLOBALS['var']
1040
1041
	$fullpath = dol_buildpath($relpath);
1042
1043
	if (!file_exists($fullpath)) {
1044
		dol_syslog('functions::dol_include_once Tried to load unexisting file: '.$relpath, LOG_WARNING);
1045
		return false;
1046
	}
1047
1048
	if (!empty($classname) && !class_exists($classname)) {
1049
		return include $fullpath;
1050
	} else {
1051
		return include_once $fullpath;
1052
	}
1053
}
1054
1055
1056
/**
1057
 *	Return path of url or filesystem. Can check into alternate dir or alternate dir + main dir depending on value of $returnemptyifnotfound.
1058
 *
1059
 * 	@param	string	$path						Relative path to file (if mode=0) or relative url (if mode=1). Ie: mydir/myfile, ../myfile
1060
 *  @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)
1061
 *  @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)
1062
 *  											1:If $type==0 and if file was not found into alternate dir, return empty string
1063
 *  											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
1064
 *  @return string								Full filesystem path (if path=0) or '' if file not found, Full url path (if mode=1)
1065
 */
1066
function dol_buildpath($path, $type = 0, $returnemptyifnotfound = 0)
1067
{
1068
	global $conf;
1069
1070
	$path = preg_replace('/^\//', '', $path);
1071
1072
	if (empty($type)) {	// For a filesystem path
1073
		$res = DOL_DOCUMENT_ROOT.'/'.$path; // Standard default path
1074
		if (is_array($conf->file->dol_document_root)) {
1075
			foreach ($conf->file->dol_document_root as $key => $dirroot) {	// ex: array("main"=>"/home/main/htdocs", "alt0"=>"/home/dirmod/htdocs", ...)
1076
				if ($key == 'main') {
1077
					continue;
1078
				}
1079
				if (file_exists($dirroot.'/'.$path)) {
1080
					$res = $dirroot.'/'.$path;
1081
					return $res;
1082
				}
1083
			}
1084
		}
1085
		if ($returnemptyifnotfound) {
1086
			// Not found into alternate dir
1087
			if ($returnemptyifnotfound == 1 || !file_exists($res)) {
1088
				return '';
1089
			}
1090
		}
1091
	} else {
1092
		// For an url path
1093
		// We try to get local path of file on filesystem from url
1094
		// Note that trying to know if a file on disk exist by forging path on disk from url
1095
		// works only for some web server and some setup. This is bugged when
1096
		// using proxy, rewriting, virtual path, etc...
1097
		$res = '';
1098
		if ($type == 1) {
1099
			$res = DOL_URL_ROOT.'/'.$path; // Standard value
1100
		}
1101
		if ($type == 2) {
1102
			$res = DOL_MAIN_URL_ROOT.'/'.$path; // Standard value
1103
		}
1104
		if ($type == 3) {
1105
			$res = DOL_URL_ROOT.'/'.$path;
1106
		}
1107
1108
		foreach ($conf->file->dol_document_root as $key => $dirroot) {	// ex: array(["main"]=>"/home/main/htdocs", ["alt0"]=>"/home/dirmod/htdocs", ...)
1109
			if ($key == 'main') {
1110
				if ($type == 3) {
1111
					global $dolibarr_main_url_root;
1112
1113
					// Define $urlwithroot
1114
					$urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
1115
					$urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file
1116
					//$urlwithroot=DOL_MAIN_URL_ROOT;					// This is to use same domain name than current
1117
1118
					$res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : $urlwithroot).'/'.$path; // Test on start with http is for old conf syntax
1119
				}
1120
				continue;
1121
			}
1122
			preg_match('/^([^\?]+(\.css\.php|\.css|\.js\.php|\.js|\.png|\.jpg|\.php)?)/i', $path, $regs); // Take part before '?'
1123
			if (!empty($regs[1])) {
1124
				//print $key.'-'.$dirroot.'/'.$path.'-'.$conf->file->dol_url_root[$type].'<br>'."\n";
1125
				if (file_exists($dirroot.'/'.$regs[1])) {
1126
					if ($type == 1) {
1127
						$res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_URL_ROOT).$conf->file->dol_url_root[$key].'/'.$path;
1128
					}
1129
					if ($type == 2) {
1130
						$res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_MAIN_URL_ROOT).$conf->file->dol_url_root[$key].'/'.$path;
1131
					}
1132
					if ($type == 3) {
1133
						global $dolibarr_main_url_root;
1134
1135
						// Define $urlwithroot
1136
						$urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
1137
						$urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file
1138
						//$urlwithroot=DOL_MAIN_URL_ROOT;					// This is to use same domain name than current
1139
1140
						$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
1141
					}
1142
					break;
1143
				}
1144
			}
1145
		}
1146
	}
1147
1148
	return $res;
1149
}
1150
1151
/**
1152
 *	Create a clone of instance of object (new instance with same value for each properties)
1153
 *  With native = 0: Property that are reference are different memory area in the new object (full isolation clone). This means $this->db of new object may not be valid.
1154
 *  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.
1155
 *  With native = 2: Property that are reference are different memory area in the new object (full isolation clone). Only scalar and array values are cloned. This means $this->db of new object is not valid.
1156
 *
1157
 * 	@param	object	$object		Object to clone
1158
 *  @param	int		$native		0=Full isolation method, 1=Native PHP method, 2=Full isolation method keeping only scalar and array properties (recommended)
1159
 *	@return object				Clone object
1160
 *  @see https://php.net/manual/language.oop5.cloning.php
1161
 */
1162
function dol_clone($object, $native = 0)
1163
{
1164
	if ($native == 0) {
1165
		// deprecated method, use the method with native = 2 instead
1166
		$tmpsavdb = null;
1167
		if (isset($object->db) && isset($object->db->db) && is_object($object->db->db) && get_class($object->db->db) == 'PgSql\Connection') {
1168
			$tmpsavdb = $object->db;
1169
			unset($object->db);		// Such property can not be serialized with pgsl (when object->db->db = 'PgSql\Connection')
1170
		}
1171
1172
		$myclone = unserialize(serialize($object));	// serialize then unserialize is a hack to be sure to have a new object for all fields
1173
1174
		if (!empty($tmpsavdb)) {
1175
			$object->db = $tmpsavdb;
1176
		}
1177
	} elseif ($native == 2) {
1178
		// recommended method to have a full isolated cloned object
1179
		$myclone = new stdClass();
1180
		$tmparray = get_object_vars($object);	// return only public properties
1181
1182
		if (is_array($tmparray)) {
1183
			foreach ($tmparray as $propertykey => $propertyval) {
1184
				if (is_scalar($propertyval) || is_array($propertyval)) {
1185
					$myclone->$propertykey = $propertyval;
1186
				}
1187
			}
1188
		}
1189
	} else {
1190
		$myclone = clone $object; // PHP clone is a shallow copy only, not a real clone, so properties of references will keep the reference (refering to the same target/variable)
1191
	}
1192
1193
	return $myclone;
1194
}
1195
1196
/**
1197
 *	Optimize a size for some browsers (phone, smarphone, ...)
1198
 *
1199
 * 	@param	int		$size		Size we want
1200
 * 	@param	string	$type		Type of optimizing:
1201
 * 								'' = function used to define a size for truncation
1202
 * 								'width' = function is used to define a width
1203
 *	@return int					New size after optimizing
1204
 */
1205
function dol_size($size, $type = '')
1206
{
1207
	global $conf;
1208
	if (empty($conf->dol_optimize_smallscreen)) {
1209
		return $size;
1210
	}
1211
	if ($type == 'width' && $size > 250) {
1212
		return 250;
1213
	} else {
1214
		return 10;
1215
	}
1216
}
1217
1218
1219
/**
1220
 *	Clean a string to use it as a file name.
1221
 *  Replace also '--' and ' -' strings, they are used for parameters separation (Note: ' - ' is allowed).
1222
 *
1223
 *	@param	string	$str            String to clean
1224
 * 	@param	string	$newstr			String to replace bad chars with.
1225
 *  @param	int	    $unaccent		1=Remove also accent (default), 0 do not remove them
1226
 *	@return string          		String cleaned (a-zA-Z_)
1227
 *
1228
 * 	@see        	dol_string_nospecial(), dol_string_unaccent(), dol_sanitizePathName()
1229
 */
1230
function dol_sanitizeFileName($str, $newstr = '_', $unaccent = 1)
1231
{
1232
	// List of special chars for filenames in windows are defined on page https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
1233
	// Char '>' '<' '|' '$' and ';' are special chars for shells.
1234
	// Char '/' and '\' are file delimiters.
1235
	// Chars '--' can be used into filename to inject special paramaters like --use-compress-program to make command with file as parameter making remote execution of command
1236
	$filesystem_forbidden_chars = array('<', '>', '/', '\\', '?', '*', '|', '"', ':', '°', '$', ';');
1237
	$tmp = dol_string_nospecial($unaccent ? dol_string_unaccent($str) : $str, $newstr, $filesystem_forbidden_chars);
1238
	$tmp = preg_replace('/\-\-+/', '_', $tmp);
1239
	$tmp = preg_replace('/\s+\-([^\s])/', ' _$1', $tmp);
1240
	$tmp = preg_replace('/\s+\-$/', '', $tmp);
1241
	$tmp = str_replace('..', '', $tmp);
1242
	return $tmp;
1243
}
1244
1245
/**
1246
 *	Clean a string to use it as a path name.
1247
 *  Replace also '--' and ' -' strings, they are used for parameters separation (Note: ' - ' is allowed).
1248
 *
1249
 *	@param	string	$str            String to clean
1250
 * 	@param	string	$newstr			String to replace bad chars with
1251
 *  @param	int	    $unaccent		1=Remove also accent (default), 0 do not remove them
1252
 *	@return string          		String cleaned (a-zA-Z_)
1253
 *
1254
 * 	@see        	dol_string_nospecial(), dol_string_unaccent(), dol_sanitizeFileName()
1255
 */
1256
function dol_sanitizePathName($str, $newstr = '_', $unaccent = 1)
1257
{
1258
	// List of special chars for filenames in windows are defined on page https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
1259
	// Char '>' '<' '|' '$' and ';' are special chars for shells.
1260
	// Chars '--' can be used into filename to inject special paramaters like --use-compress-program to make command with file as parameter making remote execution of command
1261
	$filesystem_forbidden_chars = array('<', '>', '?', '*', '|', '"', '°', '$', ';');
1262
	$tmp = dol_string_nospecial($unaccent ? dol_string_unaccent($str) : $str, $newstr, $filesystem_forbidden_chars);
1263
	$tmp = preg_replace('/\-\-+/', '_', $tmp);
1264
	$tmp = preg_replace('/\s+\-([^\s])/', ' _$1', $tmp);
1265
	$tmp = preg_replace('/\s+\-$/', '', $tmp);
1266
	$tmp = str_replace('..', '', $tmp);
1267
	return $tmp;
1268
}
1269
1270
/**
1271
 *  Clean a string to use it as an URL (into a href or src attribute)
1272
 *
1273
 *  @param      string		$stringtoclean		String to clean
1274
 *  @param		int			$type				0=Accept all Url, 1=Clean external Url (keep only relative Url)
1275
 *  @return     string     		 				Escaped string.
1276
 */
1277
function dol_sanitizeUrl($stringtoclean, $type = 1)
1278
{
1279
	// We clean string because some hacks try to obfuscate evil strings by inserting non printable chars. Example: 'java(ascci09)scr(ascii00)ipt' is processed like 'javascript' (whatever is place of evil ascii char)
1280
	// We should use dol_string_nounprintableascii but function may not be yet loaded/available
1281
	$stringtoclean = preg_replace('/[\x00-\x1F\x7F]/u', '', $stringtoclean); // /u operator makes UTF8 valid characters being ignored so are not included into the replace
1282
	// We clean html comments because some hacks try to obfuscate evil strings by inserting HTML comments. Example: on<!-- -->error=alert(1)
1283
	$stringtoclean = preg_replace('/<!--[^>]*-->/', '', $stringtoclean);
1284
1285
	$stringtoclean = str_replace('\\', '/', $stringtoclean);
1286
	if ($type == 1) {
1287
		// removing : should disable links to external url like http:aaa)
1288
		// removing ';' should disable "named" html entities encode into an url (we should not have this into an url)
1289
		$stringtoclean = str_replace(array(':', ';', '@'), '', $stringtoclean);
1290
	}
1291
1292
	do {
1293
		$oldstringtoclean = $stringtoclean;
1294
		// removing '&colon' should disable links to external url like http:aaa)
1295
		// removing '&#' should disable "numeric" html entities encode into an url (we should not have this into an url)
1296
		$stringtoclean = str_ireplace(array('javascript', 'vbscript', '&colon', '&#'), '', $stringtoclean);
1297
	} while ($oldstringtoclean != $stringtoclean);
1298
1299
	if ($type == 1) {
1300
		// removing '//' should disable links to external url like //aaa or http//)
1301
		$stringtoclean = preg_replace(array('/^[a-z]*\/\/+/i'), '', $stringtoclean);
1302
	}
1303
1304
	return $stringtoclean;
1305
}
1306
1307
/**
1308
 *  Clean a string to use it as an Email.
1309
 *
1310
 *  @param      string		$stringtoclean		String to clean. Example '[email protected] <My name>'
1311
 *  @return     string     		 				Escaped string.
1312
 */
1313
function dol_sanitizeEmail($stringtoclean)
1314
{
1315
	do {
1316
		$oldstringtoclean = $stringtoclean;
1317
		$stringtoclean = str_ireplace(array('"', ':', '[', ']',"\n", "\r", '\\', '\/'), '', $stringtoclean);
1318
	} while ($oldstringtoclean != $stringtoclean);
1319
1320
	return $stringtoclean;
1321
}
1322
1323
/**
1324
 *	Clean a string from all accent characters to be used as ref, login or by dol_sanitizeFileName
1325
 *
1326
 *	@param	string	$str			String to clean
1327
 *	@return string   	       		Cleaned string
1328
 *
1329
 * 	@see    		dol_sanitizeFilename(), dol_string_nospecial()
1330
 */
1331
function dol_string_unaccent($str)
1332
{
1333
	global $conf;
1334
1335
	if (is_null($str)) {
0 ignored issues
show
introduced by
The condition is_null($str) is always false.
Loading history...
1336
		return '';
1337
	}
1338
1339
	if (utf8_check($str)) {
1340
		if (extension_loaded('intl') && !empty($conf->global->MAIN_UNACCENT_USE_TRANSLITERATOR)) {
1341
			$transliterator = \Transliterator::createFromRules(':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: NFC;', \Transliterator::FORWARD);
1342
			return $transliterator->transliterate($str);
1343
		}
1344
		// See http://www.utf8-chartable.de/
1345
		$string = rawurlencode($str);
1346
		$replacements = array(
1347
		'%C3%80' => 'A', '%C3%81' => 'A', '%C3%82' => 'A', '%C3%83' => 'A', '%C3%84' => 'A', '%C3%85' => 'A',
1348
		'%C3%87' => 'C',
1349
		'%C3%88' => 'E', '%C3%89' => 'E', '%C3%8A' => 'E', '%C3%8B' => 'E',
1350
		'%C3%8C' => 'I', '%C3%8D' => 'I', '%C3%8E' => 'I', '%C3%8F' => 'I',
1351
		'%C3%91' => 'N',
1352
		'%C3%92' => 'O', '%C3%93' => 'O', '%C3%94' => 'O', '%C3%95' => 'O', '%C3%96' => 'O',
1353
		'%C5%A0' => 'S',
1354
		'%C3%99' => 'U', '%C3%9A' => 'U', '%C3%9B' => 'U', '%C3%9C' => 'U',
1355
		'%C3%9D' => 'Y', '%C5%B8' => 'y',
1356
		'%C3%A0' => 'a', '%C3%A1' => 'a', '%C3%A2' => 'a', '%C3%A3' => 'a', '%C3%A4' => 'a', '%C3%A5' => 'a',
1357
		'%C3%A7' => 'c',
1358
		'%C3%A8' => 'e', '%C3%A9' => 'e', '%C3%AA' => 'e', '%C3%AB' => 'e',
1359
		'%C3%AC' => 'i', '%C3%AD' => 'i', '%C3%AE' => 'i', '%C3%AF' => 'i',
1360
		'%C3%B1' => 'n',
1361
		'%C3%B2' => 'o', '%C3%B3' => 'o', '%C3%B4' => 'o', '%C3%B5' => 'o', '%C3%B6' => 'o',
1362
		'%C5%A1' => 's',
1363
		'%C3%B9' => 'u', '%C3%BA' => 'u', '%C3%BB' => 'u', '%C3%BC' => 'u',
1364
		'%C3%BD' => 'y', '%C3%BF' => 'y'
1365
		);
1366
		$string = strtr($string, $replacements);
1367
		return rawurldecode($string);
1368
	} else {
1369
		// See http://www.ascii-code.com/
1370
		$string = strtr(
1371
			$str,
1372
			"\xC0\xC1\xC2\xC3\xC4\xC5\xC7
1373
			\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1
1374
			\xD2\xD3\xD4\xD5\xD8\xD9\xDA\xDB\xDD
1375
			\xE0\xE1\xE2\xE3\xE4\xE5\xE7\xE8\xE9\xEA\xEB
1376
			\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF8
1377
			\xF9\xFA\xFB\xFC\xFD\xFF",
1378
			"AAAAAAC
1379
			EEEEIIIIDN
1380
			OOOOOUUUY
1381
			aaaaaaceeee
1382
			iiiidnooooo
1383
			uuuuyy"
1384
		);
1385
		$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"));
1386
		return $string;
1387
	}
1388
}
1389
1390
/**
1391
 *	Clean a string from all punctuation characters to use it as a ref or login.
1392
 *  This is a more complete function than dol_sanitizeFileName().
1393
 *
1394
 *	@param	string			$str            	String to clean
1395
 * 	@param	string			$newstr				String to replace forbidden chars with
1396
 *  @param  array|string	$badcharstoreplace  Array of forbidden characters to replace. Use '' to keep default list.
1397
 *  @param  array|string	$badcharstoremove   Array of forbidden characters to remove. Use '' to keep default list.
1398
 * 	@return string          					Cleaned string
1399
 *
1400
 * 	@see    		dol_sanitizeFilename(), dol_string_unaccent(), dol_string_nounprintableascii()
1401
 */
1402
function dol_string_nospecial($str, $newstr = '_', $badcharstoreplace = '', $badcharstoremove = '')
1403
{
1404
	$forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ",", ";", "=", '°', '$', ';'); // more complete than dol_sanitizeFileName
1405
	$forbidden_chars_to_remove = array();
1406
	//$forbidden_chars_to_remove=array("(",")");
1407
1408
	if (is_array($badcharstoreplace)) {
1409
		$forbidden_chars_to_replace = $badcharstoreplace;
1410
	}
1411
	if (is_array($badcharstoremove)) {
1412
		$forbidden_chars_to_remove = $badcharstoremove;
1413
	}
1414
1415
	return str_replace($forbidden_chars_to_replace, $newstr, str_replace($forbidden_chars_to_remove, "", $str));
1416
}
1417
1418
1419
/**
1420
 *	Clean a string from all non printable ASCII chars (0x00-0x1F and 0x7F). It can also removes also Tab-CR-LF. UTF8 chars remains.
1421
 *  This can be used to sanitize a string and view its real content. Some hacks try to obfuscate attacks by inserting non printable chars.
1422
 *  Note, for information: UTF8 on 1 byte are: \x00-\7F
1423
 *                                 2 bytes are: byte 1 \xc0-\xdf, byte 2 = \x80-\xbf
1424
 *                                 3 bytes are: byte 1 \xe0-\xef, byte 2 = \x80-\xbf, byte 3 = \x80-\xbf
1425
 *                                 4 bytes are: byte 1 \xf0-\xf7, byte 2 = \x80-\xbf, byte 3 = \x80-\xbf, byte 4 = \x80-\xbf
1426
 *	@param	string	$str            	String to clean
1427
 *  @param	int		$removetabcrlf		Remove also CR-LF
1428
 * 	@return string          			Cleaned string
1429
 *
1430
 * 	@see    		dol_sanitizeFilename(), dol_string_unaccent(), dol_string_nospecial()
1431
 */
1432
function dol_string_nounprintableascii($str, $removetabcrlf = 1)
1433
{
1434
	if ($removetabcrlf) {
1435
		return preg_replace('/[\x00-\x1F\x7F]/u', '', $str); // /u operator makes UTF8 valid characters being ignored so are not included into the replace
1436
	} else {
1437
		return preg_replace('/[\x00-\x08\x11-\x12\x14-\x1F\x7F]/u', '', $str); // /u operator should make UTF8 valid characters being ignored so are not included into the replace
1438
	}
1439
}
1440
1441
/**
1442
 *  Returns text escaped for inclusion into javascript code
1443
 *
1444
 *  @param      string	$stringtoescape			String to escape
1445
 *  @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 \
1446
 *  @param		int		$noescapebackslashn		0=Escape also \n. 1=Do not escape \n.
1447
 *  @return     string   		 				Escaped string. Both ' and " are escaped into ' if they are escaped.
1448
 */
1449
function dol_escape_js($stringtoescape, $mode = 0, $noescapebackslashn = 0)
1450
{
1451
	if (is_null($stringtoescape)) {
0 ignored issues
show
introduced by
The condition is_null($stringtoescape) is always false.
Loading history...
1452
		return '';
1453
	}
1454
1455
	// escape quotes and backslashes, newlines, etc.
1456
	$substitjs = array("&#039;"=>"\\'", "\r"=>'\\r');
1457
	//$substitjs['</']='<\/';	// We removed this. Should be useless.
1458
	if (empty($noescapebackslashn)) {
1459
		$substitjs["\n"] = '\\n';
1460
		$substitjs['\\'] = '\\\\';
1461
	}
1462
	if (empty($mode)) {
1463
		$substitjs["'"] = "\\'";
1464
		$substitjs['"'] = "\\'";
1465
	} elseif ($mode == 1) {
1466
		$substitjs["'"] = "\\'";
1467
	} elseif ($mode == 2) {
1468
		$substitjs['"'] = '\\"';
1469
	} elseif ($mode == 3) {
1470
		$substitjs["'"] = "\\'";
1471
		$substitjs['"'] = "\\\"";
1472
	}
1473
	return strtr($stringtoescape, $substitjs);
1474
}
1475
1476
/**
1477
 *  Returns text escaped for inclusion into javascript code
1478
 *
1479
 *  @param      string		$stringtoescape		String to escape
1480
 *  @return     string     		 				Escaped string for json content.
1481
 */
1482
function dol_escape_json($stringtoescape)
1483
{
1484
	return str_replace('"', '\"', $stringtoescape);
1485
}
1486
1487
/**
1488
 *  Returns text escaped for inclusion in HTML alt or title tags, or into values of HTML input fields.
1489
 *
1490
 *  @param      string		$stringtoescape			String to escape
1491
 *  @param		int			$keepb					1=Keep b tags, 0=remove them completely
1492
 *  @param      int         $keepn              	1=Preserve \r\n strings (otherwise, replace them with escaped value). Set to 1 when escaping for a <textarea>.
1493
 *  @param		string		$noescapetags			'' or 'common' or list of tags to not escape. TODO Does not works yet when there is attributes into tag.
1494
 *  @param		int			$escapeonlyhtmltags		1=Escape only html tags, not the special chars like accents.
1495
 *  @return     string     				 			Escaped string
1496
 *  @see		dol_string_nohtmltag(), dol_string_nospecial(), dol_string_unaccent()
1497
 */
1498
function dol_escape_htmltag($stringtoescape, $keepb = 0, $keepn = 0, $noescapetags = '', $escapeonlyhtmltags = 0)
1499
{
1500
	if ($noescapetags == 'common') {
1501
		$noescapetags = 'html,body,a,b,em,hr,i,u,ul,li,br,div,img,font,p,span,strong,table,tr,td,th,tbody';
1502
	}
1503
1504
	// escape quotes and backslashes, newlines, etc.
1505
	if ($escapeonlyhtmltags) {
1506
		$tmp = htmlspecialchars_decode((string) $stringtoescape, ENT_COMPAT);
1507
	} else {
1508
		$tmp = html_entity_decode((string) $stringtoescape, ENT_COMPAT, 'UTF-8');
1509
	}
1510
	if (!$keepb) {
1511
		$tmp = strtr($tmp, array("<b>"=>'', '</b>'=>''));
1512
	}
1513
	if (!$keepn) {
1514
		$tmp = strtr($tmp, array("\r"=>'\\r', "\n"=>'\\n'));
1515
	}
1516
1517
	if ($escapeonlyhtmltags) {
1518
		return htmlspecialchars($tmp, ENT_COMPAT, 'UTF-8');
1519
	} else {
1520
		// Escape tags to keep
1521
		// TODO Does not works yet when there is attributes into tag
1522
		$tmparrayoftags = array();
1523
		if ($noescapetags) {
1524
			$tmparrayoftags = explode(',', $noescapetags);
1525
		}
1526
		if (count($tmparrayoftags)) {
1527
			foreach ($tmparrayoftags as $tagtoreplace) {
1528
				$tmp = str_ireplace('<'.$tagtoreplace.'>', '__BEGINTAGTOREPLACE'.$tagtoreplace.'__', $tmp);
1529
				$tmp = str_ireplace('</'.$tagtoreplace.'>', '__ENDTAGTOREPLACE'.$tagtoreplace.'__', $tmp);
1530
				$tmp = str_ireplace('<'.$tagtoreplace.' />', '__BEGINENDTAGTOREPLACE'.$tagtoreplace.'__', $tmp);
1531
			}
1532
		}
1533
1534
		$result = htmlentities($tmp, ENT_COMPAT, 'UTF-8');
1535
1536
		if (count($tmparrayoftags)) {
1537
			foreach ($tmparrayoftags as $tagtoreplace) {
1538
				$result = str_ireplace('__BEGINTAGTOREPLACE'.$tagtoreplace.'__', '<'.$tagtoreplace.'>', $result);
1539
				$result = str_ireplace('__ENDTAGTOREPLACE'.$tagtoreplace.'__', '</'.$tagtoreplace.'>', $result);
1540
				$result = str_ireplace('__BEGINENDTAGTOREPLACE'.$tagtoreplace.'__', '<'.$tagtoreplace.' />', $result);
1541
			}
1542
		}
1543
1544
		return $result;
1545
	}
1546
}
1547
1548
/**
1549
 * Convert a string to lower. Never use strtolower because it does not works with UTF8 strings.
1550
 *
1551
 * @param 	string		$string		        String to encode
1552
 * @param   string      $encoding           Character set encoding
1553
 * @return 	string							String converted
1554
 */
1555
function dol_strtolower($string, $encoding = "UTF-8")
1556
{
1557
	if (function_exists('mb_strtolower')) {
1558
		return mb_strtolower($string, $encoding);
1559
	} else {
1560
		return strtolower($string);
1561
	}
1562
}
1563
1564
/**
1565
 * Convert a string to upper. Never use strtolower because it does not works with UTF8 strings.
1566
 *
1567
 * @param 	string		$string		        String to encode
1568
 * @param   string      $encoding           Character set encoding
1569
 * @return 	string							String converted
1570
 */
1571
function dol_strtoupper($string, $encoding = "UTF-8")
1572
{
1573
	if (function_exists('mb_strtoupper')) {
1574
		return mb_strtoupper($string, $encoding);
1575
	} else {
1576
		return strtoupper($string);
1577
	}
1578
}
1579
1580
/**
1581
 * Convert first character of the first word of a string to upper. Never use ucfirst because it does not works with UTF8 strings.
1582
 *
1583
 * @param   string      $string         String to encode
1584
 * @param   string      $encoding       Character set encodign
1585
 * @return  string                      String converted
1586
 */
1587
function dol_ucfirst($string, $encoding = "UTF-8")
1588
{
1589
	if (function_exists('mb_substr')) {
1590
		return mb_strtoupper(mb_substr($string, 0, 1, $encoding), $encoding).mb_substr($string, 1, null, $encoding);
1591
	} else {
1592
		return ucfirst($string);
1593
	}
1594
}
1595
1596
/**
1597
 * Convert first character of all the words of a string to upper. Never use ucfirst because it does not works with UTF8 strings.
1598
 *
1599
 * @param   string      $string         String to encode
1600
 * @param   string      $encoding       Character set encodign
1601
 * @return  string                      String converted
1602
 */
1603
function dol_ucwords($string, $encoding = "UTF-8")
1604
{
1605
	if (function_exists('mb_convert_case')) {
1606
		return mb_convert_case($string, MB_CASE_TITLE, $encoding);
1607
	} else {
1608
		return ucwords($string);
1609
	}
1610
}
1611
1612
/**
1613
 *	Write log message into outputs. Possible outputs can be:
1614
 *	SYSLOG_HANDLERS = ["mod_syslog_file"]  		file name is then defined by SYSLOG_FILE
1615
 *	SYSLOG_HANDLERS = ["mod_syslog_syslog"]  	facility is then defined by SYSLOG_FACILITY
1616
 *  Warning, syslog functions are bugged on Windows, generating memory protection faults. To solve
1617
 *  this, use logging to files instead of syslog (see setup of module).
1618
 *  Note: If constant 'SYSLOG_FILE_NO_ERROR' defined, we never output any error message when writing to log fails.
1619
 *  Note: You can get log message into html sources by adding parameter &logtohtml=1 (constant MAIN_LOGTOHTML must be set)
1620
 *  This function works only if syslog module is enabled.
1621
 * 	This must not use any call to other function calling dol_syslog (avoid infinite loop).
1622
 *
1623
 * 	@param  string		$message				Line to log. ''=Show nothing
1624
 *  @param  int			$level					Log level
1625
 *												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
1626
 *												On Linux   LOG_ERR=3, LOG_WARNING=4, LOG_NOTICE=5, LOG_INFO=6, LOG_DEBUG=7
1627
 *  @param	int			$ident					1=Increase ident of 1 (after log), -1=Decrease ident of 1 (before log)
1628
 *  @param	string		$suffixinfilename		When output is a file, append this suffix into default log filename.
1629
 *  @param	string		$restricttologhandler	Force output of log only to this log handler
1630
 *  @param	array|null	$logcontext				If defined, an array with extra informations (can be used by some log handlers)
1631
 *  @return	void
1632
 */
1633
function dol_syslog($message, $level = LOG_INFO, $ident = 0, $suffixinfilename = '', $restricttologhandler = '', $logcontext = null)
1634
{
1635
	global $conf, $user, $debugbar;
1636
1637
	// If syslog module enabled
1638
	if (empty($conf->syslog->enabled)) {
1639
		return;
1640
	}
1641
1642
	// Check if we are into execution of code of a website
1643
	if (defined('USEEXTERNALSERVER') && !defined('USEDOLIBARRSERVER') && !defined('USEDOLIBARREDITOR')) {
1644
		global $website, $websitekey;
1645
		if (is_object($website) && !empty($website->ref)) {
1646
			$suffixinfilename .= '_website_'.$website->ref;
1647
		} elseif (!empty($websitekey)) {
1648
			$suffixinfilename .= '_website_'.$websitekey;
1649
		}
1650
	}
1651
1652
	// Check if we have a forced suffix
1653
	if (defined('USESUFFIXINLOG')) {
1654
		$suffixinfilename .= constant('USESUFFIXINLOG');
1655
	}
1656
1657
	if ($ident < 0) {
1658
		foreach ($conf->loghandlers as $loghandlerinstance) {
1659
			$loghandlerinstance->setIdent($ident);
1660
		}
1661
	}
1662
1663
	if (!empty($message)) {
1664
		// Test log level
1665
		$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');
1666
		if (!array_key_exists($level, $logLevels)) {
1667
			throw new Exception('Incorrect log level');
1668
		}
1669
		if ($level > $conf->global->SYSLOG_LEVEL) {
1670
			return;
1671
		}
1672
1673
		if (empty($conf->global->MAIN_SHOW_PASSWORD_INTO_LOG)) {
1674
			$message = preg_replace('/password=\'[^\']*\'/', 'password=\'hidden\'', $message); // protection to avoid to have value of password in log
1675
		}
1676
1677
		// If adding log inside HTML page is required
1678
		if ((!empty($_REQUEST['logtohtml']) && !empty($conf->global->MAIN_ENABLE_LOG_TO_HTML))
1679
			|| (!empty($user->rights->debugbar->read) && is_object($debugbar))) {
1680
			$conf->logbuffer[] = dol_print_date(time(), "%Y-%m-%d %H:%M:%S")." ".$logLevels[$level]." ".$message;
1681
		}
1682
1683
		//TODO: Remove this. MAIN_ENABLE_LOG_INLINE_HTML should be deprecated and use a log handler dedicated to HTML output
1684
		// If html log tag enabled and url parameter log defined, we show output log on HTML comments
1685
		if (!empty($conf->global->MAIN_ENABLE_LOG_INLINE_HTML) && !empty($_GET["log"])) {
1686
			print "\n\n<!-- Log start\n";
1687
			print dol_escape_htmltag($message)."\n";
1688
			print "Log end -->\n";
1689
		}
1690
1691
		$data = array(
1692
			'message' => $message,
1693
			'script' => (isset($_SERVER['PHP_SELF']) ? basename($_SERVER['PHP_SELF'], '.php') : false),
1694
			'level' => $level,
1695
			'user' => ((is_object($user) && $user->id) ? $user->login : false),
1696
			'ip' => false
1697
		);
1698
1699
		$remoteip = getUserRemoteIP(); // Get ip when page run on a web server
1700
		if (!empty($remoteip)) {
1701
			$data['ip'] = $remoteip;
1702
			// This is when server run behind a reverse proxy
1703
			if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR'] != $remoteip) {
1704
				$data['ip'] = $_SERVER['HTTP_X_FORWARDED_FOR'].' -> '.$data['ip'];
1705
			} elseif (!empty($_SERVER['HTTP_CLIENT_IP']) && $_SERVER['HTTP_CLIENT_IP'] != $remoteip) {
1706
				$data['ip'] = $_SERVER['HTTP_CLIENT_IP'].' -> '.$data['ip'];
1707
			}
1708
		} elseif (!empty($_SERVER['SERVER_ADDR'])) {
1709
			// This is when PHP session is ran inside a web server but not inside a client request (example: init code of apache)
1710
			$data['ip'] = $_SERVER['SERVER_ADDR'];
1711
		} elseif (!empty($_SERVER['COMPUTERNAME'])) {
1712
			// 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).
1713
			$data['ip'] = $_SERVER['COMPUTERNAME'].(empty($_SERVER['USERNAME']) ? '' : '@'.$_SERVER['USERNAME']);
1714
		} elseif (!empty($_SERVER['LOGNAME'])) {
1715
			// 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).
1716
			$data['ip'] = '???@'.$_SERVER['LOGNAME'];
1717
		}
1718
1719
		// Loop on each log handler and send output
1720
		foreach ($conf->loghandlers as $loghandlerinstance) {
1721
			if ($restricttologhandler && $loghandlerinstance->code != $restricttologhandler) {
1722
				continue;
1723
			}
1724
			$loghandlerinstance->export($data, $suffixinfilename);
1725
		}
1726
		unset($data);
1727
	}
1728
1729
	if ($ident > 0) {
1730
		foreach ($conf->loghandlers as $loghandlerinstance) {
1731
			$loghandlerinstance->setIdent($ident);
1732
		}
1733
	}
1734
}
1735
1736
/**
1737
 *	Return HTML code to output a button to open a dialog popup box.
1738
 *  Such buttons must be included inside a HTML form.
1739
 *
1740
 *	@param	string	$name				A name for the html component
1741
 *	@param	string	$label 	    		Label shown in Popup title top bar
1742
 *	@param  string	$buttonstring  		button string (HTML text we can click on)
1743
 *	@param  string	$url				Relative Url to open. For example '/project/card.php'
1744
 *  @param	string	$disabled			Disabled text
1745
 *  @param	string	$morecss			More CSS
1746
 *  @param	string	$jsonopen			Some JS code to execute on click/open of popup
1747
 *  @param	string	$backtopagejsfields	The back to page must be managed using javascript instead of a redirect.
1748
 *  									Value is 'keyforpopupid:Name_of_html_component_to_set_with id,Name_of_html_component_to_set_with_label'
1749
 *  @param	string	$accesskey			A key to use shortcut
1750
 * 	@return	string						HTML component with button
1751
 */
1752
function dolButtonToOpenUrlInDialogPopup($name, $label, $buttonstring, $url, $disabled = '', $morecss = 'classlink button bordertransp', $jsonopen = '', $backtopagejsfields = '', $accesskey = '')
1753
{
1754
	global $conf;
1755
1756
	if (strpos($url, '?') > 0) {
1757
		$url .= '&dol_hide_topmenu=1&dol_hide_leftmenu=1&dol_openinpopup='.urlencode($name);
1758
	} else {
1759
		$url .= '?dol_hide_topmenu=1&dol_hide_leftmenu=1&dol_openinpopup='.urlencode($name);
1760
	}
1761
1762
	$out = '';
1763
1764
	$backtopagejsfieldsid = ''; $backtopagejsfieldslabel = '';
1765
	if ($backtopagejsfields) {
1766
		$tmpbacktopagejsfields = explode(':', $backtopagejsfields);
1767
		if (empty($tmpbacktopagejsfields[1])) {	// If the part 'keyforpopupid:' is missing, we add $name for it.
1768
			$backtopagejsfields = $name.":".$backtopagejsfields;
1769
			$tmp2backtopagejsfields = explode(',', $tmpbacktopagejsfields[0]);
1770
		} else {
1771
			$tmp2backtopagejsfields = explode(',', $tmpbacktopagejsfields[1]);
1772
		}
1773
		$backtopagejsfieldsid = empty($tmp2backtopagejsfields[0]) ? '' : $tmp2backtopagejsfields[0];
1774
		$backtopagejsfieldslabel = empty($tmp2backtopagejsfields[1]) ? '' : $tmp2backtopagejsfields[1];
1775
		$url .= '&backtopagejsfields='.urlencode($backtopagejsfields);
1776
	}
1777
1778
	//print '<input type="submit" class="button bordertransp"'.$disabled.' value="'.dol_escape_htmltag($langs->trans("MediaFiles")).'" name="file_manager">';
1779
	$out .= '<!-- a link for button to open url into a dialog popup with backtopagejsfields = '.$backtopagejsfields.' -->';
1780
	$out .= '<a '.($accesskey ? ' accesskey="'.$accesskey.'"' : '').' class="cursorpointer button_'.$name.($morecss ? ' '.$morecss : '').'"'.$disabled.' title="'.dol_escape_htmltag($label).'"';
1781
	if (empty($conf->use_javascript_ajax)) {
1782
		$out .= ' href="'.DOL_URL_ROOT.$url.'" target="_blank"';
1783
	} elseif ($jsonopen) {
1784
		$out .= ' onclick="javascript:'.$jsonopen.'"';
1785
	}
1786
	$out .= '>'.$buttonstring.'</a>';
1787
1788
	if (!empty($conf->use_javascript_ajax)) {
1789
		// Add code to open url using the popup. Add also hidden field to retreive the returned variables
1790
		$out .= '<!-- code to open popup and variables to retreive returned variables -->';
1791
		$out .= '<div id="idfordialog'.$name.'" class="hidden">div for dialog</div>';
1792
		$out .= '<div id="varforreturndialogid'.$name.'" class="hidden">div for returned id</div>';
1793
		$out .= '<div id="varforreturndialoglabel'.$name.'" class="hidden">div for returned label</div>';
1794
		$out .= '<!-- Add js code to open dialog popup on dialog -->';
1795
		$out .= '<script type="text/javascript">
1796
					jQuery(document).ready(function () {
1797
						jQuery(".button_'.$name.'").click(function () {
1798
							console.log(\'Open popup with jQuery(...).dialog() on URL '.dol_escape_js(DOL_URL_ROOT.$url).'\');
1799
							var $tmpdialog = $(\'#idfordialog'.$name.'\');
1800
							$tmpdialog.html(\'<iframe class="iframedialog" id="iframedialog'.$name.'" style="border: 0px;" src="'.DOL_URL_ROOT.$url.'" width="100%" height="98%"></iframe>\');
1801
							$tmpdialog.dialog({
1802
								autoOpen: false,
1803
							 	modal: true,
1804
							 	height: (window.innerHeight - 150),
1805
							 	width: \'80%\',
1806
							 	title: \''.dol_escape_js($label).'\',
1807
								open: function (event, ui) {
1808
									console.log("open popup name='.$name.', backtopagejsfields='.$backtopagejsfields.'");
1809
	       						},
1810
								close: function (event, ui) {
1811
									returnedid = jQuery("#varforreturndialogid'.$name.'").text();
1812
									returnedlabel = jQuery("#varforreturndialoglabel'.$name.'").text();
1813
									console.log("popup has been closed. returnedid (js var defined into parent page)="+returnedid+" returnedlabel="+returnedlabel);
1814
									if (returnedid != "" && returnedid != "div for returned id") {
1815
										jQuery("#'.(empty($backtopagejsfieldsid)?"none":$backtopagejsfieldsid).'").val(returnedid);
1816
									}
1817
									if (returnedlabel != "" && returnedlabel != "div for returned label") {
1818
										jQuery("#'.(empty($backtopagejsfieldslabel)?"none":$backtopagejsfieldslabel).'").val(returnedlabel);
1819
									}
1820
								}
1821
							});
1822
1823
							$tmpdialog.dialog(\'open\');
1824
						});
1825
					});
1826
				</script>';
1827
	}
1828
	return $out;
1829
}
1830
1831
/**
1832
 *	Show tab header of a card
1833
 *
1834
 *	@param	array	$links				Array of tabs (0=>url, 1=>label, 2=>code, 3=>not used, 4=>text after link, 5=>morecssonlink). Currently initialized by calling a function xxx_admin_prepare_head. Note that label into $links[$i][1] must be already HTML escaped.
1835
 *	@param	string	$active     		Active tab name (document', 'info', 'ldap', ....)
1836
 *	@param  string	$title      		Title
1837
 *	@param  int		$notab				-1 or 0=Add tab header, 1=no tab header (if you set this to 1, using print dol_get_fiche_end() to close tab is not required), -2=Add tab header with no sepaaration under tab (to start a tab just after), -3=Add tab header but no footer separation
1838
 * 	@param	string	$picto				Add a picto on tab title
1839
 *	@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.
1840
 *  @param	string	$morehtmlright		Add more html content on right of tabs title
1841
 *  @param	string	$morecss			More Css
1842
 *  @param	int		$limittoshow		Limit number of tabs to show. Use 0 to use automatic default value.
1843
 *  @param	string	$moretabssuffix		A suffix to use when you have several dol_get_fiche_head() in same page
1844
 * 	@return	void
1845
 *  @deprecated Use print dol_get_fiche_head() instead
1846
 */
1847
function dol_fiche_head($links = array(), $active = '0', $title = '', $notab = 0, $picto = '', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limittoshow = 0, $moretabssuffix = '')
1848
{
1849
	print dol_get_fiche_head($links, $active, $title, $notab, $picto, $pictoisfullpath, $morehtmlright, $morecss, $limittoshow, $moretabssuffix);
1850
}
1851
1852
/**
1853
 *  Show tabs of a record
1854
 *
1855
 *	@param	array	$links				Array of tabs (0=>url, 1=>label, 2=>code, 3=>not used, 4=>text after link, 5=>morecssonlink). Currently initialized by calling a function xxx_admin_prepare_head. Note that label into $links[$i][1] must be already HTML escaped.
1856
 *	@param	string	$active     		Active tab name
1857
 *	@param  string	$title      		Title
1858
 *	@param  int		$notab				-1 or 0=Add tab header, 1=no tab header (if you set this to 1, using print dol_get_fiche_end() to close tab is not required), -2=Add tab header with no seaparation under tab (to start a tab just after), -3=-2+'noborderbottom'
1859
 * 	@param	string	$picto				Add a picto on tab title
1860
 *	@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.
1861
 *  @param	string	$morehtmlright		Add more html content on right of tabs title
1862
 *  @param	string	$morecss			More CSS on the link <a>
1863
 *  @param	int		$limittoshow		Limit number of tabs to show. Use 0 to use automatic default value.
1864
 *  @param	string	$moretabssuffix		A suffix to use when you have several dol_get_fiche_head() in same page
1865
 * 	@return	string
1866
 */
1867
function dol_get_fiche_head($links = array(), $active = '', $title = '', $notab = 0, $picto = '', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limittoshow = 0, $moretabssuffix = '')
1868
{
1869
	global $conf, $langs, $hookmanager;
1870
1871
	// Show title
1872
	$showtitle = 1;
1873
	if (!empty($conf->dol_optimize_smallscreen)) {
1874
		$showtitle = 0;
1875
	}
1876
1877
	$out = "\n".'<!-- dol_fiche_head - dol_get_fiche_head -->';
1878
1879
	if ((!empty($title) && $showtitle) || $morehtmlright || !empty($links)) {
1880
		$out .= '<div class="tabs'.($picto ? '' : ' nopaddingleft').'" data-role="controlgroup" data-type="horizontal">'."\n";
1881
	}
1882
1883
	// Show right part
1884
	if ($morehtmlright) {
1885
		$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.
1886
	}
1887
1888
	// Show title
1889
	if (!empty($title) && $showtitle && empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
1890
		$limittitle = 30;
1891
		$out .= '<a class="tabTitle">';
1892
		if ($picto) {
1893
			$noprefix = $pictoisfullpath;
1894
			if (strpos($picto, 'fontawesome_') !== false) {
1895
				$noprefix = 1;
1896
			}
1897
			$out .= img_picto($title, ($noprefix ? '' : 'object_').$picto, '', $pictoisfullpath, 0, 0, '', 'imgTabTitle').' ';
1898
		}
1899
		$out .= '<span class="tabTitleText">'.dol_escape_htmltag(dol_trunc($title, $limittitle)).'</span>';
1900
		$out .= '</a>';
1901
	}
1902
1903
	// Show tabs
1904
1905
	// Define max of key (max may be higher than sizeof because of hole due to module disabling some tabs).
1906
	$maxkey = -1;
1907
	if (is_array($links) && !empty($links)) {
1908
		$keys = array_keys($links);
1909
		if (count($keys)) {
1910
			$maxkey = max($keys);
1911
		}
1912
	}
1913
1914
	// Show tabs
1915
	// if =0 we don't use the feature
1916
	if (empty($limittoshow)) {
1917
		$limittoshow = (empty($conf->global->MAIN_MAXTABS_IN_CARD) ? 99 : $conf->global->MAIN_MAXTABS_IN_CARD);
1918
	}
1919
	if (!empty($conf->dol_optimize_smallscreen)) {
1920
		$limittoshow = 2;
1921
	}
1922
1923
	$displaytab = 0;
1924
	$nbintab = 0;
1925
	$popuptab = 0;
1926
	$outmore = '';
1927
	for ($i = 0; $i <= $maxkey; $i++) {
1928
		if ((is_numeric($active) && $i == $active) || (!empty($links[$i][2]) && !is_numeric($active) && $active == $links[$i][2])) {
1929
			// If active tab is already present
1930
			if ($i >= $limittoshow) {
1931
				$limittoshow--;
1932
			}
1933
		}
1934
	}
1935
1936
	for ($i = 0; $i <= $maxkey; $i++) {
1937
		if ((is_numeric($active) && $i == $active) || (!empty($links[$i][2]) && !is_numeric($active) && $active == $links[$i][2])) {
1938
			$isactive = true;
1939
		} else {
1940
			$isactive = false;
1941
		}
1942
1943
		if ($i < $limittoshow || $isactive) {
1944
			// Output entry with a visible tab
1945
			$out .= '<div class="inline-block tabsElem'.($isactive ? ' tabsElemActive' : '').((!$isactive && !empty($conf->global->MAIN_HIDE_INACTIVETAB_ON_PRINT)) ? ' hideonprint' : '').'"><!-- id tab = '.(empty($links[$i][2]) ? '' : dol_escape_htmltag($links[$i][2])).' -->';
1946
1947
			if (isset($links[$i][2]) && $links[$i][2] == 'image') {
1948
				if (!empty($links[$i][0])) {
1949
					$out .= '<a class="tabimage'.($morecss ? ' '.$morecss : '').'" href="'.$links[$i][0].'">'.$links[$i][1].'</a>'."\n";
1950
				} else {
1951
					$out .= '<span class="tabspan">'.$links[$i][1].'</span>'."\n";
1952
				}
1953
			} elseif (!empty($links[$i][1])) {
1954
				//print "x $i $active ".$links[$i][2]." z";
1955
				$out .= '<div class="tab tab'.($isactive?'active':'unactive').'" style="margin: 0 !important">';
1956
				if (!empty($links[$i][0])) {
1957
					$titletoshow = preg_replace('/<.*$/', '', $links[$i][1]);
1958
					$out .= '<a'.(!empty($links[$i][2]) ? ' id="'.$links[$i][2].'"' : '').' class="tab inline-block valignmiddle'.($morecss ? ' '.$morecss : '').(!empty($links[$i][5]) ? ' '.$links[$i][5] : '').'" href="'.$links[$i][0].'" title="'.dol_escape_htmltag($titletoshow).'">';
1959
				}
1960
				$out .= $links[$i][1];
1961
				if (!empty($links[$i][0])) {
1962
					$out .= '</a>'."\n";
1963
				}
1964
				$out .= empty($links[$i][4]) ? '' : $links[$i][4];
1965
				$out .= '</div>';
1966
			}
1967
1968
			$out .= '</div>';
1969
		} else {
1970
			// Add entry into the combo popup with the other tabs
1971
			if (!$popuptab) {
1972
				$popuptab = 1;
1973
				$outmore .= '<div class="popuptabset wordwrap">'; // The css used to hide/show popup
1974
			}
1975
			$outmore .= '<div class="popuptab wordwrap" style="display:inherit;">';
1976
			if (isset($links[$i][2]) && $links[$i][2] == 'image') {
1977
				if (!empty($links[$i][0])) {
1978
					$outmore .= '<a class="tabimage'.($morecss ? ' '.$morecss : '').'" href="'.$links[$i][0].'">'.$links[$i][1].'</a>'."\n";
1979
				} else {
1980
					$outmore .= '<span class="tabspan">'.$links[$i][1].'</span>'."\n";
1981
				}
1982
			} elseif (!empty($links[$i][1])) {
1983
				$outmore .= '<a'.(!empty($links[$i][2]) ? ' id="'.$links[$i][2].'"' : '').' class="wordwrap inline-block'.($morecss ? ' '.$morecss : '').'" href="'.$links[$i][0].'">';
1984
				$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.
1985
				$outmore .= '</a>'."\n";
1986
			}
1987
			$outmore .= '</div>';
1988
1989
			$nbintab++;
1990
		}
1991
		$displaytab = $i;
1992
	}
1993
	if ($popuptab) {
1994
		$outmore .= '</div>';
1995
	}
1996
1997
	if ($popuptab) {	// If there is some tabs not shown
1998
		$left = ($langs->trans("DIRECTION") == 'rtl' ? 'right' : 'left');
1999
		$right = ($langs->trans("DIRECTION") == 'rtl' ? 'left' : 'right');
2000
		$widthofpopup = 200;
2001
2002
		$tabsname = $moretabssuffix;
2003
		if (empty($tabsname)) {
2004
			$tabsname = str_replace("@", "", $picto);
2005
		}
2006
		$out .= '<div id="moretabs'.$tabsname.'" class="inline-block tabsElem valignmiddle">';
2007
		$out .= '<div class="tab"><a href="#" class="tab moretab inline-block tabunactive"><span class="hideonsmartphone">'.$langs->trans("More").'</span>... ('.$nbintab.')</a></div>'; // Do not use "reposition" class in the "More".
2008
		$out .= '<div id="moretabsList'.$tabsname.'" style="width: '.$widthofpopup.'px; position: absolute; '.$left.': -999em; text-align: '.$left.'; margin:0px; padding:2px; z-index:10;">';
2009
		$out .= $outmore;
2010
		$out .= '</div>';
2011
		$out .= '<div></div>';
2012
		$out .= "</div>\n";
2013
2014
		$out .= "<script>";
2015
		$out .= "$('#moretabs".$tabsname."').mouseenter( function() {
2016
			var x = this.offsetLeft, y = this.offsetTop;
2017
			console.log('mouseenter ".$left." x='+x+' y='+y+' window.innerWidth='+window.innerWidth);
2018
			if ((window.innerWidth - x) < ".($widthofpopup + 10).") {
2019
				$('#moretabsList".$tabsname."').css('".$right."','8px');
2020
			}
2021
			$('#moretabsList".$tabsname."').css('".$left."','auto');
2022
			});
2023
		";
2024
		$out .= "$('#moretabs".$tabsname."').mouseleave( function() { console.log('mouseleave ".$left."'); $('#moretabsList".$tabsname."').css('".$left."','-999em');});";
2025
		$out .= "</script>";
2026
	}
2027
2028
	if ((!empty($title) && $showtitle) || $morehtmlright || !empty($links)) {
2029
		$out .= "</div>\n";
2030
	}
2031
2032
	if (!$notab || $notab == -1 || $notab == -2 || $notab == -3) {
2033
		$out .= "\n".'<div class="tabBar'.($notab == -1 ? '' : ($notab == -2 ? ' tabBarNoTop' : (($notab == -3 ? ' noborderbottom' : '').' tabBarWithBottom'))).'">'."\n";
2034
	}
2035
2036
	$parameters = array('tabname' => $active, 'out' => $out);
2037
	$reshook = $hookmanager->executeHooks('printTabsHead', $parameters); // This hook usage is called just before output the head of tabs. Take also a look at "completeTabsHead"
2038
	if ($reshook > 0) {
2039
		$out = $hookmanager->resPrint;
2040
	}
2041
2042
	return $out;
2043
}
2044
2045
/**
2046
 *  Show tab footer of a card
2047
 *
2048
 *  @param	int		$notab       -1 or 0=Add tab footer, 1=no tab footer
2049
 *  @return	void
2050
 *  @deprecated Use print dol_get_fiche_end() instead
2051
 */
2052
function dol_fiche_end($notab = 0)
2053
{
2054
	print dol_get_fiche_end($notab);
2055
}
2056
2057
/**
2058
 *	Return tab footer of a card
2059
 *
2060
 *	@param  int		$notab		-1 or 0=Add tab footer, 1=no tab footer
2061
 *  @return	string
2062
 */
2063
function dol_get_fiche_end($notab = 0)
2064
{
2065
	if (!$notab || $notab == -1) {
2066
		return "\n</div>\n";
2067
	} else {
2068
		return '';
2069
	}
2070
}
2071
2072
/**
2073
 *  Show tab footer of a card.
2074
 *  Note: $object->next_prev_filter can be set to restrict select to find next or previous record by $form->showrefnav.
2075
 *
2076
 *  @param	Object	$object			Object to show
2077
 *  @param	string	$paramid   		Name of parameter to use to name the id into the URL next/previous link
2078
 *  @param	string	$morehtml  		More html content to output just before the nav bar
2079
 *  @param	int		$shownav	  	Show Condition (navigation is shown if value is 1)
2080
 *  @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.
2081
 *  @param	string	$fieldref   	Nom du champ objet ref (object->ref) a utiliser pour select next et previous
2082
 *  @param	string	$morehtmlref  	More html to show after the ref (see $morehtmlleft for before)
2083
 *  @param	string	$moreparam  	More param to add in nav link url.
2084
 *	@param	int		$nodbprefix		Do not include DB prefix to forge table name
2085
 *	@param	string	$morehtmlleft	More html code to show before the ref (see $morehtmlref for after)
2086
 *	@param	string	$morehtmlstatus	More html code to show under navigation arrows
2087
 *  @param  int     $onlybanner     Put this to 1, if the card will contains only a banner (this add css 'arearefnobottom' on div)
2088
 *	@param	string	$morehtmlright	More html code to show before navigation arrows
2089
 *  @return	void
2090
 */
2091
function dol_banner_tab($object, $paramid, $morehtml = '', $shownav = 1, $fieldid = 'rowid', $fieldref = 'ref', $morehtmlref = '', $moreparam = '', $nodbprefix = 0, $morehtmlleft = '', $morehtmlstatus = '', $onlybanner = 0, $morehtmlright = '')
2092
{
2093
	global $conf, $form, $user, $langs, $hookmanager, $action;
2094
2095
	$error = 0;
2096
2097
	$maxvisiblephotos = 1;
2098
	$showimage = 1;
2099
	$entity = (empty($object->entity) ? $conf->entity : $object->entity);
2100
	$showbarcode = empty($conf->barcode->enabled) ? 0 : (empty($object->barcode) ? 0 : 1);
2101
	if (!empty($conf->global->MAIN_USE_ADVANCED_PERMS) && empty($user->rights->barcode->lire_advance)) {
2102
		$showbarcode = 0;
2103
	}
2104
	$modulepart = 'unknown';
2105
2106
	if ($object->element == 'societe' || $object->element == 'contact' || $object->element == 'product' || $object->element == 'ticket') {
2107
		$modulepart = $object->element;
2108
	} elseif ($object->element == 'member') {
2109
		$modulepart = 'memberphoto';
2110
	} elseif ($object->element == 'user') {
2111
		$modulepart = 'userphoto';
2112
	}
2113
2114
	if (class_exists("Imagick")) {
2115
		if ($object->element == 'expensereport' || $object->element == 'propal' || $object->element == 'commande' || $object->element == 'facture' || $object->element == 'supplier_proposal') {
2116
			$modulepart = $object->element;
2117
		} elseif ($object->element == 'fichinter') {
2118
			$modulepart = 'ficheinter';
2119
		} elseif ($object->element == 'contrat') {
2120
			$modulepart = 'contract';
2121
		} elseif ($object->element == 'order_supplier') {
2122
			$modulepart = 'supplier_order';
2123
		} elseif ($object->element == 'invoice_supplier') {
2124
			$modulepart = 'supplier_invoice';
2125
		}
2126
	}
2127
2128
	if ($object->element == 'product') {
2129
		$width = 80;
2130
		$cssclass = 'photowithmargin photoref';
2131
		$showimage = $object->is_photo_available($conf->product->multidir_output[$entity]);
2132
		$maxvisiblephotos = (isset($conf->global->PRODUCT_MAX_VISIBLE_PHOTO) ? $conf->global->PRODUCT_MAX_VISIBLE_PHOTO : 5);
2133
		if ($conf->browser->layout == 'phone') {
2134
			$maxvisiblephotos = 1;
2135
		}
2136
		if ($showimage) {
2137
			$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">'.$object->show_photos('product', $conf->product->multidir_output[$entity], 'small', $maxvisiblephotos, 0, 0, 0, 0, $width, 0, '').'</div>';
2138
		} else {
2139
			if (!empty($conf->global->PRODUCT_NODISPLAYIFNOPHOTO)) {
2140
				$nophoto = '';
2141
				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
2142
			} else {    // Show no photo link
2143
				$nophoto = '/public/theme/common/nophoto.png';
2144
				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><img class="photo'.$modulepart.($cssclass ? ' '.$cssclass : '').'" title="'.dol_escape_htmltag($langs->trans("UploadAnImageToSeeAPhotoHere", $langs->transnoentitiesnoconv("Documents"))).'" alt="No photo"'.($width ? ' style="width: '.$width.'px"' : '').' src="'.DOL_URL_ROOT.$nophoto.'"></div>';
2145
			}
2146
		}
2147
	} elseif ($object->element == 'ticket') {
2148
		$width = 80;
2149
		$cssclass = 'photoref';
2150
		$showimage = $object->is_photo_available($conf->ticket->multidir_output[$entity].'/'.$object->ref);
2151
		$maxvisiblephotos = (isset($conf->global->TICKET_MAX_VISIBLE_PHOTO) ? $conf->global->TICKET_MAX_VISIBLE_PHOTO : 2);
2152
		if ($conf->browser->layout == 'phone') {
2153
			$maxvisiblephotos = 1;
2154
		}
2155
2156
		if ($showimage) {
2157
			$showphoto = $object->show_photos('ticket', $conf->ticket->multidir_output[$entity], 'small', $maxvisiblephotos, 0, 0, 0, $width, 0);
2158
			if ($object->nbphoto > 0) {
2159
				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">'.$showphoto.'</div>';
2160
			} else {
2161
				$showimage = 0;
2162
			}
2163
		}
2164
		if (!$showimage) {
2165
			if (!empty($conf->global->TICKET_NODISPLAYIFNOPHOTO)) {
2166
				$nophoto = '';
2167
				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"></div>';
2168
			} else {    // Show no photo link
2169
				$nophoto = img_picto('No photo', 'object_ticket');
2170
				$morehtmlleft .= '<!-- No photo to show -->';
2171
				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
2172
				$morehtmlleft .= $nophoto;
2173
				$morehtmlleft .= '</div></div>';
2174
			}
2175
		}
2176
	} else {
2177
		if ($showimage) {
2178
			if ($modulepart != 'unknown') {
2179
				$phototoshow = '';
2180
				// Check if a preview file is available
2181
				if (in_array($modulepart, array('propal', 'commande', 'facture', 'ficheinter', 'contract', 'supplier_order', 'supplier_proposal', 'supplier_invoice', 'expensereport')) && class_exists("Imagick")) {
2182
					$objectref = dol_sanitizeFileName($object->ref);
2183
					$dir_output = (empty($conf->$modulepart->multidir_output[$entity]) ? $conf->$modulepart->dir_output : $conf->$modulepart->multidir_output[$entity])."/";
2184
					if (in_array($modulepart, array('invoice_supplier', 'supplier_invoice'))) {
2185
						$subdir = get_exdir($object->id, 2, 0, 1, $object, $modulepart);
2186
						$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
2187
					} else {
2188
						$subdir = get_exdir($object->id, 0, 0, 1, $object, $modulepart);
2189
					}
2190
					if (empty($subdir)) {
2191
						$subdir = 'errorgettingsubdirofobject'; // Protection to avoid to return empty path
2192
					}
2193
2194
					$filepath = $dir_output.$subdir."/";
2195
2196
					$filepdf = $filepath.$objectref.".pdf";
2197
					$relativepath = $subdir.'/'.$objectref.'.pdf';
2198
2199
					// Define path to preview pdf file (preview precompiled "file.ext" are "file.ext_preview.png")
2200
					$fileimage = $filepdf.'_preview.png';
2201
					$relativepathimage = $relativepath.'_preview.png';
2202
2203
					$pdfexists = file_exists($filepdf);
2204
2205
					// If PDF file exists
2206
					if ($pdfexists) {
2207
						// Conversion du PDF en image png si fichier png non existant
2208
						if (!file_exists($fileimage) || (filemtime($fileimage) < filemtime($filepdf))) {
2209
							if (empty($conf->global->MAIN_DISABLE_PDF_THUMBS)) {		// If you experience trouble with pdf thumb generation and imagick, you can disable here.
2210
								include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
2211
								$ret = dol_convert_file($filepdf, 'png', $fileimage, '0'); // Convert first page of PDF into a file _preview.png
2212
								if ($ret < 0) {
2213
									$error++;
2214
								}
2215
							}
2216
						}
2217
					}
2218
2219
					if ($pdfexists && !$error) {
2220
						$heightforphotref = 80;
2221
						if (!empty($conf->dol_optimize_smallscreen)) {
2222
							$heightforphotref = 60;
2223
						}
2224
						// If the preview file is found
2225
						if (file_exists($fileimage)) {
2226
							$phototoshow = '<div class="photoref">';
2227
							$phototoshow .= '<img height="'.$heightforphotref.'" class="photo photowithborder" src="'.DOL_URL_ROOT.'/viewimage.php?modulepart=apercu'.$modulepart.'&amp;file='.urlencode($relativepathimage).'">';
2228
							$phototoshow .= '</div>';
2229
						}
2230
					}
2231
				} elseif (!$phototoshow) { // example if modulepart = 'societe' or 'photo'
2232
					$phototoshow .= $form->showphoto($modulepart, $object, 0, 0, 0, 'photowithmargin photoref', 'small', 1, 0, $maxvisiblephotos);
2233
				}
2234
2235
				if ($phototoshow) {
2236
					$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">';
2237
					$morehtmlleft .= $phototoshow;
2238
					$morehtmlleft .= '</div>';
2239
				}
2240
			}
2241
2242
			if (empty($phototoshow)) {      // Show No photo link (picto of object)
2243
				if ($object->element == 'action') {
2244
					$width = 80;
2245
					$cssclass = 'photorefcenter';
2246
					$nophoto = img_picto('No photo', 'title_agenda');
2247
				} else {
2248
					$width = 14;
2249
					$cssclass = 'photorefcenter';
2250
					$picto = $object->picto;
2251
					$prefix = 'object_';
2252
					if ($object->element == 'project' && !$object->public) {
2253
						$picto = 'project'; // instead of projectpub
2254
					}
2255
					if (strpos($picto, 'fontawesome_') !== false) {
2256
						$prefix = '';
2257
					}
2258
					$nophoto = img_picto('No photo', $prefix.$picto);
2259
				}
2260
				$morehtmlleft .= '<!-- No photo to show -->';
2261
				$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref"><div class="photoref">';
2262
				$morehtmlleft .= $nophoto;
2263
				$morehtmlleft .= '</div></div>';
2264
			}
2265
		}
2266
	}
2267
2268
	// Show barcode
2269
	if ($showbarcode) {
2270
		$morehtmlleft .= '<div class="floatleft inline-block valignmiddle divphotoref">'.$form->showbarcode($object, 100, 'photoref valignmiddle').'</div>';
2271
	}
2272
2273
	if ($object->element == 'societe') {
2274
		if (!empty($conf->use_javascript_ajax) && $user->hasRight('societe', 'creer') && !empty($conf->global->MAIN_DIRECT_STATUS_UPDATE)) {
2275
			$morehtmlstatus .= ajax_object_onoff($object, 'status', 'status', 'InActivity', 'ActivityCeased');
2276
		} else {
2277
			$morehtmlstatus .= $object->getLibStatut(6);
2278
		}
2279
	} elseif ($object->element == 'product') {
2280
		//$morehtmlstatus.=$langs->trans("Status").' ('.$langs->trans("Sell").') ';
2281
		if (!empty($conf->use_javascript_ajax) && $user->rights->produit->creer && !empty($conf->global->MAIN_DIRECT_STATUS_UPDATE)) {
2282
			$morehtmlstatus .= ajax_object_onoff($object, 'status', 'tosell', 'ProductStatusOnSell', 'ProductStatusNotOnSell');
2283
		} else {
2284
			$morehtmlstatus .= '<span class="statusrefsell">'.$object->getLibStatut(6, 0).'</span>';
2285
		}
2286
		$morehtmlstatus .= ' &nbsp; ';
2287
		//$morehtmlstatus.=$langs->trans("Status").' ('.$langs->trans("Buy").') ';
2288
		if (!empty($conf->use_javascript_ajax) && $user->rights->produit->creer && !empty($conf->global->MAIN_DIRECT_STATUS_UPDATE)) {
2289
			$morehtmlstatus .= ajax_object_onoff($object, 'status_buy', 'tobuy', 'ProductStatusOnBuy', 'ProductStatusNotOnBuy');
2290
		} else {
2291
			$morehtmlstatus .= '<span class="statusrefbuy">'.$object->getLibStatut(6, 1).'</span>';
2292
		}
2293
	} elseif (in_array($object->element, array('facture', 'invoice', 'invoice_supplier', 'chargesociales', 'loan', 'tva', 'salary'))) {
2294
		$tmptxt = $object->getLibStatut(6, $object->totalpaid);
2295
		if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
2296
			$tmptxt = $object->getLibStatut(5, $object->totalpaid);
2297
		}
2298
		$morehtmlstatus .= $tmptxt;
2299
	} elseif ($object->element == 'contrat' || $object->element == 'contract') {
2300
		if ($object->statut == 0) {
2301
			$morehtmlstatus .= $object->getLibStatut(5);
2302
		} else {
2303
			$morehtmlstatus .= $object->getLibStatut(4);
2304
		}
2305
	} elseif ($object->element == 'facturerec') {
2306
		if ($object->frequency == 0) {
2307
			$morehtmlstatus .= $object->getLibStatut(2);
2308
		} else {
2309
			$morehtmlstatus .= $object->getLibStatut(5);
2310
		}
2311
	} elseif ($object->element == 'project_task') {
2312
		$object->fk_statut = 1;
2313
		if ($object->progress > 0) {
2314
			$object->fk_statut = 2;
2315
		}
2316
		if ($object->progress >= 100) {
2317
			$object->fk_statut = 3;
2318
		}
2319
		$tmptxt = $object->getLibStatut(5);
2320
		$morehtmlstatus .= $tmptxt; // No status on task
2321
	} else { // Generic case
2322
		if (isset($object->status)) {
2323
			$tmptxt = $object->getLibStatut(6);
2324
			if (empty($tmptxt) || $tmptxt == $object->getLibStatut(3)) {
2325
				$tmptxt = $object->getLibStatut(5);
2326
			}
2327
			$morehtmlstatus .= $tmptxt;
2328
		}
2329
	}
2330
2331
	// Add if object was dispatched "into accountancy"
2332
	if (isModEnabled('accounting') && in_array($object->element, array('bank', 'paiementcharge', 'facture', 'invoice', 'invoice_supplier', 'expensereport', 'payment_various'))) {
2333
		// Note: For 'chargesociales', 'salaries'... this is the payments that are dispatched (so element = 'bank')
2334
		if (method_exists($object, 'getVentilExportCompta')) {
2335
			$accounted = $object->getVentilExportCompta();
2336
			$langs->load("accountancy");
2337
			$morehtmlstatus .= '</div><div class="statusref statusrefbis"><span class="opacitymedium">'.($accounted > 0 ? $langs->trans("Accounted") : $langs->trans("NotYetAccounted")).'</span>';
2338
		}
2339
	}
2340
2341
	// Add alias for thirdparty
2342
	if (!empty($object->name_alias)) {
2343
		$morehtmlref .= '<div class="refidno opacitymedium">'.dol_escape_htmltag($object->name_alias).'</div>';
2344
	}
2345
2346
	// Add label
2347
	if (in_array($object->element, array('product', 'bank_account', 'project_task'))) {
2348
		if (!empty($object->label)) {
2349
			$morehtmlref .= '<div class="refidno opacitymedium">'.$object->label.'</div>';
2350
		}
2351
	}
2352
2353
	// Show address and email
2354
	if (method_exists($object, 'getBannerAddress') && !in_array($object->element, array('product', 'bookmark', 'ecm_directories', 'ecm_files'))) {
2355
		$moreaddress = $object->getBannerAddress('refaddress', $object);
2356
		if ($moreaddress) {
2357
			$morehtmlref .= '<div class="refidno">';
2358
			$morehtmlref .= $moreaddress;
2359
			$morehtmlref .= '</div>';
2360
		}
2361
	}
2362
	if (!empty($conf->global->MAIN_SHOW_TECHNICAL_ID) && ($conf->global->MAIN_SHOW_TECHNICAL_ID == '1' || preg_match('/'.preg_quote($object->element, '/').'/i', $conf->global->MAIN_SHOW_TECHNICAL_ID)) && !empty($object->id)) {
2363
		$morehtmlref .= '<div style="clear: both;"></div>';
2364
		$morehtmlref .= '<div class="refidno opacitymedium">';
2365
		$morehtmlref .= $langs->trans("TechnicalID").': '.((int) $object->id);
2366
		$morehtmlref .= '</div>';
2367
	}
2368
2369
	$parameters=array('morehtmlref'=>$morehtmlref);
2370
	$reshook = $hookmanager->executeHooks('formDolBanner', $parameters, $object, $action);
2371
	if ($reshook < 0) {
2372
		setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
2373
	} elseif (empty($reshook)) {
2374
		$morehtmlref .= $hookmanager->resPrint;
2375
	} elseif ($reshook > 0) {
2376
		$morehtmlref = $hookmanager->resPrint;
2377
	}
2378
2379
2380
	print '<div class="'.($onlybanner ? 'arearefnobottom ' : 'arearef ').'heightref valignmiddle centpercent">';
2381
	print $form->showrefnav($object, $paramid, $morehtml, $shownav, $fieldid, $fieldref, $morehtmlref, $moreparam, $nodbprefix, $morehtmlleft, $morehtmlstatus, $morehtmlright);
2382
	print '</div>';
2383
	print '<div class="underrefbanner clearboth"></div>';
2384
}
2385
2386
/**
2387
 * Show a string with the label tag dedicated to the HTML edit field.
2388
 *
2389
 * @param	string	$langkey		Translation key
2390
 * @param 	string	$fieldkey		Key of the html select field the text refers to
2391
 * @param	int		$fieldrequired	1=Field is mandatory
2392
 * @return string
2393
 * @deprecated Form::editfieldkey
2394
 */
2395
function fieldLabel($langkey, $fieldkey, $fieldrequired = 0)
2396
{
2397
	global $langs;
2398
	$ret = '';
2399
	if ($fieldrequired) {
2400
		$ret .= '<span class="fieldrequired">';
2401
	}
2402
	$ret .= '<label for="'.$fieldkey.'">';
2403
	$ret .= $langs->trans($langkey);
2404
	$ret .= '</label>';
2405
	if ($fieldrequired) {
2406
		$ret .= '</span>';
2407
	}
2408
	return $ret;
2409
}
2410
2411
/**
2412
 * Return string to add class property on html element with pair/impair.
2413
 *
2414
 * @param	string	$var			0 or 1
2415
 * @param	string	$moreclass		More class to add
2416
 * @return	string					String to add class onto HTML element
2417
 */
2418
function dol_bc($var, $moreclass = '')
2419
{
2420
	global $bc;
2421
	$ret = ' '.$bc[$var];
2422
	if ($moreclass) {
2423
		$ret = preg_replace('/class=\"/', 'class="'.$moreclass.' ', $ret);
2424
	}
2425
	return $ret;
2426
}
2427
2428
/**
2429
 *      Return a formated address (part address/zip/town/state) according to country rules.
2430
 *      See https://en.wikipedia.org/wiki/Address
2431
 *
2432
 *      @param  Object		$object			A company or contact object
2433
 * 	    @param	int			$withcountry	1=Add country into address string
2434
 *      @param	string		$sep			Separator to use to build string
2435
 *      @param	Translate	$outputlangs	Object lang that contains language for text translation.
2436
 *      @param	int			$mode			0=Standard output, 1=Remove address
2437
 *  	@param	string		$extralangcode	User extralanguage $langcode as values for address, town
2438
 *      @return string						Formated string
2439
 *      @see dol_print_address()
2440
 */
2441
function dol_format_address($object, $withcountry = 0, $sep = "\n", $outputlangs = '', $mode = 0, $extralangcode = '')
2442
{
2443
	global $conf, $langs, $hookmanager;
2444
2445
	$ret = '';
2446
	$countriesusingstate = array('AU', 'CA', 'US', 'IN', 'GB', 'ES', 'UK', 'TR', 'CN'); // See also MAIN_FORCE_STATE_INTO_ADDRESS
2447
2448
	// See format of addresses on https://en.wikipedia.org/wiki/Address
2449
	// Address
2450
	if (empty($mode)) {
2451
		$ret .= ($extralangcode ? $object->array_languages['address'][$extralangcode] : (empty($object->address) ? '' : $object->address));
2452
	}
2453
	// Zip/Town/State
2454
	if (isset($object->country_code) && in_array($object->country_code, array('AU', 'CA', 'US', 'CN')) || !empty($conf->global->MAIN_FORCE_STATE_INTO_ADDRESS)) {
2455
		// US: title firstname name \n address lines \n town, state, zip \n country
2456
		$town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2457
		$ret .= (($ret && $town) ? $sep : '').$town;
2458
2459
		if (!empty($object->state))	{
2460
			$ret .= ($ret ? ($town ? ", " : $sep) : '').$object->state;
2461
		}
2462
		if (!empty($object->zip)) {
2463
			$ret .= ($ret ? (($town || $object->state) ? ", " : $sep) : '').$object->zip;
2464
		}
2465
	} elseif (isset($object->country_code) && in_array($object->country_code, array('GB', 'UK'))) {
2466
		// UK: title firstname name \n address lines \n town state \n zip \n country
2467
		$town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2468
		$ret .= ($ret ? $sep : '').$town;
2469
		if (!empty($object->state)) {
2470
			$ret .= ($ret ? ", " : '').$object->state;
2471
		}
2472
		if (!empty($object->zip)) {
2473
			$ret .= ($ret ? $sep : '').$object->zip;
2474
		}
2475
	} elseif (isset($object->country_code) && in_array($object->country_code, array('ES', 'TR'))) {
2476
		// ES: title firstname name \n address lines \n zip town \n state \n country
2477
		$ret .= ($ret ? $sep : '').$object->zip;
2478
		$town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2479
		$ret .= ($town ? (($object->zip ? ' ' : '').$town) : '');
2480
		if (!empty($object->state)) {
2481
			$ret .= "\n".$object->state;
2482
		}
2483
	} elseif (isset($object->country_code) && in_array($object->country_code, array('JP'))) {
2484
		// JP: In romaji, title firstname name\n address lines \n [state,] town zip \n country
2485
		// See https://www.sljfaq.org/afaq/addresses.html
2486
		$town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2487
		$ret .= ($ret ? $sep : '').($object->state ? $object->state.', ' : '').$town.($object->zip ? ' ' : '').$object->zip;
2488
	} elseif (isset($object->country_code) && in_array($object->country_code, array('IT'))) {
2489
		// IT: title firstname name\n address lines \n zip town state_code \n country
2490
		$ret .= ($ret ? $sep : '').$object->zip;
2491
		$town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2492
		$ret .= ($town ? (($object->zip ? ' ' : '').$town) : '');
2493
		$ret .= (empty($object->state_code) ? '' : (' '.$object->state_code));
2494
	} else {
2495
		// Other: title firstname name \n address lines \n zip town[, state] \n country
2496
		$town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2497
		$ret .= !empty($object->zip) ? (($ret ? $sep : '').$object->zip) : '';
2498
		$ret .= ($town ? (($object->zip ? ' ' : ($ret ? $sep : '')).$town) : '');
2499
		if (!empty($object->state) && in_array($object->country_code, $countriesusingstate)) {
2500
			$ret .= ($ret ? ", " : '').$object->state;
2501
		}
2502
	}
2503
	if (!is_object($outputlangs)) {
2504
		$outputlangs = $langs;
2505
	}
2506
	if ($withcountry) {
2507
		$langs->load("dict");
2508
		$ret .= (empty($object->country_code) ? '' : ($ret ? $sep : '').$outputlangs->convToOutputCharset($outputlangs->transnoentitiesnoconv("Country".$object->country_code)));
2509
	}
2510
	if ($hookmanager) {
2511
		$parameters = array('withcountry' => $withcountry, 'sep' => $sep, 'outputlangs' => $outputlangs,'mode' => $mode, 'extralangcode' => $extralangcode);
2512
		$reshook = $hookmanager->executeHooks('formatAddress', $parameters, $object);
2513
		if ($reshook > 0) {
2514
			$ret = '';
2515
		}
2516
		$ret .= $hookmanager->resPrint;
2517
	}
2518
2519
	return $ret;
2520
}
2521
2522
2523
2524
/**
2525
 *	Format a string.
2526
 *
2527
 *	@param	string	$fmt		Format of strftime function (http://php.net/manual/fr/function.strftime.php)
2528
 *  @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)
2529
 *  @param	int		$is_gmt		See comment of timestamp parameter
2530
 *	@return	string				A formatted string
2531
 */
2532
function dol_strftime($fmt, $ts = false, $is_gmt = false)
2533
{
2534
	if ((abs($ts) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range
2535
		return ($is_gmt) ? @gmstrftime($fmt, $ts) : @strftime($fmt, $ts);
2536
	} else {
2537
		return 'Error date into a not supported range';
2538
	}
2539
}
2540
2541
/**
2542
 *	Output date in a string format according to outputlangs (or langs if not defined).
2543
 * 	Return charset is always UTF-8, except if encodetoouput is defined. In this case charset is output charset
2544
 *
2545
 *	@param	int			$time			GM Timestamps date
2546
 *	@param	string		$format      	Output date format (tag of strftime function)
2547
 *										"%d %b %Y",
2548
 *										"%d/%m/%Y %H:%M",
2549
 *										"%d/%m/%Y %H:%M:%S",
2550
 *                                      "%B"=Long text of month, "%A"=Long text of day, "%b"=Short text of month, "%a"=Short text of day
2551
 *										"day", "daytext", "dayhour", "dayhourldap", "dayhourtext", "dayrfc", "dayhourrfc", "...inputnoreduce", "...reduceformat"
2552
 * 	@param	string		$tzoutput		true or 'gmt' => string is for Greenwich location
2553
 * 										false or 'tzserver' => output string is for local PHP server TZ usage
2554
 * 										'tzuser' => output string is for user TZ (current browser TZ with current dst) => In a future, we should have same behaviour than 'tzuserrel'
2555
 *                                      'tzuserrel' => output string is for user TZ (current browser TZ with dst or not, depending on date position)
2556
 *	@param	Translate	$outputlangs	Object lang that contains language for text translation.
2557
 *  @param  boolean		$encodetooutput false=no convert into output pagecode
2558
 * 	@return string      				Formated date or '' if time is null
2559
 *
2560
 *  @see        dol_mktime(), dol_stringtotime(), dol_getdate()
2561
 */
2562
function dol_print_date($time, $format = '', $tzoutput = 'auto', $outputlangs = '', $encodetooutput = false)
2563
{
2564
	global $conf, $langs;
2565
2566
	// If date undefined or "", we return ""
2567
	if (dol_strlen($time) == 0) {
2568
		return ''; // $time=0 allowed (it means 01/01/1970 00:00:00)
2569
	}
2570
2571
	if ($tzoutput === 'auto') {
2572
		$tzoutput = (empty($conf) ? 'tzserver' : (isset($conf->tzuserinputkey) ? $conf->tzuserinputkey : 'tzserver'));
2573
	}
2574
2575
	// Clean parameters
2576
	$to_gmt = false;
2577
	$offsettz = $offsetdst = 0;
2578
	if ($tzoutput) {
2579
		$to_gmt = true; // For backward compatibility
2580
		if (is_string($tzoutput)) {
2581
			if ($tzoutput == 'tzserver') {
2582
				$to_gmt = false;
2583
				$offsettzstring = @date_default_timezone_get(); // Example 'Europe/Berlin' or 'Indian/Reunion'
2584
				$offsettz = 0;	// Timezone offset with server timezone (because to_gmt is false), so 0
2585
				$offsetdst = 0;	// Dst offset with server timezone (because to_gmt is false), so 0
2586
			} elseif ($tzoutput == 'tzuser' || $tzoutput == 'tzuserrel') {
2587
				$to_gmt = true;
2588
				$offsettzstring = (empty($_SESSION['dol_tz_string']) ? 'UTC' : $_SESSION['dol_tz_string']); // Example 'Europe/Berlin' or 'Indian/Reunion'
2589
2590
				if (class_exists('DateTimeZone')) {
2591
					$user_date_tz = new DateTimeZone($offsettzstring);
2592
					$user_dt = new DateTime();
2593
					$user_dt->setTimezone($user_date_tz);
2594
					$user_dt->setTimestamp($tzoutput == 'tzuser' ? dol_now() : (int) $time);
2595
					$offsettz = $user_dt->getOffset();	// should include dst ?
2596
				} else {	// with old method (The 'tzuser' was processed like the 'tzuserrel')
2597
					$offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60; // Will not be used anymore
2598
					$offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60; // Will not be used anymore
2599
				}
2600
			}
2601
		}
2602
	}
2603
	if (!is_object($outputlangs)) {
2604
		$outputlangs = $langs;
2605
	}
2606
	if (!$format) {
2607
		$format = 'daytextshort';
2608
	}
2609
2610
	// Do we have to reduce the length of date (year on 2 chars) to save space.
2611
	// Note: dayinputnoreduce is same than day but no reduction of year length will be done
2612
	$reduceformat = (!empty($conf->dol_optimize_smallscreen) && in_array($format, array('day', 'dayhour'))) ? 1 : 0;	// Test on original $format param.
2613
	$format = preg_replace('/inputnoreduce/', '', $format);	// so format 'dayinputnoreduce' is processed like day
2614
	$formatwithoutreduce = preg_replace('/reduceformat/', '', $format);
2615
	if ($formatwithoutreduce != $format) {
2616
		$format = $formatwithoutreduce;
2617
		$reduceformat = 1;
2618
	}  // so format 'dayreduceformat' is processed like day
2619
2620
	// Change predefined format into computer format. If found translation in lang file we use it, otherwise we use default.
2621
	// TODO Add format daysmallyear and dayhoursmallyear
2622
	if ($format == 'day') {
2623
		$format = ($outputlangs->trans("FormatDateShort") != "FormatDateShort" ? $outputlangs->trans("FormatDateShort") : $conf->format_date_short);
2624
	} elseif ($format == 'hour') {
2625
		$format = ($outputlangs->trans("FormatHourShort") != "FormatHourShort" ? $outputlangs->trans("FormatHourShort") : $conf->format_hour_short);
2626
	} elseif ($format == 'hourduration') {
2627
		$format = ($outputlangs->trans("FormatHourShortDuration") != "FormatHourShortDuration" ? $outputlangs->trans("FormatHourShortDuration") : $conf->format_hour_short_duration);
2628
	} elseif ($format == 'daytext') {
2629
		$format = ($outputlangs->trans("FormatDateText") != "FormatDateText" ? $outputlangs->trans("FormatDateText") : $conf->format_date_text);
2630
	} elseif ($format == 'daytextshort') {
2631
		$format = ($outputlangs->trans("FormatDateTextShort") != "FormatDateTextShort" ? $outputlangs->trans("FormatDateTextShort") : $conf->format_date_text_short);
2632
	} elseif ($format == 'dayhour') {
2633
		$format = ($outputlangs->trans("FormatDateHourShort") != "FormatDateHourShort" ? $outputlangs->trans("FormatDateHourShort") : $conf->format_date_hour_short);
2634
	} elseif ($format == 'dayhoursec') {
2635
		$format = ($outputlangs->trans("FormatDateHourSecShort") != "FormatDateHourSecShort" ? $outputlangs->trans("FormatDateHourSecShort") : $conf->format_date_hour_sec_short);
2636
	} elseif ($format == 'dayhourtext') {
2637
		$format = ($outputlangs->trans("FormatDateHourText") != "FormatDateHourText" ? $outputlangs->trans("FormatDateHourText") : $conf->format_date_hour_text);
2638
	} elseif ($format == 'dayhourtextshort') {
2639
		$format = ($outputlangs->trans("FormatDateHourTextShort") != "FormatDateHourTextShort" ? $outputlangs->trans("FormatDateHourTextShort") : $conf->format_date_hour_text_short);
2640
	} elseif ($format == 'dayhourlog') {
2641
		// Format not sensitive to language
2642
		$format = '%Y%m%d%H%M%S';
2643
	} elseif ($format == 'dayhourlogsmall') {
2644
		// Format not sensitive to language
2645
		$format = '%Y%m%d%H%M';
2646
	} elseif ($format == 'dayhourldap') {
2647
		$format = '%Y%m%d%H%M%SZ';
2648
	} elseif ($format == 'dayhourxcard') {
2649
		$format = '%Y%m%dT%H%M%SZ';
2650
	} elseif ($format == 'dayxcard') {
2651
		$format = '%Y%m%d';
2652
	} elseif ($format == 'dayrfc') {
2653
		$format = '%Y-%m-%d'; // DATE_RFC3339
2654
	} elseif ($format == 'dayhourrfc') {
2655
		$format = '%Y-%m-%dT%H:%M:%SZ'; // DATETIME RFC3339
2656
	} elseif ($format == 'standard') {
2657
		$format = '%Y-%m-%d %H:%M:%S';
2658
	}
2659
2660
	if ($reduceformat) {
2661
		$format = str_replace('%Y', '%y', $format);
2662
		$format = str_replace('yyyy', 'yy', $format);
2663
	}
2664
2665
	// Clean format
2666
	if (preg_match('/%b/i', $format)) {		// There is some text to translate
2667
		// We inhibate translation to text made by strftime functions. We will use trans instead later.
2668
		$format = str_replace('%b', '__b__', $format);
2669
		$format = str_replace('%B', '__B__', $format);
2670
	}
2671
	if (preg_match('/%a/i', $format)) {		// There is some text to translate
2672
		// We inhibate translation to text made by strftime functions. We will use trans instead later.
2673
		$format = str_replace('%a', '__a__', $format);
2674
		$format = str_replace('%A', '__A__', $format);
2675
	}
2676
2677
	// Analyze date
2678
	$reg = array();
2679
	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
2680
		dol_print_error('', "Functions.lib::dol_print_date function called with a bad value from page ".$_SERVER["PHP_SELF"]);
2681
		return '';
2682
	} 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
2683
		// This part of code should not be used anymore.
2684
		dol_syslog("Functions.lib::dol_print_date function called with a bad value from page ".$_SERVER["PHP_SELF"], LOG_WARNING);
2685
		//if (function_exists('debug_print_backtrace')) debug_print_backtrace();
2686
		// Date has format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'
2687
		$syear	= (!empty($reg[1]) ? $reg[1] : '');
2688
		$smonth = (!empty($reg[2]) ? $reg[2] : '');
2689
		$sday	= (!empty($reg[3]) ? $reg[3] : '');
2690
		$shour	= (!empty($reg[4]) ? $reg[4] : '');
2691
		$smin	= (!empty($reg[5]) ? $reg[5] : '');
2692
		$ssec	= (!empty($reg[6]) ? $reg[6] : '');
2693
2694
		$time = dol_mktime($shour, $smin, $ssec, $smonth, $sday, $syear, true);
2695
2696
		if ($to_gmt) {
2697
			$tzo = new DateTimeZone('UTC');	// when to_gmt is true, base for offsettz and offsetdst (so timetouse) is UTC
2698
		} else {
2699
			$tzo = new DateTimeZone(date_default_timezone_get());	// when to_gmt is false, base for offsettz and offsetdst (so timetouse) is PHP server
2700
		}
2701
		$dtts = new DateTime();
2702
		$dtts->setTimestamp($time);
2703
		$dtts->setTimezone($tzo);
2704
		$newformat = str_replace(
2705
			array('%Y', '%y', '%m', '%d', '%H', '%I', '%M', '%S', '%p', 'T', 'Z', '__a__', '__A__', '__b__', '__B__'),
2706
			array('Y', 'y', 'm', 'd', 'H', 'h', 'i', 's', 'A', '__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
2707
			$format);
2708
		$ret = $dtts->format($newformat);
2709
		$ret = str_replace(
2710
			array('__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
2711
			array('T', 'Z', '__a__', '__A__', '__b__', '__B__'),
2712
			$ret
2713
		);
2714
	} else {
2715
		// Date is a timestamps
2716
		if ($time < 100000000000) {	// Protection against bad date values
2717
			$timetouse = $time + $offsettz + $offsetdst; // TODO We could be able to disable use of offsettz and offsetdst to use only offsettzstring.
2718
2719
			if ($to_gmt) {
2720
				$tzo = new DateTimeZone('UTC');	// when to_gmt is true, base for offsettz and offsetdst (so timetouse) is UTC
2721
			} else {
2722
				$tzo = new DateTimeZone(date_default_timezone_get());	// when to_gmt is false, base for offsettz and offsetdst (so timetouse) is PHP server
2723
			}
2724
			$dtts = new DateTime();
2725
			$dtts->setTimestamp($timetouse);
2726
			$dtts->setTimezone($tzo);
2727
			$newformat = str_replace(
2728
				array('%Y', '%y', '%m', '%d', '%H', '%I', '%M', '%S', '%p', 'T', 'Z', '__a__', '__A__', '__b__', '__B__'),
2729
				array('Y', 'y', 'm', 'd', 'H', 'h', 'i', 's', 'A', '__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
2730
				$format);
2731
			$ret = $dtts->format($newformat);
2732
			$ret = str_replace(
2733
				array('__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
2734
				array('T', 'Z', '__a__', '__A__', '__b__', '__B__'),
2735
				$ret
2736
			);
2737
			//var_dump($ret);exit;
2738
		} else {
2739
			$ret = 'Bad value '.$time.' for date';
2740
		}
2741
	}
2742
2743
	if (preg_match('/__b__/i', $format)) {
2744
		$timetouse = $time + $offsettz + $offsetdst; // TODO We could be able to disable use of offsettz and offsetdst to use only offsettzstring.
2745
2746
		if ($to_gmt) {
2747
			$tzo = new DateTimeZone('UTC');	// when to_gmt is true, base for offsettz and offsetdst (so timetouse) is UTC
2748
		} else {
2749
			$tzo = new DateTimeZone(date_default_timezone_get());	// when to_gmt is false, base for offsettz and offsetdst (so timetouse) is PHP server
2750
		}
2751
		$dtts = new DateTime();
2752
		$dtts->setTimestamp($timetouse);
2753
		$dtts->setTimezone($tzo);
2754
		$month = $dtts->format("m");
2755
		$month = sprintf("%02d", $month); // $month may be return with format '06' on some installation and '6' on other, so we force it to '06'.
2756
		if ($encodetooutput) {
2757
			$monthtext = $outputlangs->transnoentities('Month'.$month);
2758
			$monthtextshort = $outputlangs->transnoentities('MonthShort'.$month);
2759
		} else {
2760
			$monthtext = $outputlangs->transnoentitiesnoconv('Month'.$month);
2761
			$monthtextshort = $outputlangs->transnoentitiesnoconv('MonthShort'.$month);
2762
		}
2763
		//print 'monthtext='.$monthtext.' monthtextshort='.$monthtextshort;
2764
		$ret = str_replace('__b__', $monthtextshort, $ret);
2765
		$ret = str_replace('__B__', $monthtext, $ret);
2766
		//print 'x'.$outputlangs->charset_output.'-'.$ret.'x';
2767
		//return $ret;
2768
	}
2769
	if (preg_match('/__a__/i', $format)) {
2770
		//print "time=$time offsettz=$offsettz offsetdst=$offsetdst offsettzstring=$offsettzstring";
2771
		$timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
2772
2773
		if ($to_gmt) {
2774
			$tzo = new DateTimeZone('UTC');
2775
		} else {
2776
			$tzo = new DateTimeZone(date_default_timezone_get());
2777
		}
2778
		$dtts = new DateTime();
2779
		$dtts->setTimestamp($timetouse);
2780
		$dtts->setTimezone($tzo);
2781
		$w = $dtts->format("w");
2782
		$dayweek = $outputlangs->transnoentitiesnoconv('Day'.$w);
2783
2784
		$ret = str_replace('__A__', $dayweek, $ret);
2785
		$ret = str_replace('__a__', dol_substr($dayweek, 0, 3), $ret);
2786
	}
2787
2788
	return $ret;
2789
}
2790
2791
2792
/**
2793
 *  Return an array with locale date info.
2794
 *  WARNING: This function use PHP server timezone by default to return locale informations.
2795
 *  Be aware to add the third parameter to "UTC" if you need to work on UTC.
2796
 *
2797
 *	@param	int			$timestamp      Timestamp
2798
 *	@param	boolean		$fast           Fast mode. deprecated.
2799
 *  @param	string		$forcetimezone	'' to use the PHP server timezone. Or use a form like 'gmt', 'Europe/Paris' or '+0200' to force timezone.
2800
 *	@return	array						Array of informations
2801
 *										'seconds' => $secs,
2802
 *										'minutes' => $min,
2803
 *										'hours' => $hour,
2804
 *										'mday' => $day,
2805
 *										'wday' => $dow,		0=sunday, 6=saturday
2806
 *										'mon' => $month,
2807
 *										'year' => $year,
2808
 *										'yday' => floor($secsInYear/$_day_power)
2809
 *										'0' => original timestamp
2810
 * 	@see 								dol_print_date(), dol_stringtotime(), dol_mktime()
2811
 */
2812
function dol_getdate($timestamp, $fast = false, $forcetimezone = '')
2813
{
2814
	$datetimeobj = new DateTime();
2815
	$datetimeobj->setTimestamp($timestamp); // Use local PHP server timezone
2816
	if ($forcetimezone) {
2817
		$datetimeobj->setTimezone(new DateTimeZone($forcetimezone == 'gmt' ? 'UTC' : $forcetimezone)); //  (add timezone relative to the date entered)
2818
	}
2819
	$arrayinfo = array(
2820
		'year'=>((int) date_format($datetimeobj, 'Y')),
2821
		'mon'=>((int) date_format($datetimeobj, 'm')),
2822
		'mday'=>((int) date_format($datetimeobj, 'd')),
2823
		'wday'=>((int) date_format($datetimeobj, 'w')),
2824
		'yday'=>((int) date_format($datetimeobj, 'z')),
2825
		'hours'=>((int) date_format($datetimeobj, 'H')),
2826
		'minutes'=>((int) date_format($datetimeobj, 'i')),
2827
		'seconds'=>((int) date_format($datetimeobj, 's')),
2828
		'0'=>$timestamp
2829
	);
2830
2831
	return $arrayinfo;
2832
}
2833
2834
/**
2835
 *	Return a timestamp date built from detailed informations (by default a local PHP server timestamp)
2836
 * 	Replace function mktime not available under Windows if year < 1970
2837
 *	PHP mktime is restricted to the years 1901-2038 on Unix and 1970-2038 on Windows
2838
 *
2839
 * 	@param	int			$hour			Hour	(can be -1 for undefined)
2840
 *	@param	int			$minute			Minute	(can be -1 for undefined)
2841
 *	@param	int			$second			Second	(can be -1 for undefined)
2842
 *	@param	int			$month			Month (1 to 12)
2843
 *	@param	int			$day			Day (1 to 31)
2844
 *	@param	int			$year			Year
2845
 *	@param	mixed		$gm				True or 1 or 'gmt'=Input informations are GMT values
2846
 *										False or 0 or 'tzserver' = local to server TZ
2847
 *										'auto'
2848
 *										'tzuser' = local to user TZ taking dst into account at the current date. Not yet implemented.
2849
 *										'tzuserrel' = local to user TZ taking dst into account at the given date. Use this one to convert date input from user into a GMT date.
2850
 *										'tz,TimeZone' = use specified timezone
2851
 *	@param	int			$check			0=No check on parameters (Can use day 32, etc...)
2852
 *	@return	int|string					Date as a timestamp, '' or false if error
2853
 * 	@see 								dol_print_date(), dol_stringtotime(), dol_getdate()
2854
 */
2855
function dol_mktime($hour, $minute, $second, $month, $day, $year, $gm = 'auto', $check = 1)
2856
{
2857
	global $conf;
2858
	//print "- ".$hour.",".$minute.",".$second.",".$month.",".$day.",".$year.",".$_SERVER["WINDIR"]." -";
2859
2860
	if ($gm === 'auto') {
2861
		$gm = (empty($conf) ? 'tzserver' : $conf->tzuserinputkey);
2862
	}
2863
	//print 'gm:'.$gm.' gm === auto:'.($gm === 'auto').'<br>';exit;
2864
2865
	// Clean parameters
2866
	if ($hour == -1 || empty($hour)) {
2867
		$hour = 0;
2868
	}
2869
	if ($minute == -1 || empty($minute)) {
2870
		$minute = 0;
2871
	}
2872
	if ($second == -1 || empty($second)) {
2873
		$second = 0;
2874
	}
2875
2876
	// Check parameters
2877
	if ($check) {
2878
		if (!$month || !$day) {
2879
			return '';
2880
		}
2881
		if ($day > 31) {
2882
			return '';
2883
		}
2884
		if ($month > 12) {
2885
			return '';
2886
		}
2887
		if ($hour < 0 || $hour > 24) {
2888
			return '';
2889
		}
2890
		if ($minute < 0 || $minute > 60) {
2891
			return '';
2892
		}
2893
		if ($second < 0 || $second > 60) {
2894
			return '';
2895
		}
2896
	}
2897
2898
	if (empty($gm) || ($gm === 'server' || $gm === 'tzserver')) {
2899
		$default_timezone = @date_default_timezone_get(); // Example 'Europe/Berlin'
2900
		$localtz = new DateTimeZone($default_timezone);
2901
	} elseif ($gm === 'user' || $gm === 'tzuser' || $gm === 'tzuserrel') {
2902
		// We use dol_tz_string first because it is more reliable.
2903
		$default_timezone = (empty($_SESSION["dol_tz_string"]) ? @date_default_timezone_get() : $_SESSION["dol_tz_string"]); // Example 'Europe/Berlin'
2904
		try {
2905
			$localtz = new DateTimeZone($default_timezone);
2906
		} catch (Exception $e) {
2907
			dol_syslog("Warning dol_tz_string contains an invalid value ".$_SESSION["dol_tz_string"], LOG_WARNING);
2908
			$default_timezone = @date_default_timezone_get();
2909
		}
2910
	} elseif (strrpos($gm, "tz,") !== false) {
2911
		$timezone = str_replace("tz,", "", $gm); // Example 'tz,Europe/Berlin'
2912
		try {
2913
			$localtz = new DateTimeZone($timezone);
2914
		} catch (Exception $e) {
2915
			dol_syslog("Warning passed timezone contains an invalid value ".$timezone, LOG_WARNING);
2916
		}
2917
	}
2918
2919
	if (empty($localtz)) {
2920
		$localtz = new DateTimeZone('UTC');
2921
	}
2922
	//var_dump($localtz);
2923
	//var_dump($year.'-'.$month.'-'.$day.'-'.$hour.'-'.$minute);
2924
	$dt = new DateTime('now', $localtz);
2925
	$dt->setDate((int) $year, (int) $month, (int) $day);
2926
	$dt->setTime((int) $hour, (int) $minute, (int) $second);
2927
	$date = $dt->getTimestamp(); // should include daylight saving time
2928
	//var_dump($date);
2929
	return $date;
2930
}
2931
2932
2933
/**
2934
 *  Return date for now. In most cases, we use this function without parameters (that means GMT time).
2935
 *
2936
 *  @param	string		$mode	'auto' => for backward compatibility (avoid this),
2937
 *  							'gmt' => we return GMT timestamp,
2938
 * 								'tzserver' => we add the PHP server timezone
2939
 *  							'tzref' => we add the company timezone. Not implemented.
2940
 * 								'tzuser' or 'tzuserrel' => we add the user timezone
2941
 *	@return int   $date	Timestamp
2942
 */
2943
function dol_now($mode = 'auto')
2944
{
2945
	$ret = 0;
2946
2947
	if ($mode === 'auto') {
2948
		$mode = 'gmt';
2949
	}
2950
2951
	if ($mode == 'gmt') {
2952
		$ret = time(); // Time for now at greenwich.
2953
	} elseif ($mode == 'tzserver') {		// Time for now with PHP server timezone added
2954
		require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
2955
		$tzsecond = getServerTimeZoneInt('now'); // Contains tz+dayling saving time
2956
		$ret = (int) (dol_now('gmt') + ($tzsecond * 3600));
2957
		//} elseif ($mode == 'tzref') {// Time for now with parent company timezone is added
2958
		//	require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
2959
		//	$tzsecond=getParentCompanyTimeZoneInt();    // Contains tz+dayling saving time
2960
		//	$ret=dol_now('gmt')+($tzsecond*3600);
2961
		//}
2962
	} elseif ($mode == 'tzuser' || $mode == 'tzuserrel') {
2963
		// Time for now with user timezone added
2964
		//print 'time: '.time();
2965
		$offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60;
2966
		$offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60;
2967
		$ret = (int) (dol_now('gmt') + ($offsettz + $offsetdst));
2968
	}
2969
2970
	return $ret;
2971
}
2972
2973
2974
/**
2975
 * Return string with formated size
2976
 *
2977
 * @param	int		$size		Size to print
2978
 * @param	int		$shortvalue	Tell if we want long value to use another unit (Ex: 1.5Kb instead of 1500b)
2979
 * @param	int		$shortunit	Use short label of size unit (for example 'b' instead of 'bytes')
2980
 * @return	string				Link
2981
 */
2982
function dol_print_size($size, $shortvalue = 0, $shortunit = 0)
2983
{
2984
	global $conf, $langs;
2985
	$level = 1024;
2986
2987
	if (!empty($conf->dol_optimize_smallscreen)) {
2988
		$shortunit = 1;
2989
	}
2990
2991
	// Set value text
2992
	if (empty($shortvalue) || $size < ($level * 10)) {
2993
		$ret = $size;
2994
		$textunitshort = $langs->trans("b");
2995
		$textunitlong = $langs->trans("Bytes");
2996
	} else {
2997
		$ret = round($size / $level, 0);
2998
		$textunitshort = $langs->trans("Kb");
2999
		$textunitlong = $langs->trans("KiloBytes");
3000
	}
3001
	// Use long or short text unit
3002
	if (empty($shortunit)) {
3003
		$ret .= ' '.$textunitlong;
3004
	} else {
3005
		$ret .= ' '.$textunitshort;
3006
	}
3007
3008
	return $ret;
3009
}
3010
3011
/**
3012
 * Show Url link
3013
 *
3014
 * @param	string		$url		Url to show
3015
 * @param	string		$target		Target for link
3016
 * @param	int			$max		Max number of characters to show
3017
 * @param	int			$withpicto	With picto
3018
 * @param	string		$morecss	More CSS
3019
 * @return	string					HTML Link
3020
 */
3021
function dol_print_url($url, $target = '_blank', $max = 32, $withpicto = 0, $morecss = 'float')
3022
{
3023
	global $langs;
3024
3025
	if (empty($url)) {
3026
		return '';
3027
	}
3028
3029
	$link = '<a href="';
3030
	if (!preg_match('/^http/i', $url)) {
3031
		$link .= 'http://';
3032
	}
3033
	$link .= $url;
3034
	$link .= '"';
3035
	if ($target) {
3036
		$link .= ' target="'.$target.'"';
3037
	}
3038
	$link .= '>';
3039
	if (!preg_match('/^http/i', $url)) {
3040
		$link .= 'http://';
3041
	}
3042
	$link .= dol_trunc($url, $max);
3043
	$link .= '</a>';
3044
3045
	if ($morecss == 'float') {
3046
		return '<div class="nospan'.($morecss ? ' '.$morecss : '').'" style="margin-right: 10px">'.($withpicto ?img_picto($langs->trans("Url"), 'globe').' ' : '').$link.'</div>';
3047
	} else {
3048
		return '<span class="nospan'.($morecss ? ' '.$morecss : '').'" style="margin-right: 10px">'.($withpicto ?img_picto($langs->trans("Url"), 'globe').' ' : '').$link.'</span>';
3049
	}
3050
}
3051
3052
/**
3053
 * Show EMail link formatted for HTML output.
3054
 *
3055
 * @param	string		$email			EMail to show (only email, without 'Name of recipient' before)
3056
 * @param 	int			$cid 			Id of contact if known
3057
 * @param 	int			$socid 			Id of third party if known
3058
 * @param 	int			$addlink		0=no link, 1=email has a html email link (+ link to create action if constant AGENDA_ADDACTIONFOREMAIL is on)
3059
 * @param	int			$max			Max number of characters to show
3060
 * @param	int			$showinvalid	1=Show warning if syntax email is wrong
3061
 * @param	int|string	$withpicto		Show picto
3062
 * @return	string						HTML Link
3063
 */
3064
function dol_print_email($email, $cid = 0, $socid = 0, $addlink = 0, $max = 64, $showinvalid = 1, $withpicto = 0)
3065
{
3066
	global $conf, $user, $langs, $hookmanager;
3067
3068
	$newemail = dol_escape_htmltag($email);
3069
3070
	if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER) && $withpicto) {
3071
		$withpicto = 0;
3072
	}
3073
3074
	if (empty($email)) {
3075
		return '&nbsp;';
3076
	}
3077
3078
	if (!empty($addlink)) {
3079
		$newemail = '<a style="text-overflow: ellipsis;" href="';
3080
		if (!preg_match('/^mailto:/i', $email)) {
3081
			$newemail .= 'mailto:';
3082
		}
3083
		$newemail .= $email;
3084
		$newemail .= '">';
3085
		$newemail .= dol_trunc($email, $max);
3086
		$newemail .= '</a>';
3087
		if ($showinvalid && !isValidEmail($email)) {
3088
			$langs->load("errors");
3089
			$newemail .= img_warning($langs->trans("ErrorBadEMail", $email));
3090
		}
3091
3092
		if (($cid || $socid) && isModEnabled('agenda') && $user->hasRight("agenda", "myactions", "create")) {
3093
			$type = 'AC_EMAIL';
3094
			$link = '';
3095
			if (!empty($conf->global->AGENDA_ADDACTIONFOREMAIL)) {
3096
				$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>';
3097
			}
3098
			if ($link) {
3099
				$newemail = '<div>'.$newemail.' '.$link.'</div>';
3100
			}
3101
		}
3102
	} else {
3103
		if ($showinvalid && !isValidEmail($email)) {
3104
			$langs->load("errors");
3105
			$newemail .= img_warning($langs->trans("ErrorBadEMail", $email));
3106
		}
3107
	}
3108
3109
	//$rep = '<div class="nospan" style="margin-right: 10px">';
3110
	$rep = ($withpicto ? img_picto($langs->trans("EMail").' : '.$email, (is_numeric($withpicto) ? 'email' : $withpicto)).' ' : '').$newemail;
3111
	//$rep .= '</div>';
3112
	if ($hookmanager) {
3113
		$parameters = array('cid' => $cid, 'socid' => $socid, 'addlink' => $addlink, 'picto' => $withpicto);
3114
3115
		$reshook = $hookmanager->executeHooks('printEmail', $parameters, $email);
3116
		if ($reshook > 0) {
3117
			$rep = '';
3118
		}
3119
		$rep .= $hookmanager->resPrint;
3120
	}
3121
3122
	return $rep;
3123
}
3124
3125
/**
3126
 * Get array of social network dictionary
3127
 *
3128
 * @return  array       Array of Social Networks Dictionary
3129
 */
3130
function getArrayOfSocialNetworks()
3131
{
3132
	global $conf, $db;
3133
3134
	$socialnetworks = array();
3135
	// Enable caching of array
3136
	require_once DOL_DOCUMENT_ROOT.'/core/lib/memory.lib.php';
3137
	$cachekey = 'socialnetworks_' . $conf->entity;
3138
	$dataretrieved = dol_getcache($cachekey);
3139
	if (!is_null($dataretrieved)) {
3140
		$socialnetworks = $dataretrieved;
3141
	} else {
3142
		$sql = "SELECT rowid, code, label, url, icon, active FROM ".MAIN_DB_PREFIX."c_socialnetworks";
3143
		$sql .= " WHERE entity=".$conf->entity;
3144
		$resql = $db->query($sql);
3145
		if ($resql) {
3146
			while ($obj = $db->fetch_object($resql)) {
3147
				$socialnetworks[$obj->code] = array(
3148
					'rowid' => $obj->rowid,
3149
					'label' => $obj->label,
3150
					'url' => $obj->url,
3151
					'icon' => $obj->icon,
3152
					'active' => $obj->active,
3153
				);
3154
			}
3155
		}
3156
		dol_setcache($cachekey, $socialnetworks); // If setting cache fails, this is not a problem, so we do not test result.
3157
	}
3158
3159
	return $socialnetworks;
3160
}
3161
3162
/**
3163
 * Show social network link
3164
 *
3165
 * @param	string		$value				Skype to show (only skype, without 'Name of recipient' before)
3166
 * @param	int 		$cid 				Id of contact if known
3167
 * @param	int 		$socid 				Id of third party if known
3168
 * @param	string 		$type				'skype','facebook',...
3169
 * @param	array		$dictsocialnetworks socialnetworks availables
3170
 * @return	string							HTML Link
3171
 */
3172
function dol_print_socialnetworks($value, $cid, $socid, $type, $dictsocialnetworks = array())
3173
{
3174
	global $conf, $user, $langs;
3175
3176
	$htmllink = $value;
3177
3178
	if (empty($value)) {
3179
		return '&nbsp;';
3180
	}
3181
3182
	if (!empty($type)) {
3183
		$htmllink = '<div class="divsocialnetwork inline-block valignmiddle">';
3184
		// Use dictionary definition for picto $dictsocialnetworks[$type]['icon']
3185
		$htmllink .= '<span class="fa pictofixedwidth '.($dictsocialnetworks[$type]['icon'] ? $dictsocialnetworks[$type]['icon'] : 'fa-link').'"></span>';
3186
		if ($type == 'skype') {
3187
			$htmllink .= dol_escape_htmltag($value);
3188
			$htmllink .= '&nbsp; <a href="skype:';
3189
			$htmllink .= dol_string_nospecial($value, '_', '', array('@'));
3190
			$htmllink .= '?call" alt="'.$langs->trans("Call").'&nbsp;'.$value.'" title="'.dol_escape_htmltag($langs->trans("Call").' '.$value).'">';
3191
			$htmllink .= '<img src="'.DOL_URL_ROOT.'/theme/common/skype_callbutton.png" border="0">';
3192
			$htmllink .= '</a><a href="skype:';
3193
			$htmllink .= dol_string_nospecial($value, '_', '', array('@'));
3194
			$htmllink .= '?chat" alt="'.$langs->trans("Chat").'&nbsp;'.$value.'" title="'.dol_escape_htmltag($langs->trans("Chat").' '.$value).'">';
3195
			$htmllink .= '<img class="paddingleft" src="'.DOL_URL_ROOT.'/theme/common/skype_chatbutton.png" border="0">';
3196
			$htmllink .= '</a>';
3197
			if (($cid || $socid) && isModEnabled('agenda') && $user->rights->agenda->myactions->create) {
3198
				$addlink = 'AC_SKYPE';
3199
				$link = '';
3200
				if (!empty($conf->global->AGENDA_ADDACTIONFORSKYPE)) {
3201
					$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>';
3202
				}
3203
				$htmllink .= ($link ? ' '.$link : '');
3204
			}
3205
		} else {
3206
			if (!empty($dictsocialnetworks[$type]['url'])) {
3207
				$tmpvirginurl = preg_replace('/\/?{socialid}/', '', $dictsocialnetworks[$type]['url']);
3208
				if ($tmpvirginurl) {
3209
					$value = preg_replace('/^www\.'.preg_quote($tmpvirginurl, '/').'\/?/', '', $value);
3210
					$value = preg_replace('/^'.preg_quote($tmpvirginurl, '/').'\/?/', '', $value);
3211
3212
					$tmpvirginurl3 = preg_replace('/^https:\/\//i', 'https://www.', $tmpvirginurl);
3213
					if ($tmpvirginurl3) {
3214
						$value = preg_replace('/^www\.'.preg_quote($tmpvirginurl3, '/').'\/?/', '', $value);
3215
						$value = preg_replace('/^'.preg_quote($tmpvirginurl3, '/').'\/?/', '', $value);
3216
					}
3217
3218
					$tmpvirginurl2 = preg_replace('/^https?:\/\//i', '', $tmpvirginurl);
3219
					if ($tmpvirginurl2) {
3220
						$value = preg_replace('/^www\.'.preg_quote($tmpvirginurl2, '/').'\/?/', '', $value);
3221
						$value = preg_replace('/^'.preg_quote($tmpvirginurl2, '/').'\/?/', '', $value);
3222
					}
3223
				}
3224
				$link = str_replace('{socialid}', $value, $dictsocialnetworks[$type]['url']);
3225
				if (preg_match('/^https?:\/\//i', $link)) {
3226
					$htmllink .= '<a href="'.dol_sanitizeUrl($link, 0).'" target="_blank" rel="noopener noreferrer">'.dol_escape_htmltag($value).'</a>';
3227
				} else {
3228
					$htmllink .= '<a href="'.dol_sanitizeUrl($link, 1).'" target="_blank" rel="noopener noreferrer">'.dol_escape_htmltag($value).'</a>';
3229
				}
3230
			} else {
3231
				$htmllink .= dol_escape_htmltag($value);
3232
			}
3233
		}
3234
		$htmllink .= '</div>';
3235
	} else {
3236
		$langs->load("errors");
3237
		$htmllink .= img_warning($langs->trans("ErrorBadSocialNetworkValue", $value));
3238
	}
3239
	return $htmllink;
3240
}
3241
3242
/**
3243
 *	Format profIDs according to country
3244
 *
3245
 *	@param	string	$profID			Value of profID to format
3246
 *	@param	string	$profIDtype		Type of profID to format ('1', '2', '3', '4', '5', '6' or 'VAT')
3247
 *	@param	string	$countrycode	Country code to use for formatting
3248
 *	@param	int		$addcpButton	Add button to copy to clipboard (1 => show only on hoover ; 2 => always display )
3249
 * 	@param	string	$separ			Separation between numbers for a better visibility example : xxx xxx xxx xxxxx
3250
 *	@return string					Formated profID
3251
 */
3252
function dol_print_profids($profID, $profIDtype, $countrycode = '', $addcpButton = 1, $separ = '&nbsp;')
3253
{
3254
	global $mysoc;
3255
3256
	if (empty($profID) || empty($profIDtype)) {
3257
		return '';
3258
	}
3259
	if (empty($countrycode))	$countrycode = $mysoc->country_code;
3260
	$newProfID = $profID;
3261
	$id = substr($profIDtype, -1);
3262
	$ret = '';
3263
	if (strtoupper($countrycode) == 'FR') {
3264
		// France
3265
		if ($id == 1 && dol_strlen($newProfID) == 9)	$newProfID = substr($newProfID, 0, 3).$separ.substr($newProfID, 3, 3).$separ.substr($newProfID, 6, 3);
3266
		if ($id == 2 && dol_strlen($newProfID) == 14)	$newProfID = substr($newProfID, 0, 3).$separ.substr($newProfID, 3, 3).$separ.substr($newProfID, 6, 3).$separ.substr($newProfID, 9, 5);
3267
		if ($profIDtype === 'VAT' && dol_strlen($newProfID) == 13)	$newProfID = substr($newProfID, 0, 4).$separ.substr($newProfID, 4, 3).$separ.substr($newProfID, 7, 3).$separ.substr($newProfID, 10, 3);
3268
	}
3269
	if (!empty($addcpButton))	$ret = showValueWithClipboardCPButton(dol_escape_htmltag($profID), ($addcpButton == 1 ? 1 : 0), $newProfID);
3270
	else $ret = $newProfID;
3271
	return $ret;
3272
}
3273
3274
/**
3275
 * 	Format phone numbers according to country
3276
 *
3277
 * 	@param  string  $phone          Phone number to format
3278
 * 	@param  string  $countrycode    Country code to use for formatting
3279
 * 	@param 	int		$cid 		    Id of contact if known
3280
 * 	@param 	int		$socid          Id of third party if known
3281
 * 	@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), 'tel'=Force "tel:..." link
3282
 * 	@param 	string	$separ 		    Separation between numbers for a better visibility example : xx.xx.xx.xx.xx
3283
 *  @param	string  $withpicto      Show picto ('fax', 'phone', 'mobile')
3284
 *  @param	string	$titlealt	    Text to show on alt
3285
 *  @param  int     $adddivfloat    Add div float around phone.
3286
 * 	@return string 				    Formated phone number
3287
 */
3288
function dol_print_phone($phone, $countrycode = '', $cid = 0, $socid = 0, $addlink = '', $separ = "&nbsp;", $withpicto = '', $titlealt = '', $adddivfloat = 0)
3289
{
3290
	global $conf, $user, $langs, $mysoc, $hookmanager;
3291
3292
	// Clean phone parameter
3293
	$phone = is_null($phone) ? '' : preg_replace("/[\s.-]/", "", trim($phone));
0 ignored issues
show
introduced by
The condition is_null($phone) is always false.
Loading history...
3294
	if (empty($phone)) {
3295
		return '';
3296
	}
3297
	if (!empty($conf->global->MAIN_PHONE_SEPAR)) {
3298
		$separ = $conf->global->MAIN_PHONE_SEPAR;
3299
	}
3300
	if (empty($countrycode) && is_object($mysoc)) {
3301
		$countrycode = $mysoc->country_code;
3302
	}
3303
3304
	// Short format for small screens
3305
	if ($conf->dol_optimize_smallscreen) {
3306
		$separ = '';
3307
	}
3308
3309
	$newphone = $phone;
3310
	if (strtoupper($countrycode) == "FR") {
3311
		// France
3312
		if (dol_strlen($phone) == 10) {
3313
			$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);
3314
		} elseif (dol_strlen($phone) == 7) {
3315
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 2).$separ.substr($newphone, 5, 2);
3316
		} elseif (dol_strlen($phone) == 9) {
3317
			$newphone = substr($newphone, 0, 2).$separ.substr($newphone, 2, 3).$separ.substr($newphone, 5, 2).$separ.substr($newphone, 7, 2);
3318
		} elseif (dol_strlen($phone) == 11) {
3319
			$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);
3320
		} elseif (dol_strlen($phone) == 12) {
3321
			$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);
3322
		} elseif (dol_strlen($phone) == 13) {
3323
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 2).$separ.substr($newphone, 6, 2).$separ.substr($newphone, 8, 3).$separ.substr($newphone, 11, 2);
3324
		}
3325
	} elseif (strtoupper($countrycode) == "CA") {
3326
		if (dol_strlen($phone) == 10) {
3327
			$newphone = ($separ != '' ? '(' : '').substr($newphone, 0, 3).($separ != '' ? ')' : '').$separ.substr($newphone, 3, 3).($separ != '' ? '-' : '').substr($newphone, 6, 4);
3328
		}
3329
	} elseif (strtoupper($countrycode) == "PT") {//Portugal
3330
		if (dol_strlen($phone) == 13) {//ex: +351_ABC_DEF_GHI
3331
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
3332
		}
3333
	} elseif (strtoupper($countrycode) == "SR") {//Suriname
3334
		if (dol_strlen($phone) == 10) {//ex: +597_ABC_DEF
3335
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3);
3336
		} elseif (dol_strlen($phone) == 11) {//ex: +597_ABC_DEFG
3337
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 4);
3338
		}
3339
	} elseif (strtoupper($countrycode) == "DE") {//Allemagne
3340
		if (dol_strlen($phone) == 14) {//ex:  +49_ABCD_EFGH_IJK
3341
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 4).$separ.substr($newphone, 7, 4).$separ.substr($newphone, 11, 3);
3342
		} elseif (dol_strlen($phone) == 13) {//ex: +49_ABC_DEFG_HIJ
3343
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 4).$separ.substr($newphone, 10, 3);
3344
		}
3345
	} elseif (strtoupper($countrycode) == "ES") {//Espagne
3346
		if (dol_strlen($phone) == 12) {//ex:  +34_ABC_DEF_GHI
3347
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
3348
		}
3349
	} elseif (strtoupper($countrycode) == "BF") {// Burkina Faso
3350
		if (dol_strlen($phone) == 12) {//ex :  +22 A BC_DE_FG_HI
3351
			$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);
3352
		}
3353
	} elseif (strtoupper($countrycode) == "RO") {// Roumanie
3354
		if (dol_strlen($phone) == 12) {//ex :  +40 AB_CDE_FG_HI
3355
			$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);
3356
		}
3357
	} elseif (strtoupper($countrycode) == "TR") {//Turquie
3358
		if (dol_strlen($phone) == 13) {//ex :  +90 ABC_DEF_GHIJ
3359
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 4);
3360
		}
3361
	} elseif (strtoupper($countrycode) == "US") {//Etat-Unis
3362
		if (dol_strlen($phone) == 12) {//ex: +1 ABC_DEF_GHIJ
3363
			$newphone = substr($newphone, 0, 2).$separ.substr($newphone, 2, 3).$separ.substr($newphone, 5, 3).$separ.substr($newphone, 8, 4);
3364
		}
3365
	} elseif (strtoupper($countrycode) == "MX") {//Mexique
3366
		if (dol_strlen($phone) == 12) {//ex: +52 ABCD_EFG_HI
3367
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 4).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 2);
3368
		} elseif (dol_strlen($phone) == 11) {//ex: +52 AB_CD_EF_GH
3369
			$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);
3370
		} elseif (dol_strlen($phone) == 13) {//ex: +52 ABC_DEF_GHIJ
3371
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 4);
3372
		}
3373
	} elseif (strtoupper($countrycode) == "ML") {//Mali
3374
		if (dol_strlen($phone) == 12) {//ex: +223 AB_CD_EF_GH
3375
			$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);
3376
		}
3377
	} elseif (strtoupper($countrycode) == "TH") {//Thaïlande
3378
		if (dol_strlen($phone) == 11) {//ex: +66_ABC_DE_FGH
3379
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 2).$separ.substr($newphone, 8, 3);
3380
		} elseif (dol_strlen($phone) == 12) {//ex: +66_A_BCD_EF_GHI
3381
			$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);
3382
		}
3383
	} elseif (strtoupper($countrycode) == "MU") {
3384
		//Maurice
3385
		if (dol_strlen($phone) == 11) {//ex: +230_ABC_DE_FG
3386
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 2).$separ.substr($newphone, 9, 2);
3387
		} elseif (dol_strlen($phone) == 12) {//ex: +230_ABCD_EF_GH
3388
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 4).$separ.substr($newphone, 8, 2).$separ.substr($newphone, 10, 2);
3389
		}
3390
	} elseif (strtoupper($countrycode) == "ZA") {//Afrique du sud
3391
		if (dol_strlen($phone) == 12) {//ex: +27_AB_CDE_FG_HI
3392
			$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);
3393
		}
3394
	} elseif (strtoupper($countrycode) == "SY") {//Syrie
3395
		if (dol_strlen($phone) == 12) {//ex: +963_AB_CD_EF_GH
3396
			$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);
3397
		} elseif (dol_strlen($phone) == 13) {//ex: +963_AB_CD_EF_GHI
3398
			$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);
3399
		}
3400
	} elseif (strtoupper($countrycode) == "AE") {//Emirats Arabes Unis
3401
		if (dol_strlen($phone) == 12) {//ex: +971_ABC_DEF_GH
3402
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 2);
3403
		} elseif (dol_strlen($phone) == 13) {//ex: +971_ABC_DEF_GHI
3404
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
3405
		} elseif (dol_strlen($phone) == 14) {//ex: +971_ABC_DEF_GHIK
3406
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 4);
3407
		}
3408
	} elseif (strtoupper($countrycode) == "DZ") {//Algérie
3409
		if (dol_strlen($phone) == 13) {//ex: +213_ABC_DEF_GHI
3410
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 3).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
3411
		}
3412
	} elseif (strtoupper($countrycode) == "BE") {//Belgique
3413
		if (dol_strlen($phone) == 11) {//ex: +32_ABC_DE_FGH
3414
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 2).$separ.substr($newphone, 8, 3);
3415
		} elseif (dol_strlen($phone) == 12) {//ex: +32_ABC_DEF_GHI
3416
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
3417
		}
3418
	} elseif (strtoupper($countrycode) == "PF") {//Polynésie française
3419
		if (dol_strlen($phone) == 12) {//ex: +689_AB_CD_EF_GH
3420
			$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);
3421
		}
3422
	} elseif (strtoupper($countrycode) == "CO") {//Colombie
3423
		if (dol_strlen($phone) == 13) {//ex: +57_ABC_DEF_GH_IJ
3424
			$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);
3425
		}
3426
	} elseif (strtoupper($countrycode) == "JO") {//Jordanie
3427
		if (dol_strlen($phone) == 12) {//ex: +962_A_BCD_EF_GH
3428
			$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);
3429
		}
3430
	} elseif (strtoupper($countrycode) == "JM") {//Jamaïque
3431
		if (dol_strlen($newphone) == 12) {//ex: +1867_ABC_DEFG
3432
			$newphone = substr($newphone, 0, 5).$separ.substr($newphone, 5, 3).$separ.substr($newphone, 8, 4);
3433
		}
3434
	} elseif (strtoupper($countrycode) == "MG") {//Madagascar
3435
		if (dol_strlen($phone) == 13) {//ex: +261_AB_CD_EFG_HI
3436
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 2).$separ.substr($newphone, 6, 2).$separ.substr($newphone, 8, 3).$separ.substr($newphone, 11, 2);
3437
		}
3438
	} elseif (strtoupper($countrycode) == "GB") {//Royaume uni
3439
		if (dol_strlen($phone) == 13) {//ex: +44_ABCD_EFG_HIJ
3440
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 4).$separ.substr($newphone, 7, 3).$separ.substr($newphone, 10, 3);
3441
		}
3442
	} elseif (strtoupper($countrycode) == "CH") {//Suisse
3443
		if (dol_strlen($phone) == 12) {//ex: +41_AB_CDE_FG_HI
3444
			$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);
3445
		} elseif (dol_strlen($phone) == 15) {// +41_AB_CDE_FGH_IJKL
3446
			$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);
3447
		}
3448
	} elseif (strtoupper($countrycode) == "TN") {//Tunisie
3449
		if (dol_strlen($phone) == 12) {//ex: +216_AB_CDE_FGH
3450
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 2).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
3451
		}
3452
	} elseif (strtoupper($countrycode) == "GF") {//Guyane francaise
3453
		if (dol_strlen($phone) == 13) {//ex: +594_ABC_DE_FG_HI  (ABC=594 de nouveau)
3454
			$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);
3455
		}
3456
	} elseif (strtoupper($countrycode) == "GP") {//Guadeloupe
3457
		if (dol_strlen($phone) == 13) {//ex: +590_ABC_DE_FG_HI  (ABC=590 de nouveau)
3458
			$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);
3459
		}
3460
	} elseif (strtoupper($countrycode) == "MQ") {//Martinique
3461
		if (dol_strlen($phone) == 13) {//ex: +596_ABC_DE_FG_HI  (ABC=596 de nouveau)
3462
			$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);
3463
		}
3464
	} elseif (strtoupper($countrycode) == "IT") {//Italie
3465
		if (dol_strlen($phone) == 12) {//ex: +39_ABC_DEF_GHI
3466
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 3).$separ.substr($newphone, 6, 3).$separ.substr($newphone, 9, 3);
3467
		} elseif (dol_strlen($phone) == 13) {//ex: +39_ABC_DEF_GH_IJ
3468
			$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);
3469
		}
3470
	} elseif (strtoupper($countrycode) == "AU") {
3471
		//Australie
3472
		if (dol_strlen($phone) == 12) {
3473
			//ex: +61_A_BCDE_FGHI
3474
			$newphone = substr($newphone, 0, 3).$separ.substr($newphone, 3, 1).$separ.substr($newphone, 4, 4).$separ.substr($newphone, 8, 4);
3475
		}
3476
	} elseif (strtoupper($countrycode) == "LU") {
3477
		// Luxembourg
3478
		if (dol_strlen($phone) == 10) {// fixe 6 chiffres +352_AA_BB_CC
3479
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 2).$separ.substr($newphone, 6, 2).$separ.substr($newphone, 8, 2);
3480
		} elseif (dol_strlen($phone) == 11) {// fixe 7 chiffres +352_AA_BB_CC_D
3481
			$newphone = substr($newphone, 0, 4).$separ.substr($newphone, 4, 2).$separ.substr($newphone, 6, 2).$separ.substr($newphone, 8, 2).$separ.substr($newphone, 10, 1);
3482
		} elseif (dol_strlen($phone) == 12) {// fixe 8 chiffres +352_AA_BB_CC_DD
3483
			$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);
3484
		} elseif (dol_strlen($phone) == 13) {// mobile +352_AAA_BB_CC_DD
3485
			$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);
3486
		}
3487
	}
3488
	if (!empty($addlink)) {	// Link on phone number (+ link to add action if conf->global->AGENDA_ADDACTIONFORPHONE set)
3489
		if ($addlink == 'tel' || $conf->browser->layout == 'phone' || (isModEnabled('clicktodial') && !empty($conf->global->CLICKTODIAL_USE_TEL_LINK_ON_PHONE_NUMBERS))) {	// If phone or option for, we use link of phone
3490
			$newphoneform = $newphone;
3491
			$newphone = '<a href="tel:'.$phone.'"';
3492
			$newphone .= '>'.$newphoneform.'</a>';
3493
		} elseif (isModEnabled('clicktodial') && $addlink == 'AC_TEL') {		// If click to dial, we use click to dial url
3494
			if (empty($user->clicktodial_loaded)) {
3495
				$user->fetch_clicktodial();
3496
			}
3497
3498
			// Define urlmask
3499
			$urlmask = 'ErrorClickToDialModuleNotConfigured';
3500
			if (!empty($conf->global->CLICKTODIAL_URL)) {
3501
				$urlmask = $conf->global->CLICKTODIAL_URL;
3502
			}
3503
			if (!empty($user->clicktodial_url)) {
3504
				$urlmask = $user->clicktodial_url;
3505
			}
3506
3507
			$clicktodial_poste = (!empty($user->clicktodial_poste) ?urlencode($user->clicktodial_poste) : '');
3508
			$clicktodial_login = (!empty($user->clicktodial_login) ?urlencode($user->clicktodial_login) : '');
3509
			$clicktodial_password = (!empty($user->clicktodial_password) ?urlencode($user->clicktodial_password) : '');
3510
			// This line is for backward compatibility
3511
			$url = sprintf($urlmask, urlencode($phone), $clicktodial_poste, $clicktodial_login, $clicktodial_password);
3512
			// Thoose lines are for substitution
3513
			$substitarray = array('__PHONEFROM__'=>$clicktodial_poste,
3514
								'__PHONETO__'=>urlencode($phone),
3515
								'__LOGIN__'=>$clicktodial_login,
3516
								'__PASS__'=>$clicktodial_password);
3517
			$url = make_substitutions($url, $substitarray);
3518
			$newphonesav = $newphone;
3519
			if (empty($conf->global->CLICKTODIAL_DO_NOT_USE_AJAX_CALL)) {
3520
				// Default and recommended: New method using ajax without submiting a page making a javascript history.go(-1) back
3521
				$newphone = '<a href="'.$url.'" class="cssforclicktodial"';	// Call of ajax is handled by the lib_foot.js.php on class 'cssforclicktodial'
3522
				$newphone .= '>'.$newphonesav.'</a>';
3523
			} else {
3524
				// Old method
3525
				$newphone = '<a href="'.$url.'"';
3526
				if (!empty($conf->global->CLICKTODIAL_FORCENEWTARGET)) {
3527
					$newphone .= ' target="_blank" rel="noopener noreferrer"';
3528
				}
3529
				$newphone .= '>'.$newphonesav.'</a>';
3530
			}
3531
		}
3532
3533
		//if (($cid || $socid) && !empty($conf->agenda->enabled) && $user->rights->agenda->myactions->create)
3534
		if (isModEnabled('agenda') && $user->hasRight("agenda", "myactions", "create")) {
3535
			$type = 'AC_TEL';
3536
			$link = '';
3537
			if ($addlink == 'AC_FAX') {
3538
				$type = 'AC_FAX';
3539
			}
3540
			if (!empty($conf->global->AGENDA_ADDACTIONFORPHONE)) {
3541
				$link = '<a href="'.DOL_URL_ROOT.'/comm/action/card.php?action=create&amp;backtopage='. urlencode($_SERVER['REQUEST_URI']) .'&amp;actioncode='.$type.($cid ? '&amp;contactid='.$cid : '').($socid ? '&amp;socid='.$socid : '').'">'.img_object($langs->trans("AddAction"), "calendar").'</a>';
3542
			}
3543
			if ($link) {
3544
				$newphone = '<div>'.$newphone.' '.$link.'</div>';
3545
			}
3546
		}
3547
	}
3548
3549
	if (empty($titlealt)) {
3550
		$titlealt = ($withpicto == 'fax' ? $langs->trans("Fax") : $langs->trans("Phone"));
3551
	}
3552
	$rep = '';
3553
3554
	if ($hookmanager) {
3555
		$parameters = array('countrycode' => $countrycode, 'cid' => $cid, 'socid' => $socid, 'titlealt' => $titlealt, 'picto' => $withpicto);
3556
		$reshook = $hookmanager->executeHooks('printPhone', $parameters, $phone);
3557
		$rep .= $hookmanager->resPrint;
3558
	}
3559
	if (empty($reshook)) {
3560
		$picto = '';
3561
		if ($withpicto) {
3562
			if ($withpicto == 'fax') {
3563
				$picto = 'phoning_fax';
3564
			} elseif ($withpicto == 'phone') {
3565
				$picto = 'phoning';
3566
			} elseif ($withpicto == 'mobile') {
3567
				$picto = 'phoning_mobile';
3568
			} else {
3569
				$picto = '';
3570
			}
3571
		}
3572
		if ($adddivfloat) {
3573
			$rep .= '<div class="nospan float" style="margin-right: 10px">';
3574
		} else {
3575
			$rep .= '<span style="margin-right: 10px;">';
3576
		}
3577
		$rep .= ($withpicto ?img_picto($titlealt, 'object_'.$picto.'.png').' ' : '').$newphone;
3578
		if ($adddivfloat) {
3579
			$rep .= '</div>';
3580
		} else {
3581
			$rep .= '</span>';
3582
		}
3583
	}
3584
3585
	return $rep;
3586
}
3587
3588
/**
3589
 * 	Return an IP formated to be shown on screen
3590
 *
3591
 * 	@param	string	$ip			IP
3592
 * 	@param	int		$mode		0=return IP + country/flag, 1=return only country/flag, 2=return only IP
3593
 * 	@return string 				Formated IP, with country if GeoIP module is enabled
3594
 */
3595
function dol_print_ip($ip, $mode = 0)
3596
{
3597
	global $conf, $langs;
3598
3599
	$ret = '';
3600
3601
	if (empty($mode)) {
3602
		$ret .= $ip;
3603
	}
3604
3605
	if ($mode != 2) {
3606
		$countrycode = dolGetCountryCodeFromIp($ip);
3607
		if ($countrycode) {	// If success, countrycode is us, fr, ...
3608
			if (file_exists(DOL_DOCUMENT_ROOT.'/theme/common/flags/'.$countrycode.'.png')) {
3609
				$ret .= ' '.img_picto($countrycode.' '.$langs->trans("AccordingToGeoIPDatabase"), DOL_URL_ROOT.'/theme/common/flags/'.$countrycode.'.png', '', 1);
3610
			} else {
3611
				$ret .= ' ('.$countrycode.')';
3612
			}
3613
		} else {
3614
			// Nothing
3615
		}
3616
	}
3617
3618
	return $ret;
3619
}
3620
3621
/**
3622
 * Return the IP of remote user.
3623
 * Take HTTP_X_FORWARDED_FOR (defined when using proxy)
3624
 * Then HTTP_CLIENT_IP if defined (rare)
3625
 * Then REMOTE_ADDR (no way to be modified by user but may be wrong if user is using a proxy)
3626
 *
3627
 * @return	string		Ip of remote user.
3628
 */
3629
function getUserRemoteIP()
3630
{
3631
	if (empty($_SERVER['HTTP_X_FORWARDED_FOR']) || preg_match('/[^0-9\.\:,\[\]]/', $_SERVER['HTTP_X_FORWARDED_FOR'])) {
3632
		if (empty($_SERVER['HTTP_CLIENT_IP']) || preg_match('/[^0-9\.\:,\[\]]/', $_SERVER['HTTP_CLIENT_IP'])) {
3633
			if (empty($_SERVER["HTTP_CF_CONNECTING_IP"])) {
3634
				$ip = (empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR']);	// value may have been the IP of the proxy and not the client
3635
			} else {
3636
				$ip = $_SERVER["HTTP_CF_CONNECTING_IP"];	// value here may have been forged by client
3637
			}
3638
		} else {
3639
			$ip = $_SERVER['HTTP_CLIENT_IP']; // value is clean here but may have been forged by proxy
3640
		}
3641
	} else {
3642
		$ip = $_SERVER['HTTP_X_FORWARDED_FOR']; // value is clean here but may have been forged by proxy
3643
	}
3644
	return $ip;
3645
}
3646
3647
/**
3648
 * Return if we are using a HTTPS connexion
3649
 * Check HTTPS (no way to be modified by user but may be empty or wrong if user is using a proxy)
3650
 * Take HTTP_X_FORWARDED_PROTO (defined when using proxy)
3651
 * Then HTTP_X_FORWARDED_SSL
3652
 *
3653
 * @return	boolean		True if user is using HTTPS
3654
 */
3655
function isHTTPS()
3656
{
3657
	$isSecure = false;
3658
	if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
3659
		$isSecure = true;
3660
	} elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' || !empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on') {
3661
		$isSecure = true;
3662
	}
3663
	return $isSecure;
3664
}
3665
3666
/**
3667
 * 	Return a country code from IP. Empty string if not found.
3668
 *
3669
 * 	@param	string	$ip			IP
3670
 * 	@return string 				Country code ('us', 'fr', ...)
3671
 */
3672
function dolGetCountryCodeFromIp($ip)
3673
{
3674
	global $conf;
3675
3676
	$countrycode = '';
3677
3678
	if (!empty($conf->geoipmaxmind->enabled)) {
3679
		$datafile = getDolGlobalString('GEOIPMAXMIND_COUNTRY_DATAFILE');
3680
		//$ip='24.24.24.24';
3681
		//$datafile='/usr/share/GeoIP/GeoIP.dat';    Note that this must be downloaded datafile (not same than datafile provided with ubuntu packages)
3682
		include_once DOL_DOCUMENT_ROOT.'/core/class/dolgeoip.class.php';
3683
		$geoip = new DolGeoIP('country', $datafile);
3684
		//print 'ip='.$ip.' databaseType='.$geoip->gi->databaseType." GEOIP_CITY_EDITION_REV1=".GEOIP_CITY_EDITION_REV1."\n";
3685
		$countrycode = $geoip->getCountryCodeFromIP($ip);
3686
	}
3687
3688
	return $countrycode;
3689
}
3690
3691
3692
/**
3693
 *  Return country code for current user.
3694
 *  If software is used inside a local network, detection may fails (we need a public ip)
3695
 *
3696
 *  @return     string      Country code (fr, es, it, us, ...)
3697
 */
3698
function dol_user_country()
3699
{
3700
	global $conf, $langs, $user;
3701
3702
	//$ret=$user->xxx;
3703
	$ret = '';
3704
	if (!empty($conf->geoipmaxmind->enabled)) {
3705
		$ip = getUserRemoteIP();
3706
		$datafile = getDolGlobalString('GEOIPMAXMIND_COUNTRY_DATAFILE');
3707
		//$ip='24.24.24.24';
3708
		//$datafile='E:\Mes Sites\Web\Admin1\awstats\maxmind\GeoIP.dat';
3709
		include_once DOL_DOCUMENT_ROOT.'/core/class/dolgeoip.class.php';
3710
		$geoip = new DolGeoIP('country', $datafile);
3711
		$countrycode = $geoip->getCountryCodeFromIP($ip);
3712
		$ret = $countrycode;
3713
	}
3714
	return $ret;
3715
}
3716
3717
/**
3718
 *  Format address string
3719
 *
3720
 *  @param	string	$address    Address string, already formatted with dol_format_address()
3721
 *  @param  int		$htmlid     Html ID (for example 'gmap')
3722
 *  @param  int		$element    'thirdparty'|'contact'|'member'|'other'
3723
 *  @param  int		$id         Id of object
3724
 *  @param	int		$noprint	No output. Result is the function return
3725
 *  @param  string  $charfornl  Char to use instead of nl2br. '' means we use a standad nl2br.
3726
 *  @return string|void			Nothing if noprint is 0, formatted address if noprint is 1
3727
 *  @see dol_format_address()
3728
 */
3729
function dol_print_address($address, $htmlid, $element, $id, $noprint = 0, $charfornl = '')
3730
{
3731
	global $conf, $user, $langs, $hookmanager;
3732
3733
	$out = '';
3734
3735
	if ($address) {
3736
		if ($hookmanager) {
3737
			$parameters = array('element' => $element, 'id' => $id);
3738
			$reshook = $hookmanager->executeHooks('printAddress', $parameters, $address);
3739
			$out .= $hookmanager->resPrint;
3740
		}
3741
		if (empty($reshook)) {
3742
			if (empty($charfornl)) {
3743
				$out .= nl2br($address);
3744
			} else {
3745
				$out .= preg_replace('/[\r\n]+/', $charfornl, $address);
3746
			}
3747
3748
			// TODO Remove this block, we can add this using the hook now
3749
			$showgmap = $showomap = 0;
3750
			if (($element == 'thirdparty' || $element == 'societe') && !empty($conf->google->enabled) && !empty($conf->global->GOOGLE_ENABLE_GMAPS)) {
3751
				$showgmap = 1;
3752
			}
3753
			if ($element == 'contact' && !empty($conf->google->enabled) && !empty($conf->global->GOOGLE_ENABLE_GMAPS_CONTACTS)) {
3754
				$showgmap = 1;
3755
			}
3756
			if ($element == 'member' && !empty($conf->google->enabled) && !empty($conf->global->GOOGLE_ENABLE_GMAPS_MEMBERS)) {
3757
				$showgmap = 1;
3758
			}
3759
			if (($element == 'thirdparty' || $element == 'societe') && !empty($conf->openstreetmap->enabled) && !empty($conf->global->OPENSTREETMAP_ENABLE_MAPS)) {
3760
				$showomap = 1;
3761
			}
3762
			if ($element == 'contact' && !empty($conf->openstreetmap->enabled) && !empty($conf->global->OPENSTREETMAP_ENABLE_MAPS_CONTACTS)) {
3763
				$showomap = 1;
3764
			}
3765
			if ($element == 'member' && !empty($conf->openstreetmap->enabled) && !empty($conf->global->OPENSTREETMAP_ENABLE_MAPS_MEMBERS)) {
3766
				$showomap = 1;
3767
			}
3768
			if ($showgmap) {
3769
				$url = dol_buildpath('/google/gmaps.php?mode='.$element.'&id='.$id, 1);
3770
				$out .= ' <a href="'.$url.'" target="_gmaps"><img id="'.$htmlid.'" class="valigntextbottom" src="'.DOL_URL_ROOT.'/theme/common/gmap.png"></a>';
3771
			}
3772
			if ($showomap) {
3773
				$url = dol_buildpath('/openstreetmap/maps.php?mode='.$element.'&id='.$id, 1);
3774
				$out .= ' <a href="'.$url.'" target="_gmaps"><img id="'.$htmlid.'_openstreetmap" class="valigntextbottom" src="'.DOL_URL_ROOT.'/theme/common/gmap.png"></a>';
3775
			}
3776
		}
3777
	}
3778
	if ($noprint) {
3779
		return $out;
3780
	} else {
3781
		print $out;
3782
	}
3783
}
3784
3785
3786
/**
3787
 *	Return true if email syntax is ok.
3788
 *
3789
 *	@param	    string		$address    			email (Ex: "[email protected]". Long form "John Do <[email protected]>" will be false)
3790
 *  @param		int			$acceptsupervisorkey	If 1, the special string '__SUPERVISOREMAIL__' is also accepted as valid
3791
 *  @param		int			$acceptuserkey			If 1, the special string '__USER_EMAIL__' is also accepted as valid
3792
 *	@return     boolean     						true if email syntax is OK, false if KO or empty string
3793
 *  @see isValidMXRecord()
3794
 */
3795
function isValidEmail($address, $acceptsupervisorkey = 0, $acceptuserkey = 0)
3796
{
3797
	if ($acceptsupervisorkey && $address == '__SUPERVISOREMAIL__') {
3798
		return true;
3799
	}
3800
	if ($acceptuserkey && $address == '__USER_EMAIL__') {
3801
		return true;
3802
	}
3803
	if (filter_var($address, FILTER_VALIDATE_EMAIL)) {
3804
		return true;
3805
	}
3806
3807
	return false;
3808
}
3809
3810
/**
3811
 *	Return if the domain name has a valid MX record.
3812
 *  WARNING: This need function idn_to_ascii, checkdnsrr and getmxrr
3813
 *
3814
 *	@param	    string		$domain	    			Domain name (Ex: "yahoo.com", "yhaoo.com", "dolibarr.fr")
3815
 *	@return     int     							-1 if error (function not available), 0=Not valid, 1=Valid
3816
 *  @see isValidEmail()
3817
 */
3818
function isValidMXRecord($domain)
3819
{
3820
	if (function_exists('idn_to_ascii') && function_exists('checkdnsrr')) {
3821
		if (!checkdnsrr(idn_to_ascii($domain), 'MX')) {
3822
			return 0;
3823
		}
3824
		if (function_exists('getmxrr')) {
3825
			$mxhosts = array();
3826
			$weight = array();
3827
			getmxrr(idn_to_ascii($domain), $mxhosts, $weight);
3828
			if (count($mxhosts) > 1) {
3829
				return 1;
3830
			}
3831
			if (count($mxhosts) == 1 && !empty($mxhosts[0])) {
3832
				return 1;
3833
			}
3834
3835
			return 0;
3836
		}
3837
	}
3838
3839
	// function idn_to_ascii or checkdnsrr or getmxrr does not exists
3840
	return -1;
3841
}
3842
3843
/**
3844
 *  Return true if phone number syntax is ok
3845
 *  TODO Decide what to do with this
3846
 *
3847
 *  @param	string		$phone		phone (Ex: "0601010101")
3848
 *  @return boolean     			true if phone syntax is OK, false if KO or empty string
3849
 */
3850
function isValidPhone($phone)
3851
{
3852
	return true;
3853
}
3854
3855
3856
/**
3857
 * Return first letters of a strings.
3858
 * Example with nbofchar=1: 'ghi' will return 'g' but 'abc def' will return 'ad'
3859
 * Example with nbofchar=2: 'ghi' will return 'gh' but 'abc def' will return 'abde'
3860
 *
3861
 * @param	string	$s				String to truncate
3862
 * @param 	int		$nbofchar		Nb of characters to keep
3863
 * @return	string					Return first chars.
3864
 */
3865
function dolGetFirstLetters($s, $nbofchar = 1)
3866
{
3867
	$ret = '';
3868
	$tmparray = explode(' ', $s);
3869
	foreach ($tmparray as $tmps) {
3870
		$ret .= dol_substr($tmps, 0, $nbofchar);
3871
	}
3872
3873
	return $ret;
3874
}
3875
3876
3877
/**
3878
 * Make a strlen call. Works even if mbstring module not enabled
3879
 *
3880
 * @param   string		$string				String to calculate length
3881
 * @param   string		$stringencoding		Encoding of string
3882
 * @return  int								Length of string
3883
 */
3884
function dol_strlen($string, $stringencoding = 'UTF-8')
3885
{
3886
	if (is_null($string)) {
0 ignored issues
show
introduced by
The condition is_null($string) is always false.
Loading history...
3887
		return 0;
3888
	}
3889
3890
	if (function_exists('mb_strlen')) {
3891
		return mb_strlen($string, $stringencoding);
3892
	} else {
3893
		return strlen($string);
3894
	}
3895
}
3896
3897
/**
3898
 * Make a substring. Works even if mbstring module is not enabled for better compatibility.
3899
 *
3900
 * @param	string		$string				String to scan
3901
 * @param	string		$start				Start position
3902
 * @param	int|null	$length				Length (in nb of characters or nb of bytes depending on trunconbytes param)
3903
 * @param   string		$stringencoding		Page code used for input string encoding
3904
 * @param	int			$trunconbytes		1=Length is max of bytes instead of max of characters
3905
 * @return  string							substring
3906
 */
3907
function dol_substr($string, $start, $length = null, $stringencoding = '', $trunconbytes = 0)
3908
{
3909
	global $langs;
3910
3911
	if (empty($stringencoding)) {
3912
		$stringencoding = $langs->charset_output;
3913
	}
3914
3915
	$ret = '';
3916
	if (empty($trunconbytes)) {
3917
		if (function_exists('mb_substr')) {
3918
			$ret = mb_substr($string, $start, $length, $stringencoding);
3919
		} else {
3920
			$ret = substr($string, $start, $length);
3921
		}
3922
	} else {
3923
		if (function_exists('mb_strcut')) {
3924
			$ret = mb_strcut($string, $start, $length, $stringencoding);
3925
		} else {
3926
			$ret = substr($string, $start, $length);
3927
		}
3928
	}
3929
	return $ret;
3930
}
3931
3932
3933
/**
3934
 *	Truncate a string to a particular length adding '…' if string larger than length.
3935
 * 	If length = max length+1, we do no truncate to avoid having just 1 char replaced with '…'.
3936
 *  MAIN_DISABLE_TRUNC=1 can disable all truncings
3937
 *
3938
 *	@param	string	$string				String to truncate
3939
 *	@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 so it does not worse to replace with ...)
3940
 *	@param	string	$trunc				Where to trunc: 'right', 'left', 'middle' (size must be a 2 power), 'wrap'
3941
 * 	@param	string	$stringencoding		Tell what is source string encoding
3942
 *  @param	int		$nodot				Truncation do not add … after truncation. So it's an exact truncation.
3943
 *  @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)
3944
 *	@return string						Truncated string. WARNING: length is never higher than $size if $nodot is set, but can be 3 chars higher otherwise.
3945
 */
3946
function dol_trunc($string, $size = 40, $trunc = 'right', $stringencoding = 'UTF-8', $nodot = 0, $display = 0)
3947
{
3948
	global $conf;
3949
3950
	if (empty($size) || !empty($conf->global->MAIN_DISABLE_TRUNC)) {
3951
		return $string;
3952
	}
3953
3954
	if (empty($stringencoding)) {
3955
		$stringencoding = 'UTF-8';
3956
	}
3957
	// reduce for small screen
3958
	if ($conf->dol_optimize_smallscreen == 1 && $display == 1) {
3959
		$size = round($size / 3);
3960
	}
3961
3962
	// We go always here
3963
	if ($trunc == 'right') {
3964
		$newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
3965
		if (dol_strlen($newstring, $stringencoding) > ($size + ($nodot ? 0 : 1))) {
3966
			// If nodot is 0 and size is 1 chars more, we don't trunc and don't add …
3967
			return dol_substr($newstring, 0, $size, $stringencoding).($nodot ? '' : '…');
3968
		} else {
3969
			//return 'u'.$size.'-'.$newstring.'-'.dol_strlen($newstring,$stringencoding).'-'.$string;
3970
			return $string;
3971
		}
3972
	} elseif ($trunc == 'middle') {
3973
		$newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
3974
		if (dol_strlen($newstring, $stringencoding) > 2 && dol_strlen($newstring, $stringencoding) > ($size + 1)) {
3975
			$size1 = round($size / 2);
3976
			$size2 = round($size / 2);
3977
			return dol_substr($newstring, 0, $size1, $stringencoding).'…'.dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size2, $size2, $stringencoding);
3978
		} else {
3979
			return $string;
3980
		}
3981
	} elseif ($trunc == 'left') {
3982
		$newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
3983
		if (dol_strlen($newstring, $stringencoding) > ($size + ($nodot ? 0 : 1))) {
3984
			// If nodot is 0 and size is 1 chars more, we don't trunc and don't add …
3985
			return '…'.dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size, $size, $stringencoding);
3986
		} else {
3987
			return $string;
3988
		}
3989
	} elseif ($trunc == 'wrap') {
3990
		$newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
3991
		if (dol_strlen($newstring, $stringencoding) > ($size + 1)) {
3992
			return dol_substr($newstring, 0, $size, $stringencoding)."\n".dol_trunc(dol_substr($newstring, $size, dol_strlen($newstring, $stringencoding) - $size, $stringencoding), $size, $trunc);
3993
		} else {
3994
			return $string;
3995
		}
3996
	} else {
3997
		return 'BadParam3CallingDolTrunc';
3998
	}
3999
}
4000
4001
/**
4002
 *	Show picto whatever it's its name (generic function)
4003
 *
4004
 *	@param      string		$titlealt         		Text on title tag for tooltip. Not used if param notitle is set to 1.
4005
 *	@param      string		$picto       			Name of image file to show ('filenew', ...)
4006
 *													If no extension provided, we use '.png'. Image must be stored into theme/xxx/img directory.
4007
 *                                  				Example: picto.png                  if picto.png is stored into htdocs/theme/mytheme/img
4008
 *                                  				Example: picto.png@mymodule         if picto.png is stored into htdocs/mymodule/img
4009
 *                                  				Example: /mydir/mysubdir/picto.png  if picto.png is stored into htdocs/mydir/mysubdir (pictoisfullpath must be set to 1)
4010
 *                                                  Example: fontawesome_envelope-open-text_fas_red_1em if you want to use fontaweseome icons: fontawesome_<icon-name>_<style>_<color>_<size> (only icon-name is mandatory)
4011
 *	@param		string		$moreatt				Add more attribute on img tag (For example 'class="pictofixedwidth"')
4012
 *	@param		boolean|int	$pictoisfullpath		If true or 1, image path is a full path
4013
 *	@param		int			$srconly				Return only content of the src attribute of img.
4014
 *  @param		int			$notitle				1=Disable tag title. Use it if you add js tooltip, to avoid duplicate tooltip.
4015
 *  @param		string		$alt					Force alt for bind people
4016
 *  @param		string		$morecss				Add more class css on img tag (For example 'myclascss').
4017
 *  @param		string		$marginleftonlyshort	1 = Add a short left margin on picto, 2 = Add a larger left margin on picto, 0 = No margin left. Works for fontawesome picto only.
4018
 *  @return     string       				    	Return img tag
4019
 *  @see        img_object(), img_picto_common()
4020
 */
4021
function img_picto($titlealt, $picto, $moreatt = '', $pictoisfullpath = false, $srconly = 0, $notitle = 0, $alt = '', $morecss = '', $marginleftonlyshort = 2)
4022
{
4023
	global $conf, $langs;
4024
4025
	// We forge fullpathpicto for image to $path/img/$picto. By default, we take DOL_URL_ROOT/theme/$conf->theme/img/$picto
4026
	$url = DOL_URL_ROOT;
4027
	$theme = isset($conf->theme) ? $conf->theme : null;
4028
	$path = 'theme/'.$theme;
4029
	// Define fullpathpicto to use into src
4030
	if ($pictoisfullpath) {
4031
		// Clean parameters
4032
		if (!preg_match('/(\.png|\.gif|\.svg)$/i', $picto)) {
4033
			$picto .= '.png';
4034
		}
4035
		$fullpathpicto = $picto;
4036
		$reg = array();
4037
		if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
4038
			$morecss .= ($morecss ? ' ' : '').$reg[1];
4039
			$moreatt = str_replace('class="'.$reg[1].'"', '', $moreatt);
4040
		}
4041
	} else {
4042
		$pictowithouttext = preg_replace('/(\.png|\.gif|\.svg)$/', '', $picto);
4043
		$pictowithouttext = str_replace('object_', '', $pictowithouttext);
4044
4045
		if (strpos($pictowithouttext, 'fontawesome_') !== false || preg_match('/^fa-/', $pictowithouttext)) {
4046
			// This is a font awesome image 'fonwtawesome_xxx' or 'fa-xxx'
4047
			$pictowithouttext = str_replace('fontawesome_', '', $pictowithouttext);
4048
			$pictowithouttext = str_replace('fa-', '', $pictowithouttext);
4049
4050
			$pictowithouttextarray = explode('_', $pictowithouttext);
4051
			$marginleftonlyshort = 0;
4052
4053
			if (!empty($pictowithouttextarray[1])) {
4054
				// Syntax is 'fontawesome_fakey_faprefix_facolor_fasize' or 'fa-fakey_faprefix_facolor_fasize'
4055
				$fakey      = 'fa-'.$pictowithouttextarray[0];
4056
				$fa         = empty($pictowithouttextarray[1]) ? 'fa' : $pictowithouttextarray[1];
4057
				$facolor    = empty($pictowithouttextarray[2]) ? '' : $pictowithouttextarray[2];
4058
				$fasize     = empty($pictowithouttextarray[3]) ? '' : $pictowithouttextarray[3];
4059
			} else {
4060
				$fakey      = 'fa-'.$pictowithouttext;
4061
				$fa         = 'fa';
4062
				$facolor    = '';
4063
				$fasize     = '';
4064
			}
4065
4066
			// This snippet only needed since function img_edit accepts only one additional parameter: no separate one for css only.
4067
			// class/style need to be extracted to avoid duplicate class/style validation errors when $moreatt is added to the end of the attributes.
4068
			$morestyle = '';
4069
			$reg = array();
4070
			if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
4071
				$morecss .= ($morecss ? ' ' : '').$reg[1];
4072
				$moreatt = str_replace('class="'.$reg[1].'"', '', $moreatt);
4073
			}
4074
			if (preg_match('/style="([^"]+)"/', $moreatt, $reg)) {
4075
				$morestyle = $reg[1];
4076
				$moreatt = str_replace('style="'.$reg[1].'"', '', $moreatt);
4077
			}
4078
			$moreatt = trim($moreatt);
4079
4080
			$enabledisablehtml = '<span class="'.$fa.' '.$fakey.($marginleftonlyshort ? ($marginleftonlyshort == 1 ? ' marginleftonlyshort' : ' marginleftonly') : '');
0 ignored issues
show
introduced by
The condition $marginleftonlyshort == 1 is always false.
Loading history...
4081
			$enabledisablehtml .= ($morecss ? ' '.$morecss : '').'" style="'.($fasize ? ('font-size: '.$fasize.';') : '').($facolor ? (' color: '.$facolor.';') : '').($morestyle ? ' '.$morestyle : '').'"'.(($notitle || empty($titlealt)) ? '' : ' title="'.dol_escape_htmltag($titlealt).'"').($moreatt ? ' '.$moreatt : '').'>';
4082
			/*if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
4083
				$enabledisablehtml .= $titlealt;
4084
			}*/
4085
			$enabledisablehtml .= '</span>';
4086
4087
			return $enabledisablehtml;
4088
		}
4089
4090
		if (empty($srconly) && in_array($pictowithouttext, array(
4091
				'1downarrow', '1uparrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected',
4092
				'accountancy', 'accounting_account', 'account', 'accountline', 'action', 'add', 'address', 'angle-double-down', 'angle-double-up', 'asset',
4093
				'bank_account', 'barcode', 'bank', 'bell', 'bill', 'billa', 'billr', 'billd', 'bookmark', 'bom', 'briefcase-medical', 'bug', 'building',
4094
				'card', 'calendarlist', 'calendar', 'calendarmonth', 'calendarweek', 'calendarday', 'calendarperuser', 'calendarpertype',
4095
				'cash-register', 'category', 'chart', 'check', 'clock', 'close_title', 'cog', 'collab', 'company', 'contact', 'country', 'contract', 'conversation', 'cron', 'cubes',
4096
				'currency', 'multicurrency',
4097
				'delete', 'dolly', 'dollyrevert', 'donation', 'download', 'dynamicprice',
4098
				'edit', 'ellipsis-h', 'email', 'entity', 'envelope', 'eraser', 'establishment', 'expensereport', 'external-link-alt', 'external-link-square-alt', 'eye',
4099
				'filter', 'file-code', 'file-export', 'file-import', 'file-upload', 'autofill', 'folder', 'folder-open', 'folder-plus',
4100
				'gears', 'generate', 'globe', 'globe-americas', 'graph', 'grip', 'grip_title', 'group',
4101
				'help', 'holiday',
4102
				'id-card', 'images', 'incoterm', 'info', 'intervention', 'inventory', 'intracommreport', 'jobprofile',
4103
				'knowledgemanagement',
4104
				'label', 'language', 'line', 'link', 'list', 'list-alt', 'listlight', 'loan', 'lock', 'lot', 'long-arrow-alt-right',
4105
				'margin', 'map-marker-alt', 'member', 'meeting', 'money-bill-alt', 'movement', 'mrp', 'note', 'next',
4106
				'off', 'on', 'order',
4107
				'paiment', 'paragraph', 'play', 'pdf', 'phone', 'phoning', 'phoning_mobile', 'phoning_fax', 'playdisabled', 'previous', 'poll', 'pos', 'printer', 'product', 'propal', 'proposal', 'puce',
4108
				'stock', 'resize', 'service', 'stats', 'trip',
4109
				'security', 'setup', 'share-alt', 'sign-out', 'split', 'stripe', 'stripe-s', 'switch_off', 'switch_on', 'switch_on_red', 'tools', 'unlink', 'uparrow', 'user', 'user-tie', 'vcard', 'wrench',
4110
				'github', 'google', 'jabber', 'microsoft', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'youtube', 'google-plus-g', 'whatsapp',
4111
				'chevron-left', 'chevron-right', 'chevron-down', 'chevron-top', 'commercial', 'companies',
4112
				'generic', 'home', 'hrm', 'members', 'products', 'invoicing',
4113
				'partnership', 'payment', 'payment_vat', 'pencil-ruler', 'preview', 'project', 'projectpub', 'projecttask', 'question', 'refresh', 'region',
4114
				'salary', 'shipment', 'state', 'supplier_invoice', 'supplier_invoicea', 'supplier_invoicer', 'supplier_invoiced',
4115
				'technic', 'ticket',
4116
				'error', 'warning',
4117
				'recent', 'reception', 'recruitmentcandidature', 'recruitmentjobposition', 'replacement', 'resource', 'recurring','rss',
4118
				'shapes', 'skill', 'square', 'stop-circle', 'supplier', 'supplier_proposal', 'supplier_order', 'supplier_invoice',
4119
				'timespent', 'title_setup', 'title_accountancy', 'title_bank', 'title_hrm', 'title_agenda',
4120
				'uncheck', 'url', 'user-cog', 'user-injured', 'user-md', 'vat', 'website', 'workstation', 'webhook', 'world', 'private',
4121
				'conferenceorbooth', 'eventorganization',
4122
				'stamp', 'signature'
4123
			))) {
4124
			$fakey = $pictowithouttext;
4125
			$facolor = '';
4126
			$fasize = '';
4127
			$fa = 'fas';
4128
			if (in_array($pictowithouttext, array('card', 'bell', 'clock', 'establishment', 'generic', 'minus-square', 'object_generic', 'pdf', 'plus-square', 'timespent', 'note', 'off', 'on', 'object_bookmark', 'bookmark', 'vcard'))) {
4129
				$fa = 'far';
4130
			}
4131
			if (in_array($pictowithouttext, array('black-tie', 'github', 'google', 'microsoft', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'stripe', 'stripe-s', 'youtube', 'google-plus-g', 'whatsapp'))) {
4132
				$fa = 'fab';
4133
			}
4134
4135
			$arrayconvpictotofa = array(
4136
				'account'=>'university', 'accounting_account'=>'clipboard-list', 'accountline'=>'receipt', 'accountancy'=>'search-dollar', 'action'=>'calendar-alt', 'add'=>'plus-circle', 'address'=> 'address-book', 'asset'=>'money-check-alt', 'autofill'=>'fill',
4137
				'bank_account'=>'university',
4138
				'bill'=>'file-invoice-dollar', 'billa'=>'file-excel', 'billr'=>'file-invoice-dollar', 'billd'=>'file-medical',
4139
				'supplier_invoice'=>'file-invoice-dollar', 'supplier_invoicea'=>'file-excel', 'supplier_invoicer'=>'file-invoice-dollar', 'supplier_invoiced'=>'file-medical',
4140
				'bom'=>'shapes',
4141
				'card'=>'address-card', 'chart'=>'chart-line', 'company'=>'building', 'contact'=>'address-book', 'contract'=>'suitcase', 'collab'=>'people-arrows', 'conversation'=>'comments', 'country'=>'globe-americas', 'cron'=>'business-time',
4142
				'donation'=>'file-alt', 'dynamicprice'=>'hand-holding-usd',
4143
				'setup'=>'cog', 'companies'=>'building', 'products'=>'cube', 'commercial'=>'suitcase', 'invoicing'=>'coins',
4144
				'accounting'=>'search-dollar', 'category'=>'tag', 'dollyrevert'=>'dolly',
4145
				'generate'=>'plus-square', 'hrm'=>'user-tie', 'incoterm'=>'truck-loading',
4146
				'margin'=>'calculator', 'members'=>'user-friends', 'ticket'=>'ticket-alt', 'globe'=>'external-link-alt', 'lot'=>'barcode',
4147
				'email'=>'at', 'establishment'=>'building', 'edit'=>'pencil-alt', 'entity'=>'globe',
4148
				'graph'=>'chart-line', 'grip_title'=>'arrows-alt', 'grip'=>'arrows-alt', 'help'=>'question-circle',
4149
				'generic'=>'file', 'holiday'=>'umbrella-beach',
4150
				'info'=>'info-circle', 'inventory'=>'boxes', 'intracommreport'=>'globe-europe', 'jobprofile'=>'cogs',
4151
				'knowledgemanagement'=>'ticket-alt', 'label'=>'layer-group', 'line'=>'bars', 'loan'=>'money-bill-alt',
4152
				'member'=>'user-alt', 'meeting'=>'chalkboard-teacher', 'mrp'=>'cubes', 'next'=>'arrow-alt-circle-right',
4153
				'trip'=>'wallet', 'expensereport'=>'wallet', 'group'=>'users', 'movement'=>'people-carry',
4154
				'sign-out'=>'sign-out-alt',
4155
				'switch_off'=>'toggle-off', 'switch_on'=>'toggle-on', 'switch_on_red'=>'toggle-on', 'check'=>'check', 'bookmark'=>'star',
4156
				'bank'=>'university', 'close_title'=>'times', 'delete'=>'trash', 'filter'=>'filter',
4157
				'list-alt'=>'list-alt', 'calendarlist'=>'bars', 'calendar'=>'calendar-alt', 'calendarmonth'=>'calendar-alt', 'calendarweek'=>'calendar-week', 'calendarday'=>'calendar-day', 'calendarperuser'=>'table',
4158
				'intervention'=>'ambulance', 'invoice'=>'file-invoice-dollar', 'currency'=>'dollar-sign', 'multicurrency'=>'dollar-sign', 'order'=>'file-invoice',
4159
				'error'=>'exclamation-triangle', 'warning'=>'exclamation-triangle',
4160
				'other'=>'square',
4161
				'playdisabled'=>'play', 'pdf'=>'file-pdf',  'poll'=>'check-double', 'pos'=>'cash-register', 'preview'=>'binoculars', 'project'=>'project-diagram', 'projectpub'=>'project-diagram', 'projecttask'=>'tasks', 'propal'=>'file-signature', 'proposal'=>'file-signature',
4162
				'partnership'=>'handshake', 'payment'=>'money-check-alt', 'payment_vat'=>'money-check-alt', 'phoning'=>'phone', 'phoning_mobile'=>'mobile-alt', 'phoning_fax'=>'fax', 'previous'=>'arrow-alt-circle-left', 'printer'=>'print', 'product'=>'cube', 'puce'=>'angle-right',
4163
				'recent' => 'question', 'reception'=>'dolly', 'recruitmentjobposition'=>'id-card-alt', 'recruitmentcandidature'=>'id-badge',
4164
				'resize'=>'crop', 'supplier_order'=>'dol-order_supplier', 'supplier_proposal'=>'file-signature',
4165
				'refresh'=>'redo', 'region'=>'map-marked', 'replacement'=>'exchange-alt', 'resource'=>'laptop-house', 'recurring'=>'history',
4166
				'service'=>'concierge-bell',
4167
				'skill'=>'shapes', 'state'=>'map-marked-alt', 'security'=>'key', 'salary'=>'wallet', 'shipment'=>'dolly', 'stock'=>'box-open', 'stats' => 'chart-bar', 'split'=>'code-branch', 'stripe'=>'stripe-s',
4168
				'supplier'=>'building', 'technic'=>'cogs',
4169
				'timespent'=>'clock', 'title_setup'=>'tools', 'title_accountancy'=>'money-check-alt', 'title_bank'=>'university', 'title_hrm'=>'umbrella-beach',
4170
				'title_agenda'=>'calendar-alt',
4171
				'uncheck'=>'times', 'uparrow'=>'share', 'url'=>'external-link-alt', 'vat'=>'money-check-alt', 'vcard'=>'arrow-alt-circle-down',
4172
				'jabber'=>'comment-o',
4173
				'website'=>'globe-americas', 'workstation'=>'pallet', 'webhook'=>'bullseye', 'world'=>'globe', 'private'=>'user-lock',
4174
				'conferenceorbooth'=>'chalkboard-teacher', 'eventorganization'=>'project-diagram'
4175
			);
4176
			if ($pictowithouttext == 'off') {
4177
				$fakey = 'fa-square';
4178
				$fasize = '1.3em';
4179
			} elseif ($pictowithouttext == 'on') {
4180
				$fakey = 'fa-check-square';
4181
				$fasize = '1.3em';
4182
			} elseif ($pictowithouttext == 'listlight') {
4183
				$fakey = 'fa-download';
4184
				$marginleftonlyshort = 1;
4185
			} elseif ($pictowithouttext == 'printer') {
4186
				$fakey = 'fa-print';
4187
				$fasize = '1.2em';
4188
			} elseif ($pictowithouttext == 'note') {
4189
				$fakey = 'fa-sticky-note';
4190
				$marginleftonlyshort = 1;
4191
			} elseif (in_array($pictowithouttext, array('1uparrow', '1downarrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected'))) {
4192
				$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');
4193
				$fakey = 'fa-'.$convertarray[$pictowithouttext];
4194
				if (preg_match('/selected/', $pictowithouttext)) {
4195
					$facolor = '#888';
4196
				}
4197
				$marginleftonlyshort = 1;
4198
			} elseif (!empty($arrayconvpictotofa[$pictowithouttext])) {
4199
				$fakey = 'fa-'.$arrayconvpictotofa[$pictowithouttext];
4200
			} else {
4201
				$fakey = 'fa-'.$pictowithouttext;
4202
			}
4203
4204
			if (in_array($pictowithouttext, array('dollyrevert', 'member', 'members', 'contract', 'group', 'resource', 'shipment'))) {
4205
				$morecss .= ' em092';
4206
			}
4207
			if (in_array($pictowithouttext, array('conferenceorbooth', 'collab', 'eventorganization', 'holiday', 'info', 'project', 'workstation'))) {
4208
				$morecss .= ' em088';
4209
			}
4210
			if (in_array($pictowithouttext, array('asset', 'intervention', 'payment', 'loan', 'partnership', 'stock', 'technic'))) {
4211
				$morecss .= ' em080';
4212
			}
4213
4214
			// Define $marginleftonlyshort
4215
			$arrayconvpictotomarginleftonly = array(
4216
				'bank', 'check', 'delete', 'generic', 'grip', 'grip_title', 'jabber',
4217
				'grip_title', 'grip', 'listlight', 'note', 'on', 'off', 'playdisabled', 'printer', 'resize', 'sign-out', 'stats', 'switch_on', 'switch_on_red', 'switch_off',
4218
				'uparrow', '1uparrow', '1downarrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected'
4219
			);
4220
			if (!isset($arrayconvpictotomarginleftonly[$pictowithouttext])) {
4221
				$marginleftonlyshort = 0;
4222
			}
4223
4224
			// Add CSS
4225
			$arrayconvpictotomorcess = array(
4226
				'action'=>'infobox-action', 'account'=>'infobox-bank_account', 'accounting_account'=>'infobox-bank_account', 'accountline'=>'infobox-bank_account', 'accountancy'=>'infobox-bank_account', 'asset'=>'infobox-bank_account',
4227
				'bank_account'=>'infobox-bank_account',
4228
				'bill'=>'infobox-commande', 'billa'=>'infobox-commande', 'billr'=>'infobox-commande', 'billd'=>'infobox-commande',
4229
				'margin'=>'infobox-bank_account', 'conferenceorbooth'=>'infobox-project',
4230
				'cash-register'=>'infobox-bank_account', 'contract'=>'infobox-contrat', 'check'=>'font-status4', 'collab'=>'infobox-action', 'conversation'=>'infobox-contrat',
4231
				'donation'=>'infobox-commande', 'dolly'=>'infobox-commande',  'dollyrevert'=>'flip infobox-order_supplier',
4232
				'ecm'=>'infobox-action', 'eventorganization'=>'infobox-project',
4233
				'hrm'=>'infobox-adherent', 'group'=>'infobox-adherent', 'intervention'=>'infobox-contrat',
4234
				'incoterm'=>'infobox-supplier_proposal',
4235
				'currency'=>'infobox-bank_account', 'multicurrency'=>'infobox-bank_account',
4236
				'members'=>'infobox-adherent', 'member'=>'infobox-adherent', 'money-bill-alt'=>'infobox-bank_account',
4237
				'order'=>'infobox-commande',
4238
				'user'=>'infobox-adherent', 'users'=>'infobox-adherent',
4239
				'error'=>'pictoerror', 'warning'=>'pictowarning', 'switch_on'=>'font-status4', 'switch_on_red'=>'font-status8',
4240
				'holiday'=>'infobox-holiday', 'info'=>'opacityhigh', 'invoice'=>'infobox-commande',
4241
				'knowledgemanagement'=>'infobox-contrat rotate90', 'loan'=>'infobox-bank_account',
4242
				'payment'=>'infobox-bank_account', 'payment_vat'=>'infobox-bank_account', 'poll'=>'infobox-adherent', 'pos'=>'infobox-bank_account', 'project'=>'infobox-project', 'projecttask'=>'infobox-project',
4243
				'propal'=>'infobox-propal', 'proposal'=>'infobox-propal','private'=>'infobox-project',
4244
				'reception'=>'flip', 'recruitmentjobposition'=>'infobox-adherent', 'recruitmentcandidature'=>'infobox-adherent',
4245
				'resource'=>'infobox-action',
4246
				'salary'=>'infobox-bank_account', 'shipment'=>'infobox-commande', 'supplier_invoice'=>'infobox-order_supplier', 'supplier_invoicea'=>'infobox-order_supplier', 'supplier_invoiced'=>'infobox-order_supplier',
4247
				'supplier'=>'infobox-order_supplier', 'supplier_order'=>'infobox-order_supplier', 'supplier_proposal'=>'infobox-supplier_proposal',
4248
				'ticket'=>'infobox-contrat', 'title_accountancy'=>'infobox-bank_account', 'title_hrm'=>'infobox-holiday', 'expensereport'=>'infobox-expensereport', 'trip'=>'infobox-expensereport', 'title_agenda'=>'infobox-action',
4249
				'vat'=>'infobox-bank_account',
4250
				//'title_setup'=>'infobox-action', 'tools'=>'infobox-action',
4251
				'list-alt'=>'imgforviewmode', 'calendar'=>'imgforviewmode', 'calendarweek'=>'imgforviewmode', 'calendarmonth'=>'imgforviewmode', 'calendarday'=>'imgforviewmode', 'calendarperuser'=>'imgforviewmode'
4252
			);
4253
			if (!empty($arrayconvpictotomorcess[$pictowithouttext])) {
4254
				$morecss .= ($morecss ? ' ' : '').$arrayconvpictotomorcess[$pictowithouttext];
4255
			}
4256
4257
			// Define $color
4258
			$arrayconvpictotocolor = array(
4259
				'address'=>'#6c6aa8', 'building'=>'#6c6aa8', 'bom'=>'#a69944',
4260
				'cog'=>'#999', 'companies'=>'#6c6aa8', 'company'=>'#6c6aa8', 'contact'=>'#6c6aa8', 'cron'=>'#555',
4261
				'dynamicprice'=>'#a69944',
4262
				'edit'=>'#444', 'note'=>'#999', 'error'=>'', 'help'=>'#bbb', 'listlight'=>'#999', 'language'=>'#555',
4263
				//'dolly'=>'#a69944', 'dollyrevert'=>'#a69944',
4264
				'lock'=>'#ddd', 'lot'=>'#a69944',
4265
				'map-marker-alt'=>'#aaa', 'mrp'=>'#a69944', 'product'=>'#a69944', 'service'=>'#a69944', 'inventory'=>'#a69944', 'stock'=>'#a69944', 'movement'=>'#a69944',
4266
				'other'=>'#ddd', 'world'=>'#986c6a',
4267
				'partnership'=>'#6c6aa8', 'playdisabled'=>'#ccc', 'printer'=>'#444', 'projectpub'=>'#986c6a', 'reception'=>'#a69944', 'resize'=>'#444', 'rss'=>'#cba',
4268
				//'shipment'=>'#a69944',
4269
				'security'=>'#999', 'square'=>'#888', 'stop-circle'=>'#888', 'stats'=>'#444', 'switch_off'=>'#999', 'technic'=>'#999', 'timespent'=>'#555',
4270
				'uncheck'=>'#800', 'uparrow'=>'#555', 'user-cog'=>'#999', 'country'=>'#aaa', 'globe-americas'=>'#aaa', 'region'=>'#aaa', 'state'=>'#aaa',
4271
				'website'=>'#304', 'workstation'=>'#a69944'
4272
			);
4273
			if (isset($arrayconvpictotocolor[$pictowithouttext])) {
4274
				$facolor = $arrayconvpictotocolor[$pictowithouttext];
4275
			}
4276
4277
			// This snippet only needed since function img_edit accepts only one additional parameter: no separate one for css only.
4278
			// class/style need to be extracted to avoid duplicate class/style validation errors when $moreatt is added to the end of the attributes.
4279
			$morestyle = '';
4280
			$reg = array();
4281
			if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
4282
				$morecss .= ($morecss ? ' ' : '').$reg[1];
4283
				$moreatt = str_replace('class="'.$reg[1].'"', '', $moreatt);
4284
			}
4285
			if (preg_match('/style="([^"]+)"/', $moreatt, $reg)) {
4286
				$morestyle = $reg[1];
4287
				$moreatt = str_replace('style="'.$reg[1].'"', '', $moreatt);
4288
			}
4289
			$moreatt = trim($moreatt);
4290
4291
			$enabledisablehtml = '<span class="'.$fa.' '.$fakey.($marginleftonlyshort ? ($marginleftonlyshort == 1 ? ' marginleftonlyshort' : ' marginleftonly') : '');
4292
			$enabledisablehtml .= ($morecss ? ' '.$morecss : '').'" style="'.($fasize ? ('font-size: '.$fasize.';') : '').($facolor ? (' color: '.$facolor.';') : '').($morestyle ? ' '.$morestyle : '').'"'.(($notitle || empty($titlealt)) ? '' : ' title="'.dol_escape_htmltag($titlealt).'"').($moreatt ? ' '.$moreatt : '').'>';
4293
			/*if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
4294
				$enabledisablehtml .= $titlealt;
4295
			}*/
4296
			$enabledisablehtml .= '</span>';
4297
4298
			return $enabledisablehtml;
4299
		}
4300
4301
		if (!empty($conf->global->MAIN_OVERWRITE_THEME_PATH)) {
4302
			$path = $conf->global->MAIN_OVERWRITE_THEME_PATH.'/theme/'.$theme; // If the theme does not have the same name as the module
4303
		} elseif (!empty($conf->global->MAIN_OVERWRITE_THEME_RES)) {
4304
			$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
4305
		} elseif (!empty($conf->modules_parts['theme']) && array_key_exists($theme, $conf->modules_parts['theme'])) {
4306
			$path = $theme.'/theme/'.$theme; // If the theme have the same name as the module
4307
		}
4308
4309
		// If we ask an image into $url/$mymodule/img (instead of default path)
4310
		$regs = array();
4311
		if (preg_match('/^([^@]+)@([^@]+)$/i', $picto, $regs)) {
4312
			$picto = $regs[1];
4313
			$path = $regs[2]; // $path is $mymodule
4314
		}
4315
4316
		// Clean parameters
4317
		if (!preg_match('/(\.png|\.gif|\.svg)$/i', $picto)) {
4318
			$picto .= '.png';
4319
		}
4320
		// If alt path are defined, define url where img file is, according to physical path
4321
		// ex: array(["main"]=>"/home/maindir/htdocs", ["alt0"]=>"/home/moddir0/htdocs", ...)
4322
		foreach ($conf->file->dol_document_root as $type => $dirroot) {
4323
			if ($type == 'main') {
4324
				continue;
4325
			}
4326
			// This need a lot of time, that's why enabling alternative dir like "custom" dir is not recommanded
4327
			if (file_exists($dirroot.'/'.$path.'/img/'.$picto)) {
4328
				$url = DOL_URL_ROOT.$conf->file->dol_url_root[$type];
4329
				break;
4330
			}
4331
		}
4332
4333
		// $url is '' or '/custom', $path is current theme or
4334
		$fullpathpicto = $url.'/'.$path.'/img/'.$picto;
4335
	}
4336
4337
	if ($srconly) {
4338
		return $fullpathpicto;
4339
	}
4340
		// tag title is used for tooltip on <a>, tag alt can be used with very simple text on image for blind people
4341
	return '<img src="'.$fullpathpicto.'"'.($notitle ? '' : ' 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
4342
}
4343
4344
/**
4345
 *	Show a picto called object_picto (generic function)
4346
 *
4347
 *	@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.
4348
 *	@param	string	$picto				Name of image to show object_picto (example: user, group, action, bill, contract, propal, product, ...)
4349
 *										For external modules use imagename@mymodule to search into directory "img" of module.
4350
 *	@param	string	$moreatt			Add more attribute on img tag (ie: class="datecallink")
4351
 *	@param	int		$pictoisfullpath	If 1, image path is a full path
4352
 *	@param	int		$srconly			Return only content of the src attribute of img.
4353
 *  @param	int		$notitle			1=Disable tag title. Use it if you add js tooltip, to avoid duplicate tooltip.
4354
 *	@return	string						Return img tag
4355
 *	@see	img_picto(), img_picto_common()
4356
 */
4357
function img_object($titlealt, $picto, $moreatt = '', $pictoisfullpath = false, $srconly = 0, $notitle = 0)
4358
{
4359
	if (strpos($picto, '^') === 0) {
4360
		return img_picto($titlealt, str_replace('^', '', $picto), $moreatt, $pictoisfullpath, $srconly, $notitle);
4361
	} else {
4362
		return img_picto($titlealt, 'object_'.$picto, $moreatt, $pictoisfullpath, $srconly, $notitle);
4363
	}
4364
}
4365
4366
/**
4367
 *	Show weather picto
4368
 *
4369
 *	@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.
4370
 *	@param      string|int	$picto       		Name of image file to show (If no extension provided, we use '.png'). Image must be stored into htdocs/theme/common directory. Or level of meteo image (0-4).
4371
 *	@param		string		$moreatt			Add more attribute on img tag
4372
 *	@param		int			$pictoisfullpath	If 1, image path is a full path
4373
 *  @param      string      $morecss            More CSS
4374
 *	@return     string      					Return img tag
4375
 *  @see        img_object(), img_picto()
4376
 */
4377
function img_weather($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $morecss = '')
4378
{
4379
	global $conf;
4380
4381
	if (is_numeric($picto)) {
4382
		//$leveltopicto = array(0=>'weather-clear.png', 1=>'weather-few-clouds.png', 2=>'weather-clouds.png', 3=>'weather-many-clouds.png', 4=>'weather-storm.png');
4383
		//$picto = $leveltopicto[$picto];
4384
		return '<i class="fa fa-weather-level'.$picto.'"></i>';
4385
	} elseif (!preg_match('/(\.png|\.gif)$/i', $picto)) {
4386
		$picto .= '.png';
4387
	}
4388
4389
	$path = DOL_URL_ROOT.'/theme/'.$conf->theme.'/img/weather/'.$picto;
4390
4391
	return img_picto($titlealt, $path, $moreatt, 1, 0, 0, '', $morecss);
4392
}
4393
4394
/**
4395
 *	Show picto (generic function)
4396
 *
4397
 *	@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.
4398
 *	@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.
4399
 *	@param		string		$moreatt			Add more attribute on img tag
4400
 *	@param		int			$pictoisfullpath	If 1, image path is a full path
4401
 *  @param		int			$notitle			1=Disable tag title. Use it if you add js tooltip, to avoid duplicate tooltip.
4402
 *	@return     string      					Return img tag
4403
 *  @see        img_object(), img_picto()
4404
 */
4405
function img_picto_common($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $notitle = 0)
4406
{
4407
	global $conf;
4408
4409
	if (!preg_match('/(\.png|\.gif)$/i', $picto)) {
4410
		$picto .= '.png';
4411
	}
4412
4413
	if ($pictoisfullpath) {
4414
		$path = $picto;
4415
	} else {
4416
		$path = DOL_URL_ROOT.'/theme/common/'.$picto;
4417
4418
		if (!empty($conf->global->MAIN_MODULE_CAN_OVERWRITE_COMMONICONS)) {
4419
			$themepath = DOL_DOCUMENT_ROOT.'/theme/'.$conf->theme.'/img/'.$picto;
4420
4421
			if (file_exists($themepath)) {
4422
				$path = $themepath;
4423
			}
4424
		}
4425
	}
4426
4427
	return img_picto($titlealt, $path, $moreatt, 1, 0, $notitle);
4428
}
4429
4430
/**
4431
 *	Show logo action
4432
 *
4433
 *	@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.
4434
 *	@param  string		$numaction   	Action id or code to show
4435
 *	@param 	string		$picto      	Name of image file to show ('filenew', ...)
4436
 *                                      If no extension provided, we use '.png'. Image must be stored into theme/xxx/img directory.
4437
 *                                      Example: picto.png                  if picto.png is stored into htdocs/theme/mytheme/img
4438
 *                                      Example: picto.png@mymodule         if picto.png is stored into htdocs/mymodule/img
4439
 *                                      Example: /mydir/mysubdir/picto.png  if picto.png is stored into htdocs/mydir/mysubdir (pictoisfullpath must be set to 1)
4440
 *	@return string      				Return an img tag
4441
 */
4442
function img_action($titlealt, $numaction, $picto = '')
4443
{
4444
	global $langs;
4445
4446
	if (empty($titlealt) || $titlealt == 'default') {
4447
		if ($numaction == '-1' || $numaction == 'ST_NO') {
4448
			$numaction = -1;
4449
			$titlealt = $langs->transnoentitiesnoconv('ChangeDoNotContact');
4450
		} elseif ($numaction == '0' || $numaction == 'ST_NEVER') {
4451
			$numaction = 0;
4452
			$titlealt = $langs->transnoentitiesnoconv('ChangeNeverContacted');
4453
		} elseif ($numaction == '1' || $numaction == 'ST_TODO') {
4454
			$numaction = 1;
4455
			$titlealt = $langs->transnoentitiesnoconv('ChangeToContact');
4456
		} elseif ($numaction == '2' || $numaction == 'ST_PEND') {
4457
			$numaction = 2;
4458
			$titlealt = $langs->transnoentitiesnoconv('ChangeContactInProcess');
4459
		} elseif ($numaction == '3' || $numaction == 'ST_DONE') {
4460
			$numaction = 3;
4461
			$titlealt = $langs->transnoentitiesnoconv('ChangeContactDone');
4462
		} else {
4463
			$titlealt = $langs->transnoentitiesnoconv('ChangeStatus '.$numaction);
4464
			$numaction = 0;
4465
		}
4466
	}
4467
	if (!is_numeric($numaction)) {
4468
		$numaction = 0;
4469
	}
4470
4471
	return img_picto($titlealt, !empty($picto) ? $picto : 'stcomm'.$numaction.'.png');
4472
}
4473
4474
/**
4475
 *  Show pdf logo
4476
 *
4477
 *  @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.
4478
 *  @param  int		    $size       Taille de l'icone : 3 = 16x16px , 2 = 14x14px
4479
 *  @return string      			Retourne tag img
4480
 */
4481
function img_pdf($titlealt = 'default', $size = 3)
4482
{
4483
	global $langs;
4484
4485
	if ($titlealt == 'default') {
4486
		$titlealt = $langs->trans('Show');
4487
	}
4488
4489
	return img_picto($titlealt, 'pdf'.$size.'.png');
4490
}
4491
4492
/**
4493
 *	Show logo +
4494
 *
4495
 *	@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.
4496
 *	@param  string	$other      Add more attributes on img
4497
 *	@return string      		Return tag img
4498
 */
4499
function img_edit_add($titlealt = 'default', $other = '')
4500
{
4501
	global $langs;
4502
4503
	if ($titlealt == 'default') {
4504
		$titlealt = $langs->trans('Add');
4505
	}
4506
4507
	return img_picto($titlealt, 'edit_add.png', $other);
4508
}
4509
/**
4510
 *	Show logo -
4511
 *
4512
 *	@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.
4513
 *	@param  string	$other      Add more attributes on img
4514
 *	@return string      		Return tag img
4515
 */
4516
function img_edit_remove($titlealt = 'default', $other = '')
4517
{
4518
	global $langs;
4519
4520
	if ($titlealt == 'default') {
4521
		$titlealt = $langs->trans('Remove');
4522
	}
4523
4524
	return img_picto($titlealt, 'edit_remove.png', $other);
4525
}
4526
4527
/**
4528
 *	Show logo editer/modifier fiche
4529
 *
4530
 *	@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.
4531
 *	@param  integer	$float      If you have to put the style "float: right"
4532
 *	@param  string	$other		Add more attributes on img
4533
 *	@return string      		Return tag img
4534
 */
4535
function img_edit($titlealt = 'default', $float = 0, $other = '')
4536
{
4537
	global $langs;
4538
4539
	if ($titlealt == 'default') {
4540
		$titlealt = $langs->trans('Modify');
4541
	}
4542
4543
	return img_picto($titlealt, 'edit.png', ($float ? 'style="float: '.($langs->tab_translate["DIRECTION"] == 'rtl' ? 'left' : 'right').'"' : "").($other ? ' '.$other : ''));
4544
}
4545
4546
/**
4547
 *	Show logo view card
4548
 *
4549
 *	@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.
4550
 *	@param  integer	$float      If you have to put the style "float: right"
4551
 *	@param  string	$other		Add more attributes on img
4552
 *	@return string      		Return tag img
4553
 */
4554
function img_view($titlealt = 'default', $float = 0, $other = 'class="valignmiddle"')
4555
{
4556
	global $langs;
4557
4558
	if ($titlealt == 'default') {
4559
		$titlealt = $langs->trans('View');
4560
	}
4561
4562
	$moreatt = ($float ? 'style="float: right" ' : '').$other;
4563
4564
	return img_picto($titlealt, 'eye', $moreatt);
4565
}
4566
4567
/**
4568
 *  Show delete logo
4569
 *
4570
 *  @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.
4571
 *	@param  string	$other      Add more attributes on img
4572
 *  @param	string	$morecss	More CSS
4573
 *  @return string      		Retourne tag img
4574
 */
4575
function img_delete($titlealt = 'default', $other = 'class="pictodelete"', $morecss = '')
4576
{
4577
	global $langs;
4578
4579
	if ($titlealt == 'default') {
4580
		$titlealt = $langs->trans('Delete');
4581
	}
4582
4583
	return img_picto($titlealt, 'delete.png', $other, false, 0, 0, '', $morecss);
4584
}
4585
4586
/**
4587
 *  Show printer logo
4588
 *
4589
 *  @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.
4590
 *  @param  string  $other      Add more attributes on img
4591
 *  @return string              Retourne tag img
4592
 */
4593
function img_printer($titlealt = "default", $other = '')
4594
{
4595
	global $langs;
4596
	if ($titlealt == "default") {
4597
		$titlealt = $langs->trans("Print");
4598
	}
4599
	return img_picto($titlealt, 'printer.png', $other);
4600
}
4601
4602
/**
4603
 *  Show split logo
4604
 *
4605
 *  @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.
4606
 *	@param  string	$other      Add more attributes on img
4607
 *  @return string      		Retourne tag img
4608
 */
4609
function img_split($titlealt = 'default', $other = 'class="pictosplit"')
4610
{
4611
	global $langs;
4612
4613
	if ($titlealt == 'default') {
4614
		$titlealt = $langs->trans('Split');
4615
	}
4616
4617
	return img_picto($titlealt, 'split.png', $other);
4618
}
4619
4620
/**
4621
 *	Show help logo with cursor "?"
4622
 *
4623
 * 	@param	int              	$usehelpcursor		1=Use help cursor, 2=Use click pointer cursor, 0=No specific cursor
4624
 * 	@param	int|string	        $usealttitle		Text to use as alt title
4625
 * 	@return string            	           			Return tag img
4626
 */
4627
function img_help($usehelpcursor = 1, $usealttitle = 1)
4628
{
4629
	global $langs;
4630
4631
	if ($usealttitle) {
4632
		if (is_string($usealttitle)) {
4633
			$usealttitle = dol_escape_htmltag($usealttitle);
4634
		} else {
4635
			$usealttitle = $langs->trans('Info');
4636
		}
4637
	}
4638
4639
	return img_picto($usealttitle, 'info.png', 'style="vertical-align: middle;'.($usehelpcursor == 1 ? ' cursor: help' : ($usehelpcursor == 2 ? ' cursor: pointer' : '')).'"');
4640
}
4641
4642
/**
4643
 *	Show info logo
4644
 *
4645
 *	@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.
4646
 *	@return string      		Return img tag
4647
 */
4648
function img_info($titlealt = 'default')
4649
{
4650
	global $langs;
4651
4652
	if ($titlealt == 'default') {
4653
		$titlealt = $langs->trans('Informations');
4654
	}
4655
4656
	return img_picto($titlealt, 'info.png', 'style="vertical-align: middle;"');
4657
}
4658
4659
/**
4660
 *	Show warning logo
4661
 *
4662
 *	@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.
4663
 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"'). If 1, add float: right. Can't be "class" attribute.
4664
 *  @param	string  $morecss	Add more CSS
4665
 *	@return string      		Return img tag
4666
 */
4667
function img_warning($titlealt = 'default', $moreatt = '', $morecss = 'pictowarning')
4668
{
4669
	global $langs;
4670
4671
	if ($titlealt == 'default') {
4672
		$titlealt = $langs->trans('Warning');
4673
	}
4674
4675
	//return '<div class="imglatecoin">'.img_picto($titlealt, 'warning_white.png', 'class="pictowarning valignmiddle"'.($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' '.$moreatt): '')).'</div>';
4676
	return img_picto($titlealt, 'warning.png', 'class="'.$morecss.'"'.($moreatt ? ($moreatt == '1' ? ' style="float: right"' : ' '.$moreatt) : ''));
4677
}
4678
4679
/**
4680
 *  Show error logo
4681
 *
4682
 *	@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.
4683
 *	@return string      		Return img tag
4684
 */
4685
function img_error($titlealt = 'default')
4686
{
4687
	global $langs;
4688
4689
	if ($titlealt == 'default') {
4690
		$titlealt = $langs->trans('Error');
4691
	}
4692
4693
	return img_picto($titlealt, 'error.png');
4694
}
4695
4696
/**
4697
 *	Show next logo
4698
 *
4699
 *	@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.
4700
*	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
4701
 *	@return string      		Return img tag
4702
 */
4703
function img_next($titlealt = 'default', $moreatt = '')
4704
{
4705
	global $langs;
4706
4707
	if ($titlealt == 'default') {
4708
		$titlealt = $langs->trans('Next');
4709
	}
4710
4711
	//return img_picto($titlealt, 'next.png', $moreatt);
4712
	return '<span class="fa fa-chevron-right paddingright paddingleft" title="'.dol_escape_htmltag($titlealt).'"></span>';
4713
}
4714
4715
/**
4716
 *	Show previous logo
4717
 *
4718
 *	@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.
4719
 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
4720
 *	@return string      		Return img tag
4721
 */
4722
function img_previous($titlealt = 'default', $moreatt = '')
4723
{
4724
	global $langs;
4725
4726
	if ($titlealt == 'default') {
4727
		$titlealt = $langs->trans('Previous');
4728
	}
4729
4730
	//return img_picto($titlealt, 'previous.png', $moreatt);
4731
	return '<span class="fa fa-chevron-left paddingright paddingleft" title="'.dol_escape_htmltag($titlealt).'"></span>';
4732
}
4733
4734
/**
4735
 *	Show down arrow logo
4736
 *
4737
 *	@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.
4738
 *	@param  int		$selected   Selected
4739
 *  @param	string	$moreclass	Add more CSS classes
4740
 *	@return string      		Return img tag
4741
 */
4742
function img_down($titlealt = 'default', $selected = 0, $moreclass = '')
4743
{
4744
	global $langs;
4745
4746
	if ($titlealt == 'default') {
4747
		$titlealt = $langs->trans('Down');
4748
	}
4749
4750
	return img_picto($titlealt, ($selected ? '1downarrow_selected.png' : '1downarrow.png'), 'class="imgdown'.($moreclass ? " ".$moreclass : "").'"');
4751
}
4752
4753
/**
4754
 *	Show top arrow logo
4755
 *
4756
 *	@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.
4757
 *	@param  int		$selected	Selected
4758
 *  @param	string	$moreclass	Add more CSS classes
4759
 *	@return string      		Return img tag
4760
 */
4761
function img_up($titlealt = 'default', $selected = 0, $moreclass = '')
4762
{
4763
	global $langs;
4764
4765
	if ($titlealt == 'default') {
4766
		$titlealt = $langs->trans('Up');
4767
	}
4768
4769
	return img_picto($titlealt, ($selected ? '1uparrow_selected.png' : '1uparrow.png'), 'class="imgup'.($moreclass ? " ".$moreclass : "").'"');
4770
}
4771
4772
/**
4773
 *	Show left arrow logo
4774
 *
4775
 *	@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.
4776
 *	@param  int		$selected	Selected
4777
 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
4778
 *	@return string      		Return img tag
4779
 */
4780
function img_left($titlealt = 'default', $selected = 0, $moreatt = '')
4781
{
4782
	global $langs;
4783
4784
	if ($titlealt == 'default') {
4785
		$titlealt = $langs->trans('Left');
4786
	}
4787
4788
	return img_picto($titlealt, ($selected ? '1leftarrow_selected.png' : '1leftarrow.png'), $moreatt);
4789
}
4790
4791
/**
4792
 *	Show right arrow logo
4793
 *
4794
 *	@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.
4795
 *	@param  int		$selected	Selected
4796
 *	@param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"')
4797
 *	@return string      		Return img tag
4798
 */
4799
function img_right($titlealt = 'default', $selected = 0, $moreatt = '')
4800
{
4801
	global $langs;
4802
4803
	if ($titlealt == 'default') {
4804
		$titlealt = $langs->trans('Right');
4805
	}
4806
4807
	return img_picto($titlealt, ($selected ? '1rightarrow_selected.png' : '1rightarrow.png'), $moreatt);
4808
}
4809
4810
/**
4811
 *	Show tick logo if allowed
4812
 *
4813
 *	@param	string	$allow		Allow
4814
 *	@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.
4815
 *	@return string      		Return img tag
4816
 */
4817
function img_allow($allow, $titlealt = 'default')
4818
{
4819
	global $langs;
4820
4821
	if ($titlealt == 'default') {
4822
		$titlealt = $langs->trans('Active');
4823
	}
4824
4825
	if ($allow == 1) {
4826
		return img_picto($titlealt, 'tick.png');
4827
	}
4828
4829
	return '-';
4830
}
4831
4832
/**
4833
 *	Return image of a credit card according to its brand name
4834
 *
4835
 *	@param  string	$brand		Brand name of credit card
4836
 *  @param  string	$morecss	More CSS
4837
 *	@return string     			Return img tag
4838
 */
4839
function img_credit_card($brand, $morecss = null)
4840
{
4841
	if (is_null($morecss)) {
4842
		$morecss = 'fa-2x';
4843
	}
4844
4845
	if ($brand == 'visa' || $brand == 'Visa') {
4846
		$brand = 'cc-visa';
4847
	} elseif ($brand == 'mastercard' || $brand == 'MasterCard') {
4848
		$brand = 'cc-mastercard';
4849
	} elseif ($brand == 'amex' || $brand == 'American Express') {
4850
		$brand = 'cc-amex';
4851
	} elseif ($brand == 'discover' || $brand == 'Discover') {
4852
		$brand = 'cc-discover';
4853
	} elseif ($brand == 'jcb' || $brand == 'JCB') {
4854
		$brand = 'cc-jcb';
4855
	} elseif ($brand == 'diners' || $brand == 'Diners club') {
4856
		$brand = 'cc-diners-club';
4857
	} elseif (!in_array($brand, array('cc-visa', 'cc-mastercard', 'cc-amex', 'cc-discover', 'cc-jcb', 'cc-diners-club'))) {
4858
		$brand = 'credit-card';
4859
	}
4860
4861
	return '<span class="fa fa-'.$brand.' fa-fw'.($morecss ? ' '.$morecss : '').'"></span>';
4862
}
4863
4864
/**
4865
 *	Show MIME img of a file
4866
 *
4867
 *	@param	string	$file		Filename
4868
 * 	@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.
4869
 *  @param	string	$morecss	More css
4870
 *	@return string     			Return img tag
4871
 */
4872
function img_mime($file, $titlealt = '', $morecss = '')
4873
{
4874
	require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
4875
4876
	$mimetype = dol_mimetype($file, '', 1);
4877
	$mimeimg = dol_mimetype($file, '', 2);
4878
	$mimefa = dol_mimetype($file, '', 4);
4879
4880
	if (empty($titlealt)) {
4881
		$titlealt = 'Mime type: '.$mimetype;
4882
	}
4883
4884
	//return img_picto_common($titlealt, 'mime/'.$mimeimg, 'class="'.$morecss.'"');
4885
	return '<i class="fa fa-'.$mimefa.' paddingright'.($morecss ? ' '.$morecss : '').'"'.($titlealt ? ' title="'.$titlealt.'"' : '').'></i>';
4886
}
4887
4888
4889
/**
4890
 *  Show search logo
4891
 *
4892
 *  @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.
4893
 *	@param  string	$other      Add more attributes on img
4894
 *  @return string      		Retourne tag img
4895
 */
4896
function img_search($titlealt = 'default', $other = '')
4897
{
4898
	global $conf, $langs;
4899
4900
	if ($titlealt == 'default') {
4901
		$titlealt = $langs->trans('Search');
4902
	}
4903
4904
	$img = img_picto($titlealt, 'search.png', $other, false, 1);
4905
4906
	$input = '<input type="image" class="liste_titre" name="button_search" src="'.$img.'" ';
4907
	$input .= 'value="'.dol_escape_htmltag($titlealt).'" title="'.dol_escape_htmltag($titlealt).'" >';
4908
4909
	return $input;
4910
}
4911
4912
/**
4913
 *  Show search logo
4914
 *
4915
 *  @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.
4916
 *	@param  string	$other      Add more attributes on img
4917
 *  @return string      		Retourne tag img
4918
 */
4919
function img_searchclear($titlealt = 'default', $other = '')
4920
{
4921
	global $conf, $langs;
4922
4923
	if ($titlealt == 'default') {
4924
		$titlealt = $langs->trans('Search');
4925
	}
4926
4927
	$img = img_picto($titlealt, 'searchclear.png', $other, false, 1);
4928
4929
	$input = '<input type="image" class="liste_titre" name="button_removefilter" src="'.$img.'" ';
4930
	$input .= 'value="'.dol_escape_htmltag($titlealt).'" title="'.dol_escape_htmltag($titlealt).'" >';
4931
4932
	return $input;
4933
}
4934
4935
/**
4936
 *	Show information for admin users or standard users
4937
 *
4938
 *	@param	string	$text				Text info
4939
 *	@param  integer	$infoonimgalt		Info is shown only on alt of star picto, otherwise it is show on output after the star picto
4940
 *	@param	int		$nodiv				No div
4941
 *  @param  string  $admin      	    '1'=Info for admin users. '0'=Info for standard users (change only the look), 'error', 'warning', 'xxx'=Other
4942
 *  @param	string	$morecss			More CSS ('', 'warning', 'error')
4943
 *  @param	string	$textfordropdown	Show a text to click to dropdown the info box.
4944
 *	@return	string						String with info text
4945
 */
4946
function info_admin($text, $infoonimgalt = 0, $nodiv = 0, $admin = '1', $morecss = 'hideonsmartphone', $textfordropdown = '')
4947
{
4948
	global $conf, $langs;
4949
4950
	if ($infoonimgalt) {
4951
		$result = img_picto($text, 'info', 'class="'.($morecss ? ' '.$morecss : '').'"');
4952
	} else {
4953
		if (empty($conf->use_javascript_ajax)) {
4954
			$textfordropdown = '';
4955
		}
4956
4957
		$class = (empty($admin) ? 'undefined' : ($admin == '1' ? 'info' : $admin));
4958
		$result = ($nodiv ? '' : '<div class="'.$class.($morecss ? ' '.$morecss : '').($textfordropdown ? ' hidden' : '').'">').'<span class="fa fa-info-circle" title="'.dol_escape_htmltag($admin ? $langs->trans('InfoAdmin') : $langs->trans('Note')).'"></span> '.$text.($nodiv ? '' : '</div>');
4959
4960
		if ($textfordropdown) {
4961
			$tmpresult = '<span class="'.$class.'text opacitymedium cursorpointer">'.$langs->trans($textfordropdown).' '.img_picto($langs->trans($textfordropdown), '1downarrow').'</span>';
4962
			$tmpresult .= '<script type="text/javascript">
4963
				jQuery(document).ready(function() {
4964
					jQuery(".'.$class.'text").click(function() {
4965
						console.log("toggle text");
4966
						jQuery(".'.$class.'").toggle();
4967
					});
4968
				});
4969
				</script>';
4970
4971
			$result = $tmpresult.$result;
4972
		}
4973
	}
4974
4975
	return $result;
4976
}
4977
4978
4979
/**
4980
 *  Displays error message system with all the information to facilitate the diagnosis and the escalation of the bugs.
4981
 *  This function must be called when a blocking technical error is encountered.
4982
 *  However, one must try to call it only within php pages, classes must return their error through their property "error".
4983
 *
4984
 *	@param	 	DoliDB|string   $db      	Database handler
4985
 *	@param  	string|string[] $error		String or array of errors strings to show
4986
 *  @param		array           $errors		Array of errors
4987
 *	@return 	void
4988
 *  @see    	dol_htmloutput_errors()
4989
 */
4990
function dol_print_error($db = '', $error = '', $errors = null)
4991
{
4992
	global $conf, $langs, $argv;
4993
	global $dolibarr_main_prod;
4994
4995
	$out = '';
4996
	$syslog = '';
4997
4998
	// If error occurs before the $lang object was loaded
4999
	if (!$langs) {
5000
		require_once DOL_DOCUMENT_ROOT.'/core/class/translate.class.php';
5001
		$langs = new Translate('', $conf);
5002
		$langs->load("main");
5003
	}
5004
5005
	// Load translation files required by the error messages
5006
	$langs->loadLangs(array('main', 'errors'));
5007
5008
	if ($_SERVER['DOCUMENT_ROOT']) {    // Mode web
5009
		$out .= $langs->trans("DolibarrHasDetectedError").".<br>\n";
5010
		if (getDolGlobalInt('MAIN_FEATURES_LEVEL') > 0) {
5011
			$out .= "You use an experimental or develop level of features, so please do NOT report any bugs or vulnerability, except if problem is confirmed after moving option MAIN_FEATURES_LEVEL back to 0.<br>\n";
5012
		}
5013
		$out .= $langs->trans("InformationToHelpDiagnose").":<br>\n";
5014
5015
		$out .= "<b>".$langs->trans("Date").":</b> ".dol_print_date(time(), 'dayhourlog')."<br>\n";
5016
		$out .= "<b>".$langs->trans("Dolibarr").":</b> ".DOL_VERSION." - https://www.dolibarr.org<br>\n";
5017
		if (isset($conf->global->MAIN_FEATURES_LEVEL)) {
5018
			$out .= "<b>".$langs->trans("LevelOfFeature").":</b> ".getDolGlobalInt('MAIN_FEATURES_LEVEL')."<br>\n";
5019
		}
5020
		if (function_exists("phpversion")) {
5021
			$out .= "<b>".$langs->trans("PHP").":</b> ".phpversion()."<br>\n";
5022
		}
5023
		$out .= "<b>".$langs->trans("Server").":</b> ".(isset($_SERVER["SERVER_SOFTWARE"]) ? dol_htmlentities($_SERVER["SERVER_SOFTWARE"], ENT_COMPAT) : '')."<br>\n";
5024
		if (function_exists("php_uname")) {
5025
			$out .= "<b>".$langs->trans("OS").":</b> ".php_uname()."<br>\n";
5026
		}
5027
		$out .= "<b>".$langs->trans("UserAgent").":</b> ".(isset($_SERVER["HTTP_USER_AGENT"]) ? dol_htmlentities($_SERVER["HTTP_USER_AGENT"], ENT_COMPAT) : '')."<br>\n";
5028
		$out .= "<br>\n";
5029
		$out .= "<b>".$langs->trans("RequestedUrl").":</b> ".dol_htmlentities($_SERVER["REQUEST_URI"], ENT_COMPAT)."<br>\n";
5030
		$out .= "<b>".$langs->trans("Referer").":</b> ".(isset($_SERVER["HTTP_REFERER"]) ? dol_htmlentities($_SERVER["HTTP_REFERER"], ENT_COMPAT) : '')."<br>\n";
5031
		$out .= "<b>".$langs->trans("MenuManager").":</b> ".(isset($conf->standard_menu) ? dol_htmlentities($conf->standard_menu, ENT_COMPAT) : '')."<br>\n";
5032
		$out .= "<br>\n";
5033
		$syslog .= "url=".dol_escape_htmltag($_SERVER["REQUEST_URI"]);
5034
		$syslog .= ", query_string=".dol_escape_htmltag($_SERVER["QUERY_STRING"]);
5035
	} else { // Mode CLI
5036
		$out .= '> '.$langs->transnoentities("ErrorInternalErrorDetected").":\n".$argv[0]."\n";
5037
		$syslog .= "pid=".dol_getmypid();
5038
	}
5039
5040
	if (!empty($conf->modules)) {
5041
		$out .= "<b>".$langs->trans("Modules").":</b> ".join(', ', $conf->modules)."<br>\n";
5042
	}
5043
5044
	if (is_object($db)) {
5045
		if ($_SERVER['DOCUMENT_ROOT']) {  // Mode web
5046
			$out .= "<b>".$langs->trans("DatabaseTypeManager").":</b> ".$db->type."<br>\n";
5047
			$lastqueryerror = $db->lastqueryerror();
5048
			if (!utf8_check($lastqueryerror)) {
5049
				$lastqueryerror = "SQL error string is not a valid UTF8 string. We can't show it.";
5050
			}
5051
			$out .= "<b>".$langs->trans("RequestLastAccessInError").":</b> ".($lastqueryerror ? dol_escape_htmltag($lastqueryerror) : $langs->trans("ErrorNoRequestInError"))."<br>\n";
5052
			$out .= "<b>".$langs->trans("ReturnCodeLastAccessInError").":</b> ".($db->lasterrno() ? dol_escape_htmltag($db->lasterrno()) : $langs->trans("ErrorNoRequestInError"))."<br>\n";
5053
			$out .= "<b>".$langs->trans("InformationLastAccessInError").":</b> ".($db->lasterror() ? dol_escape_htmltag($db->lasterror()) : $langs->trans("ErrorNoRequestInError"))."<br>\n";
5054
			$out .= "<br>\n";
5055
		} else { // Mode CLI
5056
			// No dol_escape_htmltag for output, we are in CLI mode
5057
			$out .= '> '.$langs->transnoentities("DatabaseTypeManager").":\n".$db->type."\n";
5058
			$out .= '> '.$langs->transnoentities("RequestLastAccessInError").":\n".($db->lastqueryerror() ? $db->lastqueryerror() : $langs->transnoentities("ErrorNoRequestInError"))."\n";
5059
			$out .= '> '.$langs->transnoentities("ReturnCodeLastAccessInError").":\n".($db->lasterrno() ? $db->lasterrno() : $langs->transnoentities("ErrorNoRequestInError"))."\n";
5060
			$out .= '> '.$langs->transnoentities("InformationLastAccessInError").":\n".($db->lasterror() ? $db->lasterror() : $langs->transnoentities("ErrorNoRequestInError"))."\n";
5061
		}
5062
		$syslog .= ", sql=".$db->lastquery();
5063
		$syslog .= ", db_error=".$db->lasterror();
5064
	}
5065
5066
	if ($error || $errors) {
5067
		$langs->load("errors");
5068
5069
		// Merge all into $errors array
5070
		if (is_array($error) && is_array($errors)) {
5071
			$errors = array_merge($error, $errors);
5072
		} elseif (is_array($error)) {
5073
			$errors = $error;
5074
		} elseif (is_array($errors)) {
5075
			$errors = array_merge(array($error), $errors);
5076
		} else {
5077
			$errors = array_merge(array($error), array($errors));
5078
		}
5079
5080
		foreach ($errors as $msg) {
5081
			if (empty($msg)) {
5082
				continue;
5083
			}
5084
			if ($_SERVER['DOCUMENT_ROOT']) {  // Mode web
5085
				$out .= "<b>".$langs->trans("Message").":</b> ".dol_escape_htmltag($msg)."<br>\n";
5086
			} else // Mode CLI
5087
			{
5088
				$out .= '> '.$langs->transnoentities("Message").":\n".$msg."\n";
5089
			}
5090
			$syslog .= ", msg=".$msg;
5091
		}
5092
	}
5093
	if (empty($dolibarr_main_prod) && $_SERVER['DOCUMENT_ROOT'] && function_exists('xdebug_print_function_stack') && function_exists('xdebug_call_file')) {
5094
		xdebug_print_function_stack();
5095
		$out .= '<b>XDebug informations:</b>'."<br>\n";
5096
		$out .= 'File: '.xdebug_call_file()."<br>\n";
5097
		$out .= 'Line: '.xdebug_call_line()."<br>\n";
5098
		$out .= 'Function: '.xdebug_call_function()."<br>\n";
5099
		$out .= "<br>\n";
5100
	}
5101
5102
	// Return a http header with error code if possible
5103
	if (!headers_sent()) {
5104
		if (function_exists('top_httphead')) {	// In CLI context, the method does not exists
5105
			top_httphead();
5106
		}
5107
		http_response_code(500);
5108
	}
5109
5110
	if (empty($dolibarr_main_prod)) {
5111
		print $out;
5112
	} else {
5113
		if (empty($langs->defaultlang)) {
5114
			$langs->setDefaultLang();
5115
		}
5116
		$langs->loadLangs(array("main", "errors")); // Reload main because language may have been set only on previous line so we have to reload files we need.
5117
		// This should not happen, except if there is a bug somewhere. Enabled and check log in such case.
5118
		print 'This website or feature is currently temporarly not available or failed after a technical error.<br><br>This may be due to a maintenance operation. Current status of operation ('.dol_print_date(dol_now(), 'dayhourrfc').') are on next line...<br><br>'."\n";
5119
		print $langs->trans("DolibarrHasDetectedError").'. ';
5120
		print $langs->trans("YouCanSetOptionDolibarrMainProdToZero");
5121
		define("MAIN_CORE_ERROR", 1);
5122
	}
5123
5124
	dol_syslog("Error ".$syslog, LOG_ERR);
5125
}
5126
5127
/**
5128
 * Show a public email and error code to contact if technical error
5129
 *
5130
 * @param	string	$prefixcode		Prefix of public error code
5131
 * @param   string  $errormessage   Complete error message
5132
 * @param	array	$errormessages	Array of error messages
5133
 * @param	string	$morecss		More css
5134
 * @param	string	$email			Email
5135
 * @return	void
5136
 */
5137
function dol_print_error_email($prefixcode, $errormessage = '', $errormessages = array(), $morecss = 'error', $email = '')
5138
{
5139
	global $langs, $conf;
5140
5141
	if (empty($email)) {
5142
		$email = $conf->global->MAIN_INFO_SOCIETE_MAIL;
5143
	}
5144
5145
	$langs->load("errors");
5146
	$now = dol_now();
5147
5148
	print '<br><div class="center login_main_message"><div class="'.$morecss.'">';
5149
	print $langs->trans("ErrorContactEMail", $email, $prefixcode.dol_print_date($now, '%Y%m%d%H%M%S'));
5150
	if ($errormessage) {
5151
		print '<br><br>'.$errormessage;
5152
	}
5153
	if (is_array($errormessages) && count($errormessages)) {
5154
		foreach ($errormessages as $mesgtoshow) {
5155
			print '<br><br>'.$mesgtoshow;
5156
		}
5157
	}
5158
	print '</div></div>';
5159
}
5160
5161
/**
5162
 *	Show title line of an array
5163
 *
5164
 *	@param	string	$name        Label of field
5165
 *	@param	string	$file        Url used when we click on sort picto
5166
 *	@param	string	$field       Field to use for new sorting
5167
 *	@param	string	$begin       ("" by defaut)
5168
 *	@param	string	$moreparam   Add more parameters on sort url links ("" by default)
5169
 *	@param  string	$moreattrib  Options of attribute td ("" by defaut)
5170
 *	@param  string	$sortfield   Current field used to sort
5171
 *	@param  string	$sortorder   Current sort order
5172
 *  @param	string	$prefix		 Prefix for css. Use space after prefix to add your own CSS tag.
5173
 *  @param	string	$tooltip	 Tooltip
5174
 *  @param	string	$forcenowrapcolumntitle		No need for use 'wrapcolumntitle' css style
5175
 *	@return	void
5176
 */
5177
function print_liste_field_titre($name, $file = "", $field = "", $begin = "", $moreparam = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $tooltip = "", $forcenowrapcolumntitle = 0)
5178
{
5179
	print getTitleFieldOfList($name, 0, $file, $field, $begin, $moreparam, $moreattrib, $sortfield, $sortorder, $prefix, 0, $tooltip, $forcenowrapcolumntitle);
5180
}
5181
5182
/**
5183
 *	Get title line of an array
5184
 *
5185
 *	@param	string	$name        		Translation key of field to show or complete HTML string to show
5186
 *	@param	int		$thead		 		0=To use with standard table format, 1=To use inside <thead><tr>, 2=To use with <div>
5187
 *	@param	string	$file        		Url used when we click on sort picto
5188
 *	@param	string	$field       		Field to use for new sorting. Empty if this field is not sortable. Example "t.abc" or "t.abc,t.def"
5189
 *	@param	string	$begin       		("" by defaut)
5190
 *	@param	string	$moreparam   		Add more parameters on sort url links ("" by default)
5191
 *	@param  string	$moreattrib  		Add more attributes on th ("" by defaut). To add more css class, use param $prefix.
5192
 *	@param  string	$sortfield   		Current field used to sort (Ex: 'd.datep,d.id')
5193
 *	@param  string	$sortorder   		Current sort order (Ex: 'asc,desc')
5194
 *  @param	string	$prefix		 		Prefix for css. Use space after prefix to add your own CSS tag, for example 'mycss '.
5195
 *  @param	string	$disablesortlink	1=Disable sort link
5196
 *  @param	string	$tooltip	 		Tooltip
5197
 *  @param	string	$forcenowrapcolumntitle		No need for use 'wrapcolumntitle' css style
5198
 *	@return	string
5199
 */
5200
function getTitleFieldOfList($name, $thead = 0, $file = "", $field = "", $begin = "", $moreparam = "", $moreattrib = "", $sortfield = "", $sortorder = "", $prefix = "", $disablesortlink = 0, $tooltip = '', $forcenowrapcolumntitle = 0)
5201
{
5202
	global $conf, $langs, $form;
5203
	//print "$name, $file, $field, $begin, $options, $moreattrib, $sortfield, $sortorder<br>\n";
5204
5205
	if ($moreattrib == 'class="right"') {
5206
		$prefix .= 'right '; // For backward compatibility
5207
	}
5208
5209
	$sortorder = strtoupper($sortorder);
5210
	$out = '';
5211
	$sortimg = '';
5212
5213
	$tag = 'th';
5214
	if ($thead == 2) {
5215
		$tag = 'div';
5216
	}
5217
5218
	$tmpsortfield = explode(',', $sortfield);
5219
	$sortfield1 = trim($tmpsortfield[0]); // If $sortfield is 'd.datep,d.id', it becomes 'd.datep'
5220
	$tmpfield = explode(',', $field);
5221
	$field1 = trim($tmpfield[0]); // If $field is 'd.datep,d.id', it becomes 'd.datep'
5222
5223
	if (empty($conf->global->MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE) && empty($forcenowrapcolumntitle)) {
5224
		$prefix = 'wrapcolumntitle '.$prefix;
5225
	}
5226
5227
	//var_dump('field='.$field.' field1='.$field1.' sortfield='.$sortfield.' sortfield1='.$sortfield1);
5228
	// If field is used as sort criteria we use a specific css class liste_titre_sel
5229
	// Example if (sortfield,field)=("nom","xxx.nom") or (sortfield,field)=("nom","nom")
5230
	$liste_titre = 'liste_titre';
5231
	if ($field1 && ($sortfield1 == $field1 || $sortfield1 == preg_replace("/^[^\.]+\./", "", $field1))) {
5232
		$liste_titre = 'liste_titre_sel';
5233
	}
5234
5235
	$tagstart = '<'.$tag.' class="'.$prefix.$liste_titre.'" '.$moreattrib;
5236
	//$out .= (($field && empty($conf->global->MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE) && preg_match('/^[a-zA-Z_0-9\s\.\-:&;]*$/', $name)) ? ' title="'.dol_escape_htmltag($langs->trans($name)).'"' : '');
5237
	$tagstart .= ($name && empty($conf->global->MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE) && empty($forcenowrapcolumntitle) && !dol_textishtml($name)) ? ' title="'.dol_escape_htmltag($langs->trans($name)).'"' : '';
5238
	$tagstart .= '>';
5239
5240
	if (empty($thead) && $field && empty($disablesortlink)) {    // If this is a sort field
5241
		$options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', (is_scalar($moreparam) ? $moreparam : ''));
5242
		$options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
5243
		$options = preg_replace('/&+/i', '&', $options);
5244
		if (!preg_match('/^&/', $options)) {
5245
			$options = '&'.$options;
5246
		}
5247
5248
		$sortordertouseinlink = '';
5249
		if ($field1 != $sortfield1) { // We are on another field than current sorted field
5250
			if (preg_match('/^DESC/i', $sortorder)) {
5251
				$sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
5252
			} else { // We reverse the var $sortordertouseinlink
5253
				$sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
5254
			}
5255
		} else { // We are on field that is the first current sorting criteria
5256
			if (preg_match('/^ASC/i', $sortorder)) {	// We reverse the var $sortordertouseinlink
5257
				$sortordertouseinlink .= str_repeat('desc,', count(explode(',', $field)));
5258
			} else {
5259
				$sortordertouseinlink .= str_repeat('asc,', count(explode(',', $field)));
5260
			}
5261
		}
5262
		$sortordertouseinlink = preg_replace('/,$/', '', $sortordertouseinlink);
5263
		$out .= '<a class="reposition" href="'.$file.'?sortfield='.$field.'&sortorder='.$sortordertouseinlink.'&begin='.$begin.$options.'"';
5264
		//$out .= (empty($conf->global->MAIN_DISABLE_WRAPPING_ON_COLUMN_TITLE) ? ' title="'.dol_escape_htmltag($langs->trans($name)).'"' : '');
5265
		$out .= '>';
5266
	}
5267
	if ($tooltip) {
5268
		// You can also use 'TranslationString:keyfortooltiponclick' for a tooltip on click.
5269
		if (preg_match('/:\w+$/', $tooltip)) {
5270
			$tmptooltip = explode(':', $tooltip);
5271
		} else {
5272
			$tmptooltip = array($tooltip);
5273
		}
5274
		$out .= $form->textwithpicto($langs->trans($name), $langs->trans($tmptooltip[0]), 1, 'help', '', 0, 3, (empty($tmptooltip[1]) ? '' : 'extra_'.str_replace('.', '_', $field).'_'.$tmptooltip[1]));
5275
	} else {
5276
		$out .= $langs->trans($name);
5277
	}
5278
5279
	if (empty($thead) && $field && empty($disablesortlink)) {    // If this is a sort field
5280
		$out .= '</a>';
5281
	}
5282
5283
	if (empty($thead) && $field) {    // If this is a sort field
5284
		$options = preg_replace('/sortfield=([a-zA-Z0-9,\s\.]+)/i', '', (is_scalar($moreparam) ? $moreparam : ''));
5285
		$options = preg_replace('/sortorder=([a-zA-Z0-9,\s\.]+)/i', '', $options);
5286
		$options = preg_replace('/&+/i', '&', $options);
5287
		if (!preg_match('/^&/', $options)) {
5288
			$options = '&'.$options;
5289
		}
5290
5291
		if (!$sortorder || $field1 != $sortfield1) {
5292
			//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",0).'</a>';
5293
			//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",0).'</a>';
5294
		} else {
5295
			if (preg_match('/^DESC/', $sortorder)) {
5296
				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",0).'</a>';
5297
				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",1).'</a>';
5298
				$sortimg .= '<span class="nowrap">'.img_up("Z-A", 0, 'paddingright').'</span>';
5299
			}
5300
			if (preg_match('/^ASC/', $sortorder)) {
5301
				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=asc&begin='.$begin.$options.'">'.img_down("A-Z",1).'</a>';
5302
				//$out.= '<a href="'.$file.'?sortfield='.$field.'&sortorder=desc&begin='.$begin.$options.'">'.img_up("Z-A",0).'</a>';
5303
				$sortimg .= '<span class="nowrap">'.img_down("A-Z", 0, 'paddingright').'</span>';
5304
			}
5305
		}
5306
	}
5307
5308
	$tagend = '</'.$tag.'>';
5309
5310
	$out = $tagstart.$sortimg.$out.$tagend;
5311
5312
	return $out;
5313
}
5314
5315
/**
5316
 *	Show a title.
5317
 *
5318
 *	@param	string	$title			Title to show
5319
 *  @return	void
5320
 *  @deprecated						Use load_fiche_titre instead
5321
 *  @see load_fiche_titre()
5322
 */
5323
function print_titre($title)
5324
{
5325
	dol_syslog(__FUNCTION__." is deprecated", LOG_WARNING);
5326
5327
	print '<div class="titre">'.$title.'</div>';
5328
}
5329
5330
/**
5331
 *	Show a title with picto
5332
 *
5333
 *	@param	string	$title				Title to show
5334
 *	@param	string	$mesg				Added message to show on right
5335
 *	@param	string	$picto				Icon to use before title (should be a 32x32 transparent png file)
5336
 *	@param	int		$pictoisfullpath	1=Icon name is a full absolute url of image
5337
 * 	@param	int		$id					To force an id on html objects
5338
 * 	@return	void
5339
 *  @deprecated Use print load_fiche_titre instead
5340
 */
5341
function print_fiche_titre($title, $mesg = '', $picto = 'generic', $pictoisfullpath = 0, $id = '')
5342
{
5343
	print load_fiche_titre($title, $mesg, $picto, $pictoisfullpath, $id);
5344
}
5345
5346
/**
5347
 *	Load a title with picto
5348
 *
5349
 *	@param	string	$titre				Title to show
5350
 *	@param	string	$morehtmlright		Added message to show on right
5351
 *	@param	string	$picto				Icon to use before title (should be a 32x32 transparent png file)
5352
 *	@param	int		$pictoisfullpath	1=Icon name is a full absolute url of image
5353
 * 	@param	string	$id					To force an id on html objects
5354
 *  @param  string  $morecssontable     More css on table
5355
 *	@param	string	$morehtmlcenter		Added message to show on center
5356
 * 	@return	string
5357
 *  @see print_barre_liste()
5358
 */
5359
function load_fiche_titre($titre, $morehtmlright = '', $picto = 'generic', $pictoisfullpath = 0, $id = '', $morecssontable = '', $morehtmlcenter = '')
5360
{
5361
	global $conf;
5362
5363
	$return = '';
5364
5365
	if ($picto == 'setup') {
5366
		$picto = 'generic';
5367
	}
5368
5369
	$return .= "\n";
5370
	$return .= '<table '.($id ? 'id="'.$id.'" ' : '').'class="centpercent notopnoleftnoright table-fiche-title'.($morecssontable ? ' '.$morecssontable : '').'">'; // maring bottom must be same than into print_barre_list
5371
	$return .= '<tr class="titre">';
5372
	if ($picto) {
5373
		$return .= '<td class="nobordernopadding widthpictotitle valignmiddle col-picto">'.img_picto('', $picto, 'class="valignmiddle widthpictotitle pictotitle"', $pictoisfullpath).'</td>';
5374
	}
5375
	$return .= '<td class="nobordernopadding valignmiddle col-title">';
5376
	$return .= '<div class="titre inline-block">'.$titre.'</div>';
5377
	$return .= '</td>';
5378
	if (dol_strlen($morehtmlcenter)) {
5379
		$return .= '<td class="nobordernopadding center valignmiddle col-center">'.$morehtmlcenter.'</td>';
5380
	}
5381
	if (dol_strlen($morehtmlright)) {
5382
		$return .= '<td class="nobordernopadding titre_right wordbreakimp right valignmiddle col-right">'.$morehtmlright.'</td>';
5383
	}
5384
	$return .= '</tr></table>'."\n";
5385
5386
	return $return;
5387
}
5388
5389
/**
5390
 *	Print a title with navigation controls for pagination
5391
 *
5392
 *	@param	string	    $titre				Title to show (required)
5393
 *	@param	int   	    $page				Numero of page to show in navigation links (required)
5394
 *	@param	string	    $file				Url of page (required)
5395
 *	@param	string	    $options         	More parameters for links ('' by default, does not include sortfield neither sortorder). Value must be 'urlencoded' before calling function.
5396
 *	@param	string    	$sortfield       	Field to sort on ('' by default)
5397
 *	@param	string	    $sortorder       	Order to sort ('' by default)
5398
 *	@param	string	    $morehtmlcenter     String in the middle ('' by default). We often find here string $massaction comming from $form->selectMassAction()
5399
 *	@param	int		    $num				Number of records found by select with limit+1
5400
 *	@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.
5401
 *	@param	string	    $picto				Icon to use before title (should be a 32x32 transparent png file)
5402
 *	@param	int		    $pictoisfullpath	1=Icon name is a full absolute url of image
5403
 *  @param	string	    $morehtmlright		More html to show (after arrows)
5404
 *  @param  string      $morecss            More css to the table
5405
 *  @param  int         $limit              Max number of lines (-1 = use default, 0 = no limit, > 0 = limit).
5406
 *  @param  int         $hideselectlimit    Force to hide select limit
5407
 *  @param  int         $hidenavigation     Force to hide all navigation tools
5408
 *  @param  int			$pagenavastextinput 1=Do not suggest list of pages to navigate but suggest the page number into an input field.
5409
 *  @param	string		$morehtmlrightbeforearrow	More html to show (before arrows)
5410
 *	@return	void
5411
 */
5412
function print_barre_liste($titre, $page, $file, $options = '', $sortfield = '', $sortorder = '', $morehtmlcenter = '', $num = -1, $totalnboflines = '', $picto = 'generic', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limit = -1, $hideselectlimit = 0, $hidenavigation = 0, $pagenavastextinput = 0, $morehtmlrightbeforearrow = '')
5413
{
5414
	global $conf, $langs;
5415
5416
	$savlimit = $limit;
5417
	$savtotalnboflines = $totalnboflines;
5418
	$totalnboflines = abs((int) $totalnboflines);
5419
5420
	if ($picto == 'setup') {
5421
		$picto = 'title_setup.png';
5422
	}
5423
	if (($conf->browser->name == 'ie') && $picto == 'generic') {
5424
		$picto = 'title.gif';
5425
	}
5426
	if ($limit < 0) {
5427
		$limit = $conf->liste_limit;
5428
	}
5429
	if ($savlimit != 0 && (($num > $limit) || ($num == -1) || ($limit == 0))) {
5430
		$nextpage = 1;
5431
	} else {
5432
		$nextpage = 0;
5433
	}
5434
	//print 'totalnboflines='.$totalnboflines.'-savlimit='.$savlimit.'-limit='.$limit.'-num='.$num.'-nextpage='.$nextpage;
5435
5436
	print "\n";
5437
	print "<!-- Begin title -->\n";
5438
	print '<table class="centpercent notopnoleftnoright table-fiche-title'.($morecss ? ' '.$morecss : '').'"><tr>'; // maring bottom must be same than into load_fiche_tire
5439
5440
	// Left
5441
5442
	if ($picto && $titre) {
5443
		print '<td class="nobordernopadding widthpictotitle valignmiddle col-picto">'.img_picto('', $picto, 'class="valignmiddle pictotitle widthpictotitle"', $pictoisfullpath).'</td>';
5444
	}
5445
	print '<td class="nobordernopadding valignmiddle col-title">';
5446
	print '<div class="titre inline-block">'.$titre;
5447
	if (!empty($titre) && $savtotalnboflines >= 0 && (string) $savtotalnboflines != '') {
5448
		print '<span class="opacitymedium colorblack paddingleft">('.$totalnboflines.')</span>';
5449
	}
5450
	print '</div></td>';
5451
5452
	// Center
5453
	if ($morehtmlcenter) {
5454
		print '<td class="nobordernopadding center valignmiddle col-center">'.$morehtmlcenter.'</td>';
5455
	}
5456
5457
	// Right
5458
	print '<td class="nobordernopadding valignmiddle right col-right">';
5459
	print '<input type="hidden" name="pageplusoneold" value="'.((int) $page + 1).'">';
5460
	if ($sortfield) {
5461
		$options .= "&sortfield=".urlencode($sortfield);
5462
	}
5463
	if ($sortorder) {
5464
		$options .= "&sortorder=".urlencode($sortorder);
5465
	}
5466
	// Show navigation bar
5467
	$pagelist = '';
5468
	if ($savlimit != 0 && ($page > 0 || $num > $limit)) {
5469
		if ($totalnboflines) {	// If we know total nb of lines
5470
			// Define nb of extra page links before and after selected page + ... + first or last
5471
			$maxnbofpage = (empty($conf->dol_optimize_smallscreen) ? 4 : 0);
5472
5473
			if ($limit > 0) {
5474
				$nbpages = ceil($totalnboflines / $limit);
5475
			} else {
5476
				$nbpages = 1;
5477
			}
5478
			$cpt = ($page - $maxnbofpage);
5479
			if ($cpt < 0) {
5480
				$cpt = 0;
5481
			}
5482
5483
			if ($cpt >= 1) {
5484
				if (empty($pagenavastextinput)) {
5485
					$pagelist .= '<li class="pagination"><a href="'.$file.'?page=0'.$options.'">1</a></li>';
5486
					if ($cpt > 2) {
5487
						$pagelist .= '<li class="pagination"><span class="inactive">...</span></li>';
5488
					} elseif ($cpt == 2) {
5489
						$pagelist .= '<li class="pagination"><a href="'.$file.'?page=1'.$options.'">2</a></li>';
5490
					}
5491
				}
5492
			}
5493
5494
			do {
5495
				if ($pagenavastextinput) {
5496
					if ($cpt == $page) {
5497
						$pagelist .= '<li class="pagination"><input type="text" class="width25 center pageplusone" name="pageplusone" value="'.($page + 1).'"></li>';
5498
						$pagelist .= '/';
5499
					}
5500
				} else {
5501
					if ($cpt == $page) {
5502
						$pagelist .= '<li class="pagination"><span class="active">'.($page + 1).'</span></li>';
5503
					} else {
5504
						$pagelist .= '<li class="pagination"><a href="'.$file.'?page='.$cpt.$options.'">'.($cpt + 1).'</a></li>';
5505
					}
5506
				}
5507
				$cpt++;
5508
			} while ($cpt < $nbpages && $cpt <= ($page + $maxnbofpage));
5509
5510
			if (empty($pagenavastextinput)) {
5511
				if ($cpt < $nbpages) {
5512
					if ($cpt < $nbpages - 2) {
5513
						$pagelist .= '<li class="pagination"><span class="inactive">...</span></li>';
5514
					} elseif ($cpt == $nbpages - 2) {
5515
						$pagelist .= '<li class="pagination"><a href="'.$file.'?page='.($nbpages - 2).$options.'">'.($nbpages - 1).'</a></li>';
5516
					}
5517
					$pagelist .= '<li class="pagination"><a href="'.$file.'?page='.($nbpages - 1).$options.'">'.$nbpages.'</a></li>';
5518
				}
5519
			} else {
5520
				//var_dump($page.' '.$cpt.' '.$nbpages);
5521
				$pagelist .= '<li class="pagination paginationlastpage"><a href="'.$file.'?page='.($nbpages - 1).$options.'">'.$nbpages.'</a></li>';
5522
			}
5523
		} else {
5524
			$pagelist .= '<li class="pagination"><span class="active">'.($page + 1)."</li>";
5525
		}
5526
	}
5527
5528
	if ($savlimit || $morehtmlright || $morehtmlrightbeforearrow) {
5529
		print_fleche_navigation($page, $file, $options, $nextpage, $pagelist, $morehtmlright, $savlimit, $totalnboflines, $hideselectlimit, $morehtmlrightbeforearrow); // output the div and ul for previous/last completed with page numbers into $pagelist
5530
	}
5531
5532
	// js to autoselect page field on focus
5533
	if ($pagenavastextinput) {
5534
		print ajax_autoselect('.pageplusone');
5535
	}
5536
5537
	print '</td>';
5538
5539
	print '</tr></table>'."\n";
5540
	print "<!-- End title -->\n\n";
5541
}
5542
5543
/**
5544
 *	Function to show navigation arrows into lists
5545
 *
5546
 *	@param	int				$page				Number of page
5547
 *	@param	string			$file				Page URL (in most cases provided with $_SERVER["PHP_SELF"])
5548
 *	@param	string			$options         	Other url parameters to propagate ("" by default, may include sortfield and sortorder)
5549
 *	@param	integer			$nextpage	    	Do we show a next page button
5550
 *	@param	string			$betweenarrows		HTML content to show between arrows. MUST contains '<li> </li>' tags or '<li><span> </span></li>'.
5551
 *  @param	string			$afterarrows		HTML content to show after arrows. Must NOT contains '<li> </li>' tags.
5552
 *  @param  int             $limit              Max nb of record to show  (-1 = no combo with limit, 0 = no limit, > 0 = limit)
5553
 *	@param	int		        $totalnboflines		Total number of records/lines for all pages (if known)
5554
 *  @param  int             $hideselectlimit    Force to hide select limit
5555
 *  @param	string			$beforearrows		HTML content to show before arrows. Must NOT contains '<li> </li>' tags.
5556
 *	@return	void
5557
 */
5558
function print_fleche_navigation($page, $file, $options = '', $nextpage = 0, $betweenarrows = '', $afterarrows = '', $limit = -1, $totalnboflines = 0, $hideselectlimit = 0, $beforearrows = '')
5559
{
5560
	global $conf, $langs;
5561
5562
	print '<div class="pagination"><ul>';
5563
	if ($beforearrows) {
5564
		print '<li class="paginationbeforearrows">';
5565
		print $beforearrows;
5566
		print '</li>';
5567
	}
5568
	if ((int) $limit > 0 && empty($hideselectlimit)) {
5569
		$pagesizechoices = '10:10,15:15,20:20,30:30,40:40,50:50,100:100,250:250,500:500,1000:1000';
5570
		$pagesizechoices .= ',5000:5000,10000:10000,20000:20000';
5571
		//$pagesizechoices.=',0:'.$langs->trans("All");     // Not yet supported
5572
		//$pagesizechoices.=',2:2';
5573
		if (!empty($conf->global->MAIN_PAGESIZE_CHOICES)) {
5574
			$pagesizechoices = $conf->global->MAIN_PAGESIZE_CHOICES;
5575
		}
5576
5577
		print '<li class="pagination">';
5578
		print '<select class="flat selectlimit" name="limit" title="'.dol_escape_htmltag($langs->trans("MaxNbOfRecordPerPage")).'">';
5579
		$tmpchoice = explode(',', $pagesizechoices);
5580
		$tmpkey = $limit.':'.$limit;
5581
		if (!in_array($tmpkey, $tmpchoice)) {
5582
			$tmpchoice[] = $tmpkey;
5583
		}
5584
		$tmpkey = $conf->liste_limit.':'.$conf->liste_limit;
5585
		if (!in_array($tmpkey, $tmpchoice)) {
5586
			$tmpchoice[] = $tmpkey;
5587
		}
5588
		asort($tmpchoice, SORT_NUMERIC);
5589
		foreach ($tmpchoice as $val) {
5590
			$selected = '';
5591
			$tmp = explode(':', $val);
5592
			$key = $tmp[0];
5593
			$val = $tmp[1];
5594
			if ($key != '' && $val != '') {
5595
				if ((int) $key == (int) $limit) {
5596
					$selected = ' selected="selected"';
5597
				}
5598
				print '<option name="'.$key.'"'.$selected.'>'.dol_escape_htmltag($val).'</option>'."\n";
5599
			}
5600
		}
5601
		print '</select>';
5602
		if ($conf->use_javascript_ajax) {
5603
			print '<!-- JS CODE TO ENABLE select limit to launch submit of page -->
5604
            		<script>
5605
                	jQuery(document).ready(function () {
5606
            	  		jQuery(".selectlimit").change(function() {
5607
                            console.log("Change limit. Send submit");
5608
                            $(this).parents(\'form:first\').submit();
5609
            	  		});
5610
                	});
5611
            		</script>
5612
                ';
5613
		}
5614
		print '</li>';
5615
	}
5616
	if ($page > 0) {
5617
		print '<li class="pagination paginationpage paginationpageleft"><a class="paginationprevious" href="'.$file.'?page='.($page - 1).$options.'"><i class="fa fa-chevron-left" title="'.dol_escape_htmltag($langs->trans("Previous")).'"></i></a></li>';
5618
	}
5619
	if ($betweenarrows) {
5620
		print '<!--<div class="betweenarrows nowraponall inline-block">-->';
5621
		print $betweenarrows;
5622
		print '<!--</div>-->';
5623
	}
5624
	if ($nextpage > 0) {
5625
		print '<li class="pagination paginationpage paginationpageright"><a class="paginationnext" href="'.$file.'?page='.($page + 1).$options.'"><i class="fa fa-chevron-right" title="'.dol_escape_htmltag($langs->trans("Next")).'"></i></a></li>';
5626
	}
5627
	if ($afterarrows) {
5628
		print '<li class="paginationafterarrows">';
5629
		print $afterarrows;
5630
		print '</li>';
5631
	}
5632
	print '</ul></div>'."\n";
5633
}
5634
5635
5636
/**
5637
 *	Return a string with VAT rate label formated for view output
5638
 *	Used into pdf and HTML pages
5639
 *
5640
 *	@param	string	$rate			Rate value to format ('19.6', '19,6', '19.6%', '19,6%', '19.6 (CODEX)', ...)
5641
 *  @param	boolean	$addpercent		Add a percent % sign in output
5642
 *	@param	int		$info_bits		Miscellaneous information on vat (0=Default, 1=French NPR vat)
5643
 *	@param	int		$usestarfornpr	-1=Never show, 0 or 1=Use '*' for NPR vat rates
5644
 *  @param	int		$html			Used for html output
5645
 *  @return	string					String with formated amounts ('19,6' or '19,6%' or '8.5% (NPR)' or '8.5% *' or '19,6 (CODEX)')
5646
 */
5647
function vatrate($rate, $addpercent = false, $info_bits = 0, $usestarfornpr = 0, $html = 0)
5648
{
5649
	$morelabel = '';
5650
5651
	if (preg_match('/%/', $rate)) {
5652
		$rate = str_replace('%', '', $rate);
5653
		$addpercent = true;
5654
	}
5655
	$reg = array();
5656
	if (preg_match('/\((.*)\)/', $rate, $reg)) {
5657
		$morelabel = ' ('.$reg[1].')';
5658
		$rate = preg_replace('/\s*'.preg_quote($morelabel, '/').'/', '', $rate);
5659
		$morelabel = ' '.($html ? '<span class="opacitymedium">' : '').'('.$reg[1].')'.($html ? '</span>' : '');
5660
	}
5661
	if (preg_match('/\*/', $rate)) {
5662
		$rate = str_replace('*', '', $rate);
5663
		$info_bits |= 1;
5664
	}
5665
5666
	// If rate is '9/9/9' we don't change it.  If rate is '9.000' we apply price()
5667
	if (!preg_match('/\//', $rate)) {
5668
		$ret = price($rate, 0, '', 0, 0).($addpercent ? '%' : '');
5669
	} else {
5670
		// TODO Split on / and output with a price2num to have clean numbers without ton of 000.
5671
		$ret = $rate.($addpercent ? '%' : '');
5672
	}
5673
	if (($info_bits & 1) && $usestarfornpr >= 0) {
5674
		$ret .= ' *';
5675
	}
5676
	$ret .= $morelabel;
5677
	return $ret;
5678
}
5679
5680
5681
/**
5682
 *		Function to format a value into an amount for visual output
5683
 *		Function used into PDF and HTML pages
5684
 *
5685
 *		@param	float				$amount			Amount to format
5686
 *		@param	integer				$form			Type of format, HTML or not (not by default)
5687
 *		@param	Translate|string	$outlangs		Object langs for output. '' use default lang. 'none' use international separators.
5688
 *		@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.
5689
 *		@param	int					$rounding		MINIMUM number of decimal to show. 0=no change, -1=we use min($conf->global->MAIN_MAX_DECIMALS_UNIT,$conf->global->MAIN_MAX_DECIMALS_TOT)
5690
 *		@param	int|string			$forcerounding	Force the MAXIMUM of decimal to forcerounding decimal (-1=no change, 'MU' or 'MT' or numeric to round to MU or MT or to a given number of decimal)
5691
 *		@param	string				$currency_code	To add currency symbol (''=add nothing, 'auto'=Use default currency, 'XXX'=add currency symbols for XXX currency)
5692
 *		@return	string								String with formated amount
5693
 *
5694
 *		@see	price2num()							Revert function of price
5695
 */
5696
function price($amount, $form = 0, $outlangs = '', $trunc = 1, $rounding = -1, $forcerounding = -1, $currency_code = '')
5697
{
5698
	global $langs, $conf;
5699
5700
	// Clean parameters
5701
	if (empty($amount)) {
5702
		$amount = 0; // To have a numeric value if amount not defined or = ''
5703
	}
5704
	$amount = (is_numeric($amount) ? $amount : 0); // Check if amount is numeric, for example, an error occured when amount value = o (letter) instead 0 (number)
5705
	if ($rounding < 0) {
5706
		$rounding = min($conf->global->MAIN_MAX_DECIMALS_UNIT, $conf->global->MAIN_MAX_DECIMALS_TOT);
5707
	}
5708
	$nbdecimal = $rounding;
5709
5710
	if ($outlangs === 'none') {
5711
		// Use international separators
5712
		$dec = '.';
5713
		$thousand = '';
5714
	} else {
5715
		// Output separators by default (french)
5716
		$dec = ',';
5717
		$thousand = ' ';
5718
5719
		// If $outlangs not forced, we use use language
5720
		if (!is_object($outlangs)) {
5721
			$outlangs = $langs;
5722
		}
5723
5724
		if ($outlangs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal") {
5725
			$dec = $outlangs->transnoentitiesnoconv("SeparatorDecimal");
5726
		}
5727
		if ($outlangs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") {
5728
			$thousand = $outlangs->transnoentitiesnoconv("SeparatorThousand");
5729
		}
5730
		if ($thousand == 'None') {
5731
			$thousand = '';
5732
		} elseif ($thousand == 'Space') {
5733
			$thousand = ' ';
5734
		}
5735
	}
5736
	//print "outlangs=".$outlangs->defaultlang." amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
5737
5738
	//print "amount=".$amount."-";
5739
	$amount = str_replace(',', '.', $amount); // should be useless
5740
	//print $amount."-";
5741
	$datas = explode('.', $amount);
5742
	$decpart = isset($datas[1]) ? $datas[1] : '';
5743
	$decpart = preg_replace('/0+$/i', '', $decpart); // Supprime les 0 de fin de partie decimale
5744
	//print "decpart=".$decpart."<br>";
5745
	$end = '';
5746
5747
	// We increase nbdecimal if there is more decimal than asked (to not loose information)
5748
	if (dol_strlen($decpart) > $nbdecimal) {
5749
		$nbdecimal = dol_strlen($decpart);
5750
	}
5751
	// Si on depasse max
5752
	if ($trunc && $nbdecimal > $conf->global->MAIN_MAX_DECIMALS_SHOWN) {
5753
		$nbdecimal = $conf->global->MAIN_MAX_DECIMALS_SHOWN;
5754
		if (preg_match('/\.\.\./i', $conf->global->MAIN_MAX_DECIMALS_SHOWN)) {
5755
			// Si un affichage est tronque, on montre des ...
5756
			$end = '...';
5757
		}
5758
	}
5759
5760
	// If force rounding
5761
	if ((string) $forcerounding != '-1') {
5762
		if ($forcerounding === 'MU') {
5763
			$nbdecimal = $conf->global->MAIN_MAX_DECIMALS_UNIT;
5764
		} elseif ($forcerounding === 'MT') {
5765
			$nbdecimal = $conf->global->MAIN_MAX_DECIMALS_TOT;
5766
		} elseif ($forcerounding >= 0) {
5767
			$nbdecimal = $forcerounding;
5768
		}
5769
	}
5770
5771
	// Format number
5772
	$output = number_format($amount, $nbdecimal, $dec, $thousand);
5773
	if ($form) {
5774
		$output = preg_replace('/\s/', '&nbsp;', $output);
5775
		$output = preg_replace('/\'/', '&#039;', $output);
5776
	}
5777
	// Add symbol of currency if requested
5778
	$cursymbolbefore = $cursymbolafter = '';
5779
	if ($currency_code && is_object($outlangs)) {
5780
		if ($currency_code == 'auto') {
5781
			$currency_code = $conf->currency;
5782
		}
5783
5784
		$listofcurrenciesbefore = array('AUD', 'CAD', 'CNY', 'COP', 'CLP', 'GBP', 'HKD', 'MXN', 'PEN', 'USD', 'CRC');
5785
		$listoflanguagesbefore = array('nl_NL');
5786
		if (in_array($currency_code, $listofcurrenciesbefore) || in_array($outlangs->defaultlang, $listoflanguagesbefore)) {
5787
			$cursymbolbefore .= $outlangs->getCurrencySymbol($currency_code);
5788
		} else {
5789
			$tmpcur = $outlangs->getCurrencySymbol($currency_code);
5790
			$cursymbolafter .= ($tmpcur == $currency_code ? ' '.$tmpcur : $tmpcur);
5791
		}
5792
	}
5793
	$output = $cursymbolbefore.$output.$end.($cursymbolafter ? ' ' : '').$cursymbolafter;
5794
5795
	return $output;
5796
}
5797
5798
/**
5799
 *	Function that return a number with universal decimal format (decimal separator is '.') from an amount typed by a user.
5800
 *	Function to use on each input amount before any numeric test or database insert. A better name for this function
5801
 *  should be roundtext2num().
5802
 *
5803
 *	@param	string|float	$amount			Amount to convert/clean or round
5804
 *	@param	string|int		$rounding		''=No rounding
5805
 * 											'MU'=Round to Max unit price (MAIN_MAX_DECIMALS_UNIT)
5806
 *											'MT'=Round to Max for totals with Tax (MAIN_MAX_DECIMALS_TOT)
5807
 *											'MS'=Round to Max for stock quantity (MAIN_MAX_DECIMALS_STOCK)
5808
 *      		                            'CU'=Round to Max unit price of foreign currency accuracy
5809
 *      		                            'CT'=Round to Max for totals with Tax of foreign currency accuracy
5810
 *											Numeric = Nb of digits for rounding (For example 2 for a percentage)
5811
 * 	@param	int				$option			Put 1 if you know that content is already universal format number (so no correction on decimal will be done)
5812
 * 											Put 2 if you know that number is a user input (so we know we have to fix decimal separator).
5813
 *	@return	string							Amount with universal numeric format (Example: '99.99999').
5814
 *											If conversion fails to return a numeric, it returns:
5815
 *											- text unchanged or partial if ($rounding = ''): price2num('W9ç', '', 0)   => '9ç', price2num('W9ç', '', 1)   => 'W9ç', price2num('W9ç', '', 2)   => '9ç'
5816
 *											- '0' if ($rounding is defined):                 price2num('W9ç', 'MT', 0) => '9',  price2num('W9ç', 'MT', 1) => '0',   price2num('W9ç', 'MT', 2) => '9'
5817
 *											Note: The best way to guarantee a numeric value is to add a cast (float) before the price2num().
5818
 *											If amount is null or '', it returns '' if $rounding = '' or '0' if $rounding is defined.
5819
 *
5820
 *	@see    price()							Opposite function of price2num
5821
 */
5822
function price2num($amount, $rounding = '', $option = 0)
5823
{
5824
	global $langs, $conf;
5825
5826
	// Clean parameters
5827
	if (is_null($amount)) {
0 ignored issues
show
introduced by
The condition is_null($amount) is always false.
Loading history...
5828
		$amount = '';
5829
	}
5830
5831
	// Round PHP function does not allow number like '1,234.56' nor '1.234,56' nor '1 234,56'
5832
	// Numbers must be '1234.56'
5833
	// Decimal delimiter for PHP and database SQL requests must be '.'
5834
	$dec = ',';
5835
	$thousand = ' ';
5836
	if (is_null($langs)) {	// $langs is not defined, we use english values.
5837
		$dec = '.';
5838
		$thousand = ',';
5839
	} else {
5840
		if ($langs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal") {
5841
			$dec = $langs->transnoentitiesnoconv("SeparatorDecimal");
5842
		}
5843
		if ($langs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") {
5844
			$thousand = $langs->transnoentitiesnoconv("SeparatorThousand");
5845
		}
5846
	}
5847
	if ($thousand == 'None') {
5848
		$thousand = '';
5849
	} elseif ($thousand == 'Space') {
5850
		$thousand = ' ';
5851
	}
5852
	//print "amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
5853
5854
	// Convert value to universal number format (no thousand separator, '.' as decimal separator)
5855
	if ($option != 1) {	// If not a PHP number or unknown, we change or clean format
5856
		//print "\n".'PP'.$amount.' - '.$dec.' - '.$thousand.' - '.intval($amount).'<br>';
5857
		if (!is_numeric($amount)) {
5858
			$amount = preg_replace('/[a-zA-Z\/\\\*\(\)\<\>\_]/', '', $amount);
5859
		}
5860
5861
		if ($option == 2 && $thousand == '.' && preg_match('/\.(\d\d\d)$/', (string) $amount)) {	// It means the . is used as a thousand separator and string come from input data, so 1.123 is 1123
5862
			$amount = str_replace($thousand, '', $amount);
5863
		}
5864
5865
		// Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
5866
		// to format defined by LC_NUMERIC after a calculation and we want source format to be like defined by Dolibarr setup.
5867
		// So if number was already a good number, it is converted into local Dolibarr setup.
5868
		if (is_numeric($amount)) {
5869
			// We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
5870
			$temps = sprintf("%0.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
5871
			$temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
5872
			$nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
5873
			$amount = number_format($amount, $nbofdec, $dec, $thousand);
5874
		}
5875
		//print "QQ".$amount."<br>\n";
5876
5877
		// Now make replace (the main goal of function)
5878
		if ($thousand != ',' && $thousand != '.') {
5879
			$amount = str_replace(',', '.', $amount); // To accept 2 notations for french users
5880
		}
5881
5882
		$amount = str_replace(' ', '', $amount); // To avoid spaces
5883
		$amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
5884
		$amount = str_replace($dec, '.', $amount);
5885
5886
		$amount = preg_replace('/[^0-9\-\.]/', '', $amount);	// Clean non numeric chars (so it clean some UTF8 spaces for example.
5887
	}
5888
	//print ' XX'.$amount.' '.$rounding;
5889
5890
	// Now, $amount is a real PHP float number. We make a rounding if required.
5891
	if ($rounding) {
5892
		$nbofdectoround = '';
5893
		if ($rounding == 'MU') {
5894
			$nbofdectoround = $conf->global->MAIN_MAX_DECIMALS_UNIT;
5895
		} elseif ($rounding == 'MT') {
5896
			$nbofdectoround = $conf->global->MAIN_MAX_DECIMALS_TOT;
5897
		} elseif ($rounding == 'MS') {
5898
			$nbofdectoround = isset($conf->global->MAIN_MAX_DECIMALS_STOCK) ? $conf->global->MAIN_MAX_DECIMALS_STOCK : 5;
5899
		} elseif ($rounding == 'CU') {
5900
			$nbofdectoround = max($conf->global->MAIN_MAX_DECIMALS_UNIT, 8);	// TODO Use param of currency
5901
		} elseif ($rounding == 'CT') {
5902
			$nbofdectoround = max($conf->global->MAIN_MAX_DECIMALS_TOT, 8);		// TODO Use param of currency
5903
		} elseif (is_numeric($rounding)) {
5904
			$nbofdectoround = (int) $rounding;
5905
		}
5906
5907
		//print " RR".$amount.' - '.$nbofdectoround.'<br>';
5908
		if (dol_strlen($nbofdectoround)) {
5909
			$amount = round(is_string($amount) ? (float) $amount : $amount, $nbofdectoround); // $nbofdectoround can be 0.
5910
		} else {
5911
			return 'ErrorBadParameterProvidedToFunction';
5912
		}
5913
		//print ' SS'.$amount.' - '.$nbofdec.' - '.$dec.' - '.$thousand.' - '.$nbofdectoround.'<br>';
5914
5915
		// Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
5916
		// to format defined by LC_NUMERIC after a calculation and we want source format to be defined by Dolibarr setup.
5917
		if (is_numeric($amount)) {
5918
			// We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
5919
			$temps = sprintf("%0.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
5920
			$temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
5921
			$nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
5922
			$amount = number_format($amount, min($nbofdec, $nbofdectoround), $dec, $thousand); // Convert amount to format with dolibarr dec and thousand
5923
		}
5924
		//print "TT".$amount.'<br>';
5925
5926
		// Always make replace because each math function (like round) replace
5927
		// with local values and we want a number that has a SQL string format x.y
5928
		if ($thousand != ',' && $thousand != '.') {
5929
			$amount = str_replace(',', '.', $amount); // To accept 2 notations for french users
5930
		}
5931
5932
		$amount = str_replace(' ', '', $amount); // To avoid spaces
5933
		$amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
5934
		$amount = str_replace($dec, '.', $amount);
5935
5936
		$amount = preg_replace('/[^0-9\-\.]/', '', $amount);	// Clean non numeric chars (so it clean some UTF8 spaces for example.
5937
	}
5938
5939
	return $amount;
5940
}
5941
5942
/**
5943
 * Output a dimension with best unit
5944
 *
5945
 * @param   float       $dimension      	Dimension
5946
 * @param   int         $unit           	Unit scale of dimension (Example: 0=kg, -3=g, -6=mg, 98=ounce, 99=pound, ...)
5947
 * @param   string      $type           	'weight', 'volume', ...
5948
 * @param   Translate   $outputlangs    	Translate language object
5949
 * @param   int         $round          	-1 = non rounding, x = number of decimal
5950
 * @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)
5951
 * @param	int			$use_short_label	1=Use short label ('g' instead of 'gram'). Short labels are not translated.
5952
 * @return  string                      	String to show dimensions
5953
 */
5954
function showDimensionInBestUnit($dimension, $unit, $type, $outputlangs, $round = -1, $forceunitoutput = 'no', $use_short_label = 0)
5955
{
5956
	require_once DOL_DOCUMENT_ROOT.'/core/lib/product.lib.php';
5957
5958
	if (($forceunitoutput == 'no' && $dimension < 1 / 10000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -6)) {
5959
		$dimension = $dimension * 1000000;
5960
		$unit = $unit - 6;
5961
	} elseif (($forceunitoutput == 'no' && $dimension < 1 / 10 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -3)) {
5962
		$dimension = $dimension * 1000;
5963
		$unit = $unit - 3;
5964
	} elseif (($forceunitoutput == 'no' && $dimension > 100000000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 6)) {
5965
		$dimension = $dimension / 1000000;
5966
		$unit = $unit + 6;
5967
	} elseif (($forceunitoutput == 'no' && $dimension > 100000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 3)) {
5968
		$dimension = $dimension / 1000;
5969
		$unit = $unit + 3;
5970
	}
5971
	// Special case when we want output unit into pound or ounce
5972
	/* TODO
5973
	if ($unit < 90 && $type == 'weight' && is_numeric($forceunitoutput) && (($forceunitoutput == 98) || ($forceunitoutput == 99))
5974
	{
5975
		$dimension = // convert dimension from standard unit into ounce or pound
5976
		$unit = $forceunitoutput;
5977
	}
5978
	if ($unit > 90 && $type == 'weight' && is_numeric($forceunitoutput) && $forceunitoutput < 90)
5979
	{
5980
		$dimension = // convert dimension from standard unit into ounce or pound
5981
		$unit = $forceunitoutput;
5982
	}*/
5983
5984
	$ret = price($dimension, 0, $outputlangs, 0, 0, $round);
5985
	$ret .= ' '.measuringUnitString(0, $type, $unit, $use_short_label, $outputlangs);
5986
5987
	return $ret;
5988
}
5989
5990
5991
/**
5992
 *	Return localtax rate for a particular vat, when selling a product with vat $vatrate, from a $thirdparty_buyer to a $thirdparty_seller
5993
 *  Note: This function applies same rules than get_default_tva
5994
 *
5995
 * 	@param	float		$vatrate		        Vat rate. Can be '8.5' or '8.5 (VATCODEX)' for example
5996
 * 	@param  int			$local		         	Local tax to search and return (1 or 2 return only tax rate 1 or tax rate 2)
5997
 *  @param  Societe		$thirdparty_buyer    	Object of buying third party
5998
 *  @param	Societe		$thirdparty_seller		Object of selling third party ($mysoc if not defined)
5999
 *  @param	int			$vatnpr					If vat rate is NPR or not
6000
 * 	@return	mixed			   					0 if not found, localtax rate if found
6001
 *  @see get_default_tva()
6002
 */
6003
function get_localtax($vatrate, $local, $thirdparty_buyer = "", $thirdparty_seller = "", $vatnpr = 0)
6004
{
6005
	global $db, $conf, $mysoc;
6006
6007
	if (empty($thirdparty_seller) || !is_object($thirdparty_seller)) {
6008
		$thirdparty_seller = $mysoc;
6009
	}
6010
6011
	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);
6012
6013
	$vatratecleaned = $vatrate;
6014
	$reg = array();
6015
	if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) {     // If vat is "xx (yy)"
6016
		$vatratecleaned = trim($reg[1]);
6017
		$vatratecode = $reg[2];
6018
	}
6019
6020
	/*if ($thirdparty_buyer->country_code != $thirdparty_seller->country_code)
6021
	{
6022
		return 0;
6023
	}*/
6024
6025
	// Some test to guess with no need to make database access
6026
	if ($mysoc->country_code == 'ES') { // For spain localtaxes 1 and 2, tax is qualified if buyer use local tax
6027
		if ($local == 1) {
6028
			if (!$mysoc->localtax1_assuj || (string) $vatratecleaned == "0") {
6029
				return 0;
6030
			}
6031
			if ($thirdparty_seller->id == $mysoc->id) {
6032
				if (!$thirdparty_buyer->localtax1_assuj) {
6033
					return 0;
6034
				}
6035
			} else {
6036
				if (!$thirdparty_seller->localtax1_assuj) {
6037
					return 0;
6038
				}
6039
			}
6040
		}
6041
6042
		if ($local == 2) {
6043
			//if (! $mysoc->localtax2_assuj || (string) $vatratecleaned == "0") return 0;
6044
			if (!$mysoc->localtax2_assuj) {
6045
				return 0; // If main vat is 0, IRPF may be different than 0.
6046
			}
6047
			if ($thirdparty_seller->id == $mysoc->id) {
6048
				if (!$thirdparty_buyer->localtax2_assuj) {
6049
					return 0;
6050
				}
6051
			} else {
6052
				if (!$thirdparty_seller->localtax2_assuj) {
6053
					return 0;
6054
				}
6055
			}
6056
		}
6057
	} else {
6058
		if ($local == 1 && !$thirdparty_seller->localtax1_assuj) {
6059
			return 0;
6060
		}
6061
		if ($local == 2 && !$thirdparty_seller->localtax2_assuj) {
6062
			return 0;
6063
		}
6064
	}
6065
6066
	// For some country MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY is forced to on.
6067
	if (in_array($mysoc->country_code, array('ES'))) {
6068
		$conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY = 1;
6069
	}
6070
6071
	// Search local taxes
6072
	if (!empty($conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY)) {
6073
		if ($local == 1) {
6074
			if ($thirdparty_seller != $mysoc) {
6075
				if (!isOnlyOneLocalTax($local)) {  // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
6076
					return $thirdparty_seller->localtax1_value;
6077
				}
6078
			} else { // i am the seller
6079
				if (!isOnlyOneLocalTax($local)) { // TODO If seller is me, why not always returning this, even if there is only one locatax vat.
6080
					return $conf->global->MAIN_INFO_VALUE_LOCALTAX1;
6081
				}
6082
			}
6083
		}
6084
		if ($local == 2) {
6085
			if ($thirdparty_seller != $mysoc) {
6086
				if (!isOnlyOneLocalTax($local)) {  // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
6087
					// TODO We should also return value defined on thirdparty only if defined
6088
					return $thirdparty_seller->localtax2_value;
6089
				}
6090
			} else { // i am the seller
6091
				if (in_array($mysoc->country_code, array('ES'))) {
6092
					return $thirdparty_buyer->localtax2_value;
6093
				} else {
6094
					return $conf->global->MAIN_INFO_VALUE_LOCALTAX2;
6095
				}
6096
			}
6097
		}
6098
	}
6099
6100
	// By default, search value of local tax on line of common tax
6101
	$sql = "SELECT t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
6102
	$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
6103
	$sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($thirdparty_seller->country_code)."'";
6104
	$sql .= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
6105
	if (!empty($vatratecode)) {
6106
		$sql .= " AND t.code ='".$db->escape($vatratecode)."'"; // If we have the code, we use it in priority
6107
	} else {
6108
		$sql .= " AND t.recuperableonly = '".$db->escape($vatnpr)."'";
6109
	}
6110
6111
	$resql = $db->query($sql);
6112
6113
	if ($resql) {
6114
		$obj = $db->fetch_object($resql);
6115
		if ($obj) {
6116
			if ($local == 1) {
6117
				return $obj->localtax1;
6118
			} elseif ($local == 2) {
6119
				return $obj->localtax2;
6120
			}
6121
		}
6122
	}
6123
6124
	return 0;
6125
}
6126
6127
6128
/**
6129
 * Return true if LocalTax (1 or 2) is unique.
6130
 * Example: If localtax1 is 5 on line with highest common vat rate, return true
6131
 * Example: If localtax1 is 5:8:15 on line with highest common vat rate, return false
6132
 *
6133
 * @param   int 	$local	Local tax to test (1 or 2)
6134
 * @return  boolean 		True if LocalTax have multiple values, False if not
6135
 */
6136
function isOnlyOneLocalTax($local)
6137
{
6138
	$tax = get_localtax_by_third($local);
6139
6140
	$valors = explode(":", $tax);
6141
6142
	if (count($valors) > 1) {
6143
		return false;
6144
	} else {
6145
		return true;
6146
	}
6147
}
6148
6149
/**
6150
 * Get values of localtaxes (1 or 2) for company country for the common vat with the highest value
6151
 *
6152
 * @param	int		$local 	LocalTax to get
6153
 * @return	number			Values of localtax
6154
 */
6155
function get_localtax_by_third($local)
6156
{
6157
	global $db, $mysoc;
6158
	$sql = "SELECT t.localtax1, t.localtax2 ";
6159
	$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t inner join ".MAIN_DB_PREFIX."c_country as c ON c.rowid=t.fk_pays";
6160
	$sql .= " WHERE c.code = '".$db->escape($mysoc->country_code)."' AND t.active = 1 AND t.taux=(";
6161
	$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";
6162
	$sql .= "  WHERE c.code = '".$db->escape($mysoc->country_code)."' AND tt.active = 1";
6163
	$sql .= "  )";
6164
6165
	$resql = $db->query($sql);
6166
	if ($resql) {
6167
		$obj = $db->fetch_object($resql);
6168
		if ($local == 1) {
6169
			return $obj->localtax1;
6170
		} elseif ($local == 2) {
6171
			return $obj->localtax2;
6172
		}
6173
	}
6174
6175
	return 0;
6176
}
6177
6178
6179
/**
6180
 *  Get tax (VAT) main information from Id.
6181
 *  You can also call getLocalTaxesFromRate() after to get only localtax fields.
6182
 *
6183
 *  @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.
6184
 *  @param	Societe	    $buyer         		Company object
6185
 *  @param	Societe	    $seller        		Company object
6186
 *  @param  int         $firstparamisid     1 if first param is id into table (use this if you can)
6187
 *  @return	array       	  				array('rowid'=> , 'code'=> ...)
6188
 *  @see getLocalTaxesFromRate()
6189
 */
6190
function getTaxesFromId($vatrate, $buyer = null, $seller = null, $firstparamisid = 1)
6191
{
6192
	global $db, $mysoc;
6193
6194
	dol_syslog("getTaxesFromId vat id or rate = ".$vatrate);
6195
6196
	// Search local taxes
6197
	$sql = "SELECT t.rowid, t.code, t.taux as rate, t.recuperableonly as npr, t.accountancy_code_sell, t.accountancy_code_buy,";
6198
	$sql .= " t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type";
6199
	$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t";
6200
	if ($firstparamisid) {
6201
		$sql .= " WHERE t.rowid = ".(int) $vatrate;
6202
	} else {
6203
		$vatratecleaned = $vatrate;
6204
		$vatratecode = '';
6205
		$reg = array();
6206
		if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) {      // If vat is "xx (yy)"
6207
			$vatratecleaned = $reg[1];
6208
			$vatratecode = $reg[2];
6209
		}
6210
6211
		$sql .= ", ".MAIN_DB_PREFIX."c_country as c";
6212
		/*if ($mysoc->country_code == 'ES') $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($buyer->country_code)."'";    // vat in spain use the buyer country ??
6213
		else $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($seller->country_code)."'";*/
6214
		$sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($seller->country_code)."'";
6215
		$sql .= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
6216
		if ($vatratecode) {
6217
			$sql .= " AND t.code = '".$db->escape($vatratecode)."'";
6218
		}
6219
	}
6220
6221
	$resql = $db->query($sql);
6222
	if ($resql) {
6223
		$obj = $db->fetch_object($resql);
6224
		if ($obj) {
6225
			return array(
6226
			'rowid'=>$obj->rowid,
6227
			'code'=>$obj->code,
6228
			'rate'=>$obj->rate,
6229
			'localtax1'=>$obj->localtax1,
6230
			'localtax1_type'=>$obj->localtax1_type,
6231
			'localtax2'=>$obj->localtax2,
6232
			'localtax2_type'=>$obj->localtax2_type,
6233
			'npr'=>$obj->npr,
6234
			'accountancy_code_sell'=>$obj->accountancy_code_sell,
6235
			'accountancy_code_buy'=>$obj->accountancy_code_buy
6236
			);
6237
		} else {
6238
			return array();
6239
		}
6240
	} else {
6241
		dol_print_error($db);
6242
	}
6243
6244
	return array();
6245
}
6246
6247
/**
6248
 *  Get type and rate of localtaxes for a particular vat rate/country of a thirdparty.
6249
 *  This does not take into account the seller setup if subject to vat or not, only country.
6250
 *
6251
 *  TODO This function is ALSO called to retrieve type for building PDF. Such call of function must be removed.
6252
 *  Instead this function must be called when adding a line to get the array of possible values for localtax and type, and then
6253
 *  provide the selected value to the function calcul_price_total.
6254
 *
6255
 *  @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.
6256
 *  @param	int		    $local              Number of localtax (1 or 2, or 0 to return 1 & 2)
6257
 *  @param	Societe	    $buyer         		Company object
6258
 *  @param	Societe	    $seller        		Company object
6259
 *  @param  int         $firstparamisid     1 if first param is ID into table instead of Rate+code (use this if you can)
6260
 *  @return	array    	    				array(localtax_type1(1-6 or 0 if not found), rate localtax1, localtax_type2, rate localtax2, accountancycodecust, accountancycodesupp)
6261
 *  @see getTaxesFromId()
6262
 */
6263
function getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid = 0)
6264
{
6265
	global $db, $mysoc;
6266
6267
	dol_syslog("getLocalTaxesFromRate vatrate=".$vatrate." local=".$local);
6268
6269
	// Search local taxes
6270
	$sql  = "SELECT t.taux as rate, t.code, t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type, t.accountancy_code_sell, t.accountancy_code_buy";
6271
	$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t";
6272
	if ($firstparamisid) {
6273
		$sql .= " WHERE t.rowid = ".(int) $vatrate;
6274
	} else {
6275
		$vatratecleaned = $vatrate;
6276
		$vatratecode = '';
6277
		$reg = array();
6278
		if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) {     // If vat is "x.x (yy)"
6279
			$vatratecleaned = $reg[1];
6280
			$vatratecode = $reg[2];
6281
		}
6282
6283
		$sql .= ", ".MAIN_DB_PREFIX."c_country as c";
6284
		if ($mysoc->country_code == 'ES') {
6285
			$sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($buyer->country_code)."'"; // local tax in spain use the buyer country ??
6286
		} else {
6287
			$sql .= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape(empty($seller->country_code) ? $mysoc->country_code : $seller->country_code)."'";
6288
		}
6289
		$sql .= " AND t.taux = ".((float) $vatratecleaned)." AND t.active = 1";
6290
		if ($vatratecode) {
6291
			$sql .= " AND t.code = '".$db->escape($vatratecode)."'";
6292
		}
6293
	}
6294
6295
	$resql = $db->query($sql);
6296
	if ($resql) {
6297
		$obj = $db->fetch_object($resql);
6298
6299
		if ($obj) {
6300
			$vateratestring = $obj->rate.($obj->code ? ' ('.$obj->code.')' : '');
6301
6302
			if ($local == 1) {
6303
				return array($obj->localtax1_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
6304
			} elseif ($local == 2) {
6305
				return array($obj->localtax2_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
6306
			} else {
6307
				return array($obj->localtax1_type, get_localtax($vateratestring, 1, $buyer, $seller), $obj->localtax2_type, get_localtax($vateratestring, 2, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
6308
			}
6309
		}
6310
	}
6311
6312
	return array();
6313
}
6314
6315
/**
6316
 *	Return vat rate of a product in a particular country, or default country vat if product is unknown.
6317
 *  Function called by get_default_tva().
6318
 *
6319
 *  @param	int				$idprod          	Id of product or 0 if not a predefined product
6320
 *  @param  Societe			$thirdpartytouse  	Thirdparty with a ->country_code defined (FR, US, IT, ...)
6321
 *	@param	int				$idprodfournprice	Id product_fournisseur_price (for "supplier" proposal/order/invoice)
6322
 *  @return float|string   					    Vat rate to use with format 5.0 or '5.0 (XXX)'
6323
 *  @see get_product_localtax_for_country()
6324
 */
6325
function get_product_vat_for_country($idprod, $thirdpartytouse, $idprodfournprice = 0)
6326
{
6327
	global $db, $conf, $mysoc;
6328
6329
	require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
6330
6331
	$ret = 0;
6332
	$found = 0;
6333
6334
	if ($idprod > 0) {
6335
		// Load product
6336
		$product = new Product($db);
6337
		$product->fetch($idprod);
6338
6339
		if ($mysoc->country_code == $thirdpartytouse->country_code) {
6340
			// If country to consider is ours
6341
			if ($idprodfournprice > 0) {     // We want vat for product for a "supplier" object
6342
				$result = $product->get_buyprice($idprodfournprice, 0, 0, 0);
6343
				if ($result > 0) {
6344
					$ret = $product->vatrate_supplier;
6345
					if ($product->default_vat_code_supplier) {
6346
						$ret .= ' ('.$product->default_vat_code_supplier.')';
6347
					}
6348
					$found = 1;
6349
				}
6350
			}
6351
			if (!$found) {
6352
				$ret = $product->tva_tx; 	// Default sales vat of product
6353
				if ($product->default_vat_code) {
6354
					$ret .= ' ('.$product->default_vat_code.')';
6355
				}
6356
				$found = 1;
6357
			}
6358
		} else {
6359
			// TODO Read default product vat according to product and another countrycode.
6360
			// Vat for couple anothercountrycode/product is data that is not managed and store yet, so we will fallback on next rule.
6361
		}
6362
	}
6363
6364
	if (!$found) {
6365
		if (empty($conf->global->MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS)) {
6366
			// If vat of product for the country not found or not defined, we return the first rate found (sorting on use_default, then on higher vat of country).
6367
			$sql = "SELECT t.taux as vat_rate, t.code as default_vat_code";
6368
			$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
6369
			$sql .= " WHERE t.active = 1 AND t.fk_pays = c.rowid AND c.code = '".$db->escape($thirdpartytouse->country_code)."'";
6370
			$sql .= " ORDER BY t.use_default DESC, t.taux DESC, t.code ASC, t.recuperableonly ASC";
6371
			$sql .= $db->plimit(1);
6372
6373
			$resql = $db->query($sql);
6374
			if ($resql) {
6375
				$obj = $db->fetch_object($resql);
6376
				if ($obj) {
6377
					$ret = $obj->vat_rate;
6378
					if ($obj->default_vat_code) {
6379
						$ret .= ' ('.$obj->default_vat_code.')';
6380
					}
6381
				}
6382
				$db->free($resql);
6383
			} else {
6384
				dol_print_error($db);
6385
			}
6386
		} else {
6387
			// Forced value if autodetect fails. MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS can be
6388
			// '1.23'
6389
			// or '1.23 (CODE)'
6390
			$defaulttx = '';
6391
			if ($conf->global->MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS != 'none') {
6392
				$defaulttx = $conf->global->MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS;
6393
			}
6394
			/*if (preg_match('/\((.*)\)/', $defaulttx, $reg)) {
6395
				$defaultcode = $reg[1];
6396
				$defaulttx = preg_replace('/\s*\(.*\)/', '', $defaulttx);
6397
			}*/
6398
6399
			$ret = $defaulttx;
6400
		}
6401
	}
6402
6403
	dol_syslog("get_product_vat_for_country: ret=".$ret);
6404
	return $ret;
6405
}
6406
6407
/**
6408
 *	Return localtax vat rate of a product in a particular country or default country vat if product is unknown
6409
 *
6410
 *  @param	int		$idprod         		Id of product
6411
 *  @param  int		$local          		1 for localtax1, 2 for localtax 2
6412
 *  @param  Societe	$thirdpartytouse    	Thirdparty with a ->country_code defined (FR, US, IT, ...)
6413
 *  @return int             				<0 if KO, Vat rate if OK
6414
 *  @see get_product_vat_for_country()
6415
 */
6416
function get_product_localtax_for_country($idprod, $local, $thirdpartytouse)
6417
{
6418
	global $db, $mysoc;
6419
6420
	if (!class_exists('Product')) {
6421
		require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
6422
	}
6423
6424
	$ret = 0;
6425
	$found = 0;
6426
6427
	if ($idprod > 0) {
6428
		// Load product
6429
		$product = new Product($db);
6430
		$result = $product->fetch($idprod);
6431
6432
		if ($mysoc->country_code == $thirdpartytouse->country_code) { // If selling country is ours
6433
			/* Not defined yet, so we don't use this
6434
			if ($local==1) $ret=$product->localtax1_tx;
6435
			elseif ($local==2) $ret=$product->localtax2_tx;
6436
			$found=1;
6437
			*/
6438
		} else {
6439
			// TODO Read default product vat according to product and another countrycode.
6440
			// Vat for couple anothercountrycode/product is data that is not managed and store yet, so we will fallback on next rule.
6441
		}
6442
	}
6443
6444
	if (!$found) {
6445
		// If vat of product for the country not found or not defined, we return higher vat of country.
6446
		$sql = "SELECT taux as vat_rate, localtax1, localtax2";
6447
		$sql .= " FROM ".MAIN_DB_PREFIX."c_tva as t, ".MAIN_DB_PREFIX."c_country as c";
6448
		$sql .= " WHERE t.active=1 AND t.fk_pays = c.rowid AND c.code='".$db->escape($thirdpartytouse->country_code)."'";
6449
		$sql .= " ORDER BY t.taux DESC, t.recuperableonly ASC";
6450
		$sql .= $db->plimit(1);
6451
6452
		$resql = $db->query($sql);
6453
		if ($resql) {
6454
			$obj = $db->fetch_object($resql);
6455
			if ($obj) {
6456
				if ($local == 1) {
6457
					$ret = $obj->localtax1;
6458
				} elseif ($local == 2) {
6459
					$ret = $obj->localtax2;
6460
				}
6461
			}
6462
		} else {
6463
			dol_print_error($db);
6464
		}
6465
	}
6466
6467
	dol_syslog("get_product_localtax_for_country: ret=".$ret);
6468
	return $ret;
6469
}
6470
6471
/**
6472
 *	Function that return vat rate of a product line (according to seller, buyer and product vat rate)
6473
 *   VATRULE 1: If seller does not use VAT, default VAT is 0. End of rule.
6474
 *	 VATRULE 2: If the (seller country = buyer country) then the default VAT = VAT of the product sold. End of rule.
6475
 *	 VATRULE 3: If (seller and buyer in the European Community) and (property sold = new means of transport such as car, boat, plane) then VAT by default = 0 (VAT must be paid by the buyer to the tax center of his country and not to the seller). End of rule.
6476
 *	 VATRULE 4: If (seller and buyer in the European Community) and (buyer = individual) then VAT by default = VAT of the product sold. End of rule
6477
 *	 VATRULE 5: If (seller and buyer in European Community) and (buyer = company) then VAT by default=0. End of rule
6478
 *	 VATRULE 6: Otherwise the VAT proposed by default=0. End of rule.
6479
 *
6480
 *	@param	Societe		$thirdparty_seller    	Objet societe vendeuse
6481
 *	@param  Societe		$thirdparty_buyer   	Objet societe acheteuse
6482
 *	@param  int			$idprod					Id product
6483
 *	@param	int			$idprodfournprice		Id product_fournisseur_price (for supplier order/invoice)
6484
 *	@return float|string   				      	Vat rate to use with format 5.0 or '5.0 (XXX)', -1 if we can't guess it
6485
 *  @see get_default_npr(), get_default_localtax()
6486
 */
6487
function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
6488
{
6489
	global $conf;
6490
6491
	require_once DOL_DOCUMENT_ROOT.'/core/lib/company.lib.php';
6492
6493
	// Note: possible values for tva_assuj are 0/1 or franchise/reel
6494
	$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;
6495
6496
	$seller_country_code = $thirdparty_seller->country_code;
6497
	$seller_in_cee = isInEEC($thirdparty_seller);
6498
6499
	$buyer_country_code = $thirdparty_buyer->country_code;
6500
	$buyer_in_cee = isInEEC($thirdparty_buyer);
6501
6502
	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 : ''));
6503
6504
	// 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)
6505
	// we use the buyer VAT.
6506
	if (!empty($conf->global->SERVICE_ARE_ECOMMERCE_200238EC)) {
6507
		if ($seller_in_cee && $buyer_in_cee) {
6508
			$isacompany = $thirdparty_buyer->isACompany();
6509
			if ($isacompany && getDolGlobalString('MAIN_USE_VAT_COMPANIES_IN_EEC_WITH_INVALID_VAT_ID_ARE_INDIVIDUAL')) {
6510
				require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
6511
				if (!isValidVATID($thirdparty_buyer)) {
6512
					$isacompany = 0;
6513
				}
6514
			}
6515
6516
			if (!$isacompany) {
6517
				//print 'VATRULE 0';
6518
				return get_product_vat_for_country($idprod, $thirdparty_buyer, $idprodfournprice);
6519
			}
6520
		}
6521
	}
6522
6523
	// If seller does not use VAT, default VAT is 0. End of rule.
6524
	if (!$seller_use_vat) {
6525
		//print 'VATRULE 1';
6526
		return 0;
6527
	}
6528
6529
	// If the (seller country = buyer country) then the default VAT = VAT of the product sold. End of rule.
6530
	if (($seller_country_code == $buyer_country_code)
6531
	|| (in_array($seller_country_code, array('FR', 'MC')) && in_array($buyer_country_code, array('FR', 'MC')))) { // Warning ->country_code not always defined
6532
		//print 'VATRULE 2';
6533
		$tmpvat = get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
6534
6535
		if ($seller_country_code == 'IN' && getDolGlobalString('MAIN_SALETAX_AUTOSWITCH_I_CS_FOR_INDIA')) {
6536
			// Special case for india.
6537
			//print 'VATRULE 2b';
6538
			$reg = array();
6539
			if (preg_match('/C+S-(\d+)/', $tmpvat, $reg) && $thirdparty_seller->state_id != $thirdparty_buyer->state_id) {
6540
				// we must revert the C+S into I
6541
				$tmpvat = str_replace("C+S", "I", $tmpvat);
6542
			} elseif (preg_match('/I-(\d+)/', $tmpvat, $reg) && $thirdparty_seller->state_id == $thirdparty_buyer->state_id) {
6543
				// we must revert the I into C+S
6544
				$tmpvat = str_replace("I", "C+S", $tmpvat);
6545
			}
6546
		}
6547
6548
		return $tmpvat;
6549
	}
6550
6551
	// If (seller and buyer in the European Community) and (property sold = new means of transport such as car, boat, plane) then VAT by default = 0 (VAT must be paid by the buyer to the tax center of his country and not to the seller). End of rule.
6552
	// 'VATRULE 3' - Not supported
6553
6554
	// If (seller and buyer in the European Community) and (buyer = individual) then VAT by default = VAT of the product sold. End of rule
6555
	// If (seller and buyer in European Community) and (buyer = company) then VAT by default=0. End of rule
6556
	if (($seller_in_cee && $buyer_in_cee)) {
6557
		$isacompany = $thirdparty_buyer->isACompany();
6558
		if ($isacompany && getDolGlobalString('MAIN_USE_VAT_COMPANIES_IN_EEC_WITH_INVALID_VAT_ID_ARE_INDIVIDUAL')) {
6559
			require_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
6560
			if (!isValidVATID($thirdparty_buyer)) {
6561
				$isacompany = 0;
6562
			}
6563
		}
6564
6565
		if (!$isacompany) {
6566
			//print 'VATRULE 4';
6567
			return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
6568
		} else {
6569
			//print 'VATRULE 5';
6570
			return 0;
6571
		}
6572
	}
6573
6574
	// If (seller in the European Community and buyer outside the European Community and private buyer) then VAT by default = VAT of the product sold. End of rule
6575
	// I don't see any use case that need this rule.
6576
	if (!empty($conf->global->MAIN_USE_VAT_OF_PRODUCT_FOR_INDIVIDUAL_CUSTOMER_OUT_OF_EEC) && empty($buyer_in_cee)) {
6577
		$isacompany = $thirdparty_buyer->isACompany();
6578
		if (!$isacompany) {
6579
			return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
6580
			//print 'VATRULE extra';
6581
		}
6582
	}
6583
6584
	// Otherwise the VAT proposed by default=0. End of rule.
6585
	// Rem: This means that at least one of the 2 is outside the European Community and the country differs
6586
	//print 'VATRULE 6';
6587
	return 0;
6588
}
6589
6590
6591
/**
6592
 *	Function that returns whether VAT must be recoverable collected VAT (e.g.: VAT NPR in France)
6593
 *
6594
 *	@param	Societe		$thirdparty_seller    	Thirdparty seller
6595
 *	@param  Societe		$thirdparty_buyer   	Thirdparty buyer
6596
 *  @param  int			$idprod                 Id product
6597
 *  @param	int			$idprodfournprice		Id supplier price for product
6598
 *	@return float       			        	0 or 1
6599
 *  @see get_default_tva(), get_default_localtax()
6600
 */
6601
function get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
6602
{
6603
	global $db;
6604
6605
	if ($idprodfournprice > 0) {
6606
		if (!class_exists('ProductFournisseur')) {
6607
			require_once DOL_DOCUMENT_ROOT.'/fourn/class/fournisseur.product.class.php';
6608
		}
6609
		$prodprice = new ProductFournisseur($db);
6610
		$prodprice->fetch_product_fournisseur_price($idprodfournprice);
6611
		return $prodprice->fourn_tva_npr;
6612
	} elseif ($idprod > 0) {
6613
		if (!class_exists('Product')) {
6614
			require_once DOL_DOCUMENT_ROOT.'/product/class/product.class.php';
6615
		}
6616
		$prod = new Product($db);
6617
		$prod->fetch($idprod);
6618
		return $prod->tva_npr;
6619
	}
6620
6621
	return 0;
6622
}
6623
6624
/**
6625
 *	Function that return localtax of a product line (according to seller, buyer and product vat rate)
6626
 *   Si vendeur non assujeti a TVA, TVA par defaut=0. Fin de regle.
6627
 *	 Si le (pays vendeur = pays acheteur) alors TVA par defaut=TVA du produit vendu. Fin de regle.
6628
 *	 Sinon TVA proposee par defaut=0. Fin de regle.
6629
 *
6630
 *	@param	Societe		$thirdparty_seller    	Thirdparty seller
6631
 *	@param  Societe		$thirdparty_buyer   	Thirdparty buyer
6632
 *  @param	int			$local					Localtax to process (1 or 2)
6633
 *	@param  int			$idprod					Id product
6634
 *	@return integer        				       	localtax, -1 si ne peut etre determine
6635
 *  @see get_default_tva(), get_default_npr()
6636
 */
6637
function get_default_localtax($thirdparty_seller, $thirdparty_buyer, $local, $idprod = 0)
6638
{
6639
	global $mysoc;
6640
6641
	if (!is_object($thirdparty_seller)) {
6642
		return -1;
6643
	}
6644
	if (!is_object($thirdparty_buyer)) {
6645
		return -1;
6646
	}
6647
6648
	if ($local == 1) { // Localtax 1
6649
		if ($mysoc->country_code == 'ES') {
6650
			if (is_numeric($thirdparty_buyer->localtax1_assuj) && !$thirdparty_buyer->localtax1_assuj) {
6651
				return 0;
6652
			}
6653
		} else {
6654
			// Si vendeur non assujeti a Localtax1, localtax1 par default=0
6655
			if (is_numeric($thirdparty_seller->localtax1_assuj) && !$thirdparty_seller->localtax1_assuj) {
6656
				return 0;
6657
			}
6658
			if (!is_numeric($thirdparty_seller->localtax1_assuj) && $thirdparty_seller->localtax1_assuj == 'localtax1off') {
6659
				return 0;
6660
			}
6661
		}
6662
	} elseif ($local == 2) { //I Localtax 2
6663
		// Si vendeur non assujeti a Localtax2, localtax2 par default=0
6664
		if (is_numeric($thirdparty_seller->localtax2_assuj) && !$thirdparty_seller->localtax2_assuj) {
6665
			return 0;
6666
		}
6667
		if (!is_numeric($thirdparty_seller->localtax2_assuj) && $thirdparty_seller->localtax2_assuj == 'localtax2off') {
6668
			return 0;
6669
		}
6670
	}
6671
6672
	if ($thirdparty_seller->country_code == $thirdparty_buyer->country_code) {
6673
		return get_product_localtax_for_country($idprod, $local, $thirdparty_seller);
6674
	}
6675
6676
	return 0;
6677
}
6678
6679
/**
6680
 *	Return yes or no in current language
6681
 *
6682
 *	@param	string|int	$yesno			Value to test (1, 'yes', 'true' or 0, 'no', 'false')
6683
 *	@param	integer		$case			1=Yes/No, 0=yes/no, 2=Disabled checkbox, 3=Disabled checkbox + Yes/No
6684
 *	@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.
6685
 *	@return	string						HTML string
6686
 */
6687
function yn($yesno, $case = 1, $color = 0)
6688
{
6689
	global $langs;
6690
6691
	$result = 'unknown';
6692
	$classname = '';
6693
	if ($yesno == 1 || strtolower($yesno) == 'yes' || strtolower($yesno) == 'true') { 	// A mettre avant test sur no a cause du == 0
6694
		$result = $langs->trans('yes');
6695
		if ($case == 1 || $case == 3) {
6696
			$result = $langs->trans("Yes");
6697
		}
6698
		if ($case == 2) {
6699
			$result = '<input type="checkbox" value="1" checked disabled>';
6700
		}
6701
		if ($case == 3) {
6702
			$result = '<input type="checkbox" value="1" checked disabled> '.$result;
6703
		}
6704
6705
		$classname = 'ok';
6706
	} elseif ($yesno == 0 || strtolower($yesno) == 'no' || strtolower($yesno) == 'false') {
6707
		$result = $langs->trans("no");
6708
		if ($case == 1 || $case == 3) {
6709
			$result = $langs->trans("No");
6710
		}
6711
		if ($case == 2) {
6712
			$result = '<input type="checkbox" value="0" disabled>';
6713
		}
6714
		if ($case == 3) {
6715
			$result = '<input type="checkbox" value="0" disabled> '.$result;
6716
		}
6717
6718
		if ($color == 2) {
6719
			$classname = 'ok';
6720
		} else {
6721
			$classname = 'error';
6722
		}
6723
	}
6724
	if ($color) {
6725
		return '<span class="'.$classname.'">'.$result.'</span>';
6726
	}
6727
	return $result;
6728
}
6729
6730
/**
6731
 *	Return a path to have a the directory according to object where files are stored.
6732
 *  New usage:       $conf->module->multidir_output[$object->entity].'/'.get_exdir(0, 0, 0, 1, $object, '').'/'
6733
 *         or:       $conf->module->dir_output.'/'.get_exdir(0, 0, 0, 0, $object, '')     if multidir_output not defined.
6734
 *  Example out with new usage:       $object is invoice -> 'INYYMM-ABCD'
6735
 *  Example out with old usage:       '015' with level 3->"0/1/5/", '015' with level 1->"5/", 'ABC-1' with level 3 ->"0/0/1/"
6736
 *
6737
 *	@param	string|int	$num            Id of object (deprecated, $object will be used in future)
6738
 *	@param  int			$level		    Level of subdirs to return (1, 2 or 3 levels). (deprecated, global option will be used in future)
6739
 * 	@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)
6740
 *  @param  int			$withoutslash   0=With slash at end (except if '/', we return ''), 1=without slash at end
6741
 *  @param	Object		$object			Object to use to get ref to forge the path.
6742
 *  @param	string		$modulepart		Type of object ('invoice_supplier, 'donation', 'invoice', ...'). Use '' for autodetect from $object.
6743
 *  @return	string						Dir to use ending. Example '' or '1/' or '1/2/'
6744
 */
6745
function get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart = '')
6746
{
6747
	global $conf;
6748
6749
	if (empty($modulepart) && !empty($object->module)) {
6750
		$modulepart = $object->module;
6751
	}
6752
6753
	$path = '';
6754
6755
	$arrayforoldpath = array('cheque', 'category', 'holiday', 'supplier_invoice', 'invoice_supplier', 'mailing', 'supplier_payment');
6756
	if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
6757
		$arrayforoldpath[] = 'product';
6758
	}
6759
	if (!empty($level) && in_array($modulepart, $arrayforoldpath)) {
6760
		// This part should be removed once all code is using "get_exdir" to forge path, with parameter $object and $modulepart provided.
6761
		if (empty($alpha)) {
6762
			$num = preg_replace('/([^0-9])/i', '', $num);
6763
		} else {
6764
			$num = preg_replace('/^.*\-/i', '', $num);
6765
		}
6766
		$num = substr("000".$num, -$level);
6767
		if ($level == 1) {
6768
			$path = substr($num, 0, 1);
6769
		}
6770
		if ($level == 2) {
6771
			$path = substr($num, 1, 1).'/'.substr($num, 0, 1);
6772
		}
6773
		if ($level == 3) {
6774
			$path = substr($num, 2, 1).'/'.substr($num, 1, 1).'/'.substr($num, 0, 1);
6775
		}
6776
	} else {
6777
		// We will enhance here a common way of forging path for document storage.
6778
		// In a future, we may distribute directories on several levels depending on setup and object.
6779
		// Here, $object->id, $object->ref and $modulepart are required.
6780
		//var_dump($modulepart);
6781
		$path = dol_sanitizeFileName(empty($object->ref) ? (string) $object->id : $object->ref);
6782
	}
6783
6784
	if (empty($withoutslash) && !empty($path)) {
6785
		$path .= '/';
6786
	}
6787
6788
	return $path;
6789
}
6790
6791
/**
6792
 *	Creation of a directory (this can create recursive subdir)
6793
 *
6794
 *	@param	string		$dir		Directory to create (Separator must be '/'. Example: '/mydir/mysubdir')
6795
 *	@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)
6796
 *  @param	string		$newmask	Mask for new file (Defaults to $conf->global->MAIN_UMASK or 0755 if unavailable). Example: '0444'
6797
 *	@return int         			< 0 if KO, 0 = already exists, > 0 if OK
6798
 */
6799
function dol_mkdir($dir, $dataroot = '', $newmask = '')
6800
{
6801
	global $conf;
6802
6803
	dol_syslog("functions.lib::dol_mkdir: dir=".$dir, LOG_INFO);
6804
6805
	$dir_osencoded = dol_osencode($dir);
6806
	if (@is_dir($dir_osencoded)) {
6807
		return 0;
6808
	}
6809
6810
	$nberr = 0;
6811
	$nbcreated = 0;
6812
6813
	$ccdir = '';
6814
	if (!empty($dataroot)) {
6815
		// Remove data root from loop
6816
		$dir = str_replace($dataroot.'/', '', $dir);
6817
		$ccdir = $dataroot.'/';
6818
	}
6819
6820
	$cdir = explode("/", $dir);
6821
	$num = count($cdir);
6822
	for ($i = 0; $i < $num; $i++) {
6823
		if ($i > 0) {
6824
			$ccdir .= '/'.$cdir[$i];
6825
		} else {
6826
			$ccdir .= $cdir[$i];
6827
		}
6828
		if (preg_match("/^.:$/", $ccdir, $regs)) {
6829
			continue; // Si chemin Windows incomplet, on poursuit par rep suivant
6830
		}
6831
6832
		// Attention, le is_dir() peut echouer bien que le rep existe.
6833
		// (ex selon config de open_basedir)
6834
		if ($ccdir) {
6835
			$ccdir_osencoded = dol_osencode($ccdir);
6836
			if (!@is_dir($ccdir_osencoded)) {
6837
				dol_syslog("functions.lib::dol_mkdir: Directory '".$ccdir."' does not exists or is outside open_basedir PHP setting.", LOG_DEBUG);
6838
6839
				umask(0);
6840
				$dirmaskdec = octdec((string) $newmask);
6841
				if (empty($newmask)) {
6842
					$dirmaskdec = empty($conf->global->MAIN_UMASK) ? octdec('0755') : octdec($conf->global->MAIN_UMASK);
6843
				}
6844
				$dirmaskdec |= octdec('0111'); // Set x bit required for directories
6845
				if (!@mkdir($ccdir_osencoded, $dirmaskdec)) {
6846
					// Si le is_dir a renvoye une fausse info, alors on passe ici.
6847
					dol_syslog("functions.lib::dol_mkdir: Fails to create directory '".$ccdir."' or directory already exists.", LOG_WARNING);
6848
					$nberr++;
6849
				} else {
6850
					dol_syslog("functions.lib::dol_mkdir: Directory '".$ccdir."' created", LOG_DEBUG);
6851
					$nberr = 0; // On remet a zero car si on arrive ici, cela veut dire que les echecs precedents peuvent etre ignore
6852
					$nbcreated++;
6853
				}
6854
			} else {
6855
				$nberr = 0; // On remet a zero car si on arrive ici, cela veut dire que les echecs precedents peuvent etre ignores
6856
			}
6857
		}
6858
	}
6859
	return ($nberr ? -$nberr : $nbcreated);
6860
}
6861
6862
6863
/**
6864
 *	Change mod of a file
6865
 *
6866
 *  @param	string		$filepath		Full file path
6867
 *	@return void
6868
 */
6869
function dolChmod($filepath)
6870
{
6871
	global $conf;
6872
6873
	if (!empty($conf->global->MAIN_UMASK)) {
6874
		@chmod($filepath, octdec($conf->global->MAIN_UMASK));
6875
	}
6876
}
6877
6878
6879
/**
6880
 *	Return picto saying a field is required
6881
 *
6882
 *	@return  string		Chaine avec picto obligatoire
6883
 */
6884
function picto_required()
6885
{
6886
	return '<span class="fieldrequired">*</span>';
6887
}
6888
6889
6890
/**
6891
 *	Clean a string from all HTML tags and entities.
6892
 *  This function differs from strip_tags because:
6893
 *  - <br> are replaced with \n if removelinefeed=0 or 1
6894
 *  - if entities are found, they are decoded BEFORE the strip
6895
 *  - you can decide to convert line feed into a space
6896
 *
6897
 *	@param	string	$stringtoclean		String to clean
6898
 *	@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..."
6899
 *  @param  string	$pagecodeto      	Encoding of input/output string
6900
 *  @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 or when tags is not closed like '<img onload=aaa')
6901
 *  @param	integer	$removedoublespaces	Replace double space into one space
6902
 *	@return string	    				String cleaned
6903
 *
6904
 * 	@see	dol_escape_htmltag() strip_tags() dol_string_onlythesehtmltags() dol_string_neverthesehtmltags(), dolStripPhpCode()
6905
 */
6906
function dol_string_nohtmltag($stringtoclean, $removelinefeed = 1, $pagecodeto = 'UTF-8', $strip_tags = 0, $removedoublespaces = 1)
6907
{
6908
	if (is_null($stringtoclean)) {
0 ignored issues
show
introduced by
The condition is_null($stringtoclean) is always false.
Loading history...
6909
		return '';
6910
	}
6911
6912
	if ($removelinefeed == 2) {
6913
		$stringtoclean = preg_replace('/<br[^>]*>(\n|\r)+/ims', '<br>', $stringtoclean);
6914
	}
6915
	$temp = preg_replace('/<br[^>]*>/i', "\n", $stringtoclean);
6916
6917
	// We remove entities BEFORE stripping (in case of an open separator char that is entity encoded and not the closing other, the strip will fails)
6918
	$temp = dol_html_entity_decode($temp, ENT_COMPAT | ENT_HTML5, $pagecodeto);
6919
6920
	$temp = str_replace('< ', '__ltspace__', $temp);
6921
6922
	if ($strip_tags) {
6923
		$temp = strip_tags($temp);
6924
	} else {
6925
		// Remove '<' into remainging, so remove non closing html tags like '<abc' or '<<abc'. Note: '<123abc' is not a html tag (can be kept), but '<abc123' is (must be removed).
6926
		$pattern = "/<[^<>]+>/";
6927
		// Example of $temp: <a href="/myurl" title="<u>A title</u>">0000-021</a>
6928
		// pass 1 - $temp after pass 1: <a href="/myurl" title="A title">0000-021
6929
		// pass 2 - $temp after pass 2: 0000-021
6930
		$tempbis = $temp;
6931
		do {
6932
			$temp = $tempbis;
6933
			$tempbis = str_replace('<>', '', $temp);	// No reason to have this into a text, except if value is to try bypass the next html cleaning
6934
			$tempbis = preg_replace($pattern, '', $tempbis);
6935
			//$idowhile++; print $temp.'-'.$tempbis."\n"; if ($idowhile > 100) break;
6936
		} while ($tempbis != $temp);
6937
6938
		$temp = $tempbis;
6939
6940
		// Remove '<' into remaining, so remove non closing html tags like '<abc' or '<<abc'. Note: '<123abc' is not a html tag (can be kept), but '<abc123' is (must be removed).
6941
		$temp = preg_replace('/<+([a-z]+)/i', '\1', $temp);
6942
	}
6943
6944
	$temp = dol_html_entity_decode($temp, ENT_COMPAT, $pagecodeto);
6945
6946
	// Remove also carriage returns
6947
	if ($removelinefeed == 1) {
6948
		$temp = str_replace(array("\r\n", "\r", "\n"), " ", $temp);
6949
	}
6950
6951
	// And double quotes
6952
	if ($removedoublespaces) {
6953
		while (strpos($temp, "  ")) {
6954
			$temp = str_replace("  ", " ", $temp);
6955
		}
6956
	}
6957
6958
	$temp = str_replace('__ltspace__', '< ', $temp);
6959
6960
	return trim($temp);
6961
}
6962
6963
/**
6964
 *	Clean a string to keep only desirable HTML tags.
6965
 *  WARNING: This also clean HTML comments (because they can be used to obfuscate tag name).
6966
 *
6967
 *	@param	string	$stringtoclean			String to clean
6968
 *  @param	int		$cleanalsosomestyles	Remove absolute/fixed positioning from inline styles
6969
 *  @param	int		$removeclassattribute	1=Remove the class attribute from tags
6970
 *  @param	int		$cleanalsojavascript	Remove also occurence of 'javascript:'.
6971
 *  @param	int		$allowiframe			Allow iframe tags.
6972
 *  @param	array	$allowed_tags			List of allowed tags to replace the default list
6973
 *	@return string	    					String cleaned
6974
 *
6975
 * 	@see	dol_escape_htmltag() strip_tags() dol_string_nohtmltag() dol_string_neverthesehtmltags()
6976
 */
6977
function dol_string_onlythesehtmltags($stringtoclean, $cleanalsosomestyles = 1, $removeclassattribute = 1, $cleanalsojavascript = 0, $allowiframe = 0, $allowed_tags = array())
6978
{
6979
	if (empty($allowed_tags)) {
6980
		$allowed_tags = array(
6981
			"html", "head", "meta", "body", "article", "a", "abbr", "b", "blockquote", "br", "cite", "div", "dl", "dd", "dt", "em", "font", "img", "ins", "hr", "i", "li", "link",
6982
			"ol", "p", "q", "s", "section", "span", "strike", "strong", "title", "table", "tr", "th", "td", "u", "ul", "sup", "sub", "blockquote", "pre", "h1", "h2", "h3", "h4", "h5", "h6"
6983
		);
6984
	}
6985
	$allowed_tags[] = "comment";		// this tags is added to manage comment <!--...--> that are replaced into <comment>...</comment>
6986
	if ($allowiframe) {
6987
		$allowed_tags[] = "iframe";
6988
	}
6989
6990
	$allowed_tags_string = join("><", $allowed_tags);
6991
	$allowed_tags_string = '<'.$allowed_tags_string.'>';
6992
6993
	$stringtoclean = str_replace('<!DOCTYPE html>', '__!DOCTYPE_HTML__', $stringtoclean);	// Replace DOCTYPE to avoid to have it removed by the strip_tags
6994
6995
	$stringtoclean = dol_string_nounprintableascii($stringtoclean, 0);
6996
6997
	//$stringtoclean = preg_replace('/<!--[^>]*-->/', '', $stringtoclean);
6998
	$stringtoclean = preg_replace('/<!--([^>]*)-->/', '<comment>\1</comment>', $stringtoclean);
6999
7000
	$stringtoclean = preg_replace('/&colon;/i', ':', $stringtoclean);
7001
	$stringtoclean = preg_replace('/&#58;|&#0+58|&#x3A/i', '', $stringtoclean); // refused string ':' encoded (no reason to have a : encoded like this) to disable 'javascript:...'
7002
7003
	$temp = strip_tags($stringtoclean, $allowed_tags_string);	// Warning: This remove also undesired </>, so may changes string obfuscated with </> that pass the injection detection into a harmfull string
7004
7005
	if ($cleanalsosomestyles) {	// Clean for remaining html tags
7006
		$temp = preg_replace('/position\s*:\s*(absolute|fixed)\s*!\s*important/i', '', $temp); // Note: If hacker try to introduce css comment into string to bypass this regex, the string must also be encoded by the dol_htmlentitiesbr during output so it become harmless
7007
	}
7008
	if ($removeclassattribute) {	// Clean for remaining html tags
7009
		$temp = preg_replace('/(<[^>]+)\s+class=((["\']).*?\\3|\\w*)/i', '\\1', $temp);
7010
	}
7011
7012
	// Remove 'javascript:' that we should not find into a text with
7013
	// Warning: This is not reliable to fight against obfuscated javascript, there is a lot of other solution to include js into a common html tag (only filtered by a GETPOST(.., powerfullfilter)).
7014
	if ($cleanalsojavascript) {
7015
		$temp = preg_replace('/j\s*a\s*v\s*a\s*s\s*c\s*r\s*i\s*p\s*t\s*:/i', '', $temp);
7016
	}
7017
7018
	$temp = str_replace('__!DOCTYPE_HTML__', '<!DOCTYPE html>', $temp);	// Restore the DOCTYPE
7019
7020
	$temp = preg_replace('/<comment>([^>]*)<\/comment>/', '<!--\1-->', $temp);	// Restore html comments
7021
7022
7023
	return $temp;
7024
}
7025
7026
7027
/**
7028
 *	Clean a string from some undesirable HTML tags.
7029
 *  Note: Complementary to dol_string_onlythesehtmltags().
7030
 *  This method is used for example when option MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES is set to 1.
7031
 *
7032
 *	@param	string	$stringtoclean			String to clean
7033
 *  @param	array	$allowed_attributes		Array of tags not allowed
7034
 *	@return string	    					String cleaned
7035
 *
7036
 * 	@see	dol_escape_htmltag() strip_tags() dol_string_nohtmltag() dol_string_onlythesehtmltags() dol_string_neverthesehtmltags()
7037
 */
7038
function dol_string_onlythesehtmlattributes($stringtoclean, $allowed_attributes = array("allow", "allowfullscreen", "alt", "class", "contenteditable", "data-html", "frameborder", "height", "href", "id", "name", "src", "style", "target", "title", "width"))
7039
{
7040
	if (class_exists('DOMDocument') && !empty($stringtoclean)) {
7041
		$stringtoclean = '<?xml encoding="UTF-8"><html><body>'.$stringtoclean.'</body></html>';
7042
7043
		$dom = new DOMDocument(null, 'UTF-8');
7044
		$dom->loadHTML($stringtoclean, LIBXML_ERR_NONE|LIBXML_HTML_NOIMPLIED|LIBXML_HTML_NODEFDTD|LIBXML_NONET|LIBXML_NOWARNING|LIBXML_NOXMLDECL);
7045
7046
		if (is_object($dom)) {
7047
			for ($els = $dom->getElementsByTagname('*'), $i = $els->length - 1; $i >= 0; $i--) {
7048
				for ($attrs = $els->item($i)->attributes, $ii = $attrs->length - 1; $ii >= 0; $ii--) {
7049
					//var_dump($attrs->item($ii));
7050
					if (!empty($attrs->item($ii)->name)) {
0 ignored issues
show
Bug introduced by
The method item() 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

7050
					if (!empty($attrs->/** @scrutinizer ignore-call */ item($ii)->name)) {

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...
7051
						if (! in_array($attrs->item($ii)->name, $allowed_attributes)) {
7052
							// Delete attribute if not into allowed_attributes
7053
							$els->item($i)->removeAttribute($attrs->item($ii)->name);
7054
						} elseif (in_array($attrs->item($ii)->name, array('style'))) {
7055
							// If attribute is 'style'
7056
							$valuetoclean = $attrs->item($ii)->value;
7057
7058
							if (isset($valuetoclean)) {
7059
								do {
7060
									$oldvaluetoclean = $valuetoclean;
7061
									$valuetoclean = preg_replace('/\/\*.*\*\//m', '', $valuetoclean);	// clean css comments
7062
									$valuetoclean = preg_replace('/position\s*:\s*[a-z]+/mi', '', $valuetoclean);
7063
									if ($els->item($i)->tagName == 'a') {	// more paranoiac cleaning for clickable tags.
7064
										$valuetoclean = preg_replace('/display\s*:/mi', '', $valuetoclean);
7065
										$valuetoclean = preg_replace('/z-index\s*:/mi', '', $valuetoclean);
7066
										$valuetoclean = preg_replace('/\s+(top|left|right|bottom)\s*:/mi', '', $valuetoclean);
7067
									}
7068
7069
									// We do not allow logout|passwordforgotten.php and action= into the content of a "style" tag
7070
									$valuetoclean = preg_replace('/(logout|passwordforgotten)\.php/mi', '', $valuetoclean);
7071
									$valuetoclean = preg_replace('/action=/mi', '', $valuetoclean);
7072
								} while ($oldvaluetoclean != $valuetoclean);
7073
							}
7074
7075
							$attrs->item($ii)->value = $valuetoclean;
7076
						}
7077
					}
7078
				}
7079
			}
7080
		}
7081
7082
		$return = $dom->saveHTML();
7083
		//$return = '<html><body>aaaa</p>bb<p>ssdd</p>'."\n<p>aaa</p>aa<p>bb</p>";
7084
7085
		$return = preg_replace('/^'.preg_quote('<?xml encoding="UTF-8">', '/').'/', '', $return);
7086
		$return = preg_replace('/^'.preg_quote('<html><body>', '/').'/', '', $return);
7087
		$return = preg_replace('/'.preg_quote('</body></html>', '/').'$/', '', $return);
7088
		return trim($return);
7089
	} else {
7090
		return $stringtoclean;
7091
	}
7092
}
7093
7094
/**
7095
 *	Clean a string from some undesirable HTML tags.
7096
 *  Note. Not as secured as dol_string_onlythesehtmltags().
7097
 *
7098
 *	@param	string	$stringtoclean			String to clean
7099
 *  @param	array	$disallowed_tags		Array of tags not allowed
7100
 *  @param	string	$cleanalsosomestyles	Clean also some tags
7101
 *	@return string	    					String cleaned
7102
 *
7103
 * 	@see	dol_escape_htmltag() strip_tags() dol_string_nohtmltag() dol_string_onlythesehtmltags() dol_string_onlythesehtmlattributes()
7104
 */
7105
function dol_string_neverthesehtmltags($stringtoclean, $disallowed_tags = array('textarea'), $cleanalsosomestyles = 0)
7106
{
7107
	$temp = $stringtoclean;
7108
	foreach ($disallowed_tags as $tagtoremove) {
7109
		$temp = preg_replace('/<\/?'.$tagtoremove.'>/', '', $temp);
7110
		$temp = preg_replace('/<\/?'.$tagtoremove.'\s+[^>]*>/', '', $temp);
7111
	}
7112
7113
	if ($cleanalsosomestyles) {
7114
		$temp = preg_replace('/position\s*:\s*(absolute|fixed)\s*!\s*important/', '', $temp); // Note: If hacker try to introduce css comment into string to avoid this, string should be encoded by the dol_htmlentitiesbr so be harmless
7115
	}
7116
7117
	return $temp;
7118
}
7119
7120
7121
/**
7122
 * Return first line of text. Cut will depends if content is HTML or not.
7123
 *
7124
 * @param 	string	$text		Input text
7125
 * @param	int		$nboflines  Nb of lines to get (default is 1 = first line only)
7126
 * @param   string  $charset    Charset of $text string (UTF-8 by default)
7127
 * @return	string				Output text
7128
 * @see dol_nboflines_bis(), dol_string_nohtmltag(), dol_escape_htmltag()
7129
 */
7130
function dolGetFirstLineOfText($text, $nboflines = 1, $charset = 'UTF-8')
7131
{
7132
	if ($nboflines == 1) {
7133
		if (dol_textishtml($text)) {
7134
			$firstline = preg_replace('/<br[^>]*>.*$/s', '', $text); // The s pattern modifier means the . can match newline characters
7135
			$firstline = preg_replace('/<div[^>]*>.*$/s', '', $firstline); // The s pattern modifier means the . can match newline characters
7136
		} else {
7137
			$firstline = preg_replace('/[\n\r].*/', '', $text);
7138
		}
7139
		return $firstline.((strlen($firstline) != strlen($text)) ? '...' : '');
7140
	} else {
7141
		$ishtml = 0;
7142
		if (dol_textishtml($text)) {
7143
			$text = preg_replace('/\n/', '', $text);
7144
			$ishtml = 1;
7145
			$repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
7146
		} else {
7147
			$repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
7148
		}
7149
7150
		$text = strtr($text, $repTable);
7151
		if ($charset == 'UTF-8') {
7152
			$pattern = '/(<br[^>]*>)/Uu';
7153
		} else {
7154
			// /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
7155
			$pattern = '/(<br[^>]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
7156
		}
7157
		$a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
7158
7159
		$firstline = '';
7160
		$i = 0;
7161
		$nba = count($a); // 2x nb of lines in $a because $a contains also a line for each new line separator
7162
		while (($i < $nba) && ($i < ($nboflines * 2))) {
7163
			if ($i % 2 == 0) {
7164
				$firstline .= $a[$i];
7165
			} elseif (($i < (($nboflines * 2) - 1)) && ($i < ($nba - 1))) {
7166
				$firstline .= ($ishtml ? "<br>\n" : "\n");
7167
			}
7168
			$i++;
7169
		}
7170
		unset($a);
7171
		return $firstline.(($i < $nba) ? '...' : '');
7172
	}
7173
}
7174
7175
7176
/**
7177
 * Replace CRLF in string with a HTML BR tag.
7178
 * WARNING: The content after operation contains some HTML tags (the <br>) so be sure to also have encode the special chars of stringtoencode into HTML before.
7179
 *
7180
 * @param	string	$stringtoencode		String to encode
7181
 * @param	int     $nl2brmode			0=Adding br before \n, 1=Replacing \n by br
7182
 * @param   bool	$forxml             false=Use <br>, true=Use <br />
7183
 * @return	string						String encoded
7184
 * @see dol_nboflines(), dolGetFirstLineOfText()
7185
 */
7186
function dol_nl2br($stringtoencode, $nl2brmode = 0, $forxml = false)
7187
{
7188
	if (is_null($stringtoencode)) {
0 ignored issues
show
introduced by
The condition is_null($stringtoencode) is always false.
Loading history...
7189
		return '';
7190
	}
7191
7192
	if (!$nl2brmode) {
7193
		return nl2br($stringtoencode, $forxml);
7194
	} else {
7195
		$ret = preg_replace('/(\r\n|\r|\n)/i', ($forxml ? '<br />' : '<br>'), $stringtoencode);
7196
		return $ret;
7197
	}
7198
}
7199
7200
/**
7201
 * Sanitize a HTML to remove js and dangerous content
7202
 *
7203
 * @param	string	$stringtoencode				String to encode
7204
 * @param	int     $nouseofiframesandbox		Allow use of option MAIN_SECURITY_USE_SANDBOX_FOR_HTMLWITHNOJS for html sanitizing
7205
 * @param	string	$check						'restricthtmlnolink' or  'restricthtml' or 'restricthtmlallowunvalid'
7206
 * @return	string								HTML sanitized
7207
 */
7208
function dol_htmlwithnojs($stringtoencode, $nouseofiframesandbox = 0, $check = 'restricthtml')
7209
{
7210
	global $conf;
7211
7212
	if (empty($nouseofiframesandbox) && !empty($conf->global->MAIN_SECURITY_USE_SANDBOX_FOR_HTMLWITHNOJS)) {
7213
		// TODO using sandbox on inline html content is not possible yet with current browsers
7214
		//$s = '<iframe class="iframewithsandbox" sandbox><html><body>';
7215
		//$s .= $stringtoencode;
7216
		//$s .= '</body></html></iframe>';
7217
		return $stringtoencode;
7218
	} else {
7219
		$out = $stringtoencode;
7220
7221
		do {
7222
			$oldstringtoclean = $out;
7223
7224
			if (!empty($out) && !empty($conf->global->MAIN_RESTRICTHTML_ONLY_VALID_HTML) && $check != 'restricthtmlallowunvalid') {
7225
				try {
7226
					$dom = new DOMDocument;
7227
					// Add a trick to solve pb with text without parent tag
7228
					// like '<h1>Foo</h1><p>bar</p>' that wrongly ends up without the trick into '<h1>Foo<p>bar</p></h1>'
7229
					// like 'abc' that wrongly ends up without the tric into with '<p>abc</p>'
7230
					$out = '<div class="tricktoremove">'.$out.'</div>';
7231
7232
					$dom->loadHTML($out, LIBXML_ERR_NONE|LIBXML_HTML_NOIMPLIED|LIBXML_HTML_NODEFDTD|LIBXML_NONET|LIBXML_NOWARNING|LIBXML_NOXMLDECL);
7233
					$out = trim($dom->saveHTML());
7234
7235
					// Remove the trick added to solve pb with text without parent tag
7236
					$out = preg_replace('/^<div class="tricktoremove">/', '', $out);
7237
					$out = preg_replace('/<\/div>$/', '', $out);
7238
				} catch (Exception $e) {
7239
					// If error, invalid HTML string with no way to clean it
7240
					//print $e->getMessage();
7241
					$out = 'InvalidHTMLString';
7242
				}
7243
			}
7244
7245
			// Clean some html entities that are useless so text is cleaner
7246
			$out = preg_replace('/&(tab|newline);/i', ' ', $out);
7247
7248
			// Ckeditor use the numeric entitic for apostrophe so we force it to text entity (all other special chars are
7249
			// encoded using text entities) so we can then exclude all numeric entities.
7250
			$out = preg_replace('/&#39;/i', '&apos;', $out);
7251
7252
			// We replace chars from a/A to z/Z encoded with numeric HTML entities with the real char so we won't loose the chars at the next step (preg_replace).
7253
			// No need to use a loop here, this step is not to sanitize (this is done at next step, this is to try to save chars, even if they are
7254
			// using a non coventionnel way to be encoded, to not have them sanitized just after)
7255
			$out = preg_replace_callback('/&#(x?[0-9][0-9a-f]+;?)/i', function ($m) {
7256
				return realCharForNumericEntities($m); }, $out);
7257
7258
7259
			// Now we remove all remaining HTML entities starting with a number. We don't want such entities.
7260
			$out = preg_replace('/&#x?[0-9]+/i', '', $out);	// For example if we have j&#x61vascript with an entities without the ; to hide the 'a' of 'javascript'.
7261
7262
			// Keep only some html tags and remove also some 'javascript:' strings
7263
			$out = dol_string_onlythesehtmltags($out, 0, 1, 1);
7264
7265
			// We should also exclude non expected HTML attributes and clean content of some attributes (keep only alt=, title=...).
7266
			if (!empty($conf->global->MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES)) {
7267
				// Warning, the function may add a LF so we are forced to trim to compare with old $out without having always a difference and an infinit loop.
7268
				$out = dol_string_onlythesehtmlattributes($out);
7269
			}
7270
7271
			// Restore entity &apos; into &#39; (restricthtml is for html content so we can use html entity)
7272
			$out = preg_replace('/&apos;/i', "&#39;", $out);
7273
		} while ($oldstringtoclean != $out);
7274
7275
		// Check the limit of external links in a Rich text content. We count '<img' and 'url('
7276
		$reg = array();
7277
		preg_match_all('/(<img|url\()/i', $out, $reg);
7278
		$nbextlink = count($reg[0]);
7279
		if ($nbextlink > getDolGlobalInt("MAIN_SECURITY_MAX_IMG_IN_HTML_CONTENT", 1000)) {
7280
			$out = 'TooManyLinksIntoHTMLString';
7281
		}
7282
		//
7283
		if (!empty($conf->global->MAIN_DISALLOW_EXT_URL_INTO_DESCRIPTIONS) || $check == 'restricthtmlnolink') {
7284
			if ($nbextlink > 0) {
7285
				$out = 'ExternalLinksNotAllowed';
7286
			}
7287
		}
7288
7289
		return $out;
7290
	}
7291
}
7292
7293
/**
7294
 *	This function is called to encode a string into a HTML string but differs from htmlentities because
7295
 * 	a detection is done before to see if text is already HTML or not. Also, all entities but &,<,>," are converted.
7296
 *  This permits to encode special chars to entities with no double encoding for already encoded HTML strings.
7297
 * 	This function also remove last EOL or BR if $removelasteolbr=1 (default).
7298
 *  For PDF usage, you can show text by 2 ways:
7299
 *              - writeHTMLCell -> param must be encoded into HTML.
7300
 *              - MultiCell -> param must not be encoded into HTML.
7301
 *              Because writeHTMLCell convert also \n into <br>, if function
7302
 *              is used to build PDF, nl2brmode must be 1.
7303
 *
7304
 *	@param	string	$stringtoencode		String to encode
7305
 *	@param	int		$nl2brmode			0=Adding br before \n, 1=Replacing \n by br (for use with FPDF writeHTMLCell function for example)
7306
 *  @param  string	$pagecodefrom       Pagecode stringtoencode is encoded
7307
 *  @param	int		$removelasteolbr	1=Remove last br or lasts \n (default), 0=Do nothing
7308
 *  @return	string						String encoded
7309
 */
7310
function dol_htmlentitiesbr($stringtoencode, $nl2brmode = 0, $pagecodefrom = 'UTF-8', $removelasteolbr = 1)
7311
{
7312
	if (is_null($stringtoencode)) {
0 ignored issues
show
introduced by
The condition is_null($stringtoencode) is always false.
Loading history...
7313
		return '';
7314
	}
7315
7316
	$newstring = $stringtoencode;
7317
	if (dol_textishtml($stringtoencode)) {	// Check if text is already HTML or not
7318
		$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.
7319
		if ($removelasteolbr) {
7320
			$newstring = preg_replace('/<br>$/i', '', $newstring); // Remove last <br> (remove only last one)
7321
		}
7322
		$newstring = strtr($newstring, array('&'=>'__and__', '<'=>'__lt__', '>'=>'__gt__', '"'=>'__dquot__'));
7323
		$newstring = dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom); // Make entity encoding
7324
		$newstring = strtr($newstring, array('__and__'=>'&', '__lt__'=>'<', '__gt__'=>'>', '__dquot__'=>'"'));
7325
	} else {
7326
		if ($removelasteolbr) {
7327
			$newstring = preg_replace('/(\r\n|\r|\n)$/i', '', $newstring); // Remove last \n (may remove several)
7328
		}
7329
		$newstring = dol_nl2br(dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom), $nl2brmode);
7330
	}
7331
	// Other substitutions that htmlentities does not do
7332
	//$newstring=str_replace(chr(128),'&euro;',$newstring);	// 128 = 0x80. Not in html entity table.     // Seems useles with TCPDF. Make bug with UTF8 languages
7333
	return $newstring;
7334
}
7335
7336
/**
7337
 *	This function is called to decode a HTML string (it decodes entities and br tags)
7338
 *
7339
 *	@param	string	$stringtodecode		String to decode
7340
 *	@param	string	$pagecodeto			Page code for result
7341
 *	@return	string						String decoded
7342
 */
7343
function dol_htmlentitiesbr_decode($stringtodecode, $pagecodeto = 'UTF-8')
7344
{
7345
	$ret = dol_html_entity_decode($stringtodecode, ENT_COMPAT | ENT_HTML5, $pagecodeto);
7346
	$ret = preg_replace('/'."\r\n".'<br(\s[\sa-zA-Z_="]*)?\/?>/i', "<br>", $ret);
7347
	$ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>'."\r\n".'/i', "\r\n", $ret);
7348
	$ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>'."\n".'/i', "\n", $ret);
7349
	$ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>/i', "\n", $ret);
7350
	return $ret;
7351
}
7352
7353
/**
7354
 *	This function remove all ending \n and br at end
7355
 *
7356
 *	@param	string	$stringtodecode		String to decode
7357
 *	@return	string						String decoded
7358
 */
7359
function dol_htmlcleanlastbr($stringtodecode)
7360
{
7361
	$ret = preg_replace('/&nbsp;$/i', "", $stringtodecode);		// Because wysiwyg editor may add a &nbsp; at end of last line
7362
	$ret = preg_replace('/(<br>|<br(\s[\sa-zA-Z_="]*)?\/?>|'."\n".'|'."\r".')+$/i', "", $ret);
7363
	return $ret;
7364
}
7365
7366
/**
7367
 * Replace html_entity_decode functions to manage errors
7368
 *
7369
 * @param   string	$a					Operand a
7370
 * @param   string	$b					Operand b (ENT_QUOTES|ENT_HTML5=convert simple, double quotes, colon, e accent, ...)
7371
 * @param   string	$c					Operand c
7372
 * @param	string	$keepsomeentities	Entities but &, <, >, " are not converted.
7373
 * @return  string						String decoded
7374
 */
7375
function dol_html_entity_decode($a, $b, $c = 'UTF-8', $keepsomeentities = 0)
7376
{
7377
	$newstring = $a;
7378
	if ($keepsomeentities) {
7379
		$newstring = strtr($newstring, array('&amp;'=>'__andamp__', '&lt;'=>'__andlt__', '&gt;'=>'__andgt__', '"'=>'__dquot__'));
7380
	}
7381
	$newstring = html_entity_decode((string) $newstring, (int) $b, (string) $c);
7382
	if ($keepsomeentities) {
7383
		$newstring = strtr($newstring, array('__andamp__'=>'&amp;', '__andlt__'=>'&lt;', '__andgt__'=>'&gt;', '__dquot__'=>'"'));
7384
	}
7385
	return $newstring;
7386
}
7387
7388
/**
7389
 * Replace htmlentities functions.
7390
 * Goal of this function is to be sure to have default values of htmlentities that match what we need.
7391
 *
7392
 * @param   string  $string         The input string to encode
7393
 * @param   int     $flags          Flags (see PHP doc above)
7394
 * @param   string  $encoding       Encoding page code
7395
 * @param   bool    $double_encode  When double_encode is turned off, PHP will not encode existing html entities
7396
 * @return  string  $ret            Encoded string
7397
 */
7398
function dol_htmlentities($string, $flags = ENT_QUOTES|ENT_SUBSTITUTE, $encoding = 'UTF-8', $double_encode = false)
7399
{
7400
	return htmlentities($string, $flags, $encoding, $double_encode);
7401
}
7402
7403
/**
7404
 *	Check if a string is a correct iso string
7405
 *	If not, it will we considered not HTML encoded even if it is by FPDF.
7406
 *	Example, if string contains euro symbol that has ascii code 128
7407
 *
7408
 *	@param	string		$s      	String to check
7409
 *  @param	string		$clean		Clean if it is not an ISO. Warning, if file is utf8, you will get a bad formated file.
7410
 *	@return	int|string  	   		0 if bad iso, 1 if good iso, Or the clean string if $clean is 1
7411
 */
7412
function dol_string_is_good_iso($s, $clean = 0)
7413
{
7414
	$len = dol_strlen($s);
7415
	$out = '';
7416
	$ok = 1;
7417
	for ($scursor = 0; $scursor < $len; $scursor++) {
7418
		$ordchar = ord($s[$scursor]);
7419
		//print $scursor.'-'.$ordchar.'<br>';
7420
		if ($ordchar < 32 && $ordchar != 13 && $ordchar != 10) {
7421
			$ok = 0;
7422
			break;
7423
		} elseif ($ordchar > 126 && $ordchar < 160) {
7424
			$ok = 0;
7425
			break;
7426
		} elseif ($clean) {
7427
			$out .= $s[$scursor];
7428
		}
7429
	}
7430
	if ($clean) {
7431
		return $out;
7432
	}
7433
	return $ok;
7434
}
7435
7436
/**
7437
 *	Return nb of lines of a clear text
7438
 *
7439
 *	@param	string	$s			String to check
7440
 * 	@param	int     $maxchar	Not yet used
7441
 *	@return	int					Number of lines
7442
 *  @see	dol_nboflines_bis(), dolGetFirstLineOfText()
7443
 */
7444
function dol_nboflines($s, $maxchar = 0)
7445
{
7446
	if ($s == '') {
7447
		return 0;
7448
	}
7449
	$arraystring = explode("\n", $s);
7450
	$nb = count($arraystring);
7451
7452
	return $nb;
7453
}
7454
7455
7456
/**
7457
 *	Return nb of lines of a formated text with \n and <br> (WARNING: string must not have mixed \n and br separators)
7458
 *
7459
 *	@param	string	$text      		Text
7460
 *	@param	int		$maxlinesize  	Largeur de ligne en caracteres (ou 0 si pas de limite - defaut)
7461
 * 	@param	string	$charset		Give the charset used to encode the $text variable in memory.
7462
 *	@return int						Number of lines
7463
 *	@see	dol_nboflines(), dolGetFirstLineOfText()
7464
 */
7465
function dol_nboflines_bis($text, $maxlinesize = 0, $charset = 'UTF-8')
7466
{
7467
	$repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
7468
	if (dol_textishtml($text)) {
7469
		$repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
7470
	}
7471
7472
	$text = strtr($text, $repTable);
7473
	if ($charset == 'UTF-8') {
7474
		$pattern = '/(<br[^>]*>)/Uu';
7475
	} else {
7476
		// /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
7477
		$pattern = '/(<br[^>]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
7478
	}
7479
	$a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
7480
7481
	$nblines = (int) floor((count($a) + 1) / 2);
7482
	// count possible auto line breaks
7483
	if ($maxlinesize) {
7484
		foreach ($a as $line) {
7485
			if (dol_strlen($line) > $maxlinesize) {
7486
				//$line_dec = html_entity_decode(strip_tags($line));
7487
				$line_dec = html_entity_decode($line);
7488
				if (dol_strlen($line_dec) > $maxlinesize) {
7489
					$line_dec = wordwrap($line_dec, $maxlinesize, '\n', true);
7490
					$nblines += substr_count($line_dec, '\n');
7491
				}
7492
			}
7493
		}
7494
	}
7495
7496
	unset($a);
7497
	return $nblines;
7498
}
7499
7500
/**
7501
 *	Return if a text is a html content
7502
 *
7503
 *	@param	string	$msg		Content to check
7504
 *	@param	int		$option		0=Full detection, 1=Fast check
7505
 *	@return	boolean				true/false
7506
 *	@see	dol_concatdesc()
7507
 */
7508
function dol_textishtml($msg, $option = 0)
7509
{
7510
	if (is_null($msg)) {
0 ignored issues
show
introduced by
The condition is_null($msg) is always false.
Loading history...
7511
		return false;
7512
	}
7513
7514
	if ($option == 1) {
7515
		if (preg_match('/<html/i', $msg)) {
7516
			return true;
7517
		} elseif (preg_match('/<body/i', $msg)) {
7518
			return true;
7519
		} elseif (preg_match('/<\/textarea/i', $msg)) {
7520
			return true;
7521
		} elseif (preg_match('/<(b|em|i|u)>/i', $msg)) {
7522
			return true;
7523
		} elseif (preg_match('/<br/i', $msg)) {
7524
			return true;
7525
		}
7526
		return false;
7527
	} else {
7528
		// Remove all urls because 'http://aa?param1=abc&amp;param2=def' must not be used inside detection
7529
		$msg = preg_replace('/https?:\/\/[^"\'\s]+/i', '', $msg);
7530
		if (preg_match('/<html/i', $msg)) {
7531
			return true;
7532
		} elseif (preg_match('/<body/i', $msg)) {
7533
			return true;
7534
		} elseif (preg_match('/<\/textarea/i', $msg)) {
7535
			return true;
7536
		} elseif (preg_match('/<(b|em|i|u)>/i', $msg)) {
7537
			return true;
7538
		} elseif (preg_match('/<br\/>/i', $msg)) {
7539
			return true;
7540
		} elseif (preg_match('/<(br|div|font|li|p|span|strong|table)>/i', $msg)) {
7541
			return true;
7542
		} elseif (preg_match('/<(br|div|font|li|p|span|strong|table)\s+[^<>\/]*\/?>/i', $msg)) {
7543
			return true;
7544
		} elseif (preg_match('/<img\s+[^<>]*src[^<>]*>/i', $msg)) {
7545
			return true; // must accept <img src="http://example.com/aaa.png" />
7546
		} elseif (preg_match('/<a\s+[^<>]*href[^<>]*>/i', $msg)) {
7547
			return true; // must accept <a href="http://example.com/aaa.png" />
7548
		} elseif (preg_match('/<h[0-9]>/i', $msg)) {
7549
			return true;
7550
		} elseif (preg_match('/&[A-Z0-9]{1,6};/i', $msg)) {
7551
			// TODO If content is 'A link https://aaa?param=abc&amp;param2=def', it return true but must be false
7552
			return true; // Html entities names (http://www.w3schools.com/tags/ref_entities.asp)
7553
		} elseif (preg_match('/&#[0-9]{2,3};/i', $msg)) {
7554
			return true; // Html entities numbers (http://www.w3schools.com/tags/ref_entities.asp)
7555
		}
7556
7557
		return false;
7558
	}
7559
}
7560
7561
/**
7562
 *  Concat 2 descriptions with a new line between them (second operand after first one with appropriate new line separator)
7563
 *  text1 html + text2 html => text1 + '<br>' + text2
7564
 *  text1 html + text2 txt  => text1 + '<br>' + dol_nl2br(text2)
7565
 *  text1 txt  + text2 html => dol_nl2br(text1) + '<br>' + text2
7566
 *  text1 txt  + text2 txt  => text1 + '\n' + text2
7567
 *
7568
 *  @param  string  $text1          Text 1
7569
 *  @param  string  $text2          Text 2
7570
 *  @param  bool    $forxml         true=Use <br /> instead of <br> if we have to add a br tag
7571
 *  @param  bool    $invert         invert order of description lines (we often use config MAIN_CHANGE_ORDER_CONCAT_DESCRIPTION in this parameter)
7572
 *  @return string                  Text 1 + new line + Text2
7573
 *  @see    dol_textishtml()
7574
 */
7575
function dol_concatdesc($text1, $text2, $forxml = false, $invert = false)
7576
{
7577
	if (!empty($invert)) {
7578
			$tmp = $text1;
7579
			$text1 = $text2;
7580
			$text2 = $tmp;
7581
	}
7582
7583
	$ret = '';
7584
	$ret .= (!dol_textishtml($text1) && dol_textishtml($text2)) ? dol_nl2br(dol_escape_htmltag($text1, 0, 1, '', 1), 0, $forxml) : $text1;
7585
	$ret .= (!empty($text1) && !empty($text2)) ? ((dol_textishtml($text1) || dol_textishtml($text2)) ? ($forxml ? "<br \>\n" : "<br>\n") : "\n") : "";
7586
	$ret .= (dol_textishtml($text1) && !dol_textishtml($text2)) ? dol_nl2br(dol_escape_htmltag($text2, 0, 1, '', 1), 0, $forxml) : $text2;
7587
	return $ret;
7588
}
7589
7590
7591
7592
/**
7593
 * Return array of possible common substitutions. This includes several families like: 'system', 'mycompany', 'object', 'objectamount', 'date', 'user'
7594
 *
7595
 * @param	Translate	$outputlangs	Output language
7596
 * @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)
7597
 * @param   array       $exclude        Array of family keys we want to exclude. For example array('system', 'mycompany', 'object', 'objectamount', 'date', 'user', ...)
7598
 * @param   Object      $object         Object for keys on object
7599
 * @return	array						Array of substitutions
7600
 * @see setSubstitFromObject()
7601
 */
7602
function getCommonSubstitutionArray($outputlangs, $onlykey = 0, $exclude = null, $object = null)
7603
{
7604
	global $db, $conf, $mysoc, $user, $extrafields;
7605
7606
	$substitutionarray = array();
7607
7608
	if (empty($exclude) || !in_array('user', $exclude)) {
7609
		// Add SIGNATURE into substitutionarray first, so, when we will make the substitution,
7610
		// this will include signature content first and then replace var found into content of signature
7611
		//var_dump($onlykey);
7612
		$emailsendersignature = $user->signature; //  dy default, we use the signature of current user. We must complete substitution with signature in c_email_senderprofile of array after calling getCommonSubstitutionArray()
7613
		$usersignature = $user->signature;
7614
		$substitutionarray = array_merge($substitutionarray, array(
7615
			'__SENDEREMAIL_SIGNATURE__' => (string) ((empty($conf->global->MAIN_MAIL_DO_NOT_USE_SIGN)) ? ($onlykey == 2 ? dol_trunc('SignatureFromTheSelectedSenderProfile', 30) : $emailsendersignature) : ''),
7616
			'__USER_SIGNATURE__' => (string) (($usersignature && empty($conf->global->MAIN_MAIL_DO_NOT_USE_SIGN)) ? ($onlykey == 2 ? dol_trunc(dol_string_nohtmltag($usersignature), 30) : $usersignature) : '')
7617
		));
7618
7619
		if (is_object($user)) {
7620
			$substitutionarray = array_merge($substitutionarray, array(
7621
				'__USER_ID__' => (string) $user->id,
7622
				'__USER_LOGIN__' => (string) $user->login,
7623
				'__USER_EMAIL__' => (string) $user->email,
7624
				'__USER_PHONE__' => (string) dol_print_phone($user->office_phone),
7625
				'__USER_PHONEPRO__' => (string) dol_print_phone($user->user_mobile),
7626
				'__USER_PHONEMOBILE__' => (string) dol_print_phone($user->personal_mobile),
7627
				'__USER_FAX__' => (string) $user->office_fax,
7628
				'__USER_LASTNAME__' => (string) $user->lastname,
7629
				'__USER_FIRSTNAME__' => (string) $user->firstname,
7630
				'__USER_FULLNAME__' => (string) $user->getFullName($outputlangs),
7631
				'__USER_SUPERVISOR_ID__' => (string) ($user->fk_user ? $user->fk_user : '0'),
7632
				'__USER_JOB__' => (string) $user->job,
7633
				'__USER_REMOTE_IP__' => (string) getUserRemoteIP()
7634
				));
7635
		}
7636
	}
7637
	if ((empty($exclude) || !in_array('mycompany', $exclude)) && is_object($mysoc)) {
7638
		$substitutionarray = array_merge($substitutionarray, array(
7639
			'__MYCOMPANY_NAME__'    => $mysoc->name,
7640
			'__MYCOMPANY_EMAIL__'   => $mysoc->email,
7641
			'__MYCOMPANY_PHONE__'   => dol_print_phone($mysoc->phone),
7642
			'__MYCOMPANY_FAX__'     => dol_print_phone($mysoc->fax),
7643
			'__MYCOMPANY_PROFID1__' => $mysoc->idprof1,
7644
			'__MYCOMPANY_PROFID2__' => $mysoc->idprof2,
7645
			'__MYCOMPANY_PROFID3__' => $mysoc->idprof3,
7646
			'__MYCOMPANY_PROFID4__' => $mysoc->idprof4,
7647
			'__MYCOMPANY_PROFID5__' => $mysoc->idprof5,
7648
			'__MYCOMPANY_PROFID6__' => $mysoc->idprof6,
7649
			'__MYCOMPANY_CAPITAL__' => $mysoc->capital,
7650
			'__MYCOMPANY_FULLADDRESS__' => (method_exists($mysoc, 'getFullAddress') ? $mysoc->getFullAddress(1, ', ') : ''),	// $mysoc may be stdClass
7651
			'__MYCOMPANY_ADDRESS__' => $mysoc->address,
7652
			'__MYCOMPANY_ZIP__'     => $mysoc->zip,
7653
			'__MYCOMPANY_TOWN__'    => $mysoc->town,
7654
			'__MYCOMPANY_COUNTRY__'    => $mysoc->country,
7655
			'__MYCOMPANY_COUNTRY_ID__' => $mysoc->country_id,
7656
			'__MYCOMPANY_COUNTRY_CODE__' => $mysoc->country_code,
7657
			'__MYCOMPANY_CURRENCY_CODE__' => $conf->currency
7658
		));
7659
	}
7660
7661
	if (($onlykey || is_object($object)) && (empty($exclude) || !in_array('object', $exclude))) {
7662
		if ($onlykey) {
7663
			$substitutionarray['__ID__'] = '__ID__';
7664
			$substitutionarray['__REF__'] = '__REF__';
7665
			$substitutionarray['__NEWREF__'] = '__NEWREF__';
7666
			$substitutionarray['__LABEL__'] = '__LABEL__';
7667
			$substitutionarray['__REF_CLIENT__'] = '__REF_CLIENT__';
7668
			$substitutionarray['__REF_SUPPLIER__'] = '__REF_SUPPLIER__';
7669
			$substitutionarray['__NOTE_PUBLIC__'] = '__NOTE_PUBLIC__';
7670
			$substitutionarray['__NOTE_PRIVATE__'] = '__NOTE_PRIVATE__';
7671
			$substitutionarray['__EXTRAFIELD_XXX__'] = '__EXTRAFIELD_XXX__';
7672
7673
			if (isModEnabled("societe")) {	// Most objects are concerned
7674
				$substitutionarray['__THIRDPARTY_ID__'] = '__THIRDPARTY_ID__';
7675
				$substitutionarray['__THIRDPARTY_NAME__'] = '__THIRDPARTY_NAME__';
7676
				$substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = '__THIRDPARTY_NAME_ALIAS__';
7677
				$substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = '__THIRDPARTY_CODE_CLIENT__';
7678
				$substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = '__THIRDPARTY_CODE_FOURNISSEUR__';
7679
				$substitutionarray['__THIRDPARTY_EMAIL__'] = '__THIRDPARTY_EMAIL__';
7680
				$substitutionarray['__THIRDPARTY_PHONE__'] = '__THIRDPARTY_PHONE__';
7681
				$substitutionarray['__THIRDPARTY_FAX__'] = '__THIRDPARTY_FAX__';
7682
				$substitutionarray['__THIRDPARTY_ADDRESS__'] = '__THIRDPARTY_ADDRESS__';
7683
				$substitutionarray['__THIRDPARTY_ZIP__'] = '__THIRDPARTY_ZIP__';
7684
				$substitutionarray['__THIRDPARTY_TOWN__'] = '__THIRDPARTY_TOWN__';
7685
				$substitutionarray['__THIRDPARTY_IDPROF1__'] = '__THIRDPARTY_IDPROF1__';
7686
				$substitutionarray['__THIRDPARTY_IDPROF2__'] = '__THIRDPARTY_IDPROF2__';
7687
				$substitutionarray['__THIRDPARTY_IDPROF3__'] = '__THIRDPARTY_IDPROF3__';
7688
				$substitutionarray['__THIRDPARTY_IDPROF4__'] = '__THIRDPARTY_IDPROF4__';
7689
				$substitutionarray['__THIRDPARTY_IDPROF5__'] = '__THIRDPARTY_IDPROF5__';
7690
				$substitutionarray['__THIRDPARTY_IDPROF6__'] = '__THIRDPARTY_IDPROF6__';
7691
				$substitutionarray['__THIRDPARTY_TVAINTRA__'] = '__THIRDPARTY_TVAINTRA__';
7692
				$substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = '__THIRDPARTY_NOTE_PUBLIC__';
7693
				$substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = '__THIRDPARTY_NOTE_PRIVATE__';
7694
			}
7695
			if (isModEnabled('adherent') && (!is_object($object) || $object->element == 'adherent')) {
7696
				$substitutionarray['__MEMBER_ID__'] = '__MEMBER_ID__';
7697
				$substitutionarray['__MEMBER_CIVILITY__'] = '__MEMBER_CIVILITY__';
7698
				$substitutionarray['__MEMBER_FIRSTNAME__'] = '__MEMBER_FIRSTNAME__';
7699
				$substitutionarray['__MEMBER_LASTNAME__'] = '__MEMBER_LASTNAME__';
7700
				$substitutionarray['__MEMBER_USER_LOGIN_INFORMATION__'] = 'Login and pass of the external user account';
7701
				/*$substitutionarray['__MEMBER_NOTE_PUBLIC__'] = '__MEMBER_NOTE_PUBLIC__';
7702
				$substitutionarray['__MEMBER_NOTE_PRIVATE__'] = '__MEMBER_NOTE_PRIVATE__';*/
7703
			}
7704
			// add variables subtitutions ticket
7705
			if (isModEnabled('ticket') && (!is_object($object) || $object->element == 'ticket')) {
7706
				$substitutionarray['__TICKET_TRACKID__'] = '__TICKET_TRACKID__';
7707
				$substitutionarray['__TICKET_SUBJECT__'] = '__TICKET_SUBJECT__';
7708
				$substitutionarray['__TICKET_TYPE__'] = '__TICKET_TYPE__';
7709
				$substitutionarray['__TICKET_SEVERITY__'] = '__TICKET_SEVERITY__';
7710
				$substitutionarray['__TICKET_CATEGORY__'] = '__TICKET_CATEGORY__';
7711
				$substitutionarray['__TICKET_ANALYTIC_CODE__'] = '__TICKET_ANALYTIC_CODE__';
7712
				$substitutionarray['__TICKET_MESSAGE__'] = '__TICKET_MESSAGE__';
7713
				$substitutionarray['__TICKET_PROGRESSION__'] = '__TICKET_PROGRESSION__';
7714
				$substitutionarray['__TICKET_USER_ASSIGN__'] = '__TICKET_USER_ASSIGN__';
7715
			}
7716
7717
			if (isModEnabled('recruitment') && (!is_object($object) || $object->element == 'recruitmentcandidature')) {
7718
				$substitutionarray['__CANDIDATE_FULLNAME__'] = '__CANDIDATE_FULLNAME__';
7719
				$substitutionarray['__CANDIDATE_FIRSTNAME__'] = '__CANDIDATE_FIRSTNAME__';
7720
				$substitutionarray['__CANDIDATE_LASTNAME__'] = '__CANDIDATE_LASTNAME__';
7721
			}
7722
			if (isModEnabled('project')) {		// Most objects
7723
				$substitutionarray['__PROJECT_ID__'] = '__PROJECT_ID__';
7724
				$substitutionarray['__PROJECT_REF__'] = '__PROJECT_REF__';
7725
				$substitutionarray['__PROJECT_NAME__'] = '__PROJECT_NAME__';
7726
				/*$substitutionarray['__PROJECT_NOTE_PUBLIC__'] = '__PROJECT_NOTE_PUBLIC__';
7727
				$substitutionarray['__PROJECT_NOTE_PRIVATE__'] = '__PROJECT_NOTE_PRIVATE__';*/
7728
			}
7729
			if (isModEnabled('contrat') && (!is_object($object) || $object->element == 'contract')) {
7730
				$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = 'Highest date planned for a service start';
7731
				$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = 'Highest date and hour planned for service start';
7732
				$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = 'Lowest data for planned expiration of service';
7733
				$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = 'Lowest date and hour for planned expiration of service';
7734
			}
7735
			if (isModEnabled("propal") && (!is_object($object) || $object->element == 'propal')) {
7736
				$substitutionarray['__ONLINE_SIGN_URL__'] = 'ToOfferALinkForOnlineSignature';
7737
			}
7738
			if (isModEnabled("ficheinter") && (!is_object($object) || $object->element == 'fichinter')) {
7739
				$substitutionarray['__ONLINE_SIGN_FICHINTER_URL__'] = 'ToOfferALinkForOnlineSignature';
7740
			}
7741
			$substitutionarray['__ONLINE_PAYMENT_URL__'] = 'UrlToPayOnlineIfApplicable';
7742
			$substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = 'TextAndUrlToPayOnlineIfApplicable';
7743
			$substitutionarray['__SECUREKEYPAYMENT__'] = 'Security key (if key is not unique per record)';
7744
			$substitutionarray['__SECUREKEYPAYMENT_MEMBER__'] = 'Security key for payment on a member subscription (one key per member)';
7745
			$substitutionarray['__SECUREKEYPAYMENT_ORDER__'] = 'Security key for payment on an order';
7746
			$substitutionarray['__SECUREKEYPAYMENT_INVOICE__'] = 'Security key for payment on an invoice';
7747
			$substitutionarray['__SECUREKEYPAYMENT_CONTRACTLINE__'] = 'Security key for payment on a service of a contract';
7748
7749
			$substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = 'Direct download url of a proposal';
7750
			$substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = 'Direct download url of an order';
7751
			$substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = 'Direct download url of an invoice';
7752
			$substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = 'Direct download url of a contract';
7753
			$substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = 'Direct download url of a supplier proposal';
7754
7755
			if (isModEnabled("expedition") && (!is_object($object) || $object->element == 'shipping')) {
7756
				$substitutionarray['__SHIPPINGTRACKNUM__'] = 'Shipping tracking number';
7757
				$substitutionarray['__SHIPPINGTRACKNUMURL__'] = 'Shipping tracking url';
7758
			}
7759
			if (isModEnabled("reception") && (!is_object($object) || $object->element == 'reception')) {
7760
				$substitutionarray['__RECEPTIONTRACKNUM__'] = 'Shippin tracking number of shipment';
7761
				$substitutionarray['__RECEPTIONTRACKNUMURL__'] = 'Shipping tracking url';
7762
			}
7763
		} else {
7764
			$substitutionarray['__ID__'] = $object->id;
7765
			$substitutionarray['__REF__'] = $object->ref;
7766
			$substitutionarray['__NEWREF__'] = $object->newref;
7767
			$substitutionarray['__LABEL__'] = (isset($object->label) ? $object->label : (isset($object->title) ? $object->title : null));
7768
			$substitutionarray['__REF_CLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
7769
			$substitutionarray['__REF_SUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
7770
			$substitutionarray['__NOTE_PUBLIC__'] = (isset($object->note_public) ? $object->note_public : null);
7771
			$substitutionarray['__NOTE_PRIVATE__'] = (isset($object->note_private) ? $object->note_private : null);
7772
			if ($object->element == "shipping") {
7773
				$substitutionarray['__DATE_DELIVERY__'] = (isset($object->date_delivery) ? dol_print_date($object->date_delivery, 'day', 0, $outputlangs) : '');
7774
			} else {
7775
				$substitutionarray['__DATE_DELIVERY__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, 'day', 0, $outputlangs) : '');
7776
			}
7777
			$substitutionarray['__DATE_DELIVERY_DAY__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, "%d") : '');
7778
			$substitutionarray['__DATE_DELIVERY_DAY_TEXT__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, "%A") : '');
7779
			$substitutionarray['__DATE_DELIVERY_MON__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, "%m") : '');
7780
			$substitutionarray['__DATE_DELIVERY_MON_TEXT__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, "%b") : '');
7781
			$substitutionarray['__DATE_DELIVERY_YEAR__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, "%Y") : '');
7782
			$substitutionarray['__DATE_DELIVERY_HH__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, "%H") : '');
7783
			$substitutionarray['__DATE_DELIVERY_MM__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, "%M") : '');
7784
			$substitutionarray['__DATE_DELIVERY_SS__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, "%S") : '');
7785
7786
			// For backward compatibility
7787
			$substitutionarray['__REFCLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
7788
			$substitutionarray['__REFSUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
7789
			$substitutionarray['__SUPPLIER_ORDER_DATE_DELIVERY__'] = (isset($object->date_livraison) ? dol_print_date($object->date_livraison, 'day', 0, $outputlangs) : '');
7790
			$substitutionarray['__SUPPLIER_ORDER_DELAY_DELIVERY__'] = (isset($object->availability_code) ? ($outputlangs->transnoentities("AvailabilityType".$object->availability_code) != ('AvailabilityType'.$object->availability_code) ? $outputlangs->transnoentities("AvailabilityType".$object->availability_code) : $outputlangs->convToOutputCharset(isset($object->availability) ? $object->availability : '')) : '');
7791
7792
			if (is_object($object) && ($object->element == 'adherent' || $object->element == 'member') && $object->id > 0) {
7793
				$birthday = (empty($object->birth) ? '' : dol_print_date($object->birth, 'day'));
7794
7795
				$substitutionarray['__MEMBER_ID__'] = (isset($object->id) ? $object->id : '');
7796
				if (method_exists($object, 'getCivilityLabel')) {
7797
					$substitutionarray['__MEMBER_CIVILITY__'] = $object->getCivilityLabel();
7798
				}
7799
				$substitutionarray['__MEMBER_FIRSTNAME__'] = (isset($object->firstname) ? $object->firstname : '');
7800
				$substitutionarray['__MEMBER_LASTNAME__'] = (isset($object->lastname) ? $object->lastname : '');
7801
				$substitutionarray['__MEMBER_USER_LOGIN_INFORMATION__'] = '';
7802
				if (method_exists($object, 'getFullName')) {
7803
					$substitutionarray['__MEMBER_FULLNAME__'] = $object->getFullName($outputlangs);
7804
				}
7805
				$substitutionarray['__MEMBER_COMPANY__'] = (isset($object->societe) ? $object->societe : '');
7806
				$substitutionarray['__MEMBER_ADDRESS__'] = (isset($object->address) ? $object->address : '');
7807
				$substitutionarray['__MEMBER_ZIP__'] = (isset($object->zip) ? $object->zip : '');
7808
				$substitutionarray['__MEMBER_TOWN__'] = (isset($object->town) ? $object->town : '');
7809
				$substitutionarray['__MEMBER_COUNTRY__'] = (isset($object->country) ? $object->country : '');
7810
				$substitutionarray['__MEMBER_EMAIL__'] = (isset($object->email) ? $object->email : '');
7811
				$substitutionarray['__MEMBER_BIRTH__'] = (isset($birthday) ? $birthday : '');
7812
				$substitutionarray['__MEMBER_PHOTO__'] = (isset($object->photo) ? $object->photo : '');
7813
				$substitutionarray['__MEMBER_LOGIN__'] = (isset($object->login) ? $object->login : '');
7814
				$substitutionarray['__MEMBER_PASSWORD__'] = (isset($object->pass) ? $object->pass : '');
7815
				$substitutionarray['__MEMBER_PHONE__'] = (isset($object->phone) ? dol_print_phone($object->phone) : '');
7816
				$substitutionarray['__MEMBER_PHONEPRO__'] = (isset($object->phone_perso) ? dol_print_phone($object->phone_perso) : '');
7817
				$substitutionarray['__MEMBER_PHONEMOBILE__'] = (isset($object->phone_mobile) ? dol_print_phone($object->phone_mobile) : '');
7818
				$substitutionarray['__MEMBER_TYPE__'] = (isset($object->type) ? $object->type : '');
7819
				$substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE__']       = dol_print_date($object->first_subscription_date, 'dayrfc');
7820
				$substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_START__'] = (isset($object->first_subscription_date_start) ? dol_print_date($object->first_subscription_date_start, 'dayrfc') : '');
7821
				$substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_END__']   = (isset($object->first_subscription_date_end) ? dol_print_date($object->first_subscription_date_end, 'dayrfc') : '');
7822
				$substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE__']        = dol_print_date($object->last_subscription_date, 'dayrfc');
7823
				$substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_START__']  = dol_print_date($object->last_subscription_date_start, 'dayrfc');
7824
				$substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_END__']    = dol_print_date($object->last_subscription_date_end, 'dayrfc');
7825
			}
7826
7827
			if (is_object($object) && $object->element == 'societe') {
7828
				$substitutionarray['__THIRDPARTY_ID__'] = (is_object($object) ? $object->id : '');
7829
				$substitutionarray['__THIRDPARTY_NAME__'] = (is_object($object) ? $object->name : '');
7830
				$substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = (is_object($object) ? $object->name_alias : '');
7831
				$substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = (is_object($object) ? $object->code_client : '');
7832
				$substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = (is_object($object) ? $object->code_fournisseur : '');
7833
				$substitutionarray['__THIRDPARTY_EMAIL__'] = (is_object($object) ? $object->email : '');
7834
				$substitutionarray['__THIRDPARTY_PHONE__'] = (is_object($object) ? dol_print_phone($object->phone) : '');
7835
				$substitutionarray['__THIRDPARTY_FAX__'] = (is_object($object) ? dol_print_phone($object->fax) : '');
7836
				$substitutionarray['__THIRDPARTY_ADDRESS__'] = (is_object($object) ? $object->address : '');
7837
				$substitutionarray['__THIRDPARTY_ZIP__'] = (is_object($object) ? $object->zip : '');
7838
				$substitutionarray['__THIRDPARTY_TOWN__'] = (is_object($object) ? $object->town : '');
7839
				$substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = (is_object($object) ? $object->country_id : '');
7840
				$substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = (is_object($object) ? $object->country_code : '');
7841
				$substitutionarray['__THIRDPARTY_IDPROF1__'] = (is_object($object) ? $object->idprof1 : '');
7842
				$substitutionarray['__THIRDPARTY_IDPROF2__'] = (is_object($object) ? $object->idprof2 : '');
7843
				$substitutionarray['__THIRDPARTY_IDPROF3__'] = (is_object($object) ? $object->idprof3 : '');
7844
				$substitutionarray['__THIRDPARTY_IDPROF4__'] = (is_object($object) ? $object->idprof4 : '');
7845
				$substitutionarray['__THIRDPARTY_IDPROF5__'] = (is_object($object) ? $object->idprof5 : '');
7846
				$substitutionarray['__THIRDPARTY_IDPROF6__'] = (is_object($object) ? $object->idprof6 : '');
7847
				$substitutionarray['__THIRDPARTY_TVAINTRA__'] = (is_object($object) ? $object->tva_intra : '');
7848
				$substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = (is_object($object) ? dol_htmlentitiesbr($object->note_public) : '');
7849
				$substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = (is_object($object) ? dol_htmlentitiesbr($object->note_private) : '');
7850
			} elseif (is_object($object->thirdparty)) {
7851
				$substitutionarray['__THIRDPARTY_ID__'] = (is_object($object->thirdparty) ? $object->thirdparty->id : '');
7852
				$substitutionarray['__THIRDPARTY_NAME__'] = (is_object($object->thirdparty) ? $object->thirdparty->name : '');
7853
				$substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = (is_object($object->thirdparty) ? $object->thirdparty->name_alias : '');
7854
				$substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = (is_object($object->thirdparty) ? $object->thirdparty->code_client : '');
7855
				$substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = (is_object($object->thirdparty) ? $object->thirdparty->code_fournisseur : '');
7856
				$substitutionarray['__THIRDPARTY_EMAIL__'] = (is_object($object->thirdparty) ? $object->thirdparty->email : '');
7857
				$substitutionarray['__THIRDPARTY_PHONE__'] = (is_object($object->thirdparty) ? dol_print_phone($object->thirdparty->phone) : '');
7858
				$substitutionarray['__THIRDPARTY_FAX__'] = (is_object($object->thirdparty) ? dol_print_phone($object->thirdparty->fax) : '');
7859
				$substitutionarray['__THIRDPARTY_ADDRESS__'] = (is_object($object->thirdparty) ? $object->thirdparty->address : '');
7860
				$substitutionarray['__THIRDPARTY_ZIP__'] = (is_object($object->thirdparty) ? $object->thirdparty->zip : '');
7861
				$substitutionarray['__THIRDPARTY_TOWN__'] = (is_object($object->thirdparty) ? $object->thirdparty->town : '');
7862
				$substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = (is_object($object->thirdparty) ? $object->thirdparty->country_id : '');
7863
				$substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = (is_object($object->thirdparty) ? $object->thirdparty->country_code : '');
7864
				$substitutionarray['__THIRDPARTY_IDPROF1__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof1 : '');
7865
				$substitutionarray['__THIRDPARTY_IDPROF2__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof2 : '');
7866
				$substitutionarray['__THIRDPARTY_IDPROF3__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof3 : '');
7867
				$substitutionarray['__THIRDPARTY_IDPROF4__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof4 : '');
7868
				$substitutionarray['__THIRDPARTY_IDPROF5__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof5 : '');
7869
				$substitutionarray['__THIRDPARTY_IDPROF6__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof6 : '');
7870
				$substitutionarray['__THIRDPARTY_TVAINTRA__'] = (is_object($object->thirdparty) ? $object->thirdparty->tva_intra : '');
7871
				$substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = (is_object($object->thirdparty) ? dol_htmlentitiesbr($object->thirdparty->note_public) : '');
7872
				$substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = (is_object($object->thirdparty) ? dol_htmlentitiesbr($object->thirdparty->note_private) : '');
7873
			}
7874
7875
			if (is_object($object) && $object->element == 'recruitmentcandidature') {
7876
				$substitutionarray['__CANDIDATE_FULLNAME__'] = $object->getFullName($outputlangs);
7877
				$substitutionarray['__CANDIDATE_FIRSTNAME__'] = isset($object->firstname) ? $object->firstname : '';
7878
				$substitutionarray['__CANDIDATE_LASTNAME__'] = isset($object->lastname) ? $object->lastname : '';
7879
			}
7880
7881
			if (is_object($object->project)) {
7882
				$substitutionarray['__PROJECT_ID__'] = (is_object($object->project) ? $object->project->id : '');
7883
				$substitutionarray['__PROJECT_REF__'] = (is_object($object->project) ? $object->project->ref : '');
7884
				$substitutionarray['__PROJECT_NAME__'] = (is_object($object->project) ? $object->project->title : '');
7885
			}
7886
			if (is_object($object->projet)) {	// Deprecated, for backward compatibility
7887
				$substitutionarray['__PROJECT_ID__'] = (is_object($object->projet) ? $object->projet->id : '');
7888
				$substitutionarray['__PROJECT_REF__'] = (is_object($object->projet) ? $object->projet->ref : '');
7889
				$substitutionarray['__PROJECT_NAME__'] = (is_object($object->projet) ? $object->projet->title : '');
7890
			}
7891
			if (is_object($object) && $object->element == 'project') {
7892
				$substitutionarray['__PROJECT_NAME__'] = $object->title;
7893
			}
7894
7895
			if (is_object($object) && $object->element == 'shipping') {
7896
				$substitutionarray['__SHIPPINGTRACKNUM__'] = $object->tracking_number;
7897
				$substitutionarray['__SHIPPINGTRACKNUMURL__'] = $object->tracking_url;
7898
			}
7899
			if (is_object($object) && $object->element == 'reception') {
7900
				$substitutionarray['__RECEPTIONTRACKNUM__'] = $object->tracking_number;
7901
				$substitutionarray['__RECEPTIONTRACKNUMURL__'] = $object->tracking_url;
7902
			}
7903
7904
			if (is_object($object) && $object->element == 'contrat' && $object->id > 0 && is_array($object->lines)) {
7905
				$dateplannedstart = '';
7906
				$datenextexpiration = '';
7907
				foreach ($object->lines as $line) {
7908
					if ($line->date_start > $dateplannedstart) {
7909
						$dateplannedstart = $line->date_start;
7910
					}
7911
					if ($line->statut == 4 && $line->date_end && (!$datenextexpiration || $line->date_end < $datenextexpiration)) {
7912
						$datenextexpiration = $line->date_end;
7913
					}
7914
				}
7915
				$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = dol_print_date($dateplannedstart, 'dayrfc');
7916
				$substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = dol_print_date($dateplannedstart, 'standard');
7917
				$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = dol_print_date($datenextexpiration, 'dayrfc');
7918
				$substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = dol_print_date($datenextexpiration, 'standard');
7919
			}
7920
			// add substition variable for ticket
7921
			if (is_object($object) && $object->element == 'ticket') {
7922
				$substitutionarray['__TICKET_TRACKID__'] = $object->track_id;
7923
				$substitutionarray['__REF__'] = $object->ref;
7924
				$substitutionarray['__TICKET_SUBJECT__'] = $object->subject;
7925
				$substitutionarray['__TICKET_TYPE__'] = $object->type_code;
7926
				$substitutionarray['__TICKET_SEVERITY__'] = $object->severity_code;
7927
				$substitutionarray['__TICKET_CATEGORY__'] = $object->category_code; // For backward compatibility
7928
				$substitutionarray['__TICKET_ANALYTIC_CODE__'] = $object->category_code;
7929
				$substitutionarray['__TICKET_MESSAGE__'] = $object->message;
7930
				$substitutionarray['__TICKET_PROGRESSION__'] = $object->progress;
7931
				$userstat = new User($db);
7932
				if ($object->fk_user_assign > 0) {
7933
					$userstat->fetch($object->fk_user_assign);
7934
					$substitutionarray['__TICKET_USER_ASSIGN__'] = dolGetFirstLastname($userstat->firstname, $userstat->lastname);
7935
				}
7936
7937
				if ($object->fk_user_create > 0) {
7938
					$userstat->fetch($object->fk_user_create);
7939
					$substitutionarray['__USER_CREATE__'] = dolGetFirstLastname($userstat->firstname, $userstat->lastname);
7940
				}
7941
			}
7942
7943
			// Create dynamic tags for __EXTRAFIELD_FIELD__
7944
			if ($object->table_element && $object->id > 0) {
7945
				if (!is_object($extrafields)) {
7946
					$extrafields = new ExtraFields($db);
7947
				}
7948
				$extrafields->fetch_name_optionals_label($object->table_element, true);
7949
7950
				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

7950
				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...
7951
					if (is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label']) > 0) {
7952
						foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) {
7953
							$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'__'] = $object->array_options['options_'.$key];
7954
							if ($extrafields->attributes[$object->table_element]['type'][$key] == 'date') {
7955
								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'__'] = dol_print_date($object->array_options['options_'.$key], 'day');
7956
								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_LOCALE__'] = dol_print_date($object->array_options['options_'.$key], 'day', 'tzserver', $outputlangs);
7957
								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_RFC__'] = dol_print_date($object->array_options['options_'.$key], 'dayrfc');
7958
							} elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'datetime') {
7959
								$datetime = $object->array_options['options_'.$key];
7960
								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhour') : '');
7961
								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_LOCALE__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhour', 'tzserver', $outputlangs) : '');
7962
								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_DAY_LOCALE__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'day', 'tzserver', $outputlangs) : '');
7963
								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_RFC__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhourrfc') : '');
7964
							} elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'phone') {
7965
								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'__'] = dol_print_phone($object->array_options['options_'.$key]);
7966
							} elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'price') {
7967
								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'__'] = $object->array_options['options_'.$key];
7968
								$substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_FORMATED__'] = price($object->array_options['options_'.$key]);
7969
							}
7970
						}
7971
					}
7972
				}
7973
			}
7974
7975
			// Complete substitution array with the url to make online payment
7976
			$paymenturl = '';
7977
			if (empty($substitutionarray['__REF__'])) {
7978
				$paymenturl = '';
7979
			} else {
7980
				// Set the online payment url link into __ONLINE_PAYMENT_URL__ key
7981
				require_once DOL_DOCUMENT_ROOT.'/core/lib/payments.lib.php';
7982
				$outputlangs->loadLangs(array('paypal', 'other'));
7983
				$typeforonlinepayment = 'free';
7984
				if (is_object($object) && $object->element == 'commande') {
7985
					$typeforonlinepayment = 'order';
7986
				}
7987
				if (is_object($object) && $object->element == 'facture') {
7988
					$typeforonlinepayment = 'invoice';
7989
				}
7990
				if (is_object($object) && $object->element == 'member') {
7991
					$typeforonlinepayment = 'member';
7992
				}
7993
				if (is_object($object) && $object->element == 'contrat') {
7994
					$typeforonlinepayment = 'contract';
7995
				}
7996
				if (is_object($object) && $object->element == 'fichinter') {
7997
					$typeforonlinepayment = 'ficheinter';
7998
				}
7999
				$url = getOnlinePaymentUrl(0, $typeforonlinepayment, $substitutionarray['__REF__']);
8000
				$paymenturl = $url;
8001
			}
8002
8003
			if ($object->id > 0) {
8004
				$substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = ($paymenturl ?str_replace('\n', "\n", $outputlangs->trans("PredefinedMailContentLink", $paymenturl)) : '');
8005
				$substitutionarray['__ONLINE_PAYMENT_URL__'] = $paymenturl;
8006
8007
				if (is_object($object) && $object->element == 'propal') {
8008
					require_once DOL_DOCUMENT_ROOT.'/core/lib/signature.lib.php';
8009
					$substitutionarray['__ONLINE_SIGN_URL__'] = getOnlineSignatureUrl(0, 'proposal', $object->ref);
8010
				}
8011
				if (is_object($object) && $object->element == 'fichinter') {
8012
					require_once DOL_DOCUMENT_ROOT.'/core/lib/signature.lib.php';
8013
					$substitutionarray['__ONLINE_SIGN_FICHINTER_URL__'] = getOnlineSignatureUrl(0, 'fichinter', $object->ref);
8014
				}
8015
				if (!empty($conf->global->PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'propal') {
8016
					$substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = $object->getLastMainDocLink($object->element);
8017
				} else {
8018
					$substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = '';
8019
				}
8020
				if (!empty($conf->global->ORDER_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'commande') {
8021
					$substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = $object->getLastMainDocLink($object->element);
8022
				} else {
8023
					$substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = '';
8024
				}
8025
				if (!empty($conf->global->INVOICE_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'facture') {
8026
					$substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = $object->getLastMainDocLink($object->element);
8027
				} else {
8028
					$substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = '';
8029
				}
8030
				if (!empty($conf->global->CONTRACT_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'contrat') {
8031
					$substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = $object->getLastMainDocLink($object->element);
8032
				} else {
8033
					$substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = '';
8034
				}
8035
				if (!empty($conf->global->FICHINTER_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'fichinter') {
8036
					$substitutionarray['__DIRECTDOWNLOAD_URL_FICHINTER__'] = $object->getLastMainDocLink($object->element);
8037
				} else {
8038
					$substitutionarray['__DIRECTDOWNLOAD_URL_FICHINTER__'] = '';
8039
				}
8040
				if (!empty($conf->global->SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD) && is_object($object) && $object->element == 'supplier_proposal') {
8041
					$substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = $object->getLastMainDocLink($object->element);
8042
				} else {
8043
					$substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = '';
8044
				}
8045
8046
				if (is_object($object) && $object->element == 'propal') {
8047
					$substitutionarray['__URL_PROPOSAL__'] = DOL_MAIN_URL_ROOT."/comm/propal/card.php?id=".$object->id;
8048
				}
8049
				if (is_object($object) && $object->element == 'commande') {
8050
					$substitutionarray['__URL_ORDER__'] = DOL_MAIN_URL_ROOT."/commande/card.php?id=".$object->id;
8051
				}
8052
				if (is_object($object) && $object->element == 'facture') {
8053
					$substitutionarray['__URL_INVOICE__'] = DOL_MAIN_URL_ROOT."/compta/facture/card.php?id=".$object->id;
8054
				}
8055
				if (is_object($object) && $object->element == 'contrat') {
8056
					$substitutionarray['__URL_CONTRACT__'] = DOL_MAIN_URL_ROOT."/contrat/card.php?id=".$object->id;
8057
				}
8058
				if (is_object($object) && $object->element == 'fichinter') {
8059
					$substitutionarray['__URL_FICHINTER__'] = DOL_MAIN_URL_ROOT."/fichinter/card.php?id=".$object->id;
8060
				}
8061
				if (is_object($object) && $object->element == 'supplier_proposal') {
8062
					$substitutionarray['__URL_SUPPLIER_PROPOSAL__'] = DOL_MAIN_URL_ROOT."/supplier_proposal/card.php?id=".$object->id;
8063
				}
8064
				if (is_object($object) && $object->element == 'shipping') {
8065
					$substitutionarray['__URL_SHIPMENT__'] = DOL_MAIN_URL_ROOT."/expedition/card.php?id=".$object->id;
8066
				}
8067
			}
8068
8069
			if (is_object($object) && $object->element == 'action') {
8070
				$substitutionarray['__EVENT_LABEL__'] = $object->label;
8071
				$substitutionarray['__EVENT_DATE__'] = dol_print_date($object->datep, '%A %d %b %Y');
8072
				$substitutionarray['__EVENT_TIME__'] = dol_print_date($object->datep, '%H:%M:%S');
8073
			}
8074
		}
8075
	}
8076
	if (empty($exclude) || !in_array('objectamount', $exclude)) {
8077
		include_once DOL_DOCUMENT_ROOT.'/core/lib/functionsnumtoword.lib.php';
8078
8079
		$substitutionarray['__DATE_YMD__']        = is_object($object) ? (isset($object->date) ? dol_print_date($object->date, 'day', 0, $outputlangs) : null) : '';
8080
		$substitutionarray['__DATE_DUE_YMD__']    = is_object($object) ? (isset($object->date_lim_reglement) ? dol_print_date($object->date_lim_reglement, 'day', 0, $outputlangs) : null) : '';
8081
8082
		$already_payed_all = 0;
8083
		if (is_object($object) && ($object instanceof Facture)) {
8084
			$already_payed_all = $object->sumpayed + $object->sumdeposit + $object->sumcreditnote;
8085
		}
8086
8087
		$substitutionarray['__AMOUNT_EXCL_TAX__'] = is_object($object) ? $object->total_ht : '';
8088
8089
		$substitutionarray['__AMOUNT__']          = is_object($object) ? $object->total_ttc : '';
8090
		$substitutionarray['__AMOUNT_TEXT__']     = is_object($object) ? dol_convertToWord($object->total_ttc, $outputlangs, '', true) : '';
8091
		$substitutionarray['__AMOUNT_TEXTCURRENCY__'] = is_object($object) ? dol_convertToWord($object->total_ttc, $outputlangs, $conf->currency, true) : '';
8092
8093
		$substitutionarray['__AMOUNT_REMAIN__'] = is_object($object) ? price2num($object->total_ttc - $already_payed_all, 'MT') : '';
8094
8095
		$substitutionarray['__AMOUNT_VAT__']      = is_object($object) ? (isset($object->total_vat) ? $object->total_vat : $object->total_tva) : '';
0 ignored issues
show
Bug introduced by
The property total_vat does not exist on Facture. Did you mean total_tva?
Loading history...
8096
		$substitutionarray['__AMOUNT_VAT_TEXT__']      = is_object($object) ? (isset($object->total_vat) ? dol_convertToWord($object->total_vat, $outputlangs, '', true) : dol_convertToWord($object->total_tva, $outputlangs, '', true)) : '';
8097
		$substitutionarray['__AMOUNT_VAT_TEXTCURRENCY__']      = is_object($object) ? (isset($object->total_vat) ? dol_convertToWord($object->total_vat, $outputlangs, $conf->currency, true) : dol_convertToWord($object->total_tva, $outputlangs, $conf->currency, true)) : '';
8098
8099
		if ($onlykey != 2 || $mysoc->useLocalTax(1)) {
8100
			$substitutionarray['__AMOUNT_TAX2__']     = is_object($object) ? $object->total_localtax1 : '';
8101
		}
8102
		if ($onlykey != 2 || $mysoc->useLocalTax(2)) {
8103
			$substitutionarray['__AMOUNT_TAX3__']     = is_object($object) ? $object->total_localtax2 : '';
8104
		}
8105
8106
		// Amount keys formated in a currency
8107
		$substitutionarray['__AMOUNT_EXCL_TAX_FORMATED__'] = is_object($object) ? ($object->total_ht ? price($object->total_ht, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
8108
		$substitutionarray['__AMOUNT_FORMATED__']          = is_object($object) ? ($object->total_ttc ? price($object->total_ttc, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
8109
		$substitutionarray['__AMOUNT_REMAIN_FORMATED__'] = is_object($object) ? ($object->total_ttc ? price($object->total_ttc - $already_payed_all, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
8110
		$substitutionarray['__AMOUNT_VAT_FORMATED__']      = is_object($object) ? (isset($object->total_vat) ? price($object->total_vat, 0, $outputlangs, 0, -1, -1, $conf->currency) : ($object->total_tva ? price($object->total_tva, 0, $outputlangs, 0, -1, -1, $conf->currency) : null)) : '';
8111
		if ($onlykey != 2 || $mysoc->useLocalTax(1)) {
8112
			$substitutionarray['__AMOUNT_TAX2_FORMATED__']     = is_object($object) ? ($object->total_localtax1 ? price($object->total_localtax1, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
8113
		}
8114
		if ($onlykey != 2 || $mysoc->useLocalTax(2)) {
8115
			$substitutionarray['__AMOUNT_TAX3_FORMATED__']     = is_object($object) ? ($object->total_localtax2 ? price($object->total_localtax2, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
8116
		}
8117
8118
		$substitutionarray['__AMOUNT_MULTICURRENCY__']          = (is_object($object) && isset($object->multicurrency_total_ttc)) ? $object->multicurrency_total_ttc : '';
8119
		$substitutionarray['__AMOUNT_MULTICURRENCY_TEXT__']     = (is_object($object) && isset($object->multicurrency_total_ttc)) ? dol_convertToWord($object->multicurrency_total_ttc, $outputlangs, '', true) : '';
8120
		$substitutionarray['__AMOUNT_MULTICURRENCY_TEXTCURRENCY__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? dol_convertToWord($object->multicurrency_total_ttc, $outputlangs, $object->multicurrency_code, true) : '';
8121
		// TODO Add other keys for foreign multicurrency
8122
8123
		// For backward compatibility
8124
		if ($onlykey != 2) {
8125
			$substitutionarray['__TOTAL_TTC__']    = is_object($object) ? $object->total_ttc : '';
8126
			$substitutionarray['__TOTAL_HT__']     = is_object($object) ? $object->total_ht : '';
8127
			$substitutionarray['__TOTAL_VAT__']    = is_object($object) ? (isset($object->total_vat) ? $object->total_vat : $object->total_tva) : '';
8128
		}
8129
	}
8130
8131
	//var_dump($substitutionarray['__AMOUNT_FORMATED__']);
8132
	if (empty($exclude) || !in_array('date', $exclude)) {
8133
		include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
8134
8135
		$now = dol_now();
8136
8137
		$tmp = dol_getdate($now, true);
8138
		$tmp2 = dol_get_prev_day($tmp['mday'], $tmp['mon'], $tmp['year']);
8139
		$tmp3 = dol_get_prev_month($tmp['mon'], $tmp['year']);
8140
		$tmp4 = dol_get_next_day($tmp['mday'], $tmp['mon'], $tmp['year']);
8141
		$tmp5 = dol_get_next_month($tmp['mon'], $tmp['year']);
8142
8143
		$daytext = $outputlangs->trans('Day'.$tmp['wday']);
8144
8145
		$substitutionarray = array_merge($substitutionarray, array(
8146
			'__NOW_TMS__' => (int) $now,
8147
			'__NOW_TMS_YMD__' => dol_print_date($now, 'day', 0, $outputlangs),
8148
			'__DAY__' => (string) $tmp['mday'],
8149
			'__DAY_TEXT__' => $daytext, // Monday
8150
			'__DAY_TEXT_SHORT__' => dol_trunc($daytext, 3, 'right', 'UTF-8', 1), // Mon
8151
			'__DAY_TEXT_MIN__' => dol_trunc($daytext, 1, 'right', 'UTF-8', 1), // M
8152
			'__MONTH__' => (string) $tmp['mon'],
8153
			'__MONTH_TEXT__' => $outputlangs->trans('Month'.sprintf("%02d", $tmp['mon'])),
8154
			'__MONTH_TEXT_SHORT__' => $outputlangs->trans('MonthShort'.sprintf("%02d", $tmp['mon'])),
8155
			'__MONTH_TEXT_MIN__' => $outputlangs->trans('MonthVeryShort'.sprintf("%02d", $tmp['mon'])),
8156
			'__YEAR__' => (string) $tmp['year'],
8157
			'__PREVIOUS_DAY__' => (string) $tmp2['day'],
8158
			'__PREVIOUS_MONTH__' => (string) $tmp3['month'],
8159
			'__PREVIOUS_YEAR__' => (string) ($tmp['year'] - 1),
8160
			'__NEXT_DAY__' => (string) $tmp4['day'],
8161
			'__NEXT_MONTH__' => (string) $tmp5['month'],
8162
			'__NEXT_YEAR__' => (string) ($tmp['year'] + 1),
8163
		));
8164
	}
8165
8166
	if (isModEnabled('multicompany')) {
8167
		$substitutionarray = array_merge($substitutionarray, array('__ENTITY_ID__' => $conf->entity));
8168
	}
8169
	if (empty($exclude) || !in_array('system', $exclude)) {
8170
		$substitutionarray['__DOL_MAIN_URL_ROOT__'] = DOL_MAIN_URL_ROOT;
8171
		$substitutionarray['__(AnyTranslationKey)__'] = $outputlangs->trans('TranslationOfKey');
8172
		$substitutionarray['__(AnyTranslationKey|langfile)__'] = $outputlangs->trans('TranslationOfKey').' (load also language file before)';
8173
		$substitutionarray['__[AnyConstantKey]__'] = $outputlangs->trans('ValueOfConstantKey');
8174
	}
8175
8176
	return $substitutionarray;
8177
}
8178
8179
/**
8180
 *  Make substitution into a text string, replacing keys with vals from $substitutionarray (oldval=>newval),
8181
 *  and texts like __(TranslationKey|langfile)__ and __[ConstantKey]__ are also replaced.
8182
 *  Example of usage:
8183
 *  $substitutionarray = getCommonSubstitutionArray($langs, 0, null, $thirdparty);
8184
 *  complete_substitutions_array($substitutionarray, $langs, $thirdparty);
8185
 *  $mesg = make_substitutions($mesg, $substitutionarray, $langs);
8186
 *
8187
 *  @param	string		$text	      					Source string in which we must do substitution
8188
 *  @param  array		$substitutionarray				Array with key->val to substitute. Example: array('__MYKEY__' => 'MyVal', ...)
8189
 *  @param	Translate	$outputlangs					Output language
8190
 *  @param	int			$converttextinhtmlifnecessary	0=Convert only value into HTML if text is already in HTML
8191
 *  													1=Will also convert initial $text into HTML if we try to insert one value that is HTML
8192
 * 	@return string  		    						Output string after substitutions
8193
 *  @see	complete_substitutions_array(), getCommonSubstitutionArray()
8194
 */
8195
function make_substitutions($text, $substitutionarray, $outputlangs = null, $converttextinhtmlifnecessary = 0)
8196
{
8197
	global $conf, $langs;
8198
8199
	if (!is_array($substitutionarray)) {
8200
		return 'ErrorBadParameterSubstitutionArrayWhenCalling_make_substitutions';
8201
	}
8202
8203
	if (empty($outputlangs)) {
8204
		$outputlangs = $langs;
8205
	}
8206
8207
	// Is initial text HTML or simple text ?
8208
	$msgishtml = 0;
8209
	if (dol_textishtml($text, 1)) {
8210
		$msgishtml = 1;
8211
	}
8212
8213
	// Make substitution for language keys: __(AnyTranslationKey)__ or __(AnyTranslationKey|langfile)__
8214
	if (is_object($outputlangs)) {
8215
		$reg = array();
8216
		while (preg_match('/__\(([^\)]+)\)__/', $text, $reg)) {
8217
			// If key is __(TranslationKey|langfile)__, then force load of langfile.lang
8218
			$tmp = explode('|', $reg[1]);
8219
			if (!empty($tmp[1])) {
8220
				$outputlangs->load($tmp[1]);
8221
			}
8222
8223
			$value = $outputlangs->transnoentitiesnoconv($reg[1]);
8224
8225
			if (empty($converttextinhtmlifnecessary)) {
8226
				// convert $newval into HTML is necessary
8227
				$text = preg_replace('/__\('.preg_quote($reg[1], '/').'\)__/', $msgishtml ? dol_htmlentitiesbr($value) : $value, $text);
8228
			} else {
8229
				if (! $msgishtml) {
8230
					$valueishtml = dol_textishtml($value, 1);
8231
					//var_dump("valueishtml=".$valueishtml);
8232
8233
					if ($valueishtml) {
8234
						$text = dol_htmlentitiesbr($text);
8235
						$msgishtml = 1;
8236
					}
8237
				} else {
8238
					$value = dol_nl2br("$value");
8239
				}
8240
8241
				$text = preg_replace('/__\('.preg_quote($reg[1], '/').'\)__/', $value, $text);
8242
			}
8243
		}
8244
	}
8245
8246
	// Make substitution for constant keys.
8247
	// Must be after the substitution of translation, so if the text of translation contains a string __[xxx]__, it is also converted.
8248
	$reg = array();
8249
	while (preg_match('/__\[([^\]]+)\]__/', $text, $reg)) {
8250
		$keyfound = $reg[1];
8251
		if (isASecretKey($keyfound)) {
8252
			$value = '*****forbidden*****';
8253
		} else {
8254
			$value = empty($conf->global->$keyfound) ? '' : $conf->global->$keyfound;
8255
		}
8256
8257
		if (empty($converttextinhtmlifnecessary)) {
8258
			// convert $newval into HTML is necessary
8259
			$text = preg_replace('/__\['.preg_quote($keyfound, '/').'\]__/', $msgishtml ? dol_htmlentitiesbr($value) : $value, $text);
8260
		} else {
8261
			if (! $msgishtml) {
8262
				$valueishtml = dol_textishtml($value, 1);
8263
8264
				if ($valueishtml) {
8265
					$text = dol_htmlentitiesbr($text);
8266
					$msgishtml = 1;
8267
				}
8268
			} else {
8269
				$value = dol_nl2br("$value");
8270
			}
8271
8272
			$text = preg_replace('/__\['.preg_quote($keyfound, '/').'\]__/', $value, $text);
8273
		}
8274
	}
8275
8276
	// Make substitution for array $substitutionarray
8277
	foreach ($substitutionarray as $key => $value) {
8278
		if (!isset($value)) {
8279
			continue; // If value is null, it same than not having substitution key at all into array, we do not replace.
8280
		}
8281
8282
		if (($key == '__USER_SIGNATURE__' || $key == '__SENDEREMAIL_SIGNATURE__') && (!empty($conf->global->MAIN_MAIL_DO_NOT_USE_SIGN))) {
8283
			$value = ''; // Protection
8284
		}
8285
8286
		if (empty($converttextinhtmlifnecessary)) {
8287
			$text = str_replace("$key", "$value", $text); // We must keep the " to work when value is 123.5 for example
8288
		} else {
8289
			if (! $msgishtml) {
8290
				$valueishtml = dol_textishtml($value, 1);
8291
8292
				if ($valueishtml) {
8293
					$text = dol_htmlentitiesbr($text);
8294
					$msgishtml = 1;
8295
				}
8296
			} else {
8297
				$value = dol_nl2br("$value");
8298
			}
8299
			$text = str_replace("$key", "$value", $text); // We must keep the " to work when value is 123.5 for example
8300
		}
8301
	}
8302
8303
	return $text;
8304
}
8305
8306
/**
8307
 *  Complete the $substitutionarray with more entries coming from external module that had set the "substitutions=1" into module_part array.
8308
 *  In this case, method completesubstitutionarray provided by module is called.
8309
 *
8310
 *  @param  array		$substitutionarray		Array substitution old value => new value value
8311
 *  @param  Translate	$outputlangs            Output language
8312
 *  @param  Object		$object                 Source object
8313
 *  @param  mixed		$parameters       		Add more parameters (useful to pass product lines)
8314
 *  @param  string      $callfunc               What is the name of the custom function that will be called? (default: completesubstitutionarray)
8315
 *  @return	void
8316
 *  @see 	make_substitutions()
8317
 */
8318
function complete_substitutions_array(&$substitutionarray, $outputlangs, $object = null, $parameters = null, $callfunc = "completesubstitutionarray")
8319
{
8320
	global $conf, $user;
8321
8322
	require_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
8323
8324
	// Note: substitution key for each extrafields, using key __EXTRA_XXX__ is already available into the getCommonSubstitutionArray used to build the substitution array.
8325
8326
	// Check if there is external substitution to do, requested by plugins
8327
	$dirsubstitutions = array_merge(array(), (array) $conf->modules_parts['substitutions']);
8328
8329
	foreach ($dirsubstitutions as $reldir) {
8330
		$dir = dol_buildpath($reldir, 0);
8331
8332
		// Check if directory exists
8333
		if (!dol_is_dir($dir)) {
8334
			continue;
8335
		}
8336
8337
		$substitfiles = dol_dir_list($dir, 'files', 0, 'functions_');
8338
		foreach ($substitfiles as $substitfile) {
8339
			$reg = array();
8340
			if (preg_match('/functions_(.*)\.lib\.php/i', $substitfile['name'], $reg)) {
8341
				$module = $reg[1];
8342
8343
				dol_syslog("Library ".$substitfile['name']." found into ".$dir);
8344
				// Include the user's functions file
8345
				require_once $dir.$substitfile['name'];
8346
				// Call the user's function, and only if it is defined
8347
				$function_name = $module."_".$callfunc;
8348
				if (function_exists($function_name)) {
8349
					$function_name($substitutionarray, $outputlangs, $object, $parameters);
8350
				}
8351
			}
8352
		}
8353
	}
8354
	if (!empty($conf->global->ODT_ENABLE_ALL_TAGS_IN_SUBSTITUTIONS)) {
8355
		// to list all tags in odt template
8356
		$tags = '';
8357
		foreach ($substitutionarray as $key => $value) {
8358
			$tags .= '{'.$key.'} => '.$value."\n";
8359
		}
8360
		$substitutionarray = array_merge($substitutionarray, array('__ALL_TAGS__' => $tags));
8361
	}
8362
}
8363
8364
/**
8365
 *    Format output for start and end date
8366
 *
8367
 *    @param	int	$date_start    Start date
8368
 *    @param    int	$date_end      End date
8369
 *    @param    string		$format        Output format
8370
 *    @param	Translate	$outputlangs   Output language
8371
 *    @return	void
8372
 */
8373
function print_date_range($date_start, $date_end, $format = '', $outputlangs = '')
8374
{
8375
	print get_date_range($date_start, $date_end, $format, $outputlangs);
8376
}
8377
8378
/**
8379
 *    Format output for start and end date
8380
 *
8381
 *    @param	int			$date_start    		Start date
8382
 *    @param    int			$date_end      		End date
8383
 *    @param    string		$format        		Output date format ('day', 'dayhour', ...)
8384
 *    @param	Translate	$outputlangs   		Output language
8385
 *    @param	integer		$withparenthesis	1=Add parenthesis, 0=no parenthesis
8386
 *    @return	string							String
8387
 */
8388
function get_date_range($date_start, $date_end, $format = '', $outputlangs = '', $withparenthesis = 1)
8389
{
8390
	global $langs;
8391
8392
	$out = '';
8393
8394
	if (!is_object($outputlangs)) {
8395
		$outputlangs = $langs;
8396
	}
8397
8398
	if ($date_start && $date_end) {
8399
		$out .= ($withparenthesis ? ' (' : '').$outputlangs->transnoentitiesnoconv('DateFromTo', dol_print_date($date_start, $format, false, $outputlangs), dol_print_date($date_end, $format, false, $outputlangs)).($withparenthesis ? ')' : '');
8400
	}
8401
	if ($date_start && !$date_end) {
8402
		$out .= ($withparenthesis ? ' (' : '').$outputlangs->transnoentitiesnoconv('DateFrom', dol_print_date($date_start, $format, false, $outputlangs)).($withparenthesis ? ')' : '');
8403
	}
8404
	if (!$date_start && $date_end) {
8405
		$out .= ($withparenthesis ? ' (' : '').$outputlangs->transnoentitiesnoconv('DateUntil', dol_print_date($date_end, $format, false, $outputlangs)).($withparenthesis ? ')' : '');
8406
	}
8407
8408
	return $out;
8409
}
8410
8411
/**
8412
 * Return firstname and lastname in correct order
8413
 *
8414
 * @param	string	$firstname		Firstname
8415
 * @param	string	$lastname		Lastname
8416
 * @param	int		$nameorder		-1=Auto, 0=Lastname+Firstname, 1=Firstname+Lastname, 2=Firstname, 3=Firstname if defined else lastname, 4=Lastname, 5=Lastname if defined else firstname
8417
 * @return	string					Firstname + lastname or Lastname + firstname
8418
 */
8419
function dolGetFirstLastname($firstname, $lastname, $nameorder = -1)
8420
{
8421
	global $conf;
8422
8423
	$ret = '';
8424
	// If order not defined, we use the setup
8425
	if ($nameorder < 0) {
8426
		$nameorder = (empty($conf->global->MAIN_FIRSTNAME_NAME_POSITION) ? 1 : 0);
8427
	}
8428
	if ($nameorder == 1) {
8429
		$ret .= $firstname;
8430
		if ($firstname && $lastname) {
8431
			$ret .= ' ';
8432
		}
8433
		$ret .= $lastname;
8434
	} elseif ($nameorder == 2 || $nameorder == 3) {
8435
		$ret .= $firstname;
8436
		if (empty($ret) && $nameorder == 3) {
8437
			$ret .= $lastname;
8438
		}
8439
	} else {	// 0, 4 or 5
8440
		$ret .= $lastname;
8441
		if (empty($ret) && $nameorder == 5) {
8442
			$ret .= $firstname;
8443
		}
8444
		if ($nameorder == 0) {
8445
			if ($firstname && $lastname) {
8446
				$ret .= ' ';
8447
			}
8448
			$ret .= $firstname;
8449
		}
8450
	}
8451
	return $ret;
8452
}
8453
8454
8455
/**
8456
 *	Set event message in dol_events session object. Will be output by calling dol_htmloutput_events.
8457
 *  Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function.
8458
 *  Note: Prefer to use setEventMessages instead.
8459
 *
8460
 *	@param	string|string[] $mesgs			Message string or array
8461
 *  @param  string          $style      	Which style to use ('mesgs' by default, 'warnings', 'errors')
8462
 *  @return	void
8463
 *  @see	dol_htmloutput_events()
8464
 */
8465
function setEventMessage($mesgs, $style = 'mesgs')
8466
{
8467
	//dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);		This is not deprecated, it is used by setEventMessages function
8468
	if (!is_array($mesgs)) {
8469
		// If mesgs is a string
8470
		if ($mesgs) {
8471
			$_SESSION['dol_events'][$style][] = $mesgs;
8472
		}
8473
	} else {
8474
		// If mesgs is an array
8475
		foreach ($mesgs as $mesg) {
8476
			if ($mesg) {
8477
				$_SESSION['dol_events'][$style][] = $mesg;
8478
			}
8479
		}
8480
	}
8481
}
8482
8483
/**
8484
 *	Set event messages in dol_events session object. Will be output by calling dol_htmloutput_events.
8485
 *  Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function.
8486
 *
8487
 *	@param	string		$mesg			Message string
8488
 *	@param	array|null	$mesgs			Message array
8489
 *  @param  string		$style      	Which style to use ('mesgs' by default, 'warnings', 'errors')
8490
 *  @param	string		$messagekey		A key to be used to allow the feature "Never show this message again"
8491
 *  @return	void
8492
 *  @see	dol_htmloutput_events()
8493
 */
8494
function setEventMessages($mesg, $mesgs, $style = 'mesgs', $messagekey = '')
8495
{
8496
	if (empty($mesg) && empty($mesgs)) {
8497
		dol_syslog("Try to add a message in stack with empty message", LOG_WARNING);
8498
	} else {
8499
		if ($messagekey) {
8500
			// Complete message with a js link to set a cookie "DOLHIDEMESSAGE".$messagekey;
8501
			// TODO
8502
			$mesg .= '';
8503
		}
8504
		if (empty($messagekey) || empty($_COOKIE["DOLHIDEMESSAGE".$messagekey])) {
8505
			if (!in_array((string) $style, array('mesgs', 'warnings', 'errors'))) {
8506
				dol_print_error('', 'Bad parameter style='.$style.' for setEventMessages');
8507
			}
8508
			if (empty($mesgs)) {
8509
				setEventMessage($mesg, $style);
8510
			} else {
8511
				if (!empty($mesg) && !in_array($mesg, $mesgs)) {
8512
					setEventMessage($mesg, $style); // Add message string if not already into array
8513
				}
8514
				setEventMessage($mesgs, $style);
8515
			}
8516
		}
8517
	}
8518
}
8519
8520
/**
8521
 *	Print formated messages to output (Used to show messages on html output).
8522
 *  Note: Calling dol_htmloutput_events is done into pages by standard llxFooter() function, so there is
8523
 *  no need to call it explicitely.
8524
 *
8525
 *  @param	int		$disabledoutputofmessages	Clear all messages stored into session without diplaying them
8526
 *  @return	void
8527
 *  @see    									dol_htmloutput_mesg()
8528
 */
8529
function dol_htmloutput_events($disabledoutputofmessages = 0)
8530
{
8531
	// Show mesgs
8532
	if (isset($_SESSION['dol_events']['mesgs'])) {
8533
		if (empty($disabledoutputofmessages)) {
8534
			dol_htmloutput_mesg('', $_SESSION['dol_events']['mesgs']);
8535
		}
8536
		unset($_SESSION['dol_events']['mesgs']);
8537
	}
8538
	// Show errors
8539
	if (isset($_SESSION['dol_events']['errors'])) {
8540
		if (empty($disabledoutputofmessages)) {
8541
			dol_htmloutput_mesg('', $_SESSION['dol_events']['errors'], 'error');
8542
		}
8543
		unset($_SESSION['dol_events']['errors']);
8544
	}
8545
8546
	// Show warnings
8547
	if (isset($_SESSION['dol_events']['warnings'])) {
8548
		if (empty($disabledoutputofmessages)) {
8549
			dol_htmloutput_mesg('', $_SESSION['dol_events']['warnings'], 'warning');
8550
		}
8551
		unset($_SESSION['dol_events']['warnings']);
8552
	}
8553
}
8554
8555
/**
8556
 *	Get formated messages to output (Used to show messages on html output).
8557
 *  This include also the translation of the message key.
8558
 *
8559
 *	@param	string		$mesgstring		Message string or message key
8560
 *	@param	string[]	$mesgarray      Array of message strings or message keys
8561
 *  @param  string		$style          Style of message output ('ok' or 'error')
8562
 *  @param  int			$keepembedded   Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
8563
 *	@return	string						Return html output
8564
 *
8565
 *  @see    dol_print_error()
8566
 *  @see    dol_htmloutput_errors()
8567
 *  @see    setEventMessages()
8568
 */
8569
function get_htmloutput_mesg($mesgstring = '', $mesgarray = '', $style = 'ok', $keepembedded = 0)
8570
{
8571
	global $conf, $langs;
8572
8573
	$ret = 0;
8574
	$return = '';
8575
	$out = '';
8576
	$divstart = $divend = '';
8577
8578
	// If inline message with no format, we add it.
8579
	if ((empty($conf->use_javascript_ajax) || !empty($conf->global->MAIN_DISABLE_JQUERY_JNOTIFY) || $keepembedded) && !preg_match('/<div class=".*">/i', $out)) {
8580
		$divstart = '<div class="'.$style.' clearboth">';
8581
		$divend = '</div>';
8582
	}
8583
8584
	if ((is_array($mesgarray) && count($mesgarray)) || $mesgstring) {
8585
		$langs->load("errors");
8586
		$out .= $divstart;
8587
		if (is_array($mesgarray) && count($mesgarray)) {
8588
			foreach ($mesgarray as $message) {
8589
				$ret++;
8590
				$out .= $langs->trans($message);
8591
				if ($ret < count($mesgarray)) {
8592
					$out .= "<br>\n";
8593
				}
8594
			}
8595
		}
8596
		if ($mesgstring) {
8597
			$ret++;
8598
			$out .= $langs->trans($mesgstring);
8599
		}
8600
		$out .= $divend;
8601
	}
8602
8603
	if ($out) {
8604
		if (!empty($conf->use_javascript_ajax) && empty($conf->global->MAIN_DISABLE_JQUERY_JNOTIFY) && empty($keepembedded)) {
8605
			$return = '<script>
8606
					$(document).ready(function() {
8607
						var block = '.(!empty($conf->global->MAIN_USE_JQUERY_BLOCKUI) ? "true" : "false").'
8608
						if (block) {
8609
							$.dolEventValid("","'.dol_escape_js($out).'");
8610
						} else {
8611
							/* jnotify(message, preset of message type, keepmessage) */
8612
							$.jnotify("'.dol_escape_js($out).'",
8613
							"'.($style == "ok" ? 3000 : $style).'",
8614
							'.($style == "ok" ? "false" : "true").',
8615
							{ remove: function (){} } );
8616
						}
8617
					});
8618
				</script>';
8619
		} else {
8620
			$return = $out;
8621
		}
8622
	}
8623
8624
	return $return;
8625
}
8626
8627
/**
8628
 *  Get formated error messages to output (Used to show messages on html output).
8629
 *
8630
 *  @param  string	$mesgstring         Error message
8631
 *  @param  array	$mesgarray          Error messages array
8632
 *  @param  int		$keepembedded       Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
8633
 *  @return string                		Return html output
8634
 *
8635
 *  @see    dol_print_error()
8636
 *  @see    dol_htmloutput_mesg()
8637
 */
8638
function get_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0)
8639
{
8640
	return get_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
8641
}
8642
8643
/**
8644
 *	Print formated messages to output (Used to show messages on html output).
8645
 *
8646
 *	@param	string		$mesgstring		Message string or message key
8647
 *	@param	string[]	$mesgarray      Array of message strings or message keys
8648
 *	@param  string      $style          Which style to use ('ok', 'warning', 'error')
8649
 *	@param  int         $keepembedded   Set to 1 if message must be kept embedded into its html place (this disable jnotify)
8650
 *	@return	void
8651
 *
8652
 *	@see    dol_print_error()
8653
 *	@see    dol_htmloutput_errors()
8654
 *	@see    setEventMessages()
8655
 */
8656
function dol_htmloutput_mesg($mesgstring = '', $mesgarray = array(), $style = 'ok', $keepembedded = 0)
8657
{
8658
	if (empty($mesgstring) && (!is_array($mesgarray) || count($mesgarray) == 0)) {
8659
		return;
8660
	}
8661
8662
	$iserror = 0;
8663
	$iswarning = 0;
8664
	if (is_array($mesgarray)) {
8665
		foreach ($mesgarray as $val) {
8666
			if ($val && preg_match('/class="error"/i', $val)) {
8667
				$iserror++;
8668
				break;
8669
			}
8670
			if ($val && preg_match('/class="warning"/i', $val)) {
8671
				$iswarning++;
8672
				break;
8673
			}
8674
		}
8675
	} elseif ($mesgstring && preg_match('/class="error"/i', $mesgstring)) {
8676
		$iserror++;
8677
	} elseif ($mesgstring && preg_match('/class="warning"/i', $mesgstring)) {
8678
		$iswarning++;
8679
	}
8680
	if ($style == 'error') {
8681
		$iserror++;
8682
	}
8683
	if ($style == 'warning') {
8684
		$iswarning++;
8685
	}
8686
8687
	if ($iserror || $iswarning) {
8688
		// Remove div from texts
8689
		$mesgstring = preg_replace('/<\/div><div class="(error|warning)">/', '<br>', $mesgstring);
8690
		$mesgstring = preg_replace('/<div class="(error|warning)">/', '', $mesgstring);
8691
		$mesgstring = preg_replace('/<\/div>/', '', $mesgstring);
8692
		// Remove div from texts array
8693
		if (is_array($mesgarray)) {
8694
			$newmesgarray = array();
8695
			foreach ($mesgarray as $val) {
8696
				if (is_string($val)) {
8697
					$tmpmesgstring = preg_replace('/<\/div><div class="(error|warning)">/', '<br>', $val);
8698
					$tmpmesgstring = preg_replace('/<div class="(error|warning)">/', '', $tmpmesgstring);
8699
					$tmpmesgstring = preg_replace('/<\/div>/', '', $tmpmesgstring);
8700
					$newmesgarray[] = $tmpmesgstring;
8701
				} else {
8702
					dol_syslog("Error call of dol_htmloutput_mesg with an array with a value that is not a string", LOG_WARNING);
8703
				}
8704
			}
8705
			$mesgarray = $newmesgarray;
8706
		}
8707
		print get_htmloutput_mesg($mesgstring, $mesgarray, ($iserror ? 'error' : 'warning'), $keepembedded);
8708
	} else {
8709
		print get_htmloutput_mesg($mesgstring, $mesgarray, 'ok', $keepembedded);
8710
	}
8711
}
8712
8713
/**
8714
 *  Print formated error messages to output (Used to show messages on html output).
8715
 *
8716
 *  @param	string	$mesgstring          Error message
8717
 *  @param  array	$mesgarray           Error messages array
8718
 *  @param  int		$keepembedded        Set to 1 in error message must be kept embedded into its html place (this disable jnotify)
8719
 *  @return	void
8720
 *
8721
 *  @see    dol_print_error()
8722
 *  @see    dol_htmloutput_mesg()
8723
 */
8724
function dol_htmloutput_errors($mesgstring = '', $mesgarray = array(), $keepembedded = 0)
8725
{
8726
	dol_htmloutput_mesg($mesgstring, $mesgarray, 'error', $keepembedded);
8727
}
8728
8729
/**
8730
 * 	Advanced sort array by second index function, which produces ascending (default)
8731
 *  or descending output and uses optionally natural case insensitive sorting (which
8732
 *  can be optionally case sensitive as well).
8733
 *
8734
 *  @param      array		$array      		Array to sort (array of array('key1'=>val1,'key2'=>val2,'key3'...) or array of objects)
8735
 *  @param      string		$index				Key in array to use for sorting criteria
8736
 *  @param      int			$order				Sort order ('asc' or 'desc')
8737
 *  @param      int			$natsort			If values are strings (I said value not type): 0=Use alphabetical order, 1=use "natural" sort (natsort)
8738
 *   											If values are numeric (I said value not type): 0=Use numeric order (even if type is string) so use a "natural" sort, 1=use "natural" sort too (same than 0), -1=Force alphabetical order
8739
 *  @param      int			$case_sensitive		1=sort is case sensitive, 0=not case sensitive
8740
 *  @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.
8741
 *  @return     array							Sorted array
8742
 */
8743
function dol_sort_array(&$array, $index, $order = 'asc', $natsort = 0, $case_sensitive = 0, $keepindex = 0)
8744
{
8745
	// Clean parameters
8746
	$order = strtolower($order);
8747
8748
	if (is_array($array)) {
8749
		$sizearray = count($array);
8750
		if ($sizearray > 0) {
8751
			$temp = array();
8752
			foreach (array_keys($array) as $key) {
8753
				if (is_object($array[$key])) {
8754
					$temp[$key] = empty($array[$key]->$index) ? 0 : $array[$key]->$index;
8755
				} else {
8756
					$temp[$key] = empty($array[$key][$index]) ? 0 : $array[$key][$index];
8757
				}
8758
				if ($natsort == -1) {
8759
					$temp[$key] = '___'.$temp[$key];        // We add a string at begin of value to force an alpha order when using asort.
8760
				}
8761
			}
8762
8763
			if (empty($natsort) || $natsort == -1) {
8764
				if ($order == 'asc') {
8765
					asort($temp);
8766
				} else {
8767
					arsort($temp);
8768
				}
8769
			} else {
8770
				if ($case_sensitive) {
8771
					natsort($temp);
8772
				} else {
8773
					natcasesort($temp);	// natecasesort is not sensible to case
8774
				}
8775
				if ($order != 'asc') {
8776
					$temp = array_reverse($temp, true);
8777
				}
8778
			}
8779
8780
			$sorted = array();
8781
8782
			foreach (array_keys($temp) as $key) {
8783
				(is_numeric($key) && empty($keepindex)) ? $sorted[] = $array[$key] : $sorted[$key] = $array[$key];
8784
			}
8785
8786
			return $sorted;
8787
		}
8788
	}
8789
	return $array;
8790
}
8791
8792
8793
/**
8794
 *      Check if a string is in UTF8
8795
 *
8796
 *      @param	string	$str        String to check
8797
 * 		@return	boolean				True if string is UTF8 or ISO compatible with UTF8, False if not (ISO with special char or Binary)
8798
 */
8799
function utf8_check($str)
8800
{
8801
	$str = (string) $str;	// Sometimes string is an int.
8802
8803
	// We must use here a binary strlen function (so not dol_strlen)
8804
	$strLength = dol_strlen($str);
8805
	for ($i = 0; $i < $strLength; $i++) {
8806
		if (ord($str[$i]) < 0x80) {
8807
			continue; // 0bbbbbbb
8808
		} elseif ((ord($str[$i]) & 0xE0) == 0xC0) {
8809
			$n = 1; // 110bbbbb
8810
		} elseif ((ord($str[$i]) & 0xF0) == 0xE0) {
8811
			$n = 2; // 1110bbbb
8812
		} elseif ((ord($str[$i]) & 0xF8) == 0xF0) {
8813
			$n = 3; // 11110bbb
8814
		} elseif ((ord($str[$i]) & 0xFC) == 0xF8) {
8815
			$n = 4; // 111110bb
8816
		} elseif ((ord($str[$i]) & 0xFE) == 0xFC) {
8817
			$n = 5; // 1111110b
8818
		} else {
8819
			return false; // Does not match any model
8820
		}
8821
		for ($j = 0; $j < $n; $j++) { // n bytes matching 10bbbbbb follow ?
8822
			if ((++$i == strlen($str)) || ((ord($str[$i]) & 0xC0) != 0x80)) {
8823
				return false;
8824
			}
8825
		}
8826
	}
8827
	return true;
8828
}
8829
8830
/**
8831
 *      Check if a string is in ASCII
8832
 *
8833
 *      @param	string	$str        String to check
8834
 * 		@return	boolean				True if string is ASCII, False if not (byte value > 0x7F)
8835
 */
8836
function ascii_check($str)
8837
{
8838
	if (function_exists('mb_check_encoding')) {
8839
		//if (mb_detect_encoding($str, 'ASCII', true) return false;
8840
		if (!mb_check_encoding($str, 'ASCII')) {
8841
			return false;
8842
		}
8843
	} else {
8844
		if (preg_match('/[^\x00-\x7f]/', $str)) {
8845
			return false; // Contains a byte > 7f
8846
		}
8847
	}
8848
8849
	return true;
8850
}
8851
8852
8853
/**
8854
 *      Return a string encoded into OS filesystem encoding. This function is used to define
8855
 * 	    value to pass to filesystem PHP functions.
8856
 *
8857
 *      @param	string	$str        String to encode (UTF-8)
8858
 * 		@return	string				Encoded string (UTF-8, ISO-8859-1)
8859
 */
8860
function dol_osencode($str)
8861
{
8862
	global $conf;
8863
8864
	$tmp = ini_get("unicode.filesystem_encoding"); // Disponible avec PHP 6.0
8865
	if (empty($tmp) && !empty($_SERVER["WINDIR"])) {
8866
		$tmp = 'iso-8859-1'; // By default for windows
8867
	}
8868
	if (empty($tmp)) {
8869
		$tmp = 'utf-8'; // By default for other
8870
	}
8871
	if (!empty($conf->global->MAIN_FILESYSTEM_ENCODING)) {
8872
		$tmp = $conf->global->MAIN_FILESYSTEM_ENCODING;
8873
	}
8874
8875
	if ($tmp == 'iso-8859-1') {
8876
		return utf8_decode($str);
8877
	}
8878
	return $str;
8879
}
8880
8881
8882
/**
8883
 *      Return an id or code from a code or id.
8884
 *      Store also Code-Id into a cache to speed up next request on same key.
8885
 *
8886
 * 		@param	DoliDB	$db				Database handler
8887
 * 		@param	string	$key			Code or Id to get Id or Code
8888
 * 		@param	string	$tablename		Table name without prefix
8889
 * 		@param	string	$fieldkey		Field to search the key into
8890
 * 		@param	string	$fieldid		Field to get
8891
 *      @param  int		$entityfilter	Filter by entity
8892
 *      @param	string	$filters		Filters to add. WARNING: string must be escaped for SQL and not coming from user input.
8893
 *      @return int						<0 if KO, Id of code if OK
8894
 *      @see $langs->getLabelFromKey
8895
 */
8896
function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid = 'id', $entityfilter = 0, $filters = '')
8897
{
8898
	global $cache_codes;
8899
8900
	// If key empty
8901
	if ($key == '') {
8902
		return '';
8903
	}
8904
8905
	// Check in cache
8906
	if (isset($cache_codes[$tablename][$key][$fieldid])) {	// Can be defined to 0 or ''
8907
		return $cache_codes[$tablename][$key][$fieldid]; // Found in cache
8908
	}
8909
8910
	dol_syslog('dol_getIdFromCode (value for field '.$fieldid.' from key '.$key.' not found into cache)', LOG_DEBUG);
8911
8912
	$sql = "SELECT ".$fieldid." as valuetoget";
8913
	$sql .= " FROM ".MAIN_DB_PREFIX.$tablename;
8914
	$sql .= " WHERE ".$fieldkey." = '".$db->escape($key)."'";
8915
	if (!empty($entityfilter)) {
8916
		$sql .= " AND entity IN (".getEntity($tablename).")";
8917
	}
8918
	if ($filters) {
8919
		$sql .= $filters;
8920
	}
8921
8922
	$resql = $db->query($sql);
8923
	if ($resql) {
8924
		$obj = $db->fetch_object($resql);
8925
		if ($obj) {
8926
			$cache_codes[$tablename][$key][$fieldid] = $obj->valuetoget;
8927
		} else {
8928
			$cache_codes[$tablename][$key][$fieldid] = '';
8929
		}
8930
		$db->free($resql);
8931
		return $cache_codes[$tablename][$key][$fieldid];
8932
	} else {
8933
		return -1;
8934
	}
8935
}
8936
8937
/**
8938
 * Verify if condition in string is ok or not
8939
 *
8940
 * @param 	string		$strToEvaluate	String with condition to check
8941
 * @return 	boolean						True or False. Note: It returns also True if $strToEvaluate is ''. False if error
8942
 */
8943
function verifCond($strToEvaluate)
8944
{
8945
	global $user, $conf, $langs;
8946
	global $leftmenu;
8947
	global $rights; // To export to dol_eval function
8948
8949
	//print $strToEvaluate."<br>\n";
8950
	$rights = true;
8951
	if (isset($strToEvaluate) && $strToEvaluate !== '') {
8952
		//var_dump($strToEvaluate);
8953
		$rep = dol_eval($strToEvaluate, 1, 1, '1'); // The dol_eval must contains all the global $xxx for all variables $xxx found into the string condition
8954
		$rights = $rep && (!is_string($rep) || strpos($rep, 'Bad string syntax to evaluate') === false);
8955
		//var_dump($rights);
8956
	}
8957
	return $rights;
8958
}
8959
8960
/**
8961
 * Replace eval function to add more security.
8962
 * This function is called by verifCond() or trans() and transnoentitiesnoconv().
8963
 *
8964
 * @param 	string	$s					String to evaluate
8965
 * @param	int		$returnvalue		0=No return (used to execute eval($a=something)). 1=Value of eval is returned (used to eval($something)).
8966
 * @param   int     $hideerrors     	1=Hide errors
8967
 * @param	string	$onlysimplestring	'0' (used for computed property of extrafields)=Accept all chars, '1' (most common use)=Accept only simple string with char 'a-z0-9\s^$_+-.*>&|=!?():"\',/@';',  '2' (not used)=Accept also ';[]'
8968
 * @return	mixed						Nothing or return result of eval
8969
 */
8970
function dol_eval($s, $returnvalue = 0, $hideerrors = 1, $onlysimplestring = '1')
8971
{
8972
	// Only global variables can be changed by eval function and returned to caller
8973
	global $db, $langs, $user, $conf, $website, $websitepage;
8974
	global $action, $mainmenu, $leftmenu;
8975
	global $mysoc;
8976
	global $objectoffield;
8977
8978
	// Old variables used
8979
	global $rights;
8980
	global $object;
8981
	global $obj; // To get $obj used into list when dol_eval is used for computed fields and $obj is not yet $object
8982
	global $soc; // For backward compatibility
8983
8984
	try {
8985
		// Test on dangerous char (used for RCE), we allow only characters to make PHP variable testing
8986
		if ($onlysimplestring == '1') {
8987
			// We must accept: '1 && getDolGlobalInt("doesnotexist1") && $conf->global->MAIN_FEATURES_LEVEL'
8988
			// We must accept: '$conf->barcode->enabled || preg_match(\'/^AAA/\',$leftmenu)'
8989
			// We must accept: '$user->rights->cabinetmed->read && !$object->canvas=="patient@cabinetmed"'
8990
			if (preg_match('/[^a-z0-9\s'.preg_quote('^$_+-.*>&|=!?():"\',/@', '/').']/i', $s)) {
8991
				if ($returnvalue) {
8992
					return 'Bad string syntax to evaluate (found chars that are not chars for simplestring): '.$s;
8993
				} else {
8994
					dol_syslog('Bad string syntax to evaluate (found chars that are not chars for simplestring): '.$s);
8995
					return '';
8996
				}
8997
				// TODO
8998
				// We can exclude all parenthesis ( that are not '($db' and 'getDolGlobalInt(' and 'getDolGlobalString(' and 'preg_match(' and 'isModEnabled('
8999
				// ...
9000
			}
9001
		} elseif ($onlysimplestring == '2') {
9002
			// We must accept: (($reloadedobj = new Task($db)) && ($reloadedobj->fetchNoCompute($object->id) > 0) && ($secondloadedobj = new Project($db)) && ($secondloadedobj->fetchNoCompute($reloadedobj->fk_project) > 0)) ? $secondloadedobj->ref : "Parent project not found"
9003
			if (preg_match('/[^a-z0-9\s'.preg_quote('^$_+-.*>&|=!?():"\',/@;[]', '/').']/i', $s)) {
9004
				if ($returnvalue) {
9005
					return 'Bad string syntax to evaluate (found chars that are not chars for simplestring): '.$s;
9006
				} else {
9007
					dol_syslog('Bad string syntax to evaluate (found chars that are not chars for simplestring): '.$s);
9008
					return '';
9009
				}
9010
			}
9011
		}
9012
		if (strpos($s, '::') !== false) {
9013
			if ($returnvalue) {
9014
				return 'Bad string syntax to evaluate (double : char is forbidden): '.$s;
9015
			} else {
9016
				dol_syslog('Bad string syntax to evaluate (double : char is forbidden): '.$s);
9017
				return '';
9018
			}
9019
		}
9020
		if (strpos($s, '`') !== false) {
9021
			if ($returnvalue) {
9022
				return 'Bad string syntax to evaluate (backtick char is forbidden): '.$s;
9023
			} else {
9024
				dol_syslog('Bad string syntax to evaluate (backtick char is forbidden): '.$s);
9025
				return '';
9026
			}
9027
		}
9028
		if (preg_match('/[^0-9]+\.[^0-9]+/', $s)) {	// We refuse . if not between 2 numbers
9029
			if ($returnvalue) {
9030
				return 'Bad string syntax to evaluate (dot char is forbidden): '.$s;
9031
			} else {
9032
				dol_syslog('Bad string syntax to evaluate (dot char is forbidden): '.$s);
9033
				return '';
9034
			}
9035
		}
9036
9037
		// We block use of php exec or php file functions
9038
		$forbiddenphpstrings = array('$$');
9039
		$forbiddenphpstrings = array_merge($forbiddenphpstrings, array('_ENV', '_SESSION', '_COOKIE', '_GET', '_POST', '_REQUEST'));
9040
9041
		$forbiddenphpfunctions = array("exec", "passthru", "shell_exec", "system", "proc_open", "popen", "eval", "dol_eval", "executeCLI", "verifCond", "base64_decode");
9042
		$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("fopen", "file_put_contents", "fputs", "fputscsv", "fwrite", "fpassthru", "require", "include", "mkdir", "rmdir", "symlink", "touch", "unlink", "umask"));
9043
		$forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("function", "call_user_func"));
9044
9045
		$forbiddenphpregex = 'global\s+\$|\b('.implode('|', $forbiddenphpfunctions).')\b';
9046
9047
		do {
9048
			$oldstringtoclean = $s;
9049
			$s = str_ireplace($forbiddenphpstrings, '__forbiddenstring__', $s);
9050
			$s = preg_replace('/'.$forbiddenphpregex.'/i', '__forbiddenstring__', $s);
9051
			//$s = preg_replace('/\$[a-zA-Z0-9_\->\$]+\(/i', '', $s);	// Remove $function( call and $mycall->mymethod(
9052
		} while ($oldstringtoclean != $s);
9053
9054
		if (strpos($s, '__forbiddenstring__') !== false) {
9055
			dol_syslog('Bad string syntax to evaluate: '.$s, LOG_WARNING);
9056
			if ($returnvalue) {
9057
				return 'Bad string syntax to evaluate: '.$s;
9058
			} else {
9059
				dol_syslog('Bad string syntax to evaluate: '.$s);
9060
				return '';
9061
			}
9062
		}
9063
9064
		//print $s."<br>\n";
9065
		if ($returnvalue) {
9066
			if ($hideerrors) {
9067
				return @eval('return '.$s.';');
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
9068
			} else {
9069
				return eval('return '.$s.';');
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
9070
			}
9071
		} else {
9072
			if ($hideerrors) {
9073
				@eval($s);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
9074
			} else {
9075
				eval($s);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
9076
			}
9077
		}
9078
	} catch (Error $e) {
9079
		$error = 'Caught error : ';
9080
		$error .= $e->getMessage() . ', ';
9081
		$error .= 'Trace : ';
9082
		$error .= json_encode($e->getTrace());
9083
		error_log($error, 1);
9084
	}
9085
}
9086
9087
/**
9088
 * Return if var element is ok
9089
 *
9090
 * @param   string      $element    Variable to check
9091
 * @return  boolean                 Return true of variable is not empty
9092
 */
9093
function dol_validElement($element)
9094
{
9095
	return (trim($element) != '');
9096
}
9097
9098
/**
9099
 * 	Return img flag of country for a language code or country code.
9100
 *
9101
 * 	@param	string	$codelang	Language code ('en_IN', 'fr_CA', ...) or ISO Country code on 2 characters in uppercase ('IN', 'FR')
9102
 *  @param	string	$moreatt	Add more attribute on img tag (For example 'style="float: right"' or 'class="saturatemedium"')
9103
 *  @param	int		$notitlealt	No title alt
9104
 * 	@return	string				HTML img string with flag.
9105
 */
9106
function picto_from_langcode($codelang, $moreatt = '', $notitlealt = 0)
9107
{
9108
	if (empty($codelang)) {
9109
		return '';
9110
	}
9111
9112
	if ($codelang == 'auto') {
9113
		return '<span class="fa fa-language"></span>';
9114
	}
9115
9116
	$langtocountryflag = array(
9117
		'ar_AR' => '',
9118
		'ca_ES' => 'catalonia',
9119
		'da_DA' => 'dk',
9120
		'fr_CA' => 'mq',
9121
		'sv_SV' => 'se',
9122
		'sw_SW' => 'unknown',
9123
		'AQ' => 'unknown',
9124
		'CW' => 'unknown',
9125
		'IM' => 'unknown',
9126
		'JE' => 'unknown',
9127
		'MF' => 'unknown',
9128
		'BL' => 'unknown',
9129
		'SX' => 'unknown'
9130
	);
9131
9132
	if (isset($langtocountryflag[$codelang])) {
9133
		$flagImage = $langtocountryflag[$codelang];
9134
	} else {
9135
		$tmparray = explode('_', $codelang);
9136
		$flagImage = empty($tmparray[1]) ? $tmparray[0] : $tmparray[1];
9137
	}
9138
9139
	return img_picto_common($codelang, 'flags/'.strtolower($flagImage).'.png', $moreatt, 0, $notitlealt);
9140
}
9141
9142
/**
9143
 * Return default language from country code.
9144
 * Return null if not found.
9145
 *
9146
 * @param 	string 	$countrycode	Country code like 'US', 'FR', 'CA', 'ES', 'IN', 'MX', ...
9147
 * @return	string					Value of locale like 'en_US', 'fr_FR', ... or null if not found
9148
 */
9149
function getLanguageCodeFromCountryCode($countrycode)
9150
{
9151
	global $mysoc;
9152
9153
	if (empty($countrycode)) {
9154
		return null;
9155
	}
9156
9157
	if (strtoupper($countrycode) == 'MQ') {
9158
		return 'fr_CA';
9159
	}
9160
	if (strtoupper($countrycode) == 'SE') {
9161
		return 'sv_SE'; // se_SE is Sami/Sweden, and we want in priority sv_SE for SE country
9162
	}
9163
	if (strtoupper($countrycode) == 'CH') {
9164
		if ($mysoc->country_code == 'FR') {
9165
			return 'fr_CH';
9166
		}
9167
		if ($mysoc->country_code == 'DE') {
9168
			return 'de_CH';
9169
		}
9170
		if ($mysoc->country_code == 'IT') {
9171
			return 'it_CH';
9172
		}
9173
	}
9174
9175
	// Locale list taken from:
9176
	// http://stackoverflow.com/questions/3191664/
9177
	// list-of-all-locales-and-their-short-codes
9178
	$locales = array(
9179
		'af-ZA',
9180
		'am-ET',
9181
		'ar-AE',
9182
		'ar-BH',
9183
		'ar-DZ',
9184
		'ar-EG',
9185
		'ar-IQ',
9186
		'ar-JO',
9187
		'ar-KW',
9188
		'ar-LB',
9189
		'ar-LY',
9190
		'ar-MA',
9191
		'ar-OM',
9192
		'ar-QA',
9193
		'ar-SA',
9194
		'ar-SY',
9195
		'ar-TN',
9196
		'ar-YE',
9197
		//'as-IN',		// Moved after en-IN
9198
		'ba-RU',
9199
		'be-BY',
9200
		'bg-BG',
9201
		'bn-BD',
9202
		//'bn-IN',		// Moved after en-IN
9203
		'bo-CN',
9204
		'br-FR',
9205
		'ca-ES',
9206
		'co-FR',
9207
		'cs-CZ',
9208
		'cy-GB',
9209
		'da-DK',
9210
		'de-AT',
9211
		'de-CH',
9212
		'de-DE',
9213
		'de-LI',
9214
		'de-LU',
9215
		'dv-MV',
9216
		'el-GR',
9217
		'en-AU',
9218
		'en-BZ',
9219
		'en-CA',
9220
		'en-GB',
9221
		'en-IE',
9222
		'en-IN',
9223
		'as-IN',	// as-IN must be after en-IN (en in priority if country is IN)
9224
		'bn-IN',	// bn-IN must be after en-IN (en in priority if country is IN)
9225
		'en-JM',
9226
		'en-MY',
9227
		'en-NZ',
9228
		'en-PH',
9229
		'en-SG',
9230
		'en-TT',
9231
		'en-US',
9232
		'en-ZA',
9233
		'en-ZW',
9234
		'es-AR',
9235
		'es-BO',
9236
		'es-CL',
9237
		'es-CO',
9238
		'es-CR',
9239
		'es-DO',
9240
		'es-EC',
9241
		'es-ES',
9242
		'es-GT',
9243
		'es-HN',
9244
		'es-MX',
9245
		'es-NI',
9246
		'es-PA',
9247
		'es-PE',
9248
		'es-PR',
9249
		'es-PY',
9250
		'es-SV',
9251
		'es-US',
9252
		'es-UY',
9253
		'es-VE',
9254
		'et-EE',
9255
		'eu-ES',
9256
		'fa-IR',
9257
		'fi-FI',
9258
		'fo-FO',
9259
		'fr-BE',
9260
		'fr-CA',
9261
		'fr-CH',
9262
		'fr-FR',
9263
		'fr-LU',
9264
		'fr-MC',
9265
		'fy-NL',
9266
		'ga-IE',
9267
		'gd-GB',
9268
		'gl-ES',
9269
		'gu-IN',
9270
		'he-IL',
9271
		'hi-IN',
9272
		'hr-BA',
9273
		'hr-HR',
9274
		'hu-HU',
9275
		'hy-AM',
9276
		'id-ID',
9277
		'ig-NG',
9278
		'ii-CN',
9279
		'is-IS',
9280
		'it-CH',
9281
		'it-IT',
9282
		'ja-JP',
9283
		'ka-GE',
9284
		'kk-KZ',
9285
		'kl-GL',
9286
		'km-KH',
9287
		'kn-IN',
9288
		'ko-KR',
9289
		'ky-KG',
9290
		'lb-LU',
9291
		'lo-LA',
9292
		'lt-LT',
9293
		'lv-LV',
9294
		'mi-NZ',
9295
		'mk-MK',
9296
		'ml-IN',
9297
		'mn-MN',
9298
		'mr-IN',
9299
		'ms-BN',
9300
		'ms-MY',
9301
		'mt-MT',
9302
		'nb-NO',
9303
		'ne-NP',
9304
		'nl-BE',
9305
		'nl-NL',
9306
		'nn-NO',
9307
		'oc-FR',
9308
		'or-IN',
9309
		'pa-IN',
9310
		'pl-PL',
9311
		'ps-AF',
9312
		'pt-BR',
9313
		'pt-PT',
9314
		'rm-CH',
9315
		'ro-MD',
9316
		'ro-RO',
9317
		'ru-RU',
9318
		'rw-RW',
9319
		'sa-IN',
9320
		'se-FI',
9321
		'se-NO',
9322
		'se-SE',
9323
		'si-LK',
9324
		'sk-SK',
9325
		'sl-SI',
9326
		'sq-AL',
9327
		'sv-FI',
9328
		'sv-SE',
9329
		'sw-KE',
9330
		'ta-IN',
9331
		'te-IN',
9332
		'th-TH',
9333
		'tk-TM',
9334
		'tn-ZA',
9335
		'tr-TR',
9336
		'tt-RU',
9337
		'ug-CN',
9338
		'uk-UA',
9339
		'ur-PK',
9340
		'vi-VN',
9341
		'wo-SN',
9342
		'xh-ZA',
9343
		'yo-NG',
9344
		'zh-CN',
9345
		'zh-HK',
9346
		'zh-MO',
9347
		'zh-SG',
9348
		'zh-TW',
9349
		'zu-ZA',
9350
	);
9351
9352
	$buildprimarykeytotest = strtolower($countrycode).'-'.strtoupper($countrycode);
9353
	if (in_array($buildprimarykeytotest, $locales)) {
9354
		return strtolower($countrycode).'_'.strtoupper($countrycode);
9355
	}
9356
9357
	if (function_exists('locale_get_primary_language') && function_exists('locale_get_region')) {    // Need extension php-intl
9358
		foreach ($locales as $locale) {
9359
			$locale_language = locale_get_primary_language($locale);
9360
			$locale_region = locale_get_region($locale);
9361
			if (strtoupper($countrycode) == $locale_region) {
9362
				//var_dump($locale.' - '.$locale_language.' - '.$locale_region);
9363
				return strtolower($locale_language).'_'.strtoupper($locale_region);
9364
			}
9365
		}
9366
	} else {
9367
		dol_syslog("Warning Exention php-intl is not available", LOG_WARNING);
9368
	}
9369
9370
	return null;
9371
}
9372
9373
/**
9374
 *  Complete or removed entries into a head array (used to build tabs).
9375
 *  For example, with value added by external modules. Such values are declared into $conf->modules_parts['tab'].
9376
 *  Or by change using hook completeTabsHead
9377
 *
9378
 *  @param	Conf			$conf           Object conf
9379
 *  @param  Translate		$langs          Object langs
9380
 *  @param  object|null		$object         Object object
9381
 *  @param  array			$head          	Object head
9382
 *  @param  int				$h				New position to fill
9383
 *  @param  string			$type           Value for object where objectvalue can be
9384
 *                              			'thirdparty'       to add a tab in third party view
9385
 *		                        	      	'intervention'     to add a tab in intervention view
9386
 *     		                    	     	'supplier_order'   to add a tab in purchase order view
9387
 *          		            	        'supplier_invoice' to add a tab in purchase invoice view
9388
 *                  		    	        'invoice'          to add a tab in sales invoice view
9389
 *                          			    'order'            to add a tab in sales order view
9390
 *                          				'contract'		   to add a tabl in contract view
9391
 *                      			        'product'          to add a tab in product view
9392
 *                              			'propal'           to add a tab in propal view
9393
 *                              			'user'             to add a tab in user view
9394
 *                              			'group'            to add a tab in group view
9395
 * 		        	               	     	'member'           to add a tab in fundation member view
9396
 *      		                        	'categories_x'	   to add a tab in category view ('x': type of category (0=product, 1=supplier, 2=customer, 3=member)
9397
 *      									'ecm'			   to add a tab for another ecm view
9398
 *                                          'stock'            to add a tab for warehouse view
9399
 *  @param  string		$mode  	        	'add' to complete head, 'remove' to remove entries
9400
 *  @param	string		$filterorigmodule	Filter on module origin: 'external' will show only external modules. 'core' only core modules. No filter (default) will add both.
9401
 *	@return	void
9402
 */
9403
function complete_head_from_modules($conf, $langs, $object, &$head, &$h, $type, $mode = 'add', $filterorigmodule = '')
9404
{
9405
	global $hookmanager, $db;
9406
9407
	if (isset($conf->modules_parts['tabs'][$type]) && is_array($conf->modules_parts['tabs'][$type])) {
9408
		foreach ($conf->modules_parts['tabs'][$type] as $value) {
9409
			$values = explode(':', $value);
9410
9411
			$reg = array();
9412
			if ($mode == 'add' && !preg_match('/^\-/', $values[1])) {
9413
				if (count($values) == 6) {
9414
					// new declaration with permissions:
9415
					// $value='objecttype:+tabname1:Title1:langfile@mymodule:$user->rights->mymodule->read:/mymodule/mynewtab1.php?id=__ID__'
9416
					// $value='objecttype:+tabname1:Title1,class,pathfile,method:langfile@mymodule:$user->rights->mymodule->read:/mymodule/mynewtab1.php?id=__ID__'
9417
					if ($values[0] != $type) {
9418
						continue;
9419
					}
9420
9421
					if (verifCond($values[4])) {
9422
						if ($values[3]) {
9423
							if ($filterorigmodule) {	// If a filter of module origin has been requested
9424
								if (strpos($values[3], '@')) {	// This is an external module
9425
									if ($filterorigmodule != 'external') {
9426
										continue;
9427
									}
9428
								} else {	// This looks a core module
9429
									if ($filterorigmodule != 'core') {
9430
										continue;
9431
									}
9432
								}
9433
							}
9434
							$langs->load($values[3]);
9435
						}
9436
						if (preg_match('/SUBSTITUTION_([^_]+)/i', $values[2], $reg)) {
9437
							// If label is "SUBSTITUION_..."
9438
							$substitutionarray = array();
9439
							complete_substitutions_array($substitutionarray, $langs, $object, array('needforkey'=>$values[2]));
9440
							$label = make_substitutions($reg[1], $substitutionarray);
9441
						} else {
9442
							// If label is "Label,Class,File,Method", we call the method to show content inside the badge
9443
							$labeltemp = explode(',', $values[2]);
9444
							$label = $langs->trans($labeltemp[0]);
9445
9446
							if (!empty($labeltemp[1]) && is_object($object) && !empty($object->id)) {
9447
								dol_include_once($labeltemp[2]);
9448
								$classtoload = $labeltemp[1];
9449
								if (class_exists($classtoload)) {
9450
									$obj = new $classtoload($db);
9451
									$function = $labeltemp[3];
9452
									if ($obj && $function && method_exists($obj, $function)) {
9453
										$nbrec = $obj->$function($object->id, $obj);
9454
										$label .= '<span class="badge marginleftonlyshort">'.$nbrec.'</span>';
9455
									}
9456
								}
9457
							}
9458
						}
9459
9460
						$head[$h][0] = dol_buildpath(preg_replace('/__ID__/i', ((is_object($object) && !empty($object->id)) ? $object->id : ''), $values[5]), 1);
9461
						$head[$h][1] = $label;
9462
						$head[$h][2] = str_replace('+', '', $values[1]);
9463
						$h++;
9464
					}
9465
				} elseif (count($values) == 5) {       // case deprecated
9466
					dol_syslog('Passing 5 values in tabs module_parts is deprecated. Please update to 6 with permissions.', LOG_WARNING);
9467
9468
					if ($values[0] != $type) {
9469
						continue;
9470
					}
9471
					if ($values[3]) {
9472
						if ($filterorigmodule) {	// If a filter of module origin has been requested
9473
							if (strpos($values[3], '@')) {	// This is an external module
9474
								if ($filterorigmodule != 'external') {
9475
									continue;
9476
								}
9477
							} else {	// This looks a core module
9478
								if ($filterorigmodule != 'core') {
9479
									continue;
9480
								}
9481
							}
9482
						}
9483
						$langs->load($values[3]);
9484
					}
9485
					if (preg_match('/SUBSTITUTION_([^_]+)/i', $values[2], $reg)) {
9486
						$substitutionarray = array();
9487
						complete_substitutions_array($substitutionarray, $langs, $object, array('needforkey'=>$values[2]));
9488
						$label = make_substitutions($reg[1], $substitutionarray);
9489
					} else {
9490
						$label = $langs->trans($values[2]);
9491
					}
9492
9493
					$head[$h][0] = dol_buildpath(preg_replace('/__ID__/i', ((is_object($object) && !empty($object->id)) ? $object->id : ''), $values[4]), 1);
9494
					$head[$h][1] = $label;
9495
					$head[$h][2] = str_replace('+', '', $values[1]);
9496
					$h++;
9497
				}
9498
			} elseif ($mode == 'remove' && preg_match('/^\-/', $values[1])) {
9499
				if ($values[0] != $type) {
9500
					continue;
9501
				}
9502
				$tabname = str_replace('-', '', $values[1]);
9503
				foreach ($head as $key => $val) {
9504
					$condition = (!empty($values[3]) ? verifCond($values[3]) : 1);
9505
					//var_dump($key.' - '.$tabname.' - '.$head[$key][2].' - '.$values[3].' - '.$condition);
9506
					if ($head[$key][2] == $tabname && $condition) {
9507
						unset($head[$key]);
9508
						break;
9509
					}
9510
				}
9511
			}
9512
		}
9513
	}
9514
9515
	// No need to make a return $head. Var is modified as a reference
9516
	if (!empty($hookmanager)) {
9517
		$parameters = array('object' => $object, 'mode' => $mode, 'head' => &$head, 'filterorigmodule' => $filterorigmodule);
9518
		$reshook = $hookmanager->executeHooks('completeTabsHead', $parameters);
9519
		if ($reshook > 0) {		// Hook ask to replace completely the array
9520
			$head = $hookmanager->resArray;
9521
		} else {				// Hook
9522
			$head = array_merge($head, $hookmanager->resArray);
9523
		}
9524
		$h = count($head);
9525
	}
9526
}
9527
9528
/**
9529
 * Print common footer :
9530
 * 		conf->global->MAIN_HTML_FOOTER
9531
 *      js for switch of menu hider
9532
 * 		js for conf->global->MAIN_GOOGLE_AN_ID
9533
 * 		js for conf->global->MAIN_SHOW_TUNING_INFO or $_SERVER["MAIN_SHOW_TUNING_INFO"]
9534
 * 		js for conf->logbuffer
9535
 *
9536
 * @param	string	$zone	'private' (for private pages) or 'public' (for public pages)
9537
 * @return	void
9538
 */
9539
function printCommonFooter($zone = 'private')
9540
{
9541
	global $conf, $hookmanager, $user, $debugbar;
9542
	global $action;
9543
	global $micro_start_time;
9544
9545
	if ($zone == 'private') {
9546
		print "\n".'<!-- Common footer for private page -->'."\n";
9547
	} else {
9548
		print "\n".'<!-- Common footer for public page -->'."\n";
9549
	}
9550
9551
	// A div to store page_y POST parameter so we can read it using javascript
9552
	print "\n<!-- A div to store page_y POST parameter -->\n";
9553
	print '<div id="page_y" style="display: none;">'.(GETPOST('page_y') ? GETPOST('page_y') : '').'</div>'."\n";
9554
9555
	$parameters = array();
9556
	$reshook = $hookmanager->executeHooks('printCommonFooter', $parameters); // Note that $action and $object may have been modified by some hooks
9557
	if (empty($reshook)) {
9558
		if (!empty($conf->global->MAIN_HTML_FOOTER)) {
9559
			print $conf->global->MAIN_HTML_FOOTER."\n";
9560
		}
9561
9562
		print "\n";
9563
		if (!empty($conf->use_javascript_ajax)) {
9564
			print "\n<!-- A script section to add menuhider handler on backoffice, manage focus and madatory fields, tuning info, ... -->\n";
9565
			print '<script>'."\n";
9566
			print 'jQuery(document).ready(function() {'."\n";
9567
9568
			if ($zone == 'private' && empty($conf->dol_use_jmobile)) {
9569
				print "\n";
9570
				print '/* JS CODE TO ENABLE to manage handler to switch left menu page (menuhider) */'."\n";
9571
				print 'jQuery("li.menuhider").click(function(event) {';
9572
				print '  if (!$( "body" ).hasClass( "sidebar-collapse" )){ event.preventDefault(); }'."\n";
9573
				print '  console.log("We click on .menuhider");'."\n";
9574
				print '  $("body").toggleClass("sidebar-collapse")'."\n";
9575
				print '});'."\n";
9576
			}
9577
9578
			// Management of focus and mandatory for fields
9579
			if ($action == 'create' || $action == 'edit' || (empty($action) && (preg_match('/new\.php/', $_SERVER["PHP_SELF"])))) {
9580
				print '/* JS CODE TO ENABLE to manage focus and mandatory form fields */'."\n";
9581
				$relativepathstring = $_SERVER["PHP_SELF"];
9582
				// Clean $relativepathstring
9583
				if (constant('DOL_URL_ROOT')) {
9584
					$relativepathstring = preg_replace('/^'.preg_quote(constant('DOL_URL_ROOT'), '/').'/', '', $relativepathstring);
9585
				}
9586
				$relativepathstring = preg_replace('/^\//', '', $relativepathstring);
9587
				$relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
9588
				//$tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
9589
				if (!empty($user->default_values[$relativepathstring]['focus'])) {
9590
					foreach ($user->default_values[$relativepathstring]['focus'] as $defkey => $defval) {
9591
						$qualified = 0;
9592
						if ($defkey != '_noquery_') {
9593
							$tmpqueryarraytohave = explode('&', $defkey);
9594
							$foundintru = 0;
9595
							foreach ($tmpqueryarraytohave as $tmpquerytohave) {
9596
								$tmpquerytohaveparam = explode('=', $tmpquerytohave);
9597
								//print "console.log('".$tmpquerytohaveparam[0]." ".$tmpquerytohaveparam[1]." ".GETPOST($tmpquerytohaveparam[0])."');";
9598
								if (!GETPOSTISSET($tmpquerytohaveparam[0]) || ($tmpquerytohaveparam[1] != GETPOST($tmpquerytohaveparam[0]))) {
9599
									$foundintru = 1;
9600
								}
9601
							}
9602
							if (!$foundintru) {
9603
								$qualified = 1;
9604
							}
9605
							//var_dump($defkey.'-'.$qualified);
9606
						} else {
9607
							$qualified = 1;
9608
						}
9609
9610
						if ($qualified) {
9611
							foreach ($defval as $paramkey => $paramval) {
9612
								// Set focus on field
9613
								print 'jQuery("input[name=\''.$paramkey.'\']").focus();'."\n";
9614
								print 'jQuery("textarea[name=\''.$paramkey.'\']").focus();'."\n";
9615
								print 'jQuery("select[name=\''.$paramkey.'\']").focus();'."\n"; // Not really usefull, but we keep it in case of.
9616
							}
9617
						}
9618
					}
9619
				}
9620
				if (!empty($user->default_values[$relativepathstring]['mandatory'])) {
9621
					foreach ($user->default_values[$relativepathstring]['mandatory'] as $defkey => $defval) {
9622
						$qualified = 0;
9623
						if ($defkey != '_noquery_') {
9624
							$tmpqueryarraytohave = explode('&', $defkey);
9625
							$foundintru = 0;
9626
							foreach ($tmpqueryarraytohave as $tmpquerytohave) {
9627
								$tmpquerytohaveparam = explode('=', $tmpquerytohave);
9628
								//print "console.log('".$tmpquerytohaveparam[0]." ".$tmpquerytohaveparam[1]." ".GETPOST($tmpquerytohaveparam[0])."');";
9629
								if (!GETPOSTISSET($tmpquerytohaveparam[0]) || ($tmpquerytohaveparam[1] != GETPOST($tmpquerytohaveparam[0]))) {
9630
									$foundintru = 1;
9631
								}
9632
							}
9633
							if (!$foundintru) {
9634
								$qualified = 1;
9635
							}
9636
							//var_dump($defkey.'-'.$qualified);
9637
						} else {
9638
							$qualified = 1;
9639
						}
9640
9641
						if ($qualified) {
9642
							foreach ($defval as $paramkey => $paramval) {
9643
								// Add property 'required' on input
9644
								print 'jQuery("input[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";
9645
								print 'jQuery("textarea[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";
9646
								print '// required on a select works only if key is "", so we add the required attributes but also we reset the key -1 or 0 to an empty string'."\n";
9647
								print 'jQuery("select[name=\''.$paramkey.'\']").prop(\'required\',true);'."\n";
9648
								print 'jQuery("select[name=\''.$paramkey.'\'] option[value=\'-1\']").prop(\'value\', \'\');'."\n";
9649
								print 'jQuery("select[name=\''.$paramkey.'\'] option[value=\'0\']").prop(\'value\', \'\');'."\n";
9650
9651
								// Add 'field required' class on closest td for all input elements : input, textarea and select
9652
								print 'jQuery(":input[name=\'' . $paramkey . '\']").closest("tr").find("td:first").addClass("fieldrequired");' . "\n";
9653
							}
9654
						}
9655
					}
9656
				}
9657
			}
9658
9659
			print '});'."\n";
9660
9661
			// End of tuning
9662
			if (!empty($_SERVER['MAIN_SHOW_TUNING_INFO']) || !empty($conf->global->MAIN_SHOW_TUNING_INFO)) {
9663
				print "\n";
9664
				print "/* JS CODE TO ENABLE to add memory info */\n";
9665
				print 'window.console && console.log("';
9666
				if (!empty($conf->global->MEMCACHED_SERVER)) {
9667
					print 'MEMCACHED_SERVER='.$conf->global->MEMCACHED_SERVER.' - ';
9668
				}
9669
				print 'MAIN_OPTIMIZE_SPEED='.(isset($conf->global->MAIN_OPTIMIZE_SPEED) ? $conf->global->MAIN_OPTIMIZE_SPEED : 'off');
9670
				if (!empty($micro_start_time)) {   // Works only if MAIN_SHOW_TUNING_INFO is defined at $_SERVER level. Not in global variable.
9671
					$micro_end_time = microtime(true);
9672
					print ' - Build time: '.ceil(1000 * ($micro_end_time - $micro_start_time)).' ms';
9673
				}
9674
9675
				if (function_exists("memory_get_usage")) {
9676
					print ' - Mem: '.memory_get_usage(); // Do not use true here, it seems it takes the peak amount
9677
				}
9678
				if (function_exists("memory_get_peak_usage")) {
9679
					print ' - Real mem peak: '.memory_get_peak_usage(true);
9680
				}
9681
				if (function_exists("zend_loader_file_encoded")) {
9682
					print ' - Zend encoded file: '.(zend_loader_file_encoded() ? 'yes' : 'no');
9683
				}
9684
				print '");'."\n";
9685
			}
9686
9687
			print "\n".'</script>'."\n";
9688
9689
			// Google Analytics
9690
			// TODO Add a hook here
9691
			if (!empty($conf->google->enabled) && !empty($conf->global->MAIN_GOOGLE_AN_ID)) {
9692
				$tmptagarray = explode(',', $conf->global->MAIN_GOOGLE_AN_ID);
9693
				foreach ($tmptagarray as $tmptag) {
9694
					print "\n";
9695
					print "<!-- JS CODE TO ENABLE for google analtics tag -->\n";
9696
					print "
9697
					<!-- Global site tag (gtag.js) - Google Analytics -->
9698
					<script async src=\"https://www.googletagmanager.com/gtag/js?id=".trim($tmptag)."\"></script>
9699
					<script>
9700
					window.dataLayer = window.dataLayer || [];
9701
					function gtag(){dataLayer.push(arguments);}
9702
					gtag('js', new Date());
9703
9704
					gtag('config', '".trim($tmptag)."');
9705
					</script>";
9706
					print "\n";
9707
				}
9708
			}
9709
		}
9710
9711
		// Add Xdebug coverage of code
9712
		if (defined('XDEBUGCOVERAGE')) {
9713
			print_r(xdebug_get_code_coverage());
9714
		}
9715
9716
		// Add DebugBar data
9717
		if (!empty($user->rights->debugbar->read) && is_object($debugbar)) {
9718
			$debugbar['time']->stopMeasure('pageaftermaster');
9719
			print '<!-- Output debugbar data -->'."\n";
9720
			$renderer = $debugbar->getRenderer();
9721
			print $debugbar->getRenderer()->render();
9722
		} elseif (count($conf->logbuffer)) {    // If there is some logs in buffer to show
9723
			print "\n";
9724
			print "<!-- Start of log output\n";
9725
			//print '<div class="hidden">'."\n";
9726
			foreach ($conf->logbuffer as $logline) {
9727
				print $logline."<br>\n";
9728
			}
9729
			//print '</div>'."\n";
9730
			print "End of log output -->\n";
9731
		}
9732
	}
9733
}
9734
9735
/**
9736
 * Split a string with 2 keys into key array.
9737
 * For example: "A=1;B=2;C=2" is exploded into array('A'=>1,'B'=>2,'C'=>3)
9738
 *
9739
 * @param 	string	$string		String to explode
9740
 * @param 	string	$delimiter	Delimiter between each couple of data
9741
 * @param 	string	$kv			Delimiter between key and value
9742
 * @return	array				Array of data exploded
9743
 */
9744
function dolExplodeIntoArray($string, $delimiter = ';', $kv = '=')
9745
{
9746
	if ($a = explode($delimiter, $string)) {
9747
		$ka = array();
9748
		foreach ($a as $s) { // each part
9749
			if ($s) {
9750
				if ($pos = strpos($s, $kv)) { // key/value delimiter
9751
					$ka[trim(substr($s, 0, $pos))] = trim(substr($s, $pos + strlen($kv)));
9752
				} else { // key delimiter not found
9753
					$ka[] = trim($s);
9754
				}
9755
			}
9756
		}
9757
		return $ka;
9758
	}
9759
	return array();
9760
}
9761
9762
9763
/**
9764
 * Set focus onto field with selector (similar behaviour of 'autofocus' HTML5 tag)
9765
 *
9766
 * @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.
9767
 * @return	void
9768
 */
9769
function dol_set_focus($selector)
9770
{
9771
	print "\n".'<!-- Set focus onto a specific field -->'."\n";
9772
	print '<script>jQuery(document).ready(function() { jQuery("'.dol_escape_js($selector).'").focus(); });</script>'."\n";
9773
}
9774
9775
9776
/**
9777
 * Return getmypid() or random PID when function is disabled
9778
 * Some web hosts disable this php function for security reasons
9779
 * and sometimes we can't redeclare function.
9780
 *
9781
 * @return	int
9782
 */
9783
function dol_getmypid()
9784
{
9785
	if (!function_exists('getmypid')) {
9786
		return mt_rand(99900000, 99965535);
9787
	} else {
9788
		return getmypid();	// May be a number on 64 bits (depending on OS)
9789
	}
9790
}
9791
9792
9793
/**
9794
 * Generate natural SQL search string for a criteria (this criteria can be tested on one or several fields)
9795
 *
9796
 * @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")
9797
 * @param   string 			$value 		The value to look for.
9798
 *                          		    If param $mode is 0, can contains several keywords separated with a space or |
9799
 *                                      like "keyword1 keyword2" = We want record field like keyword1 AND field like keyword2
9800
 *                                      or like "keyword1|keyword2" = We want record field like keyword1 OR field like keyword2
9801
 *                             			If param $mode is 1, can contains an operator <, > or = like "<10" or ">=100.5 < 1000"
9802
 *                             			If param $mode is 2, can contains a list of int id separated by comma like "1,3,4"
9803
 *                             			If param $mode is 3, can contains a list of string separated by comma like "a,b,c"
9804
 * @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')
9805
 * 										3=value is list of string separated with comma (Example 'text 1,text 2'), 4=value is a list of ID separated with comma (Example '2,7') to be used to search into a multiselect string '1,2,3,4'
9806
 * @param	integer			$nofirstand	1=Do not output the first 'AND'
9807
 * @return 	string 			$res 		The statement to append to the SQL query
9808
 * @see dolSqlDateFilter()
9809
 */
9810
function natural_search($fields, $value, $mode = 0, $nofirstand = 0)
9811
{
9812
	global $db, $langs;
9813
9814
	$value = trim($value);
9815
9816
	if ($mode == 0) {
9817
		$value = preg_replace('/\*/', '%', $value); // Replace * with %
9818
	}
9819
	if ($mode == 1) {
9820
		$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
9821
	}
9822
9823
	$value = preg_replace('/\s*\|\s*/', '|', $value);
9824
9825
	$crits = explode(' ', $value);
9826
	$res = '';
9827
	if (!is_array($fields)) {
9828
		$fields = array($fields);
9829
	}
9830
9831
	$j = 0;
9832
	foreach ($crits as $crit) {
9833
		$crit = trim($crit);
9834
		$i = 0;
9835
		$i2 = 0;
9836
		$newres = '';
9837
		foreach ($fields as $field) {
9838
			if ($mode == 1) {
9839
				$operator = '=';
9840
				$newcrit = preg_replace('/([!<>=]+)/', '', $crit);
9841
9842
				$reg = array();
9843
				preg_match('/([!<>=]+)/', $crit, $reg);
9844
				if (!empty($reg[1])) {
9845
					$operator = $reg[1];
9846
				}
9847
				if ($newcrit != '') {
9848
					$numnewcrit = price2num($newcrit);
9849
					if (is_numeric($numnewcrit)) {
9850
						$newres .= ($i2 > 0 ? ' OR ' : '').$field.' '.$operator.' '.((float) $numnewcrit); // should be a numeric
9851
					} else {
9852
						$newres .= ($i2 > 0 ? ' OR ' : '').'1 = 2'; // force false
9853
					}
9854
					$i2++; // a criteria was added to string
9855
				}
9856
			} elseif ($mode == 2 || $mode == -2) {
9857
				$crit = preg_replace('/[^0-9,]/', '', $crit); // ID are always integer
9858
				$newres .= ($i2 > 0 ? ' OR ' : '').$field." ".($mode == -2 ? 'NOT ' : '');
9859
				$newres .= $crit ? "IN (".$db->sanitize($db->escape($crit)).")" : "IN (0)";
9860
				if ($mode == -2) {
9861
					$newres .= ' OR '.$field.' IS NULL';
9862
				}
9863
				$i2++; // a criteria was added to string
9864
			} elseif ($mode == 3 || $mode == -3) {
9865
				$tmparray = explode(',', $crit);
9866
				if (count($tmparray)) {
9867
					$listofcodes = '';
9868
					foreach ($tmparray as $val) {
9869
						$val = trim($val);
9870
						if ($val) {
9871
							$listofcodes .= ($listofcodes ? ',' : '');
9872
							$listofcodes .= "'".$db->escape($val)."'";
9873
						}
9874
					}
9875
					$newres .= ($i2 > 0 ? ' OR ' : '').$field." ".($mode == -3 ? 'NOT ' : '')."IN (".$db->sanitize($listofcodes, 1).")";
9876
					$i2++; // a criteria was added to string
9877
				}
9878
				if ($mode == -3) {
9879
					$newres .= ' OR '.$field.' IS NULL';
9880
				}
9881
			} elseif ($mode == 4) {
9882
				$tmparray = explode(',', $crit);
9883
				if (count($tmparray)) {
9884
					$listofcodes = '';
9885
					foreach ($tmparray as $val) {
9886
						$val = trim($val);
9887
						if ($val) {
9888
							$newres .= ($i2 > 0 ? " OR (" : "(").$field." LIKE '".$db->escape($val).",%'";
9889
							$newres .= ' OR '.$field." = '".$db->escape($val)."'";
9890
							$newres .= ' OR '.$field." LIKE '%,".$db->escape($val)."'";
9891
							$newres .= ' OR '.$field." LIKE '%,".$db->escape($val).",%'";
9892
							$newres .= ')';
9893
							$i2++;
9894
						}
9895
					}
9896
				}
9897
			} else // $mode=0
9898
			{
9899
				$tmpcrits = explode('|', $crit);
9900
				$i3 = 0;
9901
				foreach ($tmpcrits as $tmpcrit) {
9902
					if ($tmpcrit !== '0' && empty($tmpcrit)) {
9903
						continue;
9904
					}
9905
9906
					$newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : '');
9907
9908
					if (preg_match('/\.(id|rowid)$/', $field)) {	// Special case for rowid that is sometimes a ref so used as a search field
9909
						$newres .= $field." = ".(is_numeric(trim($tmpcrit)) ? ((float) trim($tmpcrit)) : '0');
9910
					} else {
9911
						$tmpcrit = trim($tmpcrit);
9912
						$tmpcrit2 = $tmpcrit;
9913
						$tmpbefore = '%';
9914
						$tmpafter = '%';
9915
						if (preg_match('/^!/', $tmpcrit)) {
9916
							$newres .= $field." NOT LIKE '"; // ! as exclude character
9917
							$tmpcrit2 = preg_replace('/^!/', '', $tmpcrit2);
9918
						} else $newres .= $field." LIKE '";
9919
9920
						if (preg_match('/^[\^\$]/', $tmpcrit)) {
9921
							$tmpbefore = '';
9922
							$tmpcrit2 = preg_replace('/^[\^\$]/', '', $tmpcrit2);
9923
						}
9924
						if (preg_match('/[\^\$]$/', $tmpcrit)) {
9925
							$tmpafter = '';
9926
							$tmpcrit2 = preg_replace('/[\^\$]$/', '', $tmpcrit2);
9927
						}
9928
						$newres .= $tmpbefore;
9929
						$newres .= $db->escape($tmpcrit2);
9930
						$newres .= $tmpafter;
9931
						$newres .= "'";
9932
						if ($tmpcrit2 == '') {
9933
							$newres .= " OR ".$field." IS NULL";
9934
						}
9935
					}
9936
9937
					$i3++;
9938
				}
9939
				$i2++; // a criteria was added to string
9940
			}
9941
			$i++;
9942
		}
9943
		if ($newres) {
9944
			$res = $res.($res ? ' AND ' : '').($i2 > 1 ? '(' : '').$newres.($i2 > 1 ? ')' : '');
9945
		}
9946
		$j++;
9947
	}
9948
	$res = ($nofirstand ? "" : " AND ")."(".$res.")";
9949
	//print 'xx'.$res.'yy';
9950
	return $res;
9951
}
9952
9953
/**
9954
 * Return string with full Url. The file qualified is the one defined by relative path in $object->last_main_doc
9955
 *
9956
 * @param   Object	$object				Object
9957
 * @return	string						Url string
9958
 */
9959
function showDirectDownloadLink($object)
9960
{
9961
	global $conf, $langs;
9962
9963
	$out = '';
9964
	$url = $object->getLastMainDocLink($object->element);
9965
9966
	$out .= img_picto($langs->trans("PublicDownloadLinkDesc"), 'globe').' <span class="opacitymedium">'.$langs->trans("DirectDownloadLink").'</span><br>';
9967
	if ($url) {
9968
		$out .= '<div class="urllink"><input type="text" id="directdownloadlink" class="quatrevingtpercent" value="'.$url.'"></div>';
9969
		$out .= ajax_autoselect("directdownloadlink", 0);
9970
	} else {
9971
		$out .= '<div class="urllink">'.$langs->trans("FileNotShared").'</div>';
9972
	}
9973
9974
	return $out;
9975
}
9976
9977
/**
9978
 * Return the filename of file to get the thumbs
9979
 *
9980
 * @param   string  $file           Original filename (full or relative path)
9981
 * @param   string  $extName        Extension to differenciate thumb file name ('', '_small', '_mini')
9982
 * @param   string  $extImgTarget   Force image extension for thumbs. Use '' to keep same extension than original image (default).
9983
 * @return  string                  New file name (full or relative path, including the thumbs/). May be the original path if no thumb can exists.
9984
 */
9985
function getImageFileNameForSize($file, $extName, $extImgTarget = '')
9986
{
9987
	$dirName = dirname($file);
9988
	if ($dirName == '.') {
9989
		$dirName = '';
9990
	}
9991
9992
	$fileName = preg_replace('/(\.gif|\.jpeg|\.jpg|\.png|\.bmp|\.webp)$/i', '', $file); // We remove extension, whatever is its case
9993
	$fileName = basename($fileName);
9994
9995
	if (empty($extImgTarget)) {
9996
		$extImgTarget = (preg_match('/\.jpg$/i', $file) ? '.jpg' : '');
9997
	}
9998
	if (empty($extImgTarget)) {
9999
		$extImgTarget = (preg_match('/\.jpeg$/i', $file) ? '.jpeg' : '');
10000
	}
10001
	if (empty($extImgTarget)) {
10002
		$extImgTarget = (preg_match('/\.gif$/i', $file) ? '.gif' : '');
10003
	}
10004
	if (empty($extImgTarget)) {
10005
		$extImgTarget = (preg_match('/\.png$/i', $file) ? '.png' : '');
10006
	}
10007
	if (empty($extImgTarget)) {
10008
		$extImgTarget = (preg_match('/\.bmp$/i', $file) ? '.bmp' : '');
10009
	}
10010
	if (empty($extImgTarget)) {
10011
		$extImgTarget = (preg_match('/\.webp$/i', $file) ? '.webp' : '');
10012
	}
10013
10014
	if (!$extImgTarget) {
10015
		return $file;
10016
	}
10017
10018
	$subdir = '';
10019
	if ($extName) {
10020
		$subdir = 'thumbs/';
10021
	}
10022
10023
	return ($dirName ? $dirName.'/' : '').$subdir.$fileName.$extName.$extImgTarget; // New filename for thumb
10024
}
10025
10026
10027
/**
10028
 * Return URL we can use for advanced preview links
10029
 *
10030
 * @param   string    $modulepart     propal, facture, facture_fourn, ...
10031
 * @param   string    $relativepath   Relative path of docs.
10032
 * @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)
10033
 * @param	string	  $param		  More param on http links
10034
 * @return  string|array              Output string with href link or array with all components of link
10035
 */
10036
function getAdvancedPreviewUrl($modulepart, $relativepath, $alldata = 0, $param = '')
10037
{
10038
	global $conf, $langs;
10039
10040
	if (empty($conf->use_javascript_ajax)) {
10041
		return '';
10042
	}
10043
10044
	$isAllowedForPreview = dolIsAllowedForPreview($relativepath);
10045
10046
	if ($alldata == 1) {
10047
		if ($isAllowedForPreview) {
10048
			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));
10049
		} else {
10050
			return array();
10051
		}
10052
	}
10053
10054
	// old behavior, return a string
10055
	if ($isAllowedForPreview) {
10056
		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')).'\')';
10057
	} else {
10058
		return '';
10059
	}
10060
}
10061
10062
10063
/**
10064
 * Make content of an input box selected when we click into input field.
10065
 *
10066
 * @param string	$htmlname		Id of html object ('#idvalue' or '.classvalue')
10067
 * @param string	$addlink		Add a 'link to' after
10068
 * @param string	$textonlink		Text to show on link or 'image'
10069
 * @return string
10070
 */
10071
function ajax_autoselect($htmlname, $addlink = '', $textonlink = 'Link')
10072
{
10073
	global $langs;
10074
	$out = '<script>
10075
               jQuery(document).ready(function () {
10076
				    jQuery("'.((strpos($htmlname, '.') === 0 ? '' : '#').$htmlname).'").click(function() { jQuery(this).select(); } );
10077
				});
10078
		    </script>';
10079
	if ($addlink) {
10080
		if ($textonlink === 'image') {
10081
			$out .= ' <a href="'.$addlink.'" target="_blank" rel="noopener noreferrer">'.img_picto('', 'globe').'</a>';
10082
		} else {
10083
			$out .= ' <a href="'.$addlink.'" target="_blank" rel="noopener noreferrer">'.$langs->trans("Link").'</a>';
10084
		}
10085
	}
10086
	return $out;
10087
}
10088
10089
/**
10090
 *	Return if a file is qualified for preview
10091
 *
10092
 *	@param	string	$file		Filename we looking for information
10093
 *	@return int					1 If allowed, 0 otherwise
10094
 *  @see    dol_mimetype(), image_format_supported() from images.lib.php
10095
 */
10096
function dolIsAllowedForPreview($file)
10097
{
10098
	global $conf;
10099
10100
	// Check .noexe extension in filename
10101
	if (preg_match('/\.noexe$/i', $file)) {
10102
		return 0;
10103
	}
10104
10105
	// Check mime types
10106
	$mime_preview = array('bmp', 'jpeg', 'png', 'gif', 'tiff', 'pdf', 'plain', 'css', 'webp');
10107
	if (!empty($conf->global->MAIN_ALLOW_SVG_FILES_AS_IMAGES)) {
10108
		$mime_preview[] = 'svg+xml';
10109
	}
10110
	//$mime_preview[]='vnd.oasis.opendocument.presentation';
10111
	//$mime_preview[]='archive';
10112
	$num_mime = array_search(dol_mimetype($file, '', 1), $mime_preview);
10113
	if ($num_mime !== false) {
10114
		return 1;
10115
	}
10116
10117
	// By default, not allowed for preview
10118
	return 0;
10119
}
10120
10121
10122
/**
10123
 *	Return MIME type of a file from its name with extension.
10124
 *
10125
 *	@param	string	$file		Filename we looking for MIME type
10126
 *  @param  string	$default    Default mime type if extension not found in known list
10127
 * 	@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
10128
 *	@return string 		    	Return a mime type family (text/xxx, application/xxx, image/xxx, audio, video, archive)
10129
 *  @see    dolIsAllowedForPreview(), image_format_supported() from images.lib.php
10130
 */
10131
function dol_mimetype($file, $default = 'application/octet-stream', $mode = 0)
10132
{
10133
	$mime = $default;
10134
	$imgmime = 'other.png';
10135
	$famime = 'file-o';
10136
	$srclang = '';
10137
10138
	$tmpfile = preg_replace('/\.noexe$/', '', $file);
10139
10140
	// Plain text files
10141
	if (preg_match('/\.txt$/i', $tmpfile)) {
10142
		$mime = 'text/plain';
10143
		$imgmime = 'text.png';
10144
		$famime = 'file-text-o';
10145
	}
10146
	if (preg_match('/\.rtx$/i', $tmpfile)) {
10147
		$mime = 'text/richtext';
10148
		$imgmime = 'text.png';
10149
		$famime = 'file-text-o';
10150
	}
10151
	if (preg_match('/\.csv$/i', $tmpfile)) {
10152
		$mime = 'text/csv';
10153
		$imgmime = 'text.png';
10154
		$famime = 'file-text-o';
10155
	}
10156
	if (preg_match('/\.tsv$/i', $tmpfile)) {
10157
		$mime = 'text/tab-separated-values';
10158
		$imgmime = 'text.png';
10159
		$famime = 'file-text-o';
10160
	}
10161
	if (preg_match('/\.(cf|conf|log)$/i', $tmpfile)) {
10162
		$mime = 'text/plain';
10163
		$imgmime = 'text.png';
10164
		$famime = 'file-text-o';
10165
	}
10166
	if (preg_match('/\.ini$/i', $tmpfile)) {
10167
		$mime = 'text/plain';
10168
		$imgmime = 'text.png';
10169
		$srclang = 'ini';
10170
		$famime = 'file-text-o';
10171
	}
10172
	if (preg_match('/\.md$/i', $tmpfile)) {
10173
		$mime = 'text/plain';
10174
		$imgmime = 'text.png';
10175
		$srclang = 'md';
10176
		$famime = 'file-text-o';
10177
	}
10178
	if (preg_match('/\.css$/i', $tmpfile)) {
10179
		$mime = 'text/css';
10180
		$imgmime = 'css.png';
10181
		$srclang = 'css';
10182
		$famime = 'file-text-o';
10183
	}
10184
	if (preg_match('/\.lang$/i', $tmpfile)) {
10185
		$mime = 'text/plain';
10186
		$imgmime = 'text.png';
10187
		$srclang = 'lang';
10188
		$famime = 'file-text-o';
10189
	}
10190
	// Certificate files
10191
	if (preg_match('/\.(crt|cer|key|pub)$/i', $tmpfile)) {
10192
		$mime = 'text/plain';
10193
		$imgmime = 'text.png';
10194
		$famime = 'file-text-o';
10195
	}
10196
	// XML based (HTML/XML/XAML)
10197
	if (preg_match('/\.(html|htm|shtml)$/i', $tmpfile)) {
10198
		$mime = 'text/html';
10199
		$imgmime = 'html.png';
10200
		$srclang = 'html';
10201
		$famime = 'file-text-o';
10202
	}
10203
	if (preg_match('/\.(xml|xhtml)$/i', $tmpfile)) {
10204
		$mime = 'text/xml';
10205
		$imgmime = 'other.png';
10206
		$srclang = 'xml';
10207
		$famime = 'file-text-o';
10208
	}
10209
	if (preg_match('/\.xaml$/i', $tmpfile)) {
10210
		$mime = 'text/xml';
10211
		$imgmime = 'other.png';
10212
		$srclang = 'xaml';
10213
		$famime = 'file-text-o';
10214
	}
10215
	// Languages
10216
	if (preg_match('/\.bas$/i', $tmpfile)) {
10217
		$mime = 'text/plain';
10218
		$imgmime = 'text.png';
10219
		$srclang = 'bas';
10220
		$famime = 'file-code-o';
10221
	}
10222
	if (preg_match('/\.(c)$/i', $tmpfile)) {
10223
		$mime = 'text/plain';
10224
		$imgmime = 'text.png';
10225
		$srclang = 'c';
10226
		$famime = 'file-code-o';
10227
	}
10228
	if (preg_match('/\.(cpp)$/i', $tmpfile)) {
10229
		$mime = 'text/plain';
10230
		$imgmime = 'text.png';
10231
		$srclang = 'cpp';
10232
		$famime = 'file-code-o';
10233
	}
10234
	if (preg_match('/\.cs$/i', $tmpfile)) {
10235
		$mime = 'text/plain';
10236
		$imgmime = 'text.png';
10237
		$srclang = 'cs';
10238
		$famime = 'file-code-o';
10239
	}
10240
	if (preg_match('/\.(h)$/i', $tmpfile)) {
10241
		$mime = 'text/plain';
10242
		$imgmime = 'text.png';
10243
		$srclang = 'h';
10244
		$famime = 'file-code-o';
10245
	}
10246
	if (preg_match('/\.(java|jsp)$/i', $tmpfile)) {
10247
		$mime = 'text/plain';
10248
		$imgmime = 'text.png';
10249
		$srclang = 'java';
10250
		$famime = 'file-code-o';
10251
	}
10252
	if (preg_match('/\.php([0-9]{1})?$/i', $tmpfile)) {
10253
		$mime = 'text/plain';
10254
		$imgmime = 'php.png';
10255
		$srclang = 'php';
10256
		$famime = 'file-code-o';
10257
	}
10258
	if (preg_match('/\.phtml$/i', $tmpfile)) {
10259
		$mime = 'text/plain';
10260
		$imgmime = 'php.png';
10261
		$srclang = 'php';
10262
		$famime = 'file-code-o';
10263
	}
10264
	if (preg_match('/\.(pl|pm)$/i', $tmpfile)) {
10265
		$mime = 'text/plain';
10266
		$imgmime = 'pl.png';
10267
		$srclang = 'perl';
10268
		$famime = 'file-code-o';
10269
	}
10270
	if (preg_match('/\.sql$/i', $tmpfile)) {
10271
		$mime = 'text/plain';
10272
		$imgmime = 'text.png';
10273
		$srclang = 'sql';
10274
		$famime = 'file-code-o';
10275
	}
10276
	if (preg_match('/\.js$/i', $tmpfile)) {
10277
		$mime = 'text/x-javascript';
10278
		$imgmime = 'jscript.png';
10279
		$srclang = 'js';
10280
		$famime = 'file-code-o';
10281
	}
10282
	// Open office
10283
	if (preg_match('/\.odp$/i', $tmpfile)) {
10284
		$mime = 'application/vnd.oasis.opendocument.presentation';
10285
		$imgmime = 'ooffice.png';
10286
		$famime = 'file-powerpoint-o';
10287
	}
10288
	if (preg_match('/\.ods$/i', $tmpfile)) {
10289
		$mime = 'application/vnd.oasis.opendocument.spreadsheet';
10290
		$imgmime = 'ooffice.png';
10291
		$famime = 'file-excel-o';
10292
	}
10293
	if (preg_match('/\.odt$/i', $tmpfile)) {
10294
		$mime = 'application/vnd.oasis.opendocument.text';
10295
		$imgmime = 'ooffice.png';
10296
		$famime = 'file-word-o';
10297
	}
10298
	// MS Office
10299
	if (preg_match('/\.mdb$/i', $tmpfile)) {
10300
		$mime = 'application/msaccess';
10301
		$imgmime = 'mdb.png';
10302
		$famime = 'file-o';
10303
	}
10304
	if (preg_match('/\.doc[xm]?$/i', $tmpfile)) {
10305
		$mime = 'application/msword';
10306
		$imgmime = 'doc.png';
10307
		$famime = 'file-word-o';
10308
	}
10309
	if (preg_match('/\.dot[xm]?$/i', $tmpfile)) {
10310
		$mime = 'application/msword';
10311
		$imgmime = 'doc.png';
10312
		$famime = 'file-word-o';
10313
	}
10314
	if (preg_match('/\.xlt(x)?$/i', $tmpfile)) {
10315
		$mime = 'application/vnd.ms-excel';
10316
		$imgmime = 'xls.png';
10317
		$famime = 'file-excel-o';
10318
	}
10319
	if (preg_match('/\.xla(m)?$/i', $tmpfile)) {
10320
		$mime = 'application/vnd.ms-excel';
10321
		$imgmime = 'xls.png';
10322
		$famime = 'file-excel-o';
10323
	}
10324
	if (preg_match('/\.xls$/i', $tmpfile)) {
10325
		$mime = 'application/vnd.ms-excel';
10326
		$imgmime = 'xls.png';
10327
		$famime = 'file-excel-o';
10328
	}
10329
	if (preg_match('/\.xls[bmx]$/i', $tmpfile)) {
10330
		$mime = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
10331
		$imgmime = 'xls.png';
10332
		$famime = 'file-excel-o';
10333
	}
10334
	if (preg_match('/\.pps[mx]?$/i', $tmpfile)) {
10335
		$mime = 'application/vnd.ms-powerpoint';
10336
		$imgmime = 'ppt.png';
10337
		$famime = 'file-powerpoint-o';
10338
	}
10339
	if (preg_match('/\.ppt[mx]?$/i', $tmpfile)) {
10340
		$mime = 'application/x-mspowerpoint';
10341
		$imgmime = 'ppt.png';
10342
		$famime = 'file-powerpoint-o';
10343
	}
10344
	// Other
10345
	if (preg_match('/\.pdf$/i', $tmpfile)) {
10346
		$mime = 'application/pdf';
10347
		$imgmime = 'pdf.png';
10348
		$famime = 'file-pdf-o';
10349
	}
10350
	// Scripts
10351
	if (preg_match('/\.bat$/i', $tmpfile)) {
10352
		$mime = 'text/x-bat';
10353
		$imgmime = 'script.png';
10354
		$srclang = 'dos';
10355
		$famime = 'file-code-o';
10356
	}
10357
	if (preg_match('/\.sh$/i', $tmpfile)) {
10358
		$mime = 'text/x-sh';
10359
		$imgmime = 'script.png';
10360
		$srclang = 'bash';
10361
		$famime = 'file-code-o';
10362
	}
10363
	if (preg_match('/\.ksh$/i', $tmpfile)) {
10364
		$mime = 'text/x-ksh';
10365
		$imgmime = 'script.png';
10366
		$srclang = 'bash';
10367
		$famime = 'file-code-o';
10368
	}
10369
	if (preg_match('/\.bash$/i', $tmpfile)) {
10370
		$mime = 'text/x-bash';
10371
		$imgmime = 'script.png';
10372
		$srclang = 'bash';
10373
		$famime = 'file-code-o';
10374
	}
10375
	// Images
10376
	if (preg_match('/\.ico$/i', $tmpfile)) {
10377
		$mime = 'image/x-icon';
10378
		$imgmime = 'image.png';
10379
		$famime = 'file-image-o';
10380
	}
10381
	if (preg_match('/\.(jpg|jpeg)$/i', $tmpfile)) {
10382
		$mime = 'image/jpeg';
10383
		$imgmime = 'image.png';
10384
		$famime = 'file-image-o';
10385
	}
10386
	if (preg_match('/\.png$/i', $tmpfile)) {
10387
		$mime = 'image/png';
10388
		$imgmime = 'image.png';
10389
		$famime = 'file-image-o';
10390
	}
10391
	if (preg_match('/\.gif$/i', $tmpfile)) {
10392
		$mime = 'image/gif';
10393
		$imgmime = 'image.png';
10394
		$famime = 'file-image-o';
10395
	}
10396
	if (preg_match('/\.bmp$/i', $tmpfile)) {
10397
		$mime = 'image/bmp';
10398
		$imgmime = 'image.png';
10399
		$famime = 'file-image-o';
10400
	}
10401
	if (preg_match('/\.(tif|tiff)$/i', $tmpfile)) {
10402
		$mime = 'image/tiff';
10403
		$imgmime = 'image.png';
10404
		$famime = 'file-image-o';
10405
	}
10406
	if (preg_match('/\.svg$/i', $tmpfile)) {
10407
		$mime = 'image/svg+xml';
10408
		$imgmime = 'image.png';
10409
		$famime = 'file-image-o';
10410
	}
10411
	if (preg_match('/\.webp$/i', $tmpfile)) {
10412
		$mime = 'image/webp';
10413
		$imgmime = 'image.png';
10414
		$famime = 'file-image-o';
10415
	}
10416
	// Calendar
10417
	if (preg_match('/\.vcs$/i', $tmpfile)) {
10418
		$mime = 'text/calendar';
10419
		$imgmime = 'other.png';
10420
		$famime = 'file-text-o';
10421
	}
10422
	if (preg_match('/\.ics$/i', $tmpfile)) {
10423
		$mime = 'text/calendar';
10424
		$imgmime = 'other.png';
10425
		$famime = 'file-text-o';
10426
	}
10427
	// Other
10428
	if (preg_match('/\.torrent$/i', $tmpfile)) {
10429
		$mime = 'application/x-bittorrent';
10430
		$imgmime = 'other.png';
10431
		$famime = 'file-o';
10432
	}
10433
	// Audio
10434
	if (preg_match('/\.(mp3|ogg|au|wav|wma|mid)$/i', $tmpfile)) {
10435
		$mime = 'audio';
10436
		$imgmime = 'audio.png';
10437
		$famime = 'file-audio-o';
10438
	}
10439
	// Video
10440
	if (preg_match('/\.mp4$/i', $tmpfile)) {
10441
		$mime = 'video/mp4';
10442
		$imgmime = 'video.png';
10443
		$famime = 'file-video-o';
10444
	}
10445
	if (preg_match('/\.ogv$/i', $tmpfile)) {
10446
		$mime = 'video/ogg';
10447
		$imgmime = 'video.png';
10448
		$famime = 'file-video-o';
10449
	}
10450
	if (preg_match('/\.webm$/i', $tmpfile)) {
10451
		$mime = 'video/webm';
10452
		$imgmime = 'video.png';
10453
		$famime = 'file-video-o';
10454
	}
10455
	if (preg_match('/\.avi$/i', $tmpfile)) {
10456
		$mime = 'video/x-msvideo';
10457
		$imgmime = 'video.png';
10458
		$famime = 'file-video-o';
10459
	}
10460
	if (preg_match('/\.divx$/i', $tmpfile)) {
10461
		$mime = 'video/divx';
10462
		$imgmime = 'video.png';
10463
		$famime = 'file-video-o';
10464
	}
10465
	if (preg_match('/\.xvid$/i', $tmpfile)) {
10466
		$mime = 'video/xvid';
10467
		$imgmime = 'video.png';
10468
		$famime = 'file-video-o';
10469
	}
10470
	if (preg_match('/\.(wmv|mpg|mpeg)$/i', $tmpfile)) {
10471
		$mime = 'video';
10472
		$imgmime = 'video.png';
10473
		$famime = 'file-video-o';
10474
	}
10475
	// Archive
10476
	if (preg_match('/\.(zip|rar|gz|tgz|z|cab|bz2|7z|tar|lzh|zst)$/i', $tmpfile)) {
10477
		$mime = 'archive';
10478
		$imgmime = 'archive.png';
10479
		$famime = 'file-archive-o';
10480
	}    // application/xxx where zzz is zip, ...
10481
	// Exe
10482
	if (preg_match('/\.(exe|com)$/i', $tmpfile)) {
10483
		$mime = 'application/octet-stream';
10484
		$imgmime = 'other.png';
10485
		$famime = 'file-o';
10486
	}
10487
	// Lib
10488
	if (preg_match('/\.(dll|lib|o|so|a)$/i', $tmpfile)) {
10489
		$mime = 'library';
10490
		$imgmime = 'library.png';
10491
		$famime = 'file-o';
10492
	}
10493
	// Err
10494
	if (preg_match('/\.err$/i', $tmpfile)) {
10495
		$mime = 'error';
10496
		$imgmime = 'error.png';
10497
		$famime = 'file-text-o';
10498
	}
10499
10500
	// Return string
10501
	if ($mode == 1) {
10502
		$tmp = explode('/', $mime);
10503
		return (!empty($tmp[1]) ? $tmp[1] : $tmp[0]);
10504
	}
10505
	if ($mode == 2) {
10506
		return $imgmime;
10507
	}
10508
	if ($mode == 3) {
10509
		return $srclang;
10510
	}
10511
	if ($mode == 4) {
10512
		return $famime;
10513
	}
10514
	return $mime;
10515
}
10516
10517
/**
10518
 * Return the value of a filed into a dictionary for the record $id.
10519
 * This also set all the values into a cache for a next search.
10520
 *
10521
 * @param string	$tablename		Name of table dictionary (without the MAIN_DB_PREFIX, example: 'c_holiday_types')
10522
 * @param string	$field			The name of field where to find the value to return
10523
 * @param int		$id				Id of line record
10524
 * @param bool		$checkentity	Add filter on entity
10525
 * @param string	$rowidfield		Name of the column rowid (to use for the filter on $id)
10526
 * @return string					The value of field $field. This also set $dictvalues cache.
10527
 */
10528
function getDictionaryValue($tablename, $field, $id, $checkentity = false, $rowidfield = 'rowid')
10529
{
10530
	global $conf, $db;
10531
10532
	$tablename = preg_replace('/^'.preg_quote(MAIN_DB_PREFIX, '/').'/', '', $tablename);	// Clean name of table for backward compatibility.
10533
10534
	$dictvalues = (isset($conf->cache['dictvalues_'.$tablename]) ? $conf->cache['dictvalues_'.$tablename] : null);
10535
10536
	if (is_null($dictvalues)) {
10537
		$dictvalues = array();
10538
10539
		$sql = "SELECT * FROM ".MAIN_DB_PREFIX.$tablename." WHERE 1 = 1"; // Here select * is allowed as it is generic code and we don't have list of fields
10540
		if ($checkentity) {
10541
			$sql .= ' AND entity IN (0,'.getEntity($tablename).')';
10542
		}
10543
10544
		$resql = $db->query($sql);
10545
		if ($resql) {
10546
			while ($obj = $db->fetch_object($resql)) {
10547
				$dictvalues[$obj->{$rowidfield}] = $obj;	// $obj is stdClass
10548
			}
10549
		} else {
10550
			dol_print_error($db);
10551
		}
10552
10553
		$conf->cache['dictvalues_'.$tablename] = $dictvalues;
10554
	}
10555
10556
	if (!empty($dictvalues[$id])) {
10557
		// Found
10558
		$tmp = $dictvalues[$id];
10559
		return (property_exists($tmp, $field) ? $tmp->$field : '');
10560
	} else {
10561
		// Not found
10562
		return '';
10563
	}
10564
}
10565
10566
/**
10567
 *	Return true if the color is light
10568
 *
10569
 *  @param	string	$stringcolor		String with hex (FFFFFF) or comma RGB ('255,255,255')
10570
 *  @return	int							-1 : Error with argument passed |0 : color is dark | 1 : color is light
10571
 */
10572
function colorIsLight($stringcolor)
10573
{
10574
	$stringcolor = str_replace('#', '', $stringcolor);
10575
	$res = -1;
10576
	if (!empty($stringcolor)) {
10577
		$res = 0;
10578
		$tmp = explode(',', $stringcolor);
10579
		if (count($tmp) > 1) {   // This is a comma RGB ('255','255','255')
10580
			$r = $tmp[0];
10581
			$g = $tmp[1];
10582
			$b = $tmp[2];
10583
		} else {
10584
			$hexr = $stringcolor[0].$stringcolor[1];
10585
			$hexg = $stringcolor[2].$stringcolor[3];
10586
			$hexb = $stringcolor[4].$stringcolor[5];
10587
			$r = hexdec($hexr);
10588
			$g = hexdec($hexg);
10589
			$b = hexdec($hexb);
10590
		}
10591
		$bright = (max($r, $g, $b) + min($r, $g, $b)) / 510.0; // HSL algorithm
10592
		if ($bright > 0.6) {
10593
			$res = 1;
10594
		}
10595
	}
10596
	return $res;
10597
}
10598
10599
/**
10600
 * Function to test if an entry is enabled or not
10601
 *
10602
 * @param	string		$type_user					0=We test for internal user, 1=We test for external user
10603
 * @param	array		$menuentry					Array for feature entry to test
10604
 * @param	array		$listofmodulesforexternal	Array with list of modules allowed to external users
10605
 * @return	int										0=Hide, 1=Show, 2=Show gray
10606
 */
10607
function isVisibleToUserType($type_user, &$menuentry, &$listofmodulesforexternal)
10608
{
10609
	global $conf;
10610
10611
	//print 'type_user='.$type_user.' module='.$menuentry['module'].' enabled='.$menuentry['enabled'].' perms='.$menuentry['perms'];
10612
	//print 'ok='.in_array($menuentry['module'], $listofmodulesforexternal);
10613
	if (empty($menuentry['enabled'])) {
10614
		return 0; // Entry disabled by condition
10615
	}
10616
	if ($type_user && $menuentry['module']) {
10617
		$tmploops = explode('|', $menuentry['module']);
10618
		$found = 0;
10619
		foreach ($tmploops as $tmploop) {
10620
			if (in_array($tmploop, $listofmodulesforexternal)) {
10621
				$found++;
10622
				break;
10623
			}
10624
		}
10625
		if (!$found) {
10626
			return 0; // Entry is for menus all excluded to external users
10627
		}
10628
	}
10629
	if (!$menuentry['perms'] && $type_user) {
10630
		return 0; // No permissions and user is external
10631
	}
10632
	if (!$menuentry['perms'] && !empty($conf->global->MAIN_MENU_HIDE_UNAUTHORIZED)) {
10633
		return 0; // No permissions and option to hide when not allowed, even for internal user, is on
10634
	}
10635
	if (!$menuentry['perms']) {
10636
		return 2; // No permissions and user is external
10637
	}
10638
	return 1;
10639
}
10640
10641
/**
10642
 * Round to next multiple.
10643
 *
10644
 * @param 	double		$n		Number to round up
10645
 * @param 	integer		$x		Multiple. For example 60 to round up to nearest exact minute for a date with seconds.
10646
 * @return 	integer				Value rounded.
10647
 */
10648
function roundUpToNextMultiple($n, $x = 5)
10649
{
10650
	return (ceil($n) % $x === 0) ? ceil($n) : round(($n + $x / 2) / $x) * $x;
10651
}
10652
10653
/**
10654
 * Function dolGetBadge
10655
 *
10656
 * @param   string  $label      label of badge no html : use in alt attribute for accessibility
10657
 * @param   string  $html       optional : label of badge with html
10658
 * @param   string  $type       type of badge : Primary Secondary Success Danger Warning Info Light Dark status0 status1 status2 status3 status4 status5 status6 status7 status8 status9
10659
 * @param   string  $mode       default '' , 'pill', 'dot'
10660
 * @param   string  $url        the url for link
10661
 * @param   array   $params     various params for future : recommended rather than adding more fuction arguments. array('attr'=>array('title'=>'abc'))
10662
 * @return  string              Html badge
10663
 */
10664
function dolGetBadge($label, $html = '', $type = 'primary', $mode = '', $url = '', $params = array())
10665
{
10666
	$attr = array(
10667
		'class'=>'badge '.(!empty($mode) ? ' badge-'.$mode : '').(!empty($type) ? ' badge-'.$type : '').(empty($params['css']) ? '' : ' '.$params['css'])
10668
	);
10669
10670
	if (empty($html)) {
10671
		$html = $label;
10672
	}
10673
10674
	if (!empty($url)) {
10675
		$attr['href'] = $url;
10676
	}
10677
10678
	if ($mode === 'dot') {
10679
		$attr['class'] .= ' classfortooltip';
10680
		$attr['title'] = $html;
10681
		$attr['aria-label'] = $label;
10682
		$html = '';
10683
	}
10684
10685
	// Override attr
10686
	if (!empty($params['attr']) && is_array($params['attr'])) {
10687
		foreach ($params['attr'] as $key => $value) {
10688
			if ($key == 'class') {
10689
				$attr['class'] .= ' '.$value;
10690
			} elseif ($key == 'classOverride') {
10691
				$attr['class'] = $value;
10692
			} else {
10693
				$attr[$key] = $value;
10694
			}
10695
		}
10696
	}
10697
10698
	// TODO: add hook
10699
10700
	// escape all attribute
10701
	$attr = array_map('dol_escape_htmltag', $attr);
10702
10703
	$TCompiledAttr = array();
10704
	foreach ($attr as $key => $value) {
10705
		$TCompiledAttr[] = $key.'="'.$value.'"';
10706
	}
10707
10708
	$compiledAttributes = !empty($TCompiledAttr) ?implode(' ', $TCompiledAttr) : '';
10709
10710
	$tag = !empty($url) ? 'a' : 'span';
10711
10712
	return '<'.$tag.' '.$compiledAttributes.'>'.$html.'</'.$tag.'>';
10713
}
10714
10715
10716
/**
10717
 * Output the badge of a status.
10718
 *
10719
 * @param   string  $statusLabel       Label of badge no html : use in alt attribute for accessibility
10720
 * @param   string  $statusLabelShort  Short label of badge no html
10721
 * @param   string  $html              Optional : label of badge with html
10722
 * @param   string  $statusType        status0 status1 status2 status3 status4 status5 status6 status7 status8 status9 : image name or badge name
10723
 * @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
10724
 * @param   string  $url               The url for link
10725
 * @param   array   $params            Various params. Example: array('tooltip'=>'no|...', 'badgeParams'=>...)
10726
 * @return  string                     Html status string
10727
 */
10728
function dolGetStatus($statusLabel = '', $statusLabelShort = '', $html = '', $statusType = 'status0', $displayMode = 0, $url = '', $params = array())
10729
{
10730
	global $conf;
10731
10732
	$return = '';
10733
	$dolGetBadgeParams = array();
10734
10735
	if (!empty($params['badgeParams'])) {
10736
		$dolGetBadgeParams = $params['badgeParams'];
10737
	}
10738
10739
	// TODO : add a hook
10740
	if ($displayMode == 0) {
10741
		$return = !empty($html) ? $html : (empty($conf->dol_optimize_smallscreen) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort));
10742
	} elseif ($displayMode == 1) {
10743
		$return = !empty($html) ? $html : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort);
10744
	} elseif (!empty($conf->global->MAIN_STATUS_USES_IMAGES)) {
10745
		// Use status with images (for backward compatibility)
10746
		$return = '';
10747
		$htmlLabel      = (in_array($displayMode, array(1, 2, 5)) ? '<span class="hideonsmartphone">' : '').(!empty($html) ? $html : $statusLabel).(in_array($displayMode, array(1, 2, 5)) ? '</span>' : '');
10748
		$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>' : '');
10749
10750
		// For small screen, we always use the short label instead of long label.
10751
		if (!empty($conf->dol_optimize_smallscreen)) {
10752
			if ($displayMode == 0) {
10753
				$displayMode = 1;
10754
			} elseif ($displayMode == 4) {
10755
				$displayMode = 2;
10756
			} elseif ($displayMode == 6) {
10757
				$displayMode = 5;
10758
			}
10759
		}
10760
10761
		// For backward compatibility. Image's filename are still in French, so we use this array to convert
10762
		$statusImg = array(
10763
			'status0' => 'statut0',
10764
			'status1' => 'statut1',
10765
			'status2' => 'statut2',
10766
			'status3' => 'statut3',
10767
			'status4' => 'statut4',
10768
			'status5' => 'statut5',
10769
			'status6' => 'statut6',
10770
			'status7' => 'statut7',
10771
			'status8' => 'statut8',
10772
			'status9' => 'statut9'
10773
		);
10774
10775
		if (!empty($statusImg[$statusType])) {
10776
			$htmlImg = img_picto($statusLabel, $statusImg[$statusType]);
10777
		} else {
10778
			$htmlImg = img_picto($statusLabel, $statusType);
10779
		}
10780
10781
		if ($displayMode === 2) {
10782
			$return = $htmlImg.' '.$htmlLabelShort;
10783
		} elseif ($displayMode === 3) {
10784
			$return = $htmlImg;
10785
		} elseif ($displayMode === 4) {
10786
			$return = $htmlImg.' '.$htmlLabel;
10787
		} elseif ($displayMode === 5) {
10788
			$return = $htmlLabelShort.' '.$htmlImg;
10789
		} else { // $displayMode >= 6
10790
			$return = $htmlLabel.' '.$htmlImg;
10791
		}
10792
	} elseif (empty($conf->global->MAIN_STATUS_USES_IMAGES) && !empty($displayMode)) {
10793
		// Use new badge
10794
		$statusLabelShort = (empty($statusLabelShort) ? $statusLabel : $statusLabelShort);
10795
10796
		$dolGetBadgeParams['attr']['class'] = 'badge-status';
10797
		$dolGetBadgeParams['attr']['title'] = empty($params['tooltip']) ? $statusLabel : ($params['tooltip'] != 'no' ? $params['tooltip'] : '');
10798
10799
		if ($displayMode == 3) {
10800
			$return = dolGetBadge((empty($conf->dol_optimize_smallscreen) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort)), '', $statusType, 'dot', $url, $dolGetBadgeParams);
10801
		} elseif ($displayMode === 5) {
10802
			$return = dolGetBadge($statusLabelShort, $html, $statusType, '', $url, $dolGetBadgeParams);
10803
		} else {
10804
			$return = dolGetBadge((empty($conf->dol_optimize_smallscreen) ? $statusLabel : (empty($statusLabelShort) ? $statusLabel : $statusLabelShort)), $html, $statusType, '', $url, $dolGetBadgeParams);
10805
		}
10806
	}
10807
10808
	return $return;
10809
}
10810
10811
10812
/**
10813
 * Function dolGetButtonAction
10814
 *
10815
 * @param string    	$label      Label or tooltip of button. Also used as tooltip in title attribute. Can be escaped HTML content or full simple text.
10816
 * @param string    	$text       Optional : short label on button. Can be escaped HTML content or full simple text.
10817
 * @param string    	$actionType 'default', 'delete', 'danger'
10818
 * @param string    	$url        Url for link
10819
 * @param string    	$id         Attribute id of button
10820
 * @param int|boolean	$userRight  User action right
10821
 * // phpcs:disable
10822
 * @param array 		$params = [ // Various params for future : recommended rather than adding more function arguments
10823
 *                              'attr' => [ // to add or override button attributes
10824
 *                              'xxxxx' => '', // your xxxxx attribute you want
10825
 *                              'class' => 'reposition', // to add more css class to the button class attribute
10826
 *                              'classOverride' => '' // to replace class attribute of the button
10827
 *                              ],
10828
 *                              'confirm' => [
10829
 *                              'url' => 'http://', // Overide Url to go when user click on action btn, if empty default url is $url.?confirm=yes, for no js compatibility use $url for fallback confirm.
10830
 *                              'title' => '', // Overide title of modal,  if empty default title use "ConfirmBtnCommonTitle" lang key
10831
 *                              'action-btn-label' => '', // Overide label of action button,  if empty default label use "Confirm" lang key
10832
 *                              'cancel-btn-label' => '', // Overide label of cancel button,  if empty default label use "CloseDialog" lang key
10833
 *                              'content' => '', // Overide text of content,  if empty default content use "ConfirmBtnCommonContent" lang key
10834
 *                              'modal' => true, // true|false to display dialog as a modal (with dark background)
10835
 *                              'isDropDrown' => false, // true|false to display dialog as a dropdown (with dark background)
10836
 *                              ],
10837
 *                              ]
10838
 * // phpcs:enable
10839
 * @return string               	html button
10840
 */
10841
function dolGetButtonAction($label, $text = '', $actionType = 'default', $url = '', $id = '', $userRight = 1, $params = array())
10842
{
10843
	global $hookmanager, $action, $object, $langs;
10844
10845
	//var_dump($params);
10846
	if (!empty($params['isDropdown']))
10847
		$class = "dropdown-item";
10848
	else {
10849
		$class = 'butAction';
10850
		if ($actionType == 'danger' || $actionType == 'delete') {
10851
			$class = 'butActionDelete';
10852
			if (!empty($url) && strpos($url, 'token=') === false) $url .= '&token='.newToken();
10853
		}
10854
	}
10855
	$attr = array(
10856
		'class' => $class,
10857
		'href' => empty($url) ? '' : $url,
10858
		'title' => $label
10859
	);
10860
10861
	if (empty($text)) {
10862
		$text = $label;
10863
		$attr['title'] = ''; // if html not set, leave label on title is redundant
10864
	} else {
10865
		$attr['title'] = $label;
10866
		$attr['aria-label'] = $label;
10867
	}
10868
10869
	if (empty($userRight)) {
10870
		$attr['class'] = 'butActionRefused';
10871
		$attr['href'] = '';
10872
		$attr['title'] = (($label && $text && $label != $text) ? $label : $langs->trans('NotEnoughPermissions'));
10873
	}
10874
10875
	if (!empty($id)) {
10876
		$attr['id'] = $id;
10877
	}
10878
10879
	// Override attr
10880
	if (!empty($params['attr']) && is_array($params['attr'])) {
10881
		foreach ($params['attr'] as $key => $value) {
10882
			if ($key == 'class') {
10883
				$attr['class'] .= ' '.$value;
10884
			} elseif ($key == 'classOverride') {
10885
				$attr['class'] = $value;
10886
			} else {
10887
				$attr[$key] = $value;
10888
			}
10889
		}
10890
	}
10891
10892
	// automatic add tooltip when title is detected
10893
	if (!empty($attr['title']) && !empty($attr['class']) && strpos($attr['class'], 'classfortooltip') === false) {
10894
		$attr['class'].= ' classfortooltip';
10895
	}
10896
10897
	// Js Confirm button
10898
	if ($userRight && !empty($params['confirm'])) {
10899
		if (!is_array($params['confirm'])) {
10900
			$params['confirm'] = array();
10901
		}
10902
10903
		if (empty($params['confirm']['url'])) {
10904
			$params['confirm']['url'] = $url . (strpos($url, '?') > 0 ? '&' : '?') . 'confirm=yes';
10905
		}
10906
10907
		// for js desabled compatibility set $url as call to confirm action and $params['confirm']['url'] to confirmed action
10908
		$attr['data-confirm-url'] = $params['confirm']['url'];
10909
		$attr['data-confirm-title'] = !empty($params['confirm']['title']) ? $params['confirm']['title'] : $langs->trans('ConfirmBtnCommonTitle', $label);
10910
		$attr['data-confirm-content'] = !empty($params['confirm']['content']) ? $params['confirm']['content'] : $langs->trans('ConfirmBtnCommonContent', $label);
10911
		$attr['data-confirm-content'] = preg_replace("/\r|\n/", "", $attr['data-confirm-content']);
10912
		$attr['data-confirm-action-btn-label'] = !empty($params['confirm']['action-btn-label']) ? $params['confirm']['action-btn-label'] : $langs->trans('Confirm');
10913
		$attr['data-confirm-cancel-btn-label'] = !empty($params['confirm']['cancel-btn-label']) ? $params['confirm']['cancel-btn-label'] : $langs->trans('CloseDialog');
10914
		$attr['data-confirm-modal'] = !empty($params['confirm']['modal']) ? $params['confirm']['modal'] : true;
10915
10916
		$attr['class'].= ' butActionConfirm';
10917
	}
10918
10919
	if (isset($attr['href']) && empty($attr['href'])) {
10920
		unset($attr['href']);
10921
	}
10922
10923
	// escape all attribute
10924
	$attr = array_map('dol_escape_htmltag', $attr);
10925
10926
	$TCompiledAttr = array();
10927
	foreach ($attr as $key => $value) {
10928
		$TCompiledAttr[] = $key.'= "'.$value.'"';
10929
	}
10930
10931
	$compiledAttributes = empty($TCompiledAttr) ? '' : implode(' ', $TCompiledAttr);
10932
10933
	$tag = !empty($attr['href']) ? 'a' : 'span';
10934
10935
10936
	$parameters = array(
10937
		'TCompiledAttr' => $TCompiledAttr,				// array
10938
		'compiledAttributes' => $compiledAttributes,	// string
10939
		'attr' => $attr,
10940
		'tag' => $tag,
10941
		'label' => $label,
10942
		'html' => $text,
10943
		'actionType' => $actionType,
10944
		'url' => $url,
10945
		'id' => $id,
10946
		'userRight' => $userRight,
10947
		'params' => $params
10948
	);
10949
10950
	$reshook = $hookmanager->executeHooks('dolGetButtonAction', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
10951
	if ($reshook < 0) setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
10952
10953
	if (empty($reshook)) {
10954
		if (dol_textishtml($text)) {	// If content already HTML encoded
10955
			return '<' . $tag . ' ' . $compiledAttributes . '>' . $text . '</' . $tag . '>';
10956
		} else {
10957
			return '<' . $tag . ' ' . $compiledAttributes . '>' . dol_escape_htmltag($text) . '</' . $tag . '>';
10958
		}
10959
	} else {
10960
		return $hookmanager->resPrint;
10961
	}
10962
}
10963
10964
/**
10965
 * Add space between dolGetButtonTitle
10966
 *
10967
 * @param  string $moreClass 	more css class label
10968
 * @return string 				html of title separator
10969
 */
10970
function dolGetButtonTitleSeparator($moreClass = "")
10971
{
10972
	return '<span class="button-title-separator '.$moreClass.'" ></span>';
10973
}
10974
10975
/**
10976
 * get field error icon
10977
 *
10978
 * @param  string  $fieldValidationErrorMsg message to add in tooltip
10979
 * @return string html output
10980
 */
10981
function getFieldErrorIcon($fieldValidationErrorMsg)
10982
{
10983
	$out = '';
10984
	if (!empty($fieldValidationErrorMsg)) {
10985
		$out.= '<span class="field-error-icon classfortooltip" title="'.dol_escape_htmltag($fieldValidationErrorMsg, 1).'"  role="alert" >'; // role alert is used for accessibility
10986
		$out.= '<span class="fa fa-exclamation-circle" aria-hidden="true" ></span>'; // For accessibility icon is separated and aria-hidden
10987
		$out.= '</span>';
10988
	}
10989
10990
	return $out;
10991
}
10992
10993
/**
10994
 * Function dolGetButtonTitle : this kind of buttons are used in title in list
10995
 *
10996
 * @param string    $label      label of button
10997
 * @param string    $helpText   optional : content for help tooltip
10998
 * @param string    $iconClass  class for icon element (Example: 'fa fa-file')
10999
 * @param string    $url        the url for link
11000
 * @param string    $id         attribute id of button
11001
 * @param int       $status     0 no user rights, 1 active, 2 current action or selected, -1 Feature Disabled, -2 disable Other reason use param $helpText as tooltip help
11002
 * @param array     $params     various params for future : recommended rather than adding more function arguments
11003
 * @return string               html button
11004
 */
11005
function dolGetButtonTitle($label, $helpText = '', $iconClass = 'fa fa-file', $url = '', $id = '', $status = 1, $params = array())
11006
{
11007
	global $langs, $conf, $user;
11008
11009
	// Actually this conf is used in css too for external module compatibility and smooth transition to this function
11010
	if (!empty($conf->global->MAIN_BUTTON_HIDE_UNAUTHORIZED) && (!$user->admin) && $status <= 0) {
11011
		return '';
11012
	}
11013
11014
	$class = 'btnTitle';
11015
	if (in_array($iconClass, array('fa fa-plus-circle', 'fa fa-plus-circle size15x', 'fa fa-comment-dots', 'fa fa-paper-plane'))) {
11016
		$class .= ' btnTitlePlus';
11017
	}
11018
	$useclassfortooltip = 1;
11019
11020
	if (!empty($params['morecss'])) {
11021
		$class .= ' '.$params['morecss'];
11022
	}
11023
11024
	$attr = array(
11025
		'class' => $class,
11026
		'href' => empty($url) ? '' : $url
11027
	);
11028
11029
	if (!empty($helpText)) {
11030
		$attr['title'] = dol_escape_htmltag($helpText);
11031
	} elseif (empty($attr['title']) && $label) {
11032
		$attr['title'] = $label;
11033
		$useclassfortooltip = 0;
11034
	}
11035
11036
	if ($status == 2) {
11037
		$attr['class'] .= ' btnTitleSelected';
11038
	} elseif ($status <= 0) {
11039
		$attr['class'] .= ' refused';
11040
11041
		$attr['href'] = '';
11042
11043
		if ($status == -1) { // disable
11044
			$attr['title'] = dol_escape_htmltag($langs->transnoentitiesnoconv("FeatureDisabled"));
11045
		} elseif ($status == 0) { // Not enough permissions
11046
			$attr['title'] = dol_escape_htmltag($langs->transnoentitiesnoconv("NotEnoughPermissions"));
11047
		}
11048
	}
11049
11050
	if (!empty($attr['title']) && $useclassfortooltip) {
11051
		$attr['class'] .= ' classfortooltip';
11052
	}
11053
11054
	if (!empty($id)) {
11055
		$attr['id'] = $id;
11056
	}
11057
11058
	// Override attr
11059
	if (!empty($params['attr']) && is_array($params['attr'])) {
11060
		foreach ($params['attr'] as $key => $value) {
11061
			if ($key == 'class') {
11062
				$attr['class'] .= ' '.$value;
11063
			} elseif ($key == 'classOverride') {
11064
				$attr['class'] = $value;
11065
			} else {
11066
				$attr[$key] = $value;
11067
			}
11068
		}
11069
	}
11070
11071
	if (isset($attr['href']) && empty($attr['href'])) {
11072
		unset($attr['href']);
11073
	}
11074
11075
	// TODO : add a hook
11076
11077
	// escape all attribute
11078
	$attr = array_map('dol_escape_htmltag', $attr);
11079
11080
	$TCompiledAttr = array();
11081
	foreach ($attr as $key => $value) {
11082
		$TCompiledAttr[] = $key.'="'.$value.'"';
11083
	}
11084
11085
	$compiledAttributes = (empty($TCompiledAttr) ? '' : implode(' ', $TCompiledAttr));
11086
11087
	$tag = (empty($attr['href']) ? 'span' : 'a');
11088
11089
	$button = '<'.$tag.' '.$compiledAttributes.'>';
11090
	$button .= '<span class="'.$iconClass.' valignmiddle btnTitle-icon"></span>';
11091
	if (!empty($params['forcenohideoftext'])) {
11092
		$button .= '<span class="valignmiddle text-plus-circle btnTitle-label'.(empty($params['forcenohideoftext']) ? ' hideonsmartphone' : '').'">'.$label.'</span>';
11093
	}
11094
	$button .= '</'.$tag.'>';
11095
11096
	return $button;
11097
}
11098
11099
/**
11100
 * Get an array with properties of an element.
11101
 * Called by fetchObjectByElement.
11102
 *
11103
 * @param   string 	$element_type 	Element type (Value of $object->element). Example: 'action', 'facture', 'project_task' or 'object@mymodule'...
11104
 * @return  array					(module, classpath, element, subelement, classfile, classname)
11105
 */
11106
function getElementProperties($element_type)
11107
{
11108
	$regs = array();
11109
11110
	$classfile = $classname = $classpath = '';
11111
11112
	// Parse element/subelement (ex: project_task)
11113
	$module = $element_type;
11114
	$element = $element_type;
11115
	$subelement = $element_type;
11116
11117
	// If we ask an resource form external module (instead of default path)
11118
	if (preg_match('/^([^@]+)@([^@]+)$/i', $element_type, $regs)) {
11119
		$element = $subelement = $regs[1];
11120
		$module = $regs[2];
11121
	}
11122
11123
	//print '<br>1. element : '.$element.' - module : '.$module .'<br>';
11124
	if (preg_match('/^([^_]+)_([^_]+)/i', $element, $regs)) {
11125
		$module = $element = $regs[1];
11126
		$subelement = $regs[2];
11127
	}
11128
11129
	// For compat
11130
	if ($element_type == "action") {
11131
		$classpath = 'comm/action/class';
11132
		$subelement = 'Actioncomm';
11133
		$module = 'agenda';
11134
	}
11135
11136
	// To work with non standard path
11137
	if ($element_type == 'facture' || $element_type == 'invoice') {
11138
		$classpath = 'compta/facture/class';
11139
		$module = 'facture';
11140
		$subelement = 'facture';
11141
	}
11142
	if ($element_type == 'commande' || $element_type == 'order') {
11143
		$classpath = 'commande/class';
11144
		$module = 'commande';
11145
		$subelement = 'commande';
11146
	}
11147
	if ($element_type == 'propal') {
11148
		$classpath = 'comm/propal/class';
11149
	}
11150
	if ($element_type == 'supplier_proposal') {
11151
		$classpath = 'supplier_proposal/class';
11152
	}
11153
	if ($element_type == 'shipping') {
11154
		$classpath = 'expedition/class';
11155
		$subelement = 'expedition';
11156
		$module = 'expedition_bon';
11157
	}
11158
	if ($element_type == 'delivery') {
11159
		$classpath = 'delivery/class';
11160
		$subelement = 'delivery';
11161
		$module = 'delivery_note';
11162
	}
11163
	if ($element_type == 'contract') {
11164
		$classpath = 'contrat/class';
11165
		$module = 'contrat';
11166
		$subelement = 'contrat';
11167
	}
11168
	if ($element_type == 'member') {
11169
		$classpath = 'adherents/class';
11170
		$module = 'adherent';
11171
		$subelement = 'adherent';
11172
	}
11173
	if ($element_type == 'cabinetmed_cons') {
11174
		$classpath = 'cabinetmed/class';
11175
		$module = 'cabinetmed';
11176
		$subelement = 'cabinetmedcons';
11177
	}
11178
	if ($element_type == 'fichinter') {
11179
		$classpath = 'fichinter/class';
11180
		$module = 'ficheinter';
11181
		$subelement = 'fichinter';
11182
	}
11183
	if ($element_type == 'dolresource' || $element_type == 'resource') {
11184
		$classpath = 'resource/class';
11185
		$module = 'resource';
11186
		$subelement = 'dolresource';
11187
	}
11188
	if ($element_type == 'propaldet') {
11189
		$classpath = 'comm/propal/class';
11190
		$module = 'propal';
11191
		$subelement = 'propaleligne';
11192
	}
11193
	if ($element_type == 'order_supplier') {
11194
		$classpath = 'fourn/class';
11195
		$module = 'fournisseur';
11196
		$subelement = 'commandefournisseur';
11197
		$classfile = 'fournisseur.commande';
11198
	}
11199
	if ($element_type == 'invoice_supplier') {
11200
		$classpath = 'fourn/class';
11201
		$module = 'fournisseur';
11202
		$subelement = 'facturefournisseur';
11203
		$classfile = 'fournisseur.facture';
11204
	}
11205
	if ($element_type == "service") {
11206
		$classpath = 'product/class';
11207
		$subelement = 'product';
11208
	}
11209
11210
	if (empty($classfile)) {
11211
		$classfile = strtolower($subelement);
11212
	}
11213
	if (empty($classname)) {
11214
		$classname = ucfirst($subelement);
11215
	}
11216
	if (empty($classpath)) {
11217
		$classpath = $module.'/class';
11218
	}
11219
11220
	$element_properties = array(
11221
		'module' => $module,
11222
		'classpath' => $classpath,
11223
		'element' => $element,
11224
		'subelement' => $subelement,
11225
		'classfile' => $classfile,
11226
		'classname' => $classname
11227
	);
11228
	return $element_properties;
11229
}
11230
11231
/**
11232
 * Fetch an object from its id and element_type
11233
 * Inclusion of classes is automatic
11234
 *
11235
 * @param	int     	$element_id 	Element id
11236
 * @param	string  	$element_type 	Element type
11237
 * @param	string     	$element_ref 	Element ref (Use this or element_id but not both)
11238
 * @return 	int|object 					object || 0 || -1 if error
11239
 */
11240
function fetchObjectByElement($element_id, $element_type, $element_ref = '')
11241
{
11242
	global $conf, $db;
11243
11244
	$element_prop = getElementProperties($element_type);
11245
	if (is_array($element_prop) && $conf->{$element_prop['module']}->enabled) {
11246
		dol_include_once('/'.$element_prop['classpath'].'/'.$element_prop['classfile'].'.class.php');
11247
11248
		$objecttmp = new $element_prop['classname']($db);
11249
		$ret = $objecttmp->fetch($element_id, $element_ref);
11250
		if ($ret >= 0) {
11251
			return $objecttmp;
11252
		}
11253
	}
11254
	return 0;
11255
}
11256
11257
/**
11258
 * Return if a file can contains executable content
11259
 *
11260
 * @param   string  $filename       File name to test
11261
 * @return  boolean                 True if yes, False if no
11262
 */
11263
function isAFileWithExecutableContent($filename)
11264
{
11265
	if (preg_match('/\.(htm|html|js|phar|php|php\d+|phtml|pht|pl|py|cgi|ksh|sh|shtml|bash|bat|cmd|wpk|exe|dmg)$/i', $filename)) {
11266
		return true;
11267
	}
11268
11269
	return false;
11270
}
11271
11272
/**
11273
 * Return the value of token currently saved into session with name 'newtoken'.
11274
 * This token must be send by any POST as it will be used by next page for comparison with value in session.
11275
 *
11276
 * @return  string
11277
 */
11278
function newToken()
11279
{
11280
	return empty($_SESSION['newtoken']) ? '' : $_SESSION['newtoken'];
11281
}
11282
11283
/**
11284
 * Return the value of token currently saved into session with name 'token'.
11285
 * For ajax call, you must use this token as a parameter of the call into the js calling script (the called ajax php page must also set constant NOTOKENRENEWAL).
11286
 *
11287
 * @return  string
11288
 */
11289
function currentToken()
11290
{
11291
	return isset($_SESSION['token']) ? $_SESSION['token'] : '';
11292
}
11293
11294
/**
11295
 * Start a table with headers and a optinal clickable number (don't forget to use "finishSimpleTable()" after the last table row)
11296
 *
11297
 * @param string	$header		The first left header of the table (automatic translated)
11298
 * @param string	$link		(optional) The link to a internal dolibarr page, when click on the number (without the first "/")
11299
 * @param string	$arguments	(optional) Additional arguments for the link (e.g. "search_status=0")
11300
 * @param integer	$emptyRows	(optional) The count of empty rows after the first header
11301
 * @param integer	$number		(optional) The number that is shown right after the first header, when not set the link is shown on the right side of the header as "FullList"
11302
 * @return void
11303
 *
11304
 * @see finishSimpleTable()
11305
 */
11306
function startSimpleTable($header, $link = "", $arguments = "", $emptyRows = 0, $number = -1)
11307
{
11308
	global $langs;
11309
11310
	print '<div class="div-table-responsive-no-min">';
11311
	print '<table class="noborder centpercent">';
11312
	print '<tr class="liste_titre">';
11313
11314
	print $emptyRows < 1 ? '<th>' : '<th colspan="'.($emptyRows + 1).'">';
11315
11316
	print $langs->trans($header);
11317
11318
	// extra space between the first header and the number
11319
	if ($number > -1) {
11320
		print ' ';
11321
	}
11322
11323
	if (!empty($link)) {
11324
		if (!empty($arguments)) {
11325
			print '<a href="'.DOL_URL_ROOT.'/'.$link.'?'.$arguments.'">';
11326
		} else {
11327
			print '<a href="'.DOL_URL_ROOT.'/'.$link.'">';
11328
		}
11329
	}
11330
11331
	if ($number > -1) {
11332
		print '<span class="badge">'.$number.'</span>';
11333
	}
11334
11335
	if (!empty($link)) {
11336
		print '</a>';
11337
	}
11338
11339
	print '</th>';
11340
11341
	if ($number < 0 && !empty($link)) {
11342
		print '<th class="right">';
11343
11344
		if (!empty($arguments)) {
11345
			print '<a class="commonlink" href="'.DOL_URL_ROOT.'/'.$link.'?'.$arguments.'">';
11346
		} else {
11347
			print '<a class="commonlink" href="'.DOL_URL_ROOT.'/'.$link.'">';
11348
		}
11349
11350
		print $langs->trans("FullList");
11351
		print '</a>';
11352
		print '</th>';
11353
	}
11354
11355
	print '</tr>';
11356
}
11357
11358
/**
11359
 * Add the correct HTML close tags for "startSimpleTable(...)" (use after the last table line)
11360
 *
11361
 * @param 	bool 	$addLineBreak	(optional) Add a extra line break after the complete table (\<br\>)
11362
 * @return 	void
11363
 *
11364
 * @see startSimpleTable()
11365
 */
11366
function finishSimpleTable($addLineBreak = false)
11367
{
11368
	print '</table>';
11369
	print '</div>';
11370
11371
	if ($addLineBreak) {
11372
		print '<br>';
11373
	}
11374
}
11375
11376
/**
11377
 * Add a summary line to the current open table ("None", "XMoreLines" or "Total xxx")
11378
 *
11379
 * @param integer	$tableColumnCount		The complete count columns of the table
11380
 * @param integer	$num					The count of the rows of the table, when it is zero (0) the "$noneWord" is shown instead
11381
 * @param integer	$nbofloop				(optional)	The maximum count of rows thaht the table show (when it is zero (0) no summary line will show, expect "$noneWord" when $num === 0)
11382
 * @param integer	$total					(optional)	The total value thaht is shown after when the table has minimum of one entire
11383
 * @param string	$noneWord				(optional)	The word that is shown when the table has no entires ($num === 0)
11384
 * @param boolean	$extraRightColumn		(optional)	Add a addtional column after the summary word and total number
11385
 * @return void
11386
 */
11387
function addSummaryTableLine($tableColumnCount, $num, $nbofloop = 0, $total = 0, $noneWord = "None", $extraRightColumn = false)
11388
{
11389
	global $langs;
11390
11391
	if ($num === 0) {
11392
		print '<tr class="oddeven">';
11393
		print '<td colspan="'.$tableColumnCount.'" class="opacitymedium">'.$langs->trans($noneWord).'</td>';
11394
		print '</tr>';
11395
		return;
11396
	}
11397
11398
	if ($nbofloop === 0) {
11399
		// don't show a summary line
11400
		return;
11401
	}
11402
11403
	if ($num === 0) {
11404
		$colspan = $tableColumnCount;
11405
	} elseif ($num > $nbofloop) {
11406
		$colspan = $tableColumnCount;
11407
	} else {
11408
		$colspan = $tableColumnCount - 1;
11409
	}
11410
11411
	if ($extraRightColumn) {
11412
		$colspan--;
11413
	}
11414
11415
	print '<tr class="liste_total">';
11416
11417
	if ($nbofloop > 0 && $num > $nbofloop) {
11418
		print '<td colspan="'.$colspan.'" class="right">'.$langs->trans("XMoreLines", ($num - $nbofloop)).'</td>';
11419
	} else {
11420
		print '<td colspan="'.$colspan.'" class="right"> '.$langs->trans("Total").'</td>';
11421
		print '<td class="right" width="100">'.price($total).'</td>';
11422
	}
11423
11424
	if ($extraRightColumn) {
11425
		print '<td></td>';
11426
	}
11427
11428
	print '</tr>';
11429
}
11430
11431
/**
11432
 *  Return a file on output using a low memory. It can return very large files with no need of memory.
11433
 *  WARNING: This close output buffers.
11434
 *
11435
 *  @param	string	$fullpath_original_file_osencoded		Full path of file to return.
11436
 *  @param	int		$method									-1 automatic, 0=readfile, 1=fread, 2=stream_copy_to_stream
11437
 *  @return void
11438
 */
11439
function readfileLowMemory($fullpath_original_file_osencoded, $method = -1)
11440
{
11441
	global $conf;
11442
11443
	if ($method == -1) {
11444
		$method = 0;
11445
		if (!empty($conf->global->MAIN_FORCE_READFILE_WITH_FREAD)) {
11446
			$method = 1;
11447
		}
11448
		if (!empty($conf->global->MAIN_FORCE_READFILE_WITH_STREAM_COPY)) {
11449
			$method = 2;
11450
		}
11451
	}
11452
11453
	// Be sure we don't have output buffering enabled to have readfile working correctly
11454
	while (ob_get_level()) {
11455
		ob_end_flush();
11456
	}
11457
11458
	// Solution 0
11459
	if ($method == 0) {
11460
		readfile($fullpath_original_file_osencoded);
11461
	} elseif ($method == 1) {
11462
		// Solution 1
11463
		$handle = fopen($fullpath_original_file_osencoded, "rb");
11464
		while (!feof($handle)) {
11465
			print fread($handle, 8192);
11466
		}
11467
		fclose($handle);
11468
	} elseif ($method == 2) {
11469
		// Solution 2
11470
		$handle1 = fopen($fullpath_original_file_osencoded, "rb");
11471
		$handle2 = fopen("php://output", "wb");
11472
		stream_copy_to_stream($handle1, $handle2);
11473
		fclose($handle1);
11474
		fclose($handle2);
11475
	}
11476
}
11477
11478
/**
11479
 * Create a button to copy $valuetocopy in the clipboard (for copy and paste feature).
11480
 * Code that handle the click is inside core/js/lib_foot.js.php.
11481
 *
11482
 * @param 	string 	$valuetocopy 		The value to print
11483
 * @param	int		$showonlyonhover	Show the copy-paste button only on hover
11484
 * @param	string	$texttoshow			Replace the value to show with this text. Use 'none' to show no text (only the copy-paste picto)
11485
 * @return 	string 						The string to print for the button
11486
 */
11487
function showValueWithClipboardCPButton($valuetocopy, $showonlyonhover = 1, $texttoshow = '')
11488
{
11489
	/*
11490
	global $conf;
11491
11492
	if (!empty($conf->dol_no_mouse_hover)) {
11493
		$showonlyonhover = 0;
11494
	}*/
11495
11496
	$tag = 'span'; 	// Using div (like any style of type 'block') does not work when using the js copy code.
11497
	if ($texttoshow === 'none') {
11498
		$result = '<span class="clipboardCP'.($showonlyonhover ? ' clipboardCPShowOnHover' : '').'"><'.$tag.' class="clipboardCPValue hidewithsize">'.dol_escape_htmltag($valuetocopy, 1, 1).'</'.$tag.'><span class="clipboardCPValueToPrint"></span><span class="clipboardCPButton far fa-clipboard opacitymedium paddingleft paddingright"></span><span class="clipboardCPText"></span></span>';
11499
	} elseif ($texttoshow) {
11500
		$result = '<span class="clipboardCP'.($showonlyonhover ? ' clipboardCPShowOnHover' : '').'"><'.$tag.' class="clipboardCPValue hidewithsize">'.dol_escape_htmltag($valuetocopy, 1, 1).'</'.$tag.'><span class="clipboardCPValueToPrint">'.dol_escape_htmltag($texttoshow, 1, 1).'</span><span class="clipboardCPButton far fa-clipboard opacitymedium paddingleft paddingright"></span><span class="clipboardCPText"></span></span>';
11501
	} else {
11502
		$result = '<span class="clipboardCP'.($showonlyonhover ? ' clipboardCPShowOnHover' : '').'"><'.$tag.' class="clipboardCPValue">'.dol_escape_htmltag($valuetocopy, 1, 1).'</'.$tag.'><span class="clipboardCPButton far fa-clipboard opacitymedium paddingleft paddingright"></span><span class="clipboardCPText"></span></span>';
11503
	}
11504
11505
	return $result;
11506
}
11507
11508
11509
/**
11510
 * Decode an encode string. The string can be encoded in json format (recommended) or with serialize (avoid this)
11511
 *
11512
 * @param 	string	$stringtodecode		String to decode (json or serialize coded)
11513
 * @return	mixed						The decoded object.
11514
 */
11515
function jsonOrUnserialize($stringtodecode)
11516
{
11517
	$result = json_decode($stringtodecode);
11518
	if ($result === null) {
11519
		$result = unserialize($stringtodecode);
11520
	}
11521
11522
	return $result;
11523
}
11524
11525
11526
/**
11527
 * Return if a $sqlfilters parameter is valid and will pass the preg_replace_callback() to replace Generic filter string with SQL filter string
11528
 * Example of usage:
11529
 * if ($sqlfilters) {
11530
 *	 $errormessage = '';
11531
 *   if (dolCheckFilters($sqlfilters, $errormessage)) {
11532
 *	   $regexstring = '\(([^:\'\(\)]+:[^:\'\(\)]+:[^\(\)]+)\)';
11533
 *	   $sql .= " AND (".preg_replace_callback('/'.$regexstring.'/', 'dolForgeCriteriaCallback', $sqlfilters).")";
11534
 *   }
11535
 * }
11536
 *
11537
 * @param	string  		$sqlfilters     sqlfilter string
11538
 * @param	string			$error			Error message
11539
 * @return 	boolean			   				True if valid, False if not valid ($error is filled with the reason in such a case)
11540
 */
11541
function dolCheckFilters($sqlfilters, &$error = '')
11542
{
11543
	//$regexstring='\(([^:\'\(\)]+:[^:\'\(\)]+:[^:\(\)]+)\)';
11544
	//$tmp=preg_replace_all('/'.$regexstring.'/', '', $sqlfilters);
11545
	$tmp = $sqlfilters;
11546
	$i = 0; $nb = strlen($tmp);
11547
	$counter = 0;
11548
	while ($i < $nb) {
11549
		if ($tmp[$i] == '(') {
11550
			$counter++;
11551
		}
11552
		if ($tmp[$i] == ')') {
11553
			$counter--;
11554
		}
11555
		if ($counter < 0) {
11556
			$error = "Bad sqlfilters=".$sqlfilters;
11557
			dol_syslog($error, LOG_WARNING);
11558
			return false;
11559
		}
11560
		$i++;
11561
	}
11562
	return true;
11563
}
11564
11565
/**
11566
 * Function to forge a SQL criteria from a Generic filter string.
11567
 * Example of usage:
11568
 * if ($sqlfilters) {
11569
 *	 $errormessage = '';
11570
 *   if (dolCheckFilters($sqlfilters, $errormessage)) {
11571
 *	   $regexstring = '\(([^:\'\(\)]+:[^:\'\(\)]+:[^\(\)]+)\)';
11572
 *	   $sql .= " AND (".preg_replace_callback('/'.$regexstring.'/', 'dolForgeCriteriaCallback', $sqlfilters).")";
11573
 *   }
11574
 * }
11575
 *
11576
 * @param  array    $matches    Array of found string by regex search.
11577
 * 								Example: "t.ref:like:'SO-%'" or "t.date_creation:<:'20160101'" or "t.date_creation:<:'2016-01-01 12:30:00'" or "t.nature:is:NULL"
11578
 * @return string               Forged criteria. Example: "t.field like 'abc%'"
11579
 */
11580
function dolForgeCriteriaCallback($matches)
11581
{
11582
	global $db;
11583
11584
	dol_syslog("Convert matches ".$matches[1]);
11585
	if (empty($matches[1])) {
11586
		return '';
11587
	}
11588
	$tmp = explode(':', $matches[1], 3);
11589
11590
	if (count($tmp) < 3) {
11591
		return '';
11592
	}
11593
11594
	$operand = preg_replace('/[^a-z0-9\._]/i', '', trim($tmp[0]));
11595
11596
	$operator = strtoupper(preg_replace('/[^a-z<>=]/i', '', trim($tmp[1])));
11597
	if ($operator == 'NOTLIKE') {
11598
		$operator = 'NOT LIKE';
11599
	}
11600
11601
	$tmpescaped = trim($tmp[2]);
11602
	$regbis = array();
11603
	if ($operator == 'IN') {
11604
		$tmpescaped = "(".$db->sanitize($tmpescaped, 1).")";
11605
	} elseif (preg_match('/^\'(.*)\'$/', $tmpescaped, $regbis)) {
11606
		$tmpescaped = "'".$db->escape($regbis[1])."'";
11607
	} else {
11608
		$tmpescaped = $db->sanitize($db->escape($tmpescaped));
11609
	}
11610
11611
	return $db->escape($operand).' '.$db->escape($operator)." ".$tmpescaped;
11612
}
11613
11614
11615
/**
11616
 * Get timeline icon
11617
 * @param ActionComm $actionstatic actioncomm
11618
 * @param array $histo histo
11619
 * @param int $key key
11620
 * @return string
11621
 */
11622
function getTimelineIcon($actionstatic, &$histo, $key)
11623
{
11624
	global $conf, $langs;
11625
	$out = '<!-- timeline icon -->'."\n";
11626
	$iconClass = 'fa fa-comments';
11627
	$img_picto = '';
11628
	$colorClass = '';
11629
	$pictoTitle = '';
11630
11631
	if ($histo[$key]['percent'] == -1) {
11632
		$colorClass = 'timeline-icon-not-applicble';
11633
		$pictoTitle = $langs->trans('StatusNotApplicable');
11634
	} elseif ($histo[$key]['percent'] == 0) {
11635
		$colorClass = 'timeline-icon-todo';
11636
		$pictoTitle = $langs->trans('StatusActionToDo').' (0%)';
11637
	} elseif ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100) {
11638
		$colorClass = 'timeline-icon-in-progress';
11639
		$pictoTitle = $langs->trans('StatusActionInProcess').' ('.$histo[$key]['percent'].'%)';
11640
	} elseif ($histo[$key]['percent'] >= 100) {
11641
		$colorClass = 'timeline-icon-done';
11642
		$pictoTitle = $langs->trans('StatusActionDone').' (100%)';
11643
	}
11644
11645
	if ($actionstatic->code == 'AC_TICKET_CREATE') {
11646
		$iconClass = 'fa fa-ticket';
11647
	} elseif ($actionstatic->code == 'AC_TICKET_MODIFY') {
11648
		$iconClass = 'fa fa-pencilxxx';
11649
	} elseif (preg_match('/^TICKET_MSG/', $actionstatic->code)) {
11650
		$iconClass = 'fa fa-comments';
11651
	} elseif (preg_match('/^TICKET_MSG_PRIVATE/', $actionstatic->code)) {
11652
		$iconClass = 'fa fa-mask';
11653
	} elseif (!empty($conf->global->AGENDA_USE_EVENT_TYPE)) {
11654
		if ($actionstatic->type_picto) {
11655
			$img_picto = img_picto('', $actionstatic->type_picto);
11656
		} else {
11657
			if ($actionstatic->type_code == 'AC_RDV') {
11658
				$iconClass = 'fa fa-handshake';
11659
			} elseif ($actionstatic->type_code == 'AC_TEL') {
11660
				$iconClass = 'fa fa-phone';
11661
			} elseif ($actionstatic->type_code == 'AC_FAX') {
11662
				$iconClass = 'fa fa-fax';
11663
			} elseif ($actionstatic->type_code == 'AC_EMAIL') {
11664
				$iconClass = 'fa fa-envelope';
11665
			} elseif ($actionstatic->type_code == 'AC_INT') {
11666
				$iconClass = 'fa fa-shipping-fast';
11667
			} elseif ($actionstatic->type_code == 'AC_OTH_AUTO') {
11668
				$iconClass = 'fa fa-robot';
11669
			} elseif (!preg_match('/_AUTO/', $actionstatic->type_code)) {
11670
				$iconClass = 'fa fa-robot';
11671
			}
11672
		}
11673
	}
11674
11675
	$out .= '<i class="'.$iconClass.' '.$colorClass.'" title="'.$pictoTitle.'">'.$img_picto.'</i>'."\n";
11676
	return $out;
11677
}
11678
11679
/**
11680
 * getActionCommEcmList
11681
 *
11682
 * @param	ActionComm		$object			Object ActionComm
11683
 * @return 	array							Array of documents in index table
11684
 */
11685
function getActionCommEcmList($object)
11686
{
11687
	global $conf, $db;
11688
11689
	$documents = array();
11690
11691
	$sql = 'SELECT ecm.rowid as id, ecm.src_object_type, ecm.src_object_id, ecm.filepath, ecm.filename';
11692
	$sql .= ' FROM '.MAIN_DB_PREFIX.'ecm_files ecm';
11693
	$sql .= " WHERE ecm.filepath = 'agenda/".((int) $object->id)."'";
11694
	//$sql.= " ecm.src_object_type = '".$db->escape($object->element)."' AND ecm.src_object_id = ".((int) $object->id); // Old version didn't add object_type during upload
11695
	$sql .= ' ORDER BY ecm.position ASC';
11696
11697
	$resql = $db->query($sql);
11698
	if ($resql) {
11699
		if ($db->num_rows($resql)) {
11700
			while ($obj = $db->fetch_object($resql)) {
11701
				$documents[$obj->id] = $obj;
11702
			}
11703
		}
11704
	}
11705
11706
	return $documents;
11707
}
11708
11709
11710
11711
/**
11712
 *    	Show html area with actions in messaging format.
11713
 *      Note: Global parameter $param must be defined.
11714
 *
11715
 * 		@param	Conf		       $conf		   Object conf
11716
 * 		@param	Translate	       $langs		   Object langs
11717
 * 		@param	DoliDB		       $db			   Object db
11718
 * 		@param	mixed			   $filterobj	   Filter on object Adherent|Societe|Project|Product|CommandeFournisseur|Dolresource|Ticket|... to list events linked to an object
11719
 * 		@param	Contact		       $objcon		   Filter on object contact to filter events on a contact
11720
 *      @param  int			       $noprint        Return string but does not output it
11721
 *      @param  string		       $actioncode     Filter on actioncode
11722
 *      @param  string             $donetodo       Filter on event 'done' or 'todo' or ''=nofilter (all).
11723
 *      @param  array              $filters        Filter on other fields
11724
 *      @param  string             $sortfield      Sort field
11725
 *      @param  string             $sortorder      Sort order
11726
 *      @return	string|void				           Return html part or void if noprint is 1
11727
 */
11728
function show_actions_messaging($conf, $langs, $db, $filterobj, $objcon = '', $noprint = 0, $actioncode = '', $donetodo = 'done', $filters = array(), $sortfield = 'a.datep,a.id', $sortorder = 'DESC')
11729
{
11730
	global $user, $conf;
11731
	global $form;
11732
11733
	global $param, $massactionbutton;
11734
11735
	dol_include_once('/comm/action/class/actioncomm.class.php');
11736
11737
	// Check parameters
11738
	if (!is_object($filterobj) && !is_object($objcon)) {
11739
		dol_print_error('', 'BadParameter');
11740
	}
11741
11742
	$histo = array();
11743
	$numaction = 0;
11744
	$now = dol_now();
11745
11746
	$sortfield_list = explode(',', $sortfield);
11747
	$sortfield_label_list = array('a.id' => 'id', 'a.datep' => 'dp', 'a.percent' => 'percent');
11748
	$sortfield_new_list = array();
11749
	foreach ($sortfield_list as $sortfield_value) {
11750
		$sortfield_new_list[] = $sortfield_label_list[trim($sortfield_value)];
11751
	}
11752
	$sortfield_new = implode(',', $sortfield_new_list);
11753
11754
	if (isModEnabled('agenda')) {
11755
		// Search histo on actioncomm
11756
		if (is_object($objcon) && $objcon->id > 0) {
11757
			$sql = "SELECT DISTINCT a.id, a.label as label,";
11758
		} else {
11759
			$sql = "SELECT a.id, a.label as label,";
11760
		}
11761
		$sql .= " a.datep as dp,";
11762
		$sql .= " a.note as message,";
11763
		$sql .= " a.datep2 as dp2,";
11764
		$sql .= " a.percent as percent, 'action' as type,";
11765
		$sql .= " a.fk_element, a.elementtype,";
11766
		$sql .= " a.fk_contact,";
11767
		$sql .= " c.code as acode, c.libelle as alabel, c.picto as apicto,";
11768
		$sql .= " u.rowid as user_id, u.login as user_login, u.photo as user_photo, u.firstname as user_firstname, u.lastname as user_lastname";
11769
		if (is_object($filterobj) && get_class($filterobj) == 'Societe') {
11770
			$sql .= ", sp.lastname, sp.firstname";
11771
		} elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') {
11772
			$sql .= ", m.lastname, m.firstname";
11773
		} elseif (is_object($filterobj) && get_class($filterobj) == 'CommandeFournisseur') {
11774
			$sql .= ", o.ref";
11775
		} elseif (is_object($filterobj) && get_class($filterobj) == 'Product') {
11776
			$sql .= ", o.ref";
11777
		} elseif (is_object($filterobj) && get_class($filterobj) == 'Ticket') {
11778
			$sql .= ", o.ref";
11779
		} elseif (is_object($filterobj) && get_class($filterobj) == 'BOM') {
11780
			$sql .= ", o.ref";
11781
		} elseif (is_object($filterobj) && get_class($filterobj) == 'Contrat') {
11782
			$sql .= ", o.ref";
11783
		}
11784
		$sql .= " FROM ".MAIN_DB_PREFIX."actioncomm as a";
11785
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."user as u on u.rowid = a.fk_user_action";
11786
		$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."c_actioncomm as c ON a.fk_action = c.id";
11787
11788
		$force_filter_contact = false;
11789
		if (is_object($objcon) && $objcon->id > 0) {
11790
			$force_filter_contact = true;
11791
			$sql .= " INNER JOIN ".MAIN_DB_PREFIX."actioncomm_resources as r ON a.id = r.fk_actioncomm";
11792
			$sql .= " AND r.element_type = '".$db->escape($objcon->table_element)."' AND r.fk_element = ".((int) $objcon->id);
11793
		}
11794
11795
		if (is_object($filterobj) && get_class($filterobj) == 'Societe') {
11796
			$sql .= " LEFT JOIN ".MAIN_DB_PREFIX."socpeople as sp ON a.fk_contact = sp.rowid";
11797
		} elseif (is_object($filterobj) && get_class($filterobj) == 'Dolresource') {
11798
			$sql .= " INNER JOIN ".MAIN_DB_PREFIX."element_resources as er";
11799
			$sql .= " ON er.resource_type = 'dolresource'";
11800
			$sql .= " AND er.element_id = a.id";
11801
			$sql .= " AND er.resource_id = ".((int) $filterobj->id);
11802
		} elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') {
11803
			$sql .= ", ".MAIN_DB_PREFIX."adherent as m";
11804
		} elseif (is_object($filterobj) && get_class($filterobj) == 'CommandeFournisseur') {
11805
			$sql .= ", ".MAIN_DB_PREFIX."commande_fournisseur as o";
11806
		} elseif (is_object($filterobj) && get_class($filterobj) == 'Product') {
11807
			$sql .= ", ".MAIN_DB_PREFIX."product as o";
11808
		} elseif (is_object($filterobj) && get_class($filterobj) == 'Ticket') {
11809
			$sql .= ", ".MAIN_DB_PREFIX."ticket as o";
11810
		} elseif (is_object($filterobj) && get_class($filterobj) == 'BOM') {
11811
			$sql .= ", ".MAIN_DB_PREFIX."bom_bom as o";
11812
		} elseif (is_object($filterobj) && get_class($filterobj) == 'Contrat') {
11813
			$sql .= ", ".MAIN_DB_PREFIX."contrat as o";
11814
		}
11815
11816
		$sql .= " WHERE a.entity IN (".getEntity('agenda').")";
11817
		if ($force_filter_contact === false) {
11818
			if (is_object($filterobj) && in_array(get_class($filterobj), array('Societe', 'Client', 'Fournisseur')) && $filterobj->id) {
11819
				$sql .= " AND a.fk_soc = ".((int) $filterobj->id);
11820
			} elseif (is_object($filterobj) && get_class($filterobj) == 'Project' && $filterobj->id) {
11821
				$sql .= " AND a.fk_project = ".((int) $filterobj->id);
11822
			} elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') {
11823
				$sql .= " AND a.fk_element = m.rowid AND a.elementtype = 'member'";
11824
				if ($filterobj->id) {
11825
					$sql .= " AND a.fk_element = ".((int) $filterobj->id);
11826
				}
11827
			} elseif (is_object($filterobj) && get_class($filterobj) == 'CommandeFournisseur') {
11828
				$sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'order_supplier'";
11829
				if ($filterobj->id) {
11830
					$sql .= " AND a.fk_element = ".((int) $filterobj->id);
11831
				}
11832
			} elseif (is_object($filterobj) && get_class($filterobj) == 'Product') {
11833
				$sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'product'";
11834
				if ($filterobj->id) {
11835
					$sql .= " AND a.fk_element = ".((int) $filterobj->id);
11836
				}
11837
			} elseif (is_object($filterobj) && get_class($filterobj) == 'Ticket') {
11838
				$sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'ticket'";
11839
				if ($filterobj->id) {
11840
					$sql .= " AND a.fk_element = ".((int) $filterobj->id);
11841
				}
11842
			} elseif (is_object($filterobj) && get_class($filterobj) == 'BOM') {
11843
				$sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'bom'";
11844
				if ($filterobj->id) {
11845
					$sql .= " AND a.fk_element = ".((int) $filterobj->id);
11846
				}
11847
			} elseif (is_object($filterobj) && get_class($filterobj) == 'Contrat') {
11848
				$sql .= " AND a.fk_element = o.rowid AND a.elementtype = 'contract'";
11849
				if ($filterobj->id) {
11850
					$sql .= " AND a.fk_element = ".((int) $filterobj->id);
11851
				}
11852
			}
11853
		}
11854
11855
		// Condition on actioncode
11856
		if (!empty($actioncode)) {
11857
			if (empty($conf->global->AGENDA_USE_EVENT_TYPE)) {
11858
				if ($actioncode == 'AC_NON_AUTO') {
11859
					$sql .= " AND c.type != 'systemauto'";
11860
				} elseif ($actioncode == 'AC_ALL_AUTO') {
11861
					$sql .= " AND c.type = 'systemauto'";
11862
				} else {
11863
					if ($actioncode == 'AC_OTH') {
11864
						$sql .= " AND c.type != 'systemauto'";
11865
					} elseif ($actioncode == 'AC_OTH_AUTO') {
11866
						$sql .= " AND c.type = 'systemauto'";
11867
					}
11868
				}
11869
			} else {
11870
				if ($actioncode == 'AC_NON_AUTO') {
11871
					$sql .= " AND c.type != 'systemauto'";
11872
				} elseif ($actioncode == 'AC_ALL_AUTO') {
11873
					$sql .= " AND c.type = 'systemauto'";
11874
				} else {
11875
					$sql .= " AND c.code = '".$db->escape($actioncode)."'";
11876
				}
11877
			}
11878
		}
11879
		if ($donetodo == 'todo') {
11880
			$sql .= " AND ((a.percent >= 0 AND a.percent < 100) OR (a.percent = -1 AND a.datep > '".$db->idate($now)."'))";
11881
		} elseif ($donetodo == 'done') {
11882
			$sql .= " AND (a.percent = 100 OR (a.percent = -1 AND a.datep <= '".$db->idate($now)."'))";
11883
		}
11884
		if (is_array($filters) && $filters['search_agenda_label']) {
11885
			$sql .= natural_search('a.label', $filters['search_agenda_label']);
11886
		}
11887
	}
11888
11889
	// Add also event from emailings. TODO This should be replaced by an automatic event ? May be it's too much for very large emailing.
11890
	if (isModEnabled('mailing') && !empty($objcon->email)
11891
		&& (empty($actioncode) || $actioncode == 'AC_OTH_AUTO' || $actioncode == 'AC_EMAILING')) {
11892
		$langs->load("mails");
11893
11894
		$sql2 = "SELECT m.rowid as id, m.titre as label, mc.date_envoi as dp, mc.date_envoi as dp2, '100' as percent, 'mailing' as type";
11895
		$sql2 .= ", null as fk_element, '' as elementtype, null as contact_id";
11896
		$sql2 .= ", 'AC_EMAILING' as acode, '' as alabel, '' as apicto";
11897
		$sql2 .= ", u.rowid as user_id, u.login as user_login, u.photo as user_photo, u.firstname as user_firstname, u.lastname as user_lastname"; // User that valid action
11898
		if (is_object($filterobj) && get_class($filterobj) == 'Societe') {
11899
			$sql2 .= ", '' as lastname, '' as firstname";
11900
		} elseif (is_object($filterobj) && get_class($filterobj) == 'Adherent') {
11901
			$sql2 .= ", '' as lastname, '' as firstname";
11902
		} elseif (is_object($filterobj) && get_class($filterobj) == 'CommandeFournisseur') {
11903
			$sql2 .= ", '' as ref";
11904
		} elseif (is_object($filterobj) && get_class($filterobj) == 'Product') {
11905
			$sql2 .= ", '' as ref";
11906
		} elseif (is_object($filterobj) && get_class($filterobj) == 'Ticket') {
11907
			$sql2 .= ", '' as ref";
11908
		}
11909
		$sql2 .= " FROM ".MAIN_DB_PREFIX."mailing as m, ".MAIN_DB_PREFIX."mailing_cibles as mc, ".MAIN_DB_PREFIX."user as u";
11910
		$sql2 .= " WHERE mc.email = '".$db->escape($objcon->email)."'"; // Search is done on email.
11911
		$sql2 .= " AND mc.statut = 1";
11912
		$sql2 .= " AND u.rowid = m.fk_user_valid";
11913
		$sql2 .= " AND mc.fk_mailing=m.rowid";
11914
	}
11915
11916
	if (!empty($sql) && !empty($sql2)) {
11917
		$sql = $sql." UNION ".$sql2;
11918
	} elseif (empty($sql) && !empty($sql2)) {
11919
		$sql = $sql2;
11920
	}
11921
11922
	// TODO Add limit in nb of results
11923
	if ($sql) {	// May not be defined if module Agenda is not enabled and mailing module disabled too
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $sql does not seem to be defined for all execution paths leading up to this point.
Loading history...
11924
		$sql .= $db->order($sortfield_new, $sortorder);
11925
11926
		dol_syslog("function.lib::show_actions_messaging", LOG_DEBUG);
11927
		$resql = $db->query($sql);
11928
		if ($resql) {
11929
			$i = 0;
11930
			$num = $db->num_rows($resql);
11931
11932
			while ($i < $num) {
11933
				$obj = $db->fetch_object($resql);
11934
11935
				if ($obj->type == 'action') {
11936
					$contactaction = new ActionComm($db);
11937
					$contactaction->id = $obj->id;
11938
					$result = $contactaction->fetchResources();
11939
					if ($result < 0) {
11940
						dol_print_error($db);
11941
						setEventMessage("actions.lib::show_actions_messaging Error fetch ressource", 'errors');
11942
					}
11943
11944
					//if ($donetodo == 'todo') $sql.= " AND ((a.percent >= 0 AND a.percent < 100) OR (a.percent = -1 AND a.datep > '".$db->idate($now)."'))";
11945
					//elseif ($donetodo == 'done') $sql.= " AND (a.percent = 100 OR (a.percent = -1 AND a.datep <= '".$db->idate($now)."'))";
11946
					$tododone = '';
11947
					if (($obj->percent >= 0 and $obj->percent < 100) || ($obj->percent == -1 && $obj->dp > $now)) {
11948
						$tododone = 'todo';
11949
					}
11950
11951
					$histo[$numaction] = array(
11952
						'type'=>$obj->type,
11953
						'tododone'=>$tododone,
11954
						'id'=>$obj->id,
11955
						'datestart'=>$db->jdate($obj->dp),
11956
						'dateend'=>$db->jdate($obj->dp2),
11957
						'note'=>$obj->label,
11958
						'message'=>$obj->message,
11959
						'percent'=>$obj->percent,
11960
11961
						'userid'=>$obj->user_id,
11962
						'login'=>$obj->user_login,
11963
						'userfirstname'=>$obj->user_firstname,
11964
						'userlastname'=>$obj->user_lastname,
11965
						'userphoto'=>$obj->user_photo,
11966
11967
						'contact_id'=>$obj->fk_contact,
11968
						'socpeopleassigned' => $contactaction->socpeopleassigned,
11969
						'lastname' => (empty($obj->lastname) ? '' : $obj->lastname),
11970
						'firstname' => (empty($obj->firstname) ? '' : $obj->firstname),
11971
						'fk_element'=>$obj->fk_element,
11972
						'elementtype'=>$obj->elementtype,
11973
						// Type of event
11974
						'acode'=>$obj->acode,
11975
						'alabel'=>$obj->alabel,
11976
						'libelle'=>$obj->alabel, // deprecated
11977
						'apicto'=>$obj->apicto
11978
					);
11979
				} else {
11980
					$histo[$numaction] = array(
11981
						'type'=>$obj->type,
11982
						'tododone'=>'done',
11983
						'id'=>$obj->id,
11984
						'datestart'=>$db->jdate($obj->dp),
11985
						'dateend'=>$db->jdate($obj->dp2),
11986
						'note'=>$obj->label,
11987
						'message'=>$obj->message,
11988
						'percent'=>$obj->percent,
11989
						'acode'=>$obj->acode,
11990
11991
						'userid'=>$obj->user_id,
11992
						'login'=>$obj->user_login,
11993
						'userfirstname'=>$obj->user_firstname,
11994
						'userlastname'=>$obj->user_lastname,
11995
						'userphoto'=>$obj->user_photo
11996
					);
11997
				}
11998
11999
				$numaction++;
12000
				$i++;
12001
			}
12002
		} else {
12003
			dol_print_error($db);
12004
		}
12005
	}
12006
12007
	// Set $out to show events
12008
	$out = '';
12009
12010
	if (!isModEnabled('agenda')) {
12011
		$langs->loadLangs(array("admin", "errors"));
12012
		$out = info_admin($langs->trans("WarningModuleXDisabledSoYouMayMissEventHere", $langs->transnoentitiesnoconv("Module2400Name")), 0, 0, 'warning');
12013
	}
12014
12015
	if (isModEnabled('agenda') || (isModEnabled('mailing') && !empty($objcon->email))) {
12016
		$delay_warning = $conf->global->MAIN_DELAY_ACTIONS_TODO * 24 * 60 * 60;
12017
12018
		require_once DOL_DOCUMENT_ROOT.'/comm/action/class/actioncomm.class.php';
12019
		include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
12020
		require_once DOL_DOCUMENT_ROOT.'/core/class/html.formactions.class.php';
12021
		require_once DOL_DOCUMENT_ROOT.'/contact/class/contact.class.php';
12022
12023
		$formactions = new FormActions($db);
12024
12025
		$actionstatic = new ActionComm($db);
12026
		$userstatic = new User($db);
12027
		$contactstatic = new Contact($db);
12028
		$userGetNomUrlCache = array();
12029
12030
		$out .= '<div class="filters-container" >';
12031
		$out .= '<form name="listactionsfilter" class="listactionsfilter" action="'.$_SERVER["PHP_SELF"].'" method="POST">';
12032
		$out .= '<input type="hidden" name="token" value="'.newToken().'">';
12033
12034
		if ($objcon && get_class($objcon) == 'Contact' &&
12035
			(is_null($filterobj) || get_class($filterobj) == 'Societe')) {
12036
			$out .= '<input type="hidden" name="id" value="'.$objcon->id.'" />';
12037
		} else {
12038
			$out .= '<input type="hidden" name="id" value="'.$filterobj->id.'" />';
12039
		}
12040
		if ($filterobj && get_class($filterobj) == 'Societe') {
12041
			$out .= '<input type="hidden" name="socid" value="'.$filterobj->id.'" />';
12042
		}
12043
12044
		$out .= "\n";
12045
12046
		$out .= '<div class="div-table-responsive-no-min">';
12047
		$out .= '<table class="noborder borderbottom centpercent">';
12048
12049
		$out .= '<tr class="liste_titre">';
12050
12051
		$out .= getTitleFieldOfList('Date', 0, $_SERVER["PHP_SELF"], 'a.datep', '', $param, '', $sortfield, $sortorder, '')."\n";
12052
12053
		$out .= '<th class="liste_titre"><strong class="hideonsmartphone">'.$langs->trans("Search").' : </strong></th>';
12054
		if ($donetodo) {
12055
			$out .= '<th class="liste_titre"></th>';
12056
		}
12057
		$out .= '<th class="liste_titre">';
12058
		$out .= '<span class="fas fa-square inline-block fawidth30" style=" color: #ddd;" title="'.$langs->trans("ActionType").'"></span>';
12059
		//$out .= img_picto($langs->trans("Type"), 'type');
12060
		$out .= $formactions->select_type_actions($actioncode, "actioncode", '', empty($conf->global->AGENDA_USE_EVENT_TYPE) ? 1 : -1, 0, 0, 1, 'minwidth200imp');
12061
		$out .= '</th>';
12062
		$out .= '<th class="liste_titre maxwidth100onsmartphone">';
12063
		$out .= '<input type="text" class="maxwidth100onsmartphone" name="search_agenda_label" value="'.$filters['search_agenda_label'].'" placeholder="'.$langs->trans("Label").'">';
12064
		$out .= '</th>';
12065
12066
		$out .= '<th class="liste_titre width50 middle">';
12067
		$searchpicto = $form->showFilterAndCheckAddButtons($massactionbutton ? 1 : 0, 'checkforselect', 1);
12068
		$out .= $searchpicto;
12069
		$out .= '</th>';
12070
		$out .= '</tr>';
12071
12072
12073
		$out .= '</table>';
12074
12075
		$out .= '</form>';
12076
		$out .= '</div>';
12077
12078
		$out .= "\n";
12079
12080
		$out .= '<ul class="timeline">';
12081
12082
		if ($donetodo) {
12083
			$tmp = '';
12084
			if (get_class($filterobj) == 'Societe') {
12085
				$tmp .= '<a href="'.DOL_URL_ROOT.'/comm/action/list.php?mode=show_list&socid='.$filterobj->id.'&status=done">';
12086
			}
12087
			$tmp .= ($donetodo != 'done' ? $langs->trans("ActionsToDoShort") : '');
12088
			$tmp .= ($donetodo != 'done' && $donetodo != 'todo' ? ' / ' : '');
12089
			$tmp .= ($donetodo != 'todo' ? $langs->trans("ActionsDoneShort") : '');
12090
			//$out.=$langs->trans("ActionsToDoShort").' / '.$langs->trans("ActionsDoneShort");
12091
			if (get_class($filterobj) == 'Societe') {
12092
				$tmp .= '</a>';
12093
			}
12094
			$out .= getTitleFieldOfList($tmp);
12095
		}
12096
12097
12098
		//require_once DOL_DOCUMENT_ROOT.'/comm/action/class/cactioncomm.class.php';
12099
		//$caction=new CActionComm($db);
12100
		//$arraylist=$caction->liste_array(1, 'code', '', (empty($conf->global->AGENDA_USE_EVENT_TYPE)?1:0), '', 1);
12101
12102
		$actualCycleDate = false;
12103
12104
		// Loop on each event to show it
12105
		foreach ($histo as $key => $value) {
12106
			$actionstatic->fetch($histo[$key]['id']); // TODO Do we need this, we already have a lot of data of line into $histo
12107
12108
			$actionstatic->type_picto = $histo[$key]['apicto'];
12109
			$actionstatic->type_code = $histo[$key]['acode'];
12110
12111
			$url = DOL_URL_ROOT.'/comm/action/card.php?id='.$histo[$key]['id'];
12112
12113
			$tmpa = dol_getdate($histo[$key]['datestart'], false);
12114
			if ($actualCycleDate !== $tmpa['year'].'-'.$tmpa['yday']) {
12115
				$actualCycleDate = $tmpa['year'].'-'.$tmpa['yday'];
12116
				$out .= '<!-- timeline time label -->';
12117
				$out .= '<li class="time-label">';
12118
				$out .= '<span class="timeline-badge-date">';
12119
				$out .= dol_print_date($histo[$key]['datestart'], 'daytext', 'tzuserrel', $langs);
12120
				$out .= '</span>';
12121
				$out .= '</li>';
12122
				$out .= '<!-- /.timeline-label -->';
12123
			}
12124
12125
12126
			$out .= '<!-- timeline item -->'."\n";
12127
			$out .= '<li class="timeline-code-'.strtolower($actionstatic->code).'">';
12128
12129
			$out .= getTimelineIcon($actionstatic, $histo, $key);
12130
12131
			$out .= '<div class="timeline-item">'."\n";
12132
12133
			$out .= '<span class="timeline-header-action">';
12134
12135
			if (isset($histo[$key]['type']) && $histo[$key]['type'] == 'mailing') {
12136
				$out .= '<a class="timeline-btn" href="'.DOL_URL_ROOT.'/comm/mailing/card.php?id='.$histo[$key]['id'].'">'.img_object($langs->trans("ShowEMailing"), "email").' ';
12137
				$out .= $histo[$key]['id'];
12138
				$out .= '</a> ';
12139
			} else {
12140
				$out .= $actionstatic->getNomUrl(1, -1, 'valignmiddle').' ';
12141
			}
12142
12143
			if ($user->hasRight('agenda', 'allactions', 'create') ||
12144
				(($actionstatic->authorid == $user->id || $actionstatic->userownerid == $user->id) && !empty($user->rights->agenda->myactions->create))) {
12145
				$out .= '<a class="timeline-btn" href="'.DOL_MAIN_URL_ROOT.'/comm/action/card.php?action=edit&token='.newToken().'&id='.$actionstatic->id.'&backtopage='.urlencode($_SERVER["PHP_SELF"].'?'.$param).'"><i class="fa fa-pencil" title="'.$langs->trans("Modify").'" ></i></a>';
12146
			}
12147
12148
			$out .= '</span>';
12149
			// Date
12150
			$out .= '<span class="time"><i class="fa fa-clock-o"></i> ';
12151
			$out .= dol_print_date($histo[$key]['datestart'], 'dayhour', 'tzuserrel');
12152
			if ($histo[$key]['dateend'] && $histo[$key]['dateend'] != $histo[$key]['datestart']) {
12153
				$tmpa = dol_getdate($histo[$key]['datestart'], true);
12154
				$tmpb = dol_getdate($histo[$key]['dateend'], true);
12155
				if ($tmpa['mday'] == $tmpb['mday'] && $tmpa['mon'] == $tmpb['mon'] && $tmpa['year'] == $tmpb['year']) {
12156
					$out .= '-'.dol_print_date($histo[$key]['dateend'], 'hour', 'tzuserrel');
12157
				} else {
12158
					$out .= '-'.dol_print_date($histo[$key]['dateend'], 'dayhour', 'tzuserrel');
12159
				}
12160
			}
12161
			$late = 0;
12162
			if ($histo[$key]['percent'] == 0 && $histo[$key]['datestart'] && $histo[$key]['datestart'] < ($now - $delay_warning)) {
12163
				$late = 1;
12164
			}
12165
			if ($histo[$key]['percent'] == 0 && !$histo[$key]['datestart'] && $histo[$key]['dateend'] && $histo[$key]['datestart'] < ($now - $delay_warning)) {
12166
				$late = 1;
12167
			}
12168
			if ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100 && $histo[$key]['dateend'] && $histo[$key]['dateend'] < ($now - $delay_warning)) {
12169
				$late = 1;
12170
			}
12171
			if ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100 && !$histo[$key]['dateend'] && $histo[$key]['datestart'] && $histo[$key]['datestart'] < ($now - $delay_warning)) {
12172
				$late = 1;
12173
			}
12174
			if ($late) {
12175
				$out .= img_warning($langs->trans("Late")).' ';
12176
			}
12177
			$out .= "</span>\n";
12178
12179
			// Ref
12180
			$out .= '<h3 class="timeline-header">';
12181
12182
			// Author of event
12183
			$out .= '<span class="messaging-author">';
12184
			if ($histo[$key]['userid'] > 0) {
12185
				if (!isset($userGetNomUrlCache[$histo[$key]['userid']])) { // is in cache ?
12186
					$userstatic->fetch($histo[$key]['userid']);
12187
					$userGetNomUrlCache[$histo[$key]['userid']] = $userstatic->getNomUrl(-1, '', 0, 0, 16, 0, 'firstelselast', '');
12188
				}
12189
				$out .= $userGetNomUrlCache[$histo[$key]['userid']];
12190
			}
12191
			$out .= '</span>';
12192
12193
			// Title
12194
			$out .= ' <span class="messaging-title">';
12195
12196
			if (preg_match('/^TICKET_MSG/', $actionstatic->code)) {
12197
				$out .= $langs->trans('TicketNewMessage');
12198
			} elseif (preg_match('/^TICKET_MSG_PRIVATE/', $actionstatic->code)) {
12199
				$out .= $langs->trans('TicketNewMessage').' <em>('.$langs->trans('Private').')</em>';
12200
			} else {
12201
				if (isset($histo[$key]['type']) && $histo[$key]['type'] == 'action') {
12202
					$transcode = $langs->trans("Action".$histo[$key]['acode']);
12203
					$libelle = ($transcode != "Action".$histo[$key]['acode'] ? $transcode : $histo[$key]['alabel']);
12204
					$libelle = $histo[$key]['note'];
12205
					$actionstatic->id = $histo[$key]['id'];
12206
					$out .= dol_trunc($libelle, 120);
12207
				}
12208
				if (isset($histo[$key]['type']) && $histo[$key]['type'] == 'mailing') {
12209
					$out .= '<a href="'.DOL_URL_ROOT.'/comm/mailing/card.php?id='.$histo[$key]['id'].'">'.img_object($langs->trans("ShowEMailing"), "email").' ';
12210
					$transcode = $langs->trans("Action".$histo[$key]['acode']);
12211
					$libelle = ($transcode != "Action".$histo[$key]['acode'] ? $transcode : 'Send mass mailing');
12212
					$out .= dol_trunc($libelle, 120);
12213
				}
12214
			}
12215
12216
			$out .= '</span>';
12217
12218
			$out .= '</h3>';
12219
12220
			if (!empty($histo[$key]['message'])
12221
				&& $actionstatic->code != 'AC_TICKET_CREATE'
12222
				&& $actionstatic->code != 'AC_TICKET_MODIFY'
12223
			) {
12224
				$out .= '<div class="timeline-body">';
12225
				$out .= $histo[$key]['message'];
12226
				$out .= '</div>';
12227
			}
12228
12229
			// Timeline footer
12230
			$footer = '';
12231
12232
			// Contact for this action
12233
			if (isset($histo[$key]['socpeopleassigned']) && is_array($histo[$key]['socpeopleassigned']) && count($histo[$key]['socpeopleassigned']) > 0) {
12234
				$contactList = '';
12235
				foreach ($histo[$key]['socpeopleassigned'] as $cid => $Tab) {
12236
					$contact = new Contact($db);
12237
					$result = $contact->fetch($cid);
12238
12239
					if ($result < 0) {
12240
						dol_print_error($db, $contact->error);
12241
					}
12242
12243
					if ($result > 0) {
12244
						$contactList .= !empty($contactList) ? ', ' : '';
12245
						$contactList .= $contact->getNomUrl(1);
12246
						if (isset($histo[$key]['acode']) && $histo[$key]['acode'] == 'AC_TEL') {
12247
							if (!empty($contact->phone_pro)) {
12248
								$contactList .= '('.dol_print_phone($contact->phone_pro).')';
12249
							}
12250
						}
12251
					}
12252
				}
12253
12254
				$footer .= $langs->trans('ActionOnContact').' : '.$contactList;
12255
			} elseif (empty($objcon->id) && isset($histo[$key]['contact_id']) && $histo[$key]['contact_id'] > 0) {
12256
				$contact = new Contact($db);
12257
				$result = $contact->fetch($histo[$key]['contact_id']);
12258
12259
				if ($result < 0) {
12260
					dol_print_error($db, $contact->error);
12261
				}
12262
12263
				if ($result > 0) {
12264
					$footer .= $contact->getNomUrl(1);
12265
					if (isset($histo[$key]['acode']) && $histo[$key]['acode'] == 'AC_TEL') {
12266
						if (!empty($contact->phone_pro)) {
12267
							$footer .= '('.dol_print_phone($contact->phone_pro).')';
12268
						}
12269
					}
12270
				}
12271
			}
12272
12273
			$documents = getActionCommEcmList($actionstatic);
12274
			if (!empty($documents)) {
12275
				$footer .= '<div class="timeline-documents-container">';
12276
				foreach ($documents as $doc) {
12277
					$footer .= '<span id="document_'.$doc->id.'" class="timeline-documents" ';
12278
					$footer .= ' data-id="'.$doc->id.'" ';
12279
					$footer .= ' data-path="'.$doc->filepath.'"';
12280
					$footer .= ' data-filename="'.dol_escape_htmltag($doc->filename).'" ';
12281
					$footer .= '>';
12282
12283
					$filePath = DOL_DATA_ROOT.'/'.$doc->filepath.'/'.$doc->filename;
12284
					$mime = dol_mimetype($filePath);
12285
					$file = $actionstatic->id.'/'.$doc->filename;
12286
					$thumb = $actionstatic->id.'/thumbs/'.substr($doc->filename, 0, strrpos($doc->filename, '.')).'_mini'.substr($doc->filename, strrpos($doc->filename, '.'));
12287
					$doclink = dol_buildpath('document.php', 1).'?modulepart=actions&attachment=0&file='.urlencode($file).'&entity='.$conf->entity;
12288
					$viewlink = dol_buildpath('viewimage.php', 1).'?modulepart=actions&file='.urlencode($thumb).'&entity='.$conf->entity;
12289
12290
					$mimeAttr = ' mime="'.$mime.'" ';
12291
					$class = '';
12292
					if (in_array($mime, array('image/png', 'image/jpeg', 'application/pdf'))) {
12293
						$class .= ' documentpreview';
12294
					}
12295
12296
					$footer .= '<a href="'.$doclink.'" class="btn-link '.$class.'" target="_blank" rel="noopener noreferrer" '.$mimeAttr.' >';
12297
					$footer .= img_mime($filePath).' '.$doc->filename;
12298
					$footer .= '</a>';
12299
12300
					$footer .= '</span>';
12301
				}
12302
				$footer .= '</div>';
12303
			}
12304
12305
			if (!empty($footer)) {
12306
				$out .= '<div class="timeline-footer">'.$footer.'</div>';
12307
			}
12308
12309
			$out .= '</div>'."\n"; // end timeline-item
12310
12311
			$out .= '</li>';
12312
			$out .= '<!-- END timeline item -->';
12313
12314
			$i++;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $i does not seem to be defined for all execution paths leading up to this point.
Loading history...
12315
		}
12316
12317
		$out .= "</ul>\n";
12318
12319
		if (empty($histo)) {
12320
			$out .= '<span class="opacitymedium">'.$langs->trans("NoRecordFound").'</span>';
12321
		}
12322
	}
12323
12324
	if ($noprint) {
12325
		return $out;
12326
	} else {
12327
		print $out;
12328
	}
12329
}
12330
12331
/**
12332
 * Helper function that combines values of a dolibarr DatePicker (such as Form::selectDate) for year, month, day (and
12333
 * optionally hour, minute, second) fields to return a timestamp.
12334
 *
12335
 * @param string $prefix Prefix used to build the date selector (for instance using Form::selectDate)
12336
 * @param string $hourTime  'getpost' to include hour, minute, second values from the HTTP request, 'XX:YY:ZZ' to set
12337
 *                          hour, minute, second respectively (for instance '23:59:59')
12338
 * @param string $gm Passed to dol_mktime
12339
 * @return int|string  Date as a timestamp, '' or false if error
12340
 */
12341
function GETPOSTDATE($prefix, $hourTime = '', $gm = 'auto')
12342
{
12343
	if ($hourTime === 'getpost') {
12344
		$hour   = GETPOSTINT($prefix . 'hour');
12345
		$minute = GETPOSTINT($prefix . 'minute');
12346
		$second = GETPOSTINT($prefix . 'second');
12347
	} elseif (preg_match('/^(\d\d):(\d\d):(\d\d)$/', $hourTime, $m)) {
12348
		$hour   = intval($m[1]);
12349
		$minute = intval($m[2]);
12350
		$second = intval($m[3]);
12351
	} else {
12352
		$hour = $minute = $second = 0;
12353
	}
12354
	// normalize out of range values
12355
	$hour = min($hour, 23);
12356
	$minute = min($minute, 59);
12357
	$second = min($second, 59);
12358
	return dol_mktime($hour, $minute, $second, GETPOSTINT($prefix . 'month'), GETPOSTINT($prefix . 'day'), GETPOSTINT($prefix . 'year'), $gm);
12359
}
12360
12361
/**
12362
 * Helper function that combines values of a dolibarr DatePicker (such as Form::selectDate) for year, month, day (and
12363
 * optionally hour, minute, second) fields to return a a portion of URL reproducing the values from the current HTTP
12364
 * request.
12365
 *
12366
 * @param string $prefix Prefix used to build the date selector (for instance using Form::selectDate)
12367
 * @param int $timestamp If null, the timestamp will be created from request data
12368
 * @param bool $hourTime If timestamp is null, will be passed to GETPOSTDATE to construct the timestamp
12369
 * @param bool $gm If timestamp is null, will be passed to GETPOSTDATE to construct the timestamp
12370
 * @return string Portion of URL with query parameters for the specified date
12371
 */
12372
function buildParamDate($prefix, $timestamp = null, $hourTime = '', $gm = 'auto')
12373
{
12374
	if ($timestamp === null) $timestamp = GETPOSTDATE($prefix, $hourTime, $gm);
12375
	$TParam = array(
12376
		$prefix . 'day'   => intval(dol_print_date($timestamp, '%d')),
12377
		$prefix . 'month' => intval(dol_print_date($timestamp, '%m')),
12378
		$prefix . 'year'  => intval(dol_print_date($timestamp, '%Y')),
12379
	);
12380
	if ($hourTime === 'getpost' || ($timestamp !== null && dol_print_date($timestamp, '%H:%M:%S') !== '00:00:00')) {
12381
		$TParam = array_merge($TParam, array(
12382
			$prefix . 'hour'   => intval(dol_print_date($timestamp, '%H')),
12383
			$prefix . 'minute' => intval(dol_print_date($timestamp, '%M')),
12384
			$prefix . 'second' => intval(dol_print_date($timestamp, '%S'))
12385
		));
12386
	}
12387
12388
	return '&' . http_build_query($TParam);
12389
}
12390