Test Setup Failed
Push — dev ( 608138...99eb65 )
by Rafael
61:41 queued 16s
created

removeEmoji()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 27
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

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

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

2896
    /** @scrutinizer ignore-unhandled */ @ini_set('display_errors', $token);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
2897
    @ini_set('log_errors', $inString);
2898
    return $code;
2899
}
2900
2901
2902
/**
2903
 * Check the syntax of some PHP code.
2904
 *
2905
 * @return  int     >0 if OK, 0 if no           Return if we accept link added from the media browser into HTML field for public usage
2906
 */
2907
function acceptLocalLinktoMedia()
2908
{
2909
    global $user;
2910
2911
    // If $acceptlocallinktomedia is true, we can add link media files int email templates (we already can do this into HTML editor of an email).
2912
    // 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:
2913
    // $urlwithouturlroot = preg_replace('/'.preg_quote(DOL_URL_ROOT, '/').'$/i', '', trim($dolibarr_main_url_root));
2914
    // $urlwithroot = $urlwithouturlroot.DOL_URL_ROOT; // This is to use external domain name found into config file
2915
    $acceptlocallinktomedia = getDolGlobalInt('MAIN_DISALLOW_MEDIAS_IN_EMAIL_TEMPLATES') ? 0 : 1;
2916
    if ($acceptlocallinktomedia) {
2917
        global $dolibarr_main_url_root;
2918
        $urlwithouturlroot = preg_replace('/' . preg_quote(DOL_URL_ROOT, '/') . '$/i', '', trim($dolibarr_main_url_root));
2919
2920
        // Parse $newUrl
2921
        $newUrlArray = parse_url($urlwithouturlroot);
2922
        $hosttocheck = $newUrlArray['host'];
2923
        $hosttocheck = str_replace(array('[', ']'), '', $hosttocheck); // Remove brackets of IPv6
2924
2925
        if (function_exists('gethostbyname')) {
2926
            $iptocheck = gethostbyname($hosttocheck);
2927
        } else {
2928
            $iptocheck = $hosttocheck;
2929
        }
2930
2931
        //var_dump($iptocheck.' '.$acceptlocallinktomedia);
2932
        $allowParamName = 'MAIN_ALLOW_WYSIWYG_LOCAL_MEDIAS_ON_PRIVATE_NETWORK';
2933
        $allowPrivateNetworkIP = getDolGlobalInt($allowParamName);
2934
        if (!$allowPrivateNetworkIP && !filter_var($iptocheck, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
2935
            // If ip of public url is a private network IP, we do not allow this.
2936
            $acceptlocallinktomedia = 0;
2937
            //dol_syslog("WYSIWYG Editor : local media not allowed (checked IP: {$iptocheck}). Use {$allowParamName} = 1 to allow local URL into WYSIWYG html content");
2938
        }
2939
2940
        if (preg_match('/http:/i', $urlwithouturlroot)) {
2941
            // 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.
2942
            $acceptlocallinktomedia = 0;
2943
            // TODO Show a warning
2944
        }
2945
2946
        if (!empty($user->socid)) {
2947
            $acceptlocallinktomedia = 0;
2948
        }
2949
    }
2950
2951
    //return 1;
2952
    return $acceptlocallinktomedia;
2953
}
2954
2955
2956
/**
2957
 * Remove first and last parenthesis but only if first is the opening and last the closing of the same group
2958
 *
2959
 * @param string $string String to sanitize
2960
 * @return  string              String without global parenthesis
2961
 */
2962
function removeGlobalParenthesis($string)
2963
{
2964
    $string = trim($string);
2965
2966
    // If string does not start and end with parenthesis, we return $string as is.
2967
    if (!preg_match('/^\(.*\)$/', $string)) {
2968
        return $string;
2969
    }
2970
2971
    $nbofchars = dol_strlen($string);
2972
    $i = 0;
2973
    $g = 0;
2974
    $countparenthesis = 0;
2975
    while ($i < $nbofchars) {
2976
        $char = dol_substr($string, $i, 1);
2977
        if ($char == '(') {
2978
            $countparenthesis++;
2979
        } elseif ($char == ')') {
2980
            $countparenthesis--;
2981
            if ($countparenthesis <= 0) {   // We reach the end of an independent group of parenthesis
2982
                $g++;
2983
            }
2984
        }
2985
        $i++;
2986
    }
2987
2988
    if ($g <= 1) {
2989
        return preg_replace('/^\(/', '', preg_replace('/\)$/', '', $string));
2990
    }
2991
2992
    return $string;
2993
}
2994
2995