Passed
Push — FUNCTIONS_LIB_REVIEW_240920 ( 00d68f )
by Rafael
49:46
created

str_ends_with()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

5790
                    /** @scrutinizer ignore-call */ 
5791
                    $substitutionarray['__MEMBER_CIVILITY__'] = $object->getCivilityLabel();
Loading history...
5791
                }
5792
                $substitutionarray['__MEMBER_FIRSTNAME__'] = (isset($object->firstname) ? $object->firstname : '');
5793
                $substitutionarray['__MEMBER_LASTNAME__'] = (isset($object->lastname) ? $object->lastname : '');
5794
                $substitutionarray['__MEMBER_USER_LOGIN_INFORMATION__'] = '';
5795
                if (method_exists($object, 'getFullName')) {
5796
                    $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

5796
                    /** @scrutinizer ignore-call */ 
5797
                    $substitutionarray['__MEMBER_FULLNAME__'] = $object->getFullName($outputlangs);
Loading history...
5797
                }
5798
                $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...
5799
                $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...
5800
                $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...
5801
                $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...
5802
                $substitutionarray['__MEMBER_COUNTRY__'] = (isset($object->country) ? $object->country : '');
5803
                $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...
5804
                $substitutionarray['__MEMBER_BIRTH__'] = (isset($birthday) ? $birthday : '');
5805
                $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...
5806
                $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...
5807
                $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...
5808
                $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...
5809
                $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...
5810
                $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...
5811
                $substitutionarray['__MEMBER_TYPE__'] = (isset($object->type) ? $object->type : '');
5812
                $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...
5813
5814
                $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_RFC__'] = dol_print_date($object->first_subscription_date, 'dayrfc');
5815
                $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...
5816
                $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_START_RFC__'] = (isset($object->first_subscription_date_start) ? dol_print_date($object->first_subscription_date_start, 'dayrfc') : '');
5817
                $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...
5818
                $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_END_RFC__'] = (isset($object->first_subscription_date_end) ? dol_print_date($object->first_subscription_date_end, 'dayrfc') : '');
5819
                $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...
5820
                $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_RFC__'] = dol_print_date($object->last_subscription_date, 'dayrfc');
5821
                $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...
5822
                $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_START_RFC__'] = dol_print_date($object->last_subscription_date_start, 'dayrfc');
5823
                $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...
5824
                $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_END_RFC__'] = dol_print_date($object->last_subscription_date_end, 'dayrfc');
5825
            }
5826
5827
            if (is_object($object) && $object->element == 'societe') {
5828
                '@phan-var-force Societe $object';
5829
                $substitutionarray['__THIRDPARTY_ID__'] = (is_object($object) ? $object->id : '');
5830
                $substitutionarray['__THIRDPARTY_NAME__'] = (is_object($object) ? $object->name : '');
5831
                $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...
5832
                $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...
5833
                $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...
5834
                $substitutionarray['__THIRDPARTY_EMAIL__'] = (is_object($object) ? $object->email : '');
5835
                $substitutionarray['__THIRDPARTY_EMAIL_URLENCODED__'] = urlencode(is_object($object) ? $object->email : '');
5836
                $substitutionarray['__THIRDPARTY_PHONE__'] = (is_object($object) ? dol_print_phone($object->phone) : '');
5837
                $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...
5838
                $substitutionarray['__THIRDPARTY_ADDRESS__'] = (is_object($object) ? $object->address : '');
5839
                $substitutionarray['__THIRDPARTY_ZIP__'] = (is_object($object) ? $object->zip : '');
5840
                $substitutionarray['__THIRDPARTY_TOWN__'] = (is_object($object) ? $object->town : '');
5841
                $substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = (is_object($object) ? $object->country_id : '');
5842
                $substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = (is_object($object) ? $object->country_code : '');
5843
                $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...
5844
                $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...
5845
                $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...
5846
                $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...
5847
                $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...
5848
                $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...
5849
                $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...
5850
                $substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = (is_object($object) ? dol_htmlentitiesbr($object->note_public) : '');
5851
                $substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = (is_object($object) ? dol_htmlentitiesbr($object->note_private) : '');
5852
            } elseif (is_object($object->thirdparty)) {
5853
                $substitutionarray['__THIRDPARTY_ID__'] = (is_object($object->thirdparty) ? $object->thirdparty->id : '');
5854
                $substitutionarray['__THIRDPARTY_NAME__'] = (is_object($object->thirdparty) ? $object->thirdparty->name : '');
5855
                $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = (is_object($object->thirdparty) ? $object->thirdparty->name_alias : '');
5856
                $substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = (is_object($object->thirdparty) ? $object->thirdparty->code_client : '');
5857
                $substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = (is_object($object->thirdparty) ? $object->thirdparty->code_fournisseur : '');
5858
                $substitutionarray['__THIRDPARTY_EMAIL__'] = (is_object($object->thirdparty) ? $object->thirdparty->email : '');
5859
                $substitutionarray['__THIRDPARTY_EMAIL_URLENCODED__'] = urlencode(is_object($object->thirdparty) ? $object->thirdparty->email : '');
5860
                $substitutionarray['__THIRDPARTY_PHONE__'] = (is_object($object->thirdparty) ? dol_print_phone($object->thirdparty->phone) : '');
5861
                $substitutionarray['__THIRDPARTY_FAX__'] = (is_object($object->thirdparty) ? dol_print_phone($object->thirdparty->fax) : '');
5862
                $substitutionarray['__THIRDPARTY_ADDRESS__'] = (is_object($object->thirdparty) ? $object->thirdparty->address : '');
5863
                $substitutionarray['__THIRDPARTY_ZIP__'] = (is_object($object->thirdparty) ? $object->thirdparty->zip : '');
5864
                $substitutionarray['__THIRDPARTY_TOWN__'] = (is_object($object->thirdparty) ? $object->thirdparty->town : '');
5865
                $substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = (is_object($object->thirdparty) ? $object->thirdparty->country_id : '');
5866
                $substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = (is_object($object->thirdparty) ? $object->thirdparty->country_code : '');
5867
                $substitutionarray['__THIRDPARTY_IDPROF1__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof1 : '');
5868
                $substitutionarray['__THIRDPARTY_IDPROF2__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof2 : '');
5869
                $substitutionarray['__THIRDPARTY_IDPROF3__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof3 : '');
5870
                $substitutionarray['__THIRDPARTY_IDPROF4__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof4 : '');
5871
                $substitutionarray['__THIRDPARTY_IDPROF5__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof5 : '');
5872
                $substitutionarray['__THIRDPARTY_IDPROF6__'] = (is_object($object->thirdparty) ? $object->thirdparty->idprof6 : '');
5873
                $substitutionarray['__THIRDPARTY_TVAINTRA__'] = (is_object($object->thirdparty) ? $object->thirdparty->tva_intra : '');
5874
                $substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = (is_object($object->thirdparty) ? dol_htmlentitiesbr($object->thirdparty->note_public) : '');
5875
                $substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = (is_object($object->thirdparty) ? dol_htmlentitiesbr($object->thirdparty->note_private) : '');
5876
            }
5877
5878
            if (is_object($object) && $object->element == 'recruitmentcandidature') {
5879
                '@phan-var-force RecruitmentCandidature $object';
5880
                $substitutionarray['__CANDIDATE_FULLNAME__'] = $object->getFullName($outputlangs);
5881
                $substitutionarray['__CANDIDATE_FIRSTNAME__'] = isset($object->firstname) ? $object->firstname : '';
5882
                $substitutionarray['__CANDIDATE_LASTNAME__'] = isset($object->lastname) ? $object->lastname : '';
5883
            }
5884
            if (is_object($object) && $object->element == 'conferenceorboothattendee') {
5885
                '@phan-var-force ConferenceOrBoothAttendee $object';
5886
                $substitutionarray['__ATTENDEE_FULLNAME__'] = $object->getFullName($outputlangs);
5887
                $substitutionarray['__ATTENDEE_FIRSTNAME__'] = isset($object->firstname) ? $object->firstname : '';
5888
                $substitutionarray['__ATTENDEE_LASTNAME__'] = isset($object->lastname) ? $object->lastname : '';
5889
            }
5890
5891
            if (is_object($object) && $object->element == 'project') {
5892
                '@phan-var-force Project $object';
5893
                $substitutionarray['__PROJECT_ID__'] = $object->id;
5894
                $substitutionarray['__PROJECT_REF__'] = $object->ref;
5895
                $substitutionarray['__PROJECT_NAME__'] = $object->title;
5896
            } elseif (is_object($object)) {
5897
                $project = null;
5898
                if (!empty($object->project)) {
5899
                    $project = $object->project;
5900
                } 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...
5901
                    $project = $object->projet;
5902
                }
5903
                if (!is_null($project) && is_object($project)) {
5904
                    $substitutionarray['__PROJECT_ID__'] = $project->id;
5905
                    $substitutionarray['__PROJECT_REF__'] = $project->ref;
5906
                    $substitutionarray['__PROJECT_NAME__'] = $project->title;
5907
                } else {
5908
                    // can substitute variables for project : uses lazy load in "make_substitutions" method
5909
                    $project_id = 0;
5910
                    if (!empty($object->fk_project) && $object->fk_project > 0) {
5911
                        $project_id = $object->fk_project;
5912
                    } elseif (!empty($object->fk_projet) && $object->fk_projet > 0) {
5913
                        $project_id = $object->fk_project;
5914
                    }
5915
                    if ($project_id > 0) {
5916
                        // path:class:method:id
5917
                        $substitutionarray['__PROJECT_ID__@lazyload'] = '/projet/class/project.class.php:Project:fetchAndSetSubstitution:' . $project_id;
5918
                        $substitutionarray['__PROJECT_REF__@lazyload'] = '/projet/class/project.class.php:Project:fetchAndSetSubstitution:' . $project_id;
5919
                        $substitutionarray['__PROJECT_NAME__@lazyload'] = '/projet/class/project.class.php:Project:fetchAndSetSubstitution:' . $project_id;
5920
                    }
5921
                }
5922
            }
5923
5924
            if (is_object($object) && $object->element == 'facture') {
5925
                '@phan-var-force Facture $object';
5926
                $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...
5927
            }
5928
            if (is_object($object) && $object->element == 'shipping') {
5929
                '@phan-var-force Expedition $object';
5930
                $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...
5931
                $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...
5932
                $substitutionarray['__SHIPPINGMETHOD__'] = $object->shipping_method;
5933
            }
5934
            if (is_object($object) && $object->element == 'reception') {
5935
                '@phan-var-force Reception $object';
5936
                $substitutionarray['__RECEPTIONTRACKNUM__'] = $object->tracking_number;
5937
                $substitutionarray['__RECEPTIONTRACKNUMURL__'] = $object->tracking_url;
5938
            }
5939
5940
            if (is_object($object) && $object->element == 'contrat' && $object->id > 0 && is_array($object->lines)) {
5941
                '@phan-var-force Contrat $object';
5942
                $dateplannedstart = '';
5943
                $datenextexpiration = '';
5944
                foreach ($object->lines as $line) {
5945
                    if ($line->date_start > $dateplannedstart) {
5946
                        $dateplannedstart = $line->date_start;
5947
                    }
5948
                    if ($line->statut == 4 && $line->date_end && (!$datenextexpiration || $line->date_end < $datenextexpiration)) {
5949
                        $datenextexpiration = $line->date_end;
5950
                    }
5951
                }
5952
                $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = dol_print_date($dateplannedstart, 'day');
5953
                $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE_RFC__'] = dol_print_date($dateplannedstart, 'dayrfc');
5954
                $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = dol_print_date($dateplannedstart, 'standard');
5955
5956
                $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = dol_print_date($datenextexpiration, 'day');
5957
                $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE_RFC__'] = dol_print_date($datenextexpiration, 'dayrfc');
5958
                $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = dol_print_date($datenextexpiration, 'standard');
5959
            }
5960
            // add substitution variables for ticket
5961
            if (is_object($object) && $object->element == 'ticket') {
5962
                '@phan-var-force Ticket $object';
5963
                $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...
5964
                $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...
5965
                $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...
5966
                $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...
5967
                $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...
5968
                $substitutionarray['__TICKET_ANALYTIC_CODE__'] = $object->category_code;
5969
                $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...
5970
                $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...
5971
                $userstat = new User($db);
5972
                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...
5973
                    $userstat->fetch($object->fk_user_assign);
5974
                    $substitutionarray['__TICKET_USER_ASSIGN__'] = dolGetFirstLastname($userstat->firstname, $userstat->lastname);
5975
                }
5976
5977
                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...
5978
                    $userstat->fetch($object->fk_user_create);
5979
                    $substitutionarray['__USER_CREATE__'] = dolGetFirstLastname($userstat->firstname, $userstat->lastname);
5980
                }
5981
            }
5982
5983
            // Create dynamic tags for __EXTRAFIELD_FIELD__
5984
            if ($object->table_element && $object->id > 0) {
5985
                if (!is_object($extrafields)) {
5986
                    $extrafields = new ExtraFields($db);
5987
                }
5988
                $extrafields->fetch_name_optionals_label($object->table_element, true);
5989
5990
                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

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