dolibarr 25.0.0-alpha
functions.lib.php
Go to the documentation of this file.
1<?php
2/* Copyright (C) 2000-2007 Rodolphe Quiedeville <rodolphe@quiedeville.org>
3 * Copyright (C) 2003 Jean-Louis Bergamo <jlb@j1b.org>
4 * Copyright (C) 2004-2025 Laurent Destailleur <eldy@users.sourceforge.net>
5 * Copyright (C) 2004 Sebastien Di Cintio <sdicintio@ressource-toi.org>
6 * Copyright (C) 2004 Benoit Mortier <benoit.mortier@opensides.be>
7 * Copyright (C) 2004 Christophe Combelles <ccomb@free.fr>
8 * Copyright (C) 2005-2019 Regis Houssin <regis.houssin@inodbox.com>
9 * Copyright (C) 2008 Raphael Bertrand (Resultic) <raphael.bertrand@resultic.fr>
10 * Copyright (C) 2010-2018 Juanjo Menent <jmenent@2byte.es>
11 * Copyright (C) 2013 Cédric Salvador <csalvador@gpcsolutions.fr>
12 * Copyright (C) 2013-2026 Alexandre Spangaro <alexandre@inovea-conseil.com>
13 * Copyright (C) 2014 Cédric GROSS <c.gross@kreiz-it.fr>
14 * Copyright (C) 2014-2015 Marcos García <marcosgdf@gmail.com>
15 * Copyright (C) 2015 Jean-François Ferry <jfefe@aternatik.fr>
16 * Copyright (C) 2018-2026 Frédéric France <frederic.france@free.fr>
17 * Copyright (C) 2019-2023 Thibault Foucart <support@ptibogxiv.net>
18 * Copyright (C) 2020 Open-Dsi <support@open-dsi.fr>
19 * Copyright (C) 2021 Gauthier VERDOL <gauthier.verdol@atm-consulting.fr>
20 * Copyright (C) 2022-2026 Anthony Berton <anthony.berton@bb2a.fr>
21 * Copyright (C) 2022 Ferran Marcet <fmarcet@2byte.es>
22 * Copyright (C) 2022-2026 Charlene Benke <charlene@patas-monkey.com>
23 * Copyright (C) 2024-2026 MDW <mdeweerd@users.noreply.github.com>
24 * Copyright (C) 2023-2024 Joachim Kueter <git-jk@bloxera.com>
25 * Copyright (C) 2024 Lenin Rivas <lenin.rivas777@gmail.com>
26 * Copyright (C) 2024 Josep Lluís Amador Teruel <joseplluis@lliuretic.cat>
27 * Copyright (C) 2024 Benoît PASCAL <contact@p-ben.com>
28 * Copyright (C) 2025 Vincent Maury <vmaury@timgroup.fr>
29 * Copyright (C) 2026 Benjamin Falière <benjamin@faliere.com>
30 * Copyright (C) 2026 Pierre Ardoin <developpeur@lesmetiersdubatiment.fr>
31 *
32 * This program is free software; you can redistribute it and/or modify
33 * it under the terms of the GNU General Public License as published by
34 * the Free Software Foundation; either version 3 of the License, or
35 * (at your option) any later version.
36 *
37 * This program is distributed in the hope that it will be useful,
38 * but WITHOUT ANY WARRANTY; without even the implied warranty of
39 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
40 * GNU General Public License for more details.
41 *
42 * You should have received a copy of the GNU General Public License
43 * along with this program. If not, see <https://www.gnu.org/licenses/>.
44 * or see https://www.gnu.org/
45 */
46
53//include_once DOL_DOCUMENT_ROOT.'/core/lib/json.lib.php';
54
55// Function for better PHP x compatibility
56if (!function_exists('utf8_encode')) {
64 function utf8_encode($elements)
65 {
66 return mb_convert_encoding($elements, 'UTF-8', 'ISO-8859-1');
67 }
68}
69
70if (!function_exists('utf8_decode')) {
78 function utf8_decode($elements)
79 {
80 return mb_convert_encoding($elements, 'ISO-8859-1', 'UTF-8');
81 }
82}
83if (!function_exists('str_starts_with')) {
92 function str_starts_with($haystack, $needle)
93 {
94 return (string) $needle !== '' && strncmp($haystack, $needle, strlen($needle)) === 0;
95 }
96}
97if (!function_exists('str_ends_with')) {
106 function str_ends_with($haystack, $needle)
107 {
108 return $needle !== '' && substr($haystack, -strlen($needle)) === (string) $needle;
109 }
110}
111if (!function_exists('str_contains')) {
120 function str_contains($haystack, $needle)
121 {
122 return $needle !== '' && mb_strpos($haystack, $needle) !== false;
123 }
124}
125
126
134function formatLogObject($data)
135{
136 if (getDolGlobalInt("MAIN_LOG_ON_ONE_LINE")) {
137 return json_encode($data);
138 }
139
140 return var_export($data, true);
141}
142
143
155function getMultidirOutput($object, $module = '', $forobject = 0, $mode = 'output')
156{
157 global $conf;
158
159 $subdirectory = '';
160 if (!is_object($object) && empty($module)) {
161 return null;
162 }
163 if (empty($module) && !empty($object->element)) {
164 $module = $object->element;
165 }
166
167 // Special case for backward compatibility
168 switch ($module) {
169 case 'fichinter':
170 $module = 'ficheinter';
171 break;
172 case 'invoice_supplier':
173 $module = 'supplier_invoice';
174 break;
175 case 'order_supplier':
176 $module = 'supplier_order';
177 break;
178 case 'recruitmentjobposition':
179 $module = 'recruitment';
180 $subdirectory = '/recruitmentjobposition';
181 break;
182 case 'recruitmentcandidature':
183 $module = 'recruitment';
184 $subdirectory = '/recruitmentcandidature';
185 break;
186 case 'knowledgerecord':
187 $module = 'knowledgemanagement';
188 $subdirectory = '/knowledgerecord';
189 break;
190 case 'partnership':
191 $subdirectory = '/partnership';
192 break;
193 case 'stocktransfer':
194 $subdirectory = '/stocktransfer';
195 break;
196 case 'commande_fournisseur':
197 $module = 'fournisseur';
198 $subdirectory = '/commande';
199 break;
200 case 'expedition':
201 case 'shipment':
202 case 'shipping':
203 $module = 'expedition';
204 $subdirectory = '/sending';
205 break;
206 case 'company':
207 $module = 'societe';
208 break;
209 case 'service':
210 case 'produit':
211 $module = 'product';
212 break;
213 case 'project_task':
214 $module = 'projet';
215
216 // Fetch the project to build the correct path. The signature of this function accepts an object
217 // that is not a CommonObject, and even a null when a module is given, so we must not call a method
218 // that only a CommonObject owns without testing it exists.
219 if (is_object($object) && method_exists($object, 'fetchProject')) {
220 $object->fetchProject();
221 }
222
223 // The ref must be sanitized with dol_sanitizeFileName() and not only with dol_sanitizePathName()
224 // done at the end of this function, because a project ref is a user input that may contain a '/',
225 // a ':' or an accented char. dol_sanitizePathName() keeps them, so we would not return the
226 // directory used by projet/tasks/document.php, that sanitizes the ref with dol_sanitizeFileName().
227 if (!empty($object->project->ref)) {
228 $subdirectory = '/'.dol_sanitizeFileName($object->project->ref);
229 }
230 break;
231 case 'action':
232 case 'actioncomm':
233 case 'event':
234 $module = 'agenda';
235 break;
236 default:
237 break;
238 }
239
240 // Get the relative path of directory
241 if ($mode == 'output' || $mode == 'outputrel' || $mode == 'version') {
242 if (isset($conf->$module) && property_exists($conf->$module, 'multidir_output')) {
243 $s = '';
244 if ($mode != 'outputrel') {
245 // An entity with no declared directory returned an undefined index, so an empty path that
246 // made the caller read or write a relative path under the web root. Answer the error instead.
247 $entity = (int) (empty($object->entity) ? $conf->entity : $object->entity);
248 if (!isset($conf->$module->multidir_output[$entity])) {
249 return 'error-diroutput-not-defined-for-this-entity-and-object='.$module;
250 }
251 $s = $conf->$module->multidir_output[$entity] . $subdirectory;
252 }
253 if ($forobject && $object->id > 0) {
254 $s .= ($mode != 'outputrel' ? '/' : '') . get_exdir(0, 0, 0, 0, $object);
255 }
256 return dol_sanitizePathName($s);
257 } elseif (isset($conf->$module) && property_exists($conf->$module, 'dir_output')) {
258 $s = '';
259 if ($mode != 'outputrel') {
260 $s = $conf->$module->dir_output . $subdirectory;
261 }
262 if ($forobject && $object->id > 0) {
263 $s .= ($mode != 'outputrel' ? '/' : '') . get_exdir(0, 0, 0, 0, $object);
264 }
265 return dol_sanitizePathName($s);
266 } else {
267 return 'error-diroutput-not-defined-for-this-entity-and-object='.$module;
268 }
269 } elseif ($mode == 'temp') {
270 if (isset($conf->$module) && property_exists($conf->$module, 'multidir_temp')) {
271 // Same guard as the 'output' mode above, see the comment there
272 $entity = (int) (empty($object->entity) ? $conf->entity : $object->entity);
273 if (!isset($conf->$module->multidir_temp[$entity])) {
274 return 'error-dirtemp-not-defined-for-this-entity-and-object='.$module;
275 }
276 return dol_sanitizePathName($conf->$module->multidir_temp[$entity]);
277 } elseif (isset($conf->$module) && property_exists($conf->$module, 'dir_temp')) {
278 return dol_sanitizePathName($conf->$module->dir_temp);
279 } else {
280 return 'error-dirtemp-not-defined-for-this-entity-and-object='.$module;
281 }
282 } else {
283 return 'error-bad-value-for-mode';
284 }
285}
286
296function getMultidirTemp($object, $module = '', $forobject = 0)
297{
298 return getMultidirOutput($object, $module, $forobject, 'temp');
299}
300
310function getMultidirVersion($object, $module = '', $forobject = 0)
311{
312 return getMultidirOutput($object, $module, $forobject, 'version');
313}
314
315
324function getDolGlobalString($key, $default = '')
325{
326 global $conf;
327 return (string) (isset($conf->global->$key) ? $conf->global->$key : $default);
328}
329
336{
337 global $dolibarr_login_badcharunauthorized;
338
339 if (isset($dolibarr_login_badcharunauthorized)) {
340 if ($dolibarr_login_badcharunauthorized === 'MAIN_LOGIN_BADCHARUNAUTHORIZED') {
341 return getDolGlobalString('MAIN_LOGIN_BADCHARUNAUTHORIZED', ',@<>"\'');
342 }
343
344 return (string) $dolibarr_login_badcharunauthorized;
345 }
346
347 return ',@<>"\'';
348}
349
359function getDolGlobalInt($key, $default = 0)
360{
361 global $conf;
362 return (int) (isset($conf->global->$key) ? $conf->global->$key : $default);
363}
364
374function getDolGlobalFloat($key, $default = 0)
375{
376 global $conf;
377 return (float) (isset($conf->global->$key) ? $conf->global->$key : $default);
378}
379
388function getDolGlobalBool($key, $default = false)
389{
390 global $conf;
391 return (bool) ($conf->global->$key ?? $default);
392}
393
400{
401 global $conf;
402 return (string) $conf->currency;
403}
404
411{
412 global $conf;
413 return (string) $conf->dol_optimize_smallscreen;
414}
415
421function getDolEntity()
422{
423 global $conf;
424 return (int) $conf->entity;
425}
426
432function getDolDBType()
433{
434 global $conf;
435 return $conf->db->type;
436}
437
445{
446 return str_replace('_', '', basename(dirname($s)).basename($s, '.php'));
447}
448
458function getDolUserString($key, $default = '', $tmpuser = null)
459{
460 if (empty($tmpuser)) {
461 global $user;
462 $tmpuser = $user;
463 }
464
465 return (string) (isset($tmpuser->conf->$key) ? $tmpuser->conf->$key : $default);
466}
467
476function getDolUserInt($key, $default = 0, $tmpuser = null)
477{
478 if (empty($tmpuser)) {
479 global $user;
480 $tmpuser = $user;
481 }
482
483 return (int) (isset($tmpuser->conf->$key) ? $tmpuser->conf->$key : $default);
484}
485
486
495define(
496 'MODULE_MAPPING',
497 array(
498 // Map deprecated names to new names
499 'adherent' => 'member', // Has new directory
500 'member_type' => 'adherent_type', // No directory, but file called adherent_type
501 'banque' => 'bank', // Has new directory
502 'contrat' => 'contract', // Has new directory
503 'entrepot' => 'stock', // Has new directory
504 'projet' => 'project', // Has new directory
505 'categorie' => 'category', // Has old directory
506 'commande' => 'order', // Has old directory
507 'expedition' => 'shipping', // Has old directory
508 'facture' => 'invoice', // Has old directory
509 'fichinter' => 'intervention', // Has old directory
510 'ficheinter' => 'intervention', // Backup for 'fichinter'
511 'propale' => 'propal', // Has old directory
512 'societe' => 'thirdparty', // Has old directory
513 'socpeople' => 'contact', // Has old directory
514 'fournisseur' => 'supplier', // Has old directory
515
516 'actioncomm' => 'agenda', // NO module directory (public dir agenda)
517 'product_price' => 'productprice', // NO directory
518 'product_fournisseur_price' => 'productsupplierprice', // NO directory
519 )
520);
521
528function isModEnabled($module)
529{
530 global $conf;
531
532 if (!empty($conf->modules[$module])) {
533 return true; // Most calls use the real name of the module: no need to look at the old/new names mapping
534 }
535
536 // Fix old names (map to new names). The mappings are constant for the request, so they are built once: this function is
537 // called thousands of times per page (hooks, rights, logs...), and array_flip() on each call was most of its cost.
538 static $arrayconv = null;
539 static $arrayconvbis = null;
540 if ($arrayconv === null) {
541 $arrayconv = MODULE_MAPPING;
542 $arrayconvbis = array_flip(MODULE_MAPPING);
543
544 if (!getDolGlobalString('MAIN_USE_NEW_SUPPLIERMOD')) {
545 // Special cases: both use the same module.
546 $arrayconv['supplier_order'] = 'fournisseur';
547 $arrayconv['supplier_invoice'] = 'fournisseur';
548 }
549 }
550
551 if (!empty($arrayconv[$module]) && !empty($conf->modules[$arrayconv[$module]])) {
552 return true;
553 }
554 if (!empty($arrayconvbis[$module]) && !empty($conf->modules[$arrayconvbis[$module]])) {
555 return true;
556 }
557
558 return false;
559}
560
571function getWarningDelay($module, $parmlevel1, $parmlevel2 = '')
572{
573 global $conf;
574
575 // For compatibility with bad naming on module
576 $moduletomoduletouse = array(
577 'invoice' => 'facture',
578 );
579 $moduleParmsMapping = array(
580 'product' => 'produit',
581 );
582
583 if (!empty($moduletomoduletouse[$module])) {
584 $module = $moduletomoduletouse[$module];
585 }
586
587 $warningDelayPath = $parmlevel1;
588 if (!empty($moduleParmsMapping[$warningDelayPath])) {
589 $warningDelayPath = $moduleParmsMapping[$warningDelayPath];
590 }
591
592 if ($parmlevel2) {
593 if (!empty($conf->$module) && !empty($conf->$module->$warningDelayPath) && !empty($conf->$module->$warningDelayPath->$parmlevel2) && !empty($conf->$module->$warningDelayPath->$parmlevel2->warning_delay)) {
594 return (int) $conf->$module->$warningDelayPath->$parmlevel2->warning_delay;
595 }
596 } else {
597 if (!empty($conf->$module) && !empty($conf->$module->$warningDelayPath) && !empty($conf->$module->$warningDelayPath->warning_delay)) {
598 return (int) $conf->$module->$warningDelayPath->warning_delay;
599 }
600 }
601
602 return 0;
603}
604
611function isDolTms($timestamp)
612{
613 if ($timestamp === '') {
614 dol_syslog('Using empty string for a timestamp is deprecated, prefer use of null when calling page ' . $_SERVER["PHP_SELF"] . getCallerInfoString(), LOG_DEBUG);
615 return false;
616 }
617 if (is_null($timestamp) || !is_numeric($timestamp)) {
618 return false;
619 }
620
621 return true;
622}
623
636function getDoliDBInstance($type, $host, $user, $pass, $name, $port, $forcenew = false)
637{
638 require_once DOL_DOCUMENT_ROOT . "/core/db/" . $type . '.class.php';
639
640 $class = 'DoliDB' . ucfirst($type);
641 $db = new $class($type, $host, $user, $pass, $name, $port, $forcenew);
642 return $db;
643}
644
662function getEntity($element, $shared = 1, $currentobject = null)
663{
664 global $conf, $mc, $hookmanager, $object, $action, $db;
665
666 if (!is_object($hookmanager)) {
667 include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
668 $hookmanager = new HookManager($db);
669 }
670
671 // fix different element names (France to English)
672 switch ($element) {
673 case 'projet':
674 $element = 'project';
675 break;
676 case 'contrat':
677 $element = 'contract';
678 break; // "/contrat/class/contrat.class.php"
679 case 'order_supplier':
680 $element = 'supplier_order';
681 break; // "/fourn/class/fournisseur.commande.class.php"
682 case 'invoice_supplier':
683 $element = 'supplier_invoice';
684 break; // "/fourn/class/fournisseur.facture.class.php"
685 }
686
687 if (is_object($mc)) {
688 $out = $mc->getEntity($element, $shared, $currentobject);
689 } else {
690 $out = '';
691 $addzero = array('user', 'usergroup', 'cronjob', 'c_email_templates', 'email_template', 'default_values', 'overwrite_trans');
692 if (getDolGlobalString('HOLIDAY_ALLOW_ZERO_IN_DIC')) { // this constant break the dictionary admin without Multicompany
693 $addzero[] = 'c_holiday_types';
694 }
695 if (in_array($element, $addzero)) {
696 $out .= '0,';
697 }
698 $out .= ((int) $conf->entity);
699 }
700
701 // Manipulate entities to query on the fly
702 $parameters = array(
703 'element' => $element,
704 'shared' => $shared,
705 'object' => $object,
706 'currentobject' => $currentobject,
707 'out' => $out
708 );
709 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
710 $reshook = $hookmanager->executeHooks('hookGetEntity', $parameters, $currentobject, $action); // Note that $action and $object may have been modified by some hooks
711
712 if (is_numeric($reshook)) {
713 if ($reshook == 0 && !empty($hookmanager->resPrint)) {
714 $out .= ',' . $hookmanager->resPrint; // add
715 } elseif ($reshook == 1) {
716 $out = $hookmanager->resPrint; // replace
717 }
718 }
719
720 return $out;
721}
722
729function setEntity($currentobject)
730{
731 global $conf, $mc;
732
733 if (is_object($mc) && method_exists($mc, 'setEntity')) {
734 return $mc->setEntity($currentobject);
735 } else {
736 return ((is_object($currentobject) && $currentobject->id > 0 && ((int) $currentobject->entity) > 0) ? (int) $currentobject->entity : $conf->entity);
737 }
738}
739
746function isASecretKey($keyname)
747{
748 return preg_match('/(_pass|password|_pw|_key|securekey|serverkey|secret\d?|p12key|exportkey|_PW_[a-z]+|token)$/i', $keyname);
749}
750
751
758function num2Alpha($n)
759{
760 $r = '';
761 for ($r = ""; $n >= 0; $n = intval($n / 26) - 1) {
762 $r = chr($n % 26 + 0x41) . $r;
763 }
764 return $r;
765}
766
767
784function getBrowserInfo($user_agent)
785{
786 include_once DOL_DOCUMENT_ROOT . '/includes/mobiledetect/mobiledetectlib/Mobile_Detect.php';
787
788 $name = 'unknown';
789 $version = '';
790 $os = 'unknown';
791 $phone = '';
792
793 $user_agent = substr($user_agent, 0, 512); // Avoid to process too large user agent
794
795 // @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal Bad definition of Mobile_Detect function
796 $detectmobile = new Mobile_Detect(null, $user_agent);
797 $tablet = $detectmobile->isTablet();
798
799 if ($detectmobile->isMobile()) {
800 $phone = 'unknown';
801
802 // If phone/smartphone, we set phone os name.
803 if ($detectmobile->is('AndroidOS')) {
804 $os = $phone = 'android';
805 } elseif ($detectmobile->is('BlackBerryOS')) {
806 $os = $phone = 'blackberry';
807 } elseif ($detectmobile->is('iOS')) {
808 $os = 'ios';
809 $phone = 'iphone';
810 } elseif ($detectmobile->is('PalmOS')) {
811 $os = $phone = 'palm';
812 } elseif ($detectmobile->is('SymbianOS')) {
813 $os = 'symbian';
814 } elseif ($detectmobile->is('webOS')) {
815 $os = 'webos';
816 } elseif ($detectmobile->is('MaemoOS')) {
817 $os = 'maemo';
818 } elseif ($detectmobile->is('WindowsMobileOS') || $detectmobile->is('WindowsPhoneOS')) {
819 $os = 'windows';
820 }
821 }
822
823 // OS
824 if (preg_match('/linux/i', $user_agent)) {
825 $os = 'linux';
826 } elseif (preg_match('/macintosh/i', $user_agent)) {
827 $os = 'macintosh';
828 } elseif (preg_match('/windows/i', $user_agent)) {
829 $os = 'windows';
830 }
831
832 // Name
833 $reg = array();
834 if (preg_match('/firefox(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
835 $name = 'firefox';
836 $version = empty($reg[2]) ? '' : $reg[2];
837 } elseif (preg_match('/edge(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
838 $name = 'edge';
839 $version = empty($reg[2]) ? '' : $reg[2];
840 } elseif (preg_match('/chrome(\/|\s)([\d\.]+)/i', $user_agent, $reg)) {
841 $name = 'chrome';
842 $version = empty($reg[2]) ? '' : $reg[2];
843 } elseif (preg_match('/chrome/i', $user_agent, $reg)) {
844 // we can have 'chrome (Mozilla...) chrome x.y' in one string
845 $name = 'chrome';
846 } elseif (preg_match('/iceweasel/i', $user_agent)) {
847 $name = 'iceweasel';
848 } elseif (preg_match('/epiphany/i', $user_agent)) {
849 $name = 'epiphany';
850 } elseif (preg_match('/safari(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
851 $name = 'safari';
852 $version = empty($reg[2]) ? '' : $reg[2];
853 } elseif (preg_match('/opera(\/|\s)([\d\.]*)/i', $user_agent, $reg)) {
854 // Safari is often present in string for mobile but its not.
855 $name = 'opera';
856 $version = empty($reg[2]) ? '' : $reg[2];
857 } elseif (preg_match('/(MSIE\s([0-9]+\.[0-9]))|.*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) {
858 $name = 'ie';
859 $version = end($reg);
860 } elseif (preg_match('/(Windows NT\s([0-9]+\.[0-9])).*(Trident\/[0-9]+.[0-9];.*rv:([0-9]+\.[0-9]+))/i', $user_agent, $reg)) {
861 // MS products at end
862 $name = 'ie';
863 $version = end($reg);
864 } elseif (preg_match('/l[iy]n(x|ks)(\‍(|\/|\s)*([\d\.]+)/i', $user_agent, $reg)) {
865 // MS products at end
866 $name = 'textbrowser';
867 $version = empty($reg[3]) ? '' : $reg[3];
868 } elseif (preg_match('/w3m\/([\d\.]+)/i', $user_agent, $reg)) {
869 // MS products at end
870 $name = 'textbrowser';
871 $version = empty($reg[1]) ? '' : $reg[1];
872 }
873
874 if ($tablet) {
875 $layout = 'tablet';
876 } elseif ($phone) {
877 $layout = 'phone';
878 } else {
879 $layout = 'classic';
880 }
881
882 return array(
883 'browsername' => $name,
884 'browserversion' => $version,
885 'browseros' => $os,
886 'browserua' => $user_agent,
887 'layout' => $layout, // tablet, phone, classic
888 'phone' => $phone, // deprecated
889 'tablet' => $tablet // deprecated
890 );
891}
892
898function dol_shutdown()
899{
900 global $db;
901 $disconnectdone = false;
902 $depth = 0;
903 if (is_object($db) && !empty($db->connected)) {
904 $depth = $db->transaction_opened;
905 $disconnectdone = $db->close();
906 }
907 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));
908}
909
919function GETPOSTISSET($paramname)
920{
921 $isset = false;
922
923 $relativepathstring = $_SERVER["PHP_SELF"];
924 // Clean $relativepathstring
925 if (constant('DOL_URL_ROOT')) {
926 $relativepathstring = preg_replace('/^' . preg_quote(constant('DOL_URL_ROOT'), '/') . '/', '', $relativepathstring);
927 }
928 $relativepathstring = ltrim($relativepathstring, '/');
929 $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
930
931 // Code for search criteria persistence.
932 // Retrieve values if restore_lastsearch_values
933 if (!empty($_GET['restore_lastsearch_values'])) { // Use $_GET here and not GETPOST
934 if (!empty($_SESSION['lastsearch_values_' . $relativepathstring])) { // If there is saved values
935 $tmp = json_decode($_SESSION['lastsearch_values_' . $relativepathstring], true);
936 if (is_array($tmp)) {
937 foreach ($tmp as $key => $val) {
938 if ($key == $paramname) { // We are on the requested parameter
939 $isset = true;
940 break;
941 }
942 }
943 }
944 }
945 // If there is saved contextpage, limit, page or mode
946 if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_' . $relativepathstring])) {
947 $isset = true;
948 } elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_' . $relativepathstring])) {
949 $isset = true;
950 } elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_' . $relativepathstring])) {
951 $isset = true;
952 } elseif ($paramname == 'mode' && !empty($_SESSION['lastsearch_mode_' . $relativepathstring])) {
953 $isset = true;
954 }
955 } else {
956 $isset = (isset($_POST[$paramname]) || isset($_GET[$paramname])); // We must keep $_POST and $_GET here
957 }
958
959 return $isset;
960}
961
970function GETPOSTISARRAY($paramname, $method = 0)
971{
972 // for $method test need return the same $val as GETPOST
973 if (empty($method)) {
974 $val = isset($_GET[$paramname]) ? $_GET[$paramname] : (isset($_POST[$paramname]) ? $_POST[$paramname] : '');
975 } elseif ($method == 1) {
976 $val = isset($_GET[$paramname]) ? $_GET[$paramname] : '';
977 } elseif ($method == 2) {
978 $val = isset($_POST[$paramname]) ? $_POST[$paramname] : '';
979 } elseif ($method == 3) {
980 $val = isset($_POST[$paramname]) ? $_POST[$paramname] : (isset($_GET[$paramname]) ? $_GET[$paramname] : '');
981 } else {
982 $val = 'BadFirstParameterForGETPOST';
983 }
984
985 return is_array($val);
986}
987
988
999function GETPOSTINT($paramname, $method = 0, $nodefault = 0)
1000{
1001 return (int) GETPOST($paramname, 'int', $method, null, null, 0, $nodefault);
1002}
1003
1017function GETPOSTFLOAT($paramname, $rounding = '', $option = 2)
1018{
1019 // 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.)
1020 return (float) price2num(GETPOST($paramname), $rounding, $option);
1021}
1022
1038function GETPOSTDATE($prefix, $hourTime = '', $gm = 'auto', $saverestore = '')
1039{
1040 $m = array();
1041 if ($hourTime === 'getpost' || $hourTime === 'getpostend') {
1042 $hour = (GETPOSTISSET($prefix . 'hour') && GETPOSTINT($prefix . 'hour') >= 0) ? GETPOSTINT($prefix . 'hour') : ($hourTime === 'getpostend' ? 23 : 0);
1043 $minute = (GETPOSTISSET($prefix . 'min') && GETPOSTINT($prefix . 'min') >= 0) ? GETPOSTINT($prefix . 'min') : ($hourTime === 'getpostend' ? 59 : 0);
1044 $second = (GETPOSTISSET($prefix . 'sec') && GETPOSTINT($prefix . 'sec') >= 0) ? GETPOSTINT($prefix . 'sec') : ($hourTime === 'getpostend' ? 59 : 0);
1045 } elseif (preg_match('/^(\d\d):(\d\d):(\d\d)$/', $hourTime, $m)) {
1046 $hour = intval($m[1]);
1047 $minute = intval($m[2]);
1048 $second = intval($m[3]);
1049 } elseif ($hourTime === 'end') {
1050 $hour = 23;
1051 $minute = 59;
1052 $second = 59;
1053 } else {
1054 $hour = $minute = $second = 0;
1055 }
1056
1057 if (
1058 $saverestore
1059 && !GETPOSTISSET($prefix . 'day')
1060 && !GETPOSTISSET($prefix . 'month')
1061 && !GETPOSTISSET($prefix . 'year')
1062 && isset($_SESSION['DOLDATE_' . $saverestore . '_day'])
1063 && isset($_SESSION['DOLDATE_' . $saverestore . '_month'])
1064 && isset($_SESSION['DOLDATE_' . $saverestore . '_year'])
1065 ) {
1066 $day = $_SESSION['DOLDATE_' . $saverestore . '_day'];
1067 $month = $_SESSION['DOLDATE_' . $saverestore . '_month'];
1068 $year = $_SESSION['DOLDATE_' . $saverestore . '_year'];
1069 } else {
1070 $month = GETPOSTINT($prefix . 'month');
1071 $day = GETPOSTINT($prefix . 'day');
1072 $year = GETPOSTINT($prefix . 'year');
1073 }
1074
1075 // normalize out of range values
1076 $hour = (int) min($hour, 23);
1077 $minute = (int) min($minute, 59);
1078 $second = (int) min($second, 59);
1079
1080 if ($saverestore) {
1081 $_SESSION['DOLDATE_' . $saverestore . '_day'] = $day;
1082 $_SESSION['DOLDATE_' . $saverestore . '_month'] = $month;
1083 $_SESSION['DOLDATE_' . $saverestore . '_year'] = $year;
1084 }
1085
1086 //print "$hour, $minute, $second, $month, $day, $year, $gm<br>";
1087 return dol_mktime($hour, $minute, $second, $month, $day, $year, $gm);
1088}
1089
1131function GETPOST($paramname, $check = 'alphanohtml', $method = 0, $filter = null, $options = null, $noreplace = 0, $nodefault = 0)
1132{
1133 global $langs, $user;
1134
1135 if (empty($paramname)) { // Explicit test for null for phan.
1136 return 'BadFirstParameterForGETPOST';
1137 }
1138 if (empty($check)) {
1139 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);
1140 // Enable this line to know who call the GETPOST with '' $check parameter.
1141 //var_dump(getCallerInfoString());
1142 }
1143 if (in_array($paramname, array('sortfield', 'sortorder'))) { // Force the $check to a more appropriated value
1144 $check = 'aZ09comma';
1145 }
1146
1147 if (empty($method)) {
1148 $out = isset($_GET[$paramname]) ? $_GET[$paramname] : (isset($_POST[$paramname]) ? $_POST[$paramname] : '');
1149 } elseif ($method == 1) {
1150 $out = isset($_GET[$paramname]) ? $_GET[$paramname] : '';
1151 } elseif ($method == 2) {
1152 $out = isset($_POST[$paramname]) ? $_POST[$paramname] : '';
1153 } elseif ($method == 3) {
1154 $out = isset($_POST[$paramname]) ? $_POST[$paramname] : (isset($_GET[$paramname]) ? $_GET[$paramname] : '');
1155 } else {
1156 return 'BadThirdParameterForGETPOST';
1157 }
1158
1159 $relativepathstring = ''; // For static analysis - looks possibly undefined if not set.
1160
1161 if (empty($method) || $method == 3 || $method == 4) {
1162 $relativepathstring = (empty($_SERVER["PHP_SELF"]) ? '' : $_SERVER["PHP_SELF"]);
1163 // Clean $relativepathstring
1164 if (constant('DOL_URL_ROOT')) {
1165 $relativepathstring = preg_replace('/^' . preg_quote(constant('DOL_URL_ROOT'), '/') . '/', '', $relativepathstring);
1166 }
1167 $relativepathstring = ltrim($relativepathstring, '/');
1168 $relativepathstring = preg_replace('/^custom\//', '', $relativepathstring);
1169
1170 // Code for search criteria persistence.
1171 // Retrieve saved values if restore_lastsearch_values is set
1172 if (!empty($_GET['restore_lastsearch_values'])) { // Use $_GET here and not GETPOST
1173 if (!empty($_SESSION['lastsearch_values_' . $relativepathstring])) { // If there is saved values
1174 $tmp = json_decode($_SESSION['lastsearch_values_' . $relativepathstring], true);
1175 if (is_array($tmp)) {
1176 foreach ($tmp as $key => $val) {
1177 if ($key == $paramname) { // We are on the requested parameter
1178 $out = $val;
1179 break;
1180 }
1181 }
1182 }
1183 }
1184 // If there is saved contextpage, page or limit
1185 if ($paramname == 'contextpage' && !empty($_SESSION['lastsearch_contextpage_' . $relativepathstring])) {
1186 $out = $_SESSION['lastsearch_contextpage_' . $relativepathstring];
1187 } elseif ($paramname == 'limit' && !empty($_SESSION['lastsearch_limit_' . $relativepathstring])) {
1188 $out = $_SESSION['lastsearch_limit_' . $relativepathstring];
1189 } elseif ($paramname == 'page' && !empty($_SESSION['lastsearch_page_' . $relativepathstring])) {
1190 $out = $_SESSION['lastsearch_page_' . $relativepathstring];
1191 } elseif ($paramname == 'mode' && !empty($_SESSION['lastsearch_mode_' . $relativepathstring])) {
1192 $out = $_SESSION['lastsearch_mode_' . $relativepathstring];
1193 }
1194 } elseif (!isset($_GET['sortfield'])) {
1195 // Else, retrieve default values if we are not doing a sort
1196 // 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
1197 if (!empty($_GET['action']) && $_GET['action'] == 'create' && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
1198 // Search default value from $object->field
1199 global $object;
1200 '@phan-var-force CommonObject $object'; // Suppose it's a CommonObject for analysis, but other objects have the $fields field as well
1201 if (is_object($object) && isset($object->fields[$paramname]['default'])) {
1202 // @phan-suppress-next-line PhanTypePossiblyInvalidDimOffset
1203 $out = $object->fields[$paramname]['default'];
1204 }
1205 }
1206 if (getDolGlobalString('MAIN_ENABLE_DEFAULT_VALUES')) {
1207 if (!empty($_GET['action']) && (preg_match('/^create/', $_GET['action']) || preg_match('/^presend/', $_GET['action'])) && !isset($_GET[$paramname]) && !isset($_POST[$paramname])) {
1208 // Now search in setup to overwrite default values
1209 if (!empty($user->default_values)) { // $user->default_values defined from menu 'Setup - Default values'
1210 if (isset($user->default_values[$relativepathstring]['createform'])) {
1211 foreach ($user->default_values[$relativepathstring]['createform'] as $defkey => $defval) {
1212 $qualified = 0;
1213 if ($defkey != '_noquery_') {
1214 $tmpqueryarraytohave = explode('&', $defkey);
1215 $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
1216 $foundintru = 0;
1217 foreach ($tmpqueryarraytohave as $tmpquerytohave) {
1218 if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
1219 $foundintru = 1;
1220 }
1221 }
1222 if (!$foundintru) {
1223 $qualified = 1;
1224 }
1225 } else {
1226 $qualified = 1;
1227 }
1228
1229 if ($qualified) {
1230 if (isset($user->default_values[$relativepathstring]['createform'][$defkey][$paramname])) {
1231 $out = $user->default_values[$relativepathstring]['createform'][$defkey][$paramname];
1232 break;
1233 }
1234 }
1235 }
1236 }
1237 }
1238 } elseif (!empty($paramname) && !isset($_GET[$paramname]) && !isset($_POST[$paramname]) && empty($nodefault)) {
1239 // Management of default search_filters and sort order
1240 if (!empty($user->default_values)) {
1241 // $user->default_values defined from menu 'Setup - Default values'
1242 //var_dump($user->default_values[$relativepathstring]);
1243 if ($paramname == 'sortfield' || $paramname == 'sortorder') {
1244 // Sorted on which fields ? ASC or DESC ?
1245 if (isset($user->default_values[$relativepathstring]['sortorder'])) {
1246 // Even if paramname is sortfield, data are stored into ['sortorder...']
1247 foreach ($user->default_values[$relativepathstring]['sortorder'] as $defkey => $defval) {
1248 $qualified = 0;
1249 if ($defkey != '_noquery_') {
1250 $tmpqueryarraytohave = explode('&', $defkey);
1251 $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
1252 $foundintru = 0;
1253 foreach ($tmpqueryarraytohave as $tmpquerytohave) {
1254 if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
1255 $foundintru = 1;
1256 }
1257 }
1258 if (!$foundintru) {
1259 $qualified = 1;
1260 }
1261 } else {
1262 $qualified = 1;
1263 }
1264
1265 if ($qualified) {
1266 $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
1267 foreach ($user->default_values[$relativepathstring]['sortorder'][$defkey] as $key => $val) {
1268 if ($out) {
1269 $out .= ', ';
1270 }
1271 if ($paramname == 'sortfield') {
1272 $out .= dol_string_nospecial($key, '', $forbidden_chars_to_replace);
1273 }
1274 if ($paramname == 'sortorder') {
1275 $out .= dol_string_nospecial($val, '', $forbidden_chars_to_replace);
1276 }
1277 }
1278 //break; // No break for sortfield and sortorder so we can cumulate fields (is it really useful ?)
1279 }
1280 }
1281 }
1282 } elseif (isset($user->default_values[$relativepathstring]['filters'])) {
1283 foreach ($user->default_values[$relativepathstring]['filters'] as $defkey => $defval) { // $defkey is a querystring like 'a=b&c=d', $defval is key of user
1284 if (!empty($_GET['disabledefaultvalues'])) { // If set of default values has been disabled by a request parameter
1285 continue;
1286 }
1287 $qualified = 0;
1288 if ($defkey != '_noquery_') {
1289 $tmpqueryarraytohave = explode('&', $defkey);
1290 $tmpqueryarraywehave = explode('&', dol_string_nohtmltag($_SERVER['QUERY_STRING']));
1291 $foundintru = 0;
1292 foreach ($tmpqueryarraytohave as $tmpquerytohave) {
1293 if (!in_array($tmpquerytohave, $tmpqueryarraywehave)) {
1294 $foundintru = 1;
1295 }
1296 }
1297 if (!$foundintru) {
1298 $qualified = 1;
1299 }
1300 } else {
1301 $qualified = 1;
1302 }
1303
1304 if ($qualified && isset($user->default_values[$relativepathstring]['filters'][$defkey][$paramname])) {
1305 // We must keep $_POST and $_GET here
1306 if (isset($_POST['search_all']) || isset($_GET['search_all'])) {
1307 // We made a search from quick search menu, do we still use default filter ?
1308 if (!getDolGlobalString('MAIN_DISABLE_DEFAULT_FILTER_FOR_QUICK_SEARCH')) {
1309 $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
1310 $out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
1311 }
1312 } else {
1313 $forbidden_chars_to_replace = array(" ", "'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ";", "="); // we accept _, -, . and ,
1314 $out = dol_string_nospecial($user->default_values[$relativepathstring]['filters'][$defkey][$paramname], '', $forbidden_chars_to_replace);
1315 }
1316 break;
1317 }
1318 }
1319 }
1320 }
1321 }
1322 }
1323 }
1324 }
1325
1326 // Replace 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)
1327 // Example of variables: __DAY__, __MONTH__, __YEAR__, __MYCOMPANY_COUNTRY_ID__, __USER_ID__, ...
1328 // 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.
1329 '@phan-var-force string $paramname';
1330 if (!is_array($out) && empty($_POST[$paramname]) && empty($noreplace)) {
1331 if (preg_match('/__([A-Z0-9]+(?:_[A-Z0-9]+){0,3})__/i', $out)) { // If there is at least one substitution key, we try to replace all known substitution keys
1332 $substitutionarray = getCommonSubstitutionArray($langs, 0, null, $user, array('mycompany', 'date', 'system', 'user'));
1333 complete_substitutions_array($substitutionarray, $langs, $user);
1334
1335 $out = make_substitutions($out, $substitutionarray, $langs);
1336 }
1337 }
1338
1339 // Check type of variable and make sanitization according to this
1340 if (preg_match('/^array/', $check)) { // If 'array' or 'array:restricthtml' or 'array:aZ09' or 'array:int'
1341 $tmpcheck = 'alphanohtml';
1342 if ($out === null || $out === '') {
1343 $out = array();
1344 } elseif (!is_array($out)) {
1345 $out = explode(',', $out);
1346 } else {
1347 $tmparray = explode(':', $check);
1348 if (!empty($tmparray[1])) {
1349 $tmpcheck = $tmparray[1];
1350 }
1351 }
1352 foreach ($out as $outkey => $outval) {
1353 $out[$outkey] = sanitizeVal($outval, $tmpcheck, $filter, $options);
1354 }
1355 } else {
1356 // If field name is 'search_xxx' then we force the add of space after each < and > (when following char is numeric) because it means
1357 // 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
1358 if (strpos($paramname, 'search_') === 0) {
1359 $out = preg_replace('/([<>])([-+]?\d)/', '\1 \2', $out);
1360 }
1361
1362 // @phan-suppress-next-line UnknownSanitizeType
1363 $out = sanitizeVal($out, $check, $filter, $options);
1364 }
1365
1366 // Sanitizing for special parameters.
1367 // 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.
1368 // @TODO Merge backtopage with backtourl
1369 // @TODO Rename backtolist into backtopagelist
1370 // @TODO Merge urlfrom into backtourl
1371 if (preg_match('/^backto/i', $paramname) || preg_match('/^urlfrom/i', $paramname)) {
1372 $out = str_replace('\\', '/', $out); // Can be before the loop because only 1 char is replaced. No risk to get it after other replacements.
1373 $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.
1374 do {
1375 $oldstringtoclean = $out;
1376 $out = str_ireplace(array('javascript', 'vbscript', '&colon', '&#'), '', $out);
1377 $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'
1378 $out = preg_replace(array('/^[a-z]*\/\s*\/+/i'), '', $out); // We remove schema*// to remove external URL
1379 } while ($oldstringtoclean != $out);
1380 }
1381
1382 // Code for search criteria persistence.
1383 // Save data into session if key start with 'search_'
1384 if (empty($method) || $method == 3 || $method == 4) {
1385 if (preg_match('/^search_/', $paramname) || in_array($paramname, array('sortorder', 'sortfield'))) {
1386 //var_dump($paramname.' - '.$out.' '.$user->default_values[$relativepathstring]['filters'][$paramname]);
1387
1388 // We save search key only if $out not empty that means:
1389 // - posted value not empty, or
1390 // - 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).
1391
1392 if ($out != '' && isset($user)) { // $out = '0' or 'abc', it is a search criteria to keep
1393 $user->lastsearch_values_tmp[$relativepathstring][$paramname] = $out;
1394 }
1395 }
1396 }
1397
1398 if ($paramname == 'hashp' && $out == 'shared') {
1399 $out = ''; // We refuse to have hashp=shared as a parameter
1400 }
1401
1402 return $out;
1403}
1404
1414function sanitizeVal($out = '', $check = 'alphanohtml', $filter = null, $options = null)
1415{
1416 // TODO : use class "Validate" to perform tests (and add missing tests) if needed for factorize
1417 // Check is done after replacement
1418 if ($out === null) {
1419 $out = '';
1420 }
1421 switch ($check) {
1422 case 'none':
1423 case 'password':
1424 break;
1425 case 'int': // Check param is a numeric value (integer but also float or hexadecimal)
1426 if (!is_numeric($out)) {
1427 $out = '';
1428 }
1429 break;
1430 case 'intcomma':
1431 if (is_array($out)) {
1432 $out = implode(',', $out);
1433 }
1434 if (preg_match('/[^0-9,-]+/i', $out)) {
1435 $out = '';
1436 }
1437 break;
1438 case 'san_alpha':
1439 dol_syslog("Use of parameter value 'san_alpha' in GETPOST is deprecated. Use 'alphanohtml', 'aZ09comma', ...", LOG_WARNING);
1440 $out = filter_var($out, FILTER_SANITIZE_STRING);
1441 break;
1442 case 'email':
1443 $out = filter_var($out, FILTER_SANITIZE_EMAIL);
1444 break;
1445 case 'url':
1446 //$out = filter_var($out, FILTER_SANITIZE_URL); // Not reliable, replaced with FILTER_VALIDATE_URL
1447 $out = preg_replace('/[^:\/\[\]a-z0-9@\$\'\*\~\.\-_,;\?\!=%&+#]+/i', '', $out);
1448 // TODO Allow ( ) but only into password of https://login:password@domain...
1449 break;
1450 case 'aZ':
1451 if (!is_array($out)) {
1452 $out = trim($out);
1453 if (preg_match('/[^a-z]+/i', $out)) {
1454 $out = '';
1455 }
1456 }
1457 break;
1458 case 'aZ09':
1459 if (!is_array($out)) {
1460 $out = trim($out);
1461 if (preg_match('/[^a-z0-9_\-\.]+/i', $out)) {
1462 $out = '';
1463 }
1464 }
1465 break;
1466 case 'aZ09arobase': // great to sanitize $objecttype parameter
1467 if (!is_array($out)) {
1468 $out = trim($out);
1469 if (preg_match('/[^a-z0-9_\-\.@]+/i', $out)) {
1470 $out = '';
1471 }
1472 }
1473 break;
1474 case 'aZ09comma': // great to sanitize $sortfield or $sortorder params that can be 't.abc,t.def_gh'
1475 if (!is_array($out)) {
1476 $out = trim($out);
1477 if (preg_match('/[^a-z0-9_\-\.,]+/i', $out)) {
1478 $out = '';
1479 }
1480 }
1481 break;
1482 case 'alpha': // No html and no ../ and "
1483 case 'alphanohtml': // Recommended for most scalar parameters and search parameters. Not valid for json string.
1484 if (!is_array($out)) {
1485 $out = trim($out);
1486 do {
1487 $oldstringtoclean = $out;
1488 // Remove html tags
1489 $out = dol_string_nohtmltag($out, 0);
1490 // 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).
1491 $out = preg_replace('/\\\‍([0-9xu])/', '/\1', $out);
1492 // Remove also other dangerous string sequences
1493 // '../' or '..\' is dangerous because it allows dir transversals
1494 // '&#38', '&#0000038', '&#x26'... is a the char '&' alone but there is no reason to accept such way to encode input char
1495 // '"' = '&#34' = '&#0000034' = '&#x22' is dangerous because param in url can close the href= or src= and add javascript functions.
1496 // '&#47', '&#0000047', '&#x2F' is the char '/' but there is no reason to accept such way to encode this input char
1497 // '&#92' = '&#0000092' = '&#x5C' is the char '\' but there is no reason to accept such way to encode this input char
1498 $out = str_ireplace(array('../', '..\\', '&#38', '&#0000038', '&#x26', '&quot', '"', '&#34', '&#0000034', '&#x22', '&#47', '&#0000047', '&#x2F', '&#92', '&#0000092', '&#x5C'), '', $out);
1499 } while ($oldstringtoclean != $out);
1500 // keep lines feed
1501 }
1502 break;
1503 case 'alphawithlgt': // No " and no ../ but we keep balanced < > tags with no special chars inside. Can be used for email string like "Name <email@domain.com>". Less secured than 'alphanohtml'
1504 if (!is_array($out)) {
1505 $out = trim($out);
1506 do {
1507 $oldstringtoclean = $out;
1508 // Decode html entities
1509 $out = dol_html_entity_decode($out, ENT_COMPAT | ENT_HTML5, 'UTF-8');
1510 // 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).
1511 $out = preg_replace('/\\\‍([0-9xu])/', '/\1', $out);
1512 // Remove also other dangerous string sequences
1513 // '../' or '..\' is dangerous because it allows dir transversals
1514 // '&#38', '&#0000038', '&#x26'... is a the char '&' alone but there is no reason to accept such way to encode input char
1515 // '"' = '&#34' = '&#0000034' = '&#x22' is dangerous because param in url can close the href= or src= and add javascript functions.
1516 // '&#47', '&#0000047', '&#x2F' is the char '/' but there is no reason to accept such way to encode this input char
1517 // '&#92' = '&#0000092' = '&#x5C' is the char '\' but there is no reason to accept such way to encode this input char
1518 $out = str_ireplace(array('../', '..\\', '&#38', '&#0000038', '&#x26', '&quot', '"', '&#34', '&#0000034', '&#x22', '&#47', '&#0000047', '&#x2F', '&#92', '&#0000092', '&#x5C'), '', $out);
1519 } while ($oldstringtoclean != $out);
1520 }
1521 break;
1522 case 'nohtml': // No html. Valid for JSON strings.
1523 $out = dol_string_nohtmltag($out, 0);
1524 break;
1525 case 'restricthtmlnolink':
1526 case 'restricthtml': // Recommended for most html textarea
1527 case 'restricthtmlallowclass':
1528 case 'restricthtmlallowiframe':
1529 case 'restricthtmlallowlinkscript': // Allow link and script tag for head section.
1530 case 'restricthtmlallowunvalid':
1531 $out = dol_htmlwithnojs($out, 1, $check);
1532 break;
1533 case 'custom':
1534 if (!empty($out)) {
1535 if (empty($filter)) {
1536 return 'BadParameterForGETPOST - Param 3 of sanitizeVal()';
1537 }
1538 if (is_null($options)) {
1539 $options = 0;
1540 }
1541 $out = filter_var($out, $filter, $options);
1542 }
1543 break;
1544 default:
1545 dol_syslog("Error, you call sanitizeVal() with a bad value for the check type. Data will be sanitized with alphanohtml.", LOG_ERR);
1546 $out = GETPOST($out, 'alphanohtml');
1547 break;
1548 }
1549
1550 return $out;
1551}
1552
1561function dolSetCookie(string $cookiename, string $cookievalue, int $expire = -1)
1562{
1563 include_once DOL_DOCUMENT_ROOT.'/blockedlog/lib/securitycore.lib.php';
1564
1565 global $dolibarr_main_force_https;
1566
1567 if ($expire == -1) {
1568 $expire = (time() + (86400 * 354)); // keep cookie 1 year.
1569 }
1570
1571 if (PHP_VERSION_ID < 70300) {
1572 setcookie($cookiename, empty($cookievalue) ? '' : $cookievalue, empty($cookievalue) ? 0 : $expire, '/', '', !(empty($dolibarr_main_force_https) && isHTTPS() === false), true); // add tag httponly
1573 } else {
1574 // Only available for php >= 7.3
1575 $cookieparams = array(
1576 'expires' => empty($cookievalue) ? 0 : $expire,
1577 'path' => '/',
1578 //'domain' => '.mywebsite.com', // the dot at the beginning allows compatibility with subdomains
1579 'secure' => !(empty($dolibarr_main_force_https) && isHTTPS() === false),
1580 'httponly' => true,
1581 'samesite' => 'Lax' // None || Lax || Strict
1582 );
1583 setcookie($cookiename, empty($cookievalue) ? '' : $cookievalue, $cookieparams);
1584 }
1585 if (empty($cookievalue)) {
1586 unset($_COOKIE[$cookiename]);
1587 }
1588}
1589
1590if (!function_exists('dol_getprefix')) {
1601 function dol_getprefix($mode = '')
1602 {
1603 // If prefix is for email (we need to have $conf already loaded for this case)
1604 if ($mode == 'email') {
1605 global $conf;
1606
1607 if (getDolGlobalString('MAIL_PREFIX_FOR_EMAIL_ID')) { // If MAIL_PREFIX_FOR_EMAIL_ID is set
1608 if (getDolGlobalString('MAIL_PREFIX_FOR_EMAIL_ID') != 'SERVER_NAME') {
1609 return getDolGlobalString('MAIL_PREFIX_FOR_EMAIL_ID');
1610 } elseif (isset($_SERVER["SERVER_NAME"])) { // If MAIL_PREFIX_FOR_EMAIL_ID is set to 'SERVER_NAME'
1611 return $_SERVER["SERVER_NAME"];
1612 }
1613 }
1614
1615 // The recommended value if MAIL_PREFIX_FOR_EMAIL_ID is not defined (may be not defined for old versions)
1616 if (!empty($conf->file->instance_unique_id)) {
1617 return sha1('dolibarr' . $conf->file->instance_unique_id);
1618 }
1619
1620 // For backward compatibility when instance_unique_id is not set
1621 return sha1(DOL_DOCUMENT_ROOT . DOL_URL_ROOT);
1622 }
1623
1624 // If prefix is for session (no need to have $conf loaded)
1625 global $dolibarr_main_instance_unique_id, $dolibarr_main_cookie_cryptkey; // This is loaded by filefunc.inc.php
1626 $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
1627
1628 // The recommended value (may be not defined for old versions)
1629 if (!empty($tmp_instance_unique_id)) {
1630 return sha1('dolibarr' . $tmp_instance_unique_id);
1631 }
1632
1633 // For backward compatibility when instance_unique_id is not set
1634 if (isset($_SERVER["SERVER_NAME"]) && isset($_SERVER["DOCUMENT_ROOT"])) {
1635 return sha1($_SERVER["SERVER_NAME"] . $_SERVER["DOCUMENT_ROOT"] . DOL_DOCUMENT_ROOT . DOL_URL_ROOT);
1636 } else {
1637 return sha1(DOL_DOCUMENT_ROOT . DOL_URL_ROOT);
1638 }
1639 }
1640}
1641
1652function dol_include_once($relpath, $classname = '')
1653{
1654 global $conf, $langs, $user, $mysoc; // Do not remove this. They must be defined for files we make "include". Other globals var must be retrieved with $GLOBALS['var']
1655
1656 if (strpos($relpath, '..') !== false) {
1657 // Found a not valid path
1658 dol_syslog('functions::dol_include_once Tried to load a file with a path including a forbidden sequence ".." : ' . $relpath, LOG_WARNING);
1659 return false;
1660 }
1661 if (!preg_match('/\.php$/', $relpath)) {
1662 // Found a not valid path
1663 dol_syslog('functions::dol_include_once Tried to load a file that is not a PHP file : ' . $relpath, LOG_WARNING);
1664 return false;
1665 }
1666
1667 $fullpath = dol_buildpath($relpath);
1668
1669 if (!file_exists($fullpath)) {
1670 dol_syslog('functions::dol_include_once Tried to load unexisting file: ' . $relpath, LOG_WARNING);
1671 return false;
1672 }
1673 if (!empty($classname) && !class_exists($classname)) {
1674 return include $fullpath;
1675 } else {
1676 return include_once $fullpath;
1677 }
1678}
1679
1680
1694function dol_buildpath($path, $type = 0, $returnemptyifnotfound = 0)
1695{
1696 global $conf;
1697
1698 $path = preg_replace('/^\//', '', $path);
1699
1700 if (empty($type)) { // For a filesystem path
1701 $res = DOL_DOCUMENT_ROOT . '/' . $path; // Standard default path
1702 if (is_array($conf->file->dol_document_root)) {
1703 foreach ($conf->file->dol_document_root as $key => $dirroot) { // ex: array("main"=>"/home/main/htdocs", "alt0"=>"/home/dirmod/htdocs", ...)
1704 if ($key == 'main') {
1705 continue;
1706 }
1707 // if (@file_exists($dirroot.'/'.$path)) {
1708 if (@file_exists($dirroot . '/' . $path)) { // avoid [php:warn]
1709 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/...'
1710 continue;
1711 }
1712 $res = $dirroot . '/' . $path;
1713 return $res;
1714 }
1715 }
1716 }
1717 if ($returnemptyifnotfound) {
1718 // Not found into alternate dir
1719 if ($returnemptyifnotfound == 1 || !file_exists($res)) {
1720 return '';
1721 }
1722 }
1723 } else {
1724 // For an url path
1725 // We try to get local path of file on filesystem from url
1726 // Note that trying to know if a file on disk exist by forging path on disk from url
1727 // works only for some web server and some setup. This is bugged when
1728 // using proxy, rewriting, virtual path, etc...
1729 $res = '';
1730 if ($type == 1) {
1731 $res = DOL_URL_ROOT . '/' . $path; // Standard value
1732 }
1733 if ($type == 2) {
1734 $res = DOL_MAIN_URL_ROOT . '/' . $path; // Standard value
1735 }
1736 if ($type == 3) {
1737 $res = DOL_URL_ROOT . '/' . $path;
1738 }
1739
1740 foreach ($conf->file->dol_document_root as $key => $dirroot) { // ex: array(["main"]=>"/home/main/htdocs", ["alt0"]=>"/home/dirmod/htdocs", ...)
1741 if ($key == 'main') {
1742 if ($type == 3) {
1743 /*global $dolibarr_main_url_root;*/
1744
1745 // Define $urlwithroot
1746 $urlwithouturlroot = preg_replace('/' . preg_quote(DOL_URL_ROOT, '/') . '$/i', '', trim($conf->file->dol_main_url_root));
1747 $urlwithroot = $urlwithouturlroot . DOL_URL_ROOT; // This is to use external domain name found into config file
1748 //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
1749
1750 $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : $urlwithroot) . '/' . $path; // Test on start with http is for old conf syntax
1751 }
1752 continue;
1753 }
1754 $regs = array();
1755 preg_match('/^([^\?]+(\.css\.php|\.css|\.js\.php|\.js|\.png|\.jpg|\.php)?)/i', $path, $regs); // Take part before '?'
1756 if (!empty($regs[1])) {
1757 //print $key.'-'.$dirroot.'/'.$path.'-'.$conf->file->dol_url_root[$type].'<br>'."\n";
1758 //if (file_exists($dirroot.'/'.$regs[1])) {
1759 if (@file_exists($dirroot . '/' . $regs[1])) { // avoid [php:warn]
1760 if ($type == 1) {
1761 $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_URL_ROOT) . $conf->file->dol_url_root[$key] . '/' . $path;
1762 } elseif ($type == 2) {
1763 $res = (preg_match('/^http/i', $conf->file->dol_url_root[$key]) ? '' : DOL_MAIN_URL_ROOT) . $conf->file->dol_url_root[$key] . '/' . $path;
1764 } elseif ($type == 3) {
1765 /*global $dolibarr_main_url_root;*/
1766
1767 // Define $urlwithroot
1768 $urlwithouturlroot = preg_replace('/' . preg_quote(DOL_URL_ROOT, '/') . '$/i', '', trim($conf->file->dol_main_url_root));
1769 $urlwithroot = $urlwithouturlroot . DOL_URL_ROOT; // This is to use external domain name found into config file
1770 //$urlwithroot=DOL_MAIN_URL_ROOT; // This is to use same domain name than current
1771
1772 $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
1773 }
1774 break;
1775 }
1776 }
1777 }
1778 }
1779
1780 return $res;
1781}
1782
1796function dol_getThemeFilePath($file, $theme = '')
1797{
1798 global $conf;
1799
1800 if (empty($theme)) {
1801 $theme = $conf->theme;
1802 }
1803 $file = '/theme/'.$theme.'/'.preg_replace('/^\//', '', $file);
1804
1805 // No module registers a theme directory: the file can only be the native one.
1806 // Return it directly without an extra file_exists() call, like the historical code.
1807 if (empty($conf->modules_parts['theme'])) {
1808 return DOL_DOCUMENT_ROOT.$file;
1809 }
1810
1811 // A module may provide or override the theme: look into the native directory
1812 // first, then into the module-provided theme directories.
1813 if (file_exists(DOL_DOCUMENT_ROOT.$file)) {
1814 return DOL_DOCUMENT_ROOT.$file;
1815 }
1816 foreach ($conf->modules_parts['theme'] as $reldir) {
1817 $tmp = dol_buildpath($reldir.$file, 0, 1);
1818 if ($tmp) {
1819 return $tmp;
1820 }
1821 }
1822
1823 return '';
1824}
1825
1835function dolBuildUrl($url, $params = [], $addtoken = false, $anchor = '')
1836{
1837 global $db, $hookmanager;
1838
1839 if (!is_object($hookmanager)) {
1840 include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
1841 $hookmanager = new HookManager($db);
1842 }
1843 if ((!isset($params['mainmenu']) || empty($params['mainmenu'])) && GETPOSTISSET('mainmenu')) {
1844 $params = array_merge($params, ['mainmenu' => (GETPOST('mainmenu', 'restricthtml'))]);
1845 }
1846 if ((!isset($params['leftmenu'])/* || empty($params['leftmenu']) */) && GETPOSTISSET('leftmenu')) { // do not fill leftmenu if we have leftmenu=
1847 $params = array_merge($params, ['leftmenu' => (GETPOST('leftmenu', 'restricthtml'))]);
1848 }
1849 $parameters = [
1850 'path' => &$url,
1851 'params' => &$params,
1852 'addtoken' => &$addtoken,
1853 ];
1854 $hookmanager->executeHooks('buildurl', $parameters);
1855 if ($addtoken) {
1856 $params = array_merge($params, ['token' => newToken()]);
1857 }
1858 if ($params) {
1859 $url .= '?' . http_build_query($params);
1860 }
1861 if ($anchor) {
1862 $url .= '#' . preg_replace('/[^a-z]/i', '', $anchor);
1863 }
1864
1865 return $url;
1866}
1867
1878function dol_get_object_properties($obj, $properties = [])
1879{
1880 // Get real properties using get_object_vars() if $properties is empty
1881 if (empty($properties)) {
1882 return get_object_vars($obj);
1883 }
1884
1885 $existingProperties = [];
1886 $realProperties = get_object_vars($obj);
1887
1888 // Get the real or magic property values
1889 foreach ($properties as $property) {
1890 if (array_key_exists($property, $realProperties)) {
1891 // Real property, add the value
1892 $existingProperties[$property] = $obj->{$property};
1893 } elseif (property_exists($obj, $property)) {
1894 // Magic property
1895 $existingProperties[$property] = $obj->{$property};
1896 }
1897 }
1898
1899 return $existingProperties;
1900}
1901
1902
1920function dol_clone($srcobject, $native = 2)
1921{
1922 if ($native == 0) {
1923 // deprecated method, use the method with native = 2 instead
1924 dol_syslog("Warning, call to dol_clone() with the deprecated parameter native=0, use 2 instead", LOG_WARNING);
1925
1926 $tmpsavdb = null;
1927 if (isset($srcobject->db) && isset($srcobject->db->db) && is_object($srcobject->db->db) && get_class($srcobject->db->db) == 'PgSql\Connection') {
1928 $tmpsavdb = $srcobject->db;
1929 unset($srcobject->db); // Such property can not be serialized with pgsl (when object->db->db = 'PgSql\Connection')
1930 }
1931
1932 $myclone = unserialize(serialize($srcobject)); // serialize then unserialize is a hack to be sure to have a new object for all fields
1933
1934 if (!empty($tmpsavdb)) {
1935 $srcobject->db = $tmpsavdb;
1936 }
1937 } elseif ($native == 2) {
1938 // recommended method to have a full secured isolated cloned object
1939 $myclone = new stdClass();
1940 $tmparray = get_object_vars($srcobject); // return only public properties
1941
1942 if (is_array($tmparray)) {
1943 foreach ($tmparray as $propertykey => $propertyval) {
1944 if (is_scalar($propertyval) || is_array($propertyval)) {
1945 $myclone->$propertykey = $propertyval;
1946 }
1947 }
1948 }
1949 } else {
1950 $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)
1951 }
1952
1953 return $myclone;
1954}
1955
1956
1965function dol_clone_in_array($srcobject, $startlevel = 0)
1966{
1967 if (is_object($srcobject)) {
1968 $srcobject = get_object_vars($srcobject); // exclude private/protected properties
1969 }
1970
1971 if (is_array($srcobject)) {
1972 $result = [];
1973 foreach ($srcobject as $key => $value) {
1974 if (in_array($key, array('db', 'fields', 'error', 'errorhidden', 'errors', 'oldcopy', 'linkedObjects', 'linked_objects'))) {
1975 continue;
1976 }
1977 $result[$key] = dol_clone_in_array($value, $startlevel + 1);
1978 }
1979 return $result;
1980 }
1981
1982 return $srcobject;
1983}
1984
1985
1995function dol_size($size, $type = '')
1996{
1997 global $conf;
1998 if (empty($conf->dol_optimize_smallscreen)) {
1999 return $size;
2000 }
2001 if ($type == 'width' && $size > 250) {
2002 return 250;
2003 } else {
2004 return 10;
2005 }
2006}
2007
2008
2022function dol_sanitizeFileName($str, $newstr = '_', $unaccent = 1, $includequotes = 0, $allowdash = 0)
2023{
2024 $str = (string) $str;
2025
2026 // List of special chars for filenames in windows are defined on page https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
2027 // Char '>' '<' '|' '$' and ';' are special chars for shells.
2028 // Char '/' and '\' are file delimiters.
2029 // 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
2030 $filesystem_forbidden_chars = array('<', '>', '/', '\\', '?', '*', '|', '"', ':', '°', '$', ';', '`');
2031 if ($includequotes) {
2032 $filesystem_forbidden_chars[] = "'";
2033 }
2034 $tmp = dol_string_nospecial($unaccent ? dol_string_unaccent($str) : $str, $newstr, $filesystem_forbidden_chars);
2035 $tmp = preg_replace('/\-\-+/', '_', $tmp);
2036 if (empty($allowdash)) {
2037 $tmp = preg_replace('/\s+\-([^\s])/', ' _$1', $tmp);
2038 $tmp = preg_replace('/\s+\-$/', '', $tmp);
2039 }
2040 $tmp = str_replace('..', '', $tmp);
2041 $tmp = str_replace('~', $newstr, $tmp);
2042 $tmp = preg_replace('/\s{2,}/', ' ', $tmp);
2043
2044 return $tmp;
2045}
2046
2047
2060function dol_sanitizePathName($str, $newstr = '_', $unaccent = 0, $allowdash = 0)
2061{
2062 // List of special chars for filenames in windows are defined on page https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
2063 // Char '>' '<' '|' '$' ';' and '`' are special chars for shells.
2064 // Char '?' and '*' are for wild card chars.
2065 // Char '"' is dangerous.
2066 // Char '°' is just not expected.
2067 // 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
2068 // Chars '--' and '~' can be used for path transversal
2069 $filesystem_forbidden_chars = array('<', '>', '?', '*', '|', '"', '°', '$', ';', '`');
2070
2071 $tmp = $str;
2072 if ($unaccent) {
2073 $tmp = dol_string_unaccent($tmp);
2074 }
2075 $tmp = dol_string_nospecial($tmp, $newstr, $filesystem_forbidden_chars);
2076 $tmp = preg_replace('/\-\-+/', $newstr, $tmp);
2077 if (empty($allowdash)) {
2078 $tmp = preg_replace('/\s+\-([^\s])/', ' '.$newstr.'$1', $tmp);
2079 $tmp = preg_replace('/\s+\-$/', '', $tmp);
2080 }
2081 $tmp = str_replace('..', $newstr, $tmp);
2082 $tmp = str_replace('~', $newstr, $tmp);
2083 $tmp = preg_replace('/\s{2,}/', ' ', $tmp);
2084
2085 return $tmp;
2086}
2087
2096function dol_sanitizeUrl($stringtoclean, $type = 1)
2097{
2098 // 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)
2099 // We should use dol_string_nounprintableascii but function may not be yet loaded/available
2100 $stringtoclean = preg_replace('/[\x00-\x1F\x7F]/u', '', $stringtoclean); // /u operator makes UTF8 valid characters being ignored so are not included into the replace
2101 // We clean html comments because some hacks try to obfuscate evil strings by inserting HTML comments. Example: on<!-- -->error=alert(1)
2102 $stringtoclean = preg_replace('/<!--[^>]*-->/', '', $stringtoclean);
2103
2104 $stringtoclean = str_replace('\\', '/', $stringtoclean);
2105 if ($type == 1) {
2106 // removing : should disable links to external url like http:aaa)
2107 // removing ';' should disable "named" html entities encode into an url (we should not have this into an url)
2108 $stringtoclean = str_replace(array(':', ';', '@'), '', $stringtoclean);
2109 }
2110
2111 do {
2112 $oldstringtoclean = $stringtoclean;
2113 // removing '&colon' should disable links to external url like http:aaa)
2114 // removing '&#' should disable "numeric" html entities encode into an url (we should not have this into an url)
2115 $stringtoclean = str_ireplace(array('javascript', 'vbscript', '&colon', '&#'), '', $stringtoclean);
2116 } while ($oldstringtoclean != $stringtoclean);
2117
2118 if ($type == 1) {
2119 // removing '//' should disable links to external url like //aaa or http//)
2120 $stringtoclean = preg_replace(array('/^[a-z]*\/\/+/i'), '', $stringtoclean);
2121 }
2122
2123 // A raw < or > can not be part of a valid URL. We encode them, so the result can not open an html tag or close an inline script block (</script does not need a >).
2124 $stringtoclean = str_replace(array('<', '>'), array('%3C', '%3E'), $stringtoclean);
2125
2126 return $stringtoclean;
2127}
2128
2135function dol_sanitizeEmail($stringtoclean)
2136{
2137 do {
2138 $oldstringtoclean = $stringtoclean;
2139 $stringtoclean = str_ireplace(array('"', ':', '[', ']', "\n", "\r", '\\', '\/'), '', $stringtoclean);
2140 } while ($oldstringtoclean != $stringtoclean);
2141
2142 return $stringtoclean;
2143}
2144
2153function dol_sanitizeKeyCode($str)
2154{
2155 return preg_replace('/[^\w]+/', '', $str);
2156}
2157
2158
2167function dol_string_unaccent($str)
2168{
2169 if (is_null($str)) {
2170 return '';
2171 }
2172
2173 if (utf8_check($str)) {
2174 if (extension_loaded('intl') && getDolGlobalString('MAIN_UNACCENT_USE_TRANSLITERATOR')) {
2175 $transliterator = Transliterator::createFromRules(':: Any-Latin; :: Latin-ASCII; :: NFD; :: [:Nonspacing Mark:] Remove; :: NFC;', Transliterator::FORWARD);
2176 return $transliterator->transliterate($str);
2177 }
2178 // See http://www.utf8-chartable.de/
2179 $string = rawurlencode($str);
2180 $replacements = array(
2181 '%C3%80' => 'A', '%C3%81' => 'A', '%C3%82' => 'A', '%C3%83' => 'A', '%C3%84' => 'A', '%C3%85' => 'A',
2182 '%C3%87' => 'C',
2183 '%C3%88' => 'E', '%C3%89' => 'E', '%C3%8A' => 'E', '%C3%8B' => 'E',
2184 '%C3%8C' => 'I', '%C3%8D' => 'I', '%C3%8E' => 'I', '%C3%8F' => 'I',
2185 '%C3%91' => 'N',
2186 '%C3%92' => 'O', '%C3%93' => 'O', '%C3%94' => 'O', '%C3%95' => 'O', '%C3%96' => 'O', '%C5%90' => 'O',
2187 '%C5%A0' => 'S',
2188 '%C3%99' => 'U', '%C3%9A' => 'U', '%C3%9B' => 'U', '%C3%9C' => 'U', '%C5%B0' => 'U',
2189 '%C3%9D' => 'Y', '%C5%B8' => 'y',
2190 '%C3%A0' => 'a', '%C3%A1' => 'a', '%C3%A2' => 'a', '%C3%A3' => 'a', '%C3%A4' => 'a', '%C3%A5' => 'a',
2191 '%C3%A7' => 'c',
2192 '%C3%A8' => 'e', '%C3%A9' => 'e', '%C3%AA' => 'e', '%C3%AB' => 'e',
2193 '%C3%AC' => 'i', '%C3%AD' => 'i', '%C3%AE' => 'i', '%C3%AF' => 'i',
2194 '%C3%B1' => 'n',
2195 '%C3%B2' => 'o', '%C3%B3' => 'o', '%C3%B4' => 'o', '%C3%B5' => 'o', '%C3%B6' => 'o', '%C5%91' => 'o',
2196 '%C5%A1' => 's',
2197 '%C3%B9' => 'u', '%C3%BA' => 'u', '%C3%BB' => 'u', '%C3%BC' => 'u', '%C5%B1' => 'u',
2198 '%C3%BD' => 'y', '%C3%BF' => 'y',
2199 '%CC%80' => '',
2200 '%CC%81' => '',
2201 '%CC%82' => '',
2202 '%CC%83' => '',
2203 '%CC%84' => '',
2204 '%CC%85' => '',
2205 '%CC%86' => '',
2206 '%CC%87' => '',
2207 '%CC%88' => '',
2208 '%CC%89' => '',
2209 '%CC%8A' => '',
2210 '%CC%8B' => '',
2211 '%CC%8C' => '',
2212 '%CC%8D' => '',
2213 '%CC%8E' => '',
2214 '%CC%8F' => '',
2215 '%CC%90' => '',
2216 '%CC%91' => '',
2217 '%CC%A7' => ''
2218 );
2219 $string = strtr($string, $replacements);
2220 return rawurldecode($string);
2221 } else {
2222 // See http://www.ascii-code.com/
2223 $string = strtr(
2224 $str,
2225 "\xC0\xC1\xC2\xC3\xC4\xC5\xC7
2226 \xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1
2227 \xD2\xD3\xD4\xD5\xD8\xD9\xDA\xDB\xDD
2228 \xE0\xE1\xE2\xE3\xE4\xE5\xE7\xE8\xE9\xEA\xEB
2229 \xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF8
2230 \xF9\xFA\xFB\xFC\xFD\xFF",
2231 "AAAAAAC
2232 EEEEIIIIDN
2233 OOOOOUUUY
2234 aaaaaaceeee
2235 iiiidnooooo
2236 uuuuyy"
2237 );
2238 $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"));
2239 return $string;
2240 }
2241}
2242
2256function dol_string_nospecial($str, $newstr = '_', $badcharstoreplace = '', $badcharstoremove = '', $keepspaces = 0)
2257{
2258 $forbidden_chars_to_replace = array("'", "/", "\\", ":", "*", "?", "\"", "<", ">", "|", "[", "]", ",", ";", "=", '°', '$', ';'); // more complete than dol_sanitizeFileName
2259 if (empty($keepspaces)) {
2260 $forbidden_chars_to_replace[] = " ";
2261 }
2262 $forbidden_chars_to_remove = array();
2263 //$forbidden_chars_to_remove=array("(",")");
2264
2265 if (is_array($badcharstoreplace)) {
2266 $forbidden_chars_to_replace = $badcharstoreplace;
2267 }
2268 if (is_array($badcharstoremove)) {
2269 $forbidden_chars_to_remove = $badcharstoremove;
2270 }
2271
2272 // @phan-suppress-next-line PhanPluginSuspiciousParamOrderInternal
2273 return str_replace($forbidden_chars_to_replace, $newstr, str_replace($forbidden_chars_to_remove, "", $str));
2274}
2275
2276
2290function dol_string_nounprintableascii($str, $removetabcrlf = 1)
2291{
2292 if ($removetabcrlf) {
2293 return preg_replace('/[\x00-\x1F\x7F]/u', '', $str); // /u operator makes UTF8 valid characters being ignored so are not included into the replace
2294 } else {
2295 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
2296 }
2297}
2298
2305function dolSlugify($stringtoslugify)
2306{
2307 $slug = dol_string_unaccent($stringtoslugify);
2308
2309 // Convert special characters to their ASCII equivalents
2310 if (function_exists('iconv')) {
2311 $slug = iconv('UTF-8', 'ASCII//TRANSLIT//IGNORE', $slug);
2312 }
2313
2314 // Convert to lowercase
2315 $slug = strtolower($slug);
2316
2317 // Replace non-alphanumeric characters with hyphens
2318 $slug = preg_replace('/[^a-z0-9]+/', '-', $slug);
2319
2320 // Remove leading and trailing hyphens
2321 $slug = trim($slug, '-');
2322
2323 return $slug;
2324}
2325
2334function dol_escape_js($stringtoescape, $mode = 0, $noescapebackslashn = 0)
2335{
2336 if (is_null($stringtoescape)) {
2337 return '';
2338 }
2339
2340 // escape quotes and backslashes, newlines, etc.
2341 $substitjs = array("&#039;" => "\\'", "\r" => '\\r');
2342 //$substitjs['</']='<\/'; // We removed this. Should be useless.
2343 if (empty($noescapebackslashn)) {
2344 $substitjs["\n"] = '\\n';
2345 $substitjs['\\'] = '\\\\';
2346 }
2347 if (empty($mode)) {
2348 $substitjs["'"] = "\\'";
2349 $substitjs['"'] = "\\'";
2350 } elseif ($mode == 1) {
2351 $substitjs["'"] = "\\'";
2352 } elseif ($mode == 2) {
2353 $substitjs['"'] = '\\"';
2354 } elseif ($mode == 3) {
2355 $substitjs["'"] = "\\'";
2356 $substitjs['"'] = "\\\"";
2357 }
2358 return strtr((string) $stringtoescape, $substitjs);
2359}
2360
2370function dol_escape_uri($stringtoescape)
2371{
2372 return rawurlencode($stringtoescape);
2373}
2374
2381function dol_escape_json($stringtoescape)
2382{
2383 return str_replace('"', '\"', $stringtoescape);
2384}
2385
2393function dol_escape_php($stringtoescape, $stringforquotes = 2)
2394{
2395 if (is_null($stringtoescape)) {
2396 return '';
2397 }
2398
2399 if ($stringforquotes == 2) {
2400 return str_replace('"', "'", $stringtoescape);
2401 } elseif ($stringforquotes == 1) {
2402 // We remove the \ char.
2403 // If we allow the \ char, we can have $stringtoescape =
2404 // abc\';phpcodedanger; so the escapement will become
2405 // abc\\';phpcodedanger; and injecting this into
2406 // $a='...' will give $ac='abc\\';phpcodedanger;
2407 $stringtoescape = str_replace('\\', '', $stringtoescape);
2408 return str_replace("'", "\'", str_replace('"', "'", $stringtoescape));
2409 }
2410
2411 return 'Bad parameter for stringforquotes in dol_escape_php';
2412}
2413
2420function dol_escape_all($stringtoescape)
2421{
2422 return preg_replace('/[^a-z0-9_]/i', '', $stringtoescape);
2423}
2424
2431function dol_escape_xml($stringtoescape)
2432{
2433 return $stringtoescape;
2434}
2435
2436
2444function dol_strtolower($string, $encoding = "UTF-8")
2445{
2446 if (function_exists('mb_strtolower')) {
2447 return mb_strtolower($string, $encoding);
2448 } else {
2449 return strtolower($string);
2450 }
2451}
2452
2461function dol_strtoupper($string, $encoding = "UTF-8")
2462{
2463 if (function_exists('mb_strtoupper')) {
2464 return mb_strtoupper($string, $encoding);
2465 } else {
2466 return strtoupper($string);
2467 }
2468}
2469
2478function dol_ucfirst($string, $encoding = "UTF-8")
2479{
2480 if (function_exists('mb_substr')) {
2481 return mb_strtoupper(mb_substr($string, 0, 1, $encoding), $encoding) . mb_substr($string, 1, null, $encoding);
2482 } else {
2483 return ucfirst($string);
2484 }
2485}
2486
2495function dol_ucwords($string, $encoding = "UTF-8")
2496{
2497 if (function_exists('mb_convert_case')) {
2498 return mb_convert_case($string, MB_CASE_TITLE, $encoding);
2499 } else {
2500 return ucwords($string);
2501 }
2502}
2503
2504
2510function getCallerInfoString()
2511{
2512 $backtrace = debug_backtrace();
2513 $msg = "";
2514 if (count($backtrace) >= 1) {
2515 $pos = 1;
2516 if (count($backtrace) == 1) {
2517 $pos = 0;
2518 }
2519 $trace = $backtrace[$pos];
2520 if (isset($trace['file'], $trace['line'])) {
2521 $msg = " From {$trace['file']}:{$trace['line']}.";
2522 }
2523 }
2524 return $msg;
2525}
2526
2549function dol_syslog($message, $level = LOG_INFO, $ident = 0, $suffixinfilename = '', $restricttologhandler = '', $logcontext = null)
2550{
2551 global $conf, $user, $debugbar;
2552
2553 // If syslog module enabled
2554 if (!isModEnabled('syslog')) {
2555 return;
2556 }
2557
2558 // Check if we are into execution of code of a website
2559 if (defined('USEEXTERNALSERVER') && !defined('USEDOLIBARRSERVER') && !defined('USEDOLIBARREDITOR')) {
2560 global $website, $websitekey;
2561 if (is_object($website) && !empty($website->ref)) {
2562 $suffixinfilename .= '_website_' . $website->ref;
2563 } elseif (!empty($websitekey)) {
2564 $suffixinfilename .= '_website_' . $websitekey;
2565 }
2566 }
2567
2568 // Check if we have a forced suffix
2569 if (defined('USESUFFIXINLOG')) {
2570 $suffixinfilename .= constant('USESUFFIXINLOG');
2571 }
2572
2573 if ($ident < 0) {
2574 foreach ($conf->loghandlers as $loghandlerinstance) {
2575 $loghandlerinstance->setIdent($ident);
2576 }
2577 }
2578
2579 if (!empty($message)) {
2580 // Test log level
2581 // @phan-suppress-next-line PhanPluginDuplicateArrayKey
2582 $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');
2583
2584 if (!array_key_exists($level, $logLevels)) {
2585 dol_syslog('Error Bad Log Level ' . $level, LOG_ERR);
2586 $level = LOG_ERR;
2587 }
2588 if ($level > getDolGlobalInt('SYSLOG_LEVEL')) {
2589 return;
2590 }
2591
2592 if (!getDolGlobalString('MAIN_SHOW_PASSWORD_INTO_LOG')) {
2593 $message = preg_replace('/password=\'[^\']*\'/', 'password=\'hidden\'', $message); // protection to avoid to have value of password in log
2594 }
2595
2596 // If adding log inside HTML page is required
2597 if ((!empty($_REQUEST['logtohtml']) && getDolGlobalString('MAIN_ENABLE_LOG_TO_HTML'))
2598 || (is_object($user) && $user->hasRight('debugbar', 'read') && is_object($debugbar))
2599 ) {
2600 $ospid = sprintf("%7s", dol_trunc((string) getmypid(), 7, 'right', 'UTF-8', 1));
2601 $osuser = " " . sprintf("%6s", dol_trunc(function_exists('posix_getuid') ? posix_getuid() : '', 6, 'right', 'UTF-8', 1));
2602
2603 $conf->logbuffer[] = dol_print_date(dol_now(), "%Y-%m-%d %H:%M:%S") . " " . sprintf("%-7s", $logLevels[$level]) . " " . $ospid . " " . $osuser . " " . $message;
2604 }
2605
2606 //TODO: Remove this. MAIN_ENABLE_LOG_INLINE_HTML should be deprecated and use a log handler dedicated to HTML output
2607 // If html log tag enabled and url parameter log defined, we show output log on HTML comments
2608 if (getDolGlobalString('MAIN_ENABLE_LOG_INLINE_HTML') && GETPOSTINT("log")) {
2609 print "\n\n<!-- Log start\n";
2610 print dol_escape_htmltag($message) . "\n";
2611 print "Log end -->\n";
2612 }
2613
2614 $data = array(
2615 'message' => $message,
2616 'script' => (isset($_SERVER['PHP_SELF']) ? basename($_SERVER['PHP_SELF'], '.php') : ''),
2617 'level' => $level,
2618 'user' => ((is_object($user) && $user->id) ? $user->login : ''),
2619 'ip' => '',
2620 'osuser' => function_exists('posix_getuid') ? (string) posix_getuid() : '',
2621 'ospid' => (string) getmypid() // on linux, max value is defined into cat /proc/sys/kernel/pid_max
2622 );
2623
2624 // For log, we want the reliable IP first.
2625 $remoteip = getUserRemoteIP(1); // Get ip when page run on a web server
2626 if (!empty($remoteip)) {
2627 $data['ip'] = $remoteip;
2628 // This is when server run behind a reverse proxy
2629 // A HTTP_X_FORWARDED_FOR as format "ip real of user, ip of proxy1, ip of proxy2, ..."
2630 // $data['ip'] is last
2631 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
2632 $tmpips = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
2633 $data['ip'] = '';
2634 $foundremoteip = 0;
2635 $j = 0;
2636 foreach ($tmpips as $tmpip) {
2637 $tmpip = trim($tmpip);
2638 if (strtolower($tmpip) == strtolower($remoteip)) {
2639 $foundremoteip = 1;
2640 }
2641 if (empty($data['ip'])) {
2642 $data['ip'] = $tmpip;
2643 } else {
2644 $j++;
2645 $data['ip'] .= (($j == 1) ? ' [via ' : ',') . $tmpip;
2646 }
2647 }
2648 if (!$foundremoteip) {
2649 $j++;
2650 $data['ip'] .= (($j == 1) ? ' [via ' : ',') . $remoteip;
2651 }
2652 $data['ip'] .= (($j > 0) ? ']' : '');
2653 } elseif (!empty($_SERVER['HTTP_CLIENT_IP'])) {
2654 $tmpips = explode(',', $_SERVER['HTTP_CLIENT_IP']);
2655 $data['ip'] = '';
2656 $foundremoteip = 0;
2657 $j = 0;
2658 foreach ($tmpips as $tmpip) {
2659 $tmpip = trim($tmpip);
2660 if (strtolower($tmpip) == strtolower($remoteip)) {
2661 $foundremoteip = 1;
2662 }
2663 if (empty($data['ip'])) {
2664 $data['ip'] = $tmpip;
2665 } else {
2666 $j++;
2667 $data['ip'] .= (($j == 1) ? ' [via ' : ',') . $tmpip;
2668 }
2669 }
2670 if (!$foundremoteip) {
2671 $j++;
2672 $data['ip'] .= (($j == 1) ? ' [via ' : ',') . $remoteip;
2673 }
2674 $data['ip'] .= (($j > 0) ? ']' : '');
2675 }
2676 } elseif (!empty($_SERVER['SERVER_ADDR'])) {
2677 // This is when PHP session is ran inside a web server but not inside a client request (example: init code of apache)
2678 $data['ip'] = (string) $_SERVER['SERVER_ADDR'];
2679 } elseif (!empty($_SERVER['COMPUTERNAME'])) {
2680 // 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).
2681 $data['ip'] = (string) $_SERVER['COMPUTERNAME'];
2682 } else {
2683 $data['ip'] = '???';
2684 }
2685
2686 if (!empty($_SERVER['USERNAME'])) {
2687 // 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).
2688 $data['osuser'] = (string) $_SERVER['USERNAME'];
2689 } elseif (!empty($_SERVER['LOGNAME'])) {
2690 // 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).
2691 $data['osuser'] = (string) $_SERVER['LOGNAME'];
2692 }
2693
2694 // Loop on each log handler and send output
2695 foreach ($conf->loghandlers as $loghandlerinstance) {
2696 if ($restricttologhandler && $loghandlerinstance->code != $restricttologhandler) {
2697 continue;
2698 }
2699 $loghandlerinstance->export($data, $suffixinfilename);
2700 }
2701 unset($data);
2702 }
2703
2704 if ($ident > 0) {
2705 foreach ($conf->loghandlers as $loghandlerinstance) {
2706 $loghandlerinstance->setIdent($ident);
2707 }
2708 }
2709}
2710
2711
2725function dol_format_address($object, $withcountry = 0, $sep = "\n", $outputlangs = null, $mode = 0, $extralangcode = '')
2726{
2727 global $langs, $hookmanager;
2728
2729 $ret = '';
2730 $countriesusingstate = array('AU', 'CA', 'US', 'IN', 'GB', 'ES', 'UK', 'TR', 'CN'); // See also MAIN_FORCE_STATE_INTO_ADDRESS
2731
2732 // See format of addresses on https://en.wikipedia.org/wiki/Address
2733 // Address
2734 if (empty($mode)) {
2735 $ret .= (($extralangcode && !empty($object->array_languages['address'][$extralangcode])) ? $object->array_languages['address'][$extralangcode] : (empty($object->address) ? '' : preg_replace('/(\r\n|\r|\n)+/', $sep, $object->address)));
2736 }
2737 // Zip/Town/State
2738 if (isset($object->country_code) && in_array($object->country_code, array('AU', 'CA', 'US', 'CN')) || getDolGlobalString('MAIN_FORCE_STATE_INTO_ADDRESS')) {
2739 // US: title firstname name \n address lines \n town, state, zip \n country
2740 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2741 $ret .= (($ret && $town) ? $sep : '') . $town;
2742
2743 if (!empty($object->state)) {
2744 $ret .= ($ret ? ($town ? ", " : $sep) : '') . $object->state;
2745 }
2746 if (!empty($object->zip)) {
2747 $ret .= ($ret ? (($town || $object->state) ? ", " : $sep) : '') . $object->zip;
2748 }
2749 } elseif (isset($object->country_code) && in_array($object->country_code, array('GB', 'UK'))) {
2750 // UK: title firstname name \n address lines \n town state \n zip \n country
2751 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2752 $ret .= ($ret ? $sep : '') . $town;
2753 if (!empty($object->state)) {
2754 $ret .= ($ret ? ", " : '') . $object->state;
2755 }
2756 if (!empty($object->zip)) {
2757 $ret .= ($ret ? $sep : '') . $object->zip;
2758 }
2759 } elseif (isset($object->country_code) && in_array($object->country_code, array('ES', 'TR'))) {
2760 // ES: title firstname name \n address lines \n zip town \n state \n country
2761 $ret .= ($ret ? $sep : '') . $object->zip;
2762 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2763 $ret .= ($town ? (($object->zip ? ' ' : '') . $town) : '');
2764 if (!empty($object->state)) {
2765 $ret .= $sep . $object->state;
2766 }
2767 } elseif (isset($object->country_code) && in_array($object->country_code, array('JP'))) {
2768 // JP: In romaji, title firstname name\n address lines \n [state,] town zip \n country
2769 // See https://www.sljfaq.org/afaq/addresses.html
2770 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2771 $ret .= ($ret ? $sep : '') . ($object->state ? $object->state . ', ' : '') . $town . ($object->zip ? ' ' : '') . $object->zip;
2772 } elseif (isset($object->country_code) && in_array($object->country_code, array('IT'))) {
2773 // IT: title firstname name\n address lines \n zip town state_code \n country
2774 $ret .= ($ret ? $sep : '') . $object->zip;
2775 $town = ($extralangcode ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2776 $ret .= ($town ? (($object->zip ? ' ' : '') . $town) : '');
2777 $ret .= (empty($object->state_code) ? '' : (' ' . $object->state_code));
2778 } else {
2779 // Other: title firstname name \n address lines \n zip town[, state] \n country
2780 $town = (($extralangcode && !empty($object->array_languages['address'][$extralangcode])) ? $object->array_languages['town'][$extralangcode] : (empty($object->town) ? '' : $object->town));
2781 $ret .= !empty($object->zip) ? (($ret ? $sep : '') . $object->zip) : '';
2782 $ret .= ($town ? (($object->zip ? ' ' : ($ret ? $sep : '')) . $town) : '');
2783 if (!empty($object->state) && in_array($object->country_code, $countriesusingstate)) {
2784 $ret .= ($ret ? ", " : '') . $object->state;
2785 }
2786 }
2787
2788 if (!is_object($outputlangs)) {
2789 $outputlangs = $langs;
2790 }
2791 if ($withcountry) {
2792 $langs->load("dict");
2793 $ret .= (empty($object->country_code) ? '' : ($ret ? $sep : '') . $outputlangs->convToOutputCharset($outputlangs->transnoentitiesnoconv("Country" . $object->country_code)));
2794 }
2795 if ($hookmanager) {
2796 $parameters = array('withcountry' => $withcountry, 'sep' => $sep, 'outputlangs' => $outputlangs, 'mode' => $mode, 'extralangcode' => $extralangcode);
2797 $reshook = $hookmanager->executeHooks('formatAddress', $parameters, $object);
2798 if ($reshook > 0) {
2799 $ret = '';
2800 }
2801 $ret .= $hookmanager->resPrint;
2802 }
2803
2804 return $ret;
2805}
2806
2807
2808
2818function dol_strftime($fmt, $ts = false, $is_gmt = false)
2819{
2820 if ((abs($ts) <= 0x7FFFFFFF)) { // check if number in 32-bit signed range
2821 return dol_print_date($ts, $fmt, $is_gmt);
2822 } else {
2823 return 'Error date outside supported range';
2824 }
2825}
2826
2849function dol_print_date($time, $format = '', $tzoutput = 'auto', $outputlangs = null, $encodetooutput = false, $decorate = 0)
2850{
2851 global $conf, $langs;
2852
2853 // If date undefined or "", we return ""
2854 if (dol_strlen((string) $time) == 0) {
2855 return ''; // $time=0 allowed (it means 01/01/1970 00:00:00)
2856 }
2857
2858 if ($tzoutput === 'auto') {
2859 $tzoutput = (empty($conf) ? 'tzserver' : (isset($conf->tzuserinputkey) ? $conf->tzuserinputkey : 'tzserver'));
2860 }
2861
2862 // Clean parameters
2863 $to_gmt = false; // false if we want date in server timezone, true if we want to add offset
2864 $offsettz = $offsetdst = 0;
2865 if ($tzoutput) {
2866 $to_gmt = true; // For backward compatibility
2867 if (is_string($tzoutput)) {
2868 if ($tzoutput == 'tzserver') {
2869 $to_gmt = false;
2870 $offsettzstring = @date_default_timezone_get(); // Example 'Europe/Berlin' or 'Indian/Reunion'
2871 // @phan-suppress-next-line PhanPluginRedundantAssignment
2872 $offsettz = 0; // Timezone offset with server timezone (because to_gmt is false), so 0
2873 // @phan-suppress-next-line PhanPluginRedundantAssignment
2874 $offsetdst = 0; // Dst offset with server timezone (because to_gmt is false), so 0
2875 } elseif ($tzoutput == 'tzuser' || $tzoutput == 'tzuserrel') {
2876 $to_gmt = true;
2877 // if no session (by example in cron) may use MAIN_DOLIBARR_USER_TIMEZONE instead UTC
2878 $offsettzstring = (empty($_SESSION['dol_tz_string']) ? getDolGlobalString('MAIN_DOLIBARR_USER_TIMEZONE', 'UTC') : $_SESSION['dol_tz_string']); // Example 'Europe/Berlin' or 'Indian/Reunion'
2879
2880 if (class_exists('DateTimeZone')) {
2881 try {
2882 $user_date_tz = new DateTimeZone($offsettzstring);
2883 } catch (Exception $e) {
2884 // Bad value for $offsettzstring
2885 dol_syslog("DateInvalidTimeZoneException for timezone string '".$offsettzstring."'. Falling back to UTC.", LOG_ERR);
2886 $user_date_tz = new DateTimeZone('UTC'); // Force valid timezone as UTC
2887 }
2888 $user_dt = new DateTime();
2889 $user_dt->setTimezone($user_date_tz);
2890 $user_dt->setTimestamp($tzoutput == 'tzuser' ? dol_now() : (int) $time);
2891 $offsettz = $user_dt->getOffset(); // should include dst ?
2892 } else { // with old method (The 'tzuser' was processed like the 'tzuserrel')
2893 $offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60; // Will not be used anymore
2894 $offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60; // Will not be used anymore
2895 }
2896 }
2897 }
2898 }
2899 if (!is_object($outputlangs)) {
2900 $outputlangs = $langs;
2901 }
2902 if (!$format) {
2903 $format = 'daytextshort';
2904 }
2905
2906 // Do we have to reduce the length of date (year on 2 chars) to save space.
2907 // Note: dayinputnoreduce is same than day but no reduction of year length will be done
2908 $reduceformat = (!empty($conf->dol_optimize_smallscreen) && in_array($format, array('day', 'dayhour', 'dayhoursec'))) ? 1 : 0; // Test on original $format param.
2909 $format = preg_replace('/inputnoreduce/', '', $format); // so format 'dayinputnoreduce' is processed like day
2910 $formatwithoutreduce = preg_replace('/reduceformat/', '', $format);
2911 if ($formatwithoutreduce != $format) {
2912 $format = $formatwithoutreduce;
2913 $reduceformat = 1;
2914 } // so format 'dayreduceformat' is processed like day
2915
2916 // Change predefined format into computer format. If found translation in lang file we use it, otherwise we use default.
2917 // TODO Add format daysmallyear and dayhoursmallyear
2918 if ($format == 'day') {
2919 $format = ($outputlangs->trans("FormatDateShort") != "FormatDateShort" ? $outputlangs->trans("FormatDateShort") : $conf->format_date_short);
2920 } elseif ($format == 'hour') {
2921 $format = ($outputlangs->trans("FormatHourShort") != "FormatHourShort" ? $outputlangs->trans("FormatHourShort") : $conf->format_hour_short);
2922 } elseif ($format == 'hoursec') {
2923 $s1 = $outputlangs->trans("FormatDateShort");
2924 $s2 = $outputlangs->trans("FormatDateHourSecShort");
2925 $s3 = trim(preg_replace('/'.preg_quote($s1, '/').'/', '', $s2)); // Try to guess the format for FormatHourSecShort using FormatDateShort and FormatDateHourSecShort
2926 $format = $s3;
2927 //$format = ($outputlangs->trans("FormatHourSecShort") != "FormatHourSecShort" ? $outputlangs->trans("FormatHourSecShort") : ($s3 ? $s3 : $conf->format_hour_sec_short));
2928 } elseif ($format == 'hourduration') {
2929 $format = ($outputlangs->trans("FormatHourShortDuration") != "FormatHourShortDuration" ? $outputlangs->trans("FormatHourShortDuration") : $conf->format_hour_short_duration);
2930 } elseif ($format == 'daytext') {
2931 $format = ($outputlangs->trans("FormatDateText") != "FormatDateText" ? $outputlangs->trans("FormatDateText") : $conf->format_date_text);
2932 } elseif ($format == 'daytextshort') {
2933 $format = ($outputlangs->trans("FormatDateTextShort") != "FormatDateTextShort" ? $outputlangs->trans("FormatDateTextShort") : $conf->format_date_text_short);
2934 } elseif ($format == 'dayhour') {
2935 $format = ($outputlangs->trans("FormatDateHourShort") != "FormatDateHourShort" ? $outputlangs->trans("FormatDateHourShort") : $conf->format_date_hour_short);
2936 } elseif ($format == 'dayhoursec') {
2937 $format = ($outputlangs->trans("FormatDateHourSecShort") != "FormatDateHourSecShort" ? $outputlangs->trans("FormatDateHourSecShort") : $conf->format_date_hour_sec_short);
2938 } elseif ($format == 'dayhourtext') {
2939 $format = ($outputlangs->trans("FormatDateHourText") != "FormatDateHourText" ? $outputlangs->trans("FormatDateHourText") : $conf->format_date_hour_text);
2940 } elseif ($format == 'dayhourtextshort') {
2941 $format = ($outputlangs->trans("FormatDateHourTextShort") != "FormatDateHourTextShort" ? $outputlangs->trans("FormatDateHourTextShort") : $conf->format_date_hour_text_short);
2942 } elseif ($format == 'dayhourlog') {
2943 // Format not sensitive to language
2944 $format = '%Y%m%d%H%M%S';
2945 } elseif ($format == 'dayhourlogsmall') {
2946 // Format not sensitive to language
2947 $format = '%y%m%d%H%M';
2948 } elseif ($format == 'dayhourldap') {
2949 $format = '%Y%m%d%H%M%SZ';
2950 } elseif ($format == 'dayhourxcard') {
2951 $format = '%Y%m%dT%H%M%SZ';
2952 } elseif ($format == 'dayxcard') {
2953 $format = '%Y%m%d';
2954 } elseif ($format == 'dayrfc') {
2955 $format = '%Y-%m-%d'; // DATE_RFC3339
2956 } elseif ($format == 'dayhourrfc') {
2957 $format = '%Y-%m-%dT%H:%M:%SZ'; // DATETIME RFC3339
2958 } elseif ($format == 'standard') {
2959 $format = '%Y-%m-%d %H:%M:%S';
2960 }
2961
2962 if ($reduceformat) {
2963 $format = str_replace('%Y', '%y', $format);
2964 $format = str_replace('yyyy', 'yy', $format);
2965 }
2966
2967 // Clean format
2968 if (preg_match('/%b/i', $format)) { // There is some text to translate
2969 // We inhibit translation to text made by strftime functions. We will use trans instead later.
2970 $format = str_replace('%b', '__b__', $format);
2971 $format = str_replace('%B', '__B__', $format);
2972 }
2973 if (preg_match('/%a/i', $format)) { // There is some text to translate
2974 // We inhibit translation to text made by strftime functions. We will use trans instead later.
2975 $format = str_replace('%a', '__a__', $format);
2976 $format = str_replace('%A', '__A__', $format);
2977 }
2978
2979 // Analyze date
2980 $reg = array();
2981 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
2982 dol_print_error(null, "Functions.lib::dol_print_date function called with a bad value" . getCallerInfoString());
2983 return '';
2984 } 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
2985 // This part of code should not be used anymore.
2986 dol_syslog("Functions.lib::dol_print_date function called with a bad value" . getCallerInfoString(), LOG_WARNING);
2987 // Date has format 'YYYY-MM-DD' or 'YYYY-MM-DD HH:MM:SS'
2988 $syear = (!empty($reg[1]) ? $reg[1] : '');
2989 $smonth = (!empty($reg[2]) ? $reg[2] : '');
2990 $sday = (!empty($reg[3]) ? $reg[3] : '');
2991 $shour = (!empty($reg[4]) ? $reg[4] : '');
2992 $smin = (!empty($reg[5]) ? $reg[5] : '');
2993 $ssec = (!empty($reg[6]) ? $reg[6] : '');
2994
2995 $time = dol_mktime((int) $shour, (int) $smin, (int) $ssec, (int) $smonth, (int) $sday, (int) $syear, true);
2996
2997 if ($to_gmt) {
2998 $tzo = new DateTimeZone('UTC'); // when to_gmt is true, base for offsettz and offsetdst (so timetouse) is UTC
2999 } else {
3000 $tzo = new DateTimeZone(date_default_timezone_get()); // when to_gmt is false, base for offsettz and offsetdst (so timetouse) is PHP server
3001 }
3002 $dtts = new DateTime();
3003 $dtts->setTimestamp($time);
3004 $dtts->setTimezone($tzo);
3005 $newformat = str_replace(
3006 array('%Y', '%y', '%m', '%d', '%H', '%I', '%M', '%S', '%p', 'T', 'Z', '__a__', '__A__', '__b__', '__B__'),
3007 array('Y', 'y', 'm', 'd', 'H', 'h', 'i', 's', 'A', '__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
3008 $format
3009 );
3010 $ret = $dtts->format($newformat);
3011 $ret = str_replace(
3012 array('__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
3013 array('T', 'Z', '__a__', '__A__', '__b__', '__B__'),
3014 $ret
3015 );
3016 } else {
3017 // Date is a timestamps
3018 if ($time < 100000000000) { // Protection against bad date values
3019 $dtts = new DateTime();
3020 //var_dump($tzoutput.' '.$offsettzstring.' '.$offsettz.$offsetdst.' x '.$to_gmt);
3021 if ($to_gmt) { // to_gmt means "not in php server timezone" (if tzoutput = 'gmt', offsets should be 0 but if = 'tzuser...', offsets may be defined
3022 $timetouse = (int) $time + $offsettz + $offsetdst; // TODO We could be able to disable use of offsettz and offsetdst to use only offsettzstring.
3023
3024 $tzo = new DateTimeZone('UTC'); // when to_gmt is true, base for offsettz and offsetdst (so timetouse) is UTC
3025 $dtts->setTimezone($tzo); // important: must be before the setTimestamp
3026 $dtts->setTimestamp($timetouse);
3027 } else {
3028 $timetouse = (int) $time + $offsettz + $offsetdst; // TODO We could be able to disable use of offsettz and offsetdst to use only offsettzstring.
3029
3030 $tzo = new DateTimeZone(date_default_timezone_get()); // when to_gmt is false, base for offsettz and offsetdst (so timetouse) is PHP server
3031 $dtts->setTimestamp($timetouse); // TODO May be we can invert setTimestamp and setTimezone
3032 $dtts->setTimezone($tzo);
3033 }
3034
3035 $newformat = str_replace(
3036 array('%Y', '%y', '%m', '%d', '%H', '%I', '%M', '%S', '%p', '%w', 'T', 'Z', '__a__', '__A__', '__b__', '__B__'),
3037 array('Y', 'y', 'm', 'd', 'H', 'h', 'i', 's', 'A', 'w', '__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
3038 $format
3039 );
3040
3041 $ret = $dtts->format($newformat);
3042 //var_dump($timetouse, $offsettz, $offsetdst, $tzo, $newformat, $ret);
3043 $ret = str_replace(
3044 array('__£__', '__$__', '__{__', '__}__', '__[__', '__]__'),
3045 array('T', 'Z', '__a__', '__A__', '__b__', '__B__'),
3046 $ret
3047 );
3048 } else {
3049 $ret = 'Bad value ' . $time . ' for date';
3050 }
3051 }
3052
3053 if (preg_match('/__b__/i', $format)) {
3054 $timetouse = $time + $offsettz + $offsetdst; // TODO We could be able to disable use of offsettz and offsetdst to use only offsettzstring.
3055
3056 if ($to_gmt) {
3057 $tzo = new DateTimeZone('UTC'); // when to_gmt is true, base for offsettz and offsetdst (so timetouse) is UTC
3058 } else {
3059 $tzo = new DateTimeZone(date_default_timezone_get()); // when to_gmt is false, base for offsettz and offsetdst (so timetouse) is PHP server
3060 }
3061 $dtts = new DateTime();
3062 $dtts->setTimestamp($timetouse);
3063 $dtts->setTimezone($tzo);
3064 $month = (int) $dtts->format("m");
3065 $month = sprintf("%02d", $month); // $month may be return with format '06' on some installation and '6' on other, so we force it to '06'.
3066 if ($encodetooutput) {
3067 $monthtext = $outputlangs->transnoentities('Month' . $month);
3068 $monthtextshort = $outputlangs->transnoentities('MonthShort' . $month);
3069 } else {
3070 $monthtext = $outputlangs->transnoentitiesnoconv('Month' . $month);
3071 $monthtextshort = $outputlangs->transnoentitiesnoconv('MonthShort' . $month);
3072 }
3073 //print 'monthtext='.$monthtext.' monthtextshort='.$monthtextshort;
3074 $ret = str_replace('__b__', $monthtextshort, $ret);
3075 $ret = str_replace('__B__', $monthtext, $ret);
3076 //print 'x'.$outputlangs->charset_output.'-'.$ret.'x';
3077 //return $ret;
3078 }
3079 if (preg_match('/__a__/i', $format)) {
3080 //print "time=$time offsettz=$offsettz offsetdst=$offsetdst offsettzstring=$offsettzstring";
3081 $timetouse = $time + $offsettz + $offsetdst; // TODO Replace this with function Date PHP. We also should not use anymore offsettz and offsetdst but only offsettzstring.
3082
3083 if ($to_gmt) {
3084 $tzo = new DateTimeZone('UTC');
3085 } else {
3086 $tzo = new DateTimeZone(date_default_timezone_get());
3087 }
3088 $dtts = new DateTime();
3089 $dtts->setTimestamp($timetouse);
3090 $dtts->setTimezone($tzo);
3091 $w = $dtts->format("w");
3092 $dayweek = $outputlangs->transnoentitiesnoconv('Day' . $w);
3093
3094 $ret = str_replace('__A__', $dayweek, $ret);
3095 $ret = str_replace('__a__', dol_substr($dayweek, 0, 3), $ret);
3096 }
3097
3098 if ($decorate) {
3099 $ret = preg_replace('/(\d\d:\d\d [AP]M)$/', '<span class="'.($decorate === 1 ? 'opacitymedium' : $decorate).'">\1</span>', $ret);
3100 $ret = preg_replace('/(\d\d:\d\d)$/', '<span class="'.($decorate === 1 ? 'opacitymedium' : $decorate).'">\1</span>', $ret);
3101 }
3102
3103 return $ret;
3104}
3105
3106
3127function dol_getdate($timestamp, $fast = false, $forcetimezone = '')
3128{
3129 if ($timestamp === '') {
3130 return array();
3131 }
3132
3133 $datetimeobj = new DateTime();
3134 $datetimeobj->setTimestamp($timestamp); // Use local PHP server timezone
3135 if ($forcetimezone) {
3136 $datetimeobj->setTimezone(new DateTimeZone($forcetimezone == 'gmt' ? 'UTC' : $forcetimezone)); // (add timezone relative to the date entered)
3137 }
3138 $arrayinfo = array(
3139 'year' => ((int) date_format($datetimeobj, 'Y')),
3140 'mon' => ((int) date_format($datetimeobj, 'm')),
3141 'mday' => ((int) date_format($datetimeobj, 'd')),
3142 'wday' => ((int) date_format($datetimeobj, 'w')),
3143 'yday' => ((int) date_format($datetimeobj, 'z')),
3144 'hours' => ((int) date_format($datetimeobj, 'H')),
3145 'minutes' => ((int) date_format($datetimeobj, 'i')),
3146 'seconds' => ((int) date_format($datetimeobj, 's')),
3147 '0' => $timestamp
3148 );
3149
3150 return $arrayinfo;
3151}
3152
3174function dol_mktime($hour, $minute, $second, $month, $day, $year, $gm = 'auto', $check = 1)
3175{
3176 global $conf;
3177 //print "- ".$hour.",".$minute.",".$second.",".$month.",".$day.",".$year.",".$_SERVER["WINDIR"]." -";
3178
3179 if ($gm === 'auto') {
3180 $gm = (empty($conf) ? 'tzserver' : $conf->tzuserinputkey);
3181 }
3182 //print 'gm:'.$gm.' gm === auto:'.($gm === 'auto').'<br>';exit;
3183
3184 // Clean parameters
3185 if ($hour == -1 || empty($hour)) {
3186 $hour = 0;
3187 }
3188 if ($minute == -1 || empty($minute)) {
3189 $minute = 0;
3190 }
3191 if ($second == -1 || empty($second)) {
3192 $second = 0;
3193 }
3194
3195 // Check parameters
3196 if ($check) {
3197 if (!$month || !$day) {
3198 return '';
3199 }
3200 if ($day > 31) {
3201 return '';
3202 }
3203 if ($month > 12) {
3204 return '';
3205 }
3206 if ($hour < 0 || $hour > 24) {
3207 return '';
3208 }
3209 if ($minute < 0 || $minute > 60) {
3210 return '';
3211 }
3212 if ($second < 0 || $second > 60) {
3213 return '';
3214 }
3215 }
3216
3217 if (empty($gm) || ($gm === 'server' || $gm === 'tzserver')) {
3218 $default_timezone = @date_default_timezone_get(); // Example 'Europe/Berlin'
3219 $localtz = new DateTimeZone($default_timezone);
3220 } elseif ($gm === 'user' || $gm === 'tzuser' || $gm === 'tzuserrel') {
3221 // We use dol_tz_string first because it is more reliable.
3222 $default_timezone = (empty($_SESSION["dol_tz_string"]) ? @date_default_timezone_get() : $_SESSION["dol_tz_string"]); // Example 'Europe/Berlin'
3223 try {
3224 $localtz = new DateTimeZone($default_timezone);
3225 } catch (Exception $e) {
3226 dol_syslog("Warning dol_tz_string contains an invalid value " . json_encode($_SESSION["dol_tz_string"] ?? null), LOG_WARNING);
3227 $default_timezone = @date_default_timezone_get();
3228 }
3229 } elseif (strrpos($gm, "tz,") !== false) {
3230 $timezone = (string) str_replace("tz,", "", $gm); // Example 'tz,Europe/Berlin'
3231 try {
3232 $localtz = new DateTimeZone($timezone);
3233 } catch (Exception $e) {
3234 dol_syslog("Warning passed timezone contains an invalid value " . $timezone, LOG_WARNING);
3235 }
3236 }
3237
3238 if (empty($localtz)) {
3239 $localtz = new DateTimeZone('UTC');
3240 }
3241 $dt = new DateTime('now', $localtz);
3242 $dt->setDate((int) $year, (int) $month, (int) $day);
3243 $dt->setTime((int) $hour, (int) $minute, (int) $second);
3244 $date = $dt->getTimestamp(); // should include daylight saving time
3245
3246 return $date;
3247}
3248
3249
3260function dol_now($mode = 'gmt')
3261{
3262 $ret = 0;
3263
3264 if ($mode === 'auto') {
3265 $mode = 'gmt';
3266 }
3267
3268 if ($mode == 'gmt') {
3269 $ret = time(); // Time for now at greenwich.
3270 } elseif ($mode == 'tzserver') { // Time for now with PHP server timezone added
3271 require_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
3272 $tzsecond = getServerTimeZoneInt('now'); // Contains tz+dayling saving time
3273 $ret = (int) (dol_now('gmt') + ($tzsecond * 3600));
3274 // } elseif ($mode == 'tzref') {// Time for now with parent company timezone is added
3275 // require_once DOL_DOCUMENT_ROOT.'/core/lib/date.lib.php';
3276 // $tzsecond=getParentCompanyTimeZoneInt(); // Contains tz+dayling saving time
3277 // $ret=dol_now('gmt')+($tzsecond*3600);
3278 } elseif ($mode == 'tzuser' || $mode == 'tzuserrel') {
3279 // Time for now with user timezone added
3280 // print 'time: '.time();
3281 $offsettz = (empty($_SESSION['dol_tz']) ? 0 : $_SESSION['dol_tz']) * 60 * 60;
3282 $offsetdst = (empty($_SESSION['dol_dst']) ? 0 : $_SESSION['dol_dst']) * 60 * 60;
3283 $ret = (int) (dol_now('gmt') + ($offsettz + $offsetdst));
3284 }
3285
3286 return $ret;
3287}
3288
3289
3298function dol_print_size($size, $shortvalue = 0, $shortunit = 0)
3299{
3300 global $conf, $langs;
3301 $level = 1024;
3302
3303 if (!empty($conf->dol_optimize_smallscreen)) {
3304 $shortunit = 1;
3305 }
3306
3307 // Set value text
3308 if (empty($shortvalue) || $size < ($level * 10)) {
3309 $ret = $size;
3310 $textunitshort = $langs->trans("b");
3311 $textunitlong = $langs->trans("Bytes");
3312 } else {
3313 $ret = round($size / $level, 0);
3314 $textunitshort = $langs->trans("Kb");
3315 $textunitlong = $langs->trans("KiloBytes");
3316 }
3317 // Use long or short text unit
3318 if (empty($shortunit)) {
3319 $ret .= ' ' . $textunitlong;
3320 } else {
3321 $ret .= ' ' . $textunitshort;
3322 }
3323
3324 return $ret;
3325}
3326
3337function dol_print_url($url, $target = '_blank', $max = 32, $withpicto = 0, $morecss = '')
3338{
3339 global $langs;
3340
3341 if (empty($url)) {
3342 return '';
3343 }
3344
3345 $linkstart = '<a href="';
3346 if (!preg_match('/^http/i', $url)) {
3347 $linkstart .= 'http://';
3348 }
3349 $linkstart .= $url;
3350 $linkstart .= '"';
3351 if ($target) {
3352 $linkstart .= ' target="' . $target . '"';
3353 }
3354 $linkstart .= ' title="' . $langs->trans("URL") . ': ' . $url . '"';
3355 $linkstart .= '>';
3356
3357 $link = '';
3358 if (!preg_match('/^http/i', $url)) {
3359 $link .= 'http://';
3360 }
3361 $link .= dol_trunc($url, $max);
3362
3363 $linkend = '</a>';
3364
3365 if ($morecss == 'float') { // deprecated
3366 return '<div class="nospan' . ($morecss ? ' ' . $morecss : '') . '" style="margin-right: 10px">' . ($withpicto ? img_picto($langs->trans("Url"), 'globe', 'class="paddingrightonly"') : '') . $link . '</div>';
3367 } else {
3368 return $linkstart . '<span class="nospan' . ($morecss ? ' ' . $morecss : '') . '" style="margin-right: 10px">' . ($withpicto ? img_picto('', 'globe', 'class="paddingrightonly"') : '') . $link . '</span>' . $linkend;
3369 }
3370}
3371
3385function dol_print_email($email, $contactid = 0, $socid = 0, $addlink = 0, $max = 0, $showinvalid = 2, $withpicto = 0, $morecss = 'paddingrightonly')
3386{
3387 global $user, $langs, $hookmanager;
3388
3389 //global $conf; $conf->global->AGENDA_ADDACTIONFOREMAIL = 1;
3390 //$showinvalid = 1; $email = 'rrrrr';
3391
3392 $newemail = dol_escape_htmltag($email);
3393
3394 if (getDolGlobalString('MAIN_OPTIMIZEFORTEXTBROWSER') && $withpicto) {
3395 $withpicto = 0;
3396 }
3397
3398 if (empty($email)) {
3399 return '&nbsp;';
3400 }
3401
3402 if ($addlink == 1) {
3403 $newemail = '<a class="' . ($morecss ? $morecss : '') . '" style="text-overflow: ellipsis;" href="';
3404 if (!preg_match('/^mailto:/i', $email)) {
3405 $newemail .= 'mailto:';
3406 }
3407 $newemail .= $email;
3408 $newemail .= '" target="_blank">';
3409
3410 $newemail .= ($withpicto ? img_picto($langs->trans("EMail") . ' : ' . $email, (is_numeric($withpicto) ? 'email' : $withpicto), 'class="paddingrightonly"') : '');
3411
3412 if ($max > 0) {
3413 $newemail .= dol_trunc($email, $max);
3414 } else {
3415 $newemail .= $email;
3416 }
3417 $newemail .= '</a>';
3418
3419 if ($showinvalid) {
3420 include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
3421 include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
3422 $emailonly = CMailFile::getValidAddress($email, 2);
3423 if (!isValidEmail($emailonly)) {
3424 $langs->load("errors");
3425 $newemail .= img_warning($langs->transnoentitiesnoconv("ErrorBadEMail", $emailonly), '', 'paddingrightonly');
3426 } elseif ($showinvalid == 2 && !isValidMailDomain($emailonly)) {
3427 $langs->load("errors");
3428 $newemail .= img_warning($langs->transnoentitiesnoconv("ErrorBadMXDomain", $emailonly), '', 'paddingrightonly');
3429 }
3430 }
3431
3432 if (($contactid || $socid) && isModEnabled('agenda') && $user->hasRight("agenda", "myactions", "create")) {
3433 $type = 'AC_EMAIL';
3434 $linktoaddaction = '';
3435 if (getDolGlobalString('AGENDA_ADDACTIONFOREMAIL')) {
3436 $linktoaddaction = '<a href="' . DOL_URL_ROOT . '/comm/action/card.php?action=create&backtopage=1&actioncode=' . urlencode($type) . '&contactid=' . ((int) $contactid) . '&socid=' . ((int) $socid) . '">' . img_object($langs->trans("AddAction"), "calendar") . '</a>';
3437 }
3438 if ($linktoaddaction) {
3439 $newemail = '<div>' . $newemail . ' ' . $linktoaddaction . '</div>';
3440 }
3441 }
3442 } elseif ($addlink === 'thirdparty') {
3443 $tmpnewemail = '<a class="' . ($morecss ? $morecss : '') . '" style="text-overflow: ellipsis;" href="' . DOL_URL_ROOT . '/societe/card.php?socid=' . $socid . '&action=presend&mode=init#formmailbeforetitle">';
3444 $tmpnewemail .= ($withpicto ? img_picto($langs->trans("EMail") . ' : ' . $email, (is_numeric($withpicto) ? 'email' : $withpicto), 'class="paddingrightonly"') : '');
3445 if ($withpicto == 1) {
3446 $tmpnewemail .= $newemail;
3447 }
3448 $tmpnewemail .= '</a>';
3449
3450 $newemail = $tmpnewemail;
3451 } else {
3452 $newemail = ($withpicto ? img_picto($langs->trans("EMail") . ' : ' . $email, (is_numeric($withpicto) ? 'email' : $withpicto), 'class="paddingrightonly"') : '') . $newemail;
3453
3454 if ($showinvalid) {
3455 include_once DOL_DOCUMENT_ROOT.'/core/class/CMailFile.class.php';
3456 include_once DOL_DOCUMENT_ROOT.'/core/lib/functions2.lib.php';
3457 $emailonly = CMailFile::getValidAddress($email, 2);
3458 if (!isValidEmail($emailonly)) {
3459 $langs->load("errors");
3460 $newemail .= img_warning($langs->transnoentitiesnoconv("ErrorBadEMail", $email));
3461 } elseif ($showinvalid == 2 && !isValidMailDomain($emailonly)) {
3462 $langs->load("errors");
3463 $newemail .= img_warning($langs->transnoentitiesnoconv("ErrorBadMXDomain", $emailonly));
3464 }
3465 }
3466 }
3467
3468 //$rep = '<div class="nospan" style="margin-right: 10px">';
3469 //$rep = ($withpicto ? img_picto($langs->trans("EMail").' : '.$email, (is_numeric($withpicto) ? 'email' : $withpicto), 'class="paddingrightonly"') : '').$newemail;
3470 //$rep .= '</div>';
3471 $rep = $newemail;
3472 if (getDolGlobalString('MAIN_MAIL_COPY_ON_CLICK')) {
3473 $rep .= showValueWithClipboardCPButton($newemail, 0, 'none');
3474 }
3475
3476 if ($hookmanager) {
3477 $parameters = array('cid' => $contactid, 'socid' => $socid, 'addlink' => $addlink, 'picto' => $withpicto);
3478
3479 $reshook = $hookmanager->executeHooks('printEmail', $parameters, $email);
3480 if ($reshook > 0) {
3481 $rep = '';
3482 }
3483 $rep .= $hookmanager->resPrint;
3484 }
3485
3486 return $rep;
3487}
3488
3489
3495function getArrayOfSocialNetworks()
3496{
3497 global $db;
3498
3499 $socialnetworks = array();
3500 // Enable caching of array
3501 require_once DOL_DOCUMENT_ROOT . '/core/lib/memory.lib.php';
3502 $cachekey = dol_sanitizeKeyCode(str_replace(',', '_', 'socialnetworks_'.getEntity('c_socialnetworks')));
3503 $dataretrieved = dol_getcache($cachekey);
3504
3505 if (!is_null($dataretrieved)) {
3506 $socialnetworks = $dataretrieved;
3507 } else {
3508 $sql = "SELECT rowid, code, label, url, icon, active FROM " . MAIN_DB_PREFIX . "c_socialnetworks";
3509 $sql .= " WHERE entity IN (" . getEntity('c_socialnetworks').")";
3510
3511 $resql = $db->query($sql);
3512 if ($resql) {
3513 while ($obj = $db->fetch_object($resql)) {
3514 $socialnetworks[$obj->code] = array(
3515 'rowid' => $obj->rowid,
3516 'label' => $obj->label,
3517 'url' => $obj->url,
3518 'icon' => $obj->icon,
3519 'active' => $obj->active,
3520 );
3521 }
3522 }
3523 dol_setcache($cachekey, $socialnetworks); // If setting cache fails, this is not a problem, so we do not test result.
3524 }
3525
3526 return (is_array($socialnetworks) ? $socialnetworks : array());
3527}
3528
3539function dol_print_socialnetworks($value, $contactid, $socid, $type, $dictsocialnetworks = array())
3540{
3541 global $hookmanager, $langs, $user;
3542
3543 $htmllink = $value;
3544
3545 if (empty($value)) {
3546 return '&nbsp;';
3547 }
3548
3549 if (!empty($type)) {
3550 $htmllink = '<div class="divsocialnetwork inline-block valignmiddle">';
3551 // Use dictionary definition for picto $dictsocialnetworks[$type]['icon']
3552 $htmllink .= '<span class="fab pictofixedwidth ' . ($dictsocialnetworks[$type]['icon'] ? $dictsocialnetworks[$type]['icon'] : 'fa-link') . '"></span>';
3553 if ($type == 'skype') {
3554 $htmllink .= dol_escape_htmltag($value);
3555 $htmllink .= '&nbsp; <a href="skype:';
3556 $htmllink .= dol_string_nospecial($value, '_', '', array('@'));
3557 $htmllink .= '?call" alt="' . $langs->trans("Call") . '&nbsp;' . $value . '" title="' . dol_escape_htmltag($langs->trans("Call") . ' ' . $value) . '">';
3558 $htmllink .= '<img src="' . DOL_URL_ROOT . '/theme/common/skype_callbutton.png" border="0">';
3559 $htmllink .= '</a><a href="skype:';
3560 $htmllink .= dol_string_nospecial($value, '_', '', array('@'));
3561 $htmllink .= '?chat" alt="' . $langs->trans("Chat") . '&nbsp;' . $value . '" title="' . dol_escape_htmltag($langs->trans("Chat") . ' ' . $value) . '">';
3562 $htmllink .= '<img class="paddingleft" src="' . DOL_URL_ROOT . '/theme/common/skype_chatbutton.png" border="0">';
3563 $htmllink .= '</a>';
3564 if (($contactid || $socid) && isModEnabled('agenda') && $user->hasRight('agenda', 'myactions', 'create')) {
3565 $addlink = 'AC_SKYPE';
3566 $link = '';
3567 if (getDolGlobalString('AGENDA_ADDACTIONFORSKYPE')) {
3568 $link = '<a href="' . DOL_URL_ROOT . '/comm/action/card.php?action=create&backtopage=1&actioncode=' . $addlink . '&contactid=' . $contactid . '&socid=' . $socid . '">' . img_object($langs->trans("AddAction"), "calendar") . '</a>';
3569 }
3570 $htmllink .= ($link ? ' ' . $link : '');
3571 }
3572 } else {
3573 if (!empty($dictsocialnetworks[$type]['url'])) {
3574 $tmpvirginurl = preg_replace('/\/?{socialid}/', '', $dictsocialnetworks[$type]['url']);
3575 if ($tmpvirginurl) {
3576 $value = preg_replace('/^www\.' . preg_quote($tmpvirginurl, '/') . '\/?/', '', $value);
3577 $value = preg_replace('/^' . preg_quote($tmpvirginurl, '/') . '\/?/', '', $value);
3578
3579 $tmpvirginurl3 = preg_replace('/^https:\/\//i', 'https://www.', $tmpvirginurl);
3580 if ($tmpvirginurl3) {
3581 $value = preg_replace('/^www\.' . preg_quote($tmpvirginurl3, '/') . '\/?/', '', $value);
3582 $value = preg_replace('/^' . preg_quote($tmpvirginurl3, '/') . '\/?/', '', $value);
3583 }
3584
3585 $tmpvirginurl2 = preg_replace('/^https?:\/\//i', '', $tmpvirginurl);
3586 if ($tmpvirginurl2) {
3587 $value = preg_replace('/^www\.' . preg_quote($tmpvirginurl2, '/') . '\/?/', '', $value);
3588 $value = preg_replace('/^' . preg_quote($tmpvirginurl2, '/') . '\/?/', '', $value);
3589 }
3590 }
3591 if (preg_match('/^https?:\/\//i', $value)) {
3592 $link = $value;
3593 } else {
3594 $link = str_replace('{socialid}', $value, $dictsocialnetworks[$type]['url']);
3595 }
3596 $valuetoshow = $value;
3597 $valuetoshow = preg_replace('/https:\/\/www\.(twitter|x|linkedin)\.com\/?/', '', $valuetoshow);
3598 if (preg_match('/^https?:\/\//i', $link)) {
3599 $htmllink .= '<a href="' . dol_sanitizeUrl($link, 0) . '" target="_blank" rel="noopener noreferrer">' . dol_escape_htmltag($valuetoshow) . '</a>';
3600 } else {
3601 $htmllink .= '<a href="' . dol_sanitizeUrl($link, 1) . '" target="_blank" rel="noopener noreferrer">' . dol_escape_htmltag($valuetoshow) . '</a>';
3602 }
3603 } else {
3604 $htmllink .= dol_escape_htmltag($value);
3605 }
3606 }
3607 $htmllink .= '</div>';
3608 } else {
3609 $langs->load("errors");
3610 $htmllink .= img_warning($langs->trans("ErrorBadSocialNetworkValue", $value));
3611 }
3612
3613 if ($hookmanager) {
3614 $parameters = array(
3615 'value' => $value,
3616 'cid' => $contactid,
3617 'socid' => $socid,
3618 'type' => $type,
3619 'dictsocialnetworks' => $dictsocialnetworks,
3620 );
3621
3622 $reshook = $hookmanager->executeHooks('printSocialNetworks', $parameters);
3623 if ($reshook > 0) {
3624 $htmllink = '';
3625 }
3626 $htmllink .= $hookmanager->resPrint;
3627 }
3628
3629 return $htmllink;
3630}
3631
3641function dol_print_profids($profID, $profIDtype, $countrycode = '', $addcpButton = 1)
3642{
3643 global $mysoc;
3644
3645 if (empty($profID) || empty($profIDtype)) {
3646 return '';
3647 }
3648 if (empty($countrycode)) {
3649 $countrycode = $mysoc->country_code;
3650 }
3651 $newProfID = $profID;
3652 $id = substr($profIDtype, -1);
3653 $ret = '';
3654 if (strtoupper($countrycode) == 'FR') {
3655 // France
3656 // (see https://www.economie.gouv.fr/entreprises/numeros-identification-entreprise)
3657
3658 if ($id == 1 && dol_strlen($newProfID) == 9) {
3659 // SIREN (ex: 123 123 123)
3660 $newProfID = substr($newProfID, 0, 3) . ' ' . substr($newProfID, 3, 3) . ' ' . substr($newProfID, 6, 3);
3661 }
3662 if ($id == 2 && dol_strlen($newProfID) == 14) {
3663 // SIRET (ex: 123 123 123 12345)
3664 $newProfID = substr($newProfID, 0, 3) . ' ' . substr($newProfID, 3, 3) . ' ' . substr($newProfID, 6, 3) . ' ' . substr($newProfID, 9, 5);
3665 }
3666 if ($id == 3 && dol_strlen($newProfID) == 5) {
3667 // NAF/APE (ex: 69.20Z)
3668 $newProfID = substr($newProfID, 0, 2) . '.' . substr($newProfID, 2, 3);
3669 }
3670 if ($profIDtype === 'VAT' && dol_strlen($newProfID) == 13) {
3671 // TVA intracommunautaire (ex: FR12 123 123 123)
3672 $newProfID = substr($newProfID, 0, 4) . ' ' . substr($newProfID, 4, 3) . ' ' . substr($newProfID, 7, 3) . ' ' . substr($newProfID, 10, 3);
3673 }
3674 }
3675 if (!empty($addcpButton)) {
3676 $ret = showValueWithClipboardCPButton(dol_escape_htmltag($profID), ($addcpButton == 1 ? 1 : 0), $newProfID);
3677 } else {
3678 $ret = $newProfID;
3679 }
3680 return $ret;
3681}
3682
3698function dol_print_phone($phone, $countrycode = '', $contactid = 0, $socid = 0, $addlink = '', $separ = "&nbsp;", $withpicto = '', $titlealt = '', $adddivfloat = 0, $morecss = 'paddingright')
3699{
3700 global $conf, $user, $langs, $mysoc, $hookmanager;
3701
3702 // Clean phone parameter
3703 $phone = is_null($phone) ? '' : preg_replace("/[\s.-]/", "", trim($phone));
3704 if (empty($phone)) {
3705 return '';
3706 }
3707 if (getDolGlobalString('MAIN_PHONE_SEPAR')) {
3708 $separ = getDolGlobalString('MAIN_PHONE_SEPAR');
3709 }
3710 if (empty($countrycode) && is_object($mysoc)) {
3711 $countrycode = $mysoc->country_code;
3712 }
3713
3714 // Short format for small screens
3715 if (!empty($conf->dol_optimize_smallscreen) && $separ != 'hidenum') {
3716 $separ = '';
3717 }
3718
3719 $newphone = $phone;
3720 $newphonewa = $phone;
3721 if (strtoupper($countrycode) == "FR") {
3722 // France
3723 if (dol_strlen($phone) == 10) {
3724 $newphone = substr($newphone, 0, 2) . $separ . substr($newphone, 2, 2) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2);
3725 } elseif (dol_strlen($phone) == 7) {
3726 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 2);
3727 } elseif (dol_strlen($phone) == 9) {
3728 $newphone = substr($newphone, 0, 2) . $separ . substr($newphone, 2, 3) . $separ . substr($newphone, 5, 2) . $separ . substr($newphone, 7, 2);
3729 } elseif (dol_strlen($phone) == 11) {
3730 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 2) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2);
3731 } elseif (dol_strlen($phone) == 12) {
3732 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 1) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
3733 } elseif (dol_strlen($phone) == 13) {
3734 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 3) . $separ . substr($newphone, 11, 2);
3735 }
3736 } elseif (strtoupper($countrycode) == "CA") {
3737 if (dol_strlen($phone) == 10) {
3738 $newphone = ($separ != '' ? '(' : '') . substr($newphone, 0, 3) . ($separ != '' ? ')' : '') . $separ . substr($newphone, 3, 3) . ($separ != '' ? '-' : '') . substr($newphone, 6, 4);
3739 }
3740 } elseif (strtoupper($countrycode) == "PT") { //Portugal
3741 if (dol_strlen($phone) == 13) { //ex: +351_ABC_DEF_GHI
3742 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
3743 }
3744 } elseif (strtoupper($countrycode) == "SR") { //Suriname
3745 if (dol_strlen($phone) == 10) { //ex: +597_ABC_DEF
3746 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3);
3747 } elseif (dol_strlen($phone) == 11) { //ex: +597_ABC_DEFG
3748 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 4);
3749 }
3750 } elseif (strtoupper($countrycode) == "DE") { //Deutschland
3751 if (dol_strlen($phone) == 14) { //ex: +49_ABCD_EFGH_IJK
3752 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4) . $separ . substr($newphone, 7, 4) . $separ . substr($newphone, 11, 3);
3753 } elseif (dol_strlen($phone) == 13) { //ex: +49_ABC_DEFG_HIJ
3754 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 4) . $separ . substr($newphone, 10, 3);
3755 }
3756 } elseif (strtoupper($countrycode) == "ES") { //Spain
3757 if (dol_strlen($phone) == 12) { //ex: +34_ABC_DEF_GHI
3758 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
3759 }
3760 } elseif (strtoupper($countrycode) == "BF") { // Burkina Faso
3761 if (dol_strlen($phone) == 12) { //ex : +22 A BC_DE_FG_HI
3762 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 1) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
3763 }
3764 } elseif (strtoupper($countrycode) == "RO") { // Roumanie
3765 if (dol_strlen($phone) == 12) { //ex : +40 AB_CDE_FG_HI
3766 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
3767 }
3768 } elseif (strtoupper($countrycode) == "TR") { //Turquie
3769 if (dol_strlen($phone) == 13) { //ex : +90 ABC_DEF_GHIJ
3770 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 4);
3771 }
3772 } elseif (strtoupper($countrycode) == "US") { //Etat-Unis
3773 if (dol_strlen($phone) == 12) { //ex: +1 ABC_DEF_GHIJ
3774 $newphone = substr($newphone, 0, 2) . $separ . substr($newphone, 2, 3) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 4);
3775 }
3776 } elseif (strtoupper($countrycode) == "MX") { //Mexique
3777 if (dol_strlen($phone) == 12) { //ex: +52 ABCD_EFG_HI
3778 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 2);
3779 } elseif (dol_strlen($phone) == 11) { //ex: +52 AB_CD_EF_GH
3780 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 2) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2);
3781 } elseif (dol_strlen($phone) == 13) { //ex: +52 ABC_DEF_GHIJ
3782 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 4);
3783 }
3784 } elseif (strtoupper($countrycode) == "ML") { //Mali
3785 if (dol_strlen($phone) == 12) { //ex: +223 AB_CD_EF_GH
3786 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
3787 }
3788 } elseif (strtoupper($countrycode) == "TH") { //Thailand
3789 if (dol_strlen($phone) == 11) { //ex: +66_ABC_DE_FGH
3790 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 3);
3791 } elseif (dol_strlen($phone) == 12) { //ex: +66_A_BCD_EF_GHI
3792 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 1) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 3);
3793 }
3794 } elseif (strtoupper($countrycode) == "MU") {
3795 //Maurice
3796 if (dol_strlen($phone) == 11) { //ex: +230_ABC_DE_FG
3797 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2);
3798 } elseif (dol_strlen($phone) == 12) { //ex: +230_ABCD_EF_GH
3799 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 4) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
3800 }
3801 } elseif (strtoupper($countrycode) == "ZA") { //Afrique du sud
3802 if (dol_strlen($phone) == 12) { //ex: +27_AB_CDE_FG_HI
3803 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
3804 }
3805 } elseif (strtoupper($countrycode) == "SY") { //Syrie
3806 if (dol_strlen($phone) == 12) { //ex: +963_AB_CD_EF_GH
3807 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
3808 } elseif (dol_strlen($phone) == 13) { //ex: +963_AB_CD_EF_GHI
3809 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 3);
3810 }
3811 } elseif (strtoupper($countrycode) == "AE") { //Emirats Arabes Unis
3812 if (dol_strlen($phone) == 12) { //ex: +971_ABC_DEF_GH
3813 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 2);
3814 } elseif (dol_strlen($phone) == 13) { //ex: +971_ABC_DEF_GHI
3815 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
3816 } elseif (dol_strlen($phone) == 14) { //ex: +971_ABC_DEF_GHIK
3817 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 4);
3818 }
3819 } elseif (strtoupper($countrycode) == "DZ") { //Algeria
3820 if (dol_strlen($phone) == 13) { //ex: +213_ABC_DEF_GHI
3821 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
3822 }
3823 } elseif (strtoupper($countrycode) == "BE") { //Belgique
3824 if (dol_strlen($phone) == 11) { //ex: +32_ABC_DE_FGH
3825 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 3);
3826 } elseif (dol_strlen($phone) == 12) { //ex: +32_ABC_DEF_GHI
3827 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
3828 }
3829 } elseif (strtoupper($countrycode) == "PF") { //French Polynesia
3830 if (dol_strlen($phone) == 12) { //ex: +689_AB_CD_EF_GH
3831 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
3832 }
3833 } elseif (strtoupper($countrycode) == "CO") { //Colombie
3834 if (dol_strlen($phone) == 13) { //ex: +57_ABC_DEF_GH_IJ
3835 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 2) . $separ . substr($newphone, 11, 2);
3836 }
3837 } elseif (strtoupper($countrycode) == "JO") { //Jordanie
3838 if (dol_strlen($phone) == 12) { //ex: +962_A_BCD_EF_GH
3839 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 1) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
3840 }
3841 } elseif (strtoupper($countrycode) == "JM") { //Jamaica
3842 if (dol_strlen($newphone) == 12) { //ex: +1867_ABC_DEFG
3843 $newphone = substr($newphone, 0, 5) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 4);
3844 }
3845 } elseif (strtoupper($countrycode) == "MG") { //Madagascar
3846 if (dol_strlen($phone) == 13) { //ex: +261_AB_CD_EFG_HI
3847 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 3) . $separ . substr($newphone, 11, 2);
3848 }
3849 } elseif (strtoupper($countrycode) == "GB") { //Royaume uni
3850 if (dol_strlen($phone) == 13) { //ex: +44_ABCD_EFG_HIJ
3851 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4) . $separ . substr($newphone, 7, 3) . $separ . substr($newphone, 10, 3);
3852 }
3853 } elseif (strtoupper($countrycode) == "CH") { //Suisse
3854 if (dol_strlen($phone) == 12) { //ex: +41_AB_CDE_FG_HI
3855 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
3856 } elseif (dol_strlen($phone) == 15) { // +41_AB_CDE_FGH_IJKL
3857 $newphone = $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 3) . $separ . substr($newphone, 8, 3) . $separ . substr($newphone, 11, 4);
3858 }
3859 } elseif (strtoupper($countrycode) == "TN") { //Tunisie
3860 if (dol_strlen($phone) == 12) { //ex: +216_AB_CDE_FGH
3861 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
3862 }
3863 } elseif (strtoupper($countrycode) == "GF") { //Guyane francaise
3864 if (dol_strlen($phone) == 13) { //ex: +594_ABC_DE_FG_HI (ABC=594 de nouveau)
3865 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2) . $separ . substr($newphone, 11, 2);
3866 }
3867 } elseif (strtoupper($countrycode) == "GP") { //Guadeloupe
3868 if (dol_strlen($phone) == 13) { //ex: +590_ABC_DE_FG_HI (ABC=590 de nouveau)
3869 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2) . $separ . substr($newphone, 11, 2);
3870 }
3871 } elseif (strtoupper($countrycode) == "MQ") { //Martinique
3872 if (dol_strlen($phone) == 13) { //ex: +596_ABC_DE_FG_HI (ABC=596 de nouveau)
3873 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2) . $separ . substr($newphone, 11, 2);
3874 }
3875 } elseif (strtoupper($countrycode) == "IT") { //Italie
3876 if (dol_strlen($phone) == 12) { //ex: +39_ABC_DEF_GHI
3877 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
3878 } elseif (dol_strlen($phone) == 13) { //ex: +39_ABC_DEF_GH_IJ
3879 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 2) . $separ . substr($newphone, 11, 2);
3880 }
3881 } elseif (strtoupper($countrycode) == "AU") {
3882 //Australie
3883 if (dol_strlen($phone) == 12) {
3884 //ex: +61_A_BCDE_FGHI
3885 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 1) . $separ . substr($newphone, 4, 4) . $separ . substr($newphone, 8, 4);
3886 }
3887 } elseif (strtoupper($countrycode) == "LU") {
3888 // Luxembourg
3889 if (dol_strlen($phone) == 10) { // fix 6 digits +352_AA_BB_CC
3890 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2);
3891 } elseif (dol_strlen($phone) == 11) { // fix 7 digits +352_AA_BB_CC_D
3892 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 1);
3893 } elseif (dol_strlen($phone) == 12) { // fix 8 digits +352_AA_BB_CC_DD
3894 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 2) . $separ . substr($newphone, 6, 2) . $separ . substr($newphone, 8, 2) . $separ . substr($newphone, 10, 2);
3895 } elseif (dol_strlen($phone) == 13) { // mobile +352_AAA_BB_CC_DD
3896 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 2) . $separ . substr($newphone, 9, 2) . $separ . substr($newphone, 11, 2);
3897 }
3898 } elseif (strtoupper($countrycode) == "PE") {
3899 // Peru
3900 if (dol_strlen($phone) == 7) { // fix 7 numbers without code AAA_BBBB
3901 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 4);
3902 } elseif (dol_strlen($phone) == 9) { // mobile add code and fix 9 numbers +51_AAA_BBB_CCC
3903 $newphonewa = '+51' . $newphone;
3904 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3);
3905 } elseif (dol_strlen($phone) == 11) { // fix 11 numbers +511_AAA_BBBB
3906 $newphone = substr($newphone, 0, 4) . $separ . substr($newphone, 4, 3) . $separ . substr($newphone, 7, 4);
3907 } elseif (dol_strlen($phone) == 12) { // mobile +51_AAA_BBB_CCC
3908 $newphonewa = $newphone;
3909 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 3) . $separ . substr($newphone, 6, 3) . $separ . substr($newphone, 9, 3);
3910 }
3911 } elseif (strtoupper($countrycode) == "IN") { //India
3912 if (dol_strlen($phone) == 13) {
3913 if ($withpicto == 'phone') { //ex: +91_AB_CDEF_GHIJ
3914 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 2) . $separ . substr($newphone, 5, 4) . $separ . substr($newphone, 9, 4);
3915 } else { //ex: +91_ABCDE_FGHIJ
3916 $newphone = substr($newphone, 0, 3) . $separ . substr($newphone, 3, 5) . $separ . substr($newphone, 8, 5);
3917 }
3918 }
3919 } elseif (strtoupper($countrycode) == "CI") { //Ivory cost
3920 if (dol_strlen($phone) == 14) { //ex : +225_AB_CD_EF_GH_IJ
3921 $newphone = substr($newphone, 0, 4) . $separ.substr($newphone, 4, 2) . $separ.substr($newphone, 6, 2) . $separ.substr($newphone, 8, 2) . $separ.substr($newphone, 10, 2) . $separ.substr($newphone, 12, 2);
3922 }
3923 }
3924
3925 $newphoneastart = $newphoneaend = '';
3926 if (!empty($addlink)) { // Link on phone number (+ link to add action if conf->global->AGENDA_ADDACTIONFORPHONE set)
3927 if ($addlink == 'tel' || $conf->browser->layout == 'phone' || (isModEnabled('clicktodial') && getDolGlobalString('CLICKTODIAL_USE_TEL_LINK_ON_PHONE_NUMBERS'))) { // If phone or option for, we use link of phone
3928 $newphoneastart = '<a href="tel:' . urlencode($phone) . '">';
3929 $newphoneaend .= '</a>';
3930 } elseif (isModEnabled('clicktodial') && $addlink == 'AC_TEL') { // If click to dial, we use click to dial url
3931 if (empty($user->clicktodial_loaded)) {
3932 $user->fetch_clicktodial();
3933 }
3934
3935 // Define urlmask
3936 $urlmask = getDolGlobalString('CLICKTODIAL_URL', 'ErrorClickToDialModuleNotConfigured');
3937 if (!empty($user->clicktodial_url)) {
3938 $urlmask = $user->clicktodial_url;
3939 }
3940
3941 $clicktodial_poste = (!empty($user->clicktodial_poste) ? urlencode($user->clicktodial_poste) : '');
3942 $clicktodial_login = (!empty($user->clicktodial_login) ? urlencode($user->clicktodial_login) : '');
3943 $clicktodial_password = (!empty($user->clicktodial_password) ? urlencode($user->clicktodial_password) : '');
3944 // This line is for backward compatibility @phan-suppress-next-line PhanPluginPrintfVariableFormatString
3945 $url = sprintf($urlmask, urlencode($phone), $clicktodial_poste, $clicktodial_login, $clicktodial_password);
3946 // Those lines are for substitution
3947 $substitarray = array(
3948 '__PHONEFROM__' => $clicktodial_poste,
3949 '__PHONETO__' => urlencode($phone),
3950 '__LOGIN__' => $clicktodial_login,
3951 '__PASS__' => $clicktodial_password
3952 );
3953 $url = make_substitutions($url, $substitarray);
3954 if (!getDolGlobalString('CLICKTODIAL_DO_NOT_USE_AJAX_CALL')) {
3955 // Default and recommended: New method using ajax without submitting a page making a javascript history.go(-1) back
3956 $newphoneastart = '<a href="' . $url . '" class="cssforclicktodial">'; // Call of ajax is handled by the lib_foot.js.php on class 'cssforclicktodial'
3957 $newphoneaend = '</a>';
3958 } else {
3959 // Old method
3960 $newphoneastart = '<a href="' . $url . '"';
3961 if (getDolGlobalString('CLICKTODIAL_FORCENEWTARGET')) {
3962 $newphoneastart .= ' target="_blank" rel="noopener noreferrer"';
3963 }
3964 $newphoneastart .= '>';
3965 $newphoneaend .= '</a>';
3966 }
3967 }
3968 //if (($contactid || $socid) && isModEnabled('agenda') && $user->hasRight('agenda', 'myactions', 'create'))
3969 if (isModEnabled('agenda') && $user->hasRight("agenda", "myactions", "create")) {
3970 $type = 'AC_TEL';
3971 $addlinktoagenda = '';
3972 if ($addlink == 'AC_FAX') {
3973 $type = 'AC_FAX';
3974 }
3975 if (getDolGlobalString('AGENDA_ADDACTIONFORPHONE')) {
3976 $addlinktoagenda = '<a href="' . DOL_URL_ROOT . '/comm/action/card.php?action=create&backtopage=' . urlencode($_SERVER['REQUEST_URI']) . '&actioncode=' . $type . ($contactid ? '&contactid=' . $contactid : '') . ($socid ? '&socid=' . $socid : '') . '">' . img_object($langs->trans("AddAction"), "calendar") . '</a>';
3977 }
3978 if ($addlinktoagenda) {
3979 $newphone = '<span>' . $newphone . ' ' . $addlinktoagenda . '</span>';
3980 }
3981 }
3982 }
3983
3984 if (getDolGlobalString('CONTACT_PHONEMOBILE_SHOW_LINK_TO_WHATSAPP') && $withpicto == 'mobile') {
3985 // Link to Whatsapp
3986 $newphone .= ' <a href="https://wa.me/' . $newphonewa . '" target="_blank"'; // Use api to whatasapp contacts
3987 $newphone .= '><span class="paddingright fab fa-whatsapp" style="color:#25D366;" title="WhatsApp"></span></a>';
3988 }
3989
3990 if (empty($titlealt) && isset($langs)) {
3991 $titlealt = ($withpicto == 'fax' ? $langs->trans("Fax") : $langs->trans("Phone"));
3992 }
3993 $rep = '';
3994
3995 if ($hookmanager) {
3996 $parameters = array('countrycode' => $countrycode, 'cid' => $contactid, 'socid' => $socid, 'titlealt' => $titlealt, 'picto' => $withpicto);
3997 $reshook = $hookmanager->executeHooks('printPhone', $parameters, $phone);
3998 $rep .= $hookmanager->resPrint;
3999 }
4000 if (empty($reshook)) {
4001 $picto = '';
4002 if ($withpicto) {
4003 if ($withpicto == 'fax') {
4004 $picto = 'phoning_fax';
4005 } elseif ($withpicto == 'phone') {
4006 $picto = 'phone';
4007 } elseif ($withpicto == 'mobile') {
4008 $picto = 'phoning_mobile';
4009 } else {
4010 $picto = '';
4011 }
4012 }
4013 if ($adddivfloat == 1) {
4014 $rep .= '<div class="nospan float' . ($morecss ? ' ' . $morecss : '') . '">';
4015 } elseif (empty($adddivfloat)) {
4016 $rep .= '<span' . ($morecss ? ' class="' . $morecss . '"' : '') . '>';
4017 }
4018
4019 $rep .= $newphoneastart;
4020 $rep .= ($withpicto ? img_picto($titlealt, $picto) : '');
4021 if ($separ != 'hidenum') {
4022 $rep .= ($withpicto ? ' ' : '') . $newphone;
4023 }
4024 $rep .= $newphoneaend;
4025
4026 if ($adddivfloat == 1) {
4027 $rep .= '</div>';
4028 } elseif (empty($adddivfloat)) {
4029 $rep .= '</span>';
4030 }
4031 }
4032
4033 return $rep;
4034}
4035
4044function dol_print_ip($ip, $mode = 0, $showname = 0)
4045{
4046 global $conf;
4047
4048 $ret = '';
4049 if (!isset($conf->cache['resolveips'])) {
4050 $conf->cache['resolveips'] = array();
4051 }
4052
4053 if ($mode != 2) {
4054 $countrycode = dolGetCountryCodeFromIp($ip);
4055 if ($countrycode) { // If success, countrycode is us, fr, ...
4056 if (file_exists(DOL_DOCUMENT_ROOT . '/theme/common/flags/' . $countrycode . '.png')) {
4057 $ret .= picto_from_langcode($countrycode);
4058 } else {
4059 $ret .= '(' . $countrycode . ')';
4060 }
4061 $ret .= '&nbsp;';
4062 } else {
4063 // Nothing
4064 }
4065
4066 $cityname = dolGetCityFromIp($ip);
4067 if ($cityname) { // Only set if the GeoIP datafile in use is a City database
4068 $ret .= dol_escape_htmltag($cityname) . '&nbsp;';
4069 }
4070 }
4071
4072 if (in_array($mode, [0, 2])) {
4073 $domain = '';
4074 if ($showname) {
4075 if (!array_key_exists($ip, $conf->cache['resolveips'])) {
4076 $domain = gethostbyaddr($ip);
4077 $conf->cache['resolveips'][$ip] = $domain; // false or domain
4078 } else {
4079 $domain = $conf->cache['resolveips'][$ip];
4080 }
4081 }
4082 if ($domain) {
4083 $ret .= $domain;
4084 } else {
4085 $ret .= $ip;
4086 }
4087 }
4088
4089 return $ret;
4090}
4091
4104function getUserRemoteIP($trusted = 0)
4105{
4106 if ($trusted) { // Return only IP we can rely on (not spoofable by the client)
4107 $ip = (empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR']); // value may be the IP of a proxy
4108 // Note that if apache module remoteip has been enabled, REMOTE_ADDR can contain the real client (the value from cloudFlare HTTP_CF_CONNECTING_IP for example)
4109 // This can happen if the proxy were added in the list of trusted proxy.
4110 return $ip;
4111 }
4112
4113 // Try to guess the real IP of client (but this may not be reliable)
4114 if (empty($_SERVER['HTTP_X_FORWARDED_FOR']) || preg_match('/[^0-9\.\:,\[\]\s]/', $_SERVER['HTTP_X_FORWARDED_FOR'])) {
4115 if (empty($_SERVER['HTTP_CLIENT_IP']) || preg_match('/[^0-9\.\:,\[\]\s]/', $_SERVER['HTTP_CLIENT_IP'])) {
4116 if (empty($_SERVER["HTTP_CF_CONNECTING_IP"])) {
4117 $ip = (empty($_SERVER['REMOTE_ADDR']) ? '' : $_SERVER['REMOTE_ADDR']); // value may be the IP of the proxy and not the client
4118 } else {
4119 $ip = $_SERVER["HTTP_CF_CONNECTING_IP"]; // value here may have been forged by client
4120 }
4121 } else {
4122 $ip = preg_replace('/,.*$/', '', $_SERVER['HTTP_CLIENT_IP']); // value is clean here but may have been forged by proxy
4123 }
4124 } else {
4125 $ip = preg_replace('/,.*$/', '', $_SERVER['HTTP_X_FORWARDED_FOR']); // value is clean here but may have been forged by proxy
4126 }
4127 return $ip;
4128}
4129
4136function dolGetCountryCodeFromIp($ip)
4137{
4138 $countrycode = '';
4139
4140 if (isModEnabled('geoipmaxmind')) {
4141 if (getDolGlobalString('GEOIP_VERSION') == 'php') {
4142 $datafile = getDolGlobalString('GEOIPMAXMIND_COUNTRY_DATAFILE');
4143 } else {
4144 $diroffile = getMultidirOutput(null, 'geoipmaxmind');
4145 $datafile = $diroffile . '/' . getDolGlobalString('GEOIPMAXMIND_COUNTRY_DATAFILE_EMBEDDED');
4146 }
4147 //$ip='24.24.24.24';
4148 //$datafile='/usr/share/GeoIP/GeoIP.dat'; Note that this must be downloaded datafile (not same than datafile provided with ubuntu packages)
4149 if ($datafile) {
4150 try {
4151 include_once DOL_DOCUMENT_ROOT . '/core/class/dolgeoip.class.php';
4152 $geoip = new DolGeoIP('country', $datafile);
4153 //print 'ip='.$ip.' databaseType='.$geoip->gi->databaseType." GEOIP_CITY_EDITION_REV1=".GEOIP_CITY_EDITION_REV1."\n";
4154 $countrycode = $geoip->getCountryCodeFromIP($ip);
4155 } catch (Exception $e) {
4156 //print 'Error with GeoIP database: '.$e->getMessage();
4157 }
4158 }
4159 }
4160
4161 return $countrycode;
4162}
4163
4171function dolGetCityFromIp($ip)
4172{
4173 $cityname = '';
4174
4175 if (isModEnabled('geoipmaxmind')) {
4176 if (getDolGlobalString('GEOIP_VERSION') == 'php') {
4177 $datafile = getDolGlobalString('GEOIPMAXMIND_COUNTRY_DATAFILE');
4178 } else {
4179 $diroffile = getMultidirOutput(null, 'geoipmaxmind');
4180 $datafile = $diroffile . '/' . getDolGlobalString('GEOIPMAXMIND_COUNTRY_DATAFILE_EMBEDDED');
4181 }
4182 if ($datafile) {
4183 try {
4184 include_once DOL_DOCUMENT_ROOT . '/core/class/dolgeoip.class.php';
4185 $geoip = new DolGeoIP('country', $datafile);
4186 $cityname = $geoip->getCityNameFromIP($ip);
4187 } catch (Exception $e) {
4188 //print 'Error with GeoIP database: '.$e->getMessage();
4189 }
4190 }
4191 }
4192
4193 return $cityname;
4194}
4195
4196
4203function dol_user_country()
4204{
4205 //$ret=$user->xxx;
4206 $ret = '';
4207 if (isModEnabled('geoipmaxmind')) {
4208 $ip = getUserRemoteIP();
4209 $datafile = getDolGlobalString('GEOIPMAXMIND_COUNTRY_DATAFILE');
4210 //$ip='24.24.24.24';
4211 //$datafile='E:\Mes Sites\Web\Admin1\awstats\maxmind\GeoIP.dat';
4212 include_once DOL_DOCUMENT_ROOT . '/core/class/dolgeoip.class.php';
4213 $geoip = new DolGeoIP('country', $datafile);
4214 $countrycode = $geoip->getCountryCodeFromIP($ip);
4215 $ret = $countrycode;
4216 }
4217 return $ret;
4218}
4219
4232function dol_print_address($address, $htmlid, $element, $id, $noprint = 0, $charfornl = '')
4233{
4234 global $hookmanager;
4235
4236 $out = '';
4237
4238 if ($address) {
4239 if ($hookmanager) {
4240 $parameters = array('element' => $element, 'id' => $id);
4241 $reshook = $hookmanager->executeHooks('printAddress', $parameters, $address);
4242 $out .= $hookmanager->resPrint;
4243 }
4244 if (empty($reshook)) {
4245 if (empty($charfornl)) {
4246 $out .= nl2br((string) $address);
4247 } else {
4248 $out .= preg_replace('/[\r\n]+/', $charfornl, (string) $address);
4249 }
4250
4251 // TODO Remove this block, we can add this using the hook now
4252 $showgmap = $showomap = 0;
4253 if (($element == 'thirdparty' || $element == 'societe') && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS')) {
4254 $showgmap = 1;
4255 }
4256 if ($element == 'contact' && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS_CONTACTS')) {
4257 $showgmap = 1;
4258 }
4259 if ($element == 'member' && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS_MEMBERS')) {
4260 $showgmap = 1;
4261 }
4262 if ($element == 'user' && isModEnabled('google') && getDolGlobalString('GOOGLE_ENABLE_GMAPS_USERS')) {
4263 $showgmap = 1;
4264 }
4265 if (($element == 'thirdparty' || $element == 'societe') && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS')) {
4266 $showomap = 1;
4267 }
4268 if ($element == 'contact' && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS_CONTACTS')) {
4269 $showomap = 1;
4270 }
4271 if ($element == 'member' && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS_MEMBERS')) {
4272 $showomap = 1;
4273 }
4274 if ($element == 'user' && isModEnabled('openstreetmap') && getDolGlobalString('OPENSTREETMAP_ENABLE_MAPS_USERS')) {
4275 $showomap = 1;
4276 }
4277 if ($showgmap) {
4278 $url = dol_buildpath('/google/gmaps.php?mode=' . $element . '&id=' . $id, 1);
4279 $out .= ' <a href="' . $url . '" target="_gmaps"><img id="' . $htmlid . '" class="valigntextbottom" src="' . DOL_URL_ROOT . '/theme/common/gmap.png"></a>';
4280 }
4281 if ($showomap) {
4282 $url = dol_buildpath('/openstreetmap/maps.php?mode=' . $element . '&id=' . $id, 1);
4283 $out .= ' <a href="' . $url . '" target="_gmaps"><img id="' . $htmlid . '_openstreetmap" class="valigntextbottom" src="' . DOL_URL_ROOT . '/theme/common/gmap.png"></a>';
4284 }
4285 }
4286 }
4287 if ($noprint) {
4288 return $out;
4289 } else {
4290 print $out;
4291 return null;
4292 }
4293}
4294
4295
4305function isValidEmail($address, $acceptsupervisorkey = 0, $acceptuserkey = 0)
4306{
4307 if ($acceptsupervisorkey && $address == '__SUPERVISOREMAIL__') {
4308 return true;
4309 }
4310 if ($acceptuserkey && $address == '__USER_EMAIL__') {
4311 return true;
4312 }
4313 if (filter_var($address, FILTER_VALIDATE_EMAIL)) {
4314 return true;
4315 }
4316
4317 return false;
4318}
4319
4329function isValidMXRecord($domain)
4330{
4331 if (function_exists('idn_to_ascii') && function_exists('checkdnsrr')) {
4332 if (!checkdnsrr(idn_to_ascii($domain), 'MX')) {
4333 return 0;
4334 }
4335 if (function_exists('getmxrr')) {
4336 $mxhosts = array();
4337 $weight = array();
4338 getmxrr(idn_to_ascii($domain), $mxhosts, $weight);
4339 if (count($mxhosts) > 1) {
4340 return 1;
4341 }
4342 if (count($mxhosts) == 1 && !in_array((string) $mxhosts[0], array('', '.'))) {
4343 return 1;
4344 }
4345
4346 return 0;
4347 }
4348 }
4349
4350 // function idn_to_ascii or checkdnsrr or getmxrr does not exists
4351 return -1;
4352}
4353
4361function isValidPhone($phone)
4362{
4363 return true;
4364}
4365
4366
4376function dolGetFirstLetters($s, $nbofchar = 1)
4377{
4378 $ret = '';
4379 $tmparray = explode(' ', $s);
4380 foreach ($tmparray as $tmps) {
4381 $ret .= dol_substr($tmps, 0, $nbofchar);
4382 }
4383
4384 return $ret;
4385}
4386
4387
4395function dol_strlen($string, $stringencoding = 'UTF-8')
4396{
4397 if (is_null($string)) {
4398 return 0;
4399 }
4400
4401 if (function_exists('mb_strlen')) {
4402 return mb_strlen($string, $stringencoding);
4403 } else {
4404 return strlen($string);
4405 }
4406}
4407
4418function dol_substr($string, $start, $length = null, $stringencoding = '', $trunconbytes = 0)
4419{
4420 global $langs;
4421
4422 if (empty($stringencoding)) {
4423 $stringencoding = (empty($langs) ? 'UTF-8' : $langs->charset_output);
4424 }
4425
4426 $ret = '';
4427 if (empty($trunconbytes)) {
4428 if (function_exists('mb_substr')) {
4429 $ret = mb_substr($string, $start, $length, $stringencoding);
4430 } else {
4431 $ret = substr($string, $start, $length);
4432 }
4433 } else {
4434 if (function_exists('mb_strcut')) {
4435 $ret = mb_strcut($string, $start, $length, $stringencoding);
4436 } else {
4437 $ret = substr($string, $start, $length);
4438 }
4439 }
4440 return $ret;
4441}
4442
4443
4457function dol_trunc($string, $size = 40, $trunc = 'right', $stringencoding = 'UTF-8', $nodot = 0, $display = 0)
4458{
4459 global $conf;
4460
4461 if (empty($size) || getDolGlobalString('MAIN_DISABLE_TRUNC')) {
4462 return $string;
4463 }
4464
4465 if (empty($stringencoding)) {
4466 $stringencoding = 'UTF-8';
4467 }
4468 // reduce for small screen
4469 if (!empty($conf->dol_optimize_smallscreen) && $conf->dol_optimize_smallscreen == 1 && $display == 1) {
4470 $size = round($size / 3);
4471 }
4472
4473 // We go always here
4474 if ($trunc == 'right') {
4475 $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
4476 if (dol_strlen($newstring, $stringencoding) > ($size + ($nodot ? 0 : 1))) {
4477 // If nodot is 0 and size is 1 chars more, we don't trunc and don't add '...'
4478 return dol_substr($newstring, 0, $size, $stringencoding) . ($nodot ? '' : '…');
4479 } else {
4480 //return 'u'.$size.'-'.$newstring.'-'.dol_strlen($newstring,$stringencoding).'-'.$string;
4481 return $string;
4482 }
4483 } elseif ($trunc == 'middle') {
4484 $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
4485 if (dol_strlen($newstring, $stringencoding) > 2 && dol_strlen($newstring, $stringencoding) > ($size + 1)) {
4486 $size1 = (int) round($size / 2);
4487 $size2 = (int) round($size / 2);
4488 return dol_substr($newstring, 0, $size1, $stringencoding) . '…' . dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size2, $size2, $stringencoding);
4489 } else {
4490 return $string;
4491 }
4492 } elseif ($trunc == 'left') {
4493 $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
4494 if (dol_strlen($newstring, $stringencoding) > ($size + ($nodot ? 0 : 1))) {
4495 // If nodot is 0 and size is 1 chars more, we don't trunc and don't add '...'
4496 return '…' . dol_substr($newstring, dol_strlen($newstring, $stringencoding) - $size, $size, $stringencoding);
4497 } else {
4498 return $string;
4499 }
4500 } elseif ($trunc == 'wrap') {
4501 $newstring = dol_textishtml($string) ? dol_string_nohtmltag($string, 1) : $string;
4502 if (dol_strlen($newstring, $stringencoding) > ($size + 1)) {
4503 return dol_substr($newstring, 0, $size, $stringencoding) . "\n" . dol_trunc(dol_substr($newstring, $size, dol_strlen($newstring, $stringencoding) - $size, $stringencoding), $size, $trunc);
4504 } else {
4505 return $string;
4506 }
4507 } else {
4508 return 'BadParam3CallingDolTrunc';
4509 }
4510}
4511
4512
4524function vatrate($rate, $addpercent = false, $info_bits = 0, $usestarfornpr = 0, $html = 0)
4525{
4526 $morelabel = '';
4527
4528 if (preg_match('/%/', $rate)) {
4529 $rate = str_replace('%', '', $rate);
4530 $addpercent = true;
4531 }
4532 $reg = array();
4533 if (preg_match('/\‍((.*)\‍)/', $rate, $reg)) {
4534 $morelabel = ' (' . $reg[1] . ')';
4535 $rate = preg_replace('/\s*' . preg_quote($morelabel, '/') . '/', '', $rate);
4536 $morelabel = ' ' . ($html ? '<span class="opacitymedium small">' : '') . '(' . $reg[1] . ')' . ($html ? '</span>' : '');
4537 }
4538 if (preg_match('/\*/', $rate)) {
4539 $rate = str_replace('*', '', $rate);
4540 $info_bits |= 1;
4541 }
4542
4543 // If rate is '9/9/9' we don't change it. If rate is '9.000' we apply price()
4544 if (!preg_match('/\//', $rate)) {
4545 $ret = price($rate, 0, '', 0, 0) . ($addpercent ? '%' : '');
4546 } else {
4547 // TODO Split on / and output with a price2num to have clean numbers without ton of 000.
4548 $ret = $rate . ($addpercent ? '%' : '');
4549 }
4550 if (($info_bits & 1) && $usestarfornpr >= 0) {
4551 $ret .= ' *';
4552 }
4553 $ret .= $morelabel;
4554 return $ret;
4555}
4556
4557
4572function price($amount, $form = 0, $outlangs = '', $trunc = 1, $rounding = -1, $forcerounding = -1, $currency_code = '')
4573{
4574 global $langs, $conf;
4575
4576 // Clean parameters
4577 if (empty($amount)) {
4578 $amount = 0; // To have a numeric value if amount not defined or = ''
4579 }
4580 $amount = (is_numeric($amount) ? $amount : 0); // Check if amount is numeric, for example, an error occurred when amount value = o (letter) instead 0 (number)
4581 if ($rounding == -1) {
4582 $rounding = min(getDolGlobalString('MAIN_MAX_DECIMALS_UNIT'), getDolGlobalString('MAIN_MAX_DECIMALS_TOT'));
4583 }
4584 $nbdecimal = $rounding;
4585
4586 if ($outlangs === 'none') {
4587 // Use international separators
4588 $dec = '.';
4589 $thousand = '';
4590 } else {
4591 // Output separators by default (french)
4592 $dec = ',';
4593 $thousand = ' ';
4594
4595 // If $outlangs not forced, we use use language
4596 if (!($outlangs instanceof Translate)) {
4597 $outlangs = $langs;
4598 }
4599
4600 if ($outlangs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal") {
4601 $dec = $outlangs->transnoentitiesnoconv("SeparatorDecimal");
4602 }
4603 if ($outlangs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") {
4604 $thousand = $outlangs->transnoentitiesnoconv("SeparatorThousand");
4605 }
4606 if ($thousand == 'None') {
4607 $thousand = '';
4608 } elseif ($thousand == 'Space') {
4609 $thousand = ' ';
4610 }
4611 }
4612 //print "outlangs=".$outlangs->defaultlang." amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
4613
4614 //print "amount=".$amount."-";
4615 $amount = str_replace(',', '.', $amount); // should be useless
4616 //print $amount."-";
4617 $data = explode('.', $amount);
4618 $decpart = isset($data[1]) ? $data[1] : '';
4619 $decpart = preg_replace('/0+$/i', '', $decpart); // Remove 0 at end of decimal part
4620 //print "decpart=".$decpart."<br>";
4621 $end = '';
4622
4623 // We increase nbdecimal if there is more decimal than asked (to not loose information)
4624 if (dol_strlen($decpart) > $nbdecimal) {
4625 $nbdecimal = dol_strlen($decpart);
4626 }
4627
4628 // If nbdecimal is higher than max to show
4629 $nbdecimalmaxshown = (int) str_replace('...', '', getDolGlobalString('MAIN_MAX_DECIMALS_SHOWN'));
4630 if ($trunc && $nbdecimal > $nbdecimalmaxshown) {
4631 $nbdecimal = $nbdecimalmaxshown;
4632 if (preg_match('/\.\.\./i', getDolGlobalString('MAIN_MAX_DECIMALS_SHOWN'))) {
4633 // If output is truncated, we show ...
4634 $end = '...';
4635 }
4636 }
4637
4638 // If force rounding
4639 if ((string) $forcerounding != '-1' && (string) $forcerounding != '') {
4640 if ($forcerounding === 'MU') {
4641 $nbdecimal = getDolGlobalInt('MAIN_MAX_DECIMALS_UNIT');
4642 } elseif ($forcerounding === 'MT') {
4643 $nbdecimal = getDolGlobalInt('MAIN_MAX_DECIMALS_TOT');
4644 } elseif ($forcerounding >= 0) {
4645 $nbdecimal = (int) $forcerounding;
4646 }
4647 }
4648
4649 // Format number
4650 $output = number_format((float) $amount, $nbdecimal, $dec, $thousand);
4651 // Add symbol of currency if requested
4652 $cursymbolbefore = $cursymbolafter = '';
4653 if ($currency_code && is_object($outlangs)) {
4654 if ($currency_code == 'auto') {
4655 $currency_code = $conf->currency;
4656 }
4657
4658 $listofcurrenciesbefore = array('AUD', 'CAD', 'CNY', 'COP', 'CLP', 'GBP', 'HKD', 'MXN', 'PEN', 'USD', 'CRC', 'ZAR');
4659 $listoflanguagesbefore = array('nl_NL');
4660 if (in_array($currency_code, $listofcurrenciesbefore) || in_array($outlangs->defaultlang, $listoflanguagesbefore)) {
4661 $cursymbolbefore .= $outlangs->getCurrencySymbol($currency_code);
4662 } else {
4663 $tmpcur = $outlangs->getCurrencySymbol($currency_code);
4664 $cursymbolafter .= ($tmpcur == $currency_code ? ' ' . $tmpcur : $tmpcur);
4665 }
4666 }
4667 if ($form) {
4668 $output = preg_replace('/\s/', '&nbsp;', $output);
4669 $output = $cursymbolbefore . $output . $end . ($cursymbolafter ? ' <span class="small">'.$cursymbolafter.'</span>' : '');
4670 $output = preg_replace('/\'/', '&#039;', $output);
4671 } else {
4672 $output = $cursymbolbefore . $output . $end . ($cursymbolafter ? ' '.$cursymbolafter : '');
4673 }
4674
4675 return $output;
4676}
4677
4704function price2num($amount, $rounding = '', $option = 0)
4705{
4706 global $langs;
4707
4708 // Clean parameters
4709 if (is_null($amount)) {
4710 $amount = '';
4711 }
4712
4713 // Round PHP function does not allow number like '1,234.56' nor '1.234,56' nor '1 234,56'
4714 // Numbers must be '1234.56'
4715 // Decimal delimiter for PHP and database SQL requests must be '.'
4716 $dec = ',';
4717 $thousand = ' ';
4718 if (is_null($langs)) { // $langs is not defined, we use english values.
4719 $dec = '.';
4720 $thousand = ',';
4721 } else {
4722 if ($langs->transnoentitiesnoconv("SeparatorDecimal") != "SeparatorDecimal") {
4723 $dec = $langs->transnoentitiesnoconv("SeparatorDecimal");
4724 }
4725 if ($langs->transnoentitiesnoconv("SeparatorThousand") != "SeparatorThousand") {
4726 $thousand = $langs->transnoentitiesnoconv("SeparatorThousand");
4727 }
4728 }
4729 if ($thousand == 'None') {
4730 $thousand = '';
4731 } elseif ($thousand == 'Space') {
4732 $thousand = ' ';
4733 }
4734 //print "amount=".$amount." html=".$form." trunc=".$trunc." nbdecimal=".$nbdecimal." dec='".$dec."' thousand='".$thousand."'<br>";
4735
4736 // Convert value to universal number format (no thousand separator, '.' as decimal separator)
4737 if ($option != 1) { // If not a PHP number or unknown, we change or clean format
4738 //print "\n".'PP'.$amount.' - '.$dec.' - '.$thousand.' - '.intval($amount).'<br>';
4739 if (!is_numeric($amount)) {
4740 $amount = preg_replace('/[a-zA-Z\/\\\*\‍(\‍)<>\_]/', '', $amount);
4741 }
4742
4743 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
4744 $amount = str_replace($thousand, '', $amount);
4745 }
4746
4747 // Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
4748 // to format defined by LC_NUMERIC after a calculation and we want source format to be like defined by Dolibarr setup.
4749 // So if number was already a good number, it is converted into local Dolibarr setup.
4750 if (is_numeric($amount)) {
4751 // We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
4752 $temps = sprintf("%10.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
4753 $temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
4754 $nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
4755 $amount = number_format($amount, $nbofdec, $dec, $thousand);
4756 }
4757 //print "QQ".$amount."<br>\n";
4758
4759 // Now make replaceents (the main goal of function)
4760
4761 if ($thousand != ',' && $thousand != '.') {
4762 // Accept the two types of decimal points french users (i.e., using ' ' for thousands)
4763
4764 // REGEX: Find the integral and decimal parts.
4765 //
4766 // We require that the decimal point only appears once in $amount.
4767 // The regex `/^(?<int>[^,]*,|[^.]*\.)(?<dec>[^.,]*)$/u` can be broken down as follows:
4768 // - `(?<int>[^,]*,|[^.]*\.)` is any accepted sequence up to the last potential decimal point '.' or ',' and named `int`.
4769 // It covers two cases:
4770 // - `[^,]*,`: Any sequence of characters that is not ',' with ',' accepted as the decimal point (from start of string because of earlier `^`);
4771 // - `[^.]*\.`: Any sequence of characters that is not a '.' with '.' accepted as the decimal point (from start of string.
4772 // - `(?<dec>[^.,]*)`: The sequence after the character accepted as the decimal point, not including it.
4773 $matches = array();
4774 if (preg_match('/^(?<int>[^,]*,|[^.]*\.)(?<dec>[^.,]*)$/u', $amount, $matches)) {
4775 $intPart = $matches['int'];
4776 $decPart = $matches['dec'];
4777
4778 // Remove all commas and dots from intPart
4779 $intPart = str_replace(['.', ','], '', $intPart);
4780
4781 // Combine intPart and decPart with a dot
4782 $amount = $intPart . $dec . $decPart;
4783 }
4784 }
4785
4786 $amount = str_replace(' ', '', $amount); // To avoid spaces
4787 $amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
4788 $amount = str_replace($dec, '.', $amount);
4789
4790 $amount = preg_replace('/[^0-9\-\.]/', '', $amount); // Clean non numeric chars (so it clean some UTF8 spaces for example.
4791 }
4792 //print ' XX'.$amount.' '.$rounding;
4793
4794 // Now, $amount is a real PHP float number. We make a rounding if required.
4795 if ($rounding) {
4796 $nbofdectoround = '';
4797 if ($rounding == 'MU') {
4798 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_UNIT'); // usually 5
4799 } elseif ($rounding == 'MT') {
4800 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_TOT'); // usually 2 or 3
4801 } elseif ($rounding == 'MS') {
4802 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_STOCK', 5);
4803 } elseif ($rounding == 'CU') {
4804 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_CURRENCY_UNIT', getDolGlobalInt('MAIN_MAX_DECIMALS_UNIT')); // TODO Use param of currency
4805 } elseif ($rounding == 'CT') {
4806 $nbofdectoround = getDolGlobalInt('MAIN_MAX_DECIMALS_CURRENCY_TOT', getDolGlobalInt('MAIN_MAX_DECIMALS_TOT')); // TODO Use param of currency
4807 } elseif ($rounding == 'CR') {
4808 $currencycode = (string) getDolGlobalString('MULTICURRENCY_USE_LIMIT_BY_CURRENCY') ? getDolCurrency() : '';
4809 $rounding_const = 'MAIN_MAX_DECIMALS_CURRENCY_RATE' . ($currencycode ? '_' . $currencycode : '');
4810 $nbofdectoround = getDolGlobalInt($rounding_const, getDolGlobalInt('MAIN_MAX_DECIMALS_CURRENCY_RATE', 8));
4811 } elseif (is_numeric($rounding)) {
4812 $nbofdectoround = (int) $rounding;
4813 }
4814
4815 //print " RR".$amount.' - '.$nbofdectoround.'<br>';
4816 if (dol_strlen($nbofdectoround)) {
4817 $amount = round(is_string($amount) ? (float) $amount : $amount, $nbofdectoround); // $nbofdectoround can be 0.
4818 } else {
4819 return 'ErrorBadParameterProvidedToFunction';
4820 }
4821 //print ' SS'.$amount.' - '.$nbofdec.' - '.$dec.' - '.$thousand.' - '.$nbofdectoround.'<br>';
4822
4823 // Convert amount to format with dolibarr dec and thousand (this is because PHP convert a number
4824 // to format defined by LC_NUMERIC after a calculation and we want source format to be defined by Dolibarr setup.
4825 if (is_numeric($amount)) {
4826 // We put in temps value of decimal ("0.00001"). Works with 0 and 2.0E-5 and 9999.10
4827 $temps = sprintf("%10.10F", $amount - intval($amount)); // temps=0.0000000000 or 0.0000200000 or 9999.1000000000
4828 $temps = preg_replace('/([\.1-9])0+$/', '\\1', $temps); // temps=0. or 0.00002 or 9999.1
4829 $nbofdec = max(0, dol_strlen($temps) - 2); // -2 to remove "0."
4830 $amount = number_format($amount, min($nbofdec, $nbofdectoround), $dec, $thousand); // Convert amount to format with dolibarr dec and thousand
4831 }
4832 //print "TT".$amount.'<br>';
4833
4834 // Always make replace because each math function (like round) replace
4835 // with local values and we want a number that has a SQL string format x.y
4836 if ($thousand != ',' && $thousand != '.') {
4837 $amount = str_replace(',', '.', $amount); // To accept 2 notations for french users
4838 }
4839
4840 $amount = str_replace(' ', '', $amount); // To avoid spaces
4841 $amount = str_replace($thousand, '', $amount); // Replace of thousand before replace of dec to avoid pb if thousand is .
4842 $amount = str_replace($dec, '.', $amount);
4843
4844 $amount = preg_replace('/[^0-9\-\.]/', '', $amount); // Clean non numeric chars (so it clean some UTF8 spaces for example.
4845 }
4846
4847 return $amount;
4848}
4849
4850
4863function get_localtax($vatrate, $local, $thirdparty_buyer = null, $thirdparty_seller = null, $vatnpr = 0)
4864{
4865 global $db, $conf, $mysoc;
4866
4867 if (empty($thirdparty_seller) || !is_object($thirdparty_seller)) {
4868 $thirdparty_seller = $mysoc;
4869 }
4870
4871 dol_syslog(
4872 "get_localtax tva=" . $vatrate . " local=" . $local
4873 ." thirdparty_buyer id=" . (is_object($thirdparty_buyer) ? $thirdparty_buyer->id : '') . "/country_code=" . (is_object($thirdparty_buyer) ? $thirdparty_buyer->country_code : '')
4874 ." thirdparty_seller id=" . $thirdparty_seller->id . "/country_code=" . $thirdparty_seller->country_code
4875 ." thirdparty_seller localtax1_assuj=" . $thirdparty_seller->localtax1_assuj . " thirdparty_seller localtax2_assuj=" . $thirdparty_seller->localtax2_assuj
4876 );
4877
4878 $vatratecleaned = $vatrate;
4879 $reg = array();
4880 if (preg_match('/^(.*)\s*\‍((.*)\‍)$/', (string) $vatrate, $reg)) { // If vat is "xx (yy)"
4881 $vatratecleaned = trim($reg[1]);
4882 $vatratecode = $reg[2];
4883 }
4884
4885 /*if ($thirdparty_buyer->country_code != $thirdparty_seller->country_code)
4886 {
4887 return 0;
4888 }*/
4889
4890 // Some test to guess with no need to make database access
4891 if ($mysoc->country_code == 'ES') { // For spain localtaxes 1 and 2, tax is qualified if buyer use local tax
4892 if ($local == 1) {
4893 if (!$mysoc->localtax1_assuj || (string) $vatratecleaned == "0") {
4894 return 0;
4895 }
4896 if ($thirdparty_seller->id == $mysoc->id) {
4897 if (!$thirdparty_buyer->localtax1_assuj) {
4898 return 0;
4899 }
4900 } else {
4901 if (!$thirdparty_seller->localtax1_assuj) {
4902 return 0;
4903 }
4904 }
4905 }
4906
4907 if ($local == 2) {
4908 //if (! $mysoc->localtax2_assuj || (string) $vatratecleaned == "0") return 0;
4909 if (!$mysoc->localtax2_assuj) {
4910 return 0; // If main vat is 0, IRPF may be different than 0.
4911 }
4912 if ($thirdparty_seller->id == $mysoc->id) {
4913 if (!$thirdparty_buyer->localtax2_assuj) {
4914 return 0;
4915 }
4916 } else {
4917 if (!$thirdparty_seller->localtax2_assuj) {
4918 return 0;
4919 }
4920 }
4921 }
4922 } else {
4923 if ($local == 1 && !$thirdparty_seller->localtax1_assuj) {
4924 return 0;
4925 }
4926 if ($local == 2 && !$thirdparty_seller->localtax2_assuj) {
4927 return 0;
4928 }
4929 }
4930
4931 // For some country MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY is forced to on.
4932 if (in_array($mysoc->country_code, array('ES'))) {
4933 $conf->global->MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY = 1;
4934 }
4935
4936 // Search local taxes
4937 if (getDolGlobalString('MAIN_GET_LOCALTAXES_VALUES_FROM_THIRDPARTY')) {
4938 if ($local == 1) {
4939 if ($thirdparty_seller != $mysoc) {
4940 if (!isOnlyOneLocalTax($local)) { // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
4941 return $thirdparty_seller->localtax1_value;
4942 }
4943 } else { // i am the seller
4944 if (!isOnlyOneLocalTax($local)) { // TODO If seller is me, why not always returning this, even if there is only one locatax vat.
4945 return getDolGlobalString('MAIN_INFO_VALUE_LOCALTAX1');
4946 }
4947 }
4948 }
4949 if ($local == 2) {
4950 if ($thirdparty_seller != $mysoc) {
4951 if (!isOnlyOneLocalTax($local)) { // TODO We should provide $vatrate to search on correct line and not always on line with highest vat rate
4952 // TODO We should also return value defined on thirdparty only if defined
4953 return $thirdparty_seller->localtax2_value;
4954 }
4955 } else { // i am the seller
4956 if (in_array($mysoc->country_code, array('ES'))) {
4957 return $thirdparty_buyer->localtax2_value;
4958 } else {
4959 return getDolGlobalString('MAIN_INFO_VALUE_LOCALTAX2');
4960 }
4961 }
4962 }
4963 }
4964
4965 // By default, search value of local tax on line of common tax
4966 $sql = "SELECT t.localtax1, t.localtax2, t.localtax1_type, t.localtax2_type";
4967 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t, " . MAIN_DB_PREFIX . "c_country as c";
4968 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $db->escape($thirdparty_seller->country_code) . "'";
4969 $sql .= " AND t.taux = " . ((float) $vatratecleaned) . " AND t.active = 1";
4970 $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
4971 if (!empty($vatratecode)) {
4972 $sql .= " AND t.code ='" . $db->escape($vatratecode) . "'"; // If we have the code, we use it in priority
4973 } else {
4974 $sql .= " AND t.recuperableonly = '" . $db->escape((string) $vatnpr) . "'";
4975 }
4976
4977 $resql = $db->query($sql);
4978
4979 if ($resql) {
4980 $obj = $db->fetch_object($resql);
4981 if ($obj) {
4982 if ($local == 1) {
4983 return $obj->localtax1;
4984 } elseif ($local == 2) {
4985 return $obj->localtax2;
4986 }
4987 }
4988 }
4989
4990 return 0;
4991}
4992
4993
5002function isOnlyOneLocalTax($local)
5003{
5004 $tax = get_localtax_by_third($local);
5005
5006 $valors = explode(":", $tax);
5007
5008 if (count($valors) > 1) {
5009 return false;
5010 } else {
5011 return true;
5012 }
5013}
5014
5021function get_localtax_by_third($local)
5022{
5023 global $db, $mysoc;
5024
5025 $sql = " SELECT t.localtax" . ((int) $local) . " as localtax";
5026 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t INNER JOIN " . MAIN_DB_PREFIX . "c_country as c ON c.rowid = t.fk_pays";
5027 $sql .= " WHERE c.code = '" . $db->escape($mysoc->country_code) . "' AND t.active = 1 AND t.entity IN (" . getEntity('c_tva') . ") AND t.taux = (";
5028 $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";
5029 $sql .= " WHERE c.code = '" . $db->escape($mysoc->country_code) . "' AND t.entity IN (" . getEntity('c_tva') . ") AND tt.active = 1)";
5030 $sql .= " AND t.localtax" . ((int) $local) . "_type <> '0'";
5031 $sql .= " ORDER BY t.rowid DESC";
5032
5033 $resql = $db->query($sql);
5034 if ($resql) {
5035 $obj = $db->fetch_object($resql);
5036 if ($obj) {
5037 return $obj->localtax;
5038 } else {
5039 return '0';
5040 }
5041 }
5042
5043 return 'Error';
5044}
5045
5046
5058function getTaxesFromId($vatrate, $buyer = null, $seller = null, $firstparamisid = 1)
5059{
5060 global $db;
5061
5062 dol_syslog("getTaxesFromId vat id or rate = " . $vatrate);
5063
5064 // Search local taxes
5065 $sql = "SELECT t.rowid, t.code, t.taux as rate, t.recuperableonly as npr, t.accountancy_code_sell, t.accountancy_code_buy,";
5066 $sql .= " t.localtax1, t.localtax1_type, t.localtax2, t.localtax2_type";
5067 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t";
5068 if ($firstparamisid) {
5069 $sql .= " WHERE t.rowid = " . (int) $vatrate;
5070 } else {
5071 $vatratecleaned = $vatrate;
5072 $vatratecode = '';
5073 $reg = array();
5074 if (preg_match('/^(.*)\s*\‍((.*)\‍)$/', $vatrate, $reg)) { // If vat is "xx (yy)"
5075 $vatratecleaned = $reg[1];
5076 $vatratecode = $reg[2];
5077 }
5078
5079 $sql .= ", " . MAIN_DB_PREFIX . "c_country as c";
5080 /*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 ??
5081 else $sql.= " WHERE t.fk_pays = c.rowid AND c.code = '".$db->escape($seller->country_code)."'";*/
5082 $sql .= " WHERE t.fk_pays = c.rowid";
5083 if (getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC')) {
5084 $sql .= " AND c.code = '" . $db->escape($buyer->country_code) . "'";
5085 } else {
5086 $sql .= " AND c.code = '" . $db->escape($seller->country_code) . "'";
5087 }
5088 $sql .= " AND t.taux = " . ((float) $vatratecleaned) . " AND t.active = 1";
5089 $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
5090 if ($vatratecode) {
5091 $sql .= " AND t.code = '" . $db->escape($vatratecode) . "'";
5092 }
5093 }
5094
5095 $resql = $db->query($sql);
5096 if ($resql) {
5097 $obj = $db->fetch_object($resql);
5098 if ($obj) {
5099 return array(
5100 'rowid' => $obj->rowid,
5101 'code' => $obj->code,
5102 'rate' => $obj->rate,
5103 'localtax1' => $obj->localtax1,
5104 'localtax1_type' => $obj->localtax1_type,
5105 'localtax2' => $obj->localtax2,
5106 'localtax2_type' => $obj->localtax2_type,
5107 'npr' => $obj->npr,
5108 'accountancy_code_sell' => $obj->accountancy_code_sell,
5109 'accountancy_code_buy' => $obj->accountancy_code_buy
5110 );
5111 } else {
5112 return array();
5113 }
5114 } else {
5115 dol_print_error($db);
5116 }
5117
5118 return array();
5119}
5120
5137function getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid = 0)
5138{
5139 global $db, $mysoc;
5140
5141 dol_syslog("getLocalTaxesFromRate vatrate=" . $vatrate . " local=" . $local);
5142
5143 // Search local taxes
5144 $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";
5145 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t";
5146 if ($firstparamisid) {
5147 $sql .= " WHERE t.rowid = " . (int) $vatrate;
5148 } else {
5149 $vatratecleaned = $vatrate;
5150 $vatratecode = '';
5151 $reg = array();
5152 if (preg_match('/^(.*)\s*\‍((.*)\‍)$/', $vatrate, $reg)) { // If vat is "x.x (yy)"
5153 $vatratecleaned = $reg[1];
5154 $vatratecode = $reg[2];
5155 }
5156
5157 $sql .= ", " . MAIN_DB_PREFIX . "c_country as c";
5158 if (!empty($mysoc) && $mysoc->country_code == 'ES') {
5159 $countrycodetouse = ((empty($buyer) || empty($buyer->country_code)) ? $mysoc->country_code : $buyer->country_code);
5160 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $db->escape($countrycodetouse) . "'"; // local tax in spain use the buyer country ??
5161 } else {
5162 $countrycodetouse = ((empty($seller) || empty($seller->country_code)) ? $mysoc->country_code : $seller->country_code);
5163 $sql .= " WHERE t.fk_pays = c.rowid AND c.code = '" . $db->escape($countrycodetouse) . "'";
5164 }
5165 $sql .= " AND t.taux = " . ((float) $vatratecleaned) . " AND t.active = 1";
5166 if ($vatratecode) {
5167 $sql .= " AND t.code = '" . $db->escape($vatratecode) . "'";
5168 }
5169 }
5170
5171 $resql = $db->query($sql);
5172 if ($resql) {
5173 $obj = $db->fetch_object($resql);
5174
5175 if ($obj) {
5176 $vateratestring = $obj->rate . ($obj->code ? ' (' . $obj->code . ')' : '');
5177
5178 if ($local == 1) {
5179 return array($obj->localtax1_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
5180 } elseif ($local == 2) {
5181 return array($obj->localtax2_type, get_localtax($vateratestring, $local, $buyer, $seller), $obj->accountancy_code_sell, $obj->accountancy_code_buy);
5182 } else {
5183 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);
5184 }
5185 }
5186 }
5187
5188 return array();
5189}
5190
5201function get_product_vat_for_country($idprod, $thirdpartytouseforcountry, $idprodfournprice = 0)
5202{
5203 global $db, $mysoc;
5204
5205 require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
5206
5207 $ret = 0;
5208 $found = 0;
5209
5210 if ($idprod > 0) {
5211 // Load product
5212 $product = new Product($db);
5213 $product->fetch($idprod);
5214
5215 if (($mysoc->country_code == $thirdpartytouseforcountry->country_code)
5216 || (in_array($mysoc->country_code, array('FR', 'MC')) && in_array($thirdpartytouseforcountry->country_code, array('FR', 'MC')))
5217 || (in_array($mysoc->country_code, array('MQ', 'GP')) && in_array($thirdpartytouseforcountry->country_code, array('MQ', 'GP')))
5218 ) {
5219 // If country of thirdparty to consider is ours
5220 if ($idprodfournprice > 0) { // We want vat for product for a "supplier" object
5221 $result = $product->get_buyprice($idprodfournprice, 0, 0, '');
5222 if ($result > 0) {
5223 $ret = $product->vatrate_supplier;
5224 if ($product->default_vat_code_supplier) {
5225 $ret .= ' (' . $product->default_vat_code_supplier . ')';
5226 }
5227 $found = 1;
5228 }
5229 }
5230 if (!$found) {
5231 $ret = $product->tva_tx; // Default sales vat of product
5232 if ($product->default_vat_code) {
5233 $ret .= ' (' . $product->default_vat_code . ')';
5234 }
5235 $found = 1;
5236 }
5237 } else {
5238 // TODO Read default product vat according to product and an other countrycode.
5239 // Vat for couple anothercountrycode/product is data that is not managed and store yet, so we will fallback on next rule.
5240 }
5241 }
5242
5243 if (!$found) {
5244 if (!getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS')) {
5245 // 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).
5246 $sql = "SELECT t.taux as vat_rate, t.code as default_vat_code";
5247 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t, " . MAIN_DB_PREFIX . "c_country as c";
5248 $sql .= " WHERE t.active = 1 AND t.fk_pays = c.rowid AND c.code = '" . $db->escape($thirdpartytouseforcountry->country_code) . "'";
5249 $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
5250 $sql .= " ORDER BY t.use_default DESC, t.taux DESC, t.code ASC, t.recuperableonly ASC";
5251 $sql .= $db->plimit(1);
5252
5253 $resql = $db->query($sql);
5254 if ($resql) {
5255 $obj = $db->fetch_object($resql);
5256 if ($obj) {
5257 $ret = $obj->vat_rate;
5258 if ($obj->default_vat_code) {
5259 $ret .= ' (' . $obj->default_vat_code . ')';
5260 }
5261 }
5262 $db->free($resql);
5263 } else {
5264 dol_print_error($db);
5265 }
5266 } else {
5267 // Forced value if autodetect fails. MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS can be
5268 // '1.23'
5269 // or '1.23 (CODE)'
5270 $defaulttx = '';
5271 if (getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS') != 'none') {
5272 $defaulttx = getDolGlobalString('MAIN_VAT_DEFAULT_IF_AUTODETECT_FAILS');
5273 }
5274 /*if (preg_match('/\‍((.*)\‍)/', $defaulttx, $reg)) {
5275 $defaultcode = $reg[1];
5276 $defaulttx = preg_replace('/\s*\‍(.*\‍)/', '', $defaulttx);
5277 }*/
5278
5279 $ret = $defaulttx;
5280 }
5281 }
5282
5283 dol_syslog("get_product_vat_for_country: ret=" . $ret);
5284
5285 return $ret;
5286}
5287
5297function get_product_localtax_for_country($idprod, $local, $thirdpartytouseforcountry)
5298{
5299 global $db, $mysoc;
5300
5301 if (!class_exists('Product')) {
5302 require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
5303 }
5304
5305 $ret = 0;
5306 $found = 0;
5307
5308 if ($idprod > 0) {
5309 // Load product
5310 $product = new Product($db);
5311 $result = $product->fetch($idprod);
5312
5313 if ($mysoc->country_code == $thirdpartytouseforcountry->country_code) { // If selling country is ours
5314 /* Not defined yet, so we don't use this
5315 if ($local==1) $ret=$product->localtax1_tx;
5316 elseif ($local==2) $ret=$product->localtax2_tx;
5317 $found=1;
5318 */
5319 } else {
5320 // TODO Read default product vat according to product and another countrycode.
5321 // Vat for couple anothercountrycode/product is data that is not managed and store yet, so we will fallback on next rule.
5322 }
5323 }
5324
5325 if (!$found) {
5326 // If vat of product for the country not found or not defined, we return higher vat of country.
5327 $sql = "SELECT taux as vat_rate, localtax1, localtax2";
5328 $sql .= " FROM " . MAIN_DB_PREFIX . "c_tva as t, " . MAIN_DB_PREFIX . "c_country as c";
5329 $sql .= " WHERE t.active=1 AND t.fk_pays = c.rowid AND c.code='" . $db->escape($thirdpartytouseforcountry->country_code) . "'";
5330 $sql .= " AND t.entity IN (" . getEntity('c_tva') . ")";
5331 $sql .= " ORDER BY t.taux DESC, t.recuperableonly ASC";
5332 $sql .= $db->plimit(1);
5333
5334 $resql = $db->query($sql);
5335 if ($resql) {
5336 $obj = $db->fetch_object($resql);
5337 if ($obj) {
5338 if ($local == 1) {
5339 $ret = $obj->localtax1;
5340 } elseif ($local == 2) {
5341 $ret = $obj->localtax2;
5342 }
5343 }
5344 } else {
5345 dol_print_error($db);
5346 }
5347 }
5348
5349 dol_syslog("get_product_localtax_for_country: ret=" . $ret);
5350 return $ret;
5351}
5352
5371function get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
5372{
5373 global $mysoc, $db, $hookmanager;
5374
5375 require_once DOL_DOCUMENT_ROOT . '/core/lib/company.lib.php';
5376
5377 // Note: possible values for tva_assuj are 0/1 or franchise/reel
5378 $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;
5379
5380 if (empty($thirdparty_seller->country_code)) {
5381 $thirdparty_seller->country_code = $mysoc->country_code;
5382 }
5383 $seller_country_code = $thirdparty_seller->country_code;
5384 $seller_in_cee = isInEEC($thirdparty_seller);
5385
5386 if (empty($thirdparty_buyer->country_code)) {
5387 $thirdparty_buyer->country_code = $mysoc->country_code;
5388 }
5389 $buyer_country_code = $thirdparty_buyer->country_code;
5390 $buyer_in_cee = isInEEC($thirdparty_buyer);
5391
5392 dol_syslog(
5393 "get_default_tva: seller use vat=" . $seller_use_vat . ", seller country=" . $seller_country_code . ", seller in cee=" . ((string) (int) $seller_in_cee)
5394 .", buyer vat number=" . $thirdparty_buyer->tva_intra . " buyer country=" . $buyer_country_code . ", buyer state=" . $thirdparty_buyer->state_id . " buyer in cee=" . ((string) (int) $buyer_in_cee)
5395 .", idprod=" . $idprod . ", idprodfournprice=" . $idprodfournprice . ", SERVICE_ARE_ECOMMERCE_200238EC=" . getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC')
5396 );
5397
5398 $vatvalue = 0;
5399 $vatrule = '';
5400
5401 // 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)
5402 // we use the buyer VAT.
5403 if (getDolGlobalString('SERVICE_ARE_ECOMMERCE_200238EC')) {
5404 if ($seller_in_cee && $buyer_in_cee) {
5405 $isacompany = $thirdparty_buyer->isACompany();
5406 if ($isacompany && !getDolGlobalString('MAIN_USE_VAT_ZERO_FOR_COMPANIES_IN_EEC_EVEN_IF_VAT_ID_UNKNOWN')) {
5407 require_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php';
5408 if (!isValidVATID($thirdparty_buyer)) {
5409 $isacompany = 0;
5410 }
5411 }
5412
5413 if (!$isacompany) {
5414 $vatvalue = get_product_vat_for_country($idprod, $thirdparty_buyer, $idprodfournprice);
5415 $vatrule = 'VATRULE 0';
5416 }
5417 }
5418 }
5419
5420 // If seller does not use VAT, default VAT is 0. End of rule.
5421 if (empty($vatrule) && !$seller_use_vat) {
5422 //print 'VATRULE 1';
5423 // TODO get the VAT Code of exemption asked into setup if country isInEEC (from an array list of possible
5424 // values like VATEX-EU-132-*, VATEX-FR-FRANCHISE, VATEX-EU-AE...
5425 // When we had recorded it, we also added a corresponding entry into table of vat code if it does not exists yet.
5426 // Here we test if entry for the VAT exemption code exists in llx_vat, we can return '0 (VATEX-EU-132-xx)'
5427 // If not, we add it and we return '0 (VATEX-EU-132-xx)'
5428 $vatvalue = 0;
5429 $vatrule = 'VATRULE 1';
5430 }
5431
5432 // 'VATRULE 2' - Force VAT if a buyer department is defined on vat rates dictionary
5433 if (empty($vatrule) && !empty($thirdparty_buyer->state_id)) {
5434 $sql = "SELECT d.rowid, t.taux as vat_default_rate, t.code as vat_default_code ";
5435 $sql .= " FROM " . $db->prefix() . "c_tva as t";
5436 $sql .= " INNER JOIN " . $db->prefix() . "c_departements as d ON t.fk_department_buyer = d.rowid";
5437 $sql .= " WHERE d.rowid = " . ((int) $thirdparty_buyer->state_id);
5438 $sql .= " AND t.active > 0";
5439 $sql .= " AND t.entity IN (".getEntity('c_tva').")";
5440 $sql .= " ORDER BY t.use_default DESC, t.taux DESC, t.code ASC, t.recuperableonly ASC";
5441
5442 $res = $db->query($sql);
5443 if ($res) {
5444 if ($db->num_rows($res)) {
5445 $obj = $db->fetch_object($res);
5446
5447 $vatvalue = $obj->vat_default_rate . ' (' . $obj->vat_default_code . ')';
5448 $vatrule = 'VATRULE 2';
5449 }
5450 $db->free($res);
5451 }
5452 }
5453
5454 // If the (seller country = buyer country) then the default VAT = VAT of the product sold. End of rule.
5455 if (empty($vatrule) && (
5456 ($seller_country_code == $buyer_country_code)
5457 || (in_array($seller_country_code, array('FR', 'MC')) && in_array($buyer_country_code, array('FR', 'MC')))
5458 || (in_array($seller_country_code, array('MQ', 'GP')) && in_array($buyer_country_code, array('MQ', 'GP'))) // We should be able to manage the case of MQ, GP, ... with a deicated vat rate at previous step.
5459 )) { // Warning ->country_code not always defined
5460 //print 'VATRULE 3';
5461 $tmpvat = get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
5462
5463 if ($seller_country_code == 'IN' && getDolGlobalString('MAIN_SALETAX_AUTOSWITCH_I_CS_FOR_INDIA')) {
5464 // Special case for india.
5465 //print 'VATRULE 3b';
5466 $reg = array();
5467 if (preg_match('/C+S-(\d+)/', $tmpvat, $reg) && $thirdparty_seller->state_id != $thirdparty_buyer->state_id) {
5468 // we must revert the C+S into I
5469 $tmpvat = str_replace("C+S", "I", $tmpvat);
5470 } elseif (preg_match('/I-(\d+)/', $tmpvat, $reg) && $thirdparty_seller->state_id == $thirdparty_buyer->state_id) {
5471 // we must revert the I into C+S
5472 $tmpvat = str_replace("I", "C+S", $tmpvat);
5473 }
5474 }
5475
5476 $vatvalue = $tmpvat;
5477 $vatrule = 'VATRULE 3b';
5478 }
5479
5480 // 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.
5481 // 'VATRULE 4' - Not supported
5482
5483 // If (seller and buyer in the European Community) and (buyer = individual) then VAT by default = VAT of the product sold. End of rule
5484 // If (seller and buyer in European Community) and (buyer = company) then VAT by default=0. End of rule
5485 if (empty($vatrule) && ($seller_in_cee && $buyer_in_cee)) {
5486 $isacompany = $thirdparty_buyer->isACompany();
5487 if ($isacompany && !getDolGlobalString('MAIN_USE_VAT_ZERO_FOR_COMPANIES_IN_EEC_EVEN_IF_VAT_ID_UNKNOWN')) {
5488 require_once DOL_DOCUMENT_ROOT . '/core/lib/functions2.lib.php';
5489 if (!isValidVATID($thirdparty_buyer)) {
5490 $isacompany = 0;
5491 }
5492 }
5493
5494 if (!$isacompany) {
5495 //print 'VATRULE 5';
5496 $vatvalue = get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
5497 $vatrule = 'VATRULE 5';
5498 } else {
5499 //print 'VATRULE 6';
5500 // TODO This is the case of VAT exemption 'VATEX-EU-IC'
5501 // If entry for the VAT exemption code exists in llx_vat, we can return '0 (VATEX-EU-IC)'
5502 // If not, we add it and we return '0 (VATEX-EU-IC)'
5503 $vatvalue = 0;
5504 $vatrule = 'VATRULE 6';
5505 }
5506 }
5507
5508 // 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
5509 // I don't see any use case that need this rule, this case is on only if MAIN_USE_VAT_OF_PRODUCT_FOR_INDIVIDUAL_CUSTOMER_OUT_OF_EEC set
5510 if (empty($vatrule) && getDolGlobalString('MAIN_USE_VAT_OF_PRODUCT_FOR_INDIVIDUAL_CUSTOMER_OUT_OF_EEC') && empty($buyer_in_cee)) {
5511 $isacompany = $thirdparty_buyer->isACompany();
5512 if (!$isacompany) {
5513 $vatvalue = get_product_vat_for_country($idprod, $thirdparty_seller, $idprodfournprice);
5514 $vatrule = 'VATRULE extra';
5515 //print 'VATRULE extra';
5516 }
5517 }
5518
5519 // Otherwise the VAT proposed by default=0. End of rule.
5520 // Rem: This means that at least one of the 2 is outside the European Community and the country differs
5521 //print 'VATRULE 7';
5522 // TODO This is the case of VAT exemption 'VATEX-EU-G'
5523 // If entry for the VAT exemption code exists in llx_vat, we can return '0 (VATEX-xxx)'
5524 // If not, we add it and we return '0 (VATEX-xxx)'
5525
5526 // Allow an external module to bypass the calculation of prices
5527 $parameters = array('vatvalue' => $vatvalue, 'vatrule' => $vatrule);
5528 $tmpobject = null;
5529 $tmpaction = '';
5530 // @phan-suppress-next-line PhanPluginConstantVariableNull
5531 $reshook = $hookmanager->executeHooks('get_default_tva', $parameters, $tmpobject, $tmpaction); // @phan-suppress-current-line PhanPluginConstantVariableNull
5532 if ($reshook > 0 && !empty($hookmanager->resArray['vatvalue'])) {
5533 $vatvalue = $hookmanager->resArray['vatvalue'];
5534 $vatrule = $hookmanager->resArray['vatrule']; // For information
5535 }
5536
5537 return $vatvalue;
5538}
5539
5540
5551function get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod = 0, $idprodfournprice = 0)
5552{
5553 global $db;
5554
5555 if ($idprodfournprice > 0) {
5556 if (!class_exists('ProductFournisseur')) {
5557 require_once DOL_DOCUMENT_ROOT . '/fourn/class/fournisseur.product.class.php';
5558 }
5559 $prodprice = new ProductFournisseur($db);
5560 $prodprice->fetch_product_fournisseur_price($idprodfournprice);
5561 return $prodprice->fourn_tva_npr;
5562 } elseif ($idprod > 0) {
5563 if (!class_exists('Product')) {
5564 require_once DOL_DOCUMENT_ROOT . '/product/class/product.class.php';
5565 }
5566 $prod = new Product($db);
5567 $prod->fetch($idprod);
5568 return $prod->tva_npr;
5569 }
5570
5571 return 0;
5572}
5573
5587function get_default_localtax($thirdparty_seller, $thirdparty_buyer, $local, $idprod = 0)
5588{
5589 global $mysoc;
5590
5591 if (!is_object($thirdparty_seller)) {
5592 return -1;
5593 }
5594 if (!is_object($thirdparty_buyer)) {
5595 return -1;
5596 }
5597
5598 if (empty($thirdparty_seller->country_code)) {
5599 $thirdparty_seller->country_code = $mysoc->country_code;
5600 }
5601 $seller_country_code = $thirdparty_seller->country_code;
5602 //$seller_in_cee = isInEEC($thirdparty_seller);
5603
5604 if (empty($thirdparty_buyer->country_code)) {
5605 $thirdparty_buyer->country_code = $mysoc->country_code;
5606 }
5607 $buyer_country_code = $thirdparty_buyer->country_code;
5608 //$buyer_in_cee = isInEEC($thirdparty_buyer);
5609
5610 if ($local == 1) { // Localtax 1
5611 if ($mysoc->country_code == 'ES') {
5612 if (is_numeric($thirdparty_buyer->localtax1_assuj) && !$thirdparty_buyer->localtax1_assuj) {
5613 return 0;
5614 }
5615 } else {
5616 // Si vendeur non assujeti a Localtax1, localtax1 par default=0
5617 if (is_numeric($thirdparty_seller->localtax1_assuj) && !$thirdparty_seller->localtax1_assuj) {
5618 return 0;
5619 }
5620 if (!is_numeric($thirdparty_seller->localtax1_assuj) && $thirdparty_seller->localtax1_assuj == 'localtax1off') {
5621 return 0;
5622 }
5623 }
5624 } elseif ($local == 2) { //I Localtax 2
5625 // Si vendeur non assujeti a Localtax2, localtax2 par default=0
5626 if (is_numeric($thirdparty_seller->localtax2_assuj) && !$thirdparty_seller->localtax2_assuj) {
5627 return 0;
5628 }
5629 if (!is_numeric($thirdparty_seller->localtax2_assuj) && $thirdparty_seller->localtax2_assuj == 'localtax2off') {
5630 return 0;
5631 }
5632 }
5633
5634 if ($seller_country_code == $buyer_country_code) {
5635 return get_product_localtax_for_country($idprod, $local, $thirdparty_seller);
5636 }
5637
5638 return 0;
5639}
5640
5641
5660function get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart = '')
5661{
5662 if (empty($modulepart) && is_object($object)) {
5663 if (!empty($object->module)) {
5664 $modulepart = $object->module;
5665 } elseif (!empty($object->element)) {
5666 $modulepart = $object->element;
5667 }
5668 }
5669
5670 $path = '';
5671
5672 // Define $arrayforoldpath that is module path using a hierarchy on more than 1 level.
5673 $arrayforoldpath = array('cheque' => 2, 'category' => 2, 'supplier_invoice' => 2, 'invoice_supplier' => 2, 'mailing' => 2, 'supplier_payment' => 2);
5674 if (getDolGlobalInt('PRODUCT_USE_OLD_PATH_FOR_PHOTO')) {
5675 $arrayforoldpath['product'] = 2;
5676 }
5677
5678 if (empty($level) && array_key_exists($modulepart, $arrayforoldpath)) {
5679 $level = $arrayforoldpath[$modulepart];
5680 }
5681 if (!empty($level) && array_key_exists($modulepart, $arrayforoldpath)) {
5682 // This part should be removed once all code is using "get_exdir" to forge path, with parameter $object and $modulepart provided.
5683 if (empty($num) && is_object($object)) {
5684 $num = ((int) $object->id);
5685 }
5686 if (empty($alpha)) {
5687 $num = preg_replace('/([^0-9])/i', '', $num);
5688 } else {
5689 $num = preg_replace('/^.*\-/i', '', $num);
5690 }
5691 $num = substr("000" . $num, -$level);
5692 if ($level == 1) {
5693 $path = substr($num, 0, 1);
5694 }
5695 if ($level == 2) {
5696 $path = substr($num, 1, 1) . '/' . substr($num, 0, 1);
5697 }
5698 if ($level == 3) {
5699 $path = substr($num, 2, 1) . '/' . substr($num, 1, 1) . '/' . substr($num, 0, 1);
5700 }
5701 } else {
5702 // We will enhance here a common way of forging path for document storage.
5703 // In a future, we may distribute directories on several levels depending on setup and object.
5704 // Here, $object->id, $object->ref and $modulepart are required.
5705 if (in_array($modulepart, array('societe', 'thirdparty')) && $object instanceof Societe) {
5706 // Special case for thirdparty, where the ref is a company name that is not unique so path on disk is using the ID instead of the ref
5707 $path = dol_sanitizeFileName((string) $object->id);
5708 } else {
5709 $path = dol_sanitizeFileName(empty($object->ref) ? (string) ((is_object($object) && property_exists($object, 'id')) ? ((int) $object->id) : '') : $object->ref);
5710 }
5711 }
5712
5713 if (empty($withoutslash) && !empty($path)) {
5714 $path .= '/';
5715 }
5716
5717 return $path;
5718}
5719
5728function dol_mkdir($dir, $dataroot = '', $newmask = '')
5729{
5730 dol_syslog("functions.lib::dol_mkdir: dir=" . $dir, LOG_INFO);
5731
5732 $dir = dol_sanitizePathName($dir, '_', 0);
5733
5734 $dir_osencoded = dol_osencode($dir);
5735 if (@is_dir($dir_osencoded)) {
5736 return 0;
5737 }
5738
5739 $nberr = 0;
5740 $nbcreated = 0;
5741
5742 $ccdir = '';
5743 if (!empty($dataroot)) {
5744 // Remove data root from loop
5745 $dir = str_replace($dataroot . '/', '', $dir);
5746 $ccdir = $dataroot . '/';
5747 }
5748
5749 $cdir = explode("/", $dir);
5750 $num = count($cdir);
5751 for ($i = 0; $i < $num; $i++) {
5752 if ($i > 0) {
5753 $ccdir .= '/' . $cdir[$i];
5754 } else {
5755 $ccdir .= $cdir[$i];
5756 }
5757 $regs = array();
5758 if (preg_match("/^.:$/", $ccdir, $regs)) {
5759 continue; // If the Windows path is incomplete, continue with next directory
5760 }
5761
5762 // Attention, is_dir() can fail event if the directory exists
5763 // (i.e. according the open_basedir configuration)
5764 if ($ccdir) {
5765 $ccdir_osencoded = dol_osencode($ccdir);
5766 if (!@is_dir($ccdir_osencoded)) {
5767 dol_syslog("functions.lib::dol_mkdir: Directory '" . $ccdir . "' is not found (does not exists or is outside open_basedir PHP setting).", LOG_DEBUG);
5768
5769 umask(0);
5770 $dirmaskdec = octdec((string) $newmask);
5771 if (empty($newmask)) {
5772 $dirmaskdec = octdec(getDolGlobalString('MAIN_UMASK', '0755'));
5773 }
5774 $dirmaskdec |= octdec('0111'); // Set x bit required for directories
5775 if (!@mkdir($ccdir_osencoded, $dirmaskdec)) {
5776 // If the is_dir has returned a false information, we arrive here
5777 dol_syslog("functions.lib::dol_mkdir: Fails to create directory '" . $ccdir . "' (no permission to write into parent or directory already exists).", LOG_WARNING);
5778 $nberr++;
5779 } else {
5780 dol_syslog("functions.lib::dol_mkdir: Directory '" . $ccdir . "' created", LOG_DEBUG);
5781 $nberr = 0; // At this point in the code, the previous failures can be ignored -> set $nberr to 0
5782 $nbcreated++;
5783 }
5784 } else {
5785 $nberr = 0; // At this point in the code, the previous failures can be ignored -> set $nberr to 0
5786 }
5787 }
5788 }
5789 return ($nberr ? -$nberr : $nbcreated);
5790}
5791
5792
5800function dolChmod($filepath, $newmask = '')
5801{
5802 if (!empty($newmask)) {
5803 @chmod($filepath, octdec($newmask));
5804 } elseif (getDolGlobalString('MAIN_UMASK')) {
5805 @chmod($filepath, octdec(getDolGlobalString('MAIN_UMASK')));
5806 }
5807}
5808
5809
5815function picto_required()
5816{
5817 return '<span class="fieldrequired">*</span>';
5818}
5819
5820
5837function dol_string_nohtmltag($stringtoclean, $removelinefeed = 1, $pagecodeto = 'UTF-8', $strip_tags = 0, $removedoublespaces = 1)
5838{
5839 if (is_null($stringtoclean)) {
5840 return '';
5841 }
5842
5843 if ($removelinefeed == 2) {
5844 $stringtoclean = preg_replace('/<br[^>]*>(\n|\r)+/ims', '<br>', $stringtoclean);
5845 }
5846 $temp = preg_replace('/<br[^>]*>/i', "\n", $stringtoclean);
5847
5848 // We remove entities BEFORE stripping (in case of an open separator char that is entity encoded and not the closing other, the strip will fails)
5849 $temp = dol_html_entity_decode($temp, ENT_COMPAT | ENT_HTML5, $pagecodeto);
5850
5851 $temp = str_replace('< ', '__ltspace__', $temp);
5852 $temp = str_replace('<:', '__lttwopoints__', $temp);
5853
5854 if ($strip_tags) {
5855 $temp = strip_tags($temp);
5856 } else {
5857 // Remove '<' into remaining, so remove non closing html tags like '<abc' or '<<abc'. Note: '<123abc' is not a html tag (can be kept), but '<abc123' is (must be removed).
5858 $pattern = "/<[^<>]+>/";
5859 // Example of $temp: <a href="/myurl" title="<u>A title</u>">0000-021</a>
5860 // pass 1 - $temp after pass 1: <a href="/myurl" title="A title">0000-021
5861 // pass 2 - $temp after pass 2: 0000-021
5862 $tempbis = $temp;
5863 do {
5864 $temp = $tempbis;
5865 $tempbis = str_replace('<>', '', $temp); // No reason to have this into a text, except if value is to try bypass the next html cleaning
5866 $tempbis = preg_replace($pattern, '', $tempbis);
5867 //$idowhile++; print $temp.'-'.$tempbis."\n"; if ($idowhile > 100) break;
5868 } while ($tempbis != $temp);
5869
5870 $temp = $tempbis;
5871
5872 // Remove '<' into remaining, so remove non closing html tags like '<abc' or '<<abc'. Note: '<123abc' is not a html tag (can be kept), but '<abc123' is (must be removed).
5873 $temp = preg_replace('/<+([a-z]+)/i', '\1', $temp);
5874 }
5875
5876 $temp = dol_html_entity_decode($temp, ENT_COMPAT, $pagecodeto);
5877
5878 // Remove also carriage returns
5879 if ($removelinefeed == 1) {
5880 $temp = str_replace(array("\r\n", "\r", "\n"), " ", $temp);
5881 }
5882
5883 // And double spaces
5884 if ($removedoublespaces) {
5885 while (strpos($temp, " ") !== false) {
5886 $temp = str_replace(" ", " ", $temp);
5887 }
5888 }
5889
5890 $temp = str_replace('__ltspace__', '< ', $temp);
5891 $temp = str_replace('__lttwopoints__', '<:', $temp);
5892
5893 return trim($temp);
5894}
5895
5915function dol_string_onlythesehtmltags($stringtoclean, $cleanalsosomestyles = 1, $removeclassattribute = 1, $cleanalsojavascript = 0, $allowiframe = 0, $allowed_tags = array(), $allowlink = 0, $allowscript = 0, $allowstyle = 0, $allowphp = 0)
5916{
5917 $sav_allowed_tags = $allowed_tags;
5918
5919 if (empty($allowed_tags) || (is_string($allowed_tags) && preg_match('/^common/', $allowed_tags))) {
5920 $allowed_tags = array(
5921 // HTML 4
5922 "html",
5923 "head",
5924 "body",
5925 "article",
5926 "a",
5927 "abbr",
5928 "b",
5929 "blockquote",
5930 "br",
5931 "cite",
5932 "div",
5933 "dl",
5934 "dd",
5935 "dt",
5936 "em",
5937 "font",
5938 "img",
5939 "ins",
5940 "hr",
5941 "i",
5942 "li",
5943 "ol",
5944 "p",
5945 "q",
5946 "s",
5947 "span",
5948 "strike",
5949 "strong",
5950 "title",
5951 "table",
5952 "tr",
5953 "th",
5954 "td",
5955 "u",
5956 "ul",
5957 "sup",
5958 "sub",
5959 "blockquote",
5960 "pre",
5961 "h1",
5962 "h2",
5963 "h3",
5964 "h4",
5965 "h5",
5966 "h6",
5967
5968 // HTML 5
5969 "footer",
5970 "header",
5971 "menu",
5972 "menuitem",
5973 "nav",
5974 "section"
5975 );
5976 }
5977 $allowed_tags[] = "comment"; // this tags is added to manage comment <!--...--> that are replaced into <comment>...</comment>
5978 if ($allowiframe) {
5979 if (!in_array('iframe', $allowed_tags)) {
5980 $allowed_tags[] = "iframe";
5981 }
5982 }
5983 if ($allowlink) {
5984 if (!in_array('link', $allowed_tags)) {
5985 $allowed_tags[] = "link";
5986 }
5987 if (!in_array('meta', $allowed_tags)) {
5988 $allowed_tags[] = "meta";
5989 }
5990 }
5991 if ($allowscript) {
5992 if (!in_array('script', $allowed_tags)) {
5993 $allowed_tags[] = "script";
5994 }
5995 }
5996 if ($allowstyle) {
5997 if (!in_array('style', $allowed_tags)) {
5998 $allowed_tags[] = "style";
5999 }
6000 }
6001 if (is_string($sav_allowed_tags)) {
6002 $tmptags = explode(',', $sav_allowed_tags);
6003 foreach ($tmptags as $tag) {
6004 if ($tag != 'common') {
6005 $allowed_tags[] = $tag;
6006 }
6007 }
6008 }
6009
6010
6011 $allowed_tags_string = implode("><", $allowed_tags);
6012 $allowed_tags_string = '<' . $allowed_tags_string . '>';
6013
6014 $stringtoclean = str_replace('<!DOCTYPE html>', '__!DOCTYPE_HTML__', $stringtoclean); // Replace DOCTYPE to avoid to have it removed by the strip_tags
6015
6016 $stringtoclean = dol_string_nounprintableascii($stringtoclean, 0);
6017
6018 //$stringtoclean = preg_replace('/<!--[^>]*-->/', '', $stringtoclean);
6019 $stringtoclean = preg_replace('/<!--([^>]*)-->/', '<comment>\1</comment>', $stringtoclean);
6020
6021 if ($allowphp) {
6022 $allowed_tags[] = "commentphp";
6023 $stringtoclean = preg_replace('/^<\?php([^"]+)\?>$/i', '<commentphp>\1__</commentphp>', $stringtoclean); // Note: <?php ... > is allowed only if on the same line
6024 $stringtoclean = preg_replace('/"<\?php([^"]+)\?>"/i', '"<commentphp>\1</commentphp>"', $stringtoclean); // Note: "<?php ... >" is allowed only if on the same line
6025 }
6026
6027 $stringtoclean = preg_replace('/&colon;/i', ':', $stringtoclean);
6028 $stringtoclean = preg_replace('/&#58;|&#0+58|&#x3A/i', '', $stringtoclean); // refused string ':' encoded (no reason to have a : encoded like this) to disable 'javascript:...'
6029
6030 // Remove all HTML tags
6031 $temp = strip_tags($stringtoclean, $allowed_tags_string); // Warning: This remove also undesired </>, so may changes string obfuscated with </> that pass the injection detection into a harmfull string
6032
6033 if ($cleanalsosomestyles) { // Clean for remaining html tags
6034 //$temp = preg_replace('/position\s*:\s*(absolute|fixed)\s*!\s*important/i', '', $temp); // Note: If hacker try to introduce css comment into string to bypass this regex, the string must also be encoded by the dol_htmlentitiesbr during output so it become harmless
6035 $temp = preg_replace('/position\s*:\s*(absolute|fixed)/i', '', $temp); // Note: If hacker try to introduce css comment into string to bypass this regex, the string must also be encoded by the dol_htmlentitiesbr during output so it become harmless
6036 $temp = preg_replace('/z-index\s*:/i', '', $temp); // Note: If hacker try to introduce css comment into string to bypass this regex, the string must also be encoded by the dol_htmlentitiesbr during output so it become harmless
6037 }
6038 if ($removeclassattribute) { // Clean for remaining html tags
6039 $temp = preg_replace('/(<[^>]+)\s+class=((["\']).*?\\3|\\w*)/i', '\\1', $temp);
6040 }
6041
6042 // Remove 'javascript:' that we should not find into a text
6043 // Warning: This is not reliable to fight against obfuscated javascript, there is a lot of other solution to include js into a common html tag (only filtered by a GETPOST(.., powerfullfilter)).
6044 if ($cleanalsojavascript) {
6045 $temp = preg_replace('/j\s*a\s*v\s*a\s*s\s*c\s*r\s*i\s*p\s*t\s*:/i', '', $temp);
6046 }
6047
6048 $temp = str_replace('__!DOCTYPE_HTML__', '<!DOCTYPE html>', $temp); // Restore the DOCTYPE
6049
6050 if ($allowphp) {
6051 $temp = preg_replace('/<commentphp>(.*)<\/commentphp>/', '<?php\1?>', $temp); // Restore php code
6052 }
6053
6054 $temp = preg_replace('/<comment>([^>]*)<\/comment>/', '<!--\1-->', $temp); // Restore html comments
6055
6056
6057 return $temp;
6058}
6059
6060
6074function dol_string_onlythesehtmlattributes($stringtoclean, $allowed_attributes = null, $ishtml = null)
6075{
6076 if (is_null($allowed_attributes)) {
6077 $allowed_attributes = array(
6078 // HTML 4
6079 "allow",
6080 "allowfullscreen",
6081 "alt",
6082 "async",
6083 "class",
6084 "contenteditable",
6085 "crossorigin",
6086 "data-html",
6087 "frameborder",
6088 "height",
6089 "href",
6090 "id",
6091 "name",
6092 "property",
6093 "rel",
6094 "src",
6095 "style",
6096 "target",
6097 "title",
6098 "type",
6099 "width",
6100
6101 // HTML5
6102 "footer",
6103 "header",
6104 "menu",
6105 "menuitem",
6106 "nav",
6107 "section"
6108 );
6109 }
6110 // Always add content and http-equiv for meta tags, required to force encoding and keep html content in utf8 by load/saveHTML functions.
6111 if (!in_array("content", $allowed_attributes)) {
6112 $allowed_attributes[] = "content";
6113 }
6114 if (!in_array("http-equiv", $allowed_attributes)) {
6115 $allowed_attributes[] = "http-equiv";
6116 }
6117
6118 if (class_exists('DOMDocument') && !empty($stringtoclean)) {
6119 if ($ishtml === 0) {
6120 $stringtoclean = str_replace('&', '__AMPINTEXT__', $stringtoclean); // Restore & char not entity
6121 }
6122
6123 // Warning: loadHTML does not support HTML5 on old libxml versions.
6124 $dom = new DOMDocument('', 'UTF-8');
6125 // If $stringtoclean is wrong, it will generates warnings. So we disable warnings and restore them later.
6126 $savwarning = error_reporting();
6127 error_reporting(E_ALL & ~E_WARNING & ~E_NOTICE);
6128 $wrapperId = "dol_string_onlythesehtmlattributes___wrapper";
6129 $dom->loadHTML('<?xml encoding="UTF-8"><div id="' . $wrapperId . '">' . $stringtoclean . '</div>', LIBXML_HTML_NODEFDTD | LIBXML_ERR_NONE | LIBXML_HTML_NOIMPLIED | LIBXML_NONET | LIBXML_NOWARNING | LIBXML_NOERROR | LIBXML_NOXMLDECL);
6130 error_reporting($savwarning);
6131
6132 if ($dom instanceof DOMDocument) {
6133 for ($els = $dom->getElementsByTagname('*'), $i = $els->length - 1; $i >= 0; $i--) {
6134 $el = $els->item($i);
6135 if (!$el instanceof DOMElement) {
6136 continue;
6137 }
6138 $attrs = $el->attributes;
6139 for ($ii = $attrs->length - 1; $ii >= 0; $ii--) {
6140 //var_dump($attrs->item($ii));
6141 if (!empty($attrs->item($ii)->name)) {
6142 if (! in_array($attrs->item($ii)->name, $allowed_attributes)) {
6143 // Delete attribute if not into allowed_attributes @phan-suppress-next-line PhanUndeclaredMethod
6144 $els->item($i)->removeAttribute($attrs->item($ii)->name);
6145 } elseif (in_array($attrs->item($ii)->name, array('style'))) {
6146 // If attribute is 'style'
6147 $valuetoclean = $attrs->item($ii)->value;
6148
6149 if (isset($valuetoclean)) {
6150 do {
6151 $oldvaluetoclean = $valuetoclean;
6152 $valuetoclean = preg_replace('/\/\*.*\*\//m', '', $valuetoclean); // clean css comments
6153 $valuetoclean = preg_replace('/position\s*:\s*[a-z]+/mi', '', $valuetoclean);
6154 if ($els->item($i)->tagName == 'a') { // more paranoiac cleaning for clickable tags.
6155 $valuetoclean = preg_replace('/display\s*:/mi', '', $valuetoclean);
6156 $valuetoclean = preg_replace('/z-index\s*:/mi', '', $valuetoclean);
6157 $valuetoclean = preg_replace('/\s+(top|left|right|bottom)\s*:/mi', '', $valuetoclean);
6158 }
6159
6160 // We do not allow logout|passwordforgotten.php and action= into the content of a "style" tag
6161 $valuetoclean = preg_replace('/(logout|passwordforgotten)\.php/mi', '', $valuetoclean);
6162 $valuetoclean = preg_replace('/action=/mi', '', $valuetoclean);
6163 } while ($oldvaluetoclean != $valuetoclean);
6164 }
6165
6166 $attrs->item($ii)->value = $valuetoclean;
6167 }
6168 }
6169 }
6170 }
6171 }
6172
6173 $dom->encoding = 'UTF-8';
6174 $wrapper = $dom->getElementById($wrapperId);
6175 $return = '';
6176 foreach ($wrapper->childNodes as $child) {
6177 $return .= $dom->saveHTML($child);
6178 }
6179
6180 if ($ishtml === 0) {
6181 $return = str_replace('__AMPINTEXT__', '&', $return); // Restore & char not entity
6182 }
6183
6184 return trim($return);
6185 } else {
6186 return $stringtoclean;
6187 }
6188}
6189
6201function dol_string_neverthesehtmltags($stringtoclean, $disallowed_tags = array('textarea'), $cleanalsosomestyles = 0)
6202{
6203 $temp = $stringtoclean;
6204 foreach ($disallowed_tags as $tagtoremove) {
6205 $temp = preg_replace('/<\/?' . $tagtoremove . '>/', '', $temp);
6206 $temp = preg_replace('/<\/?' . $tagtoremove . '\s+[^>]*>/', '', $temp);
6207 }
6208
6209 if ($cleanalsosomestyles) {
6210 $temp = preg_replace('/position\s*:\s*(absolute|fixed)\s*!\s*important/', '', $temp); // Note: If hacker try to introduce css comment into string to avoid this, string should be encoded by the dol_htmlentitiesbr so be harmless
6211 }
6212
6213 return $temp;
6214}
6215
6216
6226function dolCloseUnclosedHtmlTags($text)
6227{
6228 if (!is_string($text) || $text === '') {
6229 return $text;
6230 }
6231
6232 // Tags that never carry a closing tag
6233 $selfclosing = array('br', 'hr', 'img', 'input', 'meta', 'link', 'source', 'col', 'area', 'base', 'embed', 'param', 'track', 'wbr');
6234
6235 $opened = array();
6236 if (preg_match_all('/<\s*(\/?)([a-zA-Z][a-zA-Z0-9]*)[^>]*?(\/?)\s*>/', $text, $matches, PREG_SET_ORDER)) {
6237 foreach ($matches as $match) {
6238 $tag = strtolower($match[2]);
6239 if (in_array($tag, $selfclosing) || !empty($match[3])) {
6240 continue;
6241 }
6242 if (empty($match[1])) {
6243 $opened[] = $tag;
6244 } else {
6245 // Close the most recent matching opened tag, ignore a stray closing tag
6246 $idx = array_search($tag, array_reverse($opened, true), true);
6247 if ($idx !== false) {
6248 unset($opened[$idx]);
6249 }
6250 }
6251 }
6252 }
6253
6254 foreach (array_reverse($opened) as $tag) {
6255 $text .= '</'.$tag.'>';
6256 }
6257
6258 return $text;
6259}
6260
6270function dolGetFirstLineOfText($text, $nboflines = 1, $charset = 'UTF-8')
6271{
6272 if ($nboflines == 1) {
6273 if (dol_textishtml($text)) {
6274 $firstline = preg_replace('/<br[^>]*>.*$/s', '', $text); // The s pattern modifier means the . can match newline characters
6275 $firstline = preg_replace('/<div[^>]*>.*$/s', '', $firstline); // The s pattern modifier means the . can match newline characters
6276 } else {
6277 if (isset($text)) {
6278 $firstline = preg_replace('/[\n\r].*/', '', $text);
6279 } else {
6280 $firstline = '';
6281 }
6282 }
6283 return $firstline . (isset($firstline) && isset($text) && (strlen($firstline) != strlen($text)) ? '...' : '');
6284 } else {
6285 $ishtml = 0;
6286 if (dol_textishtml($text)) {
6287 $text = preg_replace('/\n/', '', $text);
6288 $ishtml = 1;
6289 $repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
6290 } else {
6291 $repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
6292 }
6293
6294 $text = strtr($text, $repTable);
6295 if ($charset == 'UTF-8') {
6296 $pattern = '/(<br[^>]*>)/Uu';
6297 } else {
6298 // /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
6299 $pattern = '/(<br[^>]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
6300 }
6301 $a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
6302
6303 $firstline = '';
6304 $i = 0;
6305 $countline = 0;
6306 $lastaddediscontent = 1;
6307 while ($countline < $nboflines && isset($a[$i])) {
6308 if (preg_match('/<br[^>]*>/', $a[$i])) {
6309 if (array_key_exists($i + 1, $a) && !empty($a[$i + 1])) {
6310 $firstline .= ($ishtml ? "<br>\n" : "\n");
6311 // Is it a br for a new line of after a printed line ?
6312 if (!$lastaddediscontent) {
6313 $countline++;
6314 }
6315 $lastaddediscontent = 0;
6316 }
6317 } else {
6318 $firstline .= $a[$i];
6319 $lastaddediscontent = 1;
6320 $countline++;
6321 }
6322 $i++;
6323 }
6324
6325 $adddots = (isset($a[$i]) && (!preg_match('/<br[^>]*>/', $a[$i]) || (array_key_exists($i + 1, $a) && !empty($a[$i + 1]))));
6326 //unset($a);
6327 $ret = $firstline . ($adddots ? '...' : '');
6328 //exit;
6329 return $ret;
6330 }
6331}
6332
6333
6345function dol_nl2br($stringtoencode, $nl2brmode = 0, $forxml = false)
6346{
6347 if (is_null($stringtoencode)) {
6348 return '';
6349 }
6350
6351 if (!$nl2brmode) {
6352 return nl2br($stringtoencode, $forxml);
6353 } else {
6354 $ret = preg_replace('/(\r\n|\r|\n)/i', ($forxml ? '<br />' : '<br>'), $stringtoencode);
6355 return $ret;
6356 }
6357}
6358
6369function dol_htmlwithnojs($stringtoencode, $nouseofiframesandbox = 0, $check = 'restricthtml')
6370{
6371 if (empty($nouseofiframesandbox) && getDolGlobalString('MAIN_SECURITY_USE_SANDBOX_FOR_HTMLWITHNOJS')) {
6372 // TODO using sandbox on inline html content is not possible yet with current browsers
6373 //$s = '<iframe class="iframewithsandbox" sandbox><html><body>';
6374 //$s .= $stringtoencode;
6375 //$s .= '</body></html></iframe>';
6376 return $stringtoencode;
6377 } else {
6378 $out = $stringtoencode;
6379
6380 $antiinfinitloop = 0;
6381
6382 // First clean HTML content
6383 do {
6384 if ($antiinfinitloop >= 20) {
6385 dol_print_error(null, "Infinite loop detected after ".$antiinfinitloop." iterations in dol_htmlwithnojs");
6386 die; // We must not break and we must not return a string for security issue. This should never happen.
6387 }
6388 $antiinfinitloop++;
6389
6390 $oldstringtoclean = $out;
6391
6392 $outishtml = 0;
6393 if (dol_textishtml($out)) {
6394 $outishtml = 1;
6395 }
6396
6397 // HTML sanitizer by DOMDocument
6398 if (!empty($out) && getDolGlobalInt('MAIN_RESTRICTHTML_ONLY_VALID_HTML') && $check != 'restricthtmlallowunvalid') {
6399 try {
6400 libxml_use_internal_errors(false); // Avoid to fill memory with xml errors
6401 if (LIBXML_VERSION < 20900) {
6402 // Avoid load of external entities (security problem).
6403 // Required only if LIBXML_VERSION < 20900
6404 // @phan-suppress-next-line PhanDeprecatedFunctionInternal
6405 libxml_disable_entity_loader(true);
6406 }
6407
6408 if (!$outishtml) {
6409 $out = preg_replace('/&(?![a-zA-Z0-9#]+;)/', '__AMPINTEXT__', $out);
6410 }
6411
6412 $dom = new DOMDocument();
6413
6414 // Note: <a href="https://__[aaa]__/aaa.html"> is transformed into <a href="https://__[aaa]__/aaa.html">
6415 // We don't want that, so we protect __[xxx]__ by replacing [ and ] before loadHTML and restore them after saveHTML
6416 $out = preg_replace_callback(
6417 '/__\[([0-9a-zA-Z_]+)\]__/',
6422 function ($m) {
6423 return '__BRACKETSTART' . $m[1] . 'BRACKETEND__';
6424 },
6425 $out
6426 );
6427 $wrapperId = "dol_htmlwithnojs___wrapper___toremove";
6428 $dom->loadHTML('<?xml encoding="UTF-8"><div id="' . $wrapperId . '">' . $out . '</div>', LIBXML_HTML_NODEFDTD | LIBXML_ERR_NONE | LIBXML_HTML_NOIMPLIED | LIBXML_NONET | LIBXML_NOWARNING | LIBXML_NOERROR | LIBXML_NOXMLDECL);
6429
6430 $dom->encoding = 'UTF-8';
6431
6432 // Add a layer to remove some styles
6433 if (getDolGlobalInt('MAIN_RESTRICTHTML_ONLY_VALID_HTML') == 2) {
6434 foreach ($dom->getElementsByTagName('*') as $el) {
6435 if (!$el instanceof DOMElement) {
6436 continue;
6437 }
6438
6439 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
6440 if ($el->hasAttribute('style')) {
6441 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
6442 $style = $el->getAttribute('style');
6443
6444 // delete some styles
6445 $style = preg_replace('/z-index\s*:/i', '', $style);
6446 $style = preg_replace('/position\s*:/i', '', $style);
6447 $style = preg_replace('/top\s*:/i', '', $style);
6448 $style = preg_replace('/left\s*:/i', '', $style);
6449 $style = preg_replace('/background\s*:/i', '', $style);
6450 /*
6451 $style = preg_replace('/width\s*:/i', '', $style);
6452 $style = preg_replace('/height\s*:/i', '', $style);
6453 $style = preg_replace('/backdrop-filter\s*:/i', '', $style);
6454 */
6455 if (trim($style) === '') {
6456 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
6457 $el->removeAttribute('style');
6458 } else {
6459 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
6460 $el->setAttribute('style', $style);
6461 }
6462 }
6463 }
6464 }
6465
6466 $wrapper = $dom->getElementById($wrapperId);
6467 $result = '';
6468 foreach ($wrapper->childNodes as $child) {
6469 $result .= $dom->saveHTML($child);
6470 }
6471
6472 $out = trim($result);
6473
6474 // Restore [ and ] that were protected before loadHTML
6475 $out = preg_replace_callback(
6476 '/__BRACKETSTART([0-9a-zA-Z_]+)BRACKETEND__/',
6481 function ($m) {
6482 return '__[' . $m[1] . ']__';
6483 },
6484 $out
6485 );
6486
6487 if (!$outishtml) { // If $out was not HTML content we made before a dol_nl2br so we must do the opposite operation now
6488 $out = str_replace('__AMPINTEXT__', '&', $out); // Restore & char not entity
6489 $out = preg_replace('/<br\s*\/?>/i', "\n", $out);
6490 }
6491 } catch (Exception $e) {
6492 // If error, invalid HTML string with no way to clean it
6493 //print $e->getMessage();
6494 $out = 'InvalidHTMLStringCantBeCleaned ' . $e->getMessage();
6495 }
6496 }
6497
6498 // HTML sanitizer by Tidy
6499 // Tidy can't be used for restricthtmlallowunvalid and restricthtmlallowlinkscript
6500 // Tidy can't be used for non html text content as it is corrupting the new lines fields.
6501 if (!empty($out) && getDolGlobalInt('MAIN_RESTRICTHTML_ONLY_VALID_HTML_TIDY') && !in_array($check, array('restricthtmlallowunvalid', 'restricthtmlallowlinkscript')) && $outishtml) {
6502 // TODO Try to implement a hack for restricthtmlallowlinkscript by renaming tag <link> and <script> ?
6503 try {
6504 //var_dump($out);
6505
6506 // Try cleaning using tidy
6507 if (extension_loaded('tidy') && class_exists("tidy")) {
6508 //print "aaa".$out."\n";
6509
6510 // See options at https://tidy.sourceforge.net/docs/quickref.html
6511 $config = array(
6512 'clean' => false,
6513 // Best will be to set 'quote-marks' to false to not replace " that are used for real text content (not a string symbol for html attribute) into &quot;
6514 'quote-marks' => false,
6515 'doctype' => 'strict',
6516 'show-body-only' => true,
6517 "indent-attributes" => false,
6518 "vertical-space" => false,
6519 //'ident' => false, // Not always supported
6520 "wrap" => 0,
6521 'preserve-entities' => true
6522 // HTML5 tags
6523 //'new-blocklevel-tags' => 'article aside audio bdi canvas details dialog figcaption figure footer header hgroup main menu menuitem nav section source summary template track video',
6524 //'new-blocklevel-tags' => 'footer header section menu menuitem'
6525 //'new-empty-tags' => 'command embed keygen source track wbr',
6526 //'new-inline-tags' => 'audio command datalist embed keygen mark menuitem meter output progress source time video wbr',
6527 );
6528
6529 // Tidy
6530 $locale = setlocale(LC_NUMERIC, '0'); // Tidy has a bug and is changing the PHP locale. So we save it to restore it after.
6531
6532 $tidy = new tidy();
6533 $out = $tidy->repairString($out, $config, 'utf8');
6534
6535 setlocale(LC_NUMERIC, $locale); // Restore original local
6536
6537 //print "xxx".$out;exit;
6538 }
6539
6540 //var_dump($out);
6541 } catch (Exception $e) {
6542 // If error, invalid HTML string with no way to clean it
6543 //print $e->getMessage();
6544 $out = 'InvalidHTMLStringCantBeCleaned ' . $e->getMessage();
6545 }
6546 }
6547
6548 // Clear ZERO WIDTH NO-BREAK SPACE, ZERO WIDTH SPACE, ZERO WIDTH JOINER
6549 // TODO $out = preg_replace('/[\x{2000}-\x{200D}\x{FEFF}]/u', ' ', $out);
6550 $out = preg_replace('/[\x{200B}-\x{200D}\x{FEFF}]/u', ' ', $out);
6551
6552 // Clean some html entities that are useless so text is cleaner
6553 $out = preg_replace('/&(tab|newline);/i', ' ', $out);
6554
6555 // Ckeditor uses the numeric entity for apostrophe, so we force it to
6556 // the text entity (all other special chars are encoded using text entities) so we can then exclude all numeric entities.
6557 $out = preg_replace('/&#39;/i', '&apos;', $out);
6558
6559 // We replace chars from a/A to z/Z encoded with numeric HTML entities with the real char so we won't loose the chars at the next step (preg_replace).
6560 // No need to use a loop here, this step is not to sanitize (this is done at next step, this is to try to save chars, even if they are
6561 // using a non conventionnal way to be encoded, to not have them sanitized just after)
6562 if (function_exists('realCharForNumericEntities')) { // May not exist when main.inc.php not loaded, for example in a CLI context
6563 $out = preg_replace_callback(
6564 '/&#(x?[0-9][0-9a-f]+;?)/i',
6569 static function ($m) {
6570 return realCharForNumericEntities($m);
6571 },
6572 $out
6573 );
6574 }
6575
6576 // Now we remove all remaining HTML entities starting with a number. We don't want such entities.
6577 $out = preg_replace('/&#x?[0-9]+/i', '', $out); // For example if we have j&#x61vascript with an entities without the ; to hide the 'a' of 'javascript'.
6578
6579 // Keep only some html tags and remove also some 'javascript:' strings
6580 if ($check == 'restricthtmlallowlinkscript') {
6581 $out = dol_string_onlythesehtmltags($out, 0, 1, 0, 0, array(), 1, 1, 1, getDolGlobalInt("UNSECURED_restricthtmlallowlinkscript_ALLOW_PHP"));
6582 } elseif ($check == 'restricthtmlallowclass' || $check == 'restricthtmlallowunvalid') {
6583 $out = dol_string_onlythesehtmltags($out, 0, 0, 1);
6584 } elseif ($check == 'restricthtmlallowiframe') {
6585 $out = dol_string_onlythesehtmltags($out, 0, 0, 1, 1);
6586 } else {
6587 $out = dol_string_onlythesehtmltags($out, 0, 1, 1); // styles are allowed to allow rich text editor features of ckeditor managed by the "style=" attribute
6588 }
6589
6590 // Keep only some html attributes and exclude non expected HTML attributes and clean content of some attributes (keep only alt=, title=...).
6591 if (getDolGlobalString('MAIN_RESTRICTHTML_REMOVE_ALSO_BAD_ATTRIBUTES')) {
6592 $out = dol_string_onlythesehtmlattributes($out, null, $outishtml);
6593 }
6594
6595 // Restore entity &apos; into &#39; (restricthtml is for html content so we can use html entity) because it is
6596 // compatible with HTML 4 used y CKEditor, and HTML 5 (when &apos; works only with HTML5).
6597 $out = preg_replace('/&apos;/i', "&#39;", $out);
6598
6599 // Now remove js
6600 // List of dom events is on https://www.w3schools.com/jsref/dom_obj_event.asp and https://developer.mozilla.org/en-US/docs/Web/Events
6601 $out = preg_replace('/on(mouse|drag|key|load|touch|pointer|select|transition)[a-z]*\s*=/i', '', $out); // onmousexxx can be set on img or any html tag like <img title='...' onmouseover=alert(1)>
6602 $out = preg_replace('/on(abort|after|animation|auxclick|before|blur|cancel|canplay|canplaythrough|change|click|close|command|contentvisibility|context|cuechange|copy|cut)[a-z]*\s*=/i', '', $out);
6603 $out = preg_replace('/on(dblclick|drop|durationchange|emptied|end|ended|error|focus(in|out)?|formdata|gotpointercapture|hashchange|input|invalid)[a-z]*\s*=/i', '', $out);
6604 $out = preg_replace('/on(lost|offline|online|message|pagehide|pageshow)[a-z]*\s*=/i', '', $out);
6605 $out = preg_replace('/on(paste|pause|play|playing|progress|ratechange|rejectionn|reset|resize|scroll|search|security|seeked|seeking|show|stalled|start|submit|suspend)[a-z]*\s*=/i', '', $out);
6606 $out = preg_replace('/on(timeupdate|toggle|unhandled|unload|volumechange|waiting|wheel)[a-z]*\s*=/i', '', $out);
6607 // More not into the previous list
6608 $out = preg_replace('/on(repeat|begin|finish|beforeinput)[a-z]*\s*=/i', '', $out);
6609 // Add also a generic removal of any onxxx= attribute
6610 $out = preg_replace('/\son[a-z]+\s*=/i', ' ', $out);
6611 } while ($oldstringtoclean != $out);
6612
6613 // Check the limit of external links that are automatically executed in a Rich text content. We count:
6614 // '<img' to avoid <img src="http...">, we can only accept "<img src="data:..."
6615 // 'url(' to avoid inline style like background: url(http...
6616 // '<link' to avoid <link href="http...">
6617 $reg = array();
6618 $tmpout = preg_replace('/<img src="data:/mi', '<__IMG_SRC_DATA__ src="data:', $out);
6619 preg_match_all('/(<img|url\‍(|<link)/i', $tmpout, $reg);
6620 $nblinks = count($reg[0]);
6621 if ($nblinks > getDolGlobalInt("MAIN_SECURITY_MAX_IMG_IN_HTML_CONTENT", 1000)) {
6622 $out = 'ErrorTooManyLinksIntoHTMLString';
6623 }
6624
6625 if (getDolGlobalInt('MAIN_DISALLOW_URL_INTO_DESCRIPTIONS') == 2 || $check == 'restricthtmlnolink') {
6626 if ($nblinks > 0) {
6627 $out = 'ErrorHTMLLinksNotAllowed';
6628 }
6629 } elseif (getDolGlobalInt('MAIN_DISALLOW_URL_INTO_DESCRIPTIONS') == 1) {
6630 // Refuse any links except it they are to the wrapper document.php or viewimage.php
6631 $nblinks = 0;
6632
6633 // Loop on each url in src= and url(
6634 $pattern = '/src=["\']?(http[^"\']+)|url\‍(["\']?(http[^\‍)]+)/';
6635
6637
6638 $matches = array();
6639 if (preg_match_all($pattern, $out, $matches)) {
6640 // URLs are into $matches[1] or $matches[2]
6641 $urls = array();
6642 foreach ($matches[1] as $tmpval) {
6643 if (!empty($tmpval)) {
6644 $urls[] = $tmpval;
6645 }
6646 }
6647 foreach ($matches[2] as $tmpval) {
6648 if (!empty($tmpval)) {
6649 $urls[] = $tmpval;
6650 }
6651 }
6652
6653 // Show URLs
6654 $firstexturl = '';
6655 $secondexturl = '';
6656 foreach ($urls as $url) {
6657 $urlok = 0;
6658 $parsedurl = parse_url($url);
6659 if (!empty($parsedurl)) {
6660 if (preg_match('/'.preg_quote($dolibarr_main_url_root, '/').'/', $url)
6661 //&& preg_match('/(document|viewimage)\.php$/', $parsedurl['path']) && preg_match('/modulepart=(media|mycompany)/', $parsedurl['query'])
6662 ) {
6663 $urlok = 1;
6664 }
6665 }
6666 if (!$urlok) {
6667 $nblinks++;
6668 if (empty($firstexturl)) {
6669 $firstexturl = $url;
6670 } elseif (empty($secondexturl)) {
6671 $secondexturl = $url;
6672 }
6673 //echo "Found url = ".$url . "\n";
6674 }
6675 }
6676 if ($nblinks > 0) {
6677 $out = 'ErrorHTMLExternalLinksNotAllowed (Example: '.$firstexturl.($secondexturl ? ' '.$secondexturl : '').')';
6678 }
6679 }
6680 }
6681
6682 return $out;
6683 }
6684}
6685
6706function dol_htmlentitiesbr($stringtoencode, $nl2brmode = 0, $pagecodefrom = 'UTF-8', $removelasteolbr = 1)
6707{
6708 if (is_null($stringtoencode)) {
6709 return '';
6710 }
6711
6712 $newstring = $stringtoencode;
6713 if (dol_textishtml($stringtoencode)) { // Check if text is already HTML or not
6714 $newstring = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>/i', '<br>', $newstring); // Replace "<br type="_moz" />" by "<br>". It's same and avoid pb with FPDF.
6715 if ($removelasteolbr) {
6716 $newstring = preg_replace('/<br>$/i', '', $newstring); // Remove last <br> (remove only last one)
6717 }
6718 $newstring = preg_replace('/[\x{200B}-\x{200D}\x{FEFF}]/u', ' ', $newstring);
6719 $newstring = strtr($newstring, array('&' => '__PROTECTand__', '<' => '__PROTECTlt__', '>' => '__PROTECTgt__', '"' => '__PROTECTdquot__'));
6720 $newstring = dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom); // Make entity encoding
6721 $newstring = strtr($newstring, array('__PROTECTand__' => '&', '__PROTECTlt__' => '<', '__PROTECTgt__' => '>', '__PROTECTdquot__' => '"'));
6722 } else {
6723 if ($removelasteolbr) {
6724 $newstring = preg_replace('/(\r\n|\r|\n)$/i', '', $newstring); // Remove last \n (may remove several)
6725 }
6726 $newstring = dol_nl2br(dol_htmlentities($newstring, ENT_COMPAT, $pagecodefrom), $nl2brmode);
6727 }
6728 // Other substitutions that htmlentities does not do
6729 //$newstring=str_replace(chr(128),'&euro;',$newstring); // 128 = 0x80. Not in html entity table. // Seems useles with TCPDF. Make bug with UTF8 languages
6730 return $newstring;
6731}
6732
6740function dol_htmlentitiesbr_decode($stringtodecode, $pagecodeto = 'UTF-8')
6741{
6742 $ret = dol_html_entity_decode($stringtodecode, ENT_COMPAT | ENT_HTML5, $pagecodeto);
6743 $ret = preg_replace('/' . "\r\n" . '<br(\s[\sa-zA-Z_="]*)?\/?>/i', "<br>", $ret);
6744 $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>' . "\r\n" . '/i', "\r\n", $ret);
6745 $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>' . "\n" . '/i', "\n", $ret);
6746 $ret = preg_replace('/<br(\s[\sa-zA-Z_="]*)?\/?>/i', "\n", $ret);
6747 return $ret;
6748}
6749
6756function dol_htmlcleanlastbr($stringtodecode)
6757{
6758 $ret = preg_replace('/&nbsp;$/i', "", $stringtodecode); // Because wysiwyg editor may add a &nbsp; at end of last line
6759 $ret = preg_replace('/(<br>|<br(\s[\sa-zA-Z_="]*)?\/?>|' . "\n" . '|' . "\r" . ')+$/i', "", $ret);
6760 return $ret;
6761}
6762
6772function dol_html_entity_decode($a, $b, $c = 'UTF-8', $keepsomeentities = 0)
6773{
6774 $newstring = $a;
6775 if ($keepsomeentities) {
6776 $newstring = strtr($newstring, array('&amp;' => '__andamp__', '&lt;' => '__andlt__', '&gt;' => '__andgt__', '"' => '__dquot__'));
6777 }
6778 $newstring = html_entity_decode((string) $newstring, (int) $b, (string) $c);
6779 if ($keepsomeentities) {
6780 $newstring = strtr($newstring, array('__andamp__' => '&amp;', '__andlt__' => '&lt;', '__andgt__' => '&gt;', '__dquot__' => '"'));
6781 }
6782 return $newstring;
6783}
6784
6796function dol_htmlentities($string, $flags = ENT_QUOTES | ENT_SUBSTITUTE, $encoding = 'UTF-8', $double_encode = false)
6797{
6798 return htmlentities($string, $flags, $encoding, $double_encode);
6799}
6800
6812function dol_string_is_good_iso($s, $clean = 0)
6813{
6814 $len = dol_strlen($s);
6815 $out = '';
6816 $ok = 1;
6817 for ($scursor = 0; $scursor < $len; $scursor++) {
6818 $ordchar = ord($s[$scursor]);
6819 //print $scursor.'-'.$ordchar.'<br>';
6820 if ($ordchar < 32 && $ordchar != 13 && $ordchar != 10) {
6821 $ok = 0;
6822 break;
6823 } elseif ($ordchar > 126 && $ordchar < 160) {
6824 $ok = 0;
6825 break;
6826 } elseif ($clean) {
6827 $out .= $s[$scursor];
6828 }
6829 }
6830 if ($clean) {
6831 return $out;
6832 }
6833 return $ok;
6834}
6835
6844function dol_nboflines($s, $maxchar = 0)
6845{
6846 if ($s == '') {
6847 return 0;
6848 }
6849 $arraystring = explode("\n", $s);
6850 $nb = count($arraystring);
6851
6852 return $nb;
6853}
6854
6855
6865function dol_nboflines_bis($text, $maxlinesize = 0, $charset = 'UTF-8')
6866{
6867 $repTable = array("\t" => " ", "\n" => "<br>", "\r" => " ", "\0" => " ", "\x0B" => " ");
6868 if (dol_textishtml($text)) {
6869 $repTable = array("\t" => " ", "\n" => " ", "\r" => " ", "\0" => " ", "\x0B" => " ");
6870 }
6871
6872 $text = strtr($text, $repTable);
6873 if ($charset == 'UTF-8') {
6874 $pattern = '/(<br[^>]*>)/Uu';
6875 } else {
6876 // /U is to have UNGREEDY regex to limit to one html tag. /u is for UTF8 support
6877 $pattern = '/(<br[^>]*>)/U'; // /U is to have UNGREEDY regex to limit to one html tag.
6878 }
6879 $a = preg_split($pattern, $text, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
6880
6881 $nblines = (int) floor((count($a) + 1) / 2);
6882 // count possible auto line breaks
6883 if ($maxlinesize) {
6884 foreach ($a as $line) {
6885 if (dol_strlen($line) > $maxlinesize) {
6886 //$line_dec = html_entity_decode(strip_tags($line));
6887 $line_dec = html_entity_decode($line);
6888 if (dol_strlen($line_dec) > $maxlinesize) {
6889 $line_dec = wordwrap($line_dec, $maxlinesize, '\n', true);
6890 $nblines += substr_count($line_dec, '\n');
6891 }
6892 }
6893 }
6894 }
6895
6896 unset($a);
6897 return $nblines;
6898}
6899
6908function dol_textishtml($msg, $option = 0)
6909{
6910 if (is_null($msg)) {
6911 return false;
6912 }
6913
6914 // Every pattern below needs a '<' (a tag) or a '&' (an entity): without both, the string can not be HTML. This saves the
6915 // dozen of preg_match() below for the very common case of a plain label.
6916 if (strpos($msg, '<') === false && strpos($msg, '&') === false) {
6917 return false;
6918 }
6919
6920 if ($option == 1) {
6921 if (preg_match('/<(html|link|script)/i', $msg)) {
6922 return true;
6923 } elseif (preg_match('/<body/i', $msg)) {
6924 return true;
6925 } elseif (preg_match('/<\/textarea/i', $msg)) {
6926 return true;
6927 } elseif (preg_match('/<(b|em|i|u)(\s+[^>]+)?>/i', $msg)) {
6928 return true;
6929 } elseif (preg_match('/<br/i', $msg)) {
6930 return true;
6931 }
6932 return false;
6933 } else {
6934 // Remove all urls because 'http://aa?param1=abc&amp;param2=def' must not be used inside detection
6935 $msg = preg_replace('/https?:\/\/[^"\'\s]+/i', '', $msg);
6936 if (preg_match('/<(html|link|script|body)/i', $msg)) {
6937 return true;
6938 } elseif (preg_match('/<\/textarea/i', $msg)) {
6939 return true;
6940 } elseif (preg_match('/<(b|em|i|u)(\s+[^>]+)?>/i', $msg)) {
6941 return true;
6942 } elseif (preg_match('/<(br|hr)\/>/i', $msg)) {
6943 return true;
6944 } elseif (preg_match('/<(br|hr|div|font|li|p|span|strong|table)>/i', $msg)) {
6945 return true;
6946 } elseif (preg_match('/<(br|hr|div|font|li|p|span|strong|table)\s+[^<>\/]*\/?>/i', $msg)) {
6947 return true;
6948 } elseif (preg_match('/<img\s+[^<>]*src[^<>]*>/i', $msg)) {
6949 return true; // must accept <img src="http://example.com/aaa.png" />
6950 } elseif (preg_match('/<a\s+[^<>]*href[^<>]*>/i', $msg)) {
6951 return true; // must accept <a href="http://example.com/aaa.png" />
6952 } elseif (preg_match('/<h[0-9]>/i', $msg)) {
6953 return true;
6954 } elseif (preg_match('/&[A-Z0-9]{1,6};/i', $msg)) {
6955 // TODO If content is 'A link https://aaa?param=abc&amp;param2=def', it return true but must be false
6956 return true; // Html entities names (http://www.w3schools.com/tags/ref_entities.asp)
6957 } elseif (preg_match('/&#[0-9]{2,3};/i', $msg)) {
6958 return true; // Html entities numbers (http://www.w3schools.com/tags/ref_entities.asp)
6959 } elseif (preg_match('/&#x[a-f0-9][a-f0-9];/i', $msg)) {
6960 return true; // Html entities numbers in hexa
6961 }
6962
6963 return false;
6964 }
6965}
6966
6981function dol_concatdesc($text1, $text2, $forxml = false, $invert = false)
6982{
6983 if (!empty($invert)) {
6984 $tmp = $text1;
6985 $text1 = $text2;
6986 $text2 = $tmp;
6987 }
6988
6989 $ret = '';
6990 $ret .= (!dol_textishtml($text1) && dol_textishtml($text2)) ? dol_nl2br(dol_escape_htmltag($text1, 0, 1, '', 1), 0, $forxml) : $text1;
6991 $ret .= (!empty($text1) && !empty($text2)) ? ((dol_textishtml($text1) || dol_textishtml($text2)) ? ($forxml ? "<br >\n" : "<br>\n") : "\n") : "";
6992 $ret .= (dol_textishtml($text1) && !dol_textishtml($text2)) ? dol_nl2br(dol_escape_htmltag($text2, 0, 1, '', 1), 0, $forxml) : $text2;
6993 return $ret;
6994}
6995
7004function dol_concat($text1, $text2)
7005{
7006 return $text1.$text2;
7007}
7008
7016function safeArrayMap($callback, array $array)
7017{
7018 if (!is_string($callback)) {
7019 throw new InvalidArgumentException("Les callbacks sont désactivés.");
7020 }
7021 // Check that $callback is a sure function
7022 $allowed_callbacks = ['strtolower', 'strtoupper', 'intval'];
7023 if (!in_array($callback, $allowed_callbacks, true)) {
7024 throw new InvalidArgumentException("Callback function not allowed.");
7025 }
7026 return array_map($callback, $array);
7027}
7028
7029
7043function getCommonSubstitutionArray($outputlangs, $onlykey = 0, $exclude = null, $object = null, $include = null)
7044{
7045 global $db, $conf, $mysoc, $user, $extrafields;
7046
7047 $substitutionarray = array();
7048
7049 if ((empty($exclude) || !in_array('user', $exclude)) && (empty($include) || in_array('user', $include)) && $user instanceof User) {
7050 // Add SIGNATURE into substitutionarray first, so, when we will make the substitution,
7051 // this will include signature content first and then replace var found into content of signature
7052 //var_dump($onlykey);
7053 $emailsendersignature = $user->signature; // By default, we use the signature of current user. We must complete substitution with signature in c_email_senderprofile of array after calling getCommonSubstitutionArray()
7054 $usersignature = $user->signature;
7055 if (getDolGlobalString('MAIN_MAIL_DEFAULT_SIGNATURE_FOR_ALL_USERS') && !empty($user->employee)) {
7056 $emailsendersignature = getDolGlobalString('MAIN_MAIL_DEFAULT_SIGNATURE_FOR_ALL_USERS');
7057 $usersignature = getDolGlobalString('MAIN_MAIL_DEFAULT_SIGNATURE_FOR_ALL_USERS');
7058 }
7059 $substitutionarray = array_merge($substitutionarray, array(
7060 '__SENDEREMAIL_SIGNATURE__' => (string) ((!getDolGlobalString('MAIN_MAIL_DO_NOT_USE_SIGN')) ? ($onlykey == 2 ? dol_trunc('SignatureFromTheSelectedSenderProfile', 30) : $emailsendersignature) : ''),
7061 '__USER_SIGNATURE__' => (string) (($usersignature && !getDolGlobalString('MAIN_MAIL_DO_NOT_USE_SIGN')) ? ($onlykey == 2 ? dol_trunc(dol_string_nohtmltag($usersignature), 30) : $usersignature) : '')
7062 ));
7063
7064 if (is_object($user) && ($user instanceof User)) {
7065 $substitutionarray = array_merge($substitutionarray, array(
7066 '__USER_ID__' => (string) $user->id,
7067 '__USER_LOGIN__' => (string) $user->login,
7068 '__USER_EMAIL__' => (string) $user->email,
7069 '__USER_PHONE__' => (string) dol_print_phone($user->office_phone, '', 0, 0, '', " ", '', '', -1),
7070 '__USER_PHONEPRO__' => (string) dol_print_phone($user->user_mobile, '', 0, 0, '', " ", '', '', -1),
7071 '__USER_PHONEMOBILE__' => (string) dol_print_phone($user->personal_mobile, '', 0, 0, '', " ", '', '', -1),
7072 '__USER_FAX__' => (string) $user->office_fax,
7073 '__USER_LASTNAME__' => (string) $user->lastname,
7074 '__USER_FIRSTNAME__' => (string) $user->firstname,
7075 '__USER_FULLNAME__' => (string) $user->getFullName($outputlangs),
7076 '__USER_SUPERVISOR_ID__' => (string) ($user->fk_user ? $user->fk_user : '0'),
7077 '__USER_JOB__' => (string) $user->job,
7078 '__USER_REMOTE_IP__' => (string) getUserRemoteIP(),
7079 '__USER_VCARD_URL__' => (string) $user->getOnlineVirtualCardUrl('', 'external')
7080 ));
7081 if (isModEnabled('stock') && getDolGlobalString('MAIN_DEFAULT_WAREHOUSE_USER') && is_object($user->warehouse)) {
7082 $substitutionarray = array_merge($substitutionarray, array(
7083 '__USER_WAREHOUSE_ID__' => isset($user->warehouse->id) ? $user->warehouse->id : '',
7084 '__USER_WAREHOUSE_REF__' => isset($user->warehouse->ref) ? $user->warehouse->ref : '',
7085 '__USER_WAREHOUSE_DESCRIPTION__' => isset($user->warehouse->description) ? $user->warehouse->description : '',
7086 '__USER_WAREHOUSE_ADDRESS__' => isset($user->warehouse->address) ? $user->warehouse->address : '',
7087 '__USER_WAREHOUSE_ZIP__' => isset($user->warehouse->zip) ? $user->warehouse->zip : '',
7088 '__USER_WAREHOUSE_TOWN__' => isset($user->warehouse->town) ? $user->warehouse->town : '',
7089 '__USER_WAREHOUSE_PHONE__' => isset($user->warehouse->phone) ? (string) dol_print_phone($user->warehouse->phone, '', 0, 0, '', " ", '', '', -1) : '',
7090 '__USER_WAREHOUSE_FAX__' => isset($user->warehouse->fax) ? (string) dol_print_phone($user->warehouse->fax, '', 0, 0, '', " ", '', '', -1) : ''
7091 ));
7092 }
7093 }
7094 }
7095 if ((empty($exclude) || !in_array('mycompany', $exclude)) && is_object($mysoc) && (empty($include) || in_array('mycompany', $include))) {
7096 $substitutionarray = array_merge($substitutionarray, array(
7097 '__MYCOMPANY_NAME__' => $mysoc->name,
7098 '__MYCOMPANY_EMAIL__' => $mysoc->email,
7099 '__MYCOMPANY_URL__' => $mysoc->url,
7100 '__MYCOMPANY_PHONE__' => dol_print_phone((string) $mysoc->phone, '', 0, 0, '', " ", '', '', -1),
7101 '__MYCOMPANY_PHONEMOBILE__' => dol_print_phone((string) $mysoc->phone_mobile, '', 0, 0, '', " ", '', '', -1),
7102 '__MYCOMPANY_FAX__' => dol_print_phone((string) $mysoc->fax, '', 0, 0, '', " ", '', '', -1),
7103 '__MYCOMPANY_PROFID1__' => $mysoc->idprof1,
7104 '__MYCOMPANY_PROFID2__' => $mysoc->idprof2,
7105 '__MYCOMPANY_PROFID3__' => $mysoc->idprof3,
7106 '__MYCOMPANY_PROFID4__' => $mysoc->idprof4,
7107 '__MYCOMPANY_PROFID5__' => $mysoc->idprof5,
7108 '__MYCOMPANY_PROFID6__' => $mysoc->idprof6,
7109 '__MYCOMPANY_PROFID7__' => $mysoc->idprof7,
7110 '__MYCOMPANY_PROFID8__' => $mysoc->idprof8,
7111 '__MYCOMPANY_PROFID9__' => $mysoc->idprof9,
7112 '__MYCOMPANY_PROFID10__' => $mysoc->idprof10,
7113 '__MYCOMPANY_OBJECT__' => $mysoc->socialobject,
7114 '__MYCOMPANY_CAPITAL__' => $mysoc->capital,
7115 '__MYCOMPANY_FULLADDRESS__' => (method_exists($mysoc, 'getFullAddress') ? $mysoc->getFullAddress(1, ', ') : ''), // $mysoc may be stdClass
7116 '__MYCOMPANY_ADDRESS__' => $mysoc->address,
7117 '__MYCOMPANY_VATNUMBER__' => $mysoc->tva_intra,
7118 '__MYCOMPANY_ZIP__' => $mysoc->zip,
7119 '__MYCOMPANY_TOWN__' => $mysoc->town,
7120 '__MYCOMPANY_STATE__' => $mysoc->state,
7121 '__MYCOMPANY_COUNTRY__' => $mysoc->country,
7122 '__MYCOMPANY_COUNTRY_ID__' => $mysoc->country_id,
7123 '__MYCOMPANY_COUNTRY_CODE__' => $mysoc->country_code,
7124 '__MYCOMPANY_CURRENCY__' => $outputlangs->transnoentitiesnoconv("Currency".$conf->currency),
7125 '__MYCOMPANY_CURRENCY_CODE__' => $conf->currency
7126 ));
7127 }
7128
7129 if (($onlykey || is_object($object)) && (empty($exclude) || !in_array('object', $exclude)) && (empty($include) || in_array('object', $include))) {
7130 if ($onlykey) {
7131 $substitutionarray['__ID__'] = '__ID__';
7132 $substitutionarray['__REF__'] = '__REF__';
7133 $substitutionarray['__NEWREF__'] = '__NEWREF__';
7134 $substitutionarray['__LABEL__'] = '__LABEL__';
7135 $substitutionarray['__REF_CLIENT__'] = '__REF_CLIENT__';
7136 $substitutionarray['__REF_SUPPLIER__'] = '__REF_SUPPLIER__';
7137 $substitutionarray['__NOTE_PUBLIC__'] = '__NOTE_PUBLIC__';
7138 $substitutionarray['__NOTE_PRIVATE__'] = '__NOTE_PRIVATE__';
7139 $substitutionarray['__EXTRAFIELD_XXX__'] = '__EXTRAFIELD_XXX__';
7140
7141 if (isModEnabled("societe")) { // Most objects are concerned
7142 $substitutionarray['__THIRDPARTY_ID__'] = '__THIRDPARTY_ID__';
7143 $substitutionarray['__THIRDPARTY_NAME__'] = '__THIRDPARTY_NAME__';
7144 $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = '__THIRDPARTY_NAME_ALIAS__';
7145 $substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = '__THIRDPARTY_CODE_CLIENT__';
7146 $substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = '__THIRDPARTY_CODE_FOURNISSEUR__';
7147 $substitutionarray['__THIRDPARTY_EMAIL__'] = '__THIRDPARTY_EMAIL__';
7148 //$substitutionarray['__THIRDPARTY_EMAIL_URLENCODED__'] = '__THIRDPARTY_EMAIL_URLENCODED__'; // We hide this one
7149 $substitutionarray['__THIRDPARTY_URL__'] = '__THIRDPARTY_URL__';
7150 //$substitutionarray['__THIRDPARTY_URL_URLENCODED__'] = '__THIRDPARTY_URL_URLENCODED__'; // We hide this one
7151 $substitutionarray['__THIRDPARTY_PHONE__'] = '__THIRDPARTY_PHONE__';
7152 $substitutionarray['__THIRDPARTY_FAX__'] = '__THIRDPARTY_FAX__';
7153 $substitutionarray['__THIRDPARTY_ADDRESS__'] = '__THIRDPARTY_ADDRESS__';
7154 $substitutionarray['__THIRDPARTY_ZIP__'] = '__THIRDPARTY_ZIP__';
7155 $substitutionarray['__THIRDPARTY_TOWN__'] = '__THIRDPARTY_TOWN__';
7156 $substitutionarray['__THIRDPARTY_STATE__'] = '__THIRDPARTY_STATE__';
7157 $substitutionarray['__THIRDPARTY_IDPROF1__'] = '__THIRDPARTY_IDPROF1__';
7158 $substitutionarray['__THIRDPARTY_IDPROF2__'] = '__THIRDPARTY_IDPROF2__';
7159 $substitutionarray['__THIRDPARTY_IDPROF3__'] = '__THIRDPARTY_IDPROF3__';
7160 $substitutionarray['__THIRDPARTY_IDPROF4__'] = '__THIRDPARTY_IDPROF4__';
7161 $substitutionarray['__THIRDPARTY_IDPROF5__'] = '__THIRDPARTY_IDPROF5__';
7162 $substitutionarray['__THIRDPARTY_IDPROF6__'] = '__THIRDPARTY_IDPROF6__';
7163 $substitutionarray['__THIRDPARTY_IDPROF7__'] = '__THIRDPARTY_IDPROF7__';
7164 $substitutionarray['__THIRDPARTY_IDPROF8__'] = '__THIRDPARTY_IDPROF8__';
7165 $substitutionarray['__THIRDPARTY_IDPROF9__'] = '__THIRDPARTY_IDPROF9__';
7166 $substitutionarray['__THIRDPARTY_IDPROF10__'] = '__THIRDPARTY_IDPROF10__';
7167 $substitutionarray['__THIRDPARTY_TVAINTRA__'] = '__THIRDPARTY_TVAINTRA__';
7168 $substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = '__THIRDPARTY_NOTE_PUBLIC__';
7169 $substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = '__THIRDPARTY_NOTE_PRIVATE__';
7170 }
7171 if (isModEnabled('member') && (!is_object($object) || $object->element == 'adherent') && (empty($exclude) || !in_array('member', $exclude)) && (empty($include) || in_array('member', $include))) {
7172 $substitutionarray['__MEMBER_ID__'] = '__MEMBER_ID__';
7173 $substitutionarray['__MEMBER_TITLE__'] = '__MEMBER_TITLE__';
7174 $substitutionarray['__MEMBER_FIRSTNAME__'] = '__MEMBER_FIRSTNAME__';
7175 $substitutionarray['__MEMBER_LASTNAME__'] = '__MEMBER_LASTNAME__';
7176 $substitutionarray['__MEMBER_USER_LOGIN_INFORMATION__'] = 'Login and pass of the external user account';
7177 /*$substitutionarray['__MEMBER_NOTE_PUBLIC__'] = '__MEMBER_NOTE_PUBLIC__';
7178 $substitutionarray['__MEMBER_NOTE_PRIVATE__'] = '__MEMBER_NOTE_PRIVATE__';*/
7179 }
7180 // add substitution variables for ticket
7181 if (isModEnabled('ticket') && (!is_object($object) || $object->element == 'ticket') && (empty($exclude) || !in_array('ticket', $exclude)) && (empty($include) || in_array('ticket', $include))) {
7182 $substitutionarray['__TICKET_TRACKID__'] = '__TICKET_TRACKID__';
7183 $substitutionarray['__TICKET_SUBJECT__'] = '__TICKET_SUBJECT__';
7184 $substitutionarray['__TICKET_TYPE__'] = '__TICKET_TYPE__';
7185 $substitutionarray['__TICKET_SEVERITY__'] = '__TICKET_SEVERITY__';
7186 $substitutionarray['__TICKET_CATEGORY__'] = '__TICKET_CATEGORY__';
7187 $substitutionarray['__TICKET_ANALYTIC_CODE__'] = '__TICKET_ANALYTIC_CODE__';
7188 $substitutionarray['__TICKET_MESSAGE__'] = '__TICKET_MESSAGE__';
7189 $substitutionarray['__TICKET_PROGRESSION__'] = '__TICKET_PROGRESSION__';
7190 $substitutionarray['__TICKET_USER_ASSIGN__'] = '__TICKET_USER_ASSIGN__';
7191 $substitutionarray['__TICKET_TYPE_LABEL__'] = '__TICKET_TYPE_LABEL__';
7192 $substitutionarray['__TICKET_SEVERITY_LABEL__'] = '__TICKET_SEVERITY_LABEL__';
7193 $substitutionarray['__TICKET_CATEGORY_LABEL__'] = '__TICKET_CATEGORY_LABEL__';
7194 $substitutionarray['__TICKET_PUBLIC_URL__'] = '__TICKET_PUBLIC_URL__';
7195 $substitutionarray['__TICKET_MANAGEMENT_URL__'] = '__TICKET_MANAGEMENT_URL__';
7196 }
7197 if (isModEnabled('recruitment') && (!is_object($object) || $object->element == 'recruitmentcandidature') && (empty($exclude) || !in_array('recruitment', $exclude)) && (empty($include) || in_array('recruitment', $include))) {
7198 $substitutionarray['__CANDIDATE_FULLNAME__'] = '__CANDIDATE_FULLNAME__';
7199 $substitutionarray['__CANDIDATE_FIRSTNAME__'] = '__CANDIDATE_FIRSTNAME__';
7200 $substitutionarray['__CANDIDATE_LASTNAME__'] = '__CANDIDATE_LASTNAME__';
7201 }
7202 if (isModEnabled('holiday') && (!is_object($object) || $object->element == 'holiday') && (empty($exclude) || !in_array('holiday', $exclude)) && (empty($include) || in_array('holiday', $include))) {
7203 $substitutionarray['__HOLIDAY_ARRAY_PER_EMPLOYEE_FOR_PERIOD__'] = '__HOLIDAY_ARRAY_PER_EMPLOYEE_FOR_PERIOD__';
7204 }
7205 if (isModEnabled('project') && (empty($exclude) || !in_array('project', $exclude)) && (empty($include) || in_array('project', $include))) { // Most objects
7206 $substitutionarray['__PROJECT_ID__'] = '__PROJECT_ID__';
7207 $substitutionarray['__PROJECT_REF__'] = '__PROJECT_REF__';
7208 $substitutionarray['__PROJECT_NAME__'] = '__PROJECT_NAME__';
7209 /*$substitutionarray['__PROJECT_NOTE_PUBLIC__'] = '__PROJECT_NOTE_PUBLIC__';
7210 $substitutionarray['__PROJECT_NOTE_PRIVATE__'] = '__PROJECT_NOTE_PRIVATE__';*/
7211 }
7212 if (isModEnabled('contract') && (!is_object($object) || $object->element == 'contract') && (empty($exclude) || !in_array('contract', $exclude)) && (empty($include) || in_array('contract', $include))) {
7213 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = 'Highest date planned for a service start';
7214 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = 'Highest date and hour planned for service start';
7215 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = 'Lowest data for planned expiration of service';
7216 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = 'Lowest date and hour for planned expiration of service';
7217 }
7218 if (isModEnabled("propal") && (!is_object($object) || $object->element == 'propal') && (empty($exclude) || !in_array('propal', $exclude)) && (empty($include) || in_array('propal', $include))) {
7219 $substitutionarray['__ONLINE_SIGN_URL__'] = 'ToOfferALinkForOnlineSignature';
7220 }
7221 if (isModEnabled("intervention") && (!is_object($object) || $object->element == 'fichinter') && (empty($exclude) || !in_array('intervention', $exclude)) && (empty($include) || in_array('intervention', $include))) {
7222 $substitutionarray['__ONLINE_SIGN_FICHINTER_URL__'] = 'ToOfferALinkForOnlineSignature';
7223 }
7224 $substitutionarray['__ONLINE_PAYMENT_URL__'] = 'UrlToPayOnlineIfApplicable';
7225 $substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = 'TextAndUrlToPayOnlineIfApplicable';
7226 $substitutionarray['__SECUREKEYPAYMENT__'] = 'Security key (if key is not unique per record)';
7227 $substitutionarray['__SECUREKEYPAYMENT_MEMBER__'] = 'Security key for payment on a member subscription (one key per member)';
7228 $substitutionarray['__SECUREKEYPAYMENT_ORDER__'] = 'Security key for payment on an order';
7229 $substitutionarray['__SECUREKEYPAYMENT_INVOICE__'] = 'Security key for payment on an invoice';
7230 $substitutionarray['__SECUREKEYPAYMENT_CONTRACTLINE__'] = 'Security key for payment on a service of a contract';
7231
7232 $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = 'Direct download url of a proposal';
7233 $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = 'Direct download url of an order';
7234 $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = 'Direct download url of an invoice';
7235 $substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = 'Direct download url of a contract';
7236 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = 'Direct download url of a supplier proposal';
7237 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_ORDER__'] = 'Direct download url of a supplier order';
7238 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_INVOICE__'] = 'Direct download url of a supplier invoice';
7239
7240 if (isModEnabled("shipping") && (!is_object($object) || $object->element == 'shipping')) {
7241 $substitutionarray['__SHIPPINGTRACKNUM__'] = 'Shipping tracking number';
7242 $substitutionarray['__SHIPPINGTRACKNUMURL__'] = 'Shipping tracking url';
7243 $substitutionarray['__SHIPPINGMETHOD__'] = 'Shipping method';
7244 }
7245 if (isModEnabled("reception") && (!is_object($object) || $object->element == 'reception')) {
7246 $substitutionarray['__RECEPTIONTRACKNUM__'] = 'Shipping tracking number of shipment';
7247 $substitutionarray['__RECEPTIONTRACKNUMURL__'] = 'Shipping tracking url';
7248 }
7249 } else {
7250 '@phan-var-force Adherent|Delivery $object';
7252 $substitutionarray['__ID__'] = $object->id;
7253 $substitutionarray['__REF__'] = $object->ref;
7254 $substitutionarray['__NEWREF__'] = $object->newref;
7255 $substitutionarray['__LABEL__'] = (isset($object->label) ? $object->label : (isset($object->title) ? $object->title : null));
7256 $substitutionarray['__REF_CLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
7257 $substitutionarray['__REF_SUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
7258 $substitutionarray['__NOTE_PUBLIC__'] = (isset($object->note_public) ? $object->note_public : null);
7259 $substitutionarray['__NOTE_PRIVATE__'] = (isset($object->note_private) ? $object->note_private : null);
7260
7261 $substitutionarray['__DATE_CREATION__'] = (isset($object->date_creation) ? dol_print_date($object->date_creation, 'day', false, $outputlangs) : '');
7262 $substitutionarray['__DATE_MODIFICATION__'] = (isset($object->date_modification) ? dol_print_date($object->date_modification, 'day', false, $outputlangs) : '');
7263 $substitutionarray['__DATE_VALIDATION__'] = (isset($object->date_validation) ? dol_print_date($object->date_validation, 'day', false, $outputlangs) : '');
7264
7265 // handle date_delivery: in customer order/supplier order, the property name is delivery_date, in shipment/reception it is date_delivery
7266 $date_delivery = null;
7267 if (property_exists($object, 'date_delivery')) {
7268 $date_delivery = $object->date_delivery;
7269 } elseif (property_exists($object, 'delivery_date')) {
7270 $date_delivery = $object->delivery_date;
7271 }
7272 $substitutionarray['__DATE_DELIVERY__'] = (isset($date_delivery) ? dol_print_date($date_delivery, 'day', false, $outputlangs) : '');
7273 $substitutionarray['__DATE_DELIVERY_DAY__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%d") : '');
7274 $substitutionarray['__DATE_DELIVERY_DAY_TEXT__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%A") : '');
7275 $substitutionarray['__DATE_DELIVERY_MON__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%m") : '');
7276 $substitutionarray['__DATE_DELIVERY_MON_TEXT__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%b") : '');
7277 $substitutionarray['__DATE_DELIVERY_YEAR__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%Y") : '');
7278 $substitutionarray['__DATE_DELIVERY_HH__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%H") : '');
7279 $substitutionarray['__DATE_DELIVERY_MM__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%M") : '');
7280 $substitutionarray['__DATE_DELIVERY_SS__'] = (isset($date_delivery) ? dol_print_date($date_delivery, "%S") : '');
7281
7282 // For backward compatibility (deprecated)
7283 $substitutionarray['__REFCLIENT__'] = (isset($object->ref_client) ? $object->ref_client : (isset($object->ref_customer) ? $object->ref_customer : null));
7284 $substitutionarray['__REFSUPPLIER__'] = (isset($object->ref_supplier) ? $object->ref_supplier : null);
7285
7286 $substitutionarray['__SUPPLIER_ORDER_DATE_DELIVERY__'] = (isset($date_delivery) ? dol_print_date($date_delivery, 'day', false, $outputlangs) : '');
7287 $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 : '')) : '');
7288 $substitutionarray['__EXPIRATION_DATE__'] = (isset($object->fin_validite) ? dol_print_date($object->fin_validite, 'daytext') : '');
7289
7290 if (is_object($object) && ($object->element == 'adherent' || $object->element == 'member') && $object->id > 0) {
7291 '@phan-var-force Adherent $object';
7293 $birthday = (empty($object->birth) ? '' : dol_print_date($object->birth, 'day'));
7294
7295 $substitutionarray['__MEMBER_ID__'] = (isset($object->id) ? $object->id : '');
7296 if (method_exists($object, 'getCivilityLabel')) {
7297 $substitutionarray['__MEMBER_TITLE__'] = $object->getCivilityLabel();
7298 }
7299 $substitutionarray['__MEMBER_FIRSTNAME__'] = (isset($object->firstname) ? $object->firstname : '');
7300 $substitutionarray['__MEMBER_LASTNAME__'] = (isset($object->lastname) ? $object->lastname : '');
7301 $substitutionarray['__MEMBER_USER_LOGIN_INFORMATION__'] = '';
7302 if (method_exists($object, 'getFullName')) {
7303 $substitutionarray['__MEMBER_FULLNAME__'] = $object->getFullName($outputlangs);
7304 }
7305 $substitutionarray['__MEMBER_COMPANY__'] = (isset($object->societe) ? $object->societe : '');
7306 $substitutionarray['__MEMBER_ADDRESS__'] = (isset($object->address) ? $object->address : '');
7307 $substitutionarray['__MEMBER_ZIP__'] = (isset($object->zip) ? $object->zip : '');
7308 $substitutionarray['__MEMBER_TOWN__'] = (isset($object->town) ? $object->town : '');
7309 $substitutionarray['__MEMBER_STATE__'] = (isset($object->state) ? $object->state : '');
7310 $substitutionarray['__MEMBER_COUNTRY__'] = (isset($object->country) ? $object->country : '');
7311 $substitutionarray['__MEMBER_EMAIL__'] = (isset($object->email) ? $object->email : '');
7312 $substitutionarray['__MEMBER_BIRTH__'] = (isset($birthday) ? $birthday : '');
7313 $substitutionarray['__MEMBER_PHOTO__'] = (isset($object->photo) ? $object->photo : '');
7314 $substitutionarray['__MEMBER_LOGIN__'] = (isset($object->login) ? $object->login : '');
7315 $substitutionarray['__MEMBER_PASSWORD__'] = (isset($object->pass) ? $object->pass : '');
7316 $substitutionarray['__MEMBER_PHONE__'] = (isset($object->phone) ? dol_print_phone($object->phone) : '');
7317 $substitutionarray['__MEMBER_PHONEPRO__'] = (isset($object->phone_perso) ? dol_print_phone($object->phone_perso) : '');
7318 $substitutionarray['__MEMBER_PHONEMOBILE__'] = (isset($object->phone_mobile) ? dol_print_phone($object->phone_mobile) : '');
7319 $substitutionarray['__MEMBER_TYPE__'] = (isset($object->type) ? $object->type : '');
7320 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE__'] = dol_print_date($object->first_subscription_date, 'day');
7321
7322 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_RFC__'] = dol_print_date($object->first_subscription_date, 'dayrfc');
7323 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_START__'] = (isset($object->first_subscription_date_start) ? dol_print_date($object->first_subscription_date_start, 'day') : '');
7324 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_START_RFC__'] = (isset($object->first_subscription_date_start) ? dol_print_date($object->first_subscription_date_start, 'dayrfc') : '');
7325 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_END__'] = (isset($object->first_subscription_date_end) ? dol_print_date($object->first_subscription_date_end, 'day') : '');
7326 $substitutionarray['__MEMBER_FIRST_SUBSCRIPTION_DATE_END_RFC__'] = (isset($object->first_subscription_date_end) ? dol_print_date($object->first_subscription_date_end, 'dayrfc') : '');
7327 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE__'] = dol_print_date($object->last_subscription_date, 'day');
7328 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_RFC__'] = dol_print_date($object->last_subscription_date, 'dayrfc');
7329 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_START__'] = dol_print_date($object->last_subscription_date_start, 'day');
7330 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_START_RFC__'] = dol_print_date($object->last_subscription_date_start, 'dayrfc');
7331 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_END__'] = dol_print_date($object->last_subscription_date_end, 'day');
7332 $substitutionarray['__MEMBER_LAST_SUBSCRIPTION_DATE_END_RFC__'] = dol_print_date($object->last_subscription_date_end, 'dayrfc');
7333 }
7334
7335 if (is_object($object) && $object->element == 'societe') {
7337 '@phan-var-force Societe $object';
7338 $substitutionarray['__THIRDPARTY_ID__'] = $object->id ?? '';
7339 $substitutionarray['__THIRDPARTY_NAME__'] = $object->name ?? '';
7340 $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = $object->name_alias ?? '';
7341 $substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = $object->code_client ?? '';
7342 $substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = $object->code_fournisseur ?? '';
7343 $substitutionarray['__THIRDPARTY_EMAIL__'] = $object->email ?? '';
7344 $substitutionarray['__THIRDPARTY_EMAIL_URLENCODED__'] = urlencode($object->email ?? '');
7345 $substitutionarray['__THIRDPARTY_URL__'] = $object->url ?? '';
7346 $substitutionarray['__THIRDPARTY_URL_URLENCODED__'] = urlencode($object->url ?? '');
7347 $substitutionarray['__THIRDPARTY_PHONE__'] = dol_print_phone($object->phone ?? '');
7348 $substitutionarray['__THIRDPARTY_FAX__'] = dol_print_phone($object->fax ?? '');
7349 $substitutionarray['__THIRDPARTY_ADDRESS__'] = $object->address ?? '';
7350 $substitutionarray['__THIRDPARTY_ZIP__'] = $object->zip ?? '';
7351 $substitutionarray['__THIRDPARTY_TOWN__'] = $object->town ?? '';
7352 $substitutionarray['__THIRDPARTY_STATE__'] = $object->state ?? '';
7353 $substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = ($object->country_id > 0 ?: '');
7354 $substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = $object->country_code ?? '';
7355 $substitutionarray['__THIRDPARTY_IDPROF1__'] = $object->idprof1 ?? '';
7356 $substitutionarray['__THIRDPARTY_IDPROF2__'] = $object->idprof2 ?? '';
7357 $substitutionarray['__THIRDPARTY_IDPROF3__'] = $object->idprof3 ?? '';
7358 $substitutionarray['__THIRDPARTY_IDPROF4__'] = $object->idprof4 ?? '';
7359 $substitutionarray['__THIRDPARTY_IDPROF5__'] = $object->idprof5 ?? '';
7360 $substitutionarray['__THIRDPARTY_IDPROF6__'] = $object->idprof6 ?? '';
7361 $substitutionarray['__THIRDPARTY_TVAINTRA__'] = $object->tva_intra ?? '';
7362 $substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = dol_htmlentitiesbr($object->note_public ?? '');
7363 $substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = dol_htmlentitiesbr($object->note_private ?? '');
7364 } elseif (is_object($object) && is_object($object->thirdparty)) {
7365 $substitutionarray['__THIRDPARTY_ID__'] = $object->thirdparty->id ?? '';
7366 $substitutionarray['__THIRDPARTY_NAME__'] = $object->thirdparty->name ?? '';
7367 $substitutionarray['__THIRDPARTY_NAME_ALIAS__'] = $object->thirdparty->name_alias ?? '';
7368 $substitutionarray['__THIRDPARTY_CODE_CLIENT__'] = $object->thirdparty->code_client ?? '';
7369 $substitutionarray['__THIRDPARTY_CODE_FOURNISSEUR__'] = $object->thirdparty->code_fournisseur ?? '';
7370 $substitutionarray['__THIRDPARTY_EMAIL__'] = $object->thirdparty->email ?? '';
7371 $substitutionarray['__THIRDPARTY_EMAIL_URLENCODED__'] = urlencode($object->thirdparty->email ?? '');
7372 $substitutionarray['__THIRDPARTY_PHONE__'] = dol_print_phone($object->thirdparty->phone ?? '');
7373 $substitutionarray['__THIRDPARTY_FAX__'] = dol_print_phone($object->thirdparty->fax ?? '');
7374 $substitutionarray['__THIRDPARTY_ADDRESS__'] = $object->thirdparty->address ?? '';
7375 $substitutionarray['__THIRDPARTY_ZIP__'] = $object->thirdparty->zip ?? '';
7376 $substitutionarray['__THIRDPARTY_TOWN__'] = $object->thirdparty->town ?? '';
7377 $substitutionarray['__THIRDPARTY_STATE__'] = $object->thirdparty->state ?? '';
7378 $substitutionarray['__THIRDPARTY_COUNTRY_ID__'] = ($object->thirdparty->country_id > 0 ?: '');
7379 $substitutionarray['__THIRDPARTY_COUNTRY_CODE__'] = $object->thirdparty->country_code ?? '';
7380 $substitutionarray['__THIRDPARTY_IDPROF1__'] = $object->thirdparty->idprof1 ?? '';
7381 $substitutionarray['__THIRDPARTY_IDPROF2__'] = $object->thirdparty->idprof2 ?? '';
7382 $substitutionarray['__THIRDPARTY_IDPROF3__'] = $object->thirdparty->idprof3 ?? '';
7383 $substitutionarray['__THIRDPARTY_IDPROF4__'] = $object->thirdparty->idprof4 ?? '';
7384 $substitutionarray['__THIRDPARTY_IDPROF5__'] = $object->thirdparty->idprof5 ?? '';
7385 $substitutionarray['__THIRDPARTY_IDPROF6__'] = $object->thirdparty->idprof6 ?? '';
7386 $substitutionarray['__THIRDPARTY_TVAINTRA__'] = $object->thirdparty->tva_intra ?? '';
7387 $substitutionarray['__THIRDPARTY_NOTE_PUBLIC__'] = dol_htmlentitiesbr($object->thirdparty->note_public ?? '');
7388 $substitutionarray['__THIRDPARTY_NOTE_PRIVATE__'] = dol_htmlentitiesbr($object->thirdparty->note_private ?? '');
7389 }
7390
7391 if (is_object($object) && $object->element == 'recruitmentcandidature') {
7392 '@phan-var-force RecruitmentCandidature $object';
7394 $substitutionarray['__CANDIDATE_FULLNAME__'] = $object->getFullName($outputlangs);
7395 $substitutionarray['__CANDIDATE_FIRSTNAME__'] = isset($object->firstname) ? $object->firstname : '';
7396 $substitutionarray['__CANDIDATE_LASTNAME__'] = isset($object->lastname) ? $object->lastname : '';
7397 }
7398 if (is_object($object) && $object->element == 'conferenceorboothattendee') {
7399 '@phan-var-force ConferenceOrBoothAttendee $object';
7401 $substitutionarray['__ATTENDEE_FULLNAME__'] = $object->getFullName($outputlangs);
7402 $substitutionarray['__ATTENDEE_FIRSTNAME__'] = isset($object->firstname) ? $object->firstname : '';
7403 $substitutionarray['__ATTENDEE_LASTNAME__'] = isset($object->lastname) ? $object->lastname : '';
7404 }
7405
7406 if (is_object($object) && $object->element == 'project') {
7407 '@phan-var-force Project $object';
7409 $substitutionarray['__PROJECT_ID__'] = $object->id;
7410 $substitutionarray['__PROJECT_REF__'] = $object->ref;
7411 $substitutionarray['__PROJECT_NAME__'] = $object->title;
7412 } elseif (is_object($object)) {
7413 $project = null;
7414 if (!empty($object->project)) {
7415 $project = $object->project;
7416 }
7417 if (!is_null($project) && is_object($project)) {
7418 $substitutionarray['__PROJECT_ID__'] = $project->id;
7419 $substitutionarray['__PROJECT_REF__'] = $project->ref;
7420 $substitutionarray['__PROJECT_NAME__'] = $project->title;
7421 } else {
7422 // can substitute variables for project : uses lazy load in "make_substitutions" method
7423 $project_id = 0;
7424 if (!empty($object->fk_project) && $object->fk_project > 0) {
7425 $project_id = $object->fk_project;
7426 } elseif (!empty($object->fk_projet) && $object->fk_projet > 0) {
7427 $project_id = $object->fk_project;
7428 }
7429 if ($project_id > 0) {
7430 // path:class:method:id
7431 $substitutionarray['__PROJECT_ID__@lazyload'] = '/projet/class/project.class.php:Project:fetchAndSetSubstitution:' . $project_id;
7432 $substitutionarray['__PROJECT_REF__@lazyload'] = '/projet/class/project.class.php:Project:fetchAndSetSubstitution:' . $project_id;
7433 $substitutionarray['__PROJECT_NAME__@lazyload'] = '/projet/class/project.class.php:Project:fetchAndSetSubstitution:' . $project_id;
7434 }
7435 }
7436 }
7437
7438 if (is_object($object) && $object->element == 'facture') {
7439 '@phan-var-force Facture $object';
7441 $substitutionarray['__INVOICE_SITUATION_NUMBER__'] = isset($object->situation_counter) ? $object->situation_counter : '';
7442 }
7443 if (is_object($object) && $object->element == 'shipping') {
7444 '@phan-var-force Expedition $object';
7446 $substitutionarray['__SHIPPINGTRACKNUM__'] = $object->tracking_number;
7447 $substitutionarray['__SHIPPINGTRACKNUMURL__'] = $object->tracking_url;
7448 $substitutionarray['__SHIPPINGMETHOD__'] = $object->shipping_method;
7449 }
7450 if (is_object($object) && $object->element == 'reception') {
7451 '@phan-var-force Reception $object';
7453 $substitutionarray['__RECEPTIONTRACKNUM__'] = $object->tracking_number;
7454 $substitutionarray['__RECEPTIONTRACKNUMURL__'] = $object->tracking_url;
7455 }
7456
7457 if (is_object($object) && $object->element == 'contrat' && $object->id > 0 && is_array($object->lines)) {
7458 '@phan-var-force Contrat $object';
7460 $dateplannedstart = '';
7461 $datenextexpiration = '';
7462 foreach ($object->lines as $line) {
7463 if ($line->date_start > $dateplannedstart) {
7464 $dateplannedstart = $line->date_start;
7465 }
7466 if ($line->statut == 4 && $line->date_end && (!$datenextexpiration || $line->date_end < $datenextexpiration)) {
7467 $datenextexpiration = $line->date_end;
7468 }
7469 }
7470 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE__'] = dol_print_date($dateplannedstart, 'day');
7471 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATE_RFC__'] = dol_print_date($dateplannedstart, 'dayrfc');
7472 $substitutionarray['__CONTRACT_HIGHEST_PLANNED_START_DATETIME__'] = dol_print_date($dateplannedstart, 'standard');
7473
7474 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE__'] = dol_print_date($datenextexpiration, 'day');
7475 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATE_RFC__'] = dol_print_date($datenextexpiration, 'dayrfc');
7476 $substitutionarray['__CONTRACT_LOWEST_EXPIRATION_DATETIME__'] = dol_print_date($datenextexpiration, 'standard');
7477 }
7478 // add substitution variables for ticket
7479 if (is_object($object) && $object->element == 'ticket') {
7480 '@phan-var-force Ticket $object';
7482 $substitutionarray['__TICKET_TRACKID__'] = $object->track_id;
7483 $substitutionarray['__TICKET_SUBJECT__'] = $object->subject;
7484 $substitutionarray['__TICKET_TYPE__'] = $object->type_code;
7485 $substitutionarray['__TICKET_SEVERITY__'] = $object->severity_code;
7486 $substitutionarray['__TICKET_CATEGORY__'] = $object->category_code; // For backward compatibility
7487 $substitutionarray['__TICKET_ANALYTIC_CODE__'] = $object->category_code;
7488 $substitutionarray['__TICKET_MESSAGE__'] = $object->message;
7489 $substitutionarray['__TICKET_PROGRESSION__'] = $object->progress;
7490 // __TICKET_TYPE__, __TICKET_SEVERITY__ and __TICKET_CATEGORY__ hold the raw codes.
7491 // An email usually needs the human readable labels instead.
7492 $substitutionarray['__TICKET_TYPE_LABEL__'] = empty($object->type_code) ? '' : $outputlangs->getLabelFromKey($db, 'TicketTypeShort'.$object->type_code, 'c_ticket_type', 'code', 'label', $object->type_code);
7493 $substitutionarray['__TICKET_SEVERITY_LABEL__'] = empty($object->severity_code) ? '' : $outputlangs->getLabelFromKey($db, 'TicketSeverityShort'.$object->severity_code, 'c_ticket_severity', 'code', 'label', $object->severity_code);
7494 $substitutionarray['__TICKET_CATEGORY_LABEL__'] = empty($object->category_code) ? '' : $outputlangs->getLabelFromKey($db, 'TicketCategoryShort'.$object->category_code, 'c_ticket_category', 'code', 'label', $object->category_code);
7495 $substitutionarray['__TICKET_PUBLIC_URL__'] = getDolGlobalString('TICKET_URL_PUBLIC_INTERFACE', dol_buildpath('/public/ticket/', 2)).'view.php?track_id='.urlencode((string) $object->track_id);
7496 $substitutionarray['__TICKET_MANAGEMENT_URL__'] = dol_buildpath('/ticket/card.php', 2).'?track_id='.urlencode((string) $object->track_id);
7497 $userstat = new User($db);
7498 if ($object->fk_user_assign > 0) {
7499 $userstat->fetch($object->fk_user_assign);
7500 $substitutionarray['__TICKET_USER_ASSIGN__'] = dolGetFirstLastname($userstat->firstname, $userstat->lastname);
7501 }
7502
7503 if ($object->fk_user_create > 0) {
7504 $userstat->fetch($object->fk_user_create);
7505 $substitutionarray['__USER_CREATE__'] = dolGetFirstLastname($userstat->firstname, $userstat->lastname);
7506 }
7507 }
7508
7509 // Create dynamic tags for __EXTRAFIELD_FIELD__
7510 if ($object->table_element && $object->id > 0) {
7511 if (!is_object($extrafields)) {
7512 $extrafields = new ExtraFields($db);
7513 }
7514 $extrafields->fetch_name_optionals_label($object->table_element, true);
7515
7516 if ($object->fetch_optionals() > 0) { // @FIXME: Remove this, the fetch should have been done already, by the caller of getCommonSubstitutionArray()
7517 if (is_array($extrafields->attributes[$object->table_element]['label']) && count($extrafields->attributes[$object->table_element]['label']) > 0) {
7518 foreach ($extrafields->attributes[$object->table_element]['label'] as $key => $label) {
7519 if ($extrafields->attributes[$object->table_element]['type'][$key] == 'date') {
7520 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = dol_print_date($object->array_options['options_' . $key], 'day');
7521 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_LOCALE__'] = dol_print_date($object->array_options['options_' . $key], 'day', 'tzserver', $outputlangs);
7522 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_RFC__'] = dol_print_date($object->array_options['options_' . $key], 'dayrfc');
7523 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'datetime') {
7524 $datetime = $object->array_options['options_' . $key];
7525 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhour') : '');
7526 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_LOCALE__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhour', 'tzserver', $outputlangs) : '');
7527 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_DAY_LOCALE__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'day', 'tzserver', $outputlangs) : '');
7528 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_RFC__'] = ($datetime != "0000-00-00 00:00:00" ? dol_print_date($datetime, 'dayhourrfc') : '');
7529 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'phone') {
7530 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = dol_print_phone($object->array_options['options_' . $key]);
7531 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'price') {
7532 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = $object->array_options['options_' . $key];
7533 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_FORMATED__'] = price($object->array_options['options_' . $key]); // For compatibility
7534 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '_FORMATTED__'] = price($object->array_options['options_' . $key]);
7535 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] == 'select') {
7536 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = !empty($object->array_options['options_' . $key]) ? $object->array_options['options_' . $key] : '';
7537 $val = $extrafields->attributes[$object->table_element]['param'][$key]['options'][$object->array_options['options_'.$key]] ?? $object->array_options['options_'.$key];
7538 $substitutionarray['__EXTRAFIELD_'.strtoupper($key).'_LABEL__'] = $val;
7539 } elseif ($extrafields->attributes[$object->table_element]['type'][$key] != 'separator') {
7540 $substitutionarray['__EXTRAFIELD_' . strtoupper($key) . '__'] = !empty($object->array_options['options_' . $key]) ? $object->array_options['options_' . $key] : '';
7541 }
7542 }
7543 }
7544 }
7545 }
7546
7547 // Complete substitution array with the url to make online payment
7548 if (empty($substitutionarray['__REF__'])) {
7549 $paymenturl = '';
7550 } else {
7551 // Set the online payment url link into __ONLINE_PAYMENT_URL__ key
7552 require_once DOL_DOCUMENT_ROOT . '/core/lib/payments.lib.php';
7553 $outputlangs->loadLangs(array('paypal', 'other'));
7554
7555 $amounttouse = 0;
7556 $typeforonlinepayment = 'free';
7557 if (is_object($object) && $object->element == 'commande') {
7558 $typeforonlinepayment = 'order';
7559 }
7560 if (is_object($object) && $object->element == 'facture') {
7561 $typeforonlinepayment = 'invoice';
7562 }
7563 if (is_object($object) && $object->element == 'member') {
7564 $typeforonlinepayment = 'member';
7565 if (!empty($object->last_subscription_amount)) {
7566 $amounttouse = $object->last_subscription_amount;
7567 }
7568 }
7569 if (is_object($object) && $object->element == 'contrat') {
7570 $typeforonlinepayment = 'contract';
7571 }
7572 if (is_object($object) && $object->element == 'fichinter') {
7573 $typeforonlinepayment = 'ficheinter';
7574 }
7575
7576 $url = getOnlinePaymentUrl(0, $typeforonlinepayment, $substitutionarray['__REF__'], (float) $amounttouse);
7577 $paymenturl = $url;
7578 }
7579
7580 if ($object->id > 0) {
7581 $substitutionarray['__ONLINE_PAYMENT_TEXT_AND_URL__'] = ($paymenturl ? str_replace('\n', "\n", $outputlangs->trans("PredefinedMailContentLink", $paymenturl)) : '');
7582 $substitutionarray['__ONLINE_PAYMENT_URL__'] = $paymenturl;
7583
7584 // Show structured communication
7585 if (getDolGlobalString('INVOICE_PAYMENT_ENABLE_STRUCTURED_COMMUNICATION') && $object->element == 'facture') {
7586 include_once DOL_DOCUMENT_ROOT . '/core/lib/functions_be.lib.php';
7587 $substitutionarray['__PAYMENT_STRUCTURED_COMMUNICATION__'] = dolBECalculateStructuredCommunication((string) $object->ref, $object->type);
7588 }
7589
7590 if (getDolGlobalString('PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'propal') {
7591 $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = $object->getLastMainDocLink($object->element);
7592 } else {
7593 $substitutionarray['__DIRECTDOWNLOAD_URL_PROPOSAL__'] = '';
7594 }
7595 if (getDolGlobalString('ORDER_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'commande') {
7596 $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = $object->getLastMainDocLink($object->element);
7597 } else {
7598 $substitutionarray['__DIRECTDOWNLOAD_URL_ORDER__'] = '';
7599 }
7600 if (getDolGlobalString('INVOICE_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'facture') {
7601 $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = $object->getLastMainDocLink($object->element);
7602 } else {
7603 $substitutionarray['__DIRECTDOWNLOAD_URL_INVOICE__'] = '';
7604 }
7605 if (getDolGlobalString('CONTRACT_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'contrat') {
7606 $substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = $object->getLastMainDocLink($object->element);
7607 } else {
7608 $substitutionarray['__DIRECTDOWNLOAD_URL_CONTRACT__'] = '';
7609 }
7610 if (getDolGlobalString('FICHINTER_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'fichinter') {
7611 $substitutionarray['__DIRECTDOWNLOAD_URL_FICHINTER__'] = $object->getLastMainDocLink($object->element);
7612 } else {
7613 $substitutionarray['__DIRECTDOWNLOAD_URL_FICHINTER__'] = '';
7614 }
7615 if (getDolGlobalString('SUPPLIER_PROPOSAL_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'supplier_proposal') {
7616 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = $object->getLastMainDocLink($object->element);
7617 } else {
7618 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_PROPOSAL__'] = '';
7619 }
7620 if (getDolGlobalString('SUPPLIER_ORDER_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'order_supplier') {
7621 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_ORDER__'] = $object->getLastMainDocLink($object->element);
7622 } else {
7623 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_ORDER__'] = '';
7624 }
7625 if (getDolGlobalString('SUPPLIER_INVOICE_ALLOW_EXTERNAL_DOWNLOAD') && is_object($object) && $object->element == 'invoice_supplier') {
7626 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_INVOICE__'] = $object->getLastMainDocLink($object->element);
7627 } else {
7628 $substitutionarray['__DIRECTDOWNLOAD_URL_SUPPLIER_INVOICE__'] = '';
7629 }
7630
7631 if (is_object($object) && $object->element == 'propal') {
7632 '@phan-var-force Propal $object';
7634 $substitutionarray['__URL_PROPOSAL__'] = DOL_MAIN_URL_ROOT . "/comm/propal/card.php?id=" . $object->id;
7635 require_once DOL_DOCUMENT_ROOT . '/core/lib/signature.lib.php';
7636 $substitutionarray['__ONLINE_SIGN_URL__'] = getOnlineSignatureUrl(0, 'proposal', (string) $object->ref, 1, $object);
7637 }
7638 if (is_object($object) && $object->element == 'commande') {
7639 '@phan-var-force Commande $object';
7641 $substitutionarray['__URL_ORDER__'] = DOL_MAIN_URL_ROOT . "/commande/card.php?id=" . $object->id;
7642 }
7643 if (is_object($object) && $object->element == 'facture') {
7644 '@phan-var-force Facture $object';
7646 $substitutionarray['__URL_INVOICE__'] = DOL_MAIN_URL_ROOT . "/compta/facture/card.php?id=" . $object->id;
7647 }
7648 if (is_object($object) && $object->element == 'contrat') {
7649 '@phan-var-force Contrat $object';
7651 $substitutionarray['__URL_CONTRACT__'] = DOL_MAIN_URL_ROOT . "/contrat/card.php?id=" . $object->id;
7652 require_once DOL_DOCUMENT_ROOT . '/core/lib/signature.lib.php';
7653 $substitutionarray['__ONLINE_SIGN_URL__'] = getOnlineSignatureUrl(0, 'contract', (string) $object->ref, 1, $object);
7654 }
7655 if (is_object($object) && $object->element == 'fichinter') {
7656 '@phan-var-force Fichinter $object';
7658 $substitutionarray['__URL_FICHINTER__'] = DOL_MAIN_URL_ROOT . "/fichinter/card.php?id=" . $object->id;
7659 require_once DOL_DOCUMENT_ROOT . '/core/lib/signature.lib.php';
7660 $substitutionarray['__ONLINE_SIGN_FICHINTER_URL__'] = getOnlineSignatureUrl(0, 'fichinter', (string) $object->ref, 1, $object);
7661 }
7662 if (is_object($object) && $object->element == 'supplier_proposal') {
7663 '@phan-var-force SupplierProposal $object';
7665 $substitutionarray['__URL_SUPPLIER_PROPOSAL__'] = DOL_MAIN_URL_ROOT . "/supplier_proposal/card.php?id=" . $object->id;
7666 }
7667 if (is_object($object) && $object->element == 'invoice_supplier') {
7668 '@phan-var-force FactureFournisseur $object';
7670 $substitutionarray['__URL_SUPPLIER_INVOICE__'] = DOL_MAIN_URL_ROOT . "/fourn/facture/card.php?id=" . $object->id;
7671 }
7672 if (is_object($object) && $object->element == 'payment_supplier') {
7673 '@phan-var-force PaiementFourn $object';
7675 //print_r($object);
7676 $liste_factures = [];
7677 $total = 0;
7678
7679 // @FIXME We must not have any repeated SQL access into this function.
7680 $sql = 'SELECT f.ref,f.multicurrency_code as f_mccode, pf.*
7681 FROM '.MAIN_DB_PREFIX.'paiementfourn_facturefourn as pf
7682 JOIN '.MAIN_DB_PREFIX.'facture_fourn as f ON pf.fk_facturefourn = f.rowid
7683 WHERE pf.fk_paiementfourn = '.((int) $object->id);
7684
7685 $resql = $db->query($sql);
7686 if ($resql) {
7687 while ($objp = $db->fetch_object($resql)) {
7688 $liste_factures[] = ' - '.$outputlangs->trans('Invoice').' '. $objp->ref.' '.$outputlangs->trans('AmountPayed').' '.price($objp->multicurrency_amount, 0, $outputlangs, 0, -1, -1, $objp->multicurrency_code);
7689 }
7690 }
7691 $substitutionarray['__SUPPLIER_PAYMENT_INVOICES_LIST__'] = implode("\n", $liste_factures);
7692 ;
7693 $substitutionarray['__SUPPLIER_PAYMENT_INVOICES_TOTAL__'] = price($object->multicurrency_amount, 0, $outputlangs, 0, -1, -1, $object->multicurrency_code ? $object->multicurrency_code : $conf->currency);
7694 }
7695 if (is_object($object) && $object->element == 'shipping') {
7696 '@phan-var-force Expedition $object';
7698 $substitutionarray['__URL_SHIPMENT__'] = DOL_MAIN_URL_ROOT . "/expedition/card.php?id=" . $object->id;
7699 if (getDolGlobalInt('EXPEDITION_ALLOW_ONLINESIGN')) {
7700 require_once DOL_DOCUMENT_ROOT . '/core/lib/signature.lib.php';
7701 $substitutionarray['__ONLINE_SIGN_URL__'] = getOnlineSignatureUrl(0, 'expedition', (string) $object->ref, 1, $object);
7702 }
7703 }
7704 }
7705
7706 if (is_object($object) && $object->element == 'action') {
7707 '@phan-var-force ActionComm $object';
7709 $substitutionarray['__EVENT_LABEL__'] = $object->label;
7710 $substitutionarray['__EVENT_DESCRIPTION__'] = $object->note;
7711 $substitutionarray['__EVENT_TYPE__'] = $outputlangs->trans("Action" . $object->type_code);
7712 $substitutionarray['__EVENT_DATE__'] = dol_print_date($object->datep, 'day', 'auto', $outputlangs);
7713 $substitutionarray['__EVENT_TIME__'] = dol_print_date($object->datep, 'hour', 'auto', $outputlangs);
7714 $substitutionarray['__EVENT_DATE_TZUSER__'] = dol_print_date($object->datep, 'day', 'tzuserrel', $outputlangs);
7715 $substitutionarray['__EVENT_TIME_TZUSER__'] = dol_print_date($object->datep, 'hour', 'tzuserrel', $outputlangs);
7716 }
7717 }
7718 }
7719
7720 if ((empty($exclude) || !in_array('objectamount', $exclude)) && (empty($include) || in_array('objectamount', $include))) {
7721 '@phan-var-force Facture|FactureRec $object';
7723 include_once DOL_DOCUMENT_ROOT . '/core/lib/functionsnumtoword.lib.php';
7724
7725 $substitutionarray['__DATE_YMD__'] = is_object($object) ? (isset($object->date) ? dol_print_date($object->date, 'day', false, $outputlangs) : null) : '';
7726 $substitutionarray['__DATE_DUE_YMD__'] = is_object($object) ? (isset($object->date_lim_reglement) ? dol_print_date($object->date_lim_reglement, 'day', false, $outputlangs) : null) : '';
7727 $substitutionarray['__DATE_YMD_TEXT__'] = is_object($object) ? (isset($object->date) ? dol_print_date($object->date, 'daytext', false, $outputlangs) : null) : '';
7728 $substitutionarray['__DATE_DUE_YMD_TEXT__'] = is_object($object) ? (isset($object->date_lim_reglement) ? dol_print_date($object->date_lim_reglement, 'daytext', false, $outputlangs) : null) : '';
7729
7730 $already_payed_all = 0;
7731 if (is_object($object) && ($object instanceof Facture)) {
7732 $already_payed_all = $object->totalpaid + $object->totaldeposits + $object->totalcreditnotes;
7733 }
7734
7735 $substitutionarray['__SIMPLE_HTML_TABLE__'] = is_object($object) && !empty($object->lines) ? showSimpleHTMLTable($outputlangs, $object) : "";
7736 $substitutionarray['__AMOUNT_EXCL_TAX__'] = is_object($object) ? $object->total_ht : '';
7737 $substitutionarray['__AMOUNT_EXCL_TAX_TEXT__'] = is_object($object) ? dol_convertToWord($object->total_ht, $outputlangs, '', true) : '';
7738 $substitutionarray['__AMOUNT_EXCL_TAX_TEXTCURRENCY__'] = is_object($object) ? dol_convertToWord($object->total_ht, $outputlangs, $conf->currency, true) : '';
7739
7740 $substitutionarray['__AMOUNT__'] = is_object($object) ? $object->total_ttc : '';
7741 $substitutionarray['__AMOUNT_TEXT__'] = is_object($object) ? dol_convertToWord($object->total_ttc, $outputlangs, '', true) : '';
7742 $substitutionarray['__AMOUNT_TEXTCURRENCY__'] = is_object($object) ? dol_convertToWord($object->total_ttc, $outputlangs, $conf->currency, true) : '';
7743
7744 $substitutionarray['__DEPOSIT_PERCENT__'] = is_object($object) ? $object->deposit_percent : '';
7745 $substitutionarray['__DEPOSIT_AMOUNT__'] = is_object($object) ? price2num($object->total_ttc * ($object->deposit_percent / 100), 'MT') : '';
7746
7747 $substitutionarray['__AMOUNT_REMAIN__'] = is_object($object) ? price2num($object->total_ttc - $already_payed_all, 'MT') : '';
7748
7749 $substitutionarray['__AMOUNT_VAT__'] = is_object($object) ? (isset($object->total_vat) ? $object->total_vat : $object->total_tva) : '';
7750 $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)) : '';
7751 $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)) : '';
7752
7753 $mysocuselocaltax1 = false;
7754 $mysocuselocaltax2 = false;
7755 if ($mysoc instanceof Societe && !empty($mysoc->country_code)) {
7756 $tmparray = $mysoc->useLocalTax(-1);
7757 $mysocuselocaltax1 = $tmparray[1];
7758 $mysocuselocaltax2 = $tmparray[2];
7759 }
7760
7761 // Local taxes
7762 if ($onlykey != 2 || $mysocuselocaltax1) {
7763 $substitutionarray['__AMOUNT_TAX2__'] = is_object($object) ? $object->total_localtax1 : '';
7764 }
7765 if ($onlykey != 2 || $mysocuselocaltax2) {
7766 $substitutionarray['__AMOUNT_TAX3__'] = is_object($object) ? $object->total_localtax2 : '';
7767 }
7768
7769 // Amount keys formatted in a currency
7770 $substitutionarray['__AMOUNT_EXCL_TAX_FORMATTED__'] = is_object($object) ? ($object->total_ht ? price($object->total_ht, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
7771 $substitutionarray['__AMOUNT_FORMATTED__'] = is_object($object) ? ($object->total_ttc ? price($object->total_ttc, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
7772 $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) : '';
7773 $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)) : '';
7774 if ($onlykey != 2 || $mysocuselocaltax1) {
7775 $substitutionarray['__AMOUNT_TAX2_FORMATTED__'] = is_object($object) ? ($object->total_localtax1 ? price($object->total_localtax1, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
7776 }
7777 if ($onlykey != 2 || $mysocuselocaltax2) {
7778 $substitutionarray['__AMOUNT_TAX3_FORMATTED__'] = is_object($object) ? ($object->total_localtax2 ? price($object->total_localtax2, 0, $outputlangs, 0, -1, -1, $conf->currency) : null) : '';
7779 }
7780 // Amount keys formatted in a currency (with the typo error for backward compatibility)
7781 if ($onlykey != 2) {
7782 $substitutionarray['__AMOUNT_EXCL_TAX_FORMATED__'] = $substitutionarray['__AMOUNT_EXCL_TAX_FORMATTED__'];
7783 $substitutionarray['__AMOUNT_FORMATED__'] = $substitutionarray['__AMOUNT_FORMATTED__'];
7784 $substitutionarray['__AMOUNT_REMAIN_FORMATED__'] = $substitutionarray['__AMOUNT_REMAIN_FORMATTED__'];
7785 $substitutionarray['__AMOUNT_VAT_FORMATED__'] = $substitutionarray['__AMOUNT_VAT_FORMATTED__'];
7786 if ($mysocuselocaltax1) {
7787 $substitutionarray['__AMOUNT_TAX2_FORMATED__'] = $substitutionarray['__AMOUNT_TAX2_FORMATTED__'];
7788 }
7789 if ($mysoc->useLocalTax2) {
7790 $substitutionarray['__AMOUNT_TAX3_FORMATED__'] = $substitutionarray['__AMOUNT_TAX3_FORMATTED__'];
7791 }
7792 }
7793
7794 $substitutionarray['__AMOUNT_MULTICURRENCY__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? $object->multicurrency_total_ttc : '';
7795 $substitutionarray['__AMOUNT_MULTICURRENCY_FORMATED__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? price($object->multicurrency_total_ttc, 0, $outputlangs, 0, -1, -1, $object->multicurrency_code) : '';
7796 $substitutionarray['__AMOUNT_MULTICURRENCY_TEXT__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? dol_convertToWord($object->multicurrency_total_ttc, $outputlangs, '', true) : '';
7797 $substitutionarray['__AMOUNT_MULTICURRENCY_TEXTCURRENCY__'] = (is_object($object) && isset($object->multicurrency_total_ttc)) ? dol_convertToWord($object->multicurrency_total_ttc, $outputlangs, $object->multicurrency_code, true) : '';
7798 $substitutionarray['__MULTICURRENCY_CODE__'] = (is_object($object) && isset($object->multicurrency_code)) ? $object->multicurrency_code : '';
7799 // TODO Add other keys for foreign multicurrency
7800
7801 // For backward compatibility
7802 if ($onlykey != 2) {
7803 $substitutionarray['__TOTAL_TTC__'] = is_object($object) ? $object->total_ttc : '';
7804 $substitutionarray['__TOTAL_HT__'] = is_object($object) ? $object->total_ht : '';
7805 $substitutionarray['__TOTAL_VAT__'] = is_object($object) ? (isset($object->total_vat) ? $object->total_vat : $object->total_tva) : '';
7806 }
7807 }
7808
7809 if ((empty($exclude) || !in_array('date', $exclude)) && (empty($include) || in_array('date', $include))) {
7810 include_once DOL_DOCUMENT_ROOT . '/core/lib/date.lib.php';
7811
7812 $now = dol_now();
7813
7814 $tmp = dol_getdate($now, true);
7815 $tmp2 = dol_get_prev_day($tmp['mday'], $tmp['mon'], $tmp['year']);
7816 $tmp3 = dol_get_prev_month($tmp['mon'], $tmp['year']);
7817 $tmp4 = dol_get_next_day($tmp['mday'], $tmp['mon'], $tmp['year']);
7818 $tmp5 = dol_get_next_month($tmp['mon'], $tmp['year']);
7819
7820 $daytext = $outputlangs->trans('Day' . $tmp['wday']);
7821
7822 $substitutionarray = array_merge($substitutionarray, array(
7823 '__NOW_TMS__' => (string) $now, // Must be the string that represent the int
7824 '__NOW_TMS_YMD__' => dol_print_date($now, 'day', 'auto', $outputlangs),
7825 '__DAY__' => (string) $tmp['mday'],
7826 '__DAY_TEXT__' => $daytext, // Monday
7827 '__DAY_TEXT_SHORT__' => dol_trunc($daytext, 3, 'right', 'UTF-8', 1), // Mon
7828 '__DAY_TEXT_MIN__' => dol_trunc($daytext, 1, 'right', 'UTF-8', 1), // M
7829 '__MONTH__' => (string) $tmp['mon'],
7830 '__MONTH_TEXT__' => $outputlangs->transnoentitiesnoconv('Month' . sprintf("%02d", $tmp['mon'])),
7831 '__MONTH_TEXT_SHORT__' => $outputlangs->transnoentitiesnoconv('MonthShort' . sprintf("%02d", $tmp['mon'])),
7832 '__MONTH_TEXT_MIN__' => $outputlangs->transnoentitiesnoconv('MonthVeryShort' . sprintf("%02d", $tmp['mon'])),
7833 '__YEAR__' => (string) $tmp['year'],
7834 '__YEAR_PREVIOUS_MONTH__' => (string) $tmp3['year'],
7835 '__YEAR_NEXT_MONTH__' => (string) $tmp5['year'],
7836 '__PREVIOUS_DAY__' => (string) $tmp2['day'],
7837 '__PREVIOUS_MONTH__' => (string) $tmp3['month'],
7838 '__PREVIOUS_MONTH_TEXT__' => $outputlangs->transnoentitiesnoconv('Month' . sprintf("%02d", $tmp3['month'])),
7839 '__PREVIOUS_MONTH_TEXT_SHORT__' => $outputlangs->transnoentitiesnoconv('MonthShort' . sprintf("%02d", $tmp3['month'])),
7840 '__PREVIOUS_MONTH_TEXT_MIN__' => $outputlangs->transnoentitiesnoconv('MonthVeryShort' . sprintf("%02d", $tmp3['month'])),
7841 '__PREVIOUS_YEAR__' => (string) ($tmp['year'] - 1),
7842 '__NEXT_DAY__' => (string) $tmp4['day'],
7843 '__NEXT_MONTH__' => (string) $tmp5['month'],
7844 '__NEXT_MONTH_TEXT__' => $outputlangs->transnoentitiesnoconv('Month' . sprintf("%02d", $tmp5['month'])),
7845 '__NEXT_MONTH_TEXT_SHORT__' => $outputlangs->transnoentitiesnoconv('MonthShort' . sprintf("%02d", $tmp5['month'])),
7846 '__NEXT_MONTH_TEXT_MIN__' => $outputlangs->transnoentitiesnoconv('MonthVeryShort' . sprintf("%02d", $tmp5['month'])),
7847 '__NEXT_YEAR__' => (string) ($tmp['year'] + 1),
7848 ));
7849 }
7850
7851 if (isModEnabled('multicompany')) {
7852 $substitutionarray = array_merge($substitutionarray, array('__ENTITY_ID__' => $conf->entity));
7853 }
7854 if ((empty($exclude) || !in_array('system', $exclude)) && (empty($include) || in_array('user', $include))) {
7855 $substitutionarray['__DOL_MAIN_URL_ROOT__'] = DOL_MAIN_URL_ROOT;
7856 $substitutionarray['__(AnyTranslationKey)__'] = $outputlangs->transnoentitiesnoconv('TranslationOfKey');
7857 $substitutionarray['__(AnyTranslationKey|langfile)__'] = $outputlangs->transnoentitiesnoconv('TranslationOfKey') . ' (load also language file before)';
7858 $substitutionarray['__[AnyConstantKey]__'] = $outputlangs->transnoentitiesnoconv('ValueOfConstantKey');
7859 }
7860
7861 // Note: The lazyload variables are replaced only during the call by make_substitutions, and only if necessary
7862
7863 return $substitutionarray;
7864}
7865
7882function make_substitutions($text, $substitutionarray, $outputlangs = null, $converttextinhtmlifnecessary = 0)
7883{
7884 global $db, $langs;
7885
7886 if (!is_array($substitutionarray)) {
7887 return 'ErrorBadParameterSubstitutionArrayWhenCalling_make_substitutions';
7888 }
7889
7890 if (empty($outputlangs)) {
7891 $outputlangs = $langs;
7892 }
7893
7894 // Is initial text HTML or simple text ?
7895 $msgishtml = 0;
7896 if (dol_textishtml($text, 1)) {
7897 $msgishtml = 1;
7898 }
7899
7900 // Make substitution for language keys: __(AnyTranslationKey)__ or __(AnyTranslationKey|langfile)__
7901 if (is_object($outputlangs)) {
7902 $reg = array();
7903 while (preg_match('/__\‍(([^\‍)]+)\‍)__/', $text, $reg)) {
7904 // If key is __(TranslationKey|langfile)__, then force load of langfile.lang
7905 $tmp = explode('|', $reg[1]);
7906 if (!empty($tmp[1])) {
7907 $outputlangs->load($tmp[1]);
7908 }
7909
7910 $value = $outputlangs->transnoentitiesnoconv($reg[1]);
7911
7912 if (empty($converttextinhtmlifnecessary)) {
7913 // convert $newval into HTML is necessary
7914 $text = preg_replace('/__\‍(' . preg_quote($reg[1], '/') . '\‍)__/', $msgishtml ? dol_htmlentitiesbr($value) : $value, $text);
7915 } else {
7916 if (preg_match('/__\‍(' . preg_quote($reg[1], '/') . '\‍)__/', $text)) { // If found, so replacement will be done later
7917 if (! $msgishtml) {
7918 $valueishtml = dol_textishtml($value, 1);
7919 //var_dump("valueishtml=".$valueishtml);
7920
7921 if ($valueishtml) {
7922 $text = dol_htmlentitiesbr($text);
7923 $msgishtml = 1;
7924 }
7925 } else {
7926 $value = dol_nl2br((string) $value);
7927 }
7928 }
7929 $text = preg_replace('/__\‍(' . preg_quote($reg[1], '/') . '\‍)__/', $value, $text);
7930 }
7931 }
7932 }
7933
7934 // Make substitution for constant keys.
7935 // Must be after the substitution of translation, so if the text of translation contains a string __[xxx]__, it is also converted.
7936 $reg = array();
7937 while (preg_match('/__\[([^\]]+)\]__/', $text, $reg)) {
7938 $originalkeyfound = $reg[1];
7939 $keyfound = preg_replace('/\|urlencode$/', '', $originalkeyfound);
7940
7941 if (isASecretKey($keyfound)) {
7942 $value = '*****forbidden*****';
7943 } else {
7944 $value = getDolGlobalString($keyfound);
7945 // Execute some functions on value of substitution key
7946 if (preg_match('/\|urlencode$/', $originalkeyfound)) {
7947 $value = urlencode($value);
7948 }
7949 }
7950
7951 if (empty($converttextinhtmlifnecessary)) {
7952 // convert $newval into HTML is necessary
7953 $text = preg_replace('/__\[' . preg_quote($originalkeyfound, '/') . '\]__/', $msgishtml ? dol_htmlentitiesbr($value) : $value, $text);
7954 } else {
7955 if (preg_match('/__\[' . preg_quote($originalkeyfound, '/') . '\]__/', $text)) { // If found, so replacement will be done later
7956 if (! $msgishtml) {
7957 $valueishtml = dol_textishtml($value, 1);
7958
7959 if ($valueishtml) {
7960 $text = dol_htmlentitiesbr($text);
7961 $msgishtml = 1;
7962 }
7963 } else {
7964 $value = dol_nl2br((string) $value);
7965 }
7966 }
7967 $text = preg_replace('/__\[' . preg_quote($originalkeyfound, '/') . '\]__/', $value, $text);
7968 }
7969 }
7970
7971 // Make substitution for array $substitutionarray
7972 foreach ($substitutionarray as $key => $value) {
7973 if (!isset($value)) {
7974 continue; // If value is null, it same than not having substitution key at all into array, we do not replace.
7975 }
7976
7977 if (getDolGlobalString('MAIN_MAIL_DO_NOT_USE_SIGN') && ($key == '__USER_SIGNATURE__' || $key == '__SENDEREMAIL_SIGNATURE__')) {
7978 $value = ''; // Protection
7979 }
7980 if (empty($converttextinhtmlifnecessary)) {
7981 $text = str_replace((string) $key, (string) $value, $text); // Cast to string is needed when value is 123.5 for example
7982 } else {
7983 if (strpos($text, (string) $key) !== false) { // If found, so replacement will be done later
7984 if (! $msgishtml) {
7985 $valueishtml = dol_textishtml($value, 1);
7986
7987 if ($valueishtml) {
7988 $text = dol_htmlentitiesbr($text);
7989 $msgishtml = 1;
7990 }
7991 } else {
7992 $value = dol_nl2br((string) $value);
7993 }
7994 }
7995 $text = str_replace((string) $key, (string) $value, $text); // Cast to string is needed, for 123.5 for example
7996 }
7997 }
7998
7999 /*
8000 Loop to scan $substitutionarray for couples: key=__XXX__@lazyload and value='path:class:method:id' or 'path:class:method:id:keyinarrayresult' if method return array
8001 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.
8002 If no, we don't need to make replacement, so we do nothing.
8003 If yes, we can make the substitution:
8004
8005 include_once $path;
8006 $tmpobj = new $class($db);
8007 $valuetouseforsubstitution = $tmpobj->$method($id, '__XXX__');
8008 And make the replacement of "__XXX__@lazyload" with $valuetouseforsubstitution
8009 */
8010 $memory_object_list = array();
8011 foreach ($substitutionarray as $key => $value) {
8012 $lazy_load_arr = array();
8013 if (preg_match('/(__[A-Z\_]+__)@lazyload$/', $key, $lazy_load_arr)) {
8014 if (!empty($lazy_load_arr[1])) {
8015 $key_to_substitute = $lazy_load_arr[1];
8016 if (preg_match('/' . preg_quote($key_to_substitute, '/') . '/', $text)) {
8017 $param_arr = explode(':', (string) $value);
8018 // path:class:method:id
8019 if (count($param_arr) >= 4) {
8020 $path = $param_arr[0];
8021 $class = $param_arr[1];
8022 $method = $param_arr[2];
8023 $id = (int) $param_arr[3];
8024 $keyinarrayresult = empty($param_arr[4]) ? '' : $param_arr[4];
8025
8026 // load class file and init object list in memory
8027 if (!isset($memory_object_list[$class])) {
8028 if (dol_is_file(DOL_DOCUMENT_ROOT . $path)) {
8029 require_once DOL_DOCUMENT_ROOT . $path;
8030 if (class_exists($class)) {
8031 $memory_object_list[$class] = array(
8032 'list' => array(),
8033 );
8034 }
8035 }
8036 }
8037
8038 // fetch object and set substitution
8039 if (isset($memory_object_list[$class]['list'])) {
8040 if (method_exists($class, $method)) {
8041 if (!isset($memory_object_list[$class]['list'][$id])) {
8042 $tmpobj = new $class($db);
8043 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
8044 $tmpvaluetouseforsubstitution = $tmpobj->$method($id, $key_to_substitute);
8045 $memory_object_list[$class]['list'][$id] = $tmpobj;
8046 } else {
8047 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable
8048 $tmpobj = $memory_object_list[$class]['list'][$id];
8049 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
8050 $tmpvaluetouseforsubstitution = $tmpobj->$method($id, $key_to_substitute, true);
8051 }
8052
8053 if ($keyinarrayresult) {
8054 $valuetouseforsubstitution = (string) $tmpvaluetouseforsubstitution[$keyinarrayresult]; // Cast to string in case value is 123.5 for example
8055 } else {
8056 $valuetouseforsubstitution = (string) $tmpvaluetouseforsubstitution; // Cast to string in case value is 123.5 for example
8057 }
8058 $text = str_replace((string) $key_to_substitute, $valuetouseforsubstitution, $text);
8059 }
8060 }
8061 }
8062 }
8063 }
8064 }
8065 }
8066
8067 return $text;
8068}
8069
8082function complete_substitutions_array(&$substitutionarray, $outputlangs, $object = null, $parameters = null, $callfunc = "completesubstitutionarray")
8083{
8084 global $conf, $user;
8085
8086 require_once DOL_DOCUMENT_ROOT . '/core/lib/files.lib.php';
8087
8088 // Note: substitution key for each extrafields, using key __EXTRA_XXX__ is already available into the getCommonSubstitutionArray used to build the substitution array.
8089
8090 // Check if there is external substitution to do, requested by plugins
8091 $dirsubstitutions = array_merge(array(), (array) $conf->modules_parts['substitutions']);
8092
8093 foreach ($dirsubstitutions as $reldir) {
8094 $dir = dol_buildpath($reldir, 0);
8095
8096 // Check if directory exists
8097 if (!dol_is_dir($dir)) {
8098 continue;
8099 }
8100
8101 $substitfiles = dol_dir_list($dir, 'files', 0, 'functions_');
8102 foreach ($substitfiles as $substitfile) {
8103 $reg = array();
8104 if (preg_match('/functions_(.*)\.lib\.php/i', $substitfile['name'], $reg)) {
8105 $module = $reg[1];
8106
8107 dol_syslog("Library " . $substitfile['name'] . " found into " . $dir);
8108 // Include the user's functions file
8109 require_once $dir . $substitfile['name'];
8110 // Call the user's function, and only if it is defined
8111 $function_name = $module . "_" . $callfunc;
8112 if (function_exists($function_name)) {
8113 $function_name($substitutionarray, $outputlangs, $object, $parameters);
8114 }
8115 }
8116 }
8117 }
8118 if (getDolGlobalString('ODT_ENABLE_ALL_TAGS_IN_SUBSTITUTIONS')) {
8119 // to list all tags in odt template
8120 $tags = '';
8121 foreach ($substitutionarray as $key => $value) {
8122 $tags .= '{' . $key . '} => ' . $value . "\n";
8123 }
8124 $substitutionarray = array_merge($substitutionarray, array('__ALL_TAGS__' => $tags));
8125 }
8126}
8127
8137function print_date_range($date_start, $date_end, $format = '', $outputlangs = null)
8138{
8139 print get_date_range($date_start, $date_end, $format, $outputlangs);
8140}
8141
8152function get_date_range($date_start, $date_end, $format = '', $outputlangs = null, $withparenthesis = 1)
8153{
8154 global $langs;
8155
8156 $out = '';
8157
8158 if (!is_object($outputlangs)) {
8159 $outputlangs = $langs;
8160 }
8161
8162 if ($date_start && $date_end) {
8163 $out .= ($withparenthesis ? ($withparenthesis == 1 ? ' ' : '').'(' : '') . $outputlangs->transnoentitiesnoconv('DateFromTo', dol_print_date($date_start, $format, false, $outputlangs), dol_print_date($date_end, $format, false, $outputlangs)) . ($withparenthesis ? ')' : '');
8164 }
8165 if ($date_start && !$date_end) {
8166 $out .= ($withparenthesis ? ($withparenthesis == 1 ? ' ' : '').'(' : '') . $outputlangs->transnoentitiesnoconv('DateFrom', dol_print_date($date_start, $format, false, $outputlangs)) . ($withparenthesis ? ')' : '');
8167 }
8168 if (!$date_start && $date_end) {
8169 $out .= ($withparenthesis ? ($withparenthesis == 1 ? ' ' : '').'(' : '') . $outputlangs->transnoentitiesnoconv('DateUntil', dol_print_date($date_end, $format, false, $outputlangs)) . ($withparenthesis ? ')' : '');
8170 }
8171
8172 return $out;
8173}
8174
8183function dolGetFirstLastname($firstname, $lastname, $nameorder = -1)
8184{
8185 $ret = '';
8186 // If order not defined, we use the setup
8187 if ($nameorder < 0) {
8188 $nameorder = (!getDolGlobalString('MAIN_FIRSTNAME_NAME_POSITION') ? 1 : 0);
8189 }
8190 if ($nameorder == 1) {
8191 $ret .= $firstname;
8192 if ($firstname && $lastname) {
8193 $ret .= ' ';
8194 }
8195 $ret .= $lastname;
8196 } elseif ($nameorder == 2 || $nameorder == 3) {
8197 $ret .= $firstname;
8198 if (empty($ret) && $nameorder == 3) {
8199 $ret .= $lastname;
8200 }
8201 } else { // 0, 4 or 5
8202 $ret .= $lastname;
8203 if (empty($ret) && $nameorder == 5) {
8204 $ret .= $firstname;
8205 }
8206 if ($nameorder == 0) {
8207 if ($firstname && $lastname) {
8208 $ret .= ' ';
8209 }
8210 $ret .= $firstname;
8211 }
8212 }
8213 return $ret;
8214}
8215
8216
8224function dolSort($arraytosort)
8225{
8226 sort($arraytosort);
8227 return $arraytosort;
8228}
8229
8251function dol_sort_array(&$array, $index, $order = 'asc', $natsort = 0, $case_sensitive = 0, $keepindex = 0)
8252{
8253 // Clean parameters
8254 $order = strtolower($order);
8255
8256 if (is_array($array)) {
8257 $sizearray = count($array);
8258 if ($sizearray > 0) {
8259 // Build a temp array with sorting key as value
8260 $temp = array();
8261 foreach (array_keys($array) as $key) {
8262 $tmpmultikey = explode(',', $index);
8263 $newindex = $tmpmultikey[0];
8264 if (is_object($array[$key])) {
8265 $temp[$key] = empty($array[$key]->$newindex) ? 0 : $array[$key]->$newindex;
8266 // Add other keys
8267 if (!empty($tmpmultikey[1])) {
8268 $newindex = $tmpmultikey[1];
8269 $temp[$key] .= '__' . (empty($array[$key]->$newindex) ? 0 : $array[$key]->$newindex);
8270 }
8271 } else {
8272 // @phan-suppress-next-line PhanTypeArraySuspiciousNullable,PhanTypeArraySuspicious,PhanTypeMismatchDimFetch
8273 $temp[$key] = empty($array[$key][$newindex]) ? 0 : $array[$key][$newindex];
8274 // Add other keys
8275 if (!empty($tmpmultikey[1])) {
8276 $newindex = $tmpmultikey[1];
8277 // @phan-suppress-next-line PhanTypeArraySuspicious,PhanTypeMismatchDimFetch
8278 $temp[$key] .= '__' . (empty($array[$key][$newindex]) ? 0 : $array[$key][$newindex]);
8279 }
8280 }
8281 if ($natsort == -1) {
8282 $temp[$key] = '___' . $temp[$key]; // We add a string at begin of value to force an alpha order when using asort.
8283 }
8284 }
8285 if (empty($natsort) || $natsort == -1) {
8286 if ($order == 'asc') {
8287 asort($temp);
8288 } else {
8289 arsort($temp);
8290 }
8291 } else {
8292 if ($case_sensitive) {
8293 natsort($temp);
8294 } else {
8295 natcasesort($temp); // natecasesort is not sensible to case
8296 }
8297 if ($order != 'asc') {
8298 $temp = array_reverse($temp, true);
8299 }
8300 }
8301
8302 $sorted = array();
8303
8304 foreach (array_keys($temp) as $key) {
8305 (is_numeric($key) && empty($keepindex)) ? $sorted[] = $array[$key] : $sorted[$key] = $array[$key];
8306 }
8307
8308 return $sorted;
8309 }
8310 }
8311 return $array;
8312}
8313
8314
8322function utf8_check($str)
8323{
8324 $str = (string) $str; // Sometimes string is an int.
8325
8326 // We must use here a binary strlen function (so not dol_strlen)
8327 $strLength = strlen($str);
8328 for ($i = 0; $i < $strLength; $i++) {
8329 if (ord($str[$i]) < 0x80) {
8330 continue; // 0bbbbbbb
8331 } elseif ((ord($str[$i]) & 0xE0) == 0xC0) {
8332 $n = 1; // 110bbbbb
8333 } elseif ((ord($str[$i]) & 0xF0) == 0xE0) {
8334 $n = 2; // 1110bbbb
8335 } elseif ((ord($str[$i]) & 0xF8) == 0xF0) {
8336 $n = 3; // 11110bbb
8337 } elseif ((ord($str[$i]) & 0xFC) == 0xF8) {
8338 $n = 4; // 111110bb
8339 } elseif ((ord($str[$i]) & 0xFE) == 0xFC) {
8340 $n = 5; // 1111110b
8341 } else {
8342 return false; // Does not match any model
8343 }
8344 for ($j = 0; $j < $n; $j++) { // n bytes matching 10bbbbbb follow ?
8345 if ((++$i == strlen($str)) || ((ord($str[$i]) & 0xC0) != 0x80)) {
8346 return false;
8347 }
8348 }
8349 }
8350 return true;
8351}
8352
8360function utf8_valid($str)
8361{
8362 /* 2 other methods to test if string is utf8
8363 $validUTF8 = mb_check_encoding($messagetext, 'UTF-8');
8364 $validUTF8b = ! (false === mb_detect_encoding($messagetext, 'UTF-8', true));
8365 */
8366 return preg_match('//u', $str) ? true : false;
8367}
8368
8369
8376function ascii_check($str)
8377{
8378 if (function_exists('mb_check_encoding')) {
8379 //if (mb_detect_encoding($str, 'ASCII', true) return false;
8380 if (!mb_check_encoding($str, 'ASCII')) {
8381 return false;
8382 }
8383 } else {
8384 if (preg_match('/[^\x00-\x7f]/', $str)) {
8385 return false; // Contains a byte > 7f
8386 }
8387 }
8388
8389 return true;
8390}
8391
8392
8400function dol_osencode($str)
8401{
8402 $tmp = ini_get("unicode.filesystem_encoding");
8403 if (empty($tmp) && !empty($_SERVER["WINDIR"])) {
8404 $tmp = 'iso-8859-1'; // By default for windows
8405 }
8406 if (empty($tmp)) {
8407 $tmp = 'utf-8'; // By default for other
8408 }
8409 if (getDolGlobalString('MAIN_FILESYSTEM_ENCODING')) {
8410 $tmp = getDolGlobalString('MAIN_FILESYSTEM_ENCODING');
8411 }
8412
8413 if ($tmp == 'iso-8859-1') {
8414 return mb_convert_encoding($str, 'ISO-8859-1', 'UTF-8');
8415 }
8416 return $str;
8417}
8418
8419
8435function dol_getIdFromCode($db, $key, $tablename, $fieldkey = 'code', $fieldid = 'id', $entityfilter = 0, $filters = '', $useCache = true)
8436{
8437 global $conf;
8438
8439 // If key empty
8440 if ($key == '') {
8441 return 0;
8442 }
8443
8444 // Check in cache
8445 if ($useCache && isset($conf->cache['codeid'][$tablename][$key][$fieldid])) { // Can be defined to 0 or ''
8446 return $conf->cache['codeid'][$tablename][$key][$fieldid]; // Found in cache
8447 }
8448
8449 dol_syslog('dol_getIdFromCode (value for field ' . $fieldid . ' from key ' . $key . ' not found into cache)', LOG_DEBUG);
8450
8451 $sql = "SELECT " . $db->sanitize($fieldid) . " as valuetoget";
8452 $sql .= " FROM " . MAIN_DB_PREFIX . $db->sanitize($tablename);
8453 if ($fieldkey == 'id' || $fieldkey == 'rowid') {
8454 $sql .= " WHERE " . $db->sanitize($fieldkey) . " = " . ((int) $key);
8455 } else {
8456 $sql .= " WHERE " . $db->sanitize($fieldkey) . " = '" . $db->escape($key) . "'";
8457 }
8458 if (!empty($entityfilter)) {
8459 $sql .= " AND entity IN (" . getEntity($tablename) . ")";
8460 }
8461 if ($filters) {
8462 $sql .= $filters; // @phan-suppress-current-line SqlInjection
8463 }
8464
8465 $resql = $db->query($sql);
8466 if ($resql) {
8467 $obj = $db->fetch_object($resql);
8468 $valuetoget = '';
8469 if ($obj) {
8470 $valuetoget = $obj->valuetoget;
8471 $conf->cache['codeid'][$tablename][$key][$fieldid] = $valuetoget;
8472 } else {
8473 $conf->cache['codeid'][$tablename][$key][$fieldid] = '';
8474 }
8475 $db->free($resql);
8476
8477 return $valuetoget;
8478 } else {
8479 return -1;
8480 }
8481}
8482
8492function isStringVarMatching($var, $regextext, $matchrule = 1)
8493{
8494 // Tolerate callers (custom modules, older code) that already pass a full regex with delimiters
8495 // like '/^(aaa|bbb)/' instead of the bare body. Without this, the function would build
8496 // '/^/^(aaa|bbb)//' which trips preg_match() with 'Unknown modifier ^'.
8497 $regextext = preg_replace('#^/\^?#', '', (string) $regextext);
8498 $regextext = preg_replace('#\$?/[imsxuADSUXJ]*$#', '', $regextext);
8499
8500 if ($matchrule == 1) {
8501 if ($var == 'mainmenu') {
8502 global $mainmenu;
8503 return (preg_match('/^' . $regextext . '/', $mainmenu));
8504 } elseif ($var == 'leftmenu') {
8505 global $leftmenu;
8506 return (preg_match('/^' . $regextext . '/', $leftmenu));
8507 } else {
8508 return 'This variable is not accessible with dol_eval';
8509 }
8510 } else {
8511 return 'This value '.$matchrule.' for param $matchrule is not yet implemented';
8512 }
8513}
8514
8515
8528function dolEvalSimpleCondition($s)
8529{
8530 global $conf, $user, $leftmenu, $mainmenu;
8531
8532 $s = trim((string) $s);
8533 if ($s === '1' || $s === 'true') {
8534 return true;
8535 }
8536 if ($s === '0' || $s === 'false') {
8537 return false;
8538 }
8539 // Only the characters of the known terms, and parentheses only around the quoted arguments of a call
8540 if (!preg_match('/^[a-zA-Z0-9_$>=!&|\s\'",()-]+$/', $s)) {
8541 return null;
8542 }
8543 if (strpbrk($s, '()') !== false && !preg_match('/^(?:[^()]*\‍((?:\s*[\'"][a-zA-Z0-9_]+[\'"]\s*)(?:,\s*[\'"][a-zA-Z0-9_]+[\'"]\s*)*\‍))*[^()]*$/', $s)) {
8544 return null;
8545 }
8546 $hasand = (strpos($s, '&&') !== false);
8547 $hasor = (strpos($s, '||') !== false);
8548 if ($hasand && $hasor) {
8549 return null;
8550 }
8551 $terms = ($hasor ? explode('||', $s) : ($hasand ? explode('&&', $s) : array($s)));
8552
8553 $result = null;
8554 foreach ($terms as $term) {
8555 $term = trim($term);
8556 $negation = false;
8557 if (substr($term, 0, 1) === '!') {
8558 $negation = true;
8559 $term = ltrim(substr($term, 1));
8560 }
8561 $reg = array();
8562 if (preg_match('/^isModEnabled\‍(\s*[\'"]([a-zA-Z0-9_]+)[\'"]\s*\‍)$/', $term, $reg)) {
8563 $value = isModEnabled($reg[1]);
8564 } elseif (preg_match('/^\$user->hasRight\‍(\s*[\'"]([a-zA-Z0-9_]+)[\'"]\s*,\s*[\'"]([a-zA-Z0-9_]+)[\'"]\s*(?:,\s*[\'"]([a-zA-Z0-9_]+)[\'"]\s*)?\‍)$/', $term, $reg)) {
8565 $value = (bool) (!empty($reg[3]) ? $user->hasRight($reg[1], $reg[2], $reg[3]) : $user->hasRight($reg[1], $reg[2]));
8566 } elseif (preg_match('/^\$user->rights->([a-zA-Z0-9_]+)->([a-zA-Z0-9_]+)(?:->([a-zA-Z0-9_]+))?$/', $term, $reg)) {
8567 if (!empty($reg[3])) {
8568 $value = !empty($user->rights->{$reg[1]}->{$reg[2]}->{$reg[3]});
8569 } else {
8570 $value = !empty($user->rights->{$reg[1]}->{$reg[2]});
8571 }
8572 } elseif ($term === '$user->admin') {
8573 $value = !empty($user->admin);
8574 } elseif (preg_match('/^\$conf->([a-zA-Z0-9_]+)->enabled$/', $term, $reg)) {
8575 $value = !empty($conf->{$reg[1]}->enabled);
8576 } elseif (preg_match('/^getDolGlobal(String|Int)\‍(\s*[\'"]([a-zA-Z0-9_]+)[\'"]\s*\‍)$/', $term, $reg)) {
8577 $value = ($reg[1] == 'Int' ? (bool) getDolGlobalInt($reg[2]) : (bool) getDolGlobalString($reg[2]));
8578 } elseif (preg_match('/^\$(leftmenu|mainmenu)\s*(==|!=)\s*[\'"]([a-zA-Z0-9_]*)[\'"]$/', $term, $reg)) {
8579 $current = ($reg[1] == 'leftmenu' ? $leftmenu : $mainmenu);
8580 $value = ($reg[2] == '==' ? ($current == $reg[3]) : ($current != $reg[3]));
8581 } elseif ($term === '1' || $term === 'true') {
8582 $value = true;
8583 } elseif ($term === '0' || $term === 'false') {
8584 $value = false;
8585 } else {
8586 return null; // Not a known term, the caller will use eval()
8587 }
8588 if ($negation) {
8589 $value = !$value;
8590 }
8591 if ($result === null) {
8592 $result = $value;
8593 } elseif ($hasor) {
8594 $result = ($result || $value);
8595 } else {
8596 $result = ($result && $value);
8597 }
8598 }
8599
8600 return $result;
8601}
8602
8612function verifCond($strToEvaluate, $onlysimplestring = '1')
8613{
8614 //print $strToEvaluate."<br>\n";
8615 $rights = true;
8616 if (isset($strToEvaluate) && $strToEvaluate !== '') {
8617 // Most of the conditions are simple (isModEnabled('xxx'), $user->hasRight('xxx', 'yyy'), $conf->xxx->enabled...): they are
8618 // evaluated directly, without eval() and its checks, when they match one of the known shapes.
8619 $rights = dolEvalSimpleCondition($strToEvaluate);
8620 if ($rights !== null) {
8621 return $rights;
8622 }
8623 //var_dump($strToEvaluate);
8624 //$rep = dol_eval($strToEvaluate, 1, 0, '1'); // to show the error
8625 $rep = dol_eval($strToEvaluate, 1, 1, $onlysimplestring); // The dol_eval() must contains all the "global $xxx;" for all variables $xxx found into the string condition
8626
8627 // On string syntax error, dol_eval may return a string that start with 'Bad call of ...' or 'Bad string syntax to evaluate...' !!!
8628 //var_dump($strToEvaluate, $rep);
8629 $rights = (bool) $rep && (!is_string($rep) || (strpos($rep, 'Exception during') === false && strpos($rep, 'Bad call of') === false && strpos($rep, 'Bad string syntax to evaluate') === false));
8630 //var_dump($rights);
8631 }
8632 return $rights;
8633}
8634
8650function dol_eval($s, $returnvalue = 1, $hideerrors = 1, $onlysimplestring = '1')
8651{
8652 if ($returnvalue != 1) {
8653 dol_syslog("Use of dol_eval with parameter returnvalue = 0 is now forbidden. Please fix this", LOG_ERR);
8654 }
8655
8656 global $dolibarr_main_use_dol_eval_new; // experimental option, not yet ready
8657 if (!empty($dolibarr_main_use_dol_eval_new)) {
8658 return dol_eval_new($s);
8659 } else {
8660 return dol_eval_standard($s, $hideerrors, $onlysimplestring);
8661 }
8662}
8663
8674function dol_eval_new($s)
8675{
8676 // Only this global variables can be read by eval function and returned to caller
8677 global $conf, // Read of const is done with getDolGlobalString() but we need $conf->currency for example
8678 $db, $langs, $user, $website, $websitepage,
8679 $action, $mainmenu, $leftmenu,
8680 $mysoc,
8681 $objectoffield, // To allow the use of $objectoffield in computed fields
8682
8683 // Old variables used
8684 $object;
8685
8686 if (getDolGlobalString('MAIN_ALLOW_OLD_VAR_OBJ_IN_DOL_EVAL')) {
8687 global $obj; // To get $obj used into list when dol_eval() is used for computed fields and $obj is not yet $object
8688 }
8689
8690 // PHP < 7.4.0
8691 defined('T_COALESCE_EQUAL') || define('T_COALESCE_EQUAL', PHP_INT_MAX);
8692 defined('T_FN') || define('T_FN', PHP_INT_MAX);
8693
8694 // PHP < 8.0.0
8695 defined('T_ATTRIBUTE') || define('T_ATTRIBUTE', PHP_INT_MAX);
8696 defined('T_MATCH') || define('T_MATCH', PHP_INT_MAX);
8697 defined('T_NAME_FULLY_QUALIFIED') || define('T_NAME_FULLY_QUALIFIED', PHP_INT_MAX);
8698 defined('T_NAME_QUALIFIED') || define('T_NAME_QUALIFIED', PHP_INT_MAX);
8699 defined('T_NAME_RELATIVE') || define('T_NAME_RELATIVE', PHP_INT_MAX);
8700
8701 // PHP < 8.1.0
8702 defined('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG') || define('T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG', PHP_INT_MAX);
8703 defined('T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG') || define('T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG', PHP_INT_MAX);
8704 defined('T_ENUM') || define('T_ENUM', PHP_INT_MAX);
8705 defined('T_READONLY') || define('T_READONLY', PHP_INT_MAX);
8706
8707 // PHP < 8.4.0
8708 defined('T_PRIVATE_SET') || define('T_PRIVATE_SET', PHP_INT_MAX);
8709 defined('T_PROTECTED_SET') || define('T_PROTECTED_SET', PHP_INT_MAX);
8710 defined('T_PUBLIC_SET') || define('T_PUBLIC_SET', PHP_INT_MAX);
8711
8712 $prohibited_token_ids = [
8713 /*
8714 * Prohibited int tokens
8715 */
8716
8717 // T_AND_EQUAL', 'T_ARRAY', 'T_ARRAY_CAST', 'T_AS',
8718 'T_ABSTRACT',
8719 'T_AMPERSAND_FOLLOWED_BY_VAR_OR_VARARG',
8720 'T_AMPERSAND_NOT_FOLLOWED_BY_VAR_OR_VARARG',
8721 'T_ATTRIBUTE',
8722 // 'T_BOOLEAN_AND', 'T_BOOLEAN_OR', 'T_BOOL_CAST', 'T_BREAK',
8723 'T_BAD_CHARACTER',
8724 // 'T_CASE', 'T_CLASS_C', 'T_CLONE', 'T_COALESCE', 'T_COALESCE_EQUAL', 'T_COMMENT', 'T_CONCAT_EQUAL',
8725 // 'T_CONSTANT_ENCAPSED_STRING', 'T_CONTINUE', 'T_CURLY_OPEN',
8726 'T_CALLABLE',
8727 'T_CATCH',
8728 'T_CLASS',
8729 'T_CLOSE_TAG',
8730 'T_CONST',
8731 // 'T_DEC', 'T_DEFAULT', 'T_DIV_EQUAL', 'T_DNUMBER', 'T_DO', 'T_DOC_COMMENT',
8732 // 'T_DOLLAR_OPEN_CURLY_BRACES', 'T_DOUBLE_ARROW', 'T_DOUBLE_CAST', 'T_DOUBLE_COLON',
8733 'T_DECLARE',
8734 'T_DIR',
8735 // 'T_ELLIPSIS', 'T_ELSE', 'T_ELSEIF', 'T_EMPTY', 'T_ENCAPSED_AND_WHITESPACE', 'T_ENDFOR',
8736 // 'T_ENDFOREACH', 'T_ENDIF', 'T_ENDSWITCH', 'T_ENDWHILE', 'T_END_HEREDOC',
8737 'T_ECHO',
8738 'T_ENDDECLARE',
8739 'T_ENUM',
8740 'T_EVAL',
8741 'T_EXIT',
8742 'T_EXTENDS',
8743 // 'T_FOR', 'T_FOREACH',
8744 'T_FILE',
8745 'T_FINAL',
8746 'T_FINALLY',
8747 'T_FN',
8748 'T_FUNCTION',
8749 'T_FUNC_C',
8750 'T_GLOBAL',
8751 'T_GOTO',
8752 'T_HALT_COMPILER',
8753 // 'T_IF', 'T_INC', 'T_INLINE_HTML', 'T_INSTANCEOF', 'T_INT_CAST', 'T_ISSET', 'T_IS_EQUAL', 'T_IS_GREATER_OR_EQUAL',
8754 // 'T_IS_IDENTICAL', 'T_IS_NOT_EQUAL', 'T_IS_NOT_IDENTICAL', 'T_IS_SMALLER_OR_EQUAL',
8755 'T_IMPLEMENTS',
8756 'T_INCLUDE',
8757 'T_INCLUDE_ONCE',
8758 'T_INSTEADOF',
8759 'T_INTERFACE',
8760 // 'T_LIST', 'T_LNUMBER', 'T_LOGICAL_AND', 'T_LOGICAL_OR', 'T_LOGICAL_XOR',
8761 'T_LINE',
8762 // 'T_MINUS_EQUAL', 'T_MOD_EQUAL', 'T_MUL_EQUAL',
8763 'T_METHOD_C',
8764 // 'T_NEW',
8765 // 'T_NS_SEPARATOR', 'T_NUM_STRING',
8766 'T_NAMESPACE',
8767 // 'T_NAME_FULLY_QUALIFIED', 'T_NAME_QUALIFIED', 'T_NAME_RELATIVE', 'T_NS_C',
8768 // 'T_OBJECT_CAST', 'T_OBJECT_OPERATOR', 'T_OR_EQUAL',
8769 'T_OPEN_TAG',
8770 'T_OPEN_TAG_WITH_ECHO',
8771 // 'T_PAAMAYIM_NEKUDOTAYIM', 'T_PLUS_EQUAL', 'T_POW', 'T_POW_EQUAL',
8772 'T_PRINT',
8773 'T_PRIVATE',
8774 'T_PROTECTED',
8775 'T_PUBLIC',
8776 // 'T_PROPERTY_C',
8777 'T_READONLY',
8778 'T_REQUIRE',
8779 'T_REQUIRE_ONCE',
8780 'T_RETURN',
8781 // 'T_SL', 'T_SL_EQUAL', 'T_SPACESHIP', 'T_SR', 'T_SR_EQUAL', 'T_START_HEREDOC', 'T_STATIC',
8782 // 'T_STRING', 'T_STRING_CAST', 'T_STRING_VARNAME', 'T_SWITCH',
8783 'T_STATIC',
8784 'T_THROW',
8785 'T_TRAIT',
8786 'T_TRAIT_C',
8787 'T_TRY',
8788 'T_UNSET',
8789 'T_UNSET_CAST',
8790 'T_USE',
8791 // 'T_VARIABLE',
8792 'T_VAR',
8793 // 'T_WHILE', 'T_WHITESPACE',
8794 // 'T_XOR_EQUAL',
8795 // 'T_YIELD', 'T_YIELD_FROM',
8796
8797 /*
8798 * Prohibited string tokens
8799 */
8800 ';',
8801 '`',
8802 ];
8803
8804 $prohibited_variables = [
8805 '$_COOKIE',
8806 '$_ENV',
8807 '$_FILES',
8808 '$GLOBALS',
8809 '$_GET',
8810 '$_POST',
8811 '$_REQUEST',
8812 '$_SERVER',
8813 '$_SESSION',
8814 ];
8815
8816 $forbiddenphpfunctions = array();
8817 $forbiddenphpmethods = array();
8818
8819 // Same list than in dol_eval
8820 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("override_function", "session_id", "session_create_id", "session_regenerate_id"));
8821 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("get_defined_functions", "get_defined_vars", "get_defined_constants", "get_declared_classes"));
8822 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("function"));
8823
8824 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("GETPOST")); // native dolibarr functions
8825
8826 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("ob_start"));
8827
8828 // Functions with callable parameters
8829 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("call_user_func", "call_user_func_array"));
8830 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("array_all", "array_any", "array_diff_ukey", "array_filter", "array_find", "array_find_key", "array_map", "array_reduce", "array_intersect_uassoc", "array_intersect_ukey", "array_walk", "array_walk_recursive"));
8831 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("usort", "uasort", "uksort"));
8832 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("preg_replace_callback", "preg_replace_callback_array", "header_register_callback"));
8833 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("error_log", "set_error_handler", "set_exception_handler", "libxml_set_external_entity_loader", "register_shutdown_function", "register_tick_function", "unregister_tick_function"));
8834 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("spl_autoload_register", "spl_autoload_unregister", "iterator_apply", "session_set_save_handler"));
8835 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("forward_static_call", "forward_static_call_array", "register_postsend_function"));
8836 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("readline_completion_function", "readline_callback_handler_install"));
8837
8838 // Exec functions
8839 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("exec", "passthru", "shell_exec", "system", "proc_open", "popen"));
8840 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("pcntl_alarm", "pcntl_exec", "pcntl_fork", "pcntl_waitpid", "pcntl_wait", "pcntl_wifexited", "pcntl_wifstopped", "pcntl_wifsignaled", "pcntl_wifcontinued", "pcntl_wexitstatus", "pcntl_wtermsig", "pcntl_wstopsig", "pcntl_signal"));
8841 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("pcntl_signal_get_handler", "pcntl_signal_dispatch", "pcntl_get_last_error", "pcntl_strerror", "pcntl_sigprocmask", "pcntl_sigwaitinfo", "pcntl_sigtimedwait", "pcntl_getpriority", "pcntl_async_signals", "pcntl_unshare", ));
8842 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("putenv", "dl", "apache_child_terminate", "apache_setenv"));
8843 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_eval", "dol_eval_new", "dol_eval_standard", "executeCLI", "verifCond", "dolEncrypt", "dolDecrypt")); // native dolibarr functions
8844 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("eval", "create_function", "assert", "mb_ereg_replace")); // function with eval capabilities
8845
8846 // Include functions
8847 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include", "require_once", "include_once"));
8848
8849 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_compress_dir", "dol_decode", "dol_dir_list", "dol_dir_list_in_database", "dol_delete_file", "dol_delete_dir", "dol_delete_dir_recursive", "dol_copy", "archiveOrBackupFile")); // more dolibarr functions
8850 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("chdir", "dir", "fopen", "file", "file_exists", "file_get_contents", "file_put_contents", "fget", "fgetc", "fgetcsv", "flock", "fputs", "fputscsv", "fpassthru", "fscanf", "fseek", "fwrite", "is_file", "is_dir", "is_link", "mkdir", "opendir", "rmdir", "scandir", "symlink", "touch", "unlink", "umask"));
8851
8852 if (!getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL')) { // We disallow all function that allow to obfuscate the real name of a function
8853 // @phpcs:ignore
8854 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("base64" . "_" . "decode", "rawurl" . "decode", "url" . "decode", "str" . "_rot13", "hex" . "2bin", "printf", "sprintf")); // name of forbidden functions are split to avoid false positive
8855 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_concat", "dol_concatdesc")); // native dolibarr functions
8856 }
8857
8858 $forbiddenphpmethods = array_merge($forbiddenphpmethods, array('invoke', 'invokeArgs')); // Methods of ReflectionFunction to execute a function
8859
8860 $prohibited_functions = array_merge($forbiddenphpfunctions, $forbiddenphpmethods);
8861
8862 $prohibited_token_arrangements = [
8863 // Variable functions "$a(", '"$a"(', "'FN_NAME'(", ('FN_NAME')()
8864 ' T_VARIABLE ( ',
8865 ' " ( ',
8866 ' \' ( ',
8867 ' T_CONSTANT_ENCAPSED_STRING ( ',
8868 ' ) ( ',
8869 ];
8870
8871 $tokens = token_get_all("<?php return {$s};", TOKEN_PARSE);
8872
8873 $tokens_arrangement = ' ';
8874
8875 for ($i = 2, $c = count($tokens) - 1; $i < $c; ++$i) { // ignore <?php return and ;
8876 if (is_array($tokens[$i])) {
8877 $token_id = $tokens[$i][0];
8878 $token_value = $tokens[$i][1];
8879 $token_name = token_name($tokens[$i][0]);
8880 } else {
8881 $token_id = $tokens[$i];
8882 $token_value = $tokens[$i];
8883 $token_name = $tokens[$i];
8884 }
8885
8886 // Ignore whitespaces
8887 if (T_WHITESPACE === $token_id) {
8888 continue;
8889 }
8890
8891 // Keep history to check arrangements
8892 $tokens_arrangement .= "{$token_name} ";
8893
8894 // Prohibited Variables
8895 if (
8896 T_VARIABLE === $token_id
8897 && in_array($token_value, $prohibited_variables, true)
8898 ) {
8899 return "Bad string syntax to evaluate. « {$token_value} » is prohibited in « {$s} »";
8900 }
8901
8902 // Prohibited Functions
8903 if (
8904 T_STRING === $token_id
8905 && in_array($token_value, $prohibited_functions, true)
8906 ) {
8907 return "Bad string syntax to evaluate. « {$token_value} » is prohibited in « {$s} »";
8908 }
8909 }
8910
8911 // Prohibited Token IDs
8912 $maxi = count($prohibited_token_ids);
8913 for ($i = 0; $i < $maxi; ++$i) {
8914 if (false !== strpos($tokens_arrangement, " {$prohibited_token_ids[$i]} ")) {
8915 return "Bad string syntax to evaluate. « {$prohibited_token_ids[$i]} » is prohibited in « {$s} »";
8916 }
8917 }
8918
8919 // Prohibited token arrangements
8920 $maxi = count($prohibited_token_arrangements);
8921 for ($i = 0; $i < $maxi; ++$i) {
8922 if (false !== strpos($tokens_arrangement, $prohibited_token_arrangements[$i])) {
8923 return "Bad string syntax to evaluate. « {$prohibited_token_arrangements[$i]} » is prohibited in « {$s} »";
8924 }
8925 }
8926
8927 // Return result
8928 try {
8929 return @eval("return {$s};") ?? '';
8930 } catch (Throwable $ex) {
8931 return "Bad string syntax to evaluate. Exception during evaluation: " . $s . " - " . $ex->getMessage();
8932 }
8933}
8934
8949function dol_eval_standard($s, $hideerrors = 1, $onlysimplestring = '1')
8950{
8951 // Only this global variables can be read by eval function and returned to caller
8952 // The less we have, the better it is.
8953
8954 global $conf; // TODO Remove this to exclude $conf. We can read $conf->module->enabled with isModEnabled(), $conf->global->xxx properties with getDolGlobalString(), $conf->currency with getDolCurrency(), $conf->entity with getDolEntity()
8955 global $db, $langs, $user, $website, $websitepage;
8956 global $action, $mainmenu, $leftmenu;
8957 global $mysoc;
8958 global $objectoffield; // To allow the use of $objectoffield in computed fields
8959 global $object;
8960
8961 // Old variables (deprecated since v23)
8962 if (getDolGlobalString('MAIN_ALLOW_OLD_VAR_OBJ_IN_DOL_EVAL')) {
8963 global $obj; // To get $obj used into list when dol_eval() is used for computed fields and $obj is not yet $objectoffield
8964 }
8965
8966 $isObBufferActive = false; // When true, the ObBuffer must be cleaned in the exception handler
8967 if ($onlysimplestring == '0') { // '0' is deprecated, we process it as the more secured '1'
8968 $onlysimplestring = '1';
8969 }
8970 if (!in_array($onlysimplestring, array('1', '2'))) {
8971 return "Bad call of dol_eval. Parameter onlysimplestring must be '1' or '2'.";
8972 }
8973 if (!is_scalar($s)) {
8974 return "Bad call of dol_eval. First parameter must be a string, found ".var_export($s, true);
8975 }
8976
8977 try {
8978 global $dolibarr_main_restrict_eval_methods;
8979
8980 // Set $dolibarr_main_restrict_eval_methods_array
8981 if (!isset($dolibarr_main_restrict_eval_methods)) {
8982 $dolibarr_main_restrict_eval_methods = 'getDolGlobalString, getDolGlobalInt, getDolCurrency, getDolEntity, getDolDBType, fetchNoCompute, hasRight, isAdmin, isExternalUser, isModEnabled, isStringVarMatching, abs, min, max, round, dol_now, preg_match';
8983 }
8984 //print '$dolibarr_main_restrict_eval_methods = '.$dolibarr_main_restrict_eval_methods."\n";
8985 $dolibarr_main_restrict_eval_methods_array = explode(',', str_replace(" ", "", $dolibarr_main_restrict_eval_methods));
8986
8987 // Test on dangerous char (used for RCE), we allow only characters to make PHP variable testing
8988 // We must accept with 1: '1 && getDolGlobalInt("doesnotexist1") && getDolGlobalString("MAIN_FEATURES_LEVEL")'
8989 // We must accept with 1: '$user->hasRight("cabinetmed", "read") && !$objectoffield->canvas == "patient@cabinetmed"'
8990 // We must accept with 2: (($var1 = new Task($db)) && ($var1->fetchNoCompute($object->id) <= 99) && ($var2 = new Project($db)) && ($var2->fetchNoCompute($var1->fk_project) > 0)) ? $var2->ref : "Parent project not found"
8991
8992 // Check if there is dynamic call (first we check chars are all into a whitelist chars)
8993 $specialcharsallowed = '^$_+-.*>&|=!?():"\',/@';
8994 if ($onlysimplestring == '2') {
8995 $specialcharsallowed .= '<[]'; // Later we check that < has space before and after
8996 }
8997 global $dolibarr_main_allow_unsecured_special_chars_in_dol_eval;
8998 if (!empty($dolibarr_main_allow_unsecured_special_chars_in_dol_eval)) {
8999 $specialcharsallowed .= (string) $dolibarr_main_allow_unsecured_special_chars_in_dol_eval;
9000 }
9001 if (preg_match('/[^a-z0-9\s' . preg_quote($specialcharsallowed, '/') . ']/i', $s)) {
9002 return 'Bad string syntax to evaluate (found chars that are not chars for a simple one line clean eval string): ' . $s;
9003 }
9004
9005 // Check if we found a | without a space before and after
9006 /* Disabled to allow preg_match('/(AAA|BBB)/')
9007 $tmps = str_replace(' || ', '__XXX__', $s);
9008 if (strpos($tmps, '|') !== false) {
9009 return 'Bad string syntax to evaluate (The char | can be used only when duplicated || with a space before and after): ' . $s;
9010 }
9011 */
9012
9013 // Check if there is PHP comments (can be used to obfuscate code)
9014 if (strpos($s, '/*') !== false || strpos($s, '//') !== false) {
9015 return 'Bad string syntax to evaluate (The comment string /* and // are not allowed): ' . $s;
9016 }
9017
9018 // Check if we found a ? without a space before and after
9019 $tmps = str_replace(' ? ', '__XXX__', $s);
9020 if (strpos($tmps, '?') !== false) {
9021 return 'Bad string syntax to evaluate (The char ? can be used only with a space before and after): ' . $s;
9022 }
9023
9024 // Check if there is a < or <= without spaces after
9025 if (preg_match('/<=?[^\s]/', $s)) {
9026 return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found a < or <= without space after): ' . $s;
9027 }
9028
9029 // Check if there is an include or a require
9030 if (preg_match('/(include|include_once|require|require_once)/', $s)) {
9031 return 'Bad string syntax to evaluate (found not allowed key include|include_once|require|require_once): ' . $s;
9032 }
9033
9034 // Check if there is dynamic call (first we use black list patterns)
9035 if (preg_match('/\$[\w]*\s*\‍(/', $s)) {
9036 return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found a call using "$abc(" or "$abc (" instead of using the direct name of the function): ' . $s;
9037 }
9038
9039 if (empty($dolibarr_main_restrict_eval_methods)) {
9040 // If $dolibarr_main_restrict_eval_methods was set to '', we must check if we try dynamic call
9041
9042 // First we remove white list pattern of using parenthesis then testing if one open parenthesis exists
9043 $savescheck = '';
9044 $scheck = $s;
9045 while ($scheck && $savescheck != $scheck) {
9046 $savescheck = $scheck;
9047 $scheck = preg_replace('/->[a-zA-Z0-9_]+\‍(/', '->__METHOD__', $scheck); // accept parenthesis in '...->method(...'
9048 $scheck = preg_replace('/::[a-zA-Z0-9_]+\‍(/', '->__METHOD__', $scheck); // accept parenthesis in '...::method(...'
9049 $scheck = preg_replace('/^\‍(+/', '__PARENTHESIS__ ', $scheck); // accept parenthesis in '(...'. Must replace with "__PARENTHESIS__ with a space after "to allow following substitutions
9050 $scheck = preg_replace('/\&\&\s+\‍(/', '__ANDPARENTHESIS__ ', $scheck); // accept parenthesis in '&& ('. Must replace with "__PARENTHESIS__ with a space after" to allow following substitutions
9051 $scheck = preg_replace('/\|\|\s+\‍(/', '__ORPARENTHESIS__ ', $scheck); // accept parenthesis in '|| ('. Must replace with "__PARENTHESIS__ with a space after" to allow following substitutions
9052 $scheck = preg_replace('/^!?[a-zA-Z0-9_]+\‍(/', '__FUNCTION__', $scheck); // accept parenthesis in 'function(' and '!function('
9053 $scheck = preg_replace('/\s!?[a-zA-Z0-9_]+\‍(/', '__FUNCTION__', $scheck); // accept parenthesis in '... function(' and '... !function('
9054 $scheck = preg_replace('/^!\‍(/', '__NOTANDPARENTHESIS__', $scheck); // accept parenthesis in '!('
9055 $scheck = preg_replace('/\s!\‍(/', ' __NOTANDPARENTHESIS__', $scheck); // accept parenthesis in '... !('
9056 $scheck = preg_replace('/(\^|\')\‍(/', '__REGEXSTART__', $scheck); // To allow preg_match('/^(aaa|bbb)/'... or isStringVarMatching('leftmenu', '(aaa|bbb)')
9057 }
9058 //print 'scheck='.$scheck." : ".strpos($scheck, '(')."<br>\n";
9059
9060 // Now test if it remains 1 open parenthesis.
9061 if (strpos($scheck, '(') !== false) {
9062 return 'Bad string syntax to evaluate (mode ' . $onlysimplestring . ', found call of a function or method without using the direct name of the function): ' . $s;
9063 }
9064 }
9065
9066 if (strpos($s, '`') !== false) {
9067 return 'Bad string syntax to evaluate (backtick char is forbidden): ' . $s;
9068 }
9069
9070 // Disallow also concat operator
9071 if (!getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL')) {
9072 if (preg_match('/[^0-9]+\.[^0-9]+/', $s)) { // We refuse . if not between 2 numbers
9073 return 'Bad string syntax to evaluate (dot char is forbidden if not strictly between 2 numbers): ' . $s;
9074 }
9075 }
9076
9077 // We exclude string using a $ character that are not an expected global or temporary vars, so that are not:
9078 // $db, $langs, $leftmenu, $topmenu, $user, $langs, $objectoffield, $var....
9079 $savescheck = '';
9080 $scheck = $s;
9081 while ($scheck && $savescheck != $scheck) {
9082 $savescheck = $scheck;
9083 $scheck = preg_replace('/\$conf->[a-z\_]+->enabled/', '__VARCONFENABLED__', $scheck); // Remove this once $user->module->enabled has been replaced everywhere with isModEnabled.
9084 $scheck = preg_replace('/\$user->id/', '__VARUSERID__', $scheck);
9085 $scheck = preg_replace('/\$user->hasRight/', '__VARUSERHASRIGHT__', $scheck);
9086 $scheck = preg_replace('/\$user->rights/', '__VARUSERHASRIGHT__', $scheck); // Remove this once $user->rights->xxx is replaced everywhere with $user->hasRight()
9087 $scheck = preg_replace('/\$user->isAdmin/', '__VARUSERHASRIGHT__', $scheck);
9088 $scheck = preg_replace('/\$user->admin/', '__VARUSERISADMIN__', $scheck); // Remove this once $user->admin is replaced everywhere with $user->isAdmin()
9089 $scheck = preg_replace('/\$user->isExternalUser/', '__VARUSERSOCID__', $scheck);
9090 $scheck = preg_replace('/\$user->socid/', '__VARUSERSOCID__', $scheck); // Remove this once $user->admin is replaced everywhere with $user->isExternalUser()
9091 $scheck = preg_replace('/\‍(\$db\‍)/', '__VARDB__', $scheck);
9092 $scheck = preg_replace('/\$langs/', '__VARLANGSTRANS__', $scheck);
9093 $scheck = preg_replace('/\$mysoc/', '__VARMYSOC__', $scheck);
9094 $scheck = preg_replace('/\$action/', '__VARACTION__', $scheck);
9095 $scheck = preg_replace('/\$mainmenu/', '__VARMAINMENU__', $scheck); // Remove this once all tests on $mainmenu has been replaced with isStringVarMatching
9096 $scheck = preg_replace('/\$leftmenu/', '__VARLEFTMENU__', $scheck); // Remove this once all tests on $mainmenu has been replaced with isStringVarMatching
9097 $scheck = preg_replace('/\$websitepage/', '__VARWEBSITEPAGE__', $scheck);
9098 $scheck = preg_replace('/\$website/', '__VARWEBSITE__', $scheck);
9099 $scheck = preg_replace('/\$objectoffield/', '__VAROBJECTOFFIELD__', $scheck);
9100 $scheck = preg_replace('/\$object/', '__VAROBJECT__', $scheck);
9101 $scheck = preg_replace('/\$var/', '__VARVAR__', $scheck);
9102
9103 // deprecated (now we use $objecf->canvas or $objectoffield->canvas)
9104 $scheck = preg_replace('/\$soc->canvas/', '__VARSOCCANVAS__', $scheck);
9105 $scheck = preg_replace('/\$obj->canvas/', '__VAROBJCANVAS__', $scheck);
9106
9107 // Now test if it remains one '$'
9108 if (strpos($scheck, '$') !== false) {
9109 dol_syslog('Bad string syntax to evaluate (found use of $ not matching pattern: $user->hasRight, ($db), $langs, $mysoc, $action, $mainmenu, $leftmenu, $website, $websitepage, $objectoffield or $var123): ' . $s, LOG_WARNING);
9110 return 'Bad string syntax to evaluate (found use of $ not matching pattern: $user->hasRight, ($db), $langs, $mysoc, $action, $mainmenu, $leftmenu, $website, $websitepage, $objectoffield or $var123): ' . $s;
9111 }
9112 }
9113
9114 // We block use of php exec or php file functions
9115 $forbiddenphpstrings = array('_ENV', '_SESSION', '_COOKIE', '_GET', '_GLOBAL', '_POST', '_REQUEST', 'ReflectionFunction', 'SplFileObject', 'SplTempFileObject');
9116
9117 if (empty($dolibarr_main_restrict_eval_methods)) { // If forced to ''
9118 // We list all forbidden function as keywords we don't want to see (we don't mind it if is "keyword(" or just "keyword", we don't want "keyword" at all)
9119 // We must exclude all functions that allow to execute another function. This includes all function that has a parameter with type "callable" to avoid things
9120 // like we can do with array_map and its callable parameter: dol_eval('json_encode(array_map(implode("",["ex","ec"]), ["id"]))', 1, 1, '0')
9121 $forbiddenphpfunctions = array();
9122 $forbiddenphpmethods = array();
9123
9124 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("override_function", "session_id", "session_create_id", "session_regenerate_id"));
9125 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("get_defined_functions", "get_defined_vars", "get_defined_constants", "get_declared_classes"));
9126 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("function"));
9127
9128 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("GETPOST")); // native dolibarr functions
9129
9130 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("ob_start"));
9131
9132 // Functions with callable parameters
9133 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("call_user_func", "call_user_func_array"));
9134 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("array_all", "array_any", "array_diff_ukey", "array_filter", "array_find", "array_find_key", "array_map", "array_reduce", "array_intersect_uassoc", "array_intersect_ukey", "array_walk", "array_walk_recursive"));
9135 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("usort", "uasort", "uksort"));
9136 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("preg_replace_callback", "preg_replace_callback_array", "header_register_callback"));
9137 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("error_log", "set_error_handler", "set_exception_handler", "libxml_set_external_entity_loader", "register_shutdown_function", "register_tick_function", "unregister_tick_function"));
9138 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("spl_autoload_register", "spl_autoload_unregister", "iterator_apply", "session_set_save_handler"));
9139 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("forward_static_call", "forward_static_call_array", "register_postsend_function"));
9140 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("readline_completion_function", "readline_callback_handler_install"));
9141
9142 // Exec functions
9143 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("exec", "passthru", "shell_exec", "system", "proc_open", "popen"));
9144 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("pcntl_alarm", "pcntl_exec", "pcntl_fork", "pcntl_waitpid", "pcntl_wait", "pcntl_wifexited", "pcntl_wifstopped", "pcntl_wifsignaled", "pcntl_wifcontinued", "pcntl_wexitstatus", "pcntl_wtermsig", "pcntl_wstopsig", "pcntl_signal"));
9145 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("pcntl_signal_get_handler", "pcntl_signal_dispatch", "pcntl_get_last_error", "pcntl_strerror", "pcntl_sigprocmask", "pcntl_sigwaitinfo", "pcntl_sigtimedwait", "pcntl_getpriority", "pcntl_async_signals", "pcntl_unshare", ));
9146 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("putenv", "dl", "apache_child_terminate", "apache_setenv"));
9147 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_eval", "dol_eval_new", "dol_eval_standard", "executeCLI", "verifCond", "dolEncrypt", "dolDecrypt")); // native dolibarr functions
9148 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("eval", "create_function", "assert", "mb_ereg_replace")); // function with eval capabilities
9149
9150 // Include functions
9151 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("require", "include", "require_once", "include_once"));
9152
9153 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_compress_dir", "dol_decode", "dol_dir_list", "dol_dir_list_in_database", "dol_delete_file", "dol_delete_dir", "dol_delete_dir_recursive", "dol_copy", "archiveOrBackupFile")); // more dolibarr functions
9154 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("chdir", "dir", "fopen", "file", "file_exists", "file_get_contents", "file_put_contents", "fget", "fgetc", "fgetcsv", "flock", "fputs", "fputscsv", "fpassthru", "fscanf", "fseek", "fwrite", "is_file", "is_dir", "is_link", "mkdir", "opendir", "rmdir", "scandir", "symlink", "touch", "unlink", "umask"));
9155
9156 if (!getDolGlobalString('MAIN_ALLOW_OBFUSCATION_METHODS_IN_DOL_EVAL')) { // We disallow all function that allow to obfuscate the real name of a function
9157 // @phpcs:ignore
9158 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("base64" . "_" . "decode", "rawurl" . "decode", "url" . "decode", "str" . "_rot13", "hex" . "2bin", "printf", "sprintf")); // name of forbidden functions are split to avoid false positive
9159 $forbiddenphpfunctions = array_merge($forbiddenphpfunctions, array("dol_concat", "dol_concatdesc")); // native dolibarr functions
9160 }
9161 // Remove from blacklist the function that are into the whitelist
9162 /*foreach ($forbiddenphpfunctions as $key => $forbiddenphpfunction) {
9163 if (in_array($forbiddenphpfunction, $dolibarr_main_restrict_eval_methods_array)) {
9164 unset($forbiddenphpfunctions[$key]);
9165 }
9166 }*/
9167
9168 $forbiddenphpmethods = array_merge($forbiddenphpmethods, array('invoke', 'invokeArgs')); // Methods of ReflectionFunction to execute a function
9169 // Remove from blacklist the function that are into the whitelist
9170 /*foreach ($forbiddenphpmethods as $key => $forbiddenphpmethod) {
9171 if (in_array($forbiddenphpmethod, $dolibarr_main_restrict_eval_methods_array)) {
9172 unset($forbiddenphpmethods[$key]);
9173 }
9174 }*/
9175
9176 $forbiddenphpregex = 'global\s*\$';
9177 $forbiddenphpregex .= '|posix_[a-zA-Z0-9_]*|'; // All posix functions
9178 $forbiddenphpregex .= '\b(' . implode('|', $forbiddenphpfunctions) . ')\b';
9179
9180 $forbiddenphpmethodsregex = '->(' . implode('|', $forbiddenphpmethods) . ')';
9181
9182 // Now scan all forbidden patterns
9183 do {
9184 $oldstringtoclean = $s;
9185 $s = str_ireplace($forbiddenphpstrings, '__forbiddenstring__', $s);
9186 $s = preg_replace('/' . $forbiddenphpregex . '/i', '__forbiddenstring__', $s);
9187 $s = preg_replace('/' . $forbiddenphpmethodsregex . '/i', '__forbiddenstring__', $s);
9188 //$s = preg_replace('/\$[a-zA-Z0-9_\->\$]+\‍(/i', '', $s); // Remove $function( call and $mycall->mymethod(
9189 } while ($oldstringtoclean != $s);
9190
9191 if (strpos($s, '__forbiddenstring__') !== false) {
9192 dol_syslog('Bad string syntax to evaluate: ' . $s, LOG_WARNING);
9193 return 'Bad string syntax to evaluate: ' . $s;
9194 }
9195 }
9196
9197 if (!empty($dolibarr_main_restrict_eval_methods)) {
9198 // Accept only white-listed allowed function and classes
9199 // TODO Get all pattern '/([\s\w]+)\‍(/', then check that $matches[1] is a defined class or a function into a given list
9200 $pattern = '/([\s\w\'\]\"]+)\‍(/';
9201
9202 $matches = array();
9203 preg_match_all($pattern, $s, $matches);
9204
9205 if (count($matches)) {
9206 foreach ($matches[1] as $m) {
9207 $m = trim($m);
9208 if (empty($m)) {
9209 continue;
9210 }
9211 $reg = array();
9212 if (!preg_match('/new ([A-Z][\w]+)/i', $m, $reg)) {
9213 if (!in_array($m, $dolibarr_main_restrict_eval_methods_array)) {
9214 if ($m != "'" && $m != '"') {
9215 dol_syslog('Bad string syntax to evaluate: ' . $s, LOG_WARNING);
9216 return 'Bad string syntax to evaluate. A function or method "'.$m.'" was called and is not into the parameter $dolibarr_main_restrict_eval_methods of white-listed functions and methods: ' . $s;
9217 }
9218 }
9219 } else {
9220 if (!class_exists($reg[1])) {
9221 dol_syslog('Bad string syntax to evaluate: Class "'.$reg[1].'" does not exist. ' . $s, LOG_WARNING);
9222 return 'Bad string syntax to evaluate. Class "'.$reg[1].'" does not exist. ' . $s;
9223 }
9224 $parents = class_parents($reg[1]); // Get list of parent classes of class we want to check
9225 if (!in_array('CommonObject', $parents)) { // Only classes that inherit CommonObject are ok. This forbid dangerous classes like ReflectionFunction, SplFileObject, ...
9226 dol_syslog('Bad string syntax to evaluate: Class "'.$reg[1].'" is not allowed because only classes extended CommonObject can be used in dynamic evaluation. ' . $s, LOG_WARNING);
9227 return 'Bad string syntax to evaluate. Class "'.$reg[1].'" is not allowed because only classes extended CommonObject can be used in dynamic evaluation. ' . $s;
9228 }
9229 }
9230 }
9231 }
9232
9233 $forbiddenphpregex = 'global\s*\$';
9234 $forbiddenphpregex .= '|'; // or
9235 $forbiddenphpregex .= '}\s*\[';
9236 $forbiddenphpregex .= '|'; // or
9237 $forbiddenphpregex .= '\‍)\s*\‍(';
9238
9239 // Now scan all forbidden patterns
9240 do {
9241 $oldstringtoclean = $s;
9242 $s = str_ireplace($forbiddenphpstrings, '__forbiddenstring__', $s);
9243 $s = preg_replace('/' . $forbiddenphpregex . '/i', '__forbiddenstring__', $s);
9244 //$s = preg_replace('/' . $forbiddenphpmethodsregex . '/i', '__forbiddenstring__', $s);
9245 //$s = preg_replace('/\$[a-zA-Z0-9_\->\$]+\‍(/i', '', $s); // Remove $function( call and $mycall->mymethod(
9246 } while ($oldstringtoclean != $s);
9247
9248 if (strpos($s, '__forbiddenstring__') !== false) {
9249 dol_syslog('Bad string syntax to evaluate: ' . $s, LOG_WARNING);
9250 return 'Bad string syntax to evaluate: ' . $s;
9251 }
9252 }
9253
9254 //print $s."<br>\n";
9255 ob_start(); // An evaluation has no reason to output data
9256 $isObBufferActive = true;
9257 $tmps = $hideerrors ? @eval('return ' . $s . ';') : eval('return ' . $s . ';');
9258 $tmpo = ob_get_clean(); // This close the buffer
9259 $isObBufferActive = false;
9260 if ($tmpo) {
9261 print 'Bad string syntax to evaluate. Some data were output when it should not when evaluating: ' . $s;
9262 }
9263 return $tmps;
9264 } catch (Exception $e) {
9265 if ($isObBufferActive) {
9266 // Clean up buffer which was left behind due to exception.
9267 $tmpo = ob_get_clean(); // This close the buffer
9268 $isObBufferActive = false;
9269 }
9270 $error = 'dol_eval try/catch error for string: ' . $s . ' - Error: ';
9271 $error .= $e->getMessage();
9272 dol_syslog($error, LOG_WARNING);
9273 return 'Exception during evaluation: ' . $s;
9274 } catch (Error $e) {
9275 if ($isObBufferActive) {
9276 // Clean up buffer which was left behind due to exception.
9277 $tmpo = ob_get_clean(); // This close the buffer
9278 $isObBufferActive = false;
9279 }
9280 $error = 'dol_eval try/catch error for string: ' . $s . ' - Error: ';
9281 $error .= $e->getMessage();
9282 dol_syslog($error, LOG_WARNING);
9283 return 'Exception during evaluation: ' . $s;
9284 }
9285}
9286
9294function dol_validElement($element)
9295{
9296 return (trim($element) != '');
9297}
9298
9299
9307function getLanguageCodeFromCountryCode($countrycode)
9308{
9309 global $mysoc;
9310
9311 if (empty($countrycode)) {
9312 return null;
9313 }
9314
9315 if (strtoupper($countrycode) == 'MQ') {
9316 return 'fr_CA';
9317 }
9318 if (strtoupper($countrycode) == 'SE') {
9319 return 'sv_SE'; // se_SE is Sami/Sweden, and we want in priority sv_SE for SE country
9320 }
9321 if (strtoupper($countrycode) == 'CH') {
9322 if ($mysoc->country_code == 'FR') {
9323 return 'fr_CH';
9324 }
9325 if ($mysoc->country_code == 'DE') {
9326 return 'de_CH';
9327 }
9328 if ($mysoc->country_code == 'IT') {
9329 return 'it_CH';
9330 }
9331 }
9332
9333 // Locale list taken from:
9334 // http://stackoverflow.com/questions/3191664/
9335 // list-of-all-locales-and-their-short-codes
9336 $locales = array(
9337 'af-ZA',
9338 'am-ET',
9339 'ar-AE',
9340 'ar-BH',
9341 'ar-DZ',
9342 'ar-EG',
9343 'ar-IQ',
9344 'ar-JO',
9345 'ar-KW',
9346 'ar-LB',
9347 'ar-LY',
9348 'ar-MA',
9349 'ar-OM',
9350 'ar-QA',
9351 'ar-SA',
9352 'ar-SY',
9353 'ar-TN',
9354 'ar-YE',
9355 //'as-IN', // Moved after en-IN
9356 'ba-RU',
9357 'be-BY',
9358 'bg-BG',
9359 'bn-BD',
9360 //'bn-IN', // Moved after en-IN
9361 'bo-CN',
9362 'br-FR',
9363 'ca-ES',
9364 'co-FR',
9365 'cs-CZ',
9366 'cy-GB',
9367 'da-DK',
9368 'de-AT',
9369 'de-CH',
9370 'de-DE',
9371 'de-LI',
9372 'de-LU',
9373 'dv-MV',
9374 'el-GR',
9375 'en-AU',
9376 'en-BZ',
9377 'en-CA',
9378 'en-GB',
9379 'en-IE',
9380 'en-IN',
9381 'as-IN', // as-IN must be after en-IN (en in priority if country is IN)
9382 'bn-IN', // bn-IN must be after en-IN (en in priority if country is IN)
9383 'en-JM',
9384 'en-MY',
9385 'en-NZ',
9386 'en-PH',
9387 'en-SG',
9388 'en-TT',
9389 'en-US',
9390 'en-ZA',
9391 'en-ZW',
9392 'es-AR',
9393 'es-BO',
9394 'es-CL',
9395 'es-CO',
9396 'es-CR',
9397 'es-DO',
9398 'es-EC',
9399 'es-ES',
9400 'es-GT',
9401 'es-HN',
9402 'es-MX',
9403 'es-NI',
9404 'es-PA',
9405 'es-PE',
9406 'es-PR',
9407 'es-PY',
9408 'es-SV',
9409 'es-US',
9410 'es-UY',
9411 'es-VE',
9412 'et-EE',
9413 'eu-ES',
9414 'fa-IR',
9415 'fi-FI',
9416 'fo-FO',
9417 'fr-BE',
9418 'fr-CA',
9419 'fr-CH',
9420 'fr-FR',
9421 'fr-LU',
9422 'fr-MC',
9423 'fy-NL',
9424 'ga-IE',
9425 'gd-GB',
9426 'gl-ES',
9427 'gu-IN',
9428 'he-IL',
9429 'hi-IN',
9430 'hr-BA',
9431 'hr-HR',
9432 'hu-HU',
9433 'hy-AM',
9434 'id-ID',
9435 'ig-NG',
9436 'ii-CN',
9437 'is-IS',
9438 'it-CH',
9439 'it-IT',
9440 'ja-JP',
9441 'ka-GE',
9442 'kk-KZ',
9443 'kl-GL',
9444 'km-KH',
9445 'kn-IN',
9446 'ko-KR',
9447 'ky-KG',
9448 'lb-LU',
9449 'lo-LA',
9450 'lt-LT',
9451 'lv-LV',
9452 'mi-NZ',
9453 'mk-MK',
9454 'ml-IN',
9455 'mn-MN',
9456 'mr-IN',
9457 'ms-BN',
9458 'ms-MY',
9459 'mt-MT',
9460 'nb-NO',
9461 'ne-NP',
9462 'nl-BE',
9463 'nl-NL',
9464 'nn-NO',
9465 'oc-FR',
9466 'or-IN',
9467 'pa-IN',
9468 'pl-PL',
9469 'ps-AF',
9470 'pt-BR',
9471 'pt-PT',
9472 'rm-CH',
9473 'ro-MD',
9474 'ro-RO',
9475 'ru-RU',
9476 'rw-RW',
9477 'sa-IN',
9478 'se-FI',
9479 'se-NO',
9480 'se-SE',
9481 'si-LK',
9482 'sk-SK',
9483 'sl-SI',
9484 'sq-AL',
9485 'sv-FI',
9486 'sv-SE',
9487 'sw-KE',
9488 'ta-IN',
9489 'te-IN',
9490 'th-TH',
9491 'tk-TM',
9492 'tn-ZA',
9493 'tr-TR',
9494 'tt-RU',
9495 'ug-CN',
9496 'uk-UA',
9497 'ur-PK',
9498 'vi-VN',
9499 'wo-SN',
9500 'xh-ZA',
9501 'yo-NG',
9502 'zh-CN',
9503 'zh-HK',
9504 'zh-MO',
9505 'zh-SG',
9506 'zh-TW',
9507 'zu-ZA',
9508 );
9509
9510 $buildprimarykeytotest = strtolower($countrycode) . '-' . strtoupper($countrycode);
9511 if (in_array($buildprimarykeytotest, $locales)) {
9512 return strtolower($countrycode) . '_' . strtoupper($countrycode);
9513 }
9514
9515 if (function_exists('locale_get_primary_language') && function_exists('locale_get_region')) { // Need extension php-intl
9516 foreach ($locales as $locale) {
9517 $locale_language = locale_get_primary_language($locale);
9518 $locale_region = locale_get_region($locale);
9519 if (strtoupper($countrycode) == $locale_region) {
9520 //var_dump($locale.' - '.$locale_language.' - '.$locale_region);
9521 return strtolower($locale_language) . '_' . strtoupper($locale_region);
9522 }
9523 }
9524 } else {
9525 dol_syslog("Warning Extension php-intl is not available", LOG_WARNING);
9526 }
9527
9528 return null;
9529}
9530
9561function complete_head_from_modules($conf, $langs, $object, &$head, &$h, $type, $mode = 'add', $filterorigmodule = '')
9562{
9563 global $hookmanager, $db;
9564
9565 if (isset($conf->modules_parts['tabs'][$type]) && is_array($conf->modules_parts['tabs'][$type])) {
9566 foreach ($conf->modules_parts['tabs'][$type] as $value) {
9567 $values = explode(':', $value);
9568
9569 $reg = array();
9570 if ($mode == 'add' && !preg_match('/^\-/', $values[1])) {
9571 if (count($values) !== 6) {
9572 dol_syslog('The module_parts["tabs"] entries must be composed of 6 values separated by ":", but got "' . $value . '". Please check your module descriptor classes.', LOG_ERR);
9573 continue;
9574 }
9575
9576 // new declaration with permissions:
9577 // $value='objecttype:+tabname1:Title1:langfile@mymodule:$user->rights->mymodule->read:/mymodule/mynewtab1.php?id=__ID__'
9578 // $value='objecttype:+tabname1:Title1,class,pathfile,method:langfile@mymodule:$user->rights->mymodule->read:/mymodule/mynewtab1.php?id=__ID__'
9579 if ($values[0] != $type) {
9580 continue;
9581 }
9582
9583 $newtab = array();
9584 $postab = $h;
9585 // detect if position set in $values[1] ie : +(2)mytab@mymodule (first tab is 0, second is one, ...)
9586 $str = $values[1];
9587 $posstart = strpos($str, '(');
9588 if ($posstart > 0) {
9589 $posend = strpos($str, ')');
9590 if ($posstart > 0) {
9591 $res1 = substr($str, $posstart + 1, $posend - $posstart - 1);
9592 if (is_numeric($res1)) {
9593 $postab = (int) $res1;
9594 $values[1] = '+' . substr($str, $posend + 1);
9595 }
9596 }
9597 }
9598
9599 global $objectoffield; // So we can use $objectoffield int verifCond
9600 $objectoffield = $object;
9601
9602 if (!verifCond($values[4], '2')) {
9603 continue;
9604 }
9605
9606 if ($values[3]) {
9607 if ($filterorigmodule) { // If a filter of module origin has been requested
9608 if (strpos($values[3], '@')) { // This is an external module
9609 if ($filterorigmodule != 'external') {
9610 continue;
9611 }
9612 } else { // This looks a core module
9613 if ($filterorigmodule != 'core') {
9614 continue;
9615 }
9616 }
9617 }
9618 $langs->load($values[3]);
9619 }
9620
9621 if (preg_match('/SUBSTITUTION_([^_]+)/i', $values[2], $reg)) {
9622 // If label is "SUBSTITUION_..."
9623 $substitutionarray = array();
9624 complete_substitutions_array($substitutionarray, $langs, $object, array('needforkey' => $values[2]));
9625 $label = make_substitutions($reg[1], $substitutionarray);
9626 } else {
9627 // If label is "Label,Class,File,Method", we call the method to show content inside the badge
9628 $labeltemp = explode(',', $values[2]);
9629 $label = $langs->trans($labeltemp[0]);
9630
9631 if (!empty($labeltemp[1]) && is_object($object) && !empty($object->id)) {
9632 dol_include_once($labeltemp[2]);
9633 $classtoload = $labeltemp[1];
9634 if (class_exists($classtoload)) {
9635 $obj = new $classtoload($db);
9636 $function = $labeltemp[3];
9637 if ($obj && $function && method_exists($obj, $function)) {
9638 // @phan-suppress-next-line PhanPluginUnknownObjectMethodCall
9639 $nbrec = $obj->$function($object->id, $obj);
9640 if (!empty($nbrec)) {
9641 $label .= '<span class="badge marginleftonlyshort">' . $nbrec . '</span>';
9642 }
9643 }
9644 }
9645 }
9646 }
9647 $url = preg_replace('/__ID__/i', ((is_object($object) && !empty($object->id)) ? $object->id : ''), $values[5]);
9648 $link = parse_url($url);
9649 $query = [];
9650 if (isset($link['query'])) {
9651 parse_str($link['query'], $query);
9652 }
9653 $newtab[0] = dolBuildUrl(dol_buildpath($link['path'], 1), $query);
9654 $newtab[1] = $label;
9655 $newtab[2] = str_replace('+', '', $values[1]);
9656 $h++;
9657
9658 // set tab at its position
9659 $head = array_merge(array_slice($head, 0, $postab), array($newtab), array_slice($head, $postab));
9660 } elseif ($mode == 'remove' && preg_match('/^\-/', $values[1])) {
9661 if ($values[0] != $type) {
9662 continue;
9663 }
9664 $tabname = str_replace('-', '', $values[1]);
9665 foreach ($head as $key => $val) {
9666 $condition = (!empty($values[3]) ? verifCond($values[3], '2') : 1);
9667 //var_dump($key.' - '.$tabname.' - '.$head[$key][2].' - '.$values[3].' - '.$condition);
9668 if ($head[$key][2] == $tabname && $condition) {
9669 unset($head[$key]);
9670 break;
9671 }
9672 }
9673 }
9674 }
9675 }
9676
9677 // No need to make a return $head. Var is modified as a reference
9678 if (!empty($hookmanager)) {
9679 $parameters = array('object' => $object, 'mode' => $mode, 'head' => &$head, 'filterorigmodule' => $filterorigmodule, 'type' => $type);
9680 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
9681 $reshook = $hookmanager->executeHooks('completeTabsHead', $parameters, $object);
9682 if ($reshook > 0) { // Hook ask to replace completely the array
9683 $head = $hookmanager->resArray;
9684 } else { // Hook
9685 $head = array_merge($head, $hookmanager->resArray);
9686 }
9687 $h = count($head);
9688 }
9689}
9690
9691
9701function dolExplodeIntoArray($string, $delimiter = ';', $kv = '=')
9702{
9703 if (is_null($string)) {
9704 return array();
9705 }
9706
9707 if (preg_match('/^\[.*\]$/sm', $delimiter) || preg_match('/^\‍(.*\‍)$/sm', $delimiter)) {
9708 // This is a regex string
9709 $newdelimiter = $delimiter;
9710 } else {
9711 // This is a simple string
9712 // @phan-suppress-next-line PhanPluginSuspiciousParamPositionInternal
9713 $newdelimiter = preg_quote($delimiter, '/');
9714 }
9715
9716 if ($a = preg_split('/' . $newdelimiter . '/', $string)) {
9717 $ka = array();
9718 foreach ($a as $s) { // each part
9719 if ($s) {
9720 if ($pos = strpos($s, $kv)) { // key/value delimiter
9721 $ka[trim(substr($s, 0, $pos))] = trim(substr($s, $pos + strlen($kv)));
9722 } else { // key delimiter not found
9723 $ka[] = trim($s);
9724 }
9725 }
9726 }
9727 return $ka;
9728 }
9729
9730 return array();
9731}
9732
9740function dolExplodeKeepIfQuotes($input)
9741{
9742 // Use regexp to capture words and section in quotes
9743 $matches = array();
9744 preg_match_all('/"([^"]*)"|\'([^\']*)\'|(\S+)/', $input, $matches);
9745
9746 // Merge result and delete empty values
9747
9748 $result = array_map(
9755 static function ($a, $b, $c) {
9756 if ($a !== '') {
9757 return $a;
9758 }
9759 if ($b !== '') {
9760 return $b;
9761 }
9762 if ($c !== '') {
9763 return $c;
9764 }
9765 return '';
9766 },
9767 $matches[1],
9768 $matches[2],
9769 $matches[3]
9770 );
9771 return array_values(array_filter(
9772 $result,
9779 static function ($val) {
9780 return $val !== '';
9781 }
9782 ));
9783}
9784
9785
9793function dol_getmypid()
9794{
9795 if (!function_exists('getmypid')) {
9796 return mt_rand(99900000, 99965535);
9797 } else {
9798 return getmypid(); // May be a number on 64 bits (depending on OS)
9799 }
9800}
9801
9824function natural_search($fields, $value, $mode = 0, $nofirstand = 0, $sqltoadd = '')
9825{
9826 global $db, $langs;
9827
9828 $value = trim($value);
9829
9830 if ($mode == 0) {
9831 $value = preg_replace('/\*/', '%', $value); // Replace * with %
9832 }
9833 if ($mode == 1) {
9834 $value = preg_replace('/([!<>=]+)\s+([0-9' . preg_quote($langs->trans("SeparatorDecimal"), '/') . '\-])/', '\1\2', $value); // Clean string '< 10' into '<10' so we can then explode on space to get all tests to do
9835 }
9836
9837 $value = preg_replace('/\s*\|\s*/', '|', $value);
9838
9839 // Split criteria on ' ' but not if we are inside quotes.
9840 // For mode 3, the split is done later on the , only and not on the ' '.
9841 if ($mode != -3 && $mode != 3) {
9842 $crits = dolExplodeKeepIfQuotes($value);
9843 } else {
9844 $crits = array($value);
9845 }
9846
9847 $res = '';
9848 if (!is_array($fields)) {
9849 $fields = array($fields);
9850 }
9851 $i1 = 0; // count the nb of "and" criteria added (all fields / criteria)
9852 foreach ($crits as $crit) { // Loop on each AND criteria
9853 $crit = trim($crit);
9854 $i2 = 0; // count the nb of valid criteria added for this this first criteria
9855 $newres = '';
9856
9857 foreach ($fields as $field) {
9858 if ($mode == 1) {
9859 $tmpcrits = explode('|', $crit);
9860 $i3 = 0; // count the nb of valid criteria added for this current field
9861 foreach ($tmpcrits as $tmpcrit) {
9862 if ($tmpcrit !== '0' && empty($tmpcrit)) {
9863 continue;
9864 }
9865 $tmpcrit = trim($tmpcrit);
9866
9867 $newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : '');
9868
9869 $operator = '=';
9870 $newcrit = preg_replace('/([!<>=]+)/', '', $tmpcrit);
9871
9872 $reg = array();
9873 preg_match('/([!<>=]+)/', $tmpcrit, $reg);
9874 if (!empty($reg[1])) {
9875 $operator = $reg[1];
9876 }
9877 if ($newcrit != '') {
9878 $numnewcrit = price2num($newcrit);
9879 if (is_numeric($numnewcrit)) {
9880 $newres .= $db->sanitize($field) . ' ' . $operator . ' ' . ((float) $numnewcrit); // should be a numeric
9881 } else {
9882 $newres .= '1 = 2'; // force false, we received a corrupted data
9883 }
9884 $i3++; // a criteria was added to string
9885 }
9886 }
9887 $i2++; // a criteria for 1 more field was added to string
9888 } elseif ($mode == 2 || $mode == -2) {
9889 $crit = preg_replace('/[^\-0-9,]/', '', $crit); // ID are always integer
9890 $newres .= ($i2 > 0 ? ' OR ' : '') . $db->sanitize($field) . " " . ($mode == -2 ? 'NOT ' : '');
9891 $newres .= $crit ? "IN (" . $db->sanitize($db->escape($crit)) . ")" : "IN (0)";
9892 if ($mode == -2) {
9893 $newres .= ' OR ' . $db->sanitize($field) . ' IS NULL';
9894 }
9895 $i2++; // a criteria for 1 more field was added to string
9896 } elseif ($mode == 3 || $mode == -3) {
9897 $tmparray = explode(',', $crit);
9898 if (count($tmparray)) {
9899 $listofcodes = '';
9900 $listofcodesnot = '';
9901 foreach ($tmparray as $val) {
9902 $val = trim($val);
9903 if ($val !== '') {
9904 if (preg_match('/^!/', $val)) {
9905 $listofcodesnot .= ($listofcodesnot ? ',' : '');
9906 $listofcodesnot .= "'" . $db->escape(preg_replace('/^!=?/', '', $val)) . "'";
9907 } else {
9908 $listofcodes .= ($listofcodes ? ',' : '');
9909 $listofcodes .= "'" . $db->escape($val) . "'";
9910 }
9911 }
9912 }
9913 $newres .= ($i2 > 0 ? ' OR ' : '');
9914 if ($listofcodes && $listofcodesnot) {
9915 $newres .= '(';
9916 }
9917 if ($listofcodes) {
9918 $newres .= $db->sanitize($field) . " " . ($mode == -3 ? 'NOT IN' : 'IN') . " (" . $db->sanitize($listofcodes, 1, 0, 1) . ")";
9919 }
9920 if ($listofcodes && $listofcodesnot) {
9921 $newres .= ' AND ';
9922 }
9923 if ($listofcodesnot) {
9924 $newres .= $db->sanitize($field) . " " . ($mode == -3 ? 'IN ' : 'NOT IN') . " (" . $db->sanitize($listofcodesnot, 1, 0, 1) . ")";
9925 }
9926 if ($listofcodes && $listofcodesnot) {
9927 $newres .= ')';
9928 }
9929 $i2++; // a criteria for 1 more field was added to string
9930 }
9931 if ($mode == -3) {
9932 $newres .= ' OR ' . $db->sanitize($field) . ' IS NULL';
9933 }
9934 } elseif ($mode == 4) {
9935 $tmparray = explode(',', $crit);
9936 if (count($tmparray)) {
9937 $listofcodes = '';
9938 foreach ($tmparray as $val) {
9939 $val = trim($val);
9940 if ($val) {
9941 $newres .= ($i2 > 0 ? " OR (" : "(") . $db->sanitize($field) . " LIKE '" . $db->escape($val) . ",%'";
9942 $newres .= ' OR ' . $db->sanitize($field) . " = '" . $db->escape($val) . "'";
9943 $newres .= ' OR ' . $db->sanitize($field) . " LIKE '%," . $db->escape($val) . "'";
9944 $newres .= ' OR ' . $db->sanitize($field) . " LIKE '%," . $db->escape($val) . ",%'";
9945 $newres .= ')';
9946 $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)
9947 }
9948 }
9949 }
9950 } else { // $mode=0
9951 $tmpcrits = explode('|', $crit);
9952 $i3 = 0; // count the nb of valid criteria added for the current couple criteria/field
9953 foreach ($tmpcrits as $tmpcrit) { // loop on each OR criteria
9954 if ($tmpcrit !== '0' && empty($tmpcrit)) {
9955 continue;
9956 }
9957 $tmpcrit = trim($tmpcrit);
9958
9959 if ($tmpcrit == '^$' || strpos($crit, '!') === 0) { // If we search empty, we must combined different OR fields with AND
9960 $newres .= (($i2 > 0 || $i3 > 0) ? ' AND ' : '');
9961 } else {
9962 $newres .= (($i2 > 0 || $i3 > 0) ? ' OR ' : '');
9963 }
9964
9965 $isSellist = false;
9966 $table = $label = $key = null;
9967
9968 if (strpos($field, 'ef.') === 0) {
9969 $extrafieldName = substr($field, 3);
9970 $extrafields = new ExtraFields($db);
9971 $extrafields->fetch_name_optionals_label('product');
9972
9973 if (isset($extrafields->attributes['product']['type'][$extrafieldName]) && $extrafields->attributes['product']['type'][$extrafieldName] === 'sellist') {
9974 $isSellist = true;
9975 $paramArray = $extrafields->attributes['product']['param'][$extrafieldName]['options'] ?? [];
9976 $param = array_key_first($paramArray);
9977 list($table, $label, $key) = explode(':', $param);
9978 }
9979 }
9980
9981 if (preg_match('/\.(id|rowid)$/', $field)) { // Special case for rowid that is sometimes a ref so used as a search field
9982 $newres .= $db->sanitize($field) . " = " . (is_numeric($tmpcrit) ? ((float) $tmpcrit) : '0');
9983 } else {
9984 $tmpcrit2 = $tmpcrit;
9985 $tmpbefore = '%';
9986 $tmpafter = '%';
9987 $tmps = '';
9988
9989 if ($isSellist && $key && $table && $label) {
9990 $newres .= $field . " IN (SELECT t." . $db->sanitize((string) $key) . " FROM " . $db->prefix() . $db->sanitize((string) $table) . " AS t WHERE t." . $db->sanitize((string) $label) . " LIKE '%" . $db->escape($tmpcrit2) . "%')";
9991 } else {
9992 if (preg_match('/^!/', $tmpcrit)) {
9993 $tmps .= $db->sanitize($field) . " NOT LIKE "; // ! as exclude character
9994 $tmpcrit2 = preg_replace('/^!/', '', $tmpcrit2);
9995 } else {
9996 $tmps .= $db->sanitize($field) . " LIKE ";
9997 }
9998 $tmps .= "'";
9999
10000 if (preg_match('/^[\^\$]/', $tmpcrit)) {
10001 $tmpbefore = '';
10002 $tmpcrit2 = preg_replace('/^[\^\$]/', '', $tmpcrit2);
10003 }
10004 if (preg_match('/[\^\$]$/', $tmpcrit)) {
10005 $tmpafter = '';
10006 $tmpcrit2 = preg_replace('/[\^\$]$/', '', $tmpcrit2);
10007 }
10008
10009 if ($tmpcrit2 == '' || preg_match('/^!/', $tmpcrit)) {
10010 $tmps = "(" . $tmps;
10011 }
10012 $newres .= $tmps;
10013 $newres .= $tmpbefore;
10014 $newres .= $db->escape($tmpcrit2);
10015 $newres .= $tmpafter;
10016 $newres .= "'";
10017 if ($tmpcrit2 == '' || preg_match('/^!/', $tmpcrit)) {
10018 $newres .= " OR " . $db->sanitize($field) . " IS NULL)";
10019 }
10020 }
10021 }
10022
10023 $i3++;
10024 }
10025
10026 $i2++; // a criteria for 1 more field was added to string
10027 }
10028 }
10029
10030 if ($sqltoadd) {
10031 $newres .= ($newres ? '' : ' OR ').str_replace('__KEYTOSEARCH__', $db->escape($crit), $sqltoadd);
10032 }
10033
10034 if ($newres) {
10035 $res = $res . ($res ? ' AND ' : '') . ($i2 > 1 ? '(' : '') . $newres . ($i2 > 1 ? ')' : '');
10036 }
10037 $i1++;
10038 }
10039 $res = ($nofirstand ? "" : " AND ") . "(" . $res . ")";
10040
10041 return $res;
10042}
10043
10044
10053function getImageFileNameForSize($file, $extName, $extImgTarget = '')
10054{
10055 $dirName = dirname($file);
10056 if ($dirName == '.') {
10057 $dirName = '';
10058 }
10059
10060 if (!in_array($extName, array('', '_small', '_mini'))) {
10061 return 'Bad parameter extName';
10062 }
10063
10064 $fileName = preg_replace('/(\.gif|\.jpeg|\.jpg|\.png|\.bmp|\.webp|\.avif)$/i', '', $file); // We remove image extension, whatever is its case
10065 $fileName = basename($fileName);
10066
10067 if (empty($extImgTarget)) {
10068 $extImgTarget = (preg_match('/\.jpg$/i', $file) ? '.jpg' : '');
10069 }
10070 if (empty($extImgTarget)) {
10071 $extImgTarget = (preg_match('/\.jpeg$/i', $file) ? '.jpeg' : '');
10072 }
10073 if (empty($extImgTarget)) {
10074 $extImgTarget = (preg_match('/\.gif$/i', $file) ? '.gif' : '');
10075 }
10076 if (empty($extImgTarget)) {
10077 $extImgTarget = (preg_match('/\.png$/i', $file) ? '.png' : '');
10078 }
10079 if (empty($extImgTarget)) {
10080 $extImgTarget = (preg_match('/\.bmp$/i', $file) ? '.bmp' : '');
10081 }
10082 if (empty($extImgTarget)) {
10083 $extImgTarget = (preg_match('/\.webp$/i', $file) ? '.webp' : '');
10084 }
10085 if (empty($extImgTarget)) {
10086 $extImgTarget = (preg_match('/\.avif$/i', $file) ? '.avif' : '');
10087 }
10088
10089 if (!$extImgTarget) {
10090 return $file;
10091 }
10092
10093 $subdir = '';
10094 if ($extName) {
10095 $subdir = 'thumbs/';
10096 }
10097
10098 return ($dirName ? $dirName . '/' : '') . $subdir . $fileName . $extName . $extImgTarget; // New filename for thumb
10099}
10100
10101
10108function getLabelSpecialCode($idcode)
10109{
10110 global $langs;
10111
10112 $arrayspecialines = array(1 => 'Transport', 2 => 'EcoTax', 3 => 'Option');
10113 if ($idcode > 10) {
10114 return 'Module ID ' . $idcode;
10115 }
10116 if (!empty($arrayspecialines[$idcode])) {
10117 return $langs->trans($arrayspecialines[$idcode]);
10118 }
10119 return '';
10120}
10121
10122
10130function dolIsAllowedForPreview($file)
10131{
10132 // Check .noexe extension in filename
10133 if (preg_match('/\.noexe$/i', $file)) {
10134 return 0;
10135 }
10136
10137 // Check mime types
10138 $mime_preview = array('avif', 'bmp', 'jpeg', 'png', 'gif', 'tiff', 'pdf', 'plain', 'css', 'webp', 'webm', 'mp4');
10139 if (getDolGlobalString('MAIN_ALLOW_SVG_FILES_AS_IMAGES')) {
10140 $mime_preview[] = 'svg+xml';
10141 }
10142 if (getDolGlobalString('MAIN_ALLOW_XML_FILES_AS_PREVIEW')) {
10143 $mime_preview[] = 'xml';
10144 }
10145
10146 //$mime_preview[]='vnd.oasis.opendocument.presentation';
10147 //$mime_preview[]='archive';
10148 $num_mime = array_search(dol_mimetype($file, '', 1), $mime_preview);
10149 if ($num_mime !== false) {
10150 return 1;
10151 }
10152
10153 // By default, not allowed for preview
10154 return 0;
10155}
10156
10157
10167function dol_mimetype($file, $default = 'application/octet-stream', $mode = 0)
10168{
10169 $mime = $default;
10170 $imgmime = 'other.png';
10171 $famime = 'file-o';
10172 $srclang = '';
10173
10174 $tmpfile = preg_replace('/\.noexe$/', '', $file);
10175
10176 // Plain text files
10177 if (preg_match('/\.txt$/i', $tmpfile)) {
10178 $mime = 'text/plain';
10179 $imgmime = 'text.png';
10180 $famime = 'file-alt';
10181 } elseif (preg_match('/\.rtx$/i', $tmpfile)) {
10182 $mime = 'text/richtext';
10183 $imgmime = 'text.png';
10184 $famime = 'file-alt';
10185 } elseif (preg_match('/\.csv$/i', $tmpfile)) {
10186 $mime = 'text/csv';
10187 $imgmime = 'text.png';
10188 $famime = 'file-csv';
10189 } elseif (preg_match('/\.tsv$/i', $tmpfile)) {
10190 $mime = 'text/tab-separated-values';
10191 $imgmime = 'text.png';
10192 $famime = 'file-alt';
10193 } elseif (preg_match('/\.(cf|conf|log)$/i', $tmpfile)) {
10194 $mime = 'text/plain';
10195 $imgmime = 'text.png';
10196 $famime = 'file-alt';
10197 } elseif (preg_match('/\.ini$/i', $tmpfile)) {
10198 $mime = 'text/plain';
10199 $imgmime = 'text.png';
10200 $srclang = 'ini';
10201 $famime = 'file-alt';
10202 } elseif (preg_match('/\.md$/i', $tmpfile)) {
10203 $mime = 'text/plain';
10204 $imgmime = 'text.png';
10205 $srclang = 'md';
10206 $famime = 'file-alt';
10207 } elseif (preg_match('/\.css$/i', $tmpfile)) {
10208 $mime = 'text/css';
10209 $imgmime = 'css.png';
10210 $srclang = 'css';
10211 $famime = 'file-alt';
10212 } elseif (preg_match('/\.lang$/i', $tmpfile)) {
10213 $mime = 'text/plain';
10214 $imgmime = 'text.png';
10215 $srclang = 'lang';
10216 $famime = 'file-alt';
10217 } elseif (preg_match('/\.(crt|cer|key|pub)$/i', $tmpfile)) { // Certificate files
10218 $mime = 'text/plain';
10219 $imgmime = 'text.png';
10220 $famime = 'file-alt';
10221 } elseif (preg_match('/\.(html|htm|shtml)$/i', $tmpfile)) { // XML based (HTML/XML/XAML)
10222 $mime = 'text/html';
10223 $imgmime = 'html.png';
10224 $srclang = 'html';
10225 $famime = 'file-alt';
10226 } elseif (preg_match('/\.(xml|xhtml)$/i', $tmpfile)) {
10227 $mime = 'text/xml';
10228 $imgmime = 'other.png';
10229 $srclang = 'xml';
10230 $famime = 'file-alt';
10231 } elseif (preg_match('/\.xaml$/i', $tmpfile)) {
10232 $mime = 'text/xml';
10233 $imgmime = 'other.png';
10234 $srclang = 'xaml';
10235 $famime = 'file-alt';
10236 } elseif (preg_match('/\.bas$/i', $tmpfile)) { // Languages
10237 $mime = 'text/plain';
10238 $imgmime = 'text.png';
10239 $srclang = 'bas';
10240 $famime = 'file-code';
10241 } elseif (preg_match('/\.(c)$/i', $tmpfile)) {
10242 $mime = 'text/plain';
10243 $imgmime = 'text.png';
10244 $srclang = 'c';
10245 $famime = 'file-code';
10246 } elseif (preg_match('/\.(cpp)$/i', $tmpfile)) {
10247 $mime = 'text/plain';
10248 $imgmime = 'text.png';
10249 $srclang = 'cpp';
10250 $famime = 'file-code';
10251 } elseif (preg_match('/\.cs$/i', $tmpfile)) {
10252 $mime = 'text/plain';
10253 $imgmime = 'text.png';
10254 $srclang = 'cs';
10255 $famime = 'file-code';
10256 } elseif (preg_match('/\.(h)$/i', $tmpfile)) {
10257 $mime = 'text/plain';
10258 $imgmime = 'text.png';
10259 $srclang = 'h';
10260 $famime = 'file-code';
10261 } elseif (preg_match('/\.(java|jsp)$/i', $tmpfile)) {
10262 $mime = 'text/plain';
10263 $imgmime = 'text.png';
10264 $srclang = 'java';
10265 $famime = 'file-code';
10266 } elseif (preg_match('/\.php([0-9]{1})?$/i', $tmpfile)) {
10267 $mime = 'text/plain';
10268 $imgmime = 'php.png';
10269 $srclang = 'php';
10270 $famime = 'file-code';
10271 } elseif (preg_match('/\.phtml$/i', $tmpfile)) {
10272 $mime = 'text/plain';
10273 $imgmime = 'php.png';
10274 $srclang = 'php';
10275 $famime = 'file-code';
10276 } elseif (preg_match('/\.(pl|pm)$/i', $tmpfile)) {
10277 $mime = 'text/plain';
10278 $imgmime = 'pl.png';
10279 $srclang = 'perl';
10280 $famime = 'file-code';
10281 } elseif (preg_match('/\.sql$/i', $tmpfile)) {
10282 $mime = 'text/plain';
10283 $imgmime = 'text.png';
10284 $srclang = 'sql';
10285 $famime = 'file-code';
10286 } elseif (preg_match('/\.js$/i', $tmpfile)) {
10287 $mime = 'text/x-javascript';
10288 $imgmime = 'jscript.png';
10289 $srclang = 'js';
10290 $famime = 'file-code';
10291 } elseif (preg_match('/\.odp$/i', $tmpfile)) { // Open office
10292 $mime = 'application/vnd.oasis.opendocument.presentation';
10293 $imgmime = 'ooffice.png';
10294 $famime = 'file-powerpoint';
10295 } elseif (preg_match('/\.ods$/i', $tmpfile)) {
10296 $mime = 'application/vnd.oasis.opendocument.spreadsheet';
10297 $imgmime = 'ooffice.png';
10298 $famime = 'file-excel';
10299 } elseif (preg_match('/\.odt$/i', $tmpfile)) {
10300 $mime = 'application/vnd.oasis.opendocument.text';
10301 $imgmime = 'ooffice.png';
10302 $famime = 'file-word';
10303 } elseif (preg_match('/\.mdb$/i', $tmpfile)) { // MS Office
10304 $mime = 'application/msaccess';
10305 $imgmime = 'mdb.png';
10306 $famime = 'file';
10307 } elseif (preg_match('/\.doc[xm]?$/i', $tmpfile)) {
10308 $mime = 'application/msword';
10309 $imgmime = 'doc.png';
10310 $famime = 'file-word';
10311 } elseif (preg_match('/\.dot[xm]?$/i', $tmpfile)) {
10312 $mime = 'application/msword';
10313 $imgmime = 'doc.png';
10314 $famime = 'file-word';
10315 } elseif (preg_match('/\.xlt(x)?$/i', $tmpfile)) {
10316 $mime = 'application/vnd.ms-excel';
10317 $imgmime = 'xls.png';
10318 $famime = 'file-excel';
10319 } elseif (preg_match('/\.xla(m)?$/i', $tmpfile)) {
10320 $mime = 'application/vnd.ms-excel';
10321 $imgmime = 'xls.png';
10322 $famime = 'file-excel';
10323 } elseif (preg_match('/\.xls$/i', $tmpfile)) {
10324 $mime = 'application/vnd.ms-excel';
10325 $imgmime = 'xls.png';
10326 $famime = 'file-excel';
10327 } elseif (preg_match('/\.xls[bmx]$/i', $tmpfile)) {
10328 $mime = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
10329 $imgmime = 'xls.png';
10330 $famime = 'file-excel';
10331 } elseif (preg_match('/\.pps[mx]?$/i', $tmpfile)) {
10332 $mime = 'application/vnd.ms-powerpoint';
10333 $imgmime = 'ppt.png';
10334 $famime = 'file-powerpoint';
10335 } elseif (preg_match('/\.ppt[mx]?$/i', $tmpfile)) {
10336 $mime = 'application/x-mspowerpoint';
10337 $imgmime = 'ppt.png';
10338 $famime = 'file-powerpoint';
10339 } elseif (preg_match('/\.pdf$/i', $tmpfile)) { // Other
10340 $mime = 'application/pdf';
10341 $imgmime = 'pdf.png';
10342 $famime = 'file-pdf';
10343 } elseif (preg_match('/\.bat$/i', $tmpfile)) { // Scripts
10344 $mime = 'text/x-bat';
10345 $imgmime = 'script.png';
10346 $srclang = 'dos';
10347 $famime = 'file-code';
10348 } elseif (preg_match('/\.sh$/i', $tmpfile)) {
10349 $mime = 'text/x-sh';
10350 $imgmime = 'script.png';
10351 $srclang = 'bash';
10352 $famime = 'file-code';
10353 } elseif (preg_match('/\.ksh$/i', $tmpfile)) {
10354 $mime = 'text/x-ksh';
10355 $imgmime = 'script.png';
10356 $srclang = 'bash';
10357 $famime = 'file-code';
10358 } elseif (preg_match('/\.bash$/i', $tmpfile)) {
10359 $mime = 'text/x-bash';
10360 $imgmime = 'script.png';
10361 $srclang = 'bash';
10362 $famime = 'file-code';
10363 } elseif (preg_match('/\.ico$/i', $tmpfile)) { // Images
10364 $mime = 'image/x-icon';
10365 $imgmime = 'image.png';
10366 $famime = 'file-image';
10367 } elseif (preg_match('/\.(jpg|jpeg)$/i', $tmpfile)) {
10368 $mime = 'image/jpeg';
10369 $imgmime = 'image.png';
10370 $famime = 'file-image';
10371 } elseif (preg_match('/\.png$/i', $tmpfile)) {
10372 $mime = 'image/png';
10373 $imgmime = 'image.png';
10374 $famime = 'file-image';
10375 } elseif (preg_match('/\.gif$/i', $tmpfile)) {
10376 $mime = 'image/gif';
10377 $imgmime = 'image.png';
10378 $famime = 'file-image';
10379 } elseif (preg_match('/\.bmp$/i', $tmpfile)) {
10380 $mime = 'image/bmp';
10381 $imgmime = 'image.png';
10382 $famime = 'file-image';
10383 } elseif (preg_match('/\.(tif|tiff)$/i', $tmpfile)) {
10384 $mime = 'image/tiff';
10385 $imgmime = 'image.png';
10386 $famime = 'file-image';
10387 } elseif (preg_match('/\.svg$/i', $tmpfile)) {
10388 $mime = 'image/svg+xml';
10389 $imgmime = 'image.png';
10390 $famime = 'file-image';
10391 } elseif (preg_match('/\.webp$/i', $tmpfile)) {
10392 $mime = 'image/webp';
10393 $imgmime = 'image.png';
10394 $famime = 'file-image';
10395 } elseif (preg_match('/\.vcs$/i', $tmpfile)) { // Calendar
10396 $mime = 'text/calendar';
10397 $imgmime = 'other.png';
10398 $famime = 'file-alt';
10399 } elseif (preg_match('/\.ics$/i', $tmpfile)) {
10400 $mime = 'text/calendar';
10401 $imgmime = 'other.png';
10402 $famime = 'file-alt';
10403 } elseif (preg_match('/\.torrent$/i', $tmpfile)) { // Other
10404 $mime = 'application/x-bittorrent';
10405 $imgmime = 'other.png';
10406 $famime = 'file-o';
10407 } elseif (preg_match('/\.(mp3|ogg|au|wav|wma|mid)$/i', $tmpfile)) { // Audio
10408 $mime = 'audio';
10409 $imgmime = 'audio.png';
10410 $famime = 'file-audio';
10411 } elseif (preg_match('/\.mp4$/i', $tmpfile)) { // Video
10412 $mime = 'video/mp4';
10413 $imgmime = 'video.png';
10414 $famime = 'file-video';
10415 } elseif (preg_match('/\.ogv$/i', $tmpfile)) {
10416 $mime = 'video/ogg';
10417 $imgmime = 'video.png';
10418 $famime = 'file-video';
10419 } elseif (preg_match('/\.webm$/i', $tmpfile)) {
10420 $mime = 'video/webm';
10421 $imgmime = 'video.png';
10422 $famime = 'file-video';
10423 } elseif (preg_match('/\.avif$/i', $tmpfile)) {
10424 $mime = 'image/avif';
10425 $imgmime = 'image.png';
10426 $famime = 'file-image';
10427 } elseif (preg_match('/\.avi$/i', $tmpfile)) {
10428 $mime = 'video/x-msvideo';
10429 $imgmime = 'video.png';
10430 $famime = 'file-video';
10431 } elseif (preg_match('/\.divx$/i', $tmpfile)) {
10432 $mime = 'video/divx';
10433 $imgmime = 'video.png';
10434 $famime = 'file-video';
10435 } elseif (preg_match('/\.xvid$/i', $tmpfile)) {
10436 $mime = 'video/xvid';
10437 $imgmime = 'video.png';
10438 $famime = 'file-video';
10439 } elseif (preg_match('/\.(wmv|mpg|mpeg)$/i', $tmpfile)) {
10440 $mime = 'video';
10441 $imgmime = 'video.png';
10442 $famime = 'file-video';
10443 } elseif (preg_match('/\.(zip|rar|gz|tgz|xz|z|cab|bz2|7z|tar|lzh|zst)$/i', $tmpfile)) { // Archive
10444 // application/xxx where zzz is zip, ...
10445 $mime = 'archive';
10446 $imgmime = 'archive.png';
10447 $famime = 'file-archive';
10448 } elseif (preg_match('/\.(exe|com)$/i', $tmpfile)) { // Exe
10449 $mime = 'application/octet-stream';
10450 $imgmime = 'other.png';
10451 $famime = 'file-o';
10452 } elseif (preg_match('/\.(dll|lib|o|so|a)$/i', $tmpfile)) { // Lib
10453 $mime = 'library';
10454 $imgmime = 'library.png';
10455 $famime = 'file-o';
10456 } elseif (preg_match('/\.err$/i', $tmpfile)) { // phpcs:ignore
10457 $mime = 'error';
10458 $imgmime = 'error.png';
10459 $famime = 'file-alt';
10460 }
10461
10462 if ($famime == 'file-o') {
10463 // file-o seems to not work in fontawesome 5
10464 $famime = 'file';
10465 }
10466
10467 // Return mimetype string
10468 switch ((int) $mode) {
10469 case 1:
10470 $tmp = explode('/', $mime);
10471 return (!empty($tmp[1]) ? $tmp[1] : $tmp[0]);
10472 case 2:
10473 return $imgmime;
10474 case 3:
10475 return $srclang;
10476 case 4:
10477 return $famime;
10478 }
10479 return $mime;
10480}
10481
10493function getDictionaryValue($tablename, $field, $id, $checkentity = false, $rowidfield = 'rowid')
10494{
10495 global $conf, $db;
10496
10497 $tablename = preg_replace('/^' . preg_quote(MAIN_DB_PREFIX, '/') . '/', '', $tablename); // Clean name of table for backward compatibility.
10498
10499 $dictvalues = (isset($conf->cache['dictvalues_' . $tablename]) ? $conf->cache['dictvalues_' . $tablename] : null);
10500
10501 if (is_null($dictvalues)) {
10502 $dictvalues = array();
10503
10504 $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
10505 if ($checkentity) {
10506 $sql .= ' AND entity IN (0,' . getEntity($tablename) . ')';
10507 }
10508
10509 $resql = $db->query($sql);
10510 if ($resql) {
10511 while ($obj = $db->fetch_object($resql)) {
10512 $dictvalues[$obj->$rowidfield] = $obj; // $obj is stdClass
10513 }
10514 } else {
10515 dol_print_error($db);
10516 }
10517
10518 $conf->cache['dictvalues_' . $tablename] = $dictvalues;
10519 }
10520
10521 if (!empty($dictvalues[$id])) {
10522 // Found
10523 $tmp = $dictvalues[$id];
10524 return (property_exists($tmp, $field) ? $tmp->$field : '');
10525 } else {
10526 // Not found
10527 return '';
10528 }
10529}
10530
10537function colorIsLight($stringcolor)
10538{
10539 $stringcolor = str_replace('#', '', $stringcolor);
10540 $res = -1;
10541 if (!empty($stringcolor)) {
10542 $res = 0;
10543 $tmp = explode(',', $stringcolor);
10544 if (count($tmp) > 1) { // This is a comma RGB ('255','255','255')
10545 $r = $tmp[0];
10546 $g = $tmp[1];
10547 $b = $tmp[2];
10548 } else {
10549 $hexr = $stringcolor[0] . $stringcolor[1];
10550 $hexg = $stringcolor[2] . $stringcolor[3];
10551 $hexb = $stringcolor[4] . $stringcolor[5];
10552 $r = hexdec($hexr);
10553 $g = hexdec($hexg);
10554 $b = hexdec($hexb);
10555 }
10556 $bright = (max($r, $g, $b) + min($r, $g, $b)) / 510.0; // HSL algorithm
10557 if ($bright > 0.6) {
10558 $res = 1;
10559 }
10560 }
10561 return $res;
10562}
10563
10572function isVisibleToUserType($type_user, &$menuentry, &$listofmodulesforexternal)
10573{
10574 //print 'type_user='.$type_user.' module='.$menuentry['module'].' enabled='.$menuentry['enabled'].' perms='.$menuentry['perms'];
10575 //print 'ok='.in_array($menuentry['module'], $listofmodulesforexternal);
10576 if (empty($menuentry['enabled'])) {
10577 return 0; // Entry disabled by condition
10578 }
10579 if ($type_user && array_key_exists('module', $menuentry) && $menuentry['module']) {
10580 $tmploops = explode('|', $menuentry['module']);
10581 $found = 0;
10582 foreach ($tmploops as $tmploop) {
10583 if (in_array($tmploop, $listofmodulesforexternal)) {
10584 $found++;
10585 break;
10586 }
10587 }
10588 if (!$found) {
10589 return 0; // Entry is for menus all excluded to external users
10590 }
10591 }
10592 if (!$menuentry['perms'] && $type_user) {
10593 return 0; // No permissions and user is external
10594 }
10595 if (!$menuentry['perms'] && getDolGlobalString('MAIN_MENU_HIDE_UNAUTHORIZED')) {
10596 return 0; // No permissions and option to hide when not allowed, even for internal user, is on
10597 }
10598 if (!$menuentry['perms']) {
10599 return 2; // No permissions and user is external
10600 }
10601 return 1;
10602}
10603
10611function roundUpToNextMultiple($n, $x = 5)
10612{
10613 $result = (ceil($n) % $x === 0) ? ceil($n) : (round(($n + $x / 2) / $x) * $x);
10614 return (int) $result;
10615}
10616
10617
10628function getElementProperties($elementType)
10629{
10630 global $conf, $db, $hookmanager;
10631
10632 $regs = array();
10633
10634 //var_dump($elementType);
10635
10636 $classfile = $classname = $classpath = $subdir = $dir_output = $dir_temp = $parent_element = '';
10637
10638 // Parse element/subelement
10639 $module = $elementType;
10640 $element = $elementType;
10641 $subelement = $elementType;
10642 $table_element = $elementType;
10643
10644 // If we ask a resource form external module (instead of default path)
10645 if (preg_match('/^([^@]+)@([^@]+)$/i', $elementType, $regs)) { // 'myobject@mymodule' (usually external modules)
10646 $element = $subelement = $regs[1];
10647 $table_element = $regs[2].'_'.$regs[1];
10648 $subdir = '/'.$regs[1];
10649 $module = $regs[2];
10650 } elseif (preg_match('/^([^_]+)_([^_]+)/i', $element, $regs)) { // old deprecated syntax: 'myobject_mysubobject' with myobject=mymodule, example 'project_task'
10651 // This is an alternative syntax to 'myobject@mymodule', so it must not be applied when the previous case already matched,
10652 // otherwise the module resolved from the '@' syntax would be overwritten by a wrong guess when $element contains a '_'.
10653 $module = $element = $regs[1];
10654 $table_element = $elementType;
10655 $subelement = $regs[2];
10656 }
10657
10658 // Object lines will use parent classpath and module ref
10659 if (substr($elementType, -3) == 'det') {
10660 $module = preg_replace('/det$/', '', $element);
10661 $subelement = preg_replace('/det$/', '', $subelement);
10662 $classpath = $module . '/class';
10663 $classfile = $module;
10664 $classname = preg_replace('/det$/', 'Line', $element);
10665 if (in_array($module, array('expedition', 'propale', 'facture', 'contrat', 'fichinter', 'ficheinter', 'supplier_order', 'commandefournisseur'))) {
10666 $classname = preg_replace('/det$/', 'Ligne', $element);
10667 }
10668 }
10669 // For compatibility and to work with non standard path
10670 if ($elementType == "action" || $elementType == "actioncomm") {
10671 $classpath = 'comm/action/class';
10672 $subelement = 'Actioncomm';
10673 $module = 'agenda';
10674 $table_element = 'actioncomm';
10675 } elseif ($elementType == 'cronjob') {
10676 $classpath = 'cron/class';
10677 $module = 'cron';
10678 $table_element = 'cron';
10679 } elseif ($elementType == 'adherent_type') {
10680 $classpath = 'adherents/class';
10681 $classfile = 'adherent_type';
10682 $module = 'adherent';
10683 $subelement = 'adherent_type';
10684 $classname = 'AdherentType';
10685 $table_element = 'adherent_type';
10686 } elseif ($elementType == 'bank_account' || $elementType == 'bank' || $elementType == 'banque') {
10687 // 'bank' is the value used for the modulepart when downloading files attached to a bank account
10688 $classpath = 'compta/bank/class';
10689 $module = 'bank'; // We need $conf->bank->dir_output and not $conf->banque->dir_output
10690 $classfile = 'account';
10691 $classname = 'Account';
10692 $element = $subelement = 'bank_account';
10693 $table_element = 'bank_account';
10694 } elseif ($elementType == 'bank_line') {
10695 $classpath = 'compta/bank/class';
10696 $module = 'bank'; // We need $conf->bank->dir_output and not $conf->banque->dir_output
10697 $classfile = 'account';
10698 $classname = 'AccountLine';
10699 } elseif ($elementType == 'remisecheque') {
10700 $classpath = 'compta/paiement/cheque/class';
10701 $classfile = 'remisecheque';
10702 $classname = 'RemiseCheque';
10703 $module = 'bank';
10704 $element = 'chequereceipt';
10705 $subelement = 'cheque';
10706 $table_element = 'bordereau_cheque';
10707 } elseif ($elementType == 'category') {
10708 $classpath = 'categories/class';
10709 $module = 'categorie';
10710 $subelement = 'categorie';
10711 $table_element = 'categorie';
10712 } elseif ($elementType == 'contact') {
10713 $classpath = 'contact/class';
10714 $classfile = 'contact';
10715 $module = 'societe';
10716 $subelement = 'contact';
10717 $table_element = 'socpeople';
10718 $subdir = '/contact';
10719 } elseif ($elementType == 'inventory') {
10720 $module = 'product';
10721 $classpath = 'product/inventory/class';
10722 } elseif ($elementType == 'inventoryline') {
10723 $module = 'product';
10724 $classpath = 'product/inventory/class';
10725 $table_element = 'inventorydet';
10726 $parent_element = 'inventory';
10727 } elseif ($elementType == 'stock' || $elementType == 'entrepot' || $elementType == 'warehouse') {
10728 $module = 'stock';
10729 $classpath = 'product/stock/class';
10730 $classfile = 'entrepot';
10731 $classname = 'Entrepot';
10732 $table_element = 'entrepot';
10733 } elseif ($elementType == 'project' || $elementType == 'projet') {
10734 $classpath = 'projet/class';
10735 $module = 'projet';
10736 $table_element = 'projet';
10737 $classfile = 'project';
10738 $classname = 'Project';
10739 } elseif ($elementType == 'project_task') {
10740 $classpath = 'projet/class';
10741 $module = 'projet';
10742 $subelement = 'task';
10743 $table_element = 'projet_task';
10744 } elseif ($elementType == 'mo') {
10745 $classpath = 'mrp/class';
10746 $module = 'mrp';
10747 $classfile = 'mo';
10748 $classname = 'Mo';
10749 $table_element = 'mrp_mo';
10750 } elseif ($elementType == 'facture' || $elementType == 'invoice') {
10751 $classpath = 'compta/facture/class';
10752 $module = 'facture';
10753 $subelement = 'facture';
10754 $table_element = 'facture';
10755 } elseif ($elementType == 'facturedet') {
10756 $classpath = 'compta/facture/class';
10757 $classfile = 'facture';
10758 $classname = 'FactureLigne';
10759 $module = 'facture';
10760 $table_element = 'facturedet';
10761 $parent_element = 'facture';
10762 } elseif ($elementType == 'facturerec' || $elementType == 'facture_rec') {
10763 $classpath = 'compta/facture/class';
10764 $classfile = 'facture-rec';
10765 $module = 'facture';
10766 $classname = 'FactureRec';
10767 } elseif ($elementType == 'commande' || $elementType == 'order') {
10768 $classpath = 'commande/class';
10769 $module = 'commande';
10770 $subelement = 'commande';
10771 $table_element = 'commande';
10772 } elseif ($elementType == 'commandedet') {
10773 $classpath = 'commande/class';
10774 $classfile = 'commande';
10775 $classname = 'OrderLine';
10776 $module = 'commande';
10777 $table_element = 'commandedet';
10778 $parent_element = 'commande';
10779 } elseif ($elementType == 'propal') {
10780 $classpath = 'comm/propal/class';
10781 $table_element = 'propal';
10782 } elseif ($elementType == 'propaldet') {
10783 $classpath = 'comm/propal/class';
10784 $classfile = 'propal';
10785 $subelement = 'propaleligne';
10786 $module = 'propal';
10787 $table_element = 'propaldet';
10788 $parent_element = 'propal';
10789 } elseif ($elementType == 'shipping' || $elementType == 'shipment') {
10790 $classpath = 'expedition/class';
10791 $classfile = 'expedition';
10792 $classname = 'Expedition';
10793 $module = 'expedition';
10794 $table_element = 'expedition';
10795 } elseif ($elementType == 'expeditiondet' || $elementType == 'shippingdet') {
10796 $classpath = 'expedition/class';
10797 $classfile = 'expedition';
10798 $classname = 'ExpeditionLigne';
10799 $module = 'expedition';
10800 $table_element = 'expeditiondet';
10801 $parent_element = 'expedition';
10802 } elseif ($elementType == 'delivery_note') {
10803 $classpath = 'delivery/class';
10804 $subelement = 'delivery';
10805 $module = 'expedition';
10806 } elseif ($elementType == 'delivery') {
10807 $classpath = 'delivery/class';
10808 $subelement = 'delivery';
10809 $module = 'expedition';
10810 } elseif ($elementType == 'deliverydet') {
10811 // @todo
10812 } elseif ($elementType == 'supplier_proposal') {
10813 $classpath = 'supplier_proposal/class';
10814 $module = 'supplier_proposal';
10815 $element = 'supplierproposal';
10816 $classfile = 'supplier_proposal';
10817 $subelement = 'supplierproposal';
10818 } elseif ($elementType == 'supplier_proposaldet') {
10819 $classpath = 'supplier_proposal/class';
10820 $module = 'supplier_proposal';
10821 $classfile = 'supplier_proposal';
10822 $classname = 'SupplierProposalLine';
10823 $table_element = 'supplier_proposaldet';
10824 $parent_element = 'supplier_proposal';
10825 } elseif ($elementType == 'contract') {
10826 $classpath = 'contrat/class';
10827 $module = 'contrat';
10828 $subelement = 'contrat';
10829 $table_element = 'contract';
10830 } elseif ($elementType == 'contratdet') {
10831 $classpath = 'contrat/class';
10832 $module = 'contrat';
10833 $table_element = 'contratdet';
10834 $parent_element = 'contrat';
10835 } elseif ($elementType == 'mailing') {
10836 $classpath = 'comm/mailing/class';
10837 $module = 'mailing';
10838 $classfile = 'mailing';
10839 $classname = 'Mailing';
10840 $subelement = '';
10841 } elseif ($elementType == 'member' || $elementType == 'adherent') {
10842 $classpath = 'adherents/class';
10843 $module = 'adherent';
10844 $subelement = 'adherent';
10845 $table_element = 'adherent';
10846 } elseif ($elementType == 'subscription') {
10847 $classpath = 'adherents/class';
10848 $classfile = 'subscription';
10849 $module = 'adherent';
10850 $subelement = 'subscription';
10851 $classname = 'Subscription';
10852 $table_element = 'subscription';
10853 } elseif ($elementType == 'usergroup') {
10854 $classpath = 'user/class';
10855 $module = 'user';
10856 } elseif ($elementType == 'mrp') {
10857 $classpath = 'mrp/class';
10858 $classfile = 'mo';
10859 $classname = 'Mo';
10860 $module = 'mrp';
10861 $subelement = '';
10862 $table_element = 'mrp_mo';
10863 } elseif ($elementType == 'mrp_production') {
10864 $classpath = 'mrp/class';
10865 $classfile = 'mo';
10866 $classname = 'MoLine';
10867 $module = 'mrp';
10868 $subelement = '';
10869 $table_element = 'mrp_production';
10870 $parent_element = 'mo';
10871 } elseif ($elementType == 'cabinetmed_cons') {
10872 $classpath = 'cabinetmed/class';
10873 $module = 'cabinetmed';
10874 $subelement = 'cabinetmedcons';
10875 $table_element = 'cabinetmedcons';
10876 } elseif ($elementType == 'fichinter' || $elementType == 'ficheinter') {
10877 $classpath = 'fichinter/class';
10878 $module = 'ficheinter';
10879 $subelement = 'fichinter';
10880 $table_element = 'fichinter';
10881 } elseif ($elementType == 'dolresource' || $elementType == 'resource') {
10882 $classpath = 'resource/class';
10883 $module = 'resource';
10884 $subelement = 'dolresource';
10885 $table_element = 'resource';
10886 } elseif ($elementType == 'opensurvey_sondage') {
10887 $classpath = 'opensurvey/class';
10888 $module = 'opensurvey';
10889 $subelement = 'opensurveysondage';
10890 } elseif ($elementType == 'order_supplier' || $elementType == 'supplier_order' || $elementType == 'commande_fournisseur' || $elementType == 'commandefournisseur') {
10891 $classpath = 'fourn/class';
10892 $module = 'fournisseur';
10893 $classfile = 'fournisseur.commande';
10894 $element = 'order_supplier';
10895 $subelement = '';
10896 $classname = 'CommandeFournisseur';
10897 $table_element = 'commande_fournisseur';
10898 } elseif ($elementType == 'commande_fournisseurdet') {
10899 $classpath = 'fourn/class';
10900 $module = 'fournisseur';
10901 $classfile = 'fournisseur.commande';
10902 $element = 'commande_fournisseurdet';
10903 $subelement = '';
10904 $classname = 'CommandeFournisseurLigne';
10905 $table_element = 'commande_fournisseurdet';
10906 $parent_element = 'commande_fournisseur';
10907 } elseif ($elementType == 'invoice_supplier' || $elementType == 'supplier_invoice' || $elementType == 'facture_fourn') {
10908 $classpath = 'fourn/class';
10909 $module = 'fournisseur';
10910 $classfile = 'fournisseur.facture';
10911 $element = 'invoice_supplier';
10912 $subelement = '';
10913 $classname = 'FactureFournisseur';
10914 $table_element = 'facture_fourn';
10915 } elseif ($elementType == 'invoice_supplier_rec' || $elementType == 'supplier_invoice_rec' || $elementType == 'facture_fourn_rec') {
10916 $classpath = 'fourn/class';
10917 $module = 'fournisseur';
10918 $classfile = 'fournisseur.facture-rec';
10919 $element = 'invoice_supplier_rec';
10920 $subelement = '';
10921 $classname = 'FactureFournisseurRec';
10922 $table_element = 'facture_fourn_rec';
10923 } elseif ($elementType == 'facture_fourn_det') {
10924 $classpath = 'fourn/class';
10925 $module = 'fournisseur';
10926 $classfile = 'fournisseur.facture';
10927 $element = 'facture_fourn_det';
10928 $subelement = '';
10929 $classname = 'SupplierInvoiceLine';
10930 $table_element = 'facture_fourn_det';
10931 $parent_element = 'invoice_supplier';
10932 } elseif ($elementType == "service") {
10933 $classpath = 'product/class';
10934 $module = 'product';
10935 $subelement = 'product';
10936 $table_element = 'product';
10937 } elseif ($elementType == 'product_attribute') {
10938 $module = 'variants';
10939 $element = 'product_attribute';
10940 $subelement = 'product_attribute';
10941 $classpath = 'variants/class';
10942 $classfile = 'ProductAttribute';
10943 $classname = 'ProductAttribute';
10944 $table_element = 'product_attribute';
10945 } elseif ($elementType == 'product_attribute_value') {
10946 $module = 'variants';
10947 $element = 'product_attribute_value';
10948 $subelement = 'product_attribute_value';
10949 $classpath = 'variants/class';
10950 $classfile = 'ProductAttributeValue';
10951 $classname = 'ProductAttributeValue';
10952 $table_element = 'product_attribute_value';
10953 $parent_element = 'product_attribute';
10954 } elseif ($elementType == 'salary') {
10955 $classpath = 'salaries/class';
10956 $module = 'salaries';
10957 } elseif ($elementType == 'payment_salary') {
10958 $classpath = 'salaries/class';
10959 $classfile = 'paymentsalary';
10960 $classname = 'PaymentSalary';
10961 $module = 'salaries';
10962 } elseif ($elementType == 'payment') {
10963 $classpath = 'compta/paiement/class';
10964 $classfile = 'paiement';
10965 $classname = 'Paiement';
10966 $module = 'facture'; // A customer payment belongs to the invoice module, there is no 'compta' module
10967 $element = 'payment';
10968 $subelement = 'payment';
10969 $table_element = 'paiement';
10970 } elseif ($elementType == 'payment_supplier') {
10971 $classpath = 'fourn/class';
10972 $classfile = 'paiementfourn';
10973 $classname = 'PaiementFourn';
10974 $module = 'fournisseur';
10975 $element = 'payment_supplier';
10976 $subelement = 'payment_supplier';
10977 $table_element = 'paiementfourn';
10978 } elseif ($elementType == 'payment_various') {
10979 $classpath = 'compta/bank/class';
10980 $classfile = 'paymentvarious';
10981 $classname = 'PaymentVarious';
10982 $module = 'bank'; // We need $conf->bank->dir_output and not $conf->banque->dir_output
10983 $element = 'payment_various';
10984 $subelement = 'payment_various';
10985 $table_element = 'payment_various';
10986 } elseif ($elementType == 'stocktransfer') {
10987 $classpath = 'product/stock/stocktransfer/class';
10988 $classfile = 'stocktransfer';
10989 $classname = 'StockTransfer'; // Not the ucfirst() of the element, so it must be set explicitly
10990 $module = 'stocktransfer';
10991 $subelement = 'stocktransfer';
10992 $table_element = 'stocktransfer_stocktransfer';
10993 } elseif ($elementType == 'job' || $elementType == 'position' || $elementType == 'skill' || $elementType == 'evaluation') {
10994 $classpath = 'hrm/class';
10995 $classfile = $elementType;
10996 $classname = ucfirst($elementType);
10997 $module = 'hrm';
10998 $subelement = $elementType;
10999 $table_element = ($elementType == 'position' ? 'hrm_job_user' : 'hrm_'.$elementType);
11000 $subdir = '/'.$elementType;
11001 } elseif ($elementType == 'productlot') {
11002 $module = 'productbatch';
11003 $classpath = 'product/stock/class';
11004 $classfile = 'productlot';
11005 $classname = 'Productlot';
11006 $element = 'productlot';
11007 $subelement = '';
11008 $table_element = 'product_lot';
11009 } elseif ($elementType == 'societeaccount') {
11010 $classpath = 'societe/class';
11011 $classfile = 'societeaccount';
11012 $classname = 'SocieteAccount';
11013 $module = 'societe';
11014 } elseif ($elementType == 'websitepage' || $elementType == 'website_page') {
11015 $classpath = 'website/class';
11016 $classfile = 'websitepage';
11017 $classname = 'Websitepage';
11018 $module = 'website';
11019 $subelement = 'websitepage';
11020 $table_element = 'website_page';
11021 } elseif ($elementType == 'fiscalyear') {
11022 $classpath = 'core/class';
11023 $module = 'accounting';
11024 $subelement = 'fiscalyear';
11025 } elseif ($elementType == 'chargesociales') {
11026 $classpath = 'compta/sociales/class';
11027 $module = 'tax';
11028 $table_element = 'chargesociales';
11029 } elseif ($elementType == 'tva') {
11030 $classpath = 'compta/tva/class';
11031 $module = 'tax';
11032 $subdir = '/vat';
11033 $table_element = 'tva';
11034 } elseif ($elementType == 'emailsenderprofile') {
11035 $module = '';
11036 $classpath = 'core/class';
11037 $classfile = 'emailsenderprofile';
11038 $classname = 'EmailSenderProfile';
11039 $table_element = 'c_email_senderprofile';
11040 $subelement = '';
11041 } elseif ($elementType == 'conferenceorboothattendee') {
11042 $classpath = 'eventorganization/class';
11043 $classfile = 'conferenceorboothattendee';
11044 $classname = 'ConferenceOrBoothAttendee';
11045 $module = 'eventorganization';
11046 } elseif ($elementType == 'conferenceorbooth') {
11047 $classpath = 'eventorganization/class';
11048 $classfile = 'conferenceorbooth';
11049 $classname = 'ConferenceOrBooth';
11050 $module = 'eventorganization';
11051 $subdir = '/conferenceorbooth';
11052 } elseif ($elementType == 'ccountry') {
11053 $module = '';
11054 $classpath = 'core/class';
11055 $classfile = 'ccountry';
11056 $classname = 'Ccountry';
11057 $table_element = 'c_country';
11058 $subelement = '';
11059 } elseif ($elementType == 'ecmfiles') {
11060 $module = 'ecm';
11061 $classpath = 'ecm/class';
11062 $classfile = 'ecmfiles';
11063 $classname = 'Ecmfiles';
11064 $table_element = 'ecmfiles';
11065 $subelement = '';
11066 } elseif ($elementType == 'knowledgerecord' || $elementType == 'knowledgemanagement') {
11067 $module = 'knowledgemanagement';
11068 $classpath = 'knowledgemanagement/class';
11069 $classfile = 'knowledgerecord';
11070 $classname = 'KnowledgeRecord';
11071 $table_element = 'knowledgemanagement_knowledgerecord';
11072 $subelement = '';
11073 } elseif ($elementType == 'customer') {
11074 $module = 'societe';
11075 $classpath = 'societe/class';
11076 $classfile = 'client';
11077 $classname = 'Client';
11078 $table_element = 'societe';
11079 $subelement = '';
11080 } elseif ($elementType == 'fournisseur' || $elementType == 'supplier') {
11081 $module = 'societe';
11082 $classpath = 'fourn/class';
11083 $classfile = 'fournisseur';
11084 $classname = 'Fournisseur';
11085 $table_element = 'societe';
11086 $subelement = '';
11087 } elseif ($elementType == 'recruitmentcandidature') {
11088 $module = 'recruitment';
11089 $classfile = 'recruitmentcandidature';
11090 $classpath = 'recruitment/class';
11091 $classname = 'RecruitmentCandidature';
11092 $subelement = 'recruitmentcandidature';
11093 $subdir = '/recruitmentcandidature';
11094 } elseif ($elementType == 'recruitmentjobposition') {
11095 $module = 'recruitment';
11096 $classfile = 'recruitmentjobposition';
11097 $classpath = 'recruitment/class';
11098 $classname = 'RecruitmentJobPosition';
11099 $subelement = 'recruitmentjobposition';
11100 $subdir = '/recruitmentjobposition';
11101 } elseif ($elementType == 'recruitment') {
11102 // The recruitment module has no class of its own, and the document links of its objects use
11103 // the module name as modulepart (see document.php), so the module name resolves to the job
11104 // position, the main object of the module.
11105 $module = 'recruitment';
11106 $classfile = 'recruitmentjobposition';
11107 $classpath = 'recruitment/class';
11108 $classname = 'RecruitmentJobPosition';
11109 $element = $subelement = 'recruitmentjobposition';
11110 $table_element = 'recruitment_recruitmentjobposition';
11111 $subdir = '/recruitmentjobposition';
11112 } elseif ($elementType == 'product_attribute_combination') {
11113 $module = 'variants';
11114 $classpath = 'variants/class';
11115 $classfile = 'ProductCombination';
11116 $classname = 'ProductCombination';
11117 $element = 'productcombination';
11118 $subelement = '';
11119 $table_element = 'product_attribute_combination';
11120 } elseif ($elementType == 'product_attribute_combination2val') {
11121 $module = 'variants';
11122 $classpath = 'variants/class';
11123 $classfile = 'ProductCombination2ValuePair';
11124 $classname = 'ProductCombination2ValuePair';
11125 $element = 'productcombination2valuepair';
11126 $subelement = '';
11127 $table_element = 'product_attribute_combination2val';
11128 } elseif ($elementType == 'product_attribute_combination_price_level') {
11129 // Class ProductCombinationLevel is declared inside ProductCombination.class.php
11130 $module = 'variants';
11131 $classpath = 'variants/class';
11132 $classfile = 'ProductCombination';
11133 $classname = 'ProductCombinationLevel';
11134 $element = 'productcombinationlevel';
11135 $subelement = '';
11136 $table_element = 'product_attribute_combination_price_level';
11137 }
11138
11139
11140 if (empty($classfile)) {
11141 $classfile = strtolower($subelement);
11142 }
11143 if (empty($classname)) {
11144 $classname = ucfirst($subelement);
11145 }
11146 if (empty($classpath)) {
11147 $classpath = $module . '/class';
11148 }
11149
11150 //print 'getElementProperties subdir='.$subdir;
11151
11152 // Set dir_output
11153 if ($module && isset($conf->$module)) { // The generic case
11154 if (!empty($conf->$module->multidir_output[$conf->entity])) {
11155 $dir_output = $conf->$module->multidir_output[$conf->entity];
11156 } elseif (!empty($conf->$module->output[$conf->entity])) {
11157 $dir_output = $conf->$module->output[$conf->entity];
11158 } elseif (!empty($conf->$module->dir_output)) {
11159 $dir_output = $conf->$module->dir_output;
11160 }
11161 if (!empty($conf->$module->multidir_temp[$conf->entity])) {
11162 $dir_temp = $conf->$module->multidir_temp[$conf->entity];
11163 } elseif (!empty($conf->$module->temp[$conf->entity])) {
11164 $dir_temp = $conf->$module->temp[$conf->entity];
11165 } elseif (!empty($conf->$module->dir_temp)) {
11166 $dir_temp = $conf->$module->dir_temp;
11167 }
11168 }
11169
11170 // Overwrite value for special cases
11171 if ($element == 'order_supplier' && isModEnabled('fournisseur')) {
11172 $dir_output = $conf->fournisseur->commande->dir_output;
11173 $dir_temp = $conf->fournisseur->commande->dir_temp;
11174 } elseif ($element == 'invoice_supplier' && isModEnabled('fournisseur')) {
11175 $dir_output = $conf->fournisseur->facture->dir_output;
11176 $dir_temp = $conf->fournisseur->facture->dir_temp;
11177 } elseif ($elementType == 'payment' && isModEnabled('invoice') && isset($conf->compta->payment)) {
11178 // A customer payment is stored into a sub object of $conf, not handled by the generic case.
11179 // Note: we must test $elementType and not $element, because the 'myobject_mysubobject' rule above
11180 // rewrites $element to 'payment' for the element 'payment_salary' too, which is stored elsewhere.
11181 $dir_output = $conf->compta->payment->dir_output;
11182 $dir_temp = $conf->compta->payment->dir_temp;
11183 } elseif ($elementType == 'payment_supplier' && isModEnabled('fournisseur') && isset($conf->fournisseur->payment)) {
11184 $dir_output = $conf->fournisseur->payment->dir_output;
11185 $dir_temp = $conf->fournisseur->payment->dir_temp;
11186 }
11187
11188 // The sub directory must not be appended when the module is disabled, because $dir_output is then empty
11189 // and we would return a path at the root of the file system instead of an empty string.
11190 if (!empty($dir_output)) {
11191 $dir_output .= $subdir;
11192 }
11193 if (!empty($dir_temp)) {
11194 //$dir_temp = preg_replace('/\/temp$/', '', $dir_temp).$subdir.'/temp'; // To get mymoduledir/myobject/temp
11195 $dir_temp .= $subdir; // To get mymoduledir/temp/myobject
11196 }
11197
11198 $elementProperties = array(
11199 'module' => $module,
11200 'element' => $element,
11201 'table_element' => $table_element,
11202 'subelement' => $subelement,
11203 'classpath' => $classpath,
11204 'classfile' => $classfile,
11205 'classname' => $classname,
11206 'dir_output' => $dir_output,
11207 'dir_temp' => $dir_temp,
11208 'parent_element' => $parent_element,
11209 );
11210
11211
11212 // Add hook
11213 if (!is_object($hookmanager)) {
11214 include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
11215 $hookmanager = new HookManager($db);
11216 }
11217 $hookmanager->initHooks(array('elementproperties'));
11218
11219
11220 // Hook params
11221 $parameters = array(
11222 'elementType' => $elementType,
11223 'elementProperties' => $elementProperties
11224 );
11225
11226 $reshook = $hookmanager->executeHooks('getElementProperties', $parameters);
11227
11228 if ($reshook) {
11229 $elementProperties = $hookmanager->resArray;
11230 } elseif (!empty($hookmanager->resArray) && is_array($hookmanager->resArray)) { // resArray is always an array but for security against misconfigured external modules
11231 $elementProperties = array_replace($elementProperties, $hookmanager->resArray);
11232 }
11233
11234 // context of elementproperties doesn't need to exist out of this function so delete it to avoid elementproperties context is equal to all
11235 if (($key = array_search('elementproperties', $hookmanager->contextarray)) !== false) {
11236 unset($hookmanager->contextarray[$key]);
11237 }
11238
11239 return $elementProperties;
11240}
11241
11255function fetchObjectByElement($element_id, $element_type, $element_ref = '', $useCache = 0, $maxCacheByType = 10)
11256{
11257 global $db, $conf;
11258
11259 $ret = 0;
11260
11261 $element_prop = getElementProperties($element_type);
11262 //var_dump($element_type, $element_prop);
11263
11264 // Check special cases
11265 if ($element_prop['module'] == 'product' || $element_prop['module'] == 'service') {
11266 // For example, for an extrafield 'product' (shared for both product and service) that is a link to an object,
11267 // this is called with $element_type = 'product' when we need element properties of a service, we must return a product. If we create the
11268 // extrafield for a service, it is not supported and not found when editing the product/service card. So we must keep 'product' for extrafields
11269 // of service and we will return properties of a product.
11270 $ismodenabled = (isModEnabled('product') || isModEnabled('service'));
11271 } elseif ($element_prop['module'] == 'societeaccount') {
11272 $ismodenabled = isModEnabled('website') || isModEnabled('webportal');
11273 } else {
11274 $ismodenabled = isModEnabled($element_prop['module']);
11275 }
11276
11277 //var_dump('element_type='.$element_type);
11278 //var_dump($element_prop);
11279 //var_dump($element_prop['module'].' '.$ismodenabled);
11280 if (is_array($element_prop) && (empty($element_prop['module']) || $ismodenabled)) {
11281 if ($useCache === 1 && $element_id > 0
11282 && !empty($conf->cache['fetchObjectByElement'][$element_type])
11283 && !empty($conf->cache['fetchObjectByElement'][$element_type][$element_id])
11284 && is_object($conf->cache['fetchObjectByElement'][$element_type][$element_id])
11285 ) {
11286 return $conf->cache['fetchObjectByElement'][$element_type][$element_id];
11287 }
11288
11289 $includeresult = dol_include_once('/' . $element_prop['classpath'] . '/' . $element_prop['classfile'] . '.class.php');
11290 if ($includeresult === false) {
11291 dol_syslog('fetchObjectByElement: class file /' . $element_prop['classpath'] . '/' . $element_prop['classfile'] . '.class.php not found for element ' . $element_type, LOG_WARNING);
11292 }
11293 //var_dump('/' . $element_prop['classpath'] . '/' . $element_prop['classfile'] . '.class.php', $element_prop['classname']);
11294
11295 if (class_exists($element_prop['classname'])) {
11296 $className = $element_prop['classname'];
11297 // Never instantiate a PHP internal class: the name can only be a collision with a
11298 // class of the language (for example an element resolved to the native 'Attribute').
11299 try {
11300 $isinternalclass = (new ReflectionClass($className))->isInternal();
11301 } catch (ReflectionException $e) {
11302 $isinternalclass = false;
11303 }
11304 if ($isinternalclass) {
11305 dol_syslog('fetchObjectByElement: refuse to instantiate PHP internal class ' . $className . ' for element ' . $element_type, LOG_ERR);
11306 return -1;
11307 }
11308 $objecttmp = new $className($db);
11309 '@phan-var-force CommonObject $objecttmp';
11312 if ($element_id > 0 || !empty($element_ref)) {
11313 // Special case for job, there is no ref, it is the id
11314 // TODO Replace hard coded code with a test if object has a ref or not.
11315 if (empty($element_id) && !empty($element_ref) && (in_array($objecttmp->element, array('evaluation', 'job', 'position', 'skill')))) {
11316 $element_id = $element_ref;
11317 $element_ref = '';
11318 }
11319
11320 $ret = $objecttmp->fetch($element_id, $element_ref);
11321 if ($ret >= 0) {
11322 if (empty($objecttmp->module)) {
11323 $objecttmp->module = $element_prop['module'];
11324 }
11325
11326 if ($useCache > 0) {
11327 if (!isset($conf->cache['fetchObjectByElement'][$element_type])) {
11328 $conf->cache['fetchObjectByElement'][$element_type] = [];
11329 }
11330
11331 // Manage cache limit
11332 if (! empty($conf->cache['fetchObjectByElement'][$element_type]) && is_array($conf->cache['fetchObjectByElement'][$element_type]) && count($conf->cache['fetchObjectByElement'][$element_type]) >= $maxCacheByType) {
11333 array_shift($conf->cache['fetchObjectByElement'][$element_type]);
11334 }
11335
11336 $conf->cache['fetchObjectByElement'][$element_type][$element_id] = $objecttmp;
11337 }
11338
11339 return $objecttmp;
11340 }
11341 } else {
11342 return $objecttmp; // returned an object without fetch
11343 }
11344 } else {
11345 dol_syslog($element_prop['classname'] . " doesn't exists in /" . $element_prop['classpath'] . "/" . $element_prop['classfile'] . ".class.php");
11346 return -1;
11347 }
11348 }
11349
11350 return $ret;
11351}
11352
11358function getExecutableContent()
11359{
11360 $arrayofregexextension = array(
11361 'htm',
11362 'html',
11363 'shtml',
11364 'js',
11365 'phar',
11366 'php',
11367 'php3',
11368 'php4',
11369 'php5',
11370 'phtml',
11371 'pht',
11372 'pl',
11373 'py',
11374 'cgi',
11375 'ksh',
11376 'sh',
11377 'shtml',
11378 'bash',
11379 'bat',
11380 'cmd',
11381 'wpk',
11382 'exe',
11383 'dmg',
11384 'appimage'
11385 );
11386
11387 return $arrayofregexextension;
11388}
11389
11396function isAFileWithExecutableContent($filename)
11397{
11398 $arrayofregexextension = getExecutableContent();
11399
11400 foreach ($arrayofregexextension as $fileextension) {
11401 if (preg_match('/\.' . preg_quote($fileextension, '/') . '$/i', $filename)) {
11402 return true;
11403 }
11404 }
11405
11406 return false;
11407}
11408
11416function newToken()
11417{
11418 return empty($_SESSION['newtoken']) ? '' : $_SESSION['newtoken'];
11419}
11420
11428function currentToken()
11429{
11430 return isset($_SESSION['token']) ? $_SESSION['token'] : '';
11431}
11432
11438function getNonce()
11439{
11440 global $conf;
11441
11442 if (empty($conf->cache['nonce'])) {
11443 include_once DOL_DOCUMENT_ROOT . '/core/lib/security.lib.php';
11444 $conf->cache['nonce'] = dolGetRandomBytes(8);
11445 }
11446
11447 return $conf->cache['nonce'];
11448}
11449
11450
11459function readfileLowMemory($fullpath_original_file_osencoded, $method = -1)
11460{
11461 if ($method == -1) {
11462 $method = 0;
11463 if (getDolGlobalString('MAIN_FORCE_READFILE_WITH_FREAD')) {
11464 $method = 1;
11465 }
11466 if (getDolGlobalString('MAIN_FORCE_READFILE_WITH_STREAM_COPY')) {
11467 $method = 2;
11468 }
11469 }
11470
11471 // Be sure we don't have output buffering enabled to have readfile working correctly
11472 $level = ob_get_level();
11473 ob_start();
11474 while (ob_get_level() > $level) {
11475 ob_end_clean();
11476 }
11477
11478 // Solution 0
11479 if ($method == 0) {
11480 readfile($fullpath_original_file_osencoded);
11481 } elseif ($method == 1) {
11482 // Solution 1
11483 $handle = fopen($fullpath_original_file_osencoded, "rb");
11484 while (!feof($handle)) {
11485 print fread($handle, 8192);
11486 }
11487 fclose($handle);
11488 } elseif ($method == 2) {
11489 // Solution 2
11490 $handle1 = fopen($fullpath_original_file_osencoded, "rb");
11491 $handle2 = fopen("php://output", "wb");
11492 stream_copy_to_stream($handle1, $handle2);
11493 fclose($handle1);
11494 fclose($handle2);
11495 }
11496}
11497
11498
11506function jsonOrUnserialize($stringtodecode, $assoc = true)
11507{
11508 $result = json_decode($stringtodecode, $assoc);
11509 if ($result === null) {
11510 $result = unserialize($stringtodecode, ['allowed_classes' => false]); // For backward compatibility. Is no more used in recent versions.
11511 }
11512
11513 return $result;
11514}
11515
11516
11534function forgeSQLFromUniversalSearchCriteria($filter, &$errorstr = '', $noand = 0, $nopar = 0, $noerror = 0, $forbiddenfields = array())
11535{
11536 global $db, $user;
11537
11538 if (is_null($filter) || !is_string($filter) || $filter === '') {
11539 return '';
11540 }
11541 if (!preg_match('/^\‍(.*\‍)$/', $filter)) { // If $filter does not start and end with ()
11542 $filter = '(' . $filter . ')';
11543 }
11544
11545 $regexstring = '\‍(([a-zA-Z0-9_\.]+:[<>!=insotlke]+:[^\‍(\‍)]+)\‍)'; // Must be (aaa:bbb:...) with aaa is a field name (with alias or not) and bbb is one of this operator '=', '<', '>', '<=', '>=', '!=', 'in', 'notin', 'like', 'notlike', 'is', 'isnot'
11546 $firstandlastparenthesis = 0;
11547
11548 if (!dolCheckFilters($filter, $errorstr, $firstandlastparenthesis)) {
11549 if ($noerror) {
11550 return '1 = 2';
11551 } else {
11552 return 'Filter syntax error - ' . $errorstr; // Bad balance of parenthesis, we return an error message or force a SQL not found
11553 }
11554 }
11555
11556 // Test the filter syntax
11557 $t = preg_replace_callback('/' . $regexstring . '/i', 'dolForgeDummyCriteriaCallback', $filter);
11558 $t = str_ireplace(array('and', 'or', ' '), '', $t); // Remove the only strings allowed between each () criteria
11559
11560 // If the string result contains something else than '()', the syntax was wrong
11561 if (preg_match('/[^\‍(\‍)]/', $t)) {
11562 $tmperrorstr = 'Bad syntax of the search string';
11563 $errorstr = 'Bad syntax of the search string: ' . $filter;
11564 if ($noerror) {
11565 return '1 = 2';
11566 } else {
11567 dol_syslog("forgeSQLFromUniversalSearchCriteria Filter error - " . $errorstr, LOG_WARNING);
11568 return 'Filter error - ' . $tmperrorstr; // Bad syntax of the search string, we return an error message or force a SQL not found
11569 }
11570 }
11571
11572 global $globalforbiddenfields; // For use by dolForgeSQLCriteriaCallback()
11573 $globalforbiddenfields = $forbiddenfields;
11574
11575 $ret = ($noand ? "" : " AND ") . ($nopar ? "" : '(');
11576 $ret .= preg_replace_callback('/' . $regexstring . '/i', 'dolForgeSQLCriteriaCallback', $filter);
11577 $ret .= ($nopar ? "" : ')');
11578
11579 if (is_object($db)) {
11580 $ret = str_replace('__NOW__', "'" . $db->idate(dol_now()) . "'", $ret);
11581 }
11582 if (is_object($user)) {
11583 $ret = str_replace('__USER_ID__', (string) $user->id, $ret);
11584 }
11585
11586 return $ret;
11587}
11588
11596function dolForgeExplodeAnd($sqlfilters)
11597{
11598 $arrayofandtags = array();
11599 $nbofchars = dol_strlen($sqlfilters);
11600
11601 $error = '';
11602 $parenthesislevel = 0;
11603 $result = dolCheckFilters($sqlfilters, $error, $parenthesislevel);
11604 if (!$result) {
11605 return array();
11606 }
11607 if ($parenthesislevel >= 1) {
11608 $sqlfilters = preg_replace('/^\‍(/', '', preg_replace('/\‍)$/', '', $sqlfilters));
11609 }
11610
11611 $i = 0;
11612 $s = '';
11613 $countparenthesis = 0;
11614 while ($i < $nbofchars) {
11615 $char = dol_substr($sqlfilters, $i, 1);
11616
11617 if ($char == '(') {
11618 $countparenthesis++;
11619 } elseif ($char == ')') {
11620 $countparenthesis--;
11621 }
11622
11623 if ($countparenthesis == 0) {
11624 $char2 = dol_substr($sqlfilters, $i + 1, 1);
11625 $char3 = dol_substr($sqlfilters, $i + 2, 1);
11626 if ($char == 'A' && $char2 == 'N' && $char3 == 'D') {
11627 // We found a AND
11628 $s = trim($s);
11629 if (!preg_match('/^\‍(.*\‍)$/', $s)) {
11630 $s = '(' . $s . ')';
11631 }
11632 $arrayofandtags[] = $s;
11633 $s = '';
11634 $i += 2;
11635 } else {
11636 $s .= $char;
11637 }
11638 } else {
11639 $s .= $char;
11640 }
11641 $i++;
11642 }
11643 if ($s) {
11644 $s = trim($s);
11645 if (!preg_match('/^\‍(.*\‍)$/', $s)) {
11646 $s = '(' . $s . ')';
11647 }
11648 $arrayofandtags[] = $s;
11649 }
11650
11651 return $arrayofandtags;
11652}
11653
11663function dolCheckFilters($sqlfilters, &$error = '', &$parenthesislevel = 0)
11664{
11665 //$regexstring='\‍(([^:\'\‍(\‍)]+:[^:\'\‍(\‍)]+:[^:\‍(\‍)]+)\‍)';
11666 //$tmp=preg_replace_all('/'.$regexstring.'/', '', $sqlfilters);
11667 $tmp = $sqlfilters;
11668
11669 $nb = dol_strlen($tmp);
11670 $counter = 0;
11671 $parenthesislevel = 0;
11672
11673 $error = '';
11674
11675 $i = 0;
11676 while ($i < $nb) {
11677 $char = dol_substr($tmp, $i, 1);
11678
11679 if ($char == '(') {
11680 if ($i == $parenthesislevel && $parenthesislevel == $counter) {
11681 // We open a parenthesis and it is the first char
11682 $parenthesislevel++;
11683 }
11684 $counter++;
11685 } elseif ($char == ')') {
11686 $nbcharremaining = ($nb - $i - 1);
11687 if ($nbcharremaining >= $counter) {
11688 $parenthesislevel = min($parenthesislevel, $counter - 1);
11689 }
11690 if ($parenthesislevel > $counter && $nbcharremaining >= $counter) {
11691 $parenthesislevel = $counter;
11692 }
11693 $counter--;
11694 }
11695
11696 if ($counter < 0) {
11697 $error = "Wrong balance of parenthesis in sqlfilters=" . $sqlfilters;
11698 $parenthesislevel = 0;
11699 dol_syslog($error, LOG_WARNING);
11700 return false;
11701 }
11702
11703 $i++;
11704 }
11705
11706 if ($counter > 0) {
11707 $error = "Wrong balance of parenthesis in sqlfilters=" . $sqlfilters;
11708 $parenthesislevel = 0;
11709 dol_syslog($error, LOG_WARNING);
11710 return false;
11711 }
11712
11713 return true;
11714}
11715
11723function dolForgeDummyCriteriaCallback($matches)
11724{
11725 //dol_syslog("Convert matches ".$matches[1]);
11726 if (empty($matches[1])) {
11727 return '';
11728 }
11729 $tmp = explode(':', $matches[1]);
11730 if (count($tmp) < 3) {
11731 return '';
11732 }
11733
11734 return '()'; // An empty criteria
11735}
11736
11746function dolForgeSQLCriteriaCallback($matches)
11747{
11748 global $db;
11749 global $globalforbiddenfields;
11750
11751 //dol_syslog("Convert matches ".$matches[1]);
11752 if (empty($matches[1])) {
11753 return '';
11754 }
11755 $tmp = explode(':', $matches[1], 3);
11756 if (count($tmp) < 3) {
11757 return '';
11758 }
11759
11760 // Add fields that are forbidden by the caller when using USF search criteria
11761 // so we can't guess them using sequantial comparisons
11762 $newforbiddenfields = $globalforbiddenfields;
11763 if (!is_array($newforbiddenfields)) {
11764 $newforbiddenfields = array();
11765 }
11766 // Add fields that are ALWAYS forbidden when using USF search criteria
11767 $newforbiddenfields[] = 'pass';
11768 $newforbiddenfields[] = 'pass_crypted';
11769 $newforbiddenfields[] = 'api_key';
11770
11771 $operand = preg_replace('/[^a-z0-9\._]/i', '', trim($tmp[0]));
11772
11773 // Test that operand is not a forbidden search field
11774 if (!empty($newforbiddenfields)) {
11775 $operandwithoutprefix = preg_replace('/^[a-z0-9_]+\./i', '', $operand); // Remove prefix like t. or o. or s. or u. or d. or ...
11776 if (in_array(strtolower($operandwithoutprefix), $newforbiddenfields)) {
11777 return '1=1';
11778 }
11779 }
11780
11781 $operator = strtoupper(preg_replace('/[^a-z<>!=]/i', '', trim($tmp[1])));
11782
11783 $realOperator = [
11784 'NOTLIKE' => 'NOT LIKE',
11785 'ISNOT' => 'IS NOT',
11786 'NOTIN' => 'NOT IN',
11787 '!=' => '<>',
11788 ];
11789
11790 if (array_key_exists($operator, $realOperator)) {
11791 $operator = $realOperator[$operator];
11792 }
11793
11794 $tmpescaped = $tmp[2];
11795
11796 //print "Case: ".$operator." ".$operand." ".$tmpescaped."\n";
11797
11798 $regbis = array();
11799
11800 if ($operator == 'IN' || $operator == 'NOT IN') { // IN is allowed for list of ID/code/field only (or subrequest if $dolibarr_allow_unsecured_select_in_extrafields_filter not enabled)
11801 global $dolibarr_allow_unsecured_select_in_extrafields_filter;
11802
11803 //if (!preg_match('/^\‍(.*\‍)$/', $tmpescaped)) {
11804 $tmpescaped2 = '(';
11805 // Explode and sanitize each element in list
11806 $tmpelemarray = explode(',', $tmpescaped);
11807 foreach ($tmpelemarray as $tmpkey => $tmpelem) {
11808 $reg = array();
11809 $tmpelem = trim($tmpelem);
11810 if (preg_match('/^\'(.*)\'$/', $tmpelem, $reg)) {
11811 $tmpelemarray[$tmpkey] = "'".$db->escape($db->sanitize($reg[1], 2, 1, 1, 1))."'";
11812 } elseif (preg_match('/^[0-9]+$/', (string) $tmpelem)) { // if only 0-9 chars, no .
11813 $tmpelemarray[$tmpkey] = (int) $tmpelem;
11814 } elseif (is_numeric((string) $tmpelem)) { // it can be a float with a .
11815 $tmpelemarray[$tmpkey] = (float) $tmpelem;
11816 } elseif (!empty($dolibarr_allow_unsecured_select_in_extrafields_filter)) {
11817 $tmpelemarray[$tmpkey] = preg_replace('/[^a-z0-9_<>=!\s]/i', '', $tmpelem); // it can be a full subrequest (should be removed in a future as it allows blind SQL injection)
11818 } else {
11819 $tmpelemarray[$tmpkey] = preg_replace('/[^a-z0-9_]/i', '', $tmpelem); // it can be a name of field or a substitution variable like '__NOW__'
11820 }
11821 }
11822 $tmpescaped2 .= implode(',', $tmpelemarray);
11823 $tmpescaped2 .= ')';
11824
11825 $tmpescaped = $tmpescaped2;
11826 } elseif ($operator == 'LIKE' || $operator == 'NOT LIKE') {
11827 if (preg_match('/^\'([^\']*)\'$/', $tmpescaped, $regbis)) {
11828 $tmpescaped = $regbis[1];
11829 }
11830 //$tmpescaped = "'".$db->escape($db->escapeforlike($regbis[1]))."'";
11831 $tmpescaped = "'" . $db->escape($tmpescaped) . "'"; // We do not escape the _ and % so the LIKE will work as expected
11832 } elseif (preg_match('/^\'(.*)\'$/', $tmpescaped, $regbis)) {
11833 // TODO Retrieve type of field for $operand field name.
11834 // So we can complete format. For example we could complete a year with month and day.
11835 $tmpescaped = "'" . $db->escape($regbis[1]) . "'";
11836 } else {
11837 if (strtoupper($tmpescaped) == 'NULL') {
11838 $tmpescaped = 'NULL';
11839 } elseif (preg_match('/^[0-9]+$/', (string) $tmpescaped)) { // if only 0-9 chars, no .
11840 $tmpescaped = (int) $tmpescaped;
11841 } elseif (is_numeric((string) $tmpescaped)) { // it can be a float with a .
11842 $tmpescaped = (float) $tmpescaped;
11843 } else {
11844 $tmpescaped = preg_replace('/[^a-z0-9_]/i', '', $tmpescaped); // it can be a name of field or a substitution variable like '__NOW__'
11845 }
11846 }
11847
11848 return '(' . $db->escape($operand) . ' ' . strtoupper($operator) . ' ' . $tmpescaped . ')';
11849}
11850
11851
11861function getTimelineIcon($actionstatic, &$histo, $key)
11862{
11863 dol_syslog('getTimelineIcon::begin', LOG_DEBUG);
11864 global $langs;
11865
11866 $out = '<!-- timeline icon -->' . "\n";
11867 $iconClass = 'fa fa-comments';
11868 $img_picto = '';
11869 $colorClass = '';
11870 $pictoTitle = '';
11871
11872 if ($histo[$key]['percent'] == -1) {
11873 $colorClass = 'timeline-icon-not-applicble';
11874 $pictoTitle = $langs->trans('StatusNotApplicable');
11875 } elseif ($histo[$key]['percent'] == 0) {
11876 $colorClass = 'timeline-icon-todo';
11877 $pictoTitle = $langs->trans('StatusActionToDo') . ' (0%)';
11878 } elseif ($histo[$key]['percent'] > 0 && $histo[$key]['percent'] < 100) {
11879 $colorClass = 'timeline-icon-in-progress';
11880 $pictoTitle = $langs->trans('StatusActionInProcess') . ' (' . $histo[$key]['percent'] . '%)';
11881 } elseif ($histo[$key]['percent'] >= 100) {
11882 $colorClass = 'timeline-icon-done';
11883 $pictoTitle = $langs->trans('StatusActionDone') . ' (100%)';
11884 }
11885
11886 if ($actionstatic->code == 'AC_TICKET_CREATE') {
11887 $iconClass = 'fa fa-ticket';
11888 } elseif ($actionstatic->code == 'AC_TICKET_MODIFY') {
11889 $iconClass = 'fa fa-pencilxxx';
11890 } elseif (preg_match('/^TICKET_MSG/', $actionstatic->code)) {
11891 $iconClass = 'fa fa-comments';
11892 } elseif (preg_match('/^TICKET_MSG_PRIVATE/', $actionstatic->code)) {
11893 $iconClass = 'fa fa-mask';
11894 } elseif (getDolGlobalString('AGENDA_USE_EVENT_TYPE')) {
11895 if ($actionstatic->type_picto) {
11896 $img_picto = img_picto('', $actionstatic->type_picto);
11897 } else {
11898 if ($actionstatic->type_code == 'AC_RDV') {
11899 $iconClass = 'fa fa-handshake';
11900 } elseif ($actionstatic->type_code == 'AC_TEL') {
11901 $iconClass = 'fa fa-phone';
11902 } elseif ($actionstatic->type_code == 'AC_FAX') {
11903 $iconClass = 'fa fa-fax';
11904 } elseif ($actionstatic->type_code == 'AC_EMAIL') {
11905 $iconClass = 'fa fa-envelope';
11906 } elseif ($actionstatic->type_code == 'AC_INT') {
11907 $iconClass = 'fa fa-shipping-fast';
11908 } elseif ($actionstatic->type_code == 'AC_OTH_AUTO') {
11909 $iconClass = 'fa fa-robot';
11910 } elseif (!preg_match('/_AUTO/', $actionstatic->type_code)) {
11911 $iconClass = 'fa fa-robot';
11912 }
11913 }
11914 }
11915
11916 $out .= '<i class="' . $iconClass . ' ' . $colorClass . '" title="' . $pictoTitle . '">' . $img_picto . '</i>' . "\n";
11917 return $out;
11918}
11919
11927{
11928 global $db;
11929
11930 $documents = array();
11931
11932 $sql = 'SELECT ecm.rowid as id, ecm.src_object_type, ecm.src_object_id, ecm.filepath, ecm.filename, ecm.agenda_id';
11933 $sql .= ' FROM ' . MAIN_DB_PREFIX . 'ecm_files ecm';
11934 $sql .= " WHERE ecm.filepath = 'agenda/" . ((int) $object->id) . "'";
11935 //$sql.= " ecm.src_object_type = '".$db->escape($object->element)."' AND ecm.src_object_id = ".((int) $object->id); // Old version didn't add object_type during upload
11936 $sql .= ' OR ecm.agenda_id = ' . (int) $object->id;
11937 $sql .= ' ORDER BY ecm.position ASC';
11938
11939 $resql = $db->query($sql);
11940 if ($resql) {
11941 if ($db->num_rows($resql)) {
11942 while ($obj = $db->fetch_object($resql)) {
11943 $documents[$obj->id] = $obj;
11944 }
11945 }
11946 }
11947
11948 return $documents;
11949}
11950
11951
11963function buildParamDate($prefix, $timestamp = null, $hourTime = '', $gm = 'auto')
11964{
11965 if ($timestamp === null) {
11966 $timestamp = GETPOSTDATE($prefix, $hourTime, $gm);
11967 }
11968 $TParam = array(
11969 $prefix . 'day' => intval(dol_print_date($timestamp, '%d')),
11970 $prefix . 'month' => intval(dol_print_date($timestamp, '%m')),
11971 $prefix . 'year' => intval(dol_print_date($timestamp, '%Y')),
11972 );
11973 if ($hourTime === 'getpost' || ($timestamp !== null && dol_print_date($timestamp, '%H:%M:%S') !== '00:00:00')) {
11974 $TParam = array_merge($TParam, array(
11975 $prefix . 'hour' => intval(dol_print_date($timestamp, '%H')),
11976 $prefix . 'min' => intval(dol_print_date($timestamp, '%M')),
11977 $prefix . 'sec' => intval(dol_print_date($timestamp, '%S'))
11978 ));
11979 }
11980
11981 return '&' . http_build_query($TParam);
11982}
11983
12002function recordNotFound($message = '', $printheader = 1, $printfooter = 1, $showonlymessage = 0, $params = null)
12003{
12004 global $conf, $db, $langs, $hookmanager;
12005 global $action, $object;
12006
12007 if (!is_object($langs)) {
12008 include_once DOL_DOCUMENT_ROOT . '/core/class/translate.class.php';
12009 $langs = new Translate('', $conf);
12010 $langs->setDefaultLang();
12011 }
12012
12013 $langs->load("errors");
12014
12015 if ($printheader) {
12016 if (function_exists("llxHeader")) {
12017 llxHeader('');
12018 } elseif (function_exists("llxHeaderVierge")) {
12019 llxHeaderVierge('');
12020 }
12021 }
12022
12023 print '<div class="error">';
12024 if (empty($message)) {
12025 print $langs->trans("ErrorRecordNotFound");
12026 } else {
12027 print $langs->trans($message);
12028 }
12029 print '</div>';
12030 print '<br>';
12031
12032 if (empty($showonlymessage)) {
12033 if (empty($hookmanager)) {
12034 include_once DOL_DOCUMENT_ROOT . '/core/class/hookmanager.class.php';
12035 $hookmanager = new HookManager($db);
12036 // Initialize a technical object to manage hooks of page. Note that conf->hooks_modules contains an array of hook context
12037 $hookmanager->initHooks(array('main'));
12038 }
12039
12040 $parameters = array('message' => $message, 'params' => $params);
12041 $reshook = $hookmanager->executeHooks('getErrorRecordNotFound', $parameters, $object, $action); // Note that $action and $object may have been modified by some hooks
12042 print $hookmanager->resPrint;
12043 }
12044
12045 if ($printfooter && function_exists("llxFooter")) {
12046 llxFooter();
12047 if (is_object($db)) {
12048 $db->close();
12049 }
12050 }
12051 exit(0);
12052}
12053
12084function array_merge_recursive_distinct(array $array1, array $array2): array
12085{
12086 $merged = $array1;
12087
12088 foreach ($array2 as $key => $value) {
12089 if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) {
12090 $merged[$key] = array_merge_recursive_distinct($merged[$key], $value);
12091 } else {
12092 $merged[$key] = $value;
12093 }
12094 }
12095
12096 return $merged;
12097}
12098
12105function getObjectSocId($obj)
12106{
12107 if (!empty($obj->socid)) {
12108 return (int) $obj->socid;
12109 } elseif (!empty($obj->soc_id)) {
12110 return (int) $obj->soc_id;
12111 } elseif (!empty($obj->societe_id)) {
12112 return (int) $obj->societe_id;
12113 }
12114 return null;
12115}
12116
12123{
12124 $default = 10;
12125 if (!empty($_SESSION['dol_screenheight']) && $_SESSION['dol_screenheight'] < 700) {
12126 $default = 8;
12127 } elseif (!empty($_SESSION['dol_screenheight']) && $_SESSION['dol_screenheight'] < 950) {
12128 $default = 10;
12129 } elseif (!empty($_SESSION['dol_screenheight']) && $_SESSION['dol_screenheight'] > 1130) {
12130 $default = 15;
12131 }
12132
12133 return $default;
12134}
$id
Support class for third parties, contacts, members, users or resources.
Definition account.php:47
if(! $sortfield) if(! $sortorder) $object
Definition account.php:100
global $dolibarr_main_url_root
if(!defined( 'NOTOKENRENEWAL')) if(!defined('NOREQUIREMENU')) if(!defined( 'NOREQUIREHTML')) if(!defined('NOREQUIREAJAX')) if(!defined( 'NOLOGIN')) if(!defined('NOCSRFCHECK')) if(!defined( 'NOIPCHECK')) llxHeaderVierge($title, $head="", $disablejs=0, $disablehead=0, $arrayofjs=[], $arrayofcss=[], $ws='')
Header function.
llxFooter($comment='', $zone='private', $disabledoutputofmessages=0)
Empty footer.
Definition wrapper.php:91
if(!defined('NOREQUIRESOC')) if(!defined( 'NOREQUIRETRAN')) if(!defined('NOTOKENRENEWAL')) if(!defined( 'NOREQUIREMENU')) if(!defined('NOREQUIREHTML')) if(!defined( 'NOREQUIREAJAX')) llxHeader($head='', $title='', $help_url='', $target='', $disablejs=0, $disablehead=0, $arrayofjs='', $arrayofcss='', $morequerystring='', $morecssonbody='', $replacemainareaby='', $disablenofollow=0, $disablenoindex=0)
Empty header.
Definition wrapper.php:73
$c
Definition line.php:346
$object ref
Definition info.php:90
static getValidAddress($address, $format, $encode=0, $maxnumberofemail=0)
Return a formatted address string for SMTP protocol.
Class to manage GeoIP conversion Usage: $geoip=new GeoIP('country',$datfile); $geoip->getCountryCodeF...
Class to manage standard extra fields.
Class to manage invoices.
Class to manage hooks.
Class to manage predefined suppliers products.
Class to manage products or services.
Class to manage third parties objects (customers, suppliers, prospects...)
isACompany()
Check if third party is a company (Business) or an end user (Consumer)
Class to manage translations.
Class to manage Dolibarr users.
if(! $sortfield) if(! $sortorder) $module
Definition list.php:193
isInEEC($object)
Return if a country of an object is inside the EEC (European Economic Community)
global $mysoc
dol_get_prev_month($month, $year)
Return previous month.
Definition date.lib.php:535
dol_get_next_day($day, $month, $year)
Return next day.
Definition date.lib.php:520
getServerTimeZoneInt($refgmtdate='now')
Return server timezone int.
Definition date.lib.php:87
dol_get_prev_day($day, $month, $year)
Return previous day.
Definition date.lib.php:504
dol_get_next_month($month, $year)
Return next month.
Definition date.lib.php:554
print $script_file $mode $langs defaultlang(is_numeric($duration_value) ? " delay=". $duration_value :"").(is_numeric($duration_value2) ? " after cd cd cd description as description
Only used if Module[ID]Desc translation string is not found.
if(!isModEnabled('ai')||!getDolGlobalString('AI_ASSISTANT_ENABLED')) global $conf
The main.inc.php has been included so the following variable are now defined:
dol_is_file($pathoffile)
Return if path is a file.
dol_dir_list($utf8_path, $types="all", $recursive=0, $filter="", $excludefilter=null, $sortcriteria="name", $sortorder=SORT_ASC, $mode=0, $nohook=0, $relativename="", $donotfollowsymlinks=0, $nbsecondsold=0)
Scan a directory and return a list of files/directories.
Definition files.lib.php:65
dol_is_dir($folder)
Test if filename is a directory.
$date_start
Variables from include:
isValidMailDomain($mail)
Return true if email has a domain name that can be resolved to MX type.
isValidVATID($company)
Check if VAT numero is valid (check done on syntax only, no database or remote access)
dol_html_entity_decode($a, $b, $c='UTF-8', $keepsomeentities=0)
Replace html_entity_decode functions to manage errors.
dol_now($mode='gmt')
Return date for now.
dolSort($arraytosort)
Sort an array using a user defined function.
verifCond($strToEvaluate, $onlysimplestring='1')
Verify if condition in string is ok or not.
getDolGlobalLoginBadCharUnauthorized()
Return the list of unauthorized characters in user logins.
recordNotFound($message='', $printheader=1, $printfooter=1, $showonlymessage=0, $params=null)
Displays an error page when a record is not found.
getDolGlobalFloat($key, $default=0)
Return a Dolibarr global constant float value.
dol_print_size($size, $shortvalue=0, $shortunit=0)
Return string with formatted size.
isOnlyOneLocalTax($local)
Return true if LocalTax (1 or 2) is unique.
getExecutableContent()
Return array of extension for executable files of text files that can contains executable code.
dol_mktime($hour, $minute, $second, $month, $day, $year, $gm='auto', $check=1)
Return a timestamp date built from detailed information (by default a local PHP server timestamp) Rep...
dol_print_email($email, $contactid=0, $socid=0, $addlink=0, $max=0, $showinvalid=2, $withpicto=0, $morecss='paddingrightonly')
Show EMail link formatted for HTML output.
get_default_localtax($thirdparty_seller, $thirdparty_buyer, $local, $idprod=0)
Function that return localtax of a product line (according to seller, buyer and product vat rate) If ...
dol_getIdFromCode($db, $key, $tablename, $fieldkey='code', $fieldid='id', $entityfilter=0, $filters='', $useCache=true)
Return an id or code from a code or id.
dolCheckFilters($sqlfilters, &$error='', &$parenthesislevel=0)
Return if a $sqlfilters parameter has a valid balance of parenthesis.
dol_getmypid()
Return getmypid() or random PID when function is disabled Some web hosts disable this php function fo...
getLanguageCodeFromCountryCode($countrycode)
Return default language from country code.
setEntity($currentobject)
Set entity id to use when to create an object.
dolForgeExplodeAnd($sqlfilters)
Explode an universal search string with AND parts.
vatrate($rate, $addpercent=false, $info_bits=0, $usestarfornpr=0, $html=0)
Return a string with VAT rate label formatted for view output Used into pdf and HTML pages.
dolExplodeIntoArray($string, $delimiter=';', $kv='=')
Split a string with 2 keys into key array.
GETPOSTDATE($prefix, $hourTime='', $gm='auto', $saverestore='')
Helper function that combines values of a dolibarr DatePicker (such as Form\selectDate) for year,...
dol_print_ip($ip, $mode=0, $showname=0)
Return an IP formatted to be shown on screen.
dol_ucfirst($string, $encoding="UTF-8")
Convert first character of the first word of a string to upper.
dol_print_phone($phone, $countrycode='', $contactid=0, $socid=0, $addlink='', $separ="&nbsp;", $withpicto='', $titlealt='', $adddivfloat=0, $morecss='paddingright')
Format phone numbers according to country.
dol_strtolower($string, $encoding="UTF-8")
Convert a string to lower.
dol_htmlentitiesbr_decode($stringtodecode, $pagecodeto='UTF-8')
This function is called to decode a HTML string (it decodes entities and br tags)
getDoliDBInstance($type, $host, $user, $pass, $name, $port, $forcenew=false)
Return a DoliDB instance (database handler).
dol_mimetype($file, $default='application/octet-stream', $mode=0)
Return MIME type of a file from its name with extension.
isVisibleToUserType($type_user, &$menuentry, &$listofmodulesforexternal)
Function to test if an entry is enabled or not.
dolGetFirstLineOfText($text, $nboflines=1, $charset='UTF-8')
Return first line of text.
dol_format_address($object, $withcountry=0, $sep="\n", $outputlangs=null, $mode=0, $extralangcode='')
Return a formatted address (part address/zip/town/state) according to country rules.
getDolUserInt($key, $default=0, $tmpuser=null)
Return Dolibarr user constant int value.
dol_osencode($str)
Return a string encoded into OS filesystem encoding.
getObjectSocId($obj)
Get the socid of an object, supporting legacy attribute names.
getListLimitFromScreenHeight()
Get the limit of list to show according to the screen height.
isASecretKey($keyname)
Return if string has a name dedicated to store a secret.
dol_string_nohtmltag($stringtoclean, $removelinefeed=1, $pagecodeto='UTF-8', $strip_tags=0, $removedoublespaces=1)
Clean a string from all HTML tags and entities.
dol_string_onlythesehtmlattributes($stringtoclean, $allowed_attributes=null, $ishtml=null)
Clean a string from some undesirable HTML tags.
price2num($amount, $rounding='', $option=0)
Function that return a number with universal decimal format (decimal separator is '.
dol_eval_new($s)
Replace eval function to add more security.
getCallerInfoString()
Get caller info as a string that can be appended to a log message.
roundUpToNextMultiple($n, $x=5)
Round to next multiple.
dol_user_country()
Return country code for current user.
dol_getThemeFilePath($file, $theme='')
Return the full filesystem path of a file located in the currently selected theme directory.
getLabelSpecialCode($idcode)
Make content of an input box selected when we click into input field.
getMultidirTemp($object, $module='', $forobject=0)
Return the full path of the directory where a module (or an object of a module) stores its temporary ...
currentToken()
Return the value of token currently saved into session with name 'token'.
dolBuildUrl($url, $params=[], $addtoken=false, $anchor='')
Return path of url.
getDolEntity()
Return the current entity.
picto_required()
Return picto saying a field is required.
isDolTms($timestamp)
isDolTms check if a timestamp is valid.
dol_string_nospecial($str, $newstr='_', $badcharstoreplace='', $badcharstoremove='', $keepspaces=0)
Clean a string from all punctuation characters to use it as a ref or login.
dol_eval($s, $returnvalue=1, $hideerrors=1, $onlysimplestring='1')
Replace eval function to add more security.
dol_nl2br($stringtoencode, $nl2brmode=0, $forxml=false)
Replace CRLF in string with a HTML BR tag.
dol_print_url($url, $target='_blank', $max=32, $withpicto=0, $morecss='')
Show Url link.
dol_sanitizePathName($str, $newstr='_', $unaccent=0, $allowdash=0)
Clean a string to use it as a path name.
dol_sanitizeFileName($str, $newstr='_', $unaccent=1, $includequotes=0, $allowdash=0)
Clean a string to use it as a file name.
dol_strlen($string, $stringencoding='UTF-8')
Make a strlen call.
getTaxesFromId($vatrate, $buyer=null, $seller=null, $firstparamisid=1)
Get tax (VAT) main information from Id.
price($amount, $form=0, $outlangs='', $trunc=1, $rounding=-1, $forcerounding=-1, $currency_code='')
Function to format a value into an amount for visual output Function used into PDF and HTML pages.
utf8_valid($str)
Check if a string is in UTF8.
getDolUserString($key, $default='', $tmpuser=null)
Return Dolibarr user constant string value.
getDolOptimizeSmallScreen()
Return if render must be optimized for small screen.
isValidMXRecord($domain)
Return if the domain name has a valid MX record.
GETPOSTISARRAY($paramname, $method=0)
Return true if the parameter $paramname is submit from a POST OR GET as an array.
jsonOrUnserialize($stringtodecode, $assoc=true)
Decode an encoded string.
dol_print_socialnetworks($value, $contactid, $socid, $type, $dictsocialnetworks=array())
Show social network link.
dolChmod($filepath, $newmask='')
Change mod of a file.
natural_search($fields, $value, $mode=0, $nofirstand=0, $sqltoadd='')
Generate natural SQL search string for a criteria (this criteria can be tested on one or several fiel...
get_localtax_by_third($local)
Get values of localtaxes (1 or 2) for company country for the common vat with the highest value.
dol_escape_php($stringtoescape, $stringforquotes=2)
Returns text escaped for inclusion into a php string, build with double quotes " or '.
dolSetCookie(string $cookiename, string $cookievalue, int $expire=-1)
Set a cookie.
getDolGlobalInt($key, $default=0)
Return a Dolibarr global constant int value.
getElementProperties($elementType)
Get an array with properties of an element.
dol_escape_js($stringtoescape, $mode=0, $noescapebackslashn=0)
Returns text escaped for inclusion into JavaScript code.
getLocalTaxesFromRate($vatrate, $local, $buyer, $seller, $firstparamisid=0)
Get type and rate of localtaxes for a particular vat rate/country of a thirdparty.
dol_get_object_properties($obj, $properties=[])
Get value of properties for an object - including magic properties when requested.
dol_sort_array(&$array, $index, $order='asc', $natsort=0, $case_sensitive=0, $keepindex=0)
Advanced sort array by the value of a given key, which produces ascending (default) or descending out...
if(!function_exists( 'dol_getprefix')) dol_include_once($relpath, $classname='')
Make an include_once using default root and alternate root if it fails.
getMultidirOutput($object, $module='', $forobject=0, $mode='output')
Return the full path of the directory where a module (or an object of a module) stores its files.
newToken()
Return the value of token currently saved into session with name 'newtoken'.
dol_string_unaccent($str)
Clean a string from all accent characters to be used as ref, login or by dol_sanitizeFileName.
GETPOST($paramname, $check='alphanohtml', $method=0, $filter=null, $options=null, $noreplace=0, $nodefault=0)
Return value of a param into GET or POST supervariable.
dol_strftime($fmt, $ts=false, $is_gmt=false)
Format a string.
GETPOSTFLOAT($paramname, $rounding='', $option=2)
Return the value of a $_GET or $_POST supervariable, converted into float.
dolGetFirstLastname($firstname, $lastname, $nameorder=-1)
Return firstname and lastname in correct order.
dol_string_neverthesehtmltags($stringtoclean, $disallowed_tags=array('textarea'), $cleanalsosomestyles=0)
Clean a string from some undesirable HTML tags.
isValidPhone($phone)
Return true if phone number syntax is ok TODO Decide what to do with this.
dol_htmlcleanlastbr($stringtodecode)
This function remove all ending and br at end.
get_default_npr(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
Function that returns whether VAT must be recoverable collected VAT (e.g.: VAT NPR in France)
dol_concatdesc($text1, $text2, $forxml=false, $invert=false)
Concat 2 descriptions with a new line between them (second operand after first one with appropriate n...
dol_htmlentities($string, $flags=ENT_QUOTES|ENT_SUBSTITUTE, $encoding='UTF-8', $double_encode=false)
Replace htmlentities functions.
getBrowserInfo($user_agent)
Return information about user browser.
dolGetFirstLetters($s, $nbofchar=1)
Return first letters of a strings.
forgeSQLFromUniversalSearchCriteria($filter, &$errorstr='', $noand=0, $nopar=0, $noerror=0, $forbiddenfields=array())
forgeSQLFromUniversalSearchCriteria
dol_clone_in_array($srcobject, $startlevel=0)
Create a clone of instance of object into a full array, using recursive call.
dol_strtoupper($string, $encoding="UTF-8")
Convert a string to upper.
getMultidirVersion($object, $module='', $forobject=0)
Return the full path of the directory where a module (or an object of a module) stores its versioned ...
getDolCurrency()
Return the main currency ('EUR', 'USD', ...)
dol_sanitizeUrl($stringtoclean, $type=1)
Clean a string to use it as an URL (into a href or src attribute, or into a js string that is a locat...
getImageFileNameForSize($file, $extName, $extImgTarget='')
Return the filename of file to get the thumbs.
complete_substitutions_array(&$substitutionarray, $outputlangs, $object=null, $parameters=null, $callfunc="completesubstitutionarray")
Complete the $substitutionarray with more entries coming from external module that had set the "subst...
dol_substr($string, $start, $length=null, $stringencoding='', $trunconbytes=0)
Make a substring.
ascii_check($str)
Check if a string is in ASCII.
get_date_range($date_start, $date_end, $format='', $outputlangs=null, $withparenthesis=1)
Format output for start and end date.
make_substitutions($text, $substitutionarray, $outputlangs=null, $converttextinhtmlifnecessary=0)
Make substitution into a text string, replacing keys with vals from $substitutionarray (oldval=>newva...
print_date_range($date_start, $date_end, $format='', $outputlangs=null)
Format output for start and end date.
getArrayOfSocialNetworks()
Get array of social network dictionary.
getDolDefaultContextPage($s)
Return the default context page string.
safeArrayMap($callback, array $array)
Add a function to replace array_map with allowed callback.
num2Alpha($n)
Return a numeric value into an Excel like column number.
dol_size($size, $type='')
Optimize a size for some browsers (phone, smarphone...)
dolGetCountryCodeFromIp($ip)
Return a country code from IP.
dol_textishtml($msg, $option=0)
Return if a text is a html content.
colorIsLight($stringcolor)
Return true if the color is light.
dol_escape_all($stringtoescape)
Returns text escaped for all protocols (so only alpha chars and numbers)
GETPOSTINT($paramname, $method=0, $nodefault=0)
Return the value of a $_GET or $_POST supervariable, converted into integer.
dolExplodeKeepIfQuotes($input)
Explode a search string into an array but do not explode when keys are inside quotes.
readfileLowMemory($fullpath_original_file_osencoded, $method=-1)
Return a file on output using a low memory.
dolIsAllowedForPreview($file)
Return if a file is qualified for preview.
dolForgeSQLCriteriaCallback($matches)
Function to forge a SQL criteria from a USF (Universal Filter Syntax) string.
dol_shutdown()
Function called at end of web php process.
dol_print_address($address, $htmlid, $element, $id, $noprint=0, $charfornl='')
Format address string.
dolEvalSimpleCondition($s)
Evaluate a condition made of simple terms, without eval().
dol_escape_uri($stringtoescape)
Returns text escaped by RFC 3986 for inclusion into a clickable link.
dol_print_profids($profID, $profIDtype, $countrycode='', $addcpButton=1)
Format professional IDs according to their country.
if(!function_exists( 'utf8_encode')) if(!function_exists('utf8_decode')) if(!function_exists( 'str_starts_with')) if(!function_exists('str_ends_with')) if(!function_exists( 'str_contains')) formatLogObject($data)
Return a string serialized to be output on log with dol_syslog() An option allow to output log in one...
getDolDBType()
Return the current entity.
dol_buildpath($path, $type=0, $returnemptyifnotfound=0)
Return path of url or filesystem.
dol_clone($srcobject, $native=2)
Create a clone of instance of object (new instance with same value for each properties) With native =...
dol_string_nounprintableascii($str, $removetabcrlf=1)
Clean a string from all non printable ASCII chars (0x00-0x1F and 0x7F).
dol_print_date($time, $format='', $tzoutput='auto', $outputlangs=null, $encodetooutput=false, $decorate=0)
Output date in a string format according to outputlangs (or langs if not defined).
dolGetCityFromIp($ip)
Return a city name from IP.
getDolGlobalBool($key, $default=false)
Return a Dolibarr global constant boolean value.
getTimelineIcon($actionstatic, &$histo, $key)
Get timeline icon.
dolCloseUnclosedHtmlTags($text)
Close the HTML tags left open in a truncated HTML string.
get_product_localtax_for_country($idprod, $local, $thirdpartytouseforcountry)
Return localtax vat rate of a product in a particular country or default country vat if product is un...
getUserRemoteIP($trusted=0)
Return the real IP of remote user.
buildParamDate($prefix, $timestamp=null, $hourTime='', $gm='auto')
Helper function that combines values of a dolibarr DatePicker (such as Form\selectDate) for year,...
isAFileWithExecutableContent($filename)
Return if a file can contains executable content.
dol_trunc($string, $size=40, $trunc='right', $stringencoding='UTF-8', $nodot=0, $display=0)
Truncate a string to a particular length adding '...' if string larger than length.
dol_string_is_good_iso($s, $clean=0)
Check if a string is a correct iso string If not, it will not be considered as HTML encoded even if i...
getNonce()
Return a random string to be used as a nonce value for js.
GETPOSTISSET($paramname)
Return true if we are in a context of submitting the parameter $paramname from a POST of a form.
isStringVarMatching($var, $regextext, $matchrule=1)
Check if a variable with name $var start with $regextext.
dolSlugify($stringtoslugify)
Returns text slugified (lowercase and no special char, separator is "-").
dol_concat($text1, $text2)
Concat 2 strings.
complete_head_from_modules($conf, $langs, $object, &$head, &$h, $type, $mode='add', $filterorigmodule='')
Complete or removed entries into a head array (used to build tabs).
dol_htmlentitiesbr($stringtoencode, $nl2brmode=0, $pagecodefrom='UTF-8', $removelasteolbr=1)
This function is called to encode a string into a HTML string but differs from htmlentities because a...
dol_nboflines($s, $maxchar=0)
Return nb of lines of a clear text.
dol_htmlwithnojs($stringtoencode, $nouseofiframesandbox=0, $check='restricthtml')
Sanitize a HTML to remove js, dangerous content and external links.
isValidEmail($address, $acceptsupervisorkey=0, $acceptuserkey=0)
Return true if email syntax is ok.
dol_escape_xml($stringtoescape)
Returns text escaped for inclusion into a XML string.
getActionCommEcmList($object)
getActionCommEcmList
dol_ucwords($string, $encoding="UTF-8")
Convert first character of all the words of a string to upper.
dolForgeDummyCriteriaCallback($matches)
Function to forge a SQL criteria from a Dolibarr filter syntax string.
dol_string_onlythesehtmltags($stringtoclean, $cleanalsosomestyles=1, $removeclassattribute=1, $cleanalsojavascript=0, $allowiframe=0, $allowed_tags=array(), $allowlink=0, $allowscript=0, $allowstyle=0, $allowphp=0)
Clean a string to keep only desirable HTML tags.
dol_escape_json($stringtoescape)
Returns text escaped for inclusion into javascript code.
getDolGlobalString($key, $default='')
Return a Dolibarr global constant string value.
sanitizeVal($out='', $check='alphanohtml', $filter=null, $options=null)
Return a sanitized or empty value after checking value against a rule.
dol_validElement($element)
Return if var element is ok.
dol_sanitizeKeyCode($str)
Clean a string to use it as a key or code.
isModEnabled($module)
Is Dolibarr module enabled.
array_merge_recursive_distinct(array $array1, array $array2)
Recursively merges two arrays while preserving keys and replacing existing values.
getWarningDelay($module, $parmlevel1, $parmlevel2='')
Return a warning delay You can use it like this: if (getWarningDelay('module', 'paramlevel1')) It rep...
utf8_check($str)
Check if a string is in UTF8.
get_default_tva(Societe $thirdparty_seller, Societe $thirdparty_buyer, $idprod=0, $idprodfournprice=0)
Function that return vat rate of a product line (according to seller, buyer and product vat rate) VAT...
getDictionaryValue($tablename, $field, $id, $checkentity=false, $rowidfield='rowid')
Return the value of a filed into a dictionary for the record $id.
get_localtax($vatrate, $local, $thirdparty_buyer=null, $thirdparty_seller=null, $vatnpr=0)
Return localtax rate for a particular VAT rate, when selling a product with vat $vatrate,...
dol_eval_standard($s, $hideerrors=1, $onlysimplestring='1')
Replace eval function to add more security.
get_product_vat_for_country($idprod, $thirdpartytouseforcountry, $idprodfournprice=0)
Return vat rate of a product in a particular country, or default country vat if product is unknown.
get_exdir($num, $level, $alpha, $withoutslash, $object, $modulepart='')
Return a path to have a the directory according to object where files are stored.
dol_syslog($message, $level=LOG_INFO, $ident=0, $suffixinfilename='', $restricttologhandler='', $logcontext=null)
Write log message into outputs.
getEntity($element, $shared=1, $currentobject=null)
Get list of entity id to use.
dol_getdate($timestamp, $fast=false, $forcetimezone='')
Return an array with locale date info.
dol_mkdir($dir, $dataroot='', $newmask='')
Creation of a directory (this can create recursive subdir)
dol_sanitizeEmail($stringtoclean)
Clean a string to use it as an Email.
dol_nboflines_bis($text, $maxlinesize=0, $charset='UTF-8')
Return nb of lines of a formatted text with and (WARNING: string must not have mixed and br sep...
const MODULE_MAPPING
This mapping defines the conversion to the current internal names from the alternative allowed names ...
dolBECalculateStructuredCommunication($invoice_number, $invoice_type)
Calculate Structured Communication / BE Bank payment reference number.
dol_convertToWord($num, $langs, $currency='', $centimes=false)
Function to return a number into a text.
div refaddress div address
picto_from_langcode($codelang, $moreatt='', $notitlealt=0)
Return img flag of country for a language code or country code.
showValueWithClipboardCPButton($valuetocopy, $showonlyonhover=1, $texttoshow='')
Create a button to copy $valuetocopy in the clipboard (for copy and paste feature).
showSimpleHTMLTable($outputlangs, $object)
Returns simple order table template as string.
dol_escape_htmltag($stringtoescape, $keepb=0, $keepn=0, $noescapetags='', $escapeonlyhtmltags=0, $cleanalsojavascript=0)
Definition html.lib.php:181
dol_setcache($memoryid, $data, $expire=0, $filecache=0, $replace=0)
Save data into a memory area shared by all users, all sessions on server.
dol_getcache($memoryid, $filecache=0)
Read a memory area shared by all users, all sessions on server.
dolGetRandomBytes($length)
Return a string of random bytes (hexa string) with length = $length for cryptographic purposes.
isHTTPS()
Return if we are using a HTTPS connection Check HTTPS (no way to be modified by user but may be empty...
realCharForNumericEntities($matches)
Return the real char for a numeric entities.
Definition waf.inc.php:66