img_picto()   F
last analyzed

Complexity

Conditions 77
Paths > 20000

Size

Total Lines 351
Code Lines 260

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 77
eloc 260
nc 85169880
nop 9
dl 0
loc 351
rs 0
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

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:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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