* Copyright (C) 2003 Jean-Louis Bergamo * Copyright (C) 2004-2025 Laurent Destailleur * Copyright (C) 2004 Sebastien Di Cintio * Copyright (C) 2004 Benoit Mortier * Copyright (C) 2004 Christophe Combelles * Copyright (C) 2005-2019 Regis Houssin * Copyright (C) 2008 Raphael Bertrand (Resultic) * Copyright (C) 2010-2018 Juanjo Menent * Copyright (C) 2013 Cédric Salvador * Copyright (C) 2013-2024 Alexandre Spangaro * Copyright (C) 2014 Cédric GROSS * Copyright (C) 2014-2015 Marcos García * Copyright (C) 2015 Jean-François Ferry * Copyright (C) 2018-2025 Frédéric France * Copyright (C) 2019-2023 Thibault Foucart * Copyright (C) 2020 Open-Dsi * Copyright (C) 2021 Gauthier VERDOL * Copyright (C) 2022 Anthony Berton * Copyright (C) 2022 Ferran Marcet * Copyright (C) 2022-2025 Charlene Benke * Copyright (C) 2024-2025 MDW * Copyright (C) 2023-2024 Joachim Kueter * Copyright (C) 2024 Lenin Rivas * Copyright (C) 2024 Josep Lluís Amador Teruel * Copyright (C) 2024 Benoît PASCAL * Copyright (C) 2025 Vincent Maury * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . * or see https://www.gnu.org/ */ /** * \file htdocs/core/lib/functions.lib.php * \brief A set of functions for Dolibarr * This file contains all frequently used functions. */ //include_once DOL_DOCUMENT_ROOT.'/core/lib/json.lib.php'; // Function for better PHP x compatibility if (!function_exists('utf8_encode')) { /** * Implement utf8_encode for PHP that does not support it. * * @param mixed $elements PHP Object to json encode * @return string Json encoded string * @phan-suppress PhanRedefineFunctionInternal */ function utf8_encode($elements) { return mb_convert_encoding($elements, 'UTF-8', 'ISO-8859-1'); } } if (!function_exists('utf8_decode')) { /** * Implement utf8_decode for PHP that does not support it. * * @param mixed $elements PHP Object to json encode * @return string Json encoded string * @phan-suppress PhanRedefineFunctionInternal */ function utf8_decode($elements) { return mb_convert_encoding($elements, 'ISO-8859-1', 'UTF-8'); } } if (!function_exists('str_starts_with')) { /** * str_starts_with * * @param string $haystack haystack * @param string $needle needle * @return boolean * @phan-suppress PhanRedefineFunctionInternal */ function str_starts_with($haystack, $needle) { return (string) $needle !== '' && strncmp($haystack, $needle, strlen($needle)) === 0; } } if (!function_exists('str_ends_with')) { /** * str_ends_with * * @param string $haystack haystack * @param string $needle needle * @return boolean * @phan-suppress PhanRedefineFunctionInternal */ function str_ends_with($haystack, $needle) { return $needle !== '' && substr($haystack, -strlen($needle)) === (string) $needle; } } if (!function_exists('str_contains')) { /** * str_contains * * @param string $haystack haystack * @param string $needle needle * @return boolean * @phan-suppress PhanRedefineFunctionInternal */ function str_contains($haystack, $needle) { return $needle !== '' && mb_strpos($haystack, $needle) !== false; } } /** * Return the full path of the directory where a module (or an object of a module) stores its files. * Path may depends on the entity if a multicompany module is enabled. * * @param CommonObject|BlockedLog|null $object Dolibarr common object. * @param string $module Override object element, for example to use 'mycompany' instead of 'societe' * @param int $forobject Return the more complete path for the given object instead of for the module only. * @param string $mode 'output' (full main dir) or 'outputrel' (relative dir) or 'temp' (full dir for temporary files) or 'version' (full dir for archived files) * @return string|null The path of the relative directory of the module, ending with / * @since Dolibarr V18 */ function getMultidirOutput($object, $module = '', $forobject = 0, $mode = 'output') { global $conf; $subdirectory = ''; if (!is_object($object) && empty($module)) { return null; } if (empty($module) && !empty($object->element)) { $module = $object->element; } // Special case for backward compatibility switch ($module) { case 'fichinter': $module = 'ficheinter'; break; case 'invoice_supplier': $module = 'supplier_invoice'; break; case 'order_supplier': $module = 'supplier_order'; break; case 'recruitmentjobposition': $module = 'recruitment'; $subdirectory = '/recruitmentjobposition'; break; case 'recruitmentcandidature': $module = 'recruitment'; $subdirectory = '/recruitmentcandidature'; break; case 'knowledgerecord': $module = 'knowledgemanagement'; $subdirectory = '/knowledgerecord'; break; case 'service': case 'produit': $module = 'product'; break; case 'action': case 'actioncomm': case 'event': $module = 'agenda'; break; default: break; } // Get the relative path of directory if ($mode == 'output' || $mode == 'outputrel' || $mode == 'version') { if (isset($conf->$module) && property_exists($conf->$module, 'multidir_output')) { $s = ''; if ($mode != 'outputrel') { $s = $conf->$module->multidir_output[(empty($object->entity) ? $conf->entity : $object->entity)] . $subdirectory; } if ($forobject && $object->id > 0) { $s .= ($mode != 'outputrel' ? '/' : '') . get_exdir(0, 0, 0, 0, $object); } return $s; } elseif (isset($conf->$module) && property_exists($conf->$module, 'dir_output')) { $s = ''; if ($mode != 'outputrel') { $s = $conf->$module->dir_output . $subdirectory; } if ($forobject && $object->id > 0) { $s .= ($mode != 'outputrel' ? '/' : '') . get_exdir(0, 0, 0, 0, $object); } return $s; } else { return 'error-diroutput-not-defined-for-this-object=' . $module; } } elseif ($mode == 'temp') { if (isset($conf->$module) && property_exists($conf->$module, 'multidir_temp')) { return $conf->$module->multidir_temp[(empty($object->entity) ? $conf->entity : $object->entity)]; } elseif (isset($conf->$module) && property_exists($conf->$module, 'dir_temp')) { return $conf->$module->dir_temp; } else { return 'error-dirtemp-not-defined-for-this-object=' . $module; } } else { return 'error-bad-value-for-mode'; } } /** * Return the full path of the directory where a module (or an object of a module) stores its temporary files. * Path may depends on the entity if a multicompany module is enabled. * * @param CommonObject $object Dolibarr common object * @param string $module Override object element, for example to use 'mycompany' instead of 'societe' * @param int $forobject Return the more complete path for the given object instead of for the module only. * @return string|null The path of the relative temp directory of the module */ function getMultidirTemp($object, $module = '', $forobject = 0) { return getMultidirOutput($object, $module, $forobject, 'temp'); } /** * Return the full path of the directory where a module (or an object of a module) stores its versioned files. * Path may depends on the entity if a multicompany module is enabled. * * @param CommonObject $object Dolibarr common object * @param string $module Override object element, for example to use 'mycompany' instead of 'societe' * @param int $forobject Return the more complete path for the given object instead of for the module only. * @return string|null The path of the relative version directory of the module */ function getMultidirVersion($object, $module = '', $forobject = 0) { return getMultidirOutput($object, $module, $forobject, 'version'); } /** * Return a Dolibarr global constant string value * * @param string $key Key to return value, return $default if not set * @param string|int|float $default Value to return if not defined * @return string Value returned * @see getDolUserString() */ function getDolGlobalString($key, $default = '') { global $conf; return (string) (isset($conf->global->$key) ? $conf->global->$key : $default); } /** * Return a Dolibarr global constant int value. * The constants $conf->global->xxx are loaded by the script master.inc.php included at begin of any PHP page. * * @param string $key Key to return value, return $default if not set * @param int $default Value to return if not defined * @return int Value returned * @see getDolUserInt() */ function getDolGlobalInt($key, $default = 0) { global $conf; return (int) (isset($conf->global->$key) ? $conf->global->$key : $default); } /** * Return a Dolibarr global constant float value. * The constants $conf->global->xxx are loaded by the script master.inc.php included at begin of any PHP page. * * @param string $key Key to return value, return $default if not set * @param float $default Value to return if not defined * @return float Value returned * @see getDolUserInt() */ function getDolGlobalFloat($key, $default = 0) { global $conf; return (float) (isset($conf->global->$key) ? $conf->global->$key : $default); } /** * Return a Dolibarr global constant boolean value. * The constants $conf->global->xxx are loaded by the script master.inc.php included at begin of any PHP page. * * @param string $key Key to return value, return $default if not set * @param bool $default Value to return if not defined * @return bool Value returned */ function getDolGlobalBool($key, $default = false) { global $conf; return (bool) ($conf->global->$key ?? $default); } /** * Return the main currency ('EUR', 'USD', ...) * * @return string Value returned */ function getDolCurrency() { global $conf; return (string) $conf->currency; } /** * Return the current entity * * @return int Value returned */ function getDolEntity() { global $conf; return (int) $conf->entity; } /** * Return the current entity * * @return string Value returned */ function getDolDBType() { global $conf; return $conf->db->type; } /** * Return the default context page string * * @param string $s Page path * @return string Value returned */ function getDolDefaultContextPage($s) { return str_replace('_', '', basename(dirname($s)).basename($s, '.php')); } /** * Return Dolibarr user constant string value * * @param string $key Key to return value, return '' if not set * @param string|int|float $default Value to return * @param User $tmpuser To get another user than current user * @return string * @see getDolGlobalString() */ function getDolUserString($key, $default = '', $tmpuser = null) { if (empty($tmpuser)) { global $user; $tmpuser = $user; } return (string) (isset($tmpuser->conf->$key) ? $tmpuser->conf->$key : $default); } /** * Return Dolibarr user constant int value * * @param string $key Key to return value, return 0 if not set * @param int $default Value to return * @param User $tmpuser To get another user than current user * @return int */ function getDolUserInt($key, $default = 0, $tmpuser = null) { if (empty($tmpuser)) { global $user; $tmpuser = $user; } return (int) (isset($tmpuser->conf->$key) ? $tmpuser->conf->$key : $default); } /** * This mapping defines the conversion to the current internal * names from the alternative allowed names (including effectively deprecated * and future new names (not yet used as internal names). * * This allows to map any temporary or future name to the effective internal name. * * The value is typically the name of module's root directory. */ define( 'MODULE_MAPPING', array( // Map deprecated names to new names 'adherent' => 'member', // Has new directory 'member_type' => 'adherent_type', // No directory, but file called adherent_type 'banque' => 'bank', // Has new directory 'contrat' => 'contract', // Has new directory 'entrepot' => 'stock', // Has new directory 'projet' => 'project', // Has new directory 'categorie' => 'category', // Has old directory 'commande' => 'order', // Has old directory 'expedition' => 'shipping', // Has old directory 'facture' => 'invoice', // Has old directory 'fichinter' => 'intervention', // Has old directory 'ficheinter' => 'intervention', // Backup for 'fichinter' 'propale' => 'propal', // Has old directory 'socpeople' => 'contact', // Has old directory 'fournisseur' => 'supplier', // Has old directory 'actioncomm' => 'agenda', // NO module directory (public dir agenda) 'product_price' => 'productprice', // NO directory 'product_fournisseur_price' => 'productsupplierprice', // NO directory ) ); /** * Is Dolibarr module enabled * * @param string $module Module name to check * @return boolean True if module is enabled */ function isModEnabled($module) { global $conf; // Fix old names (map to new names) $arrayconv = MODULE_MAPPING; $arrayconvbis = array_flip(MODULE_MAPPING); if (!getDolGlobalString('MAIN_USE_NEW_SUPPLIERMOD')) { // Special cases: both use the same module. $arrayconv['supplier_order'] = 'fournisseur'; $arrayconv['supplier_invoice'] = 'fournisseur'; } $module_alt = $module; if (!empty($arrayconv[$module])) { $module_alt = $arrayconv[$module]; } $module_bis = $module; if (!empty($arrayconvbis[$module])) { $module_bis = $arrayconvbis[$module]; } return !empty($conf->modules[$module]) || !empty($conf->modules[$module_alt]) || !empty($conf->modules[$module_bis]); } /** * Return a warning delay * You can use it like this: if (getWarningDelay('module', 'paramlevel1')) * It replaces old syntax: if ($conf->module->user->rights->module->level1) * * @param string $module Module of permission to check (Example: 'bank', 'contrat', ...) * @param string $parmlevel1 Parameter level1 (Example: 'rappro', 'services', ...) * @param string $parmlevel2 Parameter level2 (Example: 'inactifs', ...) * @return int Return the warning delay */ function getWarningDelay($module, $parmlevel1, $parmlevel2 = '') { global $conf; // For compatibility with bad naming on module $moduletomoduletouse = array( 'invoice' => 'facture', ); $moduleParmsMapping = array( 'product' => 'produit', ); if (!empty($moduletomoduletouse[$module])) { $module = $moduletomoduletouse[$module]; } $warningDelayPath = $parmlevel1; if (!empty($moduleParmsMapping[$warningDelayPath])) { $warningDelayPath = $moduleParmsMapping[$warningDelayPath]; } if ($parmlevel2) { if (!empty($conf->$module) && !empty($conf->$module->$warningDelayPath) && !empty($conf->$module->$warningDelayPath->$parmlevel2) && !empty($conf->$module->$warningDelayPath->$parmlevel2->warning_delay)) { return (int) $conf->$module->$warningDelayPath->$parmlevel2->warning_delay; } } else { if (!empty($conf->$module) && !empty($conf->$module->$warningDelayPath) && !empty($conf->$module->$warningDelayPath->warning_delay)) { return (int) $conf->$module->$warningDelayPath->warning_delay; } } return 0; } /** * isDolTms check if a timestamp is valid. * * @param int|string|null $timestamp timestamp to check * @return bool */ function isDolTms($timestamp) { if ($timestamp === '') { dol_syslog('Using empty string for a timestamp is deprecated, prefer use of null when calling page ' . $_SERVER["PHP_SELF"] . getCallerInfoString(), LOG_NOTICE); return false; } if (is_null($timestamp) || !is_numeric($timestamp)) { return false; } return true; } /** * Return a DoliDB instance (database handler). * * @param string $type Type of database (mysql, pgsql...) * @param string $host Address of database server * @param string $user Authorized username * @param string $pass Password (clear) * @param string $name Name of database * @param int $port Port of database server * @return DoliDB A DoliDB instance */ function getDoliDBInstance($type, $host, $user, $pass, $name, $port) { require_once DOL_DOCUMENT_ROOT . "/core/db/" . $type . '.class.php'; $class = 'DoliDB' . ucfirst($type); $db = new $class($type, $host, $user, $pass, $name, $port); return $db; } /** * Get list of entity id to use. * * @param string $element Current element * 'societe', 'socpeople', 'actioncomm', 'agenda', 'resource', * 'product', 'productprice', 'stock', 'bom', 'mo', * 'propal', 'supplier_proposal', 'invoice', 'supplier_invoice', 'payment_various', * 'categorie', 'bank_account', 'bank_account', 'adherent', 'user', * 'commande', 'supplier_order', 'expedition', 'intervention', 'survey', * 'contract', 'tax', 'expensereport', 'holiday', 'multicurrency', 'project', * 'email_template', 'event', 'donation' * 'c_paiement', 'c_payment_term', ... * @param int<0,1> $shared 0=Return id of current entity only, * 1=Return id of current entity + shared entities (default) * @param ?CommonObject $currentobject Current object if needed * @return string Entity id(s) to use ( eg. entity IN ('.getEntity(elementname).')' ) */ function getEntity($element, $shared = 1, $currentobject = null) { global $conf, $mc, $hookmanager, $object, $action, $db; if (!is_object($hookmanager)) { include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php'; $hookmanager = new HookManager($db); } // fix different element names (France to English) switch ($element) { case 'projet': $element = 'project'; break; case 'contrat': $element = 'contract'; break; // "/contrat/class/contrat.class.php" case 'order_supplier': $element = 'supplier_order'; break; // "/fourn/class/fournisseur.commande.class.php" case 'invoice_supplier': $element = 'supplier_invoice'; break; // "/fourn/class/fournisseur.facture.class.php" } if (is_object($mc)) { $out = $mc->getEntity($element, $shared, $currentobject); } else { $out = ''; $addzero = array('user', 'usergroup', 'cronjob', 'c_email_templates', 'email_template', 'default_values', 'overwrite_trans'); if (getDolGlobalString('HOLIDAY_ALLOW_ZERO_IN_DIC')) { // this constant break the dictionary admin without Multicompany $addzero[] = 'c_holiday_types'; } if (in_array($element, $addzero)) { $out .= '0,'; } $out .= ((int) $conf->entity); } // Manipulate entities to query on the fly $parameters = array( 'element' => $element, 'shared' => $shared, 'object' => $object, 'currentobject' => $currentobject, 'out' => $out ); // @phan-suppress-next-line PhanTypeMismatchArgumentNullable $reshook = $hookmanager->executeHooks('hookGetEntity', $parameters, $currentobject, $action); // Note that $action and $object may have been modified by some hooks if (is_numeric($reshook)) { if ($reshook == 0 && !empty($hookmanager->resPrint)) { $out .= ',' . $hookmanager->resPrint; // add } elseif ($reshook == 1) { $out = $hookmanager->resPrint; // replace } } return $out; } /** * Set entity id to use when to create an object * * @param CommonObject $currentobject Current object * @return int Entity id to use ( eg. entity = '.setEntity($object) ) */ function setEntity($currentobject) { global $conf, $mc; if (is_object($mc) && method_exists($mc, 'setEntity')) { return $mc->setEntity($currentobject); } else { return ((is_object($currentobject) && $currentobject->id > 0 && ((int) $currentobject->entity) > 0) ? (int) $currentobject->entity : $conf->entity); } } /** * Return if string has a name dedicated to store a secret * * @param string $keyname Name of key to test * @return boolean True if key is used to store a secret */ function isASecretKey($keyname) { return preg_match('/(_pass|password|_pw|_key|securekey|serverkey|secret\d?|p12key|exportkey|_PW_[a-z]+|token)$/i', $keyname); } /** * Return a numeric value into an Excel like column number. So 0 return 'A', 1 returns 'B'..., 26 return 'AA' * * @param int|string $n Numeric value * @return string Column in Excel format */ function num2Alpha($n) { $r = ''; for ($r = ""; $n >= 0; $n = intval($n / 26) - 1) { $r = chr($n % 26 + 0x41) . $r; } return $r; } /** * Return information about user browser * * Returns array with the following format: * array( * 'browsername' => Browser name (firefox|chrome|iceweasel|epiphany|safari|opera|ie|unknown) * 'browserversion' => Browser version. Empty if unknown * 'browseros' => Set with mobile OS (android|blackberry|ios|palm|symbian|webos|maemo|windows|unknown) * 'layout' => (tablet|phone|classic) * 'phone' => empty if not mobile, (android|blackberry|ios|palm|unknown) if mobile * 'tablet' => true/false * ) * * @param string $user_agent Content of $_SERVER["HTTP_USER_AGENT"] variable * @return array{browsername:string,browserversion:string,browseros:string,browserua:string,layout:string,phone:string,tablet:bool} Check function documentation */ function getBrowserInfo($user_agent) { include_once DOL_DOCUMENT_ROOT . '/includes/mobiledetect/mobiledetectlib/Mobile_Detect.php'; $name = 'unknown'; $version = ''; $os = 'unknown'; $phone = ''; $user_agent = substr($user_agent, 0, 512); // Avoid to process too large user agent $detectmobile = new Mobile_Detect(null, $user_agent); $tablet = $detectmobile->isTablet(); if ($detectmobile->isMobile()) { $phone = 'unknown'; // If phone/smartphone, we set phone os name. if ($detectmobile->is('AndroidOS')) { $os = $phone = 'android'; } elseif ($detectmobile->is('BlackBerryOS')) { $os = $phone = 'blackberry'; } elseif ($detectmobile->is('iOS')) { $os = 'ios'; $phone = 'iphone'; } elseif ($detectmobile->is('PalmOS')) { $os = $phone = 'palm'; } elseif ($detectmobile->is('SymbianOS')) { $os = 'symbian'; } elseif ($detectmobile->is('webOS')) { $os = 'webos'; } elseif ($detectmobile->is('MaemoOS')) { $os = 'maemo'; } elseif ($detectmobile->is('WindowsMobileOS') || $detectmobile->is('WindowsPhoneOS')) { $os = 'windows'; } } // OS if (preg_match('/linux/i', $user_agent)) { $os = 'linux'; } elseif (preg_match('/macintosh/i', $user_agent)) { $os = 'macintosh'; } elseif (preg_match('/windows/i', $user_agent)) { $os = 'windows'; } // Name $reg = array(); if (preg_match('/firefox(\/|\s)([\d\.]*)/i', $user_agent, $reg)) { $name = 'firefox'; $version = empty($reg[2]) ? '' : $reg[2]; } elseif (preg_match('/edge(\/|\s)([\d\.]*)/i', $user_agent, $reg)) { $name = 'edge'; $version = empty($reg[2]) ? '' : $reg[2]; } elseif (preg_match('/chrome(\/|\s)([\d\.]+)/i', $user_agent, $reg)) { $name = 'chrome'; $version = empty($reg[2]) ? '' : $reg[2]; } elseif (preg_match('/chrome/i', $user_agent, $reg)) { // we can have 'chrome (Mozilla...) chrome x.y' in one string $name = 'chrome'; } elseif (preg_match('/iceweasel/i', $user_agent)) { $name = 'iceweasel'; } elseif (preg_match('/epiphany/i', $user_agent)) { $name = 'epiphany'; } elseif (preg_match('/safari(\/|\s)([\d\.]*)/i', $user_agent, $reg)) { $name = 'safari'; $version = empty($reg[2]) ? '' : $reg[2]; } elseif (preg_match('/opera(\/|\s)([\d\.]*)/i', $user_agent, $reg)) { // Safari is often present in string for mobile but its not. $name = 'opera'; $version = empty($reg[2]) ? '' : $reg[2]; } elseif (preg_match('/(MSIE\s([0-9]+\.[0-9]))|.*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) { $name = 'ie'; $version = end($reg); } elseif (preg_match('/(Windows NT\s([0-9]+\.[0-9])).*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) { // MS products at end $name = 'ie'; $version = end($reg); } elseif (preg_match('/l[iy]n(x|ks)(\(|\/|\s)*([\d\.]+)/i', $user_agent, $reg)) { // MS products at end $name = 'textbrowser'; $version = empty($reg[3]) ? '' : $reg[3]; } elseif (preg_match('/w3m\/([\d\.]+)/i', $user_agent, $reg)) { // MS products at end $name = 'textbrowser'; $version = empty($reg[1]) ? '' : $reg[1]; } if ($tablet) { $layout = 'tablet'; } elseif ($phone) { $layout = 'phone'; } else { $layout = 'classic'; } return array( 'browsername' => $name, 'browserversion' => $version, 'browseros' => $os, 'browserua' => $user_agent, 'layout' => $layout, // tablet, phone, classic 'phone' => $phone, // deprecated 'tablet' => $tablet // deprecated ); } /** * Function called at end of web php process * * @return void */ function dol_shutdown() { global $db; $disconnectdone = false; $depth = 0; if (is_object($db) && !empty($db->connected)) { $depth = $db->transaction_opened; $disconnectdone = $db->close(); } 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)); } /** * Return true if we are in a context of submitting the parameter $paramname from a POST of a form. * Warning: * 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. * For action=update, use: $var = GETPOSTISSET('var') ? GETPOST('var') : $object->var; * * @param string $paramname Name or parameter to test * @return boolean True if we have just submit a POST or GET request with the parameter provided (even if param is empty) */ function GETPOSTISSET($paramname) { $isset = false; $relativepathstring = $_SERVER["PHP_SELF"]; // Clean $relativepathstring if (constant('DOL_URL_ROOT')) { $relativepathstring = preg_replace('/^' . preg_quote(constant('DOL_URL_ROOT'), '/') . '/', '', $relativepathstring); } $relativepathstring = ltrim($relativepathstring, '/'); $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring); // Code for search criteria persistence. // Retrieve values if restore_lastsearch_values if (!empty($_GET['restore_lastsearch_values'])) { // Use $_GET here and not GETPOST if (!empty($_SESSION['lastsearch_values_' . $relativepathstring])) { // If there is saved values $tmp = json_decode($_SESSION['lastsearch_values_' . $relativepathstring], true); if (is_array($tmp)) { foreach ($tmp as $key => $val) { if ($key == $paramname) { // We are on the requested parameter $isset = true; break; } } } } // If there is saved contextpage, limit, page or mode if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_' . $relativepathstring])) { $isset = true; } elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_' . $relativepathstring])) { $isset = true; } elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_' . $relativepathstring])) { $isset = true; } elseif ($paramname == 'mode' && !empty($_SESSION['lastsearch_mode_' . $relativepathstring])) { $isset = true; } } else { $isset = (isset($_POST[$paramname]) || isset($_GET[$paramname])); // We must keep $_POST and $_GET here } return $isset; } /** * Return true if the parameter $paramname is submit from a POST OR GET as an array. * Can be used before GETPOST to know if the $check param of GETPOST need to check an array or a string * * @param string $paramname Name or parameter to test * @param int<0,3> $method Type of method (0 = get then post, 1 = only get, 2 = only post, 3 = post then get) * @return bool True if we have just submit a POST or GET request with the parameter provided (even if param is empty) */ function GETPOSTISARRAY($paramname, $method = 0) { // for $method test need return the same $val as GETPOST if (empty($method)) { $val = isset($_GET[$paramname]) ? $_GET[$paramname] : (isset($_POST[$paramname]) ? $_POST[$paramname] : ''); } elseif ($method == 1) { $val = isset($_GET[$paramname]) ? $_GET[$paramname] : ''; } elseif ($method == 2) { $val = isset($_POST[$paramname]) ? $_POST[$paramname] : ''; } elseif ($method == 3) { $val = isset($_POST[$paramname]) ? $_POST[$paramname] : (isset($_GET[$paramname]) ? $_GET[$paramname] : ''); } else { $val = 'BadFirstParameterForGETPOST'; } return is_array($val); } /** * Return the value of a $_GET or $_POST supervariable, converted into integer. * Use the property $user->default_values[path]['creatform'] and/or $user->default_values[path]['filters'] and/or $user->default_values[path]['sortorder'] * Note: The property $user->default_values is loaded by main.php when loading the user. * * @param string $paramname Name of the $_GET or $_POST parameter * @param int<0,3> $method Type of method (0 = $_GET then $_POST, 1 = only $_GET, 2 = only $_POST, 3 = $_POST then $_GET) * @return int Value converted into integer */ function GETPOSTINT($paramname, $method = 0) { return (int) GETPOST($paramname, 'int', $method, null, null, 0); } /** * Return the value of a $_GET or $_POST supervariable, converted into float. * Warning: This function assumes by default that the input is a number entered by end user in user format in local language (with possible thousands separator and decimal separator). * If it is not the case, use the parameter $option = 1 instead. * * @param string $paramname Name of the $_GET or $_POST parameter * @param ''|'MU'|'MT'|'MS'|'CU'|'CT'|int $rounding Type of rounding ('', 'MU', 'MT, 'MS', 'CU', 'CT', integer) {@see price2num()} * @param int<0,2> $option Put 1 if you know that content is already universal format number (so no correction on decimal will be done) * Put 2 if you know that number is a user input (so we know we have to fix decimal separator). * Use 0 if unknown (never use this anymore, automatic detection is not reliable with some languages). * @return float Value converted into float * @since Dolibarr V20 */ function GETPOSTFLOAT($paramname, $rounding = '', $option = 2) { // price2num() can be used to round to an expected accuracy and/or to sanitize any valid user input (such as "1 234.5", "1 234,5", "1'234,5", "1·234,5", "1,234.5", etc.) return (float) price2num(GETPOST($paramname), $rounding, $option); } /** * Helper function that combines values of a dolibarr DatePicker (such as Form::selectDate) for year, month, day (and * optionally hour, minute, second) fields to return a timestamp. * * @param string $prefix Prefix used to build the date selector (for instance using Form::selectDate). Example: 'select_datec' * @param string $hourTime 'getpost' or 'getpostend' to include hour, minute, second values from the HTTP request, * or 'XX:YY:ZZ' to set hour, minute, second respectively, for example '23:59:59' * or 'end' means '23:59:59' * or '' means '00:00:00' (default) * @param int|string $gm Passed to dol_mktime. In most cases, when used with 'getpost' or 'getpostend', it should be 'tzuserrel'. Use 'auto' if you need dates related to 'tzserver' (like in accountancy). * @param string $saverestore Use a string family context to save retrieved date so it will be used on the next retrieval for the same family context (if value not already defined in parameters). * @return int|string Date as a timestamp, '' or false if error * * @see dol_mktime() */ function GETPOSTDATE($prefix, $hourTime = '', $gm = 'auto', $saverestore = '') { $m = array(); if ($hourTime === 'getpost' || $hourTime === 'getpostend') { $hour = (GETPOSTISSET($prefix . 'hour') && GETPOSTINT($prefix . 'hour') >= 0) ? GETPOSTINT($prefix . 'hour') : ($hourTime === 'getpostend' ? 23 : 0); $minute = (GETPOSTISSET($prefix . 'min') && GETPOSTINT($prefix . 'min') >= 0) ? GETPOSTINT($prefix . 'min') : ($hourTime === 'getpostend' ? 59 : 0); $second = (GETPOSTISSET($prefix . 'sec') && GETPOSTINT($prefix . 'sec') >= 0) ? GETPOSTINT($prefix . 'sec') : ($hourTime === 'getpostend' ? 59 : 0); } elseif (preg_match('/^(\d\d):(\d\d):(\d\d)$/', $hourTime, $m)) { $hour = intval($m[1]); $minute = intval($m[2]); $second = intval($m[3]); } elseif ($hourTime === 'end') { $hour = 23; $minute = 59; $second = 59; } else { $hour = $minute = $second = 0; } if ( $saverestore && !GETPOSTISSET($prefix . 'day') && !GETPOSTISSET($prefix . 'month') && !GETPOSTISSET($prefix . 'year') && isset($_SESSION['DOLDATE_' . $saverestore . '_day']) && isset($_SESSION['DOLDATE_' . $saverestore . '_month']) && isset($_SESSION['DOLDATE_' . $saverestore . '_year']) ) { $day = $_SESSION['DOLDATE_' . $saverestore . '_day']; $month = $_SESSION['DOLDATE_' . $saverestore . '_month']; $year = $_SESSION['DOLDATE_' . $saverestore . '_year']; } else { $month = GETPOSTINT($prefix . 'month'); $day = GETPOSTINT($prefix . 'day'); $year = GETPOSTINT($prefix . 'year'); } // normalize out of range values $hour = (int) min($hour, 23); $minute = (int) min($minute, 59); $second = (int) min($second, 59); if ($saverestore) { $_SESSION['DOLDATE_' . $saverestore . '_day'] = $day; $_SESSION['DOLDATE_' . $saverestore . '_month'] = $month; $_SESSION['DOLDATE_' . $saverestore . '_year'] = $year; } //print "$hour, $minute, $second, $month, $day, $year, $gm
"; return dol_mktime($hour, $minute, $second, $month, $day, $year, $gm); } /** * Return value of a param into GET or POST supervariable. * Use the property $user->default_values[path]['createform'] and/or $user->default_values[path]['filters'] and/or $user->default_values[path]['sortorder'] * Note: The property $user->default_values is loaded by main.php when loading the user. * * @param string $paramname Name of parameter to found * @param 'int'|'intcomma'|'array'|'array:int'|'array:intcomma'|'array:alpha'|'array:alphanohtml'|'array:aZ09'|'array:restricthtml'|'password'|'email'|'alpha'|'alphanohtml'|'nohtml'|'restricthtml'|'alphawithlgt'|'aZ09'|'aZ'|'aZ09arobase'|'aZ09comma'|'url'|'san_alpha'|'custom'|'none'|'restricthtmlallowclass'|'restricthtmlallowunvalid'|'restricthtmlallowiframe'|'restricthtmlallowlinkscript'|'' $check Type of check * '' or 'none'=no check (deprecated) * 'password'=allow characters for a password * 'email'=allow characters for an email "email@domain.com" * 'url'=allow characters for an url * 'array', 'array:restricthtml' or 'array:aZ09' to check it's an array * 'int'=check it's numeric (integer or float) * 'intcomma'=check it's integer+comma ('1,2,3,4...') * 'alphanohtml'=check there is no html content and no " and no ../ ('alpha' is an alias of 'alphanohtml') * 'alphawithlgt'=alpha with lgt and no " and no ../ (Can be used for email string like "Name ") * 'aZ'=check it's a-Z only * 'aZ09'=check it's simple alpha string (recommended for keys, it includes a-Z0-9_\-\.) * 'aZ09arobase'=check it's a string for an element type ('myobject@mymodule') * 'aZ09comma'=check it's a string for a sortfield or sortorder * 'san_alpha'=Use filter_var with FILTER_SANITIZE_STRING (do not use this for free text string) * 'nohtml'=check there is no html content * 'restricthtml'=check html content is restricted to some tags only * 'custom'= custom filter specify $filter and $options) * 'restricthtmlallowclass' * 'restricthtmlallowunvalid' * 'restricthtmlallowiframe' * 'restricthtmlallowlinkscript' * @param int $method Type of method (0 = get then post, 1 = only get, 2 = only post, 3 = post then get) * @param ?int $filter Filter to apply when $check is set to 'custom'. (See http://php.net/manual/en/filter.filters.php for détails) * @param mixed $options Options to pass to filter_var when $check is set to 'custom' * @param int $noreplace Force disable of replacement of __xxx__ strings. * @return string|array Value found (string or array), or '' if check fails * @phpstan-return ( * $check is 'int' ? numeric-string|'' : * $check is 'array:int' ? numeric-string[]|array{} : * $check is 'array' | 'array:aZ09' | 'array:alpha' | 'array:intcomma' | 'array:restricthtml' ? string[] : * $check is 'alpha' | 'aZ' | 'aZ09' | 'aZ09arobase' | 'aZ09comma' | 'password' | 'email' | 'url' | 'alphanohtml' |'nohtml' | 'restricthtml' | 'alphawithlgt' | 'intcomma' | 'restricthtmlallowclass' | 'restricthtmlallowunvalid' | 'restricthtmlallowiframe' | 'restricthtmlallowlinkscript' ? string : string|array * ) */ function GETPOST($paramname, $check = 'alphanohtml', $method = 0, $filter = null, $options = null, $noreplace = 0) { global $mysoc, $user, $conf; if (empty($paramname)) { // Explicit test for null for phan. return 'BadFirstParameterForGETPOST'; } if (empty($check)) { 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); // Enable this line to know who call the GETPOST with '' $check parameter. //var_dump(getCallerInfoString()); } if (empty($method)) { $out = isset($_GET[$paramname]) ? $_GET[$paramname] : (isset($_POST[$paramname]) ? $_POST[$paramname] : ''); } elseif ($method == 1) { $out = isset($_GET[$paramname]) ? $_GET[$paramname] : ''; } elseif ($method == 2) { $out = isset($_POST[$paramname]) ? $_POST[$paramname] : ''; } elseif ($method == 3) { $out = isset($_POST[$paramname]) ? $_POST[$paramname] : (isset($_GET[$paramname]) ? $_GET[$paramname] : ''); } else { return 'BadThirdParameterForGETPOST'; } $relativepathstring = ''; // For static analysis - looks possibly undefined if not set. if (empty($method) || $method == 3 || $method == 4) { $relativepathstring = (empty($_SERVER["PHP_SELF"]) ? '' : $_SERVER["PHP_SELF"]); // Clean $relativepathstring if (constant('DOL_URL_ROOT')) { $relativepathstring = preg_replace('/^' . preg_quote(constant('DOL_URL_ROOT'), '/') . '/', '', $relativepathstring); } $relativepathstring = ltrim($relativepathstring, '/'); $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring); // Code for search criteria persistence. // Retrieve saved values if restore_lastsearch_values is set if (!empty($_GET['restore_lastsearch_values'])) { // Use $_GET here and not GETPOST if (!empty($_SESSION['lastsearch_values_' . $relativepathstring])) { // If there is saved values $tmp = json_decode($_SESSION['lastsearch_values_' . $relativepathstring], true); if (is_array($tmp)) { foreach ($tmp as $key => $val) { if ($key == $paramname) { // We are on the requested parameter $out = $val; break; } } } } // If there is saved contextpage, page or limit if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_' . $relativepathstring])) { $out = $_SESSION['lastsearch_contextpage_' . $relativepathstring]; } elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_' . $relativepathstring])) { $out = $_SESSION['lastsearch_limit_' . $relativepathstring]; } elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_' . $relativepathstring])) { $out = $_SESSION['lastsearch_page_' . $relativepathstring]; } elseif ($paramname == 'mode' && !empty($_SESSION['lastsearch_mode_' . $relativepathstring])) { $out = $_SESSION['lastsearch_mode_' . $relativepathstring]; } } elseif (!isset($_GET['sortfield'])) { // Else, retrieve default values if we are not doing a sort // 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 if (!empty($_GET['action']) && $_GET['action'] == 'create' && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) { // Search default value from $object->field global $object; '@phan-var-force CommonObject $object'; // Suppose it's a CommonObject for analysis, but other objects have the $fields field as well if (is_object($object) && isset($object->fields[$paramname]['default'])) { // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset $out = $object->fields[$paramname]['default']; } } if (getDolGlobalString('MAIN_ENABLE_DEFAULT_VALUES')) { if (!empty($_GET['action']) && (preg_match('/^create/', $_GET['action']) || preg_match('/^presend/', $_GET['action'])) && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) { // Now search in setup to overwrite default values if (!empty($user->default_values)) { // $user->default_values defined from menu 'Setup - Default values' if (isset($user->default_values[$relativepathstring]['createform'])) { foreach ($user->default_values[$relativepathstring]['createform'] as $defkey => $defval) { $qualified = 0; if ($defkey != '_noquery_') { $tmpqueryarraytohave = explode('&', $defkey); $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING'])); $foundintru = 0; foreach ($tmpqueryarraytohave as $tmpquerytohave) { if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) { $foundintru = 1; } } if (!$foundintru) { $qualified = 1; } } else { $qualified = 1; } if ($qualified) { if (isset($user->default_values[$relativepathstring]['createform'][$defkey][$paramname])) { $out = $user->default_values[$relativepathstring]['createform'][$defkey][$paramname]; break; } } } } } } elseif (!empty($paramname) && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) { // Management of default search_filters and sort order if (!empty($user->default_values)) { // $user->default_values defined from menu 'Setup - Default values' //var_dump($user->default_values[$relativepathstring]); if ($paramname == 'sortfield' || $paramname == 'sortorder') { // Sorted on which fields ? ASC or DESC ? if (isset($user->default_values[$relativepathstring]['sortorder'])) { // Even if paramname is sortfield, data are stored into ['sortorder...'] foreach ($user->default_values[$relativepathstring]['sortorder'] as $defkey => $defval) { $qualified = 0; if ($defkey != '_noquery_') { $tmpqueryarraytohave = explode('&', $defkey); $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING'])); $foundintru = 0; foreach ($tmpqueryarraytohave as $tmpquerytohave) { if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) { $foundintru = 1; } } if (!$foundintru) { $qualified = 1; } } else { $qualified = 1; } if ($qualified) { $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and , foreach ($user->default_values[$relativepathstring]['sortorder'][$defkey] as $key => $val) { if ($out) { $out .= ', '; } if ($paramname == 'sortfield') { $out .= dol_string_nospecial($key, '', $forbidden_chars_to_replace); } if ($paramname == 'sortorder') { $out .= dol_string_nospecial($val, '', $forbidden_chars_to_replace); } } //break; // No break for sortfield and sortorder so we can cumulate fields (is it really useful ?) } } } } elseif (isset($user->default_values[$relativepathstring]['filters'])) { foreach ($user->default_values[$relativepathstring]['filters'] as $defkey => $defval) { // $defkey is a querystring like 'a=b&c=d', $defval is key of user if (!empty($_GET['disabledefaultvalues'])) { // If set of default values has been disabled by a request parameter continue; } $qualified = 0; if ($defkey != '_noquery_') { $tmpqueryarraytohave = explode('&', $defkey); $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING'])); $foundintru = 0; foreach ($tmpqueryarraytohave as $tmpquerytohave) { if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) { $foundintru = 1; } } if (!$foundintru) { $qualified = 1; } } else { $qualified = 1; } if ($qualified && isset($user->default_values[$relativepathstring]['filters'][$defkey][$paramname])) { // We must keep $_POST and $_GET here if (isset($_POST['search_all']) || isset($_GET['search_all'])) { // We made a search from quick search menu, do we still use default filter ? if (!getDolGlobalString('MAIN_DISABLE_DEFAULT_FILTER_FOR_QUICK_SEARCH')) { $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and , $out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace); } } else { $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and , $out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace); } break; } } } } } } } } // 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) // Example of variables: __DAY__, __MONTH__, __YEAR__, __MYCOMPANY_COUNTRY_ID__, __USER_ID__, ... // 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. '@phan-var-force string $paramname'; if (!is_array($out) && empty($_POST[$paramname]) && empty($noreplace)) { $reg = array(); $regreplace = array(); $maxloop = 20; $loopnb = 0; // Protection against infinite loop while (preg_match('/__([A-Z0-9]+(?:_[A-Z0-9]+){0,3})__/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. $loopnb++; $newout = ''; if ($reg[1] == 'DAY') { $tmp = dol_getdate(dol_now(), true); $newout = $tmp['mday']; } elseif ($reg[1] == 'MONTH') { $tmp = dol_getdate(dol_now(), true); $newout = $tmp['mon']; } elseif ($reg[1] == 'YEAR') { $tmp = dol_getdate(dol_now(), true); $newout = $tmp['year']; } elseif ($reg[1] == 'PREVIOUS_DAY') { $tmp = dol_getdate(dol_now(), true); $tmp2 = dol_get_prev_day($tmp['mday'], $tmp['mon'], $tmp['year']); $newout = $tmp2['day']; } elseif ($reg[1] == 'PREVIOUS_MONTH') { $tmp = dol_getdate(dol_now(), true); $tmp2 = dol_get_prev_month($tmp['mon'], $tmp['year']); $newout = $tmp2['month']; } elseif ($reg[1] == 'PREVIOUS_YEAR') { $tmp = dol_getdate(dol_now(), true); $newout = ($tmp['year'] - 1); } elseif ($reg[1] == 'NEXT_DAY') { $tmp = dol_getdate(dol_now(), true); $tmp2 = dol_get_next_day($tmp['mday'], $tmp['mon'], $tmp['year']); $newout = $tmp2['day']; } elseif ($reg[1] == 'NEXT_MONTH') { $tmp = dol_getdate(dol_now(), true); $tmp2 = dol_get_next_month($tmp['mon'], $tmp['year']); $newout = $tmp2['month']; } elseif ($reg[1] == 'NEXT_YEAR') { $tmp = dol_getdate(dol_now(), true); $newout = ($tmp['year'] + 1); } elseif ($reg[1] == 'MYCOMPANY_COUNTRY_ID' || $reg[1] == 'MYCOUNTRY_ID' || $reg[1] == 'MYCOUNTRYID') { $newout = $mysoc->country_id; } elseif ($reg[1] == 'USER_ID' || $reg[1] == 'USERID') { $newout = $user->id; } elseif ($reg[1] == 'USER_SUPERVISOR_ID' || $reg[1] == 'SUPERVISOR_ID' || $reg[1] == 'SUPERVISORID') { $newout = $user->fk_user; } elseif ($reg[1] == 'ENTITY_ID' || $reg[1] == 'ENTITYID') { $newout = $conf->entity; } elseif ($reg[1] == 'ID') { $newout = '__ID__'; // We keep __ID__ we find into backtopage url } else { $newout = 'REGREPLACE_' . $loopnb; // Key not found, we replace with temporary string to reload later $regreplace[$loopnb] = $reg[0]; } //var_dump('__'.$reg[1].'__ -> '.$newout); $out = preg_replace('/__' . preg_quote($reg[1], '/') . '__/', $newout, $out); } if (!empty($regreplace)) { foreach ($regreplace as $key => $value) { $out = preg_replace('/REGREPLACE_' . $key . '/', $value, $out); } } } // Check type of variable and make sanitization according to this if (preg_match('/^array/', $check)) { // If 'array' or 'array:restricthtml' or 'array:aZ09' or 'array:intcomma' $tmpcheck = 'alphanohtml'; if (empty($out)) { $out = array(); } elseif (!is_array($out)) { $out = explode(',', $out); } else { $tmparray = explode(':', $check); if (!empty($tmparray[1])) { $tmpcheck = $tmparray[1]; } } foreach ($out as $outkey => $outval) { $out[$outkey] = sanitizeVal($outval, $tmpcheck, $filter, $options); } } else { // If field name is 'search_xxx' then we force the add of space after each < and > (when following char is numeric) because it means // 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 if (strpos($paramname, 'search_') === 0) { $out = preg_replace('/([<>])([-+]?\d)/', '\1 \2', $out); } // @phan-suppress-next-line UnknownSanitizeType $out = sanitizeVal($out, $check, $filter, $options); } // Sanitizing for special parameters. // Note: There is no reason to allow the backtopage/backtopageforcancel/backtopagejs, backtolist or backtourl parameter to contains an external URL. Only relative URLs are allowed. // @TODO Merge backtopage with backtourl // @TODO Rename backtolist into backtopagelist if (preg_match('/^backto/i', $paramname)) { $out = str_replace('\\', '/', $out); // Can be before the loop because only 1 char is replaced. No risk to get it after other replacements. $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. do { $oldstringtoclean = $out; $out = str_ireplace(array('javascript', 'vbscript', '&colon', '&#'), '', $out); $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' $out = preg_replace(array('/^[a-z]*\/\s*\/+/i'), '', $out); // We remove schema*// to remove external URL } while ($oldstringtoclean != $out); } // Code for search criteria persistence. // Save data into session if key start with 'search_' if (empty($method) || $method == 3 || $method == 4) { if (preg_match('/^search_/', $paramname) || in_array($paramname, array('sortorder', 'sortfield'))) { //var_dump($paramname.' - '.$out.' '.$user->default_values[$relativepathstring]['filters'][$paramname]); // We save search key only if $out not empty that means: // - posted value not empty, or // - 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). if ($out != '' && isset($user)) { // $out = '0' or 'abc', it is a search criteria to keep $user->lastsearch_values_tmp[$relativepathstring][$paramname] = $out; } } } return $out; } /** * Return a sanitized or empty value after checking value against a rule. * * @param string|mixed[]|null $out Value to check/clear. * @param string $check Type of check/sanitizing * @param ?int $filter Filter to apply when $check is set to 'custom' (deprecated). (See http://php.net/manual/en/filter.filters.php for détails) * @param ?mixed $options Options to pass to filter_var when $check is set to 'custom' * @return string|array Value sanitized (string or array). It may be '' if format check fails. */ function sanitizeVal($out = '', $check = 'alphanohtml', $filter = null, $options = null) { // TODO : use class "Validate" to perform tests (and add missing tests) if needed for factorize // Check is done after replacement if ($out === null) { $out = ''; } switch ($check) { case 'none': case 'password': break; case 'int': // Check param is a numeric value (integer but also float or hexadecimal) if (!is_numeric($out)) { $out = ''; } break; case 'intcomma': if (is_array($out)) { $out = implode(',', $out); } if (preg_match('/[^0-9,-]+/i', $out)) { $out = ''; } break; case 'san_alpha': dol_syslog("Use of parameter value 'san_alpha' in GETPOST is deprecated. Use 'alphanohtml', 'aZ09comma', ...", LOG_WARNING); $out = filter_var($out, FILTER_SANITIZE_STRING); break; case 'email': $out = filter_var($out, FILTER_SANITIZE_EMAIL); break; case 'url': //$out = filter_var($out, FILTER_SANITIZE_URL); // Not reliable, replaced with FILTER_VALIDATE_URL $out = preg_replace('/[^:\/\[\]a-z0-9@\$\'\*\~\.\-_,;\?\!=%&+#]+/i', '', $out); // TODO Allow ( ) but only into password of https://login:password@domain... break; case 'aZ': if (!is_array($out)) { $out = trim($out); if (preg_match('/[^a-z]+/i', $out)) { $out = ''; } } break; case 'aZ09': if (!is_array($out)) { $out = trim($out); if (preg_match('/[^a-z0-9_\-\.]+/i', $out)) { $out = ''; } } break; case 'aZ09arobase': // great to sanitize $objecttype parameter if (!is_array($out)) { $out = trim($out); if (preg_match('/[^a-z0-9_\-\.@]+/i', $out)) { $out = ''; } } break; case 'aZ09comma': // great to sanitize $sortfield or $sortorder params that can be 't.abc,t.def_gh' if (!is_array($out)) { $out = trim($out); if (preg_match('/[^a-z0-9_\-\.,]+/i', $out)) { $out = ''; } } break; case 'alpha': // No html and no ../ and " case 'alphanohtml': // Recommended for most scalar parameters and search parameters. Not valid for json string. if (!is_array($out)) { $out = trim($out); do { $oldstringtoclean = $out; // Remove html tags $out = dol_string_nohtmltag($out, 0); // 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). $out = preg_replace('/\\\([0-9xu])/', '/\1', $out); // Remove also other dangerous string sequences // '../' or '..\' is dangerous because it allows dir transversals // '&', '&', '&'... is a the char '&' alone but there is no reason to accept such way to encode input char // '"' = '"' = '"' = '"' is dangerous because param in url can close the href= or src= and add javascript functions. // '/', '/', '/' is the char '/' but there is no reason to accept such way to encode this input char // '\' = '\' = '\' is the char '\' but there is no reason to accept such way to encode this input char $out = str_ireplace(array('../', '..\\', '&', '&', '&', '"', '"', '"', '"', '"', '/', '/', '/', '\', '\', '\'), '', $out); } while ($oldstringtoclean != $out); // keep lines feed } break; case 'alphawithlgt': // No " and no ../ but we keep balanced < > tags with no special chars inside. Can be used for email string like "Name ". Less secured than 'alphanohtml' if (!is_array($out)) { $out = trim($out); do { $oldstringtoclean = $out; // Decode html entities $out = dol_html_entity_decode($out, ENT_COMPAT | ENT_HTML5, 'UTF-8'); // 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). $out = preg_replace('/\\\([0-9xu])/', '/\1', $out); // Remove also other dangerous string sequences // '../' or '..\' is dangerous because it allows dir transversals // '&', '&', '&'... is a the char '&' alone but there is no reason to accept such way to encode input char // '"' = '"' = '"' = '"' is dangerous because param in url can close the href= or src= and add javascript functions. // '/', '/', '/' is the char '/' but there is no reason to accept such way to encode this input char // '\' = '\' = '\' is the char '\' but there is no reason to accept such way to encode this input char $out = str_ireplace(array('../', '..\\', '&', '&', '&', '"', '"', '"', '"', '"', '/', '/', '/', '\', '\', '\'), '', $out); } while ($oldstringtoclean != $out); } break; case 'nohtml': // No html. Valid for JSON strings. $out = dol_string_nohtmltag($out, 0); break; case 'restricthtmlnolink': case 'restricthtml': // Recommended for most html textarea case 'restricthtmlallowclass': case 'restricthtmlallowiframe': case 'restricthtmlallowlinkscript': // Allow link and script tag for head section. case 'restricthtmlallowunvalid': $out = dol_htmlwithnojs($out, 1, $check); break; case 'custom': if (!empty($out)) { if (empty($filter)) { return 'BadParameterForGETPOST - Param 3 of sanitizeVal()'; } if (is_null($options)) { $options = 0; } $out = filter_var($out, $filter, $options); } break; default: dol_syslog("Error, you call sanitizeVal() with a bad value for the check type. Data will be sanitized with alphanohtml.", LOG_ERR); $out = GETPOST($out, 'alphanohtml'); break; } return $out; } /** * Set a cookie * * @param string $cookiename Cookie name * @param string $cookievalue Cookie value * @param int $expire Expire delay. If 0, expire at end of session. -1 means 1 year. * @return void */ function dolSetCookie(string $cookiename, string $cookievalue, int $expire = -1) { global $dolibarr_main_force_https; if ($expire == -1) { $expire = (time() + (86400 * 354)); // keep cookie 1 year. } if (PHP_VERSION_ID < 70300) { setcookie($cookiename, empty($cookievalue) ? '' : $cookievalue, empty($cookievalue) ? 0 : $expire, '/', '', !(empty($dolibarr_main_force_https) && isHTTPS() === false), true); // add tag httponly } else { // Only available for php >= 7.3 $cookieparams = array( 'expires' => empty($cookievalue) ? 0 : $expire, 'path' => '/', //'domain' => '.mywebsite.com', // the dot at the beginning allows compatibility with subdomains 'secure' => !(empty($dolibarr_main_force_https) && isHTTPS() === false), 'httponly' => true, 'samesite' => 'Lax' // None || Lax || Strict ); setcookie($cookiename, empty($cookievalue) ? '' : $cookievalue, $cookieparams); } if (empty($cookievalue)) { unset($_COOKIE[$cookiename]); } } if (!function_exists('dol_getprefix')) { /** * Return a prefix to use for this Dolibarr instance, for session/cookie names or email id. * The prefix is unique for instance and avoid conflict between multi-instances, even when having two instances with same root dir * or two instances in same virtual servers. * This function must not use dol_hash (that is used for password hash) and need to have all context $conf loaded. * * @param string $mode '' (prefix for session name) or 'email' (prefix for email id) * @return string A calculated prefix * @phan-suppress PhanRedefineFunction - Also defined in webportal.main.inc.php */ function dol_getprefix($mode = '') { // If prefix is for email (we need to have $conf already loaded for this case) if ($mode == 'email') { global $conf; if (getDolGlobalString('MAIL_PREFIX_FOR_EMAIL_ID')) { // If MAIL_PREFIX_FOR_EMAIL_ID is set if (getDolGlobalString('MAIL_PREFIX_FOR_EMAIL_ID') != 'SERVER_NAME') { return getDolGlobalString('MAIL_PREFIX_FOR_EMAIL_ID'); } elseif (isset($_SERVER["SERVER_NAME"])) { // If MAIL_PREFIX_FOR_EMAIL_ID is set to 'SERVER_NAME' return $_SERVER["SERVER_NAME"]; } } // The recommended value if MAIL_PREFIX_FOR_EMAIL_ID is not defined (may be not defined for old versions) if (!empty($conf->file->instance_unique_id)) { return sha1('dolibarr' . $conf->file->instance_unique_id); } // For backward compatibility when instance_unique_id is not set return sha1(DOL_DOCUMENT_ROOT . DOL_URL_ROOT); } // If prefix is for session (no need to have $conf loaded) global $dolibarr_main_instance_unique_id, $dolibarr_main_cookie_cryptkey; // This is loaded by filefunc.inc.php $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 // The recommended value (may be not defined for old versions) if (!empty($tmp_instance_unique_id)) { return sha1('dolibarr' . $tmp_instance_unique_id); } // For backward compatibility when instance_unique_id is not set if (isset($_SERVER["SERVER_NAME"]) && isset($_SERVER["DOCUMENT_ROOT"])) { return sha1($_SERVER["SERVER_NAME"] . $_SERVER["DOCUMENT_ROOT"] . DOL_DOCUMENT_ROOT . DOL_URL_ROOT); } else { return sha1(DOL_DOCUMENT_ROOT . DOL_URL_ROOT); } } } /** * Make an include_once using default root and alternate root if it fails. * To link to a core file, use include(DOL_DOCUMENT_ROOT.'/pathtofile') * To link to a module file from a module file, use include './mymodulefile'; * To link to a module file from a core file, then this function can be used (call by hook / trigger / speciales pages) * * @param string $relpath Relative path to file (Ie: mydir/myfile, ../myfile, ...) * @param string $classname Class name (deprecated) * @return bool True if load is a success, False if it fails */ function dol_include_once($relpath, $classname = '') { 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'] $fullpath = dol_buildpath($relpath); if (!file_exists($fullpath)) { dol_syslog('functions::dol_include_once Tried to load unexisting file: ' . $relpath, LOG_WARNING); return false; } if (!empty($classname) && !class_exists($classname)) { return include $fullpath; } else { return include_once $fullpath; } } /** * Return path of url or filesystem. Can check into alternate dir or alternate dir + main dir depending on value of $returnemptyifnotfound. * * @param string $path Relative path to file (if mode=0) or relative url (if mode=1). Ie: mydir/myfile, ../myfile * @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, for an access from internet) * @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) * 1:If $type==0 and if file was not found into alternate dir, return empty string * 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 * @return string Full filesystem path (if path=0) or '' if file not found, Full url path (if mode=1) */ function dol_buildpath($path, $type = 0, $returnemptyifnotfound = 0) { global $conf; $path = preg_replace('/^\//', '', $path); if (empty($type)) { // For a filesystem path $res = DOL_DOCUMENT_ROOT . '/' . $path; // Standard default path if (is_array($conf->file->dol_document_root)) { foreach ($conf->file->dol_document_root as $key => $dirroot) { // ex: array("main"=>"/home/main/htdocs", "alt0"=>"/home/dirmod/htdocs", ...) if ($key == 'main') { continue; } // if (@file_exists($dirroot.'/'.$path)) { if (@file_exists($dirroot . '/' . $path)) { // avoid [php:warn] if ($key != 'main' && preg_match('/^core\//', $path)) { // When searching into an alternative custom path, we don't want path like 'core/...' because path should be 'modulename/core/...' continue; } $res = $dirroot . '/' . $path; return $res; } } } if ($returnemptyifnotfound) { // Not found into alternate dir if ($returnemptyifnotfound == 1 || !file_exists($res)) { return ''; } } } else { // For an url path // We try to get local path of file on filesystem from url // Note that trying to know if a file on disk exist by forging path on disk from url // works only for some web server and some setup. This is bugged when // using proxy, rewriting, virtual path, etc... $res = ''; if ($type == 1) { $res = DOL_URL_ROOT . '/' . $path; // Standard value } if ($type == 2) { $res = DOL_MAIN_URL_ROOT . '/' . $path; // Standard value } if ($type == 3) { $res = DOL_URL_ROOT . '/' . $path; } foreach ($conf->file->dol_document_root as $key => $dirroot) { // ex: array(["main"]=>"/home/main/htdocs", ["alt0"]=>"/home/dirmod/htdocs", ...) if ($key == 'main') { if ($type == 3) { /*global $dolibarr_main_url_root;*/ // Define $urlwithroot $urlwithouturlroot = preg_replace('/' . preg_quote(DOL_URL_ROOT, '/') . '$/i', '', trim($conf->file->dol_main_url_root)); $urlwithroot = $urlwithouturlroot . DOL_URL_ROOT; // This is to use external domain name found into config file //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : $urlwithroot) . '/' . $path; // Test on start with http is for old conf syntax } continue; } $regs = array(); preg_match('/^([^\?]+(\.css\.php|\.css|\.js\.php|\.js|\.png|\.jpg|\.php)?)/i', $path, $regs); // Take part before '?' if (!empty($regs[1])) { //print $key.'-'.$dirroot.'/'.$path.'-'.$conf->file->dol_url_root[$type].'
'."\n"; //if (file_exists($dirroot.'/'.$regs[1])) { if (@file_exists($dirroot . '/' . $regs[1])) { // avoid [php:warn] if ($type == 1) { $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_URL_ROOT) . $conf->file->dol_url_root[$key] . '/' . $path; } elseif ($type == 2) { $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_MAIN_URL_ROOT) . $conf->file->dol_url_root[$key] . '/' . $path; } elseif ($type == 3) { /*global $dolibarr_main_url_root;*/ // Define $urlwithroot $urlwithouturlroot = preg_replace('/' . preg_quote(DOL_URL_ROOT, '/') . '$/i', '', trim($conf->file->dol_main_url_root)); $urlwithroot = $urlwithouturlroot . DOL_URL_ROOT; // This is to use external domain name found into config file //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current $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 } break; } } } } return $res; } /** * Return path of url. * * @param string $url Relative path to file * @param array $params params for the http query * @param bool $addtoken does we need to add token * @return string path */ function dolBuildUrl($url, $params = [], $addtoken = false) { global $db, $hookmanager; if (!is_object($hookmanager)) { include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php'; $hookmanager = new HookManager($db); } if ((!isset($params['mainmenu']) || empty($params['mainmenu'])) && GETPOSTISSET('mainmenu')) { $params = array_merge($params, ['mainmenu' => (GETPOST('mainmenu', 'restricthtml'))]); } if ((!isset($params['leftmenu'])/* || empty($params['leftmenu']) */) && GETPOSTISSET('leftmenu')) { // do not fill leftmenu if we have leftmenu= $params = array_merge($params, ['leftmenu' => (GETPOST('leftmenu', 'restricthtml'))]); } $parameters = [ 'path' => &$url, 'params' => &$params, 'addtoken' => &$addtoken, ]; $hookmanager->executeHooks('buildurl', $parameters); if ($addtoken) { $params = array_merge($params, ['token' => newToken()]); } // TODO TO REMOVE if (getDolGlobalString('MAIN_DEBUG_DOL_BUILDURL')) { $params = array_merge($params, ['debug' => 'debug']); } if ($params) { $url .= '?' . http_build_query($params); } return $url; } /** * Get properties for an object - including magic properties when requested * * Only returns properties that exist * * @param object $obj Object to get properties from * @param string[] $properties Optional list of properties to get. * When empty, only gets public properties. * @return array Hash for retrieved values (key=name) */ function dol_get_object_properties($obj, $properties = []) { // Get real properties using get_object_vars() if $properties is empty if (empty($properties)) { return get_object_vars($obj); } $existingProperties = []; $realProperties = get_object_vars($obj); // Get the real or magic property values foreach ($properties as $property) { if (array_key_exists($property, $realProperties)) { // Real property, add the value $existingProperties[$property] = $obj->{$property}; } elseif (property_exists($obj, $property)) { // Magic property $existingProperties[$property] = $obj->{$property}; } } return $existingProperties; } /** * Create a clone of instance of object (new instance with same value for each properties) * With native = 0: Deprecated. Property that are references are different memory area in the new object (full isolation clone). This means $this->objectproperty of the new object may not be valid (except this->db that is voluntarly kept). * 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. * 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 that the methods are not available and $this->db of new object is not valid. * You can use it with: $object->oldcopy = dol_clone($object, 2); // @phan-suppress-current-line PhanTypeMismatchProperty * * * @template T * * @param T $srcobject Object to clone * @param int $native 0=Full isolation method, 1=Native PHP method, 2=Full isolation method keeping only scalar and array properties (recommended) * @return T Clone object * * @see https://php.net/manual/language.oop5.cloning.php * @phan-suppress PhanTypeExpectedObjectPropAccess */ function dol_clone($srcobject, $native = 2) { if ($native == 0) { // deprecated method, use the method with native = 2 instead dol_syslog("Warning, call to dol_clone() with the deprecated parameter native=0, use 2 instead", LOG_WARNING); $tmpsavdb = null; if (isset($srcobject->db) && isset($srcobject->db->db) && is_object($srcobject->db->db) && get_class($srcobject->db->db) == 'PgSql\Connection') { $tmpsavdb = $srcobject->db; unset($srcobject->db); // Such property can not be serialized with pgsl (when object->db->db = 'PgSql\Connection') } $myclone = unserialize(serialize($srcobject)); // serialize then unserialize is a hack to be sure to have a new object for all fields if (!empty($tmpsavdb)) { $srcobject->db = $tmpsavdb; } } elseif ($native == 2) { // recommended method to have a full secured isolated cloned object $myclone = new stdClass(); $tmparray = get_object_vars($srcobject); // return only public properties if (is_array($tmparray)) { foreach ($tmparray as $propertykey => $propertyval) { if (is_scalar($propertyval) || is_array($propertyval)) { $myclone->$propertykey = $propertyval; } } } } else { $myclone = clone $srcobject; // 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) } return $myclone; } /** * Create a clone of instance of object into a full array, using recursive call. * It also cleans some properties. * * @param Object $srcobject Object to clone * @param int $startlevel Start level to track recursive depth * @return array Array */ function dol_clone_in_array($srcobject, $startlevel = 0) { if (is_object($srcobject)) { $srcobject = get_object_vars($srcobject); // exclude private/protected properties } if (is_array($srcobject)) { $result = []; foreach ($srcobject as $key => $value) { if (in_array($key, array('db', 'fields', 'error', 'errorhidden', 'errors', 'oldcopy', 'linkedObjects', 'linked_objects'))) { continue; } $result[$key] = dol_clone_in_array($value, $startlevel + 1); } return $result; } return $srcobject; } /** * Optimize a size for some browsers (phone, smarphone...) * * @param int $size Size we want * @param string $type Type of optimizing: * '' = function used to define a size for truncation * 'width' = function is used to define a width * @return int New size after optimizing */ function dol_size($size, $type = '') { global $conf; if (empty($conf->dol_optimize_smallscreen)) { return $size; } if ($type == 'width' && $size > 250) { return 250; } else { return 10; } } /** * Clean a string to use it as a file name. * Replace also '--' and ' -' strings, they are used for parameters separation (Note: ' - ' is allowed). * * @param string $str String to clean * @param string $newstr String to replace bad chars with. * @param int $unaccent 1=Remove also accent (default), 0 do not remove them * @param int $includequotes 1=Include simple quotes (double is already included by default) * @return string String cleaned * * @see dol_string_nospecial(), dol_string_unaccent(), dol_sanitizePathName() */ function dol_sanitizeFileName($str, $newstr = '_', $unaccent = 1, $includequotes = 0) { // List of special chars for filenames in windows are defined on page https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file // Char '>' '<' '|' '$' and ';' are special chars for shells. // Char '/' and '\' are file delimiters. // 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 $filesystem_forbidden_chars = array('<', '>', '/', '\\', '?', '*', '|', '"', ':', '°', '$', ';', '`'); if ($includequotes) { $filesystem_forbidden_chars[] = "'"; } $tmp = dol_string_nospecial($unaccent ? dol_string_unaccent($str) : $str, $newstr, $filesystem_forbidden_chars); $tmp = preg_replace('/\-\-+/', '_', $tmp); $tmp = preg_replace('/\s+\-([^\s])/', ' _$1', $tmp); $tmp = preg_replace('/\s+\-$/', '', $tmp); $tmp = str_replace('..', '', $tmp); return $tmp; } /** * Clean a string to use it as a path name. Similar to dol_sanitizeFileName but accept / and \ chars. * Replace also '--' and ' -' strings, they are used for parameters separation (Note: ' - ' and 'x-y' is allowed). * * @param string $str String to clean * @param string $newstr String to replace bad chars with * @param int $unaccent 1=Remove also accent, 0 do not remove them * @param int $allowdash 1=Allow dash char after a space and before a string, 0 do not allow * @return string String cleaned * * @see dol_string_nospecial(), dol_string_unaccent(), dol_sanitizeFileName() */ function dol_sanitizePathName($str, $newstr = '_', $unaccent = 0, $allowdash = 0) { // List of special chars for filenames in windows are defined on page https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file // Char '>' '<' '|' '$' ';' and '`' are special chars for shells. // Char '?' and '*' are for wild card chars. // Char '"' is dangerous. // Char '°' is just not expected. // Chars '-' and '--' 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 // Chars '--' and '~' can be used for path transversal $filesystem_forbidden_chars = array('<', '>', '?', '*', '|', '"', '°', '$', ';', '`'); $tmp = $str; if ($unaccent) { $tmp = dol_string_unaccent($tmp); } $tmp = dol_string_nospecial($tmp, $newstr, $filesystem_forbidden_chars); $tmp = preg_replace('/\-\-+/', $newstr, $tmp); if (empty($allowdash)) { $tmp = preg_replace('/\s+\-([^\s])/', ' '.$newstr.'$1', $tmp); $tmp = preg_replace('/\s+\-$/', '', $tmp); } $tmp = str_replace('..', $newstr, $tmp); $tmp = str_replace('~', $newstr, $tmp); return $tmp; } /** * Clean a string to use it as an URL (into a href or src attribute) * * @param string $stringtoclean String to clean * @param int $type 0=Accept all Url, 1=Clean external Url (keep only relative Url) * @return string Escaped string. */ function dol_sanitizeUrl($stringtoclean, $type = 1) { // 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) // We should use dol_string_nounprintableascii but function may not be yet loaded/available $stringtoclean = preg_replace('/[\x00-\x1F\x7F]/u', '', $stringtoclean); // /u operator makes UTF8 valid characters being ignored so are not included into the replace // We clean html comments because some hacks try to obfuscate evil strings by inserting HTML comments. Example: onerror=alert(1) $stringtoclean = preg_replace('//', '', $stringtoclean); $stringtoclean = str_replace('\\', '/', $stringtoclean); if ($type == 1) { // removing : should disable links to external url like http:aaa) // removing ';' should disable "named" html entities encode into an url (we should not have this into an url) $stringtoclean = str_replace(array(':', ';', '@'), '', $stringtoclean); } do { $oldstringtoclean = $stringtoclean; // removing '&colon' should disable links to external url like http:aaa) // removing '&#' should disable "numeric" html entities encode into an url (we should not have this into an url) $stringtoclean = str_ireplace(array('javascript', 'vbscript', '&colon', '&#'), '', $stringtoclean); } while ($oldstringtoclean != $stringtoclean); if ($type == 1) { // removing '//' should disable links to external url like //aaa or http//) $stringtoclean = preg_replace(array('/^[a-z]*\/\/+/i'), '', $stringtoclean); } return $stringtoclean; } /** * Clean a string to use it as an Email. * * @param string $stringtoclean String to clean. Example 'abc@mycompany.com ' * @return string Escaped string. */ function dol_sanitizeEmail($stringtoclean) { do { $oldstringtoclean = $stringtoclean; $stringtoclean = str_ireplace(array('"', ':', '[', ']', "\n", "\r", '\\', '\/'), '', $stringtoclean); } while ($oldstringtoclean != $stringtoclean); return $stringtoclean; } /** * Clean a string to use it as a key or code. So only char a-Z, A-Z, _ and 0-9 is kept. * * @param string $str String to clean * @return string String cleaned (a-zA-Z_) * * @see dol_string_nospecial(), dol_string_unaccent(), dol_sanitize...() */ function dol_sanitizeKeyCode($str) { return preg_replace('/[^\w]+/', '', $str); } /** * Clean a string from all accent characters to be used as ref, login or by dol_sanitizeFileName * * @param string $str String to clean. Must be an ascii or utf8 string without any htmlentities. * @return string Cleaned string * * @see dol_sanitizeFilename(), dol_string_nospecial() */ function dol_string_unaccent($str) { if (is_null($str)) { return ''; } if (utf8_check($str)) { if (extension_loaded('intl') && getDolGlobalString('MAIN_UNACCENT_USE_TRANSLITERATOR')) { $transliterator = Transliterator::createFromRules(':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: NFC;', Transliterator::FORWARD); return $transliterator->transliterate($str); } // See http://www.utf8-chartable.de/ $string = rawurlencode($str); $replacements = array( '%C3%80' => 'A', '%C3%81' => 'A', '%C3%82' => 'A', '%C3%83' => 'A', '%C3%84' => 'A', '%C3%85' => 'A', '%C3%87' => 'C', '%C3%88' => 'E', '%C3%89' => 'E', '%C3%8A' => 'E', '%C3%8B' => 'E', '%C3%8C' => 'I', '%C3%8D' => 'I', '%C3%8E' => 'I', '%C3%8F' => 'I', '%C3%91' => 'N', '%C3%92' => 'O', '%C3%93' => 'O', '%C3%94' => 'O', '%C3%95' => 'O', '%C3%96' => 'O', '%C5%A0' => 'S', '%C3%99' => 'U', '%C3%9A' => 'U', '%C3%9B' => 'U', '%C3%9C' => 'U', '%C3%9D' => 'Y', '%C5%B8' => 'y', '%C3%A0' => 'a', '%C3%A1' => 'a', '%C3%A2' => 'a', '%C3%A3' => 'a', '%C3%A4' => 'a', '%C3%A5' => 'a', '%C3%A7' => 'c', '%C3%A8' => 'e', '%C3%A9' => 'e', '%C3%AA' => 'e', '%C3%AB' => 'e', '%C3%AC' => 'i', '%C3%AD' => 'i', '%C3%AE' => 'i', '%C3%AF' => 'i', '%C3%B1' => 'n', '%C3%B2' => 'o', '%C3%B3' => 'o', '%C3%B4' => 'o', '%C3%B5' => 'o', '%C3%B6' => 'o', '%C5%A1' => 's', '%C3%B9' => 'u', '%C3%BA' => 'u', '%C3%BB' => 'u', '%C3%BC' => 'u', '%C3%BD' => 'y', '%C3%BF' => 'y', '%CC%80' => '', '%CC%81' => '', '%CC%82' => '', '%CC%83' => '', '%CC%84' => '', '%CC%85' => '', '%CC%86' => '', '%CC%87' => '', '%CC%88' => '', '%CC%89' => '', '%CC%8A' => '', '%CC%8B' => '', '%CC%8C' => '', '%CC%8D' => '', '%CC%8E' => '', '%CC%8F' => '', '%CC%90' => '', '%CC%91' => '', '%CC%A7' => '', ); $string = strtr($string, $replacements); return rawurldecode($string); } else { // See http://www.ascii-code.com/ $string = strtr( $str, "\xC0\xC1\xC2\xC3\xC4\xC5\xC7 \xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1 \xD2\xD3\xD4\xD5\xD8\xD9\xDA\xDB\xDD \xE0\xE1\xE2\xE3\xE4\xE5\xE7\xE8\xE9\xEA\xEB \xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF8 \xF9\xFA\xFB\xFC\xFD\xFF", "AAAAAAC EEEEIIIIDN OOOOOUUUY aaaaaaceeee iiiidnooooo uuuuyy" ); $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")); return $string; } } /** * Clean a string from all punctuation characters to use it as a ref or login. * This is a more complete function than dol_sanitizeFileName(). * * @param string $str String to clean * @param string $newstr String to replace forbidden chars with * @param string[]|string $badcharstoreplace Array of forbidden characters to replace. Use '' to keep default list. * @param string[]|string $badcharstoremove Array of forbidden characters to remove. Use '' to keep default list. * @param int<0,1> $keepspaces 1=Do not treat space as a special char to replace or remove * @return string Cleaned string * * @see dol_sanitizeFilename(), dol_string_unaccent(), dol_string_nounprintableascii() */ function dol_string_nospecial($str, $newstr = '_', $badcharstoreplace = '', $badcharstoremove = '', $keepspaces = 0) { $forbidden_chars_to_replace = array("'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ",", ";", "=", '°', '$', ';'); // more complete than dol_sanitizeFileName if (empty($keepspaces)) { $forbidden_chars_to_replace[] = " "; } $forbidden_chars_to_remove = array(); //$forbidden_chars_to_remove=array("(",")"); if (is_array($badcharstoreplace)) { $forbidden_chars_to_replace = $badcharstoreplace; } if (is_array($badcharstoremove)) { $forbidden_chars_to_remove = $badcharstoremove; } // @phan-suppress-next-line PhanPluginSuspiciousParamOrderInternal return str_replace($forbidden_chars_to_replace, $newstr, str_replace($forbidden_chars_to_remove, "", $str)); } /** * Clean a string from all non printable ASCII chars (0x00-0x1F and 0x7F). It can also removes also Tab-CR-LF. UTF8 chars remains. * This can be used to sanitize a string and view its real content. Some hacks try to obfuscate attacks by inserting non printable chars. * Note, for information: UTF8 on 1 byte are: \x00-\7F * 2 bytes are: byte 1 \xc0-\xdf, byte 2 = \x80-\xbf * 3 bytes are: byte 1 \xe0-\xef, byte 2 = \x80-\xbf, byte 3 = \x80-\xbf * 4 bytes are: byte 1 \xf0-\xf7, byte 2 = \x80-\xbf, byte 3 = \x80-\xbf, byte 4 = \x80-\xbf * @param string $str String to clean * @param int $removetabcrlf Remove also CR-LF * @return string Cleaned string * * @see dol_sanitizeFilename(), dol_string_unaccent(), dol_string_nospecial() */ function dol_string_nounprintableascii($str, $removetabcrlf = 1) { if ($removetabcrlf) { return preg_replace('/[\x00-\x1F\x7F]/u', '', $str); // /u operator makes UTF8 valid characters being ignored so are not included into the replace } else { 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 } } /** * Returns text slugified (lowercase and no special char, separator is "-"). * * @param string $stringtoslugify String to slugify * @return string Slugified string */ function dolSlugify($stringtoslugify) { $slug = dol_string_unaccent($stringtoslugify); // Convert special characters to their ASCII equivalents if (function_exists('iconv')) { $slug = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $slug); } // Convert to lowercase $slug = strtolower($slug); // Replace non-alphanumeric characters with hyphens $slug = preg_replace('/[^a-z0-9]+/', '-', $slug); // Remove leading and trailing hyphens $slug = trim($slug, '-'); return $slug; } /** * Returns text escaped for inclusion into javascript code * * @param string $stringtoescape String to escape * @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 \ * @param int $noescapebackslashn 0=Escape also \n. 1=Do not escape \n. * @return string Escaped string. Both ' and " are escaped into ' if they are escaped. */ function dol_escape_js($stringtoescape, $mode = 0, $noescapebackslashn = 0) { if (is_null($stringtoescape)) { return ''; } // escape quotes and backslashes, newlines, etc. $substitjs = array("'" => "\\'", "\r" => '\\r'); //$substitjs[' $stringforquotes 2=String for doublequotes, 1=String for simple quotes * @return string Escaped string for PHP content. */ function dol_escape_php($stringtoescape, $stringforquotes = 2) { if (is_null($stringtoescape)) { return ''; } if ($stringforquotes == 2) { return str_replace('"', "'", $stringtoescape); } elseif ($stringforquotes == 1) { // We remove the \ char. // If we allow the \ char, we can have $stringtoescape = // abc\';phpcodedanger; so the escapement will become // abc\\';phpcodedanger; and injecting this into // $a='...' will give $ac='abc\\';phpcodedanger; $stringtoescape = str_replace('\\', '', $stringtoescape); return str_replace("'", "\'", str_replace('"', "'", $stringtoescape)); } return 'Bad parameter for stringforquotes in dol_escape_php'; } /** * Returns text escaped for all protocols (so only alpha chars and numbers) * * @param string $stringtoescape String to escape * @return string Escaped string for XML content. */ function dol_escape_all($stringtoescape) { return preg_replace('/[^a-z0-9_]/i', '', $stringtoescape); } /** * Returns text escaped for inclusion into a XML string * * @param string $stringtoescape String to escape * @return string Escaped string for XML content. */ function dol_escape_xml($stringtoescape) { return $stringtoescape; } /** * Return a string label (so on 1 line only and that should not contains any HTML) ready to be output on HTML page. * To use text that is not HTML content inside an attribute, you can simply use only dol_escape_htmltag(). In doubt, use dolPrintHTMLForAttribute(). * * @param string $s String to print * @param int $escapeonlyhtmltags 1=Escape only html tags, not the special chars like accents. * @return string String ready for HTML output * @see dolPrintText() */ function dolPrintLabel($s, $escapeonlyhtmltags = 0) { return dol_escape_htmltag(dol_string_nohtmltag($s, 1, 'UTF-8', 0, 0), 0, 0, '', $escapeonlyhtmltags, 1); } /** * Return a string label (possible on several lines and that should not contains any HTML) ready to be output on HTML page. * To use text that is not HTML content inside an attribute, you can simply use only dol_escape_htmltag(). In doubt, use dolPrintHTMLForAttribute(). * * @param string $s String to print * @return string String ready for HTML output * @see dolPrintLabel(), dolPrintHTML() */ function dolPrintText($s) { return dol_escape_htmltag(dol_string_nohtmltag($s, 2, 'UTF-8', 0, 0), 0, 1, '', 0, 1); } /** * Return a string (that can be on several lines) ready to be output on a HTML page. * To output a text inside an attribute, you can use dolPrintHTMLForAttribute() or dolPrintHTMLForTextArea() inside a textarea * With dolPrintHTML(), only content not already in HTML is encoded with HTML. * * @param int|float|string $s String to print * @param int $allowiframe Allow iframe tags * @return string String ready for HTML output (sanitized and escape) * @see dolPrintHTMLForAttribute(), dolPrintHTMLFortextArea(), dolPrintText() */ function dolPrintHTML($s, $allowiframe = 0) { // If text is already HTML, we want to escape only dangerous chars else we want to escape all content. //$isAlreadyHTML = dol_textishtml($s); // dol_htmlentitiesbr encode all chars except "'" if string is not already HTML, but // encode only special char like é but not &, <, >, ", ' if already HTML. $stringWithEntitesForSpecialChar = dol_htmlentitiesbr((string) $s); return dol_escape_htmltag(dol_htmlwithnojs(dol_string_onlythesehtmltags($stringWithEntitesForSpecialChar, 1, 1, 1, $allowiframe)), 1, 1, 'common', 0, 1); } /** * Return a string ready to be output into an HTML attribute (alt, title, data-html, ...) * With dolPrintHTMLForAttribute(), the content is HTML encode, even if it is already HTML content. * * @param string $s String to print * @param int $escapeonlyhtmltags 1=Escape only html tags, not the special chars like accents. * @param string[] $allowothertags List of other tags allowed * @return string String ready for HTML output * @see dolPrintHTML(), dolPrintHTMLFortextArea() */ function dolPrintHTMLForAttribute($s, $escapeonlyhtmltags = 0, $allowothertags = array()) { $allowedtags = array('br', 'b', 'font', 'hr', 'span'); if (!empty($allowothertags) && is_array($allowothertags)) { $allowedtags = array_merge($allowedtags, $allowothertags); } // The dol_htmlentitiesbr will convert simple text into html, including switching accent into HTML entities // The dol_escape_htmltag will escape html tags. if ($escapeonlyhtmltags) { return dol_escape_htmltag(dol_string_onlythesehtmltags($s, 1, 0, 0, 0, $allowedtags), 1, -1, '', 1, 1); } else { return dol_escape_htmltag(dol_string_onlythesehtmltags(dol_htmlentitiesbr($s), 1, 0, 0, 0, $allowedtags), 1, -1, '', 0, 1); } } /** * Return a string ready to be output on a href attribute (this one need a special because we need content is HTML with no way to detect it is HTML). * With dolPrintHTMLForAttribute(), the content is HTML encode, even if it is already HTML content. * * @param string $s String to print * @return string String ready for HTML output * @see dolPrintHTML(), dolPrintHTMLFortextArea() */ function dolPrintHTMLForAttributeUrl($s) { // The dol_htmlentitiesbr has been removed compared to dolPrintHTMLForAttribute because we know content is a HTML URL string (even if we have no way to detect it automatically) // The dol_escape_htmltag will escape html chars. $escapeonlyhtmltags = 1; return dol_escape_htmltag(dol_string_onlythesehtmltags($s, 1, 1, 1, 0, array()), 0, 0, '', $escapeonlyhtmltags, 1); } /** * Return a string ready to be output on input textarea. * Differs from dolPrintHTML because all tags are escape. With dolPrintHTML, all tags except common one are escaped. * * @param string $s String to print * @param int $allowiframe Allow iframe tags * @return string String ready for HTML output into a textarea * @see dolPrintHTML(), dolPrintHTMLForAttribute() */ function dolPrintHTMLForTextArea($s, $allowiframe = 0) { return dol_escape_htmltag(dol_htmlwithnojs(dol_string_onlythesehtmltags(dol_htmlentitiesbr($s), 1, 1, 1, $allowiframe)), 1, 1, '', 0, 1); } /** * Return a string ready to be output on an HTML attribute (alt, title, ...) * * @param string $s String to print * @return string String ready for HTML output */ function dolPrintPassword($s) { return htmlspecialchars($s, ENT_HTML5, 'UTF-8'); } /** * Returns text escaped for inclusion in HTML alt or title or value tags, or into values of HTML input fields. * When we need to output strings on pages, we should use: * - dolPrintLabel... * - dolPrintHTML... that is dol_escape_htmltag(dol_htmlwithnojs(dol_string_onlythesehtmltags(dol_htmlentitiesbr(...), 1, 1, 1, 0)), 1, 1, 'common', 0, 1) for notes or descriptions into textarea, add 'common' if into a html content * - dolPrintPassword that is a simple htmlspecialchars(... , ENT_COMPAT, 'UTF-8') for passwords. * * @param string $stringtoescape String to escape * @param int $keepb 1=Replace b tags with escaped value (except if in $noescapetags), 0=Remove them completely * @param int $keepn 1=Preserve \r\n strings, 0=Replace them with escaped value, -1=Remove them. Set to 1 when escaping for a