dolibarr 25.0.0-alpha
functions2.lib.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2008-2011 Laurent Destailleur <eldy@users.sourceforge.net>
3 * Copyright (C) 2008-2012 Regis Houssin <regis.houssin@inodbox.com>
4 * Copyright (C) 2008 Raphael Bertrand (Resultic) <raphael.bertrand@resultic.fr>
5 * Copyright (C) 2014-2016 Marcos García <marcosgdf@gmail.com>
6 * Copyright (C) 2015 Ferran Marcet <fmarcet@2byte.es>
7 * Copyright (C) 2015-2016 Raphaël Doursenaud <rdoursenaud@gpcsolutions.fr>
8 * Copyright (C) 2017 Juanjo Menent <jmenent@2byte.es>
9 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
10 * Copyright (C) 2024-2026 Frédéric France <frederic.france@free.fr>
11 *
12 * This program is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU General Public License as published by
14 * the Free Software Foundation; either version 3 of the License, or
15 * (at your option) any later version.
16 *
17 * This program is distributed in the hope that it will be useful,
18 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 * GNU General Public License for more details.
21 *
22 * You should have received a copy of the GNU General Public License
23 * along with this program. If not, see <https://www.gnu.org/licenses/>.
24 * or see https://www.gnu.org/
25 */
26
33// Enable this line to trace path when function is called.
34//print xdebug_print_function_stack('Functions2.lib was called');exit;
35
42function jsUnEscape($source)
43{
44 $decodedStr = "";
45 $pos = 0;
46 $len = strlen($source);
47 while ($pos < $len) {
48 $charAt = substr($source, $pos, 1);
49 if ($charAt == '%') {
50 $pos++;
51 $charAt = substr($source, $pos, 1);
52 if ($charAt == 'u') {
53 // we got a unicode character
54 $pos++;
55 $unicodeHexVal = substr($source, $pos, 4);
56 $unicode = hexdec($unicodeHexVal);
57 $entity = "&#".$unicode.';';
58 $decodedStr .= mb_convert_encoding($entity, 'UTF-8', 'ISO-8859-1');
59 $pos += 4;
60 } else {
61 // we have an escaped ascii character
62 $hexVal = substr($source, $pos, 2);
63 $decodedStr .= chr(hexdec($hexVal));
64 $pos += 2;
65 }
66 } else {
67 $decodedStr .= $charAt;
68 $pos++;
69 }
70 }
71 return dol_html_entity_decode($decodedStr, ENT_COMPAT | ENT_HTML5);
72}
73
74
84function dolGetModulesDirs($subdir = '')
85{
86 global $conf;
87
88 $modulesdir = array();
89
90 foreach ($conf->file->dol_document_root as $type => $dirroot) {
91 // Default core/modules dir
92 if ($type === 'main') {
93 $modulesdir[$dirroot.'/core/modules'.$subdir.'/'] = $dirroot.'/core/modules'.$subdir.'/';
94 }
95
96 // Scan dir from external modules
97 $handle = @opendir($dirroot);
98 if (is_resource($handle)) {
99 while (($file = readdir($handle)) !== false) {
100 if (preg_match('/disabled/', $file)) {
101 continue; // We discard module if it contains disabled into name.
102 }
103
104 if (dol_substr($file, 0, 1) != '.' && is_dir($dirroot.'/'.$file) && strtoupper(dol_substr($file, 0, 3)) != 'CVS' && $file != 'includes') {
105 if (is_dir($dirroot.'/'.$file.'/core/modules'.$subdir.'/')) {
106 $modulesdir[$dirroot.'/'.$file.'/core/modules'.$subdir.'/'] = $dirroot.'/'.$file.'/core/modules'.$subdir.'/';
107 }
108 }
109 }
110 closedir($handle);
111 }
112 }
113 return $modulesdir;
114}
115
116
123function dol_getDefaultFormat($outputlangs = null)
124{
125 global $langs;
126
127 $selected = 'EUA4';
128 if (!$outputlangs) {
129 $outputlangs = $langs;
130 }
131
132 if ($outputlangs->defaultlang == 'ca_CA') {
133 $selected = 'CAP4'; // Canada
134 }
135 if ($outputlangs->defaultlang == 'en_US') {
136 $selected = 'USLetter'; // US
137 }
138 return $selected;
139}
140
141
150function dol_print_object_info($object, $usetable = 0)
151{
152 global $langs, $db;
153
154 // Load translation files required by the page
155 $langs->loadLangs(array('other', 'admin'));
156
157 include_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
158
159 $deltadateforserver = getServerTimeZoneInt('now');
160 $deltadateforclient = ((int) $_SESSION['dol_tz'] + (int) $_SESSION['dol_dst']);
161 //$deltadateforcompany=((int) $_SESSION['dol_tz'] + (int) $_SESSION['dol_dst']);
162 $deltadateforuser = round($deltadateforclient - $deltadateforserver);
163 //print "x".$deltadateforserver." - ".$deltadateforclient." - ".$deltadateforuser;
164
165 if ($usetable) {
166 print '<table class="border tableforfield centpercent">';
167 }
168
169 // Import key
170 if (!empty($object->import_key)) {
171 if ($usetable) {
172 print '<tr><td class="titlefield">';
173 }
174 print $langs->trans("ImportedWithSet");
175 if ($usetable) {
176 print '</td><td>';
177 } else {
178 print ': ';
179 }
180 print $object->import_key;
181 if ($usetable) {
182 print '</td></tr>';
183 } else {
184 print '<br>';
185 }
186 }
187
188 // Creation
189 if (!empty($object->user_creation_id) || !empty($object->date_creation)) {
190 if ($usetable) {
191 print '<tr><td class="titlefield">';
192 }
193 print $langs->trans("Creation");
194 if ($usetable) {
195 print '</td><td>';
196 } else {
197 print ': ';
198 }
199 print '<div class="valignmiddle inline-block">';
200 if ($object->user_creation_id > 0) {
201 $userstatic = new User($db);
202 $userstatic->fetch($object->user_creation_id);
203 print $userstatic->getNomUrl(-1, '', 0, 0, 0);
204 } else {
205 print $langs->trans("Unknown");
206 }
207 print '</div>';
208
209 if (!empty($object->date_creation)) {
210 print ' - ';
211 print '<div class="valignmiddle inline-block">';
212 print dol_print_date($object->date_creation, 'dayhour', 'tzserver');
213 if ($deltadateforuser) {
214 print ' <span class="opacitymedium">'.$langs->trans("CurrentHour").'</span> &nbsp; / &nbsp; '.dol_print_date($object->date_creation, "dayhour", "tzuserrel").' &nbsp;<span class="opacitymedium">'.$langs->trans("ClientHour").'</span>';
215 }
216 print '</div>';
217 }
218
219 if ($usetable) {
220 print '</td></tr>';
221 } else {
222 print '<br>';
223 }
224 }
225
226 // Last modification
227 if (!empty($object->user_modification_id) || !empty($object->date_modification)) {
228 if ($usetable) {
229 print '<tr><td class="titlefield">';
230 }
231 print $langs->trans("LastModified");
232 if ($usetable) {
233 print '</td><td>';
234 } else {
235 print ': ';
236 }
237 print '<div class="valignmiddle inline-block">';
238 if ($object->user_modification_id > 0) {
239 $userstatic = new User($db);
240 $userstatic->fetch($object->user_modification_id);
241 print $userstatic->getNomUrl(-1, '', 0, 0, 0);
242 } else {
243 print $langs->trans("Unknown");
244 }
245 print '</div>';
246
247 if (!empty($object->date_modification)) {
248 print ' - ';
249 print '<div class="valignmiddle inline-block">';
250 print dol_print_date($object->date_modification, 'dayhour', 'tzserver');
251 if ($deltadateforuser) {
252 print ' <span class="opacitymedium">'.$langs->trans("CurrentHour").'</span> &nbsp; / &nbsp; '.dol_print_date($object->date_modification, "dayhour", "tzuserrel").' &nbsp;<span class="opacitymedium">'.$langs->trans("ClientHour").'</span>';
253 }
254 print '</div>';
255 }
256
257 if ($usetable) {
258 print '</td></tr>';
259 } else {
260 print '<br>';
261 }
262 }
263
264 // Validation
265 if (!empty($object->user_validation_id) || !empty($object->date_validation)) {
266 if ($usetable) {
267 print '<tr><td class="titlefield">';
268 }
269 print $langs->trans("Validation");
270 if ($usetable) {
271 print '</td><td>';
272 } else {
273 print ': ';
274 }
275 $userstatic = new User($db);
276 $userstatic->fetch($object->user_validation_id ? $object->user_validation_id : $object->user_validation);
277 if ($userstatic->id) {
278 print $userstatic->getNomUrl(-1, '', 0, 0, 0);
279 } else {
280 print $langs->trans("Unknown");
281 }
282
283 if (!empty($object->date_validation)) {
284 print ' - ';
285 print '<div class="valignmiddle inline-block">';
286 print dol_print_date($object->date_validation, 'dayhour', 'tzserver');
287 if ($deltadateforuser) {
288 print ' <span class="opacitymedium">'.$langs->trans("CurrentHour").'</span> &nbsp; / &nbsp; '.dol_print_date($object->date_validation, "dayhour", 'tzuserrel').' &nbsp;<span class="opacitymedium">'.$langs->trans("ClientHour").'</span>';
289 }
290 print '</div>';
291 }
292
293 if ($usetable) {
294 print '</td></tr>';
295 } else {
296 print '<br>';
297 }
298 }
299
300 // Approval (old method using already loaded object and not id is kept for backward compatibility)
301 if (!empty($object->user_approve) || !empty($object->user_approve_id) || !empty($object->date_approve) || !empty($object->date_approval)) {
302 if ($usetable) {
303 print '<tr><td class="titlefield">';
304 }
305 print $langs->trans("Approval");
306 if ($usetable) {
307 print '</td><td>';
308 } else {
309 print ': ';
310 }
311 // user_approve is not defined in Dolibarr code @phan-suppress-next-line PhanUndeclaredProperty
312 if (!empty($object->user_approve) && is_object($object->user_approve)) {
313 if ($object->user_approve->id) { // @phan-suppress-current-line PhanUndeclaredProperty
314 // @phan-suppress-next-line PhanUndeclaredProperty,PhanPluginUnknownObjectMethodCall
315 print $object->user_approve->getNomUrl(-1, '', 0, 0, 0);
316 } else {
317 print $langs->trans("Unknown");
318 }
319 } else {
320 $userstatic = new User($db);
321 $userstatic->fetch($object->user_approve_id ? $object->user_approve_id : $object->user_approve);
322 if ($userstatic->id) {
323 print $userstatic->getNomUrl(-1, '', 0, 0, 0);
324 } else {
325 print $langs->trans("Unknown");
326 }
327 }
328
329 if (!empty($object->date_approve) || !empty($object->date_approval)) {
330 print ' - ';
331 print '<div class="valignmiddle inline-block">';
332 print dol_print_date($object->date_approve ? $object->date_approve : $object->date_approval, 'dayhour', 'tzserver');
333 if ($deltadateforuser) {
334 print ' <span class="opacitymedium">'.$langs->trans("CurrentHour").'</span> &nbsp; / &nbsp; '.dol_print_date($object->date_approve, "dayhour", 'tzuserrel').' &nbsp;<span class="opacitymedium">'.$langs->trans("ClientHour").'</span>';
335 }
336 print '</div>';
337 }
338
339 if ($usetable) {
340 print '</td></tr>';
341 } else {
342 print '<br>';
343 }
344 }
345
346 // Approval
347 if (!empty($object->user_approve_id2) || !empty($object->date_approve2)) {
348 '@phan-var-force CommandeFournisseur $object';
349 if ($usetable) {
350 print '<tr><td class="titlefield">';
351 }
352 print $langs->trans("Approval");
353 if ($usetable) {
354 print '</td><td>';
355 } else {
356 print ': ';
357 }
358 $userstatic = new User($db);
359 $userstatic->fetch($object->user_approve_id2);
360 if ($userstatic->id) {
361 print $userstatic->getNomUrl(-1, '', 0, 0, 0);
362 } else {
363 print $langs->trans("Unknown");
364 }
365
366 if (!empty($object->date_approve2)) {
367 print ' - ';
368 print '<div class="valignmiddle inline-block">';
369 print dol_print_date($object->date_approve2, 'dayhour', 'tzserver');
370 if ($deltadateforuser) {
371 print ' <span class="opacitymedium">'.$langs->trans("CurrentHour").'</span> &nbsp; / &nbsp; '.dol_print_date($object->date_approve2, "dayhour", 'tzuserrel').' &nbsp;<span class="opacitymedium">'.$langs->trans("ClientHour").'</span>';
372 }
373 print '</div>';
374 }
375
376 if ($usetable) {
377 print '</td></tr>';
378 } else {
379 print '<br>';
380 }
381 }
382
383 // Signature
384 if (!empty($object->user_signature) || !empty($object->user_signature_id) || !empty($object->date_signature)) {
385 '@phan-var-force Propal $object';
386 if ($usetable) {
387 print '<tr><td class="titlefield">';
388 }
389 print $langs->trans('Signature');
390 if ($usetable) {
391 print '</td><td>';
392 } else {
393 print ': ';
394 }
395 if (is_object($object->user_signature)) {
396 if ($object->user_signature->id) {
397 print $object->user_signature->getNomUrl(-1, '', 0, 0, 0);
398 } else {
399 print $langs->trans('Unknown');
400 }
401 } else {
402 $userstatic = new User($db);
403 $userstatic->fetch($object->user_signature_id ? $object->user_signature_id : $object->user_signature);
404 if ($userstatic->id) {
405 print $userstatic->getNomUrl(-1, '', 0, 0, 0);
406 } else {
407 print $langs->trans('Unknown');
408 }
409 }
410
411 if (!empty($object->date_signature)) {
412 print ' - ';
413 print '<div class="valignmiddle inline-block">';
414 print dol_print_date($object->date_signature, 'dayhour');
415 if ($deltadateforuser) {
416 print ' <span class="opacitymedium">'.$langs->trans('CurrentHour').'</span> &nbsp; / &nbsp; '.dol_print_date($object->date_signature, 'dayhour', 'tzuserrel').' &nbsp;<span class="opacitymedium">'.$langs->trans('ClientHour').'</span>';
417 }
418 print '</div>';
419 }
420
421 if ($usetable) {
422 print '</td></tr>';
423 } else {
424 print '<br>';
425 }
426 }
427
428 // Closing
429 if (!empty($object->user_closing_id) || !empty($object->date_cloture) || !empty($object->date_closing)) {
430 if ($usetable) {
431 print '<tr><td class="titlefield">';
432 }
433 print $langs->trans("Closing");
434 if ($usetable) {
435 print '</td><td>';
436 } else {
437 print ': ';
438 }
439 $userstatic = new User($db);
440 $userstatic->fetch($object->user_closing_id);
441 if ($userstatic->id) {
442 print $userstatic->getNomUrl(-1, '', 0, 0, 0);
443 } else {
444 print $langs->trans("Unknown");
445 }
446
447 if (!empty($object->date_cloture) || !empty($object->date_closing)) {
448 if (isset($object->date_cloture) && !empty($object->date_cloture)) {
449 $object->date_closing = $object->date_cloture;
450 }
451 print ' - ';
452 print '<div class="valignmiddle inline-block">';
453 print dol_print_date($object->date_closing, 'dayhour', 'tzserver');
454 if ($deltadateforuser) {
455 print ' <span class="opacitymedium">'.$langs->trans("CurrentHour").'</span> &nbsp; / &nbsp; '.dol_print_date($object->date_closing, "dayhour", 'tzuserrel').' &nbsp;<span class="opacitymedium">'.$langs->trans("ClientHour").'</span>';
456 }
457 print '</div>';
458 }
459
460 if ($usetable) {
461 print '</td></tr>';
462 } else {
463 print '<br>';
464 }
465 }
466
467 // Reconciliation
468 if (!empty($object->user_rappro) || !empty($object->user_rappro_id) || !empty($object->date_rappro)) {
469 '@phan-var-force Account $object';
470 if ($usetable) {
471 print '<tr><td class="titlefield">';
472 }
473 print $langs->trans("Reconciliation");
474 if ($usetable) {
475 print '</td><td>';
476 } else {
477 print ': ';
478 }
479 if (is_object($object->user_rappro)) {
480 $user_rappro = $object->user_rappro;
481 '@phan-var-force User $user_rappro';
482 if ($user_rappro->id) {
483 print $user_rappro->getNomUrl(-1, '', 0, 0, 0);
484 } else {
485 print $langs->trans("Unknown");
486 }
487 } else {
488 $userstatic = new User($db);
489 $userstatic->fetch($object->user_rappro_id ? $object->user_rappro_id : $object->user_rappro);
490 if ($userstatic->id) {
491 print $userstatic->getNomUrl(1, '', 0, 0, 0);
492 } else {
493 print $langs->trans("Unknown");
494 }
495 }
496
497 if (!empty($object->date_rappro)) { // Note: date_rappro is not found on Dolibarr classes
498 print ' - ';
499 print '<div class="valignmiddle inline-block">';
500 print dol_print_date($object->date_rappro, 'dayhour', 'tzserver'); // @phan-suppress-current-line PhanUndeclaredProperty
501 if ($deltadateforuser) {
502 print ' <span class="opacitymedium">'.$langs->trans("CurrentHour").'</span> &nbsp; / &nbsp; '.dol_print_date($object->date_rappro, "dayhour", 'tzuserrel').' &nbsp;<span class="opacitymedium">'.$langs->trans("ClientHour").'</span>'; // @phan-suppress-current-line PhanUndeclaredProperty
503 }
504 print '</div>';
505 }
506
507 if ($usetable) {
508 print '</td></tr>';
509 } else {
510 print '<br>';
511 }
512 }
513
514 // Last sending
515 if (!empty($object->date_envoi)) {
516 '@phan-var-force Mailing $object';
517 if ($usetable) {
518 print '<tr><td class="titlefield">';
519 }
520 print $langs->trans("LastSending");
521 if ($usetable) {
522 print '</td><td>';
523 } else {
524 print ': ';
525 }
526 print dol_print_date($object->date_envoi, 'dayhour', 'tzserver');
527 if ($deltadateforuser) {
528 print ' <span class="opacitymedium">'.$langs->trans("CurrentHour").'</span> &nbsp; / &nbsp; '.dol_print_date($object->date_envoi, "dayhour", 'tzuserrel').' &nbsp;<span class="opacitymedium">'.$langs->trans("ClientHour").'</span>';
529 }
530 if ($usetable) {
531 print '</td></tr>';
532 } else {
533 print '<br>';
534 }
535 }
536
537 if ($usetable) {
538 print '</table>';
539 }
540}
541
542
551function dolAddEmailTrackId($email, $trackingid)
552{
553 $tmp = explode('@', $email);
554 return $tmp[0].'+'.$trackingid.'@'.(isset($tmp[1]) ? $tmp[1] : '');
555}
556
563function isValidMailDomain($mail)
564{
565 list($user, $domain) = explode("@", $mail, 2);
566 return ($domain ? isValidMXRecord($domain) : 0);
567}
568
582function isValidUrl($url, $http = 0, $pass = 0, $port = 0, $path = 0, $query = 0, $anchor = 0)
583{
584 $ValidUrl = 0;
585 $urlregex = '';
586
587 // SCHEME
588 if ($http) {
589 $urlregex .= "^(http:\/\/|https:\/\/)";
590 }
591
592 // USER AND PASS
593 if ($pass) {
594 $urlregex .= "([a-z0-9+!*(),;?&=\$_.-]+(\:[a-z0-9+!*(),;?&=\$_.-]+)?@)";
595 }
596
597 // HOSTNAME OR IP
598 //$urlregex .= "[a-z0-9+\$_-]+(\.[a-z0-9+\$_-]+)*"; // x allowed (ex. http://localhost, http://routerlogin)
599 //$urlregex .= "[a-z0-9+\$_-]+(\.[a-z0-9+\$_-]+)+"; // x.x
600 $urlregex .= "([a-z0-9+\$_\\\:-])+(\.[a-z0-9+\$_-][a-z0-9+\$_-]+)*"; // x ou x.xx (2 x ou plus)
601 //use only one of the above
602
603 // PORT
604 if ($port) {
605 $urlregex .= "(\:[0-9]{2,5})";
606 }
607 // PATH
608 if ($path) {
609 $urlregex .= "(\/([a-z0-9+\$_-]\.?)+)*\/";
610 }
611 // GET Query
612 if ($query) {
613 $urlregex .= "(\?[a-z+&\$_.-][a-z0-9;:@\/&%=+\$_.-]*)";
614 }
615 // ANCHOR
616 if ($anchor) {
617 $urlregex .= "(#[a-z_.-][a-z0-9+\$_.-]*)$";
618 }
619
620 // check
621 if (preg_match('/'.$urlregex.'/i', $url)) {
622 $ValidUrl = 1;
623 }
624 //print $urlregex.' - '.$url.' - '.$ValidUrl;
625
626 return $ValidUrl;
627}
628
635function isValidVATID($company)
636{
637 if ($company->isInEEC()) { // Syntax check rules for EEC countries
638 /* Disabled because some companies can have an address in Irland and a vat number in France.
639 $vatprefix = $company->country_code;
640 if ($vatprefix == 'GR') $vatprefix = '(EL|GR)';
641 elseif ($vatprefix == 'MC') $vatprefix = 'FR'; // Monaco is using french VAT numbers
642 else $vatprefix = preg_quote($vatprefix, '/');*/
643 $vatprefix = '[a-zA-Z][a-zA-Z]';
644 if (!preg_match('/^'.$vatprefix.'[a-zA-Z0-9\-\.]{5,14}$/i', str_replace(' ', '', $company->tva_intra))) {
645 return 0;
646 }
647 }
648
649 return 1;
650}
651
659function clean_url($url, $http = 1)
660{
661 // Fixed by Matelli (see http://matelli.fr/showcases/patch%73-dolibarr/fix-cleaning-url.html)
662 // To include the minus sign in a char class, we must not escape it but put it at the end of the class
663 // Also, there's no need of escape a dot sign in a class
664 $regs = array();
665 if (preg_match('/^(https?:[\\/]+)?([0-9A-Z.-]+\.[A-Z]{2,4})(:[0-9]+)?/i', $url, $regs)) {
666 $proto = $regs[1];
667 $domain = $regs[2];
668 $port = isset($regs[3]) ? $regs[3] : '';
669 //print $url." -> ".$proto." - ".$domain." - ".$port;
670 //$url = dol_string_nospecial(trim($url));
671 $url = trim($url);
672
673 // If http: is defined, remove the http (If https, do not remove)
674 $newproto = $proto;
675 if ($http == 0) {
676 if (preg_match('/^http:[\\/]+/i', $url)) {
677 $url = preg_replace('/^http:[\\/]+/i', '', $url);
678 $newproto = '';
679 }
680 }
681
682 // On passe le nom de domaine en minuscule
683 $CleanUrl = preg_replace('/^'.preg_quote($proto.$domain, '/').'/i', $newproto.strtolower($domain), $url);
684
685 return $CleanUrl;
686 } else {
687 return $url;
688 }
689}
690
691
692
704function dolObfuscateEmail($mail, $replace = "*", $nbreplace = 8, $nbdisplaymail = 4, $nbdisplaydomain = 3, $displaytld = true)
705{
706 if (!isValidEmail($mail)) {
707 return '';
708 }
709 $tab = explode('@', $mail);
710 $tab2 = explode('.', $tab[1]);
711 $string_replace = '';
712 $mail_name = $tab[0];
713 $mail_domaine = $tab2[0];
714 $mail_tld = '';
715
716 $nbofelem = count($tab2);
717 for ($i = 1; $i < $nbofelem && $displaytld; $i++) {
718 $mail_tld .= '.'.$tab2[$i];
719 }
720
721 for ($i = 0; $i < $nbreplace; $i++) {
722 $string_replace .= $replace;
723 }
724
725 if (strlen($mail_name) > $nbdisplaymail) {
726 $mail_name = substr($mail_name, 0, $nbdisplaymail);
727 }
728
729 if (strlen($mail_domaine) > $nbdisplaydomain) {
730 $mail_domaine = substr($mail_domaine, strlen($mail_domaine) - $nbdisplaydomain);
731 }
732
733 return $mail_name.$string_replace.$mail_domaine.$mail_tld;
734}
735
736
746function array2tr($data, $troptions = '', $tdoptions = '')
747{
748 $text = '<tr '.$troptions.'>';
749 foreach ($data as $key => $item) {
750 $text .= '<td '.$tdoptions.'>'.((string) $item).'</td>';
751 }
752 $text .= '</tr>';
753 return $text;
754}
755
766function array2table($data, $tableMarkup = 1, $tableoptions = '', $troptions = '', $tdoptions = '')
767{
768 $text = '';
769 if ($tableMarkup) {
770 $text = '<table '.$tableoptions.'>';
771 }
772 foreach ($data as $key => $item) {
773 if (is_array($item)) {
774 $text .= array2tr($item, $troptions, $tdoptions);
775 } else {
776 $text .= '<tr '.$troptions.'>';
777 $text .= '<td '.$tdoptions.'>'.((string) $key).'</td>';
778 $text .= '<td '.$tdoptions.'>'.((string) $item).'</td>';
779 $text .= '</tr>';
780 }
781 }
782 if ($tableMarkup) {
783 $text .= '</table>';
784 }
785 return $text;
786}
787
805function get_next_value($db, $mask, $table, $field, $where = '', $objsoc = '', $date = '', $mode = 'next', $bentityon = true, $objuser = null, $forceentity = null, $objbookkeeping = null)
806{
807 global $user;
808
809 if (!is_object($objsoc)) {
810 $valueforccc = (string) $objsoc;
811 } elseif ($table == "commande_fournisseur" || $table == "facture_fourn" || $table == "paiementfourn") {
812 $valueforccc = dol_string_unaccent($objsoc->code_fournisseur);
813 } else {
814 $valueforccc = dol_string_unaccent($objsoc->code_client);
815 }
816
817 $sharetable = $table;
818 if ($table == 'facture' || $table == 'invoice') {
819 $sharetable = 'invoicenumber'; // for getEntity function
820 }
821
822 // Clean parameters
823 if ($date == '') {
824 $date = dol_now(); // We use local year and month of PHP server to search numbers
825 }
826 // but we should use local year and month of user
827
828 // For debugging
829 //dol_syslog("mask=".$mask, LOG_DEBUG);
830 //include_once(DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php');
831 //$mask='FA{yy}{mm}-{0000@99}';
832 //$date=dol_mktime(12, 0, 0, 1, 1, 1900);
833 //$date=dol_stringtotime('20130101');
834 $hasglobalcounter = false;
835 $maskrefclient_maskcounter = '';
836 $maskrefclient_clientcode = '';
837 $maskrefclient_maskclientcode = '';
838 $maskrefclient_maskoffset = '';
839
840 $reg = array();
841 // Extract value for mask counter, mask raz and mask offset
842 if (preg_match('/\{(0+)([@\+][0-9\-\+\=]+)?([@\+][0-9\-\+\=]+)?\}/i', $mask, $reg)) {
843 $masktri = $reg[1].(!empty($reg[2]) ? $reg[2] : '').(!empty($reg[3]) ? $reg[3] : '');
844 $maskcounter = $reg[1];
845 $hasglobalcounter = true;
846 } else {
847 // setting some defaults so the rest of the code won't fail if there is a third party counter
848 $masktri = '00000';
849 $maskcounter = '00000';
850 }
851
852 $maskraz = -1;
853 $maskoffset = 0;
854 $resetEveryMonth = false;
855 if (dol_strlen($maskcounter) < 3 && !getDolGlobalString('MAIN_COUNTER_WITH_LESS_3_DIGITS')) {
856 return 'ErrorCounterMustHaveMoreThan3Digits';
857 }
858
859 // Extract value for third party mask counter
860 $regClientRef = array();
861 if (preg_match('/\{(c+)(0*)\}/i', $mask, $regClientRef)) {
862 $maskrefclient = $regClientRef[1].$regClientRef[2];
863 $maskrefclient_maskclientcode = $regClientRef[1];
864 $maskrefclient_maskcounter = $regClientRef[2];
865 $maskrefclient_maskoffset = 0; //default value of maskrefclient_counter offset
866 $maskrefclient_clientcode = substr($valueforccc, 0, dol_strlen($maskrefclient_maskclientcode)); //get n first characters of client code where n is length in mask
867 $maskrefclient_clientcode = str_pad($maskrefclient_clientcode, dol_strlen($maskrefclient_maskclientcode), "#", STR_PAD_RIGHT); //padding maskrefclient_clientcode for having exactly n characters in maskrefclient_clientcode
868 $maskrefclient_clientcode = dol_string_nospecial($maskrefclient_clientcode); //sanitize maskrefclient_clientcode for sql insert and sql select like
869 if (dol_strlen($maskrefclient_maskcounter) > 0 && dol_strlen($maskrefclient_maskcounter) < 3) {
870 return 'ErrorCounterMustHaveMoreThan3Digits';
871 }
872 } else {
873 $maskrefclient = '';
874 }
875
876 // fail if there is neither a global nor a third party counter
877 if (!$hasglobalcounter && ($maskrefclient_maskcounter == '')) {
878 return 'ErrorBadMask';
879 }
880
881 // Extract value for third party type
882 $regType = array();
883 if (preg_match('/\{(t+)\}/i', $mask, $regType)) {
884 $masktype = $regType[1];
885 $masktype_value = dol_substr(preg_replace('/^TE_/', '', $objsoc->typent_code), 0, dol_strlen($regType[1])); // get n first characters of thirdparty typent_code (where n is length in mask)
886 $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
887 } else {
888 $masktype = '';
889 $masktype_value = '';
890 }
891
892 // Extract value for user
893 $regUser = array();
894 if (preg_match('/\{(u+)\}/i', $mask, $regUser)) {
895 $lastname = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
896 if (is_object($objuser)) {
897 $lastname = $objuser->lastname;
898 }
899
900 $maskuser = $regUser[1];
901 $maskuser_value = substr($lastname, 0, dol_strlen($regUser[1])); // get n first characters of user firstname (where n is length in mask)
902 $maskuser_value = str_pad($maskuser_value, dol_strlen($regUser[1]), "#", STR_PAD_RIGHT); // we fill on right with # to have same number of char than into mask
903 } else {
904 $maskuser = '';
905 $maskuser_value = '';
906 }
907
908 // Extract value for journal code
909 $regJournal = array();
910 if (preg_match('/\{(jj+)\}/i', $mask, $regJournal)) {
911 $journalcode = 'JJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJJ';
912 if (is_object($objbookkeeping)) {
913 $journalcode = (string) $objbookkeeping->code_journal;
914 }
915
916 $maskjournal = $regJournal[1];
917 $maskjournal_value = substr($journalcode, 0, dol_strlen($regJournal[1])); // get n first characters of journal code (where n is length in mask)
918 $maskjournal_value = str_pad($maskjournal_value, dol_strlen($regJournal[1]), "#", STR_PAD_RIGHT); // we fill on right with # to have same number of char than into mask
919 } else {
920 $maskjournal = '';
921 $maskjournal_value = '';
922 }
923
924 // Personalized field {XXX-1} to {XXX-99}
925 $maskperso = array();
926 $maskpersonew = array();
927 $tmpmask = $mask;
928 $regKey = array();
929 while (preg_match('/\{([A-Z]+)\-([0-9]+)\}/', $tmpmask, $regKey)) {
930 $maskperso[$regKey[1]] = '{'.$regKey[1].'-'.$regKey[2].'}';
931 // @phan-suppress-next-line PhanParamSuspiciousOrder
932 $maskpersonew[$regKey[1]] = str_pad('', (int) $regKey[2], '_', STR_PAD_RIGHT);
933 $tmpmask = preg_replace('/\{'.$regKey[1].'\-'.$regKey[2].'\}/i', $maskpersonew[$regKey[1]], $tmpmask);
934 }
935
936 if (strstr($mask, 'user_extra_')) {
937 $start = "{user_extra_";
938 $end = "\}";
939 $extra = get_string_between($mask, "user_extra_", "}");
940 if (!empty($user->array_options['options_'.$extra])) {
941 $mask = preg_replace('#('.$start.')(.*?)('.$end.')#si', $user->array_options['options_'.$extra], $mask);
942 }
943 }
944
945 // Now define value for all alternative $mask variable we will need to work
946 $maskwithonlyymcode = $mask;
947 $maskwithonlyymcode = preg_replace('/\{(0+)([@\+][0-9\-\+\=]+)?([@\+][0-9\-\+\=]+)?\}/i', $maskcounter, $maskwithonlyymcode);
948 $maskwithonlyymcode = preg_replace('/\{dd\}/i', 'dd', $maskwithonlyymcode);
949 $maskwithonlyymcode = preg_replace('/\{(c+)(0*)\}/i', $maskrefclient, $maskwithonlyymcode);
950 $maskwithonlyymcode = preg_replace('/\{(t+)\}/i', $masktype_value, $maskwithonlyymcode);
951 $maskwithonlyymcode = preg_replace('/\{(u+)\}/i', $maskuser_value, $maskwithonlyymcode);
952 $maskwithonlyymcode = preg_replace('/\{(j+)\}/i', $maskjournal_value, $maskwithonlyymcode);
953 foreach ($maskperso as $key => $val) {
954 $maskwithonlyymcode = preg_replace('/'.preg_quote($val, '/').'/i', $maskpersonew[$key], $maskwithonlyymcode);
955 }
956 $maskwithnocode = $maskwithonlyymcode;
957 $maskwithnocode = preg_replace('/\{yyyy\}/i', 'yyyy', $maskwithnocode);
958 $maskwithnocode = preg_replace('/\{yy\}/i', 'yy', $maskwithnocode);
959 $maskwithnocode = preg_replace('/\{y\}/i', 'y', $maskwithnocode);
960 $maskwithnocode = preg_replace('/\{mm\}/i', 'mm', $maskwithnocode);
961 // Now maskwithnocode = 0000ddmmyyyyccc for example
962 // and maskcounter = 0000 for example
963 //print "maskwithonlyymcode=".$maskwithonlyymcode." maskwithnocode=".$maskwithnocode."\n<br>";
964 //var_dump($reg);
965
966 // If an offset is asked ($reg is parsed from the counter mask)
967 if (!empty($reg[2]) && preg_match('/^\+/', $reg[2])) {
968 $maskoffset = preg_replace('/^\+/', '', $reg[2]);
969 }
970 if (!empty($reg[3]) && preg_match('/^\+/', $reg[3])) {
971 $maskoffset = preg_replace('/^\+/', '', $reg[3]);
972 }
973
974 // Define $sqlwhere
975 $sqlwhere = '';
976 $yearoffset = 0; // Use year of current $date by default
977 $yearoffsettype = false; // false: no reset, 0,-,=,+: reset at offset month SOCIETE_FISCAL_MONTH_START, x=reset at offset month x
978
979 // If a restore to zero after a month is asked we check if there is already a value for this year ($reg is parsed from the counter mask)
980 if (!empty($reg[2]) && preg_match('/^@/', $reg[2])) {
981 $yearoffsettype = preg_replace('/^@/', '', $reg[2]);
982 }
983 if (!empty($reg[3]) && preg_match('/^@/', $reg[3])) {
984 $yearoffsettype = preg_replace('/^@/', '', $reg[3]);
985 }
986
987 //print "yearoffset=".$yearoffset." yearoffsettype=".$yearoffsettype;
988 if (is_numeric($yearoffsettype) && $yearoffsettype >= 1) {
989 $maskraz = $yearoffsettype; // For backward compatibility
990 } elseif ($yearoffsettype === '0' || (!empty($yearoffsettype) && !is_numeric($yearoffsettype) && getDolGlobalInt('SOCIETE_FISCAL_MONTH_START') > 1)) {
991 $maskraz = getDolGlobalString('SOCIETE_FISCAL_MONTH_START');
992 }
993 //print "maskraz=".$maskraz; // -1=no reset
994
995 $monthcomp = 0;
996 $yearcomp = '';
997
998 if ($maskraz > 0) { // A reset is required
999 if ($maskraz == 99) {
1000 $maskraz = (int) date('m', $date);
1001 $resetEveryMonth = true;
1002 }
1003 if ($maskraz > 12) {
1004 return 'ErrorBadMaskBadRazMonth';
1005 }
1006
1007 // Define posy, posm and reg
1008 if ($maskraz > 1) { // if reset is not first month, we need month and year into mask
1009 if (preg_match('/^(.*)\{(y+)\}\{(m+)\}/i', $maskwithonlyymcode, $reg)) {
1010 $posy = 2;
1011 $posm = 3;
1012 } elseif (preg_match('/^(.*)\{(m+)\}\{(y+)\}/i', $maskwithonlyymcode, $reg)) {
1013 $posy = 3;
1014 $posm = 2;
1015 } else {
1016 return 'ErrorCantUseRazInStartedYearIfNoYearMonthInMask';
1017 }
1018
1019 if (dol_strlen($reg[$posy]) < 2) {
1020 return 'ErrorCantUseRazWithYearOnOneDigit';
1021 }
1022 } else { // if reset is for a specific month in year, we need year
1023 if (preg_match('/^(.*)\{(m+)\}\{(y+)\}/i', $maskwithonlyymcode, $reg)) {
1024 $posy = 3; //index in regex
1025 $posm = 2;
1026 } elseif (preg_match('/^(.*)\{(y+)\}\{(m+)\}/i', $maskwithonlyymcode, $reg)) {
1027 $posy = 2; //index in regex
1028 $posm = 3;
1029 } elseif (preg_match('/^(.*)\{(y+)\}/i', $maskwithonlyymcode, $reg)) {
1030 $posy = 2; //index in regex
1031 $posm = 0;
1032 } else {
1033 return 'ErrorCantUseRazIfNoYearInMask';
1034 }
1035 }
1036 // Define length
1037 $yearlen = $posy ? dol_strlen($reg[$posy]) : 0;
1038 $monthlen = $posm ? dol_strlen($reg[$posm]) : 0;
1039 // Define pos
1040 $yearpos = (dol_strlen($reg[1]) + 1);
1041 $monthpos = ($yearpos + $yearlen);
1042 if ($posy == 3 && $posm == 2) { // if month is before year
1043 $monthpos = (dol_strlen($reg[1]) + 1);
1044 $yearpos = ($monthpos + $monthlen);
1045 }
1046 //print "xxx ".$maskwithonlyymcode." maskraz=".$maskraz." posy=".$posy." yearlen=".$yearlen." yearpos=".$yearpos." posm=".$posm." monthlen=".$monthlen." monthpos=".$monthpos." yearoffsettype=".$yearoffsettype." resetEveryMonth=".$resetEveryMonth."\n";
1047
1048 // Define $yearcomp and $monthcomp (that will be use in the select where to search max number)
1049 $monthcomp = $maskraz;
1050 $yearcomp = '';
1051
1052 if (!empty($yearoffsettype) && !is_numeric($yearoffsettype) && $yearoffsettype != '=') { // $yearoffsettype is - or +
1053 $currentyear = (int) date("Y", $date);
1054 $fiscaldate = dol_mktime(0, 0, 0, $maskraz, 1, $currentyear);
1055 $newyeardate = dol_mktime(0, 0, 0, 1, 1, $currentyear);
1056 $nextnewyeardate = dol_mktime(0, 0, 0, 1, 1, $currentyear + 1);
1057 //echo 'currentyear='.$currentyear.' date='.dol_print_date($date, 'day').' fiscaldate='.dol_print_date($fiscaldate, 'day').'<br>';
1058
1059 // If after or equal of current fiscal date
1060 if ($date >= $fiscaldate) {
1061 // If before of next new year date
1062 if ($date < $nextnewyeardate && $yearoffsettype == '+') {
1063 $yearoffset = 1;
1064 }
1065 } elseif ($date >= $newyeardate && $yearoffsettype == '-') {
1066 // If after or equal of current new year date
1067 $yearoffset = -1;
1068 }
1069 } elseif ((int) date("m", $date) < $maskraz && empty($resetEveryMonth)) {
1070 // For backward compatibility
1071 $yearoffset = -1;
1072 } // If current month lower that month of return to zero, year is previous year
1073
1074 if ($yearlen == 4) {
1075 $yearcomp = sprintf("%04d", idate("Y", $date) + $yearoffset);
1076 } elseif ($yearlen == 2) {
1077 $yearcomp = sprintf("%02d", idate("y", $date) + $yearoffset);
1078 } elseif ($yearlen == 1) {
1079 $yearcomp = (string) ((int) substr(date('y', $date), 1, 1) + $yearoffset);
1080 }
1081 if ($monthcomp > 1 && empty($resetEveryMonth)) { // Test with month is useless if monthcomp = 1 (0 is same as 1)
1082 if ($yearlen == 4) {
1083 $yearcomp1 = sprintf("%04d", idate("Y", $date) + $yearoffset + 1);
1084 } elseif ($yearlen == 2) {
1085 $yearcomp1 = sprintf("%02d", idate("y", $date) + $yearoffset + 1);
1086 } else {
1087 $yearcomp1 = '';
1088 }
1089
1090 $sqlwhere .= "(";
1091 $sqlwhere .= " (SUBSTRING(".$field.", ".((int) $yearpos).", ".((int) $yearlen).") = '".$db->escape($yearcomp)."'";
1092 $sqlwhere .= " AND SUBSTRING(".$field.", ".((int) $monthpos).", ".((int) $monthlen).") >= '".str_pad((string) ((int) $monthcomp), ((int) $monthlen), '0', STR_PAD_LEFT)."')";
1093 $sqlwhere .= " OR";
1094 $sqlwhere .= " (SUBSTRING(".$field.", ".((int) $yearpos).", ".((int) $yearlen).") = '".$db->escape($yearcomp1)."'";
1095 $sqlwhere .= " AND SUBSTRING(".$field.", ".((int) $monthpos).", ".((int) $monthlen).") < '".str_pad((string) ((int) $monthcomp), ((int) $monthlen), '0', STR_PAD_LEFT)."') ";
1096 $sqlwhere .= ')';
1097 } elseif ($resetEveryMonth) {
1098 $sqlwhere .= "(SUBSTRING(".$field.", ".((int) $yearpos).", ".((int) $yearlen).") = '".$db->escape($yearcomp)."'";
1099 $sqlwhere .= " AND SUBSTRING(".$field.", ".((int) $monthpos).", ".((int) $monthlen).") = '".str_pad((string) ((int) $monthcomp), ((int) $monthlen), '0', STR_PAD_LEFT)."')";
1100 } else { // reset is done on january
1101 $sqlwhere .= "(SUBSTRING(".$field.", ".((int) $yearpos).", ".((int) $yearlen).") = '".$db->escape($yearcomp)."')";
1102 }
1103 }
1104 //print "sqlwhere=".$sqlwhere." yearcomp=".$yearcomp."<br>\n"; // sqlwhere and yearcomp defined only if we ask a reset
1105 //print "masktri=".$masktri." maskcounter=".$maskcounter." maskraz=".$maskraz." maskoffset=".$maskoffset."<br>\n";
1106
1107 // Define $sqlstring
1108 if (function_exists('mb_strrpos')) {
1109 $posnumstart = mb_strrpos($maskwithnocode, $maskcounter, 0, 'UTF-8');
1110 } else {
1111 $posnumstart = strrpos($maskwithnocode, $maskcounter);
1112 } // Pos of counter in final string (from 0 to ...)
1113 if ($posnumstart < 0) {
1114 return 'ErrorBadMaskFailedToLocatePosOfSequence';
1115 }
1116 $sqlstring = "SUBSTRING(".$field.", ".(((int) $posnumstart) + 1).", ".dol_strlen($maskcounter).")";
1117
1118 // Define $maskLike
1119 $maskLike = dol_string_nospecial($mask);
1120 $maskLike = str_replace("%", "_", $maskLike);
1121
1122 // Replace protected special codes with matching number of _ as wild card character
1123 if ($resetEveryMonth && $yearcomp && $monthcomp) { // Perf optimization, when a reset is requested at each month, we can include the year and month inside the filter
1124 $maskLike = preg_replace('/\{yyyy\}/i', $yearcomp, $maskLike);
1125 $maskLike = preg_replace('/\{yy\}/i', $yearcomp, $maskLike);
1126 $maskLike = preg_replace('/\{y\}/i', $yearcomp, $maskLike);
1127 $maskLike = preg_replace('/\{mm\}/i', sprintf("%02d", $monthcomp), $maskLike);
1128 } elseif ($maskraz == 1 && $yearcomp) { // Perf optimization, when a reset is requested at first month, we can include the year inside the filter, but not the month
1129 $maskLike = preg_replace('/\{yyyy\}/i', $yearcomp, $maskLike);
1130 $maskLike = preg_replace('/\{yy\}/i', $yearcomp, $maskLike);
1131 $maskLike = preg_replace('/\{y\}/i', $yearcomp, $maskLike);
1132 $maskLike = preg_replace('/\{mm\}/i', '__', $maskLike); // we can't include the month in the filter
1133 } else {
1134 $maskLike = preg_replace('/\{yyyy\}/i', '____', $maskLike);
1135 $maskLike = preg_replace('/\{yy\}/i', '__', $maskLike);
1136 $maskLike = preg_replace('/\{y\}/i', '_', $maskLike);
1137 $maskLike = preg_replace('/\{mm\}/i', '__', $maskLike);
1138 }
1139 $maskLike = preg_replace('/\{dd\}/i', '__', $maskLike);
1140 // @phan-suppress-next-line PhanParamSuspiciousOrder
1141 $maskLike = str_replace(dol_string_nospecial('{'.$masktri.'}'), str_pad("", dol_strlen($maskcounter), "_"), $maskLike);
1142 if ($maskrefclient) {
1143 // @phan-suppress-next-line PhanParamSuspiciousOrder
1144 $maskLike = str_replace(dol_string_nospecial('{'.$maskrefclient.'}'), str_pad("", dol_strlen($maskrefclient), "_"), $maskLike);
1145 }
1146 if ($masktype) {
1147 $maskLike = str_replace(dol_string_nospecial('{'.$masktype.'}'), $masktype_value, $maskLike);
1148 }
1149 if ($maskuser) {
1150 $maskLike = str_replace(dol_string_nospecial('{'.$maskuser.'}'), $maskuser_value, $maskLike);
1151 }
1152 if ($maskjournal) {
1153 $maskLike = str_replace(dol_string_nospecial('{'.$maskjournal.'}'), $maskjournal_value, $maskLike);
1154 }
1155 foreach ($maskperso as $key => $val) {
1156 $maskLike = str_replace(dol_string_nospecial($maskperso[$key]), $maskpersonew[$key], $maskLike);
1157 }
1158
1159 // Get counter in database
1160 $counter = 0;
1161 $sql = "SELECT MAX(".$sqlstring.") as val";
1162 $sql .= " FROM ".MAIN_DB_PREFIX.$db->sanitize($table);
1163 $sql .= " WHERE ".$db->sanitize($field)." LIKE '".$db->escape($maskLike) . (getDolGlobalString('SEARCH_FOR_NEXT_VAL_ON_START_ONLY') ? "%" : "") . "'";
1164 $sql .= " AND ".$db->sanitize($field)." NOT LIKE '(PROV%)'";
1165
1166 // To ensure that all variables within the MAX() brackets are integers
1167 // This avoid bad detection of max when data are noised with non numeric values at the position of the numero
1168 if (getDolGlobalInt('MAIN_NUMBERING_FILTER_ON_INT_ONLY')) {
1169 // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
1170 $sql .= " AND ". $db->regexpsql($sqlstring, '^[0-9]+$', 1);
1171 }
1172
1173 if ($bentityon) { // only if entity enable
1174 $sql .= " AND entity IN (".getEntity($sharetable).")";
1175 } elseif (!empty($forceentity)) {
1176 $sql .= " AND entity IN (".$db->sanitize($forceentity).")";
1177 }
1178 if ($where) {
1179 $sql .= $where;
1180 }
1181 if ($sqlwhere) {
1182 $sanitizedsqlwhere = $sqlwhere;
1183 $sql .= " AND ".$sanitizedsqlwhere;
1184 }
1185
1186 //print $sql.'<br>';
1187 dol_syslog("functions2::get_next_value mode=".$mode, LOG_DEBUG);
1188 $resql = $db->query($sql);
1189 if ($resql) {
1190 $obj = $db->fetch_object($resql);
1191 $counter = $obj->val;
1192 } else {
1193 dol_print_error($db);
1194 }
1195
1196 // Check if we must force counter to maskoffset
1197 if (empty($counter)) {
1198 $counter = $maskoffset;
1199 } elseif (preg_match('/[^0-9]/i', $counter)) {
1200 dol_syslog("Error, the last counter found is '".$counter."' so is not a numeric value. We will restart to 1.", LOG_ERR);
1201 $counter = 0;
1202 } elseif ($counter < $maskoffset && !getDolGlobalString('MAIN_NUMBERING_OFFSET_ONLY_FOR_FIRST')) {
1203 $counter = $maskoffset;
1204 }
1205
1206 if ($mode == 'last') { // We found value for counter = last counter value. Now need to get corresponding ref of invoice.
1207 $counterpadded = str_pad($counter, dol_strlen($maskcounter), "0", STR_PAD_LEFT);
1208
1209 // Define $maskLike
1210 $maskLike = dol_string_nospecial($mask);
1211 $maskLike = str_replace("%", "_", $maskLike);
1212 // Replace protected special codes with matching number of _ as wild card character
1213 $maskLike = preg_replace('/\{yyyy\}/i', '____', $maskLike);
1214 $maskLike = preg_replace('/\{yy\}/i', '__', $maskLike);
1215 $maskLike = preg_replace('/\{y\}/i', '_', $maskLike);
1216 $maskLike = preg_replace('/\{mm\}/i', '__', $maskLike);
1217 $maskLike = preg_replace('/\{dd\}/i', '__', $maskLike);
1218 $maskLike = str_replace(dol_string_nospecial('{'.$masktri.'}'), $counterpadded, $maskLike);
1219 if ($maskrefclient) {
1220 // @phan-suppress-next-line PhanParamSuspiciousOrder
1221 $maskLike = str_replace(dol_string_nospecial('{'.$maskrefclient.'}'), str_pad("", dol_strlen($maskrefclient), "_"), $maskLike);
1222 }
1223 if ($masktype) {
1224 $maskLike = str_replace(dol_string_nospecial('{'.$masktype.'}'), $masktype_value, $maskLike);
1225 }
1226 if ($maskuser) {
1227 $maskLike = str_replace(dol_string_nospecial('{'.$maskuser.'}'), $maskuser_value, $maskLike);
1228 }
1229
1230 $ref = '';
1231 $sql = "SELECT ".$db->sanitize($field)." as ref";
1232 $sql .= " FROM ".MAIN_DB_PREFIX.$db->sanitize($table);
1233 $sql .= " WHERE ".$db->sanitize($field)." LIKE '".$db->escape($maskLike) . (getDolGlobalString('SEARCH_FOR_NEXT_VAL_ON_START_ONLY') ? "%" : "") . "'";
1234 $sql .= " AND ".$db->sanitize($field)." NOT LIKE '%PROV%'";
1235 if ($bentityon) { // only if entity enable
1236 $sql .= " AND entity IN (".getEntity($sharetable).")";
1237 } elseif (!empty($forceentity)) {
1238 $sql .= " AND entity IN (".$db->sanitize($forceentity).")";
1239 }
1240 if ($where) {
1241 $sql .= $where;
1242 }
1243 if ($sqlwhere) {
1244 $sql .= " AND ".$sqlwhere;
1245 }
1246
1247 dol_syslog("functions2::get_next_value mode=".$mode, LOG_DEBUG);
1248 $resql = $db->query($sql);
1249 if ($resql) {
1250 $obj = $db->fetch_object($resql);
1251 if ($obj) {
1252 $ref = $obj->ref;
1253 }
1254 } else {
1255 dol_print_error($db);
1256 }
1257
1258 $numFinal = $ref;
1259 } elseif ($mode == 'next') {
1260 $counter++;
1261 $maskrefclient_counter = 0;
1262
1263 // If value for $counter has a length higher than $maskcounter chars
1264 if ($counter >= pow(10, dol_strlen($maskcounter))) {
1265 $counter = 'ErrorMaxNumberReachForThisMask';
1266 }
1267
1268 if (!empty($maskrefclient_maskcounter)) {
1269 //print "maskrefclient_maskcounter=".$maskrefclient_maskcounter." maskwithnocode=".$maskwithnocode." maskrefclient=".$maskrefclient."\n<br>";
1270
1271 // Define $sqlstring
1272 $maskrefclient_posnumstart = strpos($maskwithnocode, $maskrefclient_maskcounter, strpos($maskwithnocode, $maskrefclient)); // Pos of counter in final string (from 0 to ...)
1273 if ($maskrefclient_posnumstart <= 0) {
1274 return 'ErrorBadMask';
1275 }
1276 $sanitized_maskrefclient_string = 'SUBSTRING('.$field.', '.((int) $maskrefclient_posnumstart + 1).', '.dol_strlen($maskrefclient_maskcounter).')';
1277 //print "x".$sqlstring;
1278
1279 // Define $maskrefclient_maskLike
1280 $maskrefclient_maskLike = dol_string_nospecial($mask);
1281 $maskrefclient_maskLike = str_replace("%", "_", $maskrefclient_maskLike);
1282 // Replace protected special codes with matching number of _ as wild card character
1283 $maskrefclient_maskLike = str_replace(dol_string_nospecial('{yyyy}'), '____', $maskrefclient_maskLike);
1284 $maskrefclient_maskLike = str_replace(dol_string_nospecial('{yy}'), '__', $maskrefclient_maskLike);
1285 $maskrefclient_maskLike = str_replace(dol_string_nospecial('{y}'), '_', $maskrefclient_maskLike);
1286 $maskrefclient_maskLike = str_replace(dol_string_nospecial('{mm}'), '__', $maskrefclient_maskLike);
1287 $maskrefclient_maskLike = str_replace(dol_string_nospecial('{dd}'), '__', $maskrefclient_maskLike);
1288 // @phan-suppress-next-line PhanParamSuspiciousOrder
1289 $maskrefclient_maskLike = str_replace(dol_string_nospecial('{'.$masktri.'}'), str_pad("", dol_strlen($maskcounter), "_"), $maskrefclient_maskLike);
1290 // @phan-suppress-next-line PhanParamSuspiciousOrder
1291 $maskrefclient_maskLike = str_replace(dol_string_nospecial('{'.$maskrefclient.'}'), $maskrefclient_clientcode.str_pad("", dol_strlen($maskrefclient_maskcounter), "_"), $maskrefclient_maskLike);
1292
1293 // Get counter in database
1294 $maskrefclient_sql = "SELECT MAX(".$sanitized_maskrefclient_string.") as val";
1295 $maskrefclient_sql .= " FROM ".MAIN_DB_PREFIX.$db->sanitize($table);
1296 $maskrefclient_sql .= " WHERE ".$db->sanitize($field)." LIKE '".$db->escape($maskrefclient_maskLike) . (getDolGlobalString('SEARCH_FOR_NEXT_VAL_ON_START_ONLY') ? "%" : "") . "'";
1297 if ($bentityon) { // only if entity enable
1298 $maskrefclient_sql .= " AND entity IN (".getEntity($sharetable).")";
1299 } elseif (!empty($forceentity)) {
1300 $maskrefclient_sql .= " AND entity IN (".$db->sanitize($forceentity).")";
1301 }
1302 if ($where) {
1303 $maskrefclient_sql .= $where; //use the same optional where as general mask
1304 }
1305 if ($sqlwhere) {
1306 $maskrefclient_sql .= ' AND '.$sqlwhere; //use the same sqlwhere as general mask
1307 }
1308 $maskrefclient_sql .= " AND (SUBSTRING(".$field.", ".(strpos($maskwithnocode, $maskrefclient) + 1).", ".dol_strlen($maskrefclient_maskclientcode).") = '".$db->escape($maskrefclient_clientcode)."')";
1309
1310 dol_syslog("functions2::get_next_value maskrefclient", LOG_DEBUG);
1311 $maskrefclient_resql = $db->query($maskrefclient_sql);
1312 if ($maskrefclient_resql) {
1313 $maskrefclient_obj = $db->fetch_object($maskrefclient_resql);
1314 $maskrefclient_counter = $maskrefclient_obj->val;
1315 } else {
1316 dol_print_error($db);
1317 }
1318
1319 if (empty($maskrefclient_counter) || preg_match('/[^0-9]/i', $maskrefclient_counter)) {
1320 $maskrefclient_counter = $maskrefclient_maskoffset;
1321 }
1322 $maskrefclient_counter++;
1323 }
1324
1325 // Build numFinal
1326 $numFinal = $mask;
1327
1328 // We replace special codes except refclient
1329 if (!empty($yearoffsettype) && !is_numeric($yearoffsettype) && $yearoffsettype != '=') { // yearoffsettype is - or +, so we don't want current year
1330 $numFinal = preg_replace('/\{yyyy\}/i', (string) ((int) date("Y", $date) + $yearoffset), $numFinal);
1331 $numFinal = preg_replace('/\{yy\}/i', (string) ((int) date("y", $date) + $yearoffset), $numFinal);
1332 $numFinal = preg_replace('/\{y\}/i', (string) ((int) substr((string) date("y", $date), 1, 1) + $yearoffset), $numFinal);
1333 } else { // we want yyyy to be current year
1334 $numFinal = preg_replace('/\{yyyy\}/i', date("Y", $date), $numFinal);
1335 $numFinal = preg_replace('/\{yy\}/i', date("y", $date), $numFinal);
1336 $numFinal = preg_replace('/\{y\}/i', substr(date("y", $date), 1, 1), $numFinal);
1337 }
1338 $numFinal = preg_replace('/\{mm\}/i', date("m", $date), $numFinal);
1339 $numFinal = preg_replace('/\{dd\}/i', date("d", $date), $numFinal);
1340
1341 // Now we replace the counter
1342 $maskbefore = '{'.$masktri.'}';
1343 $maskafter = str_pad($counter, dol_strlen($maskcounter), "0", STR_PAD_LEFT);
1344 //print 'x'.$numFinal.' - '.$maskbefore.' - '.$maskafter.'y';exit;
1345 $numFinal = str_replace($maskbefore, $maskafter, $numFinal);
1346
1347 // Now we replace the refclient
1348 if ($maskrefclient) {
1349 //print "maskrefclient=".$maskrefclient." maskrefclient_counter=".$maskrefclient_counter." maskwithonlyymcode=".$maskwithonlyymcode." maskwithnocode=".$maskwithnocode." maskrefclient_clientcode=".$maskrefclient_clientcode." maskrefclient_maskcounter=".$maskrefclient_maskcounter."\n<br>";exit;
1350 $maskrefclient_maskbefore = '{'.$maskrefclient.'}';
1351 $maskrefclient_maskafter = $maskrefclient_clientcode;
1352 if (dol_strlen($maskrefclient_maskcounter) > 0) {
1353 $maskrefclient_maskafter .= str_pad((string) $maskrefclient_counter, dol_strlen($maskrefclient_maskcounter), "0", STR_PAD_LEFT);
1354 }
1355 $numFinal = str_replace($maskrefclient_maskbefore, (string) $maskrefclient_maskafter, $numFinal);
1356 }
1357
1358 // Now we replace the type
1359 if ($masktype) {
1360 $masktype_maskbefore = '{'.$masktype.'}';
1361 $masktype_maskafter = $masktype_value;
1362 $numFinal = str_replace($masktype_maskbefore, $masktype_maskafter, $numFinal);
1363 }
1364
1365 // Now we replace the user
1366 if ($maskuser) {
1367 $maskuser_maskbefore = '{'.$maskuser.'}';
1368 $maskuser_maskafter = $maskuser_value;
1369 $numFinal = str_replace($maskuser_maskbefore, $maskuser_maskafter, $numFinal);
1370 }
1371
1372 // Now we replace the journal code
1373 if ($maskjournal) {
1374 $maskjournal_maskbefore = '{'.$maskjournal.'}';
1375 $maskjournal_maskafter = $maskjournal_value;
1376 $numFinal = str_replace($maskjournal_maskbefore, $maskjournal_maskafter, $numFinal);
1377 }
1378 } else {
1379 $numFinal = "ErrorBadMode";
1380 dol_syslog("functions2::get_next_value ErrorBadMode '$mode'", LOG_ERR);
1381 }
1382
1383 dol_syslog("functions2::get_next_value return ".$numFinal, LOG_DEBUG);
1384 return $numFinal;
1385}
1386
1398function get_string_between($string, $start, $end)
1399{
1400 $ini = strpos($string, $start);
1401 if ($ini === false) {
1402 return '';
1403 }
1404 $ini += strlen($start);
1405 $endpos = strpos($string, $end, $ini);
1406 if ($endpos === false) {
1407 return '';
1408 }
1409 return substr($string, $ini, $endpos - $ini);
1410}
1411
1419function check_value($mask, $value)
1420{
1421 $result = 0;
1422
1423 $hasglobalcounter = false;
1424 $maskrefclient_maskcounter = '';
1425
1426 // Extract value for mask counter, mask raz and mask offset
1427 $reg = array();
1428 if (preg_match('/\{(0+)([@\+][0-9]+)?([@\+][0-9]+)?\}/i', $mask, $reg)) {
1429 $masktri = $reg[1].(isset($reg[2]) ? $reg[2] : '').(isset($reg[3]) ? $reg[3] : '');
1430 $maskcounter = $reg[1];
1431 $hasglobalcounter = true;
1432 } else {
1433 // setting some defaults so the rest of the code won't fail if there is a third party counter
1434 $masktri = '00000';
1435 $maskcounter = '00000';
1436 }
1437 $maskraz = -1;
1438 $maskoffset = 0;
1439 if (dol_strlen($maskcounter) < 3) {
1440 return 'ErrorCounterMustHaveMoreThan3Digits';
1441 }
1442
1443 // Extract value for third party mask counter
1444 $regClientRef = array();
1445 if (preg_match('/\{(c+)(0*)\}/i', $mask, $regClientRef)) {
1446 $maskrefclient = $regClientRef[1].$regClientRef[2];
1447 $maskrefclient_maskclientcode = $regClientRef[1];
1448 $maskrefclient_maskcounter = $regClientRef[2];
1449 $maskrefclient_maskoffset = 0; //default value of maskrefclient_counter offset
1450 $maskrefclient_clientcode = substr('', 0, dol_strlen($maskrefclient_maskclientcode)); //get n first characters of client code to form maskrefclient_clientcode
1451 $maskrefclient_clientcode = str_pad($maskrefclient_clientcode, dol_strlen($maskrefclient_maskclientcode), "#", STR_PAD_RIGHT); //padding maskrefclient_clientcode for having exactly n characters in maskrefclient_clientcode
1452 $maskrefclient_clientcode = dol_string_nospecial($maskrefclient_clientcode); //sanitize maskrefclient_clientcode for sql insert and sql select like
1453 if (dol_strlen($maskrefclient_maskcounter) > 0 && dol_strlen($maskrefclient_maskcounter) < 3) {
1454 return 'ErrorCounterMustHaveMoreThan3Digits';
1455 }
1456 } else {
1457 $maskrefclient = '';
1458 }
1459
1460 // fail if there is neither a global nor a third party counter
1461 if (!$hasglobalcounter && ($maskrefclient_maskcounter == '')) {
1462 return 'ErrorBadMask';
1463 }
1464
1465 $maskwithonlyymcode = $mask;
1466 $maskwithonlyymcode = preg_replace('/\{(0+)([@\+][0-9]+)?([@\+][0-9]+)?\}/i', $maskcounter, $maskwithonlyymcode);
1467 $maskwithonlyymcode = preg_replace('/\{dd\}/i', 'dd', $maskwithonlyymcode);
1468 $maskwithonlyymcode = preg_replace('/\{(c+)(0*)\}/i', $maskrefclient, $maskwithonlyymcode);
1469 $maskwithnocode = $maskwithonlyymcode;
1470 $maskwithnocode = preg_replace('/\{yyyy\}/i', 'yyyy', $maskwithnocode);
1471 $maskwithnocode = preg_replace('/\{yy\}/i', 'yy', $maskwithnocode);
1472 $maskwithnocode = preg_replace('/\{y\}/i', 'y', $maskwithnocode);
1473 $maskwithnocode = preg_replace('/\{mm\}/i', 'mm', $maskwithnocode);
1474 // Now maskwithnocode = 0000ddmmyyyyccc for example
1475 // and maskcounter = 0000 for example
1476 //print "maskwithonlyymcode=".$maskwithonlyymcode." maskwithnocode=".$maskwithnocode."\n<br>";
1477
1478 // If an offset is asked
1479 if (!empty($reg[2]) && preg_match('/^\+/', $reg[2])) {
1480 $maskoffset = preg_replace('/^\+/', '', $reg[2]);
1481 }
1482 if (!empty($reg[3]) && preg_match('/^\+/', $reg[3])) {
1483 $maskoffset = preg_replace('/^\+/', '', $reg[3]);
1484 }
1485
1486 // Define $sqlwhere
1487
1488 // If a restore to zero after a month is asked we check if there is already a value for this year.
1489 if (!empty($reg[2]) && preg_match('/^@/', $reg[2])) {
1490 $maskraz = preg_replace('/^@/', '', $reg[2]);
1491 }
1492 if (!empty($reg[3]) && preg_match('/^@/', $reg[3])) {
1493 $maskraz = preg_replace('/^@/', '', $reg[3]);
1494 }
1495 if ($maskraz >= 0) {
1496 if ($maskraz == 99) {
1497 $maskraz = (int) date('m');
1498 $resetEveryMonth = true;
1499 }
1500 if ($maskraz > 12) {
1501 return 'ErrorBadMaskBadRazMonth';
1502 }
1503
1504 // Define reg
1505 if ($maskraz > 1 && !preg_match('/^(.*)\{(y+)\}\{(m+)\}/i', $maskwithonlyymcode, $reg)) {
1506 return 'ErrorCantUseRazInStartedYearIfNoYearMonthInMask';
1507 }
1508 if ($maskraz <= 1 && !preg_match('/^(.*)\{(y+)\}/i', $maskwithonlyymcode, $reg)) {
1509 return 'ErrorCantUseRazIfNoYearInMask';
1510 }
1511 //print "x".$maskwithonlyymcode." ".$maskraz;
1512 }
1513 //print "masktri=".$masktri." maskcounter=".$maskcounter." maskwithonlyymcode=".$maskwithonlyymcode." maskwithnocode=".$maskwithnocode." maskraz=".$maskraz." maskoffset=".$maskoffset."<br>\n";
1514
1515 if (function_exists('mb_strrpos')) {
1516 $posnumstart = mb_strrpos($maskwithnocode, $maskcounter, 0, 'UTF-8');
1517 } else {
1518 $posnumstart = strrpos($maskwithnocode, $maskcounter);
1519 } // Pos of counter in final string (from 0 to ...)
1520 if ($posnumstart < 0) {
1521 return 'ErrorBadMaskFailedToLocatePosOfSequence';
1522 }
1523
1524 // Check we have a number in $value at position ($posnumstart+1).', '.dol_strlen($maskcounter)
1525 // TODO
1526
1527 // Check length
1528 $len = dol_strlen($maskwithnocode);
1529 if (dol_strlen($value) != $len) {
1530 $result = -1;
1531 }
1532
1533 dol_syslog("functions2::check_value result=".$result, LOG_DEBUG);
1534 return $result;
1535}
1536
1545function binhex($bin, $pad = false, $upper = false)
1546{
1547 $last = dol_strlen($bin) - 1;
1548 $x = 0;
1549 for ($i = 0; $i <= $last; $i++) {
1550 $x += ($bin[$last - $i] ? 1 : 0) << $i;
1551 }
1552 $x = dechex($x);
1553 if ($pad) {
1554 while (dol_strlen($x) < intval(dol_strlen($bin)) / 4) {
1555 $x = "0$x";
1556 }
1557 }
1558 if ($upper) {
1559 $x = strtoupper($x);
1560 }
1561 return $x;
1562}
1563
1570function hexbin($hexa)
1571{
1572 $bin = '';
1573 $strLength = dol_strlen($hexa);
1574 for ($i = 0; $i < $strLength; $i++) {
1575 $bin .= str_pad(decbin(hexdec($hexa[$i])), 4, '0', STR_PAD_LEFT);
1576 }
1577 return $bin;
1578}
1579
1586function numero_semaine($time)
1587{
1588 $stime = dol_print_date($time, '%Y-%m-%d');
1589
1590 $reg = array();
1591 if (preg_match('/^([0-9]+)\-([0-9]+)\-([0-9]+)\s?([0-9]+)?:?([0-9]+)?/i', $stime, $reg)) {
1592 // Date is in 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS' format
1593 $annee = (int) $reg[1];
1594 $mois = (int) $reg[2];
1595 $jour = (int) $reg[3];
1596 } else {
1597 $annee = 0;
1598 $mois = 0;
1599 $jour = 0;
1600 }
1601
1602 /*
1603 * ISO-8601 standard:
1604 * - Week 1 of the year contains Jan 4th, or contains the first Thursday of January.
1605 * - Most years have 52 weeks, but 53 weeks for years starting on a Thursday and bisectile years that start on a Wednesday.
1606 * - The first day of a week is Monday
1607 */
1608
1609 // Definition of the Thursday of the week
1610 if ((int) date("w", mktime(12, 0, 0, $mois, $jour, $annee)) == 0) { // Sunday
1611 $jeudiSemaine = mktime(12, 0, 0, $mois, $jour, $annee) - 3 * 24 * 60 * 60;
1612 } elseif (date("w", mktime(12, 0, 0, $mois, $jour, $annee)) < 4) { // Monday to Wednesday
1613 $jeudiSemaine = mktime(12, 0, 0, $mois, $jour, $annee) + (4 - (int) date("w", mktime(12, 0, 0, $mois, $jour, $annee))) * 24 * 60 * 60;
1614 } elseif ((int) date("w", mktime(12, 0, 0, $mois, $jour, $annee)) > 4) { // Friday to Saturday
1615 $jeudiSemaine = mktime(12, 0, 0, $mois, $jour, $annee) - ((int) date("w", mktime(12, 0, 0, $mois, $jour, $annee)) - 4) * 24 * 60 * 60;
1616 } else { // Thursday
1617 $jeudiSemaine = mktime(12, 0, 0, $mois, $jour, $annee);
1618 }
1619
1620 // Definition of the first Thursday of the year
1621 if ((int) date("w", mktime(12, 0, 0, 1, 1, (int) date("Y", $jeudiSemaine))) == 0) { // Sunday
1622 $premierJeudiAnnee = mktime(12, 0, 0, 1, 1, (int) date("Y", $jeudiSemaine)) + 4 * 24 * 60 * 60;
1623 } elseif ((int) date("w", mktime(12, 0, 0, 1, 1, (int) date("Y", $jeudiSemaine))) < 4) { // Monday to Wednesday
1624 $premierJeudiAnnee = mktime(12, 0, 0, 1, 1, (int) date("Y", $jeudiSemaine)) + (4 - (int) date("w", mktime(12, 0, 0, 1, 1, (int) date("Y", $jeudiSemaine)))) * 24 * 60 * 60;
1625 } elseif ((int) date("w", mktime(12, 0, 0, 1, 1, (int) date("Y", $jeudiSemaine))) > 4) { // Friday to Saturday
1626 $premierJeudiAnnee = mktime(12, 0, 0, 1, 1, (int) date("Y", $jeudiSemaine)) + (7 - ((int) date("w", mktime(12, 0, 0, 1, 1, (int) date("Y", $jeudiSemaine))) - 4)) * 24 * 60 * 60;
1627 } else { // Thursday
1628 $premierJeudiAnnee = mktime(12, 0, 0, 1, 1, (int) date("Y", $jeudiSemaine));
1629 }
1630
1631 // Definition of the week number: nb of days between "first Thursday of the year" and "Thursday of the week"
1632 $numeroSemaine = (
1633 (
1634 (int) date("z", mktime(12, 0, 0, (int) date("m", $jeudiSemaine), (int) date("d", $jeudiSemaine), (int) date("Y", $jeudiSemaine)))
1635 -
1636 (int) date("z", mktime(12, 0, 0, (int) date("m", $premierJeudiAnnee), (int) date("d", $premierJeudiAnnee), (int) date("Y", $premierJeudiAnnee)))
1637 ) / 7
1638 ) + 1;
1639
1640 // Special case of week 53
1641 if ($numeroSemaine == 53) {
1642 // Years starting on a Thursday, and leap years starting on a Wednesday, have 53
1643 if (
1644 ((int) date("w", mktime(12, 0, 0, 1, 1, (int) date("Y", $jeudiSemaine))) == 4)
1645 || (
1646 ((int) date("w", mktime(12, 0, 0, 1, 1, (int) date("Y", $jeudiSemaine))) == 3)
1647 && ((int) date("z", mktime(12, 0, 0, 12, 31, (int) date("Y", $jeudiSemaine))) == 365)
1648 )
1649 ) {
1650 $numeroSemaine = 53;
1651 } else {
1652 $numeroSemaine = 1;
1653 }
1654 }
1655
1656 //echo $jour."-".$mois."-".$annee." (".date("d-m-Y",$premierJeudiAnnee)." - ".date("d-m-Y",$jeudiSemaine).") -> ".$numeroSemaine."<BR>";
1657
1658 return sprintf("%02d", $numeroSemaine);
1659}
1660
1669function weight_convert($weight, &$from_unit, $to_unit)
1670{
1671 /* Pour convertire 320 gr en Kg appeler
1672 * $f = -3
1673 * weigh_convert(320, $f, 0) retournera 0.32
1674 *
1675 */
1676 $weight = is_numeric($weight) ? $weight : 0;
1677 while ($from_unit != $to_unit) {
1678 if ($from_unit > $to_unit) {
1679 $weight *= 10;
1680 $from_unit -= 1;
1681 $weight = weight_convert($weight, $from_unit, $to_unit);
1682 }
1683 if ($from_unit < $to_unit) {
1684 $weight /= 10;
1685 $from_unit += 1;
1686 $weight = weight_convert($weight, $from_unit, $to_unit);
1687 }
1688 }
1689
1690 return $weight;
1691}
1692
1705function dol_set_user_param($db, $conf, &$user, $tab, $entity = -1)
1706{
1707 // Verification parameters
1708 if (count($tab) < 1) {
1709 return -1;
1710 }
1711
1712 $entity = ($entity == -1 ? ((int) $conf->entity) : ((int) $entity));
1713
1714 $db->begin();
1715
1716 // We remove old parameters for all keys in $tab
1717 $sql = "DELETE FROM ".MAIN_DB_PREFIX."user_param";
1718 $sql .= " WHERE fk_user = ".((int) $user->id);
1719 $sql .= " AND entity = ".((int) $entity);
1720 $sql .= " AND param IN (";
1721 $i = 0;
1722 foreach ($tab as $key => $value) {
1723 if ($i > 0) {
1724 $sql .= ',';
1725 }
1726 $sql .= "'".$db->escape($key)."'";
1727 $i++;
1728 }
1729 $sql .= ")";
1730 dol_syslog("functions2.lib::dol_set_user_param", LOG_DEBUG);
1731
1732 $resql = $db->query($sql);
1733 if (!$resql) {
1734 dol_print_error($db);
1735 $db->rollback();
1736 return -1;
1737 }
1738
1739 foreach ($tab as $key => $value) {
1740 // Set new parameters
1741 $forcevalue = 0;
1742 if (is_array($value)) {
1743 if ($value["forcevalue"] == 1) {
1744 $forcevalue = 1;
1745 }
1746 $value = $value["value"];
1747 }
1748 if ($forcevalue == 1 || $value) {
1749 $sql = "INSERT INTO ".MAIN_DB_PREFIX."user_param (fk_user, entity, param, value)";
1750 $sql .= " VALUES (".((int) $user->id).",".((int) $entity).",";
1751 $sql .= " '".$db->escape($key)."','".$db->escape($value)."')";
1752
1753 dol_syslog("functions2.lib::dol_set_user_param", LOG_DEBUG);
1754 $result = $db->query($sql);
1755 if (!$result) {
1756 dol_print_error($db);
1757 $db->rollback();
1758 return -1;
1759 }
1760 $user->conf->$key = $value;
1761 //print "key=".$key." user->conf->key=".$user->conf->$key;
1762 } else {
1763 unset($user->conf->$key);
1764 }
1765 }
1766
1767 $db->commit();
1768 return 1;
1769}
1770
1778function dol_print_reduction($reduction, $langs)
1779{
1780 $string = '';
1781 if ($reduction == 100) {
1782 $string = $langs->transnoentities("Offered");
1783 } else {
1784 $string = vatrate((string) $reduction, true);
1785 }
1786
1787 return $string;
1788}
1789
1797function version_os($option = '')
1798{
1799 if ($option == 'smr') {
1800 $osversion = php_uname('s').' '.php_uname('m').' '.php_uname('r');
1801 } else {
1802 $osversion = php_uname();
1803 }
1804 return $osversion;
1805}
1806
1813function version_php()
1814{
1815 return phpversion();
1816}
1817
1823function version_db()
1824{
1825 global $db;
1826 if (is_object($db) && method_exists($db, 'getVersion')) {
1827 return $db->getVersion();
1828 }
1829 return '';
1830}
1831
1839{
1840 return DOL_VERSION;
1841}
1842
1849{
1850 return $_SERVER["SERVER_SOFTWARE"];
1851}
1852
1862function getListOfModels($db, $type, $maxfilenamelength = 0, $showempty = 0)
1863{
1864 global $conf, $hookmanager, $langs;
1865
1866 $docmodels = array();
1867 $found = 0;
1868 $dirtoscan = '';
1869
1870 $sql = "SELECT nom as id, nom as doc_template_name, libelle as label, description as description";
1871 $sql .= " FROM ".MAIN_DB_PREFIX."document_model";
1872 $sql .= " WHERE type = '".$db->escape($type)."'";
1873 $sql .= " AND entity IN (0,".((int) $conf->entity).")";
1874 $sql .= " ORDER BY description DESC";
1875
1876 dol_syslog('/core/lib/function2.lib.php::getListOfModels', LOG_DEBUG);
1877
1878 $resql_models = $db->query($sql);
1879 if ($resql_models) {
1880 $num = $db->num_rows($resql_models);
1881
1882 if ($showempty) {
1883 $docmodels[0] = '&nbsp;';
1884 }
1885
1886 $i = 0;
1887 while ($i < $num) {
1888 $found = 1;
1889
1890 $obj = $db->fetch_object($resql_models);
1891
1892 if ($obj->id == '0') { // We discard bad record (should not happen)
1893 $i++;
1894 continue;
1895 }
1896
1897 // If this generation module needs to scan a directory, then description field is filled
1898 // with the constant that contains list of directories to scan (COMPANY_ADDON_PDF_ODT_PATH, ...).
1899 if (!empty($obj->description)) { // A list of directories to scan is defined
1900 include_once DOL_DOCUMENT_ROOT.'/core/lib/files.lib.php';
1901
1902 $const = $obj->description;
1903 $dirtoscan = preg_replace('/[\r\n]+/', ',', trim(getDolGlobalString($const)));
1904
1905 $listoffiles = array();
1906
1907 // Now we add models found in directories scanned
1908 $listofdir = explode(',', $dirtoscan);
1909 foreach ($listofdir as $key => $tmpdir) {
1910 $tmpdir = trim($tmpdir);
1911 $tmpdir = preg_replace('/DOL_DATA_ROOT/', DOL_DATA_ROOT, $tmpdir);
1912 if (!$tmpdir) {
1913 unset($listofdir[$key]);
1914 continue;
1915 }
1916 if (is_dir($tmpdir)) {
1917 // all type of template is allowed
1918 $tmpfiles = dol_dir_list($tmpdir, 'files', 0, '', array(), 'name', SORT_ASC, 0);
1919 if (count($tmpfiles)) {
1920 $listoffiles = array_merge($listoffiles, $tmpfiles);
1921 }
1922 }
1923 }
1924
1925 if (count($listoffiles)) {
1926 foreach ($listoffiles as $record) {
1927 $max = ($maxfilenamelength ? $maxfilenamelength : 28);
1928 $docmodels[$obj->id.':'.$record['fullname']] = dol_trunc($record['name'], $max, 'middle');
1929 }
1930 } else {
1931 $docmodels[0] = $obj->label.': '.$langs->trans("None");
1932 }
1933 } else {
1934 if ($type == 'member' && $obj->doc_template_name == 'standard_member') { // Special case, if member template, we add variant per format
1935 global $_Avery_Labels;
1936 include_once DOL_DOCUMENT_ROOT.'/core/lib/format_cards.lib.php';
1937 foreach ($_Avery_Labels as $key => $val) {
1938 $docmodels[$obj->id.':'.$key] = ($obj->label ? $obj->label : $obj->doc_template_name).' '.$val['name'];
1939 }
1940 } else {
1941 // Common usage
1942 $docmodels[$obj->id] = $obj->label ? $obj->label : $obj->doc_template_name;
1943 }
1944 }
1945
1946 $i++;
1947 }
1948 } else {
1949 dol_print_error($db);
1950 return -1;
1951 }
1952 $parameters = array(
1953 'list' => &$docmodels,
1954 'found' => &$found,
1955 'type' => $type,
1956 'maxfilenamelength' => $maxfilenamelength,
1957 );
1958 $reshook = $hookmanager->executeHooks('getListOfModels', $parameters);
1959 if ($reshook < 0) {
1960 setEventMessages($hookmanager->error, $hookmanager->errors, 'errors');
1961 }
1962 if ($found) {
1963 return $docmodels;
1964 } else {
1965 return 0;
1966 }
1967}
1968
1976function is_ip($ip)
1977{
1978 // First we test if it is a valid IPv4
1979 if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
1980 // Then we test if it is a private range
1981 if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)) {
1982 return 2;
1983 }
1984
1985 // Then we test if it is a reserved range
1986 if (!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_RES_RANGE)) {
1987 return 0;
1988 }
1989
1990 return 1;
1991 }
1992
1993 return 0; // If not valid
1994}
1995
2003function dol_buildlogin($lastname, $firstname)
2004{
2005 //$conf->global->MAIN_BUILD_LOGIN_RULE = 'f.lastname';
2006 $charforseparator = getDolGlobalString("MAIN_USER_SEPARATOR_CHAR_FOR_GENERATED_LOGIN", '.');
2007 if ($charforseparator == 'none') {
2008 $charforseparator = '';
2009 }
2010
2011 if (getDolGlobalString('MAIN_BUILD_LOGIN_RULE') == 'flastname') { // flastname
2012 $login = strtolower(dol_string_unaccent(dol_trunc($firstname, 1, 'right', 'UTF-8', 1)));
2013 $login .= strtolower(dol_string_unaccent($lastname));
2014 $login = dol_string_nospecial($login, ''); // For special names
2015 } elseif (getDolGlobalString('MAIN_BUILD_LOGIN_RULE') == 'f.lastname') { // f.lastname
2016 $login = strtolower(dol_string_unaccent(dol_trunc($firstname, 1, 'right', 'UTF-8', 1)));
2017 $login .= ($login ? $charforseparator : '');
2018 $login .= strtolower(dol_string_unaccent($lastname));
2019 $login = dol_string_nospecial($login, ''); // For special names
2020 } else { // firstname.lastname
2021 $login = strtolower(dol_string_unaccent($firstname));
2022 $login .= ($login ? $charforseparator : '');
2023 $login .= strtolower(dol_string_unaccent($lastname));
2024 $login = dol_string_nospecial($login, ''); // For special names
2025 }
2026
2027 // TODO Add a hook to allow external modules to suggest new rules
2028
2029 return $login;
2030}
2031
2038{
2039 $params = array();
2040 $proxyuse = getDolGlobalString('MAIN_PROXY_USE');
2041 $proxyhost = (!$proxyuse ? false : getDolGlobalString('MAIN_PROXY_HOST'));
2042 $proxyport = (!$proxyuse ? false : getDolGlobalString('MAIN_PROXY_PORT'));
2043 $proxyuser = (!$proxyuse ? false : getDolGlobalString('MAIN_PROXY_USER'));
2044 $proxypass = (!$proxyuse ? false : getDolGlobalString('MAIN_PROXY_PASS'));
2045 $timeout = getDolGlobalInt('MAIN_USE_CONNECT_TIMEOUT', 10); // Connection timeout
2046 $response_timeout = getDolGlobalInt('MAIN_USE_RESPONSE_TIMEOUT', 30); // Response timeout
2047 //print extension_loaded('soap');
2048 if ($proxyuse) {
2049 $params = array('connection_timeout' => $timeout,
2050 'response_timeout' => $response_timeout,
2051 'proxy_use' => 1,
2052 'proxy_host' => $proxyhost,
2053 'proxy_port' => $proxyport,
2054 'proxy_login' => $proxyuser,
2055 'proxy_password' => $proxypass,
2056 'trace' => 1
2057 );
2058 } else {
2059 $params = array('connection_timeout' => $timeout,
2060 'response_timeout' => $response_timeout,
2061 'proxy_use' => 0,
2062 'proxy_host' => false,
2063 'proxy_port' => false,
2064 'proxy_login' => false,
2065 'proxy_password' => false,
2066 'trace' => 1
2067 );
2068 }
2069 return $params;
2070}
2071
2072
2082function dolGetElementUrl($objectid, $objecttype, $withpicto = 0, $option = '')
2083{
2084 global $db, $langs;
2085
2086 $ret = '';
2087 $regs = array();
2088
2089 // If we ask a resource form external module (instead of default path)
2090 if (preg_match('/^([^@]+)@([^@]+)$/i', $objecttype, $regs)) {
2091 $myobject = $regs[1];
2092 $module = $regs[2];
2093 } else {
2094 // Parse $objecttype (ex: project_task)
2095 $module = $myobject = $objecttype;
2096 if (preg_match('/^([^_]+)_([^_]+)/i', $objecttype, $regs)) {
2097 $module = $regs[1];
2098 $myobject = $regs[2];
2099 }
2100 }
2101
2102 // Generic case for $classpath
2103 $classpath = $module.'/class';
2104
2105 // Special cases, to work with non standard path
2106 if ($objecttype == 'facture' || $objecttype == 'invoice') {
2107 $langs->load('bills');
2108 $classpath = 'compta/facture/class';
2109 $module = 'facture';
2110 $myobject = 'facture';
2111 } elseif ($objecttype == 'commande' || $objecttype == 'order') {
2112 $langs->load('orders');
2113 $classpath = 'commande/class';
2114 $module = 'commande';
2115 $myobject = 'commande';
2116 } elseif ($objecttype == 'mailing') {
2117 $langs->load('mailing');
2118 $classpath = 'comm/mailing/class';
2119 } elseif ($objecttype == 'propal') {
2120 $langs->load('propal');
2121 $classpath = 'comm/propal/class';
2122 } elseif ($objecttype == 'supplier_proposal') {
2123 $langs->load('supplier_proposal');
2124 $classpath = 'supplier_proposal/class';
2125 } elseif ($objecttype == 'shipping') {
2126 $langs->load('sendings');
2127 $classpath = 'expedition/class';
2128 $myobject = 'expedition';
2129 $module = 'expedition';
2130 } elseif ($objecttype == 'delivery') {
2131 $langs->load('sendings');
2132 $classpath = 'delivery/class';
2133 $myobject = 'delivery';
2134 $module = 'delivery_note';
2135 } elseif ($objecttype == 'contract') {
2136 $langs->load('contracts');
2137 $classpath = 'contrat/class';
2138 $module = 'contrat';
2139 $myobject = 'contrat';
2140 } elseif ($objecttype == 'member') {
2141 $langs->load('members');
2142 $classpath = 'adherents/class';
2143 $module = 'adherent';
2144 $myobject = 'adherent';
2145 } elseif ($objecttype == 'cabinetmed_cons') {
2146 $classpath = 'cabinetmed/class';
2147 $module = 'cabinetmed';
2148 $myobject = 'cabinetmedcons';
2149 } elseif ($objecttype == 'fichinter') {
2150 $langs->load('interventions');
2151 $classpath = 'fichinter/class';
2152 $module = 'ficheinter';
2153 $myobject = 'fichinter';
2154 } elseif ($objecttype == 'project') {
2155 $langs->load('projects');
2156 $classpath = 'projet/class';
2157 $module = 'projet';
2158 } elseif ($objecttype == 'project_task') {
2159 $langs->load('projects');
2160 $classpath = 'projet/class';
2161 $module = 'projet';
2162 $myobject = 'task';
2163 } elseif ($objecttype == 'stock') {
2164 $classpath = 'product/stock/class';
2165 $module = 'stock';
2166 $myobject = 'stock';
2167 } elseif ($objecttype == 'inventory') {
2168 $classpath = 'product/inventory/class';
2169 $module = 'stock';
2170 $myobject = 'inventory';
2171 } elseif ($objecttype == 'mo') {
2172 $classpath = 'mrp/class';
2173 $module = 'mrp';
2174 $myobject = 'mo';
2175 } elseif ($objecttype == 'productlot') {
2176 $classpath = 'product/stock/class';
2177 $module = 'stock';
2178 $myobject = 'productlot';
2179 }
2180
2181 // Generic case for $classfile and $classname
2182 $classfile = strtolower($myobject);
2183 $classname = ucfirst($myobject);
2184 //print "objecttype=".$objecttype." module=".$module." subelement=".$subelement." classfile=".$classfile." classname=".$classname." classpath=".$classpath;
2185
2186 if ($objecttype == 'invoice_supplier') {
2187 $classfile = 'fournisseur.facture';
2188 $classname = 'FactureFournisseur';
2189 $classpath = 'fourn/class';
2190 $module = 'fournisseur';
2191 } elseif ($objecttype == 'order_supplier') {
2192 $classfile = 'fournisseur.commande';
2193 $classname = 'CommandeFournisseur';
2194 $classpath = 'fourn/class';
2195 $module = 'fournisseur';
2196 } elseif ($objecttype == 'supplier_proposal') {
2197 $classfile = 'supplier_proposal';
2198 $classname = 'SupplierProposal';
2199 $classpath = 'supplier_proposal/class';
2200 $module = 'supplier_proposal';
2201 } elseif ($objecttype == 'stock') {
2202 $classpath = 'product/stock/class';
2203 $classfile = 'entrepot';
2204 $classname = 'Entrepot';
2205 } elseif ($objecttype == 'facturerec') {
2206 $classpath = 'compta/facture/class';
2207 $classfile = 'facture-rec';
2208 $classname = 'FactureRec';
2209 $module = 'facture';
2210 } elseif ($objecttype == 'mailing') {
2211 $classpath = 'comm/mailing/class';
2212 $classfile = 'mailing';
2213 $classname = 'Mailing';
2214 }
2215
2216 if (isModEnabled($module)) {
2217 $res = dol_include_once('/'.$classpath.'/'.$classfile.'.class.php');
2218 if ($res) {
2219 if (class_exists($classname)) {
2220 $object = new $classname($db);
2221 $res = $object->fetch($objectid);
2222 if ($res > 0) {
2223 $ret = $object->getNomUrl($withpicto, $option);
2224 } elseif ($res == 0) {
2225 $ret = $langs->trans('Deleted');
2226 }
2227 unset($object);
2228 } else {
2229 dol_syslog("Class with classname ".$classname." is unknown even after the include", LOG_ERR);
2230 }
2231 }
2232 }
2233 return $ret;
2234}
2235
2236
2245function cleanCorruptedTree($db, $tabletocleantree, $fieldfkparent)
2246{
2247 $totalnb = 0;
2248 $listofid = array();
2249 $listofparentid = array();
2250
2251 // Get list of all id in array listofid and all parents in array listofparentid
2252 $sql = "SELECT rowid, ".$db->sanitize($fieldfkparent)." as parent_id FROM ".MAIN_DB_PREFIX.$db->sanitize($tabletocleantree);
2253 $resql = $db->query($sql);
2254 if ($resql) {
2255 $num = $db->num_rows($resql);
2256 $i = 0;
2257 while ($i < $num) {
2258 $obj = $db->fetch_object($resql);
2259 $listofid[] = $obj->rowid;
2260 if ($obj->parent_id > 0) {
2261 $listofparentid[$obj->rowid] = $obj->parent_id;
2262 }
2263 $i++;
2264 }
2265 } else {
2266 dol_print_error($db);
2267 }
2268
2269 if (count($listofid)) {
2270 print 'Code requested to clean tree (may be to solve data corruption), so we check/clean orphelins and loops.'."<br>\n";
2271
2272 // Check loops on each other
2273 $sql = "UPDATE ".MAIN_DB_PREFIX.$db->sanitize($tabletocleantree)." SET ".$db->sanitize($fieldfkparent)." = 0 WHERE ".$db->sanitize($fieldfkparent)." = rowid"; // So we update only records linked to themself
2274 $resql = $db->query($sql);
2275 if ($resql) {
2276 $nb = $db->affected_rows($resql);
2277 if ($nb > 0) {
2278 print '<br>Some record that were parent of themself were cleaned.';
2279 }
2280
2281 $totalnb += $nb;
2282 }
2283 //else dol_print_error($db);
2284
2285 // Check other loops
2286 $listofidtoclean = array();
2287 foreach ($listofparentid as $id => $pid) {
2288 // Check depth
2289 //print 'Analyse record id='.$id.' with parent '.$pid.'<br>';
2290
2291 $cursor = $id;
2292 $arrayidparsed = array(); // We start from child $id
2293 while ($cursor > 0) {
2294 $arrayidparsed[$cursor] = 1;
2295 if ($arrayidparsed[$listofparentid[$cursor]]) { // We detect a loop. A record with a parent that was already into child
2296 print 'Found a loop between id '.$id.' - '.$cursor.'<br>';
2297 unset($arrayidparsed);
2298 $listofidtoclean[$cursor] = $id;
2299 break;
2300 }
2301 // @phpstan-ignore-next-line PHPStan thinks this line is never reached
2302 $cursor = $listofparentid[$cursor];
2303 }
2304
2305 if (count($listofidtoclean)) {
2306 break;
2307 }
2308 }
2309
2310 $sql = "UPDATE ".MAIN_DB_PREFIX.$db->sanitize($tabletocleantree);
2311 $sql .= " SET ".$db->sanitize($fieldfkparent)." = 0";
2312 $sql .= " WHERE rowid IN (".$db->sanitize(implode(',', $listofidtoclean)).")"; // So we update only records detected wrong
2313 $resql = $db->query($sql);
2314 if ($resql) {
2315 $nb = $db->affected_rows($resql);
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 implode(',', $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.$db->sanitize($tabletocleantree);
2328 $sql .= " SET ".$db->sanitize($fieldfkparent)." = 0";
2329 $sql .= " WHERE ".$db->sanitize($fieldfkparent)." NOT IN (".$db->sanitize(implode(',', $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($resql);
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 implode(',', $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 return -1;
2347}
2348
2349
2359function colorArrayToHex($arraycolor, $colorifnotfound = '888888')
2360{
2361 if (!is_array($arraycolor)) {
2362 return $colorifnotfound;
2363 }
2364 if (empty($arraycolor)) {
2365 return $colorifnotfound;
2366 }
2367 return sprintf("%02s", dechex($arraycolor[0])).sprintf("%02s", dechex($arraycolor[1])).sprintf("%02s", dechex($arraycolor[2]));
2368}
2369
2380function colorStringToArray($stringcolor, $colorifnotfound = array(88, 88, 88))
2381{
2382 if (is_array($stringcolor)) {
2383 return $stringcolor; // If already in the correct output format, we return as is
2384 }
2385 $reg = array();
2386 $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);
2387 if (!$tmp) {
2388 $tmp = array_map('intval', explode(',', $stringcolor));
2389 '@phan-var-force int[] $tmp';
2390 if (count($tmp) < 3) {
2391 return $colorifnotfound;
2392 }
2393 return $tmp;
2394 }
2395 return array(hexdec($reg[1]), hexdec($reg[2]), hexdec($reg[3]));
2396}
2397
2403function colorValidateHex($color, $allow_white = true)
2404{
2405 if (!$allow_white && ($color === '#fff' || $color === '#ffffff')) {
2406 return false;
2407 }
2408
2409 if (preg_match('/^#[a-f0-9]{6}$/i', $color)) { //hex color is valid
2410 return true;
2411 }
2412 return false;
2413}
2414
2424function colorAgressiveness($hex, $ratio = -50, $brightness = 0)
2425{
2426 if (empty($ratio)) {
2427 $ratio = 0; // To avoid null
2428 }
2429
2430 // Steps should be between -255 and 255. Negative = darker, positive = lighter
2431 $ratio = max(-100, min(100, $ratio));
2432
2433 // Normalize into a six character long hex string
2434 $hex = str_replace('#', '', $hex);
2435 if (strlen($hex) == 3) {
2436 $hex = str_repeat(substr($hex, 0, 1), 2).str_repeat(substr($hex, 1, 1), 2).str_repeat(substr($hex, 2, 1), 2);
2437 }
2438
2439 // Split into three parts: R, G and B
2440 $color_parts = str_split($hex, 2);
2441 $return = '#';
2442
2443 foreach ($color_parts as $color) {
2444 $color = hexdec($color); // Convert to decimal
2445 if ($ratio > 0) { // We increase aggressivity
2446 if ($color > 127) {
2447 $color += ((255 - $color) * ($ratio / 100));
2448 }
2449 if ($color < 128) {
2450 $color -= ($color * ($ratio / 100));
2451 }
2452 } else { // We decrease aggressiveness
2453 if ($color > 128) {
2454 $color -= (($color - 128) * (abs($ratio) / 100));
2455 }
2456 if ($color < 127) {
2457 $color += ((128 - $color) * (abs($ratio) / 100));
2458 }
2459 }
2460 if ($brightness > 0) {
2461 $color = (int) ($color * (100 + abs($brightness)) / 100);
2462 } else {
2463 $color = (int) ($color * (100 - abs($brightness)) / 100);
2464 }
2465
2466 $color = max(0, min(255, (int) $color)); // Adjust color to stay into valid range
2467 $return .= str_pad(dechex($color), 2, '0', STR_PAD_LEFT); // Make two char hex code
2468 }
2469
2470 //var_dump($hex.' '.$ratio.' -> '.$return);
2471 return $return;
2472}
2473
2480function colorAdjustBrightness($hex, $steps)
2481{
2482 // Steps should be between -255 and 255. Negative = darker, positive = lighter
2483 $steps = max(-255, min(255, $steps));
2484
2485 // Normalize into a six character long hex string
2486 $hex = str_replace('#', '', $hex);
2487 if (strlen($hex) == 3) {
2488 $hex = str_repeat(substr($hex, 0, 1), 2).str_repeat(substr($hex, 1, 1), 2).str_repeat(substr($hex, 2, 1), 2);
2489 }
2490
2491 // Split into three parts: R, G and B
2492 $color_parts = str_split($hex, 2);
2493 $return = '#';
2494
2495 foreach ($color_parts as $color) {
2496 $color = hexdec($color); // Convert to decimal
2497 $color = max(0, min(255, $color + $steps)); // Adjust color
2498 $return .= str_pad(dechex($color), 2, '0', STR_PAD_LEFT); // Make two char hex code
2499 }
2500
2501 return $return;
2502}
2503
2509function colorDarker($hex, $percent)
2510{
2511 $steps = intval(255 * $percent / 100) * -1;
2512 return colorAdjustBrightness($hex, $steps);
2513}
2514
2520function colorLighten($hex, $percent)
2521{
2522 $steps = intval(255 * $percent / 100);
2523 return colorAdjustBrightness($hex, $steps);
2524}
2525
2526
2533function colorHexToRgb($hex, $alpha = false, $returnArray = false)
2534{
2535 $string = '';
2536 $hex = str_replace('#', '', $hex);
2537 $length = strlen($hex);
2538 $rgb = array();
2539 $rgb['r'] = hexdec($length == 6 ? substr($hex, 0, 2) : ($length == 3 ? str_repeat(substr($hex, 0, 1), 2) : 0));
2540 $rgb['g'] = hexdec($length == 6 ? substr($hex, 2, 2) : ($length == 3 ? str_repeat(substr($hex, 1, 1), 2) : 0));
2541 $rgb['b'] = hexdec($length == 6 ? substr($hex, 4, 2) : ($length == 3 ? str_repeat(substr($hex, 2, 1), 2) : 0));
2542 if ($alpha !== false) {
2543 $rgb['a'] = (float) $alpha;
2544 $string = 'rgba('.implode(',', array_map('strval', $rgb)).')';
2545 } else {
2546 $string = 'rgb('.implode(',', array_map('strval', $rgb)).')';
2547 }
2548
2549 if ($returnArray) {
2550 return $rgb;
2551 } else {
2552 return $string;
2553 }
2554}
2555
2564function colorHexToHsl($hex, $alpha = false, $returnArray = false)
2565{
2566 $hex = colorArrayToHex(colorStringToArray($hex));
2567 $hex = str_replace('#', '', $hex);
2568 $red = hexdec(substr($hex, 0, 2)) / 255;
2569 $green = hexdec(substr($hex, 2, 2)) / 255;
2570 $blue = hexdec(substr($hex, 4, 2)) / 255;
2571
2572 $cmin = min($red, $green, $blue);
2573 $cmax = max($red, $green, $blue);
2574 $delta = $cmax - $cmin;
2575
2576 if ($delta == 0) {
2577 $hue = 0;
2578 } elseif ($cmax === $red) {
2579 $hue = (($green - $blue) / $delta);
2580 } elseif ($cmax === $green) {
2581 $hue = ($blue - $red) / $delta + 2;
2582 } else {
2583 $hue = ($red - $green) / $delta + 4;
2584 }
2585
2586 $hue = round($hue * 60);
2587 if ($hue < 0) {
2588 $hue += 360;
2589 }
2590
2591 $lightness = (($cmax + $cmin) / 2);
2592 $saturation = $delta === 0 ? 0 : ($delta / (1 - abs(2 * $lightness - 1)));
2593 if ($saturation < 0) {
2594 $saturation += 1;
2595 }
2596
2597 $lightness = round($lightness * 100);
2598 $saturation = round($saturation * 100);
2599
2600 if ($returnArray) {
2601 return array(
2602 'h' => $hue,
2603 'l' => $lightness,
2604 's' => $saturation,
2605 'a' => $alpha === false ? 1 : $alpha
2606 );
2607 } elseif ($alpha) {
2608 return 'hsla('.$hue.', '.$saturation.', '.$lightness.' / '.$alpha.')';
2609 } else {
2610 return 'hsl('.$hue.', '.$saturation.', '.$lightness.')';
2611 }
2612}
2613
2621function cartesianArray(array $input)
2622{
2623 // filter out empty values
2624 $input = array_filter($input);
2625
2626 $result = array(array());
2627
2628 foreach ($input as $key => $values) {
2629 $append = array();
2630
2631 foreach ($result as $product) {
2632 foreach ($values as $item) {
2633 $product[$key] = $item;
2634 $append[] = $product;
2635 }
2636 }
2637
2638 $result = $append;
2639 }
2640
2641 return $result;
2642}
2643
2644
2651function getModuleDirForApiClass($moduleobject)
2652{
2653 $moduledirforclass = $moduleobject;
2654 if ($moduledirforclass != 'api') {
2655 $moduledirforclass = preg_replace('/api$/i', '', $moduledirforclass);
2656 }
2657
2658 if ($moduleobject == 'contracts') {
2659 $moduledirforclass = 'contrat';
2660 } elseif (in_array($moduleobject, array('admin', 'login', 'setup', 'access', 'status', 'tools', 'documents', 'objectlinks', 'emailtemplates'))) {
2661 $moduledirforclass = 'api';
2662 } elseif (in_array($moduleobject, ['contact', 'contacts', 'customer', 'thirdparty', 'thirdparties'])) {
2663 $moduledirforclass = 'societe';
2664 } elseif ($moduleobject == 'propal' || $moduleobject == 'propale' || $moduleobject == 'proposals') {
2665 $moduledirforclass = 'comm/propal';
2666 } elseif ($moduleobject == 'agenda' || $moduleobject == 'agendaevents') {
2667 $moduledirforclass = 'comm/action';
2668 } elseif ($moduleobject == 'mailing' || $moduleobject == 'mailings') {
2669 $moduledirforclass = 'comm/mailing';
2670 } elseif (in_array($moduleobject, ['adherent', 'members', 'memberstypes', 'subscriptions'])) {
2671 $moduledirforclass = 'adherents';
2672 } elseif ($moduleobject == 'don' || $moduleobject == 'donations') {
2673 $moduledirforclass = 'don';
2674 } elseif ($moduleobject == 'banque' || $moduleobject == 'bankaccounts' || $moduleobject == 'variouspayments') {
2675 $moduledirforclass = 'compta/bank';
2676 } elseif (in_array($moduleobject, array('vatpayments', 'localtaxes', 'socialcontributions'))) {
2677 // The tax module exposes its API classes from htdocs/tax/class/ (its module dir),
2678 // even though the business classes live under compta/tva, compta/localtax and
2679 // compta/sociales. The API Explorer discovers them there via the default 'tax' dir.
2680 $moduledirforclass = 'tax';
2681 } elseif ($moduleobject == 'category' || $moduleobject == 'categorie') {
2682 $moduledirforclass = 'categories';
2683 } elseif ($moduleobject == 'order' || $moduleobject == 'orders') {
2684 $moduledirforclass = 'commande';
2685 } elseif ($moduleobject == 'shipments') {
2686 $moduledirforclass = 'expedition';
2687 } elseif ($moduleobject == 'multicurrencies') {
2688 $moduledirforclass = 'multicurrency';
2689 } elseif ($moduleobject == 'facture' || $moduleobject == 'invoice' || $moduleobject == 'invoices') {
2690 $moduledirforclass = 'compta/facture';
2691 } elseif ($moduleobject == 'project' || $moduleobject == 'projects' || $moduleobject == 'task' || $moduleobject == 'tasks') {
2692 $moduledirforclass = 'projet';
2693 } elseif ($moduleobject == 'stock' || $moduleobject == 'stockmovements' || $moduleobject == 'warehouses' || $moduleobject == 'productlots') {
2694 $moduledirforclass = 'product/stock';
2695 } elseif ($moduleobject == 'supplierproposals' || $moduleobject == 'supplierproposal' || $moduleobject == 'supplier_proposal') {
2696 $moduledirforclass = 'supplier_proposal';
2697 } elseif ($moduleobject == 'fournisseur' || $moduleobject == 'supplierinvoices' || $moduleobject == 'supplierorders') {
2698 $moduledirforclass = 'fourn';
2699 } elseif ($moduleobject == 'ficheinter' || $moduleobject == 'interventions') {
2700 $moduledirforclass = 'fichinter';
2701 } elseif ($moduleobject == 'cronjobs') {
2702 $moduledirforclass = 'cron';
2703 } elseif ($moduleobject == 'mos') {
2704 $moduledirforclass = 'mrp';
2705 } elseif ($moduleobject == 'workstations') {
2706 $moduledirforclass = 'workstation';
2707 } elseif ($moduleobject == 'accounting') {
2708 $moduledirforclass = 'accountancy';
2709 } elseif ($moduleobject == 'paiements') {
2710 $moduledirforclass = 'compta/facture';
2711 } elseif (in_array($moduleobject, array('products', 'expensereports', 'users', 'tickets', 'boms', 'receptions', 'partnerships', 'recruitments', 'websites'))) {
2712 $moduledirforclass = preg_replace('/s$/', '', $moduleobject);
2713 } elseif ($moduleobject == 'paymentsalaries') {
2714 $moduledirforclass = 'salaries';
2715 } elseif ($moduleobject == 'paymentexpensereports') {
2716 $moduledirforclass = 'expensereport';
2717 } elseif ($moduleobject == 'eventattendees') {
2718 $moduledirforclass = 'eventorganization';
2719 } elseif ($moduleobject == 'holidays') {
2720 $moduledirforclass = 'holiday';
2721 } elseif ($moduleobject == 'resources' || $moduleobject == 'dolresources') {
2722 $moduledirforclass = 'resource';
2723 }
2724
2725 return $moduledirforclass;
2726}
2727
2735function randomColorPart($min = 0, $max = 255)
2736{
2737 return str_pad(dechex(mt_rand($min, $max)), 2, '0', STR_PAD_LEFT);
2738}
2739
2747function randomColor($min = 0, $max = 255)
2748{
2749 return randomColorPart($min, $max).randomColorPart($min, $max).randomColorPart($min, $max);
2750}
2751
2752
2753if (!function_exists('dolEscapeXML')) {
2760 function dolEscapeXML($string)
2761 {
2762 return strtr($string, array('\'' => '&apos;', '"' => '&quot;', '&' => '&amp;', '<' => '&lt;', '>' => '&gt;'));
2763 }
2764}
2765
2766
2774{
2776 // Define $urlwithroot
2777 $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
2778 $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file
2779 //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
2780 $notetoshow = preg_replace('/src="[a-zA-Z0-9_\/\-\.]*(viewimage\.php\?modulepart=medias[^"]*)"/', 'src="'.$urlwithroot.'/\1"', $notetoshow);
2781 return $notetoshow;
2782}
2783
2792function price2fec($amount)
2793{
2794 global $conf;
2795
2796 // Clean parameters
2797 if (empty($amount)) {
2798 $amount = 0; // To have a numeric value if amount not defined or = ''
2799 }
2800 $amount = (is_numeric($amount) ? $amount : 0); // Check if amount is numeric, for example, an error occurred when amount value = o (letter) instead 0 (number)
2801
2802 // Output decimal number by default
2803 $nbdecimal = (!getDolGlobalString('ACCOUNTING_FEC_DECIMAL_LENGTH') ? 2 : $conf->global->ACCOUNTING_FEC_DECIMAL_LENGTH);
2804
2805 // Output separators by default
2806 $dec = (!getDolGlobalString('ACCOUNTING_FEC_DECIMAL_SEPARATOR') ? ',' : $conf->global->ACCOUNTING_FEC_DECIMAL_SEPARATOR);
2807 $thousand = (!getDolGlobalString('ACCOUNTING_FEC_THOUSAND_SEPARATOR') ? '' : $conf->global->ACCOUNTING_FEC_THOUSAND_SEPARATOR);
2808
2809 // Format number
2810 $output = number_format($amount, $nbdecimal, $dec, $thousand);
2811
2812 return $output;
2813}
2814
2821function phpSyntaxError($code)
2822{
2823 if (!defined("CR")) {
2824 define("CR", "\r");
2825 }
2826 if (!defined("LF")) {
2827 define("LF", "\n");
2828 }
2829 if (!defined("CRLF")) {
2830 define("CRLF", "\r\n");
2831 }
2832
2833 $braces = 0;
2834 $inString = 0;
2835 foreach (token_get_all('<?php '.$code) as $token) {
2836 if (is_array($token)) {
2837 switch ($token[0]) {
2838 case T_CURLY_OPEN:
2839 case T_DOLLAR_OPEN_CURLY_BRACES:
2840 case T_START_HEREDOC:
2841 ++$inString;
2842 break;
2843 case T_END_HEREDOC:
2844 --$inString;
2845 break;
2846 }
2847 } elseif ($inString & 1) {
2848 switch ($token) {
2849 case '`':
2850 case '\'':
2851 case '"':
2852 --$inString;
2853 break;
2854 }
2855 } else {
2856 switch ($token) {
2857 case '`':
2858 case '\'':
2859 case '"':
2860 ++$inString;
2861 break;
2862 case '{':
2863 ++$braces;
2864 break;
2865 case '}':
2866 if ($inString) {
2867 --$inString;
2868 } else {
2869 --$braces;
2870 if ($braces < 0) {
2871 break 2;
2872 }
2873 }
2874 break;
2875 }
2876 }
2877 }
2878 $inString = @ini_set('log_errors', false);
2879 $token = @ini_set('display_errors', true);
2880 ob_start();
2881 $code = substr($code, strlen('<?php '));
2882 $braces || $code = "if(0){{$code}\n}";
2883 // @phan-suppress-next-line PhanPluginUnsafeEval
2884 if (eval($code) === false) {
2885 if ($braces) {
2886 $braces = PHP_INT_MAX;
2887 } else {
2888 false !== strpos($code, CR) && $code = strtr(str_replace(CRLF, LF, $code), CR, LF);
2889 $braces = substr_count($code, LF);
2890 }
2891 $code = ob_get_clean();
2892 $code = strip_tags($code);
2893 if (preg_match("'syntax error, (.+) in .+ on line (\d+)$'s", $code, $code)) {
2894 $code[2] = (int) $code[2];
2895 $code = $code[2] <= $braces
2896 ? array($code[1], $code[2])
2897 : array('unexpected $end'.substr($code[1], 14), $braces);
2898 } else {
2899 $code = array('syntax error', 0);
2900 }
2901 } else {
2902 ob_end_clean();
2903 $code = false;
2904 }
2905 @ini_set('display_errors', $token);
2906 @ini_set('log_errors', $inString);
2907 return $code;
2908}
2909
2910
2917{
2918 global $user;
2919
2920 // If $acceptlocallinktomedia is true, we can add link media files int email templates (we already can do this into HTML editor of an email).
2921 // Note that local link to a file into medias are replaced with a real link by email in CMailFile.class.php with value $urlwithroot defined like this:
2922 // $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
2923 // $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file
2924
2925 $acceptlocallinktomedia = getDolGlobalInt('MAIN_DISALLOW_MEDIAS_IN_EMAIL_TEMPLATES') ? 0 : 1;
2926
2927 // By default we accept to add medias from emails templates but this may be refused in the future
2928 // we detect we are not on a public url that we can access remotely (if we are on a private network, such files can't be reached),
2929 // except if MAIN_ALLOW_WYSIWYG_LOCAL_MEDIAS_ON_PRIVATE_NETWORK is net, in which case we accept also if instance has a local or private network URL.
2930
2931 if ($acceptlocallinktomedia) {
2933 $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
2934
2935 // Parse $newUrl to get the IP of the server
2936 $newUrlArray = parse_url($urlwithouturlroot);
2937 $hosttocheck = $newUrlArray['host'];
2938 $hosttocheck = str_replace(array('[', ']'), '', $hosttocheck); // Remove brackets of IPv6
2939
2940 if (function_exists('gethostbyname')) {
2941 $iptocheck = gethostbyname($hosttocheck);
2942 } else {
2943 $iptocheck = $hosttocheck;
2944 }
2945
2946 //var_dump($iptocheck.' '.$acceptlocallinktomedia);
2947 $allowPrivateNetworkIP = getDolGlobalInt('MAIN_ALLOW_WYSIWYG_LOCAL_MEDIAS_ON_PRIVATE_NETWORK');
2948 if (!$allowPrivateNetworkIP && !filter_var($iptocheck, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
2949 // If ip of public url ($iptocheck) is a private network IP (192.168.0.1; 127.0.0.1...), we do not allow to upload files on media (they are unreachable publicly).
2950 $acceptlocallinktomedia = 0;
2951 //dol_syslog("WYSIWYG Editor : local media not allowed (checked IP: {$iptocheck}). Use MAIN_ALLOW_WYSIWYG_LOCAL_MEDIAS_ON_PRIVATE_NETWORK = 1 to allow local URL into WYSIWYG html content");
2952 }
2953
2954 $allowUrlInHTTP = getDolGlobalInt('MAIN_ALLOW_WYSIWYG_EVEN_ON_UNSECURED_EXTERNAL_HTTP_URL');
2955 if (!$allowUrlInHTTP && preg_match('/http:/i', $urlwithouturlroot)) {
2956 // If public url is not a https, we do not allow to add medias link. It will generate security alerts when email will be sent.
2957 $acceptlocallinktomedia = 0;
2958 // TODO Show a warning
2959 }
2960
2961 if (!empty($user->socid)) {
2962 $acceptlocallinktomedia = 0;
2963 }
2964 }
2965
2966 //return 1;
2967 return $acceptlocallinktomedia;
2968}
2969
2970
2978{
2979 $string = trim($string);
2980
2981 // If string does not start and end with parenthesis, we return $string as is.
2982 if (! preg_match('/^\‍(.*\‍)$/', $string)) {
2983 return $string;
2984 }
2985
2986 $nbofchars = dol_strlen($string);
2987 $i = 0;
2988 $g = 0;
2989 $countparenthesis = 0;
2990 while ($i < $nbofchars) {
2991 $char = dol_substr($string, $i, 1);
2992 if ($char == '(') {
2993 $countparenthesis++;
2994 } elseif ($char == ')') {
2995 $countparenthesis--;
2996 if ($countparenthesis <= 0) { // We reach the end of an independent group of parenthesis
2997 $g++;
2998 }
2999 }
3000 $i++;
3001 }
3002
3003 if ($g <= 1) {
3004 return preg_replace('/^\‍(/', '', preg_replace('/\‍)$/', '', $string));
3005 }
3006
3007 return $string;
3008}
3009
3010
3018{
3019 $arrayofcommonemoji = array(
3020 'misc' => array('2600', '26FF'), // Miscellaneous Symbols
3021 'ding' => array('2700', '27BF'), // Dingbats
3022 '????' => array('9989', '9989'), // Variation Selectors
3023 'vars' => array('FE00', 'FE0F'), // Variation Selectors
3024 'pict' => array('1F300', '1F5FF'), // Miscellaneous Symbols and Pictographs
3025 'emot' => array('1F600', '1F64F'), // Emoticons
3026 'tran' => array('1F680', '1F6FF'), // Transport and Map Symbols
3027 'flag' => array('1F1E0', '1F1FF'), // Flags (note: may be 1F1E6 instead of 1F1E0)
3028 'supp' => array('1F900', '1F9FF'), // Supplemental Symbols and Pictographs
3029 );
3030
3031 return $arrayofcommonemoji;
3032}
3033
3041function removeEmoji($text, $allowedemoji = 1)
3042{
3043 // $allowedemoji can be
3044 // 0=no emoji, 1=exclude the main known emojis (default), 2=keep only the main known (not implemented), 3=accept all
3045 // Note that to accept emoji in database, you must use utf8mb4, utf8mb3 is not enough.
3046
3047 if ($allowedemoji == 0) {
3048 // For a large removal:
3049 $text = preg_replace('/[\x{2600}-\x{FFFF}]/u', '', $text);
3050 $text = preg_replace('/[\x{10000}-\x{10FFFF}]/u', '', $text);
3051 }
3052
3053 // Delete emoji chars with a regex
3054 // See https://www.unicode.org/emoji/charts/full-emoji-list.html
3055 if ($allowedemoji == 1) {
3056 $arrayofcommonemoji = getArrayOfEmojiBis();
3057
3058 foreach ($arrayofcommonemoji as $key => $valarray) {
3059 $text = preg_replace('/[\x{'.$valarray[0].'}-\x{'.$valarray[1].'}]/u', '', $text);
3060 }
3061 }
3062
3063 if ($allowedemoji == 2) {
3064 // TODO Not yet implemented
3065 }
3066
3067 return $text;
3068}
3069
3070
3079function csvClean($newvalue, $charset = '', $separator = '')
3080{
3081 global $langs;
3082
3083 $addquote = 0;
3084
3085 if (empty($charset)) {
3086 $charset = getDolGlobalString('EXPORT_CSV_FORCE_CHARSET');
3087 }
3088 if (empty($separator)) {
3089 $separator = getDolGlobalString('EXPORT_CSV_SEPARATOR_TO_USE');
3090 }
3091
3092
3093 $newvalue = $langs->convToOutputCharset((string) $newvalue, 'UTF-8', $charset); // newvalue is now encoded into $charset
3094
3095
3096 // Rule Dolibarr: No HTML
3097 //print $charset.' '.$newvalue."\n";
3098 //$newvalue=dol_string_nohtmltag($newvalue,0,$charset);
3099 $newvalue = dol_htmlcleanlastbr($newvalue);
3100 //print $charset.' '.$newvalue."\n";
3101
3102 // Rule 1 CSV: No CR, LF in cells (except if USE_STRICT_CSV_RULES is 1, we can keep record as it is but we must add quotes)
3103 $oldvalue = $newvalue;
3104 $newvalue = str_replace("\r", '', $newvalue);
3105 $newvalue = str_replace("\n", '\n', $newvalue);
3106 if (getDolGlobalString('USE_STRICT_CSV_RULES') && $oldvalue != $newvalue) {
3107 // If we must use enclusure on text with CR/LF)
3108 if (getDolGlobalInt('USE_STRICT_CSV_RULES') == 1) {
3109 // If we use strict CSV rules (original value must remain but we add quote)
3110 $newvalue = $oldvalue;
3111 }
3112 $addquote = 1;
3113 }
3114
3115 // Rule 2 CSV: If value contains ", we must escape with ", and add "
3116 if (preg_match('/"/', $newvalue)) {
3117 $addquote = 1;
3118 $newvalue = str_replace('"', '""', $newvalue);
3119 }
3120
3121 // Rule 3 CSV: If value contains separator, we must add "
3122 if (preg_match('/'.$separator.'/', $newvalue)) {
3123 $addquote = 1;
3124 }
3125
3126 // Rule 4 CSV: If value is a string started with char that trigger formula in calc software, we must add "
3127 if (preg_match('/^\s*[=+-@]/', $newvalue)) {
3128 $addquote = 1;
3129 }
3130
3131 return ($addquote ? '"' : '').$newvalue.($addquote ? '"' : '');
3132}
3133
3134
3144function printCodeForPing($constanttosavelastko, $constanttosavefirstok, $arrayofdata = array(), $forceping = 0)
3145{
3146 global $dolibarr_distrib;
3147 global $db, $conf;
3148
3149 require_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/blockedlog.lib.php';
3150
3151 $algo = 'sha256';
3152 $hash_unique_id = getHashUniqueIdOfRegistration($algo);
3153
3154 // Disable ping if $constanttosavelastpingko is set and is recent (this month)
3155 if (getDolGlobalString($constanttosavelastko) && substr(getDolGlobalString($constanttosavelastko), 0, 6) == dol_print_date(dol_now(), '%Y%m') && !$forceping) {
3156 print "\n";
3157 print '<!-- printCodeForPing: NO JS CODE TO ENABLE the call for '.$constanttosavefirstok.'. An error already occurred this month ('.$constanttosavelastko.' is set), we will re-try next month. -->'."\n";
3158 } else {
3159 print "\n";
3160 print '<!-- printCodeForPing: Includes JS to make ajax call for '.$constanttosavefirstok.'. forceping='.$forceping.' '.$constanttosavefirstok.'='.getDolGlobalString($constanttosavefirstok).' '.$constanttosavelastko.'='.getDolGlobalString($constanttosavelastko).' -->'."\n";
3161 print "<!-- JS CODE TO ENABLE the call -->\n";
3162 $url_for_ping = getDolGlobalString('MAIN_URL_FOR_PING', "https://ping.dolibarr.org/");
3163 // Try to guess the distrib used
3164 $distrib = 'standard';
3165 if (isset($_SERVER["SERVER_ADMIN"]) && $_SERVER["SERVER_ADMIN"] == 'doliwamp@localhost') {
3166 $distrib = 'doliwamp';
3167 }
3168 if (!empty($dolibarr_distrib)) {
3169 $distrib = $dolibarr_distrib;
3170 }
3171 ?>
3172 <script>
3173 jQuery(document).ready(function (tmp) {
3174 console.log("Try remote call with hash_unique_id is dol_hash('dolibarr'+instance_unique_id, 'sha256')");
3175 $.ajax({
3176 method: "POST",
3177 url: "<?php echo $url_for_ping ?>",
3178 timeout: 500, // timeout milliseconds
3179 cache: false,
3180 data: {
3181 <?php
3182 foreach ($arrayofdata as $datakey => $dataval) {
3183 print $datakey.": '".dol_escape_js($dataval)."',\n";
3184 }
3185 ?>
3186 hash_algo: 'dol_hash-<?php echo $algo; ?>',
3187 hash_unique_id: <?php echo "'".dol_escape_js($hash_unique_id)."'"; ?>,
3188 version: '<?php echo (float) DOL_VERSION; ?>',
3189 version_full: '<?php echo DOL_VERSION; ?>',
3190 versionblockedlog: '<?php echo (float) getBlockedLogVersionToShow(); ?>',
3191 versionblockedlog_full: '<?php echo getBlockedLogVersionToShow(); ?>',
3192 instance_entity: '<?php echo (int) $conf->entity; ?>',
3193 dbtype: <?php echo "'".dol_escape_js($db->type)."'"; ?>,
3194 php_version: <?php echo "'".dol_escape_js(phpversion())."'"; ?>,
3195 os_version: <?php echo "'".dol_escape_js(version_os('smr'))."'"; ?>,
3196 db_version: <?php echo "'".dol_escape_js(version_db())."'"; ?>,
3197 distrib: <?php echo "'".dol_escape_js($distrib)."'"; ?>,
3198 token: 'notrequired'
3199 },
3200 success: function (data, status, xhr) { // success callback function (data contains body of response)
3201 console.log("Ping ok - we call pingresult to save this");
3202 $.ajax({
3203 method: 'GET',
3204 url: '<?php echo DOL_URL_ROOT.'/core/ajax/pingresult.php'; ?>',
3205 timeout: 500, // timeout milliseconds
3206 cache: false,
3207 data: { action: '<?php echo $constanttosavefirstok ?>', token: <?php echo "'".currentToken()."'"; ?> }, // for update
3208 });
3209 },
3210 error: function (data,status,xhr) { // error callback function
3211 console.log("Ping ko - we call pingresult to save this: " + data);
3212 $.ajax({
3213 method: 'GET',
3214 url: '<?php echo DOL_URL_ROOT.'/core/ajax/pingresult.php'; ?>',
3215 timeout: 500, // timeout milliseconds
3216 cache: false,
3217 data: { action: '<?php echo $constanttosavelastko ?>', token: <?php echo "'".currentToken()."'"; ?> },
3218 });
3219 }
3220 });
3221 });
3222 </script>
3223 <?php
3224 }
3225}
3226
3227
3237function validateZipFile($zip, $originalfilename, $zipfile, $langs)
3238{
3239 global $count, $results;
3240
3241 $error = 0;
3242 $return = array(
3243 'error' => 0,
3244 'errormsg' => '', // Concatenating to this, so must be string
3245 'upload' => 0
3246 );
3247
3248 dol_syslog("Validate zip file " . $originalfilename);
3249 $subdir = basename($zipfile);
3250 //$dir='/home/dolibarr/dolistore.com/tmp/'.$subdir;
3251 $dir = sys_get_temp_dir().'/unzip-dir-'.$subdir;
3252 mkdir($dir, 0777, true);
3253 $zip->extractTo($dir.'/');
3254 $zip->close();
3255
3256 // Zip content of a module should be ./mymodule or ./htdocs/mymodule
3257
3258 // But we first we check if we need to change dir (for zip that are ./module/htdocs/module instead of ./htdocs/module)
3259 if ($dh = opendir($dir)) {
3260 $nbofsubdirs = 0;
3261 while (($file = readdir($dh)) !== false) {
3262 if (in_array($file, array('.', '..'))) {
3263 continue;
3264 }
3265 dol_syslog("We check if dir ".$dir.'/'.$file.'/htdocs exists');
3266 if (is_dir($dir.'/'.$file.'/htdocs')) {
3267 dol_syslog('Dir '.$dir.'/'.$file.'/htdocs exists. So we use dir='.$dir.'/'.$file.' as root for package to analyse.');
3268 $dir = $dir.'/'.$file;
3269 break;
3270 }
3271 }
3272 closedir($dh);
3273 }
3274
3275 // Now $dir contains root of zip, so mymodule or htdocs/mymodule
3276 // Analyze files
3277 $ismodule = $istheme = 0;
3278
3279 $reg = array();
3280 if (preg_match('/^module([a-zA-Z0-9]*)_([-a-zA-Z0-9]+)\-([0-9][0-9\.]*)\.zip$/i', $originalfilename, $reg)) {
3281 $ismodule = $reg[2];
3282 $extmoduleornot = $reg[1];
3283 if ($extmoduleornot) {
3284 $ismodule = 0;
3285 }
3286 }
3287 if (preg_match('/^theme_([-a-zA-Z0-9]+)\-([0-9][0-9\.]*)\.zip$/i', $originalfilename, $reg)) {
3288 $istheme = $reg[1];
3289 }
3290
3291 dol_syslog("Now dir is the directory with root of the zip = ".$dir, LOG_DEBUG);
3292
3293 $dirmoduletheme = $dir.'/'.($ismodule ? $ismodule : ($istheme ? $istheme : ''));
3294 if (is_dir($dir.'/htdocs')) {
3295 $dirmoduletheme = $dir.'/htdocs/'.($ismodule ? $ismodule : ($istheme ? $istheme : ''));
3296 }
3297 $dirmodulethemeroot = dirname($dirmoduletheme);
3298 dol_syslog("Now dirmodulethemeroot = dir where is the module dir = ".$dirmodulethemeroot." and dirmoduletheme = dir with name of the module = ".$dirmoduletheme, LOG_DEBUG);
3299
3300 if (! empty($ismodule) || ! empty($istheme)) {
3301 dol_syslog("file ismodule=".$ismodule." istheme=".$istheme);
3302 // It's a module or theme file
3303 if ((! empty($ismodule) || ! empty($istheme)) && $dh = opendir($dir)) {
3304 $nbofsubdirs = 0;
3305 $direrror = '';
3306 while (($file = readdir($dh)) !== false) {
3307 if (in_array($file, array('.', '..', 'README', 'README.txt', 'README.md'))) {
3308 continue;
3309 }
3310 dol_syslog("subdirs found for package:".$file);
3311 $nbofsubdirs++;
3312 $alloweddirs = array('htdocs', 'docs', 'scripts', 'test', 'build', ($ismodule ? $ismodule : ($istheme ? $istheme : '')));
3313 if (! in_array($file, $alloweddirs)) {
3314 $error++;
3315 $direrror = $file;
3316 break;
3317 }
3318 }
3319 if ($error) {
3320 $return['errormsg'] .= $langs->trans("UnvalidZipFile") . '<br>';
3321 $return['errormsg'] .= $langs->trans("moduleContents") . '<br>';
3322 $return['errormsg'] .= $langs->trans("moduleDirectoryRule1") . '<br>';
3323 $return['errormsg'] .= $langs->trans("moduleDirectoryRule2") . '<br>';
3324 $return['errormsg'] .= $langs->trans("directoryFound").' '.$direrror.'<br><br>'."\n";
3325 }
3326 closedir($dh);
3327 }
3328
3329 // It's a module or theme file (check htdocs directory)
3330 if (! $error && ! empty($ismodule) && is_dir($dirmodulethemeroot) && $dh = opendir($dirmodulethemeroot)) {
3331 dol_syslog("Scanning module dir ".$dirmodulethemeroot." to ensure there is only one directory (with name of the module) in the root path");
3332 $nbofsubdir = 0;
3333 $lastdirfound = '';
3334
3335 while (($file = readdir($dh)) !== false) {
3336 if (in_array($file, array('.', '..', 'README', 'README.txt', 'README.md'))) {
3337 continue;
3338 }
3339 $lastdirfound = $file;
3340 dol_syslog("Found file or dir ".$file);
3341 $nbofsubdir++;
3342 }
3343 closedir($dh);
3344 if ($nbofsubdir >= 2 && ! is_file($dirmoduletheme.'/metapackage.conf')) {
3345 $return['errormsg'] .= $langs->trans("rootDirWarning", $nbofsubdir, basename($dirmoduletheme)) . '<br><br>';
3346 $error++;
3347 }
3348
3349 if ($ismodule != $lastdirfound) {
3350 if (is_file($dirmoduletheme.'/metapackage.conf')) {
3351 // Check each dir found is inside list of modules
3352 } else {
3353 $return['errormsg'] .= $langs->trans("moduleNameMismatch", $lastdirfound, $ismodule) .'<br><br>';
3354 $error++;
3355 }
3356 }
3357 }
3358 // Check "custom" compatibility
3359 if (! $error && ! empty($ismodule)) {
3360 dol_syslog("check the good practice of code");
3361 $count = 0;
3362 $results = array();
3363
3364 $search = array(
3365 0 => array(
3366 'name' => 'main',
3367 'types' => array('php'),
3368 'pattern' => '(require|include).*(main|master)\.inc\.php',
3369 'multiple' => true // Means we must find 0 or several times the pattern. Error if found 1 occurrence.
3370 ),
3371 1 => array(
3372 'name' => 'dol_document_root',
3373 'types' => array('php', 'class.php', 'lib.php', 'modules.php'),
3374 'pattern' => '(require|include)(_once)?\‍(?(.*)[\"\']+\‍)?;',
3375 'id' => 3, // eg. Use $regs[3] for test instead $regs[1]
3376 'contain' => array('DOL_DOCUMENT_ROOT'),
3377 'notcontain' => array($ismodule),
3378 'strict' => true // if true ('contain' && 'notcontain'), if false or not use ('contain' || 'notcontain')
3379 )
3380 );
3381
3382 analyzeDirContents($dir, $search, $results, $count); // This include a global $count
3383
3384 dol_syslog("count of errors = ".$count);
3385
3386 if (!empty($count)) { // count of errors is not null
3387 foreach ($results as $result) {
3388 if ($result['testname'] == 'main') {
3389 $return['errormsg'] .= $langs->trans("mainIncludeError", $result['filename'], $ismodule) .'<br><br>';
3390 } elseif ($result['testname'] == 'dol_include_once') {
3391 $return['errormsg'] .= $langs->trans("dolIncludeError", $result['filename'], $ismodule) .'<br>';
3392 $return['errormsg'] .= $result['line'].'<br><br>';
3393 } elseif ($result['testname'] == 'dol_document_root') {
3394 $return['errormsg'] .= $langs->trans("docRootError", $result['filename'], $ismodule) .'<br>';
3395 $return['errormsg'] .= $result['line'].'<br><br>';
3396 }
3397 }
3398
3399 dol_syslog("file ".$originalfilename." is not compatible with custom directory!", LOG_ERR);
3400
3401 $error++;
3402 }
3403 }
3404 } else {
3405 dol_syslog("file ".$originalfilename." is not a module and not a theme!", LOG_WARNING); // It can be a doc, android app, ...
3406 }
3407
3408 if (!empty($error)) {
3409 dol_syslog("validateZipFile Error");
3410
3411 $link = '<a target="_blank" class="linktowiki" href="https://wiki.dolibarr.org/index.php/Modules - Packaging rules and Dolistore validation rules">Dolibarr wiki developer documentation</a>';
3412 $return['errormsg'] .= $langs->trans("UnvalidZipFile") .'<br>';
3413 $return['errormsg'] .= $langs->trans("SeeDocumentation", $link).'<br>';
3414 $return['errormsg'] .= "<br>\n";
3415 $return['errormsg'] .= $langs->trans("Contact");
3416 $return['upload'] = -1;
3417 $error++;
3418 } else {
3419 dol_syslog("validateZipFile OK");
3420 }
3421
3422 if ($return['errormsg'] === '') {
3423 $return['errormsg'] = null;
3424 }
3425
3426 $return['error'] = $error;
3427
3428 return $return;
3429}
3430
3431
3442function analyzeDirContents($dir, $search = array(), &$results = array(), &$count = 0)
3443{
3444 $files = scandir($dir);
3445
3446 foreach ($files as $key => $value) {
3447 $path = realpath($dir . DIRECTORY_SEPARATOR . $value);
3448 if (!is_dir($path)) {
3449 $content = file_get_contents($path);
3450
3451 // Clean the content of file to make analysis easier and avoid some false positive
3452 $content = preg_replace('/^\s*\/\/.*$/m', '', $content);
3453
3454 $fileName = basename($path);
3455 $fileNameWithoutTmp = preg_replace('/\/tmp\/unzip[^\/]+\//', '', $path);
3456
3457 $file_array = explode(".", $fileName);
3458
3459 foreach ($search as $pattern) {
3460 if (count($file_array) > 1) {
3461 $ext = $file_array[1];
3462 if (!empty($file_array[2])) {
3463 $ext = $file_array[1] . '.' . $file_array[2];
3464 }
3465
3466 if (in_array($ext, $pattern['types'])) {
3467 if (strpos($content, '<?php') !== 0) {
3468 continue;
3469 } // We discard files that are not php pages (we discard scripts)
3470 if (strpos($path, 'htdocs_') > 0) {
3471 continue;
3472 } // We discard files that are files into htdocs_... because it is files for core, so no need to be compatible with custom
3473 if (strpos($path, 'phpunit') > 0) {
3474 continue;
3475 } // We discard files that are files into phpunit because it is files for core, so no need to be compatible with custom
3476
3477 $regs = array();
3478 if (!empty($pattern['multiple'])) {
3479 preg_match_all('/' . $pattern['pattern'] . '/', $content, $regs);
3480 // Error if only one result (0 is ok, >1 is ok)
3481 if (!empty($regs) && count($regs[0]) == 1) {
3482 $results[] = array(
3483 'testname' => $pattern['name'],
3484 'filename' => $fileNameWithoutTmp
3485 );
3486 $count++;
3487 }
3488 } else {
3489 $id = (!empty($pattern['id']) ? $pattern['id'] : 1);
3490 preg_match_all('/' . $pattern['pattern'] . '/', $content, $regs);
3491
3492 if (!empty($regs) && !empty($regs[$id])) {
3493 foreach ($regs[$id] as $i => $string) {
3494 if (!empty($pattern['contain']) && is_array($pattern['contain'])) {
3495 foreach ($pattern['contain'] as $contain) {
3496 // Strict mode true: must contain && must not contain
3497 if (!empty($pattern['notcontain']) && !empty($pattern['strict']) && is_array($pattern['notcontain'])) {
3498 foreach ($pattern['notcontain'] as $notcontain) {
3499 if (strstr($string, $contain) && strstr($string, $notcontain)) {
3500 $results[] = array(
3501 'testname' => $pattern['name'],
3502 'filename' => $fileName,
3503 'line' => $regs[0][$i]
3504 );
3505 $count++;
3506 }
3507 }
3508 } elseif (!strstr($string, $contain)) { // If found
3509 // Mode strict false : must contains. Note strstr return false if not found
3510
3511 // We found $contain into $string
3512 $results[] = array(
3513 'testname' => $pattern['name'],
3514 'filename' => $fileName,
3515 'line' => $regs[0][$i]
3516 );
3517 $count++;
3518 }
3519 }
3520 }
3521 // Or must not contain
3522 if (empty($count) && !empty($pattern['notcontain']) && empty($pattern['strict']) && is_array($pattern['notcontain'])) {
3523 foreach ($pattern['notcontain'] as $notcontain) {
3524 if (strstr($string, $notcontain)) {
3525 $results[] = array(
3526 'testname' => $pattern['name'],
3527 'filename' => $fileName,
3528 'line' => $regs[0][$i]
3529 );
3530 $count++;
3531 }
3532 }
3533 }
3534 }
3535 }
3536 }
3537 }
3538 }
3539 }
3540 } elseif ($value != "." && $value != "..") {
3541 analyzeDirContents($path, $search, $results, $count);
3542 }
3543 }
3544}
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:47
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
global $dolibarr_main_url_root
getHashUniqueIdOfRegistration($algo='sha256')
Return a hash unique identifier of the registration (used to identify the registration of instance wi...
Class to manage Dolibarr users.
print $langs trans("Ref").' m titre as m m statut as status
Or an array listing all the potential status of the object: array: int of the status => translated la...
Definition index.php:169
if(! $sortfield) if(! $sortorder) $module
Definition list.php:193
getServerTimeZoneInt($refgmtdate='now')
Return server timezone int.
Definition date.lib.php:87
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
dol_dir_list($utf8_path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition files.lib.php:65
array2table($data, $tableMarkup=1, $tableoptions='', $troptions='', $tdoptions='')
Return an html table from an array.
dol_buildlogin($lastname, $firstname)
Build a login from lastname, firstname.
if(!function_exists( 'dolEscapeXML')) convertBackOfficeMediasLinksToPublicLinks($notetoshow)
Convert links to local wrapper to medias files into a string into a public external URL readable on i...
getArrayOfEmojiBis()
Return array of Emojis for miscellaneous use.
colorHexToRgb($hex, $alpha=false, $returnArray=false)
get_string_between($string, $start, $end)
Get string from "$start" up to "$end".
dolObfuscateEmail($mail, $replace="*", $nbreplace=8, $nbdisplaymail=4, $nbdisplaydomain=3, $displaytld=true)
Returns an email value with obfuscated parts.
version_webserver()
Return web server version.
getModuleDirForApiClass($moduleobject)
Get name of directory where the api_...class.php file is stored.
phpSyntaxError($code)
Check the syntax of some PHP code.
dolGetModulesDirs($subdir='')
Return list of directories that contain modules.
array2tr($data, $troptions='', $tdoptions='')
Return lines of an html table from an array Used by array2table function only.
version_dolibarr()
Return Dolibarr version.
binhex($bin, $pad=false, $upper=false)
Convert a binary data to string that represent hexadecimal value.
colorAgressiveness($hex, $ratio=-50, $brightness=0)
Change color to make it less aggressive (ratio is negative) or more aggressive (ratio is positive)
removeGlobalParenthesis($string)
Remove first and last parenthesis but only if first is the opening and last the closing of the same g...
cartesianArray(array $input)
Applies the Cartesian product algorithm to an array Source: http://stackoverflow.com/a/15973172.
getSoapParams()
Return array to use for SoapClient constructor.
analyzeDirContents($dir, $search=array(), &$results=array(), &$count=0)
Analyze files of a directory and subdirectories.
colorAdjustBrightness($hex, $steps)
cleanCorruptedTree($db, $tabletocleantree, $fieldfkparent)
Clean corrupted database tree (orphelins linked to a not existing parent), record linked to themself,...
colorArrayToHex($arraycolor, $colorifnotfound='888888')
Convert an array with RGB value into hex RGB value.
dol_print_reduction($reduction, $langs)
Returns formatted reduction.
colorStringToArray($stringcolor, $colorifnotfound=array(88, 88, 88))
Convert a string RGB value ('FFFFFF', '255,255,255') into an array RGB array(255,255,...
acceptLocalLinktoMedia()
Check the syntax of some PHP code.
randomColor($min=0, $max=255)
Return hexadecimal color randomly.
is_ip($ip)
This function evaluates a string that should be a valid IPv4 Note: For ip 169.254....
check_value($mask, $value)
Check value.
validateZipFile($zip, $originalfilename, $zipfile, $langs)
Check that a zip file is Dolibarr rule compliant.
weight_convert($weight, &$from_unit, $to_unit)
Convertit une masse d'une unite vers une autre unite.
getListOfModels($db, $type, $maxfilenamelength=0, $showempty=0)
Return list of activated modules usable for document generation.
clean_url($url, $http=1)
Clean an url string.
version_php()
Return PHP version.
colorHexToHsl($hex, $alpha=false, $returnArray=false)
Color Hex to Hsl (used for style)
dol_getDefaultFormat($outputlangs=null)
Try to guess default paper format according to language into $langs.
printCodeForPing($constanttosavelastko, $constanttosavefirstok, $arrayofdata=array(), $forceping=0)
Function to output HTML to make an ajax call to make registration.
hexbin($hexa)
Convert an hexadecimal string into a binary string.
numero_semaine($time)
Return the week number for a given date.
randomColorPart($min=0, $max=255)
Return 2 hexa code randomly.
colorDarker($hex, $percent)
dolGetElementUrl($objectid, $objecttype, $withpicto=0, $option='')
Return link url to an object.
dol_set_user_param($db, $conf, &$user, $tab, $entity=-1)
Save personal parameter.
dol_print_object_info($object, $usetable=0)
Show information on an object TODO Move this into html.formother.
isValidUrl($url, $http=0, $pass=0, $port=0, $path=0, $query=0, $anchor=0)
Url string validation <http[s]> :// [user[:pass]@] hostname [port] [/path] [?getquery] [anchor].
version_db()
Return DB version.
isValidMailDomain($mail)
Return true if email has a domain name that can be resolved to MX type.
dolAddEmailTrackId($email, $trackingid)
Return an email formatted to include a tracking id For example myemail@example.com becom myemail+trac...
colorLighten($hex, $percent)
isValidVATID($company)
Check if VAT numero is valid (check done on syntax only, no database or remote access)
version_os($option='')
Return OS version.
price2fec($amount)
Function to format a value into a defined format for French administration (no thousand separator & d...
get_next_value($db, $mask, $table, $field, $where='', $objsoc='', $date='', $mode='next', $bentityon=true, $objuser=null, $forceentity=null, $objbookkeeping=null)
Return last or next value for a mask (according to area we should not reset)
csvClean($newvalue, $charset='', $separator='')
Clean a cell to respect rules of CSV file cells.
removeEmoji($text, $allowedemoji=1)
Remove EMoji from email content.
jsUnEscape($source)
Same function than javascript unescape() function but in PHP.
colorValidateHex($color, $allow_white=true)
dol_html_entity_decode($a, $b, $c='UTF-8', $keepsomeentities=0)
Replace html_entity_decode functions to manage errors.
dol_now($mode='gmt')
Return date for now.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
vatrate($rate, $addpercent=false, $info_bits=0, $usestarfornpr=0, $html=0)
Return a string with VAT rate label formatted for view output Used into pdf and HTML pages.
dol_string_nospecial($str, $newstr='_', $badcharstoreplace='', $badcharstoremove='', $keepspaces=0)
Clean a string from all punctuation characters to use it as a ref or login.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
isValidMXRecord($domain)
Return if the domain name has a valid MX record.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
dol_escape_js($stringtoescape, $mode=0, $noescapebackslashn=0)
Returns text escaped for inclusion into JavaScript code.
if(!function_exists( 'dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
dol_string_unaccent($str)
Clean a string from all accent characters to be used as ref, login or by dol_sanitizeFileName.
dol_htmlcleanlastbr($stringtodecode)
This function remove all ending and br at end.
dol_substr($string, $start, $length=null, $stringencoding='', $trunconbytes=0)
Make a substring.
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '...' if string larger than length.
isValidEmail($address, $acceptsupervisorkey=0, $acceptuserkey=0)
Return true if email syntax is ok.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
isModEnabled($module)
Is Dolibarr module enabled.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
$conf browser version
Definition main.inc.php:229
print $langs trans('Date')." left Ref Label right Qty right Price right TotalHT right TotalTTC right right right right right right right right right centpercent right TotalHT right n right VAT right n right TotalVAT right n No sujeto a RE IRPF right TotalLT1 right n right TotalLT2 right n right TotalTTC right n takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency takeposcustomercurrency right TotalTTC takeposcustomercurrency right takeposcustomercurrency n right Paid right PaymentTypeShortLIQ right SELECT p pos_change as p datep as date
Definition receipt.php:492