natural_search()   F
last analyzed

Complexity

Conditions 58
Paths > 20000

Size

Total Lines 166
Code Lines 118

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 58
eloc 118
nc 46960
nop 4
dl 0
loc 166
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) 2000-2007  Rodolphe Quiedeville        <[email protected]>
4
 * Copyright (C) 2003		Jean-Louis Bergamo			<[email protected]>
5
 * Copyright (C) 2004-2022	Laurent Destailleur			<[email protected]>
6
 * Copyright (C) 2004		Sebastien Di Cintio			<[email protected]>
7
 * Copyright (C) 2004		Benoit Mortier				<[email protected]>
8
 * Copyright (C) 2004		Christophe Combelles		<[email protected]>
9
 * Copyright (C) 2005-2019	Regis Houssin				<[email protected]>
10
 * Copyright (C) 2008		Raphael Bertrand (Resultic)	<[email protected]>
11
 * Copyright (C) 2010-2018	Juanjo Menent				<[email protected]>
12
 * Copyright (C) 2013		Cédric Salvador				<[email protected]>
13
 * Copyright (C) 2013-2021	Alexandre Spangaro			<[email protected]>
14
 * Copyright (C) 2014		Cédric GROSS				<[email protected]>
15
 * Copyright (C) 2014-2015	Marcos García				<[email protected]>
16
 * Copyright (C) 2015		Jean-François Ferry			<[email protected]>
17
 * Copyright (C) 2018-2024  Frédéric France             <[email protected]>
18
 * Copyright (C) 2019-2023  Thibault Foucart            <[email protected]>
19
 * Copyright (C) 2020       Open-Dsi         			<[email protected]>
20
 * Copyright (C) 2021       Gauthier VERDOL         	<[email protected]>
21
 * Copyright (C) 2022       Anthony Berton	         	<[email protected]>
22
 * Copyright (C) 2022       Ferran Marcet           	<[email protected]>
23
 * Copyright (C) 2022       Charlene Benke           	<[email protected]>
24
 * Copyright (C) 2023       Joachim Kueter              <[email protected]>
25
 * Copyright (C) 2024		MDW							<[email protected]>
26
 * Copyright (C) 2024		Lenin Rivas					<[email protected]>
27
 * Copyright (C) 2024       Rafael San José             <[email protected]>
28
 *
29
 * This program is free software; you can redistribute it and/or modify
30
 * it under the terms of the GNU General Public License as published by
31
 * the Free Software Foundation; either version 3 of the License, or
32
 * (at your option) any later version.
33
 *
34
 * This program is distributed in the hope that it will be useful,
35
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
36
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
37
 * GNU General Public License for more details.
38
 *
39
 * You should have received a copy of the GNU General Public License
40
 * along with this program. If not, see <https://www.gnu.org/licenses/>.
41
 * or see https://www.gnu.org/
42
 */
43
44
use Dolibarr\Code\Core\Classes\DolGeoIP;
45
use Dolibarr\Code\Core\Classes\HookManager;
46
use Dolibarr\Code\Core\Classes\Translate;
47
use Dolibarr\Code\Fourn\Classes\ProductFournisseur;
48
use Dolibarr\Code\Product\Classes\Product;
49
use Dolibarr\Code\Societe\Classes\Societe;
50
use Dolibarr\Code\User\Classes\User;
51
use Dolibarr\Core\Base\CommonObject;
52
use Dolibarr\Lib\ViewMain;
53
54
/**
55
 *  \file           htdocs/core/lib/functions.lib.php
56
 *  \brief          A set of functions for Dolibarr
57
 *                  This file contains all frequently used functions.
58
 */
59
60
include_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/json.lib.php';
61
62
/**
63
 * Functions extracted from functions.lib
64
 */
65
include_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/data.lib.php';
66
include_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/html.lib.php';
67
68
/**
69
 * Return the full path of the directory where a module (or an object of a module) stores its files,
70
 * Path may depends on the entity if a multicompany module is enabled.
71
 *
72
 * @param CommonObject $object Dolibarr common object
73
 * @param string $module Override object element, for example to use 'mycompany' instead of 'societe'
74
 * @param int $forobject Return the more complete path for the given object instead of for the module only.
75
 * @param string $mode 'output' (full main dir) or 'outputrel' (relative dir) or 'temp' (for temporary files) or 'version' (dir for archived files)
76
 * @return  string|null                 The path of the relative directory of the module, ending with /
77
 * @since Dolibarr V18
78
 */
79
function getMultidirOutput($object, $module = '', $forobject = 0, $mode = 'output')
80
{
81
    global $conf;
82
83
    if (!is_object($object) && empty($module)) {
84
        return null;
85
    }
86
    if (empty($module) && !empty($object->element)) {
87
        $module = $object->element;
88
    }
89
90
    // Special case for backward compatibility
91
    if ($module == 'fichinter') {
92
        $module = 'ficheinter';
93
    } elseif ($module == 'invoice_supplier') {
94
        $module = 'supplier_invoice';
95
    } elseif ($module == 'order_supplier') {
96
        $module = 'supplier_order';
97
    }
98
99
    // Get the relative path of directory
100
    if ($mode == 'output' || $mode == 'outputrel' || $mode == 'version') {
101
        if (isset($conf->$module) && property_exists($conf->$module, 'multidir_output')) {
102
            $s = '';
103
            if ($mode != 'outputrel') {
104
                $s = $conf->$module->multidir_output[(empty($object->entity) ? $conf->entity : $object->entity)];
105
            }
106
            if ($forobject && $object->id > 0) {
107
                $s .= ($mode != 'outputrel' ? '/' : '') . get_exdir(0, 0, 0, 0, $object);
108
            }
109
            return $s;
110
        } else {
111
            return 'error-diroutput-not-defined-for-this-object=' . $module;
112
        }
113
    } elseif ($mode == 'temp') {
114
        if (isset($conf->$module) && property_exists($conf->$module, 'multidir_temp')) {
115
            return $conf->$module->multidir_temp[(empty($object->entity) ? $conf->entity : $object->entity)];
116
        } else {
117
            return 'error-dirtemp-not-defined-for-this-object=' . $module;
118
        }
119
    } else {
120
        return 'error-bad-value-for-mode';
121
    }
122
}
123
124
/**
125
 * Return the full path of the directory where a module (or an object of a module) stores its temporary files.
126
 * Path may depends on the entity if a multicompany module is enabled.
127
 *
128
 * @param CommonObject $object Dolibarr common object
129
 * @param string $module Override object element, for example to use 'mycompany' instead of 'societe'
130
 * @param int $forobject Return the more complete path for the given object instead of for the module only.
131
 * @return  string|null                 The path of the relative temp directory of the module
132
 */
133
function getMultidirTemp($object, $module = '', $forobject = 0)
134
{
135
    return getMultidirOutput($object, $module, $forobject, 'temp');
136
}
137
138
/**
139
 * Return the full path of the directory where a module (or an object of a module) stores its versioned files.
140
 * Path may depends on the entity if a multicompany module is enabled.
141
 *
142
 * @param CommonObject $object Dolibarr common object
143
 * @param string $module Override object element, for example to use 'mycompany' instead of 'societe'
144
 * @param int $forobject Return the more complete path for the given object instead of for the module only.
145
 * @return string|null                  The path of the relative version directory of the module
146
 */
147
function getMultidirVersion($object, $module = '', $forobject = 0)
148
{
149
    return getMultidirOutput($object, $module, $forobject, 'version');
150
}
151
152
153
/**
154
 * Return dolibarr global constant string value
155
 *
156
 * @param string $key key to return value, return $default if not set
157
 * @param string $default value to return
158
 * @return string
159
 * @see getDolUserString()
160
 */
161
function getDolGlobalString($key, $default = '')
162
{
163
    global $conf;
164
    return (string)(isset($conf->global->$key) ? $conf->global->$key : $default);
165
}
166
167
/**
168
 * Return a Dolibarr global constant int value.
169
 * The constants $conf->global->xxx are loaded by the script master.inc.php included at begin of any PHP page.
170
 *
171
 * @param string $key key to return value, return 0 if not set
172
 * @param int $default value to return
173
 * @return int
174
 * @see getDolUserInt()
175
 */
176
function getDolGlobalInt($key, $default = 0)
177
{
178
    global $conf;
179
    return (int)(isset($conf->global->$key) ? $conf->global->$key : $default);
180
}
181
182
/**
183
 * Return Dolibarr user constant string value
184
 *
185
 * @param string $key key to return value, return '' if not set
186
 * @param string $default value to return
187
 * @param User $tmpuser To get another user than current user
188
 * @return string
189
 */
190
function getDolUserString($key, $default = '', $tmpuser = null)
191
{
192
    if (empty($tmpuser)) {
193
        global $user;
194
        $tmpuser = $user;
195
    }
196
197
    return (string)(empty($tmpuser->conf->$key) ? $default : $tmpuser->conf->$key);
198
}
199
200
/**
201
 * Return Dolibarr user constant int value
202
 *
203
 * @param string $key key to return value, return 0 if not set
204
 * @param int $default value to return
205
 * @param User $tmpuser To get another user than current user
206
 * @return int
207
 */
208
function getDolUserInt($key, $default = 0, $tmpuser = null)
209
{
210
    if (empty($tmpuser)) {
211
        global $user;
212
        $tmpuser = $user;
213
    }
214
215
    return (int)(empty($tmpuser->conf->$key) ? $default : $tmpuser->conf->$key);
216
}
217
218
219
/**
220
 * This mapping defines the conversion to the current internal
221
 * names from the alternative allowed names (including effectively deprecated
222
 * and future new names (not yet used as internal names).
223
 *
224
 * This allows to map any temporary or future name to the effective internal name.
225
 *
226
 * The value is typically the name of module's root directory.
227
 */
228
define(
229
    'MODULE_MAPPING',
230
    array(
231
        // Map deprecated names to new names
232
        'adherent' => 'member',  // Has new directory
233
        'member_type' => 'adherent_type',   // No directory, but file called adherent_type
234
        'banque' => 'bank',   // Has new directory
235
        'contrat' => 'contract', // Has new directory
236
        'entrepot' => 'stock',   // Has new directory
237
        'projet' => 'project', // Has new directory
238
        'categorie' => 'category', // Has old directory
239
        'commande' => 'order',    // Has old directory
240
        'expedition' => 'shipping', // Has old directory
241
        'facture' => 'invoice', // Has old directory
242
        'fichinter' => 'intervention', // Has old directory
243
        'ficheinter' => 'intervention',  // Backup for 'fichinter'
244
        'propale' => 'propal', // Has old directory
245
        'socpeople' => 'contact', // Has old directory
246
        'fournisseur' => 'supplier',  // Has old directory
247
248
        'actioncomm' => 'agenda',  // NO module directory (public dir agenda)
249
        'product_price' => 'productprice', // NO directory
250
        'product_fournisseur_price' => 'productsupplierprice', // NO directory
251
    )
252
);
253
254
/**
255
 * Is Dolibarr module enabled
256
 *
257
 * @param string $module Module name to check
258
 * @return  boolean             True if module is enabled
259
 */
260
function isModEnabled($module)
261
{
262
    global $conf;
263
264
    // Fix old names (map to new names)
265
    $arrayconv = MODULE_MAPPING;
266
    $arrayconvbis = array_flip(MODULE_MAPPING);
267
268
    if (!getDolGlobalString('MAIN_USE_NEW_SUPPLIERMOD')) {
269
        // Special cases: both use the same module.
270
        $arrayconv['supplier_order'] = 'fournisseur';
271
        $arrayconv['supplier_invoice'] = 'fournisseur';
272
    }
273
    // Special case.
274
    // @TODO Replace isModEnabled('delivery_note') with
275
    // isModEnabled('shipping') && getDolGlobalString('MAIN_SUBMODULE_EXPEDITION')
276
    if ($module == 'delivery_note') {
277
        if (!getDolGlobalString('MAIN_SUBMODULE_EXPEDITION')) {
278
            return false;
279
        } else {
280
            $module = 'shipping';
281
        }
282
    }
283
284
    $module_alt = $module;
285
    if (!empty($arrayconv[$module])) {
286
        $module_alt = $arrayconv[$module];
287
    }
288
    $module_bis = $module;
289
    if (!empty($arrayconvbis[$module])) {
290
        $module_bis = $arrayconvbis[$module];
291
    }
292
293
    return !empty($conf->modules[$module]) || !empty($conf->modules[$module_alt]) || !empty($conf->modules[$module_bis]);
294
    //return !empty($conf->$module->enabled);
295
}
296
297
/**
298
 * isDolTms check if a timestamp is valid.
299
 *
300
 * @param int|string|null $timestamp timestamp to check
301
 * @return bool
302
 */
303
function isDolTms($timestamp)
304
{
305
    if ($timestamp === '') {
306
        dol_syslog('Using empty string for a timestamp is deprecated, prefer use of null when calling page ' . $_SERVER["PHP_SELF"], LOG_NOTICE);
307
        return false;
308
    }
309
    if (is_null($timestamp) || !is_numeric($timestamp)) {
310
        return false;
311
    }
312
313
    return true;
314
}
315
316
/**
317
 * Return a DoliDB instance (database handler).
318
 *
319
 * @param string $type Type of database (mysql, pgsql...)
320
 * @param string $host Address of database server
321
 * @param string $user Authorized username
322
 * @param string $pass Password
323
 * @param string $name Name of database
324
 * @param int $port Port of database server
325
 * @return  DoliDB              A DoliDB instance
326
 */
327
function getDoliDBInstance($type, $host, $user, $pass, $name, $port)
328
{
329
    require_once DOL_DOCUMENT_ROOT . "/core/db/" . $type . '.class.php';
330
331
    $class = 'DoliDB' . ucfirst($type);
332
    $db = new $class($type, $host, $user, $pass, $name, $port);
333
    return $db;
334
}
335
336
/**
337
 *  Get list of entity id to use.
338
 *
339
 * @param string $element Current element
340
 *                                  'societe', 'socpeople', 'actioncomm', 'agenda', 'resource',
341
 *                                  'product', 'productprice', 'stock', 'bom', 'mo',
342
 *                                  'propal', 'supplier_proposal', 'invoice', 'supplier_invoice', 'payment_various',
343
 *                                  'categorie', 'bank_account', 'bank_account', 'adherent', 'user',
344
 *                                  'commande', 'supplier_order', 'expedition', 'intervention', 'survey',
345
 *                                  'contract', 'tax', 'expensereport', 'holiday', 'multicurrency', 'project',
346
 *                                  'email_template', 'event', 'donation'
347
 *                                  'c_paiement', 'c_payment_term', ...
348
 * @param int<0,1> $shared 0=Return id of current entity only,
349
 *                                  1=Return id of current entity + shared entities (default)
350
 * @param  ?CommonObject $currentobject Current object if needed
351
 * @return string                  Entity id(s) to use ( eg. entity IN ('.getEntity(elementname).')' )
352
 */
353
function getEntity($element, $shared = 1, $currentobject = null)
354
{
355
    global $conf, $mc, $hookmanager, $object, $action, $db;
356
357
    if (!is_object($hookmanager)) {
358
        $hookmanager = new HookManager($db);
359
    }
360
361
    // fix different element names (France to English)
362
    switch ($element) {
363
        case 'projet':
364
            $element = 'project';
365
            break;
366
        case 'contrat':
367
            $element = 'contract';
368
            break; // "/contrat/class/contrat.class.php"
369
        case 'order_supplier':
370
            $element = 'supplier_order';
371
            break; // "/fourn/class/fournisseur.commande.class.php"
372
        case 'invoice_supplier':
373
            $element = 'supplier_invoice';
374
            break; // "/fourn/class/fournisseur.facture.class.php"
375
    }
376
377
    if (is_object($mc)) {
378
        $out = $mc->getEntity($element, $shared, $currentobject);
379
    } else {
380
        $out = '';
381
        $addzero = array('user', 'usergroup', 'cronjob', 'c_email_templates', 'email_template', 'default_values', 'overwrite_trans');
382
        if (in_array($element, $addzero)) {
383
            $out .= '0,';
384
        }
385
        $out .= ((int)$conf->entity);
386
    }
387
388
    // Manipulate entities to query on the fly
389
    $parameters = array(
390
        'element' => $element,
391
        'shared' => $shared,
392
        'object' => $object,
393
        'currentobject' => $currentobject,
394
        'out' => $out
395
    );
396
    $reshook = $hookmanager->executeHooks('hookGetEntity', $parameters, $currentobject, $action); // Note that $action and $object may have been modified by some hooks
397
398
    if (is_numeric($reshook)) {
399
        if ($reshook == 0 && !empty($hookmanager->resPrint)) {
400
            $out .= ',' . $hookmanager->resPrint; // add
401
        } elseif ($reshook == 1) {
402
            $out = $hookmanager->resPrint; // replace
403
        }
404
    }
405
406
    return $out;
407
}
408
409
/**
410
 *  Set entity id to use when to create an object
411
 *
412
 * @param CommonObject $currentobject Current object
413
 * @return int                             Entity id to use ( eg. entity = '.setEntity($object) )
414
 */
415
function setEntity($currentobject)
416
{
417
    global $conf, $mc;
418
419
    if (is_object($mc) && method_exists($mc, 'setEntity')) {
420
        return $mc->setEntity($currentobject);
421
    } else {
422
        return ((is_object($currentobject) && $currentobject->id > 0 && $currentobject->entity > 0) ? $currentobject->entity : $conf->entity);
423
    }
424
}
425
426
/**
427
 *  Return if string has a name dedicated to store a secret
428
 *
429
 * @param string $keyname Name of key to test
430
 * @return boolean             True if key is used to store a secret
431
 */
432
function isASecretKey($keyname)
433
{
434
    return preg_match('/(_pass|password|_pw|_key|securekey|serverkey|secret\d?|p12key|exportkey|_PW_[a-z]+|token)$/i', $keyname);
435
}
436
437
438
/**
439
 * Return a numeric value into an Excel like column number. So 0 return 'A', 1 returns 'B'..., 26 return 'AA'
440
 *
441
 * @param int|string $n Numeric value
442
 * @return  string                  Column in Excel format
443
 */
444
function num2Alpha($n)
445
{
446
    for ($r = ""; $n >= 0; $n = intval($n / 26) - 1) {
447
        $r = chr($n % 26 + 0x41) . $r;
448
    }
449
    return $r;
450
}
451
452
453
/**
454
 * Return information about user browser
455
 *
456
 * Returns array with the following format:
457
 * array(
458
 *  'browsername' => Browser name (firefox|chrome|iceweasel|epiphany|safari|opera|ie|unknown)
459
 *  'browserversion' => Browser version. Empty if unknown
460
 *  'browseros' => Set with mobile OS (android|blackberry|ios|palm|symbian|webos|maemo|windows|unknown)
461
 *  'layout' => (tablet|phone|classic)
462
 *  'phone' => empty if not mobile, (android|blackberry|ios|palm|unknown) if mobile
463
 *  'tablet' => true/false
464
 * )
465
 *
466
 * @param string $user_agent Content of $_SERVER["HTTP_USER_AGENT"] variable
467
 * @return  array{browsername:string,browserversion:string,browseros:string,browserua:string,layout:string,phone:string,tablet:bool} Check function documentation
468
 */
469
function getBrowserInfo($user_agent)
470
{
471
    include_once DOL_DOCUMENT_ROOT . '/includes/mobiledetect/mobiledetectlib/Mobile_Detect.php';
472
473
    $name = 'unknown';
474
    $version = '';
475
    $os = 'unknown';
476
    $phone = '';
477
478
    $user_agent = substr($user_agent, 0, 512);  // Avoid to process too large user agent
479
480
    $detectmobile = new Mobile_Detect(null, $user_agent);
481
    $tablet = $detectmobile->isTablet();
482
483
    if ($detectmobile->isMobile()) {
484
        $phone = 'unknown';
485
486
        // If phone/smartphone, we set phone os name.
487
        if ($detectmobile->is('AndroidOS')) {
488
            $os = $phone = 'android';
489
        } elseif ($detectmobile->is('BlackBerryOS')) {
490
            $os = $phone = 'blackberry';
491
        } elseif ($detectmobile->is('iOS')) {
492
            $os = 'ios';
493
            $phone = 'iphone';
494
        } elseif ($detectmobile->is('PalmOS')) {
495
            $os = $phone = 'palm';
496
        } elseif ($detectmobile->is('SymbianOS')) {
497
            $os = 'symbian';
498
        } elseif ($detectmobile->is('webOS')) {
499
            $os = 'webos';
500
        } elseif ($detectmobile->is('MaemoOS')) {
501
            $os = 'maemo';
502
        } elseif ($detectmobile->is('WindowsMobileOS') || $detectmobile->is('WindowsPhoneOS')) {
503
            $os = 'windows';
504
        }
505
    }
506
507
    // OS
508
    if (preg_match('/linux/i', $user_agent)) {
509
        $os = 'linux';
510
    } elseif (preg_match('/macintosh/i', $user_agent)) {
511
        $os = 'macintosh';
512
    } elseif (preg_match('/windows/i', $user_agent)) {
513
        $os = 'windows';
514
    }
515
516
    // Name
517
    $reg = array();
518
    if (preg_match('/firefox(\/|\s)([\d.]*)/i', $user_agent, $reg)) {
519
        $name = 'firefox';
520
        $version = empty($reg[2]) ? '' : $reg[2];
521
    } elseif (preg_match('/edge(\/|\s)([\d.]*)/i', $user_agent, $reg)) {
522
        $name = 'edge';
523
        $version = empty($reg[2]) ? '' : $reg[2];
524
    } elseif (preg_match('/chrome(\/|\s)([\d.]+)/i', $user_agent, $reg)) {
525
        $name = 'chrome';
526
        $version = empty($reg[2]) ? '' : $reg[2];
527
    } elseif (preg_match('/chrome/i', $user_agent, $reg)) {
528
        // we can have 'chrome (Mozilla...) chrome x.y' in one string
529
        $name = 'chrome';
530
    } elseif (preg_match('/iceweasel/i', $user_agent)) {
531
        $name = 'iceweasel';
532
    } elseif (preg_match('/epiphany/i', $user_agent)) {
533
        $name = 'epiphany';
534
    } elseif (preg_match('/safari(\/|\s)([\d.]*)/i', $user_agent, $reg)) {
535
        $name = 'safari';
536
        $version = empty($reg[2]) ? '' : $reg[2];
537
    } elseif (preg_match('/opera(\/|\s)([\d.]*)/i', $user_agent, $reg)) {
538
        // Safari is often present in string for mobile but its not.
539
        $name = 'opera';
540
        $version = empty($reg[2]) ? '' : $reg[2];
541
    } elseif (preg_match('/(MSIE\s([0-9]+\.[0-9]))|.*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) {
542
        $name = 'ie';
543
        $version = end($reg);
544
    } elseif (preg_match('/(Windows NT\s([0-9]+\.[0-9])).*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) {
545
        // MS products at end
546
        $name = 'ie';
547
        $version = end($reg);
548
    } elseif (preg_match('/l[iy]n(x|ks)(\(|\/|\s)*([\d.]+)/i', $user_agent, $reg)) {
549
        // MS products at end
550
        $name = 'lynxlinks';
551
        $version = empty($reg[3]) ? '' : $reg[3];
552
    }
553
554
    if ($tablet) {
555
        $layout = 'tablet';
556
    } elseif ($phone) {
557
        $layout = 'phone';
558
    } else {
559
        $layout = 'classic';
560
    }
561
562
    return array(
563
        'browsername' => $name,
564
        'browserversion' => $version,
565
        'browseros' => $os,
566
        'browserua' => $user_agent,
567
        'layout' => $layout,    // tablet, phone, classic
568
        'phone' => $phone,      // deprecated
569
        'tablet' => $tablet     // deprecated
570
    );
571
}
572
573
/**
574
 *  Function called at end of web php process
575
 *
576
 * @return void
577
 */
578
function dol_shutdown()
579
{
580
    global $db;
581
    $disconnectdone = false;
582
    $depth = 0;
583
    if (is_object($db) && !empty($db->connected)) {
584
        $depth = $db->transaction_opened;
585
        $disconnectdone = $db->close();
586
    }
587
    dol_syslog("--- End access to " . (empty($_SERVER["PHP_SELF"]) ? 'unknown' : $_SERVER["PHP_SELF"]) . (($disconnectdone && $depth) ? ' (Warn: db disconnection forced, transaction depth was ' . $depth . ')' : ''), (($disconnectdone && $depth) ? LOG_WARNING : LOG_INFO));
588
}
589
590
/**
591
 * Return true if we are in a context of submitting the parameter $paramname from a POST of a form.
592
 * Warning:
593
 * For action=add, use:     $var = GETPOST('var');      // No GETPOSTISSET, so GETPOST always called and default value is retrieved if not a form POST, and value of form is retrieved if it is a form POST.
594
 * For action=update, use:  $var = GETPOSTISSET('var') ? GETPOST('var') : $object->var;
595
 *
596
 * @param string $paramname Name or parameter to test
597
 * @return  boolean                 True if we have just submit a POST or GET request with the parameter provided (even if param is empty)
598
 */
599
function GETPOSTISSET($paramname)
600
{
601
    $isset = false;
602
603
    $relativepathstring = $_SERVER["PHP_SELF"];
604
    // Clean $relativepathstring
605
    if (constant('DOL_URL_ROOT')) {
606
        $relativepathstring = preg_replace('/^' . preg_quote(constant('DOL_URL_ROOT'), '/') . '/', '', $relativepathstring);
607
    }
608
    $relativepathstring = preg_replace('/^\//', '', $relativepathstring);
609
    $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
610
    //var_dump($relativepathstring);
611
    //var_dump($user->default_values);
612
613
    // Code for search criteria persistence.
614
    // Retrieve values if restore_lastsearch_values
615
    if (!empty($_GET['restore_lastsearch_values'])) {        // Use $_GET here and not GETPOST
616
        if (!empty($_SESSION['lastsearch_values_' . $relativepathstring])) {  // If there is saved values
617
            $tmp = json_decode($_SESSION['lastsearch_values_' . $relativepathstring], true);
618
            if (is_array($tmp)) {
619
                foreach ($tmp as $key => $val) {
620
                    if ($key == $paramname) {   // We are on the requested parameter
621
                        $isset = true;
622
                        break;
623
                    }
624
                }
625
            }
626
        }
627
        // If there is saved contextpage, limit, page or mode
628
        if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_' . $relativepathstring])) {
629
            $isset = true;
630
        } elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_' . $relativepathstring])) {
631
            $isset = true;
632
        } elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_' . $relativepathstring])) {
633
            $isset = true;
634
        } elseif ($paramname == 'mode' && !empty($_SESSION['lastsearch_mode_' . $relativepathstring])) {
635
            $isset = true;
636
        }
637
    } else {
638
        $isset = (isset($_POST[$paramname]) || isset($_GET[$paramname])); // We must keep $_POST and $_GET here
639
    }
640
641
    return $isset;
642
}
643
644
/**
645
 * Return true if the parameter $paramname is submit from a POST OR GET as an array.
646
 * Can be used before GETPOST to know if the $check param of GETPOST need to check an array or a string
647
 *
648
 * @param string $paramname Name or parameter to test
649
 * @param int<0,3> $method Type of method (0 = get then post, 1 = only get, 2 = only post, 3 = post then get)
650
 * @return  bool                    True if we have just submit a POST or GET request with the parameter provided (even if param is empty)
651
 */
652
function GETPOSTISARRAY($paramname, $method = 0)
653
{
654
    // for $method test need return the same $val as GETPOST
655
    if (empty($method)) {
656
        $val = $_GET[$paramname] ?? ($_POST[$paramname] ?? '');
657
    } elseif ($method == 1) {
658
        $val = $_GET[$paramname] ?? '';
659
    } elseif ($method == 2) {
660
        $val = $_POST[$paramname] ?? '';
661
    } elseif ($method == 3) {
662
        $val = $_POST[$paramname] ?? ($_GET[$paramname] ?? '');
663
    } else {
664
        $val = 'BadFirstParameterForGETPOST';
665
    }
666
667
    return is_array($val);
668
}
669
670
/**
671
 *  Return value of a param into GET or POST supervariable.
672
 *  Use the property $user->default_values[path]['createform'] and/or $user->default_values[path]['filters'] and/or $user->default_values[path]['sortorder']
673
 *  Note: The property $user->default_values is loaded by main.php when loading the user.
674
 *
675
 * @param string $paramname Name of parameter to found
676
 * @param string $check Type of check
677
 *                               ''=no check (deprecated)
678
 *                               'none'=no check (only for param that should have very rich content like passwords)
679
 *                               'array', 'array:restricthtml' or 'array:aZ09' to check it's an array
680
 *                               'int'=check it's numeric (integer or float)
681
 *                               'intcomma'=check it's integer+comma ('1,2,3,4...')
682
 *                               'alpha'=Same than alphanohtml
683
 *                               'alphawithlgt'=alpha with lgt
684
 *                               'alphanohtml'=check there is no html content and no " and no ../
685
 *                               'aZ'=check it's a-z only
686
 *                               'aZ09'=check it's simple alpha string (recommended for keys)
687
 *                               'aZ09arobase'=check it's a string for an element type ('myobject@mymodule')
688
 *                               'aZ09comma'=check it's a string for a sortfield or sortorder
689
 *                               'san_alpha'=Use filter_var with FILTER_SANITIZE_STRING (do not use this for free text string)
690
 *                               'nohtml'=check there is no html content
691
 *                               'restricthtml'=check html content is restricted to some tags only
692
 *                               'custom'= custom filter specify $filter and $options)
693
 * @param int $method Type of method (0 = get then post, 1 = only get, 2 = only post, 3 = post then get)
694
 * @param  ?int $filter Filter to apply when $check is set to 'custom'. (See http://php.net/manual/en/filter.filters.php for détails)
695
 * @param mixed $options Options to pass to filter_var when $check is set to 'custom'
696
 * @param int $noreplace Force disable of replacement of __xxx__ strings.
697
 * @return string|array         Value found (string or array), or '' if check fails
698
 */
699
function GETPOST($paramname, $check = 'alphanohtml', $method = 0, $filter = null, $options = null, $noreplace = 0)
700
{
701
    global $mysoc, $user, $conf;
702
703
    if (empty($paramname)) {   // Explicit test for null for phan.
704
        return 'BadFirstParameterForGETPOST';
705
    }
706
    if (empty($check)) {
707
        dol_syslog("Deprecated use of GETPOST, called with 1st param = " . $paramname . " and a 2nd param that is '', when calling page " . $_SERVER["PHP_SELF"], LOG_WARNING);
708
        // Enable this line to know who call the GETPOST with '' $check parameter.
709
        //var_dump(debug_backtrace()[0]);
710
    }
711
712
    if (empty($method)) {
713
        $out = isset($_GET[$paramname]) ? $_GET[$paramname] : (isset($_POST[$paramname]) ? $_POST[$paramname] : '');
714
    } elseif ($method == 1) {
715
        $out = isset($_GET[$paramname]) ? $_GET[$paramname] : '';
716
    } elseif ($method == 2) {
717
        $out = isset($_POST[$paramname]) ? $_POST[$paramname] : '';
718
    } elseif ($method == 3) {
719
        $out = isset($_POST[$paramname]) ? $_POST[$paramname] : (isset($_GET[$paramname]) ? $_GET[$paramname] : '');
720
    } else {
721
        return 'BadThirdParameterForGETPOST';
722
    }
723
724
    $relativepathstring = ''; // For static analysis - looks possibly undefined if not set.
725
726
    if (empty($method) || $method == 3 || $method == 4) {
727
        $relativepathstring = (empty($_SERVER["PHP_SELF"]) ? '' : $_SERVER["PHP_SELF"]);
728
        // Clean $relativepathstring
729
        if (constant('DOL_URL_ROOT')) {
730
            $relativepathstring = preg_replace('/^' . preg_quote(constant('DOL_URL_ROOT'), '/') . '/', '', $relativepathstring);
731
        }
732
        $relativepathstring = preg_replace('/^\//', '', $relativepathstring);
733
        $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
734
        //var_dump($relativepathstring);
735
        //var_dump($user->default_values);
736
737
        // Code for search criteria persistence.
738
        // Retrieve saved values if restore_lastsearch_values is set
739
        if (!empty($_GET['restore_lastsearch_values'])) {        // Use $_GET here and not GETPOST
740
            if (!empty($_SESSION['lastsearch_values_' . $relativepathstring])) {  // If there is saved values
741
                $tmp = json_decode($_SESSION['lastsearch_values_' . $relativepathstring], true);
742
                if (is_array($tmp)) {
743
                    foreach ($tmp as $key => $val) {
744
                        if ($key == $paramname) {   // We are on the requested parameter
745
                            $out = $val;
746
                            break;
747
                        }
748
                    }
749
                }
750
            }
751
            // If there is saved contextpage, page or limit
752
            if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_' . $relativepathstring])) {
753
                $out = $_SESSION['lastsearch_contextpage_' . $relativepathstring];
754
            } elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_' . $relativepathstring])) {
755
                $out = $_SESSION['lastsearch_limit_' . $relativepathstring];
756
            } elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_' . $relativepathstring])) {
757
                $out = $_SESSION['lastsearch_page_' . $relativepathstring];
758
            } elseif ($paramname == 'mode' && !empty($_SESSION['lastsearch_mode_' . $relativepathstring])) {
759
                $out = $_SESSION['lastsearch_mode_' . $relativepathstring];
760
            }
761
        } elseif (!isset($_GET['sortfield'])) {
762
            // Else, retrieve default values if we are not doing a sort
763
            // If we did a click on a field to sort, we do no apply default values. Same if option MAIN_ENABLE_DEFAULT_VALUES is not set
764
            if (!empty($_GET['action']) && $_GET['action'] == 'create' && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
765
                // Search default value from $object->field
766
                global $object;
767
                '@phan-var-force CommonObject $object'; // Suppose it's a CommonObject for analysis, but other objects have the $fields field as well
768
                if (is_object($object) && isset($object->fields[$paramname]['default'])) {
769
                    // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
770
                    $out = $object->fields[$paramname]['default'];
771
                }
772
            }
773
            if (getDolGlobalString('MAIN_ENABLE_DEFAULT_VALUES')) {
774
                if (!empty($_GET['action']) && (preg_match('/^create/', $_GET['action']) || preg_match('/^presend/', $_GET['action'])) && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
775
                    // Now search in setup to overwrite default values
776
                    if (!empty($user->default_values)) {        // $user->default_values defined from menu 'Setup - Default values'
777
                        if (isset($user->default_values[$relativepathstring]['createform'])) {
778
                            foreach ($user->default_values[$relativepathstring]['createform'] as $defkey => $defval) {
779
                                $qualified = 0;
780
                                if ($defkey != '_noquery_') {
781
                                    $tmpqueryarraytohave = explode('&', $defkey);
782
                                    $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
783
                                    $foundintru = 0;
784
                                    foreach ($tmpqueryarraytohave as $tmpquerytohave) {
785
                                        if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
786
                                            $foundintru = 1;
787
                                        }
788
                                    }
789
                                    if (!$foundintru) {
790
                                        $qualified = 1;
791
                                    }
792
                                    //var_dump($defkey.'-'.$qualified);
793
                                } else {
794
                                    $qualified = 1;
795
                                }
796
797
                                if ($qualified) {
798
                                    if (isset($user->default_values[$relativepathstring]['createform'][$defkey][$paramname])) {
799
                                        $out = $user->default_values[$relativepathstring]['createform'][$defkey][$paramname];
800
                                        break;
801
                                    }
802
                                }
803
                            }
804
                        }
805
                    }
806
                } elseif (!empty($paramname) && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
807
                    // Management of default search_filters and sort order
808
                    if (!empty($user->default_values)) {
809
                        // $user->default_values defined from menu 'Setup - Default values'
810
                        //var_dump($user->default_values[$relativepathstring]);
811
                        if ($paramname == 'sortfield' || $paramname == 'sortorder') {
812
                            // Sorted on which fields ? ASC or DESC ?
813
                            if (isset($user->default_values[$relativepathstring]['sortorder'])) {
814
                                // Even if paramname is sortfield, data are stored into ['sortorder...']
815
                                foreach ($user->default_values[$relativepathstring]['sortorder'] as $defkey => $defval) {
816
                                    $qualified = 0;
817
                                    if ($defkey != '_noquery_') {
818
                                        $tmpqueryarraytohave = explode('&', $defkey);
819
                                        $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
820
                                        $foundintru = 0;
821
                                        foreach ($tmpqueryarraytohave as $tmpquerytohave) {
822
                                            if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
823
                                                $foundintru = 1;
824
                                            }
825
                                        }
826
                                        if (!$foundintru) {
827
                                            $qualified = 1;
828
                                        }
829
                                        //var_dump($defkey.'-'.$qualified);
830
                                    } else {
831
                                        $qualified = 1;
832
                                    }
833
834
                                    if ($qualified) {
835
                                        $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
836
                                        foreach ($user->default_values[$relativepathstring]['sortorder'][$defkey] as $key => $val) {
837
                                            if ($out) {
838
                                                $out .= ', ';
839
                                            }
840
                                            if ($paramname == 'sortfield') {
841
                                                $out .= dol_string_nospecial($key, '', $forbidden_chars_to_replace);
842
                                            }
843
                                            if ($paramname == 'sortorder') {
844
                                                $out .= dol_string_nospecial($val, '', $forbidden_chars_to_replace);
845
                                            }
846
                                        }
847
                                        //break;    // No break for sortfield and sortorder so we can cumulate fields (is it really useful ?)
848
                                    }
849
                                }
850
                            }
851
                        } elseif (isset($user->default_values[$relativepathstring]['filters'])) {
852
                            foreach ($user->default_values[$relativepathstring]['filters'] as $defkey => $defval) { // $defkey is a querystring like 'a=b&c=d', $defval is key of user
853
                                if (!empty($_GET['disabledefaultvalues'])) {    // If set of default values has been disabled by a request parameter
854
                                    continue;
855
                                }
856
                                $qualified = 0;
857
                                if ($defkey != '_noquery_') {
858
                                    $tmpqueryarraytohave = explode('&', $defkey);
859
                                    $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
860
                                    $foundintru = 0;
861
                                    foreach ($tmpqueryarraytohave as $tmpquerytohave) {
862
                                        if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
863
                                            $foundintru = 1;
864
                                        }
865
                                    }
866
                                    if (!$foundintru) {
867
                                        $qualified = 1;
868
                                    }
869
                                    //var_dump($defkey.'-'.$qualified);
870
                                } else {
871
                                    $qualified = 1;
872
                                }
873
874
                                if ($qualified && isset($user->default_values[$relativepathstring]['filters'][$defkey][$paramname])) {
875
                                    // We must keep $_POST and $_GET here
876
                                    if (isset($_POST['sall']) || isset($_POST['search_all']) || isset($_GET['sall']) || isset($_GET['search_all'])) {
877
                                        // We made a search from quick search menu, do we still use default filter ?
878
                                        if (!getDolGlobalString('MAIN_DISABLE_DEFAULT_FILTER_FOR_QUICK_SEARCH')) {
879
                                            $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
880
                                            $out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
881
                                        }
882
                                    } else {
883
                                        $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
884
                                        $out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
885
                                    }
886
                                    break;
887
                                }
888
                            }
889
                        }
890
                    }
891
                }
892
            }
893
        }
894
    }
895
896
    // Substitution variables for GETPOST (used to get final url with variable parameters or final default value, when using variable parameters __XXX__ in the GET URL)
897
    // Example of variables: __DAY__, __MONTH__, __YEAR__, __MYCOMPANY_COUNTRY_ID__, __USER_ID__, ...
898
    // We do this only if var is a GET. If it is a POST, may be we want to post the text with vars as the setup text.
899
    '@phan-var-force string $paramname';
900
    if (!is_array($out) && empty($_POST[$paramname]) && empty($noreplace)) {
901
        $reg = array();
902
        $maxloop = 20;
903
        $loopnb = 0; // Protection against infinite loop
904
        while (preg_match('/__([A-Z0-9]+_?[A-Z0-9]+)__/i', $out, $reg) && ($loopnb < $maxloop)) {    // Detect '__ABCDEF__' as key 'ABCDEF' and '__ABC_DEF__' as key 'ABC_DEF'. Detection is also correct when 2 vars are side by side.
905
            $loopnb++;
906
            $newout = '';
907
908
            if ($reg[1] == 'DAY') {
909
                $tmp = dol_getdate(dol_now(), true);
910
                $newout = $tmp['mday'];
911
            } elseif ($reg[1] == 'MONTH') {
912
                $tmp = dol_getdate(dol_now(), true);
913
                $newout = $tmp['mon'];
914
            } elseif ($reg[1] == 'YEAR') {
915
                $tmp = dol_getdate(dol_now(), true);
916
                $newout = $tmp['year'];
917
            } elseif ($reg[1] == 'PREVIOUS_DAY') {
918
                $tmp = dol_getdate(dol_now(), true);
919
                $tmp2 = dol_get_prev_day($tmp['mday'], $tmp['mon'], $tmp['year']);
920
                $newout = $tmp2['day'];
921
            } elseif ($reg[1] == 'PREVIOUS_MONTH') {
922
                $tmp = dol_getdate(dol_now(), true);
923
                $tmp2 = dol_get_prev_month($tmp['mon'], $tmp['year']);
924
                $newout = $tmp2['month'];
925
            } elseif ($reg[1] == 'PREVIOUS_YEAR') {
926
                $tmp = dol_getdate(dol_now(), true);
927
                $newout = ($tmp['year'] - 1);
928
            } elseif ($reg[1] == 'NEXT_DAY') {
929
                $tmp = dol_getdate(dol_now(), true);
930
                $tmp2 = dol_get_next_day($tmp['mday'], $tmp['mon'], $tmp['year']);
931
                $newout = $tmp2['day'];
932
            } elseif ($reg[1] == 'NEXT_MONTH') {
933
                $tmp = dol_getdate(dol_now(), true);
934
                $tmp2 = dol_get_next_month($tmp['mon'], $tmp['year']);
935
                $newout = $tmp2['month'];
936
            } elseif ($reg[1] == 'NEXT_YEAR') {
937
                $tmp = dol_getdate(dol_now(), true);
938
                $newout = ($tmp['year'] + 1);
939
            } elseif ($reg[1] == 'MYCOMPANY_COUNTRY_ID' || $reg[1] == 'MYCOUNTRY_ID' || $reg[1] == 'MYCOUNTRYID') {
940
                $newout = $mysoc->country_id;
941
            } elseif ($reg[1] == 'USER_ID' || $reg[1] == 'USERID') {
942
                $newout = $user->id;
943
            } elseif ($reg[1] == 'USER_SUPERVISOR_ID' || $reg[1] == 'SUPERVISOR_ID' || $reg[1] == 'SUPERVISORID') {
944
                $newout = $user->fk_user;
945
            } elseif ($reg[1] == 'ENTITY_ID' || $reg[1] == 'ENTITYID') {
946
                $newout = $conf->entity;
947
            } else {
948
                $newout = ''; // Key not found, we replace with empty string
949
            }
950
            //var_dump('__'.$reg[1].'__ -> '.$newout);
951
            $out = preg_replace('/__' . preg_quote($reg[1], '/') . '__/', $newout, $out);
952
        }
953
    }
954
955
    // Check type of variable and make sanitization according to this
956
    if (preg_match('/^array/', $check)) {   // If 'array' or 'array:restricthtml' or 'array:aZ09' or 'array:intcomma'
957
        if (!is_array($out) || empty($out)) {
958
            $out = array();
959
        } else {
960
            $tmparray = explode(':', $check);
961
            if (!empty($tmparray[1])) {
962
                $tmpcheck = $tmparray[1];
963
            } else {
964
                $tmpcheck = 'alphanohtml';
965
            }
966
            foreach ($out as $outkey => $outval) {
967
                $out[$outkey] = sanitizeVal($outval, $tmpcheck, $filter, $options);
968
            }
969
        }
970
    } else {
971
        // If field name is 'search_xxx' then we force the add of space after each < and > (when following char is numeric) because it means
972
        // we use the < or > to make a search on a numeric value to do higher or lower so we can add a space to break html tags
973
        if (strpos($paramname, 'search_') === 0) {
974
            $out = preg_replace('/([<>])([-+]?\d)/', '\1 \2', $out);
975
        }
976
977
        // @phan-suppress-next-line UnknownSanitizeType
978
        $out = sanitizeVal($out, $check, $filter, $options);
979
    }
980
981
    // Sanitizing for special parameters.
982
    // Note: There is no reason to allow the backtopage, backtolist or backtourl parameter to contains an external URL. Only relative URLs are allowed.
983
    if ($paramname == 'backtopage' || $paramname == 'backtolist' || $paramname == 'backtourl') {
984
        $out = str_replace('\\', '/', $out);                                // Can be before the loop because only 1 char is replaced. No risk to get it after other replacements.
985
        $out = str_replace(array(':', ';', '@', "\t", ' '), '', $out);      // Can be before the loop because only 1 char is replaced. No risk to retrieve it after other replacements.
986
        do {
987
            $oldstringtoclean = $out;
988
            $out = str_ireplace(array('javascript', 'vbscript', '&colon', '&#'), '', $out);
989
            $out = preg_replace(array('/^[^?]*%/'), '', $out);             // We remove any % chars before the ?. Example in url: '/product/stock/card.php?action=create&backtopage=%2Fdolibarr_dev%2Fhtdocs%2Fpro%25duct%2Fcard.php%3Fid%3Dabc'
990
            $out = preg_replace(array('/^[a-z]*\/\s*\/+/i'), '', $out);     // We remove schema*// to remove external URL
991
        } while ($oldstringtoclean != $out);
992
    }
993
994
    // Code for search criteria persistence.
995
    // Save data into session if key start with 'search_'
996
    if (empty($method) || $method == 3 || $method == 4) {
997
        if (preg_match('/^search_/', $paramname) || in_array($paramname, array('sortorder', 'sortfield'))) {
998
            //var_dump($paramname.' - '.$out.' '.$user->default_values[$relativepathstring]['filters'][$paramname]);
999
1000
            // We save search key only if $out not empty that means:
1001
            // - posted value not empty, or
1002
            // - if posted value is empty and a default value exists that is not empty (it means we did a filter to an empty value when default was not).
1003
1004
            if ($out != '' && isset($user)) {// $out = '0' or 'abc', it is a search criteria to keep
1005
                $user->lastsearch_values_tmp[$relativepathstring][$paramname] = $out;
1006
            }
1007
        }
1008
    }
1009
1010
    return $out;
1011
}
1012
1013
/**
1014
 *  Return the value of a $_GET or $_POST supervariable, converted into integer.
1015
 *  Use the property $user->default_values[path]['creatform'] and/or $user->default_values[path]['filters'] and/or $user->default_values[path]['sortorder']
1016
 *  Note: The property $user->default_values is loaded by main.php when loading the user.
1017
 *
1018
 * @param string $paramname Name of the $_GET or $_POST parameter
1019
 * @param int<0,3> $method Type of method (0 = $_GET then $_POST, 1 = only $_GET, 2 = only $_POST, 3 = $_POST then $_GET)
1020
 * @return int                     Value converted into integer
1021
 */
1022
function GETPOSTINT($paramname, $method = 0)
1023
{
1024
    return (int)GETPOST($paramname, 'int', $method, null, null, 0);
1025
}
1026
1027
1028
/**
1029
 *  Return the value of a $_GET or $_POST supervariable, converted into float.
1030
 *
1031
 * @param string $paramname Name of the $_GET or $_POST parameter
1032
 * @param string|int $rounding Type of rounding ('', 'MU', 'MT, 'MS', 'CU', 'CT', integer) {@see price2num()}
1033
 * @return float                           Value converted into float
1034
 * @since  Dolibarr V20
1035
 */
1036
function GETPOSTFLOAT($paramname, $rounding = '')
1037
{
1038
    // price2num() is used to sanitize any valid user input (such as "1 234.5", "1 234,5", "1'234,5", "1·234,5", "1,234.5", etc.)
1039
    return (float)price2num(GETPOST($paramname), $rounding, 2);
1040
}
1041
1042
1043
/**
1044
 *  Return a sanitized or empty value after checking value against a rule.
1045
 *
1046
 * @param string|array $out Value to check/clear.
1047
 * @param string $check Type of check/sanitizing
1048
 * @param int $filter Filter to apply when $check is set to 'custom'. (See http://php.net/manual/en/filter.filters.php for détails)
1049
 * @param mixed $options Options to pass to filter_var when $check is set to 'custom'
1050
 * @return string|array                 Value sanitized (string or array). It may be '' if format check fails.
1051
 * @deprecated
1052
 */
1053
function checkVal($out = '', $check = 'alphanohtml', $filter = null, $options = null)
1054
{
1055
    return sanitizeVal($out, $check, $filter, $options);
1056
}
1057
1058
/**
1059
 *  Return a sanitized or empty value after checking value against a rule.
1060
 *
1061
 * @param string|array $out Value to check/clear.
1062
 * @param string $check Type of check/sanitizing
1063
 * @param int $filter Filter to apply when $check is set to 'custom'. (See http://php.net/manual/en/filter.filters.php for détails)
1064
 * @param mixed $options Options to pass to filter_var when $check is set to 'custom'
1065
 * @return string|array                 Value sanitized (string or array). It may be '' if format check fails.
1066
 */
1067
function sanitizeVal($out = '', $check = 'alphanohtml', $filter = null, $options = null)
1068
{
1069
    // TODO : use class "Validate" to perform tests (and add missing tests) if needed for factorize
1070
    // Check is done after replacement
1071
    switch ($check) {
1072
        case 'none':
1073
            break;
1074
        case 'int':    // Check param is a numeric value (integer but also float or hexadecimal)
1075
            if (!is_numeric($out)) {
1076
                $out = '';
1077
            }
1078
            break;
1079
        case 'intcomma':
1080
            if (is_array($out)) {
1081
                $out = implode(',', $out);
1082
            }
1083
            if (preg_match('/[^0-9,-]+/i', $out)) {
1084
                $out = '';
1085
            }
1086
            break;
1087
        case 'san_alpha':
1088
            $out = filter_var($out, FILTER_SANITIZE_STRING);
1089
            break;
1090
        case 'email':
1091
            $out = filter_var($out, FILTER_SANITIZE_EMAIL);
1092
            break;
1093
        case 'aZ':
1094
            if (!is_array($out)) {
1095
                $out = trim($out);
1096
                if (preg_match('/[^a-z]+/i', $out)) {
1097
                    $out = '';
1098
                }
1099
            }
1100
            break;
1101
        case 'aZ09':
1102
            if (!is_array($out)) {
1103
                $out = trim($out);
1104
                if (preg_match('/[^a-z0-9_\-.]+/i', $out)) {
1105
                    $out = '';
1106
                }
1107
            }
1108
            break;
1109
        case 'aZ09arobase':     // great to sanitize $objecttype parameter
1110
            if (!is_array($out)) {
1111
                $out = trim($out);
1112
                if (preg_match('/[^a-z0-9_\-.@]+/i', $out)) {
1113
                    $out = '';
1114
                }
1115
            }
1116
            break;
1117
        case 'aZ09comma':       // great to sanitize $sortfield or $sortorder params that can be 't.abc,t.def_gh'
1118
            if (!is_array($out)) {
1119
                $out = trim($out);
1120
                if (preg_match('/[^a-z0-9_\-.,]+/i', $out)) {
1121
                    $out = '';
1122
                }
1123
            }
1124
            break;
1125
        case 'alpha':       // No html and no ../ and "
1126
        case 'alphanohtml': // Recommended for most scalar parameters and search parameters
1127
            if (!is_array($out)) {
1128
                $out = trim($out);
1129
                do {
1130
                    $oldstringtoclean = $out;
1131
                    // Remove html tags
1132
                    $out = dol_string_nohtmltag($out, 0);
1133
                    // Refuse octal syntax \999, hexa syntax \x999 and unicode syntax \u{999} by replacing the \ into / (so if it is a \ for a windows path, it is still ok).
1134
                    $out = preg_replace('/\\\([0-9xu])/', '/\1', $out);
1135
                    // Remove also other dangerous string sequences
1136
                    // '../' or '..\' is dangerous because it allows dir transversals
1137
                    // '&#38', '&#0000038', '&#x26'... is a the char '&' alone but there is no reason to accept such way to encode input char
1138
                    // '"' = '&#34' = '&#0000034' = '&#x22' is dangerous because param in url can close the href= or src= and add javascript functions.
1139
                    // '&#47', '&#0000047', '&#x2F' is the char '/' but there is no reason to accept such way to encode this input char
1140
                    // '&#92' = '&#0000092' = '&#x5C' is the char '\' but there is no reason to accept such way to encode this input char
1141
                    $out = str_ireplace(array('../', '..\\', '&#38', '&#0000038', '&#x26', '&quot', '"', '&#34', '&#0000034', '&#x22', '&#47', '&#0000047', '&#x2F', '&#92', '&#0000092', '&#x5C'), '', $out);
1142
                } while ($oldstringtoclean != $out);
1143
                // keep lines feed
1144
            }
1145
            break;
1146
        case 'alphawithlgt':        // No " and no ../ but we keep balanced < > tags with no special chars inside. Can be used for email string like "Name <email>". Less secured than 'alphanohtml'
1147
            if (!is_array($out)) {
1148
                $out = trim($out);
1149
                do {
1150
                    $oldstringtoclean = $out;
1151
                    // Decode html entities
1152
                    $out = dol_html_entity_decode($out, ENT_COMPAT | ENT_HTML5, 'UTF-8');
1153
                    // Refuse octal syntax \999, hexa syntax \x999 and unicode syntax \u{999} by replacing the \ into / (so if it is a \ for a windows path, it is still ok).
1154
                    $out = preg_replace('/\\\([0-9xu])/', '/\1', $out);
1155
                    // Remove also other dangerous string sequences
1156
                    // '../' or '..\' is dangerous because it allows dir transversals
1157
                    // '&#38', '&#0000038', '&#x26'... is a the char '&' alone but there is no reason to accept such way to encode input char
1158
                    // '"' = '&#34' = '&#0000034' = '&#x22' is dangerous because param in url can close the href= or src= and add javascript functions.
1159
                    // '&#47', '&#0000047', '&#x2F' is the char '/' but there is no reason to accept such way to encode this input char
1160
                    // '&#92' = '&#0000092' = '&#x5C' is the char '\' but there is no reason to accept such way to encode this input char
1161
                    $out = str_ireplace(array('../', '..\\', '&#38', '&#0000038', '&#x26', '&quot', '"', '&#34', '&#0000034', '&#x22', '&#47', '&#0000047', '&#x2F', '&#92', '&#0000092', '&#x5C'), '', $out);
1162
                } while ($oldstringtoclean != $out);
1163
            }
1164
            break;
1165
        case 'nohtml':      // No html
1166
            $out = dol_string_nohtmltag($out, 0);
1167
            break;
1168
        case 'restricthtmlnolink':
1169
        case 'restricthtml':        // Recommended for most html textarea
1170
        case 'restricthtmlallowclass':
1171
        case 'restricthtmlallowunvalid':
1172
            $out = dol_htmlwithnojs($out, 1, $check);
1173
            break;
1174
        case 'custom':
1175
            if (!empty($out)) {
1176
                if (empty($filter)) {
1177
                    return 'BadParameterForGETPOST - Param 3 of sanitizeVal()';
1178
                }
1179
                if (is_null($options)) {
1180
                    $options = 0;
1181
                }
1182
                $out = filter_var($out, $filter, $options);
1183
            }
1184
            break;
1185
        default:
1186
            dol_syslog("Error, you call sanitizeVal() with a bad value for the check type. Data will be sanitized with alphanohtml.", LOG_ERR);
1187
            $out = GETPOST($out, 'alphanohtml');
1188
            break;
1189
    }
1190
1191
    return $out;
1192
}
1193
1194
1195
if (!function_exists('dol_getprefix')) {
1196
    /**
1197
     *  Return a prefix to use for this Dolibarr instance, for session/cookie names or email id.
1198
     *  The prefix is unique for instance and avoid conflict between multi-instances, even when having two instances with same root dir
1199
     *  or two instances in same virtual servers.
1200
     *  This function must not use dol_hash (that is used for password hash) and need to have all context $conf loaded.
1201
     *
1202
     * @param string $mode '' (prefix for session name) or 'email' (prefix for email id)
1203
     * @return string                          A calculated prefix
1204
     * @phan-suppress PhanRedefineFunction - Also defined in webportal.main.inc.php
1205
     */
1206
    function dol_getprefix($mode = '')
1207
    {
1208
        // If prefix is for email (we need to have $conf already loaded for this case)
1209
        if ($mode == 'email') {
1210
            global $conf;
1211
1212
            if (getDolGlobalString('MAIL_PREFIX_FOR_EMAIL_ID')) {   // If MAIL_PREFIX_FOR_EMAIL_ID is set
1213
                if (getDolGlobalString('MAIL_PREFIX_FOR_EMAIL_ID') != 'SERVER_NAME') {
1214
                    return $conf->global->MAIL_PREFIX_FOR_EMAIL_ID;
1215
                } elseif (isset($_SERVER["SERVER_NAME"])) { // If MAIL_PREFIX_FOR_EMAIL_ID is set to 'SERVER_NAME'
1216
                    return $_SERVER["SERVER_NAME"];
1217
                }
1218
            }
1219
1220
            // The recommended value if MAIL_PREFIX_FOR_EMAIL_ID is not defined (may be not defined for old versions)
1221
            if (!empty($conf->file->instance_unique_id)) {
1222
                return sha1('dolibarr' . $conf->file->instance_unique_id);
1223
            }
1224
1225
            // For backward compatibility when instance_unique_id is not set
1226
            return sha1(DOL_DOCUMENT_ROOT . DOL_URL_ROOT);
1227
        }
1228
1229
        // If prefix is for session (no need to have $conf loaded)
1230
        global $dolibarr_main_instance_unique_id, $dolibarr_main_cookie_cryptkey;   // This is loaded by filefunc.inc.php
1231
        $tmp_instance_unique_id = empty($dolibarr_main_instance_unique_id) ? (empty($dolibarr_main_cookie_cryptkey) ? '' : $dolibarr_main_cookie_cryptkey) : $dolibarr_main_instance_unique_id; // Unique id of instance
1232
1233
        // The recommended value (may be not defined for old versions)
1234
        if (!empty($tmp_instance_unique_id)) {
1235
            return sha1('dolibarr' . $tmp_instance_unique_id);
1236
        }
1237
1238
        // For backward compatibility when instance_unique_id is not set
1239
        if (isset($_SERVER["SERVER_NAME"]) && isset($_SERVER["DOCUMENT_ROOT"])) {
1240
            return sha1($_SERVER["SERVER_NAME"] . $_SERVER["DOCUMENT_ROOT"] . DOL_DOCUMENT_ROOT . DOL_URL_ROOT);
1241
        } else {
1242
            return sha1(DOL_DOCUMENT_ROOT . DOL_URL_ROOT);
1243
        }
1244
    }
1245
}
1246
1247
/**
1248
 *  Make an include_once using default root and alternate root if it fails.
1249
 *  To link to a core file, use include(DOL_DOCUMENT_ROOT.'/pathtofile')
1250
 *  To link to a module file from a module file, use include './mymodulefile';
1251
 *  To link to a module file from a core file, then this function can be used (call by hook / trigger / speciales pages)
1252
 *
1253
 * @param string $relpath Relative path to file (Ie: mydir/myfile, ../myfile, ...)
1254
 * @param string $classname Class name (deprecated)
1255
 * @return bool                True if load is a success, False if it fails
1256
 */
1257
function dol_include_once($relpath, $classname = '')
1258
{
1259
    global $conf, $langs, $user, $mysoc; // Do not remove this. They must be defined for files we include. Other globals var must be retrieved with $GLOBALS['var']
1260
1261
    $fullpath = dol_buildpath($relpath);
1262
1263
    if (!file_exists($fullpath)) {
1264
        dol_syslog('functions::dol_include_once Tried to load unexisting file: ' . $relpath, LOG_WARNING);
1265
        return false;
1266
    }
1267
1268
    if (!empty($classname) && !class_exists($classname)) {
1269
        return include $fullpath;
1270
    } else {
1271
        return include_once $fullpath;
1272
    }
1273
}
1274
1275
1276
/**
1277
 *  Return path of url or filesystem. Can check into alternate dir or alternate dir + main dir depending on value of $returnemptyifnotfound.
1278
 *
1279
 * @param string $path Relative path to file (if mode=0) or relative url (if mode=1). Ie: mydir/myfile, ../myfile
1280
 * @param int $type 0=Used for a Filesystem path, 1=Used for an URL path (output relative), 2=Used for an URL path (output full path using same host that current url), 3=Used for an URL path (output full path using host defined into $dolibarr_main_url_root of conf file)
1281
 * @param int $returnemptyifnotfound 0:If $type==0 and if file was not found into alternate dir, return default path into main dir (no test on it)
1282
 *                                              1:If $type==0 and if file was not found into alternate dir, return empty string
1283
 *                                              2:If $type==0 and if file was not found into alternate dir, test into main dir, return default path if found, empty string if not found
1284
 * @return string                              Full filesystem path (if path=0) or '' if file not found, Full url path (if mode=1)
1285
 */
1286
function dol_buildpath($path, $type = 0, $returnemptyifnotfound = 0): string
1287
{
1288
    global $conf;
1289
1290
    $path = preg_replace('/^\//', '', $path);
1291
1292
    if (empty($type)) { // For a filesystem path
1293
        $res = DOL_DOCUMENT_ROOT . '/' . $path; // Standard default path
1294
        if (is_array($conf->file->dol_document_root)) {
1295
            foreach ($conf->file->dol_document_root as $key => $dirroot) {  // ex: array("main"=>"/home/main/htdocs", "alt0"=>"/home/dirmod/htdocs", ...)
1296
                if ($key == 'main') {
1297
                    continue;
1298
                }
1299
                // if (@file_exists($dirroot.'/'.$path)) {
1300
                if (@file_exists($dirroot . '/' . $path)) { // avoid [php:warn]
1301
                    $res = $dirroot . '/' . $path;
1302
                    return $res;
1303
                }
1304
            }
1305
        }
1306
        if ($returnemptyifnotfound) {
1307
            // Not found into alternate dir
1308
            if ($returnemptyifnotfound == 1 || !file_exists($res)) {
1309
                return '';
1310
            }
1311
        }
1312
    } else {
1313
        // For an url path
1314
        // We try to get local path of file on filesystem from url
1315
        // Note that trying to know if a file on disk exist by forging path on disk from url
1316
        // works only for some web server and some setup. This is bugged when
1317
        // using proxy, rewriting, virtual path, etc...
1318
        $res = '';
1319
        if ($type == 1) {
1320
            $res = constant('BASE_URL') . '/' . $path; // Standard value
1321
        }
1322
        if ($type == 2) {
1323
            $res = DOL_MAIN_URL_ROOT . '/' . $path; // Standard value
1324
        }
1325
        if ($type == 3) {
1326
            $res = constant('DOL_URL_ROOT') . '/' . $path;
1327
        }
1328
1329
        foreach ($conf->file->dol_document_root as $key => $dirroot) {  // ex: array(["main"]=>"/home/main/htdocs", ["alt0"]=>"/home/dirmod/htdocs", ...)
1330
            if ($key == 'main') {
1331
                if ($type == 3) {
1332
                    /*global $dolibarr_main_url_root;*/
1333
1334
                    // Define $urlwithroot
1335
                    $urlwithouturlroot = preg_replace('/' . preg_quote(DOL_URL_ROOT, '/') . '$/i', '', trim($conf->file->dol_main_url_root));
1336
                    $urlwithroot = $urlwithouturlroot . DOL_URL_ROOT; // This is to use external domain name found into config file
1337
                    //$urlwithroot=DOL_MAIN_URL_ROOT;                   // This is to use same domain name than current
1338
1339
                    $res2 = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : $urlwithroot) . '/' . $path; // Test on start with http is for old conf syntax
1340
1341
                    $res = constant('DOL_URL_ROOT') . '/' . $path;
1342
                }
1343
                continue;
1344
            }
1345
            $regs = array();
1346
            preg_match('/^([^?]+(\.css\.php|\.css|\.js\.php|\.js|\.png|\.jpg|\.php)?)/i', $path, $regs); // Take part before '?'
1347
            if (!empty($regs[1])) {
1348
                //print $key.'-'.$dirroot.'/'.$path.'-'.$conf->file->dol_url_root[$type].'<br>'."\n";
1349
                //if (file_exists($dirroot.'/'.$regs[1])) {
1350
                if (@file_exists($dirroot . '/' . $regs[1])) {  // avoid [php:warn]
1351
                    if ($type == 1) {
1352
                        $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_URL_ROOT) . $conf->file->dol_url_root[$key] . '/' . $path;
1353
                    }
1354
                    if ($type == 2) {
1355
                        $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_MAIN_URL_ROOT) . $conf->file->dol_url_root[$key] . '/' . $path;
1356
                    }
1357
                    if ($type == 3) {
1358
                        /*global $dolibarr_main_url_root;*/
1359
1360
                        // Define $urlwithroot
1361
                        $urlwithouturlroot = preg_replace('/' . preg_quote(DOL_URL_ROOT, '/') . '$/i', '', trim($conf->file->dol_main_url_root));
1362
                        $urlwithroot = $urlwithouturlroot . DOL_URL_ROOT; // This is to use external domain name found into config file
1363
                        //$urlwithroot=DOL_MAIN_URL_ROOT;                   // This is to use same domain name than current
1364
1365
                        $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : $urlwithroot) . $conf->file->dol_url_root[$key] . '/' . $path; // Test on start with http is for old conf syntax
1366
                    }
1367
                    break;
1368
                }
1369
            }
1370
        }
1371
    }
1372
1373
    return $res;
1374
}
1375
1376
/**
1377
 *  Get properties for an object - including magic properties when requested
1378
 *
1379
 *  Only returns properties that exist
1380
 *
1381
 * @param object $obj Object to get properties from
1382
 * @param string[] $properties Optional list of properties to get.
1383
 *                                  When empty, only gets public properties.
1384
 * @return array<string,mixed>     Hash for retrieved values (key=name)
1385
 */
1386
function dol_get_object_properties($obj, $properties = [])
1387
{
1388
    // Get real properties using get_object_vars() if $properties is empty
1389
    if (empty($properties)) {
1390
        return get_object_vars($obj);
1391
    }
1392
1393
    $existingProperties = [];
1394
    $realProperties = get_object_vars($obj);
1395
1396
    // Get the real or magic property values
1397
    foreach ($properties as $property) {
1398
        if (array_key_exists($property, $realProperties)) {
1399
            // Real property, add the value
1400
            $existingProperties[$property] = $obj->{$property};
1401
        } elseif (property_exists($obj, $property)) {
1402
            // Magic property
1403
            $existingProperties[$property] = $obj->{$property};
1404
        }
1405
    }
1406
1407
    return $existingProperties;
1408
}
1409
1410
1411
/**
1412
 *  Create a clone of instance of object (new instance with same value for each properties)
1413
 *  With native = 0: Property that are references are different memory area in the new object (full isolation clone). This means $this->object of new object may not be valid (except this->db that is voluntarly kept).
1414
 *  With native = 1: Use PHP clone. Property that are reference are same pointer. This means $this->db of new object is still valid but point to same this->db than original object.
1415
 *  With native = 2: Property that are reference are different memory area in the new object (full isolation clone). Only scalar and array values are cloned. This means method are not availables and $this->db of new object is not valid.
1416
 *
1417
 * @template T of object
1418
 *
1419
 * @param T $object Object to clone
1420
 * @param int $native 0=Full isolation method, 1=Native PHP method, 2=Full isolation method keeping only scalar and array properties (recommended)
1421
 * @return T                   Clone object
1422
 * @see https://php.net/manual/language.oop5.cloning.php
1423
 * @phan-suppress PhanTypeExpectedObjectPropAccess
1424
 */
1425
function dol_clone($object, $native = 0)
1426
{
1427
    if ($native == 0) {
1428
        // deprecated method, use the method with native = 2 instead
1429
        $tmpsavdb = null;
1430
        if (isset($object->db) && isset($object->db->db) && is_object($object->db->db) && get_only_class($object->db->db) == 'PgSql\Connection') {
1431
            $tmpsavdb = $object->db;
1432
            unset($object->db);     // Such property can not be serialized with pgsl (when object->db->db = 'PgSql\Connection')
1433
        }
1434
1435
        $myclone = unserialize(serialize($object)); // serialize then unserialize is a hack to be sure to have a new object for all fields
1436
1437
        if (!empty($tmpsavdb)) {
1438
            $object->db = $tmpsavdb;
1439
        }
1440
    } elseif ($native == 2) {
1441
        // recommended method to have a full isolated cloned object
1442
        $myclone = new stdClass();
1443
        $tmparray = get_object_vars($object);   // return only public properties
1444
1445
        if (is_array($tmparray)) {
1446
            foreach ($tmparray as $propertykey => $propertyval) {
1447
                if (is_scalar($propertyval) || is_array($propertyval)) {
1448
                    $myclone->$propertykey = $propertyval;
1449
                }
1450
            }
1451
        }
1452
    } else {
1453
        $myclone = clone $object; // PHP clone is a shallow copy only, not a real clone, so properties of references will keep the reference (referring to the same target/variable)
1454
    }
1455
1456
    return $myclone;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $myclone also could return the type stdClass which is incompatible with the documented return type T.
Loading history...
1457
}
1458
1459
/**
1460
 *  Optimize a size for some browsers (phone, smarphone, ...)
1461
 *
1462
 * @param int $size Size we want
1463
 * @param string $type Type of optimizing:
1464
 *                              '' = function used to define a size for truncation
1465
 *                              'width' = function is used to define a width
1466
 * @return int                 New size after optimizing
1467
 */
1468
function dol_size($size, $type = '')
1469
{
1470
    global $conf;
1471
    if (empty($conf->dol_optimize_smallscreen)) {
1472
        return $size;
1473
    }
1474
    if ($type == 'width' && $size > 250) {
1475
        return 250;
1476
    } else {
1477
        return 10;
1478
    }
1479
}
1480
1481
1482
/**
1483
 *  Clean a string to use it as a file name.
1484
 *  Replace also '--' and ' -' strings, they are used for parameters separation (Note: ' - ' is allowed).
1485
 *
1486
 * @param string $str String to clean
1487
 * @param string $newstr String to replace bad chars with.
1488
 * @param int $unaccent 1=Remove also accent (default), 0 do not remove them
1489
 * @return string                  String cleaned (a-zA-Z_)
1490
 *
1491
 * @see            dol_string_nospecial(), dol_string_unaccent(), dol_sanitizePathName()
1492
 */
1493
function dol_sanitizeFileName($str, $newstr = '_', $unaccent = 1)
1494
{
1495
    // List of special chars for filenames in windows are defined on page https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
1496
    // Char '>' '<' '|' '$' and ';' are special chars for shells.
1497
    // Char '/' and '\' are file delimiters.
1498
    // Chars '--' can be used into filename to inject special parameters like --use-compress-program to make command with file as parameter making remote execution of command
1499
    $filesystem_forbidden_chars = array('<', '>', '/', '\\', '?', '*', '|', '"', ':', '°', '$', ';', '`');
1500
    $tmp = dol_string_nospecial($unaccent ? dol_string_unaccent($str) : $str, $newstr, $filesystem_forbidden_chars);
1501
    $tmp = preg_replace('/--+/', '_', $tmp);
1502
    $tmp = preg_replace('/\s+-([^\s])/', ' _$1', $tmp);
1503
    $tmp = preg_replace('/\s+-$/', '', $tmp);
1504
    $tmp = str_replace('..', '', $tmp);
1505
    return $tmp;
1506
}
1507
1508
1509
/**
1510
 *  Clean a string to use it as a path name. Similar to dol_sanitizeFileName but accept / and \ chars.
1511
 *  Replace also '--' and ' -' strings, they are used for parameters separation (Note: ' - ' is allowed).
1512
 *
1513
 * @param string $str String to clean
1514
 * @param string $newstr String to replace bad chars with
1515
 * @param int $unaccent 1=Remove also accent (default), 0 do not remove them
1516
 * @return string                  String cleaned (a-zA-Z_)
1517
 *
1518
 * @see            dol_string_nospecial(), dol_string_unaccent(), dol_sanitizeFileName()
1519
 */
1520
function dol_sanitizePathName($str, $newstr = '_', $unaccent = 1)
1521
{
1522
    // List of special chars for filenames in windows are defined on page https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
1523
    // Char '>' '<' '|' '$' and ';' are special chars for shells.
1524
    // Chars '--' can be used into filename to inject special parameters like --use-compress-program to make command with file as parameter making remote execution of command
1525
    $filesystem_forbidden_chars = array('<', '>', '?', '*', '|', '"', '°', '$', ';', '`');
1526
    $tmp = dol_string_nospecial($unaccent ? dol_string_unaccent($str) : $str, $newstr, $filesystem_forbidden_chars);
1527
    $tmp = preg_replace('/--+/', '_', $tmp);
1528
    $tmp = preg_replace('/\s+-([^\s])/', ' _$1', $tmp);
1529
    $tmp = preg_replace('/\s+-$/', '', $tmp);
1530
    $tmp = str_replace('..', '', $tmp);
1531
    return $tmp;
1532
}
1533
1534
/**
1535
 *  Clean a string to use it as an URL (into a href or src attribute)
1536
 *
1537
 * @param string $stringtoclean String to clean
1538
 * @param int $type 0=Accept all Url, 1=Clean external Url (keep only relative Url)
1539
 * @return     string                          Escaped string.
1540
 */
1541
function dol_sanitizeUrl($stringtoclean, $type = 1)
1542
{
1543
    // We clean string because some hacks try to obfuscate evil strings by inserting non printable chars. Example: 'java(ascci09)scr(ascii00)ipt' is processed like 'javascript' (whatever is place of evil ascii char)
1544
    // We should use dol_string_nounprintableascii but function may not be yet loaded/available
1545
    $stringtoclean = preg_replace('/[\x00-\x1F\x7F]/u', '', $stringtoclean); // /u operator makes UTF8 valid characters being ignored so are not included into the replace
1546
    // We clean html comments because some hacks try to obfuscate evil strings by inserting HTML comments. Example: on<!-- -->error=alert(1)
1547
    $stringtoclean = preg_replace('/<!--[^>]*-->/', '', $stringtoclean);
1548
1549
    $stringtoclean = str_replace('\\', '/', $stringtoclean);
1550
    if ($type == 1) {
1551
        // removing : should disable links to external url like http:aaa)
1552
        // removing ';' should disable "named" html entities encode into an url (we should not have this into an url)
1553
        $stringtoclean = str_replace(array(':', ';', '@'), '', $stringtoclean);
1554
    }
1555
1556
    do {
1557
        $oldstringtoclean = $stringtoclean;
1558
        // removing '&colon' should disable links to external url like http:aaa)
1559
        // removing '&#' should disable "numeric" html entities encode into an url (we should not have this into an url)
1560
        $stringtoclean = str_ireplace(array('javascript', 'vbscript', '&colon', '&#'), '', $stringtoclean);
1561
    } while ($oldstringtoclean != $stringtoclean);
1562
1563
    if ($type == 1) {
1564
        // removing '//' should disable links to external url like //aaa or http//)
1565
        $stringtoclean = preg_replace(array('/^[a-z]*\/\/+/i'), '', $stringtoclean);
1566
    }
1567
1568
    return $stringtoclean;
1569
}
1570
1571
/**
1572
 *  Clean a string to use it as an Email.
1573
 *
1574
 * @param string $stringtoclean String to clean. Example '[email protected] <My name>'
1575
 * @return     string                          Escaped string.
1576
 */
1577
function dol_sanitizeEmail($stringtoclean)
1578
{
1579
    do {
1580
        $oldstringtoclean = $stringtoclean;
1581
        $stringtoclean = str_ireplace(array('"', ':', '[', ']', "\n", "\r", '\\', '\/'), '', $stringtoclean);
1582
    } while ($oldstringtoclean != $stringtoclean);
1583
1584
    return $stringtoclean;
1585
}
1586
1587
/**
1588
 *  Clean a string from all accent characters to be used as ref, login or by dol_sanitizeFileName
1589
 *
1590
 * @param string $str String to clean
1591
 * @return string                  Cleaned string
1592
 *
1593
 * @see            dol_sanitizeFilename(), dol_string_nospecial()
1594
 */
1595
function dol_string_unaccent($str)
1596
{
1597
    global $conf;
1598
1599
    if (is_null($str)) {
1600
        return '';
1601
    }
1602
1603
    if (utf8_check($str)) {
1604
        if (extension_loaded('intl') && getDolGlobalString('MAIN_UNACCENT_USE_TRANSLITERATOR')) {
1605
            $transliterator = Transliterator::createFromRules(':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: NFC;', Transliterator::FORWARD);
1606
            return $transliterator->transliterate($str);
1607
        }
1608
        // See http://www.utf8-chartable.de/
1609
        $string = rawurlencode($str);
1610
        $replacements = array(
1611
            '%C3%80' => 'A', '%C3%81' => 'A', '%C3%82' => 'A', '%C3%83' => 'A', '%C3%84' => 'A', '%C3%85' => 'A',
1612
            '%C3%87' => 'C',
1613
            '%C3%88' => 'E', '%C3%89' => 'E', '%C3%8A' => 'E', '%C3%8B' => 'E',
1614
            '%C3%8C' => 'I', '%C3%8D' => 'I', '%C3%8E' => 'I', '%C3%8F' => 'I',
1615
            '%C3%91' => 'N',
1616
            '%C3%92' => 'O', '%C3%93' => 'O', '%C3%94' => 'O', '%C3%95' => 'O', '%C3%96' => 'O',
1617
            '%C5%A0' => 'S',
1618
            '%C3%99' => 'U', '%C3%9A' => 'U', '%C3%9B' => 'U', '%C3%9C' => 'U',
1619
            '%C3%9D' => 'Y', '%C5%B8' => 'y',
1620
            '%C3%A0' => 'a', '%C3%A1' => 'a', '%C3%A2' => 'a', '%C3%A3' => 'a', '%C3%A4' => 'a', '%C3%A5' => 'a',
1621
            '%C3%A7' => 'c',
1622
            '%C3%A8' => 'e', '%C3%A9' => 'e', '%C3%AA' => 'e', '%C3%AB' => 'e',
1623
            '%C3%AC' => 'i', '%C3%AD' => 'i', '%C3%AE' => 'i', '%C3%AF' => 'i',
1624
            '%C3%B1' => 'n',
1625
            '%C3%B2' => 'o', '%C3%B3' => 'o', '%C3%B4' => 'o', '%C3%B5' => 'o', '%C3%B6' => 'o',
1626
            '%C5%A1' => 's',
1627
            '%C3%B9' => 'u', '%C3%BA' => 'u', '%C3%BB' => 'u', '%C3%BC' => 'u',
1628
            '%C3%BD' => 'y', '%C3%BF' => 'y'
1629
        );
1630
        $string = strtr($string, $replacements);
1631
        return rawurldecode($string);
1632
    } else {
1633
        // See http://www.ascii-code.com/
1634
        $string = strtr(
1635
            $str,
1636
            "\xC0\xC1\xC2\xC3\xC4\xC5\xC7
1637
			\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1
1638
			\xD2\xD3\xD4\xD5\xD8\xD9\xDA\xDB\xDD
1639
			\xE0\xE1\xE2\xE3\xE4\xE5\xE7\xE8\xE9\xEA\xEB
1640
			\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF8
1641
			\xF9\xFA\xFB\xFC\xFD\xFF",
1642
            "AAAAAAC
1643
			EEEEIIIIDN
1644
			OOOOOUUUY
1645
			aaaaaaceeee
1646
			iiiidnooooo
1647
			uuuuyy"
1648
        );
1649
        $string = strtr($string, array("\xC4" => "Ae", "\xC6" => "AE", "\xD6" => "Oe", "\xDC" => "Ue", "\xDE" => "TH", "\xDF" => "ss", "\xE4" => "ae", "\xE6" => "ae", "\xF6" => "oe", "\xFC" => "ue", "\xFE" => "th"));
1650
        return $string;
1651
    }
1652
}
1653
1654
/**
1655
 *  Clean a string from all punctuation characters to use it as a ref or login.
1656
 *  This is a more complete function than dol_sanitizeFileName().
1657
 *
1658
 * @param string $str String to clean
1659
 * @param string $newstr String to replace forbidden chars with
1660
 * @param array|string $badcharstoreplace Array of forbidden characters to replace. Use '' to keep default list.
1661
 * @param array|string $badcharstoremove Array of forbidden characters to remove. Use '' to keep default list.
1662
 * @param int $keepspaces 1=Do not treat space as a special char to replace or remove
1663
 * @return string                              Cleaned string
1664
 *
1665
 * @see            dol_sanitizeFilename(), dol_string_unaccent(), dol_string_nounprintableascii()
1666
 */
1667
function dol_string_nospecial($str, $newstr = '_', $badcharstoreplace = '', $badcharstoremove = '', $keepspaces = 0)
1668
{
1669
    $forbidden_chars_to_replace = array("'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ",", ";", "=", '°', '$', ';'); // more complete than dol_sanitizeFileName
1670
    if (empty($keepspaces)) {
1671
        $forbidden_chars_to_replace[] = " ";
1672
    }
1673
    $forbidden_chars_to_remove = array();
1674
    //$forbidden_chars_to_remove=array("(",")");
1675
1676
    if (is_array($badcharstoreplace)) {
1677
        $forbidden_chars_to_replace = $badcharstoreplace;
1678
    }
1679
    if (is_array($badcharstoremove)) {
1680
        $forbidden_chars_to_remove = $badcharstoremove;
1681
    }
1682
1683
    // @phan-suppress-next-line PhanPluginSuspiciousParamOrderInternal
1684
    return str_replace($forbidden_chars_to_replace, $newstr, str_replace($forbidden_chars_to_remove, "", $str));
1685
}
1686
1687
1688
/**
1689
 *  Clean a string from all non printable ASCII chars (0x00-0x1F and 0x7F). It can also removes also Tab-CR-LF. UTF8 chars remains.
1690
 *  This can be used to sanitize a string and view its real content. Some hacks try to obfuscate attacks by inserting non printable chars.
1691
 *  Note, for information: UTF8 on 1 byte are: \x00-\7F
1692
 *                                 2 bytes are: byte 1 \xc0-\xdf, byte 2 = \x80-\xbf
1693
 *                                 3 bytes are: byte 1 \xe0-\xef, byte 2 = \x80-\xbf, byte 3 = \x80-\xbf
1694
 *                                 4 bytes are: byte 1 \xf0-\xf7, byte 2 = \x80-\xbf, byte 3 = \x80-\xbf, byte 4 = \x80-\xbf
1695
 * @param string $str String to clean
1696
 * @param int $removetabcrlf Remove also CR-LF
1697
 * @return string                      Cleaned string
1698
 *
1699
 * @see            dol_sanitizeFilename(), dol_string_unaccent(), dol_string_nospecial()
1700
 */
1701
function dol_string_nounprintableascii($str, $removetabcrlf = 1)
1702
{
1703
    if ($removetabcrlf) {
1704
        return preg_replace('/[\x00-\x1F\x7F]/u', '', $str); // /u operator makes UTF8 valid characters being ignored so are not included into the replace
1705
    } else {
1706
        return preg_replace('/[\x00-\x08\x11-\x12\x14-\x1F\x7F]/u', '', $str); // /u operator should make UTF8 valid characters being ignored so are not included into the replace
1707
    }
1708
}
1709
1710
/**
1711
 *  Returns text escaped for inclusion into javascript code
1712
 *
1713
 * @param string $stringtoescape String to escape
1714
 * @param int<0,3> $mode 0=Escape also ' and " into ', 1=Escape ' but not " for usage into 'string', 2=Escape " but not ' for usage into "string", 3=Escape ' and " with \
1715
 * @param int $noescapebackslashn 0=Escape also \n. 1=Do not escape \n.
1716
 * @return string                          Escaped string. Both ' and " are escaped into ' if they are escaped.
1717
 */
1718
function dol_escape_js($stringtoescape, $mode = 0, $noescapebackslashn = 0)
1719
{
1720
    if (is_null($stringtoescape)) {
1721
        return '';
1722
    }
1723
1724
    // escape quotes and backslashes, newlines, etc.
1725
    $substitjs = array("&#039;" => "\\'", "\r" => '\\r');
1726
    //$substitjs['</']='<\/';   // We removed this. Should be useless.
1727
    if (empty($noescapebackslashn)) {
1728
        $substitjs["\n"] = '\\n';
1729
        $substitjs['\\'] = '\\\\';
1730
    }
1731
    if (empty($mode)) {
1732
        $substitjs["'"] = "\\'";
1733
        $substitjs['"'] = "\\'";
1734
    } elseif ($mode == 1) {
1735
        $substitjs["'"] = "\\'";
1736
    } elseif ($mode == 2) {
1737
        $substitjs['"'] = '\\"';
1738
    } elseif ($mode == 3) {
1739
        $substitjs["'"] = "\\'";
1740
        $substitjs['"'] = "\\\"";
1741
    }
1742
    return strtr($stringtoescape, $substitjs);
1743
}
1744
1745
/**
1746
 *  Returns text escaped for inclusion into javascript code
1747
 *
1748
 * @param string $stringtoescape String to escape
1749
 * @return     string                          Escaped string for JSON content.
1750
 */
1751
function dol_escape_json($stringtoescape)
1752
{
1753
    return str_replace('"', '\"', $stringtoescape);
1754
}
1755
1756
/**
1757
 *  Returns text escaped for inclusion into a php string, build with double quotes " or '
1758
 *
1759
 * @param string $stringtoescape String to escape
1760
 * @param int<1,2> $stringforquotes 2=String for doublequotes, 1=String for simple quotes
1761
 * @return     string                          Escaped string for PHP content.
1762
 */
1763
function dol_escape_php($stringtoescape, $stringforquotes = 2)
1764
{
1765
    if (is_null($stringtoescape)) {
1766
        return '';
1767
    }
1768
1769
    if ($stringforquotes == 2) {
1770
        return str_replace('"', "'", $stringtoescape);
1771
    } elseif ($stringforquotes == 1) {
1772
        // We remove the \ char.
1773
        // If we allow the \ char, we can have $stringtoescape =
1774
        // abc\';phpcodedanger;  so the escapement will become
1775
        // abc\\';phpcodedanger;  and injecting this into
1776
        // $a='...' will give $ac='abc\\';phpcodedanger;
1777
        $stringtoescape = str_replace('\\', '', $stringtoescape);
1778
        return str_replace("'", "\'", str_replace('"', "'", $stringtoescape));
1779
    }
1780
1781
    return 'Bad parameter for stringforquotes in dol_escape_php';
1782
}
1783
1784
/**
1785
 *  Returns text escaped for inclusion into a XML string
1786
 *
1787
 * @param string $stringtoescape String to escape
1788
 * @return     string                          Escaped string for XML content.
1789
 */
1790
function dol_escape_xml($stringtoescape)
1791
{
1792
    return $stringtoescape;
1793
}
1794
1795
/**
1796
 * Return a string label (so on 1 line only and that should not contains any HTML) ready to be output on HTML page
1797
 * To use text that is not HTML content inside an attribute, use can simply only dol_escape_htmltag(). In doubt, use dolPrintHTMLForAttribute().
1798
 *
1799
 * @param string $s String to print
1800
 * @return  string          String ready for HTML output
1801
 */
1802
function dolPrintLabel($s)
1803
{
1804
    return dol_escape_htmltag(dol_string_nohtmltag($s, 1, 'UTF-8', 0, 0), 0, 0, '', 0, 1);
1805
}
1806
1807
/**
1808
 * Return a string (that can be on several lines) ready to be output on a HTML page.
1809
 * To output a text inside an attribute, you can use dolPrintHTMLForAttribute() or dolPrintHTMLForTextArea() inside a textarea
1810
 *
1811
 * @param string $s String to print
1812
 * @param int $allowiframe Allow iframe tags
1813
 * @return  string                  String ready for HTML output (sanitized and escape)
1814
 * @see dolPrintHTMLForAttribute(), dolPrintHTMLFortextArea()
1815
 */
1816
function dolPrintHTML($s, $allowiframe = 0)
1817
{
1818
    return dol_escape_htmltag(dol_htmlwithnojs(dol_string_onlythesehtmltags(dol_htmlentitiesbr($s), 1, 1, 1, $allowiframe)), 1, 1, 'common', 0, 1);
1819
}
1820
1821
/**
1822
 * Return a string ready to be output on an HTML attribute (alt, title, data-html, ...)
1823
 *
1824
 * @param string $s String to print
1825
 * @return  string          String ready for HTML output
1826
 * @see dolPrintHTML(), dolPrintHTMLFortextArea()
1827
 */
1828
function dolPrintHTMLForAttribute($s)
1829
{
1830
    // The dol_htmlentitiesbr will convert simple text into html
1831
    // The dol_escape_htmltag will escape html chars.
1832
    return dol_escape_htmltag(dol_string_onlythesehtmltags(dol_htmlentitiesbr($s), 1, 0, 0, 0, array('br', 'b', 'font', 'span')), 1, -1, '', 0, 1);
1833
}
1834
1835
/**
1836
 * Return a string ready to be output on input textarea
1837
 *
1838
 * @param string $s String to print
1839
 * @param int $allowiframe Allow iframe tags
1840
 * @return  string                  String ready for HTML output into a textarea
1841
 * @see dolPrintHTML(), dolPrintHTMLForAttribute()
1842
 */
1843
function dolPrintHTMLForTextArea($s, $allowiframe = 0)
1844
{
1845
    return dol_escape_htmltag(dol_htmlwithnojs(dol_string_onlythesehtmltags(dol_htmlentitiesbr($s), 1, 1, 1, $allowiframe)), 1, 1, '', 0, 1);
1846
}
1847
1848
/**
1849
 * Return a string ready to be output on an HTML attribute (alt, title, ...)
1850
 *
1851
 * @param string $s String to print
1852
 * @return  string          String ready for HTML output
1853
 */
1854
function dolPrintPassword($s)
1855
{
1856
    return htmlspecialchars($s, ENT_COMPAT, 'UTF-8');
1857
}
1858
1859
1860
/**
1861
 *  Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input fields.
1862
 *  When we need to output strings on pages, we should use:
1863
 *        - dolPrintHTML... that is dol_escape_htmltag(dol_htmlwithnojs(dol_string_onlythesehtmltags(dol_htmlentitiesbr(), 1, 1, 1)), 1, 1) for notes or descriptions into textarea, add 'common' if into a html content
1864
 *        - dolPrintPassword that is abelhtmlspecialchars( , ENT_COMPAT, 'UTF-8') for passwords.
1865
 *
1866
 * @param string $stringtoescape String to escape
1867
 * @param int $keepb 1=Replace b tags with escaped value (except if in $noescapetags), 0=Remove them completely
1868
 * @param int $keepn 1=Preserve \r\n strings, 0=Replace them with escaped value, -1=Remove them. Set to 1 when escaping for a <textarea>.
1869
 * @param string $noescapetags '' or 'common' or list of tags to not escape.
1870
 * @param int $escapeonlyhtmltags 1=Escape only html tags, not the special chars like accents.
1871
 * @param int $cleanalsojavascript Clean also javascript. @TODO switch this option to 1 by default.
1872
 * @return     string                              Escaped string
1873
 * @see        dol_string_nohtmltag(), dol_string_onlythesehtmltags(), dol_string_nospecial(), dol_string_unaccent(), dol_htmlentitiesbr()
1874
 */
1875
function dol_escape_htmltag($stringtoescape, $keepb = 0, $keepn = 0, $noescapetags = '', $escapeonlyhtmltags = 0, $cleanalsojavascript = 0)
1876
{
1877
    if ($noescapetags == 'common') {
1878
        $noescapetags = 'html,body,a,b,em,hr,i,u,ul,li,br,div,img,font,p,span,strong,table,tr,td,th,tbody,h1,h2,h3,h4,h5,h6,h7,h8,h9';
1879
        // Add also html5 tags
1880
        $noescapetags .= ',header,footer,nav,section,menu,menuitem';
1881
    }
1882
    if ($cleanalsojavascript) {
1883
        $stringtoescape = dol_string_onlythesehtmltags($stringtoescape, 0, 0, $cleanalsojavascript, 0, array(), 0);
1884
    }
1885
1886
    // escape quotes and backslashes, newlines, etc.
1887
    if ($escapeonlyhtmltags) {
1888
        $tmp = htmlspecialchars_decode((string)$stringtoescape, ENT_COMPAT);
1889
    } else {
1890
        $tmp = html_entity_decode((string)$stringtoescape, ENT_COMPAT, 'UTF-8');
1891
    }
1892
    if (!$keepb) {
1893
        $tmp = strtr($tmp, array("<b>" => '', '</b>' => '', '<strong>' => '', '</strong>' => ''));
1894
    }
1895
    if (!$keepn) {
1896
        $tmp = strtr($tmp, array("\r" => '\\r', "\n" => '\\n'));
1897
    } elseif ($keepn == -1) {
1898
        $tmp = strtr($tmp, array("\r" => '', "\n" => ''));
1899
    }
1900
1901
    if ($escapeonlyhtmltags) {
1902
        return htmlspecialchars($tmp, ENT_COMPAT, 'UTF-8');
1903
    } else {
1904
        // Escape tags to keep
1905
        $tmparrayoftags = array();
1906
        if ($noescapetags) {
1907
            $tmparrayoftags = explode(',', $noescapetags);
1908
        }
1909
        if (count($tmparrayoftags)) {
1910
            $tmp = str_ireplace('__DOUBLEQUOTE', '', $tmp); // The keyword DOUBLEQUOTE is forbidden. Reserved, so we removed it if we find it.
1911
1912
            foreach ($tmparrayoftags as $tagtoreplace) {
1913
                $tmp = preg_replace('/<' . preg_quote($tagtoreplace, '/') . '>/', '__BEGINTAGTOREPLACE' . $tagtoreplace . '__', $tmp);
1914
                $tmp = str_ireplace('</' . $tagtoreplace . '>', '__ENDTAGTOREPLACE' . $tagtoreplace . '__', $tmp);
1915
                $tmp = preg_replace('/<' . preg_quote($tagtoreplace, '/') . ' \/>/', '__BEGINENDTAGTOREPLACE' . $tagtoreplace . '__', $tmp);
1916
1917
                // For case of tag with attribute
1918
                $reg = array();
1919
                if (preg_match('/<' . preg_quote($tagtoreplace, '/') . '\s+([^>]+)>/', $tmp, $reg)) {
1920
                    $tmpattributes = str_ireplace(array('[', ']'), '_', $reg[1]);   // We must never have [ ] inside the attribute string
1921
                    $tmpattributes = str_ireplace('href="http:', '__HREFHTTPA', $tmpattributes);
1922
                    $tmpattributes = str_ireplace('href="https:', '__HREFHTTPSA', $tmpattributes);
1923
                    $tmpattributes = str_ireplace('src="http:', '__SRCHTTPIMG', $tmpattributes);
1924
                    $tmpattributes = str_ireplace('src="https:', '__SRCHTTPSIMG', $tmpattributes);
1925
                    $tmpattributes = str_ireplace('"', '__DOUBLEQUOTE', $tmpattributes);
1926
                    $tmpattributes = preg_replace('#[^a-z0-9_/?;\s=&.-]#i', '', $tmpattributes);
1927
                    $tmp = preg_replace('/<' . preg_quote($tagtoreplace, '/') . '\s+([^>]+)>/', '__BEGINTAGTOREPLACE' . $tagtoreplace . '[' . $tmpattributes . ']__', $tmp);
1928
                }
1929
                if (preg_match('/<' . preg_quote($tagtoreplace, '/') . '\s+([^>]+)> \/>/', $tmp, $reg)) {
1930
                    $tmpattributes = str_ireplace(array('[', ']'), '_', $reg[1]);   // We must not have [ ] inside the attribute string
1931
                    $tmpattributes = str_ireplace('"', '__DOUBLEQUOTE', $tmpattributes);
1932
                    $tmpattributes = preg_replace('/[^a-z0-9_\/?;\s=&]/i', '', $tmpattributes);
1933
                    $tmp = preg_replace('/<' . preg_quote($tagtoreplace, '/') . '\s+([^>]+) \/>/', '__BEGINENDTAGTOREPLACE' . $tagtoreplace . '[' . $tmpattributes . ']__', $tmp);
1934
                }
1935
            }
1936
        }
1937
1938
        $result = htmlentities($tmp, ENT_COMPAT, 'UTF-8');
1939
1940
        if (count($tmparrayoftags)) {
1941
            foreach ($tmparrayoftags as $tagtoreplace) {
1942
                $result = str_ireplace('__BEGINTAGTOREPLACE' . $tagtoreplace . '__', '<' . $tagtoreplace . '>', $result);
1943
                $result = preg_replace('/__BEGINTAGTOREPLACE' . $tagtoreplace . '\[(.*)]__/', '<' . $tagtoreplace . ' \1>', $result);
1944
                $result = str_ireplace('__ENDTAGTOREPLACE' . $tagtoreplace . '__', '</' . $tagtoreplace . '>', $result);
1945
                $result = str_ireplace('__BEGINENDTAGTOREPLACE' . $tagtoreplace . '__', '<' . $tagtoreplace . ' />', $result);
1946
                $result = preg_replace('/__BEGINENDTAGTOREPLACE' . $tagtoreplace . '\[(.*)]__/', '<' . $tagtoreplace . ' \1 />', $result);
1947
            }
1948
1949
            $result = str_ireplace('__HREFHTTPA', 'href="http:', $result);
1950
            $result = str_ireplace('__HREFHTTPSA', 'href="https:', $result);
1951
            $result = str_ireplace('__SRCHTTPIMG', 'src="http:', $result);
1952
            $result = str_ireplace('__SRCHTTPSIMG', 'src="https:', $result);
1953
            $result = str_ireplace('__DOUBLEQUOTE', '"', $result);
1954
        }
1955
1956
        return $result;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $result also could return the type array which is incompatible with the documented return type string.
Loading history...
1957
    }
1958
}
1959
1960
/**
1961
 * Convert a string to lower. Never use strtolower because it does not works with UTF8 strings.
1962
 *
1963
 * @param string $string String to encode
1964
 * @param string $encoding Character set encoding
1965
 * @return  string                          String converted
1966
 */
1967
function dol_strtolower($string, $encoding = "UTF-8")
1968
{
1969
    if (function_exists('mb_strtolower')) {
1970
        return mb_strtolower($string, $encoding);
1971
    } else {
1972
        return strtolower($string);
1973
    }
1974
}
1975
1976
/**
1977
 * Convert a string to upper. Never use strtolower because it does not works with UTF8 strings.
1978
 *
1979
 * @param string $string String to encode
1980
 * @param string $encoding Character set encoding
1981
 * @return  string                          String converted
1982
 * @see dol_ucfirst(), dol_ucwords()
1983
 */
1984
function dol_strtoupper($string, $encoding = "UTF-8")
1985
{
1986
    if (function_exists('mb_strtoupper')) {
1987
        return mb_strtoupper($string, $encoding);
1988
    } else {
1989
        return strtoupper($string);
1990
    }
1991
}
1992
1993
/**
1994
 * Convert first character of the first word of a string to upper. Never use ucfirst because it does not works with UTF8 strings.
1995
 *
1996
 * @param string $string String to encode
1997
 * @param string $encoding Character set encodign
1998
 * @return  string                      String converted
1999
 * @see dol_strtoupper(), dol_ucwords()
2000
 */
2001
function dol_ucfirst($string, $encoding = "UTF-8")
2002
{
2003
    if (function_exists('mb_substr')) {
2004
        return mb_strtoupper(mb_substr($string, 0, 1, $encoding), $encoding) . mb_substr($string, 1, null, $encoding);
2005
    } else {
2006
        return ucfirst($string);
2007
    }
2008
}
2009
2010
/**
2011
 * Convert first character of all the words of a string to upper.
2012
 *
2013
 * @param string $string String to encode
2014
 * @param string $encoding Character set encodign
2015
 * @return  string                      String converted
2016
 * @see dol_strtoupper(), dol_ucfirst()
2017
 */
2018
function dol_ucwords($string, $encoding = "UTF-8")
2019
{
2020
    if (function_exists('mb_convert_case')) {
2021
        return mb_convert_case($string, MB_CASE_TITLE, $encoding);
2022
    } else {
2023
        return ucwords($string);
2024
    }
2025
}
2026
2027
/**
2028
 *  Write log message into outputs. Possible outputs can be:
2029
 *  SYSLOG_HANDLERS = ["mod_syslog_file"]       file name is then defined by SYSLOG_FILE
2030
 *  SYSLOG_HANDLERS = ["mod_syslog_syslog"]     facility is then defined by SYSLOG_FACILITY
2031
 *  Warning, syslog functions are bugged on Windows, generating memory protection faults. To solve
2032
 *  this, use logging to files instead of syslog (see setup of module).
2033
 *  Note: If constant 'SYSLOG_FILE_NO_ERROR' defined, we never output any error message when writing to log fails.
2034
 *  Note: You can get log message into html sources by adding parameter &logtohtml=1 (constant MAIN_LOGTOHTML must be set)
2035
 *  This function works only if syslog module is enabled.
2036
 *  This must not use any call to other function calling dol_syslog (avoid infinite loop).
2037
 *
2038
 * @param string $message Line to log. ''=Show nothing
2039
 * @param int $level Log level
2040
 *                                              On Windows LOG_ERR=4, LOG_WARNING=5, LOG_NOTICE=LOG_INFO=6, LOG_DEBUG=6 if define_syslog_variables ou PHP 5.3+, 7 if dolibarr
2041
 *                                              On Linux   LOG_ERR=3, LOG_WARNING=4, LOG_NOTICE=5, LOG_INFO=6, LOG_DEBUG=7
2042
 * @param int $ident 1=Increase ident of 1 (after log), -1=Decrease ident of 1 (before log)
2043
 * @param string $suffixinfilename When output is a file, append this suffix into default log filename. Example '_stripe', '_mail'
2044
 * @param string $restricttologhandler Force output of log only to this log handler
2045
 * @param array|null $logcontext If defined, an array with extra information (can be used by some log handlers)
2046
 * @return void
2047
 * @phan-suppress PhanPluginUnknownArrayFunctionParamType  $logcontext is not defined in detail
2048
 */
2049
function dol_syslog($message, $level = LOG_INFO, $ident = 0, $suffixinfilename = '', $restricttologhandler = '', $logcontext = null)
2050
{
2051
    global $conf, $user, $debugbar;
2052
2053
    // If syslog module enabled
2054
    if (!isModEnabled('syslog')) {
2055
        return;
2056
    }
2057
2058
    // Check if we are into execution of code of a website
2059
    if (defined('USEEXTERNALSERVER') && !defined('USEDOLIBARRSERVER') && !defined('USEDOLIBARREDITOR')) {
2060
        global $website, $websitekey;
2061
        if (is_object($website) && !empty($website->ref)) {
2062
            $suffixinfilename .= '_website_' . $website->ref;
2063
        } elseif (!empty($websitekey)) {
2064
            $suffixinfilename .= '_website_' . $websitekey;
2065
        }
2066
    }
2067
2068
    // Check if we have a forced suffix
2069
    if (defined('USESUFFIXINLOG')) {
2070
        $suffixinfilename .= constant('USESUFFIXINLOG');
2071
    }
2072
2073
    if ($ident < 0) {
2074
        foreach ($conf->loghandlers as $loghandlerinstance) {
2075
            $loghandlerinstance->setIdent($ident);
2076
        }
2077
    }
2078
2079
    if (!empty($message)) {
2080
        // Test log level
2081
        // @phan-suppress-next-line PhanPluginDuplicateArrayKey
2082
        $logLevels = array(LOG_EMERG => 'EMERG', LOG_ALERT => 'ALERT', LOG_CRIT => 'CRITICAL', LOG_ERR => 'ERR', LOG_WARNING => 'WARN', LOG_NOTICE => 'NOTICE', LOG_INFO => 'INFO', LOG_DEBUG => 'DEBUG');
2083
        if (!array_key_exists($level, $logLevels)) {
2084
            throw new Exception('Incorrect log level');
2085
        }
2086
        if ($level > getDolGlobalInt('SYSLOG_LEVEL')) {
2087
            return;
2088
        }
2089
2090
        if (!getDolGlobalString('MAIN_SHOW_PASSWORD_INTO_LOG')) {
2091
            $message = preg_replace('/password=\'[^\']*\'/', 'password=\'hidden\'', $message); // protection to avoid to have value of password in log
2092
        }
2093
2094
        // If adding log inside HTML page is required
2095
        if (
2096
            (!empty($_REQUEST['logtohtml']) && getDolGlobalString('MAIN_ENABLE_LOG_TO_HTML'))
2097
            || (is_object($user) && $user->hasRight('debugbar', 'read') && is_object($debugbar))
2098
        ) {
2099
            $ospid = sprintf("%7s", dol_trunc(getmypid(), 7, 'right', 'UTF-8', 1));
2100
            $osuser = " " . sprintf("%6s", dol_trunc(function_exists('posix_getuid') ? posix_getuid() : '', 6, 'right', 'UTF-8', 1));
2101
2102
            $conf->logbuffer[] = dol_print_date(time(), "%Y-%m-%d %H:%M:%S") . " " . sprintf("%-7s", $logLevels[$level]) . " " . $ospid . " " . $osuser . " " . $message;
2103
        }
2104
2105
        //TODO: Remove this. MAIN_ENABLE_LOG_INLINE_HTML should be deprecated and use a log handler dedicated to HTML output
2106
        // If html log tag enabled and url parameter log defined, we show output log on HTML comments
2107
        if (getDolGlobalString('MAIN_ENABLE_LOG_INLINE_HTML') && GETPOSTINT("log")) {
2108
            print "\n\n<!-- Log start\n";
2109
            print dol_escape_htmltag($message) . "\n";
2110
            print "Log end -->\n";
2111
        }
2112
2113
        $data = array(
2114
            'message' => $message,
2115
            'script' => (isset($_SERVER['PHP_SELF']) ? basename($_SERVER['PHP_SELF'], '.php') : false),
2116
            'level' => $level,
2117
            'user' => ((is_object($user) && $user->id) ? $user->login : false),
2118
            'ip' => false,
2119
            'osuser' => function_exists('posix_getuid') ? posix_getuid() : false,
2120
            'ospid' => getmypid()   // on linux, max value is defined into cat /proc/sys/kernel/pid_max
2121
        );
2122
2123
        $remoteip = getUserRemoteIP(); // Get ip when page run on a web server
2124
        if (!empty($remoteip)) {
2125
            $data['ip'] = $remoteip;
2126
            // This is when server run behind a reverse proxy
2127
            if (!empty($_SERVER['HTTP_X_FORWARDED_FOR']) && $_SERVER['HTTP_X_FORWARDED_FOR'] != $remoteip) {
2128
                $data['ip'] = $_SERVER['HTTP_X_FORWARDED_FOR'] . ' -> ' . $data['ip'];
2129
            } elseif (!empty($_SERVER['HTTP_CLIENT_IP']) && $_SERVER['HTTP_CLIENT_IP'] != $remoteip) {
2130
                $data['ip'] = $_SERVER['HTTP_CLIENT_IP'] . ' -> ' . $data['ip'];
2131
            }
2132
        } elseif (!empty($_SERVER['SERVER_ADDR'])) {
2133
            // This is when PHP session is ran inside a web server but not inside a client request (example: init code of apache)
2134
            $data['ip'] = $_SERVER['SERVER_ADDR'];
2135
        } elseif (!empty($_SERVER['COMPUTERNAME'])) {
2136
            // This is when PHP session is ran outside a web server, like from Windows command line (Not always defined, but useful if OS defines it).
2137
            $data['ip'] = $_SERVER['COMPUTERNAME'];
2138
        } else {
2139
            $data['ip'] = '???';
2140
        }
2141
2142
        if (!empty($_SERVER['USERNAME'])) {
2143
            // This is when PHP session is ran outside a web server, like from Linux command line (Not always defined, but useful if OS defines it).
2144
            $data['osuser'] = $_SERVER['USERNAME'];
2145
        } elseif (!empty($_SERVER['LOGNAME'])) {
2146
            // This is when PHP session is ran outside a web server, like from Linux command line (Not always defined, but useful if OS defines it).
2147
            $data['osuser'] = $_SERVER['LOGNAME'];
2148
        }
2149
2150
        // Loop on each log handler and send output
2151
        foreach ($conf->loghandlers as $loghandlerinstance) {
2152
            if ($restricttologhandler && $loghandlerinstance->code != $restricttologhandler) {
2153
                continue;
2154
            }
2155
            $loghandlerinstance->export($data, $suffixinfilename);
2156
        }
2157
        unset($data);
2158
    }
2159
2160
    if ($ident > 0) {
2161
        foreach ($conf->loghandlers as $loghandlerinstance) {
2162
            $loghandlerinstance->setIdent($ident);
2163
        }
2164
    }
2165
}
2166
2167
/**
2168
 *  Show tab header of a card
2169
 *
2170
 * @param array<string,array<int<0,5>,string>> $links Array of tabs (0=>url, 1=>label, 2=>code, 3=>not used, 4=>text after link, 5=>morecssonlink). Currently initialized by calling a function xxx_admin_prepare_head. Note that label into $links[$i][1] must be already HTML escaped.
0 ignored issues
show
Documentation Bug introduced by
The doc comment array<string,array<int<0,5>,string>> at position 6 could not be parsed: Expected '>' at position 6, but found 'int'.
Loading history...
2171
 * @param string $active Active tab name (document', 'info', 'ldap', ....)
2172
 * @param string $title Title
2173
 * @param int $notab -1 or 0=Add tab header, 1=no tab header (if you set this to 1, using print dol_get_fiche_end() to close tab is not required), -2=Add tab header with no sepaaration under tab (to start a tab just after), -3=Add tab header but no footer separation
2174
 * @param string $picto Add a picto on tab title
2175
 * @param int $pictoisfullpath If 1, image path is a full path. If you set this to 1, you can use url returned by dol_buildpath('/mymodyle/img/myimg.png',1) for $picto.
2176
 * @param string $morehtmlright Add more html content on right of tabs title
2177
 * @param string $morecss More Css
2178
 * @param int $limittoshow Limit number of tabs to show. Use 0 to use automatic default value.
2179
 * @param string $moretabssuffix A suffix to use when you have several dol_get_fiche_head() in same page
2180
 * @return void
2181
 * @deprecated Use print dol_get_fiche_head() instead
2182
 */
2183
function dol_fiche_head($links = array(), $active = '0', $title = '', $notab = 0, $picto = '', $pictoisfullpath = 0, $morehtmlright = '', $morecss = '', $limittoshow = 0, $moretabssuffix = '')
2184
{
2185
    print dol_get_fiche_head($links, $active, $title, $notab, $picto, $pictoisfullpath, $morehtmlright, $morecss, $limittoshow, $moretabssuffix);
2186
}
2187
2188
/**
2189
 *  Show tab footer of a card
2190
 *
2191
 * @param int<-1,1> $notab -1 or 0=Add tab footer, 1=no tab footer
2192
 * @return void
2193
 * @deprecated Use print dol_get_fiche_end() instead
2194
 */
2195
function dol_fiche_end($notab = 0)
2196
{
2197
    print dol_get_fiche_end($notab);
2198
}
2199
2200
/**
2201
 *  Return tab footer of a card
2202
 *
2203
 * @param int<-1,1> $notab -1 or 0=Add tab footer, 1=no tab footer
2204
 * @return string
2205
 */
2206
function dol_get_fiche_end($notab = 0)
2207
{
2208
    if (!$notab || $notab == -1) {
2209
        return "\n</div>\n";
2210
    } else {
2211
        return '';
2212
    }
2213
}
2214
2215
/**
2216
 *      Return a formatted address (part address/zip/town/state) according to country rules.
2217
 *      See https://en.wikipedia.org/wiki/Address
2218
 *
2219
 * @param Object $object A company or contact object
2220
 * @param int $withcountry 1=Add country into address string
2221
 * @param string $sep Separator to use to separate info when building string
2222
 * @param  ?Translate $outputlangs Object lang that contains language for text translation.
2223
 * @param int $mode 0=Standard output, 1=Remove address
2224
 * @param string $extralangcode User extralanguage $langcode as values for address, town
2225
 * @return string                      Formatted string
2226
 * @see dol_print_address()
2227
 */
2228
function dol_format_address($object, $withcountry = 0, $sep = "\n", $outputlangs = null, $mode = 0, $extralangcode = '')
2229
{
2230
    global $langs, $hookmanager;
2231
2232
    $ret = '';
2233
    $countriesusingstate = array('AU', 'CA', 'US', 'IN', 'GB', 'ES', 'UK', 'TR', 'CN'); // See also MAIN_FORCE_STATE_INTO_ADDRESS
2234
2235
    // See format of addresses on https://en.wikipedia.org/wiki/Address
2236
    // Address
2237
    if (empty($mode)) {
2238
        $ret .= ($extralangcode ? $object->array_languages['address'][$extralangcode] : (empty($object->address) ? '' : preg_replace('/(\r\n|\r|\n)+/', $sep, $object->address)));
2239
    }
2240
    // Zip/Town/State
2241
    if (isset($object->country_code) && in_array($object->country_code, array('AU', 'CA', 'US', 'CN')) || getDolGlobalString('MAIN_FORCE_STATE_INTO_ADDRESS')) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (IssetNode && in_array($...CE_STATE_INTO_ADDRESS'), Probably Intended Meaning: IssetNode && (in_array($...E_STATE_INTO_ADDRESS'))
Loading history...
2242
        // US: title firstname name \n address lines \n town, state, zip \n country
2243
        $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2244
        $ret .= (($ret && $town) ? $sep : '') . $town;
2245
2246
        if (!empty($object->state)) {
2247
            $ret .= ($ret ? ($town ? ", " : $sep) : '') . $object->state;
2248
        }
2249
        if (!empty($object->zip)) {
2250
            $ret .= ($ret ? (($town || $object->state) ? ", " : $sep) : '') . $object->zip;
2251
        }
2252
    } elseif (isset($object->country_code) && in_array($object->country_code, array('GB', 'UK'))) {
2253
        // UK: title firstname name \n address lines \n town state \n zip \n country
2254
        $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2255
        $ret .= ($ret ? $sep : '') . $town;
2256
        if (!empty($object->state)) {
2257
            $ret .= ($ret ? ", " : '') . $object->state;
2258
        }
2259
        if (!empty($object->zip)) {
2260
            $ret .= ($ret ? $sep : '') . $object->zip;
2261
        }
2262
    } elseif (isset($object->country_code) && in_array($object->country_code, array('ES', 'TR'))) {
2263
        // ES: title firstname name \n address lines \n zip town \n state \n country
2264
        $ret .= ($ret ? $sep : '') . $object->zip;
2265
        $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2266
        $ret .= ($town ? (($object->zip ? ' ' : '') . $town) : '');
2267
        if (!empty($object->state)) {
2268
            $ret .= $sep . $object->state;
2269
        }
2270
    } elseif (isset($object->country_code) && $object->country_code == 'JP') {
2271
        // JP: In romaji, title firstname name\n address lines \n [state,] town zip \n country
2272
        // See https://www.sljfaq.org/afaq/addresses.html
2273
        $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2274
        $ret .= ($ret ? $sep : '') . ($object->state ? $object->state . ', ' : '') . $town . ($object->zip ? ' ' : '') . $object->zip;
2275
    } elseif (isset($object->country_code) && $object->country_code == 'IT') {
2276
        // IT: title firstname name\n address lines \n zip town state_code \n country
2277
        $ret .= ($ret ? $sep : '') . $object->zip;
2278
        $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2279
        $ret .= ($town ? (($object->zip ? ' ' : '') . $town) : '');
2280
        $ret .= (empty($object->state_code) ? '' : (' ' . $object->state_code));
2281
    } else {
2282
        // Other: title firstname name \n address lines \n zip town[, state] \n country
2283
        $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2284
        $ret .= !empty($object->zip) ? (($ret ? $sep : '') . $object->zip) : '';
2285
        $ret .= ($town ? (($object->zip ? ' ' : ($ret ? $sep : '')) . $town) : '');
2286
        if (!empty($object->state) && in_array($object->country_code, $countriesusingstate)) {
2287
            $ret .= ($ret ? ", " : '') . $object->state;
2288
        }
2289
    }
2290
2291
    if (!is_object($outputlangs)) {
2292
        $outputlangs = $langs;
2293
    }
2294
    if ($withcountry) {
2295
        $langs->load("dict");
2296
        $ret .= (empty($object->country_code) ? '' : ($ret ? $sep : '') . $outputlangs->convToOutputCharset($outputlangs->transnoentitiesnoconv("Country" . $object->country_code)));
0 ignored issues
show
Bug introduced by
The method transnoentitiesnoconv() does not exist on null. ( Ignorable by Annotation )

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

2296
        $ret .= (empty($object->country_code) ? '' : ($ret ? $sep : '') . $outputlangs->convToOutputCharset($outputlangs->/** @scrutinizer ignore-call */ transnoentitiesnoconv("Country" . $object->country_code)));

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
2297
    }
2298
    if ($hookmanager) {
2299
        $parameters = array('withcountry' => $withcountry, 'sep' => $sep, 'outputlangs' => $outputlangs, 'mode' => $mode, 'extralangcode' => $extralangcode);
2300
        $reshook = $hookmanager->executeHooks('formatAddress', $parameters, $object);
2301
        if ($reshook > 0) {
2302
            $ret = '';
2303
        }
2304
        $ret .= $hookmanager->resPrint;
2305
    }
2306
2307
    return $ret;
2308
}
2309
2310
2311
/**
2312
 *  Format a string.
2313
 *
2314
 * @param string $fmt Format of strftime function (http://php.net/manual/fr/function.strftime.php)
2315
 * @param int|false $ts Timestamp (If is_gmt is true, timestamp is already includes timezone and daylight saving offset, if is_gmt is false, timestamp is a GMT timestamp and we must compensate with server PHP TZ)
2316
 * @param bool $is_gmt See comment of timestamp parameter
2317
 * @return string                  A formatted string
2318
 * @see dol_stringtotime()
2319
 */
2320
function dol_strftime($fmt, $ts = false, $is_gmt = false)
2321
{
2322
    if ((abs($ts) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range
2323
        return dol_print_date($ts, $fmt, $is_gmt);
2324
    } else {
2325
        return 'Error date outside supported range';
2326
    }
2327
}
2328
2329
/**
2330
 *  Output date in a string format according to outputlangs (or langs if not defined).
2331
 *  Return charset is always UTF-8, except if encodetoouput is defined. In this case charset is output charset
2332
 *
2333
 * @param int|string $time GM Timestamps date
2334
 * @param string $format Output date format (tag of strftime function)
2335
 *                                      "%d %b %Y",
2336
 *                                      "%d/%m/%Y %H:%M",
2337
 *                                      "%d/%m/%Y %H:%M:%S",
2338
 *                                      "%B"=Long text of month, "%A"=Long text of day, "%b"=Short text of month, "%a"=Short text of day
2339
 *                                      "day", "daytext", "dayhour", "dayhourldap", "dayhourtext", "dayrfc", "dayhourrfc", "...inputnoreduce", "...reduceformat"
2340
 * @param string|bool $tzoutput true or 'gmt' => string is for Greenwich location
2341
 *                                      false or 'tzserver' => output string is for local PHP server TZ usage
2342
 *                                      'tzuser' => output string is for user TZ (current browser TZ with current dst) => In a future, we should have same behaviour than 'tzuserrel'
2343
 *                                      'tzuserrel' => output string is for user TZ (current browser TZ with dst or not, depending on date position)
2344
 * @param Translate $outputlangs Object lang that contains language for text translation.
2345
 * @param boolean $encodetooutput false=no convert into output pagecode
2346
 * @return string                      Formatted date or '' if time is null
2347
 *
2348
 * @throws DateInvalidTimeZoneException
2349
 * @throws DateInvalidTimeZoneException
2350
 * @see        dol_mktime(), dol_stringtotime(), dol_getdate(), selectDate()
2351
 */
2352
function dol_print_date($time, $format = '', $tzoutput = 'auto', $outputlangs = null, $encodetooutput = false)
2353
{
2354
    global $conf, $langs;
2355
2356
    // If date undefined or "", we return ""
2357
    if (dol_strlen($time) == 0) {
2358
        return ''; // $time=0 allowed (it means 01/01/1970 00:00:00)
2359
    }
2360
2361
    if ($tzoutput === 'auto') {
2362
        $tzoutput = (empty($conf) ? 'tzserver' : ($conf->tzuserinputkey ?? 'tzserver'));
2363
    }
2364
2365
    // Clean parameters
2366
    $to_gmt = false;
2367
    $offsettz = $offsetdst = 0;
2368
    if ($tzoutput) {
2369
        $to_gmt = true; // For backward compatibility
2370
        if (is_string($tzoutput)) {
2371
            if ($tzoutput == 'tzserver') {
2372
                $to_gmt = false;
2373
                $offsettzstring = @date_default_timezone_get(); // Example 'Europe/Berlin' or 'Indian/Reunion'
2374
                // @phan-suppress-next-line PhanPluginRedundantAssignment
2375
                $offsettz = 0;  // Timezone offset with server timezone (because to_gmt is false), so 0
2376
                // @phan-suppress-next-line PhanPluginRedundantAssignment
2377
                $offsetdst = 0; // Dst offset with server timezone (because to_gmt is false), so 0
2378
            } elseif ($tzoutput == 'tzuser' || $tzoutput == 'tzuserrel') {
2379
                $to_gmt = true;
2380
                $offsettzstring = (empty($_SESSION['dol_tz_string']) ? 'UTC' : $_SESSION['dol_tz_string']); // Example 'Europe/Berlin' or 'Indian/Reunion'
2381
2382
                if (class_exists('DateTimeZone')) {
2383
                    $user_date_tz = new DateTimeZone($offsettzstring);
2384
                    $user_dt = new DateTime();
2385
                    $user_dt->setTimezone($user_date_tz);
2386
                    $user_dt->setTimestamp($tzoutput == 'tzuser' ? dol_now() : (int)$time);
2387
                    $offsettz = $user_dt->getOffset();  // should include dst ?
2388
                } else {    // with old method (The 'tzuser' was processed like the 'tzuserrel')
2389
                    $offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60; // Will not be used anymore
2390
                    $offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60; // Will not be used anymore
2391
                }
2392
            }
2393
        }
2394
    }
2395
    if (!is_object($outputlangs)) {
2396
        $outputlangs = $langs;
2397
    }
2398
    if (!$format) {
2399
        $format = 'daytextshort';
2400
    }
2401
2402
    // Do we have to reduce the length of date (year on 2 chars) to save space.
2403
    // Note: dayinputnoreduce is same than day but no reduction of year length will be done
2404
    $reduceformat = (!empty($conf->dol_optimize_smallscreen) && in_array($format, array('day', 'dayhour', 'dayhoursec'))) ? 1 : 0;  // Test on original $format param.
2405
    $format = preg_replace('/inputnoreduce/', '', $format); // so format 'dayinputnoreduce' is processed like day
2406
    $formatwithoutreduce = preg_replace('/reduceformat/', '', $format);
2407
    if ($formatwithoutreduce != $format) {
2408
        $format = $formatwithoutreduce;
2409
        $reduceformat = 1;
2410
    }  // so format 'dayreduceformat' is processed like day
2411
2412
    // Change predefined format into computer format. If found translation in lang file we use it, otherwise we use default.
2413
    // TODO Add format daysmallyear and dayhoursmallyear
2414
    if ($format == 'day') {
2415
        $format = ($outputlangs->trans("FormatDateShort") != "FormatDateShort" ? $outputlangs->trans("FormatDateShort") : $conf->format_date_short);
2416
    } elseif ($format == 'hour') {
2417
        $format = ($outputlangs->trans("FormatHourShort") != "FormatHourShort" ? $outputlangs->trans("FormatHourShort") : $conf->format_hour_short);
2418
    } elseif ($format == 'hourduration') {
2419
        $format = ($outputlangs->trans("FormatHourShortDuration") != "FormatHourShortDuration" ? $outputlangs->trans("FormatHourShortDuration") : $conf->format_hour_short_duration);
2420
    } elseif ($format == 'daytext') {
2421
        $format = ($outputlangs->trans("FormatDateText") != "FormatDateText" ? $outputlangs->trans("FormatDateText") : $conf->format_date_text);
2422
    } elseif ($format == 'daytextshort') {
2423
        $format = ($outputlangs->trans("FormatDateTextShort") != "FormatDateTextShort" ? $outputlangs->trans("FormatDateTextShort") : $conf->format_date_text_short);
2424
    } elseif ($format == 'dayhour') {
2425
        $format = ($outputlangs->trans("FormatDateHourShort") != "FormatDateHourShort" ? $outputlangs->trans("FormatDateHourShort") : $conf->format_date_hour_short);
2426
    } elseif ($format == 'dayhoursec') {
2427
        $format = ($outputlangs->trans("FormatDateHourSecShort") != "FormatDateHourSecShort" ? $outputlangs->trans("FormatDateHourSecShort") : $conf->format_date_hour_sec_short);
2428
    } elseif ($format == 'dayhourtext') {
2429
        $format = ($outputlangs->trans("FormatDateHourText") != "FormatDateHourText" ? $outputlangs->trans("FormatDateHourText") : $conf->format_date_hour_text);
2430
    } elseif ($format == 'dayhourtextshort') {
2431
        $format = ($outputlangs->trans("FormatDateHourTextShort") != "FormatDateHourTextShort" ? $outputlangs->trans("FormatDateHourTextShort") : $conf->format_date_hour_text_short);
2432
    } elseif ($format == 'dayhourlog') {
2433
        // Format not sensitive to language
2434
        $format = '%Y%m%d%H%M%S';
2435
    } elseif ($format == 'dayhourlogsmall') {
2436
        // Format not sensitive to language
2437
        $format = '%y%m%d%H%M';
2438
    } elseif ($format == 'dayhourldap') {
2439
        $format = '%Y%m%d%H%M%SZ';
2440
    } elseif ($format == 'dayhourxcard') {
2441
        $format = '%Y%m%dT%H%M%SZ';
2442
    } elseif ($format == 'dayxcard') {
2443
        $format = '%Y%m%d';
2444
    } elseif ($format == 'dayrfc') {
2445
        $format = '%Y-%m-%d'; // DATE_RFC3339
2446
    } elseif ($format == 'dayhourrfc') {
2447
        $format = '%Y-%m-%dT%H:%M:%SZ'; // DATETIME RFC3339
2448
    } elseif ($format == 'standard') {
2449
        $format = '%Y-%m-%d %H:%M:%S';
2450
    }
2451
2452
    if ($reduceformat) {
2453
        $format = str_replace('%Y', '%y', $format);
2454
        $format = str_replace('yyyy', 'yy', $format);
2455
    }
2456
2457
    // Clean format
2458
    if (preg_match('/%b/i', $format)) {     // There is some text to translate
2459
        // We inhibit translation to text made by strftime functions. We will use trans instead later.
2460
        $format = str_replace('%b', '__b__', $format);
2461
        $format = str_replace('%B', '__B__', $format);
2462
    }
2463
    if (preg_match('/%a/i', $format)) {     // There is some text to translate
2464
        // We inhibit translation to text made by strftime functions. We will use trans instead later.
2465
        $format = str_replace('%a', '__a__', $format);
2466
        $format = str_replace('%A', '__A__', $format);
2467
    }
2468
2469
    // Analyze date
2470
    $reg = array();
2471
    if (preg_match('/^([0-9][0-9][0-9][0-9])([0-9][0-9])([0-9][0-9])([0-9][0-9])([0-9][0-9])([0-9][0-9])$/i', (string)$time, $reg)) {  // Deprecated. Ex: 1970-01-01, 1970-01-01 01:00:00, 19700101010000
2472
        dol_print_error(null, "Functions.lib::dol_print_date function called with a bad value from page " . (empty($_SERVER["PHP_SELF"]) ? 'unknown' : $_SERVER["PHP_SELF"]));
2473
        return '';
2474
    } elseif (preg_match('/^([0-9]+)-([0-9]+)-([0-9]+) ?([0-9]+)?:?([0-9]+)?:?([0-9]+)?/i', (string)$time, $reg)) {    // Still available to solve problems in extrafields of type date
2475
        // This part of code should not be used anymore.
2476
        dol_syslog("Functions.lib::dol_print_date function called with a bad value from page " . (empty($_SERVER["PHP_SELF"]) ? 'unknown' : $_SERVER["PHP_SELF"]), LOG_WARNING);
2477
        //if (function_exists('debug_print_backtrace')) debug_print_backtrace();
2478
        // Date has format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'
2479
        $syear = (!empty($reg[1]) ? $reg[1] : '');
2480
        $smonth = (!empty($reg[2]) ? $reg[2] : '');
2481
        $sday = (!empty($reg[3]) ? $reg[3] : '');
2482
        $shour = (!empty($reg[4]) ? $reg[4] : '');
2483
        $smin = (!empty($reg[5]) ? $reg[5] : '');
2484
        $ssec = (!empty($reg[6]) ? $reg[6] : '');
2485
2486
        $time = dol_mktime($shour, $smin, $ssec, $smonth, $sday, $syear, true);
2487
2488
        if ($to_gmt) {
2489
            $tzo = new DateTimeZone('UTC'); // when to_gmt is true, base for offsettz and offsetdst (so timetouse) is UTC
2490
        } else {
2491
            $tzo = new DateTimeZone(date_default_timezone_get());   // when to_gmt is false, base for offsettz and offsetdst (so timetouse) is PHP server
2492
        }
2493
        $dtts = new DateTime();
2494
        $dtts->setTimestamp($time);
2495
        $dtts->setTimezone($tzo);
2496
        $newformat = str_replace(
2497
            array('%Y', '%y', '%m', '%d', '%H', '%I', '%M', '%S', '%p', 'T', 'Z', '__a__', '__A__', '__b__', '__B__'),
2498
            array('Y', 'y', 'm', 'd', 'H', 'h', 'i', 's', 'A', '__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
2499
            $format
2500
        );
2501
        $ret = $dtts->format($newformat);
2502
        $ret = str_replace(
2503
            array('__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
2504
            array('T', 'Z', '__a__', '__A__', '__b__', '__B__'),
2505
            $ret
2506
        );
2507
    } else {
2508
        // Date is a timestamps
2509
        if ($time < 100000000000) { // Protection against bad date values
2510
            $timetouse = $time + $offsettz + $offsetdst; // TODO We could be able to disable use of offsettz and offsetdst to use only offsettzstring.
2511
2512
            if ($to_gmt) {
2513
                $tzo = new DateTimeZone('UTC'); // when to_gmt is true, base for offsettz and offsetdst (so timetouse) is UTC
2514
            } else {
2515
                $tzo = new DateTimeZone(date_default_timezone_get());   // when to_gmt is false, base for offsettz and offsetdst (so timetouse) is PHP server
2516
            }
2517
            $dtts = new DateTime();
2518
            $dtts->setTimestamp($timetouse);
2519
            $dtts->setTimezone($tzo);
2520
            $newformat = str_replace(
2521
                array('%Y', '%y', '%m', '%d', '%H', '%I', '%M', '%S', '%p', '%w', 'T', 'Z', '__a__', '__A__', '__b__', '__B__'),
2522
                array('Y', 'y', 'm', 'd', 'H', 'h', 'i', 's', 'A', 'w', '__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
2523
                $format
2524
            );
2525
            $ret = $dtts->format($newformat);
2526
            $ret = str_replace(
2527
                array('__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
2528
                array('T', 'Z', '__a__', '__A__', '__b__', '__B__'),
2529
                $ret
2530
            );
2531
            //var_dump($ret);exit;
2532
        } else {
2533
            $ret = 'Bad value ' . $time . ' for date';
2534
        }
2535
    }
2536
2537
    if (preg_match('/__b__/i', $format)) {
2538
        $timetouse = $time + $offsettz + $offsetdst; // TODO We could be able to disable use of offsettz and offsetdst to use only offsettzstring.
2539
2540
        if ($to_gmt) {
2541
            $tzo = new DateTimeZone('UTC'); // when to_gmt is true, base for offsettz and offsetdst (so timetouse) is UTC
2542
        } else {
2543
            $tzo = new DateTimeZone(date_default_timezone_get());   // when to_gmt is false, base for offsettz and offsetdst (so timetouse) is PHP server
2544
        }
2545
        $dtts = new DateTime();
2546
        $dtts->setTimestamp($timetouse);
2547
        $dtts->setTimezone($tzo);
2548
        $month = (int)$dtts->format("m");
2549
        $month = sprintf("%02d", $month); // $month may be return with format '06' on some installation and '6' on other, so we force it to '06'.
2550
        if ($encodetooutput) {
2551
            $monthtext = $outputlangs->transnoentities('Month' . $month);
2552
            $monthtextshort = $outputlangs->transnoentities('MonthShort' . $month);
2553
        } else {
2554
            $monthtext = $outputlangs->transnoentitiesnoconv('Month' . $month);
2555
            $monthtextshort = $outputlangs->transnoentitiesnoconv('MonthShort' . $month);
2556
        }
2557
        //print 'monthtext='.$monthtext.' monthtextshort='.$monthtextshort;
2558
        $ret = str_replace('__b__', $monthtextshort, $ret);
2559
        $ret = str_replace('__B__', $monthtext, $ret);
2560
        //print 'x'.$outputlangs->charset_output.'-'.$ret.'x';
2561
        //return $ret;
2562
    }
2563
    if (preg_match('/__a__/i', $format)) {
2564
        //print "time=$time offsettz=$offsettz offsetdst=$offsetdst offsettzstring=$offsettzstring";
2565
        $timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
2566
2567
        if ($to_gmt) {
2568
            $tzo = new DateTimeZone('UTC');
2569
        } else {
2570
            $tzo = new DateTimeZone(date_default_timezone_get());
2571
        }
2572
        $dtts = new DateTime();
2573
        $dtts->setTimestamp($timetouse);
2574
        $dtts->setTimezone($tzo);
2575
        $w = $dtts->format("w");
2576
        $dayweek = $outputlangs->transnoentitiesnoconv('Day' . $w);
2577
2578
        $ret = str_replace('__A__', $dayweek, $ret);
2579
        $ret = str_replace('__a__', dol_substr($dayweek, 0, 3), $ret);
2580
    }
2581
2582
    return $ret;
2583
}
2584
2585
2586
/**
2587
 *  Return an array with locale date info.
2588
 *  WARNING: This function use PHP server timezone by default to return locale information.
2589
 *  Be aware to add the third parameter to "UTC" if you need to work on UTC.
2590
 *
2591
 * @param int $timestamp Timestamp
2592
 * @param boolean $fast Fast mode. deprecated.
2593
 * @param string $forcetimezone '' to use the PHP server timezone. Or use a form like 'gmt', 'Europe/Paris' or '+0200' to force timezone.
2594
 * @return array{}|array{seconds:int<0,59>,minutes:int<0,59>,hours:int<0,23>,mday:int<1,31>,wday:int<0,6>,mon:int<1,12>,year:int<0,9999>,yday:int<0,366>,0:int}                        Array of information
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{}|array{seconds:in...,yday:int<0,366>,0:int} at position 8 could not be parsed: Expected '}' at position 8, but found 'int'.
Loading history...
2595
 *                                      'seconds' => $secs,
2596
 *                                      'minutes' => $min,
2597
 *                                      'hours' => $hour,
2598
 *                                      'mday' => $day,
2599
 *                                      'wday' => $dow,     0=sunday, 6=saturday
2600
 *                                      'mon' => $month,
2601
 *                                      'year' => $year,
2602
 *                                      'yday' => floor($secsInYear/$_day_power)
2603
 *                                      '0' => original timestamp
2604
 * @throws DateInvalidTimeZoneException
2605
 * @see                                dol_print_date(), dol_stringtotime(), dol_mktime()
2606
 */
2607
function dol_getdate($timestamp, $fast = false, $forcetimezone = '')
2608
{
2609
    if ($timestamp === '') {
2610
        return array();
2611
    }
2612
2613
    $datetimeobj = new DateTime();
2614
    $datetimeobj->setTimestamp($timestamp); // Use local PHP server timezone
2615
    if ($forcetimezone) {
2616
        $datetimeobj->setTimezone(new DateTimeZone($forcetimezone == 'gmt' ? 'UTC' : $forcetimezone)); //  (add timezone relative to the date entered)
2617
    }
2618
    $arrayinfo = array(
2619
        'year' => ((int)date_format($datetimeobj, 'Y')),
2620
        'mon' => ((int)date_format($datetimeobj, 'm')),
2621
        'mday' => ((int)date_format($datetimeobj, 'd')),
2622
        'wday' => ((int)date_format($datetimeobj, 'w')),
2623
        'yday' => ((int)date_format($datetimeobj, 'z')),
2624
        'hours' => ((int)date_format($datetimeobj, 'H')),
2625
        'minutes' => ((int)date_format($datetimeobj, 'i')),
2626
        'seconds' => ((int)date_format($datetimeobj, 's')),
2627
        '0' => $timestamp
2628
    );
2629
2630
    return $arrayinfo;
2631
}
2632
2633
/**
2634
 *  Return a timestamp date built from detailed information (by default a local PHP server timestamp)
2635
 *  Replace function mktime not available under Windows if year < 1970
2636
 *  PHP mktime is restricted to the years 1901-2038 on Unix and 1970-2038 on Windows
2637
 *
2638
 * @param int $hour Hour    (can be -1 for undefined)
2639
 * @param int $minute Minute  (can be -1 for undefined)
2640
 * @param int $second Second  (can be -1 for undefined)
2641
 * @param int $month Month (1 to 12)
2642
 * @param int $day Day (1 to 31)
2643
 * @param int $year Year
2644
 * @param mixed $gm True or 1 or 'gmt'=Input information are GMT values
2645
 *                                      False or 0 or 'tzserver' = local to server TZ
2646
 *                                      'auto'
2647
 *                                      'tzuser' = local to user TZ taking dst into account at the current date. Not yet implemented.
2648
 *                                      'tzuserrel' = local to user TZ taking dst into account at the given date. Use this one to convert date input from user into a GMT date.
2649
 *                                      'tz,TimeZone' = use specified timezone
2650
 * @param int $check 0=No check on parameters (Can use day 32, etc...)
2651
 * @return int|string                  Date as a timestamp, '' if error
2652
 * @see                                dol_print_date(), dol_stringtotime(), dol_getdate()
2653
 */
2654
function dol_mktime($hour, $minute, $second, $month, $day, $year, $gm = 'auto', $check = 1)
2655
{
2656
    global $conf;
2657
    //print "- ".$hour.",".$minute.",".$second.",".$month.",".$day.",".$year.",".$_SERVER["WINDIR"]." -";
2658
2659
    if ($gm === 'auto') {
2660
        $gm = (empty($conf) ? 'tzserver' : $conf->tzuserinputkey);
2661
    }
2662
    //print 'gm:'.$gm.' gm === auto:'.($gm === 'auto').'<br>';exit;
2663
2664
    // Clean parameters
2665
    if ($hour == -1 || empty($hour)) {
2666
        $hour = 0;
2667
    }
2668
    if ($minute == -1 || empty($minute)) {
2669
        $minute = 0;
2670
    }
2671
    if ($second == -1 || empty($second)) {
2672
        $second = 0;
2673
    }
2674
2675
    // Check parameters
2676
    if ($check) {
2677
        if (!$month || !$day) {
2678
            return '';
2679
        }
2680
        if ($day > 31) {
2681
            return '';
2682
        }
2683
        if ($month > 12) {
2684
            return '';
2685
        }
2686
        if ($hour < 0 || $hour > 24) {
2687
            return '';
2688
        }
2689
        if ($minute < 0 || $minute > 60) {
2690
            return '';
2691
        }
2692
        if ($second < 0 || $second > 60) {
2693
            return '';
2694
        }
2695
    }
2696
2697
    if (empty($gm) || ($gm === 'server' || $gm === 'tzserver')) {
2698
        $default_timezone = @date_default_timezone_get(); // Example 'Europe/Berlin'
2699
        $localtz = new DateTimeZone($default_timezone);
2700
    } elseif ($gm === 'user' || $gm === 'tzuser' || $gm === 'tzuserrel') {
2701
        // We use dol_tz_string first because it is more reliable.
2702
        $default_timezone = (empty($_SESSION["dol_tz_string"]) ? @date_default_timezone_get() : $_SESSION["dol_tz_string"]); // Example 'Europe/Berlin'
2703
        try {
2704
            $localtz = new DateTimeZone($default_timezone);
2705
        } catch (Exception $e) {
2706
            dol_syslog("Warning dol_tz_string contains an invalid value " . json_encode($_SESSION["dol_tz_string"] ?? null), LOG_WARNING);
2707
            $default_timezone = @date_default_timezone_get();
2708
        }
2709
    } elseif (strrpos($gm, "tz,") !== false) {
2710
        $timezone = str_replace("tz,", "", $gm); // Example 'tz,Europe/Berlin'
2711
        try {
2712
            $localtz = new DateTimeZone($timezone);
2713
        } catch (Exception $e) {
2714
            dol_syslog("Warning passed timezone contains an invalid value " . $timezone, LOG_WARNING);
2715
        }
2716
    }
2717
2718
    if (empty($localtz)) {
2719
        $localtz = new DateTimeZone('UTC');
2720
    }
2721
    //var_dump($localtz);
2722
    //var_dump($year.'-'.$month.'-'.$day.'-'.$hour.'-'.$minute);
2723
    $dt = new DateTime('now', $localtz);
2724
    $dt->setDate((int)$year, (int)$month, (int)$day);
2725
    $dt->setTime((int)$hour, (int)$minute, (int)$second);
2726
    $date = $dt->getTimestamp(); // should include daylight saving time
2727
    //var_dump($date);
2728
    return $date;
2729
}
2730
2731
2732
/**
2733
 *  Return date for now. In most cases, we use this function without parameters (that means GMT time).
2734
 *
2735
 * @param string $mode 'auto' => for backward compatibility (avoid this),
2736
 *                              'gmt' => we return GMT timestamp,
2737
 *                              'tzserver' => we add the PHP server timezone
2738
 *                              'tzref' => we add the company timezone. Not implemented.
2739
 *                              'tzuser' or 'tzuserrel' => we add the user timezone
2740
 * @return int   $date Timestamp
2741
 */
2742
function dol_now($mode = 'auto')
2743
{
2744
    $ret = 0;
2745
2746
    if ($mode === 'auto') {
2747
        $mode = 'gmt';
2748
    }
2749
2750
    if ($mode == 'gmt') {
2751
        $ret = time(); // Time for now at greenwich.
2752
    } elseif ($mode == 'tzserver') {        // Time for now with PHP server timezone added
2753
        require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/date.lib.php';
2754
        $tzsecond = getServerTimeZoneInt('now'); // Contains tz+dayling saving time
2755
        $ret = (int)(dol_now('gmt') + ($tzsecond * 3600));
2756
        //} elseif ($mode == 'tzref') {// Time for now with parent company timezone is added
2757
        //  require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
2758
        //  $tzsecond=getParentCompanyTimeZoneInt();    // Contains tz+dayling saving time
2759
        //  $ret=dol_now('gmt')+($tzsecond*3600);
2760
        //}
2761
    } elseif ($mode == 'tzuser' || $mode == 'tzuserrel') {
2762
        // Time for now with user timezone added
2763
        //print 'time: '.time();
2764
        $offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60;
2765
        $offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60;
2766
        $ret = (int)(dol_now('gmt') + ($offsettz + $offsetdst));
2767
    }
2768
2769
    return $ret;
2770
}
2771
2772
2773
/**
2774
 * Return string with formatted size
2775
 *
2776
 * @param int $size Size to print
2777
 * @param int $shortvalue Tell if we want long value to use another unit (Ex: 1.5Kb instead of 1500b)
2778
 * @param int $shortunit Use short label of size unit (for example 'b' instead of 'bytes')
2779
 * @return  string              Link
2780
 */
2781
function dol_print_size($size, $shortvalue = 0, $shortunit = 0)
2782
{
2783
    global $conf, $langs;
2784
    $level = 1024;
2785
2786
    if (!empty($conf->dol_optimize_smallscreen)) {
2787
        $shortunit = 1;
2788
    }
2789
2790
    // Set value text
2791
    if (empty($shortvalue) || $size < ($level * 10)) {
2792
        $ret = $size;
2793
        $textunitshort = $langs->trans("b");
2794
        $textunitlong = $langs->trans("Bytes");
2795
    } else {
2796
        $ret = round($size / $level, 0);
2797
        $textunitshort = $langs->trans("Kb");
2798
        $textunitlong = $langs->trans("KiloBytes");
2799
    }
2800
    // Use long or short text unit
2801
    if (empty($shortunit)) {
2802
        $ret .= ' ' . $textunitlong;
2803
    } else {
2804
        $ret .= ' ' . $textunitshort;
2805
    }
2806
2807
    return $ret;
2808
}
2809
2810
/**
2811
 * Get array of social network dictionary
2812
 *
2813
 * @return  array<string,array{rowid:int,label:string,url:string,icon:string,active:int}>   Array of Social Networks Dictionary
2814
 */
2815
function getArrayOfSocialNetworks()
2816
{
2817
    global $conf, $db;
2818
2819
    $socialnetworks = array();
2820
    // Enable caching of array
2821
    require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/memory.lib.php';
2822
    $cachekey = 'socialnetworks_' . $conf->entity;
2823
    $dataretrieved = dol_getcache($cachekey);
2824
    if (!is_null($dataretrieved)) {
2825
        $socialnetworks = $dataretrieved;
2826
    } else {
2827
        $sql = "SELECT rowid, code, label, url, icon, active FROM " . MAIN_DB_PREFIX . "c_socialnetworks";
2828
        $sql .= " WHERE entity=" . $conf->entity;
2829
        $resql = $db->query($sql);
2830
        if ($resql) {
2831
            while ($obj = $db->fetch_object($resql)) {
2832
                $socialnetworks[$obj->code] = array(
2833
                    'rowid' => $obj->rowid,
2834
                    'label' => $obj->label,
2835
                    'url' => $obj->url,
2836
                    'icon' => $obj->icon,
2837
                    'active' => $obj->active,
2838
                );
2839
            }
2840
        }
2841
        dol_setcache($cachekey, $socialnetworks); // If setting cache fails, this is not a problem, so we do not test result.
2842
    }
2843
    return $socialnetworks;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $socialnetworks also could return the type integer which is incompatible with the documented return type array<string,array>.
Loading history...
2844
}
2845
2846
/**
2847
 *  Format professional IDs according to their country
2848
 *
2849
 * @param string $profID Value of profID to format
2850
 * @param string $profIDtype Type of profID to format ('1', '2', '3', '4', '5', '6' or 'VAT')
2851
 * @param string $countrycode Country code to use for formatting
2852
 * @param int<0,2> $addcpButton Add button to copy to clipboard (1 => show only on hoover ; 2 => always display )
2853
 * @return string                  Formatted profID
2854
 */
2855
function dol_print_profids($profID, $profIDtype, $countrycode = '', $addcpButton = 1)
2856
{
2857
    global $mysoc;
2858
2859
    if (empty($profID) || empty($profIDtype)) {
2860
        return '';
2861
    }
2862
    if (empty($countrycode)) {
2863
        $countrycode = $mysoc->country_code;
2864
    }
2865
    $newProfID = $profID;
2866
    $id = substr($profIDtype, -1);
2867
    $ret = '';
2868
    if (strtoupper($countrycode) == 'FR') {
2869
        // France
2870
        // (see https://www.economie.gouv.fr/entreprises/numeros-identification-entreprise)
2871
2872
        if ($id == 1 && dol_strlen($newProfID) == 9) {
2873
            // SIREN (ex: 123 123 123)
2874
            $newProfID = substr($newProfID, 0, 3) . ' ' . substr($newProfID, 3, 3) . ' ' . substr($newProfID, 6, 3);
2875
        }
2876
        if ($id == 2 && dol_strlen($newProfID) == 14) {
2877
            // SIRET (ex: 123 123 123 12345)
2878
            $newProfID = substr($newProfID, 0, 3) . ' ' . substr($newProfID, 3, 3) . ' ' . substr($newProfID, 6, 3) . ' ' . substr($newProfID, 9, 5);
2879
        }
2880
        if ($id == 3 && dol_strlen($newProfID) == 5) {
2881
            // NAF/APE (ex: 69.20Z)
2882
            $newProfID = substr($newProfID, 0, 2) . '.' . substr($newProfID, 2, 3);
2883
        }
2884
        if ($profIDtype === 'VAT' && dol_strlen($newProfID) == 13) {
2885
            // TVA intracommunautaire (ex: FR12 123 123 123)
2886
            $newProfID = substr($newProfID, 0, 4) . ' ' . substr($newProfID, 4, 3) . ' ' . substr($newProfID, 7, 3) . ' ' . substr($newProfID, 10, 3);
2887
        }
2888
    }
2889
    if (!empty($addcpButton)) {
2890
        $ret = showValueWithClipboardCPButton(dol_escape_htmltag($profID), ($addcpButton == 1 ? 1 : 0), $newProfID);
2891
    } else {
2892
        $ret = $newProfID;
2893
    }
2894
    return $ret;
2895
}
2896
2897
/**
2898
 *  Return an IP formatted to be shown on screen
2899
 *
2900
 * @param string $ip IP
2901
 * @param int $mode 0=return IP + country/flag, 1=return only country/flag, 2=return only IP
2902
 * @return string              Formatted IP, with country if GeoIP module is enabled
2903
 */
2904
function dol_print_ip($ip, $mode = 0)
2905
{
2906
    global $langs;
2907
2908
    $ret = '';
2909
2910
    if (empty($mode)) {
2911
        $ret .= $ip;
2912
    }
2913
2914
    if ($mode != 2) {
2915
        $countrycode = dolGetCountryCodeFromIp($ip);
2916
        if ($countrycode) { // If success, countrycode is us, fr, ...
2917
            if (file_exists(DOL_DOCUMENT_ROOT . '/theme/common/flags/' . $countrycode . '.png')) {
2918
                $ret .= ' ' . img_picto($countrycode . ' ' . $langs->trans("AccordingToGeoIPDatabase"), constant('DOL_URL_ROOT') . '/theme/common/flags/' . $countrycode . '.png', '', 1);
2919
            } else {
2920
                $ret .= ' (' . $countrycode . ')';
2921
            }
2922
        }
2923
    }
2924
2925
    return $ret;
2926
}
2927
2928
/**
2929
 * Return the IP of remote user.
2930
 * Take HTTP_X_FORWARDED_FOR (defined when using proxy)
2931
 * Then HTTP_CLIENT_IP if defined (rare)
2932
 * Then REMOTE_ADDR (no way to be modified by user but may be wrong if user is using a proxy)
2933
 *
2934
 * @return  string      Ip of remote user.
2935
 */
2936
function getUserRemoteIP()
2937
{
2938
    if (empty($_SERVER['HTTP_X_FORWARDED_FOR']) || preg_match('/[^0-9.:,\[\]]/', $_SERVER['HTTP_X_FORWARDED_FOR'])) {
2939
        if (empty($_SERVER['HTTP_CLIENT_IP']) || preg_match('/[^0-9.:,\[\]]/', $_SERVER['HTTP_CLIENT_IP'])) {
2940
            if (empty($_SERVER["HTTP_CF_CONNECTING_IP"])) {
2941
                $ip = (empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR']);  // value may have been the IP of the proxy and not the client
2942
            } else {
2943
                $ip = $_SERVER["HTTP_CF_CONNECTING_IP"];    // value here may have been forged by client
2944
            }
2945
        } else {
2946
            $ip = $_SERVER['HTTP_CLIENT_IP']; // value is clean here but may have been forged by proxy
2947
        }
2948
    } else {
2949
        $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; // value is clean here but may have been forged by proxy
2950
    }
2951
    return $ip;
2952
}
2953
2954
/**
2955
 * Return if we are using a HTTPS connection
2956
 * Check HTTPS (no way to be modified by user but may be empty or wrong if user is using a proxy)
2957
 * Take HTTP_X_FORWARDED_PROTO (defined when using proxy)
2958
 * Then HTTP_X_FORWARDED_SSL
2959
 *
2960
 * @return  boolean     True if user is using HTTPS
2961
 */
2962
function isHTTPS()
2963
{
2964
    $isSecure = false;
2965
    if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
2966
        $isSecure = true;
2967
    } elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' || !empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on') {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: (! empty($_SERVER['HTTP_...FORWARDED_SSL'] == 'on', Probably Intended Meaning: ! empty($_SERVER['HTTP_X...ORWARDED_SSL'] == 'on')
Loading history...
2968
        $isSecure = true;
2969
    }
2970
    return $isSecure;
2971
}
2972
2973
/**
2974
 *  Return a country code from IP. Empty string if not found.
2975
 *
2976
 * @param string $ip IP
2977
 * @return string              Country code ('us', 'fr', ...)
2978
 */
2979
function dolGetCountryCodeFromIp($ip)
2980
{
2981
    global $conf;
2982
2983
    $countrycode = '';
2984
2985
    if (!empty($conf->geoipmaxmind->enabled)) {
2986
        $datafile = getDolGlobalString('GEOIPMAXMIND_COUNTRY_DATAFILE');
2987
        //$ip='24.24.24.24';
2988
        //$datafile='/usr/share/GeoIP/GeoIP.dat';    Note that this must be downloaded datafile (not same than datafile provided with ubuntu packages)
2989
        $geoip = new DolGeoIP('country', $datafile);
2990
        //print 'ip='.$ip.' databaseType='.$geoip->gi->databaseType." GEOIP_CITY_EDITION_REV1=".GEOIP_CITY_EDITION_REV1."\n";
2991
        $countrycode = $geoip->getCountryCodeFromIP($ip);
2992
    }
2993
2994
    return $countrycode;
2995
}
2996
2997
2998
/**
2999
 *  Return country code for current user.
3000
 *  If software is used inside a local network, detection may fails (we need a public ip)
3001
 *
3002
 * @return     string      Country code (fr, es, it, us, ...)
3003
 */
3004
function dol_user_country()
3005
{
3006
    global $conf, $langs, $user;
3007
3008
    //$ret=$user->xxx;
3009
    $ret = '';
3010
    if (!empty($conf->geoipmaxmind->enabled)) {
3011
        $ip = getUserRemoteIP();
3012
        $datafile = getDolGlobalString('GEOIPMAXMIND_COUNTRY_DATAFILE');
3013
        //$ip='24.24.24.24';
3014
        //$datafile='E:\Mes Sites\Web\Admin1\awstats\maxmind\GeoIP.dat';
3015
        $geoip = new DolGeoIP('country', $datafile);
3016
        $countrycode = $geoip->getCountryCodeFromIP($ip);
3017
        $ret = $countrycode;
3018
    }
3019
    return $ret;
3020
}
3021
3022
/**
3023
 *  Format address string
3024
 *
3025
 * @param string $address Address string, already formatted with dol_format_address()
3026
 * @param int $htmlid Html ID (for example 'gmap')
3027
 * @param int $element 'thirdparty'|'contact'|'member'|'user'|'other'
3028
 * @param int $id Id of object
3029
 * @param int $noprint No output. Result is the function return
3030
 * @param string $charfornl Char to use instead of nl2br. '' means we use a standad nl2br.
3031
 * @return string|void         Nothing if noprint is 0, formatted address if noprint is 1
3032
 * @see dol_format_address()
3033
 */
3034
function dol_print_address($address, $htmlid, $element, $id, $noprint = 0, $charfornl = '')
3035
{
3036
    global $conf, $user, $langs, $hookmanager;
3037
3038
    $out = '';
3039
3040
    if ($address) {
3041
        if ($hookmanager) {
3042
            $parameters = array('element' => $element, 'id' => $id);
3043
            $reshook = $hookmanager->executeHooks('printAddress', $parameters, $address);
3044
            $out .= $hookmanager->resPrint;
3045
        }
3046
        if (empty($reshook)) {
3047
            if (empty($charfornl)) {
3048
                $out .= nl2br($address);
3049
            } else {
3050
                $out .= preg_replace('/[\r\n]+/', $charfornl, $address);
3051
            }
3052
3053
            // TODO Remove this block, we can add this using the hook now
3054
            $showgmap = $showomap = 0;
3055
            if (($element == 'thirdparty' || $element == 'societe') && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS')) {
3056
                $showgmap = 1;
3057
            }
3058
            if ($element == 'contact' && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS_CONTACTS')) {
3059
                $showgmap = 1;
3060
            }
3061
            if ($element == 'member' && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS_MEMBERS')) {
3062
                $showgmap = 1;
3063
            }
3064
            if ($element == 'user' && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS_USERS')) {
3065
                $showgmap = 1;
3066
            }
3067
            if (($element == 'thirdparty' || $element == 'societe') && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS')) {
3068
                $showomap = 1;
3069
            }
3070
            if ($element == 'contact' && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS_CONTACTS')) {
3071
                $showomap = 1;
3072
            }
3073
            if ($element == 'member' && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS_MEMBERS')) {
3074
                $showomap = 1;
3075
            }
3076
            if ($element == 'user' && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS_USERS')) {
3077
                $showomap = 1;
3078
            }
3079
            if ($showgmap) {
3080
                $url = dol_buildpath('/google/gmaps.php?mode=' . $element . '&id=' . $id, 1);
3081
                $out .= ' <a href="' . $url . '" target="_gmaps"><img id="' . $htmlid . '" class="valigntextbottom" src="' . constant('DOL_URL_ROOT') . '/theme/common/gmap.png"></a>';
3082
            }
3083
            if ($showomap) {
3084
                $url = dol_buildpath('/openstreetmap/maps.php?mode=' . $element . '&id=' . $id, 1);
3085
                $out .= ' <a href="' . $url . '" target="_gmaps"><img id="' . $htmlid . '_openstreetmap" class="valigntextbottom" src="' . constant('DOL_URL_ROOT') . '/theme/common/gmap.png"></a>';
3086
            }
3087
        }
3088
    }
3089
    if ($noprint) {
3090
        return $out;
3091
    } else {
3092
        print $out;
3093
    }
3094
}
3095
3096
3097
/**
3098
 *  Return true if email syntax is ok.
3099
 *
3100
 * @param string $address email (Ex: "[email protected]". Long form "John Do <[email protected]>" will be false)
3101
 * @param int $acceptsupervisorkey If 1, the special string '__SUPERVISOREMAIL__' is also accepted as valid
3102
 * @param int $acceptuserkey If 1, the special string '__USER_EMAIL__' is also accepted as valid
3103
 * @return     boolean                             true if email syntax is OK, false if KO or empty string
3104
 * @see isValidMXRecord()
3105
 */
3106
function isValidEmail($address, $acceptsupervisorkey = 0, $acceptuserkey = 0)
3107
{
3108
    if ($acceptsupervisorkey && $address == '__SUPERVISOREMAIL__') {
3109
        return true;
3110
    }
3111
    if ($acceptuserkey && $address == '__USER_EMAIL__') {
3112
        return true;
3113
    }
3114
    if (filter_var($address, FILTER_VALIDATE_EMAIL)) {
3115
        return true;
3116
    }
3117
3118
    return false;
3119
}
3120
3121
/**
3122
 *  Return if the domain name has a valid MX record.
3123
 *  WARNING: This need function idn_to_ascii, checkdnsrr and getmxrr
3124
 *
3125
 * @param string $domain Domain name (Ex: "yahoo.com", "yhaoo.com", "dolibarr.fr")
3126
 * @return     int                                 -1 if error (function not available), 0=Not valid, 1=Valid
3127
 * @see isValidEmail()
3128
 * @suppress PhanDeprecatedFunctionInternal Error in Phan plugins incorrectly tags some functions here
3129
 */
3130
function isValidMXRecord($domain)
3131
{
3132
    if (function_exists('idn_to_ascii') && function_exists('checkdnsrr')) {
3133
        if (!checkdnsrr(idn_to_ascii($domain), 'MX')) {
3134
            return 0;
3135
        }
3136
        if (function_exists('getmxrr')) {
3137
            $mxhosts = array();
3138
            $weight = array();
3139
            getmxrr(idn_to_ascii($domain), $mxhosts, $weight);
3140
            if (count($mxhosts) > 1) {
3141
                return 1;
3142
            }
3143
            if (count($mxhosts) == 1 && !in_array((string)$mxhosts[0], array('', '.'))) {
3144
                return 1;
3145
            }
3146
3147
            return 0;
3148
        }
3149
    }
3150
3151
    // function idn_to_ascii or checkdnsrr or getmxrr does not exists
3152
    return -1;
3153
}
3154
3155
/**
3156
 *  Return true if phone number syntax is ok
3157
 *  TODO Decide what to do with this
3158
 *
3159
 * @param string $phone phone (Ex: "0601010101")
3160
 * @return boolean                 true if phone syntax is OK, false if KO or empty string
3161
 */
3162
function isValidPhone($phone)
3163
{
3164
    return true;
3165
}
3166
3167
3168
/**
3169
 * Return first letters of a strings.
3170
 * Example with nbofchar=1: 'ghi' will return 'g' but 'abc def' will return 'ad'
3171
 * Example with nbofchar=2: 'ghi' will return 'gh' but 'abc def' will return 'abde'
3172
 *
3173
 * @param string $s String to truncate
3174
 * @param int $nbofchar Nb of characters to keep
3175
 * @return  string                  Return first chars.
3176
 */
3177
function dolGetFirstLetters($s, $nbofchar = 1)
3178
{
3179
    $ret = '';
3180
    $tmparray = explode(' ', $s);
3181
    foreach ($tmparray as $tmps) {
3182
        $ret .= dol_substr($tmps, 0, $nbofchar);
3183
    }
3184
3185
    return $ret;
3186
}
3187
3188
3189
/**
3190
 * Make a strlen call. Works even if mbstring module not enabled
3191
 *
3192
 * @param string $string String to calculate length
3193
 * @param string $stringencoding Encoding of string
3194
 * @return  int                             Length of string
3195
 */
3196
function dol_strlen($string, $stringencoding = 'UTF-8')
3197
{
3198
    if (is_null($string)) {
3199
        return 0;
3200
    }
3201
3202
    if (function_exists('mb_strlen')) {
3203
        return mb_strlen($string, $stringencoding);
3204
    } else {
3205
        return strlen($string);
3206
    }
3207
}
3208
3209
/**
3210
 * Make a substring. Works even if mbstring module is not enabled for better compatibility.
3211
 *
3212
 * @param string $string String to scan
3213
 * @param int $start Start position (0 for first char)
3214
 * @param int|null $length Length (in nb of characters or nb of bytes depending on trunconbytes param)
3215
 * @param string $stringencoding Page code used for input string encoding
3216
 * @param int $trunconbytes 1=Length is max of bytes instead of max of characters
3217
 * @return  string                          substring
3218
 */
3219
function dol_substr($string, $start, $length = null, $stringencoding = '', $trunconbytes = 0)
3220
{
3221
    global $langs;
3222
3223
    if (empty($stringencoding)) {
3224
        $stringencoding = (empty($langs) ? 'UTF-8' : $langs->charset_output);
3225
    }
3226
3227
    $ret = '';
3228
    if (empty($trunconbytes)) {
3229
        if (function_exists('mb_substr')) {
3230
            $ret = mb_substr($string, $start, $length, $stringencoding);
3231
        } else {
3232
            $ret = substr($string, $start, $length);
3233
        }
3234
    } else {
3235
        if (function_exists('mb_strcut')) {
3236
            $ret = mb_strcut($string, $start, $length, $stringencoding);
3237
        } else {
3238
            $ret = substr($string, $start, $length);
3239
        }
3240
    }
3241
    return $ret;
3242
}
3243
3244
3245
/**
3246
 *  Truncate a string to a particular length adding '…' if string larger than length.
3247
 *  If length = max length+1, we do no truncate to avoid having just 1 char replaced with '…'.
3248
 *  MAIN_DISABLE_TRUNC=1 can disable all truncings
3249
 *
3250
 * @param string $string String to truncate
3251
 * @param int $size Max string size visible (excluding …). 0 for no limit. WARNING: Final string size can have 3 more chars (if we added …, or if size was max+1 so it does not worse to replace with ...)
3252
 * @param string $trunc Where to trunc: 'right', 'left', 'middle' (size must be a 2 power), 'wrap'
3253
 * @param string $stringencoding Tell what is source string encoding
3254
 * @param int $nodot Truncation do not add … after truncation. So it's an exact truncation.
3255
 * @param int $display Trunc is used to display data and can be changed for small screen. TODO Remove this param (must be dealt with CSS)
3256
 * @return string                      Truncated string. WARNING: length is never higher than $size if $nodot is set, but can be 3 chars higher otherwise.
3257
 */
3258
function dol_trunc($string, $size = 40, $trunc = 'right', $stringencoding = 'UTF-8', $nodot = 0, $display = 0)
3259
{
3260
    global $conf;
3261
3262
    if (empty($size) || getDolGlobalString('MAIN_DISABLE_TRUNC')) {
3263
        return $string;
3264
    }
3265
3266
    if (empty($stringencoding)) {
3267
        $stringencoding = 'UTF-8';
3268
    }
3269
    // reduce for small screen
3270
    if (!empty($conf->dol_optimize_smallscreen) && $conf->dol_optimize_smallscreen == 1 && $display == 1) {
3271
        $size = round($size / 3);
3272
    }
3273
3274
    // We go always here
3275
    if ($trunc == 'right') {
3276
        $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
3277
        if (dol_strlen($newstring, $stringencoding) > ($size + ($nodot ? 0 : 1))) {
3278
            // If nodot is 0 and size is 1 chars more, we don't trunc and don't add …
3279
            return dol_substr($newstring, 0, $size, $stringencoding) . ($nodot ? '' : '…');
3280
        } else {
3281
            //return 'u'.$size.'-'.$newstring.'-'.dol_strlen($newstring,$stringencoding).'-'.$string;
3282
            return $string;
3283
        }
3284
    } elseif ($trunc == 'middle') {
3285
        $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
3286
        if (dol_strlen($newstring, $stringencoding) > 2 && dol_strlen($newstring, $stringencoding) > ($size + 1)) {
3287
            $size1 = round($size / 2);
3288
            $size2 = round($size / 2);
3289
            return dol_substr($newstring, 0, $size1, $stringencoding) . '…' . dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size2, $size2, $stringencoding);
3290
        } else {
3291
            return $string;
3292
        }
3293
    } elseif ($trunc == 'left') {
3294
        $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
3295
        if (dol_strlen($newstring, $stringencoding) > ($size + ($nodot ? 0 : 1))) {
3296
            // If nodot is 0 and size is 1 chars more, we don't trunc and don't add …
3297
            return '…' . dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size, $size, $stringencoding);
3298
        } else {
3299
            return $string;
3300
        }
3301
    } elseif ($trunc == 'wrap') {
3302
        $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
3303
        if (dol_strlen($newstring, $stringencoding) > ($size + 1)) {
3304
            return dol_substr($newstring, 0, $size, $stringencoding) . "\n" . dol_trunc(dol_substr($newstring, $size, dol_strlen($newstring, $stringencoding) - $size, $stringencoding), $size, $trunc);
3305
        } else {
3306
            return $string;
3307
        }
3308
    } else {
3309
        return 'BadParam3CallingDolTrunc';
3310
    }
3311
}
3312
3313
/**
3314
 * Return the picto for a data type
3315
 *
3316
 * @param string $key Key
3317
 * @param string $morecss Add more css to the object
3318
 * @return  string                  Pïcto for the key
3319
 */
3320
function getPictoForType($key, $morecss = '')
3321
{
3322
    // Set array with type -> picto
3323
    $type2picto = array(
3324
        'varchar' => 'font',
3325
        'text' => 'font',
3326
        'html' => 'code',
3327
        'int' => 'sort-numeric-down',
3328
        'double' => 'sort-numeric-down',
3329
        'price' => 'currency',
3330
        'pricecy' => 'multicurrency',
3331
        'password' => 'key',
3332
        'boolean' => 'check-square',
3333
        'date' => 'calendar',
3334
        'datetime' => 'calendar',
3335
        'phone' => 'phone',
3336
        'mail' => 'email',
3337
        'url' => 'url',
3338
        'ip' => 'country',
3339
        'select' => 'list',
3340
        'sellist' => 'list',
3341
        'radio' => 'check-circle',
3342
        'checkbox' => 'check-square',
3343
        'chkbxlst' => 'check-square',
3344
        'link' => 'link',
3345
        'icon' => "question",
3346
        'point' => "country",
3347
        'multipts' => 'country',
3348
        'linestrg' => "country",
3349
        'polygon' => "country",
3350
        'separate' => 'minus'
3351
    );
3352
3353
    if (!empty($type2picto[$key])) {
3354
        return img_picto('', $type2picto[$key], 'class="pictofixedwidth' . ($morecss ? ' ' . $morecss : '') . '"');
3355
    }
3356
3357
    return img_picto('', 'generic', 'class="pictofixedwidth' . ($morecss ? ' ' . $morecss : '') . '"');
3358
}
3359
3360
3361
/**
3362
 *  Show picto whatever it's its name (generic function)
3363
 *
3364
 * @param string $titlealt Text on title tag for tooltip. Not used if param notitle is set to 1.
3365
 * @param string $picto Name of image file to show ('filenew', ...).
3366
 *                                                  For font awesome icon (example 'user'), you can use picto_nocolor to not have the color of picto forced.
3367
 *                                                  If no extension provided and it is not a font awesome icon, we use '.png'. Image must be stored into theme/xxx/img directory.
3368
 *                                                  Example: picto.png                  if picto.png is stored into htdocs/theme/mytheme/img
3369
 *                                                  Example: picto.png@mymodule         if picto.png is stored into htdocs/mymodule/img
3370
 *                                                  Example: /mydir/mysubdir/picto.png  if picto.png is stored into htdocs/mydir/mysubdir (pictoisfullpath must be set to 1)
3371
 *                                                  Example: fontawesome_envelope-open-text_fas_red_1em if you want to use fontaweseome icons: fontawesome_<icon-name>_<style>_<color>_<size> (only icon-name is mandatory)
3372
 * @param string $moreatt Add more attribute on img tag (For example 'class="pictofixedwidth"')
3373
 * @param int<0,1> $pictoisfullpath If true or 1, image path is a full path, 0 if not
3374
 * @param int $srconly Return only content of the src attribute of img.
3375
 * @param int $notitle 1=Disable tag title. Use it if you add js tooltip, to avoid duplicate tooltip.
3376
 * @param string $alt Force alt for bind people
3377
 * @param string $morecss Add more class css on img tag (For example 'myclascss').
3378
 * @param int $marginleftonlyshort 1 = Add a short left margin on picto, 2 = Add a larger left margin on picto, 0 = No margin left. Works for fontawesome picto only.
3379
 * @return     string                              Return img tag
3380
 * @see        img_object(), img_picto_common()
3381
 */
3382
function img_picto($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $srconly = 0, $notitle = 0, $alt = '', $morecss = '', $marginleftonlyshort = 2)
3383
{
3384
    global $conf;
3385
3386
    // We forge fullpathpicto for image to $path/img/$picto. By default, we take DOL_URL_ROOT/theme/$conf->theme/img/$picto
3387
    $url = DOL_URL_ROOT;
3388
    $theme = isset($conf->theme) ? $conf->theme : null;
3389
    $path = 'theme/' . $theme;
3390
    if (empty($picto)) {
3391
        $picto = 'generic';
3392
    }
3393
3394
    // Define fullpathpicto to use into src
3395
    if ($pictoisfullpath) {
3396
        // Clean parameters
3397
        if (!preg_match('/(\.png|\.gif|\.svg)$/i', $picto)) {
3398
            $picto .= '.png';
3399
        }
3400
        $fullpathpicto = $picto;
3401
        $reg = array();
3402
        if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
3403
            $morecss .= ($morecss ? ' ' : '') . $reg[1];
3404
            $moreatt = str_replace('class="' . $reg[1] . '"', '', $moreatt);
3405
        }
3406
    } else {
3407
        $pictowithouttext = preg_replace('/(\.png|\.gif|\.svg)$/', '', (is_null($picto) ? '' : $picto));
3408
        $pictowithouttext = str_replace('object_', '', $pictowithouttext);
3409
        $pictowithouttext = str_replace('_nocolor', '', $pictowithouttext);
3410
3411
        if (strpos($pictowithouttext, 'fontawesome_') === 0 || strpos($pictowithouttext, 'fa-') === 0) {
3412
            // This is a font awesome image 'fontawesome_xxx' or 'fa-xxx'
3413
            $pictowithouttext = str_replace('fontawesome_', '', $pictowithouttext);
3414
            $pictowithouttext = str_replace('fa-', '', $pictowithouttext);
3415
3416
            // Compatibility with old fontawesome versions
3417
            if ($pictowithouttext == 'file-o') {
3418
                $pictowithouttext = 'file';
3419
            }
3420
3421
            $pictowithouttextarray = explode('_', $pictowithouttext);
3422
            $marginleftonlyshort = 0;
3423
3424
            if (!empty($pictowithouttextarray[1])) {
3425
                // Syntax is 'fontawesome_fakey_faprefix_facolor_fasize' or 'fa-fakey_faprefix_facolor_fasize'
3426
                $fakey = 'fa-' . $pictowithouttextarray[0];
3427
                $faprefix = empty($pictowithouttextarray[1]) ? 'fas' : $pictowithouttextarray[1];
3428
                $facolor = empty($pictowithouttextarray[2]) ? '' : $pictowithouttextarray[2];
3429
                $fasize = empty($pictowithouttextarray[3]) ? '' : $pictowithouttextarray[3];
3430
            } else {
3431
                $fakey = 'fa-' . $pictowithouttext;
3432
                $faprefix = 'fas';
3433
                $facolor = '';
3434
                $fasize = '';
3435
            }
3436
3437
            // This snippet only needed since function img_edit accepts only one additional parameter: no separate one for css only.
3438
            // class/style need to be extracted to avoid duplicate class/style validation errors when $moreatt is added to the end of the attributes.
3439
            $morestyle = '';
3440
            $reg = array();
3441
            if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
3442
                $morecss .= ($morecss ? ' ' : '') . $reg[1];
3443
                $moreatt = str_replace('class="' . $reg[1] . '"', '', $moreatt);
3444
            }
3445
            if (preg_match('/style="([^"]+)"/', $moreatt, $reg)) {
3446
                $morestyle = $reg[1];
3447
                $moreatt = str_replace('style="' . $reg[1] . '"', '', $moreatt);
3448
            }
3449
            $moreatt = trim($moreatt);
3450
3451
            $enabledisablehtml = '<span class="' . $faprefix . ' ' . $fakey . ($marginleftonlyshort ? ($marginleftonlyshort == 1 ? ' marginleftonlyshort' : ' marginleftonly') : '');
3452
            $enabledisablehtml .= ($morecss ? ' ' . $morecss : '') . '" style="' . ($fasize ? ('font-size: ' . $fasize . ';') : '') . ($facolor ? (' color: ' . $facolor . ';') : '') . ($morestyle ? ' ' . $morestyle : '') . '"' . (($notitle || empty($titlealt)) ? '' : ' title="' . dol_escape_htmltag($titlealt) . '"') . ($moreatt ? ' ' . $moreatt : '') . '>';
3453
            /*if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
3454
                $enabledisablehtml .= $titlealt;
3455
            }*/
3456
            $enabledisablehtml .= '</span>';
3457
3458
            return $enabledisablehtml;
3459
        }
3460
3461
        if (
3462
            empty($srconly) && in_array($pictowithouttext, array(
3463
                '1downarrow', '1uparrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected',
3464
                'accountancy', 'accounting_account', 'account', 'accountline', 'action', 'add', 'address', 'ai', 'angle-double-down', 'angle-double-up', 'asset',
3465
                'bank_account', 'barcode', 'bank', 'bell', 'bill', 'billa', 'billr', 'billd', 'birthday-cake', 'bom', 'bookcal', 'bookmark', 'briefcase-medical', 'bug', 'building',
3466
                'card', 'calendarlist', 'calendar', 'calendarmonth', 'calendarweek', 'calendarday', 'calendarperuser', 'calendarpertype',
3467
                'cash-register', 'category', 'chart', 'check', 'clock', 'clone', 'close_title', 'code', 'cog', 'collab', 'company', 'contact', 'country', 'contract', 'conversation', 'cron', 'cross', 'cubes',
3468
                'check-circle', 'check-square', 'circle', 'stop-circle', 'currency', 'multicurrency',
3469
                'chevron-left', 'chevron-right', 'chevron-down', 'chevron-top',
3470
                'chevron-double-left', 'chevron-double-right', 'chevron-double-down', 'chevron-double-top',
3471
                'commercial', 'companies',
3472
                'delete', 'dolly', 'dollyrevert', 'donation', 'download', 'dynamicprice',
3473
                'edit', 'ellipsis-h', 'email', 'entity', 'envelope', 'eraser', 'establishment', 'expensereport', 'external-link-alt', 'external-link-square-alt', 'eye',
3474
                'filter', 'file', 'file-o', 'file-code', 'file-export', 'file-import', 'file-upload', 'autofill', 'folder', 'folder-open', 'folder-plus', 'font',
3475
                'gears', 'generate', 'generic', 'globe', 'globe-americas', 'graph', 'grip', 'grip_title', 'group',
3476
                'hands-helping', 'help', 'holiday',
3477
                'id-card', 'images', 'incoterm', 'info', 'intervention', 'inventory', 'intracommreport', 'jobprofile',
3478
                'key', 'knowledgemanagement',
3479
                'label', 'language', 'layout', 'line', 'link', 'list', 'list-alt', 'listlight', 'loan', 'lock', 'lot', 'long-arrow-alt-right',
3480
                'margin', 'map-marker-alt', 'member', 'meeting', 'minus', 'money-bill-alt', 'movement', 'mrp', 'note', 'next',
3481
                'off', 'on', 'order',
3482
                'paiment', 'paragraph', 'play', 'pdf', 'phone', 'phoning', 'phoning_mobile', 'phoning_fax', 'playdisabled', 'previous', 'poll', 'pos', 'printer', 'product', 'propal', 'proposal', 'puce',
3483
                'stock', 'resize', 'service', 'stats',
3484
                'security', 'setup', 'share-alt', 'sign-out', 'split', 'stripe', 'stripe-s', 'switch_off', 'switch_on', 'switch_on_warning', 'switch_on_red', 'tools', 'unlink', 'uparrow', 'user', 'user-tie', 'vcard', 'wrench',
3485
                'github', 'google', 'jabber', 'microsoft', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'youtube', 'google-plus-g', 'whatsapp',
3486
                'generic', 'home', 'hrm', 'members', 'products', 'invoicing',
3487
                'partnership', 'payment', 'payment_vat', 'pencil-ruler', 'pictoconfirm', 'preview', 'project', 'projectpub', 'projecttask', 'question', 'refresh', 'region',
3488
                'salary', 'shipment', 'state', 'supplier_invoice', 'supplier_invoicea', 'supplier_invoicer', 'supplier_invoiced',
3489
                'technic', 'ticket',
3490
                'error', 'warning',
3491
                'recent', 'reception', 'recruitmentcandidature', 'recruitmentjobposition', 'replacement', 'resource', 'recurring', 'rss',
3492
                'shapes', 'skill', 'square', 'sort-numeric-down', 'status', 'stop-circle', 'supplier', 'supplier_proposal', 'supplier_order', 'supplier_invoice',
3493
                'terminal', 'tick', 'timespent', 'title_setup', 'title_accountancy', 'title_bank', 'title_hrm', 'title_agenda', 'trip',
3494
                'uncheck', 'url', 'user-cog', 'user-injured', 'user-md', 'vat', 'website', 'workstation', 'webhook', 'world', 'private',
3495
                'conferenceorbooth', 'eventorganization',
3496
                'stamp', 'signature',
3497
                'webportal'
3498
            ))
3499
        ) {
3500
            $fakey = $pictowithouttext;
3501
            $facolor = '';
3502
            $fasize = '';
3503
            $fa = getDolGlobalString('MAIN_FONTAWESOME_ICON_STYLE', 'fas');
3504
            if (in_array($pictowithouttext, array('card', 'bell', 'clock', 'establishment', 'file', 'file-o', 'generic', 'minus-square', 'object_generic', 'pdf', 'plus-square', 'timespent', 'note', 'off', 'on', 'object_bookmark', 'bookmark', 'vcard'))) {
3505
                $fa = 'far';
3506
            }
3507
            if (in_array($pictowithouttext, array('black-tie', 'github', 'google', 'microsoft', 'skype', 'twitter', 'facebook', 'linkedin', 'instagram', 'snapchat', 'stripe', 'stripe-s', 'youtube', 'google-plus-g', 'whatsapp'))) {
3508
                $fa = 'fab';
3509
            }
3510
3511
            $arrayconvpictotofa = array(
3512
                'account' => 'university', 'accounting_account' => 'clipboard-list', 'accountline' => 'receipt', 'accountancy' => 'search-dollar', 'action' => 'calendar-alt', 'add' => 'plus-circle', 'address' => 'address-book', 'ai' => 'magic',
3513
                'asset' => 'money-check-alt', 'autofill' => 'fill',
3514
                'bank_account' => 'university',
3515
                'bill' => 'file-invoice-dollar', 'billa' => 'file-excel', 'billr' => 'file-invoice-dollar', 'billd' => 'file-medical',
3516
                'bookcal' => 'calendar-check',
3517
                'supplier_invoice' => 'file-invoice-dollar', 'supplier_invoicea' => 'file-excel', 'supplier_invoicer' => 'file-invoice-dollar', 'supplier_invoiced' => 'file-medical',
3518
                'bom' => 'shapes',
3519
                'card' => 'address-card', 'chart' => 'chart-line', 'company' => 'building', 'contact' => 'address-book', 'contract' => 'suitcase', 'collab' => 'people-arrows', 'conversation' => 'comments', 'country' => 'globe-americas', 'cron' => 'business-time', 'cross' => 'times',
3520
                'chevron-double-left' => 'angle-double-left', 'chevron-double-right' => 'angle-double-right', 'chevron-double-down' => 'angle-double-down', 'chevron-double-top' => 'angle-double-up',
3521
                'donation' => 'file-alt', 'dynamicprice' => 'hand-holding-usd',
3522
                'setup' => 'cog', 'companies' => 'building', 'products' => 'cube', 'commercial' => 'suitcase', 'invoicing' => 'coins',
3523
                'accounting' => 'search-dollar', 'category' => 'tag', 'dollyrevert' => 'dolly',
3524
                'file-o' => 'file', 'generate' => 'plus-square', 'hrm' => 'user-tie', 'incoterm' => 'truck-loading',
3525
                'margin' => 'calculator', 'members' => 'user-friends', 'ticket' => 'ticket-alt', 'globe' => 'external-link-alt', 'lot' => 'barcode',
3526
                'email' => 'at', 'establishment' => 'building', 'edit' => 'pencil-alt', 'entity' => 'globe',
3527
                'graph' => 'chart-line', 'grip_title' => 'arrows-alt', 'grip' => 'arrows-alt', 'help' => 'question-circle',
3528
                'generic' => 'file', 'holiday' => 'umbrella-beach',
3529
                'info' => 'info-circle', 'inventory' => 'boxes', 'intracommreport' => 'globe-europe', 'jobprofile' => 'cogs',
3530
                'knowledgemanagement' => 'ticket-alt', 'label' => 'layer-group', 'layout' => 'columns', 'line' => 'bars', 'loan' => 'money-bill-alt',
3531
                'member' => 'user-alt', 'meeting' => 'chalkboard-teacher', 'mrp' => 'cubes', 'next' => 'arrow-alt-circle-right',
3532
                'trip' => 'wallet', 'expensereport' => 'wallet', 'group' => 'users', 'movement' => 'people-carry',
3533
                'sign-out' => 'sign-out-alt',
3534
                'switch_off' => 'toggle-off', 'switch_on' => 'toggle-on', 'switch_on_warning' => 'toggle-on', 'switch_on_red' => 'toggle-on', 'check' => 'check', 'bookmark' => 'star',
3535
                'bank' => 'university', 'close_title' => 'times', 'delete' => 'trash', 'filter' => 'filter',
3536
                'list-alt' => 'list-alt', 'calendarlist' => 'bars', 'calendar' => 'calendar-alt', 'calendarmonth' => 'calendar-alt', 'calendarweek' => 'calendar-week', 'calendarday' => 'calendar-day', 'calendarperuser' => 'table',
3537
                'intervention' => 'ambulance', 'invoice' => 'file-invoice-dollar', 'order' => 'file-invoice',
3538
                'error' => 'exclamation-triangle', 'warning' => 'exclamation-triangle',
3539
                'other' => 'square',
3540
                'playdisabled' => 'play', 'pdf' => 'file-pdf', 'poll' => 'check-double', 'pos' => 'cash-register', 'preview' => 'binoculars', 'project' => 'project-diagram', 'projectpub' => 'project-diagram', 'projecttask' => 'tasks', 'propal' => 'file-signature', 'proposal' => 'file-signature',
3541
                'partnership' => 'handshake', 'payment' => 'money-check-alt', 'payment_vat' => 'money-check-alt', 'pictoconfirm' => 'check-square', 'phoning' => 'phone', 'phoning_mobile' => 'mobile-alt', 'phoning_fax' => 'fax', 'previous' => 'arrow-alt-circle-left', 'printer' => 'print', 'product' => 'cube', 'puce' => 'angle-right',
3542
                'recent' => 'check-square', 'reception' => 'dolly', 'recruitmentjobposition' => 'id-card-alt', 'recruitmentcandidature' => 'id-badge',
3543
                'resize' => 'crop', 'supplier_order' => 'dol-order_supplier', 'supplier_proposal' => 'file-signature',
3544
                'refresh' => 'redo', 'region' => 'map-marked', 'replacement' => 'exchange-alt', 'resource' => 'laptop-house', 'recurring' => 'history',
3545
                'service' => 'concierge-bell',
3546
                'skill' => 'shapes', 'state' => 'map-marked-alt', 'security' => 'key', 'salary' => 'wallet', 'shipment' => 'dolly', 'stock' => 'box-open', 'stats' => 'chart-bar', 'split' => 'code-branch',
3547
                'status' => 'stop-circle',
3548
                'stripe' => 'stripe-s', 'supplier' => 'building',
3549
                'technic' => 'cogs', 'tick' => 'check', 'timespent' => 'clock', 'title_setup' => 'tools', 'title_accountancy' => 'money-check-alt', 'title_bank' => 'university', 'title_hrm' => 'umbrella-beach',
3550
                'title_agenda' => 'calendar-alt',
3551
                'uncheck' => 'times', 'uparrow' => 'share', 'url' => 'external-link-alt', 'vat' => 'money-check-alt', 'vcard' => 'arrow-alt-circle-down',
3552
                'jabber' => 'comment-o',
3553
                'website' => 'globe-americas', 'workstation' => 'pallet', 'webhook' => 'bullseye', 'world' => 'globe', 'private' => 'user-lock',
3554
                'conferenceorbooth' => 'chalkboard-teacher', 'eventorganization' => 'project-diagram',
3555
                'webportal' => 'door-open'
3556
            );
3557
            if ($conf->currency == 'EUR') {
3558
                $arrayconvpictotofa['currency'] = 'euro-sign';
3559
                $arrayconvpictotofa['multicurrency'] = 'dollar-sign';
3560
            } else {
3561
                $arrayconvpictotofa['currency'] = 'dollar-sign';
3562
                $arrayconvpictotofa['multicurrency'] = 'euro-sign';
3563
            }
3564
            if ($pictowithouttext == 'off') {
3565
                $fakey = 'fa-square';
3566
                $fasize = '1.3em';
3567
            } elseif ($pictowithouttext == 'on') {
3568
                $fakey = 'fa-check-square';
3569
                $fasize = '1.3em';
3570
            } elseif ($pictowithouttext == 'listlight') {
3571
                $fakey = 'fa-download';
3572
                $marginleftonlyshort = 1;
3573
            } elseif ($pictowithouttext == 'printer') {
3574
                $fakey = 'fa-print';
3575
                $fasize = '1.2em';
3576
            } elseif ($pictowithouttext == 'note') {
3577
                $fakey = 'fa-sticky-note';
3578
                $marginleftonlyshort = 1;
3579
            } elseif (in_array($pictowithouttext, array('1uparrow', '1downarrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected'))) {
3580
                $convertarray = array('1uparrow' => 'caret-up', '1downarrow' => 'caret-down', '1leftarrow' => 'caret-left', '1rightarrow' => 'caret-right', '1uparrow_selected' => 'caret-up', '1downarrow_selected' => 'caret-down', '1leftarrow_selected' => 'caret-left', '1rightarrow_selected' => 'caret-right');
3581
                $fakey = 'fa-' . $convertarray[$pictowithouttext];
3582
                if (preg_match('/selected/', $pictowithouttext)) {
3583
                    $facolor = '#888';
3584
                }
3585
                $marginleftonlyshort = 1;
3586
            } elseif (!empty($arrayconvpictotofa[$pictowithouttext])) {
3587
                $fakey = 'fa-' . $arrayconvpictotofa[$pictowithouttext];
3588
            } else {
3589
                $fakey = 'fa-' . $pictowithouttext;
3590
            }
3591
3592
            if (in_array($pictowithouttext, array('dollyrevert', 'member', 'members', 'contract', 'group', 'resource', 'shipment', 'reception'))) {
3593
                $morecss .= ' em092';
3594
            }
3595
            if (in_array($pictowithouttext, array('conferenceorbooth', 'collab', 'eventorganization', 'holiday', 'info', 'project', 'workstation'))) {
3596
                $morecss .= ' em088';
3597
            }
3598
            if (in_array($pictowithouttext, array('asset', 'intervention', 'payment', 'loan', 'partnership', 'stock', 'technic'))) {
3599
                $morecss .= ' em080';
3600
            }
3601
3602
            // Define $marginleftonlyshort
3603
            $arrayconvpictotomarginleftonly = array(
3604
                'bank', 'check', 'delete', 'generic', 'grip', 'grip_title', 'jabber',
3605
                'grip_title', 'grip', 'listlight', 'note', 'on', 'off', 'playdisabled', 'printer', 'resize', 'sign-out', 'stats', 'switch_on', 'switch_on_red', 'switch_off',
3606
                'uparrow', '1uparrow', '1downarrow', '1leftarrow', '1rightarrow', '1uparrow_selected', '1downarrow_selected', '1leftarrow_selected', '1rightarrow_selected'
3607
            );
3608
            if (!isset($arrayconvpictotomarginleftonly[$pictowithouttext])) {
3609
                $marginleftonlyshort = 0;
3610
            }
3611
3612
            // Add CSS
3613
            $arrayconvpictotomorcess = array(
3614
                'action' => 'infobox-action', 'account' => 'infobox-bank_account', 'accounting_account' => 'infobox-bank_account', 'accountline' => 'infobox-bank_account', 'accountancy' => 'infobox-bank_account', 'asset' => 'infobox-bank_account',
3615
                'bank_account' => 'infobox-bank_account',
3616
                'bill' => 'infobox-commande', 'billa' => 'infobox-commande', 'billr' => 'infobox-commande', 'billd' => 'infobox-commande',
3617
                'bookcal' => 'infobox-action',
3618
                'margin' => 'infobox-bank_account', 'conferenceorbooth' => 'infobox-project',
3619
                'cash-register' => 'infobox-bank_account', 'contract' => 'infobox-contrat', 'check' => 'font-status4', 'collab' => 'infobox-action', 'conversation' => 'infobox-contrat',
3620
                'donation' => 'infobox-commande', 'dolly' => 'infobox-commande', 'dollyrevert' => 'flip infobox-order_supplier',
3621
                'ecm' => 'infobox-action', 'eventorganization' => 'infobox-project',
3622
                'hrm' => 'infobox-adherent', 'group' => 'infobox-adherent', 'intervention' => 'infobox-contrat',
3623
                'incoterm' => 'infobox-supplier_proposal',
3624
                'currency' => 'infobox-bank_account', 'multicurrency' => 'infobox-bank_account',
3625
                'members' => 'infobox-adherent', 'member' => 'infobox-adherent', 'money-bill-alt' => 'infobox-bank_account',
3626
                'order' => 'infobox-commande',
3627
                'user' => 'infobox-adherent', 'users' => 'infobox-adherent',
3628
                'error' => 'pictoerror', 'warning' => 'pictowarning', 'switch_on' => 'font-status4', 'switch_on_warning' => 'font-status4 warning', 'switch_on_red' => 'font-status8',
3629
                'holiday' => 'infobox-holiday', 'info' => 'opacityhigh', 'invoice' => 'infobox-commande',
3630
                'knowledgemanagement' => 'infobox-contrat rotate90', 'loan' => 'infobox-bank_account',
3631
                'payment' => 'infobox-bank_account', 'payment_vat' => 'infobox-bank_account', 'poll' => 'infobox-adherent', 'pos' => 'infobox-bank_account', 'project' => 'infobox-project', 'projecttask' => 'infobox-project',
3632
                'propal' => 'infobox-propal', 'proposal' => 'infobox-propal', 'private' => 'infobox-project',
3633
                'reception' => 'flip infobox-order_supplier', 'recruitmentjobposition' => 'infobox-adherent', 'recruitmentcandidature' => 'infobox-adherent',
3634
                'resource' => 'infobox-action',
3635
                'salary' => 'infobox-bank_account', 'shapes' => 'infobox-adherent', 'shipment' => 'infobox-commande', 'stripe' => 'infobox-bank_account', 'supplier_invoice' => 'infobox-order_supplier', 'supplier_invoicea' => 'infobox-order_supplier', 'supplier_invoiced' => 'infobox-order_supplier',
3636
                'supplier' => 'infobox-order_supplier', 'supplier_order' => 'infobox-order_supplier', 'supplier_proposal' => 'infobox-supplier_proposal',
3637
                'ticket' => 'infobox-contrat', 'title_accountancy' => 'infobox-bank_account', 'title_hrm' => 'infobox-holiday', 'expensereport' => 'infobox-expensereport', 'trip' => 'infobox-expensereport', 'title_agenda' => 'infobox-action',
3638
                'vat' => 'infobox-bank_account',
3639
                //'title_setup'=>'infobox-action', 'tools'=>'infobox-action',
3640
                'list-alt' => 'imgforviewmode', 'calendar' => 'imgforviewmode', 'calendarweek' => 'imgforviewmode', 'calendarmonth' => 'imgforviewmode', 'calendarday' => 'imgforviewmode', 'calendarperuser' => 'imgforviewmode'
3641
            );
3642
            if (!empty($arrayconvpictotomorcess[$pictowithouttext]) && strpos($picto, '_nocolor') === false) {
3643
                $morecss .= ($morecss ? ' ' : '') . $arrayconvpictotomorcess[$pictowithouttext];
3644
            }
3645
3646
            // Define $color
3647
            $arrayconvpictotocolor = array(
3648
                'address' => '#6c6aa8', 'building' => '#6c6aa8', 'bom' => '#a69944',
3649
                'clone' => '#999', 'cog' => '#999', 'companies' => '#6c6aa8', 'company' => '#6c6aa8', 'contact' => '#6c6aa8', 'cron' => '#555',
3650
                'dynamicprice' => '#a69944',
3651
                'edit' => '#444', 'note' => '#999', 'error' => '', 'help' => '#bbb', 'listlight' => '#999', 'language' => '#555',
3652
                //'dolly'=>'#a69944', 'dollyrevert'=>'#a69944',
3653
                'lock' => '#ddd', 'lot' => '#a69944',
3654
                'map-marker-alt' => '#aaa', 'mrp' => '#a69944', 'product' => '#a69944', 'service' => '#a69944', 'inventory' => '#a69944', 'stock' => '#a69944', 'movement' => '#a69944',
3655
                'other' => '#ddd', 'world' => '#986c6a',
3656
                'partnership' => '#6c6aa8', 'playdisabled' => '#ccc', 'printer' => '#444', 'projectpub' => '#986c6a', 'resize' => '#444', 'rss' => '#cba',
3657
                //'shipment'=>'#a69944',
3658
                'security' => '#999', 'square' => '#888', 'stop-circle' => '#888', 'stats' => '#444', 'switch_off' => '#999',
3659
                'technic' => '#999', 'tick' => '#282', 'timespent' => '#555',
3660
                'uncheck' => '#800', 'uparrow' => '#555', 'user-cog' => '#999', 'country' => '#aaa', 'globe-americas' => '#aaa', 'region' => '#aaa', 'state' => '#aaa',
3661
                'website' => '#304', 'workstation' => '#a69944'
3662
            );
3663
            if (isset($arrayconvpictotocolor[$pictowithouttext]) && strpos($picto, '_nocolor') === false) {
3664
                $facolor = $arrayconvpictotocolor[$pictowithouttext];
3665
            }
3666
3667
            // This snippet only needed since function img_edit accepts only one additional parameter: no separate one for css only.
3668
            // class/style need to be extracted to avoid duplicate class/style validation errors when $moreatt is added to the end of the attributes.
3669
            $morestyle = '';
3670
            $reg = array();
3671
            if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
3672
                $morecss .= ($morecss ? ' ' : '') . $reg[1];
3673
                $moreatt = str_replace('class="' . $reg[1] . '"', '', $moreatt);
3674
            }
3675
            if (preg_match('/style="([^"]+)"/', $moreatt, $reg)) {
3676
                $morestyle = $reg[1];
3677
                $moreatt = str_replace('style="' . $reg[1] . '"', '', $moreatt);
3678
            }
3679
            $moreatt = trim($moreatt);
3680
3681
            $enabledisablehtml = '<span class="' . $fa . ' ' . $fakey . ($marginleftonlyshort ? ($marginleftonlyshort == 1 ? ' marginleftonlyshort' : ' marginleftonly') : '');
3682
            $enabledisablehtml .= ($morecss ? ' ' . $morecss : '') . '" style="' . ($fasize ? ('font-size: ' . $fasize . ';') : '') . ($facolor ? (' color: ' . $facolor . ';') : '') . ($morestyle ? ' ' . $morestyle : '') . '"' . (($notitle || empty($titlealt)) ? '' : ' title="' . dol_escape_htmltag($titlealt) . '"') . ($moreatt ? ' ' . $moreatt : '') . '>';
3683
            /*if (!empty($conf->global->MAIN_OPTIMIZEFORTEXTBROWSER)) {
3684
                $enabledisablehtml .= $titlealt;
3685
            }*/
3686
            $enabledisablehtml .= '</span>';
3687
3688
            return $enabledisablehtml;
3689
        }
3690
3691
        if (getDolGlobalString('MAIN_OVERWRITE_THEME_PATH')) {
3692
            $path = getDolGlobalString('MAIN_OVERWRITE_THEME_PATH') . '/theme/' . $theme; // If the theme does not have the same name as the module
3693
        } elseif (getDolGlobalString('MAIN_OVERWRITE_THEME_RES')) {
3694
            $path = getDolGlobalString('MAIN_OVERWRITE_THEME_RES') . '/theme/' . getDolGlobalString('MAIN_OVERWRITE_THEME_RES'); // To allow an external module to overwrite image resources whatever is activated theme
3695
        } elseif (!empty($conf->modules_parts['theme']) && array_key_exists($theme, $conf->modules_parts['theme'])) {
3696
            $path = $theme . '/theme/' . $theme; // If the theme have the same name as the module
3697
        }
3698
3699
        // If we ask an image into $url/$mymodule/img (instead of default path)
3700
        $regs = array();
3701
        if (preg_match('/^([^@]+)@([^@]+)$/i', $picto, $regs)) {
3702
            $picto = $regs[1];
3703
            $path = $regs[2]; // $path is $mymodule
3704
        }
3705
3706
        // Clean parameters
3707
        if (!preg_match('/(\.png|\.gif|\.svg)$/i', $picto)) {
3708
            $picto .= '.png';
3709
        }
3710
        // If alt path are defined, define url where img file is, according to physical path
3711
        // ex: array(["main"]=>"/home/maindir/htdocs", ["alt0"]=>"/home/moddir0/htdocs", ...)
3712
        foreach ($conf->file->dol_document_root as $type => $dirroot) {
3713
            if ($type == 'main') {
3714
                continue;
3715
            }
3716
            // This need a lot of time, that's why enabling alternative dir like "custom" dir is not recommended
3717
            if (file_exists($dirroot . '/' . $path . '/img/' . $picto)) {
3718
                $url = DOL_URL_ROOT . $conf->file->dol_url_root[$type];
3719
                break;
3720
            }
3721
        }
3722
3723
        // $url is '' or '/custom', $path is current theme or
3724
        $fullpathpicto = $url . '/' . $path . '/img/' . $picto;
3725
    }
3726
3727
    if ($srconly) {
3728
        return $fullpathpicto;
3729
    }
3730
3731
    // tag title is used for tooltip on <a>, tag alt can be used with very simple text on image for blind people
3732
    return '<img src="' . $fullpathpicto . '"' . ($notitle ? '' : ' alt="' . dol_escape_htmltag($alt) . '"') . (($notitle || empty($titlealt)) ? '' : ' title="' . dol_escape_htmltag($titlealt) . '"') . ($moreatt ? ' ' . $moreatt . ($morecss ? ' class="' . $morecss . '"' : '') : ' class="inline-block' . ($morecss ? ' ' . $morecss : '') . '"') . '>'; // Alt is used for accessibility, title for popup
3733
}
3734
3735
/**
3736
 *  Show a picto called object_picto (generic function)
3737
 *
3738
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3739
 * @param string $picto Name of image to show object_picto (example: user, group, action, bill, contract, propal, product, ...)
3740
 *                                      For external modules use imagename@mymodule to search into directory "img" of module.
3741
 * @param string $moreatt Add more attribute on img tag (ie: class="datecallink")
3742
 * @param int $pictoisfullpath If 1, image path is a full path
3743
 * @param int $srconly Return only content of the src attribute of img.
3744
 * @param int $notitle 1=Disable tag title. Use it if you add js tooltip, to avoid duplicate tooltip.
3745
 * @return string                      Return img tag
3746
 * @see    img_picto(), img_picto_common()
3747
 */
3748
function img_object($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $srconly = 0, $notitle = 0)
3749
{
3750
    if (strpos($picto, '^') === 0) {
3751
        return img_picto($titlealt, str_replace('^', '', $picto), $moreatt, $pictoisfullpath, $srconly, $notitle);
3752
    } else {
3753
        return img_picto($titlealt, 'object_' . $picto, $moreatt, $pictoisfullpath, $srconly, $notitle);
3754
    }
3755
}
3756
3757
/**
3758
 *  Show weather picto
3759
 *
3760
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3761
 * @param string|int $picto Name of image file to show (If no extension provided, we use '.png'). Image must be stored into htdocs/theme/common directory. Or level of meteo image (0-4).
3762
 * @param string $moreatt Add more attribute on img tag
3763
 * @param int $pictoisfullpath If 1, image path is a full path
3764
 * @param string $morecss More CSS
3765
 * @return     string                          Return img tag
3766
 * @see        img_object(), img_picto()
3767
 */
3768
function img_weather($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $morecss = '')
3769
{
3770
    global $conf;
3771
3772
    if (is_numeric($picto)) {
3773
        //$leveltopicto = array(0=>'weather-clear.png', 1=>'weather-few-clouds.png', 2=>'weather-clouds.png', 3=>'weather-many-clouds.png', 4=>'weather-storm.png');
3774
        //$picto = $leveltopicto[$picto];
3775
        return '<i class="fa fa-weather-level' . $picto . '"></i>';
3776
    } elseif (!preg_match('/(\.png|\.gif)$/i', $picto)) {
3777
        $picto .= '.png';
3778
    }
3779
3780
    $path = constant('DOL_URL_ROOT') . '/theme/' . $conf->theme . '/img/weather/' . $picto;
3781
3782
    return img_picto($titlealt, $path, $moreatt, 1, 0, 0, '', $morecss);
3783
}
3784
3785
/**
3786
 *  Show picto (generic function)
3787
 *
3788
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3789
 * @param string $picto Name of image file to show (If no extension provided, we use '.png'). Image must be stored into htdocs/theme/common directory.
3790
 * @param string $moreatt Add more attribute on img tag
3791
 * @param int $pictoisfullpath If 1, image path is a full path
3792
 * @param int $notitle 1=Disable tag title. Use it if you add js tooltip, to avoid duplicate tooltip.
3793
 * @return     string                          Return img tag
3794
 * @see        img_object(), img_picto()
3795
 */
3796
function img_picto_common($titlealt, $picto, $moreatt = '', $pictoisfullpath = 0, $notitle = 0)
3797
{
3798
    global $conf;
3799
3800
    if (!preg_match('/(\.png|\.gif)$/i', $picto)) {
3801
        $picto .= '.png';
3802
    }
3803
3804
    if ($pictoisfullpath) {
3805
        $path = $picto;
3806
    } else {
3807
        $path = constant('DOL_URL_ROOT') . '/theme/common/' . $picto;
3808
3809
        if (getDolGlobalInt('MAIN_MODULE_CAN_OVERWRITE_COMMONICONS')) {
3810
            $themepath = DOL_DOCUMENT_ROOT . '/theme/' . $conf->theme . '/img/' . $picto;
3811
3812
            if (file_exists($themepath)) {
3813
                $path = $themepath;
3814
            }
3815
        }
3816
    }
3817
3818
    return img_picto($titlealt, $path, $moreatt, 1, 0, $notitle);
3819
}
3820
3821
/**
3822
 *  Show logo action
3823
 *
3824
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3825
 * @param string $numaction Action id or code to show
3826
 * @param string $picto Name of image file to show ('filenew', ...)
3827
 *                                      If no extension provided, we use '.png'. Image must be stored into theme/xxx/img directory.
3828
 *                                      Example: picto.png                  if picto.png is stored into htdocs/theme/mytheme/img
3829
 *                                      Example: picto.png@mymodule         if picto.png is stored into htdocs/mymodule/img
3830
 *                                      Example: /mydir/mysubdir/picto.png  if picto.png is stored into htdocs/mydir/mysubdir (pictoisfullpath must be set to 1)
3831
 * @param string $moreatt More attributes
3832
 * @return string                      Return an img tag
3833
 */
3834
function img_action($titlealt, $numaction, $picto = '', $moreatt = '')
3835
{
3836
    global $langs;
3837
3838
    if (empty($titlealt) || $titlealt == 'default') {
3839
        if ($numaction == '-1' || $numaction == 'ST_NO') {
3840
            $numaction = -1;
3841
            $titlealt = $langs->transnoentitiesnoconv('ChangeDoNotContact');
3842
        } elseif ($numaction == '0' || $numaction == 'ST_NEVER') {
3843
            $numaction = 0;
3844
            $titlealt = $langs->transnoentitiesnoconv('ChangeNeverContacted');
3845
        } elseif ($numaction == '1' || $numaction == 'ST_TODO') {
3846
            $numaction = 1;
3847
            $titlealt = $langs->transnoentitiesnoconv('ChangeToContact');
3848
        } elseif ($numaction == '2' || $numaction == 'ST_PEND') {
3849
            $numaction = 2;
3850
            $titlealt = $langs->transnoentitiesnoconv('ChangeContactInProcess');
3851
        } elseif ($numaction == '3' || $numaction == 'ST_DONE') {
3852
            $numaction = 3;
3853
            $titlealt = $langs->transnoentitiesnoconv('ChangeContactDone');
3854
        } else {
3855
            $titlealt = $langs->transnoentitiesnoconv('ChangeStatus ' . $numaction);
3856
            $numaction = 0;
3857
        }
3858
    }
3859
    if (!is_numeric($numaction)) {
3860
        $numaction = 0;
3861
    }
3862
3863
    return img_picto($titlealt, (empty($picto) ? 'stcomm' . $numaction . '.png' : $picto), $moreatt);
3864
}
3865
3866
/**
3867
 *  Show pdf logo
3868
 *
3869
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3870
 * @param int $size Taille de l'icone : 3 = 16x16px , 2 = 14x14px
3871
 * @return string                  Retourne tag img
3872
 */
3873
function img_pdf($titlealt = 'default', $size = 3)
3874
{
3875
    global $langs;
3876
3877
    if ($titlealt == 'default') {
3878
        $titlealt = $langs->trans('Show');
3879
    }
3880
3881
    return img_picto($titlealt, 'pdf' . $size . '.png');
3882
}
3883
3884
/**
3885
 *  Show logo +
3886
 *
3887
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3888
 * @param string $other Add more attributes on img
3889
 * @return string              Return tag img
3890
 */
3891
function img_edit_add($titlealt = 'default', $other = '')
3892
{
3893
    global $langs;
3894
3895
    if ($titlealt == 'default') {
3896
        $titlealt = $langs->trans('Add');
3897
    }
3898
3899
    return img_picto($titlealt, 'edit_add.png', $other);
3900
}
3901
3902
/**
3903
 *  Show logo -
3904
 *
3905
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3906
 * @param string $other Add more attributes on img
3907
 * @return string              Return tag img
3908
 */
3909
function img_edit_remove($titlealt = 'default', $other = '')
3910
{
3911
    global $langs;
3912
3913
    if ($titlealt == 'default') {
3914
        $titlealt = $langs->trans('Remove');
3915
    }
3916
3917
    return img_picto($titlealt, 'edit_remove.png', $other);
3918
}
3919
3920
/**
3921
 *  Show logo edit/modify fiche
3922
 *
3923
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3924
 * @param integer $float If you have to put the style "float: right"
3925
 * @param string $other Add more attributes on img
3926
 * @return string              Return tag img
3927
 */
3928
function img_edit($titlealt = 'default', $float = 0, $other = '')
3929
{
3930
    global $langs;
3931
3932
    if ($titlealt == 'default') {
3933
        $titlealt = $langs->trans('Modify');
3934
    }
3935
3936
    return img_picto($titlealt, 'edit.png', ($float ? 'style="float: ' . ($langs->tab_translate["DIRECTION"] == 'rtl' ? 'left' : 'right') . '"' : "") . ($other ? ' ' . $other : ''));
3937
}
3938
3939
/**
3940
 *  Show logo view card
3941
 *
3942
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3943
 * @param integer $float If you have to put the style "float: right"
3944
 * @param string $other Add more attributes on img
3945
 * @return string              Return tag img
3946
 */
3947
function img_view($titlealt = 'default', $float = 0, $other = 'class="valignmiddle"')
3948
{
3949
    global $langs;
3950
3951
    if ($titlealt == 'default') {
3952
        $titlealt = $langs->trans('View');
3953
    }
3954
3955
    $moreatt = ($float ? 'style="float: right" ' : '') . $other;
3956
3957
    return img_picto($titlealt, 'eye', $moreatt);
3958
}
3959
3960
/**
3961
 *  Show delete logo
3962
 *
3963
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3964
 * @param string $other Add more attributes on img
3965
 * @param string $morecss More CSS
3966
 * @return string              Retourne tag img
3967
 */
3968
function img_delete($titlealt = 'default', $other = 'class="pictodelete"', $morecss = '')
3969
{
3970
    global $langs;
3971
3972
    if ($titlealt == 'default') {
3973
        $titlealt = $langs->trans('Delete');
3974
    }
3975
3976
    return img_picto($titlealt, 'delete.png', $other, 0, 0, 0, '', $morecss);
3977
}
3978
3979
/**
3980
 *  Show printer logo
3981
 *
3982
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3983
 * @param string $other Add more attributes on img
3984
 * @return string              Retourne tag img
3985
 */
3986
function img_printer($titlealt = "default", $other = '')
3987
{
3988
    global $langs;
3989
    if ($titlealt == "default") {
3990
        $titlealt = $langs->trans("Print");
3991
    }
3992
    return img_picto($titlealt, 'printer.png', $other);
3993
}
3994
3995
/**
3996
 *  Show split logo
3997
 *
3998
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
3999
 * @param string $other Add more attributes on img
4000
 * @return string              Retourne tag img
4001
 */
4002
function img_split($titlealt = 'default', $other = 'class="pictosplit"')
4003
{
4004
    global $langs;
4005
4006
    if ($titlealt == 'default') {
4007
        $titlealt = $langs->trans('Split');
4008
    }
4009
4010
    return img_picto($titlealt, 'split.png', $other);
4011
}
4012
4013
/**
4014
 *  Show help logo with cursor "?"
4015
 *
4016
 * @param int $usehelpcursor 1=Use help cursor, 2=Use click pointer cursor, 0=No specific cursor
4017
 * @param int|string $usealttitle Text to use as alt title
4018
 * @return string                                  Return tag img
4019
 */
4020
function img_help($usehelpcursor = 1, $usealttitle = 1)
4021
{
4022
    global $langs;
4023
4024
    if ($usealttitle) {
4025
        if (is_string($usealttitle)) {
4026
            $usealttitle = dol_escape_htmltag($usealttitle);
4027
        } else {
4028
            $usealttitle = $langs->trans('Info');
4029
        }
4030
    }
4031
4032
    return img_picto($usealttitle, 'info.png', 'style="vertical-align: middle;' . ($usehelpcursor == 1 ? ' cursor: help' : ($usehelpcursor == 2 ? ' cursor: pointer' : '')) . '"');
4033
}
4034
4035
/**
4036
 *  Show info logo
4037
 *
4038
 * @param string $titlealt Text on alt and title of image. Alt only if param notitle is set to 1. If text is "TextA:TextB", use Text A on alt and Text B on title.
4039
 * @return string              Return img tag
4040
 */
4041
function img_info($titlealt = 'default')
4042
{
4043
    global $langs;
4044
4045
    if ($titlealt == 'default') {
4046
        $titlealt = $langs->trans('Informations');
4047
    }
4048
4049
    return img_picto($titlealt, 'info.png', 'style="vertical-align: middle;"');
4050
}
4051
4052
/**
4053
 *  Return a string with VAT rate label formatted for view output
4054
 *  Used into pdf and HTML pages
4055
 *
4056
 * @param string $rate Rate value to format ('19.6', '19,6', '19.6%', '19,6%', '19.6 (CODEX)', ...)
4057
 * @param boolean $addpercent Add a percent % sign in output
4058
 * @param int $info_bits Miscellaneous information on vat (0=Default, 1=French NPR vat)
4059
 * @param int $usestarfornpr -1=Never show, 0 or 1=Use '*' for NPR vat rates
4060
 * @param int $html Used for html output
4061
 * @return string                  String with formatted amounts ('19,6' or '19,6%' or '8.5% (NPR)' or '8.5% *' or '19,6 (CODEX)')
4062
 */
4063
function vatrate($rate, $addpercent = false, $info_bits = 0, $usestarfornpr = 0, $html = 0)
4064
{
4065
    $morelabel = '';
4066
4067
    if (preg_match('/%/', $rate)) {
4068
        $rate = str_replace('%', '', $rate);
4069
        $addpercent = true;
4070
    }
4071
    $reg = array();
4072
    if (preg_match('/\((.*)\)/', $rate, $reg)) {
4073
        $morelabel = ' (' . $reg[1] . ')';
4074
        $rate = preg_replace('/\s*' . preg_quote($morelabel, '/') . '/', '', $rate);
4075
        $morelabel = ' ' . ($html ? '<span class="opacitymedium">' : '') . '(' . $reg[1] . ')' . ($html ? '</span>' : '');
4076
    }
4077
    if (preg_match('/\*/', $rate)) {
4078
        $rate = str_replace('*', '', $rate);
4079
        $info_bits |= 1;
4080
    }
4081
4082
    // If rate is '9/9/9' we don't change it.  If rate is '9.000' we apply price()
4083
    if (!preg_match('/\//', $rate)) {
4084
        $ret = price($rate, 0, '', 0, 0) . ($addpercent ? '%' : '');
4085
    } else {
4086
        // TODO Split on / and output with a price2num to have clean numbers without ton of 000.
4087
        $ret = $rate . ($addpercent ? '%' : '');
4088
    }
4089
    if (($info_bits & 1) && $usestarfornpr >= 0) {
4090
        $ret .= ' *';
4091
    }
4092
    $ret .= $morelabel;
4093
    return $ret;
4094
}
4095
4096
4097
/**
4098
 *      Function to format a value into an amount for visual output
4099
 *      Function used into PDF and HTML pages
4100
 *
4101
 * @param string|float $amount Amount value to format
4102
 * @param int<0,1> $form Type of formatting: 1=HTML, 0=no formatting (no by default)
4103
 * @param Translate|string|null $outlangs Object langs for output. '' use default lang. 'none' use international separators.
4104
 * @param int $trunc 1=Truncate if there is more decimals than MAIN_MAX_DECIMALS_SHOWN (default), 0=Does not truncate. Deprecated because amount are rounded (to unit or total amount accuracy) before being inserted into database or after a computation, so this parameter should be useless.
4105
 * @param int $rounding MINIMUM number of decimal to show: 0=no change, -1=we use min($conf->global->MAIN_MAX_DECIMALS_UNIT,$conf->global->MAIN_MAX_DECIMALS_TOT)
4106
 * @param int|string $forcerounding MAXIMUM number of decimal to forcerounding decimal: -1=no change, 'MU' or 'MT' or a numeric to round to MU or MT or to a given number of decimal
4107
 * @param string $currency_code To add currency symbol (''=add nothing, 'auto'=Use default currency, 'XXX'=add currency symbols for XXX currency)
4108
 * @return string                                  String with formatted amount
4109
 *
4110
 * @see    price2num()                             Revert function of price
4111
 */
4112
function price($amount, $form = 0, $outlangs = '', $trunc = 1, $rounding = -1, $forcerounding = -1, $currency_code = '')
4113
{
4114
    global $langs, $conf;
4115
4116
    // Clean parameters
4117
    if (empty($amount)) {
4118
        $amount = 0; // To have a numeric value if amount not defined or = ''
4119
    }
4120
    $amount = (is_numeric($amount) ? $amount : 0); // Check if amount is numeric, for example, an error occurred when amount value = o (letter) instead 0 (number)
4121
    if ($rounding == -1) {
4122
        $rounding = min(getDolGlobalString('MAIN_MAX_DECIMALS_UNIT'), getDolGlobalString('MAIN_MAX_DECIMALS_TOT'));
4123
    }
4124
    $nbdecimal = $rounding;
4125
4126
    if ($outlangs === 'none') {
4127
        // Use international separators
4128
        $dec = '.';
4129
        $thousand = '';
4130
    } else {
4131
        // Output separators by default (french)
4132
        $dec = ',';
4133
        $thousand = ' ';
4134
4135
        // If $outlangs not forced, we use use language
4136
        if (!($outlangs instanceof Translate)) {
4137
            $outlangs = $langs;
4138
        }
4139
4140
        if ($outlangs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal") {
0 ignored issues
show
Bug introduced by
The method transnoentitiesnoconv() does not exist on null. ( Ignorable by Annotation )

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

4140
        if ($outlangs->/** @scrutinizer ignore-call */ transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal") {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
4141
            $dec = $outlangs->transnoentitiesnoconv("SeparatorDecimal");
4142
        }
4143
        if ($outlangs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") {
4144
            $thousand = $outlangs->transnoentitiesnoconv("SeparatorThousand");
4145
        }
4146
        if ($thousand == 'None') {
4147
            $thousand = '';
4148
        } elseif ($thousand == 'Space') {
4149
            $thousand = ' ';
4150
        }
4151
    }
4152
    //print "outlangs=".$outlangs->defaultlang." amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
4153
4154
    //print "amount=".$amount."-";
4155
    $amount = str_replace(',', '.', $amount); // should be useless
4156
    //print $amount."-";
4157
    $data = explode('.', $amount);
4158
    $decpart = isset($data[1]) ? $data[1] : '';
4159
    $decpart = preg_replace('/0+$/i', '', $decpart); // Supprime les 0 de fin de partie decimale
4160
    //print "decpart=".$decpart."<br>";
4161
    $end = '';
4162
4163
    // We increase nbdecimal if there is more decimal than asked (to not loose information)
4164
    if (dol_strlen($decpart) > $nbdecimal) {
4165
        $nbdecimal = dol_strlen($decpart);
4166
    }
4167
    // Si on depasse max
4168
    $max_nbdecimal = getDolGlobalString('MAIN_MAX_DECIMALS_SHOWN');
4169
    if ($trunc && $nbdecimal > (int)$max_nbdecimal) {
4170
        $nbdecimal = $max_nbdecimal;
4171
        if (preg_match('/\.\.\./i', $nbdecimal)) {
4172
            // Si un affichage est tronque, on montre des ...
4173
            $end = '...';
4174
        }
4175
    }
4176
4177
    // If force rounding
4178
    if ((string)$forcerounding != '-1') {
4179
        if ($forcerounding === 'MU') {
4180
            $nbdecimal = getDolGlobalString('MAIN_MAX_DECIMALS_UNIT');
4181
        } elseif ($forcerounding === 'MT') {
4182
            $nbdecimal = getDolGlobalString('MAIN_MAX_DECIMALS_TOT');
4183
        } elseif ($forcerounding >= 0) {
4184
            $nbdecimal = $forcerounding;
4185
        }
4186
    }
4187
4188
    // Format number
4189
    $output = number_format((float)$amount, $nbdecimal, $dec, $thousand);
4190
    if ($form) {
4191
        $output = preg_replace('/\s/', '&nbsp;', $output);
4192
        $output = preg_replace('/\'/', '&#039;', $output);
4193
    }
4194
    // Add symbol of currency if requested
4195
    $cursymbolbefore = $cursymbolafter = '';
4196
    if ($currency_code && is_object($outlangs)) {
4197
        if ($currency_code == 'auto') {
4198
            $currency_code = $conf->currency;
4199
        }
4200
4201
        $listofcurrenciesbefore = array('AUD', 'CAD', 'CNY', 'COP', 'CLP', 'GBP', 'HKD', 'MXN', 'PEN', 'USD', 'CRC', 'ZAR');
4202
        $listoflanguagesbefore = array('nl_NL');
4203
        if (in_array($currency_code, $listofcurrenciesbefore) || in_array($outlangs->defaultlang, $listoflanguagesbefore)) {
4204
            $cursymbolbefore .= $outlangs->getCurrencySymbol($currency_code);
4205
        } else {
4206
            $tmpcur = $outlangs->getCurrencySymbol($currency_code);
4207
            $cursymbolafter .= ($tmpcur == $currency_code ? ' ' . $tmpcur : $tmpcur);
4208
        }
4209
    }
4210
    $output = $cursymbolbefore . $output . $end . ($cursymbolafter ? ' ' : '') . $cursymbolafter;
4211
4212
    return $output;
4213
}
4214
4215
/**
4216
 *  Function that return a number with universal decimal format (decimal separator is '.') from an amount typed by a user.
4217
 *  Function to use on each input amount before any numeric test or database insert. A better name for this function
4218
 *  should be roundtext2num().
4219
 *
4220
 * @param string|float $amount Amount to convert/clean or round
4221
 * @param string|int $rounding ''=No rounding
4222
 *                                          'MU'=Round to Max unit price (MAIN_MAX_DECIMALS_UNIT)
4223
 *                                          'MT'=Round to Max for totals with Tax (MAIN_MAX_DECIMALS_TOT)
4224
 *                                          'MS'=Round to Max for stock quantity (MAIN_MAX_DECIMALS_STOCK)
4225
 *                                          'CU'=Round to Max unit price of foreign currency accuracy
4226
 *                                          'CT'=Round to Max for totals with Tax of foreign currency accuracy
4227
 *                                          Numeric = Nb of digits for rounding (For example 2 for a percentage)
4228
 * @param int $option Put 1 if you know that content is already universal format number (so no correction on decimal will be done)
4229
 *                                          Put 2 if you know that number is a user input (so we know we have to fix decimal separator).
4230
 * @return string                          Amount with universal numeric format (Example: '99.99999'), or error message.
4231
 *                                          If conversion fails to return a numeric, it returns:
4232
 *                                          - text unchanged or partial if ($rounding = ''): price2num('W9ç', '', 0)   => '9ç', price2num('W9ç', '', 1)   => 'W9ç', price2num('W9ç', '', 2)   => '9ç'
4233
 *                                          - '0' if ($rounding is defined):                 price2num('W9ç', 'MT', 0) => '9',  price2num('W9ç', 'MT', 1) => '0',   price2num('W9ç', 'MT', 2) => '9'
4234
 *                                          Note: The best way to guarantee a numeric value is to add a cast (float) before the price2num().
4235
 *                                          If amount is null or '', it returns '' if $rounding = '', it returns '0' if $rounding is defined.
4236
 *
4237
 * @see    price()                         Opposite function of price2num
4238
 */
4239
function price2num($amount, $rounding = '', $option = 0)
4240
{
4241
    global $langs, $conf;
4242
4243
    // Clean parameters
4244
    if (is_null($amount)) {
4245
        $amount = '';
4246
    }
4247
4248
    // Round PHP function does not allow number like '1,234.56' nor '1.234,56' nor '1 234,56'
4249
    // Numbers must be '1234.56'
4250
    // Decimal delimiter for PHP and database SQL requests must be '.'
4251
    $dec = ',';
4252
    $thousand = ' ';
4253
    if (is_null($langs)) {  // $langs is not defined, we use english values.
4254
        $dec = '.';
4255
        $thousand = ',';
4256
    } else {
4257
        if ($langs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal") {
4258
            $dec = $langs->transnoentitiesnoconv("SeparatorDecimal");
4259
        }
4260
        if ($langs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") {
4261
            $thousand = $langs->transnoentitiesnoconv("SeparatorThousand");
4262
        }
4263
    }
4264
    if ($thousand == 'None') {
4265
        $thousand = '';
4266
    } elseif ($thousand == 'Space') {
4267
        $thousand = ' ';
4268
    }
4269
    //print "amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
4270
4271
    // Convert value to universal number format (no thousand separator, '.' as decimal separator)
4272
    if ($option != 1) { // If not a PHP number or unknown, we change or clean format
4273
        //print "\n".'PP'.$amount.' - '.$dec.' - '.$thousand.' - '.intval($amount).'<br>';
4274
        if (!is_numeric($amount)) {
4275
            $amount = preg_replace('/[a-zA-Z\/\\\*\(\)\<\>\_]/', '', $amount);
4276
        }
4277
4278
        if ($option == 2 && $thousand == '.' && preg_match('/\.(\d\d\d)$/', (string)$amount)) {    // It means the . is used as a thousand separator and string come from input data, so 1.123 is 1123
4279
            $amount = str_replace($thousand, '', $amount);
4280
        }
4281
4282
        // Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
4283
        // to format defined by LC_NUMERIC after a calculation and we want source format to be like defined by Dolibarr setup.
4284
        // So if number was already a good number, it is converted into local Dolibarr setup.
4285
        if (is_numeric($amount)) {
4286
            // We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
4287
            $temps = sprintf("%10.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
4288
            $temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
4289
            $nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
4290
            $amount = number_format($amount, $nbofdec, $dec, $thousand);
4291
        }
4292
        //print "QQ".$amount."<br>\n";
4293
4294
        // Now make replace (the main goal of function)
4295
        if ($thousand != ',' && $thousand != '.') {
4296
            $amount = str_replace(',', '.', $amount); // To accept 2 notations for french users
4297
        }
4298
4299
        $amount = str_replace(' ', '', $amount); // To avoid spaces
4300
        $amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
4301
        $amount = str_replace($dec, '.', $amount);
4302
4303
        $amount = preg_replace('/[^0-9\-\.]/', '', $amount);    // Clean non numeric chars (so it clean some UTF8 spaces for example.
4304
    }
4305
    //print ' XX'.$amount.' '.$rounding;
4306
4307
    // Now, $amount is a real PHP float number. We make a rounding if required.
4308
    if ($rounding) {
4309
        $nbofdectoround = '';
4310
        if ($rounding == 'MU') {
4311
            $nbofdectoround = getDolGlobalString('MAIN_MAX_DECIMALS_UNIT');
4312
        } elseif ($rounding == 'MT') {
4313
            $nbofdectoround = getDolGlobalString('MAIN_MAX_DECIMALS_TOT');
4314
        } elseif ($rounding == 'MS') {
4315
            $nbofdectoround = isset($conf->global->MAIN_MAX_DECIMALS_STOCK) ? $conf->global->MAIN_MAX_DECIMALS_STOCK : 5;
4316
        } elseif ($rounding == 'CU') {
4317
            $nbofdectoround = max(getDolGlobalString('MAIN_MAX_DECIMALS_UNIT'), 8); // TODO Use param of currency
4318
        } elseif ($rounding == 'CT') {
4319
            $nbofdectoround = max(getDolGlobalString('MAIN_MAX_DECIMALS_TOT'), 8);      // TODO Use param of currency
4320
        } elseif (is_numeric($rounding)) {
4321
            $nbofdectoround = (int)$rounding;
4322
        }
4323
4324
        //print " RR".$amount.' - '.$nbofdectoround.'<br>';
4325
        if (dol_strlen($nbofdectoround)) {
4326
            $amount = round(is_string($amount) ? (float)$amount : $amount, $nbofdectoround); // $nbofdectoround can be 0.
4327
        } else {
4328
            return 'ErrorBadParameterProvidedToFunction';
4329
        }
4330
        //print ' SS'.$amount.' - '.$nbofdec.' - '.$dec.' - '.$thousand.' - '.$nbofdectoround.'<br>';
4331
4332
        // Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
4333
        // to format defined by LC_NUMERIC after a calculation and we want source format to be defined by Dolibarr setup.
4334
        if (is_numeric($amount)) {
4335
            // We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
4336
            $temps = sprintf("%10.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
4337
            $temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
4338
            $nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
4339
            $amount = number_format($amount, min($nbofdec, $nbofdectoround), $dec, $thousand); // Convert amount to format with dolibarr dec and thousand
4340
        }
4341
        //print "TT".$amount.'<br>';
4342
4343
        // Always make replace because each math function (like round) replace
4344
        // with local values and we want a number that has a SQL string format x.y
4345
        if ($thousand != ',' && $thousand != '.') {
4346
            $amount = str_replace(',', '.', $amount); // To accept 2 notations for french users
4347
        }
4348
4349
        $amount = str_replace(' ', '', $amount); // To avoid spaces
4350
        $amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
4351
        $amount = str_replace($dec, '.', $amount);
4352
4353
        $amount = preg_replace('/[^0-9\-\.]/', '', $amount);    // Clean non numeric chars (so it clean some UTF8 spaces for example.
4354
    }
4355
4356
    return $amount;
4357
}
4358
4359
/**
4360
 * Output a dimension with best unit
4361
 *
4362
 * @param float $dimension Dimension
4363
 * @param int $unit Unit scale of dimension (Example: 0=kg, -3=g, -6=mg, 98=ounce, 99=pound, ...)
4364
 * @param string $type 'weight', 'volume', ...
4365
 * @param Translate $outputlangs Translate language object
4366
 * @param int $round -1 = non rounding, x = number of decimal
4367
 * @param string $forceunitoutput 'no' or numeric (-3, -6, ...) compared to $unit (In most case, this value is value defined into $conf->global->MAIN_WEIGHT_DEFAULT_UNIT)
4368
 * @param int $use_short_label 1=Use short label ('g' instead of 'gram'). Short labels are not translated.
4369
 * @return  string                          String to show dimensions
4370
 */
4371
function showDimensionInBestUnit($dimension, $unit, $type, $outputlangs, $round = -1, $forceunitoutput = 'no', $use_short_label = 0)
4372
{
4373
    require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/product.lib.php';
4374
4375
    if (($forceunitoutput == 'no' && $dimension < 1 / 10000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -6)) {
4376
        $dimension *= 1000000;
4377
        $unit -= 6;
4378
    } elseif (($forceunitoutput == 'no' && $dimension < 1 / 10 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == -3)) {
4379
        $dimension *= 1000;
4380
        $unit -= 3;
4381
    } elseif (($forceunitoutput == 'no' && $dimension > 100000000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 6)) {
4382
        $dimension /= 1000000;
4383
        $unit += 6;
4384
    } elseif (($forceunitoutput == 'no' && $dimension > 100000 && $unit < 90) || (is_numeric($forceunitoutput) && $forceunitoutput == 3)) {
4385
        $dimension /= 1000;
4386
        $unit += 3;
4387
    }
4388
    // Special case when we want output unit into pound or ounce
4389
    /* TODO
4390
    if ($unit < 90 && $type == 'weight' && is_numeric($forceunitoutput) && (($forceunitoutput == 98) || ($forceunitoutput == 99))
4391
    {
4392
        $dimension = // convert dimension from standard unit into ounce or pound
4393
        $unit = $forceunitoutput;
4394
    }
4395
    if ($unit > 90 && $type == 'weight' && is_numeric($forceunitoutput) && $forceunitoutput < 90)
4396
    {
4397
        $dimension = // convert dimension from standard unit into ounce or pound
4398
        $unit = $forceunitoutput;
4399
    }*/
4400
4401
    $ret = price($dimension, 0, $outputlangs, 0, 0, $round);
4402
    // @phan-suppress-next-line PhanPluginSuspiciousParamPosition
4403
    $ret .= ' ' . measuringUnitString(0, $type, $unit, $use_short_label, $outputlangs);
4404
4405
    return $ret;
4406
}
4407
4408
4409
/**
4410
 *  Return localtax rate for a particular vat, when selling a product with vat $vatrate, from a $thirdparty_buyer to a $thirdparty_seller
4411
 *  Note: This function applies same rules than get_default_tva
4412
 *
4413
 * @param float|string $vatrate Vat rate. Can be '8.5' or '8.5 (VATCODEX)' for example
4414
 * @param int $local Local tax to search and return (1 or 2 return only tax rate 1 or tax rate 2)
4415
 * @param Societe $thirdparty_buyer Object of buying third party
4416
 * @param Societe $thirdparty_seller Object of selling third party ($mysoc if not defined)
4417
 * @param int $vatnpr If vat rate is NPR or not
4418
 * @return int<0,0>|string                     0 if not found, localtax rate if found
4419
 * @see get_default_tva()
4420
 */
4421
function get_localtax($vatrate, $local, $thirdparty_buyer = null, $thirdparty_seller = null, $vatnpr = 0)
4422
{
4423
    global $db, $conf, $mysoc;
4424
4425
    if (empty($thirdparty_seller) || !is_object($thirdparty_seller)) {
4426
        $thirdparty_seller = $mysoc;
4427
    }
4428
4429
    dol_syslog("get_localtax tva=" . $vatrate . " local=" . $local . " thirdparty_buyer id=" . (is_object($thirdparty_buyer) ? $thirdparty_buyer->id : '') . "/country_code=" . (is_object($thirdparty_buyer) ? $thirdparty_buyer->country_code : '') . " thirdparty_seller id=" . $thirdparty_seller->id . "/country_code=" . $thirdparty_seller->country_code . " thirdparty_seller localtax1_assuj=" . $thirdparty_seller->localtax1_assuj . "  thirdparty_seller localtax2_assuj=" . $thirdparty_seller->localtax2_assuj);
4430
4431
    $vatratecleaned = $vatrate;
4432
    $reg = array();
4433
    if (preg_match('/^(.*)\s*\((.*)\)$/', (string)$vatrate, $reg)) {     // If vat is "xx (yy)"
4434
        $vatratecleaned = trim($reg[1]);
4435
        $vatratecode = $reg[2];
4436
    }
4437
4438
    /*if ($thirdparty_buyer->country_code != $thirdparty_seller->country_code)
4439
    {
4440
        return 0;
4441
    }*/
4442
4443
    // Some test to guess with no need to make database access
4444
    if ($mysoc->country_code == 'ES') { // For spain localtaxes 1 and 2, tax is qualified if buyer use local tax
4445
        if ($local == 1) {
4446
            if (!$mysoc->localtax1_assuj || (string)$vatratecleaned == "0") {
4447
                return 0;
4448
            }
4449
            if ($thirdparty_seller->id == $mysoc->id) {
4450
                if (!$thirdparty_buyer->localtax1_assuj) {
4451
                    return 0;
4452
                }
4453
            } else {
4454
                if (!$thirdparty_seller->localtax1_assuj) {
4455
                    return 0;
4456
                }
4457
            }
4458
        }
4459
4460
        if ($local == 2) {
4461
            //if (! $mysoc->localtax2_assuj || (string) $vatratecleaned == "0") return 0;
4462
            if (!$mysoc->localtax2_assuj) {
4463
                return 0; // If main vat is 0, IRPF may be different than 0.
4464
            }
4465
            if ($thirdparty_seller->id == $mysoc->id) {
4466
                if (!$thirdparty_buyer->localtax2_assuj) {
4467
                    return 0;
4468
                }
4469
            } else {
4470
                if (!$thirdparty_seller->localtax2_assuj) {
4471
                    return 0;
4472
                }
4473
            }
4474
        }
4475
    } else {
4476
        if ($local == 1 && !$thirdparty_seller->localtax1_assuj) {
4477
            return 0;
4478
        }
4479
        if ($local == 2 && !$thirdparty_seller->localtax2_assuj) {
4480
            return 0;
4481
        }
4482
    }
4483
4484
    // For some country MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY is forced to on.
4485
    if (in_array($mysoc->country_code, array('ES'))) {
4486
        $conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY = 1;
4487
    }
4488
4489
    // Search local taxes
4490
    if (getDolGlobalString('MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY')) {
4491
        if ($local == 1) {
4492
            if ($thirdparty_seller != $mysoc) {
4493
                if (!isOnlyOneLocalTax($local)) {  // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
4494
                    return $thirdparty_seller->localtax1_value;
4495
                }
4496
            } else { // i am the seller
4497
                if (!isOnlyOneLocalTax($local)) { // TODO If seller is me, why not always returning this, even if there is only one locatax vat.
4498
                    return $conf->global->MAIN_INFO_VALUE_LOCALTAX1;
4499
                }
4500
            }
4501
        }
4502
        if ($local == 2) {
4503
            if ($thirdparty_seller != $mysoc) {
4504
                if (!isOnlyOneLocalTax($local)) {  // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
4505
                    // TODO We should also return value defined on thirdparty only if defined
4506
                    return $thirdparty_seller->localtax2_value;
4507
                }
4508
            } else { // i am the seller
4509
                if (in_array($mysoc->country_code, array('ES'))) {
4510
                    return $thirdparty_buyer->localtax2_value;
4511
                } else {
4512
                    return $conf->global->MAIN_INFO_VALUE_LOCALTAX2;
4513
                }
4514
            }
4515
        }
4516
    }
4517
4518
    // By default, search value of local tax on line of common tax
4519
    $sql = "SELECT t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
4520
    $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t, " . MAIN_DB_PREFIX . "c_country as c";
4521
    $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $db->escape($thirdparty_seller->country_code) . "'";
4522
    $sql .= " AND t.taux = " . ((float)$vatratecleaned) . " AND t.active = 1";
4523
    $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
4524
    if (!empty($vatratecode)) {
4525
        $sql .= " AND t.code ='" . $db->escape($vatratecode) . "'"; // If we have the code, we use it in priority
4526
    } else {
4527
        $sql .= " AND t.recuperableonly = '" . $db->escape($vatnpr) . "'";
4528
    }
4529
4530
    $resql = $db->query($sql);
4531
4532
    if ($resql) {
4533
        $obj = $db->fetch_object($resql);
4534
        if ($obj) {
4535
            if ($local == 1) {
4536
                return $obj->localtax1;
4537
            } elseif ($local == 2) {
4538
                return $obj->localtax2;
4539
            }
4540
        }
4541
    }
4542
4543
    return 0;
4544
}
4545
4546
4547
/**
4548
 * Return true if LocalTax (1 or 2) is unique.
4549
 * Example: If localtax1 is 5 on line with highest common vat rate, return true
4550
 * Example: If localtax1 is 5:8:15 on line with highest common vat rate, return false
4551
 *
4552
 * @param int $local Local tax to test (1 or 2)
4553
 * @return  boolean         True if LocalTax have multiple values, False if not
4554
 */
4555
function isOnlyOneLocalTax($local)
4556
{
4557
    $tax = get_localtax_by_third($local);
4558
4559
    $valors = explode(":", $tax);
4560
4561
    if (count($valors) > 1) {
4562
        return false;
4563
    } else {
4564
        return true;
4565
    }
4566
}
4567
4568
/**
4569
 * Get values of localtaxes (1 or 2) for company country for the common vat with the highest value
4570
 *
4571
 * @param int $local LocalTax to get
4572
 * @return  string                      Values of localtax (Can be '20', '-19:-15:-9') or 'Error'
4573
 */
4574
function get_localtax_by_third($local)
4575
{
4576
    global $db, $mysoc;
4577
4578
    $sql = " SELECT t.localtax" . $local . " as localtax";
4579
    $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t INNER JOIN " . MAIN_DB_PREFIX . "c_country as c ON c.rowid = t.fk_pays";
4580
    $sql .= " WHERE c.code = '" . $db->escape($mysoc->country_code) . "' AND t.active = 1 AND t.entity IN (" . getEntity('c_tva') . ") AND t.taux = (";
4581
    $sql .= "SELECT MAX(tt.taux) FROM " . MAIN_DB_PREFIX . "c_tva as tt INNER JOIN " . MAIN_DB_PREFIX . "c_country as c ON c.rowid = tt.fk_pays";
4582
    $sql .= " WHERE c.code = '" . $db->escape($mysoc->country_code) . "' AND t.entity IN (" . getEntity('c_tva') . ") AND tt.active = 1)";
4583
    $sql .= " AND t.localtax" . $local . "_type <> '0'";
4584
    $sql .= " ORDER BY t.rowid DESC";
4585
4586
    $resql = $db->query($sql);
4587
    if ($resql) {
4588
        $obj = $db->fetch_object($resql);
4589
        if ($obj) {
4590
            return $obj->localtax;
4591
        } else {
4592
            return '0';
4593
        }
4594
    }
4595
4596
    return 'Error';
4597
}
4598
4599
4600
/**
4601
 *  Get tax (VAT) main information from Id.
4602
 *  You can also call getLocalTaxesFromRate() after to get only localtax fields.
4603
 *
4604
 * @param int|string $vatrate VAT ID or Rate. Value can be value or the string with code into parenthesis or rowid if $firstparamisid is 1. Example: '8.5' or '8.5 (8.5NPR)' or 123.
4605
 * @param Societe $buyer Company object
4606
 * @param Societe $seller Company object
4607
 * @param int<0,1> $firstparamisid 1 if first param is id into table (use this if you can)
4608
 * @return array{}|array{rowid:int,code:string,rate:float,localtax1:float,localtax1_type:string,localtax2:float,localtax2_type:string,npr:float,accountancy_code_sell:string,accountancy_code_buy:string} array('rowid'=> , 'code'=> ...)
4609
 * @see getLocalTaxesFromRate()
4610
 */
4611
function getTaxesFromId($vatrate, $buyer = null, $seller = null, $firstparamisid = 1)
4612
{
4613
    global $db;
4614
4615
    dol_syslog("getTaxesFromId vat id or rate = " . $vatrate);
4616
4617
    // Search local taxes
4618
    $sql = "SELECT t.rowid, t.code, t.taux as rate, t.recuperableonly as npr, t.accountancy_code_sell, t.accountancy_code_buy,";
4619
    $sql .= " t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type";
4620
    $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t";
4621
    if ($firstparamisid) {
4622
        $sql .= " WHERE t.rowid = " . (int)$vatrate;
4623
    } else {
4624
        $vatratecleaned = $vatrate;
4625
        $vatratecode = '';
4626
        $reg = array();
4627
        if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) {      // If vat is "xx (yy)"
4628
            $vatratecleaned = $reg[1];
4629
            $vatratecode = $reg[2];
4630
        }
4631
4632
        $sql .= ", " . MAIN_DB_PREFIX . "c_country as c";
4633
        /*if ($mysoc->country_code == 'ES') $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($buyer->country_code)."'";    // vat in spain use the buyer country ??
4634
        else $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($seller->country_code)."'";*/
4635
        $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $db->escape($seller->country_code) . "'";
4636
        $sql .= " AND t.taux = " . ((float)$vatratecleaned) . " AND t.active = 1";
4637
        $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
4638
        if ($vatratecode) {
4639
            $sql .= " AND t.code = '" . $db->escape($vatratecode) . "'";
4640
        }
4641
    }
4642
4643
    $resql = $db->query($sql);
4644
    if ($resql) {
4645
        $obj = $db->fetch_object($resql);
4646
        if ($obj) {
4647
            return array(
4648
                'rowid' => $obj->rowid,
4649
                'code' => $obj->code,
4650
                'rate' => $obj->rate,
4651
                'localtax1' => $obj->localtax1,
4652
                'localtax1_type' => $obj->localtax1_type,
4653
                'localtax2' => $obj->localtax2,
4654
                'localtax2_type' => $obj->localtax2_type,
4655
                'npr' => $obj->npr,
4656
                'accountancy_code_sell' => $obj->accountancy_code_sell,
4657
                'accountancy_code_buy' => $obj->accountancy_code_buy
4658
            );
4659
        } else {
4660
            return array();
4661
        }
4662
    } else {
4663
        dol_print_error($db);
4664
    }
4665
4666
    return array();
4667
}
4668
4669
/**
4670
 *  Get type and rate of localtaxes for a particular vat rate/country of a thirdparty.
4671
 *  This does not take into account the seller setup if subject to vat or not, only country.
4672
 *
4673
 *  TODO This function is ALSO called to retrieve type for building PDF. Such call of function must be removed.
4674
 *  Instead this function must be called when adding a line to get the array of possible values for localtax and type, and then
4675
 *  provide the selected value to the function calcul_price_total.
4676
 *
4677
 * @param int|string $vatrate VAT ID or Rate+Code. Value can be value or the string with code into parenthesis or rowid if $firstparamisid is 1. Example: '8.5' or '8.5 (8.5NPR)' or 123.
4678
 * @param int $local Number of localtax (1 or 2, or 0 to return 1 & 2)
4679
 * @param Societe $buyer Company object
4680
 * @param Societe $seller Company object
4681
 * @param int $firstparamisid 1 if first param is ID into table instead of Rate+code (use this if you can)
4682
 * @return array{}|array{0:string,1:float,2:string,3:string}|array{0:string,1:float,2:string,3:float,4:string,5:string}    array(localtax_type1(1-6 or 0 if not found), rate localtax1, localtax_type2, rate localtax2, accountancycodecust, accountancycodesupp)
4683
 * @see getTaxesFromId()
4684
 */
4685
function getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid = 0)
4686
{
4687
    global $db, $mysoc;
4688
4689
    dol_syslog("getLocalTaxesFromRate vatrate=" . $vatrate . " local=" . $local);
4690
4691
    // Search local taxes
4692
    $sql = "SELECT t.taux as rate, t.code, t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type, t.accountancy_code_sell, t.accountancy_code_buy";
4693
    $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t";
4694
    if ($firstparamisid) {
4695
        $sql .= " WHERE t.rowid = " . (int)$vatrate;
4696
    } else {
4697
        $vatratecleaned = $vatrate;
4698
        $vatratecode = '';
4699
        $reg = array();
4700
        if (preg_match('/^(.*)\s*\((.*)\)$/', $vatrate, $reg)) {     // If vat is "x.x (yy)"
4701
            $vatratecleaned = $reg[1];
4702
            $vatratecode = $reg[2];
4703
        }
4704
4705
        $sql .= ", " . MAIN_DB_PREFIX . "c_country as c";
4706
        if (!empty($mysoc) && $mysoc->country_code == 'ES') {
4707
            $countrycodetouse = ((empty($buyer) || empty($buyer->country_code)) ? $mysoc->country_code : $buyer->country_code);
4708
            $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $db->escape($countrycodetouse) . "'"; // local tax in spain use the buyer country ??
4709
        } else {
4710
            $countrycodetouse = ((empty($seller) || empty($seller->country_code)) ? $mysoc->country_code : $seller->country_code);
4711
            $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $db->escape($countrycodetouse) . "'";
4712
        }
4713
        $sql .= " AND t.taux = " . ((float)$vatratecleaned) . " AND t.active = 1";
4714
        if ($vatratecode) {
4715
            $sql .= " AND t.code = '" . $db->escape($vatratecode) . "'";
4716
        }
4717
    }
4718
4719
    $resql = $db->query($sql);
4720
    if ($resql) {
4721
        $obj = $db->fetch_object($resql);
4722
4723
        if ($obj) {
4724
            $vateratestring = $obj->rate . ($obj->code ? ' (' . $obj->code . ')' : '');
4725
4726
            if ($local == 1) {
4727
                return array($obj->localtax1_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
4728
            } elseif ($local == 2) {
4729
                return array($obj->localtax2_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
4730
            } else {
4731
                return array($obj->localtax1_type, get_localtax($vateratestring, 1, $buyer, $seller), $obj->localtax2_type, get_localtax($vateratestring, 2, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
4732
            }
4733
        }
4734
    }
4735
4736
    return array();
4737
}
4738
4739
/**
4740
 *  Return vat rate of a product in a particular country, or default country vat if product is unknown.
4741
 *  Function called by get_default_tva(). Do not use this function directly, prefer to use get_default_tva().
4742
 *
4743
 * @param int $idprod Id of product or 0 if not a predefined product
4744
 * @param Societe $thirdpartytouse Thirdparty with a ->country_code defined (FR, US, IT, ...)
4745
 * @param int $idprodfournprice Id product_fournisseur_price (for "supplier" proposal/order/invoice)
4746
 * @return float|string                        Vat rate to use with format 5.0 or '5.0 (XXX)'
4747
 * @see get_default_tva(), get_product_localtax_for_country()
4748
 */
4749
function get_product_vat_for_country($idprod, $thirdpartytouse, $idprodfournprice = 0)
4750
{
4751
    global $db, $mysoc;
4752
4753
4754
    $ret = 0;
4755
    $found = 0;
4756
4757
    if ($idprod > 0) {
4758
        // Load product
4759
        $product = new Product($db);
4760
        $product->fetch($idprod);
4761
4762
        if (
4763
            ($mysoc->country_code == $thirdpartytouse->country_code)
4764
            || (in_array($mysoc->country_code, array('FR', 'MC')) && in_array($thirdpartytouse->country_code, array('FR', 'MC')))
4765
            || (in_array($mysoc->country_code, array('MQ', 'GP')) && in_array($thirdpartytouse->country_code, array('MQ', 'GP')))
4766
        ) {
4767
            // If country of thirdparty to consider is ours
4768
            if ($idprodfournprice > 0) {     // We want vat for product for a "supplier" object
4769
                $result = $product->get_buyprice($idprodfournprice, 0, 0, 0);
4770
                if ($result > 0) {
4771
                    $ret = $product->vatrate_supplier;
4772
                    if ($product->default_vat_code_supplier) {
4773
                        $ret .= ' (' . $product->default_vat_code_supplier . ')';
4774
                    }
4775
                    $found = 1;
4776
                }
4777
            }
4778
            if (!$found) {
4779
                $ret = $product->tva_tx;    // Default sales vat of product
4780
                if ($product->default_vat_code) {
4781
                    $ret .= ' (' . $product->default_vat_code . ')';
4782
                }
4783
                $found = 1;
4784
            }
4785
        } else {
4786
            // TODO Read default product vat according to product and an other countrycode.
4787
            // Vat for couple anothercountrycode/product is data that is not managed and store yet, so we will fallback on next rule.
4788
        }
4789
    }
4790
4791
    if (!$found) {
4792
        if (!getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS')) {
4793
            // If vat of product for the country not found or not defined, we return the first rate found (sorting on use_default, then on higher vat of country).
4794
            $sql = "SELECT t.taux as vat_rate, t.code as default_vat_code";
4795
            $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t, " . MAIN_DB_PREFIX . "c_country as c";
4796
            $sql .= " WHERE t.active = 1 AND t.fk_pays = c.rowid AND c.code = '" . $db->escape($thirdpartytouse->country_code) . "'";
4797
            $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
4798
            $sql .= " ORDER BY t.use_default DESC, t.taux DESC, t.code ASC, t.recuperableonly ASC";
4799
            $sql .= $db->plimit(1);
4800
4801
            $resql = $db->query($sql);
4802
            if ($resql) {
4803
                $obj = $db->fetch_object($resql);
4804
                if ($obj) {
4805
                    $ret = $obj->vat_rate;
4806
                    if ($obj->default_vat_code) {
4807
                        $ret .= ' (' . $obj->default_vat_code . ')';
4808
                    }
4809
                }
4810
                $db->free($resql);
4811
            } else {
4812
                dol_print_error($db);
4813
            }
4814
        } else {
4815
            // Forced value if autodetect fails. MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS can be
4816
            // '1.23'
4817
            // or '1.23 (CODE)'
4818
            $defaulttx = '';
4819
            if (getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS') != 'none') {
4820
                $defaulttx = getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS');
4821
            }
4822
            /*if (preg_match('/\((.*)\)/', $defaulttx, $reg)) {
4823
                $defaultcode = $reg[1];
4824
                $defaulttx = preg_replace('/\s*\(.*\)/', '', $defaulttx);
4825
            }*/
4826
4827
            $ret = $defaulttx;
4828
        }
4829
    }
4830
4831
    dol_syslog("get_product_vat_for_country: ret=" . $ret);
4832
    return $ret;
4833
}
4834
4835
/**
4836
 *  Return localtax vat rate of a product in a particular country or default country vat if product is unknown
4837
 *
4838
 * @param int $idprod Id of product
4839
 * @param int $local 1 for localtax1, 2 for localtax 2
4840
 * @param Societe $thirdpartytouse Thirdparty with a ->country_code defined (FR, US, IT, ...)
4841
 * @return int                             Return integer <0 if KO, Vat rate if OK
4842
 * @see get_product_vat_for_country()
4843
 */
4844
function get_product_localtax_for_country($idprod, $local, $thirdpartytouse)
4845
{
4846
    global $db, $mysoc;
4847
4848
    if (!class_exists('Product')) {
4849
    }
4850
4851
    $ret = 0;
4852
    $found = 0;
4853
4854
    if ($idprod > 0) {
4855
        // Load product
4856
        $product = new Product($db);
4857
        $result = $product->fetch($idprod);
4858
4859
        if ($mysoc->country_code == $thirdpartytouse->country_code) { // If selling country is ours
4860
            /* Not defined yet, so we don't use this
4861
            if ($local==1) $ret=$product->localtax1_tx;
4862
            elseif ($local==2) $ret=$product->localtax2_tx;
4863
            $found=1;
4864
            */
4865
        } else {
4866
            // TODO Read default product vat according to product and another countrycode.
4867
            // Vat for couple anothercountrycode/product is data that is not managed and store yet, so we will fallback on next rule.
4868
        }
4869
    }
4870
4871
    if (!$found) {
4872
        // If vat of product for the country not found or not defined, we return higher vat of country.
4873
        $sql = "SELECT taux as vat_rate, localtax1, localtax2";
4874
        $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t, " . MAIN_DB_PREFIX . "c_country as c";
4875
        $sql .= " WHERE t.active=1 AND t.fk_pays = c.rowid AND c.code='" . $db->escape($thirdpartytouse->country_code) . "'";
4876
        $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
4877
        $sql .= " ORDER BY t.taux DESC, t.recuperableonly ASC";
4878
        $sql .= $db->plimit(1);
4879
4880
        $resql = $db->query($sql);
4881
        if ($resql) {
4882
            $obj = $db->fetch_object($resql);
4883
            if ($obj) {
4884
                if ($local == 1) {
4885
                    $ret = $obj->localtax1;
4886
                } elseif ($local == 2) {
4887
                    $ret = $obj->localtax2;
4888
                }
4889
            }
4890
        } else {
4891
            dol_print_error($db);
4892
        }
4893
    }
4894
4895
    dol_syslog("get_product_localtax_for_country: ret=" . $ret);
4896
    return $ret;
4897
}
4898
4899
/**
4900
 *  Function that return vat rate of a product line (according to seller, buyer and product vat rate)
4901
 *   VATRULE 1: If seller does not use VAT, default VAT is 0. End of rule.
4902
 *   VATRULE 2: If the (seller country = buyer country) then the default VAT = VAT of the product sold. End of rule.
4903
 *   VATRULE 3: If (seller and buyer in the European Community) and (property sold = new means of transport such as car, boat, plane) then VAT by default = 0 (VAT must be paid by the buyer to the tax center of his country and not to the seller). End of rule.
4904
 *   VATRULE 4: If (seller and buyer in the European Community) and (buyer = individual) then VAT by default = VAT of the product sold. End of rule
4905
 *   VATRULE 5: If (seller and buyer in European Community) and (buyer = company) then VAT by default=0. End of rule
4906
 *   VATRULE 6: Otherwise the VAT proposed by default=0. End of rule.
4907
 *
4908
 * @param Societe $thirdparty_seller Object Seller company
4909
 * @param Societe $thirdparty_buyer Object Buyer company
4910
 * @param int $idprod Id product
4911
 * @param int $idprodfournprice Id product_fournisseur_price (for supplier order/invoice)
4912
 * @return float|string                        Vat rate to use with format 5.0 or '5.0 (XXX)', -1 if we can't guess it
4913
 * @see get_default_localtax(), get_default_npr()
4914
 */
4915
function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
4916
{
4917
    global $conf;
4918
4919
    require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/company.lib.php';
4920
4921
    // Note: possible values for tva_assuj are 0/1 or franchise/reel
4922
    $seller_use_vat = ((is_numeric($thirdparty_seller->tva_assuj) && !$thirdparty_seller->tva_assuj) || (!is_numeric($thirdparty_seller->tva_assuj) && $thirdparty_seller->tva_assuj == 'franchise')) ? 0 : 1;
4923
4924
    $seller_country_code = $thirdparty_seller->country_code;
4925
    $seller_in_cee = isInEEC($thirdparty_seller);
4926
4927
    $buyer_country_code = $thirdparty_buyer->country_code;
4928
    $buyer_in_cee = isInEEC($thirdparty_buyer);
4929
4930
    dol_syslog("get_default_tva: seller use vat=" . $seller_use_vat . ", seller country=" . $seller_country_code . ", seller in cee=" . ((string)(int)$seller_in_cee) . ", buyer vat number=" . $thirdparty_buyer->tva_intra . " buyer country=" . $buyer_country_code . ", buyer in cee=" . ((string)(int)$buyer_in_cee) . ", idprod=" . $idprod . ", idprodfournprice=" . $idprodfournprice . ", SERVICE_ARE_ECOMMERCE_200238EC=" . (getDolGlobalString('SERVICES_ARE_ECOMMERCE_200238EC') ? $conf->global->SERVICES_ARE_ECOMMERCE_200238EC : ''));
4931
4932
    // If services are eServices according to EU Council Directive 2002/38/EC (http://ec.europa.eu/taxation_customs/taxation/vat/traders/e-commerce/article_1610_en.htm)
4933
    // we use the buyer VAT.
4934
    if (getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC')) {
4935
        if ($seller_in_cee && $buyer_in_cee) {
4936
            $isacompany = $thirdparty_buyer->isACompany();
4937
            if ($isacompany && getDolGlobalString('MAIN_USE_VAT_COMPANIES_IN_EEC_WITH_INVALID_VAT_ID_ARE_INDIVIDUAL')) {
4938
                require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/functions2.lib.php';
4939
                if (!isValidVATID($thirdparty_buyer)) {
4940
                    $isacompany = 0;
4941
                }
4942
            }
4943
4944
            if (!$isacompany) {
4945
                //print 'VATRULE 0';
4946
                return get_product_vat_for_country($idprod, $thirdparty_buyer, $idprodfournprice);
4947
            }
4948
        }
4949
    }
4950
4951
    // If seller does not use VAT, default VAT is 0. End of rule.
4952
    if (!$seller_use_vat) {
4953
        //print 'VATRULE 1';
4954
        return 0;
4955
    }
4956
4957
    // If the (seller country = buyer country) then the default VAT = VAT of the product sold. End of rule.
4958
    if (
4959
        ($seller_country_code == $buyer_country_code)
4960
        || (in_array($seller_country_code, array('FR', 'MC')) && in_array($buyer_country_code, array('FR', 'MC')))
4961
        || (in_array($seller_country_code, array('MQ', 'GP')) && in_array($buyer_country_code, array('MQ', 'GP')))
4962
    ) { // Warning ->country_code not always defined
4963
        //print 'VATRULE 2';
4964
        $tmpvat = get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
4965
4966
        if ($seller_country_code == 'IN' && getDolGlobalString('MAIN_SALETAX_AUTOSWITCH_I_CS_FOR_INDIA')) {
4967
            // Special case for india.
4968
            //print 'VATRULE 2b';
4969
            $reg = array();
4970
            if (preg_match('/C+S-(\d+)/', $tmpvat, $reg) && $thirdparty_seller->state_id != $thirdparty_buyer->state_id) {
4971
                // we must revert the C+S into I
4972
                $tmpvat = str_replace("C+S", "I", $tmpvat);
4973
            } elseif (preg_match('/I-(\d+)/', $tmpvat, $reg) && $thirdparty_seller->state_id == $thirdparty_buyer->state_id) {
4974
                // we must revert the I into C+S
4975
                $tmpvat = str_replace("I", "C+S", $tmpvat);
4976
            }
4977
        }
4978
4979
        return $tmpvat;
4980
    }
4981
4982
    // If (seller and buyer in the European Community) and (property sold = new means of transport such as car, boat, plane) then VAT by default = 0 (VAT must be paid by the buyer to the tax center of his country and not to the seller). End of rule.
4983
    // 'VATRULE 3' - Not supported
4984
4985
    // If (seller and buyer in the European Community) and (buyer = individual) then VAT by default = VAT of the product sold. End of rule
4986
    // If (seller and buyer in European Community) and (buyer = company) then VAT by default=0. End of rule
4987
    if (($seller_in_cee && $buyer_in_cee)) {
4988
        $isacompany = $thirdparty_buyer->isACompany();
4989
        if ($isacompany && getDolGlobalString('MAIN_USE_VAT_COMPANIES_IN_EEC_WITH_INVALID_VAT_ID_ARE_INDIVIDUAL')) {
4990
            require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/functions2.lib.php';
4991
            if (!isValidVATID($thirdparty_buyer)) {
4992
                $isacompany = 0;
4993
            }
4994
        }
4995
4996
        if (!$isacompany) {
4997
            //print 'VATRULE 4';
4998
            return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
4999
        } else {
5000
            //print 'VATRULE 5';
5001
            return 0;
5002
        }
5003
    }
5004
5005
    // If (seller in the European Community and buyer outside the European Community and private buyer) then VAT by default = VAT of the product sold. End of rule
5006
    // I don't see any use case that need this rule.
5007
    if (getDolGlobalString('MAIN_USE_VAT_OF_PRODUCT_FOR_INDIVIDUAL_CUSTOMER_OUT_OF_EEC') && empty($buyer_in_cee)) {
5008
        $isacompany = $thirdparty_buyer->isACompany();
5009
        if (!$isacompany) {
5010
            return get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
5011
            //print 'VATRULE extra';
5012
        }
5013
    }
5014
5015
    // Otherwise the VAT proposed by default=0. End of rule.
5016
    // Rem: This means that at least one of the 2 is outside the European Community and the country differs
5017
    //print 'VATRULE 6';
5018
    return 0;
5019
}
5020
5021
5022
/**
5023
 *  Function that returns whether VAT must be recoverable collected VAT (e.g.: VAT NPR in France)
5024
 *
5025
 * @param Societe $thirdparty_seller Thirdparty seller
5026
 * @param Societe $thirdparty_buyer Thirdparty buyer
5027
 * @param int $idprod Id product
5028
 * @param int $idprodfournprice Id supplier price for product
5029
 * @return float                               0 or 1
5030
 * @see get_default_tva(), get_default_localtax()
5031
 */
5032
function get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
5033
{
5034
    global $db;
5035
5036
    if ($idprodfournprice > 0) {
5037
        $prodprice = new ProductFournisseur($db);
5038
        $prodprice->fetch_product_fournisseur_price($idprodfournprice);
5039
        return $prodprice->fourn_tva_npr;
5040
    }
5041
5042
    if ($idprod > 0) {
5043
        $prod = new Product($db);
5044
        $prod->fetch($idprod);
5045
        return $prod->tva_npr;
5046
    }
5047
5048
    return 0;
5049
}
5050
5051
/**
5052
 *  Function that return localtax of a product line (according to seller, buyer and product vat rate)
5053
 *   If the seller is not subject to VAT, then default VAT=0. Rule/Test ends.
5054
 *   If (seller country == buyer country) default VAT=sold product VAT. Rule/Test ends.
5055
 *   Else, default VAT=0. Rule/Test ends
5056
 *
5057
 * @param Societe $thirdparty_seller Third party seller
5058
 * @param Societe $thirdparty_buyer Third party buyer
5059
 * @param int $local Localtax to process (1 or 2)
5060
 * @param int $idprod Id product
5061
 * @return int                                 localtax, -1 if it can not be determined
5062
 * @see get_default_tva(), get_default_npr()
5063
 */
5064
function get_default_localtax($thirdparty_seller, $thirdparty_buyer, $local, $idprod = 0)
5065
{
5066
    global $mysoc;
5067
5068
    if (!is_object($thirdparty_seller)) {
5069
        return -1;
5070
    }
5071
    if (!is_object($thirdparty_buyer)) {
5072
        return -1;
5073
    }
5074
5075
    if ($local == 1) { // Localtax 1
5076
        if ($mysoc->country_code == 'ES') {
5077
            if (is_numeric($thirdparty_buyer->localtax1_assuj) && !$thirdparty_buyer->localtax1_assuj) {
5078
                return 0;
5079
            }
5080
        } else {
5081
            // Si vendeur non assujeti a Localtax1, localtax1 par default=0
5082
            if (is_numeric($thirdparty_seller->localtax1_assuj) && !$thirdparty_seller->localtax1_assuj) {
5083
                return 0;
5084
            }
5085
            if (!is_numeric($thirdparty_seller->localtax1_assuj) && $thirdparty_seller->localtax1_assuj == 'localtax1off') {
5086
                return 0;
5087
            }
5088
        }
5089
    } elseif ($local == 2) { //I Localtax 2
5090
        // Si vendeur non assujeti a Localtax2, localtax2 par default=0
5091
        if (is_numeric($thirdparty_seller->localtax2_assuj) && !$thirdparty_seller->localtax2_assuj) {
5092
            return 0;
5093
        }
5094
        if (!is_numeric($thirdparty_seller->localtax2_assuj) && $thirdparty_seller->localtax2_assuj == 'localtax2off') {
5095
            return 0;
5096
        }
5097
    }
5098
5099
    if ($thirdparty_seller->country_code == $thirdparty_buyer->country_code) {
5100
        return get_product_localtax_for_country($idprod, $local, $thirdparty_seller);
5101
    }
5102
5103
    return 0;
5104
}
5105
5106
/**
5107
 *  Return a path to have a the directory according to object where files are stored.
5108
 *  This function is called by getMultidirOutput
5109
 *  New usage:  $conf->module->multidir_output[$object->entity].'/'.get_exdir(0, 0, 0, 1, $object, '').'/'
5110
 *         or:  $conf->module->dir_output.'/'.get_exdir(0, 0, 0, 0, $object, '')
5111
 *
5112
 *  Example of output with new usage:       $object is invoice -> 'INYYMM-ABCD'
5113
 *  Example of output with old usage:       '015' with level 3->"0/1/5/", '015' with level 1->"5/", 'ABC-1' with level 3 ->"0/0/1/"
5114
 *
5115
 * @param string|int $num Id of object (deprecated, $object->id will be used in future)
5116
 * @param int $level Level of subdirs to return (1, 2 or 3 levels). (deprecated, global setup will be used in future)
5117
 * @param int $alpha 0=Keep number only to forge path, 1=Use alpha part after the - (By default, use 0). (deprecated, global option will be used in future)
5118
 * @param int $withoutslash 0=With slash at end (except if '/', we return ''), 1=without slash at end
5119
 * @param  ?CommonObject $object Object to use to get ref to forge the path.
5120
 * @param string $modulepart Type of object ('invoice_supplier, 'donation', 'invoice', ...'). Use '' for autodetect from $object.
5121
 * @return string                          Dir to use ending. Example '' or '1/' or '1/2/'
5122
 * @see getMultidirOutput()
5123
 */
5124
function get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart = '')
5125
{
5126
    if (empty($modulepart) && is_object($object)) {
5127
        if (!empty($object->module)) {
5128
            $modulepart = $object->module;
5129
        } elseif (!empty($object->element)) {
5130
            $modulepart = $object->element;
5131
        }
5132
    }
5133
5134
    $path = '';
5135
5136
    // Define $arrayforoldpath that is module path using a hierarchy on more than 1 level.
5137
    $arrayforoldpath = array('cheque' => 2, 'category' => 2, 'holiday' => 2, 'supplier_invoice' => 2, 'invoice_supplier' => 2, 'mailing' => 2, 'supplier_payment' => 2);
5138
    if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
5139
        $arrayforoldpath['product'] = 2;
5140
    }
5141
5142
    if (empty($level) && array_key_exists($modulepart, $arrayforoldpath)) {
5143
        $level = $arrayforoldpath[$modulepart];
5144
    }
5145
5146
    if (!empty($level) && array_key_exists($modulepart, $arrayforoldpath)) {
5147
        // This part should be removed once all code is using "get_exdir" to forge path, with parameter $object and $modulepart provided.
5148
        if (empty($num) && is_object($object)) {
5149
            $num = $object->id;
5150
        }
5151
        if (empty($alpha)) {
5152
            $num = preg_replace('/([^0-9])/i', '', $num);
5153
        } else {
5154
            $num = preg_replace('/^.*-/i', '', $num);
5155
        }
5156
        $num = substr("000" . $num, -$level);
5157
        if ($level == 1) {
5158
            $path = substr($num, 0, 1);
5159
        }
5160
        if ($level == 2) {
5161
            $path = substr($num, 1, 1) . '/' . substr($num, 0, 1);
5162
        }
5163
        if ($level == 3) {
5164
            $path = substr($num, 2, 1) . '/' . substr($num, 1, 1) . '/' . substr($num, 0, 1);
5165
        }
5166
    } else {
5167
        // We will enhance here a common way of forging path for document storage.
5168
        // In a future, we may distribute directories on several levels depending on setup and object.
5169
        // Here, $object->id, $object->ref and $modulepart are required.
5170
        //var_dump($modulepart);
5171
        $path = dol_sanitizeFileName(empty($object->ref) ? (string)((is_object($object) && property_exists($object, 'id')) ? $object->id : '') : $object->ref);
5172
    }
5173
5174
    if (empty($withoutslash) && !empty($path)) {
5175
        $path .= '/';
5176
    }
5177
5178
    return $path;
5179
}
5180
5181
/**
5182
 *  Creation of a directory (this can create recursive subdir)
5183
 *
5184
 * @param string $dir Directory to create (Separator must be '/'. Example: '/mydir/mysubdir')
5185
 * @param string $dataroot Data root directory (To avoid having the data root in the loop. Using this will also lost the warning, on first dir, saying PHP has no permission when open_basedir is used)
5186
 * @param string $newmask Mask for new file (Defaults to $conf->global->MAIN_UMASK or 0755 if unavailable). Example: '0444'
5187
 * @return int                     Return integer < 0 if KO, 0 = already exists, > 0 if OK
5188
 */
5189
function dol_mkdir($dir, $dataroot = '', $newmask = '')
5190
{
5191
    global $conf;
5192
5193
    dol_syslog("functions.lib::dol_mkdir: dir=" . $dir, LOG_INFO);
5194
5195
    $dir_osencoded = dol_osencode($dir);
5196
    if (@is_dir($dir_osencoded)) {
5197
        return 0;
5198
    }
5199
5200
    $nberr = 0;
5201
    $nbcreated = 0;
5202
5203
    $ccdir = '';
5204
    if (!empty($dataroot)) {
5205
        // Remove data root from loop
5206
        $dir = str_replace($dataroot . '/', '', $dir);
5207
        $ccdir = $dataroot . '/';
5208
    }
5209
5210
    $cdir = explode("/", $dir);
5211
    $num = count($cdir);
5212
    for ($i = 0; $i < $num; $i++) {
5213
        if ($i > 0) {
5214
            $ccdir .= '/' . $cdir[$i];
5215
        } else {
5216
            $ccdir .= $cdir[$i];
5217
        }
5218
        $regs = array();
5219
        if (preg_match("/^.:$/", $ccdir, $regs)) {
5220
            continue; // If the Windows path is incomplete, continue with next directory
5221
        }
5222
5223
        // Attention, is_dir() can fail event if the directory exists
5224
        // (i.e. according the open_basedir configuration)
5225
        if ($ccdir) {
5226
            $ccdir_osencoded = dol_osencode($ccdir);
5227
            if (!@is_dir($ccdir_osencoded)) {
5228
                dol_syslog("functions.lib::dol_mkdir: Directory '" . $ccdir . "' does not exists or is outside open_basedir PHP setting.", LOG_DEBUG);
5229
5230
                umask(0);
5231
                $dirmaskdec = octdec((string)$newmask);
5232
                if (empty($newmask)) {
5233
                    $dirmaskdec = !getDolGlobalString('MAIN_UMASK') ? octdec('0755') : octdec($conf->global->MAIN_UMASK);
5234
                }
5235
                $dirmaskdec |= octdec('0111'); // Set x bit required for directories
5236
                if (!@mkdir($ccdir_osencoded, $dirmaskdec)) {
5237
                    // If the is_dir has returned a false information, we arrive here
5238
                    dol_syslog("functions.lib::dol_mkdir: Fails to create directory '" . $ccdir . "' or directory already exists.", LOG_WARNING);
5239
                    $nberr++;
5240
                } else {
5241
                    dol_syslog("functions.lib::dol_mkdir: Directory '" . $ccdir . "' created", LOG_DEBUG);
5242
                    $nberr = 0; // At this point in the code, the previous failures can be ignored -> set $nberr to 0
5243
                    $nbcreated++;
5244
                }
5245
            } else {
5246
                $nberr = 0; // At this point in the code, the previous failures can be ignored -> set $nberr to 0
5247
            }
5248
        }
5249
    }
5250
    return ($nberr ? -$nberr : $nbcreated);
5251
}
5252
5253
5254
/**
5255
 *  Change mod of a file
5256
 *
5257
 * @param string $filepath Full file path
5258
 * @param string $newmask Force new mask. For example '0644'
5259
 * @return void
5260
 */
5261
function dolChmod($filepath, $newmask = '')
5262
{
5263
    global $conf;
5264
5265
    if (!empty($newmask)) {
5266
        @chmod($filepath, octdec($newmask));
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for chmod(). 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

5266
        /** @scrutinizer ignore-unhandled */ @chmod($filepath, octdec($newmask));

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...
5267
    } elseif (getDolGlobalString('MAIN_UMASK')) {
5268
        @chmod($filepath, octdec($conf->global->MAIN_UMASK));
5269
    }
5270
}
5271
5272
/**
5273
 * Return first line of text. Cut will depends if content is HTML or not.
5274
 *
5275
 * @param string $text Input text
5276
 * @param int $nboflines Nb of lines to get (default is 1 = first line only)
5277
 * @param string $charset Charset of $text string (UTF-8 by default)
5278
 * @return  string              Output text
5279
 * @see dol_nboflines_bis(), dol_string_nohtmltag(), dol_escape_htmltag()
5280
 */
5281
function dolGetFirstLineOfText($text, $nboflines = 1, $charset = 'UTF-8')
5282
{
5283
    if ($nboflines == 1) {
5284
        if (dol_textishtml($text)) {
5285
            $firstline = preg_replace('/<br[^>]*>.*$/s', '', $text); // The s pattern modifier means the . can match newline characters
5286
            $firstline = preg_replace('/<div[^>]*>.*$/s', '', $firstline); // The s pattern modifier means the . can match newline characters
5287
        } else {
5288
            if (isset($text)) {
5289
                $firstline = preg_replace('/[\n\r].*/', '', $text);
5290
            } else {
5291
                $firstline = '';
5292
            }
5293
        }
5294
        return $firstline . (isset($firstline) && isset($text) && (strlen($firstline) != strlen($text)) ? '...' : '');
5295
    } else {
5296
        $ishtml = 0;
5297
        if (dol_textishtml($text)) {
5298
            $text = preg_replace('/\n/', '', $text);
5299
            $ishtml = 1;
5300
            $repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
5301
        } else {
5302
            $repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
5303
        }
5304
5305
        $text = strtr($text, $repTable);
5306
        if ($charset == 'UTF-8') {
5307
            $pattern = '/(<br[^>]*>)/Uu';
5308
        } else {
5309
            // /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
5310
            $pattern = '/(<br[^>]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
5311
        }
5312
        $a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
5313
5314
        $firstline = '';
5315
        $i = 0;
5316
        $countline = 0;
5317
        $lastaddediscontent = 1;
5318
        while ($countline < $nboflines && isset($a[$i])) {
5319
            if (preg_match('/<br[^>]*>/', $a[$i])) {
5320
                if (array_key_exists($i + 1, $a) && !empty($a[$i + 1])) {
5321
                    $firstline .= ($ishtml ? "<br>\n" : "\n");
5322
                    // Is it a br for a new line of after a printed line ?
5323
                    if (!$lastaddediscontent) {
5324
                        $countline++;
5325
                    }
5326
                    $lastaddediscontent = 0;
5327
                }
5328
            } else {
5329
                $firstline .= $a[$i];
5330
                $lastaddediscontent = 1;
5331
                $countline++;
5332
            }
5333
            $i++;
5334
        }
5335
5336
        $adddots = (isset($a[$i]) && (!preg_match('/<br[^>]*>/', $a[$i]) || (array_key_exists($i + 1, $a) && !empty($a[$i + 1]))));
5337
        //unset($a);
5338
        $ret = $firstline . ($adddots ? '...' : '');
5339
        //exit;
5340
        return $ret;
5341
    }
5342
}
5343
5344
5345
/**
5346
 * Replace CRLF in string with a HTML BR tag.
5347
 * WARNING: The content after operation contains some HTML tags (the <br>) so be sure to also have
5348
 *          encoded the special chars of stringtoencode into HTML before with dol_htmlentitiesbr().
5349
 *
5350
 * @param string $stringtoencode String to encode
5351
 * @param int $nl2brmode 0=Adding br before \n, 1=Replacing \n by br
5352
 * @param bool $forxml false=Use <br>, true=Use <br />
5353
 * @return  string                      String encoded
5354
 * @see dol_htmlentitiesbr(), dol_nboflines(), dolGetFirstLineOfText()
5355
 */
5356
function dol_nl2br($stringtoencode, $nl2brmode = 0, $forxml = false)
5357
{
5358
    if (is_null($stringtoencode)) {
5359
        return '';
5360
    }
5361
5362
    if (!$nl2brmode) {
5363
        return nl2br($stringtoencode, $forxml);
5364
    } else {
5365
        $ret = preg_replace('/(\r\n|\r|\n)/i', ($forxml ? '<br />' : '<br>'), $stringtoencode);
5366
        return $ret;
5367
    }
5368
}
5369
5370
/**
5371
 *  Check if a string is a correct iso string
5372
 *  If not, it will not be considered as HTML encoded even if it is by FPDF.
5373
 *  Example, if string contains euro symbol that has ascii code 128
5374
 *
5375
 * @param string $s String to check
5376
 * @param int $clean Clean if it is not an ISO. Warning, if file is utf8, you will get a bad formatted file.
5377
 * @return int|string              0 if bad iso, 1 if good iso, Or the clean string if $clean is 1
5378
 * @deprecated Duplicate of ascii_check()
5379
 * @see ascii_check()
5380
 */
5381
function dol_string_is_good_iso($s, $clean = 0)
5382
{
5383
    $len = dol_strlen($s);
5384
    $out = '';
5385
    $ok = 1;
5386
    for ($scursor = 0; $scursor < $len; $scursor++) {
5387
        $ordchar = ord($s[$scursor]);
5388
        //print $scursor.'-'.$ordchar.'<br>';
5389
        if ($ordchar < 32 && $ordchar != 13 && $ordchar != 10) {
5390
            $ok = 0;
5391
            break;
5392
        } elseif ($ordchar > 126 && $ordchar < 160) {
5393
            $ok = 0;
5394
            break;
5395
        } elseif ($clean) {
5396
            $out .= $s[$scursor];
5397
        }
5398
    }
5399
    if ($clean) {
5400
        return $out;
5401
    }
5402
    return $ok;
5403
}
5404
5405
/**
5406
 *  Return nb of lines of a clear text
5407
 *
5408
 * @param string $s String to check
5409
 * @param int $maxchar Not yet used
5410
 * @return int                 Number of lines
5411
 * @see    dol_nboflines_bis(), dolGetFirstLineOfText()
5412
 */
5413
function dol_nboflines($s, $maxchar = 0)
5414
{
5415
    if ($s == '') {
5416
        return 0;
5417
    }
5418
    $arraystring = explode("\n", $s);
5419
    $nb = count($arraystring);
5420
5421
    return $nb;
5422
}
5423
5424
5425
/**
5426
 *  Return nb of lines of a formatted text with \n and <br> (WARNING: string must not have mixed \n and br separators)
5427
 *
5428
 * @param string $text Text
5429
 * @param int $maxlinesize Linewidth in character count (default = 0 == nolimit)
5430
 * @param string $charset Give the charset used to encode the $text variable in memory.
5431
 * @return int                     Number of lines
5432
 * @see    dol_nboflines(), dolGetFirstLineOfText()
5433
 */
5434
function dol_nboflines_bis($text, $maxlinesize = 0, $charset = 'UTF-8')
5435
{
5436
    $repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
5437
    if (dol_textishtml($text)) {
5438
        $repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
5439
    }
5440
5441
    $text = strtr($text, $repTable);
5442
    if ($charset == 'UTF-8') {
5443
        $pattern = '/(<br[^>]*>)/Uu';
5444
    } else {
5445
        // /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
5446
        $pattern = '/(<br[^>]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
5447
    }
5448
    $a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
5449
5450
    $nblines = (int)floor((count($a) + 1) / 2);
5451
    // count possible auto line breaks
5452
    if ($maxlinesize) {
5453
        foreach ($a as $line) {
5454
            if (dol_strlen($line) > $maxlinesize) {
5455
                //$line_dec = html_entity_decode(strip_tags($line));
5456
                $line_dec = html_entity_decode($line);
5457
                if (dol_strlen($line_dec) > $maxlinesize) {
5458
                    $line_dec = wordwrap($line_dec, $maxlinesize, '\n', true);
5459
                    $nblines += substr_count($line_dec, '\n');
5460
                }
5461
            }
5462
        }
5463
    }
5464
5465
    unset($a);
5466
    return $nblines;
5467
}
5468
5469
/**
5470
 *  Return if a text is a html content
5471
 *
5472
 * @param string $msg Content to check
5473
 * @param int $option 0=Full detection, 1=Fast check
5474
 * @return boolean             true/false
5475
 * @see    dol_concatdesc()
5476
 */
5477
function dol_textishtml($msg, $option = 0)
5478
{
5479
    if (is_null($msg)) {
5480
        return false;
5481
    }
5482
5483
    if ($option == 1) {
5484
        if (preg_match('/<html/i', $msg)) {
5485
            return true;
5486
        } elseif (preg_match('/<body/i', $msg)) {
5487
            return true;
5488
        } elseif (preg_match('/<\/textarea/i', $msg)) {
5489
            return true;
5490
        } elseif (preg_match('/<(b|em|i|u)(\s+[^>]+)?>/i', $msg)) {
5491
            return true;
5492
        } elseif (preg_match('/<br/i', $msg)) {
5493
            return true;
5494
        }
5495
        return false;
5496
    } else {
5497
        // Remove all urls because 'http://aa?param1=abc&amp;param2=def' must not be used inside detection
5498
        $msg = preg_replace('/https?:\/\/[^"\'\s]+/i', '', $msg);
5499
        if (preg_match('/<html/i', $msg)) {
5500
            return true;
5501
        } elseif (preg_match('/<body/i', $msg)) {
5502
            return true;
5503
        } elseif (preg_match('/<\/textarea/i', $msg)) {
5504
            return true;
5505
        } elseif (preg_match('/<(b|em|i|u)(\s+[^>]+)?>/i', $msg)) {
5506
            return true;
5507
        } elseif (preg_match('/<(br|hr)\/>/i', $msg)) {
5508
            return true;
5509
        } elseif (preg_match('/<(br|hr|div|font|li|p|span|strong|table)>/i', $msg)) {
5510
            return true;
5511
        } elseif (preg_match('/<(br|hr|div|font|li|p|span|strong|table)\s+[^<>\/]*\/?>/i', $msg)) {
5512
            return true;
5513
        } elseif (preg_match('/<img\s+[^<>]*src[^<>]*>/i', $msg)) {
5514
            return true; // must accept <img src="http://example.com/aaa.png" />
5515
        } elseif (preg_match('/<a\s+[^<>]*href[^<>]*>/i', $msg)) {
5516
            return true; // must accept <a href="http://example.com/aaa.png" />
5517
        } elseif (preg_match('/<h[0-9]>/i', $msg)) {
5518
            return true;
5519
        } elseif (preg_match('/&[A-Z0-9]{1,6};/i', $msg)) {
5520
            // TODO If content is 'A link https://aaa?param=abc&amp;param2=def', it return true but must be false
5521
            return true; // Html entities names (http://www.w3schools.com/tags/ref_entities.asp)
5522
        } elseif (preg_match('/&#[0-9]{2,3};/i', $msg)) {
5523
            return true; // Html entities numbers (http://www.w3schools.com/tags/ref_entities.asp)
5524
        }
5525
5526
        return false;
5527
    }
5528
}
5529
5530
/**
5531
 *  Concat 2 descriptions with a new line between them (second operand after first one with appropriate new line separator)
5532
 *  text1 html + text2 html => text1 + '<br>' + text2
5533
 *  text1 html + text2 txt  => text1 + '<br>' + dol_nl2br(text2)
5534
 *  text1 txt  + text2 html => dol_nl2br(text1) + '<br>' + text2
5535
 *  text1 txt  + text2 txt  => text1 + '\n' + text2
5536
 *
5537
 * @param string $text1 Text 1
5538
 * @param string $text2 Text 2
5539
 * @param bool $forxml true=Use <br /> instead of <br> if we have to add a br tag
5540
 * @param bool $invert invert order of description lines (we often use config MAIN_CHANGE_ORDER_CONCAT_DESCRIPTION in this parameter)
5541
 * @return string                  Text 1 + new line + Text2
5542
 * @see    dol_textishtml()
5543
 */
5544
function dol_concatdesc($text1, $text2, $forxml = false, $invert = false)
5545
{
5546
    if (!empty($invert)) {
5547
        $tmp = $text1;
5548
        $text1 = $text2;
5549
        $text2 = $tmp;
5550
    }
5551
5552
    $ret = '';
5553
    $ret .= (!dol_textishtml($text1) && dol_textishtml($text2)) ? dol_nl2br(dol_escape_htmltag($text1, 0, 1, '', 1), 0, $forxml) : $text1;
5554
    $ret .= (!empty($text1) && !empty($text2)) ? ((dol_textishtml($text1) || dol_textishtml($text2)) ? ($forxml ? "<br \>\n" : "<br>\n") : "\n") : "";
5555
    $ret .= (dol_textishtml($text1) && !dol_textishtml($text2)) ? dol_nl2br(dol_escape_htmltag($text2, 0, 1, '', 1), 0, $forxml) : $text2;
5556
    return $ret;
5557
}
5558
5559
5560
/**
5561
 * Return array of possible common substitutions. This includes several families like: 'system', 'mycompany', 'object', 'objectamount', 'date', 'user'
5562
 *
5563
 * @param Translate $outputlangs Output language
5564
 * @param int $onlykey 1=Do not calculate some heavy values of keys (performance enhancement when we need only the keys),
5565
 *                                          2=Values are trunc and html sanitized (to use for help tooltip)
5566
 * @param string[]|null $exclude Array of family keys we want to exclude. For example array('system', 'mycompany', 'object', 'objectamount', 'date', 'user', ...)
5567
 * @param   ?CommonObject $object Object for keys on object
5568
 * @param string[]|null $include Array of family keys we want to include. For example array('system', 'mycompany', 'object', 'objectamount', 'date', 'user', ...)
5569
 * @return  array<string,string>            Array of substitutions
5570
 * @see setSubstitFromObject()
5571
 * @phan-suppress PhanTypeArraySuspiciousNullable,PhanTypePossiblyInvalidDimOffset,PhanUndeclaredProperty
5572
 */
5573
function getCommonSubstitutionArray($outputlangs, $onlykey = 0, $exclude = null, $object = null, $include = null)
5574
{
5575
    global $db, $conf, $mysoc, $user, $extrafields;
5576
5577
    $substitutionarray = array();
5578
5579
    if ((empty($exclude) || !in_array('user', $exclude)) && (empty($include) || in_array('user', $include))) {
5580
        // Add SIGNATURE into substitutionarray first, so, when we will make the substitution,
5581
        // this will include signature content first and then replace var found into content of signature
5582
        //var_dump($onlykey);
5583
        $emailsendersignature = $user->signature; //  dy default, we use the signature of current user. We must complete substitution with signature in c_email_senderprofile of array after calling getCommonSubstitutionArray()
5584
        $usersignature = $user->signature;
5585
        $substitutionarray = array_merge($substitutionarray, array(
5586
            '__SENDEREMAIL_SIGNATURE__' => (string)((!getDolGlobalString('MAIN_MAIL_DO_NOT_USE_SIGN')) ? ($onlykey == 2 ? dol_trunc('SignatureFromTheSelectedSenderProfile', 30) : $emailsendersignature) : ''),
5587
            '__USER_SIGNATURE__' => (string)(($usersignature && !getDolGlobalString('MAIN_MAIL_DO_NOT_USE_SIGN')) ? ($onlykey == 2 ? dol_trunc(dol_string_nohtmltag($usersignature), 30) : $usersignature) : '')
5588
        ));
5589
5590
        if (is_object($user) && ($user instanceof User)) {
5591
            $substitutionarray = array_merge($substitutionarray, array(
5592
                '__USER_ID__' => (string)$user->id,
5593
                '__USER_LOGIN__' => (string)$user->login,
5594
                '__USER_EMAIL__' => (string)$user->email,
5595
                '__USER_PHONE__' => (string)dol_print_phone($user->office_phone, '', 0, 0, '', " ", '', '', -1),
5596
                '__USER_PHONEPRO__' => (string)dol_print_phone($user->user_mobile, '', 0, 0, '', " ", '', '', -1),
5597
                '__USER_PHONEMOBILE__' => (string)dol_print_phone($user->personal_mobile, '', 0, 0, '', " ", '', '', -1),
5598
                '__USER_FAX__' => (string)$user->office_fax,
5599
                '__USER_LASTNAME__' => (string)$user->lastname,
5600
                '__USER_FIRSTNAME__' => (string)$user->firstname,
5601
                '__USER_FULLNAME__' => (string)$user->getFullName($outputlangs),
5602
                '__USER_SUPERVISOR_ID__' => (string)($user->fk_user ? $user->fk_user : '0'),
5603
                '__USER_JOB__' => (string)$user->job,
5604
                '__USER_REMOTE_IP__' => (string)getUserRemoteIP(),
5605
                '__USER_VCARD_URL__' => (string)$user->getOnlineVirtualCardUrl('', 'external')
5606
            ));
5607
        }
5608
    }
5609
    if ((empty($exclude) || !in_array('mycompany', $exclude)) && is_object($mysoc) && (empty($include) || in_array('mycompany', $include))) {
5610
        $substitutionarray = array_merge($substitutionarray, array(
5611
            '__MYCOMPANY_NAME__' => $mysoc->name,
5612
            '__MYCOMPANY_EMAIL__' => $mysoc->email,
5613
            '__MYCOMPANY_PHONE__' => dol_print_phone($mysoc->phone, '', 0, 0, '', " ", '', '', -1),
5614
            '__MYCOMPANY_FAX__' => dol_print_phone($mysoc->fax, '', 0, 0, '', " ", '', '', -1),
5615
            '__MYCOMPANY_PROFID1__' => $mysoc->idprof1,
5616
            '__MYCOMPANY_PROFID2__' => $mysoc->idprof2,
5617
            '__MYCOMPANY_PROFID3__' => $mysoc->idprof3,
5618
            '__MYCOMPANY_PROFID4__' => $mysoc->idprof4,
5619
            '__MYCOMPANY_PROFID5__' => $mysoc->idprof5,
5620
            '__MYCOMPANY_PROFID6__' => $mysoc->idprof6,
5621
            '__MYCOMPANY_PROFID7__' => $mysoc->idprof7,
5622
            '__MYCOMPANY_PROFID8__' => $mysoc->idprof8,
5623
            '__MYCOMPANY_PROFID9__' => $mysoc->idprof9,
5624
            '__MYCOMPANY_PROFID10__' => $mysoc->idprof10,
5625
            '__MYCOMPANY_CAPITAL__' => $mysoc->capital,
5626
            '__MYCOMPANY_FULLADDRESS__' => (method_exists($mysoc, 'getFullAddress') ? $mysoc->getFullAddress(1, ', ') : ''),    // $mysoc may be stdClass
5627
            '__MYCOMPANY_ADDRESS__' => $mysoc->address,
5628
            '__MYCOMPANY_ZIP__' => $mysoc->zip,
5629
            '__MYCOMPANY_TOWN__' => $mysoc->town,
5630
            '__MYCOMPANY_COUNTRY__' => $mysoc->country,
5631
            '__MYCOMPANY_COUNTRY_ID__' => $mysoc->country_id,
5632
            '__MYCOMPANY_COUNTRY_CODE__' => $mysoc->country_code,
5633
            '__MYCOMPANY_CURRENCY_CODE__' => $conf->currency
5634
        ));
5635
    }
5636
5637
    if (($onlykey || is_object($object)) && (empty($exclude) || !in_array('object', $exclude)) && (empty($include) || in_array('object', $include))) {
5638
        if ($onlykey) {
5639
            $substitutionarray['__ID__'] = '__ID__';
5640
            $substitutionarray['__REF__'] = '__REF__';
5641
            $substitutionarray['__NEWREF__'] = '__NEWREF__';
5642
            $substitutionarray['__LABEL__'] = '__LABEL__';
5643
            $substitutionarray['__REF_CLIENT__'] = '__REF_CLIENT__';
5644
            $substitutionarray['__REF_SUPPLIER__'] = '__REF_SUPPLIER__';
5645
            $substitutionarray['__NOTE_PUBLIC__'] = '__NOTE_PUBLIC__';
5646
            $substitutionarray['__NOTE_PRIVATE__'] = '__NOTE_PRIVATE__';
5647
            $substitutionarray['__EXTRAFIELD_XXX__'] = '__EXTRAFIELD_XXX__';
5648
5649
            if (isModEnabled("societe")) {  // Most objects are concerned
5650
                $substitutionarray['__THIRDPARTY_ID__'] = '__THIRDPARTY_ID__';
5651
                $substitutionarray['__THIRDPARTY_NAME__'] = '__THIRDPARTY_NAME__';
5652
                $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = '__THIRDPARTY_NAME_ALIAS__';
5653
                $substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = '__THIRDPARTY_CODE_CLIENT__';
5654
                $substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = '__THIRDPARTY_CODE_FOURNISSEUR__';
5655
                $substitutionarray['__THIRDPARTY_EMAIL__'] = '__THIRDPARTY_EMAIL__';
5656
                //$substitutionarray['__THIRDPARTY_EMAIL_URLENCODED__'] = '__THIRDPARTY_EMAIL_URLENCODED__';    // We hide this one
5657
                $substitutionarray['__THIRDPARTY_PHONE__'] = '__THIRDPARTY_PHONE__';
5658
                $substitutionarray['__THIRDPARTY_FAX__'] = '__THIRDPARTY_FAX__';
5659
                $substitutionarray['__THIRDPARTY_ADDRESS__'] = '__THIRDPARTY_ADDRESS__';
5660
                $substitutionarray['__THIRDPARTY_ZIP__'] = '__THIRDPARTY_ZIP__';
5661
                $substitutionarray['__THIRDPARTY_TOWN__'] = '__THIRDPARTY_TOWN__';
5662
                $substitutionarray['__THIRDPARTY_IDPROF1__'] = '__THIRDPARTY_IDPROF1__';
5663
                $substitutionarray['__THIRDPARTY_IDPROF2__'] = '__THIRDPARTY_IDPROF2__';
5664
                $substitutionarray['__THIRDPARTY_IDPROF3__'] = '__THIRDPARTY_IDPROF3__';
5665
                $substitutionarray['__THIRDPARTY_IDPROF4__'] = '__THIRDPARTY_IDPROF4__';
5666
                $substitutionarray['__THIRDPARTY_IDPROF5__'] = '__THIRDPARTY_IDPROF5__';
5667
                $substitutionarray['__THIRDPARTY_IDPROF6__'] = '__THIRDPARTY_IDPROF6__';
5668
                $substitutionarray['__THIRDPARTY_IDPROF7__'] = '__THIRDPARTY_IDPROF7__';
5669
                $substitutionarray['__THIRDPARTY_IDPROF8__'] = '__THIRDPARTY_IDPROF8__';
5670
                $substitutionarray['__THIRDPARTY_IDPROF9__'] = '__THIRDPARTY_IDPROF9__';
5671
                $substitutionarray['__THIRDPARTY_IDPROF10__'] = '__THIRDPARTY_IDPROF10__';
5672
                $substitutionarray['__THIRDPARTY_TVAINTRA__'] = '__THIRDPARTY_TVAINTRA__';
5673
                $substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = '__THIRDPARTY_NOTE_PUBLIC__';
5674
                $substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = '__THIRDPARTY_NOTE_PRIVATE__';
5675
            }
5676
            if (isModEnabled('member') && (!is_object($object) || $object->element == 'adherent') && (empty($exclude) || !in_array('member', $exclude)) && (empty($include) || in_array('member', $include))) {
5677
                $substitutionarray['__MEMBER_ID__'] = '__MEMBER_ID__';
5678
                $substitutionarray['__MEMBER_CIVILITY__'] = '__MEMBER_CIVILITY__';
5679
                $substitutionarray['__MEMBER_FIRSTNAME__'] = '__MEMBER_FIRSTNAME__';
5680
                $substitutionarray['__MEMBER_LASTNAME__'] = '__MEMBER_LASTNAME__';
5681
                $substitutionarray['__MEMBER_USER_LOGIN_INFORMATION__'] = 'Login and pass of the external user account';
5682
                /*$substitutionarray['__MEMBER_NOTE_PUBLIC__'] = '__MEMBER_NOTE_PUBLIC__';
5683
                $substitutionarray['__MEMBER_NOTE_PRIVATE__'] = '__MEMBER_NOTE_PRIVATE__';*/
5684
            }
5685
            // add substitution variables for ticket
5686
            if (isModEnabled('ticket') && (!is_object($object) || $object->element == 'ticket') && (empty($exclude) || !in_array('ticket', $exclude)) && (empty($include) || in_array('ticket', $include))) {
5687
                $substitutionarray['__TICKET_TRACKID__'] = '__TICKET_TRACKID__';
5688
                $substitutionarray['__TICKET_SUBJECT__'] = '__TICKET_SUBJECT__';
5689
                $substitutionarray['__TICKET_TYPE__'] = '__TICKET_TYPE__';
5690
                $substitutionarray['__TICKET_SEVERITY__'] = '__TICKET_SEVERITY__';
5691
                $substitutionarray['__TICKET_CATEGORY__'] = '__TICKET_CATEGORY__';
5692
                $substitutionarray['__TICKET_ANALYTIC_CODE__'] = '__TICKET_ANALYTIC_CODE__';
5693
                $substitutionarray['__TICKET_MESSAGE__'] = '__TICKET_MESSAGE__';
5694
                $substitutionarray['__TICKET_PROGRESSION__'] = '__TICKET_PROGRESSION__';
5695
                $substitutionarray['__TICKET_USER_ASSIGN__'] = '__TICKET_USER_ASSIGN__';
5696
            }
5697
5698
            if (isModEnabled('recruitment') && (!is_object($object) || $object->element == 'recruitmentcandidature') && (empty($exclude) || !in_array('recruitment', $exclude)) && (empty($include) || in_array('recruitment', $include))) {
5699
                $substitutionarray['__CANDIDATE_FULLNAME__'] = '__CANDIDATE_FULLNAME__';
5700
                $substitutionarray['__CANDIDATE_FIRSTNAME__'] = '__CANDIDATE_FIRSTNAME__';
5701
                $substitutionarray['__CANDIDATE_LASTNAME__'] = '__CANDIDATE_LASTNAME__';
5702
            }
5703
            if (isModEnabled('project') && (empty($exclude) || !in_array('project', $exclude)) && (empty($include) || in_array('project', $include))) {     // Most objects
5704
                $substitutionarray['__PROJECT_ID__'] = '__PROJECT_ID__';
5705
                $substitutionarray['__PROJECT_REF__'] = '__PROJECT_REF__';
5706
                $substitutionarray['__PROJECT_NAME__'] = '__PROJECT_NAME__';
5707
                /*$substitutionarray['__PROJECT_NOTE_PUBLIC__'] = '__PROJECT_NOTE_PUBLIC__';
5708
                $substitutionarray['__PROJECT_NOTE_PRIVATE__'] = '__PROJECT_NOTE_PRIVATE__';*/
5709
            }
5710
            if (isModEnabled('contract') && (!is_object($object) || $object->element == 'contract') && (empty($exclude) || !in_array('contract', $exclude)) && (empty($include) || in_array('contract', $include))) {
5711
                $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = 'Highest date planned for a service start';
5712
                $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = 'Highest date and hour planned for service start';
5713
                $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = 'Lowest data for planned expiration of service';
5714
                $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = 'Lowest date and hour for planned expiration of service';
5715
            }
5716
            if (isModEnabled("propal") && (!is_object($object) || $object->element == 'propal') && (empty($exclude) || !in_array('propal', $exclude)) && (empty($include) || in_array('propal', $include))) {
5717
                $substitutionarray['__ONLINE_SIGN_URL__'] = 'ToOfferALinkForOnlineSignature';
5718
            }
5719
            if (isModEnabled("intervention") && (!is_object($object) || $object->element == 'fichinter') && (empty($exclude) || !in_array('intervention', $exclude)) && (empty($include) || in_array('intervention', $include))) {
5720
                $substitutionarray['__ONLINE_SIGN_FICHINTER_URL__'] = 'ToOfferALinkForOnlineSignature';
5721
            }
5722
            $substitutionarray['__ONLINE_PAYMENT_URL__'] = 'UrlToPayOnlineIfApplicable';
5723
            $substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = 'TextAndUrlToPayOnlineIfApplicable';
5724
            $substitutionarray['__SECUREKEYPAYMENT__'] = 'Security key (if key is not unique per record)';
5725
            $substitutionarray['__SECUREKEYPAYMENT_MEMBER__'] = 'Security key for payment on a member subscription (one key per member)';
5726
            $substitutionarray['__SECUREKEYPAYMENT_ORDER__'] = 'Security key for payment on an order';
5727
            $substitutionarray['__SECUREKEYPAYMENT_INVOICE__'] = 'Security key for payment on an invoice';
5728
            $substitutionarray['__SECUREKEYPAYMENT_CONTRACTLINE__'] = 'Security key for payment on a service of a contract';
5729
5730
            $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = 'Direct download url of a proposal';
5731
            $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = 'Direct download url of an order';
5732
            $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = 'Direct download url of an invoice';
5733
            $substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = 'Direct download url of a contract';
5734
            $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = 'Direct download url of a supplier proposal';
5735
5736
            if (isModEnabled("shipping") && (!is_object($object) || $object->element == 'shipping')) {
5737
                $substitutionarray['__SHIPPINGTRACKNUM__'] = 'Shipping tracking number';
5738
                $substitutionarray['__SHIPPINGTRACKNUMURL__'] = 'Shipping tracking url';
5739
                $substitutionarray['__SHIPPINGMETHOD__'] = 'Shipping method';
5740
            }
5741
            if (isModEnabled("reception") && (!is_object($object) || $object->element == 'reception')) {
5742
                $substitutionarray['__RECEPTIONTRACKNUM__'] = 'Shipping tracking number of shipment';
5743
                $substitutionarray['__RECEPTIONTRACKNUMURL__'] = 'Shipping tracking url';
5744
            }
5745
        } else {
5746
            '@phan-var-force Adherent|Delivery $object';
5747
            $substitutionarray['__ID__'] = $object->id;
5748
            $substitutionarray['__REF__'] = $object->ref;
5749
            $substitutionarray['__NEWREF__'] = $object->newref;
5750
            $substitutionarray['__LABEL__'] = (isset($object->label) ? $object->label : (isset($object->title) ? $object->title : null));
0 ignored issues
show
Bug Best Practice introduced by
The property title does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5751
            $substitutionarray['__REF_CLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
0 ignored issues
show
Bug Best Practice introduced by
The property ref_client does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property ref_customer does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5752
            $substitutionarray['__REF_SUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
0 ignored issues
show
Bug Best Practice introduced by
The property ref_supplier does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5753
            $substitutionarray['__NOTE_PUBLIC__'] = (isset($object->note_public) ? $object->note_public : null);
5754
            $substitutionarray['__NOTE_PRIVATE__'] = (isset($object->note_private) ? $object->note_private : null);
5755
            $substitutionarray['__DATE_CREATION__'] = (isset($object->date_creation) ? dol_print_date($object->date_creation, 'day', 0, $outputlangs) : '');
5756
            $substitutionarray['__DATE_MODIFICATION__'] = (isset($object->date_modification) ? dol_print_date($object->date_modification, 'day', 0, $outputlangs) : '');
5757
            $substitutionarray['__DATE_VALIDATION__'] = (isset($object->date_validation) ? dol_print_date($object->date_validation, 'day', 0, $outputlangs) : '');
5758
            $substitutionarray['__DATE_DELIVERY__'] = (isset($object->date_delivery) ? dol_print_date($object->date_delivery, 'day', 0, $outputlangs) : '');
0 ignored issues
show
Bug Best Practice introduced by
The property date_delivery does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5759
            $substitutionarray['__DATE_DELIVERY_DAY__'] = (isset($object->date_delivery) ? dol_print_date($object->date_delivery, "%d") : '');
5760
            $substitutionarray['__DATE_DELIVERY_DAY_TEXT__'] = (isset($object->date_delivery) ? dol_print_date($object->date_delivery, "%A") : '');
5761
            $substitutionarray['__DATE_DELIVERY_MON__'] = (isset($object->date_delivery) ? dol_print_date($object->date_delivery, "%m") : '');
5762
            $substitutionarray['__DATE_DELIVERY_MON_TEXT__'] = (isset($object->date_delivery) ? dol_print_date($object->date_delivery, "%b") : '');
5763
            $substitutionarray['__DATE_DELIVERY_YEAR__'] = (isset($object->date_delivery) ? dol_print_date($object->date_delivery, "%Y") : '');
5764
            $substitutionarray['__DATE_DELIVERY_HH__'] = (isset($object->date_delivery) ? dol_print_date($object->date_delivery, "%H") : '');
5765
            $substitutionarray['__DATE_DELIVERY_MM__'] = (isset($object->date_delivery) ? dol_print_date($object->date_delivery, "%M") : '');
5766
            $substitutionarray['__DATE_DELIVERY_SS__'] = (isset($object->date_delivery) ? dol_print_date($object->date_delivery, "%S") : '');
5767
5768
            // For backward compatibility (deprecated)
5769
            $substitutionarray['__REFCLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
5770
            $substitutionarray['__REFSUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
5771
            $substitutionarray['__SUPPLIER_ORDER_DATE_DELIVERY__'] = (isset($object->delivery_date) ? dol_print_date($object->delivery_date, 'day', 0, $outputlangs) : '');
0 ignored issues
show
Bug Best Practice introduced by
The property delivery_date does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5772
            $substitutionarray['__SUPPLIER_ORDER_DELAY_DELIVERY__'] = (isset($object->availability_code) ? ($outputlangs->transnoentities("AvailabilityType" . $object->availability_code) != 'AvailabilityType' . $object->availability_code ? $outputlangs->transnoentities("AvailabilityType" . $object->availability_code) : $outputlangs->convToOutputCharset(isset($object->availability) ? $object->availability : '')) : '');
0 ignored issues
show
Bug Best Practice introduced by
The property availability does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property availability_code does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5773
            $substitutionarray['__EXPIRATION_DATE__'] = (isset($object->fin_validite) ? dol_print_date($object->fin_validite, 'daytext') : '');
0 ignored issues
show
Bug Best Practice introduced by
The property fin_validite does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5774
5775
            if (is_object($object) && ($object->element == 'adherent' || $object->element == 'member') && $object->id > 0) {
5776
                '@phan-var-force Adherent $object';
5777
                $birthday = (empty($object->birth) ? '' : dol_print_date($object->birth, 'day'));
0 ignored issues
show
Bug Best Practice introduced by
The property birth does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5778
5779
                $substitutionarray['__MEMBER_ID__'] = (isset($object->id) ? $object->id : '');
5780
                if (method_exists($object, 'getCivilityLabel')) {
5781
                    $substitutionarray['__MEMBER_CIVILITY__'] = $object->getCivilityLabel();
0 ignored issues
show
Bug introduced by
The method getCivilityLabel() does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

5781
                    /** @scrutinizer ignore-call */ 
5782
                    $substitutionarray['__MEMBER_CIVILITY__'] = $object->getCivilityLabel();
Loading history...
5782
                }
5783
                $substitutionarray['__MEMBER_FIRSTNAME__'] = (isset($object->firstname) ? $object->firstname : '');
5784
                $substitutionarray['__MEMBER_LASTNAME__'] = (isset($object->lastname) ? $object->lastname : '');
5785
                $substitutionarray['__MEMBER_USER_LOGIN_INFORMATION__'] = '';
5786
                if (method_exists($object, 'getFullName')) {
5787
                    $substitutionarray['__MEMBER_FULLNAME__'] = $object->getFullName($outputlangs);
0 ignored issues
show
Bug introduced by
The method getFullName() does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __call, consider adding a @method annotation. ( Ignorable by Annotation )

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

5787
                    /** @scrutinizer ignore-call */ 
5788
                    $substitutionarray['__MEMBER_FULLNAME__'] = $object->getFullName($outputlangs);
Loading history...
5788
                }
5789
                $substitutionarray['__MEMBER_COMPANY__'] = (isset($object->societe) ? $object->societe : '');
0 ignored issues
show
Bug Best Practice introduced by
The property societe does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5790
                $substitutionarray['__MEMBER_ADDRESS__'] = (isset($object->address) ? $object->address : '');
0 ignored issues
show
Bug Best Practice introduced by
The property address does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5791
                $substitutionarray['__MEMBER_ZIP__'] = (isset($object->zip) ? $object->zip : '');
0 ignored issues
show
Bug Best Practice introduced by
The property zip does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5792
                $substitutionarray['__MEMBER_TOWN__'] = (isset($object->town) ? $object->town : '');
0 ignored issues
show
Bug Best Practice introduced by
The property town does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5793
                $substitutionarray['__MEMBER_COUNTRY__'] = (isset($object->country) ? $object->country : '');
5794
                $substitutionarray['__MEMBER_EMAIL__'] = (isset($object->email) ? $object->email : '');
0 ignored issues
show
Bug Best Practice introduced by
The property email does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5795
                $substitutionarray['__MEMBER_BIRTH__'] = (isset($birthday) ? $birthday : '');
5796
                $substitutionarray['__MEMBER_PHOTO__'] = (isset($object->photo) ? $object->photo : '');
0 ignored issues
show
Bug Best Practice introduced by
The property photo does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5797
                $substitutionarray['__MEMBER_LOGIN__'] = (isset($object->login) ? $object->login : '');
0 ignored issues
show
Bug Best Practice introduced by
The property login does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5798
                $substitutionarray['__MEMBER_PASSWORD__'] = (isset($object->pass) ? $object->pass : '');
0 ignored issues
show
Bug Best Practice introduced by
The property pass does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5799
                $substitutionarray['__MEMBER_PHONE__'] = (isset($object->phone) ? dol_print_phone($object->phone) : '');
0 ignored issues
show
Bug Best Practice introduced by
The property phone does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5800
                $substitutionarray['__MEMBER_PHONEPRO__'] = (isset($object->phone_perso) ? dol_print_phone($object->phone_perso) : '');
0 ignored issues
show
Bug Best Practice introduced by
The property phone_perso does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5801
                $substitutionarray['__MEMBER_PHONEMOBILE__'] = (isset($object->phone_mobile) ? dol_print_phone($object->phone_mobile) : '');
0 ignored issues
show
Bug Best Practice introduced by
The property phone_mobile does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5802
                $substitutionarray['__MEMBER_TYPE__'] = (isset($object->type) ? $object->type : '');
5803
                $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE__'] = dol_print_date($object->first_subscription_date, 'day');
0 ignored issues
show
Bug Best Practice introduced by
The property first_subscription_date does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5804
5805
                $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_RFC__'] = dol_print_date($object->first_subscription_date, 'dayrfc');
5806
                $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_START__'] = (isset($object->first_subscription_date_start) ? dol_print_date($object->first_subscription_date_start, 'day') : '');
0 ignored issues
show
Bug Best Practice introduced by
The property first_subscription_date_start does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5807
                $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_START_RFC__'] = (isset($object->first_subscription_date_start) ? dol_print_date($object->first_subscription_date_start, 'dayrfc') : '');
5808
                $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_END__'] = (isset($object->first_subscription_date_end) ? dol_print_date($object->first_subscription_date_end, 'day') : '');
0 ignored issues
show
Bug Best Practice introduced by
The property first_subscription_date_end does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5809
                $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_END_RFC__'] = (isset($object->first_subscription_date_end) ? dol_print_date($object->first_subscription_date_end, 'dayrfc') : '');
5810
                $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE__'] = dol_print_date($object->last_subscription_date, 'day');
0 ignored issues
show
Bug Best Practice introduced by
The property last_subscription_date does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5811
                $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_RFC__'] = dol_print_date($object->last_subscription_date, 'dayrfc');
5812
                $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_START__'] = dol_print_date($object->last_subscription_date_start, 'day');
0 ignored issues
show
Bug Best Practice introduced by
The property last_subscription_date_start does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5813
                $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_START_RFC__'] = dol_print_date($object->last_subscription_date_start, 'dayrfc');
5814
                $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_END__'] = dol_print_date($object->last_subscription_date_end, 'day');
0 ignored issues
show
Bug Best Practice introduced by
The property last_subscription_date_end does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5815
                $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_END_RFC__'] = dol_print_date($object->last_subscription_date_end, 'dayrfc');
5816
            }
5817
5818
            if (is_object($object) && $object->element == 'societe') {
5819
                '@phan-var-force Societe $object';
5820
                $substitutionarray['__THIRDPARTY_ID__'] = (is_object($object) ? $object->id : '');
5821
                $substitutionarray['__THIRDPARTY_NAME__'] = (is_object($object) ? $object->name : '');
5822
                $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = (is_object($object) ? $object->name_alias : '');
0 ignored issues
show
Bug Best Practice introduced by
The property name_alias does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5823
                $substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = (is_object($object) ? $object->code_client : '');
0 ignored issues
show
Bug Best Practice introduced by
The property code_client does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5824
                $substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = (is_object($object) ? $object->code_fournisseur : '');
0 ignored issues
show
Bug Best Practice introduced by
The property code_fournisseur does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5825
                $substitutionarray['__THIRDPARTY_EMAIL__'] = (is_object($object) ? $object->email : '');
5826
                $substitutionarray['__THIRDPARTY_EMAIL_URLENCODED__'] = urlencode(is_object($object) ? $object->email : '');
5827
                $substitutionarray['__THIRDPARTY_PHONE__'] = (is_object($object) ? dol_print_phone($object->phone) : '');
5828
                $substitutionarray['__THIRDPARTY_FAX__'] = (is_object($object) ? dol_print_phone($object->fax) : '');
0 ignored issues
show
Bug Best Practice introduced by
The property fax does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5829
                $substitutionarray['__THIRDPARTY_ADDRESS__'] = (is_object($object) ? $object->address : '');
5830
                $substitutionarray['__THIRDPARTY_ZIP__'] = (is_object($object) ? $object->zip : '');
5831
                $substitutionarray['__THIRDPARTY_TOWN__'] = (is_object($object) ? $object->town : '');
5832
                $substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = (is_object($object) ? $object->country_id : '');
5833
                $substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = (is_object($object) ? $object->country_code : '');
5834
                $substitutionarray['__THIRDPARTY_IDPROF1__'] = (is_object($object) ? $object->idprof1 : '');
0 ignored issues
show
Bug Best Practice introduced by
The property idprof1 does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5835
                $substitutionarray['__THIRDPARTY_IDPROF2__'] = (is_object($object) ? $object->idprof2 : '');
0 ignored issues
show
Bug Best Practice introduced by
The property idprof2 does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5836
                $substitutionarray['__THIRDPARTY_IDPROF3__'] = (is_object($object) ? $object->idprof3 : '');
0 ignored issues
show
Bug Best Practice introduced by
The property idprof3 does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5837
                $substitutionarray['__THIRDPARTY_IDPROF4__'] = (is_object($object) ? $object->idprof4 : '');
0 ignored issues
show
Bug Best Practice introduced by
The property idprof4 does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5838
                $substitutionarray['__THIRDPARTY_IDPROF5__'] = (is_object($object) ? $object->idprof5 : '');
0 ignored issues
show
Bug Best Practice introduced by
The property idprof5 does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5839
                $substitutionarray['__THIRDPARTY_IDPROF6__'] = (is_object($object) ? $object->idprof6 : '');
0 ignored issues
show
Bug Best Practice introduced by
The property idprof6 does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5840
                $substitutionarray['__THIRDPARTY_TVAINTRA__'] = (is_object($object) ? $object->tva_intra : '');
0 ignored issues
show
Bug Best Practice introduced by
The property tva_intra does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5841
                $substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = (is_object($object) ? dol_htmlentitiesbr($object->note_public) : '');
5842
                $substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = (is_object($object) ? dol_htmlentitiesbr($object->note_private) : '');
5843
            } elseif (is_object($object->thirdparty)) {
5844
                $substitutionarray['__THIRDPARTY_ID__'] = (is_object($object->thirdparty) ? $object->thirdparty->id : '');
5845
                $substitutionarray['__THIRDPARTY_NAME__'] = (is_object($object->thirdparty) ? $object->thirdparty->name : '');
5846
                $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = (is_object($object->thirdparty) ? $object->thirdparty->name_alias : '');
5847
                $substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = (is_object($object->thirdparty) ? $object->thirdparty->code_client : '');
5848
                $substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = (is_object($object->thirdparty) ? $object->thirdparty->code_fournisseur : '');
5849
                $substitutionarray['__THIRDPARTY_EMAIL__'] = (is_object($object->thirdparty) ? $object->thirdparty->email : '');
5850
                $substitutionarray['__THIRDPARTY_EMAIL_URLENCODED__'] = urlencode(is_object($object->thirdparty) ? $object->thirdparty->email : '');
5851
                $substitutionarray['__THIRDPARTY_PHONE__'] = (is_object($object->thirdparty) ? dol_print_phone($object->thirdparty->phone) : '');
5852
                $substitutionarray['__THIRDPARTY_FAX__'] = (is_object($object->thirdparty) ? dol_print_phone($object->thirdparty->fax) : '');
5853
                $substitutionarray['__THIRDPARTY_ADDRESS__'] = (is_object($object->thirdparty) ? $object->thirdparty->address : '');
5854
                $substitutionarray['__THIRDPARTY_ZIP__'] = (is_object($object->thirdparty) ? $object->thirdparty->zip : '');
5855
                $substitutionarray['__THIRDPARTY_TOWN__'] = (is_object($object->thirdparty) ? $object->thirdparty->town : '');
5856
                $substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = (is_object($object->thirdparty) ? $object->thirdparty->country_id : '');
5857
                $substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = (is_object($object->thirdparty) ? $object->thirdparty->country_code : '');
5858
                $substitutionarray['__THIRDPARTY_IDPROF1__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof1 : '');
5859
                $substitutionarray['__THIRDPARTY_IDPROF2__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof2 : '');
5860
                $substitutionarray['__THIRDPARTY_IDPROF3__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof3 : '');
5861
                $substitutionarray['__THIRDPARTY_IDPROF4__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof4 : '');
5862
                $substitutionarray['__THIRDPARTY_IDPROF5__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof5 : '');
5863
                $substitutionarray['__THIRDPARTY_IDPROF6__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof6 : '');
5864
                $substitutionarray['__THIRDPARTY_TVAINTRA__'] = (is_object($object->thirdparty) ? $object->thirdparty->tva_intra : '');
5865
                $substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = (is_object($object->thirdparty) ? dol_htmlentitiesbr($object->thirdparty->note_public) : '');
5866
                $substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = (is_object($object->thirdparty) ? dol_htmlentitiesbr($object->thirdparty->note_private) : '');
5867
            }
5868
5869
            if (is_object($object) && $object->element == 'recruitmentcandidature') {
5870
                '@phan-var-force RecruitmentCandidature $object';
5871
                $substitutionarray['__CANDIDATE_FULLNAME__'] = $object->getFullName($outputlangs);
5872
                $substitutionarray['__CANDIDATE_FIRSTNAME__'] = isset($object->firstname) ? $object->firstname : '';
5873
                $substitutionarray['__CANDIDATE_LASTNAME__'] = isset($object->lastname) ? $object->lastname : '';
5874
            }
5875
            if (is_object($object) && $object->element == 'conferenceorboothattendee') {
5876
                '@phan-var-force ConferenceOrBoothAttendee $object';
5877
                $substitutionarray['__ATTENDEE_FULLNAME__'] = $object->getFullName($outputlangs);
5878
                $substitutionarray['__ATTENDEE_FIRSTNAME__'] = isset($object->firstname) ? $object->firstname : '';
5879
                $substitutionarray['__ATTENDEE_LASTNAME__'] = isset($object->lastname) ? $object->lastname : '';
5880
            }
5881
5882
            if (is_object($object) && $object->element == 'project') {
5883
                '@phan-var-force Project $object';
5884
                $substitutionarray['__PROJECT_ID__'] = $object->id;
5885
                $substitutionarray['__PROJECT_REF__'] = $object->ref;
5886
                $substitutionarray['__PROJECT_NAME__'] = $object->title;
5887
            } elseif (is_object($object)) {
5888
                $project = null;
5889
                if (!empty($object->project)) {
5890
                    $project = $object->project;
5891
                } elseif (!empty($object->projet)) { // Deprecated, for backward compatibility
0 ignored issues
show
Bug Best Practice introduced by
The property $projet is declared private in Dolibarr\Core\Base\CommonObject. Since you implement __get, consider adding a @property or @property-read.
Loading history...
5892
                    $project = $object->projet;
5893
                }
5894
                if (!is_null($project) && is_object($project)) {
5895
                    $substitutionarray['__PROJECT_ID__'] = $project->id;
5896
                    $substitutionarray['__PROJECT_REF__'] = $project->ref;
5897
                    $substitutionarray['__PROJECT_NAME__'] = $project->title;
5898
                } else {
5899
                    // can substitute variables for project : uses lazy load in "make_substitutions" method
5900
                    $project_id = 0;
5901
                    if (!empty($object->fk_project) && $object->fk_project > 0) {
5902
                        $project_id = $object->fk_project;
5903
                    } elseif (!empty($object->fk_projet) && $object->fk_projet > 0) {
5904
                        $project_id = $object->fk_project;
5905
                    }
5906
                    if ($project_id > 0) {
5907
                        // path:class:method:id
5908
                        $substitutionarray['__PROJECT_ID__@lazyload'] = '/projet/class/project.class.php:Project:fetchAndSetSubstitution:' . $project_id;
5909
                        $substitutionarray['__PROJECT_REF__@lazyload'] = '/projet/class/project.class.php:Project:fetchAndSetSubstitution:' . $project_id;
5910
                        $substitutionarray['__PROJECT_NAME__@lazyload'] = '/projet/class/project.class.php:Project:fetchAndSetSubstitution:' . $project_id;
5911
                    }
5912
                }
5913
            }
5914
5915
            if (is_object($object) && $object->element == 'facture') {
5916
                '@phan-var-force Facture $object';
5917
                $substitutionarray['__INVOICE_SITUATION_NUMBER__'] = isset($object->situation_counter) ? $object->situation_counter : '';
0 ignored issues
show
Bug Best Practice introduced by
The property situation_counter does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5918
            }
5919
            if (is_object($object) && $object->element == 'shipping') {
5920
                '@phan-var-force Expedition $object';
5921
                $substitutionarray['__SHIPPINGTRACKNUM__'] = $object->tracking_number;
0 ignored issues
show
Bug Best Practice introduced by
The property tracking_number does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5922
                $substitutionarray['__SHIPPINGTRACKNUMURL__'] = $object->tracking_url;
0 ignored issues
show
Bug Best Practice introduced by
The property tracking_url does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5923
                $substitutionarray['__SHIPPINGMETHOD__'] = $object->shipping_method;
5924
            }
5925
            if (is_object($object) && $object->element == 'reception') {
5926
                '@phan-var-force Reception $object';
5927
                $substitutionarray['__RECEPTIONTRACKNUM__'] = $object->tracking_number;
5928
                $substitutionarray['__RECEPTIONTRACKNUMURL__'] = $object->tracking_url;
5929
            }
5930
5931
            if (is_object($object) && $object->element == 'contrat' && $object->id > 0 && is_array($object->lines)) {
5932
                '@phan-var-force Contrat $object';
5933
                $dateplannedstart = '';
5934
                $datenextexpiration = '';
5935
                foreach ($object->lines as $line) {
5936
                    if ($line->date_start > $dateplannedstart) {
5937
                        $dateplannedstart = $line->date_start;
5938
                    }
5939
                    if ($line->statut == 4 && $line->date_end && (!$datenextexpiration || $line->date_end < $datenextexpiration)) {
5940
                        $datenextexpiration = $line->date_end;
5941
                    }
5942
                }
5943
                $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = dol_print_date($dateplannedstart, 'day');
5944
                $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE_RFC__'] = dol_print_date($dateplannedstart, 'dayrfc');
5945
                $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = dol_print_date($dateplannedstart, 'standard');
5946
5947
                $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = dol_print_date($datenextexpiration, 'day');
5948
                $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE_RFC__'] = dol_print_date($datenextexpiration, 'dayrfc');
5949
                $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = dol_print_date($datenextexpiration, 'standard');
5950
            }
5951
            // add substitution variables for ticket
5952
            if (is_object($object) && $object->element == 'ticket') {
5953
                '@phan-var-force Ticket $object';
5954
                $substitutionarray['__TICKET_TRACKID__'] = $object->track_id;
0 ignored issues
show
Bug Best Practice introduced by
The property track_id does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5955
                $substitutionarray['__TICKET_SUBJECT__'] = $object->subject;
0 ignored issues
show
Bug Best Practice introduced by
The property subject does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5956
                $substitutionarray['__TICKET_TYPE__'] = $object->type_code;
0 ignored issues
show
Bug Best Practice introduced by
The property type_code does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5957
                $substitutionarray['__TICKET_SEVERITY__'] = $object->severity_code;
0 ignored issues
show
Bug Best Practice introduced by
The property severity_code does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5958
                $substitutionarray['__TICKET_CATEGORY__'] = $object->category_code; // For backward compatibility
0 ignored issues
show
Bug Best Practice introduced by
The property category_code does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5959
                $substitutionarray['__TICKET_ANALYTIC_CODE__'] = $object->category_code;
5960
                $substitutionarray['__TICKET_MESSAGE__'] = $object->message;
0 ignored issues
show
Bug Best Practice introduced by
The property message does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5961
                $substitutionarray['__TICKET_PROGRESSION__'] = $object->progress;
0 ignored issues
show
Bug Best Practice introduced by
The property progress does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5962
                $userstat = new User($db);
5963
                if ($object->fk_user_assign > 0) {
0 ignored issues
show
Bug Best Practice introduced by
The property fk_user_assign does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5964
                    $userstat->fetch($object->fk_user_assign);
5965
                    $substitutionarray['__TICKET_USER_ASSIGN__'] = dolGetFirstLastname($userstat->firstname, $userstat->lastname);
5966
                }
5967
5968
                if ($object->fk_user_create > 0) {
0 ignored issues
show
Bug Best Practice introduced by
The property fk_user_create does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
5969
                    $userstat->fetch($object->fk_user_create);
5970
                    $substitutionarray['__USER_CREATE__'] = dolGetFirstLastname($userstat->firstname, $userstat->lastname);
5971
                }
5972
            }
5973
5974
            // Create dynamic tags for __EXTRAFIELD_FIELD__
5975
            if ($object->table_element && $object->id > 0) {
5976
                if (!is_object($extrafields)) {
5977
                    $extrafields = new ExtraFields($db);
5978
                }
5979
                $extrafields->fetch_name_optionals_label($object->table_element, true);
5980
5981
                if ($object->fetch_optionals() > 0) {
0 ignored issues
show
Bug introduced by
The method fetch_optionals() does not exist on null. ( Ignorable by Annotation )

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

5981
                if ($object->/** @scrutinizer ignore-call */ fetch_optionals() > 0) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
5982
                    if (is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label']) > 0) {
5983
                        foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) {
5984
                            if ($extrafields->attributes[$object->table_element]['type'][$key] == 'date') {
5985
                                $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = dol_print_date($object->array_options['options_' . $key], 'day');
5986
                                $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_LOCALE__'] = dol_print_date($object->array_options['options_' . $key], 'day', 'tzserver', $outputlangs);
5987
                                $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_RFC__'] = dol_print_date($object->array_options['options_' . $key], 'dayrfc');
5988
                            } elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'datetime') {
5989
                                $datetime = $object->array_options['options_' . $key];
5990
                                $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhour') : '');
5991
                                $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_LOCALE__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhour', 'tzserver', $outputlangs) : '');
5992
                                $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_DAY_LOCALE__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'day', 'tzserver', $outputlangs) : '');
5993
                                $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_RFC__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhourrfc') : '');
5994
                            } elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'phone') {
5995
                                $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = dol_print_phone($object->array_options['options_' . $key]);
5996
                            } elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'price') {
5997
                                $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = $object->array_options['options_' . $key];
5998
                                $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_FORMATED__'] = price($object->array_options['options_' . $key]);    // For compatibility
5999
                                $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_FORMATTED__'] = price($object->array_options['options_' . $key]);
6000
                            } elseif ($extrafields->attributes[$object->table_element]['type'][$key] != 'separator') {
6001
                                $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = !empty($object->array_options['options_' . $key]) ? $object->array_options['options_' . $key] : '';
6002
                            }
6003
                        }
6004
                    }
6005
                }
6006
            }
6007
6008
            // Complete substitution array with the url to make online payment
6009
            if (empty($substitutionarray['__REF__'])) {
6010
                $paymenturl = '';
6011
            } else {
6012
                // Set the online payment url link into __ONLINE_PAYMENT_URL__ key
6013
                require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/payments.lib.php';
6014
                $outputlangs->loadLangs(array('paypal', 'other'));
6015
6016
                $amounttouse = 0;
6017
                $typeforonlinepayment = 'free';
6018
                if (is_object($object) && $object->element == 'commande') {
6019
                    $typeforonlinepayment = 'order';
6020
                }
6021
                if (is_object($object) && $object->element == 'facture') {
6022
                    $typeforonlinepayment = 'invoice';
6023
                }
6024
                if (is_object($object) && $object->element == 'member') {
6025
                    $typeforonlinepayment = 'member';
6026
                    if (!empty($object->last_subscription_amount)) {
0 ignored issues
show
Bug Best Practice introduced by
The property last_subscription_amount does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
6027
                        $amounttouse = $object->last_subscription_amount;
6028
                    }
6029
                }
6030
                if (is_object($object) && $object->element == 'contrat') {
6031
                    $typeforonlinepayment = 'contract';
6032
                }
6033
                if (is_object($object) && $object->element == 'fichinter') {
6034
                    $typeforonlinepayment = 'ficheinter';
6035
                }
6036
6037
                $url = getOnlinePaymentUrl(0, $typeforonlinepayment, $substitutionarray['__REF__'], $amounttouse);
6038
                $paymenturl = $url;
6039
            }
6040
6041
            if ($object->id > 0) {
6042
                $substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = ($paymenturl ? str_replace('\n', "\n", $outputlangs->trans("PredefinedMailContentLink", $paymenturl)) : '');
6043
                $substitutionarray['__ONLINE_PAYMENT_URL__'] = $paymenturl;
6044
6045
                if (getDolGlobalString('PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'propal') {
6046
                    $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = $object->getLastMainDocLink($object->element);
6047
                } else {
6048
                    $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = '';
6049
                }
6050
                if (getDolGlobalString('ORDER_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'commande') {
6051
                    $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = $object->getLastMainDocLink($object->element);
6052
                } else {
6053
                    $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = '';
6054
                }
6055
                if (getDolGlobalString('INVOICE_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'facture') {
6056
                    $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = $object->getLastMainDocLink($object->element);
6057
                } else {
6058
                    $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = '';
6059
                }
6060
                if (getDolGlobalString('CONTRACT_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'contrat') {
6061
                    $substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = $object->getLastMainDocLink($object->element);
6062
                } else {
6063
                    $substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = '';
6064
                }
6065
                if (getDolGlobalString('FICHINTER_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'fichinter') {
6066
                    $substitutionarray['__DIRECTDOWNLOAD_URL_FICHINTER__'] = $object->getLastMainDocLink($object->element);
6067
                } else {
6068
                    $substitutionarray['__DIRECTDOWNLOAD_URL_FICHINTER__'] = '';
6069
                }
6070
                if (getDolGlobalString('SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'supplier_proposal') {
6071
                    $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = $object->getLastMainDocLink($object->element);
6072
                } else {
6073
                    $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = '';
6074
                }
6075
6076
                if (is_object($object) && $object->element == 'propal') {
6077
                    '@phan-var-force Propal $object';
6078
                    $substitutionarray['__URL_PROPOSAL__'] = DOL_MAIN_URL_ROOT . "/comm/propal/card.php?id=" . $object->id;
6079
                    require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/signature.lib.php';
6080
                    $substitutionarray['__ONLINE_SIGN_URL__'] = getOnlineSignatureUrl(0, 'proposal', $object->ref, 1, $object);
6081
                }
6082
                if (is_object($object) && $object->element == 'commande') {
6083
                    '@phan-var-force Commande $object';
6084
                    $substitutionarray['__URL_ORDER__'] = DOL_MAIN_URL_ROOT . "/commande/card.php?id=" . $object->id;
6085
                }
6086
                if (is_object($object) && $object->element == 'facture') {
6087
                    '@phan-var-force Facture $object';
6088
                    $substitutionarray['__URL_INVOICE__'] = DOL_MAIN_URL_ROOT . "/compta/facture/card.php?id=" . $object->id;
6089
                }
6090
                if (is_object($object) && $object->element == 'contrat') {
6091
                    '@phan-var-force Contrat $object';
6092
                    $substitutionarray['__URL_CONTRACT__'] = DOL_MAIN_URL_ROOT . "/contrat/card.php?id=" . $object->id;
6093
                    require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/signature.lib.php';
6094
                    $substitutionarray['__ONLINE_SIGN_URL__'] = getOnlineSignatureUrl(0, 'contract', $object->ref, 1, $object);
6095
                }
6096
                if (is_object($object) && $object->element == 'fichinter') {
6097
                    '@phan-var-force Fichinter $object';
6098
                    $substitutionarray['__URL_FICHINTER__'] = DOL_MAIN_URL_ROOT . "/fichinter/card.php?id=" . $object->id;
6099
                    require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/signature.lib.php';
6100
                    $substitutionarray['__ONLINE_SIGN_FICHINTER_URL__'] = getOnlineSignatureUrl(0, 'fichinter', $object->ref, 1, $object);
6101
                }
6102
                if (is_object($object) && $object->element == 'supplier_proposal') {
6103
                    '@phan-var-force SupplierProposal $object';
6104
                    $substitutionarray['__URL_SUPPLIER_PROPOSAL__'] = DOL_MAIN_URL_ROOT . "/supplier_proposal/card.php?id=" . $object->id;
6105
                }
6106
                if (is_object($object) && $object->element == 'invoice_supplier') {
6107
                    '@phan-var-force FactureFournisseur $object';
6108
                    $substitutionarray['__URL_SUPPLIER_INVOICE__'] = DOL_MAIN_URL_ROOT . "/fourn/facture/card.php?id=" . $object->id;
6109
                }
6110
                if (is_object($object) && $object->element == 'shipping') {
6111
                    '@phan-var-force Expedition $object';
6112
                    $substitutionarray['__URL_SHIPMENT__'] = DOL_MAIN_URL_ROOT . "/expedition/card.php?id=" . $object->id;
6113
                }
6114
            }
6115
6116
            if (is_object($object) && $object->element == 'action') {
6117
                '@phan-var-force ActionComm $object';
6118
                $substitutionarray['__EVENT_LABEL__'] = $object->label;
6119
                $substitutionarray['__EVENT_TYPE__'] = $outputlangs->trans("Action" . $object->type_code);
6120
                $substitutionarray['__EVENT_DATE__'] = dol_print_date($object->datep, 'day', 'auto', $outputlangs);
0 ignored issues
show
Bug Best Practice introduced by
The property datep does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
6121
                $substitutionarray['__EVENT_TIME__'] = dol_print_date($object->datep, 'hour', 'auto', $outputlangs);
6122
            }
6123
        }
6124
    }
6125
    if ((empty($exclude) || !in_array('objectamount', $exclude)) && (empty($include) || in_array('objectamount', $include))) {
6126
        '@phan-var-force Facture|FactureRec $object';
6127
        include_once DOL_DOCUMENT_ROOT . '/core/lib/functionsnumtoword.lib.php';
6128
6129
        $substitutionarray['__DATE_YMD__'] = is_object($object) ? (isset($object->date) ? dol_print_date($object->date, 'day', 0, $outputlangs) : null) : '';
0 ignored issues
show
Bug Best Practice introduced by
The property date does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
6130
        $substitutionarray['__DATE_DUE_YMD__'] = is_object($object) ? (isset($object->date_lim_reglement) ? dol_print_date($object->date_lim_reglement, 'day', 0, $outputlangs) : null) : '';
0 ignored issues
show
Bug Best Practice introduced by
The property date_lim_reglement does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
6131
        $substitutionarray['__DATE_YMD_TEXT__'] = is_object($object) ? (isset($object->date) ? dol_print_date($object->date, 'daytext', 0, $outputlangs) : null) : '';
6132
        $substitutionarray['__DATE_DUE_YMD_TEXT__'] = is_object($object) ? (isset($object->date_lim_reglement) ? dol_print_date($object->date_lim_reglement, 'daytext', 0, $outputlangs) : null) : '';
6133
6134
        $already_payed_all = 0;
6135
        if (is_object($object) && ($object instanceof Facture)) {
6136
            $already_payed_all = $object->sumpayed + $object->sumdeposit + $object->sumcreditnote;
0 ignored issues
show
Bug Best Practice introduced by
The property sumcreditnote does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property sumdeposit does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
Bug Best Practice introduced by
The property sumpayed does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
6137
        }
6138
6139
        $substitutionarray['__AMOUNT_EXCL_TAX__'] = is_object($object) ? $object->total_ht : '';
6140
        $substitutionarray['__AMOUNT_EXCL_TAX_TEXT__'] = is_object($object) ? dol_convertToWord($object->total_ht, $outputlangs, '', true) : '';
6141
        $substitutionarray['__AMOUNT_EXCL_TAX_TEXTCURRENCY__'] = is_object($object) ? dol_convertToWord($object->total_ht, $outputlangs, $conf->currency, true) : '';
6142
6143
        $substitutionarray['__AMOUNT__'] = is_object($object) ? $object->total_ttc : '';
6144
        $substitutionarray['__AMOUNT_TEXT__'] = is_object($object) ? dol_convertToWord($object->total_ttc, $outputlangs, '', true) : '';
6145
        $substitutionarray['__AMOUNT_TEXTCURRENCY__'] = is_object($object) ? dol_convertToWord($object->total_ttc, $outputlangs, $conf->currency, true) : '';
6146
6147
        $substitutionarray['__AMOUNT_REMAIN__'] = is_object($object) ? price2num($object->total_ttc - $already_payed_all, 'MT') : '';
6148
6149
        $substitutionarray['__AMOUNT_VAT__'] = is_object($object) ? (isset($object->total_vat) ? $object->total_vat : $object->total_tva) : '';
0 ignored issues
show
Bug Best Practice introduced by
The property total_vat does not exist on Dolibarr\Core\Base\CommonObject. Since you implemented __get, consider adding a @property annotation.
Loading history...
6150
        $substitutionarray['__AMOUNT_VAT_TEXT__'] = is_object($object) ? (isset($object->total_vat) ? dol_convertToWord($object->total_vat, $outputlangs, '', true) : dol_convertToWord($object->total_tva, $outputlangs, '', true)) : '';
6151
        $substitutionarray['__AMOUNT_VAT_TEXTCURRENCY__'] = is_object($object) ? (isset($object->total_vat) ? dol_convertToWord($object->total_vat, $outputlangs, $conf->currency, true) : dol_convertToWord($object->total_tva, $outputlangs, $conf->currency, true)) : '';
6152
6153
        if ($onlykey != 2 || $mysoc->useLocalTax(1)) {
6154
            $substitutionarray['__AMOUNT_TAX2__'] = is_object($object) ? $object->total_localtax1 : '';
6155
        }
6156
        if ($onlykey != 2 || $mysoc->useLocalTax(2)) {
6157
            $substitutionarray['__AMOUNT_TAX3__'] = is_object($object) ? $object->total_localtax2 : '';
6158
        }
6159
6160
        // Amount keys formatted in a currency
6161
        $substitutionarray['__AMOUNT_EXCL_TAX_FORMATTED__'] = is_object($object) ? ($object->total_ht ? price($object->total_ht, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
6162
        $substitutionarray['__AMOUNT_FORMATTED__'] = is_object($object) ? ($object->total_ttc ? price($object->total_ttc, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
6163
        $substitutionarray['__AMOUNT_REMAIN_FORMATTED__'] = is_object($object) ? ($object->total_ttc ? price($object->total_ttc - $already_payed_all, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
6164
        $substitutionarray['__AMOUNT_VAT_FORMATTED__'] = is_object($object) ? (isset($object->total_vat) ? price($object->total_vat, 0, $outputlangs, 0, -1, -1, $conf->currency) : ($object->total_tva ? price($object->total_tva, 0, $outputlangs, 0, -1, -1, $conf->currency) : null)) : '';
6165
        if ($onlykey != 2 || $mysoc->useLocalTax(1)) {
6166
            $substitutionarray['__AMOUNT_TAX2_FORMATTED__'] = is_object($object) ? ($object->total_localtax1 ? price($object->total_localtax1, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
6167
        }
6168
        if ($onlykey != 2 || $mysoc->useLocalTax(2)) {
6169
            $substitutionarray['__AMOUNT_TAX3_FORMATTED__'] = is_object($object) ? ($object->total_localtax2 ? price($object->total_localtax2, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
6170
        }
6171
        // Amount keys formatted in a currency (with the typo error for backward compatibility)
6172
        if ($onlykey != 2) {
6173
            $substitutionarray['__AMOUNT_EXCL_TAX_FORMATED__'] = $substitutionarray['__AMOUNT_EXCL_TAX_FORMATTED__'];
6174
            $substitutionarray['__AMOUNT_FORMATED__'] = $substitutionarray['__AMOUNT_FORMATTED__'];
6175
            $substitutionarray['__AMOUNT_REMAIN_FORMATED__'] = $substitutionarray['__AMOUNT_REMAIN_FORMATTED__'];
6176
            $substitutionarray['__AMOUNT_VAT_FORMATED__'] = $substitutionarray['__AMOUNT_VAT_FORMATTED__'];
6177
            if ($mysoc instanceof Societe && $mysoc->useLocalTax(1)) {
6178
                $substitutionarray['__AMOUNT_TAX2_FORMATED__'] = $substitutionarray['__AMOUNT_TAX2_FORMATTED__'];
6179
            }
6180
            if ($mysoc instanceof Societe && $mysoc->useLocalTax(2)) {
6181
                $substitutionarray['__AMOUNT_TAX3_FORMATED__'] = $substitutionarray['__AMOUNT_TAX3_FORMATTED__'];
6182
            }
6183
        }
6184
6185
        $substitutionarray['__AMOUNT_MULTICURRENCY__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? $object->multicurrency_total_ttc : '';
6186
        $substitutionarray['__AMOUNT_MULTICURRENCY_TEXT__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? dol_convertToWord($object->multicurrency_total_ttc, $outputlangs, '', true) : '';
6187
        $substitutionarray['__AMOUNT_MULTICURRENCY_TEXTCURRENCY__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? dol_convertToWord($object->multicurrency_total_ttc, $outputlangs, $object->multicurrency_code, true) : '';
6188
        // TODO Add other keys for foreign multicurrency
6189
6190
        // For backward compatibility
6191
        if ($onlykey != 2) {
6192
            $substitutionarray['__TOTAL_TTC__'] = is_object($object) ? $object->total_ttc : '';
6193
            $substitutionarray['__TOTAL_HT__'] = is_object($object) ? $object->total_ht : '';
6194
            $substitutionarray['__TOTAL_VAT__'] = is_object($object) ? (isset($object->total_vat) ? $object->total_vat : $object->total_tva) : '';
6195
        }
6196
    }
6197
6198
6199
    if ((empty($exclude) || !in_array('date', $exclude)) && (empty($include) || in_array('date', $include))) {
6200
        include_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
6201
6202
        $now = dol_now();
6203
6204
        $tmp = dol_getdate($now, true);
6205
        $tmp2 = dol_get_prev_day($tmp['mday'], $tmp['mon'], $tmp['year']);
6206
        $tmp3 = dol_get_prev_month($tmp['mon'], $tmp['year']);
6207
        $tmp4 = dol_get_next_day($tmp['mday'], $tmp['mon'], $tmp['year']);
6208
        $tmp5 = dol_get_next_month($tmp['mon'], $tmp['year']);
6209
6210
        $daytext = $outputlangs->trans('Day' . $tmp['wday']);
6211
6212
        $substitutionarray = array_merge($substitutionarray, array(
6213
            '__NOW_TMS__' => (string)$now,     // Must be the string that represent the int
6214
            '__NOW_TMS_YMD__' => dol_print_date($now, 'day', 'auto', $outputlangs),
6215
            '__DAY__' => (string)$tmp['mday'],
6216
            '__DAY_TEXT__' => $daytext, // Monday
6217
            '__DAY_TEXT_SHORT__' => dol_trunc($daytext, 3, 'right', 'UTF-8', 1), // Mon
6218
            '__DAY_TEXT_MIN__' => dol_trunc($daytext, 1, 'right', 'UTF-8', 1), // M
6219
            '__MONTH__' => (string)$tmp['mon'],
6220
            '__MONTH_TEXT__' => $outputlangs->trans('Month' . sprintf("%02d", $tmp['mon'])),
6221
            '__MONTH_TEXT_SHORT__' => $outputlangs->trans('MonthShort' . sprintf("%02d", $tmp['mon'])),
6222
            '__MONTH_TEXT_MIN__' => $outputlangs->trans('MonthVeryShort' . sprintf("%02d", $tmp['mon'])),
6223
            '__YEAR__' => (string)$tmp['year'],
6224
            '__PREVIOUS_DAY__' => (string)$tmp2['day'],
6225
            '__PREVIOUS_MONTH__' => (string)$tmp3['month'],
6226
            '__PREVIOUS_YEAR__' => (string)($tmp['year'] - 1),
6227
            '__NEXT_DAY__' => (string)$tmp4['day'],
6228
            '__NEXT_MONTH__' => (string)$tmp5['month'],
6229
            '__NEXT_MONTH_TEXT__' => $outputlangs->trans('Month' . sprintf("%02d", $tmp5['month'])),
6230
            '__NEXT_MONTH_TEXT_SHORT__' => $outputlangs->trans('MonthShort' . sprintf("%02d", $tmp5['month'])),
6231
            '__NEXT_MONTH_TEXT_MIN__' => $outputlangs->trans('MonthVeryShort' . sprintf("%02d", $tmp5['month'])),
6232
            '__NEXT_YEAR__' => (string)($tmp['year'] + 1),
6233
        ));
6234
    }
6235
6236
    if (isModEnabled('multicompany')) {
6237
        $substitutionarray = array_merge($substitutionarray, array('__ENTITY_ID__' => $conf->entity));
6238
    }
6239
    if ((empty($exclude) || !in_array('system', $exclude)) && (empty($include) || in_array('user', $include))) {
6240
        $substitutionarray['__DOL_MAIN_URL_ROOT__'] = DOL_MAIN_URL_ROOT;
6241
        $substitutionarray['__(AnyTranslationKey)__'] = $outputlangs->trans('TranslationOfKey');
6242
        $substitutionarray['__(AnyTranslationKey|langfile)__'] = $outputlangs->trans('TranslationOfKey') . ' (load also language file before)';
6243
        $substitutionarray['__[AnyConstantKey]__'] = $outputlangs->trans('ValueOfConstantKey');
6244
    }
6245
6246
    // Note: The lazyload variables are replaced only during the call by make_substitutions, and only if necessary
6247
6248
    return $substitutionarray;
6249
}
6250
6251
/**
6252
 *  Make substitution into a text string, replacing keys with vals from $substitutionarray (oldval=>newval),
6253
 *  and texts like __(TranslationKey|langfile)__ and __[ConstantKey]__ are also replaced.
6254
 *  Example of usage:
6255
 *  $substitutionarray = getCommonSubstitutionArray($langs, 0, null, $thirdparty);
6256
 *  complete_substitutions_array($substitutionarray, $langs, $thirdparty);
6257
 *  $mesg = make_substitutions($mesg, $substitutionarray, $langs);
6258
 *
6259
 * @param string $text Source string in which we must do substitution
6260
 * @param array<string,string> $substitutionarray Array with key->val to substitute. Example: array('__MYKEY__' => 'MyVal', ...)
6261
 * @param  ?Translate $outputlangs Output language
6262
 * @param int $converttextinhtmlifnecessary 0=Convert only value into HTML if text is already in HTML
6263
 *                                                      1=Will also convert initial $text into HTML if we try to insert one value that is HTML
6264
 * @return string                                      Output string after substitutions
6265
 * @see    complete_substitutions_array(), getCommonSubstitutionArray()
6266
 */
6267
function make_substitutions($text, $substitutionarray, $outputlangs = null, $converttextinhtmlifnecessary = 0)
6268
{
6269
    global $conf, $db, $langs;
6270
6271
    if (!is_array($substitutionarray)) {
6272
        return 'ErrorBadParameterSubstitutionArrayWhenCalling_make_substitutions';
6273
    }
6274
6275
    if (empty($outputlangs)) {
6276
        $outputlangs = $langs;
6277
    }
6278
6279
    // Is initial text HTML or simple text ?
6280
    $msgishtml = 0;
6281
    if (dol_textishtml($text, 1)) {
6282
        $msgishtml = 1;
6283
    }
6284
6285
    // Make substitution for language keys: __(AnyTranslationKey)__ or __(AnyTranslationKey|langfile)__
6286
    if (is_object($outputlangs)) {
6287
        $reg = array();
6288
        while (preg_match('/__\(([^\)]+)\)__/', $text, $reg)) {
6289
            // If key is __(TranslationKey|langfile)__, then force load of langfile.lang
6290
            $tmp = explode('|', $reg[1]);
6291
            if (!empty($tmp[1])) {
6292
                $outputlangs->load($tmp[1]);
6293
            }
6294
6295
            $value = $outputlangs->transnoentitiesnoconv($reg[1]);
6296
6297
            if (empty($converttextinhtmlifnecessary)) {
6298
                // convert $newval into HTML is necessary
6299
                $text = preg_replace('/__\(' . preg_quote($reg[1], '/') . '\)__/', $msgishtml ? dol_htmlentitiesbr($value) : $value, $text);
6300
            } else {
6301
                if (!$msgishtml) {
6302
                    $valueishtml = dol_textishtml($value, 1);
6303
                    //var_dump("valueishtml=".$valueishtml);
6304
6305
                    if ($valueishtml) {
6306
                        $text = dol_htmlentitiesbr($text);
6307
                        $msgishtml = 1;
6308
                    }
6309
                } else {
6310
                    $value = dol_nl2br("$value");
6311
                }
6312
6313
                $text = preg_replace('/__\(' . preg_quote($reg[1], '/') . '\)__/', $value, $text);
6314
            }
6315
        }
6316
    }
6317
6318
    // Make substitution for constant keys.
6319
    // Must be after the substitution of translation, so if the text of translation contains a string __[xxx]__, it is also converted.
6320
    $reg = array();
6321
    while (preg_match('/__\[([^\]]+)\]__/', $text, $reg)) {
6322
        $keyfound = $reg[1];
6323
        if (isASecretKey($keyfound)) {
6324
            $value = '*****forbidden*****';
6325
        } else {
6326
            $value = empty($conf->global->$keyfound) ? '' : $conf->global->$keyfound;
6327
        }
6328
6329
        if (empty($converttextinhtmlifnecessary)) {
6330
            // convert $newval into HTML is necessary
6331
            $text = preg_replace('/__\[' . preg_quote($keyfound, '/') . '\]__/', $msgishtml ? dol_htmlentitiesbr($value) : $value, $text);
6332
        } else {
6333
            if (!$msgishtml) {
6334
                $valueishtml = dol_textishtml($value, 1);
6335
6336
                if ($valueishtml) {
6337
                    $text = dol_htmlentitiesbr($text);
6338
                    $msgishtml = 1;
6339
                }
6340
            } else {
6341
                $value = dol_nl2br("$value");
6342
            }
6343
6344
            $text = preg_replace('/__\[' . preg_quote($keyfound, '/') . '\]__/', $value, $text);
6345
        }
6346
    }
6347
6348
    // Make substitution for array $substitutionarray
6349
    foreach ($substitutionarray as $key => $value) {
6350
        if (!isset($value)) {
6351
            continue; // If value is null, it same than not having substitution key at all into array, we do not replace.
6352
        }
6353
6354
        if (($key == '__USER_SIGNATURE__' || $key == '__SENDEREMAIL_SIGNATURE__') && (getDolGlobalString('MAIN_MAIL_DO_NOT_USE_SIGN'))) {
6355
            $value = ''; // Protection
6356
        }
6357
6358
        if (empty($converttextinhtmlifnecessary)) {
6359
            $text = str_replace("$key", "$value", $text); // We must keep the " to work when value is 123.5 for example
6360
        } else {
6361
            if (!$msgishtml) {
6362
                $valueishtml = dol_textishtml($value, 1);
6363
6364
                if ($valueishtml) {
6365
                    $text = dol_htmlentitiesbr($text);
6366
                    $msgishtml = 1;
6367
                }
6368
            } else {
6369
                $value = dol_nl2br("$value");
6370
            }
6371
            $text = str_replace("$key", "$value", $text); // We must keep the " to work when value is 123.5 for example
6372
        }
6373
    }
6374
6375
    // TODO Implement the lazyload substitution
6376
    /*
6377
    add a loop to scan $substitutionarray:
6378
    For each key ending with '@lazyload', we extract the substitution key 'XXX' and we check inside the $text (the 1st parameter of make_substitutions), if the string XXX exists.
6379
    If no, we don't need to make replacement, so we do nothing.
6380
    If yes, we can make the substitution:
6381
6382
    include_once $path;
6383
    $tmpobj = new $class($db);
6384
    $valuetouseforsubstitution = $tmpobj->$method($id, '__XXX__');
6385
    And make the replacement of "__XXX__@lazyload" with $valuetouseforsubstitution
6386
    */
6387
    $memory_object_list = array();
6388
    foreach ($substitutionarray as $key => $value) {
6389
        $lazy_load_arr = array();
6390
        if (preg_match('/(__[A-Z\_]+__)@lazyload$/', $key, $lazy_load_arr)) {
6391
            if (isset($lazy_load_arr[1]) && !empty($lazy_load_arr[1])) {
6392
                $key_to_substitute = $lazy_load_arr[1];
6393
                if (preg_match('/' . preg_quote($key_to_substitute, '/') . '/', $text)) {
6394
                    $param_arr = explode(':', $value);
6395
                    // path:class:method:id
6396
                    if (count($param_arr) == 4) {
6397
                        $path = $param_arr[0];
6398
                        $class = $param_arr[1];
6399
                        $method = $param_arr[2];
6400
                        $id = (int)$param_arr[3];
6401
6402
                        // load class file and init object list in memory
6403
                        if (!isset($memory_object_list[$class])) {
6404
                            if (dol_is_file(DOL_DOCUMENT_ROOT . $path)) {
6405
                                require_once DOL_DOCUMENT_ROOT . $path;
6406
                                if (class_exists($class)) {
6407
                                    $memory_object_list[$class] = array(
6408
                                        'list' => array(),
6409
                                    );
6410
                                }
6411
                            }
6412
                        }
6413
6414
                        // fetch object and set substitution
6415
                        if (isset($memory_object_list[$class]) && isset($memory_object_list[$class]['list'])) {
6416
                            if (method_exists($class, $method)) {
6417
                                if (!isset($memory_object_list[$class]['list'][$id])) {
6418
                                    $tmpobj = new $class($db);
6419
                                    // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
6420
                                    $valuetouseforsubstitution = $tmpobj->$method($id, $key_to_substitute);
6421
                                    $memory_object_list[$class]['list'][$id] = $tmpobj;
6422
                                } else {
6423
                                    // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
6424
                                    $tmpobj = $memory_object_list[$class]['list'][$id];
6425
                                    // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
6426
                                    $valuetouseforsubstitution = $tmpobj->$method($id, $key_to_substitute, true);
6427
                                }
6428
6429
                                $text = str_replace("$key_to_substitute", "$valuetouseforsubstitution", $text); // We must keep the " to work when value is 123.5 for example
6430
                            }
6431
                        }
6432
                    }
6433
                }
6434
            }
6435
        }
6436
    }
6437
6438
    return $text;
6439
}
6440
6441
/**
6442
 *  Complete the $substitutionarray with more entries coming from external module that had set the "substitutions=1" into module_part array.
6443
 *  In this case, method completesubstitutionarray provided by module is called.
6444
 *
6445
 * @param array<string,string> $substitutionarray Array substitution old value => new value value
6446
 * @param Translate $outputlangs Output language
6447
 * @param CommonObject $object Source object
6448
 * @param mixed $parameters Add more parameters (useful to pass product lines)
6449
 * @param string $callfunc What is the name of the custom function that will be called? (default: completesubstitutionarray)
6450
 * @return void
6451
 * @see    make_substitutions()
6452
 */
6453
function complete_substitutions_array(&$substitutionarray, $outputlangs, $object = null, $parameters = null, $callfunc = "completesubstitutionarray")
6454
{
6455
    global $conf, $user;
6456
6457
    require_once constant('DOL_DOCUMENT_ROOT') . '/core/lib/files.lib.php';
6458
6459
    // Note: substitution key for each extrafields, using key __EXTRA_XXX__ is already available into the getCommonSubstitutionArray used to build the substitution array.
6460
6461
    // Check if there is external substitution to do, requested by plugins
6462
    $dirsubstitutions = array_merge(array(), (array)$conf->modules_parts['substitutions']);
6463
6464
    foreach ($dirsubstitutions as $reldir) {
6465
        $dir = dol_buildpath($reldir, 0);
6466
6467
        // Check if directory exists
6468
        if (!dol_is_dir($dir)) {
6469
            continue;
6470
        }
6471
6472
        $substitfiles = dol_dir_list($dir, 'files', 0, 'functions_');
6473
        foreach ($substitfiles as $substitfile) {
6474
            $reg = array();
6475
            if (preg_match('/functions_(.*)\.lib\.php/i', $substitfile['name'], $reg)) {
6476
                $module = $reg[1];
6477
6478
                dol_syslog("Library " . $substitfile['name'] . " found into " . $dir);
6479
                // Include the user's functions file
6480
                require_once $dir . $substitfile['name'];
6481
                // Call the user's function, and only if it is defined
6482
                $function_name = $module . "_" . $callfunc;
6483
                if (function_exists($function_name)) {
6484
                    $function_name($substitutionarray, $outputlangs, $object, $parameters);
6485
                }
6486
            }
6487
        }
6488
    }
6489
    if (getDolGlobalString('ODT_ENABLE_ALL_TAGS_IN_SUBSTITUTIONS')) {
6490
        // to list all tags in odt template
6491
        $tags = '';
6492
        foreach ($substitutionarray as $key => $value) {
6493
            $tags .= '{' . $key . '} => ' . $value . "\n";
6494
        }
6495
        $substitutionarray = array_merge($substitutionarray, array('__ALL_TAGS__' => $tags));
6496
    }
6497
}
6498
6499
/**
6500
 *    Format output for start and end date
6501
 *
6502
 * @param int $date_start Start date
6503
 * @param int $date_end End date
6504
 * @param string $format Output format
6505
 * @param Translate $outputlangs Output language
6506
 * @return   void
6507
 */
6508
function print_date_range($date_start, $date_end, $format = '', $outputlangs = null)
6509
{
6510
    print get_date_range($date_start, $date_end, $format, $outputlangs);
6511
}
6512
6513
/**
6514
 *    Format output for start and end date
6515
 *
6516
 * @param int $date_start Start date
6517
 * @param int $date_end End date
6518
 * @param string $format Output date format ('day', 'dayhour', ...)
6519
 * @param Translate $outputlangs Output language
6520
 * @param integer $withparenthesis 1=Add parenthesis, 0=no parenthesis
6521
 * @return   string                          String
6522
 */
6523
function get_date_range($date_start, $date_end, $format = '', $outputlangs = null, $withparenthesis = 1)
6524
{
6525
    global $langs;
6526
6527
    $out = '';
6528
6529
    if (!is_object($outputlangs)) {
6530
        $outputlangs = $langs;
6531
    }
6532
6533
    if ($date_start && $date_end) {
6534
        $out .= ($withparenthesis ? ' (' : '') . $outputlangs->transnoentitiesnoconv('DateFromTo', dol_print_date($date_start, $format, false, $outputlangs), dol_print_date($date_end, $format, false, $outputlangs)) . ($withparenthesis ? ')' : '');
6535
    }
6536
    if ($date_start && !$date_end) {
6537
        $out .= ($withparenthesis ? ' (' : '') . $outputlangs->transnoentitiesnoconv('DateFrom', dol_print_date($date_start, $format, false, $outputlangs)) . ($withparenthesis ? ')' : '');
6538
    }
6539
    if (!$date_start && $date_end) {
6540
        $out .= ($withparenthesis ? ' (' : '') . $outputlangs->transnoentitiesnoconv('DateUntil', dol_print_date($date_end, $format, false, $outputlangs)) . ($withparenthesis ? ')' : '');
6541
    }
6542
6543
    return $out;
6544
}
6545
6546
/**
6547
 * Return firstname and lastname in correct order
6548
 *
6549
 * @param string $firstname Firstname
6550
 * @param string $lastname Lastname
6551
 * @param int $nameorder -1=Auto, 0=Lastname+Firstname, 1=Firstname+Lastname, 2=Firstname, 3=Firstname if defined else lastname, 4=Lastname, 5=Lastname if defined else firstname
6552
 * @return  string                  Firstname + lastname or Lastname + firstname
6553
 */
6554
function dolGetFirstLastname($firstname, $lastname, $nameorder = -1)
6555
{
6556
    global $conf;
6557
6558
    $ret = '';
6559
    // If order not defined, we use the setup
6560
    if ($nameorder < 0) {
6561
        $nameorder = (!getDolGlobalString('MAIN_FIRSTNAME_NAME_POSITION') ? 1 : 0);
6562
    }
6563
    if ($nameorder == 1) {
6564
        $ret .= $firstname;
6565
        if ($firstname && $lastname) {
6566
            $ret .= ' ';
6567
        }
6568
        $ret .= $lastname;
6569
    } elseif ($nameorder == 2 || $nameorder == 3) {
6570
        $ret .= $firstname;
6571
        if (empty($ret) && $nameorder == 3) {
6572
            $ret .= $lastname;
6573
        }
6574
    } else {    // 0, 4 or 5
6575
        $ret .= $lastname;
6576
        if (empty($ret) && $nameorder == 5) {
6577
            $ret .= $firstname;
6578
        }
6579
        if ($nameorder == 0) {
6580
            if ($firstname && $lastname) {
6581
                $ret .= ' ';
6582
            }
6583
            $ret .= $firstname;
6584
        }
6585
    }
6586
    return $ret;
6587
}
6588
6589
6590
/**
6591
 *  Set event message in dol_events session object. Will be output by calling dol_htmloutput_events.
6592
 *  Note: Calling dol_htmloutput_events is done into pages by standard ViewMain::llxFooter() function.
6593
 *  Note: Prefer to use setEventMessages instead.
6594
 *
6595
 * @param string|string[] $mesgs Message string or array
6596
 * @param string $style Which style to use ('mesgs' by default, 'warnings', 'errors')
6597
 * @param int $noduplicate 1 means we do not add the message if already present in session stack
6598
 * @return void
6599
 * @see    dol_htmloutput_events()
6600
 */
6601
function setEventMessage($mesgs, $style = 'mesgs', $noduplicate = 0)
6602
{
6603
    //dol_syslog(__FUNCTION__ . " is deprecated", LOG_WARNING);     This is not deprecated, it is used by setEventMessages function
6604
    if (!is_array($mesgs)) {
6605
        $mesgs = trim((string)$mesgs);
6606
        // If mesgs is a not an empty string
6607
        if ($mesgs) {
6608
            if (!empty($noduplicate) && isset($_SESSION['dol_events'][$style]) && in_array($mesgs, $_SESSION['dol_events'][$style])) {
6609
                return;
6610
            }
6611
            $_SESSION['dol_events'][$style][] = $mesgs;
6612
        }
6613
    } else {
6614
        // If mesgs is an array
6615
        foreach ($mesgs as $mesg) {
6616
            $mesg = trim((string)$mesg);
6617
            if ($mesg) {
6618
                if (!empty($noduplicate) && isset($_SESSION['dol_events'][$style]) && in_array($mesg, $_SESSION['dol_events'][$style])) {
6619
                    return;
6620
                }
6621
                $_SESSION['dol_events'][$style][] = $mesg;
6622
            }
6623
        }
6624
    }
6625
}
6626
6627
/**
6628
 *  Set event messages in dol_events session object. Will be output by calling dol_htmloutput_events.
6629
 *  Note: Calling dol_htmloutput_events is done into pages by standard ViewMain::llxFooter() function.
6630
 *
6631
 * @param string|null $mesg Message string
6632
 * @param string[]|null $mesgs Message array
6633
 * @param string $style Which style to use ('mesgs' by default, 'warnings', 'errors')
6634
 * @param string $messagekey A key to be used to allow the feature "Never show this message during this session again"
6635
 * @param int $noduplicate 1 means we do not add the message if already present in session stack
6636
 * @return void
6637
 * @see    dol_htmloutput_events()
6638
 */
6639
function setEventMessages($mesg, $mesgs, $style = 'mesgs', $messagekey = '', $noduplicate = 0)
6640
{
6641
    if (empty($mesg) && empty($mesgs)) {
6642
        dol_syslog("Try to add a message in stack, but value to add is empty message", LOG_WARNING);
6643
    } else {
6644
        if ($messagekey) {
6645
            // Complete message with a js link to set a cookie "DOLHIDEMESSAGE".$messagekey;
6646
            // TODO
6647
            $mesg .= '';
6648
        }
6649
        if (empty($messagekey) || empty($_COOKIE["DOLHIDEMESSAGE" . $messagekey])) {
6650
            if (!in_array((string)$style, array('mesgs', 'warnings', 'errors'))) {
6651
                dol_print_error(null, 'Bad parameter style=' . $style . ' for setEventMessages');
6652
            }
6653
            if (empty($mesgs)) {
6654
                setEventMessage($mesg, $style, $noduplicate);
6655
            } else {
6656
                if (!empty($mesg) && !in_array($mesg, $mesgs)) {
6657
                    setEventMessage($mesg, $style, $noduplicate); // Add message string if not already into array
6658
                }
6659
                setEventMessage($mesgs, $style, $noduplicate);
6660
            }
6661
        }
6662
    }
6663
}
6664
6665
/**
6666
 *  Advanced sort array by the value of a given key, which produces ascending (default) or descending
6667
 *  output and uses optionally natural case insensitive sorting (which can be optionally case sensitive as well).
6668
 *
6669
 * @param array<string|int,mixed> $array Array to sort (array of array('key1'=>val1,'key2'=>val2,'key3'...) or array of objects)
6670
 * @param string $index Key in array to use for sorting criteria
6671
 * @param string $order Sort order ('asc' or 'desc')
6672
 * @param int<0,1> $natsort If values are strings (I said value not type): 0=Use alphabetical order, 1=use "natural" sort (natsort)
6673
 *                                          If values are numeric (I said value not type): 0=Use numeric order (even if type is string) so use a "natural" sort, 1=use "natural" sort too (same than 0), -1=Force alphabetical order
6674
 * @param int<0,1> $case_sensitive 1=sort is case sensitive, 0=not case sensitive
6675
 * @param int<0,1> $keepindex If 0 and index key of array to sort is a numeric, then index will be rewritten. If 1 or index key is not numeric, key for index is kept after sorting.
6676
 * @return array<string|int,mixed>         Return the sorted array (the source array is not modified !)
6677
 */
6678
function dol_sort_array(&$array, $index, $order = 'asc', $natsort = 0, $case_sensitive = 0, $keepindex = 0)
6679
{
6680
    // Clean parameters
6681
    $order = strtolower($order);
6682
6683
    if (is_array($array)) {
6684
        $sizearray = count($array);
6685
        if ($sizearray > 0) {
6686
            $temp = array();
6687
            foreach (array_keys($array) as $key) {
6688
                if (is_object($array[$key])) {
6689
                    $temp[$key] = empty($array[$key]->$index) ? 0 : $array[$key]->$index;
6690
                } else {
6691
                    // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
6692
                    $temp[$key] = empty($array[$key][$index]) ? 0 : $array[$key][$index];
6693
                }
6694
                if ($natsort == -1) {
6695
                    $temp[$key] = '___' . $temp[$key];        // We add a string at begin of value to force an alpha order when using asort.
6696
                }
6697
            }
6698
6699
            if (empty($natsort) || $natsort == -1) {
6700
                if ($order == 'asc') {
6701
                    asort($temp);
6702
                } else {
6703
                    arsort($temp);
6704
                }
6705
            } else {
6706
                if ($case_sensitive) {
6707
                    natsort($temp);
6708
                } else {
6709
                    natcasesort($temp); // natecasesort is not sensible to case
6710
                }
6711
                if ($order != 'asc') {
6712
                    $temp = array_reverse($temp, true);
6713
                }
6714
            }
6715
6716
            $sorted = array();
6717
6718
            foreach (array_keys($temp) as $key) {
6719
                (is_numeric($key) && empty($keepindex)) ? $sorted[] = $array[$key] : $sorted[$key] = $array[$key];
6720
            }
6721
6722
            return $sorted;
6723
        }
6724
    }
6725
    return $array;
6726
}
6727
6728
6729
/**
6730
 *  Check if a string is in UTF8. Seems similar to utf8_valid() but in pure PHP.
6731
 *
6732
 * @param string $str String to check
6733
 * @return boolean             True if string is UTF8 or ISO compatible with UTF8, False if not (ISO with special non utf8 char or Binary)
6734
 * @see utf8_valid()
6735
 */
6736
function utf8_check($str)
6737
{
6738
    $str = (string)$str;   // Sometimes string is an int.
6739
6740
    // We must use here a binary strlen function (so not dol_strlen)
6741
    $strLength = strlen($str);
6742
    for ($i = 0; $i < $strLength; $i++) {
6743
        if (ord($str[$i]) < 0x80) {
6744
            continue; // 0bbbbbbb
6745
        } elseif ((ord($str[$i]) & 0xE0) == 0xC0) {
6746
            $n = 1; // 110bbbbb
6747
        } elseif ((ord($str[$i]) & 0xF0) == 0xE0) {
6748
            $n = 2; // 1110bbbb
6749
        } elseif ((ord($str[$i]) & 0xF8) == 0xF0) {
6750
            $n = 3; // 11110bbb
6751
        } elseif ((ord($str[$i]) & 0xFC) == 0xF8) {
6752
            $n = 4; // 111110bb
6753
        } elseif ((ord($str[$i]) & 0xFE) == 0xFC) {
6754
            $n = 5; // 1111110b
6755
        } else {
6756
            return false; // Does not match any model
6757
        }
6758
        for ($j = 0; $j < $n; $j++) { // n bytes matching 10bbbbbb follow ?
6759
            if ((++$i == strlen($str)) || ((ord($str[$i]) & 0xC0) != 0x80)) {
6760
                return false;
6761
            }
6762
        }
6763
    }
6764
    return true;
6765
}
6766
6767
/**
6768
 *      Check if a string is in UTF8. Seems similar to utf8_check().
6769
 *
6770
 * @param string $str String to check
6771
 * @return boolean             True if string is valid UTF8 string, false if corrupted
6772
 * @see utf8_check()
6773
 */
6774
function utf8_valid($str)
6775
{
6776
    /* 2 other methods to test if string is utf8
6777
     $validUTF8 = mb_check_encoding($messagetext, 'UTF-8');
6778
     $validUTF8b = ! (false === mb_detect_encoding($messagetext, 'UTF-8', true));
6779
     */
6780
    return preg_match('//u', $str) ? true : false;
6781
}
6782
6783
6784
/**
6785
 *      Check if a string is in ASCII
6786
 *
6787
 * @param string $str String to check
6788
 * @return boolean             True if string is ASCII, False if not (byte value > 0x7F)
6789
 */
6790
function ascii_check($str)
6791
{
6792
    if (function_exists('mb_check_encoding')) {
6793
        //if (mb_detect_encoding($str, 'ASCII', true) return false;
6794
        if (!mb_check_encoding($str, 'ASCII')) {
6795
            return false;
6796
        }
6797
    } else {
6798
        if (preg_match('/[^\x00-\x7f]/', $str)) {
6799
            return false; // Contains a byte > 7f
6800
        }
6801
    }
6802
6803
    return true;
6804
}
6805
6806
6807
/**
6808
 *      Return a string encoded into OS filesystem encoding. This function is used to define
6809
 *      value to pass to filesystem PHP functions.
6810
 *
6811
 * @param string $str String to encode (UTF-8)
6812
 * @return string              Encoded string (UTF-8, ISO-8859-1)
6813
 */
6814
function dol_osencode($str)
6815
{
6816
    $tmp = ini_get("unicode.filesystem_encoding");
6817
    if (empty($tmp) && !empty($_SERVER["WINDIR"])) {
6818
        $tmp = 'iso-8859-1'; // By default for windows
6819
    }
6820
    if (empty($tmp)) {
6821
        $tmp = 'utf-8'; // By default for other
6822
    }
6823
    if (getDolGlobalString('MAIN_FILESYSTEM_ENCODING')) {
6824
        $tmp = getDolGlobalString('MAIN_FILESYSTEM_ENCODING');
6825
    }
6826
6827
    if ($tmp == 'iso-8859-1') {
6828
        return mb_convert_encoding($str, 'ISO-8859-1', 'UTF-8');
0 ignored issues
show
Bug Best Practice introduced by
The expression return mb_convert_encodi... 'ISO-8859-1', 'UTF-8') also could return the type array which is incompatible with the documented return type string.
Loading history...
6829
    }
6830
    return $str;
6831
}
6832
6833
6834
/**
6835
 *      Return an id or code from a code or id.
6836
 *      Store also Code-Id into a cache to speed up next request on same table and key.
6837
 *
6838
 * @param DoliDB $db Database handler
6839
 * @param string $key Code or Id to get Id or Code
6840
 * @param string $tablename Table name without prefix
6841
 * @param string $fieldkey Field to search the key into
6842
 * @param string $fieldid Field to get
6843
 * @param int $entityfilter Filter by entity
6844
 * @param string $filters Filters to add. WARNING: string must be escaped for SQL and not coming from user input.
6845
 * @return int<-1,max>|string                  ID of code if OK, 0 if key empty, -1 if KO
6846
 * @see $langs->getLabelFromKey
6847
 */
6848
function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid = 'id', $entityfilter = 0, $filters = '')
6849
{
6850
    global $conf;
6851
6852
    // If key empty
6853
    if ($key == '') {
6854
        return 0;
6855
    }
6856
6857
    // Check in cache
6858
    if (isset($conf->cache['codeid'][$tablename][$key][$fieldid])) {    // Can be defined to 0 or ''
6859
        return $conf->cache['codeid'][$tablename][$key][$fieldid]; // Found in cache
6860
    }
6861
6862
    dol_syslog('dol_getIdFromCode (value for field ' . $fieldid . ' from key ' . $key . ' not found into cache)', LOG_DEBUG);
6863
6864
    $sql = "SELECT " . $fieldid . " as valuetoget";
6865
    $sql .= " FROM " . MAIN_DB_PREFIX . $tablename;
6866
    $sql .= " WHERE " . $fieldkey . " = '" . $db->escape($key) . "'";
6867
    if (!empty($entityfilter)) {
6868
        $sql .= " AND entity IN (" . getEntity($tablename) . ")";
6869
    }
6870
    if ($filters) {
6871
        $sql .= $filters;
6872
    }
6873
6874
    $resql = $db->query($sql);
6875
    if ($resql) {
6876
        $obj = $db->fetch_object($resql);
6877
        if ($obj) {
6878
            $conf->cache['codeid'][$tablename][$key][$fieldid] = $obj->valuetoget;
6879
        } else {
6880
            $conf->cache['codeid'][$tablename][$key][$fieldid] = '';
6881
        }
6882
        $db->free($resql);
6883
6884
        return $conf->cache['codeid'][$tablename][$key][$fieldid];
6885
    } else {
6886
        return -1;
6887
    }
6888
}
6889
6890
/**
6891
 *  Check if a variable with name $var startx with $text.
6892
 *  Can be used to forge dol_eval() conditions.
6893
 *
6894
 * @param string $var Variable
6895
 * @param string $regextext Text that must be a valid regex string
6896
 * @param int<0,1> $matchrule 1=Test if start with, 0=Test if equal
6897
 * @return boolean|string      True or False, text if bad usage.
6898
 */
6899
function isStringVarMatching($var, $regextext, $matchrule = 1)
6900
{
6901
    if ($matchrule == 1) {
6902
        if ($var == 'mainmenu') {
6903
            global $mainmenu;
6904
            return (preg_match('/^' . $regextext . '/', $mainmenu));
6905
        } elseif ($var == 'leftmenu') {
6906
            global $leftmenu;
6907
            return (preg_match('/^' . $regextext . '/', $leftmenu));
6908
        } else {
6909
            return 'This variable is not accessible with dol_eval';
6910
        }
6911
    } else {
6912
        return 'This value for matchrule is not implemented';
6913
    }
6914
}
6915
6916
6917
/**
6918
 * Verify if condition in string is ok or not
6919
 *
6920
 * @param string $strToEvaluate String with condition to check
6921
 * @param string $onlysimplestring '0' (deprecated, do not use it anymore)=Accept all chars,
6922
 *                                      '1' (most common use)=Accept only simple string with char 'a-z0-9\s^$_+-.*>&|=!?():"\',/@';',
6923
 *                                      '2' (used for example for the compute property of extrafields)=Accept also '[]'
6924
 * @return  boolean                     True or False. Note: It returns also True if $strToEvaluate is ''. False if error
6925
 */
6926
function verifCond($strToEvaluate, $onlysimplestring = '1')
6927
{
6928
    //print $strToEvaluate."<br>\n";
6929
    $rights = true;
6930
    if (isset($strToEvaluate) && $strToEvaluate !== '') {
6931
        //var_dump($strToEvaluate);
6932
        //$rep = dol_eval($strToEvaluate, 1, 0, '1'); // to show the error
6933
        $rep = (int)dol_eval($strToEvaluate, 1, 1, $onlysimplestring); // The dol_eval() must contains all the "global $xxx;" for all variables $xxx found into the string condition
6934
        $rights = $rep && (!is_string($rep) || strpos($rep, 'Bad string syntax to evaluate') === false);
6935
        //var_dump($rights);
6936
    }
6937
    return $rights;
6938
}
6939
6940
/**
6941
 * Replace eval function to add more security.
6942
 * This function is called by verifCond() or trans() and transnoentitiesnoconv().
6943
 *
6944
 * @param string $s String to evaluate
6945
 * @param int<0,1> $returnvalue 0=No return (deprecated, used to execute eval($a=something)). 1=Value of eval is returned (used to eval($something)).
6946
 * @param int<0,1> $hideerrors 1=Hide errors
6947
 * @param string $onlysimplestring '0' (deprecated, do not use it anymore)=Accept all chars,
6948
 *                                          '1' (most common use)=Accept only simple string with char 'a-z0-9\s^$_+-.*>&|=!?():"\',/@';',
6949
 *                                          '2' (used for example for the compute property of extrafields)=Accept also '[]'
6950
 * @return  void|string                     Nothing or return result of eval (even if type can be int, it is safer to assume string and find all potential typing issues as abs(dol_eval(...)).
6951
 * @see verifCond()
6952
 * @phan-suppress PhanPluginUnsafeEval
6953
 */
6954
function dol_eval($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestring = '1')
6955
{
6956
    // Only this global variables can be read by eval function and returned to caller
6957
    global $conf;   // Read of const is done with getDolGlobalString() but we need $conf->currency for example
6958
    global $db, $langs, $user, $website, $websitepage;
6959
    global $action, $mainmenu, $leftmenu;
6960
    global $mysoc;
6961
    global $objectoffield;  // To allow the use of $objectoffield in computed fields
6962
6963
    // Old variables used
6964
    global $object;
6965
    global $obj; // To get $obj used into list when dol_eval() is used for computed fields and $obj is not yet $object
6966
6967
    $isObBufferActive = false;  // When true, the ObBuffer must be cleaned in the exception handler
6968
    if (!in_array($onlysimplestring, array('0', '1', '2'))) {
6969
        return "Bad call of dol_eval. Parameter onlysimplestring must be '0' (deprecated), '1' or '2'";
6970
    }
6971
6972
    try {
6973
        // Test on dangerous char (used for RCE), we allow only characters to make PHP variable testing
6974
        if ($onlysimplestring == '1' || $onlysimplestring == '2') {
6975
            // We must accept with 1: '1 && getDolGlobalInt("doesnotexist1") && getDolGlobalString("MAIN_FEATURES_LEVEL")'
6976
            // We must accept with 1: '$user->hasRight("cabinetmed", "read") && !$object->canvas=="patient@cabinetmed"'
6977
            // We must accept with 2: (($reloadedobj = new Task($db)) && ($reloadedobj->fetchNoCompute($object->id) > 0) && ($secondloadedobj = new Project($db)) && ($secondloadedobj->fetchNoCompute($reloadedobj->fk_project) > 0)) ? $secondloadedobj->ref : "Parent project not found"
6978
            $specialcharsallowed = '^$_+-.*>&|=!?():"\',/@';
6979
            if ($onlysimplestring == '2') {
6980
                $specialcharsallowed .= '[]';
6981
            }
6982
            if (getDolGlobalString('MAIN_ALLOW_UNSECURED_SPECIAL_CHARS_IN_DOL_EVAL')) {
6983
                $specialcharsallowed .= getDolGlobalString('MAIN_ALLOW_UNSECURED_SPECIAL_CHARS_IN_DOL_EVAL');
6984
            }
6985
            if (preg_match('/[^a-z0-9\s' . preg_quote($specialcharsallowed, '/') . ']/i', $s)) {
6986
                if ($returnvalue) {
6987
                    return 'Bad string syntax to evaluate (found chars that are not chars for a simple clean eval string): ' . $s;
6988
                } else {
6989
                    dol_syslog('Bad string syntax to evaluate (found chars that are not chars for a simple clean eval string): ' . $s);
6990
                    return '';
6991
                }
6992
            }
6993
            $savescheck = '';
6994
            $scheck = $s;
6995
            while ($scheck && $savescheck != $scheck) {
6996
                $savescheck = $scheck;
6997
                $scheck = preg_replace('/->[a-zA-Z0-9_]+\(/', '->__METHOD__', $scheck); // accept parenthesis in '...->method(...'
6998
                $scheck = preg_replace('/^\(/', '__PARENTHESIS__ ', $scheck);   // accept parenthesis in '(...'. Must replace with __PARENTHESIS__ with a space after to allow following substitutions
6999
                $scheck = preg_replace('/\s\(/', '__PARENTHESIS__ ', $scheck);  // accept parenthesis in '... ('. Must replace with __PARENTHESIS__ with a space after to allow following substitutions
7000
                $scheck = preg_replace('/^!?[a-zA-Z0-9_]+\(/', '__FUNCTION__', $scheck); // accept parenthesis in 'function(' and '!function('
7001
                $scheck = preg_replace('/\s!?[a-zA-Z0-9_]+\(/', '__FUNCTION__', $scheck); // accept parenthesis in '... function(' and '... !function('
7002
                $scheck = preg_replace('/(\^|\')\(/', '__REGEXSTART__', $scheck);   // To allow preg_match('/^(aaa|bbb)/'...  or  isStringVarMatching('leftmenu', '(aaa|bbb)')
7003
            }
7004
            //print 'scheck='.$scheck." : ".strpos($scheck, '(')."<br>\n";
7005
            if (strpos($scheck, '(') !== false) {
7006
                if ($returnvalue) {
7007
                    return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found call of a function or method without using the direct name of the function): ' . $s;
7008
                } else {
7009
                    dol_syslog('Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found call of a function or method without using the direct name of the function): ' . $s);
7010
                    return '';
7011
                }
7012
            }
7013
            // TODO
7014
            // We can exclude $ char that are not:
7015
            // $db, $langs, $leftmenu, $topmenu, $user, $langs, $objectoffield, $object...,
7016
        }
7017
        if (is_array($s) || $s === 'Array') {
7018
            return 'Bad string syntax to evaluate (value is Array) ' . var_export($s, true);
7019
        }
7020
        if (strpos($s, '::') !== false) {
7021
            if ($returnvalue) {
7022
                return 'Bad string syntax to evaluate (double : char is forbidden): ' . $s;
7023
            } else {
7024
                dol_syslog('Bad string syntax to evaluate (double : char is forbidden): ' . $s);
7025
                return '';
7026
            }
7027
        }
7028
        if (strpos($s, '`') !== false) {
7029
            if ($returnvalue) {
7030
                return 'Bad string syntax to evaluate (backtick char is forbidden): ' . $s;
7031
            } else {
7032
                dol_syslog('Bad string syntax to evaluate (backtick char is forbidden): ' . $s);
7033
                return '';
7034
            }
7035
        }
7036
        if (preg_match('/[^0-9]+\.[^0-9]+/', $s)) { // We refuse . if not between 2 numbers
7037
            if ($returnvalue) {
7038
                return 'Bad string syntax to evaluate (dot char is forbidden): ' . $s;
7039
            } else {
7040
                dol_syslog('Bad string syntax to evaluate (dot char is forbidden): ' . $s);
7041
                return '';
7042
            }
7043
        }
7044
7045
        // We block use of php exec or php file functions
7046
        $forbiddenphpstrings = array('$$');
7047
        $forbiddenphpstrings = array_merge($forbiddenphpstrings, array('_ENV', '_SESSION', '_COOKIE', '_GET', '_POST', '_REQUEST', 'ReflectionFunction'));
7048
7049
        $forbiddenphpfunctions = array("exec", "passthru", "shell_exec", "system", "proc_open", "popen");
7050
        $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_eval", "executeCLI", "verifCond")); // native dolibarr functions
7051
        $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("base64_decode", "rawurldecode", "urldecode", "str_rot13", "hex2bin")); // decode string functions used to obfuscated function name
7052
        $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("fopen", "file_put_contents", "fputs", "fputscsv", "fwrite", "fpassthru", "require", "include", "mkdir", "rmdir", "symlink", "touch", "unlink", "umask"));
7053
        $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("get_defined_functions", "get_defined_vars", "get_defined_constants", "get_declared_classes"));
7054
        $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("function", "call_user_func"));
7055
        $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include", "require_once", "include_once"));
7056
        $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("eval", "create_function", "assert", "mb_ereg_replace")); // function with eval capabilities
7057
        $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_compress_dir", "dol_decode", "dol_delete_file", "dol_delete_dir", "dol_delete_dir_recursive", "dol_copy", "archiveOrBackupFile")); // more dolibarr functions
7058
7059
        $forbiddenphpmethods = array('invoke', 'invokeArgs');   // Method of ReflectionFunction to execute a function
7060
7061
        $forbiddenphpregex = 'global\s+\$|\b(' . implode('|', $forbiddenphpfunctions) . ')\b';
7062
7063
        $forbiddenphpmethodsregex = '->(' . implode('|', $forbiddenphpmethods) . ')';
7064
7065
        do {
7066
            $oldstringtoclean = $s;
7067
            $s = str_ireplace($forbiddenphpstrings, '__forbiddenstring__', $s);
7068
            $s = preg_replace('/' . $forbiddenphpregex . '/i', '__forbiddenstring__', $s);
7069
            $s = preg_replace('/' . $forbiddenphpmethodsregex . '/i', '__forbiddenstring__', $s);
7070
            //$s = preg_replace('/\$[a-zA-Z0-9_\->\$]+\(/i', '', $s);   // Remove $function( call and $mycall->mymethod(
7071
        } while ($oldstringtoclean != $s);
7072
7073
        if (strpos($s, '__forbiddenstring__') !== false) {
7074
            dol_syslog('Bad string syntax to evaluate: ' . $s, LOG_WARNING);
7075
            if ($returnvalue) {
7076
                return 'Bad string syntax to evaluate: ' . $s;
7077
            } else {
7078
                dol_syslog('Bad string syntax to evaluate: ' . $s);
7079
                return '';
7080
            }
7081
        }
7082
7083
        //print $s."<br>\n";
7084
        if ($returnvalue) {
7085
            if ($hideerrors) {
7086
                ob_start(); // An evaluation has no reason to output data
7087
                $isObBufferActive = true;
7088
                $tmps = @eval('return ' . $s . ';');
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
7089
                $tmpo = ob_get_clean();
7090
                $isObBufferActive = false;
7091
                if ($tmpo) {
7092
                    print 'Bad string syntax to evaluate. Some data were output when it should not when evaluating: ' . $s;
7093
                }
7094
                return $tmps;
7095
            } else {
7096
                ob_start(); // An evaluation has no reason to output data
7097
                $isObBufferActive = true;
7098
                $tmps = eval('return ' . $s . ';');
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
7099
                $tmpo = ob_get_clean();
7100
                $isObBufferActive = false;
7101
                if ($tmpo) {
7102
                    print 'Bad string syntax to evaluate. Some data were output when it should not when evaluating: ' . $s;
7103
                }
7104
                return $tmps;
7105
            }
7106
        } else {
7107
            dol_syslog('Do not use anymore dol_eval with param returnvalue=0', LOG_WARNING);
7108
            if ($hideerrors) {
7109
                @eval($s);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
7110
            } else {
7111
                eval($s);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
7112
            }
7113
            return '';
7114
        }
7115
    } catch (Error $e) {
7116
        if ($isObBufferActive) {
7117
            // Clean up buffer which was left behind due to exception.
7118
            $tmpo = ob_get_clean();
7119
            $isObBufferActive = false;
7120
        }
7121
        $error = 'dol_eval try/catch error : ';
7122
        $error .= $e->getMessage();
7123
        dol_syslog($error, LOG_WARNING);
7124
        if ($returnvalue) {
7125
            return 'Exception during evaluation: ' . $s;
7126
        } else {
7127
            return '';
7128
        }
7129
    }
7130
}
7131
7132
/**
7133
 * Return if var element is ok
7134
 *
7135
 * @param string $element Variable to check
7136
 * @return  boolean                 Return true of variable is not empty
7137
 * @see getElementProperties()
7138
 */
7139
function dol_validElement($element)
7140
{
7141
    return (trim($element) != '');
7142
}
7143
7144
/**
7145
 *  Return img flag of country for a language code or country code.
7146
 *
7147
 * @param string $codelang Language code ('en_IN', 'fr_CA', ...) or ISO Country code on 2 characters in uppercase ('IN', 'FR')
7148
 * @param string $moreatt Add more attribute on img tag (For example 'style="float: right"' or 'class="saturatemedium"')
7149
 * @param int<0,1> $notitlealt No title alt
7150
 * @return string              HTML img string with flag.
7151
 */
7152
function picto_from_langcode($codelang, $moreatt = '', $notitlealt = 0)
7153
{
7154
    if (empty($codelang)) {
7155
        return '';
7156
    }
7157
7158
    if ($codelang == 'auto') {
7159
        return '<span class="fa fa-language"></span>';
7160
    }
7161
7162
    $langtocountryflag = array(
7163
        'ar_AR' => '',
7164
        'ca_ES' => 'catalonia',
7165
        'da_DA' => 'dk',
7166
        'fr_CA' => 'mq',
7167
        'sv_SV' => 'se',
7168
        'sw_SW' => 'unknown',
7169
        'AQ' => 'unknown',
7170
        'CW' => 'unknown',
7171
        'IM' => 'unknown',
7172
        'JE' => 'unknown',
7173
        'MF' => 'unknown',
7174
        'BL' => 'unknown',
7175
        'SX' => 'unknown'
7176
    );
7177
7178
    if (isset($langtocountryflag[$codelang])) {
7179
        $flagImage = $langtocountryflag[$codelang];
7180
    } else {
7181
        $tmparray = explode('_', $codelang);
7182
        $flagImage = empty($tmparray[1]) ? $tmparray[0] : $tmparray[1];
7183
    }
7184
7185
    $morecss = '';
7186
    $reg = array();
7187
    if (preg_match('/class="([^"]+)"/', $moreatt, $reg)) {
7188
        $morecss = $reg[1];
7189
        $moreatt = "";
7190
    }
7191
7192
    // return img_picto_common($codelang, 'flags/'.strtolower($flagImage).'.png', $moreatt, 0, $notitlealt);
7193
    return '<span class="flag-sprite ' . strtolower($flagImage) . ($morecss ? ' ' . $morecss : '') . '"' . ($moreatt ? ' ' . $moreatt : '') . (!$notitlealt ? ' title="' . $codelang . '"' : '') . '></span>';
7194
}
7195
7196
/**
7197
 * Return default language from country code.
7198
 * Return null if not found.
7199
 *
7200
 * @param string $countrycode Country code like 'US', 'FR', 'CA', 'ES', 'IN', 'MX', ...
7201
 * @return  ?string                 Value of locale like 'en_US', 'fr_FR', ... or null if not found
7202
 */
7203
function getLanguageCodeFromCountryCode($countrycode)
7204
{
7205
    global $mysoc;
7206
7207
    if (empty($countrycode)) {
7208
        return null;
7209
    }
7210
7211
    if (strtoupper($countrycode) == 'MQ') {
7212
        return 'fr_CA';
7213
    }
7214
    if (strtoupper($countrycode) == 'SE') {
7215
        return 'sv_SE'; // se_SE is Sami/Sweden, and we want in priority sv_SE for SE country
7216
    }
7217
    if (strtoupper($countrycode) == 'CH') {
7218
        if ($mysoc->country_code == 'FR') {
7219
            return 'fr_CH';
7220
        }
7221
        if ($mysoc->country_code == 'DE') {
7222
            return 'de_CH';
7223
        }
7224
        if ($mysoc->country_code == 'IT') {
7225
            return 'it_CH';
7226
        }
7227
    }
7228
7229
    // Locale list taken from:
7230
    // http://stackoverflow.com/questions/3191664/
7231
    // list-of-all-locales-and-their-short-codes
7232
    $locales = array(
7233
        'af-ZA',
7234
        'am-ET',
7235
        'ar-AE',
7236
        'ar-BH',
7237
        'ar-DZ',
7238
        'ar-EG',
7239
        'ar-IQ',
7240
        'ar-JO',
7241
        'ar-KW',
7242
        'ar-LB',
7243
        'ar-LY',
7244
        'ar-MA',
7245
        'ar-OM',
7246
        'ar-QA',
7247
        'ar-SA',
7248
        'ar-SY',
7249
        'ar-TN',
7250
        'ar-YE',
7251
        //'as-IN',      // Moved after en-IN
7252
        'ba-RU',
7253
        'be-BY',
7254
        'bg-BG',
7255
        'bn-BD',
7256
        //'bn-IN',      // Moved after en-IN
7257
        'bo-CN',
7258
        'br-FR',
7259
        'ca-ES',
7260
        'co-FR',
7261
        'cs-CZ',
7262
        'cy-GB',
7263
        'da-DK',
7264
        'de-AT',
7265
        'de-CH',
7266
        'de-DE',
7267
        'de-LI',
7268
        'de-LU',
7269
        'dv-MV',
7270
        'el-GR',
7271
        'en-AU',
7272
        'en-BZ',
7273
        'en-CA',
7274
        'en-GB',
7275
        'en-IE',
7276
        'en-IN',
7277
        'as-IN',    // as-IN must be after en-IN (en in priority if country is IN)
7278
        'bn-IN',    // bn-IN must be after en-IN (en in priority if country is IN)
7279
        'en-JM',
7280
        'en-MY',
7281
        'en-NZ',
7282
        'en-PH',
7283
        'en-SG',
7284
        'en-TT',
7285
        'en-US',
7286
        'en-ZA',
7287
        'en-ZW',
7288
        'es-AR',
7289
        'es-BO',
7290
        'es-CL',
7291
        'es-CO',
7292
        'es-CR',
7293
        'es-DO',
7294
        'es-EC',
7295
        'es-ES',
7296
        'es-GT',
7297
        'es-HN',
7298
        'es-MX',
7299
        'es-NI',
7300
        'es-PA',
7301
        'es-PE',
7302
        'es-PR',
7303
        'es-PY',
7304
        'es-SV',
7305
        'es-US',
7306
        'es-UY',
7307
        'es-VE',
7308
        'et-EE',
7309
        'eu-ES',
7310
        'fa-IR',
7311
        'fi-FI',
7312
        'fo-FO',
7313
        'fr-BE',
7314
        'fr-CA',
7315
        'fr-CH',
7316
        'fr-FR',
7317
        'fr-LU',
7318
        'fr-MC',
7319
        'fy-NL',
7320
        'ga-IE',
7321
        'gd-GB',
7322
        'gl-ES',
7323
        'gu-IN',
7324
        'he-IL',
7325
        'hi-IN',
7326
        'hr-BA',
7327
        'hr-HR',
7328
        'hu-HU',
7329
        'hy-AM',
7330
        'id-ID',
7331
        'ig-NG',
7332
        'ii-CN',
7333
        'is-IS',
7334
        'it-CH',
7335
        'it-IT',
7336
        'ja-JP',
7337
        'ka-GE',
7338
        'kk-KZ',
7339
        'kl-GL',
7340
        'km-KH',
7341
        'kn-IN',
7342
        'ko-KR',
7343
        'ky-KG',
7344
        'lb-LU',
7345
        'lo-LA',
7346
        'lt-LT',
7347
        'lv-LV',
7348
        'mi-NZ',
7349
        'mk-MK',
7350
        'ml-IN',
7351
        'mn-MN',
7352
        'mr-IN',
7353
        'ms-BN',
7354
        'ms-MY',
7355
        'mt-MT',
7356
        'nb-NO',
7357
        'ne-NP',
7358
        'nl-BE',
7359
        'nl-NL',
7360
        'nn-NO',
7361
        'oc-FR',
7362
        'or-IN',
7363
        'pa-IN',
7364
        'pl-PL',
7365
        'ps-AF',
7366
        'pt-BR',
7367
        'pt-PT',
7368
        'rm-CH',
7369
        'ro-MD',
7370
        'ro-RO',
7371
        'ru-RU',
7372
        'rw-RW',
7373
        'sa-IN',
7374
        'se-FI',
7375
        'se-NO',
7376
        'se-SE',
7377
        'si-LK',
7378
        'sk-SK',
7379
        'sl-SI',
7380
        'sq-AL',
7381
        'sv-FI',
7382
        'sv-SE',
7383
        'sw-KE',
7384
        'ta-IN',
7385
        'te-IN',
7386
        'th-TH',
7387
        'tk-TM',
7388
        'tn-ZA',
7389
        'tr-TR',
7390
        'tt-RU',
7391
        'ug-CN',
7392
        'uk-UA',
7393
        'ur-PK',
7394
        'vi-VN',
7395
        'wo-SN',
7396
        'xh-ZA',
7397
        'yo-NG',
7398
        'zh-CN',
7399
        'zh-HK',
7400
        'zh-MO',
7401
        'zh-SG',
7402
        'zh-TW',
7403
        'zu-ZA',
7404
    );
7405
7406
    $buildprimarykeytotest = strtolower($countrycode) . '-' . strtoupper($countrycode);
7407
    if (in_array($buildprimarykeytotest, $locales)) {
7408
        return strtolower($countrycode) . '_' . strtoupper($countrycode);
7409
    }
7410
7411
    if (function_exists('locale_get_primary_language') && function_exists('locale_get_region')) {    // Need extension php-intl
7412
        foreach ($locales as $locale) {
7413
            $locale_language = locale_get_primary_language($locale);
7414
            $locale_region = locale_get_region($locale);
7415
            if (strtoupper($countrycode) == $locale_region) {
7416
                //var_dump($locale.' - '.$locale_language.' - '.$locale_region);
7417
                return strtolower($locale_language) . '_' . strtoupper($locale_region);
7418
            }
7419
        }
7420
    } else {
7421
        dol_syslog("Warning Extension php-intl is not available", LOG_WARNING);
7422
    }
7423
7424
    return null;
7425
}
7426
7427
/**
7428
 * Split a string with 2 keys into key array.
7429
 * For example: "A=1;B=2;C=2" is exploded into array('A'=>'1','B'=>'2','C'=>'3')
7430
 *
7431
 * @param   ?string $string String to explode
7432
 * @param string $delimiter Delimiter between each couple of data. Example: ';' or '[\n;]+' or '(\n\r|\r|\n|;)'
7433
 * @param string $kv Delimiter between key and value
7434
 * @return  array<string,string>    Array of data exploded
7435
 */
7436
function dolExplodeIntoArray($string, $delimiter = ';', $kv = '=')
7437
{
7438
    if (is_null($string)) {
7439
        return array();
7440
    }
7441
7442
    if (preg_match('/^\[.*\]$/sm', $delimiter) || preg_match('/^\(.*\)$/sm', $delimiter)) {
7443
        // This is a regex string
7444
        $newdelimiter = $delimiter;
7445
    } else {
7446
        // This is a simple string
7447
        // @phan-suppress-next-line PhanPluginSuspiciousParamPositionInternal
7448
        $newdelimiter = preg_quote($delimiter, '/');
7449
    }
7450
7451
    if ($a = preg_split('/' . $newdelimiter . '/', $string)) {
7452
        $ka = array();
7453
        foreach ($a as $s) { // each part
7454
            if ($s) {
7455
                if ($pos = strpos($s, $kv)) { // key/value delimiter
7456
                    $ka[trim(substr($s, 0, $pos))] = trim(substr($s, $pos + strlen($kv)));
7457
                } else { // key delimiter not found
7458
                    $ka[] = trim($s);
7459
                }
7460
            }
7461
        }
7462
        return $ka;
7463
    }
7464
7465
    return array();
7466
}
7467
7468
/**
7469
 * Return getmypid() or random PID when function is disabled
7470
 * Some web hosts disable this php function for security reasons
7471
 * and sometimes we can't redeclare function.
7472
 *
7473
 * @return  int
7474
 */
7475
function dol_getmypid()
7476
{
7477
    if (!function_exists('getmypid')) {
7478
        return mt_rand(99900000, 99965535);
7479
    } else {
7480
        return getmypid();  // May be a number on 64 bits (depending on OS)
7481
    }
7482
}
7483
7484
7485
/**
7486
 * Generate natural SQL search string for a criteria (this criteria can be tested on one or several fields)
7487
 *
7488
 * @param string|string[] $fields String or array of strings, filled with the name of all fields in the SQL query we must check (combined with a OR). Example: array("p.field1","p.field2")
7489
 * @param string $value The value to look for.
7490
 *                                      If param $mode is 0, can contains several keywords separated with a space or |
7491
 *                                      like "keyword1 keyword2" = We want record field like keyword1 AND field like keyword2
7492
 *                                      or like "keyword1|keyword2" = We want record field like keyword1 OR field like keyword2
7493
 *                                      If param $mode is 1, can contains an operator <, > or = like "<10" or ">=100.5 < -1000"
7494
 *                                      If param $mode is 2 or -2, can contains a list of int id separated by comma like "1,3,4"
7495
 *                                      If param $mode is 3 or -3, can contains a list of string separated by comma like "a,b,c".
7496
 * @param integer $mode 0=value is list of keyword strings,
7497
 *                                      1=value is a numeric test (Example ">5.5 <10"),
7498
 *                                      2=value is a list of ID separated with comma (Example '1,3,4'), -2 is for exclude list,
7499
 *                                      3=value is list of string separated with comma (Example 'text 1,text 2'), -3 if for exclude list,
7500
 *                                      4=value is a list of ID separated with comma (Example '2,7') to be used to search into a multiselect string '1,2,3,4'
7501
 * @param integer $nofirstand 1=Do not output the first 'AND'
7502
 * @return  string          $res        The statement to append to the SQL query
7503
 * @see dolSqlDateFilter()
7504
 * @see forgeSQLFromUniversalSearchCriteria()
7505
 */
7506
function natural_search($fields, $value, $mode = 0, $nofirstand = 0)
7507
{
7508
    global $db, $langs;
7509
7510
    $value = trim($value);
7511
7512
    if ($mode == 0) {
7513
        $value = preg_replace('/\*/', '%', $value); // Replace * with %
7514
    }
7515
    if ($mode == 1) {
7516
        $value = preg_replace('/([!<>=]+)\s+([0-9' . preg_quote($langs->trans("DecimalSeparator"), '/') . '\-])/', '\1\2', $value); // Clean string '< 10' into '<10' so we can then explode on space to get all tests to do
7517
    }
7518
7519
    $value = preg_replace('/\s*\|\s*/', '|', $value);
7520
7521
    $crits = explode(' ', $value);
7522
    $res = '';
7523
    if (!is_array($fields)) {
7524
        $fields = array($fields);
7525
    }
7526
7527
    $i1 = 0;    // count the nb of and criteria added (all fields / criteria)
7528
    foreach ($crits as $crit) {     // Loop on each AND criteria
7529
        $crit = trim($crit);
7530
        $i2 = 0;    // count the nb of valid criteria added for this this first criteria
7531
        $newres = '';
7532
        foreach ($fields as $field) {
7533
            if ($mode == 1) {
7534
                $tmpcrits = explode('|', $crit);
7535
                $i3 = 0;    // count the nb of valid criteria added for this current field
7536
                foreach ($tmpcrits as $tmpcrit) {
7537
                    if ($tmpcrit !== '0' && empty($tmpcrit)) {
7538
                        continue;
7539
                    }
7540
                    $tmpcrit = trim($tmpcrit);
7541
7542
                    $newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : '');
7543
7544
                    $operator = '=';
7545
                    $newcrit = preg_replace('/([!<>=]+)/', '', $tmpcrit);
7546
7547
                    $reg = array();
7548
                    preg_match('/([!<>=]+)/', $tmpcrit, $reg);
7549
                    if (!empty($reg[1])) {
7550
                        $operator = $reg[1];
7551
                    }
7552
                    if ($newcrit != '') {
7553
                        $numnewcrit = price2num($newcrit);
7554
                        if (is_numeric($numnewcrit)) {
7555
                            $newres .= $field . ' ' . $operator . ' ' . ((float)$numnewcrit); // should be a numeric
7556
                        } else {
7557
                            $newres .= '1 = 2'; // force false, we received a corrupted data
7558
                        }
7559
                        $i3++; // a criteria was added to string
7560
                    }
7561
                }
7562
                $i2++; // a criteria for 1 more field was added to string
7563
            } elseif ($mode == 2 || $mode == -2) {
7564
                $crit = preg_replace('/[^0-9,]/', '', $crit); // ID are always integer
7565
                $newres .= ($i2 > 0 ? ' OR ' : '') . $field . " " . ($mode == -2 ? 'NOT ' : '');
7566
                $newres .= $crit ? "IN (" . $db->sanitize($db->escape($crit)) . ")" : "IN (0)";
7567
                if ($mode == -2) {
7568
                    $newres .= ' OR ' . $field . ' IS NULL';
7569
                }
7570
                $i2++; // a criteria for 1 more field was added to string
7571
            } elseif ($mode == 3 || $mode == -3) {
7572
                $tmparray = explode(',', $crit);
7573
                if (count($tmparray)) {
7574
                    $listofcodes = '';
7575
                    foreach ($tmparray as $val) {
7576
                        $val = trim($val);
7577
                        if ($val) {
7578
                            $listofcodes .= ($listofcodes ? ',' : '');
7579
                            $listofcodes .= "'" . $db->escape($val) . "'";
7580
                        }
7581
                    }
7582
                    $newres .= ($i2 > 0 ? ' OR ' : '') . $field . " " . ($mode == -3 ? 'NOT ' : '') . "IN (" . $db->sanitize($listofcodes, 1) . ")";
7583
                    $i2++; // a criteria for 1 more field was added to string
7584
                }
7585
                if ($mode == -3) {
7586
                    $newres .= ' OR ' . $field . ' IS NULL';
7587
                }
7588
            } elseif ($mode == 4) {
7589
                $tmparray = explode(',', $crit);
7590
                if (count($tmparray)) {
7591
                    $listofcodes = '';
7592
                    foreach ($tmparray as $val) {
7593
                        $val = trim($val);
7594
                        if ($val) {
7595
                            $newres .= ($i2 > 0 ? " OR (" : "(") . $field . " LIKE '" . $db->escape($val) . ",%'";
7596
                            $newres .= ' OR ' . $field . " = '" . $db->escape($val) . "'";
7597
                            $newres .= ' OR ' . $field . " LIKE '%," . $db->escape($val) . "'";
7598
                            $newres .= ' OR ' . $field . " LIKE '%," . $db->escape($val) . ",%'";
7599
                            $newres .= ')';
7600
                            $i2++; // a criteria for 1 more field was added to string (we can add several criteria for the same field as it is a multiselect search criteria)
7601
                        }
7602
                    }
7603
                }
7604
            } else { // $mode=0
7605
                $tmpcrits = explode('|', $crit);
7606
                $i3 = 0;    // count the nb of valid criteria added for the current couple criteria/field
7607
                foreach ($tmpcrits as $tmpcrit) {   // loop on each OR criteria
7608
                    if ($tmpcrit !== '0' && empty($tmpcrit)) {
7609
                        continue;
7610
                    }
7611
                    $tmpcrit = trim($tmpcrit);
7612
7613
                    if ($tmpcrit == '^$' || strpos($crit, '!') === 0) { // If we search empty, we must combined different OR fields with AND
7614
                        $newres .= (($i2 > 0 || $i3 > 0) ? ' AND ' : '');
7615
                    } else {
7616
                        $newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : '');
7617
                    }
7618
7619
                    if (preg_match('/\.(id|rowid)$/', $field)) {    // Special case for rowid that is sometimes a ref so used as a search field
7620
                        $newres .= $field . " = " . (is_numeric($tmpcrit) ? ((float)$tmpcrit) : '0');
7621
                    } else {
7622
                        $tmpcrit2 = $tmpcrit;
7623
                        $tmpbefore = '%';
7624
                        $tmpafter = '%';
7625
                        $tmps = '';
7626
7627
                        if (preg_match('/^!/', $tmpcrit)) {
7628
                            $tmps .= $field . " NOT LIKE "; // ! as exclude character
7629
                            $tmpcrit2 = preg_replace('/^!/', '', $tmpcrit2);
7630
                        } else {
7631
                            $tmps .= $field . " LIKE ";
7632
                        }
7633
                        $tmps .= "'";
7634
7635
                        if (preg_match('/^[\^\$]/', $tmpcrit)) {
7636
                            $tmpbefore = '';
7637
                            $tmpcrit2 = preg_replace('/^[\^\$]/', '', $tmpcrit2);
7638
                        }
7639
                        if (preg_match('/[\^\$]$/', $tmpcrit)) {
7640
                            $tmpafter = '';
7641
                            $tmpcrit2 = preg_replace('/[\^\$]$/', '', $tmpcrit2);
7642
                        }
7643
7644
                        if ($tmpcrit2 == '' || preg_match('/^!/', $tmpcrit)) {
7645
                            $tmps = "(" . $tmps;
7646
                        }
7647
                        $newres .= $tmps;
7648
                        $newres .= $tmpbefore;
7649
                        $newres .= $db->escape($tmpcrit2);
7650
                        $newres .= $tmpafter;
7651
                        $newres .= "'";
7652
                        if ($tmpcrit2 == '' || preg_match('/^!/', $tmpcrit)) {
7653
                            $newres .= " OR " . $field . " IS NULL)";
7654
                        }
7655
                    }
7656
7657
                    $i3++;
7658
                }
7659
7660
                $i2++; // a criteria for 1 more field was added to string
7661
            }
7662
        }
7663
7664
        if ($newres) {
7665
            $res = $res . ($res ? ' AND ' : '') . ($i2 > 1 ? '(' : '') . $newres . ($i2 > 1 ? ')' : '');
7666
        }
7667
        $i1++;
7668
    }
7669
    $res = ($nofirstand ? "" : " AND ") . "(" . $res . ")";
7670
7671
    return $res;
7672
}
7673
7674
/**
7675
 * Return the filename of file to get the thumbs
7676
 *
7677
 * @param string $file Original filename (full or relative path)
7678
 * @param string $extName Extension to differentiate thumb file name ('', '_small', '_mini')
7679
 * @param string $extImgTarget Force image extension for thumbs. Use '' to keep same extension than original image (default).
7680
 * @return  string                  New file name (full or relative path, including the thumbs/). May be the original path if no thumb can exists.
7681
 */
7682
function getImageFileNameForSize($file, $extName, $extImgTarget = '')
7683
{
7684
    $dirName = dirname($file);
7685
    if ($dirName == '.') {
7686
        $dirName = '';
7687
    }
7688
7689
    $fileName = preg_replace('/(\.gif|\.jpeg|\.jpg|\.png|\.bmp|\.webp)$/i', '', $file); // We remove extension, whatever is its case
7690
    $fileName = basename($fileName);
7691
7692
    if (empty($extImgTarget)) {
7693
        $extImgTarget = (preg_match('/\.jpg$/i', $file) ? '.jpg' : '');
7694
    }
7695
    if (empty($extImgTarget)) {
7696
        $extImgTarget = (preg_match('/\.jpeg$/i', $file) ? '.jpeg' : '');
7697
    }
7698
    if (empty($extImgTarget)) {
7699
        $extImgTarget = (preg_match('/\.gif$/i', $file) ? '.gif' : '');
7700
    }
7701
    if (empty($extImgTarget)) {
7702
        $extImgTarget = (preg_match('/\.png$/i', $file) ? '.png' : '');
7703
    }
7704
    if (empty($extImgTarget)) {
7705
        $extImgTarget = (preg_match('/\.bmp$/i', $file) ? '.bmp' : '');
7706
    }
7707
    if (empty($extImgTarget)) {
7708
        $extImgTarget = (preg_match('/\.webp$/i', $file) ? '.webp' : '');
7709
    }
7710
7711
    if (!$extImgTarget) {
7712
        return $file;
7713
    }
7714
7715
    $subdir = '';
7716
    if ($extName) {
7717
        $subdir = 'thumbs/';
7718
    }
7719
7720
    return ($dirName ? $dirName . '/' : '') . $subdir . $fileName . $extName . $extImgTarget; // New filename for thumb
7721
}
7722
7723
7724
/**
7725
 * Return URL we can use for advanced preview links
7726
 *
7727
 * @param string $modulepart propal, facture, facture_fourn, ...
7728
 * @param string $relativepath Relative path of docs.
7729
 * @param int<0,1> $alldata Return array with all components (1 is recommended, then use a simple a href link with the class, target and mime attribute added. 'documentpreview' css class is handled by jquery code into main.inc.php)
7730
 * @param string $param More param on http links
7731
 * @return  string|array{}|array{target:string,css:string,url:string,mime:string}   Output string with href link or array with all components of link
7732
 */
7733
function getAdvancedPreviewUrl($modulepart, $relativepath, $alldata = 0, $param = '')
7734
{
7735
    global $conf, $langs;
7736
7737
    if (empty($conf->use_javascript_ajax)) {
7738
        return '';
7739
    }
7740
7741
    $isAllowedForPreview = dolIsAllowedForPreview($relativepath);
7742
7743
    if ($alldata == 1) {
7744
        if ($isAllowedForPreview) {
7745
            return array('target' => '_blank', 'css' => 'documentpreview', 'url' => constant('BASE_URL') . '/document.php?modulepart=' . urlencode($modulepart) . '&attachment=0&file=' . urlencode($relativepath) . ($param ? '&' . $param : ''), 'mime' => dol_mimetype($relativepath));
7746
        } else {
7747
            return array();
7748
        }
7749
    }
7750
7751
    // old behavior, return a string
7752
    if ($isAllowedForPreview) {
7753
        $tmpurl = constant('BASE_URL') . '/document.php?modulepart=' . urlencode($modulepart) . '&attachment=0&file=' . urlencode($relativepath) . ($param ? '&' . $param : '');
7754
        $title = $langs->transnoentities("Preview");
7755
        //$title = '%27-alert(document.domain)-%27';
7756
        //$tmpurl = 'file='.urlencode("'-alert(document.domain)-'_small.jpg");
7757
7758
        // We need to urlencode the parameter after the dol_escape_js($tmpurl) because  $tmpurl may contain n url with param file=abc%27def if file has a ' inside.
7759
        // and when we click on href with this javascript string, a urlcode is done by browser, converted the %27 of file param
7760
        return 'javascript:document_preview(\'' . urlencode(dol_escape_js($tmpurl)) . '\', \'' . urlencode(dol_mimetype($relativepath)) . '\', \'' . urlencode(dol_escape_js($title)) . '\')';
7761
    } else {
7762
        return '';
7763
    }
7764
}
7765
7766
7767
/**
7768
 * Make content of an input box selected when we click into input field.
7769
 *
7770
 * @param string $htmlname Id of html object ('#idvalue' or '.classvalue')
7771
 * @param string $addlink Add a 'link to' after
7772
 * @param string $textonlink Text to show on link or 'image'
7773
 * @return string
7774
 */
7775
function ajax_autoselect($htmlname, $addlink = '', $textonlink = 'Link')
7776
{
7777
    global $langs;
7778
    $out = '<script nonce="' . getNonce() . '">
7779
               jQuery(document).ready(function () {
7780
				    jQuery("' . ((strpos($htmlname, '.') === 0 ? '' : '#') . $htmlname) . '").click(function() { jQuery(this).select(); } );
7781
				});
7782
		    </script>';
7783
    if ($addlink) {
7784
        if ($textonlink === 'image') {
7785
            $out .= ' <a href="' . $addlink . '" target="_blank" rel="noopener noreferrer">' . img_picto('', 'globe') . '</a>';
7786
        } else {
7787
            $out .= ' <a href="' . $addlink . '" target="_blank" rel="noopener noreferrer">' . $langs->trans("Link") . '</a>';
7788
        }
7789
    }
7790
    return $out;
7791
}
7792
7793
/**
7794
 *  Return if a file is qualified for preview
7795
 *
7796
 * @param string $file Filename we looking for information
7797
 * @return int<0,1>            1 If allowed, 0 otherwise
7798
 * @see    dol_mimetype(), image_format_supported() from images.lib.php
7799
 */
7800
function dolIsAllowedForPreview($file)
7801
{
7802
    // Check .noexe extension in filename
7803
    if (preg_match('/\.noexe$/i', $file)) {
7804
        return 0;
7805
    }
7806
7807
    // Check mime types
7808
    $mime_preview = array('bmp', 'jpeg', 'png', 'gif', 'tiff', 'pdf', 'plain', 'css', 'webp');
7809
    if (getDolGlobalString('MAIN_ALLOW_SVG_FILES_AS_IMAGES')) {
7810
        $mime_preview[] = 'svg+xml';
7811
    }
7812
    //$mime_preview[]='vnd.oasis.opendocument.presentation';
7813
    //$mime_preview[]='archive';
7814
    $num_mime = array_search(dol_mimetype($file, '', 1), $mime_preview);
7815
    if ($num_mime !== false) {
7816
        return 1;
7817
    }
7818
7819
    // By default, not allowed for preview
7820
    return 0;
7821
}
7822
7823
7824
/**
7825
 *  Return MIME type of a file from its name with extension.
7826
 *
7827
 * @param string $file Filename we looking for MIME type
7828
 * @param string $default Default mime type if extension not found in known list
7829
 * @param int<0,4> $mode 0=Return full mime, 1=otherwise short mime string, 2=image for mime type, 3=source language, 4=css of font fa
7830
 * @return string              Return a mime type family (text/xxx, application/xxx, image/xxx, audio, video, archive)
7831
 * @see    dolIsAllowedForPreview(), image_format_supported() from images.lib.php
7832
 */
7833
function dol_mimetype($file, $default = 'application/octet-stream', $mode = 0)
7834
{
7835
    $mime = $default;
7836
    $imgmime = 'other.png';
7837
    $famime = 'file-o';
7838
    $srclang = '';
7839
7840
    $tmpfile = preg_replace('/\.noexe$/', '', $file);
7841
7842
    // Plain text files
7843
    if (preg_match('/\.txt$/i', $tmpfile)) {
7844
        $mime = 'text/plain';
7845
        $imgmime = 'text.png';
7846
        $famime = 'file-alt';
7847
    } elseif (preg_match('/\.rtx$/i', $tmpfile)) {
7848
        $mime = 'text/richtext';
7849
        $imgmime = 'text.png';
7850
        $famime = 'file-alt';
7851
    } elseif (preg_match('/\.csv$/i', $tmpfile)) {
7852
        $mime = 'text/csv';
7853
        $imgmime = 'text.png';
7854
        $famime = 'file-csv';
7855
    } elseif (preg_match('/\.tsv$/i', $tmpfile)) {
7856
        $mime = 'text/tab-separated-values';
7857
        $imgmime = 'text.png';
7858
        $famime = 'file-alt';
7859
    } elseif (preg_match('/\.(cf|conf|log)$/i', $tmpfile)) {
7860
        $mime = 'text/plain';
7861
        $imgmime = 'text.png';
7862
        $famime = 'file-alt';
7863
    } elseif (preg_match('/\.ini$/i', $tmpfile)) {
7864
        $mime = 'text/plain';
7865
        $imgmime = 'text.png';
7866
        $srclang = 'ini';
7867
        $famime = 'file-alt';
7868
    } elseif (preg_match('/\.md$/i', $tmpfile)) {
7869
        $mime = 'text/plain';
7870
        $imgmime = 'text.png';
7871
        $srclang = 'md';
7872
        $famime = 'file-alt';
7873
    } elseif (preg_match('/\.css$/i', $tmpfile)) {
7874
        $mime = 'text/css';
7875
        $imgmime = 'css.png';
7876
        $srclang = 'css';
7877
        $famime = 'file-alt';
7878
    } elseif (preg_match('/\.lang$/i', $tmpfile)) {
7879
        $mime = 'text/plain';
7880
        $imgmime = 'text.png';
7881
        $srclang = 'lang';
7882
        $famime = 'file-alt';
7883
    } elseif (preg_match('/\.(crt|cer|key|pub)$/i', $tmpfile)) {    // Certificate files
7884
        $mime = 'text/plain';
7885
        $imgmime = 'text.png';
7886
        $famime = 'file-alt';
7887
    } elseif (preg_match('/\.(html|htm|shtml)$/i', $tmpfile)) {     // XML based (HTML/XML/XAML)
7888
        $mime = 'text/html';
7889
        $imgmime = 'html.png';
7890
        $srclang = 'html';
7891
        $famime = 'file-alt';
7892
    } elseif (preg_match('/\.(xml|xhtml)$/i', $tmpfile)) {
7893
        $mime = 'text/xml';
7894
        $imgmime = 'other.png';
7895
        $srclang = 'xml';
7896
        $famime = 'file-alt';
7897
    } elseif (preg_match('/\.xaml$/i', $tmpfile)) {
7898
        $mime = 'text/xml';
7899
        $imgmime = 'other.png';
7900
        $srclang = 'xaml';
7901
        $famime = 'file-alt';
7902
    } elseif (preg_match('/\.bas$/i', $tmpfile)) {                  // Languages
7903
        $mime = 'text/plain';
7904
        $imgmime = 'text.png';
7905
        $srclang = 'bas';
7906
        $famime = 'file-code';
7907
    } elseif (preg_match('/\.(c)$/i', $tmpfile)) {
7908
        $mime = 'text/plain';
7909
        $imgmime = 'text.png';
7910
        $srclang = 'c';
7911
        $famime = 'file-code';
7912
    } elseif (preg_match('/\.(cpp)$/i', $tmpfile)) {
7913
        $mime = 'text/plain';
7914
        $imgmime = 'text.png';
7915
        $srclang = 'cpp';
7916
        $famime = 'file-code';
7917
    } elseif (preg_match('/\.cs$/i', $tmpfile)) {
7918
        $mime = 'text/plain';
7919
        $imgmime = 'text.png';
7920
        $srclang = 'cs';
7921
        $famime = 'file-code';
7922
    } elseif (preg_match('/\.(h)$/i', $tmpfile)) {
7923
        $mime = 'text/plain';
7924
        $imgmime = 'text.png';
7925
        $srclang = 'h';
7926
        $famime = 'file-code';
7927
    } elseif (preg_match('/\.(java|jsp)$/i', $tmpfile)) {
7928
        $mime = 'text/plain';
7929
        $imgmime = 'text.png';
7930
        $srclang = 'java';
7931
        $famime = 'file-code';
7932
    } elseif (preg_match('/\.php([0-9]{1})?$/i', $tmpfile)) {
7933
        $mime = 'text/plain';
7934
        $imgmime = 'php.png';
7935
        $srclang = 'php';
7936
        $famime = 'file-code';
7937
    } elseif (preg_match('/\.phtml$/i', $tmpfile)) {
7938
        $mime = 'text/plain';
7939
        $imgmime = 'php.png';
7940
        $srclang = 'php';
7941
        $famime = 'file-code';
7942
    } elseif (preg_match('/\.(pl|pm)$/i', $tmpfile)) {
7943
        $mime = 'text/plain';
7944
        $imgmime = 'pl.png';
7945
        $srclang = 'perl';
7946
        $famime = 'file-code';
7947
    } elseif (preg_match('/\.sql$/i', $tmpfile)) {
7948
        $mime = 'text/plain';
7949
        $imgmime = 'text.png';
7950
        $srclang = 'sql';
7951
        $famime = 'file-code';
7952
    } elseif (preg_match('/\.js$/i', $tmpfile)) {
7953
        $mime = 'text/x-javascript';
7954
        $imgmime = 'jscript.png';
7955
        $srclang = 'js';
7956
        $famime = 'file-code';
7957
    } elseif (preg_match('/\.odp$/i', $tmpfile)) {                  // Open office
7958
        $mime = 'application/vnd.oasis.opendocument.presentation';
7959
        $imgmime = 'ooffice.png';
7960
        $famime = 'file-powerpoint';
7961
    } elseif (preg_match('/\.ods$/i', $tmpfile)) {
7962
        $mime = 'application/vnd.oasis.opendocument.spreadsheet';
7963
        $imgmime = 'ooffice.png';
7964
        $famime = 'file-excel';
7965
    } elseif (preg_match('/\.odt$/i', $tmpfile)) {
7966
        $mime = 'application/vnd.oasis.opendocument.text';
7967
        $imgmime = 'ooffice.png';
7968
        $famime = 'file-word';
7969
    } elseif (preg_match('/\.mdb$/i', $tmpfile)) {                  // MS Office
7970
        $mime = 'application/msaccess';
7971
        $imgmime = 'mdb.png';
7972
        $famime = 'file';
7973
    } elseif (preg_match('/\.doc[xm]?$/i', $tmpfile)) {
7974
        $mime = 'application/msword';
7975
        $imgmime = 'doc.png';
7976
        $famime = 'file-word';
7977
    } elseif (preg_match('/\.dot[xm]?$/i', $tmpfile)) {
7978
        $mime = 'application/msword';
7979
        $imgmime = 'doc.png';
7980
        $famime = 'file-word';
7981
    } elseif (preg_match('/\.xlt(x)?$/i', $tmpfile)) {
7982
        $mime = 'application/vnd.ms-excel';
7983
        $imgmime = 'xls.png';
7984
        $famime = 'file-excel';
7985
    } elseif (preg_match('/\.xla(m)?$/i', $tmpfile)) {
7986
        $mime = 'application/vnd.ms-excel';
7987
        $imgmime = 'xls.png';
7988
        $famime = 'file-excel';
7989
    } elseif (preg_match('/\.xls$/i', $tmpfile)) {
7990
        $mime = 'application/vnd.ms-excel';
7991
        $imgmime = 'xls.png';
7992
        $famime = 'file-excel';
7993
    } elseif (preg_match('/\.xls[bmx]$/i', $tmpfile)) {
7994
        $mime = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
7995
        $imgmime = 'xls.png';
7996
        $famime = 'file-excel';
7997
    } elseif (preg_match('/\.pps[mx]?$/i', $tmpfile)) {
7998
        $mime = 'application/vnd.ms-powerpoint';
7999
        $imgmime = 'ppt.png';
8000
        $famime = 'file-powerpoint';
8001
    } elseif (preg_match('/\.ppt[mx]?$/i', $tmpfile)) {
8002
        $mime = 'application/x-mspowerpoint';
8003
        $imgmime = 'ppt.png';
8004
        $famime = 'file-powerpoint';
8005
    } elseif (preg_match('/\.pdf$/i', $tmpfile)) {                  // Other
8006
        $mime = 'application/pdf';
8007
        $imgmime = 'pdf.png';
8008
        $famime = 'file-pdf';
8009
    } elseif (preg_match('/\.bat$/i', $tmpfile)) {                  // Scripts
8010
        $mime = 'text/x-bat';
8011
        $imgmime = 'script.png';
8012
        $srclang = 'dos';
8013
        $famime = 'file-code';
8014
    } elseif (preg_match('/\.sh$/i', $tmpfile)) {
8015
        $mime = 'text/x-sh';
8016
        $imgmime = 'script.png';
8017
        $srclang = 'bash';
8018
        $famime = 'file-code';
8019
    } elseif (preg_match('/\.ksh$/i', $tmpfile)) {
8020
        $mime = 'text/x-ksh';
8021
        $imgmime = 'script.png';
8022
        $srclang = 'bash';
8023
        $famime = 'file-code';
8024
    } elseif (preg_match('/\.bash$/i', $tmpfile)) {
8025
        $mime = 'text/x-bash';
8026
        $imgmime = 'script.png';
8027
        $srclang = 'bash';
8028
        $famime = 'file-code';
8029
    } elseif (preg_match('/\.ico$/i', $tmpfile)) {                  // Images
8030
        $mime = 'image/x-icon';
8031
        $imgmime = 'image.png';
8032
        $famime = 'file-image';
8033
    } elseif (preg_match('/\.(jpg|jpeg)$/i', $tmpfile)) {
8034
        $mime = 'image/jpeg';
8035
        $imgmime = 'image.png';
8036
        $famime = 'file-image';
8037
    } elseif (preg_match('/\.png$/i', $tmpfile)) {
8038
        $mime = 'image/png';
8039
        $imgmime = 'image.png';
8040
        $famime = 'file-image';
8041
    } elseif (preg_match('/\.gif$/i', $tmpfile)) {
8042
        $mime = 'image/gif';
8043
        $imgmime = 'image.png';
8044
        $famime = 'file-image';
8045
    } elseif (preg_match('/\.bmp$/i', $tmpfile)) {
8046
        $mime = 'image/bmp';
8047
        $imgmime = 'image.png';
8048
        $famime = 'file-image';
8049
    } elseif (preg_match('/\.(tif|tiff)$/i', $tmpfile)) {
8050
        $mime = 'image/tiff';
8051
        $imgmime = 'image.png';
8052
        $famime = 'file-image';
8053
    } elseif (preg_match('/\.svg$/i', $tmpfile)) {
8054
        $mime = 'image/svg+xml';
8055
        $imgmime = 'image.png';
8056
        $famime = 'file-image';
8057
    } elseif (preg_match('/\.webp$/i', $tmpfile)) {
8058
        $mime = 'image/webp';
8059
        $imgmime = 'image.png';
8060
        $famime = 'file-image';
8061
    } elseif (preg_match('/\.vcs$/i', $tmpfile)) {                  // Calendar
8062
        $mime = 'text/calendar';
8063
        $imgmime = 'other.png';
8064
        $famime = 'file-alt';
8065
    } elseif (preg_match('/\.ics$/i', $tmpfile)) {
8066
        $mime = 'text/calendar';
8067
        $imgmime = 'other.png';
8068
        $famime = 'file-alt';
8069
    } elseif (preg_match('/\.torrent$/i', $tmpfile)) {              // Other
8070
        $mime = 'application/x-bittorrent';
8071
        $imgmime = 'other.png';
8072
        $famime = 'file-o';
8073
    } elseif (preg_match('/\.(mp3|ogg|au|wav|wma|mid)$/i', $tmpfile)) { // Audio
8074
        $mime = 'audio';
8075
        $imgmime = 'audio.png';
8076
        $famime = 'file-audio';
8077
    } elseif (preg_match('/\.mp4$/i', $tmpfile)) {                  // Video
8078
        $mime = 'video/mp4';
8079
        $imgmime = 'video.png';
8080
        $famime = 'file-video';
8081
    } elseif (preg_match('/\.ogv$/i', $tmpfile)) {
8082
        $mime = 'video/ogg';
8083
        $imgmime = 'video.png';
8084
        $famime = 'file-video';
8085
    } elseif (preg_match('/\.webm$/i', $tmpfile)) {
8086
        $mime = 'video/webm';
8087
        $imgmime = 'video.png';
8088
        $famime = 'file-video';
8089
    } elseif (preg_match('/\.avi$/i', $tmpfile)) {
8090
        $mime = 'video/x-msvideo';
8091
        $imgmime = 'video.png';
8092
        $famime = 'file-video';
8093
    } elseif (preg_match('/\.divx$/i', $tmpfile)) {
8094
        $mime = 'video/divx';
8095
        $imgmime = 'video.png';
8096
        $famime = 'file-video';
8097
    } elseif (preg_match('/\.xvid$/i', $tmpfile)) {
8098
        $mime = 'video/xvid';
8099
        $imgmime = 'video.png';
8100
        $famime = 'file-video';
8101
    } elseif (preg_match('/\.(wmv|mpg|mpeg)$/i', $tmpfile)) {
8102
        $mime = 'video';
8103
        $imgmime = 'video.png';
8104
        $famime = 'file-video';
8105
    } elseif (preg_match('/\.(zip|rar|gz|tgz|z|cab|bz2|7z|tar|lzh|zst)$/i', $tmpfile)) {    // Archive
8106
        // application/xxx where zzz is zip, ...
8107
        $mime = 'archive';
8108
        $imgmime = 'archive.png';
8109
        $famime = 'file-archive';
8110
    } elseif (preg_match('/\.(exe|com)$/i', $tmpfile)) {                    // Exe
8111
        $mime = 'application/octet-stream';
8112
        $imgmime = 'other.png';
8113
        $famime = 'file-o';
8114
    } elseif (preg_match('/\.(dll|lib|o|so|a)$/i', $tmpfile)) {             // Lib
8115
        $mime = 'library';
8116
        $imgmime = 'library.png';
8117
        $famime = 'file-o';
8118
    } elseif (preg_match('/\.err$/i', $tmpfile)) {                             // phpcs:ignore
8119
        $mime = 'error';
8120
        $imgmime = 'error.png';
8121
        $famime = 'file-alt';
8122
    }
8123
8124
    // Return mimetype string
8125
    switch ((int)$mode) {
8126
        case 1:
8127
            $tmp = explode('/', $mime);
8128
            return (!empty($tmp[1]) ? $tmp[1] : $tmp[0]);
8129
        case 2:
8130
            return $imgmime;
8131
        case 3:
8132
            return $srclang;
8133
        case 4:
8134
            return $famime;
8135
    }
8136
    return $mime;
8137
}
8138
8139
/**
8140
 * Return the value of a filed into a dictionary for the record $id.
8141
 * This also set all the values into a cache for a next search.
8142
 *
8143
 * @param string $tablename Name of table dictionary (without the MAIN_DB_PREFIX, example: 'c_holiday_types')
8144
 * @param string $field The name of field where to find the value to return
8145
 * @param int $id Id of line record
8146
 * @param bool $checkentity Add filter on entity
8147
 * @param string $rowidfield Name of the column rowid (to use for the filter on $id)
8148
 * @return string                   The value of field $field. This also set $dictvalues cache.
8149
 */
8150
function getDictionaryValue($tablename, $field, $id, $checkentity = false, $rowidfield = 'rowid')
8151
{
8152
    global $conf, $db;
8153
8154
    $tablename = preg_replace('/^' . preg_quote(MAIN_DB_PREFIX, '/') . '/', '', $tablename);    // Clean name of table for backward compatibility.
8155
8156
    $dictvalues = (isset($conf->cache['dictvalues_' . $tablename]) ? $conf->cache['dictvalues_' . $tablename] : null);
8157
8158
    if (is_null($dictvalues)) {
8159
        $dictvalues = array();
8160
8161
        $sql = "SELECT * FROM " . MAIN_DB_PREFIX . $tablename . " WHERE 1 = 1"; // Here select * is allowed as it is generic code and we don't have list of fields
8162
        if ($checkentity) {
8163
            $sql .= ' AND entity IN (0,' . getEntity($tablename) . ')';
8164
        }
8165
8166
        $resql = $db->query($sql);
8167
        if ($resql) {
8168
            while ($obj = $db->fetch_object($resql)) {
8169
                $dictvalues[$obj->$rowidfield] = $obj;  // $obj is stdClass
8170
            }
8171
        } else {
8172
            dol_print_error($db);
8173
        }
8174
8175
        $conf->cache['dictvalues_' . $tablename] = $dictvalues;
8176
    }
8177
8178
    if (!empty($dictvalues[$id])) {
8179
        // Found
8180
        $tmp = $dictvalues[$id];
8181
        return (property_exists($tmp, $field) ? $tmp->$field : '');
8182
    } else {
8183
        // Not found
8184
        return '';
8185
    }
8186
}
8187
8188
/**
8189
 *  Return true if the color is light
8190
 *
8191
 * @param string $stringcolor String with hex (FFFFFF) or comma RGB ('255,255,255')
8192
 * @return int<-1,1>                   -1 : Error with argument passed |0 : color is dark | 1 : color is light
8193
 */
8194
function colorIsLight($stringcolor)
8195
{
8196
    $stringcolor = str_replace('#', '', $stringcolor);
8197
    $res = -1;
8198
    if (!empty($stringcolor)) {
8199
        $res = 0;
8200
        $tmp = explode(',', $stringcolor);
8201
        if (count($tmp) > 1) {   // This is a comma RGB ('255','255','255')
8202
            $r = $tmp[0];
8203
            $g = $tmp[1];
8204
            $b = $tmp[2];
8205
        } else {
8206
            $hexr = $stringcolor[0] . $stringcolor[1];
8207
            $hexg = $stringcolor[2] . $stringcolor[3];
8208
            $hexb = $stringcolor[4] . $stringcolor[5];
8209
            $r = hexdec($hexr);
8210
            $g = hexdec($hexg);
8211
            $b = hexdec($hexb);
8212
        }
8213
        $bright = (max($r, $g, $b) + min($r, $g, $b)) / 510.0; // HSL algorithm
8214
        if ($bright > 0.6) {
8215
            $res = 1;
8216
        }
8217
    }
8218
    return $res;
8219
}
8220
8221
/**
8222
 * Function to test if an entry is enabled or not
8223
 *
8224
 * @param int<0,1> $type_user 0=We test for internal user, 1=We test for external user
8225
 * @param array{enabled:int<0,1>,module:string,perms:string} $menuentry Array for feature entry to test
0 ignored issues
show
Documentation Bug introduced by
The doc comment array{enabled:int<0,1>,m...le:string,perms:string} at position 4 could not be parsed: Expected '}' at position 4, but found 'int'.
Loading history...
8226
 * @param string[] $listofmodulesforexternal Array with list of modules allowed to external users
8227
 * @return  int<0,2>                                0=Hide, 1=Show, 2=Show gray
8228
 */
8229
function isVisibleToUserType($type_user, &$menuentry, &$listofmodulesforexternal)
8230
{
8231
    global $conf;
8232
8233
    //print 'type_user='.$type_user.' module='.$menuentry['module'].' enabled='.$menuentry['enabled'].' perms='.$menuentry['perms'];
8234
    //print 'ok='.in_array($menuentry['module'], $listofmodulesforexternal);
8235
    if (empty($menuentry['enabled'])) {
8236
        return 0; // Entry disabled by condition
8237
    }
8238
    if ($type_user && $menuentry['module']) {
8239
        $tmploops = explode('|', $menuentry['module']);
8240
        $found = 0;
8241
        foreach ($tmploops as $tmploop) {
8242
            if (in_array($tmploop, $listofmodulesforexternal)) {
8243
                $found++;
8244
                break;
8245
            }
8246
        }
8247
        if (!$found) {
8248
            return 0; // Entry is for menus all excluded to external users
8249
        }
8250
    }
8251
    if (!$menuentry['perms'] && $type_user) {
8252
        return 0; // No permissions and user is external
8253
    }
8254
    if (!$menuentry['perms'] && getDolGlobalString('MAIN_MENU_HIDE_UNAUTHORIZED')) {
8255
        return 0; // No permissions and option to hide when not allowed, even for internal user, is on
8256
    }
8257
    if (!$menuentry['perms']) {
8258
        return 2; // No permissions and user is external
8259
    }
8260
    return 1;
8261
}
8262
8263
/**
8264
 * Round to next multiple.
8265
 *
8266
 * @param float $n Number to round up
8267
 * @param int $x Multiple. For example 60 to round up to nearest exact minute for a date with seconds.
8268
 * @return  int             Value rounded.
8269
 */
8270
function roundUpToNextMultiple($n, $x = 5)
8271
{
8272
    $result = (ceil($n) % $x === 0) ? ceil($n) : (round(($n + $x / 2) / $x) * $x);
8273
    return (int)$result;
8274
}
8275
8276
/**
8277
 * Fetch an object from its id and element_type
8278
 * Inclusion of classes is automatic
8279
 *
8280
 * @param int $element_id Element id (Use this or element_ref but not both. If id and ref are empty, object with no fetch is returned)
8281
 * @param string $element_type Element type ('module' or 'myobject@mymodule' or 'mymodule_myobject')
8282
 * @param string $element_ref Element ref (Use this or element_id but not both. If id and ref are empty, object with no fetch is returned)
8283
 * @param int<0,2> $useCache If you want to store object in cache or get it from cache 0 => no use cache , 1 use cache, 2 force reload  cache
8284
 * @param int $maxCacheByType Number of object in cache for this element type
8285
 * @return  int<-1,0>|object                object || 0 || <0 if error
8286
 * @see getElementProperties()
8287
 */
8288
function fetchObjectByElement($element_id, $element_type, $element_ref = '', $useCache = 0, $maxCacheByType = 10)
8289
{
8290
    global $db, $globalCacheForGetObjectFromCache;
8291
8292
    $ret = 0;
8293
8294
    $element_prop = getElementProperties($element_type);
8295
8296
    if ($element_prop['module'] == 'product' || $element_prop['module'] == 'service') {
8297
        // For example, for an extrafield 'product' (shared for both product and service) that is a link to an object,
8298
        // this is called with $element_type = 'product' when we need element properties of a service, we must return a product. If we create the
8299
        // extrafield for a service, it is not supported and not found when editing the product/service card. So we must keep 'product' for extrafields
8300
        // of service and we will return properties of a product.
8301
        $ismodenabled = (isModEnabled('product') || isModEnabled('service'));
8302
    } elseif ($element_prop['module'] == 'societeaccount') {
8303
        $ismodenabled = isModEnabled('website') || isModEnabled('webportal');
8304
    } else {
8305
        $ismodenabled = isModEnabled($element_prop['module']);
8306
    }
8307
    //var_dump('element_type='.$element_type);
8308
    //var_dump($element_prop);
8309
    //var_dump($element_prop['module'].' '.$ismodenabled);
8310
    if (is_array($element_prop) && (empty($element_prop['module']) || $ismodenabled)) {
8311
        if (
8312
            $useCache === 1
8313
            && !empty($globalCacheForGetObjectFromCache[$element_type])
8314
            && !empty($globalCacheForGetObjectFromCache[$element_type][$element_id])
8315
            && is_object($globalCacheForGetObjectFromCache[$element_type][$element_id])
8316
        ) {
8317
            return $globalCacheForGetObjectFromCache[$element_type][$element_id];
8318
        }
8319
8320
        dol_include_once('/' . $element_prop['classpath'] . '/' . $element_prop['classfile'] . '.class.php');
8321
8322
        if (class_exists($element_prop['classname'])) {
8323
            $className = $element_prop['classname'];
8324
            $objecttmp = new $className($db);
8325
            '@phan-var-force CommonObject $objecttmp';
8326
8327
            if ($element_id > 0 || !empty($element_ref)) {
8328
                $ret = $objecttmp->fetch($element_id, $element_ref);
8329
                if ($ret >= 0) {
8330
                    if (empty($objecttmp->module)) {
8331
                        $objecttmp->module = $element_prop['module'];
8332
                    }
8333
8334
                    if ($useCache > 0) {
8335
                        if (!isset($globalCacheForGetObjectFromCache[$element_type])) {
8336
                            $globalCacheForGetObjectFromCache[$element_type] = [];
8337
                        }
8338
8339
                        // Manage cache limit
8340
                        if (!empty($globalCacheForGetObjectFromCache[$element_type]) && is_array($globalCacheForGetObjectFromCache[$element_type]) && count($globalCacheForGetObjectFromCache[$element_type]) >= $maxCacheByType) {
8341
                            array_shift($globalCacheForGetObjectFromCache[$element_type]);
8342
                        }
8343
8344
                        $globalCacheForGetObjectFromCache[$element_type][$element_id] = $objecttmp;
8345
                    }
8346
8347
                    return $objecttmp;
8348
                }
8349
            } else {
8350
                return $objecttmp;  // returned an object without fetch
8351
            }
8352
        } else {
8353
            return -1;
8354
        }
8355
    }
8356
8357
    return $ret;
8358
}
8359
8360
/**
8361
 * Return if a file can contains executable content
8362
 *
8363
 * @param string $filename File name to test
8364
 * @return  boolean                 True if yes, False if no
8365
 */
8366
function isAFileWithExecutableContent($filename)
8367
{
8368
    if (preg_match('/\.(htm|html|js|phar|php|php\d+|phtml|pht|pl|py|cgi|ksh|sh|shtml|bash|bat|cmd|wpk|exe|dmg)$/i', $filename)) {
8369
        return true;
8370
    }
8371
8372
    return false;
8373
}
8374
8375
/**
8376
 * Return the value of token currently saved into session with name 'newtoken'.
8377
 * This token must be send by any POST as it will be used by next page for comparison with value in session.
8378
 *
8379
 * @return  string
8380
 * @since Dolibarr v10.0.7
8381
 */
8382
function newToken()
8383
{
8384
    return empty($_SESSION['newtoken']) ? '' : $_SESSION['newtoken'];
8385
}
8386
8387
/**
8388
 * Return the value of token currently saved into session with name 'token'.
8389
 * For ajax call, you must use this token as a parameter of the call into the js calling script (the called ajax php page must also set constant NOTOKENRENEWAL).
8390
 *
8391
 * @return  string
8392
 * @since Dolibarr v10.0.7
8393
 */
8394
function currentToken()
8395
{
8396
    return isset($_SESSION['token']) ? $_SESSION['token'] : '';
8397
}
8398
8399
/**
8400
 * Return a random string to be used as a nonce value for js
8401
 *
8402
 * @return  string
8403
 */
8404
function getNonce()
8405
{
8406
    global $conf;
8407
8408
    if (empty($conf->cache['nonce'])) {
8409
        $conf->cache['nonce'] = dolGetRandomBytes(8);
8410
    }
8411
8412
    return $conf->cache['nonce'];
8413
}
8414
8415
/**
8416
 *  Return a file on output using a low memory. It can return very large files with no need of memory.
8417
 *  WARNING: This close output buffers.
8418
 *
8419
 * @param string $fullpath_original_file_osencoded Full path of file to return.
8420
 * @param int<-1,2> $method -1 automatic, 0=readfile, 1=fread, 2=stream_copy_to_stream
8421
 * @return void
8422
 */
8423
function readfileLowMemory($fullpath_original_file_osencoded, $method = -1)
8424
{
8425
    if ($method == -1) {
8426
        $method = 0;
8427
        if (getDolGlobalString('MAIN_FORCE_READFILE_WITH_FREAD')) {
8428
            $method = 1;
8429
        }
8430
        if (getDolGlobalString('MAIN_FORCE_READFILE_WITH_STREAM_COPY')) {
8431
            $method = 2;
8432
        }
8433
    }
8434
8435
    // Be sure we don't have output buffering enabled to have readfile working correctly
8436
    while (ob_get_level()) {
8437
        ob_end_flush();
8438
    }
8439
8440
    // Solution 0
8441
    if ($method == 0) {
8442
        readfile($fullpath_original_file_osencoded);
8443
    } elseif ($method == 1) {
8444
        // Solution 1
8445
        $handle = fopen($fullpath_original_file_osencoded, "rb");
8446
        while (!feof($handle)) {
8447
            print fread($handle, 8192);
8448
        }
8449
        fclose($handle);
8450
    } elseif ($method == 2) {
8451
        // Solution 2
8452
        $handle1 = fopen($fullpath_original_file_osencoded, "rb");
8453
        $handle2 = fopen("php://output", "wb");
8454
        stream_copy_to_stream($handle1, $handle2);
8455
        fclose($handle1);
8456
        fclose($handle2);
8457
    }
8458
}
8459