dolGetElementUrl()   F
last analyzed

Complexity

Conditions 32
Paths 2142

Size

Total Lines 149
Code Lines 126

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 32
eloc 126
nc 2142
nop 4
dl 0
loc 149
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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