Passed
Branch develop (c00bef)
by
unknown
28:39
created

autoOrManual()   C

Complexity

Conditions 17
Paths 50

Size

Total Lines 40
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 27
nc 50
nop 3
dl 0
loc 40
rs 5.2166
c 0
b 0
f 0

1 Method

Rating   Name   Duplication   Size   Complexity  
A price2fec() 0 21 6

How to fix   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) 2008-2011  Laurent Destailleur         <[email protected]>
3
 * Copyright (C) 2008-2012  Regis Houssin               <[email protected]>
4
 * Copyright (C) 2008       Raphael Bertrand (Resultic) <[email protected]>
5
 * Copyright (C) 2014-2016  Marcos García               <[email protected]>
6
 * Copyright (C) 2015       Ferran Marcet               <[email protected]>
7
 * Copyright (C) 2015-2016  Raphaël Doursenaud          <[email protected]>
8
 * Copyright (C) 2017       Juanjo Menent               <[email protected]>
9
 *
10
 * This program is free software; you can redistribute it and/or modify
11
 * it under the terms of the GNU General Public License as published by
12
 * the Free Software Foundation; either version 3 of the License, or
13
 * (at your option) any later version.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
 * GNU General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU General Public License
21
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
22
 * or see https://www.gnu.org/
23
 */
24
25
/**
26
 *	\file			htdocs/core/lib/functions2.lib.php
27
 *	\brief			A set of functions for Dolibarr
28
 *					This file contains all rare functions.
29
 */
30
31
// Enable this line to trace path when function is called.
32
//print xdebug_print_function_stack('Functions2.lib was called');exit;
33
34
/**
35
 * Same function than javascript unescape() function but in PHP.
36
 *
37
 * @param 	string	$source		String to decode
38
 * @return	string				Unescaped string
39
 */
40
function jsUnEscape($source)
41
{
42
	$decodedStr = "";
43
	$pos = 0;
44
	$len = strlen($source);
45
	while ($pos < $len) {
46
		$charAt = substr($source, $pos, 1);
47
		if ($charAt == '%') {
48
			$pos++;
49
			$charAt = substr($source, $pos, 1);
50
			if ($charAt == 'u') {
51
				// we got a unicode character
52
				$pos++;
53
				$unicodeHexVal = substr($source, $pos, 4);
54
				$unicode = hexdec($unicodeHexVal);
55
				$entity = "&#".$unicode.';';
56
				$decodedStr .= utf8_encode($entity);
57
				$pos += 4;
58
			} else {
59
				// we have an escaped ascii character
60
				$hexVal = substr($source, $pos, 2);
61
				$decodedStr .= chr(hexdec($hexVal));
62
				$pos += 2;
63
			}
64
		} else {
65
			$decodedStr .= $charAt;
66
			$pos++;
67
		}
68
	}
69
	return dol_html_entity_decode($decodedStr, ENT_COMPAT | ENT_HTML5);
70
}
71
72
73
/**
74
 * Return list of modules directories. We detect directories that contains a subdirectory /core/modules
75
 * We discard directory modules that contains 'disabled' into their name.
76
 *
77
 * @param	string	$subdir		Sub directory (Example: '/mailings')
78
 * @return	array				Array of directories that can contains module descriptors
79
 */
80
function dolGetModulesDirs($subdir = '')
81
{
82
	global $conf;
83
84
	$modulesdir = array();
85
86
	foreach ($conf->file->dol_document_root as $type => $dirroot) {
87
		// Default core/modules dir
88
		if ($type === 'main') {
89
			$modulesdir[$dirroot.'/core/modules'.$subdir.'/'] = $dirroot.'/core/modules'.$subdir.'/';
90
		}
91
92
		// Scan dir from external modules
93
		$handle = @opendir($dirroot);
94
		if (is_resource($handle)) {
95
			while (($file = readdir($handle)) !== false) {
96
				if (preg_match('/disabled/', $file)) {
97
					continue; // We discard module if it contains disabled into name.
98
				}
99
100
				if (is_dir($dirroot.'/'.$file) && substr($file, 0, 1) <> '.' && substr($file, 0, 3) <> 'CVS' && $file != 'includes') {
101
					if (is_dir($dirroot.'/'.$file.'/core/modules'.$subdir.'/')) {
102
						$modulesdir[$dirroot.'/'.$file.'/core/modules'.$subdir.'/'] = $dirroot.'/'.$file.'/core/modules'.$subdir.'/';
103
					}
104
				}
105
			}
106
			closedir($handle);
107
		}
108
	}
109
	return $modulesdir;
110
}
111
112
113
/**
114
 *  Try to guess default paper format according to language into $langs
115
 *
116
 *	@param		Translate	$outputlangs		Output lang to use to autodetect output format if setup not done
117
 *	@return		string							Default paper format code
118
 */
119
function dol_getDefaultFormat(Translate $outputlangs = null)
120
{
121
	global $langs;
122
123
	$selected = 'EUA4';
124
	if (!$outputlangs) {
125
		$outputlangs = $langs;
126
	}
127
128
	if ($outputlangs->defaultlang == 'ca_CA') {
129
		$selected = 'CAP4'; // Canada
130
	}
131
	if ($outputlangs->defaultlang == 'en_US') {
132
		$selected = 'USLetter'; // US
133
	}
134
	return $selected;
135
}
136
137
/**
138
 *  Output content of a file $filename in version of current language (otherwise may use an alternate language)
139
 *
140
 *  @param	Translate	$langs          Object language to use for output
141
 *  @param  string		$filename       Relative filename to output
142
 *  @param  int			$searchalt      1=Search also in alternative languages
143
 *	@return	boolean						true if OK, false if KO
144
 */
145
function dol_print_file($langs, $filename, $searchalt = 0)
146
{
147
	global $conf;
148
149
	// Test if file is in lang directory
150
	foreach ($langs->dir as $searchdir) {
151
		$formfile = ($searchdir."/langs/".$langs->defaultlang."/".$filename);
152
		dol_syslog('functions2::dol_print_file search file '.$formfile, LOG_DEBUG);
153
		if (is_readable($formfile)) {
154
			$content = file_get_contents($formfile);
155
			$isutf8 = utf8_check($content);
156
			if (!$isutf8 && $conf->file->character_set_client == 'UTF-8') {
157
				print utf8_encode($content);
158
			} elseif ($isutf8 && $conf->file->character_set_client == 'ISO-8859-1') {
159
				print utf8_decode($content);
160
			} else {
161
				print $content;
162
			}
163
			return true;
164
		} else {
165
			dol_syslog('functions2::dol_print_file not found', LOG_DEBUG);
166
		}
167
168
		if ($searchalt) {
169
			// Test si fichier dans repertoire de la langue alternative
170
			if ($langs->defaultlang != "en_US") {
171
				$formfilealt = $searchdir."/langs/en_US/".$filename;
172
			} else {
173
				$formfilealt = $searchdir."/langs/fr_FR/".$filename;
174
			}
175
			dol_syslog('functions2::dol_print_file search alt file '.$formfilealt, LOG_DEBUG);
176
			//print 'getcwd='.getcwd().' htmlfilealt='.$formfilealt.' X '.file_exists(getcwd().'/'.$formfilealt);
177
			if (is_readable($formfilealt)) {
178
				$content = file_get_contents($formfilealt);
179
				$isutf8 = utf8_check($content);
180
				if (!$isutf8 && $conf->file->character_set_client == 'UTF-8') {
181
					print utf8_encode($content);
182
				} elseif ($isutf8 && $conf->file->character_set_client == 'ISO-8859-1') {
183
					print utf8_decode($content);
184
				} else {
185
					print $content;
186
				}
187
				return true;
188
			} else {
189
				dol_syslog('functions2::dol_print_file not found', LOG_DEBUG);
190
			}
191
		}
192
	}
193
194
	return false;
195
}
196
197
/**
198
 *	Show informations on an object
199
 *  TODO Move this into html.formother
200
 *
201
 *	@param	object	$object			Objet to show
202
 *  @param  int     $usetable       Output into a table
203
 *	@return	void
204
 */
205
function dol_print_object_info($object, $usetable = 0)
206
{
207
	global $langs, $db;
208
209
	// Load translation files required by the page
210
	$langs->loadLangs(array('other', 'admin'));
211
212
	include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
213
214
	$deltadateforserver = getServerTimeZoneInt('now');
215
	$deltadateforclient = ((int) $_SESSION['dol_tz'] + (int) $_SESSION['dol_dst']);
216
	//$deltadateforcompany=((int) $_SESSION['dol_tz'] + (int) $_SESSION['dol_dst']);
217
	$deltadateforuser = round($deltadateforclient - $deltadateforserver);
218
	//print "x".$deltadateforserver." - ".$deltadateforclient." - ".$deltadateforuser;
219
220
	if ($usetable) {
221
		print '<table class="border tableforfield centpercent">';
222
	}
223
224
	// Import key
225
	if (!empty($object->import_key)) {
226
		if ($usetable) {
227
			print '<tr><td class="titlefield">';
228
		}
229
		print $langs->trans("ImportedWithSet");
230
		if ($usetable) {
231
			print '</td><td>';
232
		} else {
233
			print ': ';
234
		}
235
		print $object->import_key;
236
		if ($usetable) {
237
			print '</td></tr>';
238
		} else {
239
			print '<br>';
240
		}
241
	}
242
243
	// User creation (old method using already loaded object and not id is kept for backward compatibility)
244
	if (!empty($object->user_creation) || !empty($object->user_creation_id)) {
245
		if ($usetable) {
246
			print '<tr><td class="titlefield">';
247
		}
248
		print $langs->trans("CreatedBy");
249
		if ($usetable) {
250
			print '</td><td>';
251
		} else {
252
			print ': ';
253
		}
254
		if (is_object($object->user_creation)) {
255
			if ($object->user_creation->id) {
256
				print $object->user_creation->getNomUrl(-1, '', 0, 0, 0);
257
			} else {
258
				print $langs->trans("Unknown");
259
			}
260
		} else {
261
			$userstatic = new User($db);
262
			$userstatic->fetch($object->user_creation_id ? $object->user_creation_id : $object->user_creation);
263
			if ($userstatic->id) {
264
				print $userstatic->getNomUrl(-1, '', 0, 0, 0);
265
			} else {
266
				print $langs->trans("Unknown");
267
			}
268
		}
269
		if ($usetable) {
270
			print '</td></tr>';
271
		} else {
272
			print '<br>';
273
		}
274
	}
275
276
	// Date creation
277
	if (!empty($object->date_creation)) {
278
		if ($usetable) {
279
			print '<tr><td class="titlefield">';
280
		}
281
		print $langs->trans("DateCreation");
282
		if ($usetable) {
283
			print '</td><td>';
284
		} else {
285
			print ': ';
286
		}
287
		print dol_print_date($object->date_creation, 'dayhour', 'tzserver');
288
		if ($deltadateforuser) {
289
			print ' '.$langs->trans("CurrentHour").' &nbsp; / &nbsp; '.dol_print_date($object->date_creation, "dayhour", "tzuserrel").' &nbsp;'.$langs->trans("ClientHour");
290
		}
291
		if ($usetable) {
292
			print '</td></tr>';
293
		} else {
294
			print '<br>';
295
		}
296
	}
297
298
	// User change (old method using already loaded object and not id is kept for backward compatibility)
299
	if (!empty($object->user_modification) || !empty($object->user_modification_id)) {
300
		if ($usetable) {
301
			print '<tr><td class="titlefield">';
302
		}
303
		print $langs->trans("ModifiedBy");
304
		if ($usetable) {
305
			print '</td><td>';
306
		} else {
307
			print ': ';
308
		}
309
		if (is_object($object->user_modification)) {
310
			if ($object->user_modification->id) {
311
				print $object->user_modification->getNomUrl(-1, '', 0, 0, 0);
312
			} else {
313
				print $langs->trans("Unknown");
314
			}
315
		} else {
316
			$userstatic = new User($db);
317
			$userstatic->fetch($object->user_modification_id ? $object->user_modification_id : $object->user_modification);
318
			if ($userstatic->id) {
319
				print $userstatic->getNomUrl(-1, '', 0, 0, 0);
320
			} else {
321
				print $langs->trans("Unknown");
322
			}
323
		}
324
		if ($usetable) {
325
			print '</td></tr>';
326
		} else {
327
			print '<br>';
328
		}
329
	}
330
331
	// Date change
332
	if (!empty($object->date_modification)) {
333
		if ($usetable) {
334
			print '<tr><td class="titlefield">';
335
		}
336
		print $langs->trans("DateLastModification");
337
		if ($usetable) {
338
			print '</td><td>';
339
		} else {
340
			print ': ';
341
		}
342
		print dol_print_date($object->date_modification, 'dayhour', 'tzserver');
343
		if ($deltadateforuser) {
344
			print ' '.$langs->trans("CurrentHour").' &nbsp; / &nbsp; '.dol_print_date($object->date_modification, "dayhour", "tzuserrel").' &nbsp;'.$langs->trans("ClientHour");
345
		}
346
		if ($usetable) {
347
			print '</td></tr>';
348
		} else {
349
			print '<br>';
350
		}
351
	}
352
353
	// User validation (old method using already loaded object and not id is kept for backward compatibility)
354
	if (!empty($object->user_validation) || !empty($object->user_validation_id)) {
355
		if ($usetable) {
356
			print '<tr><td class="titlefield">';
357
		}
358
		print $langs->trans("ValidatedBy");
359
		if ($usetable) {
360
			print '</td><td>';
361
		} else {
362
			print ': ';
363
		}
364
		if (is_object($object->user_validation)) {
365
			if ($object->user_validation->id) {
366
				print $object->user_validation->getNomUrl(-1, '', 0, 0, 0);
367
			} else {
368
				print $langs->trans("Unknown");
369
			}
370
		} else {
371
			$userstatic = new User($db);
372
			$userstatic->fetch($object->user_validation_id ? $object->user_validation_id : $object->user_validation);
373
			if ($userstatic->id) {
374
				print $userstatic->getNomUrl(-1, '', 0, 0, 0);
375
			} else {
376
				print $langs->trans("Unknown");
377
			}
378
		}
379
		if ($usetable) {
380
			print '</td></tr>';
381
		} else {
382
			print '<br>';
383
		}
384
	}
385
386
	// Date validation
387
	if (!empty($object->date_validation)) {
388
		if ($usetable) {
389
			print '<tr><td class="titlefield">';
390
		}
391
		print $langs->trans("DateValidation");
392
		if ($usetable) {
393
			print '</td><td>';
394
		} else {
395
			print ': ';
396
		}
397
		print dol_print_date($object->date_validation, 'dayhour', 'tzserver');
398
		if ($deltadateforuser) {
399
			print ' '.$langs->trans("CurrentHour").' &nbsp; / &nbsp; '.dol_print_date($object->date_validation, "dayhour", 'tzuserrel').' &nbsp;'.$langs->trans("ClientHour");
400
		}
401
		if ($usetable) {
402
			print '</td></tr>';
403
		} else {
404
			print '<br>';
405
		}
406
	}
407
408
	// User approve (old method using already loaded object and not id is kept for backward compatibility)
409
	if (!empty($object->user_approve) || !empty($object->user_approve_id)) {
410
		if ($usetable) {
411
			print '<tr><td class="titlefield">';
412
		}
413
		print $langs->trans("ApprovedBy");
414
		if ($usetable) {
415
			print '</td><td>';
416
		} else {
417
			print ': ';
418
		}
419
		if (is_object($object->user_approve)) {
420
			if ($object->user_approve->id) {
421
				print $object->user_approve->getNomUrl(-1, '', 0, 0, 0);
422
			} else {
423
				print $langs->trans("Unknown");
424
			}
425
		} else {
426
			$userstatic = new User($db);
427
			$userstatic->fetch($object->user_approve_id ? $object->user_approve_id : $object->user_approve);
428
			if ($userstatic->id) {
429
				print $userstatic->getNomUrl(-1, '', 0, 0, 0);
430
			} else {
431
				print $langs->trans("Unknown");
432
			}
433
		}
434
		if ($usetable) {
435
			print '</td></tr>';
436
		} else {
437
			print '<br>';
438
		}
439
	}
440
441
	// Date approve
442
	if (!empty($object->date_approve)) {
443
		if ($usetable) {
444
			print '<tr><td class="titlefield">';
445
		}
446
		print $langs->trans("DateApprove");
447
		if ($usetable) {
448
			print '</td><td>';
449
		} else {
450
			print ': ';
451
		}
452
		print dol_print_date($object->date_approve, 'dayhour', 'tzserver');
453
		if ($deltadateforuser) {
454
			print ' '.$langs->trans("CurrentHour").' &nbsp; / &nbsp; '.dol_print_date($object->date_approve, "dayhour", 'tzuserrel').' &nbsp;'.$langs->trans("ClientHour");
455
		}
456
		if ($usetable) {
457
			print '</td></tr>';
458
		} else {
459
			print '<br>';
460
		}
461
	}
462
463
	// User approve
464
	if (!empty($object->user_approve_id2)) {
465
		if ($usetable) {
466
			print '<tr><td class="titlefield">';
467
		}
468
		print $langs->trans("ApprovedBy");
469
		if ($usetable) {
470
			print '</td><td>';
471
		} else {
472
			print ': ';
473
		}
474
		$userstatic = new User($db);
475
		$userstatic->fetch($object->user_approve_id2);
476
		if ($userstatic->id) {
477
			print $userstatic->getNomUrl(-1, '', 0, 0, 0);
478
		} else {
479
			print $langs->trans("Unknown");
480
		}
481
		if ($usetable) {
482
			print '</td></tr>';
483
		} else {
484
			print '<br>';
485
		}
486
	}
487
488
	// Date approve
489
	if (!empty($object->date_approve2)) {
490
		if ($usetable) {
491
			print '<tr><td class="titlefield">';
492
		}
493
		print $langs->trans("DateApprove2");
494
		if ($usetable) {
495
			print '</td><td>';
496
		} else {
497
			print ': ';
498
		}
499
		print dol_print_date($object->date_approve2, 'dayhour', 'tzserver');
500
		if ($deltadateforuser) {
501
			print ' '.$langs->trans("CurrentHour").' &nbsp; / &nbsp; '.dol_print_date($object->date_approve2, "dayhour", 'tzuserrel').' &nbsp;'.$langs->trans("ClientHour");
502
		}
503
		if ($usetable) {
504
			print '</td></tr>';
505
		} else {
506
			print '<br>';
507
		}
508
	}
509
510
	// User signature
511
	if (!empty($object->user_signature)) {
512
		if ($usetable) {
513
			print '<tr><td class="titlefield">';
514
		}
515
		print $langs->trans('SignedBy');
516
		if ($usetable) {
517
			print '</td><td>';
518
		} else {
519
			print ': ';
520
		}
521
		if (is_object($object->user_signature)) {
522
			if ($object->user_signature->id) {
523
				print $object->user_signature->getNomUrl(-1, '', 0, 0, 0);
524
			} else {
525
				print $langs->trans('Unknown');
526
			}
527
		} else {
528
			$userstatic = new User($db);
529
			$userstatic->fetch($object->user_signature);
530
			if ($userstatic->id) {
531
				print $userstatic->getNomUrl(-1, '', 0, 0, 0);
532
			} else {
533
				print $langs->trans('Unknown');
534
			}
535
		}
536
		if ($usetable) {
537
			print '</td></tr>';
538
		} else {
539
			print '<br>';
540
		}
541
	}
542
543
	// Date signature
544
	if (!empty($object->date_signature)) {
545
		if ($usetable) {
546
			print '<tr><td class="titlefield">';
547
		}
548
		print $langs->trans('DateSigning');
549
		if ($usetable) {
550
			print '</td><td>';
551
		} else {
552
			print ': ';
553
		}
554
		print dol_print_date($object->date_signature, 'dayhour');
555
		if ($deltadateforuser) {
556
			print ' '.$langs->trans('CurrentHour').' &nbsp; / &nbsp; '.dol_print_date($object->date_signature, 'dayhour', 'tzuserrel').' &nbsp;'.$langs->trans('ClientHour');
557
		}
558
		if ($usetable) {
559
			print '</td></tr>';
560
		} else {
561
			print '<br>';
562
		}
563
	}
564
565
	// User close
566
	if (!empty($object->user_cloture) || !empty($object->user_closing)) {
567
		if (isset($object->user_cloture) && !empty($object->user_cloture)) {
568
			$object->user_closing = $object->user_cloture;
569
		}
570
		if ($usetable) {
571
			print '<tr><td class="titlefield">';
572
		}
573
		print $langs->trans("ClosedBy");
574
		if ($usetable) {
575
			print '</td><td>';
576
		} else {
577
			print ': ';
578
		}
579
		if (is_object($object->user_closing)) {
580
			if ($object->user_closing->id) {
581
				print $object->user_closing->getNomUrl(-1, '', 0, 0, 0);
582
			} else {
583
				print $langs->trans("Unknown");
584
			}
585
		} else {
586
			$userstatic = new User($db);
587
			$userstatic->fetch($object->user_closing);
588
			if ($userstatic->id) {
589
				print $userstatic->getNomUrl(-1, '', 0, 0, 0);
590
			} else {
591
				print $langs->trans("Unknown");
592
			}
593
		}
594
		if ($usetable) {
595
			print '</td></tr>';
596
		} else {
597
			print '<br>';
598
		}
599
	}
600
601
	// Date close
602
	if (!empty($object->date_cloture) || !empty($object->date_closing)) {
603
		if (isset($object->date_cloture) && !empty($object->date_cloture)) {
604
			$object->date_closing = $object->date_cloture;
605
		}
606
		if ($usetable) {
607
			print '<tr><td class="titlefield">';
608
		}
609
		print $langs->trans("DateClosing");
610
		if ($usetable) {
611
			print '</td><td>';
612
		} else {
613
			print ': ';
614
		}
615
		print dol_print_date($object->date_closing, 'dayhour', 'tzserver');
616
		if ($deltadateforuser) {
617
			print ' '.$langs->trans("CurrentHour").' &nbsp; / &nbsp; '.dol_print_date($object->date_closing, "dayhour", 'tzuserrel').' &nbsp;'.$langs->trans("ClientHour");
618
		}
619
		if ($usetable) {
620
			print '</td></tr>';
621
		} else {
622
			print '<br>';
623
		}
624
	}
625
626
	// User conciliate
627
	if (!empty($object->user_rappro)) {
628
		if ($usetable) {
629
			print '<tr><td class="titlefield">';
630
		}
631
		print $langs->trans("ConciliatedBy");
632
		if ($usetable) {
633
			print '</td><td>';
634
		} else {
635
			print ': ';
636
		}
637
		if (is_object($object->user_rappro)) {
638
			if ($object->user_rappro->id) {
639
				print $object->user_rappro->getNomUrl(-1, '', 0, 0, 0);
640
			} else {
641
				print $langs->trans("Unknown");
642
			}
643
		} else {
644
			$userstatic = new User($db);
645
			$userstatic->fetch($object->user_rappro);
646
			if ($userstatic->id) {
647
				print $userstatic->getNomUrl(1, '', 0, 0, 0);
648
			} else {
649
				print $langs->trans("Unknown");
650
			}
651
		}
652
		if ($usetable) {
653
			print '</td></tr>';
654
		} else {
655
			print '<br>';
656
		}
657
	}
658
659
	// Date conciliate
660
	if (!empty($object->date_rappro)) {
661
		if ($usetable) {
662
			print '<tr><td class="titlefield">';
663
		}
664
		print $langs->trans("DateConciliating");
665
		if ($usetable) {
666
			print '</td><td>';
667
		} else {
668
			print ': ';
669
		}
670
		print dol_print_date($object->date_rappro, 'dayhour', 'tzserver');
671
		if ($deltadateforuser) {
672
			print ' '.$langs->trans("CurrentHour").' &nbsp; / &nbsp; '.dol_print_date($object->date_rappro, "dayhour", 'tzuserrel').' &nbsp;'.$langs->trans("ClientHour");
673
		}
674
		if ($usetable) {
675
			print '</td></tr>';
676
		} else {
677
			print '<br>';
678
		}
679
	}
680
681
	// Date send
682
	if (!empty($object->date_envoi)) {
683
		if ($usetable) {
684
			print '<tr><td class="titlefield">';
685
		}
686
		print $langs->trans("DateLastSend");
687
		if ($usetable) {
688
			print '</td><td>';
689
		} else {
690
			print ': ';
691
		}
692
		print dol_print_date($object->date_envoi, 'dayhour', 'tzserver');
693
		if ($deltadateforuser) {
694
			print ' '.$langs->trans("CurrentHour").' &nbsp; / &nbsp; '.dol_print_date($object->date_envoi, "dayhour", 'tzuserrel').' &nbsp;'.$langs->trans("ClientHour");
695
		}
696
		if ($usetable) {
697
			print '</td></tr>';
698
		} else {
699
			print '<br>';
700
		}
701
	}
702
703
	if ($usetable) {
704
		print '</table>';
705
	}
706
}
707
708
709
/**
710
 *	Return an email formatted to include a tracking id
711
 *  For example  [email protected] becom [email protected]
712
 *
713
 *	@param	string	$email       	Email address (Ex: "[email protected]", "John Do <[email protected]>")
714
 *	@param	string	$trackingid    	Tracking id (Ex: thi123 for thirdparty with id 123)
715
 *	@return string     			    Return email tracker string
716
 */
717
function dolAddEmailTrackId($email, $trackingid)
718
{
719
	$tmp = explode('@', $email);
720
	return $tmp[0].'+'.$trackingid.'@'.(isset($tmp[1]) ? $tmp[1] : '');
721
}
722
723
/**
724
 *	Return true if email has a domain name that can be resolved to MX type.
725
 *
726
 *	@param	string	$mail       Email address (Ex: "[email protected]", "John Do <[email protected]>")
727
 *	@return int     			-1 if error (function not available), 0=Not valid, 1=Valid
728
 */
729
function isValidMailDomain($mail)
730
{
731
	list($user, $domain) = explode("@", $mail, 2);
732
	return ($domain ? isValidMXRecord($domain) : 0);
733
}
734
735
/**
736
 *	Url string validation
737
 *  <http[s]> :// [user[:pass]@] hostname [port] [/path] [?getquery] [anchor]
738
 *
739
 *	@param	string	$url		Url
740
 *  @param  int		$http		1: verify http is provided, 0: not verify http
741
 *  @param  int		$pass		1: verify user and pass is provided, 0: not verify user and pass
742
 *  @param  int		$port		1: verify port is provided, 0: not verify port
743
 *  @param  int		$path		1: verify a path is provided "/" or "/..." or "/.../", 0: not verify path
744
 *  @param  int		$query		1: verify query is provided, 0: not verify query
745
 *  @param  int		$anchor		1: verify anchor is provided, 0: not verify anchor
746
 *	@return int					1=Check is OK, 0=Check is KO
747
 */
748
function isValidUrl($url, $http = 0, $pass = 0, $port = 0, $path = 0, $query = 0, $anchor = 0)
749
{
750
	$ValidUrl = 0;
751
	$urlregex = '';
752
753
	// SCHEME
754
	if ($http) {
755
		$urlregex .= "^(http:\/\/|https:\/\/)";
756
	}
757
758
	// USER AND PASS
759
	if ($pass) {
760
		$urlregex .= "([a-z0-9+!*(),;?&=\$_.-]+(\:[a-z0-9+!*(),;?&=\$_.-]+)?@)";
761
	}
762
763
	// HOSTNAME OR IP
764
	//$urlregex .= "[a-z0-9+\$_-]+(\.[a-z0-9+\$_-]+)*";  // x allowed (ex. http://localhost, http://routerlogin)
765
	//$urlregex .= "[a-z0-9+\$_-]+(\.[a-z0-9+\$_-]+)+";  // x.x
766
	$urlregex .= "([a-z0-9+\$_\\\:-])+(\.[a-z0-9+\$_-][a-z0-9+\$_-]+)*"; // x ou x.xx (2 x ou plus)
767
	//use only one of the above
768
769
	// PORT
770
	if ($port) {
771
		$urlregex .= "(\:[0-9]{2,5})";
772
	}
773
	// PATH
774
	if ($path) {
775
		$urlregex .= "(\/([a-z0-9+\$_-]\.?)+)*\/";
776
	}
777
	// GET Query
778
	if ($query) {
779
		$urlregex .= "(\?[a-z+&\$_.-][a-z0-9;:@\/&%=+\$_.-]*)";
780
	}
781
	// ANCHOR
782
	if ($anchor) {
783
		$urlregex .= "(#[a-z_.-][a-z0-9+\$_.-]*)$";
784
	}
785
786
	// check
787
	if (preg_match('/'.$urlregex.'/i', $url)) {
788
		$ValidUrl = 1;
789
	}
790
	//print $urlregex.' - '.$url.' - '.$ValidUrl;
791
792
	return $ValidUrl;
793
}
794
795
/**
796
 *	Check if VAT numero is valid (check done on syntax only, no database or remote access)
797
 *
798
 *	@param	Societe   $company       VAT number
799
 *	@return int					     1=Check is OK, 0=Check is KO
800
 */
801
function isValidVATID($company)
802
{
803
	if ($company->isInEEC()) {    // Syntax check rules for EEC countries
804
		/* Disabled because some companies can have an address in Irland and a vat number in France.
805
		$vatprefix = $company->country_code;
806
		if ($vatprefix == 'GR') $vatprefix = '(EL|GR)';
807
		elseif ($vatprefix == 'MC') $vatprefix = 'FR';	// Monaco is using french VAT numbers
808
		else $vatprefix = preg_quote($vatprefix, '/');*/
809
		$vatprefix = '[a-zA-Z][a-zA-Z]';
810
		if (!preg_match('/^'.$vatprefix.'[a-zA-Z0-9\-\.]{5,14}$/i', str_replace(' ', '', $company->tva_intra))) {
811
			return 0;
812
		}
813
	}
814
815
	return 1;
816
}
817
818
/**
819
 *	Clean an url string
820
 *
821
 *	@param	string	$url		Url
822
 *	@param  integer	$http		1 = keep both http:// and https://, 0: remove http:// but not https://
823
 *	@return string				Cleaned url
824
 */
825
function clean_url($url, $http = 1)
826
{
827
	// Fixed by Matelli (see http://matelli.fr/showcases/patchs-dolibarr/fix-cleaning-url.html)
828
	// To include the minus sign in a char class, we must not escape it but put it at the end of the class
829
	// Also, there's no need of escape a dot sign in a class
830
	$regs = array();
831
	if (preg_match('/^(https?:[\\/]+)?([0-9A-Z.-]+\.[A-Z]{2,4})(:[0-9]+)?/i', $url, $regs)) {
832
		$proto = $regs[1];
833
		$domain = $regs[2];
834
		$port = isset($regs[3]) ? $regs[3] : '';
835
		//print $url." -> ".$proto." - ".$domain." - ".$port;
836
		//$url = dol_string_nospecial(trim($url));
837
		$url = trim($url);
838
839
		// Si http: defini on supprime le http (Si https on ne supprime pas)
840
		$newproto = $proto;
841
		if ($http == 0) {
842
			if (preg_match('/^http:[\\/]+/i', $url)) {
843
				$url = preg_replace('/^http:[\\/]+/i', '', $url);
844
				$newproto = '';
845
			}
846
		}
847
848
		// On passe le nom de domaine en minuscule
849
		$CleanUrl = preg_replace('/^'.preg_quote($proto.$domain, '/').'/i', $newproto.strtolower($domain), $url);
850
851
		return $CleanUrl;
852
	} else {
853
		return $url;
854
	}
855
}
856
857
858
859
/**
860
 * 	Returns an email value with obfuscated parts.
861
 *
862
 * 	@param 		string		$mail				Email
863
 * 	@param 		string		$replace			Replacement character (defaul: *)
864
 * 	@param 		int			$nbreplace			Number of replacement character (default: 8)
865
 * 	@param 		int			$nbdisplaymail		Number of character unchanged (default: 4)
866
 * 	@param 		int			$nbdisplaydomain	Number of character unchanged of domain (default: 3)
867
 * 	@param 		bool		$displaytld			Display tld (default: true)
868
 * 	@return		string							Return email with hidden parts or '';
869
 */
870
function dolObfuscateEmail($mail, $replace = "*", $nbreplace = 8, $nbdisplaymail = 4, $nbdisplaydomain = 3, $displaytld = true)
871
{
872
	if (!isValidEmail($mail)) {
873
		return '';
874
	}
875
	$tab = explode('@', $mail);
876
	$tab2 = explode('.', $tab[1]);
877
	$string_replace = '';
878
	$mail_name = $tab[0];
879
	$mail_domaine = $tab2[0];
880
	$mail_tld = '';
881
882
	$nbofelem = count($tab2);
883
	for ($i = 1; $i < $nbofelem && $displaytld; $i++) {
884
		$mail_tld .= '.'.$tab2[$i];
885
	}
886
887
	for ($i = 0; $i < $nbreplace; $i++) {
888
		$string_replace .= $replace;
889
	}
890
891
	if (strlen($mail_name) > $nbdisplaymail) {
892
		$mail_name = substr($mail_name, 0, $nbdisplaymail);
893
	}
894
895
	if (strlen($mail_domaine) > $nbdisplaydomain) {
896
		$mail_domaine = substr($mail_domaine, strlen($mail_domaine) - $nbdisplaydomain);
897
	}
898
899
	return $mail_name.$string_replace.$mail_domaine.$mail_tld;
900
}
901
902
903
/**
904
 * 	Return lines of an html table from an array
905
 * 	Used by array2table function only
906
 *
907
 * 	@param	array	$data		Array of data
908
 * 	@param	string	$troptions	Options for tr
909
 * 	@param	string	$tdoptions	Options for td
910
 * 	@return	string
911
 */
912
function array2tr($data, $troptions = '', $tdoptions = '')
913
{
914
	$text = '<tr '.$troptions.'>';
915
	foreach ($data as $key => $item) {
916
		$text .= '<td '.$tdoptions.'>'.$item.'</td>';
917
	}
918
	$text .= '</tr>';
919
	return $text;
920
}
921
922
/**
923
 * 	Return an html table from an array
924
 *
925
 * 	@param	array	$data			Array of data
926
 * 	@param	int		$tableMarkup	Table markup
927
 * 	@param	string	$tableoptions	Options for table
928
 * 	@param	string	$troptions		Options for tr
929
 * 	@param	string	$tdoptions		Options for td
930
 * 	@return	string
931
 */
932
function array2table($data, $tableMarkup = 1, $tableoptions = '', $troptions = '', $tdoptions = '')
933
{
934
	$text = '';
935
	if ($tableMarkup) {
936
		$text = '<table '.$tableoptions.'>';
937
	}
938
	foreach ($data as $key => $item) {
939
		if (is_array($item)) {
940
			$text .= array2tr($item, $troptions, $tdoptions);
941
		} else {
942
			$text .= '<tr '.$troptions.'>';
943
			$text .= '<td '.$tdoptions.'>'.$key.'</td>';
944
			$text .= '<td '.$tdoptions.'>'.$item.'</td>';
945
			$text .= '</tr>';
946
		}
947
	}
948
	if ($tableMarkup) {
949
		$text .= '</table>';
950
	}
951
	return $text;
952
}
953
954
/**
955
 * Return last or next value for a mask (according to area we should not reset)
956
 *
957
 * @param   DoliDB		$db				Database handler
958
 * @param   string		$mask			Mask to use
959
 * @param   string		$table			Table containing field with counter
960
 * @param   string		$field			Field containing already used values of counter
961
 * @param   string		$where			To add a filter on selection (for exemple to filter on invoice types)
962
 * @param   Societe		$objsoc			The company that own the object we need a counter for
963
 * @param   string		$date			Date to use for the {y},{m},{d} tags.
964
 * @param   string		$mode			'next' for next value or 'last' for last value
965
 * @param   bool		$bentityon		Activate the entity filter. Default is true (for modules not compatible with multicompany)
966
 * @param	User		$objuser		Object user we need data from.
967
 * @param	int			$forceentity	Entity id to force
968
 * @return 	string						New value (numeric) or error message
969
 */
970
function get_next_value($db, $mask, $table, $field, $where = '', $objsoc = '', $date = '', $mode = 'next', $bentityon = true, $objuser = null, $forceentity = null)
971
{
972
	global $conf, $user;
973
974
	if (!is_object($objsoc)) {
975
		$valueforccc = $objsoc;
976
	} elseif ($table == "commande_fournisseur" || $table == "facture_fourn") {
977
		$valueforccc = dol_string_unaccent($objsoc->code_fournisseur);
978
	} else {
979
		$valueforccc = dol_string_unaccent($objsoc->code_client);
980
	}
981
982
	$sharetable = $table;
983
	if ($table == 'facture' || $table == 'invoice') {
984
		$sharetable = 'invoicenumber'; // for getEntity function
985
	}
986
987
	// Clean parameters
988
	if ($date == '') {
989
		$date = dol_now(); // We use local year and month of PHP server to search numbers
990
	}
991
	// but we should use local year and month of user
992
993
	// For debugging
994
	//dol_syslog("mask=".$mask, LOG_DEBUG);
995
	//include_once(DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php');
996
	//$mask='FA{yy}{mm}-{0000@99}';
997
	//$date=dol_mktime(12, 0, 0, 1, 1, 1900);
998
	//$date=dol_stringtotime('20130101');
999
1000
	$hasglobalcounter = false;
1001
	$reg = array();
1002
	// Extract value for mask counter, mask raz and mask offset
1003
	if (preg_match('/\{(0+)([@\+][0-9\-\+\=]+)?([@\+][0-9\-\+\=]+)?\}/i', $mask, $reg)) {
1004
		$masktri = $reg[1].(!empty($reg[2]) ? $reg[2] : '').(!empty($reg[3]) ? $reg[3] : '');
1005
		$maskcounter = $reg[1];
1006
		$hasglobalcounter = true;
1007
	} else {
1008
		// setting some defaults so the rest of the code won't fail if there is a third party counter
1009
		$masktri = '00000';
1010
		$maskcounter = '00000';
1011
	}
1012
1013
	$maskraz = -1;
1014
	$maskoffset = 0;
1015
	$resetEveryMonth = false;
1016
	if (dol_strlen($maskcounter) < 3 && empty($conf->global->MAIN_COUNTER_WITH_LESS_3_DIGITS)) {
1017
		return 'ErrorCounterMustHaveMoreThan3Digits';
1018
	}
1019
1020
	// Extract value for third party mask counter
1021
	$regClientRef = array();
1022
	if (preg_match('/\{(c+)(0*)\}/i', $mask, $regClientRef)) {
1023
		$maskrefclient = $regClientRef[1].$regClientRef[2];
1024
		$maskrefclient_maskclientcode = $regClientRef[1];
1025
		$maskrefclient_maskcounter = $regClientRef[2];
1026
		$maskrefclient_maskoffset = 0; //default value of maskrefclient_counter offset
1027
		$maskrefclient_clientcode = substr($valueforccc, 0, dol_strlen($maskrefclient_maskclientcode)); //get n first characters of client code where n is length in mask
1028
		$maskrefclient_clientcode = str_pad($maskrefclient_clientcode, dol_strlen($maskrefclient_maskclientcode), "#", STR_PAD_RIGHT); //padding maskrefclient_clientcode for having exactly n characters in maskrefclient_clientcode
1029
		$maskrefclient_clientcode = dol_string_nospecial($maskrefclient_clientcode); //sanitize maskrefclient_clientcode for sql insert and sql select like
1030
		if (dol_strlen($maskrefclient_maskcounter) > 0 && dol_strlen($maskrefclient_maskcounter) < 3) {
1031
			return 'ErrorCounterMustHaveMoreThan3Digits';
1032
		}
1033
	} else {
1034
		$maskrefclient = '';
1035
	}
1036
1037
	// fail if there is neither a global nor a third party counter
1038
	if (!$hasglobalcounter && ($maskrefclient_maskcounter == '')) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $maskrefclient_maskcounter does not seem to be defined for all execution paths leading up to this point.
Loading history...
1039
		return 'ErrorBadMask';
1040
	}
1041
1042
	// Extract value for third party type
1043
	$regType = array();
1044
	if (preg_match('/\{(t+)\}/i', $mask, $regType)) {
1045
		$masktype = $regType[1];
1046
		$masktype_value = substr(preg_replace('/^TE_/', '', $objsoc->typent_code), 0, dol_strlen($regType[1])); // get n first characters of thirdpaty typent_code (where n is length in mask)
1047
		$masktype_value = str_pad($masktype_value, dol_strlen($regType[1]), "#", STR_PAD_RIGHT); // we fill on right with # to have same number of char than into mask
1048
	} else {
1049
		$masktype = '';
1050
		$masktype_value = '';
1051
	}
1052
1053
	// Extract value for user
1054
	$regType = array();
1055
	if (preg_match('/\{(u+)\}/i', $mask, $regType)) {
1056
		$lastname = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
1057
		if (is_object($objuser)) {
1058
			$lastname = $objuser->lastname;
1059
		}
1060
1061
		$maskuser = $regType[1];
1062
		$maskuser_value = substr($lastname, 0, dol_strlen($regType[1])); // get n first characters of user firstname (where n is length in mask)
1063
		$maskuser_value = str_pad($maskuser_value, dol_strlen($regType[1]), "#", STR_PAD_RIGHT); // we fill on right with # to have same number of char than into mask
1064
	} else {
1065
		$maskuser = '';
1066
		$maskuser_value = '';
1067
	}
1068
1069
	// Personalized field {XXX-1} à {XXX-9}
1070
	$maskperso = array();
1071
	$maskpersonew = array();
1072
	$tmpmask = $mask;
1073
	$regKey = array();
1074
	while (preg_match('/\{([A-Z]+)\-([1-9])\}/', $tmpmask, $regKey)) {
1075
		$maskperso[$regKey[1]] = '{'.$regKey[1].'-'.$regKey[2].'}';
1076
		$maskpersonew[$regKey[1]] = str_pad('', $regKey[2], '_', STR_PAD_RIGHT);
1077
		$tmpmask = preg_replace('/\{'.$regKey[1].'\-'.$regKey[2].'\}/i', $maskpersonew[$regKey[1]], $tmpmask);
1078
	}
1079
1080
	if (strstr($mask, 'user_extra_')) {
1081
		$start = "{user_extra_";
1082
		$end = "\}";
1083
		$extra = get_string_between($mask, "user_extra_", "}");
1084
		if (!empty($user->array_options['options_'.$extra])) {
1085
			$mask = preg_replace('#('.$start.')(.*?)('.$end.')#si', $user->array_options['options_'.$extra], $mask);
1086
		}
1087
	}
1088
	$maskwithonlyymcode = $mask;
1089
	$maskwithonlyymcode = preg_replace('/\{(0+)([@\+][0-9\-\+\=]+)?([@\+][0-9\-\+\=]+)?\}/i', $maskcounter, $maskwithonlyymcode);
1090
	$maskwithonlyymcode = preg_replace('/\{dd\}/i', 'dd', $maskwithonlyymcode);
1091
	$maskwithonlyymcode = preg_replace('/\{(c+)(0*)\}/i', $maskrefclient, $maskwithonlyymcode);
1092
	$maskwithonlyymcode = preg_replace('/\{(t+)\}/i', $masktype_value, $maskwithonlyymcode);
1093
	$maskwithonlyymcode = preg_replace('/\{(u+)\}/i', $maskuser_value, $maskwithonlyymcode);
1094
	foreach ($maskperso as $key => $val) {
1095
		$maskwithonlyymcode = preg_replace('/'.preg_quote($val, '/').'/i', $maskpersonew[$key], $maskwithonlyymcode);
1096
	}
1097
	$maskwithnocode = $maskwithonlyymcode;
1098
	$maskwithnocode = preg_replace('/\{yyyy\}/i', 'yyyy', $maskwithnocode);
1099
	$maskwithnocode = preg_replace('/\{yy\}/i', 'yy', $maskwithnocode);
1100
	$maskwithnocode = preg_replace('/\{y\}/i', 'y', $maskwithnocode);
1101
	$maskwithnocode = preg_replace('/\{mm\}/i', 'mm', $maskwithnocode);
1102
	// Now maskwithnocode = 0000ddmmyyyyccc for example
1103
	// and maskcounter    = 0000 for example
1104
	//print "maskwithonlyymcode=".$maskwithonlyymcode." maskwithnocode=".$maskwithnocode."\n<br>";
1105
	//var_dump($reg);
1106
1107
	// If an offset is asked
1108
	if (!empty($reg[2]) && preg_match('/^\+/', $reg[2])) {
1109
		$maskoffset = preg_replace('/^\+/', '', $reg[2]);
1110
	}
1111
	if (!empty($reg[3]) && preg_match('/^\+/', $reg[3])) {
1112
		$maskoffset = preg_replace('/^\+/', '', $reg[3]);
1113
	}
1114
1115
	// Define $sqlwhere
1116
	$sqlwhere = '';
1117
	$yearoffset = 0; // Use year of current $date by default
1118
	$yearoffsettype = false; // false: no reset, 0,-,=,+: reset at offset SOCIETE_FISCAL_MONTH_START, x=reset at offset x
1119
1120
	// If a restore to zero after a month is asked we check if there is already a value for this year.
1121
	if (!empty($reg[2]) && preg_match('/^@/', $reg[2])) {
1122
		$yearoffsettype = preg_replace('/^@/', '', $reg[2]);
1123
	}
1124
	if (!empty($reg[3]) && preg_match('/^@/', $reg[3])) {
1125
		$yearoffsettype = preg_replace('/^@/', '', $reg[3]);
1126
	}
1127
1128
	//print "yearoffset=".$yearoffset." yearoffsettype=".$yearoffsettype;
1129
	if (is_numeric($yearoffsettype) && $yearoffsettype >= 1) {
1130
		$maskraz = $yearoffsettype; // For backward compatibility
1131
	} elseif ($yearoffsettype === '0' || (!empty($yearoffsettype) && !is_numeric($yearoffsettype) && $conf->global->SOCIETE_FISCAL_MONTH_START > 1)) {
1132
		$maskraz = $conf->global->SOCIETE_FISCAL_MONTH_START;
1133
	}
1134
	//print "maskraz=".$maskraz;	// -1=no reset
1135
1136
	if ($maskraz > 0) {   // A reset is required
1137
		if ($maskraz == 99) {
1138
			$maskraz = date('m', $date);
1139
			$resetEveryMonth = true;
1140
		}
1141
		if ($maskraz > 12) {
1142
			return 'ErrorBadMaskBadRazMonth';
1143
		}
1144
1145
		// Define posy, posm and reg
1146
		if ($maskraz > 1) {	// if reset is not first month, we need month and year into mask
1147
			if (preg_match('/^(.*)\{(y+)\}\{(m+)\}/i', $maskwithonlyymcode, $reg)) {
1148
				$posy = 2;
1149
				$posm = 3;
1150
			} elseif (preg_match('/^(.*)\{(m+)\}\{(y+)\}/i', $maskwithonlyymcode, $reg)) {
1151
				$posy = 3;
1152
				$posm = 2;
1153
			} else {
1154
				return 'ErrorCantUseRazInStartedYearIfNoYearMonthInMask';
1155
			}
1156
1157
			if (dol_strlen($reg[$posy]) < 2) {
1158
				return 'ErrorCantUseRazWithYearOnOneDigit';
1159
			}
1160
		} else // if reset is for a specific month in year, we need year
1161
		{
1162
			if (preg_match('/^(.*)\{(m+)\}\{(y+)\}/i', $maskwithonlyymcode, $reg)) {
1163
				$posy = 3;
1164
				$posm = 2;
1165
			} elseif (preg_match('/^(.*)\{(y+)\}\{(m+)\}/i', $maskwithonlyymcode, $reg)) {
1166
				$posy = 2;
1167
				$posm = 3;
1168
			} elseif (preg_match('/^(.*)\{(y+)\}/i', $maskwithonlyymcode, $reg)) {
1169
				$posy = 2;
1170
				$posm = 0;
1171
			} else {
1172
				return 'ErrorCantUseRazIfNoYearInMask';
1173
			}
1174
		}
1175
		// Define length
1176
		$yearlen = $posy ?dol_strlen($reg[$posy]) : 0;
1177
		$monthlen = $posm ?dol_strlen($reg[$posm]) : 0;
1178
		// Define pos
1179
		$yearpos = (dol_strlen($reg[1]) + 1);
1180
		$monthpos = ($yearpos + $yearlen);
1181
		if ($posy == 3 && $posm == 2) {		// if month is before year
1182
			$monthpos = (dol_strlen($reg[1]) + 1);
1183
			$yearpos = ($monthpos + $monthlen);
1184
		}
1185
		//print "xxx ".$maskwithonlyymcode." maskraz=".$maskraz." posy=".$posy." yearlen=".$yearlen." yearpos=".$yearpos." posm=".$posm." monthlen=".$monthlen." monthpos=".$monthpos." yearoffsettype=".$yearoffsettype." resetEveryMonth=".$resetEveryMonth."\n";
1186
1187
		// Define $yearcomp and $monthcomp (that will be use in the select where to search max number)
1188
		$monthcomp = $maskraz;
1189
		$yearcomp = 0;
1190
1191
		if (!empty($yearoffsettype) && !is_numeric($yearoffsettype) && $yearoffsettype != '=') {	// $yearoffsettype is - or +
1192
			$currentyear = date("Y", $date);
1193
			$fiscaldate = dol_mktime('0', '0', '0', $maskraz, '1', $currentyear);
1194
			$newyeardate = dol_mktime('0', '0', '0', '1', '1', $currentyear);
1195
			$nextnewyeardate = dol_mktime('0', '0', '0', '1', '1', $currentyear + 1);
1196
			//echo 'currentyear='.$currentyear.' date='.dol_print_date($date, 'day').' fiscaldate='.dol_print_date($fiscaldate, 'day').'<br>';
1197
1198
			// If after or equal of current fiscal date
1199
			if ($date >= $fiscaldate) {
1200
				// If before of next new year date
1201
				if ($date < $nextnewyeardate && $yearoffsettype == '+') {
1202
					$yearoffset = 1;
1203
				}
1204
			} elseif ($date >= $newyeardate && $yearoffsettype == '-') {
1205
				// If after or equal of current new year date
1206
				$yearoffset = -1;
1207
			}
1208
		} elseif (date("m", $date) < $maskraz && empty($resetEveryMonth)) {
1209
			// For backward compatibility
1210
			$yearoffset = -1;
1211
		}	// If current month lower that month of return to zero, year is previous year
1212
1213
		if ($yearlen == 4) {
1214
			$yearcomp = sprintf("%04d", date("Y", $date) + $yearoffset);
1215
		} elseif ($yearlen == 2) {
1216
			$yearcomp = sprintf("%02d", date("y", $date) + $yearoffset);
1217
		} elseif ($yearlen == 1) {
1218
			$yearcomp = substr(date("y", $date), 2, 1) + $yearoffset;
1219
		}
1220
		if ($monthcomp > 1 && empty($resetEveryMonth)) {	// Test with month is useless if monthcomp = 0 or 1 (0 is same as 1) (regis: $monthcomp can't equal 0)
1221
			if ($yearlen == 4) {
1222
				$yearcomp1 = sprintf("%04d", date("Y", $date) + $yearoffset + 1);
1223
			} elseif ($yearlen == 2) {
1224
				$yearcomp1 = sprintf("%02d", date("y", $date) + $yearoffset + 1);
1225
			}
1226
1227
			$sqlwhere .= "(";
1228
			$sqlwhere .= " (SUBSTRING(".$field.", ".$yearpos.", ".$yearlen.") = '".$db->escape($yearcomp)."'";
1229
			$sqlwhere .= " AND SUBSTRING(".$field.", ".$monthpos.", ".$monthlen.") >= '".str_pad($monthcomp, $monthlen, '0', STR_PAD_LEFT)."')";
1230
			$sqlwhere .= " OR";
1231
			$sqlwhere .= " (SUBSTRING(".$field.", ".$yearpos.", ".$yearlen.") = '".$db->escape($yearcomp1)."'";
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $yearcomp1 does not seem to be defined for all execution paths leading up to this point.
Loading history...
1232
			$sqlwhere .= " AND SUBSTRING(".$field.", ".$monthpos.", ".$monthlen.") < '".str_pad($monthcomp, $monthlen, '0', STR_PAD_LEFT)."') ";
1233
			$sqlwhere .= ')';
1234
		} elseif ($resetEveryMonth) {
1235
			$sqlwhere .= "(SUBSTRING(".$field.", ".$yearpos.", ".$yearlen.") = '".$db->escape($yearcomp)."'";
1236
			$sqlwhere .= " AND SUBSTRING(".$field.", ".$monthpos.", ".$monthlen.") = '".str_pad($monthcomp, $monthlen, '0', STR_PAD_LEFT)."')";
1237
		} else { // reset is done on january
1238
			$sqlwhere .= '(SUBSTRING('.$field.', '.$yearpos.', '.$yearlen.") = '".$db->escape($yearcomp)."')";
1239
		}
1240
	}
1241
	//print "sqlwhere=".$sqlwhere." yearcomp=".$yearcomp."<br>\n";	// sqlwhere and yearcomp defined only if we ask a reset
1242
	//print "masktri=".$masktri." maskcounter=".$maskcounter." maskraz=".$maskraz." maskoffset=".$maskoffset."<br>\n";
1243
1244
	// Define $sqlstring
1245
	if (function_exists('mb_strrpos')) {
1246
		$posnumstart = mb_strrpos($maskwithnocode, $maskcounter, 0, 'UTF-8');
1247
	} else {
1248
		$posnumstart = strrpos($maskwithnocode, $maskcounter);
1249
	}	// Pos of counter in final string (from 0 to ...)
1250
	if ($posnumstart < 0) {
1251
		return 'ErrorBadMaskFailedToLocatePosOfSequence';
1252
	}
1253
	$sqlstring = 'SUBSTRING('.$field.', '.($posnumstart + 1).', '.dol_strlen($maskcounter).')';
1254
1255
	// Define $maskLike
1256
	$maskLike = dol_string_nospecial($mask);
1257
	$maskLike = str_replace("%", "_", $maskLike);
1258
1259
	// Replace protected special codes with matching number of _ as wild card caracter
1260
	$maskLike = preg_replace('/\{yyyy\}/i', '____', $maskLike);
1261
	$maskLike = preg_replace('/\{yy\}/i', '__', $maskLike);
1262
	$maskLike = preg_replace('/\{y\}/i', '_', $maskLike);
1263
	$maskLike = preg_replace('/\{mm\}/i', '__', $maskLike);
1264
	$maskLike = preg_replace('/\{dd\}/i', '__', $maskLike);
1265
	$maskLike = str_replace(dol_string_nospecial('{'.$masktri.'}'), str_pad("", dol_strlen($maskcounter), "_"), $maskLike);
1266
	if ($maskrefclient) {
1267
		$maskLike = str_replace(dol_string_nospecial('{'.$maskrefclient.'}'), str_pad("", dol_strlen($maskrefclient), "_"), $maskLike);
1268
	}
1269
	if ($masktype) {
1270
		$maskLike = str_replace(dol_string_nospecial('{'.$masktype.'}'), $masktype_value, $maskLike);
1271
	}
1272
	if ($maskuser) {
1273
		$maskLike = str_replace(dol_string_nospecial('{'.$maskuser.'}'), $maskuser_value, $maskLike);
1274
	}
1275
	foreach ($maskperso as $key => $val) {
1276
		$maskLike = str_replace(dol_string_nospecial($maskperso[$key]), $maskpersonew[$key], $maskLike);
1277
	}
1278
1279
	// Get counter in database
1280
	$counter = 0;
1281
	$sql = "SELECT MAX(".$sqlstring.") as val";
1282
	$sql .= " FROM ".MAIN_DB_PREFIX.$table;
1283
	$sql .= " WHERE ".$field." LIKE '".$db->escape($maskLike)."'";
1284
	$sql .= " AND ".$field." NOT LIKE '(PROV%)'";
1285
	if ($bentityon) { // only if entity enable
1286
		$sql .= " AND entity IN (".getEntity($sharetable).")";
1287
	} elseif (!empty($forceentity)) {
1288
		$sql .= " AND entity IN (".$db->sanitize($forceentity).")";
1289
	}
1290
	if ($where) {
1291
		$sql .= $where;
1292
	}
1293
	if ($sqlwhere) {
1294
		$sql .= ' AND '.$sqlwhere;
1295
	}
1296
1297
	//print $sql.'<br>';
1298
	dol_syslog("functions2::get_next_value mode=".$mode."", LOG_DEBUG);
1299
	$resql = $db->query($sql);
1300
	if ($resql) {
1301
		$obj = $db->fetch_object($resql);
1302
		$counter = $obj->val;
1303
	} else {
1304
		dol_print_error($db);
1305
	}
1306
1307
	// Check if we must force counter to maskoffset
1308
	if (empty($counter)) {
1309
		$counter = $maskoffset;
1310
	} elseif (preg_match('/[^0-9]/i', $counter)) {
1311
		$counter = 0;
1312
		dol_syslog("Error, the last counter found is '".$counter."' so is not a numeric value. We will restart to 1.", LOG_ERR);
1313
	} elseif ($counter < $maskoffset && empty($conf->global->MAIN_NUMBERING_OFFSET_ONLY_FOR_FIRST)) {
1314
		$counter = $maskoffset;
1315
	}
1316
1317
	if ($mode == 'last') {	// We found value for counter = last counter value. Now need to get corresponding ref of invoice.
1318
		$counterpadded = str_pad($counter, dol_strlen($maskcounter), "0", STR_PAD_LEFT);
1319
1320
		// Define $maskLike
1321
		$maskLike = dol_string_nospecial($mask);
1322
		$maskLike = str_replace("%", "_", $maskLike);
1323
		// Replace protected special codes with matching number of _ as wild card caracter
1324
		$maskLike = preg_replace('/\{yyyy\}/i', '____', $maskLike);
1325
		$maskLike = preg_replace('/\{yy\}/i', '__', $maskLike);
1326
		$maskLike = preg_replace('/\{y\}/i', '_', $maskLike);
1327
		$maskLike = preg_replace('/\{mm\}/i', '__', $maskLike);
1328
		$maskLike = preg_replace('/\{dd\}/i', '__', $maskLike);
1329
		$maskLike = str_replace(dol_string_nospecial('{'.$masktri.'}'), $counterpadded, $maskLike);
1330
		if ($maskrefclient) {
1331
			$maskLike = str_replace(dol_string_nospecial('{'.$maskrefclient.'}'), str_pad("", dol_strlen($maskrefclient), "_"), $maskLike);
1332
		}
1333
		if ($masktype) {
1334
			$maskLike = str_replace(dol_string_nospecial('{'.$masktype.'}'), $masktype_value, $maskLike);
1335
		}
1336
		if ($maskuser) {
1337
			$maskLike = str_replace(dol_string_nospecial('{'.$maskuser.'}'), $maskuser_value, $maskLike);
1338
		}
1339
1340
		$ref = '';
1341
		$sql = "SELECT ".$field." as ref";
1342
		$sql .= " FROM ".MAIN_DB_PREFIX.$table;
1343
		$sql .= " WHERE ".$field." LIKE '".$db->escape($maskLike)."'";
1344
		$sql .= " AND ".$field." NOT LIKE '%PROV%'";
1345
		if ($bentityon) { // only if entity enable
1346
			$sql .= " AND entity IN (".getEntity($sharetable).")";
1347
		} elseif (!empty($forceentity)) {
1348
			$sql .= " AND entity IN (".$db->sanitize($forceentity).")";
1349
		}
1350
		if ($where) {
1351
			$sql .= $where;
1352
		}
1353
		if ($sqlwhere) {
1354
			$sql .= ' AND '.$sqlwhere;
1355
		}
1356
1357
		dol_syslog("functions2::get_next_value mode=".$mode."", LOG_DEBUG);
1358
		$resql = $db->query($sql);
1359
		if ($resql) {
1360
			$obj = $db->fetch_object($resql);
1361
			if ($obj) {
1362
				$ref = $obj->ref;
1363
			}
1364
		} else {
1365
			dol_print_error($db);
1366
		}
1367
1368
		$numFinal = $ref;
1369
	} elseif ($mode == 'next') {
1370
		$counter++;
1371
1372
		// If value for $counter has a length higher than $maskcounter chars
1373
		if ($counter >= pow(10, dol_strlen($maskcounter))) {
1374
			$counter = 'ErrorMaxNumberReachForThisMask';
1375
		}
1376
1377
		if (!empty($maskrefclient_maskcounter)) {
1378
			//print "maskrefclient_maskcounter=".$maskrefclient_maskcounter." maskwithnocode=".$maskwithnocode." maskrefclient=".$maskrefclient."\n<br>";
1379
1380
			// Define $sqlstring
1381
			$maskrefclient_posnumstart = strpos($maskwithnocode, $maskrefclient_maskcounter, strpos($maskwithnocode, $maskrefclient)); // Pos of counter in final string (from 0 to ...)
1382
			if ($maskrefclient_posnumstart <= 0) {
1383
				return 'ErrorBadMask';
1384
			}
1385
			$maskrefclient_sqlstring = 'SUBSTRING('.$field.', '.($maskrefclient_posnumstart + 1).', '.dol_strlen($maskrefclient_maskcounter).')';
1386
			//print "x".$sqlstring;
1387
1388
			// Define $maskrefclient_maskLike
1389
			$maskrefclient_maskLike = dol_string_nospecial($mask);
1390
			$maskrefclient_maskLike = str_replace("%", "_", $maskrefclient_maskLike);
1391
			// Replace protected special codes with matching number of _ as wild card caracter
1392
			$maskrefclient_maskLike = str_replace(dol_string_nospecial('{yyyy}'), '____', $maskrefclient_maskLike);
1393
			$maskrefclient_maskLike = str_replace(dol_string_nospecial('{yy}'), '__', $maskrefclient_maskLike);
1394
			$maskrefclient_maskLike = str_replace(dol_string_nospecial('{y}'), '_', $maskrefclient_maskLike);
1395
			$maskrefclient_maskLike = str_replace(dol_string_nospecial('{mm}'), '__', $maskrefclient_maskLike);
1396
			$maskrefclient_maskLike = str_replace(dol_string_nospecial('{dd}'), '__', $maskrefclient_maskLike);
1397
			$maskrefclient_maskLike = str_replace(dol_string_nospecial('{'.$masktri.'}'), str_pad("", dol_strlen($maskcounter), "_"), $maskrefclient_maskLike);
1398
			$maskrefclient_maskLike = str_replace(dol_string_nospecial('{'.$maskrefclient.'}'), $maskrefclient_clientcode.str_pad("", dol_strlen($maskrefclient_maskcounter), "_"), $maskrefclient_maskLike);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $maskrefclient_clientcode does not seem to be defined for all execution paths leading up to this point.
Loading history...
1399
1400
			// Get counter in database
1401
			$maskrefclient_counter = 0;
1402
			$maskrefclient_sql = "SELECT MAX(".$maskrefclient_sqlstring.") as val";
1403
			$maskrefclient_sql .= " FROM ".MAIN_DB_PREFIX.$table;
1404
			//$sql.= " WHERE ".$field." not like '(%'";
1405
			$maskrefclient_sql .= " WHERE ".$field." LIKE '".$db->escape($maskrefclient_maskLike)."'";
1406
			if ($bentityon) { // only if entity enable
1407
				$maskrefclient_sql .= " AND entity IN (".getEntity($sharetable).")";
1408
			} elseif (!empty($forceentity)) {
1409
				$sql .= " AND entity IN (".$db->sanitize($forceentity).")";
1410
			}
1411
			if ($where) {
1412
				$maskrefclient_sql .= $where; //use the same optional where as general mask
1413
			}
1414
			if ($sqlwhere) {
1415
				$maskrefclient_sql .= ' AND '.$sqlwhere; //use the same sqlwhere as general mask
1416
			}
1417
			$maskrefclient_sql .= ' AND (SUBSTRING('.$field.', '.(strpos($maskwithnocode, $maskrefclient) + 1).', '.dol_strlen($maskrefclient_maskclientcode).")='".$db->escape($maskrefclient_clientcode)."')";
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $maskrefclient_maskclientcode does not seem to be defined for all execution paths leading up to this point.
Loading history...
1418
1419
			dol_syslog("functions2::get_next_value maskrefclient", LOG_DEBUG);
1420
			$maskrefclient_resql = $db->query($maskrefclient_sql);
1421
			if ($maskrefclient_resql) {
1422
				$maskrefclient_obj = $db->fetch_object($maskrefclient_resql);
1423
				$maskrefclient_counter = $maskrefclient_obj->val;
1424
			} else {
1425
				dol_print_error($db);
1426
			}
1427
1428
			if (empty($maskrefclient_counter) || preg_match('/[^0-9]/i', $maskrefclient_counter)) {
1429
				$maskrefclient_counter = $maskrefclient_maskoffset;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $maskrefclient_maskoffset does not seem to be defined for all execution paths leading up to this point.
Loading history...
1430
			}
1431
			$maskrefclient_counter++;
1432
		}
1433
1434
		// Build numFinal
1435
		$numFinal = $mask;
1436
1437
		// We replace special codes except refclient
1438
		if (!empty($yearoffsettype) && !is_numeric($yearoffsettype) && $yearoffsettype != '=') {	// yearoffsettype is - or +, so we don't want current year
1439
			$numFinal = preg_replace('/\{yyyy\}/i', date("Y", $date) + $yearoffset, $numFinal);
1440
			$numFinal = preg_replace('/\{yy\}/i', date("y", $date) + $yearoffset, $numFinal);
1441
			$numFinal = preg_replace('/\{y\}/i', substr(date("y", $date), 1, 1) + $yearoffset, $numFinal);
1442
		} else // we want yyyy to be current year
1443
		{
1444
			$numFinal = preg_replace('/\{yyyy\}/i', date("Y", $date), $numFinal);
1445
			$numFinal = preg_replace('/\{yy\}/i', date("y", $date), $numFinal);
1446
			$numFinal = preg_replace('/\{y\}/i', substr(date("y", $date), 1, 1), $numFinal);
1447
		}
1448
		$numFinal = preg_replace('/\{mm\}/i', date("m", $date), $numFinal);
1449
		$numFinal = preg_replace('/\{dd\}/i', date("d", $date), $numFinal);
1450
1451
		// Now we replace the counter
1452
		$maskbefore = '{'.$masktri.'}';
1453
		$maskafter = str_pad($counter, dol_strlen($maskcounter), "0", STR_PAD_LEFT);
1454
		//print 'x'.$maskbefore.'-'.$maskafter.'y';
1455
		$numFinal = str_replace($maskbefore, $maskafter, $numFinal);
1456
1457
		// Now we replace the refclient
1458
		if ($maskrefclient) {
1459
			//print "maskrefclient=".$maskrefclient." maskwithonlyymcode=".$maskwithonlyymcode." maskwithnocode=".$maskwithnocode." maskrefclient_clientcode=".$maskrefclient_clientcode."\n<br>";exit;
1460
			$maskrefclient_maskbefore = '{'.$maskrefclient.'}';
1461
			$maskrefclient_maskafter = $maskrefclient_clientcode.str_pad($maskrefclient_counter, dol_strlen($maskrefclient_maskcounter), "0", STR_PAD_LEFT);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $maskrefclient_counter does not seem to be defined for all execution paths leading up to this point.
Loading history...
1462
			$numFinal = str_replace($maskrefclient_maskbefore, $maskrefclient_maskafter, $numFinal);
1463
		}
1464
1465
		// Now we replace the type
1466
		if ($masktype) {
1467
			$masktype_maskbefore = '{'.$masktype.'}';
1468
			$masktype_maskafter = $masktype_value;
1469
			$numFinal = str_replace($masktype_maskbefore, $masktype_maskafter, $numFinal);
1470
		}
1471
1472
		// Now we replace the user
1473
		if ($maskuser) {
1474
			$maskuser_maskbefore = '{'.$maskuser.'}';
1475
			$maskuser_maskafter = $maskuser_value;
1476
			$numFinal = str_replace($maskuser_maskbefore, $maskuser_maskafter, $numFinal);
1477
		}
1478
	}
1479
1480
	dol_syslog("functions2::get_next_value return ".$numFinal, LOG_DEBUG);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $numFinal does not seem to be defined for all execution paths leading up to this point.
Loading history...
1481
	return $numFinal;
1482
}
1483
1484
/**
1485
 * Get string between
1486
 *
1487
 * @param   string  $string     String to test
1488
 * @param   int     $start      Value for start
1489
 * @param   int     $end        Value for end
1490
 * @return  string              Return part of string
1491
 */
1492
function get_string_between($string, $start, $end)
1493
{
1494
	$string = " ".$string;
1495
	 $ini = strpos($string, $start);
1496
	if ($ini == 0) {
1497
		return "";
1498
	}
1499
	 $ini += strlen($start);
1500
	 $len = strpos($string, $end, $ini) - $ini;
1501
	 return substr($string, $ini, $len);
1502
}
1503
1504
/**
1505
 * Check value
1506
 *
1507
 * @param 	string	$mask		Mask to use
1508
 * @param 	string	$value		Value
1509
 * @return	int|string		    <0 or error string if KO, 0 if OK
1510
 */
1511
function check_value($mask, $value)
1512
{
1513
	$result = 0;
1514
1515
	$hasglobalcounter = false;
1516
	// Extract value for mask counter, mask raz and mask offset
1517
	$reg = array();
1518
	if (preg_match('/\{(0+)([@\+][0-9]+)?([@\+][0-9]+)?\}/i', $mask, $reg)) {
1519
		$masktri = $reg[1].(isset($reg[2]) ? $reg[2] : '').(isset($reg[3]) ? $reg[3] : '');
1520
		$maskcounter = $reg[1];
1521
		$hasglobalcounter = true;
1522
	} else {
1523
		// setting some defaults so the rest of the code won't fail if there is a third party counter
1524
		$masktri = '00000';
1525
		$maskcounter = '00000';
1526
	}
1527
	$maskraz = -1;
1528
	$maskoffset = 0;
1529
	if (dol_strlen($maskcounter) < 3) {
1530
		return 'ErrorCounterMustHaveMoreThan3Digits';
1531
	}
1532
1533
	// Extract value for third party mask counter
1534
	$regClientRef = array();
1535
	if (preg_match('/\{(c+)(0*)\}/i', $mask, $regClientRef)) {
1536
		$maskrefclient = $regClientRef[1].$regClientRef[2];
1537
		$maskrefclient_maskclientcode = $regClientRef[1];
1538
		$maskrefclient_maskcounter = $regClientRef[2];
1539
		$maskrefclient_maskoffset = 0; //default value of maskrefclient_counter offset
1540
		$maskrefclient_clientcode = substr('', 0, dol_strlen($maskrefclient_maskclientcode)); //get n first characters of client code to form maskrefclient_clientcode
1541
		$maskrefclient_clientcode = str_pad($maskrefclient_clientcode, dol_strlen($maskrefclient_maskclientcode), "#", STR_PAD_RIGHT); //padding maskrefclient_clientcode for having exactly n characters in maskrefclient_clientcode
1542
		$maskrefclient_clientcode = dol_string_nospecial($maskrefclient_clientcode); //sanitize maskrefclient_clientcode for sql insert and sql select like
1543
		if (dol_strlen($maskrefclient_maskcounter) > 0 && dol_strlen($maskrefclient_maskcounter) < 3) {
1544
			return 'ErrorCounterMustHaveMoreThan3Digits';
1545
		}
1546
	} else {
1547
		$maskrefclient = '';
1548
	}
1549
1550
	// fail if there is neither a global nor a third party counter
1551
	if (!$hasglobalcounter && ($maskrefclient_maskcounter == '')) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $maskrefclient_maskcounter does not seem to be defined for all execution paths leading up to this point.
Loading history...
1552
		return 'ErrorBadMask';
1553
	}
1554
1555
	$maskwithonlyymcode = $mask;
1556
	$maskwithonlyymcode = preg_replace('/\{(0+)([@\+][0-9]+)?([@\+][0-9]+)?\}/i', $maskcounter, $maskwithonlyymcode);
1557
	$maskwithonlyymcode = preg_replace('/\{dd\}/i', 'dd', $maskwithonlyymcode);
1558
	$maskwithonlyymcode = preg_replace('/\{(c+)(0*)\}/i', $maskrefclient, $maskwithonlyymcode);
1559
	$maskwithnocode = $maskwithonlyymcode;
1560
	$maskwithnocode = preg_replace('/\{yyyy\}/i', 'yyyy', $maskwithnocode);
1561
	$maskwithnocode = preg_replace('/\{yy\}/i', 'yy', $maskwithnocode);
1562
	$maskwithnocode = preg_replace('/\{y\}/i', 'y', $maskwithnocode);
1563
	$maskwithnocode = preg_replace('/\{mm\}/i', 'mm', $maskwithnocode);
1564
	// Now maskwithnocode = 0000ddmmyyyyccc for example
1565
	// and maskcounter    = 0000 for example
1566
	//print "maskwithonlyymcode=".$maskwithonlyymcode." maskwithnocode=".$maskwithnocode."\n<br>";
1567
1568
	// If an offset is asked
1569
	if (!empty($reg[2]) && preg_match('/^\+/', $reg[2])) {
1570
		$maskoffset = preg_replace('/^\+/', '', $reg[2]);
1571
	}
1572
	if (!empty($reg[3]) && preg_match('/^\+/', $reg[3])) {
1573
		$maskoffset = preg_replace('/^\+/', '', $reg[3]);
1574
	}
1575
1576
	// Define $sqlwhere
1577
1578
	// If a restore to zero after a month is asked we check if there is already a value for this year.
1579
	if (!empty($reg[2]) && preg_match('/^@/', $reg[2])) {
1580
		$maskraz = preg_replace('/^@/', '', $reg[2]);
1581
	}
1582
	if (!empty($reg[3]) && preg_match('/^@/', $reg[3])) {
1583
		$maskraz = preg_replace('/^@/', '', $reg[3]);
1584
	}
1585
	if ($maskraz >= 0) {
1586
		if ($maskraz == 99) {
1587
			$maskraz = date('m');
1588
			$resetEveryMonth = true;
1589
		}
1590
		if ($maskraz > 12) {
1591
			return 'ErrorBadMaskBadRazMonth';
1592
		}
1593
1594
		// Define reg
1595
		if ($maskraz > 1 && !preg_match('/^(.*)\{(y+)\}\{(m+)\}/i', $maskwithonlyymcode, $reg)) {
1596
			return 'ErrorCantUseRazInStartedYearIfNoYearMonthInMask';
1597
		}
1598
		if ($maskraz <= 1 && !preg_match('/^(.*)\{(y+)\}/i', $maskwithonlyymcode, $reg)) {
1599
			return 'ErrorCantUseRazIfNoYearInMask';
1600
		}
1601
		//print "x".$maskwithonlyymcode." ".$maskraz;
1602
	}
1603
	//print "masktri=".$masktri." maskcounter=".$maskcounter." maskwithonlyymcode=".$maskwithonlyymcode." maskwithnocode=".$maskwithnocode." maskraz=".$maskraz." maskoffset=".$maskoffset."<br>\n";
1604
1605
	if (function_exists('mb_strrpos')) {
1606
		$posnumstart = mb_strrpos($maskwithnocode, $maskcounter, 0, 'UTF-8');
1607
	} else {
1608
		$posnumstart = strrpos($maskwithnocode, $maskcounter);
1609
	}	// Pos of counter in final string (from 0 to ...)
1610
	if ($posnumstart < 0) {
1611
		return 'ErrorBadMaskFailedToLocatePosOfSequence';
1612
	}
1613
1614
	// Check we have a number in $value at position ($posnumstart+1).', '.dol_strlen($maskcounter)
1615
	// TODO
1616
1617
	// Check length
1618
	$len = dol_strlen($maskwithnocode);
1619
	if (dol_strlen($value) != $len) {
1620
		$result = -1;
1621
	}
1622
1623
	dol_syslog("functions2::check_value result=".$result, LOG_DEBUG);
1624
	return $result;
1625
}
1626
1627
/**
1628
 *	Convert a binary data to string that represent hexadecimal value
1629
 *
1630
 *	@param   string		$bin		Value to convert
1631
 *	@param   boolean	$pad      	Add 0
1632
 *	@param   boolean	$upper		Convert to tupper
1633
 *	@return  string					x
1634
 */
1635
function binhex($bin, $pad = false, $upper = false)
1636
{
1637
	$last = dol_strlen($bin) - 1;
1638
	for ($i = 0; $i <= $last; $i++) {
1639
		$x += $bin[$last - $i] * pow(2, $i);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $x seems to be never defined.
Loading history...
1640
	}
1641
	$x = dechex($x);
1642
	if ($pad) {
1643
		while (dol_strlen($x) < intval(dol_strlen($bin)) / 4) {
1644
			$x = "0$x";
1645
		}
1646
	}
1647
	if ($upper) {
1648
		$x = strtoupper($x);
1649
	}
1650
	return $x;
1651
}
1652
1653
/**
1654
 *	Convert an hexadecimal string into a binary string
1655
 *
1656
 *	@param	string	$hexa		Hexadecimal string to convert (example: 'FF')
1657
 *	@return string	    		bin
1658
 */
1659
function hexbin($hexa)
1660
{
1661
	$bin = '';
1662
	$strLength = dol_strlen($hexa);
1663
	for ($i = 0; $i < $strLength; $i++) {
1664
		$bin .= str_pad(decbin(hexdec($hexa[$i])), 4, '0', STR_PAD_LEFT);
1665
	}
1666
	return $bin;
1667
}
1668
1669
/**
1670
 *	Retourne le numero de la semaine par rapport a une date
1671
 *
1672
 *	@param	string	$time   	Date au format 'timestamp'
1673
 *	@return string					Number of week
1674
 */
1675
function numero_semaine($time)
1676
{
1677
	$stime = strftime('%Y-%m-%d', $time);
1678
1679
	if (preg_match('/^([0-9]+)\-([0-9]+)\-([0-9]+)\s?([0-9]+)?:?([0-9]+)?/i', $stime, $reg)) {
1680
		// Date est au format 'YYYY-MM-DD' ou 'YYYY-MM-DD HH:MM:SS'
1681
		$annee = $reg[1];
1682
		$mois = $reg[2];
1683
		$jour = $reg[3];
1684
	}
1685
1686
	/*
1687
	 * Norme ISO-8601:
1688
	 * - La semaine 1 de toute annee est celle qui contient le 4 janvier ou que la semaine 1 de toute annee est celle qui contient le 1er jeudi de janvier.
1689
	 * - La majorite des annees ont 52 semaines mais les annees qui commence un jeudi et les annees bissextiles commencant un mercredi en possede 53.
1690
	 * - Le 1er jour de la semaine est le Lundi
1691
	 */
1692
1693
	// Definition du Jeudi de la semaine
1694
	if (date("w", mktime(12, 0, 0, $mois, $jour, $annee)) == 0) { // Dimanche
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $annee does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $mois does not seem to be defined for all execution paths leading up to this point.
Loading history...
Comprehensibility Best Practice introduced by
The variable $jour does not seem to be defined for all execution paths leading up to this point.
Loading history...
1695
		$jeudiSemaine = mktime(12, 0, 0, $mois, $jour, $annee) - 3 * 24 * 60 * 60;
1696
	} elseif (date("w", mktime(12, 0, 0, $mois, $jour, $annee)) < 4) { // du Lundi au Mercredi
1697
		$jeudiSemaine = mktime(12, 0, 0, $mois, $jour, $annee) + (4 - date("w", mktime(12, 0, 0, $mois, $jour, $annee))) * 24 * 60 * 60;
1698
	} elseif (date("w", mktime(12, 0, 0, $mois, $jour, $annee)) > 4) { // du Vendredi au Samedi
1699
		$jeudiSemaine = mktime(12, 0, 0, $mois, $jour, $annee) - (date("w", mktime(12, 0, 0, $mois, $jour, $annee)) - 4) * 24 * 60 * 60;
1700
	} else { // Jeudi
1701
		$jeudiSemaine = mktime(12, 0, 0, $mois, $jour, $annee);
1702
	}
1703
1704
	// Definition du premier Jeudi de l'annee
1705
	if (date("w", mktime(12, 0, 0, 1, 1, date("Y", $jeudiSemaine))) == 0) { // Dimanche
1706
		$premierJeudiAnnee = mktime(12, 0, 0, 1, 1, date("Y", $jeudiSemaine)) + 4 * 24 * 60 * 60;
1707
	} elseif (date("w", mktime(12, 0, 0, 1, 1, date("Y", $jeudiSemaine))) < 4) { // du Lundi au Mercredi
1708
		$premierJeudiAnnee = mktime(12, 0, 0, 1, 1, date("Y", $jeudiSemaine)) + (4 - date("w", mktime(12, 0, 0, 1, 1, date("Y", $jeudiSemaine)))) * 24 * 60 * 60;
1709
	} elseif (date("w", mktime(12, 0, 0, 1, 1, date("Y", $jeudiSemaine))) > 4) { // du Vendredi au Samedi
1710
		$premierJeudiAnnee = mktime(12, 0, 0, 1, 1, date("Y", $jeudiSemaine)) + (7 - (date("w", mktime(12, 0, 0, 1, 1, date("Y", $jeudiSemaine))) - 4)) * 24 * 60 * 60;
1711
	} else // Jeudi
1712
	{
1713
		$premierJeudiAnnee = mktime(12, 0, 0, 1, 1, date("Y", $jeudiSemaine));
1714
	}
1715
1716
	// Definition du numero de semaine: nb de jours entre "premier Jeudi de l'annee" et "Jeudi de la semaine";
1717
	$numeroSemaine = (
1718
	(
1719
	date("z", mktime(12, 0, 0, date("m", $jeudiSemaine), date("d", $jeudiSemaine), date("Y", $jeudiSemaine)))
1720
	-
1721
	date("z", mktime(12, 0, 0, date("m", $premierJeudiAnnee), date("d", $premierJeudiAnnee), date("Y", $premierJeudiAnnee)))
1722
	) / 7
1723
	) + 1;
1724
1725
	// Cas particulier de la semaine 53
1726
	if ($numeroSemaine == 53) {
1727
		// Les annees qui commence un Jeudi et les annees bissextiles commencant un Mercredi en possede 53
1728
		if (date("w", mktime(12, 0, 0, 1, 1, date("Y", $jeudiSemaine))) == 4 || (date("w", mktime(12, 0, 0, 1, 1, date("Y", $jeudiSemaine))) == 3 && date("z", mktime(12, 0, 0, 12, 31, date("Y", $jeudiSemaine))) == 365)) {
1729
			$numeroSemaine = 53;
1730
		} else {
1731
			$numeroSemaine = 1;
1732
		}
1733
	}
1734
1735
	//echo $jour."-".$mois."-".$annee." (".date("d-m-Y",$premierJeudiAnnee)." - ".date("d-m-Y",$jeudiSemaine).") -> ".$numeroSemaine."<BR>";
1736
1737
	return sprintf("%02d", $numeroSemaine);
1738
}
1739
1740
/**
1741
 *	Convertit une masse d'une unite vers une autre unite
1742
 *
1743
 *	@param	float	$weight    		Masse a convertir
1744
 *	@param  int		$from_unit 		Unite originale en puissance de 10
1745
 *	@param  int		$to_unit   		Nouvelle unite  en puissance de 10
1746
 *	@return float	        		Masse convertie
1747
 */
1748
function weight_convert($weight, &$from_unit, $to_unit)
1749
{
1750
	/* Pour convertire 320 gr en Kg appeler
1751
	 *  $f = -3
1752
	 *  weigh_convert(320, $f, 0) retournera 0.32
1753
	 *
1754
	 */
1755
	while ($from_unit <> $to_unit) {
1756
		if ($from_unit > $to_unit) {
1757
			$weight = $weight * 10;
1758
			$from_unit = $from_unit - 1;
1759
			$weight = weight_convert($weight, $from_unit, $to_unit);
1760
		}
1761
		if ($from_unit < $to_unit) {
1762
			$weight = $weight / 10;
1763
			$from_unit = $from_unit + 1;
1764
			$weight = weight_convert($weight, $from_unit, $to_unit);
1765
		}
1766
	}
1767
1768
	return $weight;
1769
}
1770
1771
/**
1772
 *	Save personnal parameter
1773
 *
1774
 *	@param	DoliDB	$db         Handler database
1775
 *	@param	Conf	$conf		Object conf
1776
 *	@param	User	$user      	Object user
1777
 *	@param	array	$tab        Array (key=>value) with all parameters to save
1778
 *	@return int         		<0 if KO, >0 if OK
1779
 *
1780
 *	@see		dolibarr_get_const(), dolibarr_set_const(), dolibarr_del_const()
1781
 */
1782
function dol_set_user_param($db, $conf, &$user, $tab)
1783
{
1784
	// Verification parametres
1785
	if (count($tab) < 1) {
1786
		return -1;
1787
	}
1788
1789
	$db->begin();
1790
1791
	// We remove old parameters for all keys in $tab
1792
	$sql = "DELETE FROM ".MAIN_DB_PREFIX."user_param";
1793
	$sql .= " WHERE fk_user = ".$user->id;
1794
	$sql .= " AND entity = ".$conf->entity;
1795
	$sql .= " AND param in (";
1796
	$i = 0;
1797
	foreach ($tab as $key => $value) {
1798
		if ($i > 0) {
1799
			$sql .= ',';
1800
		}
1801
		$sql .= "'".$db->escape($key)."'";
1802
		$i++;
1803
	}
1804
	$sql .= ")";
1805
	dol_syslog("functions2.lib::dol_set_user_param", LOG_DEBUG);
1806
1807
	$resql = $db->query($sql);
1808
	if (!$resql) {
1809
		dol_print_error($db);
1810
		$db->rollback();
1811
		return -1;
1812
	}
1813
1814
	foreach ($tab as $key => $value) {
1815
		// Set new parameters
1816
		if ($value) {
1817
			$sql = "INSERT INTO ".MAIN_DB_PREFIX."user_param(fk_user,entity,param,value)";
1818
			$sql .= " VALUES (".$user->id.",".$conf->entity.",";
1819
			$sql .= " '".$db->escape($key)."','".$db->escape($value)."')";
1820
1821
			dol_syslog("functions2.lib::dol_set_user_param", LOG_DEBUG);
1822
			$result = $db->query($sql);
1823
			if (!$result) {
1824
				dol_print_error($db);
1825
				$db->rollback();
1826
				return -1;
1827
			}
1828
			$user->conf->$key = $value;
1829
			//print "key=".$key." user->conf->key=".$user->conf->$key;
1830
		} else {
1831
			unset($user->conf->$key);
1832
		}
1833
	}
1834
1835
	$db->commit();
1836
	return 1;
1837
}
1838
1839
/**
1840
 *	Returns formated reduction
1841
 *
1842
 *	@param	int			$reduction		Reduction percentage
1843
 *	@param	Translate	$langs			Output language
1844
 *	@return	string						Formated reduction
1845
 */
1846
function dol_print_reduction($reduction, $langs)
1847
{
1848
	$string = '';
1849
	if ($reduction == 100) {
1850
		$string = $langs->transnoentities("Offered");
1851
	} else {
1852
		$string = vatrate($reduction, true);
1853
	}
1854
1855
	return $string;
1856
}
1857
1858
/**
1859
 * 	Return OS version.
1860
 *  Note that PHP_OS returns only OS (not version) and OS PHP was built on, not necessarly OS PHP runs on.
1861
 *
1862
 *  @param 		string		$option 	Option string
1863
 * 	@return		string					OS version
1864
 */
1865
function version_os($option = '')
1866
{
1867
	if ($option == 'smr') {
1868
		$osversion = php_uname('s').' '.php_uname('m').' '.php_uname('r');
1869
	} else {
1870
		$osversion = php_uname();
1871
	}
1872
	return $osversion;
1873
}
1874
1875
/**
1876
 * 	Return PHP version
1877
 *
1878
 * 	@return		string			PHP version
1879
 *  @see		versionphparray()
1880
 */
1881
function version_php()
1882
{
1883
	return phpversion();
1884
}
1885
1886
/**
1887
 * 	Return Dolibarr version
1888
 *
1889
 * 	@return		string			Dolibarr version
1890
 *  @see		versiondolibarrarray()
1891
 */
1892
function version_dolibarr()
1893
{
1894
	return DOL_VERSION;
1895
}
1896
1897
/**
1898
 * 	Return web server version
1899
 *
1900
 * 	@return		string			Web server version
1901
 */
1902
function version_webserver()
1903
{
1904
	return $_SERVER["SERVER_SOFTWARE"];
1905
}
1906
1907
/**
1908
 * 	Return list of activated modules usable for document generation
1909
 *
1910
 * 	@param	DoliDB		$db				    Database handler
1911
 * 	@param	string		$type			    Type of models (company, invoice, ...)
1912
 *  @param  int		    $maxfilenamelength  Max length of value to show
1913
 * 	@return	array|int			    		0 if no module is activated, or array(key=>label). For modules that need directory scan, key is completed with ":filename".
1914
 */
1915
function getListOfModels($db, $type, $maxfilenamelength = 0)
1916
{
1917
	global $conf, $langs;
1918
	$liste = array();
1919
	$found = 0;
1920
	$dirtoscan = '';
1921
1922
	$sql = "SELECT nom as id, nom as doc_template_name, libelle as label, description as description";
1923
	$sql .= " FROM ".MAIN_DB_PREFIX."document_model";
1924
	$sql .= " WHERE type = '".$db->escape($type)."'";
1925
	$sql .= " AND entity IN (0,".$conf->entity.")";
1926
	$sql .= " ORDER BY description DESC";
1927
1928
	dol_syslog('/core/lib/function2.lib.php::getListOfModels', LOG_DEBUG);
1929
	$resql = $db->query($sql);
1930
	if ($resql) {
1931
		$num = $db->num_rows($resql);
1932
		$i = 0;
1933
		while ($i < $num) {
1934
			$found = 1;
1935
1936
			$obj = $db->fetch_object($resql);
1937
1938
			// If this generation module needs to scan a directory, then description field is filled
1939
			// with the constant that contains list of directories to scan (COMPANY_ADDON_PDF_ODT_PATH, ...).
1940
			if (!empty($obj->description)) {	// A list of directories to scan is defined
1941
				include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1942
1943
				$const = $obj->description;
1944
				//irtoscan.=($dirtoscan?',':'').preg_replace('/[\r\n]+/',',',trim($conf->global->$const));
1945
				$dirtoscan = preg_replace('/[\r\n]+/', ',', trim($conf->global->$const));
1946
1947
				$listoffiles = array();
1948
1949
				// Now we add models found in directories scanned
1950
				$listofdir = explode(',', $dirtoscan);
1951
				foreach ($listofdir as $key => $tmpdir) {
1952
					$tmpdir = trim($tmpdir);
1953
					$tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir);
1954
					if (!$tmpdir) {
1955
						unset($listofdir[$key]);
1956
						continue;
1957
					}
1958
					if (is_dir($tmpdir)) {
1959
						// all type of template is allowed
1960
						$tmpfiles = dol_dir_list($tmpdir, 'files', 0, '', '', 'name', SORT_ASC, 0);
1961
						if (count($tmpfiles)) {
1962
							$listoffiles = array_merge($listoffiles, $tmpfiles);
1963
						}
1964
					}
1965
				}
1966
1967
				if (count($listoffiles)) {
1968
					foreach ($listoffiles as $record) {
1969
						$max = ($maxfilenamelength ? $maxfilenamelength : 28);
1970
						$liste[$obj->id.':'.$record['fullname']] = dol_trunc($record['name'], $max, 'middle');
1971
					}
1972
				} else {
1973
					$liste[0] = $obj->label.': '.$langs->trans("None");
1974
				}
1975
			} else {
1976
				if ($type == 'member' && $obj->doc_template_name == 'standard') {   // Special case, if member template, we add variant per format
1977
					global $_Avery_Labels;
1978
					include_once DOL_DOCUMENT_ROOT.'/core/lib/format_cards.lib.php';
1979
					foreach ($_Avery_Labels as $key => $val) {
1980
						$liste[$obj->id.':'.$key] = ($obj->label ? $obj->label : $obj->doc_template_name).' '.$val['name'];
1981
					}
1982
				} else {
1983
					// Common usage
1984
					$liste[$obj->id] = $obj->label ? $obj->label : $obj->doc_template_name;
1985
				}
1986
			}
1987
			$i++;
1988
		}
1989
	} else {
1990
		dol_print_error($db);
1991
		return -1;
1992
	}
1993
1994
	if ($found) {
1995
		return $liste;
1996
	} else {
1997
		return 0;
1998
	}
1999
}
2000
2001
/**
2002
 * This function evaluates a string that should be a valid IPv4
2003
 * Note: For ip 169.254.0.0, it returns 0 with some PHP (5.6.24) and 2 with some minor patchs of PHP (5.6.25). See https://github.com/php/php-src/pull/1954.
2004
 *
2005
 * @param	string $ip IP Address
2006
 * @return	int 0 if not valid or reserved range, 1 if valid and public IP, 2 if valid and private range IP
2007
 */
2008
function is_ip($ip)
2009
{
2010
	// First we test if it is a valid IPv4
2011
	if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
2012
		// Then we test if it is a private range
2013
		if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)) {
2014
			return 2;
2015
		}
2016
2017
		// Then we test if it is a reserved range
2018
		if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE)) {
2019
			return 0;
2020
		}
2021
2022
		return 1;
2023
	}
2024
2025
	return 0;
2026
}
2027
2028
/**
2029
 *  Build a login from lastname, firstname
2030
 *
2031
 *  @param	string		$lastname		Lastname
2032
 *  @param  string		$firstname		Firstname
2033
 *	@return	string						Login
2034
 */
2035
function dol_buildlogin($lastname, $firstname)
2036
{
2037
	global $conf;
2038
2039
	//$conf->global->MAIN_BUILD_LOGIN_RULE = 'f.lastname';
2040
	if (!empty($conf->global->MAIN_BUILD_LOGIN_RULE) && $conf->global->MAIN_BUILD_LOGIN_RULE == 'f.lastname') {	// f.lastname
2041
		$login = strtolower(dol_string_unaccent(dol_trunc($firstname, 1, 'right', 'UTF-8', 1)));
2042
		$login .= ($login ? '.' : '');
2043
		$login .= strtolower(dol_string_unaccent($lastname));
2044
		$login = dol_string_nospecial($login, ''); // For special names
2045
	} else {	// firstname.lastname
2046
		$login = strtolower(dol_string_unaccent($firstname));
2047
		$login .= ($login ? '.' : '');
2048
		$login .= strtolower(dol_string_unaccent($lastname));
2049
		$login = dol_string_nospecial($login, ''); // For special names
2050
	}
2051
2052
	// TODO Add a hook to allow external modules to suggest new rules
2053
2054
	return $login;
2055
}
2056
2057
/**
2058
 *  Return array to use for SoapClient constructor
2059
 *
2060
 *  @return     array
2061
 */
2062
function getSoapParams()
2063
{
2064
	global $conf;
2065
2066
	$params = array();
2067
	$proxyuse = (empty($conf->global->MAIN_PROXY_USE) ?false:true);
2068
	$proxyhost = (empty($conf->global->MAIN_PROXY_USE) ?false:$conf->global->MAIN_PROXY_HOST);
2069
	$proxyport = (empty($conf->global->MAIN_PROXY_USE) ?false:$conf->global->MAIN_PROXY_PORT);
2070
	$proxyuser = (empty($conf->global->MAIN_PROXY_USE) ?false:$conf->global->MAIN_PROXY_USER);
2071
	$proxypass = (empty($conf->global->MAIN_PROXY_USE) ?false:$conf->global->MAIN_PROXY_PASS);
2072
	$timeout = (empty($conf->global->MAIN_USE_CONNECT_TIMEOUT) ? 10 : $conf->global->MAIN_USE_CONNECT_TIMEOUT); // Connection timeout
2073
	$response_timeout = (empty($conf->global->MAIN_USE_RESPONSE_TIMEOUT) ? 30 : $conf->global->MAIN_USE_RESPONSE_TIMEOUT); // Response timeout
2074
	//print extension_loaded('soap');
2075
	if ($proxyuse) {
2076
		$params = array('connection_timeout'=>$timeout,
2077
					  'response_timeout'=>$response_timeout,
2078
					  'proxy_use'      => 1,
2079
					  'proxy_host'     => $proxyhost,
2080
					  'proxy_port'     => $proxyport,
2081
					  'proxy_login'    => $proxyuser,
2082
					  'proxy_password' => $proxypass,
2083
					  'trace'		   => 1
2084
		);
2085
	} else {
2086
		$params = array('connection_timeout'=>$timeout,
2087
					  'response_timeout'=>$response_timeout,
2088
					  'proxy_use'      => 0,
2089
					  'proxy_host'     => false,
2090
					  'proxy_port'     => false,
2091
					  'proxy_login'    => false,
2092
					  'proxy_password' => false,
2093
					  'trace'		   => 1
2094
		);
2095
	}
2096
	return $params;
2097
}
2098
2099
2100
/**
2101
 * Return link url to an object
2102
 *
2103
 * @param 	int		$objectid		Id of record
2104
 * @param 	string	$objecttype		Type of object ('invoice', 'order', 'expedition_bon', 'myobject@mymodule', ...)
2105
 * @param 	int		$withpicto		Picto to show
2106
 * @param 	string	$option			More options
2107
 * @return	string					URL of link to object id/type
2108
 */
2109
function dolGetElementUrl($objectid, $objecttype, $withpicto = 0, $option = '')
2110
{
2111
	global $db, $conf, $langs;
2112
2113
	$ret = '';
2114
	$regs = array();
2115
2116
	// If we ask a resource form external module (instead of default path)
2117
	if (preg_match('/^([^@]+)@([^@]+)$/i', $objecttype, $regs)) {
2118
		$myobject = $regs[1];
2119
		$module = $regs[2];
2120
	} else {
2121
		// Parse $objecttype (ex: project_task)
2122
		$module = $myobject = $objecttype;
2123
		if (preg_match('/^([^_]+)_([^_]+)/i', $objecttype, $regs)) {
2124
			$module = $regs[1];
2125
			$myobject = $regs[2];
2126
		}
2127
	}
2128
2129
	// Generic case for $classpath
2130
	$classpath = $module.'/class';
2131
2132
	// Special cases, to work with non standard path
2133
	if ($objecttype == 'facture' || $objecttype == 'invoice') {
2134
		$classpath = 'compta/facture/class';
2135
		$module = 'facture';
2136
		$myobject = 'facture';
2137
	} elseif ($objecttype == 'commande' || $objecttype == 'order') {
2138
		$classpath = 'commande/class';
2139
		$module = 'commande';
2140
		$myobject = 'commande';
2141
	} elseif ($objecttype == 'propal') {
2142
		$classpath = 'comm/propal/class';
2143
	} elseif ($objecttype == 'supplier_proposal') {
2144
		$classpath = 'supplier_proposal/class';
2145
	} elseif ($objecttype == 'shipping') {
2146
		$classpath = 'expedition/class';
2147
		$myobject = 'expedition';
2148
		$module = 'expedition_bon';
2149
	} elseif ($objecttype == 'delivery') {
2150
		$classpath = 'delivery/class';
2151
		$myobject = 'delivery';
2152
		$module = 'delivery_note';
2153
	} elseif ($objecttype == 'contract') {
2154
		$classpath = 'contrat/class';
2155
		$module = 'contrat';
2156
		$myobject = 'contrat';
2157
	} elseif ($objecttype == 'member') {
2158
		$classpath = 'adherents/class';
2159
		$module = 'adherent';
2160
		$myobject = 'adherent';
2161
	} elseif ($objecttype == 'cabinetmed_cons') {
2162
		$classpath = 'cabinetmed/class';
2163
		$module = 'cabinetmed';
2164
		$myobject = 'cabinetmedcons';
2165
	} elseif ($objecttype == 'fichinter') {
2166
		$classpath = 'fichinter/class';
2167
		$module = 'ficheinter';
2168
		$myobject = 'fichinter';
2169
	} elseif ($objecttype == 'task') {
2170
		$classpath = 'projet/class';
2171
		$module = 'projet';
2172
		$myobject = 'task';
2173
	} elseif ($objecttype == 'stock') {
2174
		$classpath = 'product/stock/class';
2175
		$module = 'stock';
2176
		$myobject = 'stock';
2177
	} elseif ($objecttype == 'inventory') {
2178
		$classpath = 'product/inventory/class';
2179
		$module = 'stock';
2180
		$myobject = 'inventory';
2181
	} elseif ($objecttype == 'mo') {
2182
		$classpath = 'mrp/class';
2183
		$module = 'mrp';
2184
		$myobject = 'mo';
2185
	} elseif ($objecttype == 'productlot') {
2186
		$classpath = 'product/stock/class';
2187
		$module = 'stock';
2188
		$myobject = 'productlot';
2189
	}
2190
2191
	// Generic case for $classfile and $classname
2192
	$classfile = strtolower($myobject);
2193
	$classname = ucfirst($myobject);
2194
	//print "objecttype=".$objecttype." module=".$module." subelement=".$subelement." classfile=".$classfile." classname=".$classname." classpath=".$classpath;
2195
2196
	if ($objecttype == 'invoice_supplier') {
2197
		$classfile = 'fournisseur.facture';
2198
		$classname = 'FactureFournisseur';
2199
		$classpath = 'fourn/class';
2200
		$module = 'fournisseur';
2201
	} elseif ($objecttype == 'order_supplier') {
2202
		$classfile = 'fournisseur.commande';
2203
		$classname = 'CommandeFournisseur';
2204
		$classpath = 'fourn/class';
2205
		$module = 'fournisseur';
2206
	} elseif ($objecttype == 'supplier_proposal') {
2207
		$classfile = 'supplier_proposal';
2208
		$classname = 'SupplierProposal';
2209
		$classpath = 'supplier_proposal/class';
2210
		$module = 'supplier_proposal';
2211
	} elseif ($objecttype == 'stock') {
2212
		$classpath = 'product/stock/class';
2213
		$classfile = 'entrepot';
2214
		$classname = 'Entrepot';
2215
	}
2216
2217
	if (!empty($conf->$module->enabled)) {
2218
		$res = dol_include_once('/'.$classpath.'/'.$classfile.'.class.php');
2219
		if ($res) {
2220
			if (class_exists($classname)) {
2221
				$object = new $classname($db);
2222
				$res = $object->fetch($objectid);
2223
				if ($res > 0) {
2224
					$ret = $object->getNomUrl($withpicto, $option);
2225
				} elseif ($res == 0) {
2226
					$ret = $langs->trans('Deleted');
2227
				}
2228
				unset($object);
2229
			} else {
2230
				dol_syslog("Class with classname ".$classname." is unknown even after the include", LOG_ERR);
2231
			}
2232
		}
2233
	}
2234
	return $ret;
2235
}
2236
2237
2238
/**
2239
 * Clean corrupted tree (orphelins linked to a not existing parent), record linked to themself and child-parent loop
2240
 *
2241
 * @param	DoliDB	$db					Database handler
2242
 * @param	string	$tabletocleantree	Table to clean
2243
 * @param	string	$fieldfkparent		Field name that contains id of parent
2244
 * @return	int							Nb of records fixed/deleted
2245
 */
2246
function cleanCorruptedTree($db, $tabletocleantree, $fieldfkparent)
2247
{
2248
	$totalnb = 0;
2249
	$listofid = array();
2250
	$listofparentid = array();
2251
2252
	// Get list of all id in array listofid and all parents in array listofparentid
2253
	$sql = 'SELECT rowid, '.$fieldfkparent.' as parent_id FROM '.MAIN_DB_PREFIX.$tabletocleantree;
2254
	$resql = $db->query($sql);
2255
	if ($resql) {
2256
		$num = $db->num_rows($resql);
2257
		$i = 0;
2258
		while ($i < $num) {
2259
			$obj = $db->fetch_object($resql);
2260
			$listofid[] = $obj->rowid;
2261
			if ($obj->parent_id > 0) {
2262
				$listofparentid[$obj->rowid] = $obj->parent_id;
2263
			}
2264
			$i++;
2265
		}
2266
	} else {
2267
		dol_print_error($db);
2268
	}
2269
2270
	if (count($listofid)) {
2271
		print 'Code requested to clean tree (may be to solve data corruption), so we check/clean orphelins and loops.'."<br>\n";
2272
2273
		// Check loops on each other
2274
		$sql = "UPDATE ".MAIN_DB_PREFIX.$tabletocleantree." SET ".$fieldfkparent." = 0 WHERE ".$fieldfkparent." = rowid"; // So we update only records linked to themself
2275
		$resql = $db->query($sql);
2276
		if ($resql) {
2277
			$nb = $db->affected_rows($sql);
2278
			if ($nb > 0) {
2279
				print '<br>Some record that were parent of themself were cleaned.';
2280
			}
2281
2282
			$totalnb += $nb;
2283
		}
2284
		//else dol_print_error($db);
2285
2286
		// Check other loops
2287
		$listofidtoclean = array();
2288
		foreach ($listofparentid as $id => $pid) {
2289
			// Check depth
2290
			//print 'Analyse record id='.$id.' with parent '.$pid.'<br>';
2291
2292
			$cursor = $id;
2293
			$arrayidparsed = array(); // We start from child $id
2294
			while ($cursor > 0) {
2295
				$arrayidparsed[$cursor] = 1;
2296
				if ($arrayidparsed[$listofparentid[$cursor]]) {	// We detect a loop. A record with a parent that was already into child
2297
					print 'Found a loop between id '.$id.' - '.$cursor.'<br>';
2298
					unset($arrayidparsed);
2299
					$listofidtoclean[$cursor] = $id;
2300
					break;
2301
				}
2302
				$cursor = $listofparentid[$cursor];
2303
			}
2304
2305
			if (count($listofidtoclean)) {
2306
				break;
2307
			}
2308
		}
2309
2310
		$sql = "UPDATE ".MAIN_DB_PREFIX.$tabletocleantree;
2311
		$sql .= " SET ".$fieldfkparent." = 0";
2312
		$sql .= " WHERE rowid IN (".$db->sanitize(join(',', $listofidtoclean)).")"; // So we update only records detected wrong
2313
		$resql = $db->query($sql);
2314
		if ($resql) {
2315
			$nb = $db->affected_rows($sql);
2316
			if ($nb > 0) {
2317
				// Removed orphelins records
2318
				print '<br>Some records were detected to have parent that is a child, we set them as root record for id: ';
2319
				print join(',', $listofidtoclean);
2320
			}
2321
2322
			$totalnb += $nb;
2323
		}
2324
		//else dol_print_error($db);
2325
2326
		// Check and clean orphelins
2327
		$sql = "UPDATE ".MAIN_DB_PREFIX.$tabletocleantree;
2328
		$sql .= " SET ".$fieldfkparent." = 0";
2329
		$sql .= " WHERE ".$fieldfkparent." NOT IN (".$db->sanitize(join(',', $listofid), 1).")"; // So we update only records linked to a non existing parent
2330
		$resql = $db->query($sql);
2331
		if ($resql) {
2332
			$nb = $db->affected_rows($sql);
2333
			if ($nb > 0) {
2334
				// Removed orphelins records
2335
				print '<br>Some orphelins were found and modified to be parent so records are visible again for id: ';
2336
				print join(',', $listofid);
2337
			}
2338
2339
			$totalnb += $nb;
2340
		}
2341
		//else dol_print_error($db);
2342
2343
		print '<br>We fixed '.$totalnb.' record(s). Some records may still be corrupted. New check may be required.';
2344
		return $totalnb;
2345
	}
2346
}
2347
2348
2349
/**
2350
 *	Convert an array with RGB value into hex RGB value.
2351
 *  This is the opposite function of colorStringToArray
2352
 *
2353
 *  @param	array	$arraycolor			Array
2354
 *  @param	string	$colorifnotfound	Color code to return if entry not defined or not a RGB format
2355
 *  @return	string						RGB hex value (without # before). For example: 'FF00FF', '01FF02'
2356
 *  @see	colorStringToArray(), colorHexToRgb()
2357
 */
2358
function colorArrayToHex($arraycolor, $colorifnotfound = '888888')
2359
{
2360
	if (!is_array($arraycolor)) {
2361
		return $colorifnotfound;
2362
	}
2363
	if (empty($arraycolor)) {
2364
		return $colorifnotfound;
2365
	}
2366
	return sprintf("%02s", dechex($arraycolor[0])).sprintf("%02s", dechex($arraycolor[1])).sprintf("%02s", dechex($arraycolor[2]));
2367
}
2368
2369
/**
2370
 *	Convert a string RGB value ('FFFFFF', '255,255,255') into an array RGB array(255,255,255).
2371
 *  This is the opposite function of colorArrayToHex.
2372
 *  If entry is already an array, return it.
2373
 *
2374
 *  @param	string	$stringcolor		String with hex (FFFFFF) or comma RGB ('255,255,255')
2375
 *  @param	array	$colorifnotfound	Color code array to return if entry not defined
2376
 *  @return	array   					RGB hex value (without # before). For example: FF00FF
2377
 *  @see	colorArrayToHex(), colorHexToRgb()
2378
 */
2379
function colorStringToArray($stringcolor, $colorifnotfound = array(88, 88, 88))
2380
{
2381
	if (is_array($stringcolor)) {
0 ignored issues
show
introduced by
The condition is_array($stringcolor) is always false.
Loading history...
2382
		return $stringcolor; // If already into correct output format, we return as is
2383
	}
2384
	$reg = array();
2385
	$tmp = preg_match('/^#?([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])$/', $stringcolor, $reg);
2386
	if (!$tmp) {
2387
		$tmp = explode(',', $stringcolor);
2388
		if (count($tmp) < 3) {
2389
			return $colorifnotfound;
2390
		}
2391
		return $tmp;
2392
	}
2393
	return array(hexdec($reg[1]), hexdec($reg[2]), hexdec($reg[3]));
2394
}
2395
2396
/**
2397
 * @param string 	$color 			the color you need to valid
2398
 * @param boolean 	$allow_white 	in case of white isn't valid
2399
 * @return boolean
2400
 */
2401
function colorValidateHex($color, $allow_white = true)
2402
{
2403
	if (!$allow_white && ($color === '#fff' || $color === '#ffffff')) {
2404
		return false;
2405
	}
2406
2407
	if (preg_match('/^#[a-f0-9]{6}$/i', $color)) { //hex color is valid
2408
		return true;
2409
	}
2410
	return false;
2411
}
2412
2413
/**
2414
 * Change color to make it less aggressive (ratio is negative) or more aggressive (ratio is positive)
2415
 *
2416
 * @param 	string 		$hex			Color in hex ('#AA1122' or 'AA1122' or '#a12' or 'a12')
2417
 * @param 	integer		$ratio			Default=-50. Note: 0=Component color is unchanged, -100=Component color become 88, +100=Component color become 00 or FF
2418
 * @param	integer		$brightness 	Default=0. Adjust brightness. -100=Decrease brightness by 100%, +100=Increase of 100%.
2419
 * @return string		New string of color
2420
 * @see colorAdjustBrightness()
2421
 */
2422
function colorAgressiveness($hex, $ratio = -50, $brightness = 0)
2423
{
2424
	if (empty($ratio)) {
2425
		$ratio = 0; // To avoid null
2426
	}
2427
2428
	// Steps should be between -255 and 255. Negative = darker, positive = lighter
2429
	$ratio = max(-100, min(100, $ratio));
2430
2431
	// Normalize into a six character long hex string
2432
	$hex = str_replace('#', '', $hex);
2433
	if (strlen($hex) == 3) {
2434
		$hex = str_repeat(substr($hex, 0, 1), 2).str_repeat(substr($hex, 1, 1), 2).str_repeat(substr($hex, 2, 1), 2);
2435
	}
2436
2437
	// Split into three parts: R, G and B
2438
	$color_parts = str_split($hex, 2);
2439
	$return = '#';
2440
2441
	foreach ($color_parts as $color) {
2442
		$color = hexdec($color); // Convert to decimal
2443
		if ($ratio > 0) {	// We increase aggressivity
2444
			if ($color > 127) {
2445
				$color += ((255 - $color) * ($ratio / 100));
2446
			}
2447
			if ($color < 128) {
2448
				$color -= ($color * ($ratio / 100));
2449
			}
2450
		} else // We decrease agressiveness
2451
		{
2452
			if ($color > 128) {
2453
				$color -= (($color - 128) * (abs($ratio) / 100));
2454
			}
2455
			if ($color < 127) {
2456
				$color += ((128 - $color) * (abs($ratio) / 100));
2457
			}
2458
		}
2459
		if ($brightness > 0) {
2460
			$color = ($color * (100 + abs($brightness)) / 100);
2461
		} else {
2462
			$color = ($color * (100 - abs($brightness)) / 100);
2463
		}
2464
2465
		$color   = max(0, min(255, $color)); // Adjust color to stay into valid range
2466
		$return .= str_pad(dechex($color), 2, '0', STR_PAD_LEFT); // Make two char hex code
2467
	}
2468
2469
	//var_dump($hex.' '.$ratio.' -> '.$return);
2470
	return $return;
2471
}
2472
2473
/**
2474
 * @param string 	$hex 		Color in hex ('#AA1122' or 'AA1122' or '#a12' or 'a12')
2475
 * @param integer 	$steps 		Step/offset added to each color component. It should be between -255 and 255. Negative = darker, positive = lighter
2476
 * @return string				New color with format '#AA1122'
2477
 * @see colorAgressiveness()
2478
 */
2479
function colorAdjustBrightness($hex, $steps)
2480
{
2481
	// Steps should be between -255 and 255. Negative = darker, positive = lighter
2482
	$steps = max(-255, min(255, $steps));
2483
2484
	// Normalize into a six character long hex string
2485
	$hex = str_replace('#', '', $hex);
2486
	if (strlen($hex) == 3) {
2487
		$hex = str_repeat(substr($hex, 0, 1), 2).str_repeat(substr($hex, 1, 1), 2).str_repeat(substr($hex, 2, 1), 2);
2488
	}
2489
2490
	// Split into three parts: R, G and B
2491
	$color_parts = str_split($hex, 2);
2492
	$return = '#';
2493
2494
	foreach ($color_parts as $color) {
2495
		$color   = hexdec($color); // Convert to decimal
2496
		$color   = max(0, min(255, $color + $steps)); // Adjust color
2497
		$return .= str_pad(dechex($color), 2, '0', STR_PAD_LEFT); // Make two char hex code
2498
	}
2499
2500
	return $return;
2501
}
2502
2503
/**
2504
 * @param string $hex color in hex
2505
 * @param integer $percent 0 to 100
2506
 * @return string
2507
 */
2508
function colorDarker($hex, $percent)
2509
{
2510
	$steps = intval(255 * $percent / 100) * -1;
2511
	return colorAdjustBrightness($hex, $steps);
2512
}
2513
2514
/**
2515
 * @param string $hex color in hex
2516
 * @param integer $percent 0 to 100
2517
 * @return string
2518
 */
2519
function colorLighten($hex, $percent)
2520
{
2521
	$steps = intval(255 * $percent / 100);
2522
	return colorAdjustBrightness($hex, $steps);
2523
}
2524
2525
2526
/**
2527
 * @param string 	$hex 			color in hex
2528
 * @param float 	$alpha 			0 to 1 to add alpha channel
2529
 * @param bool 		$returnArray	true=return an array instead, false=return string
2530
 * @return string|array				String or array
2531
 */
2532
function colorHexToRgb($hex, $alpha = false, $returnArray = false)
2533
{
2534
	$string = '';
2535
	$hex      = str_replace('#', '', $hex);
2536
	$length   = strlen($hex);
2537
	$rgb = array();
2538
	$rgb['r'] = hexdec($length == 6 ? substr($hex, 0, 2) : ($length == 3 ? str_repeat(substr($hex, 0, 1), 2) : 0));
2539
	$rgb['g'] = hexdec($length == 6 ? substr($hex, 2, 2) : ($length == 3 ? str_repeat(substr($hex, 1, 1), 2) : 0));
2540
	$rgb['b'] = hexdec($length == 6 ? substr($hex, 4, 2) : ($length == 3 ? str_repeat(substr($hex, 2, 1), 2) : 0));
2541
	if ($alpha !== false) {
2542
		$rgb['a'] = floatval($alpha);
2543
		$string = 'rgba('.implode(',', $rgb).')';
2544
	} else {
2545
		$string = 'rgb('.implode(',', $rgb).')';
2546
	}
2547
2548
	if ($returnArray) {
2549
		return $rgb;
2550
	} else {
2551
		return $string;
2552
	}
2553
}
2554
2555
2556
/**
2557
 * Applies the Cartesian product algorithm to an array
2558
 * Source: http://stackoverflow.com/a/15973172
2559
 *
2560
 * @param   array $input    Array of products
2561
 * @return  array           Array of combinations
2562
 */
2563
function cartesianArray(array $input)
2564
{
2565
	// filter out empty values
2566
	$input = array_filter($input);
2567
2568
	$result = array(array());
2569
2570
	foreach ($input as $key => $values) {
2571
		$append = array();
2572
2573
		foreach ($result as $product) {
2574
			foreach ($values as $item) {
2575
				$product[$key] = $item;
2576
				$append[] = $product;
2577
			}
2578
		}
2579
2580
		$result = $append;
2581
	}
2582
2583
	return $result;
2584
}
2585
2586
2587
/**
2588
 * Get name of directory where the api_...class.php file is stored
2589
 *
2590
 * @param   string  $moduleobject     Module object name
2591
 * @return  string              	  Directory name
2592
 */
2593
function getModuleDirForApiClass($moduleobject)
2594
{
2595
	$moduledirforclass = $moduleobject;
2596
	if ($moduledirforclass != 'api') {
2597
		$moduledirforclass = preg_replace('/api$/i', '', $moduledirforclass);
2598
	}
2599
2600
	if ($moduleobject == 'contracts') {
2601
		$moduledirforclass = 'contrat';
2602
	} elseif (in_array($moduleobject, array('admin', 'login', 'setup', 'access', 'status', 'tools', 'documents'))) {
2603
		$moduledirforclass = 'api';
2604
	} elseif ($moduleobject == 'contact' || $moduleobject == 'contacts' || $moduleobject == 'customer' || $moduleobject == 'thirdparty' || $moduleobject == 'thirdparties') {
2605
		$moduledirforclass = 'societe';
2606
	} elseif ($moduleobject == 'propale' || $moduleobject == 'proposals') {
2607
		$moduledirforclass = 'comm/propal';
2608
	} elseif ($moduleobject == 'agenda' || $moduleobject == 'agendaevents') {
2609
		$moduledirforclass = 'comm/action';
2610
	} elseif ($moduleobject == 'adherent' || $moduleobject == 'members' || $moduleobject == 'memberstypes' || $moduleobject == 'subscriptions') {
2611
		$moduledirforclass = 'adherents';
2612
	} elseif ($moduleobject == 'don' || $moduleobject == 'donations') {
2613
		$moduledirforclass = 'don';
2614
	} elseif ($moduleobject == 'banque' || $moduleobject == 'bankaccounts') {
2615
		$moduledirforclass = 'compta/bank';
2616
	} elseif ($moduleobject == 'category' || $moduleobject == 'categorie') {
2617
		$moduledirforclass = 'categories';
2618
	} elseif ($moduleobject == 'order' || $moduleobject == 'orders') {
2619
		$moduledirforclass = 'commande';
2620
	} elseif ($moduleobject == 'shipments') {
2621
		$moduledirforclass = 'expedition';
2622
	} elseif ($moduleobject == 'facture' || $moduleobject == 'invoice' || $moduleobject == 'invoices') {
2623
		$moduledirforclass = 'compta/facture';
2624
	} elseif ($moduleobject == 'project' || $moduleobject == 'projects' || $moduleobject == 'task' || $moduleobject == 'tasks') {
2625
		$moduledirforclass = 'projet';
2626
	} elseif ($moduleobject == 'stock' || $moduleobject == 'stockmovements' || $moduleobject == 'warehouses') {
2627
		$moduledirforclass = 'product/stock';
2628
	} elseif ($moduleobject == 'supplierproposals' || $moduleobject == 'supplierproposal' || $moduleobject == 'supplier_proposal') {
2629
		$moduledirforclass = 'supplier_proposal';
2630
	} elseif ($moduleobject == 'fournisseur' || $moduleobject == 'supplierinvoices' || $moduleobject == 'supplierorders') {
2631
		$moduledirforclass = 'fourn';
2632
	} elseif ($moduleobject == 'ficheinter' || $moduleobject == 'interventions') {
2633
		$moduledirforclass = 'fichinter';
2634
	} elseif ($moduleobject == 'mos') {
2635
		$moduledirforclass = 'mrp';
2636
	} elseif (in_array($moduleobject, array('products', 'expensereports', 'users', 'tickets', 'boms'))) {
2637
		$moduledirforclass = preg_replace('/s$/', '', $moduleobject);
2638
	}
2639
2640
	return $moduledirforclass;
2641
}
2642
2643
/**
2644
 * Return 2 hexa code randomly
2645
 *
2646
 * @param	int   $min	    Between 0 and 255
2647
 * @param	int   $max	    Between 0 and 255
2648
 * @return  string          A color string '12'
2649
 */
2650
function randomColorPart($min = 0, $max = 255)
2651
{
2652
	return str_pad(dechex(mt_rand($min, $max)), 2, '0', STR_PAD_LEFT);
2653
}
2654
2655
/**
2656
 * Return hexadecimal color randomly
2657
 *
2658
 * @param	int   $min	   Between 0 and 255
2659
 * @param	int   $max	   Between 0 and 255
2660
 * @return  string         A color string '123456'
2661
 */
2662
function randomColor($min = 0, $max = 255)
2663
{
2664
	return randomColorPart($min, $max).randomColorPart($min, $max).randomColorPart($min, $max);
2665
}
2666
2667
2668
if (!function_exists('dolEscapeXML')) {
2669
	/**
2670
	 * Encode string for xml usage
2671
	 *
2672
	 * @param 	string	$string		String to encode
2673
	 * @return	string				String encoded
2674
	 */
2675
	function dolEscapeXML($string)
2676
	{
2677
		return strtr($string, array('\''=>'&apos;', '"'=>'&quot;', '&'=>'&amp;', '<'=>'&lt;', '>'=>'&gt;'));
2678
	}
2679
}
2680
2681
2682
/**
2683
 * Convert links to local wrapper to medias files into a string into a public external URL readable on internet
2684
 *
2685
 * @param   string      $notetoshow      Text to convert
2686
 * @return  string                       String
2687
 */
2688
function convertBackOfficeMediasLinksToPublicLinks($notetoshow)
2689
{
2690
	global $dolibarr_main_url_root;
2691
	// Define $urlwithroot
2692
	$urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
2693
	$urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file
2694
	//$urlwithroot=DOL_MAIN_URL_ROOT;					// This is to use same domain name than current
2695
	$notetoshow = preg_replace('/src="[a-zA-Z0-9_\/\-\.]*(viewimage\.php\?modulepart=medias[^"]*)"/', 'src="'.$urlwithroot.'/\1"', $notetoshow);
2696
	return $notetoshow;
2697
}
2698
2699
/**
2700
 *		Function to format a value into a defined format for French administration (no thousand separator & decimal separator force to ',' with two decimals)
2701
 *		Function used into accountancy FEC export
2702
 *
2703
 *		@param	float		$amount			Amount to format
2704
 *		@return	string					Chain with formatted upright
2705
 *		@see	price2num()				Format a numeric into a price for FEC files
2706
 */
2707
function price2fec($amount)
2708
{
2709
	global $conf;
2710
2711
	// Clean parameters
2712
	if (empty($amount)) {
2713
		$amount = 0; // To have a numeric value if amount not defined or = ''
2714
	}
2715
	$amount = (is_numeric($amount) ? $amount : 0); // Check if amount is numeric, for example, an error occured when amount value = o (letter) instead 0 (number)
2716
2717
	// Output decimal number by default
2718
	$nbdecimal = (empty($conf->global->ACCOUNTING_FEC_DECIMAL_LENGTH) ? 2 : $conf->global->ACCOUNTING_FEC_DECIMAL_LENGTH);
2719
2720
	// Output separators by default
2721
	$dec = (empty($conf->global->ACCOUNTING_FEC_DECIMAL_SEPARATOR) ? ',' : $conf->global->ACCOUNTING_FEC_DECIMAL_SEPARATOR);
2722
	$thousand = (empty($conf->global->ACCOUNTING_FEC_THOUSAND_SEPARATOR) ? '' : $conf->global->ACCOUNTING_FEC_THOUSAND_SEPARATOR);
2723
2724
	// Format number
2725
	$output = number_format($amount, $nbdecimal, $dec, $thousand);
2726
2727
	return $output;
2728
}
2729
2730
/**
2731
 * Check the syntax of some PHP code.
2732
 *
2733
 * @param 	string 			$code 	PHP code to check.
2734
 * @return 	boolean|array 			If false, then check was successful, otherwise an array(message,line) of errors is returned.
2735
 */
2736
function phpSyntaxError($code)
2737
{
2738
	if (!defined("CR")) {
2739
		define("CR", "\r");
2740
	}
2741
	if (!defined("LF")) {
2742
		define("LF", "\n");
2743
	}
2744
	if (!defined("CRLF")) {
2745
		define("CRLF", "\r\n");
2746
	}
2747
2748
	$braces = 0;
2749
	$inString = 0;
2750
	foreach (token_get_all('<?php '.$code) as $token) {
2751
		if (is_array($token)) {
2752
			switch ($token[0]) {
2753
				case T_CURLY_OPEN:
2754
				case T_DOLLAR_OPEN_CURLY_BRACES:
2755
				case T_START_HEREDOC:
2756
					++$inString;
2757
					break;
2758
				case T_END_HEREDOC:
2759
					--$inString;
2760
					break;
2761
			}
2762
		} elseif ($inString & 1) {
2763
			switch ($token) {
2764
				case '`':
2765
				case '\'':
2766
				case '"':
2767
					--$inString;
2768
					break;
2769
			}
2770
		} else {
2771
			switch ($token) {
2772
				case '`':
2773
				case '\'':
2774
				case '"':
2775
					++$inString;
2776
					break;
2777
				case '{':
2778
					++$braces;
2779
					break;
2780
				case '}':
2781
					if ($inString) {
2782
						--$inString;
2783
					} else {
2784
						--$braces;
2785
						if ($braces < 0) {
2786
							break 2;
2787
						}
2788
					}
2789
					break;
2790
			}
2791
		}
2792
	}
2793
	$inString = @ini_set('log_errors', false);
2794
	$token = @ini_set('display_errors', true);
2795
	ob_start();
2796
	$code = substr($code, strlen('<?php '));
2797
	$braces || $code = "if(0){{$code}\n}";
2798
	if (eval($code) === false) {
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
2799
		if ($braces) {
2800
			$braces = PHP_INT_MAX;
2801
		} else {
2802
			false !== strpos($code, CR) && $code = strtr(str_replace(CRLF, LF, $code), CR, LF);
2803
			$braces = substr_count($code, LF);
2804
		}
2805
		$code = ob_get_clean();
2806
		$code = strip_tags($code);
2807
		if (preg_match("'syntax error, (.+) in .+ on line (\d+)$'s", $code, $code)) {
2808
			$code[2] = (int) $code[2];
2809
			$code = $code[2] <= $braces
2810
			? array($code[1], $code[2])
2811
			: array('unexpected $end'.substr($code[1], 14), $braces);
2812
		} else {
2813
			$code = array('syntax error', 0);
2814
		}
2815
	} else {
2816
		ob_end_clean();
2817
		$code = false;
2818
	}
2819
	@ini_set('display_errors', $token);
2820
	@ini_set('log_errors', $inString);
2821
	return $code;
2822
}
2823